feat(paseo)!: drop go and sdkman, rename AGENT_CLIS to AGENTS

The image installs python, gh, glab, build-essential, sudo, zsh and nano
only; go or a jvm goes into $HOME from a terminal instead.

SHELL and TZ are written into compose.yml rather than read from .env, so
the deploying shell's own values can no longer win over them.

The entrypoint calls chpasswd, chown and gosu by absolute path, tolerates
a chpasswd failure instead of taking the start down with it, turns
globbing off around the agent loop, and counts an agent as installed only
when its binary actually runs.
This commit is contained in:
tiennm99 committed 2026-09-18 11:39:09 +07:00
1 parent ac00eb9674
commit c20aed29cb
5 files changed
+115 -123

No files matched your search

+1 -8
View File
@@ -17,18 +17,11 @@ PASEO_HOSTNAMES=
# `uniquelocal` covers the private ranges Docker bridge networks use. # `uniquelocal` covers the private ranges Docker bridge networks use.
PASEO_TRUSTED_PROXIES=uniquelocal PASEO_TRUSTED_PROXIES=uniquelocal
# Shell for Paseo's terminals. Paseo reads $SHELL and otherwise falls back to
# /bin/sh (dash); it ignores the user's login shell, so `chsh` has no effect.
SHELL=/bin/zsh
# Agent CLIs to install on start, if not already present. Space- or # Agent CLIs to install on start, if not already present. Space- or
# comma-separated, from: claude codex opencode copilot omp pi. Leave empty to # comma-separated, from: claude codex opencode copilot omp pi. Leave empty to
# install none. The first start with a new paseo-home volume downloads a few # install none. The first start with a new paseo-home volume downloads a few
# hundred MB per agent and takes a while; later starts only check. # hundred MB per agent and takes a while; later starts only check.
AGENT_CLIS=claude codex AGENTS=claude codex
# Timezone for logs and agent shells.
TZ=Asia/Ho_Chi_Minh
# Git identity for agents and terminals, as author and committer. git reads # Git identity for agents and terminals, as author and committer. git reads
# these directly, so no `git config` step is needed. # these directly, so no `git config` step is needed.
+6 -20
View File
@@ -1,4 +1,4 @@
# Adds language toolchains and CLI tooling to the official image, which ships # Adds Python, a C toolchain and CLI tooling to the official image, which ships
# none of it. Agent CLIs are not among them -- see README.md, which carries the # none of it. Agent CLIs are not among them -- see README.md, which carries the
# reasoning for everything here. # reasoning for everything here.
FROM ghcr.io/getpaseo/paseo:latest FROM ghcr.io/getpaseo/paseo:latest
@@ -6,8 +6,8 @@ FROM ghcr.io/getpaseo/paseo:latest
# --- system packages ------------------------------------------------------- # --- system packages -------------------------------------------------------
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
less nano jq unzip zip lsof psmisc ugrep bfs zsh sudo \ build-essential sudo \
build-essential \ zsh nano \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& usermod -aG sudo paseo && usermod -aG sudo paseo
@@ -19,14 +19,6 @@ ENV UV_INSTALL_DIR=/usr/local/bin \
RUN curl -fsSL https://astral.sh/uv/install.sh | sh \ RUN curl -fsSL https://astral.sh/uv/install.sh | sh \
&& uv python install "${PYTHON_VERSION}" --default && uv python install "${PYTHON_VERSION}" --default
# --- go --------------------------------------------------------------------
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" \
-o /tmp/go.tar.gz \
&& tar -C /usr/local -xzf /tmp/go.tar.gz \
&& rm /tmp/go.tar.gz
# --- gh and glab ----------------------------------------------------------- # --- gh and glab -----------------------------------------------------------
# gh from GitHub's signed apt repository, glab from the .deb on its releases # gh from GitHub's signed apt repository, glab from the .deb on its releases
# page. # page.
@@ -44,15 +36,9 @@ RUN install -d -m 0755 /etc/apt/keyrings \
&& rm -f /tmp/glab.deb \ && rm -f /tmp/glab.deb \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# --- agent cli and sdkman path --------------------------------------------- # --- agent cli path --------------------------------------------------------
# Puts the $HOME directories the agent installers and SDKMAN write to on PATH. # Puts the $HOME directories the agent installers write to on PATH.
ENV SDKMAN_DIR=/home/paseo/.sdkman ENV PATH=/home/paseo/.local/bin:/home/paseo/.opencode/bin:$PATH
ENV PATH=/home/paseo/.local/bin:/home/paseo/.opencode/bin:\
$SDKMAN_DIR/candidates/java/current/bin:\
$SDKMAN_DIR/candidates/scala/current/bin:\
$SDKMAN_DIR/candidates/gradle/current/bin:\
$SDKMAN_DIR/candidates/maven/current/bin:\
$SDKMAN_DIR/candidates/sbt/current/bin:$PATH
# --- entrypoint ------------------------------------------------------------ # --- entrypoint ------------------------------------------------------------
# Wraps the image's entrypoint with the root-stage setup -- see entrypoint.sh. # Wraps the image's entrypoint with the root-stage setup -- see entrypoint.sh.
+73 -65
View File
@@ -1,11 +1,11 @@
# paseo # paseo
[Paseo](https://paseo.sh) — self-hosted daemon and web UI for running coding [Paseo](https://paseo.sh) — self-hosted daemon and web UI for running coding
agents. Built from a local `Dockerfile` that adds `gh`, `glab`, Go, Python, agents. Built from a local `Dockerfile` that adds `gh`, `glab`, Python, a C
a C toolchain, and shell tooling to the toolchain, `zsh` and `nano` to the
[official image](https://paseo.sh/docs/docker), which ships none of it. The [official image](https://paseo.sh/docs/docker), which ships none of it. The
agent CLIs and [SDKMAN](#sdkman) are not baked in; `entrypoint.sh` installs agent CLIs are not baked in; `entrypoint.sh` installs them into `$HOME` on
them into `$HOME` on start, see [Agents](#agents). start, see [Agents](#agents).
## Setup ## Setup
@@ -18,7 +18,7 @@ them into `$HOME` on start, see [Agents](#agents).
paseo.example.com:443 paseo.example.com:443
``` ```
4. List the agents you want in `AGENT_CLIS`; the first start installs them. 4. List the agents you want in `AGENTS`; the first start installs them.
Log in to each — see [Agents](#agents) — plus `gh auth login` and Log in to each — see [Agents](#agents) — plus `gh auth login` and
`glab auth login`. `glab auth login`.
@@ -38,11 +38,9 @@ the old entry is cached in `localStorage`.
| `PASEO_PASSWORD` | Web UI and API login, and the `paseo` user's `sudo` password. Generate with `openssl rand -base64 24`. | | `PASEO_PASSWORD` | Web UI and API login, and the `paseo` user's `sudo` password. Generate with `openssl rand -base64 24`. |
| `PASEO_HOSTNAMES` | Domains allowed to reach the daemon, comma-separated. Your domain must be listed. | | `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_TRUSTED_PROXIES` | Set to `uniquelocal`, or the UI loads but never connects. |
| `AGENT_CLIS` | Agent CLIs to install on start if missing, space- or comma-separated. Empty installs none. See [Agents](#agents). | | `AGENTS` | Agent CLIs to install on start if missing, space- or comma-separated. Empty installs none. See [Agents](#agents). |
| `SERVICE_HOSTNAME` | Container hostname, shown as the host label in the UI and in the shell prompt. Without it the label is a random container ID. | | `SERVICE_HOSTNAME` | Container hostname, shown as the host label in the UI and in the shell prompt. Without it the label is a random container ID. |
| `GIT_NAME` / `GIT_EMAIL` | Git author and committer identity for agents and terminals. | | `GIT_NAME` / `GIT_EMAIL` | Git author and committer identity for agents and terminals. |
| `TZ` | Timezone for logs and agent shells. |
| `SHELL` | Shell for Paseo's terminals. Paseo reads `$SHELL` and falls back to `/bin/sh`, ignoring the login shell, so `chsh` has no effect. |
`SERVICE_HOSTNAME` is used twice: as the container's `hostname:` and as the `SERVICE_HOSTNAME` is used twice: as the container's `hostname:` and as the
`HOST` variable inside it. Coolify injects `HOST=0.0.0.0` into every compose `HOST` variable inside it. Coolify injects `HOST=0.0.0.0` into every compose
@@ -57,9 +55,22 @@ lets the deploying shell's environment win over the `.env` file, and `HOSTNAME`
is set in every container -- including the one Coolify itself runs in. The is set in every container -- including the one Coolify itself runs in. The
container would silently take Coolify's hostname instead of this value. container would silently take Coolify's hostname instead of this value.
`PASEO_LABEL` was this variable's old name. It was never a Paseo variable, Neither name is a Paseo variable: the daemon reads neither `SERVICE_HOSTNAME`
only ours -- the daemon reads none of `PASEO_LABEL`, `SERVICE_HOSTNAME` or nor `HOST`, and takes the host label from the container hostname.
`HOST`, and takes the host label from the container hostname.
`SHELL` and `TZ` hit the same trap, which is why neither is in the table above
or in `.env.example`: they are written into `compose.yml` directly, the way the
`code-server` services do it. `SHELL` is the worse of the two, since every
interactive shell exports it -- a `docker compose up` from a terminal, the way
you would test this locally, would hand Paseo's terminals the *host's* shell.
Harmless when that is bash, which the image has; fatal to every terminal when
it is a path the image lacks. A UTC host would override `TZ` the same way. Both
are properties of this setup rather than of whoever deploys it, so there is
nothing to fill in per deployment.
Paseo reads `$SHELL` for its terminals and falls back to `/bin/sh` (dash)
otherwise; it ignores the user's login shell, so `chsh` has no effect. That is
what pins it to `/bin/zsh` here.
`PASEO_TRUSTED_PROXIES` matches the proxy's *source IP*, so hostnames are `PASEO_TRUSTED_PROXIES` matches the proxy's *source IP*, so hostnames are
rejected. The daemon trusts `X-Forwarded-Proto` from loopback only, but rejected. The daemon trusts `X-Forwarded-Proto` from loopback only, but
@@ -78,16 +89,16 @@ Listens on `6767`, published nowhere — the platform maps the domain to it, so
## Agents ## Agents
The image installs none of them. `entrypoint.sh` does, on start, for every name The image installs none of them. `entrypoint.sh` does, on start, for every name
in `AGENT_CLIS` whose command is not already on `PATH`: in `AGENTS` whose command does not already run:
``` ```
AGENT_CLIS=claude codex AGENTS=claude codex
``` ```
That is the default in `.env.example`. The other four are opt-in — add their That is the default in `.env.example`. The other four are opt-in — add their
names to install them too. names to install them too.
| Agent | Name in `AGENT_CLIS` | Installer it runs | Log in with | | Agent | Name in `AGENTS` | Installer it runs | Log in with |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| Claude Code | `claude` | `claude.ai/install.sh` | `claude` | | Claude Code | `claude` | `claude.ai/install.sh` | `claude` |
| Codex | `codex` | `chatgpt.com/codex/install.sh` | `codex login` | | Codex | `codex` | `chatgpt.com/codex/install.sh` | `codex login` |
@@ -109,6 +120,16 @@ already present. An agent that fails to install is logged and skipped rather
than taking the container with it, so a bad release or a network blip cannot than taking the container with it, so a bad release or a network blip cannot
leave you without a shell. leave you without a shell.
The check is whether the command *runs*, not whether the file exists: the start
asks it for `--version` and reinstalls only on the two exit codes a shell uses
for a binary it could not execute. A `curl | bash` cut short — by a network
drop, or by the platform stopping the container mid-download — leaves a
truncated binary on the volume, and a file-existence check would then skip the
reinstall on every later start while the daemon advertised an agent that fails
on every invocation. An agent that runs but does not understand `--version`
exits with some other code and counts as present, so nothing assumes all six
support the flag.
An unrecognised name is logged and skipped too. To install one by hand instead, An unrecognised name is logged and skipped too. To install one by hand instead,
run its installer in a terminal inside Paseo as `paseo` — never under `sudo`, run its installer in a terminal inside Paseo as `paseo` — never under `sudo`,
where they target root's home and land outside the volume, and where Claude where they target root's home and land outside the volume, and where Claude
@@ -139,37 +160,11 @@ themselves in place afterwards.
Pi and Oh My Pi are separate projects sharing an ancestor; their commands do Pi and Oh My Pi are separate projects sharing an ancestor; their commands do
not collide. not collide.
## SDKMAN
`entrypoint.sh` installs [SDKMAN](https://sdkman.io) on start too, into
`~/.sdkman`, whenever that directory is missing. No variable gates it — the
JVM toolchain is small next to an agent CLI and the image ships no Java at all.
Install what you need from a terminal:
```
sdk install java
sdk install gradle
```
`sdk` is a shell function, defined by the hook the installer appends to
`.bashrc` and `.zshrc`, so it exists in terminals only. The `current/bin`
directory of five candidates — `java`, `scala`, `gradle`, `maven`, `sbt` — is
on the image's `PATH` regardless, so the binaries themselves resolve for the
daemon and for commands an agent runs non-interactively, where no rc file is
read. Install a candidate outside that five and you get the `sdk` function in a
terminal but not the binary elsewhere; add its `current/bin` to the `PATH` line
in the `Dockerfile` if you want it there.
`SDKMAN_DIR` is set in the image, to the same `~/.sdkman` the installer would
have picked on its own. It is what puts the candidate paths above and the
install location in one place.
## Storage ## Storage
| Volume | Mount | Holds | | Volume | Mount | Holds |
| --- | --- | --- | | --- | --- | --- |
| `paseo-home` | `/home/paseo` | Daemon state, agent CLIs and their configs and credentials (`.claude`, `.codex`, `.config/*`), SDKMAN and its candidates | | `paseo-home` | `/home/paseo` | Daemon state, agent CLIs and their configs and credentials (`.claude`, `.codex`, `.config/*`) |
| `paseo-workspace` | `/workspace` | Code the agents work on | | `paseo-workspace` | `/workspace` | Code the agents work on |
The agent CLIs, `gh` and `glab` all keep their config under `/home/paseo`, so The agent CLIs, `gh` and `glab` all keep their config under `/home/paseo`, so
@@ -185,52 +180,65 @@ oh-my-zsh install persists. Anything written outside `$HOME` (`chsh`,
| --- | --- | --- | | --- | --- | --- |
| `gh` | GitHub's signed apt repo | 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 | | `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 | | 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`, `sudo` | apt | — |
| `build-essential` | apt | — | | `zsh`, `nano` | apt | — |
Bump a pinned version with a build arg, e.g. `--build-arg GO_VERSION=1.27.1`. Bump a pinned version with a build arg, e.g.
`uv` itself is installed too. `GLAB_VERSION` is pinned rather than tracking the `--build-arg PYTHON_VERSION=3.13`. `uv` itself is installed too.
latest because GitLab's download URL carries the version in the path. `GLAB_VERSION` is pinned rather than tracking the latest because GitLab's
download URL carries the version in the path.
The `Dockerfile` itself only says what each layer installs. The reasoning is The `Dockerfile` itself only says what each layer installs. The reasoning is
all here: all here:
- `$HOME` is `/home/paseo`, a volume that masks anything the build writes - `$HOME` is `/home/paseo`, a volume that masks anything the build writes
there. Hence `/opt/python` and `/usr/local/go` rather than the defaults. there. Hence `/opt/python` rather than the default.
- Do not add agent CLIs here, or a runtime only they need. Under `/usr/local` - 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 they cannot self-update; in `$HOME` they can, and they persist anyway. `omp`
used to be here for `omp` and went the same way. See [Agents](#agents). needs Bun for its source build, and that belongs in `$HOME` for the same
reason. See [Agents](#agents).
- One concern per layer, cheapest and least-changing first, so bumping a - One concern per layer, cheapest and least-changing first, so bumping a
version rebuilds as little as possible. version rebuilds as little as possible.
- `build-essential` is the C toolchain the language layers assume but do not - `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 ship: npm's node-gyp addons and Python C extensions both shell out to `gcc`
`gcc` and `make`. It rides along in the apt layer so there is one and `make`. It rides along in the apt layer so there is one `apt-get update`.
`apt-get update`. - No other language toolchain is in the image, because none is wanted often
enough to pay for a rebuild. Install Go, a JVM or anything else into `$HOME`
from a terminal, where it persists on the `paseo-home` volume like the agent
CLIs do.
- `git` and `curl` are already in the base image. `sudo` is not, despite - `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 Debian's `base-passwd` shipping an empty `sudo` group, so the apt layer adds
it and puts `paseo` in the group. it and puts `paseo` in the group.
- The image stays root: the entrypoint chowns the volumes, then drops to the - The image stays root: `entrypoint.sh` chowns `/home/paseo`, then hands over
`paseo` user (uid 1000) with `gosu`. to the base entrypoint, which drops to the `paseo` user (uid 1000) with
`gosu`.
- `entrypoint.sh` is installed as `/usr/local/bin/entrypoint`, next to the - `entrypoint.sh` is installed as `/usr/local/bin/entrypoint`, next to the
base image's `paseo-docker-entrypoint`, which it wraps. It was base image's `paseo-docker-entrypoint`, which it wraps.
`paseo-sudo-entrypoint` when setting the `sudo` password was all it did.
- `entrypoint.sh` runs before the base entrypoint, not after: that one ends in - `entrypoint.sh` runs before the base entrypoint, not after: that one ends in
`exec gosu paseo` and never returns, and by then is no longer root. Every `exec gosu paseo` and never returns, and by then is no longer root. Every
job it has needs root — `chpasswd`, the `chown`, and `gosu paseo` for the job it has needs root — `chpasswd`, the `chown`, and `gosu paseo` for the
SDKMAN and agent installs. agent installs.
- It sets the `paseo` password on every start rather than at build, so the - It sets the `paseo` password on every start rather than at build, so the
password never lands in an image layer, and because `/etc/shadow` is in the password never lands in an image layer, and because `/etc/shadow` is in the
image rather than on a volume and reverts on each recreate. The password is image rather than on a volume and reverts on each recreate. The password is
piped, not passed as an argument, since arguments are visible in `ps`; piped, not passed as an argument, since arguments are visible in `ps`;
`chpasswd` splits on the first colon, so a colon in the password is fine. An `chpasswd` splits on the first colon, so a colon in the password is fine. A
empty `PASEO_PASSWORD` leaves the account locked and `sudo` unusable. failure there is logged and the start continues, because `chpasswd` rejects a
- It also `chown`s `/home/paseo` before installing anything, agent CLI or multi-line value and under `set -e` that would otherwise take the whole
SDKMAN. A freshly created volume can arrive owned by root, and the base service down rather than just the password. An empty `PASEO_PASSWORD` leaves
entrypoint's own `chown` has not run yet at that point. the account locked and `sudo` unusable.
- `sudo` resets `PATH` to its `secure_path`, which excludes - It also `chown`s `/home/paseo` before installing any agent CLI. A freshly
`/usr/local/go/bin`. Use `sudo env PATH="$PATH" go ...` or the full path. created volume can arrive owned by root, and the base entrypoint's own
`chown` has not run yet at that point.
- `chpasswd`, `chown` and `gosu` are called by absolute path. The agent `PATH`
entries come first in the image's `PATH`, including root's, and they live on
a volume that anything running as `paseo` — an agent, by design — can write
to; a file planted there under one of those names would otherwise run as root
on the next start.
- Globbing is off around the `AGENTS` loop. The list is split unquoted, so
a `*` in it would otherwise expand against `/workspace`, the working
directory, and report every file in it as an unknown agent.
- The agent `PATH` entries belong in the image, not in a shell rc: the daemon - The agent `PATH` entries belong in the image, not in a shell rc: the daemon
probes for each provider's binary with `which` in its own environment, which probes for each provider's binary with `which` in its own environment, which
comes from the image and never sources an rc file. They are spelled comes from the image and never sources an rc file. They are spelled
+3 -3
View File
@@ -6,9 +6,9 @@ services:
- PASEO_PASSWORD=${PASEO_PASSWORD} - PASEO_PASSWORD=${PASEO_PASSWORD}
- PASEO_HOSTNAMES=${PASEO_HOSTNAMES} - PASEO_HOSTNAMES=${PASEO_HOSTNAMES}
- PASEO_TRUSTED_PROXIES=${PASEO_TRUSTED_PROXIES} - PASEO_TRUSTED_PROXIES=${PASEO_TRUSTED_PROXIES}
- SHELL=${SHELL} - SHELL=/bin/zsh
- AGENT_CLIS=${AGENT_CLIS} - AGENTS=${AGENTS}
- TZ=${TZ} - TZ=Asia/Ho_Chi_Minh
- GIT_AUTHOR_NAME=${GIT_NAME} - GIT_AUTHOR_NAME=${GIT_NAME}
- GIT_AUTHOR_EMAIL=${GIT_EMAIL} - GIT_AUTHOR_EMAIL=${GIT_EMAIL}
- GIT_COMMITTER_NAME=${GIT_NAME} - GIT_COMMITTER_NAME=${GIT_NAME}
+32 -27
View File
@@ -1,11 +1,16 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Runs as root ahead of the image's own entrypoint: sets the paseo user's # Runs as root ahead of the image's own entrypoint: sets the paseo user's
# login password, installs SDKMAN, then installs any agent CLI named in # login password, then installs any agent CLI named in AGENTS whose command
# AGENT_CLIS that is not already on PATH. See README.md. # does not already run. See README.md.
set -euo pipefail set -euo pipefail
if [[ "$(id -u)" == "0" && -n "${PASEO_PASSWORD:-}" ]]; then if [[ "$(id -u)" != "0" ]]; then
printf 'paseo:%s\n' "$PASEO_PASSWORD" | chpasswd exec /usr/local/bin/paseo-docker-entrypoint "$@"
fi
if [[ -n "${PASEO_PASSWORD:-}" ]]; then
printf 'paseo:%s\n' "$PASEO_PASSWORD" | /usr/sbin/chpasswd \
|| echo "entrypoint: could not set the paseo password, continuing" >&2
fi fi
agent_installer() { agent_installer() {
@@ -19,33 +24,33 @@ agent_installer() {
esac esac
} }
if [[ "$(id -u)" == "0" ]]; then /usr/bin/chown paseo:paseo /home/paseo
chown paseo:paseo /home/paseo
if [[ ! -d "${SDKMAN_DIR:-/home/paseo/.sdkman}" ]]; then agents="${AGENTS:-}"
echo "entrypoint: installing sdkman"
gosu paseo bash -c 'curl -fsSL https://get.sdkman.io | bash' \ set -f
|| echo "entrypoint: sdkman failed to install, continuing" >&2
for agent in ${agents//,/ }; do
installer="$(agent_installer "$agent")"
if [[ -z "$installer" ]]; then
echo "entrypoint: no such agent CLI: $agent" >&2
continue
fi fi
agents="${AGENT_CLIS:-}" # 126 and 127 are the shell's own codes for a binary that is missing or
# cannot be executed; any other code means it ran.
rc=0
/usr/sbin/gosu paseo bash -c '"$0" --version' "$agent" >/dev/null 2>&1 \
|| rc=$?
for agent in ${agents//,/ }; do if (( rc != 126 && rc != 127 )); then
installer="$(agent_installer "$agent")" continue
fi
if [[ -z "$installer" ]]; then echo "entrypoint: installing $agent"
echo "entrypoint: no such agent CLI: $agent" >&2 /usr/sbin/gosu paseo bash -c "$installer" \
continue || echo "entrypoint: $agent failed to install, continuing" >&2
fi done
if command -v "$agent" >/dev/null 2>&1; then
continue
fi
echo "entrypoint: installing $agent"
gosu paseo bash -c "$installer" \
|| echo "entrypoint: $agent failed to install, continuing" >&2
done
fi
exec /usr/local/bin/paseo-docker-entrypoint "$@" exec /usr/local/bin/paseo-docker-entrypoint "$@"