shell: source cli_utils' functions, and install the iproute2 that one of them needs
v1.8.11 linked cli_utils' bin/ COMMANDS onto PATH and stopped there. Nothing ever
sourced cli_utils.sh, so its 14 FUNCTIONS were missing from every interactive shell
whose $HOME has no zsh rc -- which is the normal case, not an edge case: the
container's interactive shell is bash and zsh is not installed in the image. A
symlink cannot carry a shell function and a function cannot be reached from a
non-interactive shell, so the two mechanisms are disjoint and both are required.
The image was already paying this layer's dependency cost (fzf, bat, fd, rg, jq are
baked partly FOR these functions) while delivering none of its benefit.
The two changes ship together because they are coupled: portcheck is one of the 14,
and it was a hard stub in every image up to v1.8.11 -- neither ss nor ip nor lsof
nor netstat was present, so it printed "portcheck requires at least one of: ss,
lsof, netstat" and exited. Wiring the functions in without iproute2 would have
shipped a visibly broken one.
MEASURED, not assumed:
- the loader is bash-safe despite the *.zsh filenames: `bash --noprofile --norc`
exits 0, defines all 14, and they run (pathls, mkcd, up, extract, agents-sync,
fhist verified). The tree's one zsh-only construct (print -z in fzf/fhist.zsh)
is already guarded by [[ -n $ZSH_VERSION ]] with a bash fallback.
- fresh-$HOME seeding resolves 14/14; CLI_UTILS_SOURCE=0 is honoured; an absent
checkout is a genuinely silent no-op (no output, no leaked _cu).
- interactive shell startup 12 ms -> 17 ms.
- iproute2 is ~5.5 MB (4.2 MB itself + 6 libs under --no-install-recommends;
libpam-cap is a Recommends and correctly dropped). ss lands at /usr/bin/ss,
ip at /usr/sbin/ip, both already on the developer PATH, and `portcheck --all`
then correctly identifies the socat listener on 8765.
- hadolint clean on both Dockerfiles; repo-wide shellcheck -S error and bash -n
clean. .bash_aliases is outside CI's discovery (no shebang, not *.sh), so it
was checked by hand with -s bash at error AND warning level.
Named explicitly per this repo's floating-ref rule: /workspace/cli_utils is a HOST
BIND MOUNT, not a pinned ref, so the image now executes unpinned content in every
interactive shell. Errors are left visible rather than sent to /dev/null so that a
future zsh-only file in that repo is diagnosable rather than mysterious, and
CLI_UTILS_SOURCE=0 is the documented escape hatch. It is deliberately independent
of CLI_UTILS_LINK=0: the two disable independent mechanisms.
Deployment: needs a rebuild AND a recreate. $HOME is the container's writable layer
rather than a named volume (verified -- ~/.bash_aliases carries the container start
mtime while ~/.bashrc carries the image's), so the skel file is re-seeded on every
recreate; a host-bind-mounted ~/.bash_aliases is still never overwritten.
This commit is contained in:
+56
-2
@@ -73,10 +73,64 @@ by `pi-devbox-version`. Skill directories are picked up by a glob in
|
||||
assumed, since an enumerated list would have left the skill inert, which would
|
||||
have been a fitting way for *this* skill to fail.
|
||||
|
||||
Neither change reaches a running container until the image is rebuilt **and** the
|
||||
container recreated: `~/.agents/skills/` and the global `AGENTS.md` both live in
|
||||
Neither skills change reaches a running container until the image is rebuilt **and**
|
||||
the container recreated: `~/.agents/skills/` and the global `AGENTS.md` both live in
|
||||
the image, not in a volume or a mount.
|
||||
|
||||
**`cli_utils`' shell *functions* are now sourced, closing the half of that wiring
|
||||
the image never did.** v1.8.11 linked the repo's `bin/` **commands** into
|
||||
`~/.local/bin` so they resolve in non-interactive shells; nothing ever sourced
|
||||
`cli_utils.sh`, so its 14 **functions** (`fgit`, `fhist`, `fssh`, `fdocker`,
|
||||
`fmark`, `fproc`, `fex`, `fenv`, `extract`, `mkcd`, `pathls`, `portcheck`,
|
||||
`agents-sync`, `up`) were missing from every interactive shell whose `$HOME` had
|
||||
no zsh rc. That is the normal case, not an edge case: the container's interactive
|
||||
shell is bash and **zsh is not installed in the image**. A symlink cannot carry a
|
||||
shell function and a function cannot be reached from a non-interactive shell, so
|
||||
the two mechanisms are disjoint and both are required — the image had been paying
|
||||
this layer's dependency cost (`fzf`, `bat`, `fd`, `rg`, `jq` are baked partly *for*
|
||||
these functions) while delivering none of its benefit. Now sourced from
|
||||
`/etc/skel-devbox/.bash_aliases`, with the same detection order as the symlink
|
||||
block so commands and functions can never come from two different clones.
|
||||
`CLI_UTILS_SOURCE=0` opts out, deliberately independent of `CLI_UTILS_LINK=0`
|
||||
because the two disable independent mechanisms. Measured: all 14 resolve in a
|
||||
freshly-seeded `$HOME`, the opt-out is honoured, an absent checkout is a genuinely
|
||||
silent no-op (no output, no leaked `_cu` variable), and interactive shell startup
|
||||
goes from 12 ms to 17 ms.
|
||||
|
||||
**Named explicitly, per this repo's own floating-ref rule: `/workspace/cli_utils`
|
||||
is a host bind mount, not a pinned ref.** Sourcing it means the image now executes
|
||||
content it does not pin, on every interactive shell, on every device. It is
|
||||
bash-safe today and that was measured rather than assumed — sourcing under
|
||||
`bash --noprofile --norc` exits 0 and defines all 14 despite the `*.zsh`
|
||||
filenames, the functions run, and the tree's single zsh-only construct (`print -z`
|
||||
in `fzf/fhist.zsh`) is already guarded by `[[ -n $ZSH_VERSION ]]` with a bash
|
||||
fallback. The residual risk is future content: a cli_utils commit adding a
|
||||
genuinely zsh-only file would surface as parse errors at every prompt, fleet-wide.
|
||||
Errors are therefore left visible rather than sent to `/dev/null`, so the failure
|
||||
is diagnosable, and `CLI_UTILS_SOURCE=0` is the one-line escape hatch.
|
||||
|
||||
**`iproute2` is installed, so the container can answer "what is listening in
|
||||
here".** Neither `ss` nor `ip` was present in any image up to and including
|
||||
v1.8.11 — nor `lsof`, nor `netstat` — which made `cli_utils`' `portcheck` a hard
|
||||
stub that printed `portcheck requires at least one of: ss, lsof, netstat` and
|
||||
exited. `ss` satisfies its preferred branch (`ss -tlnp`), which is also the only
|
||||
branch that reports the owning PID. `net-tools` is deliberately **not** added
|
||||
(`netstat` is deprecated and only a fallback path) and neither is `lsof` (~500 KB
|
||||
for a third route to the same answer). Cost measured, not estimated: ~5.5 MB total
|
||||
— `iproute2` is 4.2 MB and pulls six libs under `--no-install-recommends`
|
||||
(`libbpf1`, `libmnl0`, `libtirpc-common`, `libtirpc3t64`, `libxtables12`,
|
||||
`libcap2-bin`; `libpam-cap` is a Recommends and is correctly dropped). Verified in
|
||||
a live container: `ss` at `/usr/bin/ss`, `ip` at `/usr/sbin/ip`, both already on
|
||||
the developer `PATH`, and `portcheck --all` then correctly identifies the `socat`
|
||||
listener on 8765.
|
||||
|
||||
The two changes above also need a rebuild **and** a recreate, for a different
|
||||
reason than the skills: `$HOME` is the container's writable layer rather than a
|
||||
named volume (verified — `~/.bash_aliases` carries the container's start mtime
|
||||
while `~/.bashrc` carries the image's), so the skel file is re-seeded on every
|
||||
recreate. A `$HOME/.bash_aliases` that is bind-mounted from the host is still
|
||||
never overwritten, which is the existing contract.
|
||||
|
||||
---
|
||||
|
||||
## v1.8.11 — 2026-08-27
|
||||
|
||||
Reference in New Issue
Block a user