#!/usr/bin/env bash set -euo pipefail # Create a new age-encrypted secret with auto-determined recipients # Usage: ./create-secret.sh [content] # path: relative to secrets/ (e.g., "usda-dash/my-secret.age" or "global/shared.age") # content: stdin if not provided SECRETS_DIR="$(cd "$(dirname "$0")" && pwd)" if [ $# -lt 1 ]; then echo "Usage: $0 [content]" >&2 echo "Examples:" >&2 echo " $0 usda-dash/database-url.age <<< 'postgresql://...'" >&2 echo " $0 global/api-key.age < secret-file.txt" >&2 echo " echo 'secret' | $0 nix-builder/token.age" >&2 exit 1 fi SECRET_PATH="$1" shift # Extract directory from path (e.g., "usda-dash/file.age" -> "usda-dash") SECRET_DIR="$(dirname "$SECRET_PATH")" SECRET_FILE="$(basename "$SECRET_PATH")" # Ensure .age extension if [[ ! "$SECRET_FILE" =~ \.age$ ]]; then echo "Error: Secret file must have .age extension" >&2 exit 1 fi TARGET_FILE="$SECRETS_DIR/$SECRET_PATH" # Ensure target directory exists mkdir -p "$(dirname "$TARGET_FILE")" # Collect recipient keys RECIPIENTS=() if [ "$SECRET_DIR" = "global" ]; then echo "Creating global secret (encrypted for all hosts + admins)..." >&2 # Add all host keys for host_dir in "$SECRETS_DIR"/*/; do host_name="$(basename "$host_dir")" # Skip non-host directories if [ "$host_name" = "admins" ] || [ "$host_name" = "global" ]; then continue fi # Add all .age.pub files from this host while IFS= read -r -d '' key_file; do RECIPIENTS+=("$key_file") done < <(find "$host_dir" -maxdepth 1 -name "*.age.pub" -print0) done # Add global keys while IFS= read -r -d '' key_file; do RECIPIENTS+=("$key_file") done < <(find "$SECRETS_DIR/global" -maxdepth 1 -name "*.age.pub" -print0 2>/dev/null || true) else echo "Creating host-specific secret for $SECRET_DIR..." >&2 # Check if host directory exists if [ ! -d "$SECRETS_DIR/$SECRET_DIR" ]; then echo "Error: Host directory $SECRET_DIR does not exist" >&2 echo "Create it first: mkdir -p secrets/$SECRET_DIR" >&2 exit 1 fi # Add this host's keys while IFS= read -r -d '' key_file; do RECIPIENTS+=("$key_file") done < <(find "$SECRETS_DIR/$SECRET_DIR" -maxdepth 1 -name "*.age.pub" -print0) # Add global keys (so global hosts can also decrypt) while IFS= read -r -d '' key_file; do RECIPIENTS+=("$key_file") done < <(find "$SECRETS_DIR/global" -maxdepth 1 -name "*.age.pub" -print0 2>/dev/null || true) fi # Add admin keys (for editing from workstations) if [ -d "$SECRETS_DIR/admins" ]; then while IFS= read -r -d '' key_file; do RECIPIENTS+=("$key_file") done < <(find "$SECRETS_DIR/admins" -maxdepth 1 -name "*.age.pub" -print0 2>/dev/null || true) fi # Check if we have any recipients if [ ${#RECIPIENTS[@]} -eq 0 ]; then echo "Error: No recipient keys found!" >&2 echo "Run ./update-age-keys.sh first to generate .age.pub files" >&2 exit 1 fi echo "Found ${#RECIPIENTS[@]} recipient key(s):" >&2 for key in "${RECIPIENTS[@]}"; do echo " - $(basename "$key")" >&2 done # Create recipient list file (temporary) RECIPIENT_LIST=$(mktemp) trap "rm -f $RECIPIENT_LIST" EXIT for key in "${RECIPIENTS[@]}"; do cat "$key" >> "$RECIPIENT_LIST" done # Encrypt the secret if [ $# -gt 0 ]; then # Content provided as argument echo "$@" | age -R "$RECIPIENT_LIST" -o "$TARGET_FILE" else # Content from stdin age -R "$RECIPIENT_LIST" -o "$TARGET_FILE" fi echo "✓ Created $TARGET_FILE" >&2 echo " Edit with: ragenix -e secrets/$SECRET_PATH" >&2