mirror of
https://github.com/tiennm99/composes.git
synced 2026-10-13 12:14:21 +00:00
feat(paseo): install glab, and stop preinstalling the agent CLIs
The paseo user cannot write /usr/local, so a baked-in agent CLI can never apply its own update — every one of them ships an updater that expects to rewrite its own binary, and Claude Code nags about the failure at startup. Installed under $HOME they update themselves and still persist, since that is the paseo-home volume. The README now lists each vendor's installer. Bun goes with them: it was only there because omp is compiled against it. glab arrives as the .deb from GitLab's releases page, pinned by GLAB_VERSION because the URL carries the version. GitLab runs no apt repository and calls Homebrew its only officially supported Linux package manager. The Dockerfile drops its explanations along the way; they are in the README.
This commit is contained in:
1 parent
044d9b5594
commit
55e8f7321d
2 files changed
+76
-77
No files matched your search
+12
-44
@@ -1,18 +1,9 @@
|
||||
# The official image ships no agent CLIs or language toolchains. Add them here,
|
||||
# one concern per layer, cheapest and least-changing first.
|
||||
#
|
||||
# Stays root: the entrypoint needs root to chown the mounted volumes, then
|
||||
# drops to paseo with gosu itself. Nothing installs into $HOME -- that is
|
||||
# /home/paseo, a volume mount that masks anything baked in at build time.
|
||||
# Adds language toolchains and CLI tooling to the official image, which ships
|
||||
# none of it. Agent CLIs are not among them -- see README.md, which carries the
|
||||
# reasoning for everything here.
|
||||
FROM ghcr.io/getpaseo/paseo:latest
|
||||
|
||||
# --- system packages -------------------------------------------------------
|
||||
# Everyday shell tooling. git and curl are already in the base image; sudo is
|
||||
# not, despite Debian's base-passwd shipping an (empty) sudo group.
|
||||
#
|
||||
# build-essential is the C toolchain the language layers below assume but do
|
||||
# not ship: cgo, npm's node-gyp addons and Python C extensions all shell out to
|
||||
# gcc and make. Kept in this layer so there is only one apt-get update.
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
less nano jq unzip zip lsof psmisc ugrep bfs zsh sudo \
|
||||
@@ -21,8 +12,6 @@ RUN apt-get update \
|
||||
&& usermod -aG sudo paseo
|
||||
|
||||
# --- python ----------------------------------------------------------------
|
||||
# Debian 12 only carries 3.11, so fetch a standalone CPython build with uv
|
||||
# (astral.sh/uv). Both directories sit outside $HOME.
|
||||
ARG PYTHON_VERSION=3.12
|
||||
ENV UV_INSTALL_DIR=/usr/local/bin \
|
||||
UV_PYTHON_INSTALL_DIR=/opt/python \
|
||||
@@ -31,7 +20,6 @@ RUN curl -fsSL https://astral.sh/uv/install.sh | sh \
|
||||
&& uv python install "${PYTHON_VERSION}" --default
|
||||
|
||||
# --- go --------------------------------------------------------------------
|
||||
# Debian 12 carries 1.19, far too old, so use the official tarball.
|
||||
ARG GO_VERSION=1.26.8
|
||||
ENV PATH=/usr/local/go/bin:$PATH
|
||||
RUN curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-$(dpkg --print-architecture).tar.gz" \
|
||||
@@ -39,45 +27,25 @@ RUN curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-$(dpkg --print-architect
|
||||
&& tar -C /usr/local -xzf /tmp/go.tar.gz \
|
||||
&& rm /tmp/go.tar.gz
|
||||
|
||||
# --- gh --------------------------------------------------------------------
|
||||
# Debian does not package gh, so use GitHub's own signed repository.
|
||||
# --- gh and glab -----------------------------------------------------------
|
||||
# gh from GitHub's signed apt repository, glab from the .deb on its releases
|
||||
# page.
|
||||
ARG GLAB_VERSION=1.118.0
|
||||
RUN install -d -m 0755 /etc/apt/keyrings \
|
||||
&& curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
|
||||
-o /etc/apt/keyrings/githubcli-archive-keyring.gpg \
|
||||
&& chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
|
||||
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
|
||||
> /etc/apt/sources.list.d/github-cli.list \
|
||||
&& curl -fsSL "https://gitlab.com/gitlab-org/cli/-/releases/v${GLAB_VERSION}/downloads/glab_${GLAB_VERSION}_linux_$(dpkg --print-architecture).deb" \
|
||||
-o /tmp/glab.deb \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends gh \
|
||||
&& apt-get install -y --no-install-recommends gh /tmp/glab.deb \
|
||||
&& rm -f /tmp/glab.deb \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# --- bun -------------------------------------------------------------------
|
||||
# Only omp needs it: @oh-my-pi/pi-coding-agent is Bun-compiled and its bin
|
||||
# opens with `#!/usr/bin/env bun`, so without Bun on PATH npm installs an omp
|
||||
# that cannot start. BUN_INSTALL lands it in /usr/local, not the $HOME/.bun
|
||||
# default that the paseo-home volume would mask.
|
||||
ENV BUN_INSTALL=/usr/local
|
||||
RUN curl -fsSL https://bun.sh/install | bash
|
||||
|
||||
# --- agent CLIs ------------------------------------------------------------
|
||||
# These all publish the same way: a thin launcher plus the real binary as a
|
||||
# per-platform optional dependency. npm therefore installs exactly what each
|
||||
# vendor's own installer would, and is not a Node wrapper.
|
||||
#
|
||||
# Do not swap any of them for their `curl | bash` installer -- those write to
|
||||
# $HOME/.local, and $HOME is /home/paseo, a volume that masks whatever the
|
||||
# build put there. Last because these change most often.
|
||||
RUN npm install -g \
|
||||
@anthropic-ai/claude-code \
|
||||
@openai/codex \
|
||||
opencode-ai \
|
||||
@github/copilot \
|
||||
@earendil-works/pi-coding-agent \
|
||||
@oh-my-pi/pi-coding-agent
|
||||
|
||||
# --- entrypoint ------------------------------------------------------------
|
||||
# Wraps the image's entrypoint to set the paseo user's password while still
|
||||
# root -- see entrypoint.sh. tini stays in front of it, as in the base image.
|
||||
# Last so that editing the script rebuilds only this layer.
|
||||
# root -- see entrypoint.sh.
|
||||
COPY --chmod=0755 entrypoint.sh /usr/local/bin/paseo-sudo-entrypoint
|
||||
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/paseo-sudo-entrypoint"]
|
||||
+64
-33
@@ -1,9 +1,11 @@
|
||||
# paseo
|
||||
|
||||
[Paseo](https://paseo.sh) — self-hosted daemon and web UI for running coding
|
||||
agents. Built from a local `Dockerfile` that adds six agent CLIs, `gh`, Go,
|
||||
Python, a C toolchain, and shell tooling to the
|
||||
[official image](https://paseo.sh/docs/docker), which ships none of it.
|
||||
agents. Built from a local `Dockerfile` that adds `gh`, `glab`, Go, Python,
|
||||
a C toolchain, and shell tooling to the
|
||||
[official image](https://paseo.sh/docs/docker), which ships none of it. The
|
||||
agent CLIs are not baked in — install them into `$HOME` yourself, see
|
||||
[Agents](#agents).
|
||||
|
||||
## Setup
|
||||
|
||||
@@ -16,8 +18,8 @@ Python, a C toolchain, and shell tooling to the
|
||||
paseo.example.com:443
|
||||
```
|
||||
|
||||
4. In a terminal inside Paseo, log in once per tool you use — see
|
||||
[Agents](#agents) — plus `gh auth login`.
|
||||
4. In a terminal inside Paseo, install and log in to the agents you use — see
|
||||
[Agents](#agents) — plus `gh auth login` and `glab auth login`.
|
||||
|
||||
The port must be typed by hand. The UI rejects a bare hostname, and the
|
||||
auto-connect hint does not help: the daemon builds it from the `Host` header,
|
||||
@@ -56,18 +58,41 @@ Listens on `6767`, published nowhere — the platform maps the domain to it, so
|
||||
|
||||
## Agents
|
||||
|
||||
| Agent | Command | Package | Log in with |
|
||||
| --- | --- | --- | --- |
|
||||
| Claude Code | `claude` | `@anthropic-ai/claude-code` | `claude` |
|
||||
| Codex | `codex` | `@openai/codex` | `codex login` |
|
||||
| opencode | `opencode` | `opencode-ai` | `opencode auth login` |
|
||||
| Copilot CLI | `copilot` | `@github/copilot` | `/login` inside `copilot` |
|
||||
| Pi | `pi` | `@earendil-works/pi-coding-agent` | `pi` |
|
||||
| Oh My Pi | `omp` | `@oh-my-pi/pi-coding-agent` | `omp` |
|
||||
The image installs none of them. Run each vendor's own installer once, in a
|
||||
terminal inside Paseo, as the `paseo` user — never with `sudo`:
|
||||
|
||||
All six come from npm, so another provider is one more entry on the `npm
|
||||
install` line. Pi and Oh My Pi are separate projects sharing an ancestor; their
|
||||
commands do not collide.
|
||||
| Agent | Command | Install with | Log in with |
|
||||
| --- | --- | --- | --- |
|
||||
| Claude Code | `claude` | `curl -fsSL https://claude.ai/install.sh \| bash` | `claude` |
|
||||
| Codex | `codex` | `curl -fsSL https://chatgpt.com/codex/install.sh \| sh` | `codex login` |
|
||||
| opencode | `opencode` | `curl -fsSL https://opencode.ai/install \| bash` | `opencode auth login` |
|
||||
| Copilot CLI | `copilot` | `curl -fsSL https://gh.io/copilot-install \| bash` | `/login` inside `copilot` |
|
||||
| Oh My Pi | `omp` | `curl -fsSL https://omp.sh/install \| sh` | `omp` |
|
||||
| Pi | `pi` | `curl -fsSL https://pi.dev/install.sh \| sh` | `pi` |
|
||||
|
||||
Each lands in `$HOME` — `~/.local/bin` for most, `~/.opencode/bin` for
|
||||
opencode — and appends that directory to your shell rc, so open a new terminal
|
||||
afterwards. `$HOME` is the `paseo-home` volume, so both the binaries and the
|
||||
logins survive a redeploy.
|
||||
|
||||
The installers pull in any runtime they need, into `$HOME` as well. `omp` is
|
||||
the one to know about: it is Bun-compiled, and with no Bun on `PATH` its
|
||||
installer takes the prebuilt binary. Ask for the source build (`--source`) and
|
||||
it installs Bun to `~/.bun` first.
|
||||
|
||||
Why not bake them into the image: `paseo` cannot write `/usr/local`, so a
|
||||
preinstalled CLI can never apply its own update. Every one of these ships an
|
||||
updater (`claude update`, `codex update`, `omp update`, …) that expects to
|
||||
rewrite its own binary, and under `/usr/local` it fails on permissions — Claude
|
||||
Code nags about it at startup. Installed in `$HOME` they update themselves, and
|
||||
you are never waiting on an image rebuild for a release that shipped that
|
||||
morning.
|
||||
|
||||
Run the installers unprivileged. Under `sudo` they target root's home and land
|
||||
outside the volume; Claude Code's refuses outright.
|
||||
|
||||
Pi and Oh My Pi are separate projects sharing an ancestor; their commands do
|
||||
not collide.
|
||||
|
||||
## Storage
|
||||
|
||||
@@ -76,9 +101,10 @@ commands do not collide.
|
||||
| `paseo-home` | `/home/paseo` | Daemon state, agent configs, credentials (`.claude`, `.codex`, `.config/*`) |
|
||||
| `paseo-workspace` | `/workspace` | Code the agents work on |
|
||||
|
||||
Every agent CLI and `gh` keep their config under `/home/paseo`, so all logins
|
||||
survive a redeploy — the base image points `CLAUDE_CONFIG_DIR`, `CODEX_HOME`
|
||||
and the `XDG_*` variables into it. Dotfiles live there too, so a `.zshrc` or
|
||||
The agent CLIs, `gh` and `glab` all keep their config under `/home/paseo`, so
|
||||
every login survives a redeploy — the base image points `CLAUDE_CONFIG_DIR`,
|
||||
`CODEX_HOME` and the `XDG_*` variables into it. The agent binaries live there
|
||||
too, since you install them yourself. Dotfiles as well, so a `.zshrc` or
|
||||
oh-my-zsh install persists. Anything written outside `$HOME` (`chsh`,
|
||||
`apt install`) is lost on rebuild.
|
||||
|
||||
@@ -86,29 +112,34 @@ oh-my-zsh install persists. Anything written outside `$HOME` (`chsh`,
|
||||
|
||||
| Tool | Source | Why not apt |
|
||||
| --- | --- | --- |
|
||||
| Agent CLIs (see [above](#agents)) | npm | — |
|
||||
| Bun | Official `bun.sh` install script | Debian does not package it |
|
||||
| `gh` | GitHub's signed apt repo | Debian does not package it |
|
||||
| `glab` (`GLAB_VERSION`) | The `.deb` on GitLab's releases page | Debian does not package it, and GitLab runs no apt repo |
|
||||
| Go (`GO_VERSION`) | Official go.dev tarball | Debian 12 ships 1.19 |
|
||||
| Python (`PYTHON_VERSION`) | `uv python install` | Debian 12 ships 3.11 |
|
||||
| `less nano jq unzip zip lsof psmisc ugrep bfs zsh sudo` | apt | — |
|
||||
| `build-essential` | apt | — |
|
||||
|
||||
Bump a language with a build arg, e.g. `--build-arg GO_VERSION=1.27.1`. `uv`
|
||||
itself is installed too.
|
||||
Bump a pinned version with a build arg, e.g. `--build-arg GO_VERSION=1.27.1`.
|
||||
`uv` itself is installed too. `GLAB_VERSION` is pinned rather than tracking the
|
||||
latest because GitLab's download URL carries the version in the path.
|
||||
|
||||
Constraints worth knowing before changing the `Dockerfile`:
|
||||
The `Dockerfile` itself only says what each layer installs. The reasoning is
|
||||
all here:
|
||||
|
||||
- `$HOME` is `/home/paseo`, a volume that masks anything the build writes
|
||||
there. Hence `/opt/python` and `/usr/local/go` rather than the defaults, and
|
||||
hence npm rather than each vendor's `curl | bash` installer, which writes to
|
||||
`$HOME/.local`. npm delivers the same native binaries regardless — every one
|
||||
of these packages is a thin launcher with the real binary as a per-platform
|
||||
optional dependency.
|
||||
- Bun exists for `omp` alone, whose bin opens with `#!/usr/bin/env bun`.
|
||||
Node runs the other five.
|
||||
- The agent CLIs cannot auto-update, since `paseo` cannot write `/usr/local`.
|
||||
Claude Code shows a notice at startup. Rebuild to update.
|
||||
there. Hence `/opt/python` and `/usr/local/go` rather than the defaults.
|
||||
- Do not add agent CLIs here, or a runtime only they need. Under `/usr/local`
|
||||
they cannot self-update; in `$HOME` they can, and they persist anyway. Bun
|
||||
used to be here for `omp` and went the same way. See [Agents](#agents).
|
||||
- One concern per layer, cheapest and least-changing first, so bumping a
|
||||
version rebuilds as little as possible.
|
||||
- `build-essential` is the C toolchain the language layers assume but do not
|
||||
ship: cgo, npm's node-gyp addons and Python C extensions all shell out to
|
||||
`gcc` and `make`. It rides along in the apt layer so there is one
|
||||
`apt-get update`.
|
||||
- `git` and `curl` are already in the base image. `sudo` is not, despite
|
||||
Debian's `base-passwd` shipping an empty `sudo` group, so the apt layer adds
|
||||
it and puts `paseo` in the group.
|
||||
- The image stays root: the entrypoint chowns the volumes, then drops to the
|
||||
`paseo` user (uid 1000) with `gosu`.
|
||||
- `entrypoint.sh` sets the `paseo` password from `PASEO_PASSWORD` on every
|
||||
|
||||
Reference in new issue
Block a user