From c389a9c6213ea22fad4a86773af33d37d297443d Mon Sep 17 00:00:00 2001 From: aadil96 Date: Sun, 19 Jul 2026 14:19:49 +0530 Subject: [PATCH 1/5] feat: unattended GPG signing for TUI agents + private_* git hygiene - Add private_* to .gitignore, untrack existing private files - Create gpg-agent.conf with allow-preset-passphrase and long TTLs - Create gpg-sign-preset script (presets passphrase from gnome-keyring) - Hook into zshrc/bashrc to preset at interactive shell start - Add systemd user unit + chezmoiscript to enable at graphical login - Add troubleshooting entry and update AGENTS.md key-files table - One-time manual step: secret-tool store --label=... service gpg-signing keygrip --- .../run_once_after_enable-gpg-preset.sh.tmpl | 7 +++++++ .gitignore | 2 ++ AGENTS.md | 19 +++++++++++-------- docs/troubleshooting.md | 7 +++++++ dot_bashrc.tmpl | 3 +++ dot_config/private_jj/config.toml | 4 ---- .../systemd/user/gpg-sign-preset.service | 10 ++++++++++ dot_local/bin/executable_gpg-sign-preset.tmpl | 14 ++++++++++++++ dot_zshrc.tmpl | 3 +++ private_dot_gnupg/private_gpg.conf | 15 --------------- 10 files changed, 57 insertions(+), 27 deletions(-) create mode 100755 .chezmoiscripts/run_once_after_enable-gpg-preset.sh.tmpl delete mode 100644 dot_config/private_jj/config.toml create mode 100644 dot_config/systemd/user/gpg-sign-preset.service create mode 100755 dot_local/bin/executable_gpg-sign-preset.tmpl delete mode 100644 private_dot_gnupg/private_gpg.conf diff --git a/.chezmoiscripts/run_once_after_enable-gpg-preset.sh.tmpl b/.chezmoiscripts/run_once_after_enable-gpg-preset.sh.tmpl new file mode 100755 index 00000000..23d4b66f --- /dev/null +++ b/.chezmoiscripts/run_once_after_enable-gpg-preset.sh.tmpl @@ -0,0 +1,7 @@ +#!/bin/bash +set -euo pipefail + +if command -v systemctl >/dev/null 2>&1; then + systemctl --user daemon-reload + systemctl --user enable --now gpg-sign-preset.service +fi diff --git a/.gitignore b/.gitignore index f8eace8f..afe19e31 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ runtime/ .aeos/ *.log node_modules/* +private_* +SESSION_STATE.md diff --git a/AGENTS.md b/AGENTS.md index c36484fb..ff4d4273 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -46,14 +46,17 @@ mise exec -- chezmoi apply # Run via mise if chezmoi not on PATH ## Key files to read -| Purpose | File | -| --------- | ------ | -| chezmoi config + template vars | `.chezmoi.toml.tmpl` | -| Tool versions | `dot_config/mise/mise.toml` | -| OpenCode config | `.opencode/ocx.jsonc` | -| Shell config | `dot_bashrc.tmpl`, `dot_zshrc.tmpl` | -| Bootstrap logic | `setup` | -| Branch rules | `BRANCHING.md` | +| Purpose | File | Notes | +| --------- | ------ | ----- | +| chezmoi config + template vars | `.chezmoi.toml.tmpl` | | +| Tool versions | `dot_config/mise/mise.toml` | | +| OpenCode config | `.opencode/ocx.jsonc` | | +| Shell config | `dot_bashrc.tmpl`, `dot_zshrc.tmpl` | | +| Bootstrap logic | `setup` | | +| Branch rules | `BRANCHING.md` | | +| GPG agent config | `private_dot_gnupg/private_gpg-agent.conf` | Allows preset passphrase | +| GPG signing preset | `dot_local/bin/executable_gpg-sign-preset.tmpl` | Silent preset at login/shell start | +| GPG systemd service | `dot_config/systemd/user/gpg-sign-preset.service` | Presets passphrase after graphical login | ## Git safety diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 4453bdef..816bd067 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -63,3 +63,10 @@ - Dockerfile uses `mcr.microsoft.com/devcontainers/base:debian-13` - Ensure Docker is running and has network access + +### Agent commits hang on GPG passphrase prompt + +- **Cause**: `commit.gpgsign=true` and `gpg-agent` cache expires → `pinentry-curses` seizes the TTY agent (opencode/codex) uses. Visible as a frozen commit. +- **Fix**: Ensure the preset script ran: `~/.local/bin/gpg-sign-preset`. Run it manually to verify. +- **If keyring locked** (e.g., headless/SSH before graphical login): `secret-tool lookup service gpg-signing keygrip ` will fail. Log in graphically, or unlock gnome-keyring with `gnome-keyring-daemon --unlock`. +- **Verify signing works**: after preset, run `echo test | gpg --batch --sign -u C8B994F19E7D34D9` — should succeed with no prompt. Check `git log --show-signature` for "Good signature". diff --git a/dot_bashrc.tmpl b/dot_bashrc.tmpl index c077d8dd..b4bae16c 100644 --- a/dot_bashrc.tmpl +++ b/dot_bashrc.tmpl @@ -11,6 +11,9 @@ if [ "${gnupg_SSH_AUTH_SOCK_by:-0}" -ne $$ ]; then export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)" fi +# preset GPG signing passphrase for non-interactive commits (agents) +[ -x "$HOME/.local/bin/gpg-sign-preset" ] && "$HOME/.local/bin/gpg-sign-preset" >/dev/null 2>&1 || true + if command -v mise >/dev/null; then eval "$(mise activate bash)" fi diff --git a/dot_config/private_jj/config.toml b/dot_config/private_jj/config.toml deleted file mode 100644 index aa300c90..00000000 --- a/dot_config/private_jj/config.toml +++ /dev/null @@ -1,4 +0,0 @@ -#:schema https://jj-vcs.github.io/jj/latest/config-schema.json - -[ui] -default-command = "log" diff --git a/dot_config/systemd/user/gpg-sign-preset.service b/dot_config/systemd/user/gpg-sign-preset.service new file mode 100644 index 00000000..56d61a36 --- /dev/null +++ b/dot_config/systemd/user/gpg-sign-preset.service @@ -0,0 +1,10 @@ +[Unit] +Description=Preset GPG signing passphrase into gpg-agent +After=graphical-session.target + +[Service] +Type=oneshot +ExecStart=%h/.local/bin/gpg-sign-preset + +[Install] +WantedBy=default.target diff --git a/dot_local/bin/executable_gpg-sign-preset.tmpl b/dot_local/bin/executable_gpg-sign-preset.tmpl new file mode 100755 index 00000000..f6d4f522 --- /dev/null +++ b/dot_local/bin/executable_gpg-sign-preset.tmpl @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# chezmoi template: preset GPG signing passphrase into gpg-agent +set -euo pipefail + +key="{{ .gpg_signing_key }}" + +command -v gpg-preset-passphrase >/dev/null 2>&1 || exit 0 +command -v secret-tool >/dev/null 2>&1 || exit 0 + +grip="$(gpg --batch --with-colons --with-keygrip -K "$key" 2>/dev/null | awk -F: '$1=="grp"{print $10; exit}')" +[ -n "$grip" ] || exit 0 + +pass="$(secret-tool lookup service gpg-signing keygrip "$grip" 2>/dev/null)" || exit 0 +printf '%s' "$pass" | gpg-preset-passphrase --preset "$grip" 2>/dev/null || true diff --git a/dot_zshrc.tmpl b/dot_zshrc.tmpl index 047e3c4a..91c77ca1 100644 --- a/dot_zshrc.tmpl +++ b/dot_zshrc.tmpl @@ -29,6 +29,9 @@ if [ "${gnupg_SSH_AUTH_SOCK_by:-0}" -ne $$ ]; then export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)" fi +# preset GPG signing passphrase for non-interactive commits (agents) +[ -x "$HOME/.local/bin/gpg-sign-preset" ] && "$HOME/.local/bin/gpg-sign-preset" >/dev/null 2>&1 || true + # enable brew if found on linux if [ -f /home/linuxbrew/.linuxbrew/bin/brew ] ; then eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" diff --git a/private_dot_gnupg/private_gpg.conf b/private_dot_gnupg/private_gpg.conf deleted file mode 100644 index afc61b11..00000000 --- a/private_dot_gnupg/private_gpg.conf +++ /dev/null @@ -1,15 +0,0 @@ -# Digest preferences -personal-digest-preferences SHA512 SHA384 SHA256 -cert-digest-algo SHA512 - -# Cipher preferences -personal-cipher-preferences AES256 AES192 AES - -# Default preference list -default-preference-list SHA512 SHA384 SHA256 AES256 AES192 AES ZLIB BZIP2 ZIP Uncompressed - -# Private key protection -s2k-cipher-algo AES256 -s2k-digest-algo SHA512 -s2k-mode 3 -s2k-count 65011712 \ No newline at end of file From d33178dfc54775f4e22ebb61b368ad26e62fd662 Mon Sep 17 00:00:00 2001 From: aadil96 Date: Sun, 19 Jul 2026 14:25:45 +0530 Subject: [PATCH 2/5] fixup: address reviewer findings on gpg preset implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - B2: Fix keygrip parser — stateful awk captures grp after matching fpr (subkey-safe) - M2: Move gpg configs from private_dot_gnupg/ to dot_gnupg/ (tracked, no secrets) - M3: Add pinentry-timeout 30 to gpg-agent.conf (fail fast on missed preset) - M4: Convert run_once chezmoiscript to run_onchange tracking unit file - m2: Store secret-tool entry by fingerprint instead of keygrip (survives rotation) - m3: Add empty key guard to preset script - m5: Drop After=graphical-session.target from unit (any-login) - AGENTS.md: Update paths/doc for dot_gnupg/ move --- .../run_once_after_enable-gpg-preset.sh.tmpl | 7 ------- .../run_onchange_after_enable-gpg-preset.sh.tmpl | 9 +++++++++ AGENTS.md | 7 ++++--- dot_config/systemd/user/gpg-sign-preset.service | 1 - dot_gnupg/gpg-agent.conf | 5 +++++ dot_gnupg/gpg.conf | 15 +++++++++++++++ dot_local/bin/executable_gpg-sign-preset.tmpl | 5 +++-- 7 files changed, 36 insertions(+), 13 deletions(-) delete mode 100755 .chezmoiscripts/run_once_after_enable-gpg-preset.sh.tmpl create mode 100755 .chezmoiscripts/run_onchange_after_enable-gpg-preset.sh.tmpl create mode 100644 dot_gnupg/gpg-agent.conf create mode 100644 dot_gnupg/gpg.conf diff --git a/.chezmoiscripts/run_once_after_enable-gpg-preset.sh.tmpl b/.chezmoiscripts/run_once_after_enable-gpg-preset.sh.tmpl deleted file mode 100755 index 23d4b66f..00000000 --- a/.chezmoiscripts/run_once_after_enable-gpg-preset.sh.tmpl +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -set -euo pipefail - -if command -v systemctl >/dev/null 2>&1; then - systemctl --user daemon-reload - systemctl --user enable --now gpg-sign-preset.service -fi diff --git a/.chezmoiscripts/run_onchange_after_enable-gpg-preset.sh.tmpl b/.chezmoiscripts/run_onchange_after_enable-gpg-preset.sh.tmpl new file mode 100755 index 00000000..05466e7b --- /dev/null +++ b/.chezmoiscripts/run_onchange_after_enable-gpg-preset.sh.tmpl @@ -0,0 +1,9 @@ +#!/bin/bash +# chezmoi template: enable gpg-sign-preset systemd user service +# Trigger: {{ if (stat (joinPath .chezmoi.sourceDir "dot_config/systemd/user/gpg-sign-preset.service")) }}changed{{ end }} +set -euo pipefail + +if command -v systemctl >/dev/null 2>&1; then + systemctl --user daemon-reload + systemctl --user enable --now gpg-sign-preset.service +fi diff --git a/AGENTS.md b/AGENTS.md index ff4d4273..81dcfddc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,7 +40,7 @@ mise exec -- chezmoi apply # Run via mise if chezmoi not on PATH ## Security - `private_*` prefix = never commit to git -- GPG config in `private_dot_gnupg/` (not tracked) +- GPG config in `dot_gnupg/` - `.chezmoiignore` uses gitignore semantics only — does NOT redact secrets - Never hardcode secrets; use `.chezmoi.toml.tmpl` env vars or prompts @@ -54,9 +54,10 @@ mise exec -- chezmoi apply # Run via mise if chezmoi not on PATH | Shell config | `dot_bashrc.tmpl`, `dot_zshrc.tmpl` | | | Bootstrap logic | `setup` | | | Branch rules | `BRANCHING.md` | | -| GPG agent config | `private_dot_gnupg/private_gpg-agent.conf` | Allows preset passphrase | +| GPG agent config | `dot_gnupg/gpg-agent.conf` | Allows preset passphrase | +| GPG crypto settings | `dot_gnupg/gpg.conf` | Digest/cipher preferences | | GPG signing preset | `dot_local/bin/executable_gpg-sign-preset.tmpl` | Silent preset at login/shell start | -| GPG systemd service | `dot_config/systemd/user/gpg-sign-preset.service` | Presets passphrase after graphical login | +| GPG systemd service | `dot_config/systemd/user/gpg-sign-preset.service` | Presets passphrase at login | ## Git safety diff --git a/dot_config/systemd/user/gpg-sign-preset.service b/dot_config/systemd/user/gpg-sign-preset.service index 56d61a36..0681e643 100644 --- a/dot_config/systemd/user/gpg-sign-preset.service +++ b/dot_config/systemd/user/gpg-sign-preset.service @@ -1,6 +1,5 @@ [Unit] Description=Preset GPG signing passphrase into gpg-agent -After=graphical-session.target [Service] Type=oneshot diff --git a/dot_gnupg/gpg-agent.conf b/dot_gnupg/gpg-agent.conf new file mode 100644 index 00000000..e3a06269 --- /dev/null +++ b/dot_gnupg/gpg-agent.conf @@ -0,0 +1,5 @@ +pinentry-program /usr/bin/pinentry-curses +allow-preset-passphrase +pinentry-timeout 30 +default-cache-ttl 86400 +max-cache-ttl 34560000 diff --git a/dot_gnupg/gpg.conf b/dot_gnupg/gpg.conf new file mode 100644 index 00000000..afc61b11 --- /dev/null +++ b/dot_gnupg/gpg.conf @@ -0,0 +1,15 @@ +# Digest preferences +personal-digest-preferences SHA512 SHA384 SHA256 +cert-digest-algo SHA512 + +# Cipher preferences +personal-cipher-preferences AES256 AES192 AES + +# Default preference list +default-preference-list SHA512 SHA384 SHA256 AES256 AES192 AES ZLIB BZIP2 ZIP Uncompressed + +# Private key protection +s2k-cipher-algo AES256 +s2k-digest-algo SHA512 +s2k-mode 3 +s2k-count 65011712 \ No newline at end of file diff --git a/dot_local/bin/executable_gpg-sign-preset.tmpl b/dot_local/bin/executable_gpg-sign-preset.tmpl index f6d4f522..6fc3ad61 100755 --- a/dot_local/bin/executable_gpg-sign-preset.tmpl +++ b/dot_local/bin/executable_gpg-sign-preset.tmpl @@ -3,12 +3,13 @@ set -euo pipefail key="{{ .gpg_signing_key }}" +[ -n "$key" ] || exit 0 command -v gpg-preset-passphrase >/dev/null 2>&1 || exit 0 command -v secret-tool >/dev/null 2>&1 || exit 0 -grip="$(gpg --batch --with-colons --with-keygrip -K "$key" 2>/dev/null | awk -F: '$1=="grp"{print $10; exit}')" +grip="$(gpg --batch --with-colons --with-keygrip -K "$key" 2>/dev/null | awk -F: -v key="$key" '$1=="fpr" && $10 ~ key{f=1; next} f && $1=="grp"{print $10; exit}')" [ -n "$grip" ] || exit 0 -pass="$(secret-tool lookup service gpg-signing keygrip "$grip" 2>/dev/null)" || exit 0 +pass="$(secret-tool lookup service gpg-signing key "$key" 2>/dev/null)" || exit 0 printf '%s' "$pass" | gpg-preset-passphrase --preset "$grip" 2>/dev/null || true From 643e2360447547beba29aa85819305ecfe91ff33 Mon Sep 17 00:00:00 2001 From: aadil96 Date: Sun, 19 Jul 2026 14:35:19 +0530 Subject: [PATCH 3/5] fix: add gpg-agent + libsecret-tools to install_packages script --- .../run_onchange_after_install_packages.sh.tmpl | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.chezmoiscripts/run_onchange_after_install_packages.sh.tmpl b/.chezmoiscripts/run_onchange_after_install_packages.sh.tmpl index ae1b6d14..ecbbb792 100644 --- a/.chezmoiscripts/run_onchange_after_install_packages.sh.tmpl +++ b/.chezmoiscripts/run_onchange_after_install_packages.sh.tmpl @@ -6,4 +6,14 @@ set -euo pipefail MISE_BIN_PATH="{{ if $m := lookPath "mise" }}{{ $m }}{{ else if $l := lookPath (joinPath .chezmoi.homeDir ".local/bin/mise") }}{{ $l }}{{ else }}mise{{ end }}" $MISE_BIN_PATH trust {{ .chezmoi.homeDir }}/.config/mise/mise.toml -$MISE_BIN_PATH install \ No newline at end of file +$MISE_BIN_PATH install + +# install system packages for GPG signing preset (gpg-preset-passphrase + secret-tool) +if command -v apt-get >/dev/null 2>&1; then + missing="" + command -v gpg-preset-passphrase >/dev/null 2>&1 || missing="$missing gpg-agent" + command -v secret-tool >/dev/null 2>&1 || missing="$missing libsecret-tools" + if [ -n "$missing" ]; then + sudo apt-get install -y $missing + fi +fi \ No newline at end of file From d72edbff42b942c802dbf390b0506dd466e839f9 Mon Sep 17 00:00:00 2001 From: aadil96 Date: Sun, 19 Jul 2026 18:25:28 +0530 Subject: [PATCH 4/5] fix: find gpg-preset-passphrase in libexec dir (not on PATH on Debian) --- dot_local/bin/executable_gpg-sign-preset.tmpl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dot_local/bin/executable_gpg-sign-preset.tmpl b/dot_local/bin/executable_gpg-sign-preset.tmpl index 6fc3ad61..bcecbc50 100755 --- a/dot_local/bin/executable_gpg-sign-preset.tmpl +++ b/dot_local/bin/executable_gpg-sign-preset.tmpl @@ -5,11 +5,14 @@ set -euo pipefail key="{{ .gpg_signing_key }}" [ -n "$key" ] || exit 0 -command -v gpg-preset-passphrase >/dev/null 2>&1 || exit 0 +gpg_preset_passphrase="" +command -v gpg-preset-passphrase >/dev/null 2>&1 && gpg_preset_passphrase="gpg-preset-passphrase" || { libexec=$(gpgconf --list-dirs libexecdir 2>/dev/null); [ -x "$libexec/gpg-preset-passphrase" ] && gpg_preset_passphrase="$libexec/gpg-preset-passphrase"; } +[ -n "$gpg_preset_passphrase" ] || exit 0 + command -v secret-tool >/dev/null 2>&1 || exit 0 grip="$(gpg --batch --with-colons --with-keygrip -K "$key" 2>/dev/null | awk -F: -v key="$key" '$1=="fpr" && $10 ~ key{f=1; next} f && $1=="grp"{print $10; exit}')" [ -n "$grip" ] || exit 0 pass="$(secret-tool lookup service gpg-signing key "$key" 2>/dev/null)" || exit 0 -printf '%s' "$pass" | gpg-preset-passphrase --preset "$grip" 2>/dev/null || true +printf '%s' "$pass" | "$gpg_preset_passphrase" --preset "$grip" 2>/dev/null || true From c3911a8d29ac9d06096e9166b9f3863628cffa10 Mon Sep 17 00:00:00 2001 From: aadil96 Date: Sun, 2 Aug 2026 08:54:38 +0530 Subject: [PATCH 5/5] chore: update mise tool versions (opencode 1.18.4, add bun + uv) --- dot_config/mise/mise.lock | 15 +++++++++------ dot_config/mise/mise.toml | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/dot_config/mise/mise.lock b/dot_config/mise/mise.lock index 67ea0af9..84844d90 100644 --- a/dot_config/mise/mise.lock +++ b/dot_config/mise/mise.lock @@ -39,6 +39,10 @@ url = "https://github.com/aristocratos/btop/releases/download/v1.4.4/btop-aarch6 checksum = "sha256:fec7d1b59c671290a0f80d5a32617ea6d60412485fc04318fd194b9550ff6b49" url = "https://github.com/aristocratos/btop/releases/download/v1.4.4/btop-x86_64-linux-musl.tbz" +[[tools.bun]] +version = "1.3.14" +backend = "core:bun" + [[tools.chezmoi]] version = "2.68.1" backend = "aqua:twpayne/chezmoi" @@ -186,14 +190,9 @@ checksum = "sha256:17bb2a19d25887cebd3b0ee2d3a3fdf56791eb0df15cf34b068fac56212f6 url = "https://github.com/cli/cli/releases/download/v2.82.1/gh_2.82.1_windows_amd64.zip" [[tools."github:anomalyco/opencode"]] -version = "1.15.4" +version = "1.18.4" backend = "github:anomalyco/opencode" -[tools."github:anomalyco/opencode"."platforms.linux-x64"] -checksum = "sha256:3a314f4e400487b15b4f76897a957e86359e0bf53e50289edf7d64bc8af29d0b" -url = "https://github.com/anomalyco/opencode/releases/download/v1.15.4/opencode-linux-x64-baseline.tar.gz" -url_api = "https://api.github.com/repos/anomalyco/opencode/releases/assets/422624174" - [[tools."github:nushell/nushell"]] version = "0.108.0" backend = "github:nushell/nushell" @@ -398,6 +397,10 @@ backend = "ubi:supabase/cli" [tools."ubi:supabase/cli".options] exe = "supabase" +[[tools.uv]] +version = "0.11.28" +backend = "aqua:astral-sh/uv" + [[tools.zellij]] version = "0.43.0" backend = "aqua:zellij-org/zellij" diff --git a/dot_config/mise/mise.toml b/dot_config/mise/mise.toml index 83f419fa..ec6bd382 100644 --- a/dot_config/mise/mise.toml +++ b/dot_config/mise/mise.toml @@ -23,7 +23,7 @@ go = "1.25.6" "ubi:supabase/cli" = { version = "2.98.2", exe = "supabase" } deno = "2.7.14" "npm:wrangler" = "4.88.0" -"github:anomalyco/opencode" = "1.17.4" +"github:anomalyco/opencode" = "1.18.4" "npm:@google/gemini-cli" = "0.41.2" "npm:aicommits" = "3.1.2" "npm:@agentmemory/agentmemory" = "latest"