Skip to content

Instantly share code, notes, and snippets.

@io41
Forked from wmudge/README.md
Last active June 19, 2026 15:02
Show Gist options
  • Select an option

  • Save io41/c4af3923c74c0da7596d2457009c6bc9 to your computer and use it in GitHub Desktop.

Select an option

Save io41/c4af3923c74c0da7596d2457009c6bc9 to your computer and use it in GitHub Desktop.
Create GPG Key, Managed by 1Password + 1Password pinentry for gpg-agent

Create GPG Key, Managed by 1Password

gpg-setup.sh makes 1Password the home and the live unlock agent for your GPG key. Run it once and the manual fiddling goes away:

  • It generates a new GPG key whose passphrase is a 64-character secret that 1Password creates — you never see it or type it.
  • It stores the public key, private key, and revocation certificate as documents in your 1Password vault, so a fresh machine is one download and gpg --import away.
  • It rewires gpg-agent so that every operation that needs your private key — signing and decryption — pulls the passphrase straight from 1Password through a small pinentry wrapper. Nothing to memorize, no passphrase on disk.
  • It sets 1Password's own expiry date to match the key's, so the item itself shows when the key is due for rotation.

After it runs, GPG just works: while 1Password is unlocked, signing and decryption succeed silently; when it's locked, you approve the 1Password CLI prompt once. There's nothing else to remember.

It's an interactive zsh script and it's deliberately careful — it verifies 1Password access first, shows existing keys before any deletion, requires an explicit typed confirmation to destroy anything, and prompts before each major change. Press Ctrl+C to abort at any point.

How the pieces connect

After setup, four things hold together with the 1Password item as the hub:

  • The passphrase lives in 1Password, in the credential field of an API Credential item — one reference, op://<vault>/<item>/credential, is the source of truth.
  • gpg-agent asks 1Password for it. The script installs a pinentry program (pinentry-1password.sh) and points gpg-agent.conf at it. When GPG needs the private key, the wrapper runs op read against that reference and hands the passphrase back. The wrapper stores only the location of the secret — never the secret itself.
  • The key material is backed up in the same vault as three document items (public, private, revocation), tagged gpg.
  • The passphrase item carries the metadata — fingerprint, key ID, user ID, the names of the three documents, and the expiry date — so one item describes the whole key.

The optional signing test at the end proves the wiring: it signs a message without loopback mode, so a success means gpg-agent really did reach 1Password through the wrapper rather than falling back to something else.

Requirements

  • zsh
  • gpg, gpgconf, and gpg-connect-agent
  • op, the 1Password CLI, signed in
  • jq
  • awk
  • date

Run it with:

./gpg-setup.sh

Interactive prompts use zsh line editing, so cursor movement and editing keys work normally.

What it does

The run is a sequence of confirmed steps. The ones that matter most:

  1. Picks the 1Password passphrase item and verifies access (creating the item if needed — see below).
  2. Shows your existing GPG secret keys and, only if you ask, deletes them.
  3. Generates a new key using the 1Password passphrase, then exports the public key, private key, and revocation certificate.
  4. Stores those three exports as 1Password documents and writes the key's metadata onto the passphrase item.
  5. Sets the 1Password expiry date to match the key's expiry.
  6. Installs the 1Password pinentry wrapper, points gpg-agent at it, and restarts the agent.
  7. Runs an optional signing test to confirm the integration is live.

The supporting steps — checking required commands and preparing a private temp directory for the exports — happen around these.

Choosing the 1Password item

The passphrase always lives in the credential field of a 1Password API Credential item — that's the item type with native valid from / expires fields, which is how 1Password tracks the key's expiry. The script prompts you for the vault and item; the field is always credential. The default is:

op://Employee/gpg_key/credential

You can pre-fill the prompts from the environment so you only have to press Enter:

export OP_GPG_VAULT="Employee"
export OP_GPG_ITEM="gpg_key"

