Feature: dev-sync command — Script-level updates without container rebuild
IMPLEMENTATION RULES: Before implementing this plan, read and follow:
- WORKFLOW.md - The implementation process
- PLANS.md - Plan structure and best practices
Status: Active
Goal: Enable users to update toolbox scripts inside a running container without rebuilding the image, using a separate scripts version system.
GitHub Issues: #54 (Phases 2-4)
Prerequisites: PLAN-dev-sync-foundation (Phase 1) — ✅ DONE
Last Updated: 2026-02-16
Overview
Two version systems
The toolbox needs two independent version tracks:
| Container version | Scripts version | |
|---|---|---|
| What changes | Dockerfile, base image, OS packages, runtimes | Shell scripts, configs, commands |
| Version file | version.txt (manual bump) | scripts-version.txt (auto-generated by CI) |
| Update command | dev-update → docker pull + rebuild | dev-sync → download zip, swap scripts |
| When bumped | Manually, before major releases | Automatically by CI when scripts change |
| Frequency | Rare | Every script fix or feature |
Examples:
- Added a new
install-tool-powershell.sh→ scripts version bumps,dev-syncpicks it up - Changed
Dockerfile.baseto add a new OS package → container version bumps,dev-updateneeded - Fixed a bug in
dev-setup.sh→ scripts version bumps,dev-syncpicks it up
How dev-sync works
dev-sync
1. Fetch scripts-version.txt from GitHub raw content (tiny, fast)
2. Compare with local scripts-version.txt
3. If different: download dev_containers.zip from GitHub release (423KB)
4. Extract to temp dir
5. Atomic swap of additions/ and manage/ directories
6. Update symlinks for dev-* commands
7. Report what changed (per-tool version diffs)
What already exists
version-utils.sh— centralized version checking (_load_version_info,_check_for_updates,show_version_info)dev-update.sh— fetches remoteversion.txt(container version) and comparesdev_containers.zip— published to GitHub releases byzip_dev_setup.ymlon every CI passversion.txt— container version, bumped manuallytools.json— has per-toolSCRIPT_VERversions- Pre-commit hook auto-bumps
SCRIPT_VERon script changes
What needs to be created
scripts-version.txt— auto-generated by CI, tracks when scripts last changeddev-sync.sh— the sync command
Integration point: version-utils.sh
The scripts-version check hooks into the same place where version.txt is checked: .devcontainer/manage/lib/version-utils.sh. This file already provides:
_load_version_info()→ reads localversion.txt_check_for_updates()→ fetches remoteversion.txt, comparesshow_version_info()→ displays version + update notification
We extend it with:
_load_scripts_version_info()→ reads localscripts-version.txt_check_for_scripts_updates()→ fetches remotescripts-version.txt, comparesshow_version_info()updated to show both statuses
This way dev-help, the welcome message, dev-sync --check, and any future consumer all get both checks from one centralized place.
Phase 1: Add scripts-version.txt to CI pipeline — ✅ DONE
Tasks
- 1.1 Create
scripts-version.txtgeneration inzip_dev_setup.yml✓- Added generation step before zip archive step
- Format:
SCRIPTS_VERSION=<date>-<hash>,SCRIPTS_DATE=<date>,SCRIPTS_HASH=<hash> - Included in the zip (
scripts-version.txtat zip root) AND committed to repo
- 1.2 Version format: Option B — date + short commit hash (e.g.,
2026-02-16-a3f2b1c) ✓ - 1.3 Add
scripts-version.txtto the container image build ✓- Added COPY to
image/Dockerfilealongsideversion.txt - Added generation step to
build-image.yml(avoids timing issue with zip workflow) - Created initial placeholder
scripts-version.txtat repo root
- Added COPY to
- 1.4 Test that
scripts-version.txtis published:- Verify it's in the zip
- Verify it's fetchable from
https://raw.githubusercontent.com/terchris/devcontainer-toolbox/main/scripts-version.txt
Validation
Push a script change and verify:
scripts-version.txtis updated in the reposcripts-version.txtis included indev_containers.zip
User confirms scripts-version.txt is working.
Phase 2: Create dev-sync command — ✅ DONE
Tasks
- 2.1 Create
.devcontainer/manage/dev-sync.shwith script metadata ✓ - 2.2 Add scripts-version functions to
version-utils.sh✓_load_scripts_version_info(),_check_for_scripts_updates()show_version_info()updated to show both container and scripts version status
- 2.3 Implement version check in
dev-sync.sh✓ - 2.4 Implement download ✓ (curl from GitHub release, verify size)
- 2.5 Implement atomic swap ✓ (backup → extract → replace → cleanup via trap)
- 2.6 Implement symlink update ✓ (removes old symlinks, creates new ones)
- 2.7 Implement reporting ✓ (shows version change, tool count diff)
- 2.8 Add CLI flags ✓ (--help, --force, --quiet, --check, --rollback)
- 2.9 Add dev-sync symlink to
image/Dockerfile✓ - 2.10 Regenerate tools.json after swap ✓ (runs generate-tools-json.sh)
Validation
Test in devcontainer:
dev-sync --check # Should show current vs remote scripts version
dev-sync --help # Should show usage
dev-sync # Should sync or say "up to date"
dev-sync --rollback # Should restore backup
User confirms dev-sync works correctly.
Phase 3: Integrate with entrypoint (auto-sync on startup) — ✅ DONE
Tasks
- 3.1 Add
dev-sync --quietcall toimage/entrypoint.shduring startup ✓- Runs after services start, before first-start initialization
- Uses
timeout 10for offline safety - Errors suppressed with
2>/dev/null || true
- 3.2 Cache sync status to prevent repeated checks ✓
- Writes timestamp to
/tmp/.dct-last-sync - Skips if checked within last 24 hours (86400 seconds)
--forcebypasses the cache
- Writes timestamp to
- 3.3 Verify
dev-helpand welcome message show both statuses ✓show_version_info()inversion-utils.shalready shows both (updated in Phase 2)dev-welcome.shanddev-help.shboth callshow_version_info()— no changes needed
Validation
Rebuild container and verify:
- Auto-sync runs on startup (check startup log)
dev-helpshows both script and container sync status- Offline startup doesn't hang or error
User confirms auto-sync works correctly.
Phase 4: Documentation and cleanup — ✅ DONE
Tasks
- 4.1
dev-syncauto-generated bydev-docs(SYSTEM_COMMANDS category) ✓ - 4.2 Update
dev-updatehelp text to mentiondev-sync✓ - 4.3 Move investigation file to completed ✓
- 4.4 Update contributor docs (releasing.md) with two version systems ✓
Validation
User confirms documentation is complete.
Acceptance Criteria
- Two independent version systems:
version.txt(container) andscripts-version.txt(scripts) -
scripts-version.txtauto-updates in CI when scripts change -
dev-synccommand works inside the devcontainer -
dev-syncchecksscripts-version.txt, NOTversion.txt -
dev-updatecontinues to checkversion.txtfor container updates - Scripts are replaced atomically (no partial updates)
- Backup/rollback mechanism works
- Auto-sync runs on container startup (quiet, non-blocking)
- Offline startup doesn't fail or hang
-
dev-helpshows both container and scripts version status - New/removed
dev-*commands get symlinks updated - No shellcheck errors in dev-sync.sh
Files to Create/Modify
Create
scripts-version.txt— auto-generated scripts version (committed by CI).devcontainer/manage/dev-sync.sh— the sync command
Modify
.github/workflows/zip_dev_setup.yml— generate and includescripts-version.txt.github/workflows/build-image.yml— copyscripts-version.txtinto imageimage/Dockerfile— COPYscripts-version.txtimage/entrypoint.sh— add auto-sync on startup.devcontainer/manage/lib/version-utils.sh— add scripts-version functions alongside existing version.txt functions.devcontainer/manage/dev-update.sh— mention dev-sync for script updates
Implementation Notes
scripts-version.txt format
SCRIPTS_VERSION=2026.02.16.1
SCRIPTS_DATE=2026-02-16
SCRIPTS_HASH=a3f2b1c
Download URL for the zip
curl -fsSL "https://github.com/terchris/devcontainer-toolbox/releases/download/latest/dev_containers.zip" -o /tmp/dev_containers.zip
Version check URL
curl -fsSL "https://raw.githubusercontent.com/terchris/devcontainer-toolbox/main/scripts-version.txt"
Atomic swap pattern
TEMP_DIR=$(mktemp -d /tmp/dct-sync-XXXXX)
BACKUP_DIR="$DCT_HOME/.backup"
# Extract
unzip -q "$TEMP_DIR/dev_containers.zip" -d "$TEMP_DIR/"
# Backup
rm -rf "$BACKUP_DIR"
mkdir -p "$BACKUP_DIR"
cp -a "$DCT_HOME/additions" "$BACKUP_DIR/"
cp -a "$DCT_HOME/manage" "$BACKUP_DIR/"
cp "$DCT_HOME/scripts-version.txt" "$BACKUP_DIR/"
# Swap
rm -rf "$DCT_HOME/additions" "$DCT_HOME/manage"
mv "$TEMP_DIR/.devcontainer/additions" "$DCT_HOME/"
mv "$TEMP_DIR/.devcontainer/manage" "$DCT_HOME/"
# Cleanup
rm -rf "$TEMP_DIR"
Symlink pattern (from entrypoint.sh)
for script in "$DCT_HOME"/manage/dev-*.sh; do
cmd_name=$(basename "$script" .sh)
ln -sf "$script" "/usr/local/bin/$cmd_name"
done