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
6 changes: 6 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,12 @@ jobs:
-v "$PWD/scripts:/workspace/scripts:ro" \
squarebox:test bash -c 'scripts/e2e-test.sh update'

- name: "Section 10: dotfile refresh (#89)"
run: |
docker run --rm \
-v "$PWD/scripts:/workspace/scripts:ro" \
squarebox:test bash -c 'scripts/e2e-test.sh dotfiles'

# ── Dev Container ──────────────────────────────────────────────────────
devcontainer:
needs: build-amd64
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ docker start -ai squarebox

The `squarebox-home` named volume holds per-user state. Bind mounts at sub-paths inside `/home/dev` (`.bashrc`, `.config/starship.toml`, `.config/lazygit`, `.config/git`) override the volume so repo-managed files stay in lockstep with the image.

**Pull/compose path (no bind mounts):** the `docker-compose.yml` / GHCR install mounts only `squarebox-home:/home/dev`, so the named volume would otherwise shadow the image-baked dotfiles and they'd go stale on upgrade (issue #89). To prevent this, the managed dotfiles also ship to a non-volume path (`/usr/local/lib/squarebox/dotfiles/`), and `squarebox-entrypoint` runs `refresh-dotfiles.sh` on every start to re-seed `.bashrc`/`starship.toml` from there over the volume copy. The refresh skips any path the operator bind-mounted (the desktop path above), so it never clobbers host-managed files.

Replace `docker` with `podman` above if using Podman. The `install.sh` script auto-detects the runtime; override with `SQUAREBOX_RUNTIME=docker|podman`.

The `install.sh` script automates initial setup (clone, build, create container, add `sqrbx` shell alias). A `.devcontainer/devcontainer.json` is also provided for VS Code Dev Containers / Codespaces.
Expand Down
15 changes: 14 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,21 @@ COPY scripts/squarebox-setup.sh /usr/local/bin/sqrbx-setup
COPY scripts/sqrbx-learn /usr/local/bin/sqrbx-learn
COPY scripts/squarebox-help.sh /usr/local/bin/sqrbx-help
COPY scripts/squarebox-entrypoint.sh /usr/local/bin/squarebox-entrypoint
COPY scripts/squarebox-refresh-dotfiles.sh /usr/local/lib/squarebox/refresh-dotfiles.sh
COPY scripts/lib/tools.yaml /usr/local/lib/squarebox/tools.yaml
COPY scripts/lib/tool-lib.sh /usr/local/lib/squarebox/tool-lib.sh

# Image-managed dotfiles also live under a non-volume path so the entrypoint can
# refresh them into the squarebox-home volume on every start. Without this the
# volume shadows the /home/dev image layer and dotfile updates never reach
# upgraded containers (issue #89). The /home/dev copies below still seed a fresh
# volume; these are the source of truth the refresh re-applies thereafter.
COPY dotfiles/bashrc /usr/local/lib/squarebox/dotfiles/bashrc
COPY starship.toml /usr/local/lib/squarebox/dotfiles/starship.toml

RUN chmod +x /usr/local/lib/squarebox/setup.sh \
/usr/local/lib/squarebox/motd.sh \
/usr/local/lib/squarebox/refresh-dotfiles.sh \
/usr/local/bin/sqrbx-update \
/usr/local/bin/sqrbx-setup \
/usr/local/bin/sqrbx-learn \
Expand All @@ -168,7 +179,9 @@ ENV PGID=1000
# The .bashrc lives in dotfiles/ on the host so install.sh can bind-mount it
# into the container — keeping it in sync with the repo while shell history
# and per-user state stay in the squarebox-home named volume. The COPY here
# is what seeds a fresh volume; subsequent runs see the bind-mounted version.
# seeds a fresh volume; the desktop install path then bind-mounts the host copy,
# and the pull/compose path keeps it current via the entrypoint dotfile refresh
# (issue #89) since there is no bind-mount in that path.

COPY --chown=dev:dev dotfiles/bashrc /home/dev/.bashrc