If the item does not exist, the script offers to create it: it asks 1Password to generate a 64-character secret (letters, digits, symbols) and stores it in credential.

If the item does exist, it must already be an API Credential item — otherwise the script stops with a clear message rather than writing expiry data it can't track. The passphrase is never printed.

Expiry reminders

When you choose the key's expiry, the script maps it to a date and writes it to the item's native valid from and expires fields, which drive 1Password's own expiry reminders. It understands:

  • Nd, Nw, Nm, Ny (the default is 1y)
  • YYYY-MM-DD

For 0 (never) or any unsupported format, no 1Password date is set.

Safe by default

  • Existing keys are never deleted unless you answer yes and type DELETE-ALL-GPG-KEYS.
  • If you decline deletion, the new key is created alongside your existing keys.
  • The recommended key type is ed25519 with a cv25519 encryption subkey; rsa4096 is offered as an alternative.
  • The passphrase is passed to GPG over stdin with loopback pinentry — never written to a temporary passphrase or batch file.
  • Exports are created in a private temp directory (umask 077) and removed when the script exits.
  • It does not configure Git signing defaults, publish to a keyserver, or upload anything outside the selected vault.

Files it writes

The pinentry setup creates:

~/.local/bin/pinentry-1password.sh
~/.config/gpg-1password-pinentry/config.zsh
~/.gnupg/gpg-agent.conf

Before editing gpg-agent.conf, the script saves a timestamped backup next to the original. The config file holds only the 1Password reference, not the secret.

You can override the pinentry paths:

export PINENTRY_1PASSWORD_PATH="$HOME/.local/bin/pinentry-1password.sh"
export PINENTRY_1PASSWORD_CONFIG="$HOME/.config/gpg-1password-pinentry/config.zsh"

Recovery

If the pinentry setup causes trouble, restore the gpg-agent.conf backup or remove the pinentry-program line, then restart the agent:

gpg-connect-agent killagent /bye
gpg-connect-agent updatestartuptty /bye

To restore the private key on another machine, download the private key document from 1Password and import it:

gpg --import <private-key-file.asc>

Keep the private key export and revocation certificate private. Store them only in a vault with appropriate access controls.

Using it with an existing key

The script always generates a new key — there is no configure-only path. The pinentry wrapper it installs, however, is generic: it serves whatever key's passphrase sits at the configured op://… reference. So the automatic backup, metadata, and expiry tracking apply only to keys this script creates, while the 1Password-as-unlock-agent behavior works for any key whose passphrase you keep in 1Password at that reference.

Credits

