:PROPERTIES: :GPTEL_MODEL: claude-opus-4-8 :GPTEL_BACKEND: Claude :GPTEL_SYSTEM: Act as a Code Review Specialist. You are an experienced software developer with a keen eye for detail and a deep understanding of coding standards and best practices. \n\nYour task is to review the code provided by the user, focusing on areas such as:\n- Code quality and readability\n- Adherence to coding standards\n- Potential bugs and security vulnerabilities\n- Performance optimization\n\nYou will:\n- Provide constructive feedback on the code\n- Suggest improvements and refactoring where necessary\n- Highlight any security concerns\n- Ensure the code follows best practices\n\nRules:\n- Be objective and professional in your feedback\n- Prioritize clarity and maintainability in your suggestions\n- Consider the specific context and requirements provided with the code :GPTEL_TOOLS: :GPTEL_MAX_TOKENS: 10000 :GPTEL_BOUNDS: ((response (1310 15709) (15767 21231) (21271 23808))) :END: *** I have a script that helps me insert records into a recutils database that works well. I would like to adapt this to work when the recfile database is encrypted using age. Create a wrapper script that can take in recfiles encrypted with age then reencrypts them when finished. I would like this to work for both Ubuntu and MacOS. * Code Review & Age-Encryption Wrapper Before diving into the wrapper, let me share a few observations on your existing =zrec= script, then provide the encryption wrapper. ** Quick Review Notes on =zrec= A few things worth flagging for the wrapper to work cleanly: 1. *Bug in mandatory/free-form prompts*: In the "Mandatory fields" and "Additional fields" loops, you call =prompt_field "$f" "$mand" ...= but =$mand= is not set in that scope (it's a leftover from the allowed-fields loop). For mandatory fields it should be ="yes"=; for free-form fields it should be ="no"=. This is a latent bug regardless of encryption. 2. *=recins= modifies the file in place*, which is exactly what our wrapper needs to intercept. Good news: the wrapper can stay decoupled by operating on a temp plaintext file and passing /that/ path to =zrec=. 3. The script reads from =recfile= via =recinf=/=awk= and writes via =recins= — so the wrapper just needs to hand =zrec= a decrypted temp file path. --- ** The Wrapper: =zrec-age= This wrapper: - Decrypts an age file to a secure temp location - Runs =zrec= against the plaintext - Re-encrypts on success - Cleans up plaintext securely (best-effort) on all exit paths - Supports both *passphrase* and *identity/recipient key* workflows - Works on Ubuntu and macOS #+begin_src bash #!/usr/bin/env bash # # zrec-age - Encrypted-recfile wrapper around zrec. # # Decrypts an age-encrypted recfile to a temporary plaintext file, runs zrec # against it, then re-encrypts the result back to the original location. # # Usage: # zrec-age [options] [record-type] # # Options: # -i age identity (private key) file for DECRYPTION. # May be repeated. (Implies key-based mode.) # -r age recipient (public key) for ENCRYPTION. # May be repeated. (Implies key-based mode.) # -R File containing one recipient per line. # -p Use passphrase (symmetric) mode for both # decrypt and encrypt. (Default if no -i/-r/-R given.) # -z Path to the zrec script (default: looks on PATH, # then alongside this script). # -h Show this help. # # Environment: # ZREC_BIN Overrides the zrec location (same as -z). # # Notes: # * In passphrase mode you will be prompted up to three times by age # (decrypt, then encrypt). This is unavoidable with symmetric age. # * Plaintext only ever lives in a 0700 temp directory and is removed # (best-effort shredded) on exit. # set -o pipefail # --------------------------------------------------------------------------- # Globals # --------------------------------------------------------------------------- PROG="$(basename "$0")" WORKDIR="" PLAINTEXT="" PASSPHRASE_MODE="auto" # auto | yes | no declare -a IDENTITIES=() declare -a RECIPIENTS=() declare -a RECIPIENT_FILES=() ZREC_BIN="${ZREC_BIN:-}" # --------------------------------------------------------------------------- # Logging helpers (all to stderr; stdout stays clean) # --------------------------------------------------------------------------- log() { printf '%s\n' "$*" >&2; } err() { printf 'Error: %s\n' "$*" >&2; } die() { err "$*"; exit 1; } usage() { sed -n '3,40p' "$0" | sed 's/^# \{0,1\}//' >&2 exit "${1:-1}" } # --------------------------------------------------------------------------- # Dependency checks # --------------------------------------------------------------------------- check_deps() { command -v age >/dev/null 2>&1 || die "'age' not found. Install it (brew install age / apt install age)." if [ -z "$ZREC_BIN" ]; then if command -v zrec >/dev/null 2>&1; then ZREC_BIN="$(command -v zrec)" else # Fall back to a zrec sitting next to this wrapper. local self_dir self_dir="$(cd "$(dirname "$0")" && pwd)" if [ -x "$self_dir/zrec" ]; then ZREC_BIN="$self_dir/zrec" fi fi fi [ -n "$ZREC_BIN" ] && [ -x "$ZREC_BIN" ] \ || die "Could not find an executable 'zrec' (use -z or set ZREC_BIN)." } # --------------------------------------------------------------------------- # Cross-platform secure temp dir creation. # --------------------------------------------------------------------------- make_workdir() { # mktemp -d differs slightly across platforms but -d