Expand Down
9 changes: 5 additions & 4 deletions dotfiles/bashrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# squarebox /home/dev/.bashrc — managed by the repo, bind-mounted read-only
# from $INSTALL_DIR/dotfiles/bashrc by install.sh / install.ps1. Edits to this
# file inside the container will not persist across rebuilds; edit the copy in
# ~/squarebox/dotfiles/bashrc on the host instead.
# squarebox /home/dev/.bashrc — managed by the repo. On the desktop install path
# it is bind-mounted read-only from $INSTALL_DIR/dotfiles/bashrc by install.sh /
# install.ps1; on the pull/compose path the entrypoint re-seeds it from the image
# on every start (refresh-dotfiles.sh, issue #89). Either way, edits made inside
# the container do not persist — edit ~/squarebox/dotfiles/bashrc on the host.

# Bail out if not running interactively.
case $- in
Expand Down
39 changes: 38 additions & 1 deletion scripts/e2e-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -376,11 +376,46 @@ suite_setup_rerun() {
run_test "9.6 setup.sh --rerun parses cleanly" bash -c '/usr/local/lib/squarebox/setup.sh --rerun git </dev/null'
}

# ── Suite: dotfiles ──────────────────────────────────────────────────────
# Covers: entrypoint dotfile refresh that defeats the named-volume shadow (#89)

suite_dotfiles() {
local src="/usr/local/lib/squarebox/dotfiles/bashrc"
local refresh="/usr/local/lib/squarebox/refresh-dotfiles.sh"

# 10.1 the non-volume managed source ships in the image
run_test "10.1 managed bashrc source present" test -f "$src"
run_test "10.2 refresh-dotfiles.sh installed and executable" test -x "$refresh"

# 10.3 the live (volume) bashrc matches the image source — proves the
# start-time refresh already ran and the volume copy is not stale
run_test "10.3 live ~/.bashrc matches image source" cmp -s "$HOME/.bashrc" "$src"

# 10.4 staling the volume copy and re-running the refresh restores it.
# This is the actual #89 regression: an upgraded volume holds an old bashrc.
TEST_NUM=$((TEST_NUM + 1))
if cp -f "$HOME/.bashrc" /tmp/bashrc.e2e.bak 2>/dev/null \
&& printf '\n# __e2e_stale_marker__\n' >> "$HOME/.bashrc" \
&& "$refresh" \
&& ! grep -q '__e2e_stale_marker__' "$HOME/.bashrc" \
&& cmp -s "$HOME/.bashrc" "$src"; then
PASS_COUNT=$((PASS_COUNT + 1))
echo "ok ${TEST_NUM} - 10.4 refresh restores a staled bashrc"
else
FAIL_COUNT=$((FAIL_COUNT + 1))
echo "not ok ${TEST_NUM} - 10.4 refresh restores a staled bashrc"
cp -f /tmp/bashrc.e2e.bak "$HOME/.bashrc" 2>/dev/null || true
fi

# 10.5 the refresh never exits non-zero (must not be able to abort boot)
run_test "10.5 refresh-dotfiles.sh exits 0" "$refresh"
}

# ── Main ─────────────────────────────────────────────────────────────────

usage() {
echo "Usage: $0 <suite|all>"
echo "Suites: tools, shell, setup, setup-editors, update, devcontainer, setup-rerun"
echo "Suites: tools, shell, setup, setup-editors, update, devcontainer, setup-rerun, dotfiles"
exit 1
}

Expand All @@ -398,13 +433,15 @@ main() {
update) suite_update ;;
devcontainer) suite_devcontainer ;;
setup-rerun) suite_setup_rerun ;;
dotfiles) suite_dotfiles ;;
all)
suite_tools
suite_shell
suite_setup
suite_update
suite_devcontainer
suite_setup_rerun
suite_dotfiles
;;
*) usage ;;
esac
Expand Down
9 changes: 8 additions & 1 deletion scripts/squarebox-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,17 @@ if [ "$(id -u)" = "0" ]; then
chown "$PUID:$PGID" /workspace 2>/dev/null || true
fi

# Re-seed image-managed dotfiles over the (volume-shadowed) home so image
# updates reach upgraded containers — issue #89. Done as root, before the
# privilege drop, so refreshed files can be chowned to the resolved dev user.
/usr/local/lib/squarebox/refresh-dotfiles.sh "$PUID:$PGID" || true

# Drop to dev. --init-groups picks up dev's supplementary groups; numeric
# ids resolve back to the (now-remapped) dev passwd entry.
exec setpriv --reuid "$PUID" --regid "$PGID" --init-groups -- "$@"
fi

# Already unprivileged (rootless Podman, or --user override): run as-is.
# Already unprivileged (rootless Podman, or --user override): run as-is, but
# still refresh managed dotfiles (owned by the running user; no chown needed).
/usr/local/lib/squarebox/refresh-dotfiles.sh || true
exec "$@"
56 changes: 56 additions & 0 deletions scripts/squarebox-refresh-dotfiles.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# squarebox-refresh-dotfiles — re-seed image-managed dotfiles into /home/dev.
#
# Why: /home/dev is the persisted `squarebox-home` named volume. Docker seeds a
# volume from the image only when the volume is first created, so the dotfiles
# baked into /home/dev (.bashrc, starship.toml) are shadowed by the volume on
# every later start. An upgraded container therefore keeps whatever dotfile
# shipped when its volume was born — image updates never reach it (issue #89).
#
# Fix: the managed copies also live under /usr/local/lib/squarebox/dotfiles/ — a
# plain image path the volume cannot shadow. The entrypoint runs this on every
# start to copy the current managed dotfile back over the (possibly stale) volume
# copy, so dotfile changes ship with the image like every other tool.
#
# Skips any target the operator bind-mounted (the desktop install.sh path mounts
# dotfiles/bashrc and starship.toml from the host repo, often read-only — the
# host owns those and keeps them in sync itself).
#
# Arg 1 (optional): uid:gid to chown refreshed files to. The entrypoint passes
# the resolved PUID:PGID when it runs this as root before dropping privileges;
# omit it when running unprivileged and the copying user owns the result.
#
# Deliberately NOT `set -e`: refreshing a convenience dotfile must never abort
# container boot. Every step is best-effort and the script always exits 0.
set -uo pipefail

owner="${1:-}"

# src:dest pairs — src is the non-volume image copy, dest is the volume path.
pairs=(
"/usr/local/lib/squarebox/dotfiles/bashrc:/home/dev/.bashrc"
"/usr/local/lib/squarebox/dotfiles/starship.toml:/home/dev/.config/starship.toml"
)

for pair in "${pairs[@]}"; do
src="${pair%%:*}"
dest="${pair#*:}"

[ -f "$src" ] || continue

# Operator bind-mounted this path — host-managed, leave it alone.
if mountpoint -q -- "$dest" 2>/dev/null; then
continue
fi

# Already current — don't churn the mtime on every start.
if cmp -s "$src" "$dest" 2>/dev/null; then
continue
fi

mkdir -p "$(dirname "$dest")" 2>/dev/null
cp -f "$src" "$dest" 2>/dev/null || continue
[ -n "$owner" ] && chown "$owner" "$dest" 2>/dev/null
done

exit 0
9 changes: 9 additions & 0 deletions uat-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@
- [ ] After rebuild: `/workspace/.squarebox/` selections reused (no re-prompts)
- [ ] After rebuild: GH CLI stays authenticated

## Pull/Compose Upgrade (cross-version, #89)
> Real cross-version test: start a container on an **older** image so the
> `squarebox-home` volume is seeded with that image's dotfiles, then upgrade the
> image and restart. The e2e `dotfiles` suite only simulates this within one
> image; this verifies it across an actual version bump.
- [ ] Start old image → stop → `docker compose pull` newer image → up: `~/.bashrc` matches the new image (entrypoint refresh defeated the volume shadow)
- [ ] fzf keybindings (Ctrl+R / Ctrl+T / Alt+C) and git tab-completion work in the upgraded container
- [ ] Desktop install path: host-edited `~/squarebox/dotfiles/bashrc` (bind-mounted) is NOT clobbered by the refresh

## Dev Container
- [ ] VS Code "Reopen in Container" builds and connects
- [ ] Manual `sqrbx-setup` works in VS Code integrated terminal
Loading