diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 201e18f..7220b09 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -1,7 +1,7 @@ # Static checks for the repository. # # - shellcheck: every shell script (build.sh, entrypoint-containerd.sh, -# tools/transfer.sh, scripts/*.sh, tests/*.sh). scripts/lib.sh is sourced by +# tools/*.sh, scripts/*.sh, tests/*.sh). scripts/lib.sh is sourced by # the install scripts, which point shellcheck at it with a source directive. # - hadolint: Dockerfile and Dockerfile.slim, with the rules in .hadolint.yaml. # hadolint 2.12 cannot parse `--mount=type=secret,...,env=...`; 2.14 can. @@ -86,7 +86,7 @@ jobs: shellcheck --external-sources --source-path=SCRIPTDIR \ build.sh \ entrypoint-containerd.sh \ - tools/transfer.sh \ + tools/*.sh \ scripts/*.sh \ tests/*.sh diff --git a/CLAUDE.md b/CLAUDE.md index c4b7625..61b0e0f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -79,13 +79,13 @@ Both Dockerfiles start with a named `uv` stage (`FROM ghcr.io/astral-sh/uv: ```text uv (ghcr.io/astral-sh/uv@sha256) ──COPY /uv /uvx──┐ ▼ - debian:trixie@sha256 → base → docker + debian:trixie-slim@sha256 → base → docker → podman → nerdctl → containerd ``` -The base image is pinned as `debian:trixie@sha256:` (tag kept so Dependabot bumps the digest), in both Dockerfiles. `.github/workflows/dependabot-auto-merge.yml` never auto-merges docker-ecosystem PRs: the gatekeeper does not build Dependabot PRs, so a maintainer approves, re-runs the build (smoke test) and merges by hand. +The base image is pinned as `debian:trixie-slim@sha256:` (the slim variant: no docs, man pages or translations in the base layer) (tag kept so Dependabot bumps the digest), in both Dockerfiles. `.github/workflows/dependabot-auto-merge.yml` never auto-merges docker-ecosystem PRs: the gatekeeper does not build Dependabot PRs, so a maintainer approves, re-runs the build (smoke test) and merges by hand. - **`base`**: Installs all networking/system tools, the upstream-release binaries, Zsh with Oh My Zsh + Powerlevel10k, plugins, and helper scripts - **`docker`**: Docker CLI only (`docker-ce-cli`, `docker-buildx-plugin`, `docker-compose-plugin`) from Docker's official apt repo. No `dockerd`, no `containerd.io`: users mount the host socket (`-v /var/run/docker.sock:/var/run/docker.sock`) @@ -136,7 +136,8 @@ Version override build args (all default to empty = latest): - **Shell**: `SHELL ["/bin/bash", "-o", "pipefail", "-c"]` in the base stage, and heredoc RUN bodies use `set -eux`. - **gitstatus binary persistence**: The Powerlevel10k gitstatus install uses `GITSTATUS_CACHE_DIR=$ZSH/custom/themes/powerlevel10k/gitstatus/usrbin`, a path *outside* the `/root/.cache` cache mount. Without this, the compiled binary would live only in the cache mount and be absent from the final image layer. - **Locale**: `LANG=C.UTF-8`, the only UTF-8 locale built into Debian without the `locales` package (`en_US.UTF-8` is not generated, and `man` complained about it on every call). -- **man pages**: `man-db` is installed in the full image. Before the install, any dpkg `path-exclude=/usr/share/man` rule in `/etc/dpkg/dpkg.cfg.d/` is removed so pages actually land (the pinned image has none today; it is a guard). +- **man pages**: `man-db` is installed in the full image. Before the install, any dpkg `path-exclude /usr/share/man` rule in `/etc/dpkg/dpkg.cfg.d/` is removed so pages actually land. debian:trixie-slim ships one in the space-separated form, so the sed matches both `path-exclude=` and `path-exclude `. Man pages of packages already in the base layer (bash, coreutils...) stay missing. +- **dpkg excludes**: `configs/dpkg-excludes` is copied to `/etc/dpkg/dpkg.cfg.d/netshoot-excludes` in both Dockerfiles before any apt step, so `/usr/share/doc` (except `copyright`) and `/usr/share/locale` are skipped. Man pages are not excluded. Use `mtr-tiny`, never `mtr` (GTK build): the smoke test fails if `libgtk-3-0t64` is installed. ### Shell Layout (works for root and any uid) @@ -155,6 +156,9 @@ Version override build args (all default to empty = latest): - `configs/p10k.zsh` — Powerlevel10k theme configuration (installed as `/etc/zsh/netshoot/.p10k.zsh`) - `configs/podman-storage.conf` — Podman storage config for rootless operation - `tools/transfer.sh` — File transfer helper script (installed to `/usr/local/bin/transfer.sh`) +- `tools/install-dockerd.sh`: docker variant only, installed as `/usr/local/bin/install-dockerd`: installs `docker-ce` + `containerd.io` on demand from the already configured Docker apt repo; `--start` launches dockerd and waits for it (needs `--privileged`) +- `configs/motd/`: message of the day fragments, copied to `/etc/netshoot/motd.d/` (`10-netshoot` common text, `50-variant` replaced by each variant stage, `90-footer`); `configs/zshrc` prints them before the p10k instant prompt, on a tty only, once per `docker run`/`docker exec` (exported guard `_NETSHOOT_MOTD_SHOWN`), never with `NETSHOOT_NO_MOTD` set +- `configs/dpkg-excludes`: dpkg path-exclude rules (docs and translations), see Key Design Patterns - `entrypoint-containerd.sh` — Entrypoint for containerd variant (starts containerd daemon, then exec's the user command) ### Installation Scripts @@ -269,10 +273,10 @@ The CI workflow uses `type=gha` (GitHub Actions cache, `scope=buildkit- There is no `make`, `npm`, or test runner; the checks are plain scripts and CI workflows. - **Smoke test**: `tests/smoke.sh ` (variants: `base`, `slim`, `docker`, `podman`, `nerdctl`, `containerd`) runs every check in `docker run --rm --entrypoint /bin/bash -c '...'` (entrypoint overridden so the containerd variant needs no privileges) and reports PASS/FAIL per check, exiting 1 on any failure. It checks: expected binaries present and removed ones (dstat, sudo) absent (pip/pip3 are not checked: `httpie` and `python3-venv` depend on `python3-pip` in trixie, so pip stays installed), per variant; functional checks (grpcurl, witr, doggo, websocat, kubectl, `check-tls --help` as uid 1000, `python3 -m venv`, `man -w tcpdump` on the full image); an interactive zsh as root and as `--user 1000:1000`, both without a terminal and on a `docker run -t` pty (`timeout --foreground -k`), which must print a probe marker proving the bundled config loaded (`ZSH=/opt/oh-my-zsh`, `p10k` defined, `ZSH_COMPDUMP` set), with no error output and no newuser/p10k wizard; variant tools (docker CLI + buildx + compose with `dockerd` absent, podman, nerdctl, containerd + nerdctl). Run it on a locally built image; CI runs it for every variant before pushing. -- **Lint** (`.github/workflows/lint.yaml`, on `pull_request` and push to `main`, `contents: read`): shellcheck on `build.sh`, `entrypoint-containerd.sh`, `tools/transfer.sh`, `scripts/*.sh` and `tests/*.sh` (`scripts/lib.sh` is sourced, the install scripts point shellcheck at it with a `source` directive); hadolint on `Dockerfile` and `Dockerfile.slim` with `.hadolint.yaml` (it ignores no rule globally: rules are ignored inline with `# hadolint ignore=` above the instruction, with a justification comment); actionlint on the workflows. Linter binaries are pinned by version and SHA256 in the workflow. +- **Lint** (`.github/workflows/lint.yaml`, on `pull_request` and push to `main`, `contents: read`): shellcheck on `build.sh`, `entrypoint-containerd.sh`, `tools/*.sh`, `scripts/*.sh` and `tests/*.sh` (`scripts/lib.sh` is sourced, the install scripts point shellcheck at it with a `source` directive); hadolint on `Dockerfile` and `Dockerfile.slim` with `.hadolint.yaml` (it ignores no rule globally: rules are ignored inline with `# hadolint ignore=` above the instruction, with a justification comment); actionlint on the workflows. Linter binaries are pinned by version and SHA256 in the workflow. - README.md must stay markdownlint-clean per `.markdownlint.json` (80-column lines outside code blocks and tables). -All shell scripts (`build.sh`, `entrypoint-containerd.sh`, `tools/transfer.sh`, `scripts/*.sh`, `tests/*.sh`) use `bash` and must pass shellcheck. +All shell scripts (`build.sh`, `entrypoint-containerd.sh`, `tools/*.sh`, `scripts/*.sh`, `tests/*.sh`) use `bash` and must pass shellcheck. ## Development Notes diff --git a/Dockerfile b/Dockerfile index da17966..d09b31d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ FROM ghcr.io/astral-sh/uv:0.12.18@sha256:3adc3706091ce7c2fe595e669628caedd6d9515 # Base Stage: Debian Trixie with Essential Tools and Shell Enhancements # ============================================================================== # The tag is kept next to the digest so Dependabot can bump the digest. -FROM debian:trixie@sha256:9cc080028c43b27d2074d63a5f9caf7166d731494965616c1a6d2827a004585c AS base +FROM debian:trixie-slim@sha256:a29215f6a35e51e22adffa17f89e9d2ef06214e64a2bad10d765c46aea49f11f AS base ARG TARGETARCH @@ -48,6 +48,11 @@ RUN < /etc/apt/apt.conf.d/keep-cache EOT +# Do not install package docs and message translations (~150 MB): nobody reads +# /usr/share/doc in a debug container. Copyright files stay for license +# compliance. Man pages are kept on purpose (man-db is installed below). +COPY --link configs/dpkg-excludes /etc/dpkg/dpkg.cfg.d/netshoot-excludes + # Upgrade all packages to their latest versions. # Uses BuildKit cache mounts with sharing=locked for parallel builds safety. # CACHE_BUST (the ISO week, passed by CI and build.sh) invalidates this layer @@ -70,9 +75,11 @@ RUN --mount=type=cache,target=/var/cache/apt,id=apt-cache-${TARGETARCH},sharing= set -eux # Make sure man pages are not excluded by a dpkg path-exclude rule, so that - # man-db has something to index. The pinned debian:trixie image ships no such - # rule today: this is a no-op guard in case a future digest adds one. - sed -i '\|^path-exclude=/usr/share/man|d' /etc/dpkg/dpkg.cfg.d/* + # man-db has something to index. debian:trixie-slim ships such a rule + # (`path-exclude /usr/share/man/*`, space-separated); dpkg also accepts the + # `=` form, so match both. Man pages of packages already in the base layer + # stay missing; every package installed below gets its pages. + sed -i -E '\|^path-exclude[= ]/usr/share/man|d' /etc/dpkg/dpkg.cfg.d/* # Core system tools (rarely change) CORE_TOOLS=( @@ -104,7 +111,6 @@ RUN --mount=type=cache,target=/var/cache/apt,id=apt-cache-${TARGETARCH},sharing= jq kitty-terminfo lsof - magic-wormhole ncdu python3-venv rsync @@ -112,6 +118,7 @@ RUN --mount=type=cache,target=/var/cache/apt,id=apt-cache-${TARGETARCH},sharing= sysstat thefuck tmux + wormhole-rs ) # Networking tools @@ -139,7 +146,7 @@ RUN --mount=type=cache,target=/var/cache/apt,id=apt-cache-${TARGETARCH},sharing= ipvsadm ldap-utils masscan - mtr + mtr-tiny net-tools netcat-openbsd nethogs @@ -174,6 +181,10 @@ RUN --mount=type=cache,target=/var/cache/apt,id=apt-cache-${TARGETARCH},sharing= "${CORE_TOOLS[@]}" \ "${SYSTEM_TOOLS[@]}" \ "${NETWORKING_TOOLS[@]}" + + # wormhole-rs replaces the Python magic-wormhole (and its Twisted stack); + # keep the usual `wormhole` command name. Same send/receive protocol. + ln -s /usr/bin/wormhole-rs /usr/local/bin/wormhole EOT # Install official Ookla speedtest CLI. @@ -298,6 +309,12 @@ COPY --link configs/zshrc /etc/zsh/netshoot/.zshrc COPY --link configs/p10k.zsh /etc/zsh/netshoot/.p10k.zsh COPY --link --chmod=755 tools/transfer.sh /usr/local/bin/transfer.sh +# Message of the day, printed by the zshrc in name order. Each variant stage +# replaces 50-variant with its own notes. +COPY --link configs/motd/netshoot /etc/netshoot/motd.d/10-netshoot +COPY --link configs/motd/variant-base /etc/netshoot/motd.d/50-variant +COPY --link configs/motd/footer /etc/netshoot/motd.d/90-footer + # Set the working directory and default command. WORKDIR /root CMD ["zsh"] @@ -341,6 +358,11 @@ RUN --mount=type=cache,target=/var/cache/apt,id=apt-cache-${TARGETARCH},sharing= docker-compose-plugin EOT +# On-demand engine: `install-dockerd [--start]` installs docker-ce and +# containerd.io from the repository configured above (needs --privileged to run). +COPY --link --chmod=755 tools/install-dockerd.sh /usr/local/bin/install-dockerd +COPY --link configs/motd/variant-docker /etc/netshoot/motd.d/50-variant + # ============================================================================== # Podman Stage: Installs Podman and related tools # ============================================================================== @@ -364,6 +386,7 @@ EOT # Copy Podman storage configuration file. COPY --link configs/podman-storage.conf /root/.config/containers/storage.conf +COPY --link configs/motd/variant-podman /etc/netshoot/motd.d/50-variant # ============================================================================== # nerdctl Stage: Installs nerdctl (client only version) @@ -383,6 +406,8 @@ RUN --mount=type=bind,source=scripts,target=/tmp/scripts \ --mount=type=secret,id=github_token,env=GITHUB_TOKEN \ bash /tmp/scripts/install-nerdctl.sh client +COPY --link configs/motd/variant-nerdctl /etc/netshoot/motd.d/50-variant + # ============================================================================== # Containerd/nerdctl Stage: Installs nerdctl (full version with containerd) # ============================================================================== @@ -403,5 +428,6 @@ RUN --mount=type=bind,source=scripts,target=/tmp/scripts \ # Copy the entrypoint script for containerd. COPY --link --chmod=755 entrypoint-containerd.sh /entrypoint.sh +COPY --link configs/motd/variant-containerd /etc/netshoot/motd.d/50-variant ENTRYPOINT ["/entrypoint.sh"] diff --git a/Dockerfile.slim b/Dockerfile.slim index 4d599f9..475dc6f 100644 --- a/Dockerfile.slim +++ b/Dockerfile.slim @@ -19,7 +19,7 @@ FROM ghcr.io/astral-sh/uv:0.12.18@sha256:3adc3706091ce7c2fe595e669628caedd6d9515 # Base Stage: Debian Trixie with a focused set of tools # ============================================================================== # The tag is kept next to the digest so Dependabot can bump the digest. -FROM debian:trixie@sha256:9cc080028c43b27d2074d63a5f9caf7166d731494965616c1a6d2827a004585c AS base +FROM debian:trixie-slim@sha256:a29215f6a35e51e22adffa17f89e9d2ef06214e64a2bad10d765c46aea49f11f AS base ARG TARGETARCH @@ -41,6 +41,10 @@ RUN < /etc/apt/apt.conf.d/keep-cache EOT +# Do not install package docs and message translations; copyright files stay +# for license compliance. Same rules as the full image. +COPY --link configs/dpkg-excludes /etc/dpkg/dpkg.cfg.d/netshoot-excludes + # Upgrade all packages to their latest versions. # Uses BuildKit cache mounts with sharing=locked for parallel builds safety. # CACHE_BUST (the ISO week, passed by CI and build.sh) invalidates this layer @@ -74,7 +78,7 @@ RUN --mount=type=cache,target=/var/cache/apt,id=apt-cache-${TARGETARCH},sharing= iproute2 iputils-ping iputils-tracepath - mtr + mtr-tiny net-tools netcat-openbsd nmap @@ -190,6 +194,10 @@ COPY --link configs/zshrc /etc/zsh/netshoot/.zshrc COPY --link configs/p10k.zsh /etc/zsh/netshoot/.p10k.zsh COPY --link --chmod=755 tools/transfer.sh /usr/local/bin/transfer.sh +# Message of the day, printed by the zshrc in name order (slim text and footer). +COPY --link configs/motd/slim /etc/netshoot/motd.d/10-netshoot +COPY --link configs/motd/footer /etc/netshoot/motd.d/90-footer + # Set the working directory and default command. WORKDIR /root CMD ["zsh"] diff --git a/README.md b/README.md index 7468d37..9eb1b23 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ variants extend from a common `base` stage: ```mermaid flowchart TB U["ghcr.io/astral-sh/uv (pinned)"] -. "COPY uv, uvx" .-> B - A["debian:trixie (pinned digest)"] --> B["base"] + A["debian:trixie-slim (pinned digest)"] --> B["base"] B --> C["docker"] B --> D["podman"] B --> E["nerdctl"] @@ -200,6 +200,27 @@ The `docker` variant ships the client only `containerd` in the image. Mount the socket of the engine you want to talk to. +When you do need a local engine, `install-dockerd` +installs `docker-ce` and `containerd.io` on demand +from the Docker repository already configured in the +image. Running it needs a privileged container: + +```bash +docker run -it --rm --privileged obeoneorg/netshoot:docker +install-dockerd --start # installs, starts dockerd, waits for the socket +docker run --rm hello-world +``` + +### Message of the Day + +Every variant prints a short message when an +interactive shell starts on a terminal: a few +example commands and the notes for that variant. +It shows once per `docker run` or `docker exec`, +not again in nested shells or tmux panes. Hide it +with `-e NETSHOOT_NO_MOTD=1`. The fragments live in +`/etc/netshoot/motd.d/`. + ### Pulling Specific Variants ```bash @@ -244,7 +265,7 @@ organized by category: | HTTP/HTTPS | curl, wget, httpie, apache2-utils (ab) | | WebSocket | websocat | | Remote Access | openssh-client, telnet | -| Data Transfer | socat, rsync, magic-wormhole | +| Data Transfer | socat, rsync, wormhole-rs (as `wormhole`) | | VPN | wireguard-tools | | SMTP Testing | swaks | | LDAP | ldap-utils (ldapsearch, ldapwhoami, ...) | @@ -313,7 +334,7 @@ bind9-dnsutils, bmon, bridge-utils, conntrack, curl, dhcpdump, ethtool, fping, hping3, httpie, iftop, iperf, iperf3, iproute2, ipset, iptables, iputils-ping, iputils-tracepath, ipvsadm, ldap-utils, -masscan, mtr, netcat-openbsd, net-tools, nethogs, +masscan, mtr-tiny, netcat-openbsd, net-tools, nethogs, netperf, nftables, ngrep, nload, nmap, openssh-client, sipcalc, snmp, socat, speedtest, sslscan, swaks, tcpdump, tcpflow, tcptraceroute, @@ -322,9 +343,10 @@ wireguard-tools **System**: bash, btop, ca-certificates, coreutils, file, fzf, git, gnupg, htop, iotop, jq, -kitty-terminfo, less, lsof, magic-wormhole, man-db, +kitty-terminfo, less, lsof, man-db, ncdu, openssl, procps, python3-venv, rsync, strace, -sysstat, thefuck, tmux, unzip, util-linux, vim, zip, +sysstat, thefuck, tmux, unzip, util-linux, vim, +wormhole-rs, zip, zsh **Upstream releases** (not packaged, or too old, in @@ -339,7 +361,7 @@ fast-syntax-highlighting bind9-dnsutils, btop, ca-certificates, coreutils, curl, ethtool, file, fping, git, httpie, iperf3, iproute2, iputils-ping, iputils-tracepath, jq, -kitty-terminfo, less, lsof, mtr, netcat-openbsd, +kitty-terminfo, less, lsof, mtr-tiny, netcat-openbsd, net-tools, nmap, openssh-client, procps, rsync, socat, strace, tcpdump, telnet, tmux, traceroute, unzip, util-linux, vim, wget, whois, zip, zsh, plus diff --git a/configs/dpkg-excludes b/configs/dpkg-excludes new file mode 100644 index 0000000..73978bb --- /dev/null +++ b/configs/dpkg-excludes @@ -0,0 +1,6 @@ +# Netshoot: skip package docs and message translations to keep the image small. +# Man pages are deliberately NOT excluded (man-db is part of the toolset). +path-exclude=/usr/share/doc/* +path-include=/usr/share/doc/*/copyright +path-exclude=/usr/share/locale/* +path-include=/usr/share/locale/locale.alias diff --git a/configs/motd/footer b/configs/motd/footer new file mode 100644 index 0000000..71a12ce --- /dev/null +++ b/configs/motd/footer @@ -0,0 +1 @@ + Hide this message with -e NETSHOOT_NO_MOTD=1 diff --git a/configs/motd/netshoot b/configs/motd/netshoot new file mode 100644 index 0000000..4aa9be5 --- /dev/null +++ b/configs/motd/netshoot @@ -0,0 +1,6 @@ +netshoot: network troubleshooting toolkit (https://github.com/obeone/netshoot) + Capture tcpdump -ni any port 53 termshark -i eth0 + DNS doggo example.com dig +short example.com + Path mtr 1.1.1.1 ss -tulpn + HTTP/TLS curl -v https://host check-tls host + Share wormhole send transfer.sh send diff --git a/configs/motd/slim b/configs/motd/slim new file mode 100644 index 0000000..e728f6a --- /dev/null +++ b/configs/motd/slim @@ -0,0 +1,7 @@ +netshoot slim: reduced network troubleshooting toolkit (https://github.com/obeone/netshoot) + Capture tcpdump -ni any port 53 + DNS dig +short example.com + Path mtr 1.1.1.1 ss -tulpn + HTTP/TLS curl -v https://host check-tls host + Share transfer.sh send + Variant slim: reduced toolset, no container runtime diff --git a/configs/motd/variant-base b/configs/motd/variant-base new file mode 100644 index 0000000..23f4e31 --- /dev/null +++ b/configs/motd/variant-base @@ -0,0 +1 @@ + Variant base: no container runtime (see the docker, podman, nerdctl and containerd tags) diff --git a/configs/motd/variant-containerd b/configs/motd/variant-containerd new file mode 100644 index 0000000..a0bcca7 --- /dev/null +++ b/configs/motd/variant-containerd @@ -0,0 +1,2 @@ + Variant containerd: nerdctl full, containerd started by the entrypoint (needs --privileged) + Logs: /var/log/containerd.log diff --git a/configs/motd/variant-docker b/configs/motd/variant-docker new file mode 100644 index 0000000..9c8adc6 --- /dev/null +++ b/configs/motd/variant-docker @@ -0,0 +1,3 @@ + Variant docker: CLI, buildx and compose, no engine + Host engine: -v /var/run/docker.sock:/var/run/docker.sock + Local engine: install-dockerd --start (needs --privileged) diff --git a/configs/motd/variant-nerdctl b/configs/motd/variant-nerdctl new file mode 100644 index 0000000..e18409e --- /dev/null +++ b/configs/motd/variant-nerdctl @@ -0,0 +1,2 @@ + Variant nerdctl: client only + Host containerd: -v /run/containerd/containerd.sock:/run/containerd/containerd.sock diff --git a/configs/motd/variant-podman b/configs/motd/variant-podman new file mode 100644 index 0000000..d42082b --- /dev/null +++ b/configs/motd/variant-podman @@ -0,0 +1 @@ + Variant podman: podman and fuse-overlayfs (run containers with --privileged) diff --git a/configs/zshrc b/configs/zshrc index 0a707c4..1c8b5b9 100644 --- a/configs/zshrc +++ b/configs/zshrc @@ -10,6 +10,20 @@ if [[ -z "$_NETSHOOT_USER_ZSHRC" && -f "$HOME/.zshrc" && ! "$HOME/.zshrc" -ef "$ return fi +# Message of the day: the fragments in /etc/netshoot/motd.d, in name order +# (common text, variant notes, footer). Interactive shells on a terminal only, +# and never when NETSHOOT_NO_MOTD is set. The exported guard is inherited by +# child shells (omz reload's exec zsh, nested zsh, tmux panes), so it shows +# once per `docker run` / `docker exec`. It must print before the instant prompt +# block below: console output after it triggers a Powerlevel10k warning. +if [[ -o interactive && -t 1 && -z "$NETSHOOT_NO_MOTD" && -z "$_NETSHOOT_MOTD_SHOWN" ]]; then + for _netshoot_motd in /etc/netshoot/motd.d/*(N); do + cat -- "$_netshoot_motd" + done + unset _netshoot_motd + export _NETSHOOT_MOTD_SHOWN=1 +fi + ZSH_DISABLE_COMPFIX=true # Pick a writable cache directory: $XDG_CACHE_HOME or ~/.cache when usable, diff --git a/tests/smoke.sh b/tests/smoke.sh index 70a8d60..46f01db 100755 --- a/tests/smoke.sh +++ b/tests/smoke.sh @@ -65,7 +65,7 @@ FULL_PRESENT=( # System btop htop iotop iostat lsof strace ncdu rsync file # Shell, editors and utilities - zsh bash tmux vim less man git jq fzf thefuck wormhole + zsh bash tmux vim less man git jq fzf thefuck wormhole wormhole-rs gpg openssl unzip zip python3 uv uvx transfer.sh ) # Removed from the full image on purpose. pip/pip3 are not listed: python3-pip @@ -248,6 +248,16 @@ check "install scripts are not left in the image" -- \ '! compgen -G "/tmp/install-*.sh" >/dev/null && [[ ! -e /tmp/scripts ]]' check "no zsh configuration in /root" -- \ '[[ ! -e /root/.zshrc && ! -e /root/.p10k.zsh && ! -e /root/.oh-my-zsh ]]' +# Size guards: the GTK build of mtr drags in a whole desktop stack, and dpkg +# must skip docs and translations (copyright files excepted). Packages of the +# debian base image predate the exclude rules, so probe one we install: git. +check "no GTK in the image (mtr-tiny, not mtr)" -- \ + '! dpkg -s libgtk-3-0t64 >/dev/null 2>&1' +# shellcheck disable=SC2016 # expanded inside the container +check "no package docs or translations (git)" -- \ + '[[ -f /usr/share/doc/git/copyright ]] && + [[ -z "$(find /usr/share/doc/git -type f ! -name copyright -print -quit)" ]] && + [[ -z "$(find /usr/share/locale -name git.mo -print -quit)" ]]' if [[ "$VARIANT" != slim ]]; then check "grpcurl --version" -- 'grpcurl --version' check "doggo --version" -- 'doggo --version' @@ -262,6 +272,21 @@ check_zsh "zsh loads the bundled config as uid 1000" notty --user 1000:1000 check_zsh "zsh on a tty as root" tty check_zsh "zsh on a tty as uid 1000" tty --user 1000:1000 +echo +echo "== Message of the day" +# script(1) gives zsh a pseudo-terminal, as an interactive docker run -t does. +# shellcheck disable=SC2016 # expanded inside the container +motd_count='timeout 60 script -qec "$1" /dev/null | grep -c "Hide this message" || true' +# shellcheck disable=SC2016 # expanded inside the container +check "motd shows the ${VARIANT} variant notes on a tty" -- \ + 'timeout 60 script -qec "zsh -ic true" /dev/null | grep -q "Variant $1:"' "$VARIANT" +check "motd is shown once, not again in a nested zsh" -- \ + "[[ \$($motd_count) -eq 1 ]]" 'zsh -ic "zsh -ic true"' +check "motd is hidden with NETSHOOT_NO_MOTD=1" -e NETSHOOT_NO_MOTD=1 -- \ + "[[ \$($motd_count) -eq 0 ]]" 'zsh -ic true' +check "motd is not printed without a terminal" -- \ + '! zsh -ic true 2>&1 | grep -q "Hide this message"' + case "$VARIANT" in docker) echo @@ -271,6 +296,10 @@ case "$VARIANT" in check "docker compose version" -- 'docker compose version' # CLI only: no engine and no containerd in this variant. check_binaries absent dockerd containerd + # The engine is opt-in through the bundled helper. + check "install-dockerd --help" -- 'install-dockerd --help | grep -q -- --start' + check "install-dockerd refuses non-root" --user 1000:1000 -- \ + '! install-dockerd 2>/dev/null' ;; podman) echo diff --git a/tools/install-dockerd.sh b/tools/install-dockerd.sh new file mode 100755 index 0000000..19cecce --- /dev/null +++ b/tools/install-dockerd.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +#################################################################### +# +# Script Name: install-dockerd +# Description: Install the Docker engine (dockerd + containerd) on demand +# in the netshoot docker variant, which ships the CLI only. +# Author: Grégoire Compagnon (obeone) +# License: MIT License +# +# Usage: +# install-dockerd [--start] +# +# Notes: +# - Must run as root. The Docker apt repository is already +# configured in the image, only the package lists are missing. +# - --start launches dockerd in the background and waits for +# its socket. Running an engine needs a privileged container +# (docker run --privileged ...). +# +#################################################################### + +set -euo pipefail + +LOG_FILE=/var/log/dockerd.log +SOCKET=/var/run/docker.sock + +# usage +# Print the help text. +usage() { + sed -n 's/^# \{0,1\}//; 11,19p' "$0" +} + +# start_dockerd +# Launch dockerd in the background and wait up to 30 s for its socket, +# so the caller can use `docker` right after this script returns. +start_dockerd() { + if [[ -S "$SOCKET" ]] && docker info >/dev/null 2>&1; then + echo "dockerd is already running." + return 0 + fi + nohup dockerd >"$LOG_FILE" 2>&1 & + for _ in $(seq 30); do + if docker info >/dev/null 2>&1; then + echo "dockerd is running (logs: $LOG_FILE)." + return 0 + fi + sleep 1 + done + echo "dockerd did not come up in 30 s. Is the container --privileged?" >&2 + tail -n 20 "$LOG_FILE" >&2 || true + return 1 +} + +START=false +case "${1:-}" in + "") ;; + --start) START=true ;; + -h | --help) usage; exit 0 ;; + *) usage >&2; exit 2 ;; +esac + +if [[ $EUID -ne 0 ]]; then + echo "install-dockerd must run as root." >&2 + exit 1 +fi + +if ! command -v dockerd >/dev/null 2>&1; then + # Lists are not kept in the image (they live in a build cache mount). + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + containerd.io \ + docker-ce +fi + +if [[ "$START" == true ]]; then + start_dockerd +else + echo "Docker engine installed. Start it with: install-dockerd --start" +fi