Skip to main content

Feature: dev-sync command — Script-level updates without container rebuild

IMPLEMENTATION RULES: Before implementing this plan, read and follow:

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 versionScripts version
What changesDockerfile, base image, OS packages, runtimesShell scripts, configs, commands
Version fileversion.txt (manual bump)scripts-version.txt (auto-generated by CI)
Update commanddev-update → docker pull + rebuilddev-sync → download zip, swap scripts
When bumpedManually, before major releasesAutomatically by CI when scripts change
FrequencyRareEvery script fix or feature

Examples:

  • Added a new install-tool-powershell.sh → scripts version bumps, dev-sync picks it up
  • Changed Dockerfile.base to add a new OS package → container version bumps, dev-update needed
  • Fixed a bug in dev-setup.sh → scripts version bumps, dev-sync picks 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 remote version.txt (container version) and compares
  • dev_containers.zip — published to GitHub releases by zip_dev_setup.yml on every CI pass
  • version.txt — container version, bumped manually
  • tools.json — has per-tool SCRIPT_VER versions
  • Pre-commit hook auto-bumps SCRIPT_VER on script changes

What needs to be created

  • scripts-version.txt — auto-generated by CI, tracks when scripts last changed
  • dev-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 local version.txt
  • _check_for_updates() → fetches remote version.txt, compares
  • show_version_info() → displays version + update notification

We extend it with:

  • _load_scripts_version_info() → reads local scripts-version.txt
  • _check_for_scripts_updates() → fetches remote scripts-version.txt, compares
  • show_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.txt generation in zip_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.txt at 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.txt to the container image build ✓
    • Added COPY to image/Dockerfile alongside version.txt
    • Added generation step to build-image.yml (avoids timing issue with zip workflow)
    • Created initial placeholder scripts-version.txt at repo root
  • 1.4 Test that scripts-version.txt is 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.txt is updated in the repo
  • scripts-version.txt is included in dev_containers.zip

User confirms scripts-version.txt is working.


Phase 2: Create dev-sync command — ✅ DONE

Tasks

  • 2.1 Create .devcontainer/manage/dev-sync.sh with 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 --quiet call to image/entrypoint.sh during startup ✓
    • Runs after services start, before first-start initialization
    • Uses timeout 10 for 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)
    • --force bypasses the cache
  • 3.3 Verify dev-help and welcome message show both statuses ✓
    • show_version_info() in version-utils.sh already shows both (updated in Phase 2)
    • dev-welcome.sh and dev-help.sh both call show_version_info() — no changes needed

Validation

Rebuild container and verify:

  • Auto-sync runs on startup (check startup log)
  • dev-help shows 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-sync auto-generated by dev-docs (SYSTEM_COMMANDS category) ✓
  • 4.2 Update dev-update help text to mention dev-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) and scripts-version.txt (scripts)
  • scripts-version.txt auto-updates in CI when scripts change
  • dev-sync command works inside the devcontainer
  • dev-sync checks scripts-version.txt, NOT version.txt
  • dev-update continues to check version.txt for 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-help shows 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 include scripts-version.txt
  • .github/workflows/build-image.yml — copy scripts-version.txt into image
  • image/Dockerfile — COPY scripts-version.txt
  • image/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"
for script in "$DCT_HOME"/manage/dev-*.sh; do
cmd_name=$(basename "$script" .sh)
ln -sf "$script" "/usr/local/bin/$cmd_name"
done