Enhance secrets CLI to support additional secret file types and clear command

- Updated file tracking to include `.dev.vars` alongside `.env` and `.env.*`.
- Improved pre-commit hook to block plaintext secret files, including `.dev.vars`.
- Added `clear` command to remove plaintext secret files from the current directory and workspaces.
- Enhanced tests to cover new functionality for `.dev.vars` and the `clear` command.
- Updated documentation to reflect changes in tracked files and command usage.
This commit is contained in:
Brian Majewski 2026-03-24 06:58:45 -07:00
parent 585367b9a6
commit e347e73976
5 changed files with 503 additions and 74 deletions

131
secrets
View file

@ -46,12 +46,13 @@ derive_project_name() {
basename "$PWD"
}
# Collect .env and .env.* files from a directory (excluding .envrc, .environment-*)
# Collect secret files from a directory:
# .env, .env.*, .dev.vars (excluding .envrc, .environment-*)
# Sets the COLLECTED_FILES array. Returns 1 if no files found.
collect_env_files() {
local dir="$1"
COLLECTED_FILES=()
for f in "$dir"/.env "$dir"/.env.*; do
for f in "$dir"/.env "$dir"/.env.* "$dir"/.dev.vars; do
[ -f "$f" ] || continue
local basename_f
basename_f=$(basename "$f")
@ -97,9 +98,9 @@ install_hook() {
# Inline hook if template not found (e.g. secrets installed standalone)
cat > "$hook_dst" << 'HOOKEOF'
#!/usr/bin/env bash
BLOCKED=$(git diff --cached --name-only | grep -E '\.env' | grep -v '\.age$' || true)
BLOCKED=$(git diff --cached --name-only | grep -E '\.(env|dev\.vars)' | grep -v '\.age$' || true)
if [ -n "$BLOCKED" ]; then
echo "ERROR: Plaintext env files staged for commit:"
echo "ERROR: Plaintext secret files staged for commit:"
echo "$BLOCKED"
echo "Only .age (encrypted) files should be committed."
exit 1
@ -132,13 +133,15 @@ cmd_init() {
# Never commit the private key
key.txt
# Block plaintext env files
# Block plaintext secret files
**/.env
**/.env.*
**/.dev.vars
# Allow encrypted env files
# Allow encrypted files
!**/.env.age
!**/.env.*.age
!**/.dev.vars.age
EOF
# Install pre-commit hook
@ -220,7 +223,7 @@ cmd_push() {
pubkey=$(get_pubkey)
if ! push_dir_to_project "$PWD" "$project" "$pubkey"; then
die "No .env or .env.* files found in $PWD"
die "No secret files (.env, .env.*, .dev.vars) found in $PWD"
fi
commit_and_push_secrets "update $project"
@ -260,7 +263,7 @@ cmd_push_workspaces() {
done <<< "$workspaces"
if [ "$total" -eq 0 ]; then
die "No .env files found in any workspace"
die "No secret files found in any workspace"
fi
commit_and_push_secrets "update $monorepo_name workspaces"
@ -513,27 +516,114 @@ cmd_rekey() {
echo "For full rotation, create a fresh repo."
}
cmd_clear() {
local dir="$PWD"
if ! collect_env_files "$dir"; then
info "No secret files to clear in $dir"
return
fi
local count=0
for f in "${COLLECTED_FILES[@]}"; do
rm "$f"
count=$((count + 1))
done
info "Cleared $count secret file(s) from $dir"
}
cmd_clear_workspaces() {
check_cmd jq
local root="$PWD"
local total=0
# Clear root
if collect_env_files "$root"; then
for f in "${COLLECTED_FILES[@]}"; do
rm "$f"
total=$((total + 1))
done
fi
# Clear each workspace
local workspaces
workspaces=$(get_workspaces "$root")
while IFS= read -r ws; do
[ -n "$ws" ] || continue
local ws_dir="$root/$ws"
if collect_env_files "$ws_dir"; then
for f in "${COLLECTED_FILES[@]}"; do
rm "$f"
total=$((total + 1))
done
fi
done <<< "$workspaces"
info "Cleared $total secret file(s) from workspace"
}
cmd_run() {
local workspace_mode=false
local project=""
# Parse flags before the command
while [ $# -gt 0 ]; do
case "$1" in
-w|--workspaces) workspace_mode=true; shift ;;
--) shift; break ;;
-*) die "Unknown flag: $1. Usage: secrets run [-w] [--] <command...>" ;;
*) break ;;
esac
done
[ $# -gt 0 ] || die "Usage: secrets run [-w] [--] <command...>"
# Pull secrets
if [ "$workspace_mode" = true ]; then
cmd_pull_workspaces
else
cmd_pull "$project"
fi
# Set trap to clear secrets on exit (normal, error, interrupt, terminate)
if [ "$workspace_mode" = true ]; then
trap 'cmd_clear_workspaces' EXIT
else
trap 'cmd_clear' EXIT
fi
# Execute the command, capturing exit code (don't let set -e kill us)
local rc=0
"$@" || rc=$?
exit "$rc"
}
cmd_help() {
cat << 'EOF'
secrets — encrypted env file sync between machines
secrets — encrypted secret file sync between machines
Usage:
secrets init Initialize the secrets repo and generate an age key
secrets push [project] Encrypt .env* files and push to the secrets repo
secrets push -w|--workspaces Push .env* from all workspaces in package.json
secrets pull [project] Pull and decrypt .env* files into current directory
secrets pull -w|--workspaces Pull .env* into all workspaces from package.json
secrets push [project] Encrypt secret files and push to the secrets repo
secrets push -w|--workspaces Push secrets from all workspaces in package.json
secrets pull [project] Pull and decrypt secret files into current directory
secrets pull -w|--workspaces Pull secrets into all workspaces from package.json
secrets clear Remove plaintext secret files from current directory
secrets clear -w|--workspaces Clear secrets from all workspaces in package.json
secrets run [-w] <command> Pull secrets, run command, clear secrets on exit
secrets list List all projects and their secret files
secrets rm <project> Remove a project's secrets from the repo
secrets rekey Re-encrypt all secrets with a new key
Tracked files: .env, .env.*, .dev.vars
If [project] is omitted, it is derived from the current directory's
git remote (if available) or the directory name.
Workspaces:
With -w/--workspaces, reads package.json "workspaces" field to find
workspace directories. Each workspace's .env* files are stored under
<monorepo>/<workspace-path>/ in the secrets repo. Root .env* files
workspace directories. Each workspace's secret files are stored under
<monorepo>/<workspace-path>/ in the secrets repo. Root secret files
are stored under <monorepo>/ directly. Requires jq.
Environment:
@ -559,6 +649,17 @@ case "${1:-help}" in
cmd_pull "${2:-}"
fi
;;
clear)
if [ "${2:-}" = "-w" ] || [ "${2:-}" = "--workspaces" ]; then
cmd_clear_workspaces
else
cmd_clear
fi
;;
run)
shift
cmd_run "$@"
;;
list) cmd_list ;;
rm) cmd_rm "${2:-}" ;;
rekey) cmd_rekey ;;