From fcab8f23ce8693a316c4e43c9c58740e5e42b304 Mon Sep 17 00:00:00 2001 From: virskl Date: Tue, 25 Aug 2026 13:07:43 +0200 Subject: [PATCH] Move the login volumes to the shared feature, and add gh's Builds on #76, which persisted the Codex login by declaring its volume in each committed variant. Two things were left. gh was not covered, and has the same problem for the same reason: its configuration lives in the container's home, so every rebuild costs a fresh `gh auth login` - which is noticed one push later rather than at the rebuild. And the volume was declared twice, in linux/ and in wslg/, which is one place too many for a mount both variants want. It belongs where ~/.platformio already is: the shared feature, which every variant loads. That also covers a variant nobody had thought about - wslg had the same gap and was fixed by the same line. So the two per-variant declarations come out again; leaving them in would put two mounts on one target, which fails container creation rather than being ignored. The Dockerfile seeds ~/.config/gh beside ~/.codex, at 0700 for both: an empty named volume takes its ownership from the path it covers, and gh refuses to read a config directory others can enter. The README's two passages became one. #76 put its section high up, where somebody looking for it will find it, and that is where this stays - carrying both volumes now, the reason they are in the feature, and the seeding. Further down, the sentence #76 struck out is replaced rather than left as a gap: what must stay out of a committed variant is a secret or an internal address, and a mount line is neither. Not verified by a build. There is no Docker daemon in the container, so what was checked is that the JSON parses and that no variant now collides with the feature on a mount target. The test is two rebuilds of the Linux variant: the first creates the volumes, the second is the one that shows a login survived. Co-Authored-By: Claude Opus 5 (1M context) --- .devcontainer/Dockerfile | 10 +++--- .devcontainer/README.md | 34 ++++++++++++++----- .devcontainer/linux/devcontainer.json | 1 - .../shared/devcontainer-feature.json | 10 ++++++ .devcontainer/wslg/devcontainer.json | 3 +- 5 files changed, 43 insertions(+), 15 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index e35cab3f..85b649b5 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -129,10 +129,12 @@ RUN python3 -m venv /opt/playwright && \ # cannot write the ~1.5 GB of toolchain into it. RUN install -d -o vscode -g vscode -m 0755 /home/vscode/.platformio -# Codex keeps its login and local configuration here. The devcontainer variants mount a -# named volume over this path so those files survive a rebuild. Seed its ownership from -# the image; otherwise a newly created Docker/Podman volume is owned by root. -RUN install -d -o vscode -g vscode -m 0700 /home/vscode/.codex +# The same arrangement for the two logins a rebuild would otherwise throw away: Codex, whose +# extension the shared feature installs into every variant, and gh, which every push and +# every pull request goes through. Mode 0700 rather than 0755, because what these hold is a +# credential and not a cache - and gh refuses to read a config directory others can enter. +RUN install -d -o vscode -g vscode -m 0700 /home/vscode/.codex && \ + install -d -o vscode -g vscode -m 0700 /home/vscode/.config/gh # GitHub's SSH host key, so that the first git operation in a fresh container verifies # against something rather than failing with "Host key verification failed" - which is diff --git a/.devcontainer/README.md b/.devcontainer/README.md index e167e328..51e15c24 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -11,12 +11,22 @@ Choose the configuration matching the host. Keeping the WSLg mount in its own configuration prevents native Linux container startup from failing when `/mnt/wslg` does not exist. -## Persistent Codex login +## Logins that survive a rebuild -Both configurations mount the named `wordclock-codex` volume at -`/home/vscode/.codex`. This preserves the Codex login and local configuration -when the development container is rebuilt. The volume contains credentials; do -not share, export or remove it unless the saved login should be discarded. +Two named volumes carry a login across a rebuild: `wordclock-codex` at `/home/vscode/.codex` +and `wordclock-gh` at `/home/vscode/.config/gh`. Without them both have to be entered again +by hand every time the container is built. + +They are declared by the **shared feature**, so every variant gets them, because both tools +belong to every variant: the Codex extension is in the shared extension list, and every push +and pull request goes through `gh`. The `Dockerfile` creates both directories as the remote +user at mode 0700 first — an empty named volume takes its ownership from the path it covers, +so a path that does not exist in the image yields a volume owned by root that the tool cannot +write. 0700 rather than the package cache's 0755, because a credential is not a cache, and +`gh` refuses to read a config directory that others can enter. + +Both volumes hold credentials: do not share, export or remove one unless the saved login +should be discarded. ## SSH agent on Linux @@ -192,9 +202,17 @@ be committed (internal base images, registry-hosted features, credentials). The Add such a directory to `.gitignore` to keep it local; `/.devcontainer/vector/` is already listed there. -Persisting another tool's login across rebuilds takes a named volume plus one -`chown`, because a volume whose target does not exist in the image is created -owned by root: +What must stay local is a **secret or an internal address** — a registry-hosted feature, an +internal base image, a token written into the file. A *mount* that persists a login is +neither: the line names a volume, the volume lives on the machine that created it, and +nothing about it reaches the repository. That is why the two login volumes above are in the +committed shared feature, where every variant gets them, rather than in a variant somebody +has to build for themselves. + +Persisting a further tool's login is therefore one mount beside them and one line in the +`Dockerfile`, the way `~/.codex` and `~/.config/gh` are done. In a variant that cannot +change the image, the ownership is fixed after creation instead, because a volume whose +target does not exist in the image is created owned by root: ```jsonc "mounts": [ diff --git a/.devcontainer/linux/devcontainer.json b/.devcontainer/linux/devcontainer.json index de1cd8a8..e5f41653 100644 --- a/.devcontainer/linux/devcontainer.json +++ b/.devcontainer/linux/devcontainer.json @@ -27,7 +27,6 @@ ], "mounts": [ "source=/tmp/.X11-unix,target=/tmp/.X11-unix,type=bind", - "source=wordclock-codex,target=/home/vscode/.codex,type=volume", "source=${localEnv:SSH_AUTH_SOCK},target=/tmp/ssh-agent,type=bind" ] } diff --git a/.devcontainer/shared/devcontainer-feature.json b/.devcontainer/shared/devcontainer-feature.json index f06872f5..5b4b563f 100644 --- a/.devcontainer/shared/devcontainer-feature.json +++ b/.devcontainer/shared/devcontainer-feature.json @@ -36,6 +36,16 @@ "source": "wordclock-platformio", "target": "/home/vscode/.platformio", "type": "volume" + }, + { + "source": "wordclock-codex", + "target": "/home/vscode/.codex", + "type": "volume" + }, + { + "source": "wordclock-gh", + "target": "/home/vscode/.config/gh", + "type": "volume" } ], "postStartCommand": "platformio-trust-store" diff --git a/.devcontainer/wslg/devcontainer.json b/.devcontainer/wslg/devcontainer.json index af87acec..13107b1e 100644 --- a/.devcontainer/wslg/devcontainer.json +++ b/.devcontainer/wslg/devcontainer.json @@ -17,7 +17,6 @@ }, "mounts": [ "source=/tmp/.X11-unix,target=/tmp/.X11-unix,type=bind", - "source=/mnt/wslg,target=/mnt/wslg,type=bind", - "source=wordclock-codex,target=/home/vscode/.codex,type=volume" + "source=/mnt/wslg,target=/mnt/wslg,type=bind" ] }