diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index dd84229ab..26531bb7b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -79,8 +79,8 @@ jobs: COMMIT_SHA=${{ github.sha }} GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} GITHUB_PR=${{ github.event.pull_request.number }} - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=registry,ref=${{ env.REGISTRY_IMAGE }}:buildcache-${{ matrix.build.arch }} + cache-to: type=registry,ref=${{ env.REGISTRY_IMAGE }}:buildcache-${{ matrix.build.arch }},mode=max - name: Export digest run: | mkdir -p ${{ runner.temp }}/digests diff --git a/config/mempalace/default.nix b/config/mempalace/default.nix index 483e688d4..4f1f0ba47 100644 --- a/config/mempalace/default.nix +++ b/config/mempalace/default.nix @@ -1,5 +1,4 @@ -{ ... }: -{ +_: { home.file.".mempalace/config.json" = { source = ./config.json; force = true; diff --git a/home-manager/modules/secure-dotenv/default.nix b/home-manager/modules/secure-dotenv/default.nix index f35d62420..ad7e9b3fe 100644 --- a/home-manager/modules/secure-dotenv/default.nix +++ b/home-manager/modules/secure-dotenv/default.nix @@ -6,24 +6,13 @@ }: let homeDir = config.home.homeDirectory; - script = pkgs.writeShellScript "secure-dotenv" '' - set -euo pipefail - # Enforce 600 on all .env files under home directory - ${pkgs.findutils}/bin/find "${homeDir}" \ - -maxdepth 4 \ - -name '.env' -o -name '.env.*' -o -name '*.env' \ - 2>/dev/null | while IFS= read -r f; do - if [ -f "$f" ] && [ ! -L "$f" ]; then - current=$(${pkgs.coreutils}/bin/stat -c '%a' "$f") - if [ "$current" != "600" ]; then - chmod 600 "$f" - fi - fi - done - ''; + script = pkgs.replaceVars ./secure-dotenv.sh { + find = "${pkgs.findutils}/bin/find"; + stat = "${pkgs.coreutils}/bin/stat"; + }; in { home.activation.secureDotenv = lib.hm.dag.entryAfter [ "writeBoundary" ] '' - $DRY_RUN_CMD ${script} + $DRY_RUN_CMD ${pkgs.bash}/bin/bash "${script}" "${homeDir}" ''; } diff --git a/home-manager/modules/secure-dotenv/secure-dotenv.sh b/home-manager/modules/secure-dotenv/secure-dotenv.sh new file mode 100644 index 000000000..2452afef9 --- /dev/null +++ b/home-manager/modules/secure-dotenv/secure-dotenv.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# @find@ and @stat@ are substituted by pkgs.replaceVars. +set -euo pipefail + +HOME_DIR="$1" + +@find@ "${HOME_DIR}" \ + -maxdepth 4 \ + \( -name '.env' -o -name '.env.*' -o -name '*.env' \) \ + 2>/dev/null | while IFS= read -r f; do + if [ -f "$f" ] && [ ! -L "$f" ]; then + current=$(@stat@ -c '%a' "$f") + if [ "$current" != "600" ]; then + chmod 600 "$f" + fi + fi +done diff --git a/home-manager/services/docker-postgres/default.nix b/home-manager/services/docker-postgres/default.nix index dfb8e0944..b74d1cdbf 100644 --- a/home-manager/services/docker-postgres/default.nix +++ b/home-manager/services/docker-postgres/default.nix @@ -6,7 +6,7 @@ }: let inherit (inputs.host) isGalactica isMatic; - enabled = !(isGalactica || isMatic); + enabled = isGalactica || isMatic; startScript = ./start-postgres.sh; # Smart wrapper that handles both NixOS and non-NixOS Linux diff --git a/home-manager/services/ollama/default.nix b/home-manager/services/ollama/default.nix index 34824999f..b63b6b5b4 100644 --- a/home-manager/services/ollama/default.nix +++ b/home-manager/services/ollama/default.nix @@ -1,4 +1,9 @@ -{ pkgs, lib, inputs, ... }: +{ + pkgs, + lib, + inputs, + ... +}: let inherit (inputs.host) isGalactica isMatic; enabled = isGalactica || isMatic; diff --git a/spec/clipboard_copy_spec.sh b/spec/clipboard_copy_spec.sh index 33944e7c0..27ad83c98 100644 --- a/spec/clipboard_copy_spec.sh +++ b/spec/clipboard_copy_spec.sh @@ -116,7 +116,7 @@ Before 'setup' After 'cleanup' It 'uses OSC 52 escape sequence' -When run bash "$SCRIPT" <<< "hello" +When run bash "$SCRIPT" <<<"hello" The status should be success The output should start with $'\033]52;c;' End diff --git a/spec/coverage_spec.sh b/spec/coverage_spec.sh index 2fa49562e..d3d583a0a 100644 --- a/spec/coverage_spec.sh +++ b/spec/coverage_spec.sh @@ -153,6 +153,10 @@ It 'has spec file for home-manager/modules/npm-globals/install-npm-globals.sh' The path "spec/npm_globals_spec.sh" should be exist End +It 'has spec file for home-manager/modules/secure-dotenv/secure-dotenv.sh' +The path "spec/secure_dotenv_spec.sh" should be exist +End + It 'has spec file for home-manager/modules/uv-globals/install-uv-globals.sh' The path "spec/uv_globals_spec.sh" should be exist End @@ -379,6 +383,7 @@ home-manager/modules/local-scripts/notify-local.sh home-manager/modules/local-scripts/pushover-notify.sh home-manager/modules/local-scripts/tmux-bridge.sh home-manager/modules/npm-globals/install-npm-globals.sh +home-manager/modules/secure-dotenv/secure-dotenv.sh home-manager/services/obsidian/obsidian-git-trigger.sh home-manager/services/obsidian/obsidian-headless.sh home-manager/services/openclaw/activate.sh diff --git a/spec/secure_dotenv_spec.sh b/spec/secure_dotenv_spec.sh new file mode 100644 index 000000000..0fe8df6c9 --- /dev/null +++ b/spec/secure_dotenv_spec.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2329,SC2016 + +Describe 'home-manager/modules/secure-dotenv/secure-dotenv.sh' +SCRIPT="$PWD/home-manager/modules/secure-dotenv/secure-dotenv.sh" + +Describe 'script properties' +It 'uses bash shebang' +When run bash -c "head -1 '$SCRIPT'" +The output should include '#!/usr/bin/env bash' +End + +It 'uses strict mode' +When run bash -c "head -5 '$SCRIPT'" +The output should include 'set -euo pipefail' +End + +It 'passes bash syntax check after stripping placeholders' +When run bash -c "sed 's|@[A-Za-z_][A-Za-z0-9_]*@|/usr/bin/test|g' '$SCRIPT' | bash -n" +The status should be success +End +End + +Describe 'placeholder substitutions' +It 'references @find@' +When run bash -c "grep '@find@' '$SCRIPT'" +The output should include '@find@' +End + +It 'references @stat@' +When run bash -c "grep '@stat@' '$SCRIPT'" +The output should include '@stat@' +End +End + +Describe 'requires HOME_DIR argument' +It 'reads HOME_DIR from $1' +When run bash -c "grep 'HOME_DIR=.\$1.' '$SCRIPT'" +The output should include 'HOME_DIR="$1"' +End +End + +Describe 'functional behavior' +setup() { + TEST_HOME="$(mktemp -d)" + # Create .env files with non-600 permissions + echo "SECRET=value" >"$TEST_HOME/.env" + chmod 644 "$TEST_HOME/.env" + + mkdir -p "$TEST_HOME/subdir" + echo "DB_URL=postgres://..." >"$TEST_HOME/subdir/.env.local" + chmod 755 "$TEST_HOME/subdir/.env.local" + + echo "KEY=val" >"$TEST_HOME/app.env" + chmod 644 "$TEST_HOME/app.env" + + # Create a file already at 600 + echo "OK=true" >"$TEST_HOME/.env.safe" + chmod 600 "$TEST_HOME/.env.safe" + + # Create a symlink (should be skipped) + ln -s "$TEST_HOME/.env" "$TEST_HOME/.env.link" + + # Preprocess the script, replacing placeholders with real commands + PROCESSED_SCRIPT="$TEST_HOME/secure-dotenv-test.sh" + sed \ + -e "s|@find@|$(command -v find)|g" \ + -e "s|@stat@|$(command -v stat)|g" \ + "$SCRIPT" >"$PROCESSED_SCRIPT" + chmod +x "$PROCESSED_SCRIPT" + + export TEST_HOME PROCESSED_SCRIPT +} +cleanup() { + rm -rf "$TEST_HOME" + unset TEST_HOME PROCESSED_SCRIPT +} +Before 'setup' +After 'cleanup' + +It 'changes .env from 644 to 600' +When run bash "$PROCESSED_SCRIPT" "$TEST_HOME" +The status should be success +End + +It 'changes .env.local from 755 to 600' +When run bash -c "bash '$PROCESSED_SCRIPT' '$TEST_HOME' && stat -c '%a' '$TEST_HOME/subdir/.env.local'" +The output should equal '600' +End + +It 'changes app.env from 644 to 600' +When run bash -c "bash '$PROCESSED_SCRIPT' '$TEST_HOME' && stat -c '%a' '$TEST_HOME/app.env'" +The output should equal '600' +End + +It 'leaves already-600 files unchanged' +When run bash -c "bash '$PROCESSED_SCRIPT' '$TEST_HOME' && stat -c '%a' '$TEST_HOME/.env.safe'" +The output should equal '600' +End + +It 'does not follow symlinks' +When run bash -c "bash '$PROCESSED_SCRIPT' '$TEST_HOME' && test -L '$TEST_HOME/.env.link' && echo 'still-symlink'" +The output should equal 'still-symlink' +End +End + +Describe 'depth limit' +It 'uses maxdepth 4' +When run bash -c "grep 'maxdepth 4' '$SCRIPT'" +The output should include 'maxdepth 4' +End +End + +End