Skip to content

Commit 97d48e1

Browse files
committed
fix(chezmoi): harden atuin postCreateCommand hook and add regression tests
- Replace dynamic environment variable assignment with literal heredoc + sed placeholder injection pattern (consistent with persist-ccache and persist-pre-commit features) - Add early exit if atuin credentials are not configured (graceful no-op) - Add explicit check for atuin binary before attempting login/sync (prevents 'command not found' from blocking container start) - Separate atuin-specific operations from /.persist-shell-history symlink setup for better error isolation - Add regression scenario test with dummy atuin credentials but no atuin installed - Expand test.sh checks to verify postCreateCommand script exists and chezmoi state is initialized - Update README to clarify that atuin, starship, and shell themes are not installed by this feature - Document optional atuin integration and relationship to persist-shell-history feature Fixes regression where postCreateCommand would fail with 'atuin: command not found' when atuin_user/password/key were configured but atuin binary was not installed, breaking container initialization and shell setup.
1 parent 1606bc2 commit 97d48e1

5 files changed

Lines changed: 83 additions & 25 deletions

File tree

src/chezmoi/README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
11

22
# Chezmoi (chezmoi)
33

4-
Install chezmoi and apply dotfiles.
4+
Install chezmoi and apply dotfiles during the container build.
5+
6+
## Behavior
7+
8+
This feature:
9+
10+
- Installs chezmoi
11+
- runs `chezmoi init --apply --exclude=encrypted` for the configured user during the build
12+
- creates `/usr/local/share/chezmoi-atuin-init.sh` as a `postCreateCommand`
13+
14+
If `atuin_user`, `atuin_password`, and `atuin_key` are set, the post-create hook prepares optional Atuin history persistence and then runs `atuin login` and `atuin sync` when the `atuin` binary is available.
15+
16+
This feature does not install Atuin, Starship, or shell themes. Those tools must come from your dotfiles repository or another feature.
17+
18+
To persist Atuin history across rebuilds, combine this feature with [persist-shell-history](../persist-shell-history/).
519

620
## Example Usage
721

