diff --git a/CLAUDE.md b/CLAUDE.md index 4dff3bf..2490e2e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,6 +11,18 @@ shorter one. Existing services that still use `docker-compose.yml` stay as they are; do not rename them, not even while touching the file for something else. +## Installing software in an image + +Follow the upstream project's own documented install method, or the one the +community has settled on. In order of preference: the vendor's signed package +repository, the vendor's official tarball or install script, then a well-known +community installer. Do not hand-roll a download, and do not take a stale +distro package just because `apt install` is shorter — check what version it +actually gives you first. + +State the reason in a comment when the obvious route is the wrong one, so the +next person does not "simplify" it back. + The root `README.md` is an index only — it covers the shared conventions and links out to each service. Per-service detail (variables, ports, storage) belongs in that service's README, not the root one. Adding a service means diff --git a/paseo/.env.example b/paseo/.env.example index 7174730..e771961 100644 --- a/paseo/.env.example +++ b/paseo/.env.example @@ -16,3 +16,10 @@ PASEO_HOSTNAMES= # ws://...:6767 from an HTTPS page -- which the browser blocks. # `uniquelocal` covers the private ranges Docker bridge networks use. PASEO_TRUSTED_PROXIES=uniquelocal + +# Container hostname. Paseo shows it as the host label in the web UI, so +# without it you get a random container ID. +PASEO_LABEL=paseo + +# Timezone for logs and agent shells. +TZ=Asia/Ho_Chi_Minh diff --git a/paseo/Dockerfile b/paseo/Dockerfile index 7815fc8..d2f8b4d 100644 --- a/paseo/Dockerfile +++ b/paseo/Dockerfile @@ -1,18 +1,20 @@ -# The official image ships no agent CLIs. Add Claude Code globally under -# /usr/local, where the unprivileged paseo user can run it. -# -# npm here delivers the same native binary as the standalone installer -- it is -# not a Node wrapper. Do not swap this for the `curl | bash` installer: that -# writes to $HOME/.local, and $HOME is /home/paseo, a volume mount that masks -# anything baked in at build time. +# The official image ships no agent CLIs or language toolchains. Add them here. # # Stays root: the entrypoint needs root to chown the mounted volumes, then -# drops to paseo with gosu itself. +# 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. FROM ghcr.io/getpaseo/paseo:latest +ARG GO_VERSION=1.26.8 +ARG PYTHON_VERSION=3.12 + +# npm here delivers the same native binary as Anthropic's standalone installer; +# it is not a Node wrapper. Do not swap it for the `curl | bash` installer, +# which writes to $HOME/.local. RUN npm install -g @anthropic-ai/claude-code -# gh is not in the Debian repos, so pull it from GitHub's own signed one. +# Everyday shell tooling, plus gh from GitHub's own signed repo since Debian +# does not package it. 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 \ @@ -20,5 +22,21 @@ RUN install -d -m 0755 /etc/apt/keyrings \ && 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 \ && apt-get update \ - && apt-get install -y --no-install-recommends gh \ + && apt-get install -y --no-install-recommends \ + gh less jq unzip zip lsof psmisc ugrep bfs zsh \ && rm -rf /var/lib/apt/lists/* + +# Python via uv (astral.sh/uv), which fetches a standalone CPython build. +# Debian 12 only carries 3.11, and both dirs are kept outside $HOME. +ENV UV_INSTALL_DIR=/usr/local/bin \ + UV_PYTHON_INSTALL_DIR=/opt/python \ + UV_PYTHON_BIN_DIR=/usr/local/bin +RUN curl -fsSL https://astral.sh/uv/install.sh | sh \ + && uv python install "${PYTHON_VERSION}" --default + +# Go from the official tarball. Debian 12 carries 1.19, far too old. +ENV PATH=/usr/local/go/bin:$PATH +RUN curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-$(dpkg --print-architecture).tar.gz" \ + -o /tmp/go.tar.gz \ + && tar -C /usr/local -xzf /tmp/go.tar.gz \ + && rm /tmp/go.tar.gz diff --git a/paseo/README.md b/paseo/README.md index 6af6325..49d3a2c 100644 --- a/paseo/README.md +++ b/paseo/README.md @@ -1,12 +1,13 @@ # paseo [Paseo](https://paseo.sh) — self-hosted daemon and web UI for running coding -agents. Built from a local `Dockerfile` that adds Claude Code and `gh` to the -[official image](https://paseo.sh/docs/docker), which ships neither. +agents. Built from a local `Dockerfile` that adds Claude Code, `gh`, Go, +Python, and shell tooling to the [official image](https://paseo.sh/docs/docker), +which ships none of it. ## Setup -1. Set the three variables from `.env.example` in Coolify or Dokploy. +1. Set the variables from `.env.example` in Coolify or Dokploy. 2. Point the domain at port `6767` and deploy. 3. Open the domain. At the pairing screen enter the host **with the port**: @@ -33,6 +34,8 @@ the old entry is cached in `localStorage`. | `PASEO_PASSWORD` | Web UI and API login. Generate with `openssl rand -base64 24`. | | `PASEO_HOSTNAMES` | Domains allowed to reach the daemon, comma-separated. Your domain must be listed. | | `PASEO_TRUSTED_PROXIES` | Set to `uniquelocal`, or the UI loads but never connects. | +| `PASEO_LABEL` | Container hostname. Paseo shows it as the host label in the UI; without it you get a random container ID. | +| `TZ` | Timezone for logs and agent shells. | `PASEO_TRUSTED_PROXIES` matches the *source IP* of the proxy, so hostnames are rejected. By default the daemon believes `X-Forwarded-Proto` only from @@ -60,14 +63,22 @@ a redeploy. ## Image -Claude Code comes from npm; `gh` from GitHub's signed apt repo, since Debian -does not package it. Add other agent providers to the `npm install` line: +| Tool | Source | Why not apt | +| --- | --- | --- | +| Claude Code | npm | — | +| `gh` | GitHub's signed apt repo | Debian does not package it | +| Go (`GO_VERSION`) | Official go.dev tarball | Debian 12 ships 1.19 | +| Python (`PYTHON_VERSION`) | `uv python install` | Debian 12 ships 3.11 | +| `less jq unzip zip lsof psmisc ugrep bfs zsh` | apt | — | + +Bump a language with a build arg, e.g. `--build-arg GO_VERSION=1.27.1`. Add +other agent providers to the `npm install` line: ```dockerfile RUN npm install -g @anthropic-ai/claude-code @openai/codex opencode-ai ``` -Notes for anyone tempted to change it: +`uv` itself is installed too. Notes for anyone tempted to change things: - npm installs the same native binary as Anthropic's standalone installer. Don't swap in `curl | bash` — it writes to `$HOME/.local`, and `$HOME` is @@ -76,3 +87,6 @@ Notes for anyone tempted to change it: a notice at startup. Rebuild to update. - The image stays root on purpose: the entrypoint chowns the volumes, then drops to the `paseo` user (uid 1000) with `gosu`. +- Nothing installs into `$HOME`. That is `/home/paseo`, a volume mount that + hides anything baked in at build time — hence `/opt/python` and + `/usr/local/go` rather than the defaults. diff --git a/paseo/compose.yml b/paseo/compose.yml index e2e1cae..e577a4f 100644 --- a/paseo/compose.yml +++ b/paseo/compose.yml @@ -1,7 +1,9 @@ services: paseo: build: . + hostname: ${PASEO_LABEL} environment: + - TZ=${TZ} - PASEO_PASSWORD=${PASEO_PASSWORD} - PASEO_HOSTNAMES=${PASEO_HOSTNAMES} - PASEO_TRUSTED_PROXIES=${PASEO_TRUSTED_PROXIES}