Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 26 additions & 8 deletions .devcontainer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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": [
Expand Down
1 change: 0 additions & 1 deletion .devcontainer/linux/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
10 changes: 10 additions & 0 deletions .devcontainer/shared/devcontainer-feature.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 1 addition & 2 deletions .devcontainer/wslg/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}