@@ -12,3 +26,17 @@ Install chezmoi and apply dotfiles.
1226
}
1327
}
1428
```
29+
30+
## Example With Atuin
31+
32+
```json
33+
"features": {
34+
"ghcr.io/ckagerer/devcontainer-features/chezmoi:1": {
35+
"dotfiles_repo": "twpayne/dotfiles",
36+
"atuin_user": "your-user",
37+
"atuin_password": "your-token",
38+
"atuin_key": "your-key"
39+
},
40+
"ghcr.io/ckagerer/devcontainer-features/persist-shell-history:1": {}
41+
}
42+
```

src/chezmoi/devcontainer-feature.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "chezmoi",
33
"id": "chezmoi",
4-
"version": "1.6.2",
4+
"version": "1.6.3",
55
"description": "Install chezmoi",
66
"documentationURL": "https://github.com/ckagerer/devcontainer-features/tree/main/src/chezmoi",
77
"options": {

src/chezmoi/install.sh

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ if [ -z "${DOTFILES_REPO}" ]; then
1717
exit 1
1818
fi
1919

20+
escape_sed_replacement() {
21+
printf '%s' "$1" | sed 's/[\\&|]/\\&/g'
22+
}
23+
2024
# Function to update and install packages on Debian-based systems
2125
install_debian_packages() {
2226
apt update
@@ -100,45 +104,65 @@ sudo --user "${CHEZMOI_USER}" bash -c "cd '${CHEZMOI_USER_HOME}' && REMOTE_CONTA
100104
# --- Generate a 'pull-git-lfs-artifacts.sh' script to be executed by the 'postCreateCommand' lifecycle hook
101105
INIT_ATUIN_SCRIPT_PATH="/usr/local/share/chezmoi-atuin-init.sh"
102106

103-
tee "$INIT_ATUIN_SCRIPT_PATH" >/dev/null <<EOF
107+
tee "$INIT_ATUIN_SCRIPT_PATH" >/dev/null <<'EOF'
104108
#!/usr/bin/env bash
105109
# (C) Copyright 2025 Christian Kagerer
106110
# Purpose: Initialize Atuin login and sync for chezmoi devcontainer feature
107111
108-
KEEP_GOING="${KEEP_GOING:-false}"
112+
KEEP_GOING="__KEEP_GOING_PLACEHOLDER__"
109113
110-
if [[ "\${KEEP_GOING}" == "true" ]]; then
114+
if [[ "${KEEP_GOING}" == "true" ]]; then
111115
set +o errexit +o nounset +o pipefail
112116
else
113117
set -o errexit -o nounset -o pipefail
114118
fi
115119
set -x
116120
117-
ATUIN_USER="${ATUIN_USER}"
118-
ATUIN_PASSWORD="${ATUIN_PASSWORD}"
119-
ATUIN_KEY="${ATUIN_KEY}"
120-
121-
# exit if required environment variables are not set
122-
if [ -n "\${ATUIN_USER}" ] && [ -n "\${ATUIN_PASSWORD}" ] && [ -n "\${ATUIN_KEY}" ]; then
123-
# If /.persist-shell-history exists, we assume that the user wants to persist also the atuin history
124-
if [ -d "/.persist-shell-history" ]; then
125-
if [ -d ~/.local/share/atuin ]; then
126-
mv ~/.local/share/atuin ~/.local/share/atuin.bak
127-
fi
128-
mkdir -p /.persist-shell-history/atuin
129-
mkdir -p ~/.local/share
130-
ln -s -f /.persist-shell-history/atuin ~/.local/share/atuin
131-
fi
132-
133-
atuin login --username "\${ATUIN_USER}" --password "\${ATUIN_PASSWORD}" --key "\${ATUIN_KEY}" || true
134-
atuin sync
121+
ATUIN_USER="__ATUIN_USER_PLACEHOLDER__"
122+
ATUIN_PASSWORD="__ATUIN_PASSWORD_PLACEHOLDER__"
123+
ATUIN_KEY="__ATUIN_KEY_PLACEHOLDER__"
124+
125+
if [[ -z "${ATUIN_USER}" || -z "${ATUIN_PASSWORD}" || -z "${ATUIN_KEY}" ]]; then
126+
echo "Atuin credentials not configured; skipping atuin initialization"
127+
exit 0
135128
fi
136129
137-
if [[ "\${KEEP_GOING}" == "true" ]]; then
130+
# If /.persist-shell-history exists, we assume that the user wants to persist also the atuin history.
131+
if [[ -d "/.persist-shell-history" ]]; then
132+
if [[ -d "${HOME}/.local/share/atuin" && ! -L "${HOME}/.local/share/atuin" ]]; then
133+
mv "${HOME}/.local/share/atuin" "${HOME}/.local/share/atuin.bak"
134+
fi
135+
136+
mkdir -p "/.persist-shell-history/atuin"
137+
mkdir -p "${HOME}/.local/share"
138+
ln -sfn "/.persist-shell-history/atuin" "${HOME}/.local/share/atuin"
139+
fi
140+
141+
if ! command -v atuin >/dev/null 2>&1; then
142+
echo "Atuin credentials are configured, but atuin is not installed. Skipping login and sync." >&2
143+
exit 0
144+
fi
145+
146+
atuin login --username "${ATUIN_USER}" --password "${ATUIN_PASSWORD}" --key "${ATUIN_KEY}" || true
147+
atuin sync
148+
149+
if [[ "${KEEP_GOING}" == "true" ]]; then
138150
exit 0
139151
fi
140152
EOF
141153

154+
KEEP_GOING_ESCAPED="$(escape_sed_replacement "${KEEP_GOING:-false}")"
155+
ATUIN_USER_ESCAPED="$(escape_sed_replacement "${ATUIN_USER:-}")"
156+
ATUIN_PASSWORD_ESCAPED="$(escape_sed_replacement "${ATUIN_PASSWORD:-}")"
157+
ATUIN_KEY_ESCAPED="$(escape_sed_replacement "${ATUIN_KEY:-}")"
158+
159+
sed -i \
160+
-e "s|__KEEP_GOING_PLACEHOLDER__|${KEEP_GOING_ESCAPED}|g" \
161+
-e "s|__ATUIN_USER_PLACEHOLDER__|${ATUIN_USER_ESCAPED}|g" \
162+
-e "s|__ATUIN_PASSWORD_PLACEHOLDER__|${ATUIN_PASSWORD_ESCAPED}|g" \
163+
-e "s|__ATUIN_KEY_PLACEHOLDER__|${ATUIN_KEY_ESCAPED}|g" \
164+
"$INIT_ATUIN_SCRIPT_PATH"
165+
142166
chmod 755 "$INIT_ATUIN_SCRIPT_PATH"
143167

144168
echo "Done"

test/chezmoi/scenarios.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
"username": "octocat"
1010
},
1111
"chezmoi": {
12-
"dotfiles_repo": "twpayne/dotfiles"
12+
"dotfiles_repo": "twpayne/dotfiles",
13+
"atuin_user": "octocat",
14+
"atuin_password": "dummy-token",
15+
"atuin_key": "dummy key for feature testing"
1316
}
1417
},
1518
"remoteUser": "octocat"

test/chezmoi/test.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ source dev-container-features-test-lib
4141
# The 'check' command comes from the dev-container-features-test-lib. Syntax is...
4242
# check <LABEL> <cmd> [args...]
4343
check "validate if chezmoi command is installed" chezmoi --version
44+
check "validate if chezmoi Atuin init script exists" test -x /usr/local/share/chezmoi-atuin-init.sh
45+
# shellcheck disable=SC2016
46+
check "validate if chezmoi has been initialized for the current user" sh -c 'source_path=$(chezmoi source-path) && [ -n "$source_path" ] && [ -d "$source_path" ]'
4447

4548
# Report result
4649
# If any of the checks above exited with a non-zero exit code, the test will fail.

0 commit comments

Comments
 (0)