diff options
| author | 2026-04-21 01:23:46 +0100 | |
|---|---|---|
| committer | 2026-04-21 01:23:46 +0100 | |
| commit | 372b8b27a64179602a8c81fe9d12931ebb5b8cef (patch) | |
| tree | d0b7ccd2c11cf9f02fa422f2c95e64278690350c | |
| parent | 9f74c9a819396d766f735ec2cc3339fb1659a716 (diff) | |
| download | dotfiles-372b8b27a64179602a8c81fe9d12931ebb5b8cef.tar.gz dotfiles-372b8b27a64179602a8c81fe9d12931ebb5b8cef.tar.bz2 dotfiles-372b8b27a64179602a8c81fe9d12931ebb5b8cef.zip | |
feat(etc): drift detection + auto-enumerating deploy template
- `just etc-drift` reports /etc files modified from pacman defaults
(via pacman -Qii) and user-created files (via pacman -Qo), subtracting
already-managed paths and patterns listed in etc/.ignore.
- Refactor run_onchange_after_deploy-etc.sh.tmpl to enumerate files under
etc/ automatically via find; single combined hash via chezmoi output +
sha256sum, so new files only need to be dropped into etc/.
- etc/.ignore seeds noise filters: machine-id, ssh host keys, pacman
keyring, mirrorlist, shadow/passwd backups, sbctl keys, ca-certs.
| -rw-r--r-- | .github/copilot-instructions.md | 4 | ||||
| -rw-r--r-- | etc/.ignore | 35 | ||||
| -rw-r--r-- | justfile | 42 | ||||
| -rwxr-xr-x | run_onchange_after_deploy-etc.sh.tmpl | 26 |
4 files changed, 86 insertions, 21 deletions
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 44239ac..1e1b733 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -21,7 +21,7 @@ The repo root is a chezmoi source directory. Files targeting `$HOME` use chezmoi - `bootstrap.sh` at the repo root is a POSIX shell script that takes a fresh minimal Arch install (only `base`) to a fully deployed state. It installs prerequisites, enables `%wheel` sudoers, bootstraps `paru-bin` from the AUR, clones the repo, runs `just init`, and optionally invokes `create-efi`. Designed to be curlable: `curl -fsSL .../bootstrap.sh | sh`. - `.chezmoiignore` excludes non-home files (`etc/`, `meta/`, `systemd-units/`, `firefox/`, docs) from deployment to `$HOME`. - `.githooks/` contains git hooks (notably `post-commit` which runs `chezmoi apply`). Activated by `just init`. -- `justfile` provides recipes: `init` (first-time setup), `sync` (apply + fix), `apply`, `fix`, `status`, `pkg-drift`, `dotfile-drift`, `undeclared`, `diff`, `merge`, `groups`, `install`, `install-all`, `add`, `remove`, `services`, `services-enable`, `services-drift`. Run `just` or `just --list` to see them. +- `justfile` provides recipes: `init` (first-time setup), `sync` (apply + fix), `apply`, `fix`, `status`, `pkg-drift`, `dotfile-drift`, `undeclared`, `diff`, `merge`, `groups`, `install`, `install-all`, `add`, `remove`, `services`, `services-enable`, `services-drift`, `etc-drift`. Run `just` or `just --list` to see them. ## Window manager @@ -53,7 +53,7 @@ Additionally, `dot_config/sh/inputrc` provides readline config for non-zsh tools When modifying configs, use chezmoi naming conventions: `dot_` prefix for dotfiles, `private_` for restricted-permission dirs/files, `executable_` for scripts. To add a new config file, use `chezmoi add <target-path>`. -The `run_onchange_after_*` scripts are chezmoi templates (`.tmpl`) that embed `sha256sum` hashes of the files they deploy. Chezmoi only re-runs them when file content changes. When adding a new file to `etc/` or `firefox/`, you must add its hash comment and file path to the corresponding run script. +The `run_onchange_after_*` scripts are chezmoi templates (`.tmpl`) that embed `sha256sum` hashes of the files they deploy. Chezmoi only re-runs them when file content changes. The `etc` deploy script auto-enumerates every file under `etc/` (single combined hash via chezmoi's `output` function + `find`); just drop new files into `etc/` and they'll be deployed on next `chezmoi apply`. The `firefox` deploy script still lists its files explicitly. When editing shell config, all zsh configuration goes in `dot_config/zsh/` — do not create files in `dot_config/sh/` (only `inputrc` remains there). diff --git a/etc/.ignore b/etc/.ignore new file mode 100644 index 0000000..c15fb70 --- /dev/null +++ b/etc/.ignore @@ -0,0 +1,35 @@ +# Paths excluded from `just etc-drift` output. +# Shell-glob patterns (case $path in $pat) work here: *, ?, []. + +# Per-host state / auto-generated +/etc/machine-id +/etc/adjtime +/etc/.updated +/etc/.pwd.lock +/etc/mtab +/etc/ld.so.cache + +# Per-host identity / secrets +/etc/ssh/ssh_host_* +/etc/shadow +/etc/shadow- +/etc/gshadow +/etc/gshadow- +/etc/passwd- +/etc/group- + +# Regenerated by tools (not worth versioning) +/etc/resolv.conf +/etc/ssl/certs/* +/etc/ca-certificates/extracted/* +/etc/pacman.d/gnupg/* +/etc/pacman.d/mirrorlist + +# Managed by useradd (podman uses them) +/etc/subuid +/etc/subgid +/etc/subuid- +/etc/subgid- + +# sbctl signed-boot state (keys live here; never commit) +/etc/secureboot/* @@ -179,6 +179,48 @@ services-drift: # ═══════════════════════════════════════════════════════════════════ +# System config (/etc) +# ═══════════════════════════════════════════════════════════════════ + +# Show /etc drift: package configs modified from defaults, plus user-created files +etc-drift: + #!/usr/bin/env bash + set -eo pipefail + tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT + + find etc -type f ! -name .ignore 2>/dev/null \ + | sed 's|^etc/|/etc/|' | sort -u > "$tmp/managed" + + patterns=() + if [ -f etc/.ignore ]; then + while IFS= read -r line; do + [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue + patterns+=("$line") + done < etc/.ignore + fi + + keep() { + local path=$1 + grep -qxF "$path" "$tmp/managed" && return 1 + for pat in ${patterns[@]+"${patterns[@]}"}; do + [[ "$path" == $pat ]] && return 1 + done + return 0 + } + + echo "=== /etc drift ===" + echo "--- modified package configs ---" + pacman -Qii 2>/dev/null | grep -oP 'MODIFIED\t\K/\S+' | sort -u \ + | while IFS= read -r p; do keep "$p" && echo " modified: $p"; done + + echo "--- user-created (no owning package) ---" + find /etc -xdev -type f -print0 2>/dev/null \ + | xargs -0 pacman -Qo 2>&1 >/dev/null \ + | sed -n 's/^error: No package owns //p' | sort -u \ + | while IFS= read -r p; do keep "$p" && echo " unowned: $p"; done + + +# ═══════════════════════════════════════════════════════════════════ # Package management # ═══════════════════════════════════════════════════════════════════ diff --git a/run_onchange_after_deploy-etc.sh.tmpl b/run_onchange_after_deploy-etc.sh.tmpl index 225ceac..04f72c1 100755 --- a/run_onchange_after_deploy-etc.sh.tmpl +++ b/run_onchange_after_deploy-etc.sh.tmpl @@ -1,26 +1,14 @@ #!/bin/sh -# Deploy system-level configs from etc/ to /etc/ -# chezmoi re-runs this script when any hash below changes. -# {{ include "etc/doas.conf" | sha256sum }} -# {{ include "etc/modules-load.d/tcp_bbr.conf" | sha256sum }} -# {{ include "etc/pacman.d/hooks/orphans.hook" | sha256sum }} -# {{ include "etc/sysctl.d/99-sysctl.conf" | sha256sum }} -# {{ include "etc/systemd/system.conf.d/timeout.conf" | sha256sum }} -# {{ include "etc/xdg/reflector/reflector.conf" | sha256sum }} +# Deploy system-level configs from etc/ to /etc/. +# chezmoi re-runs this script whenever any file under etc/ changes. +# etc/ content hash: {{ output "sh" "-c" (printf "cd %q && find etc -type f ! -name .ignore -exec sha256sum {} + | LC_ALL=C sort" .chezmoi.sourceDir) | sha256sum }} set -eu -for f in \ - doas.conf \ - modules-load.d/tcp_bbr.conf \ - pacman.d/hooks/orphans.hook \ - sysctl.d/99-sysctl.conf \ - systemd/system.conf.d/timeout.conf \ - xdg/reflector/reflector.conf -do - doas mkdir -p "/etc/$(dirname "$f")" - doas cp --remove-destination "$CHEZMOI_SOURCE_DIR/etc/$f" "/etc/$f" +cd "$CHEZMOI_SOURCE_DIR" +find etc -type f ! -name .ignore | while IFS= read -r src; do + dest="/${src}" + doas install -D -m 0644 -o root -g root "$src" "$dest" done # doas refuses to parse /etc/doas.conf unless it's 0400 root:root -doas chown root:root /etc/doas.conf doas chmod 0400 /etc/doas.conf |