#!/usr/bin/env zsh
set -euo pipefail
umask 077
step_no=1
work_dir=""
cleanup() {
if [[ -n "${work_dir:-}" && -d "$work_dir" ]]; then
rm -rf "$work_dir"
work_dir=""
fi
}
abort_script() {
local exit_code="${1:-130}"
print -u2 -- ""
print -u2 -- "Interrupted; exiting."
trap - EXIT INT TERM
cleanup
exit "$exit_code"
}
trap cleanup EXIT
trap 'abort_script 130' INT
trap 'abort_script 143' TERM
fail() {
print -u2 -- "ERROR: $*"
exit 1
}
step() {
print
print -- "== Step ${step_no}: $*"
(( step_no++ ))
}
prompt_input() {
local prompt="$1"
REPLY=""
if [[ -t 0 && -t 1 ]]; then
vared -p "$prompt" -c REPLY || abort_script 130
else
print -rn -- "$prompt"
IFS= read -r REPLY || fail "No input received for prompt: $prompt"
fi
}
ask_yes_no() {
local prompt="$1"
local default="${2:-n}"
local reply suffix
if [[ "$default" == "y" ]]; then
suffix="[Y/n]"
else
suffix="[y/N]"
fi
while true; do
prompt_input "${prompt} ${suffix} "
reply="${REPLY}"
reply="${reply:l}"
if [[ -z "$reply" ]]; then
[[ "$default" == "y" ]] && return 0 || return 1
fi
case "$reply" in
y|yes) return 0 ;;
n|no) return 1 ;;
*) print -- "Please answer yes or no." ;;
esac
done
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || fail "Missing required command: $1"
}
collect_secret_fprs() {
gpg --with-colons --fingerprint --list-secret-keys 2>/dev/null |
awk -F: '/^sec/ { want=1; next } want && /^fpr:/ { print $10; want=0 }'
}
contains_value() {
local needle="$1"
shift
local item
for item in "$@"; do
[[ "$item" == "$needle" ]] && return 0
done
return 1
}
gpg_with_passphrase() {
print -r -- "$passphrase" |
gpg --batch --yes --pinentry-mode loopback --passphrase-fd 0 "$@"
}
op_ref() {
print -r -- "op://${op_vault}/${op_item}/${op_field}"
}
generate_1password_passphrase() {
local generated
generated="$(
op item create --category=password --title "${op_item} generated passphrase preview" \
--vault "$op_vault" --generate-password='letters,digits,symbols,64' \
--dry-run --reveal --format json |
jq -r '.fields[] | select(.id == "password") | .value // empty'
)" || return 1
[[ -n "$generated" ]] || return 1
print -r -- "$generated"
}
create_1password_api_credential_item() {
local credential="$1"
print -rn -- "$credential" |
jq -Rs --arg title "$op_item" '{
title: $title,
category: "API_CREDENTIAL",
fields: [
{
id: "credential",
type: "CONCEALED",
label: "credential",
value: .
},
{
id: "notesPlain",
type: "STRING",
purpose: "NOTES",
label: "notesPlain",
value: "Created by gpg-setup.sh for a GPG key passphrase."
}
]
}' |
op item create --vault "$op_vault" --tags "gpg,gpg-key" - >/dev/null
}
date_add_iso() {
local amount="$1"
local unit="$2"
local mac_unit="$2"
local gnu_unit result
case "$unit" in
d) gnu_unit="days" ;;
w) gnu_unit="weeks" ;;
m) gnu_unit="months" ;;
y) gnu_unit="years" ;;
*) return 1 ;;
esac
if result="$(date "-v+${amount}${mac_unit}" +%F 2>/dev/null)"; then
print -r -- "$result"
return 0
fi
if result="$(date -d "+${amount} ${gnu_unit}" +%F 2>/dev/null)"; then
print -r -- "$result"
return 0
fi
return 1
}
gpg_expiry_to_iso() {
local value="$1"
local amount unit
if [[ "$value" == "0" ]]; then
return 1
fi
if [[ "$value" =~ "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" ]]; then
print -r -- "$value"
return 0
fi
if [[ "$value" =~ "^([0-9]+)([dwmy])$" ]]; then
amount="$match[1]"
unit="$match[2]"
date_add_iso "$amount" "$unit"
return $?
fi
return 1
}
update_1password_expiry() {
local expires="$1"
local valid_from="$2"
op item edit "$op_item" --vault "$op_vault" \
"validFrom[date]=$valid_from" \
"expires[date]=$expires" >/dev/null ||
fail "Could not set 1Password expiry on '$op_item'."
print -- "Set native 1Password expiry to $expires."
}
write_pinentry_script() {
local pinentry_path="$1"
local config_path="$2"
local vault="$3"
local item="$4"
local field="$5"
mkdir -p "${pinentry_path:h}" "${config_path:h}"
chmod 700 "${pinentry_path:h}" "${config_path:h}"
{
print -r -- "# Written by gpg-setup.sh. Contains no secret, only the 1Password item location."
print -r -- "OP_GPG_VAULT=${(qq)vault}"
print -r -- "OP_GPG_ITEM=${(qq)item}"
print -r -- "OP_GPG_FIELD=${(qq)field}"
} > "$config_path"
chmod 600 "$config_path"
cat > "$pinentry_path" <<'PINENTRY'
#!/usr/bin/env zsh
set -u
CONFIG_FILE="${PINENTRY_1PASSWORD_CONFIG:-$HOME/.config/gpg-1password-pinentry/config.zsh}"
LOG_FILE="${PINENTRY_1PASSWORD_LOG:-/dev/null}"
log_line() {
print -r -- "$*" >> "$LOG_FILE"
}
assuan_escape() {
local value="$1"
value="${value//\%/%25}"
value="${value//$'\r'/%0D}"
value="${value//$'\n'/%0A}"
print -r -- "$value"
}
read_passphrase() {
[[ -r "$CONFIG_FILE" ]] && source "$CONFIG_FILE"
local vault="${OP_GPG_VAULT:?OP_GPG_VAULT is not set}"
local item="${OP_GPG_ITEM:?OP_GPG_ITEM is not set}"
local field="${OP_GPG_FIELD:-credential}"
op read "op://${vault}/${item}/${field}"
}
print -r -- "OK"
while IFS= read -r line; do
cmd="${line%% *}"
log_line "$(date '+%Y-%m-%d %H:%M:%S') ${line}"
case "$cmd" in
GETPIN)
if secret="$(read_passphrase 2>>"$LOG_FILE")"; then
print -r -- "D $(assuan_escape "$secret")"
print -r -- "OK"
else
print -r -- "ERR 83886179 1Password lookup failed"
fi
;;
BYE)
print -r -- "OK"
exit 0
;;
\#*|OPTION|SETDESC|SETPROMPT|SETTITLE|SETKEYINFO|SETOK|SETCANCEL|SETNOTOK|SETERROR)
print -r -- "OK"
;;
CONFIRM)
print -r -- "OK"
;;
*)
print -r -- "OK"
;;
esac
done
PINENTRY
chmod 700 "$pinentry_path"
}
configure_gpg_agent() {
local agent_conf="$1"
local pinentry_path="$2"
local backup tmp
mkdir -p "${agent_conf:h}"
chmod 700 "${agent_conf:h}"
if [[ -f "$agent_conf" ]]; then
backup="${agent_conf}.backup.$(date +%Y%m%d-%H%M%S)"
cp -p "$agent_conf" "$backup"
print -- "Backed up existing gpg-agent.conf to: $backup"
fi
tmp="${agent_conf}.tmp.$$"
if [[ -f "$agent_conf" ]]; then
awk '
/^[[:space:]]*pinentry-program([[:space:]]|$)/ { next }
/^[[:space:]]*allow-loopback-pinentry([[:space:]]|$)/ { next }
{ print }
' "$agent_conf" > "$tmp"
else
: > "$tmp"
fi
{
print -r -- ""
print -r -- "# Added by gpg-setup.sh on $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
print -r -- "pinentry-program $pinentry_path"
print -r -- "allow-loopback-pinentry"
} >> "$tmp"
mv "$tmp" "$agent_conf"
chmod 600 "$agent_conf"
}
restart_gpg_agent() {
gpg-connect-agent killagent /bye >/dev/null 2>&1 || true
gpg-connect-agent updatestartuptty /bye >/dev/null 2>&1 || true
}
step "Check required commands"
require_cmd gpg
require_cmd gpgconf
require_cmd gpg-connect-agent
require_cmd op
require_cmd awk
require_cmd date
require_cmd jq
print -- "Found required commands."
step "Choose the 1Password item that holds the GPG passphrase"
default_vault="${OP_GPG_VAULT:-Employee}"
default_item="${OP_GPG_ITEM:-gpg_key}"
op_field="credential"
prompt_input "1Password vault [${default_vault}]: "
op_vault="${REPLY}"
op_vault="${op_vault:-$default_vault}"
prompt_input "1Password item [${default_item}]: "
op_item="${REPLY}"
op_item="${op_item:-$default_item}"
print -- "Passphrase source: $(op_ref)"
print -- "The passphrase is stored as an API Credential and used without printing it."
ask_yes_no "Verify access to this 1Password item now?" "y" || fail "Stopped before verifying 1Password access."
if existing_category="$(op item get "$op_item" --vault "$op_vault" --format json 2>/dev/null | jq -r '.category // empty')"; then
[[ "$existing_category" == "API_CREDENTIAL" ]] ||
fail "Item '$op_item' in vault '$op_vault' is a ${existing_category:-non-API-Credential} item. This script keeps the passphrase in the 'credential' field of an API Credential item so 1Password can track the key's expiry. Choose a different item name or convert the existing one."
else
print -- "Could not find item '$op_item' in 1Password vault '$op_vault'."
print -- "It will be created as an API Credential item so 1Password can track the key's expiry."
if ask_yes_no "Create '$op_item' now with a 1Password-generated 64-character credential?" "y"; then
generated_passphrase="$(generate_1password_passphrase)" ||
fail "Could not generate a 1Password passphrase preview."
create_1password_api_credential_item "$generated_passphrase" ||
fail "Could not create 1Password item '$op_item' in vault '$op_vault'."
unset generated_passphrase
print -- "Created $(op_ref) with a 1Password-generated credential."
else
fail "Stopped because the 1Password item does not exist."
fi
fi
passphrase="$(op read "$(op_ref)")" ||
fail "Could not verify access to $(op_ref). Unlock/sign in to 1Password CLI and retry."
[[ -n "$passphrase" ]] || fail "The 1Password field is empty."
[[ "$passphrase" != *$'\n'* ]] || fail "The passphrase contains a newline; this script expects a single-line passphrase."
print -- "1Password secret read successfully. The passphrase was not printed."
step "Review existing GPG secret keys"
gpg_home="$(gpgconf --list-dirs homedir)"
print -- "GPG home: $gpg_home"
if gpg --list-secret-keys --keyid-format LONG; then
existing_fprs=(${(f)"$(collect_secret_fprs)"})
else
existing_fprs=()
fi
if (( ${#existing_fprs[@]} == 0 )); then
print -- "No existing secret keys found."
else
print
print -- "The script can delete every secret key listed above, plus its matching public key."
print -- "This is destructive. Only continue if these are the forgotten-passphrase keys you want to replace."
if ask_yes_no "Delete all listed secret/public keys?" "n"; then
prompt_input "Type DELETE-ALL-GPG-KEYS to confirm deletion: "
delete_confirm="${REPLY}"
[[ "$delete_confirm" == "DELETE-ALL-GPG-KEYS" ]] || fail "Deletion confirmation did not match; stopped."
for fpr in "${existing_fprs[@]}"; do
print -- "Deleting $fpr"
gpg --batch --yes --delete-secret-and-public-key "$fpr"
done
else
print -- "Skipped key deletion."
fi
fi
step "Choose new GPG key details"
if command -v git >/dev/null 2>&1; then
default_name="$(git config --global user.name 2>/dev/null || true)"
default_email="$(git config --global user.email 2>/dev/null || true)"
else
default_name=""
default_email=""
fi
default_expiry="1y"
prompt_input "Name for the new key${default_name:+ [$default_name]}: "
real_name="${REPLY}"
real_name="${real_name:-$default_name}"
[[ -n "$real_name" ]] || fail "Name is required."
prompt_input "Email for the new key${default_email:+ [$default_email]}: "
email="${REPLY}"
email="${email:-$default_email}"
[[ -n "$email" ]] || fail "Email is required."
prompt_input "Optional comment, usually blank: "
comment="${REPLY}"
prompt_input "Expiry, for example 1y, 2y, 0 for never [${default_expiry}]: "
expiry="${REPLY}"
expiry="${expiry:-$default_expiry}"
onepassword_valid_from="$(date +%F)"
onepassword_expiry_date=""
if onepassword_expiry_date="$(gpg_expiry_to_iso "$expiry")"; then
:
else
onepassword_expiry_date=""
fi
print -- "Key type:"
print -- " 1) ed25519 primary key + cv25519 encryption subkey (recommended)"
print -- " 2) rsa4096 primary key + rsa4096 encryption subkey"
prompt_input "Choose key type [1]: "
key_choice="${REPLY}"
key_choice="${key_choice:-1}"
case "$key_choice" in
1) key_type="ed25519" ;;
2) key_type="rsa4096" ;;
*) fail "Unknown key type choice: $key_choice" ;;
esac
uid="$real_name"
[[ -n "$comment" ]] && uid+=" ($comment)"
uid+=" <$email>"
print
print -- "New key summary:"
print -- " User ID: $uid"
print -- " Expiry: $expiry"
if [[ -n "$onepassword_expiry_date" ]]; then
print -- " 1Password expiry: $onepassword_expiry_date"
else
print -- " 1Password expiry: not set for '$expiry'"
fi
print -- " Type: $key_type"
ask_yes_no "Generate this new GPG key using the 1Password passphrase?" "y" || fail "Stopped before key generation."
step "Generate new GPG key"
work_dir="$(mktemp -d "${TMPDIR:-/tmp}/gpg-setup.XXXXXX")"
chmod 700 "$work_dir"
keygen_log="$work_dir/keygen.log"
subkey_log="$work_dir/subkey.log"
before_fprs=(${(f)"$(collect_secret_fprs)"})
if [[ "$key_type" == "rsa4096" ]]; then
primary_algo="rsa4096"
subkey_algo="rsa4096"
else
primary_algo="ed25519"
subkey_algo="cv25519"
fi
if ! gpg_with_passphrase --status-fd=1 --quick-generate-key "$uid" "$primary_algo" sign "$expiry" > "$keygen_log" 2>&1; then
print -u2 -- "GPG key generation failed. Output:"
sed 's/^/ /' "$keygen_log" >&2
fail "Stopped after failed key generation."
fi
new_fpr="$(awk '/^\[GNUPG:\] KEY_CREATED/ { print $4; exit }' "$keygen_log")"
if [[ -z "$new_fpr" ]]; then
after_fprs=(${(f)"$(collect_secret_fprs)"})
new_fprs=()
for fpr in "${after_fprs[@]}"; do
if ! contains_value "$fpr" "${before_fprs[@]}"; then
new_fprs+=("$fpr")
fi
done
(( ${#new_fprs[@]} == 1 )) || fail "Could not determine the new key fingerprint automatically."
new_fpr="$new_fprs[1]"
fi
if ! gpg_with_passphrase --quick-add-key "$new_fpr" "$subkey_algo" encr "$expiry" > "$subkey_log" 2>&1; then
print -u2 -- "GPG encryption subkey generation failed. Output:"
sed 's/^/ /' "$subkey_log" >&2
fail "Stopped after failed subkey generation. The primary key was created: $new_fpr"
fi
key_id="${new_fpr[-16,-1]}"
short_fpr="${new_fpr[-12,-1]}"
print -- "Created new key:"
print -- " Fingerprint: $new_fpr"
print -- " Key ID: $key_id"
step "Set 1Password expiry reminder"
if [[ -n "$onepassword_expiry_date" ]]; then
update_1password_expiry "$onepassword_expiry_date" "$onepassword_valid_from"
else
print -- "Skipped 1Password expiry reminder because '$expiry' does not map to a reminder date."
fi
step "Export public key, private key, and revocation certificate"
public_file="$work_dir/${short_fpr}-public.asc"
private_file="$work_dir/${short_fpr}-private.asc"
revocation_source="${gpg_home}/openpgp-revocs.d/${new_fpr}.rev"
revocation_file="$work_dir/${short_fpr}-revocation-cert.rev"
gpg --armor --export "$new_fpr" > "$public_file"
gpg_with_passphrase --armor --export-secret-keys "$new_fpr" > "$private_file"
[[ -r "$revocation_source" ]] ||
fail "Could not find the auto-generated revocation certificate at $revocation_source"
cp "$revocation_source" "$revocation_file"
chmod 600 "$public_file" "$private_file" "$revocation_file"
print -- "Exports prepared in a temporary private directory. They will be removed from disk when the script exits."
step "Store exported artifacts in 1Password"
timestamp="$(date +%Y%m%d-%H%M%S)"
doc_prefix="${op_item} ${short_fpr} ${timestamp}"
public_title="${doc_prefix} public key"
private_title="${doc_prefix} private key"
revocation_title="${doc_prefix} revocation certificate"
print -- "The following 1Password document items will be created in vault '$op_vault':"
print -- " $public_title"
print -- " $private_title"
print -- " $revocation_title"
ask_yes_no "Create these 1Password documents now?" "y" || fail "Stopped before storing artifacts in 1Password."
op document create "$public_file" --vault "$op_vault" --title "$public_title" --tags "gpg,gpg-key" >/dev/null
op document create "$private_file" --vault "$op_vault" --title "$private_title" --tags "gpg,gpg-key,secret" >/dev/null
op document create "$revocation_file" --vault "$op_vault" --title "$revocation_title" --tags "gpg,gpg-key,revocation" >/dev/null
print -- "Created 1Password document items."
if ask_yes_no "Add the new key metadata to op://${op_vault}/${op_item}?" "y"; then
op item edit "$op_item" --vault "$op_vault" \
"GPG.fingerprint=$new_fpr" \
"GPG.key_id=$key_id" \
"GPG.user_id=$uid" \
"GPG.public_key_document=$public_title" \
"GPG.private_key_document=$private_title" \
"GPG.revocation_certificate_document=$revocation_title" >/dev/null
print -- "Updated metadata on op://${op_vault}/${op_item}."
fi
step "Install and configure 1Password pinentry for gpg-agent"
pinentry_path="${PINENTRY_1PASSWORD_PATH:-$HOME/.local/bin/pinentry-1password.sh}"
pinentry_config="${PINENTRY_1PASSWORD_CONFIG:-$HOME/.config/gpg-1password-pinentry/config.zsh}"
agent_conf="${gpg_home}/gpg-agent.conf"
print -- "Pinentry script: $pinentry_path"
print -- "Pinentry config: $pinentry_config"
print -- "gpg-agent config: $agent_conf"
ask_yes_no "Install the pinentry wrapper and update gpg-agent.conf?" "y" || fail "Stopped before pinentry setup."
write_pinentry_script "$pinentry_path" "$pinentry_config" "$op_vault" "$op_item" "$op_field"
configure_gpg_agent "$agent_conf" "$pinentry_path"
restart_gpg_agent
print -- "gpg-agent restarted."
step "Optional signing test"
print -- "This test kills/restarts gpg-agent and signs a short message without loopback mode."
print -- "If 1Password is locked, you may need to approve the 1Password CLI prompt."
if ask_yes_no "Run the signing test now?" "y"; then
restart_gpg_agent
if print -r -- "gpg-setup signing test $(date -u '+%Y-%m-%dT%H:%M:%SZ')" |
gpg --armor --local-user "$new_fpr" --clearsign >/dev/null; then
print -- "Signing test passed."
else
fail "Signing test failed. Check $agent_conf and try: gpg-connect-agent killagent /bye"
fi
fi
step "Done"
print -- "New GPG key fingerprint: $new_fpr"
print -- "Public/private/revocation artifacts were stored in 1Password vault '$op_vault'."
print -- "The passphrase remains in op://${op_vault}/${op_item}/${op_field}."
print -- "The pinentry wrapper is installed at: $pinentry_path"
print -- "To show the public key later, run:"
print -- " gpg --armor --export $new_fpr"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment