From c3d72441e19461ba189227b9a97c2a6d9c64ad13 Mon Sep 17 00:00:00 2001 From: rosstaco Date: Fri, 20 Feb 2026 00:51:20 +0000 Subject: [PATCH 1/6] feat: enable documentation generation and enhance copilot persistence setup --- .github/workflows/release.yaml | 2 +- src/copilot-persistence/NOTES.md | 28 ++++++++++++++ src/copilot-persistence/README.md | 8 +--- src/copilot-persistence/install.sh | 61 ++++++------------------------ test/copilot-persistence/debian.sh | 7 +--- test/copilot-persistence/test.sh | 28 +++++++++----- test/copilot-persistence/ubuntu.sh | 7 +--- 7 files changed, 64 insertions(+), 77 deletions(-) create mode 100644 src/copilot-persistence/NOTES.md diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1c14ffb..57572c7 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -18,7 +18,7 @@ jobs: with: publish-features: "true" base-path-to-features: "./src" - generate-docs: "false" + generate-docs: "true" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/src/copilot-persistence/NOTES.md b/src/copilot-persistence/NOTES.md new file mode 100644 index 0000000..0b03dde --- /dev/null +++ b/src/copilot-persistence/NOTES.md @@ -0,0 +1,28 @@ +## How It Works + +Inspired by the [shell-history pattern](https://github.com/stuartleeks/dev-container-features/tree/main/src/shell-history), this feature: + +1. **Mounts a named volume** (scoped per dev container via `${devcontainerId}`) to `/copilot-data` +2. **Creates a symlink** from `~/.copilot` → `/copilot-data` +3. **Sets ownership** to the container user during installation (auto-detects from `$_REMOTE_USER`) + +> **Note:** If `~/.copilot` already exists as a directory during installation, it is moved into the volume at `/copilot-data/migrated-/` before the symlink is created. + +## What Persists + +- ✅ Chat history and sessions +- ✅ CLI configuration (model preferences, settings) +- ✅ Command history +- ✅ Trusted folders + +## Troubleshooting + +View the volume data: +```bash +ls -la /copilot-data +``` + +Check the symlink: +```bash +ls -la ~/.copilot +``` diff --git a/src/copilot-persistence/README.md b/src/copilot-persistence/README.md index 5347400..4877b9f 100644 --- a/src/copilot-persistence/README.md +++ b/src/copilot-persistence/README.md @@ -10,6 +10,7 @@ Inspired by the [shell-history pattern](https://github.com/stuartleeks/dev-conta 2. **Creates a symlink** from `~/.copilot` → `/copilot-data` 3. **Sets ownership** to the container user during installation (auto-detects from `$_REMOTE_USER`) +> **Note:** If `~/.copilot` already exists as a directory during installation, it is moved into the volume at `/copilot-data/migrated-/` before the symlink is created. ## What Persists @@ -29,13 +30,6 @@ Inspired by the [shell-history pattern](https://github.com/stuartleeks/dev-conta } ``` -## Benefits Over Direct Mount - -- No permission conflicts (volume created in neutral location) -- Works even if Copilot CLI has XDG_CONFIG_HOME bugs -- Clean lifecycle management during feature install -- Easy to share across projects - ## Troubleshooting View the volume data: diff --git a/src/copilot-persistence/install.sh b/src/copilot-persistence/install.sh index 90699f7..4504748 100755 --- a/src/copilot-persistence/install.sh +++ b/src/copilot-persistence/install.sh @@ -1,64 +1,25 @@ #!/bin/bash set -e -echo "Setting up Copilot CLI persistence..." - -# Determine the user (defaults to vscode if not set) USERNAME="${_REMOTE_USER:-"${USERNAME:-"vscode"}"}" - -# Resolve home directory via the passwd database or shell expansion -USER_HOME="" -if command -v getent >/dev/null 2>&1; then - USER_HOME="$(getent passwd "${USERNAME}" | cut -d: -f6)" -fi +USER_HOME="${_REMOTE_USER_HOME:-"$(getent passwd "${USERNAME}" 2>/dev/null | cut -d: -f6)"}" if [ -z "${USER_HOME}" ]; then - USER_HOME="$(eval echo "~${USERNAME}" 2>/dev/null || true)" -fi -if [ -z "${USER_HOME}" ]; then - echo "Error: Unable to determine home directory for user '${USERNAME}'" >&2 + echo "ERROR: Could not determine home directory for user '${USERNAME}'" >&2 exit 1 fi - -# Create the persistent data directory in a neutral location -mkdir -p /copilot-data - -# Get the user's UID and GID USER_UID=$(id -u "${USERNAME}" 2>/dev/null || echo "1000") USER_GID=$(id -g "${USERNAME}" 2>/dev/null || echo "1000") -# Fix ownership for the user (build-time, may be overridden by volume mount) -chown -R "${USER_UID}:${USER_GID}" /copilot-data +# Prepare the persistent data directory (permissions carry into named volume on first use) +mkdir -p /copilot-data +chown "${USER_UID}:${USER_GID}" /copilot-data chmod 700 /copilot-data -# Create an init script that fixes volume permissions at container start. -# When a named volume is mounted, build-time permissions may not carry over, -# so we fix ownership on every shell login. -mkdir -p /usr/local/share/copilot-persistence -cat > /usr/local/share/copilot-persistence/init.sh << 'INIT' -#!/bin/bash -# Fix /copilot-data ownership if it exists and is not writable by current user -if [ -d /copilot-data ] && [ ! -w /copilot-data ]; then - sudo chown -R "$(id -u):$(id -g)" /copilot-data 2>/dev/null || true -fi -INIT -chmod 755 /usr/local/share/copilot-persistence/init.sh - -# Source the init script from profile so it runs on container start -cat > /etc/profile.d/copilot-persistence.sh << 'PROFILE' -# Fix copilot-data volume permissions on login -if [ -f /usr/local/share/copilot-persistence/init.sh ]; then - . /usr/local/share/copilot-persistence/init.sh -fi -PROFILE - -# Create a symlink from the default location to our persistent volume -# This handles the case where Copilot doesn't use XDG_CONFIG_HOME correctly -mkdir -p "${USER_HOME}" -if [ ! -L "${USER_HOME}/.copilot" ] || [ "$(readlink "${USER_HOME}/.copilot")" != "/copilot-data" ]; then - rm -rf "${USER_HOME}/.copilot" 2>/dev/null || true - ln -sf /copilot-data "${USER_HOME}/.copilot" - chown -h "${USER_UID}:${USER_GID}" "${USER_HOME}/.copilot" +# Migrate any pre-existing Copilot data into the volume, then symlink +if [ -e "${USER_HOME}/.copilot" ] && [ ! -L "${USER_HOME}/.copilot" ]; then + mv "${USER_HOME}/.copilot" "/copilot-data/migrated-$(date +%s)" fi +ln -sfn /copilot-data "${USER_HOME}/.copilot" +chown -h "${USER_UID}:${USER_GID}" "${USER_HOME}/.copilot" -echo "Copilot CLI persistence configured successfully for user: ${USERNAME}" -echo "Data will be stored in /copilot-data (mounted volume)" +echo "Copilot persistence: ~/.copilot → /copilot-data" diff --git a/test/copilot-persistence/debian.sh b/test/copilot-persistence/debian.sh index 53b5836..e40f4eb 100644 --- a/test/copilot-persistence/debian.sh +++ b/test/copilot-persistence/debian.sh @@ -7,11 +7,6 @@ set -e source dev-container-features-test-lib -# Run the init script to fix volume permissions (normally runs via /etc/profile.d on login) -if [ -f /usr/local/share/copilot-persistence/init.sh ]; then - . /usr/local/share/copilot-persistence/init.sh -fi - check "copilot-data directory exists" test -d /copilot-data check "COPILOT_DATA_DIR is set" test "$COPILOT_DATA_DIR" = "/copilot-data" @@ -20,4 +15,6 @@ check "symlink exists at ~/.copilot" test -L ~/.copilot check "can write to copilot-data" bash -c "touch /copilot-data/test-file && rm /copilot-data/test-file" +check "copilot-data has restricted permissions" bash -c 'test "$(stat -c %a /copilot-data)" = "700"' + reportResults diff --git a/test/copilot-persistence/test.sh b/test/copilot-persistence/test.sh index cce6391..e023252 100644 --- a/test/copilot-persistence/test.sh +++ b/test/copilot-persistence/test.sh @@ -19,17 +19,8 @@ set -e # Optional: Import test library bundled with the devcontainer CLI source dev-container-features-test-lib -# Run the init script to fix volume permissions (normally runs via /etc/profile.d on login) -if [ -f /usr/local/share/copilot-persistence/init.sh ]; then - . /usr/local/share/copilot-persistence/init.sh -fi - # Feature-specific tests -check "init script exists" test -f /usr/local/share/copilot-persistence/init.sh - -check "profile.d script exists" test -f /etc/profile.d/copilot-persistence.sh - check "copilot-data directory exists" test -d /copilot-data check "copilot-data directory is writable" test -w /copilot-data @@ -42,5 +33,24 @@ check "symlink exists at ~/.copilot" test -L ~/.copilot check "symlink target is /copilot-data" test "$(readlink ~/.copilot)" = "/copilot-data" +check "copilot-data has restricted permissions" bash -c 'test "$(stat -c %a /copilot-data)" = "700"' + +check "data written to volume is accessible via symlink" bash -c 'echo "test" > /copilot-data/test-persist && test "$(cat ~/.copilot/test-persist)" = "test" && rm /copilot-data/test-persist' + +# Test migration: simulate pre-existing .copilot directory and verify mv behavior +check "migration preserves pre-existing data" bash -c ' + rm -f ~/.copilot + mkdir -p ~/.copilot + echo "precious-data" > ~/.copilot/history.json + if [ -e ~/.copilot ] && [ ! -L ~/.copilot ]; then + mv ~/.copilot "/copilot-data/migrated-test" + fi + ln -sfn /copilot-data ~/.copilot + test -f /copilot-data/migrated-test/history.json && + test "$(cat /copilot-data/migrated-test/history.json)" = "precious-data" && + test -L ~/.copilot && + rm -rf /copilot-data/migrated-test +' + # Report results reportResults diff --git a/test/copilot-persistence/ubuntu.sh b/test/copilot-persistence/ubuntu.sh index 703ab97..d999af8 100644 --- a/test/copilot-persistence/ubuntu.sh +++ b/test/copilot-persistence/ubuntu.sh @@ -7,11 +7,6 @@ set -e source dev-container-features-test-lib -# Run the init script to fix volume permissions (normally runs via /etc/profile.d on login) -if [ -f /usr/local/share/copilot-persistence/init.sh ]; then - . /usr/local/share/copilot-persistence/init.sh -fi - check "copilot-data directory exists" test -d /copilot-data check "COPILOT_DATA_DIR is set" test "$COPILOT_DATA_DIR" = "/copilot-data" @@ -20,4 +15,6 @@ check "symlink exists at ~/.copilot" test -L ~/.copilot check "can write to copilot-data" bash -c "touch /copilot-data/test-file && rm /copilot-data/test-file" +check "copilot-data has restricted permissions" bash -c 'test "$(stat -c %a /copilot-data)" = "700"' + reportResults From 6decbc4f551ebceef9af5c7435e17a41386f132d Mon Sep 17 00:00:00 2001 From: rosstaco Date: Fri, 20 Feb 2026 00:56:57 +0000 Subject: [PATCH 2/6] fix: add runtime volume permission fix and harden install script - Add profile.d one-liner to fix volume ownership at login - Use _REMOTE_USER_HOME instead of manual getent resolution - Add mkdir -p for USER_HOME before symlink creation - Add guard for empty USER_HOME with clear error message - Add tests for permissions, symlink write-through, and migration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/copilot-persistence/install.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/copilot-persistence/install.sh b/src/copilot-persistence/install.sh index 4504748..cf28fef 100755 --- a/src/copilot-persistence/install.sh +++ b/src/copilot-persistence/install.sh @@ -15,7 +15,13 @@ mkdir -p /copilot-data chown "${USER_UID}:${USER_GID}" /copilot-data chmod 700 /copilot-data +# Fix volume permissions at login (build-time ownership may not carry into mounted volumes) +cat > /etc/profile.d/copilot-persistence.sh << EOF +[ -d /copilot-data ] && [ ! -w /copilot-data ] && sudo -n chown -R "${USER_UID}:${USER_GID}" /copilot-data 2>/dev/null || true +EOF + # Migrate any pre-existing Copilot data into the volume, then symlink +mkdir -p "${USER_HOME}" if [ -e "${USER_HOME}/.copilot" ] && [ ! -L "${USER_HOME}/.copilot" ]; then mv "${USER_HOME}/.copilot" "/copilot-data/migrated-$(date +%s)" fi From bf89f03049b2f17bcb718b57517d774884771980 Mon Sep 17 00:00:00 2001 From: rosstaco Date: Fri, 20 Feb 2026 01:02:08 +0000 Subject: [PATCH 3/6] fix: source profile.d in tests to mirror login shell behavior Tests run via sh -c (not login shell), so profile.d scripts aren't auto-sourced. Explicitly sourcing copilot-persistence.sh mirrors what happens when a real user opens a terminal in the devcontainer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test/copilot-persistence/debian.sh | 3 +++ test/copilot-persistence/test.sh | 3 +++ test/copilot-persistence/ubuntu.sh | 3 +++ 3 files changed, 9 insertions(+) diff --git a/test/copilot-persistence/debian.sh b/test/copilot-persistence/debian.sh index e40f4eb..9272321 100644 --- a/test/copilot-persistence/debian.sh +++ b/test/copilot-persistence/debian.sh @@ -7,6 +7,9 @@ set -e source dev-container-features-test-lib +# Source profile scripts to fix volume permissions (mirrors login shell behavior) +. /etc/profile.d/copilot-persistence.sh 2>/dev/null || true + check "copilot-data directory exists" test -d /copilot-data check "COPILOT_DATA_DIR is set" test "$COPILOT_DATA_DIR" = "/copilot-data" diff --git a/test/copilot-persistence/test.sh b/test/copilot-persistence/test.sh index e023252..28754d4 100644 --- a/test/copilot-persistence/test.sh +++ b/test/copilot-persistence/test.sh @@ -19,6 +19,9 @@ set -e # Optional: Import test library bundled with the devcontainer CLI source dev-container-features-test-lib +# Source profile scripts to fix volume permissions (mirrors login shell behavior) +. /etc/profile.d/copilot-persistence.sh 2>/dev/null || true + # Feature-specific tests check "copilot-data directory exists" test -d /copilot-data diff --git a/test/copilot-persistence/ubuntu.sh b/test/copilot-persistence/ubuntu.sh index d999af8..7f2c4f6 100644 --- a/test/copilot-persistence/ubuntu.sh +++ b/test/copilot-persistence/ubuntu.sh @@ -7,6 +7,9 @@ set -e source dev-container-features-test-lib +# Source profile scripts to fix volume permissions (mirrors login shell behavior) +. /etc/profile.d/copilot-persistence.sh 2>/dev/null || true + check "copilot-data directory exists" test -d /copilot-data check "COPILOT_DATA_DIR is set" test "$COPILOT_DATA_DIR" = "/copilot-data" From 3e5af1eedd324e7760bae5fd282f171a4bcc8950 Mon Sep 17 00:00:00 2001 From: rosstaco Date: Fri, 20 Feb 2026 01:29:13 +0000 Subject: [PATCH 4/6] debug: add diagnostics to test volume permissions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test/copilot-persistence/test.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/copilot-persistence/test.sh b/test/copilot-persistence/test.sh index 28754d4..e448e25 100644 --- a/test/copilot-persistence/test.sh +++ b/test/copilot-persistence/test.sh @@ -22,6 +22,13 @@ source dev-container-features-test-lib # Source profile scripts to fix volume permissions (mirrors login shell behavior) . /etc/profile.d/copilot-persistence.sh 2>/dev/null || true +# Debug: show permission state +echo "DEBUG: uid=$(id -u) user=$(whoami)" +ls -la / | grep copilot-data || true +cat /etc/profile.d/copilot-persistence.sh 2>/dev/null || echo "DEBUG: no profile.d script" +sudo chown -R "$(id -u):$(id -g)" /copilot-data 2>&1 || echo "DEBUG: sudo chown failed" +ls -la / | grep copilot-data || true + # Feature-specific tests check "copilot-data directory exists" test -d /copilot-data From 8b9b718cf4a5d7026f605cca083518983b451a3b Mon Sep 17 00:00:00 2001 From: rosstaco Date: Fri, 20 Feb 2026 01:34:36 +0000 Subject: [PATCH 5/6] fix: use runtime UID in profile.d instead of build-time UID MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The devcontainer CLI remaps the vscode user UID at runtime (e.g., 1000→1001). The profile.d script was using build-time UIDs via unquoted heredoc, causing chown to set wrong ownership. Switch to quoted heredoc ('EOF') so $(id -u):$(id -g) resolves at runtime. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/copilot-persistence/install.sh | 4 ++-- test/copilot-persistence/test.sh | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/copilot-persistence/install.sh b/src/copilot-persistence/install.sh index cf28fef..5f82e6d 100755 --- a/src/copilot-persistence/install.sh +++ b/src/copilot-persistence/install.sh @@ -16,8 +16,8 @@ chown "${USER_UID}:${USER_GID}" /copilot-data chmod 700 /copilot-data # Fix volume permissions at login (build-time ownership may not carry into mounted volumes) -cat > /etc/profile.d/copilot-persistence.sh << EOF -[ -d /copilot-data ] && [ ! -w /copilot-data ] && sudo -n chown -R "${USER_UID}:${USER_GID}" /copilot-data 2>/dev/null || true +cat > /etc/profile.d/copilot-persistence.sh << 'EOF' +[ -d /copilot-data ] && [ ! -w /copilot-data ] && sudo -n chown -R "$(id -u):$(id -g)" /copilot-data 2>/dev/null || true EOF # Migrate any pre-existing Copilot data into the volume, then symlink diff --git a/test/copilot-persistence/test.sh b/test/copilot-persistence/test.sh index e448e25..28754d4 100644 --- a/test/copilot-persistence/test.sh +++ b/test/copilot-persistence/test.sh @@ -22,13 +22,6 @@ source dev-container-features-test-lib # Source profile scripts to fix volume permissions (mirrors login shell behavior) . /etc/profile.d/copilot-persistence.sh 2>/dev/null || true -# Debug: show permission state -echo "DEBUG: uid=$(id -u) user=$(whoami)" -ls -la / | grep copilot-data || true -cat /etc/profile.d/copilot-persistence.sh 2>/dev/null || echo "DEBUG: no profile.d script" -sudo chown -R "$(id -u):$(id -g)" /copilot-data 2>&1 || echo "DEBUG: sudo chown failed" -ls -la / | grep copilot-data || true - # Feature-specific tests check "copilot-data directory exists" test -d /copilot-data From 91aa7f4bd70ad9475e35eff49b05b8d941249c33 Mon Sep 17 00:00:00 2001 From: rosstaco Date: Fri, 20 Feb 2026 04:46:12 +0000 Subject: [PATCH 6/6] fix: address PR review comments - Remove migration note from README.md (NOTES.md already has it, generate-docs would overwrite README anyway) - Assert profile.d script exists in all tests before sourcing - Use collision-resistant suffix for migration dir (%s%N-152937) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/copilot-persistence/README.md | 2 -- src/copilot-persistence/install.sh | 2 +- test/copilot-persistence/debian.sh | 5 +++-- test/copilot-persistence/test.sh | 5 +++-- test/copilot-persistence/ubuntu.sh | 5 +++-- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/copilot-persistence/README.md b/src/copilot-persistence/README.md index 4877b9f..b17daed 100644 --- a/src/copilot-persistence/README.md +++ b/src/copilot-persistence/README.md @@ -10,8 +10,6 @@ Inspired by the [shell-history pattern](https://github.com/stuartleeks/dev-conta 2. **Creates a symlink** from `~/.copilot` → `/copilot-data` 3. **Sets ownership** to the container user during installation (auto-detects from `$_REMOTE_USER`) -> **Note:** If `~/.copilot` already exists as a directory during installation, it is moved into the volume at `/copilot-data/migrated-/` before the symlink is created. - ## What Persists - ✅ Chat history and sessions diff --git a/src/copilot-persistence/install.sh b/src/copilot-persistence/install.sh index 5f82e6d..4b81854 100755 --- a/src/copilot-persistence/install.sh +++ b/src/copilot-persistence/install.sh @@ -23,7 +23,7 @@ EOF # Migrate any pre-existing Copilot data into the volume, then symlink mkdir -p "${USER_HOME}" if [ -e "${USER_HOME}/.copilot" ] && [ ! -L "${USER_HOME}/.copilot" ]; then - mv "${USER_HOME}/.copilot" "/copilot-data/migrated-$(date +%s)" + mv "${USER_HOME}/.copilot" "/copilot-data/migrated-$(date +%s%N)-$$" fi ln -sfn /copilot-data "${USER_HOME}/.copilot" chown -h "${USER_UID}:${USER_GID}" "${USER_HOME}/.copilot" diff --git a/test/copilot-persistence/debian.sh b/test/copilot-persistence/debian.sh index 9272321..05da458 100644 --- a/test/copilot-persistence/debian.sh +++ b/test/copilot-persistence/debian.sh @@ -7,8 +7,9 @@ set -e source dev-container-features-test-lib -# Source profile scripts to fix volume permissions (mirrors login shell behavior) -. /etc/profile.d/copilot-persistence.sh 2>/dev/null || true +# Verify profile script was created and source it to fix volume permissions +check "copilot-persistence profile script exists" test -f /etc/profile.d/copilot-persistence.sh +. /etc/profile.d/copilot-persistence.sh check "copilot-data directory exists" test -d /copilot-data diff --git a/test/copilot-persistence/test.sh b/test/copilot-persistence/test.sh index 28754d4..673a95c 100644 --- a/test/copilot-persistence/test.sh +++ b/test/copilot-persistence/test.sh @@ -19,8 +19,9 @@ set -e # Optional: Import test library bundled with the devcontainer CLI source dev-container-features-test-lib -# Source profile scripts to fix volume permissions (mirrors login shell behavior) -. /etc/profile.d/copilot-persistence.sh 2>/dev/null || true +# Verify profile script was created and source it to fix volume permissions +check "copilot-persistence profile script exists" test -f /etc/profile.d/copilot-persistence.sh +. /etc/profile.d/copilot-persistence.sh # Feature-specific tests diff --git a/test/copilot-persistence/ubuntu.sh b/test/copilot-persistence/ubuntu.sh index 7f2c4f6..eb9e431 100644 --- a/test/copilot-persistence/ubuntu.sh +++ b/test/copilot-persistence/ubuntu.sh @@ -7,8 +7,9 @@ set -e source dev-container-features-test-lib -# Source profile scripts to fix volume permissions (mirrors login shell behavior) -. /etc/profile.d/copilot-persistence.sh 2>/dev/null || true +# Verify profile script was created and source it to fix volume permissions +check "copilot-persistence profile script exists" test -f /etc/profile.d/copilot-persistence.sh +. /etc/profile.d/copilot-persistence.sh check "copilot-data directory exists" test -d /copilot-data