From 281ccbaa7078640f6562173f37159cf65ce0fd60 Mon Sep 17 00:00:00 2001 From: pi Date: Mon, 13 Jul 2026 19:55:50 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20pi-devbox=20parity=20=E2=80=94=20typst?= =?UTF-8?q?=20PDF,=20terminal=20terminfo,=20nvim=20colour,=20host-ssh=20ch?= =?UTF-8?q?eck,=20hygiene?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ports the base additions from pi-devbox v1.4.0 + v1.5.0 that opencode-devbox lacked (opencode-devbox already tracks pi-devbox for CLI-toolset parity, v2.6.0): - typst PDF engine for pandoc (v1.4.0) + the pandoc typst-template default-font patch (v1.5.0) so 'pandoc --pdf-engine=typst' works without -V mainfont. pandoc shipped since v2.6.0 as a front-end only (no PDF back-end). +xz-utils. Tracks latest; --build-arg TYPST_VERSION escape hatch. - Terminal support (v1.5.0): ncurses-term + kitty-terminfo + a compiled xterm-ghostty alias (tic -x, use=ghostty), so wezterm/alacritty/foot/ghostty/ kitty resolve TERM over SSH instead of degrading to a dumb fallback. - Readable Neovim colours (v1.5.0): system-wide /etc/xdg/nvim/sysinit.vim with termguicolors. - Host SSH reachability check at shell startup (v1.4.0): one-time probe in .bash_aliases warning (with fix steps + inline pubkey) when the Mac host is unreachable. The rest of the LAN stack was already present. - .claude/settings.local.json added to the gitignore_global seed (v1.5.0). - Repo hygiene (v1.5.0): LICENSE (MIT), THIRD_PARTY.md, hadolint CI job (pinned v2.14.0) + .hadolint.yaml, IDEAS.md backlog. Base-affecting (Dockerfile.base + rootfs) → base- advances, base rebuilds. smoke-test gains typst/PDF, terminfo, and nvim-tgc assertions. Validated: hadolint clean on both Dockerfiles, bash -n OK, base-hash guard OK, workflow guard OK. CHANGELOG v2.7.0. --- .gitea/workflows/lint.yml | 24 ++++++ .hadolint.yaml | 27 +++++++ CHANGELOG.md | 76 +++++++++++++++++++ Dockerfile.base | 56 +++++++++++++- IDEAS.md | 60 +++++++++++++++ LICENSE | 21 +++++ THIRD_PARTY.md | 48 ++++++++++++ rootfs/etc/xdg/nvim/sysinit.vim | 18 +++++ rootfs/home/developer/.bash_aliases | 32 ++++++++ rootfs/home/developer/.gitignore_global | 4 + .../local/share/terminfo-src/ghostty.terminfo | 14 ++++ scripts/smoke-test.sh | 10 +++ 12 files changed, 389 insertions(+), 1 deletion(-) create mode 100644 .hadolint.yaml create mode 100644 IDEAS.md create mode 100644 LICENSE create mode 100644 THIRD_PARTY.md create mode 100644 rootfs/etc/xdg/nvim/sysinit.vim create mode 100644 rootfs/usr/local/share/terminfo-src/ghostty.terminfo diff --git a/.gitea/workflows/lint.yml b/.gitea/workflows/lint.yml index d6f2075..5dafa2e 100644 --- a/.gitea/workflows/lint.yml +++ b/.gitea/workflows/lint.yml @@ -66,3 +66,27 @@ jobs: # ("no project was found"). Globbing the workflow files is the # supported way to lint a non-GitHub layout. run: actionlint -color .gitea/workflows/*.yml + + hadolint: + # Lint the two Dockerfiles that ARE the project (the shell/actions linting + # above never looked at them). Config — ignored rules + failure threshold + # — lives in .hadolint.yaml, which hadolint reads automatically, so a local + # `hadolint Dockerfile.base` reproduces CI exactly. + runs-on: ubuntu-latest + container: + image: catthehacker/ubuntu:act-latest + steps: + - uses: actions/checkout@v4 + + - name: Install hadolint (pinned) + env: + HADOLINT_VERSION: 2.14.0 + run: | + curl -fsSL \ + "https://github.com/hadolint/hadolint/releases/download/v${HADOLINT_VERSION}/hadolint-Linux-x86_64" \ + -o /usr/local/bin/hadolint + chmod +x /usr/local/bin/hadolint + hadolint --version + + - name: Run hadolint + run: hadolint Dockerfile.base Dockerfile.variant diff --git a/.hadolint.yaml b/.hadolint.yaml new file mode 100644 index 0000000..4d61183 --- /dev/null +++ b/.hadolint.yaml @@ -0,0 +1,27 @@ +# hadolint configuration for opencode-devbox. +# +# Both Dockerfiles are linted in CI (.gitea/workflows/lint.yml → `hadolint` +# job). hadolint reads this file automatically, so a local +# `hadolint Dockerfile.base` reproduces CI exactly. +# +# The ignores below are DELIBERATE project choices — they mirror the +# philosophy of the shellcheck excludes already applied to `run:` steps +# (SHELLCHECK_OPTS in lint.yml). Anything NOT listed here still fails the +# build at `warning` and above, so new Dockerfile smells are caught going +# forward. +ignored: + - DL3008 # "pin apt versions" — intentionally unpinned: the base tracks + # Debian stable and runs `apt-get upgrade`, so pinning point + # versions would rot and fight security updates. + - DL3016 # "pin npm versions" — opencode / oh-my-opencode-slim ARE pinned, + # but via build-args (CI-resolved from npm), not the npm CLI. + - DL4006 # "set -o pipefail before a pipe" — the piped RUNs are + # download|extract steps with their own retries / `set -e`. + # Switching the global SHELL to bash is a larger, base-affecting + # change — tracked in IDEAS.md. + - DL3003 # "use WORKDIR, not cd" — cosmetic in the few `cd` RUNs here. + - SC2086 # "double-quote to prevent word-splitting" — the same code is + # excluded for shell `run:` steps in lint.yml; splitting is + # intentional in these contexts. + +failure-threshold: warning diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dc08fa..def7a54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,82 @@ Tags follow **independent semver** (since `v2.0.0`) — they version *this image --- +## v2.7.0 — 2026-07-13 + +Minor release. Headline: **further parity with the sibling `pi-devbox` image** — +ports the base additions from pi-devbox v1.4.0 + v1.5.0 that opencode-devbox +lacked. **PDF export now works out of the box** (`typst` as the pandoc PDF +engine — pandoc shipped since v2.6.0 as a front-end only), modern terminal +emulators resolve their `TERM` over SSH (ncurses-term + kitty-terminfo + a +compiled `xterm-ghostty` alias), Neovim renders in readable 24-bit colour by +default, and a first-shell **host SSH reachability check** warns when the Mac +host isn't reachable. Also adds repository hygiene (LICENSE, THIRD_PARTY.md, +hadolint CI, IDEAS backlog) and a `.claude` gitignore-seed pattern. All the +tooling/rootfs changes are base-affecting, so `base-` advances and the +base rebuilds. + +### Added + +- **`typst` — PDF engine for pandoc (Markdown→PDF).** `pandoc` shipped in the + base since v2.6.0 but as a front-end only — with no PDF back-end, + `pandoc -o out.pdf` failed with "xelatex not found". The base now installs + `typst`, a single ~30 MB static Rust binary (no LaTeX), used via + `pandoc --pdf-engine=typst`; a fuller TeX Live remains the higher-fidelity + fallback (install on demand). Also patches pandoc's bundled typst template + (`/usr/share/pandoc/data/templates/template.typst`), whose empty `font: ()` + default otherwise made a naked `--pdf-engine=typst` fail with "font fallback + list must not be empty" — defaulted to `Libertinus Serif` so PDF export works + without `-V mainfont`. Adds `xz-utils` to the apt layer (typst ships a + `.tar.xz`). Tracks `latest`; pin with `--build-arg TYPST_VERSION=vX.Y.Z`. + Ported from pi-devbox v1.4.0 (+ its v1.5.0 font-default fix). Base-affecting. + +- **Terminal support beyond the default: `ncurses-term` + `kitty-terminfo` + a + compiled `xterm-ghostty` alias.** The base previously shipped only + `ncurses-base`, so SSHing in from a modern emulator degraded to a dumb + fallback. Now installs `ncurses-term` (terminfo for WezTerm, Alacritty, foot, + st, the base `ghostty` entry, and many more) and `kitty-terminfo` + (`xterm-kitty`), and compiles an `xterm-ghostty` alias with `tic -x` + (`use=ghostty`) — Ghostty connects as `TERM=xterm-ghostty` and no distro + packages that name. iTerm2's `xterm-256color` is already in ncurses-base. New + `rootfs/usr/local/share/terminfo-src/ghostty.terminfo`; the build asserts the + entry landed via `infocmp`. Ported from pi-devbox v1.5.0. Base-affecting. + +- **Readable Neovim colours out of the box.** New system-wide Neovim config + (`rootfs/etc/xdg/nvim/sysinit.vim`) enables `termguicolors`. Vanilla Neovim + otherwise fell back to a muddy, low-contrast 256-colour palette over ssh. + `sysinit.vim` is Neovim's system vimrc — it loads for every user before any + personal `~/.config/nvim` and can still be overridden per-user + (`:set notermguicolors`). Pairs with `kitty-terminfo` above. Ported from + pi-devbox v1.5.0. Base-affecting. + +- **Host SSH reachability check at shell startup.** `~/.bash_aliases` (baked + into the base) now runs a one-time SSH probe on the first bash session of each + container. If the Mac host is not reachable (Remote Login disabled or the + `devbox_jump` key not yet authorized) it prints a clear warning with the exact + two steps to fix it, including the container's public key inline. Subsequent + shells skip the check (a `/tmp` flag cleared on recreate); silent when SSH + works. Complements the existing `setup-lan-access.sh` key-generation message. + Ported from pi-devbox v1.4.0. Base-affecting. + +- **Seeded global gitignore now ignores `**/.claude/settings.local.json`.** + Claude Code's per-machine local settings file holds machine-specific + permissions and can carry credentials, so it should never be committed. The + seed (`rootfs/home/developer/.gitignore_global`, baked to `/etc/skel-devbox/`) + gains the pattern. Existing containers are unaffected (the seed is copied only + when `~/.gitignore_global` is absent). Base-affecting (rootfs COPY). + +- **Repository hygiene: `LICENSE`, `THIRD_PARTY.md`, `hadolint` CI, `IDEAS.md`.** + The repo declared MIT only in prose; it now ships an actual `LICENSE` file + (MIT, © Joakim Persson) plus `THIRD_PARTY.md` recording that the published + images bundle third-party software under its own terms (opencode, OMOS, + mempalace — MIT; gosu/uv/typst Apache-2.0; Debian packages under their own + licenses). A new `hadolint` job (pinned v2.14.0) in the lint workflow lints + `Dockerfile.base` and `Dockerfile.variant`; `.hadolint.yaml` grandfathers the + deliberate choices (unpinned apt/npm, `cd`-in-`RUN`, `SC2086` — mirroring the + existing shellcheck excludes) and fails on anything new at `warning`+. + `IDEAS.md` parks vetted-but-unscheduled follow-ups. Repo/CI only — not baked + into the image. + ## v2.6.0 — 2026-07-08 Minor release. Headline: **CLI-toolset parity with the sibling `pi-devbox` diff --git a/Dockerfile.base b/Dockerfile.base index b3cfa07..a7d7246 100644 --- a/Dockerfile.base +++ b/Dockerfile.base @@ -15,7 +15,7 @@ # content-addressed over this file, so any byte change invalidates the # cache. Recommended cadence: once per release for security updates. # -# BASE_REBUILD_DATE: 2026-05-14 (v1.14.50b — fresh apt + first promote-base-latest) +# BASE_REBUILD_DATE: 2026-07-13 (v2.7.0 — typst PDF engine + terminal terminfo (ncurses-term/kitty/xterm-ghostty) + nvim true-colour; pi-devbox parity) # # See the project README's "Build pipeline" section for the rationale. @@ -74,7 +74,10 @@ RUN apt-get update && \ python3-venv \ nano \ pandoc \ + xz-utils \ graphviz \ + kitty-terminfo \ + ncurses-term \ && ln -s /usr/bin/fdfind /usr/local/bin/fd \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* @@ -314,6 +317,40 @@ RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" chmod +x /usr/local/bin/tldr && \ tldr --version +# ── typst — lightweight PDF engine for pandoc (Markdown→PDF) ───────── +# pandoc (apt-installed above) is only a front-end; rendering PDF needs a +# back-end engine. Rather than a ~600 MB TeX Live install, we ship typst: +# a single ~30 MB static Rust binary with no LaTeX dependency, used via +# `pandoc --pdf-engine=typst`. A fuller TeX Live remains the higher-fidelity +# fallback for anyone who needs LaTeX-exact output (install on demand). +# Ported from pi-devbox (its v1.4.0 + v1.5.0 font-default fix). +# +# Follows the `latest` GitHub-release convention (like tealdeer/uv/bat). +# typst ships a `.tar.xz` asset (hence xz-utils in the apt layer above) +# that extracts to typst--unknown-linux-musl/typst. Pin a specific +# tag with --build-arg TYPST_VERSION=vX.Y.Z. +# +# We also patch pandoc's bundled typst template +# (/usr/share/pandoc/data/templates/template.typst): its conf() defaults the +# document font to an empty tuple (`font: ()`), so a naked +# `pandoc --pdf-engine=typst` fails with "font fallback list must not be empty" +# unless the caller passes `-V mainfont=...`. We default it to Libertinus Serif +# (typst's own bundled default font) so PDF export works out-of-the-box. +ARG TYPST_VERSION=latest +RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \ + V="${TYPST_VERSION}" && \ + if [ "$V" = "latest" ]; then \ + V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/typst/typst/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \ + fi && \ + V="${V#v}" && [ -n "$V" ] && \ + echo "Installing typst ${V}" && \ + curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/typst/typst/releases/download/v${V}/typst-${ARCH}-unknown-linux-musl.tar.xz" | tar -xJ -C /tmp && \ + install /tmp/typst-${ARCH}-unknown-linux-musl/typst /usr/local/bin/typst && \ + rm -rf /tmp/typst-${ARCH}-unknown-linux-musl && \ + typst --version && \ + sed -i 's/^ font: (),$/ font: ("Libertinus Serif",),/' /usr/share/pandoc/data/templates/template.typst && \ + grep -q 'font: ("Libertinus Serif",),' /usr/share/pandoc/data/templates/template.typst + # ── MemPalace — local-first AI memory system ───────────────────────── # Provides semantic search over conversation history via 29 MCP tools. # Always installed in the base (variant-independent). Set @@ -494,6 +531,23 @@ COPY rootfs/home/developer/.bash_aliases /etc/skel-devbox/.bash_aliases COPY rootfs/home/developer/.inputrc /etc/skel-devbox/.inputrc COPY rootfs/home/developer/.gitignore_global /etc/skel-devbox/.gitignore_global +# ── Editor defaults: system-wide Neovim true-colour ────────────────── +# /etc/xdg/nvim/sysinit.vim is Neovim's system vimrc: it loads for every user +# (before any personal ~/.config/nvim) and can still be overridden per-user. +# Enables termguicolors so the default theme renders in 24-bit colour instead +# of a muddy 256-colour fallback. Pairs with kitty-terminfo (installed above). +COPY rootfs/etc/xdg/nvim/sysinit.vim /etc/xdg/nvim/sysinit.vim + +# ── Terminal support: xterm-ghostty terminfo alias ────────────────── +# ncurses-term (installed above) covers wezterm/alacritty/foot/st and the base +# `ghostty` entry, but Ghostty connects with TERM=xterm-ghostty, for which no +# distro packages an entry. Ship a thin alias (use=ghostty) and compile it into +# the system terminfo db with `tic -x`, so it inherits the maintained ghostty +# capability set. The `infocmp` check fails the build if the entry didn't land. +COPY rootfs/usr/local/share/terminfo-src/ghostty.terminfo /usr/local/share/terminfo-src/ghostty.terminfo +RUN tic -x -o /usr/share/terminfo /usr/local/share/terminfo-src/ghostty.terminfo && \ + infocmp -x xterm-ghostty >/dev/null + # ── Entrypoint ──────────────────────────────────────────────────────── COPY rootfs/usr/local/lib/opencode-devbox/ /usr/local/lib/opencode-devbox/ COPY rootfs/usr/local/bin/dot-watch /usr/local/bin/dot-watch diff --git a/IDEAS.md b/IDEAS.md new file mode 100644 index 0000000..7036fdc --- /dev/null +++ b/IDEAS.md @@ -0,0 +1,60 @@ +# Ideas & backlog + +A living list of potential improvements for opencode-devbox that are **not yet +scheduled**. This is intentionally lightweight — a place to park ideas so they +aren't lost between sessions. When an item ships, describe it in +[`CHANGELOG.md`](CHANGELOG.md) and remove it from here. + +Rough effort tags: 🟢 small · 🟡 medium · 🔴 large. Status: `idea` (unvetted) · +`planned` (agreed, not started). + +--- + +## Supply-chain hardening + +- 🟡 `planned` — **Pin CI actions to commit SHAs.** The workflows use floating + major tags (`actions/checkout@v4`, `docker/build-push-action@v7`, + `docker/setup-buildx-action@v4`, `docker/login-action@v3`, + `docker/setup-qemu-action@v3`). This is inconsistent with the project's own + philosophy of SHA-pinning *content* refs to defeat floating refs. Pin each + action to a SHA with a trailing `# vX.Y.Z` comment. Pairs naturally with the + renovate item below to keep the pins fresh. + +- 🟡 `planned` — **Vulnerability scanning in CI.** No CVE scan runs on the + published images today. Add a `trivy image` (or grype) job to + `docker-publish-split.yml` after `smoke`. Start non-blocking (report only), + then tighten to fail on `HIGH`/`CRITICAL` with an available fix. + +- 🟢🟡 `planned` — **Standardize build provenance → buildx SBOM + attestations.** + The image already carries hand-rolled provenance (OCI labels + + `build-manifest`). `docker/build-push-action` can emit a standard SBOM and + SLSA provenance attestation nearly for free (`provenance: mode=max`, + `sbom: true`). Makes provenance machine-consumable and pairs well with the + trivy item (scan the SBOM). + +## Dockerfile hardening + +- 🟡 `idea` — **Address hadolint DL4006 properly.** Currently ignored in + `.hadolint.yaml`. The clean fix is `SHELL ["/bin/bash", "-o", "pipefail", + "-c"]` so piped `RUN`s fail on the first non-zero stage. This changes the + default `RUN` shell from `sh` to `bash` for all subsequent layers, so it is + base-affecting and needs a careful pass over existing `RUN`s before removing + the ignore. + +## Developer experience + +- 🟢 `idea` — **`Makefile`/`justfile` for local iteration.** Reproducing a CI + build locally means hand-assembling many `--build-arg`s. Thin targets + (`make build-base`, `make build-variant`, `make smoke`, `make lint`) would + make local testing painless and document the canonical invocations. + +- 🟡 `idea` — **Dependency-update automation (renovate).** With CI actions + SHA-pinned (above), a `renovate.json` keeps those pins — plus the pinned tool + versions (`ACTIONLINT_VERSION`, `HADOLINT_VERSION`, gosu, etc.) — current via + automated PRs. Requires a renovate runner against the Gitea instance. + +## Housekeeping + +- 🟢 `idea` — **Registry retention for `base-` tags.** The base-hash + caching scheme accumulates `base-` tags over time. Confirm whether the + registry prunes old ones, and add a retention/cleanup step if not. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7e3c6fb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Joakim Persson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/THIRD_PARTY.md b/THIRD_PARTY.md new file mode 100644 index 0000000..23a5b53 --- /dev/null +++ b/THIRD_PARTY.md @@ -0,0 +1,48 @@ +# Third-party notices + +opencode-devbox is distributed under the MIT License (see [`LICENSE`](LICENSE)), +which covers **this repository's own contents** — the Dockerfiles, entrypoint +scripts, `rootfs/` seeds, CI workflows, and docs. + +The **published container images** (`joakimp/opencode-devbox:*`) additionally +*bundle* third-party software, each of which remains under its own license. This +file is a good-faith summary; the authoritative sources are the upstream +projects and, for OS packages, the per-package copyright files inside the image +at `/usr/share/doc//copyright`. + +## opencode and agent components (installed in the variant layer) + +| Component | Upstream | License | +| --- | --- | --- | +| opencode | github.com/sst/opencode | MIT | +| oh-my-opencode-slim (OMOS) *(`-omos` variant only)* | npm | MIT | +| mempalace / mempalace-toolkit | authored by the maintainer (Joakim Persson) | MIT | +| gitea-mcp | gitea.com/gitea/gitea-mcp | MIT | + +## Tooling baked into the base image + +| Component | Upstream | License (best effort) | +| --- | --- | --- | +| gosu | github.com/tianon/gosu | Apache-2.0 | +| Node.js | nodejs.org | MIT (bundles components under their own licenses) | +| uv | github.com/astral-sh/uv | Apache-2.0 OR MIT | +| Neovim | neovim.io | Apache-2.0 + Vim license | +| micro | github.com/zyedidia/micro | MIT | +| Pandoc | pandoc.org | GPL-2.0-or-later | +| Typst | github.com/typst/typst | Apache-2.0 | +| Graphviz | graphviz.org | CPL-1.0 | +| ripgrep / fd / bat / eza / zoxide / tealdeer / yq (mikefarah) | respective repos | MIT / Apache-2.0 / Unlicense (varies) | +| bun *(`-omos` variant only)* | github.com/oven-sh/bun | MIT | + +## Base OS + +The image is built `FROM` a Debian base and installs packages via `apt`. Debian +and its packages are distributed under their respective licenses (GPL, LGPL, +MIT, BSD, and others). See each package's copyright file in the image under +`/usr/share/doc//copyright`. + +--- + +*Licenses marked "best effort" are widely known but were not each verified at +the exact bundled version; consult the upstream project for authoritative +terms. Corrections welcome.* diff --git a/rootfs/etc/xdg/nvim/sysinit.vim b/rootfs/etc/xdg/nvim/sysinit.vim new file mode 100644 index 0000000..465fe6b --- /dev/null +++ b/rootfs/etc/xdg/nvim/sysinit.vim @@ -0,0 +1,18 @@ +" opencode-devbox — system-wide Neovim defaults. +" +" This is Neovim's *system vimrc*: it loads for every user before any personal +" ~/.config/nvim, and personal configs can still override it. +" +" Enable 24-bit ("true") colour. Without it, Neovim's default theme is squeezed +" into a 256-colour palette where strings/comments become a muddy, low-contrast +" dark colour — a common complaint over ssh/kitty where COLORTERM often isn't +" propagated into the container. Modern terminals (kitty, WezTerm, iTerm2, +" Alacritty, ...) all support true colour; the bundled kitty-terminfo also lets +" Neovim auto-detect it, but forcing it here guarantees readable colour +" regardless of how the terminal type / COLORTERM reach the container. +" +" Opt out for a session: :set notermguicolors +" Override permanently: set your own value in ~/.config/nvim/init.lua +if has('termguicolors') + set termguicolors +endif diff --git a/rootfs/home/developer/.bash_aliases b/rootfs/home/developer/.bash_aliases index c11ab13..dbe8cbd 100644 --- a/rootfs/home/developer/.bash_aliases +++ b/rootfs/home/developer/.bash_aliases @@ -54,6 +54,38 @@ alias gs='git status' alias gd='git diff' alias gl='git log --oneline --graph --decorate -20' +# ── Host SSH reachability check (once per container lifetime) ───────────── +# Warns at first shell startup if the Mac host is not reachable via SSH. +# Only runs inside a container, only if the jump key exists, and only once +# per container lifetime (/tmp flag is cleared on recreate). +_devbox_check_host_ssh() { + [ -f "/.dockerenv" ] || return 0 + local ssh_cfg="$HOME/.ssh-local/config" + [ -f "$ssh_cfg" ] || return 0 + local key_pub="$HOME/.ssh-local/devbox_jump_ed25519.pub" + [ -f "$key_pub" ] || return 0 + local flag="/tmp/.devbox_host_ssh_ok" + [ -f "$flag" ] && return 0 + if ssh -F "$ssh_cfg" \ + -o BatchMode=yes \ + -o ConnectTimeout=2 \ + -o StrictHostKeyChecking=accept-new \ + mac true 2>/dev/null; then + touch "$flag" + return 0 + fi + local pub_key + pub_key=$(cat "$key_pub") + printf '\n\033[1;33m⚠ devbox: Mac host not reachable via SSH\033[0m\n' + printf ' Some tools use SSH to run commands on the Mac host.\n' + printf ' Fix (run both on the Mac):\n\n' + printf ' \033[1mStep 1\033[0m System Settings → General → Sharing → Remote Login → ON\n\n' + printf ' \033[1mStep 2\033[0m echo '"'"'%s'"'"' >> ~/.ssh/authorized_keys\n' "$pub_key" + printf '\n Then open a new shell in the container to verify.\n\n' +} +_devbox_check_host_ssh +unset -f _devbox_check_host_ssh + # ── LAN access via the host (dssh) ─────────────────────────────────── # When running on a VM-backed host (macOS OrbStack / Docker Desktop), the # entrypoint's setup-lan-access.sh generates ~/.ssh-local/config so the host diff --git a/rootfs/home/developer/.gitignore_global b/rootfs/home/developer/.gitignore_global index c35a754..78426ce 100644 --- a/rootfs/home/developer/.gitignore_global +++ b/rootfs/home/developer/.gitignore_global @@ -9,3 +9,7 @@ *.orig *.swp *.tmp + +# Claude Code per-machine local settings: holds machine-specific permissions +# and can carry credentials — must never be committed. +**/.claude/settings.local.json diff --git a/rootfs/usr/local/share/terminfo-src/ghostty.terminfo b/rootfs/usr/local/share/terminfo-src/ghostty.terminfo new file mode 100644 index 0000000..2f55f43 --- /dev/null +++ b/rootfs/usr/local/share/terminfo-src/ghostty.terminfo @@ -0,0 +1,14 @@ +# xterm-ghostty — alias of the maintained ncurses `ghostty` terminfo entry. +# +# Ghostty sets TERM=xterm-ghostty by default, but the ncurses terminfo +# database (Debian: ncurses-term) ships the entry under the name `ghostty` +# only — there is no `xterm-ghostty` alias, and no distro packages one. This +# thin alias makes xterm-ghostty resolve to the same upstream-maintained +# capability set, so SSH sessions from a Ghostty terminal work without +# vendoring Ghostty's full (Zig-generated) terminfo here. +# +# `use=ghostty` is resolved by `tic` at compile time against the base +# `ghostty` entry from ncurses-term (installed in Dockerfile.base before the +# compile step). Compiled with `tic -x`. +xterm-ghostty|Ghostty terminal emulator (xterm-ghostty alias), + use=ghostty, diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index 6bd856c..5574c48 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -8,6 +8,10 @@ # - Entrypoint runs cleanly as non-root after UID adjustment # - Generated opencode.json has the expected shape # - MCP wrapper works (when mempalace is installed) +# - typst PDF engine for pandoc — `pandoc --pdf-engine=typst` +# - terminfo for modern emulators: xterm-kitty, xterm-ghostty, wezterm, +# alacritty, foot (kitty-terminfo + ncurses-term + compiled ghostty alias) +# - Neovim true-colour default (sysinit.vim termguicolors) # # Usage: ./scripts/smoke-test.sh [--variant base|omos] # @@ -119,6 +123,10 @@ run "node" "node --version" run "npm" "npm --version" run "git" "git --version" run "nvim" "nvim --version | head -1" +run "nvim true-colour default (sysinit.vim)" "nvim --headless -c 'lua os.exit(vim.o.termguicolors and 0 or 1)'" +run "kitty-terminfo" "infocmp -x xterm-kitty >/dev/null 2>&1" +run "terminfo: modern emulators (ncurses-term)" 'for t in wezterm alacritty foot ghostty st-256color; do infocmp -x "$t" >/dev/null 2>&1 || exit 1; done' +run "terminfo: xterm-ghostty alias (tic)" "infocmp -x xterm-ghostty >/dev/null 2>&1" run "nano" "nano --version | head -1" run "micro" "micro --version" run "bat" "bat --version" @@ -139,6 +147,8 @@ run "gitea-mcp" "gitea-mcp --version" run "gosu" "gosu --version" run "tmux" "tmux -V" run "pandoc" "pandoc --version | head -1" +run "typst" "typst --version" +run "pandoc+typst PDF engine" "printf '# hi\n' | pandoc --pdf-engine=typst -o /tmp/_smoke.pdf - && test -s /tmp/_smoke.pdf; rm -f /tmp/_smoke.pdf" run "graphviz (dot)" "dot -V" run "tldr (tealdeer)" "tldr --version" run "dot-watch" "test -x /usr/local/bin/dot-watch && bash -n /usr/local/bin/dot-watch && echo ok"