Files
project-charlie/.gitea/actions/sops-decrypt/action.yml
T
2026-05-05 12:40:42 +02:00

103 lines
3.5 KiB
YAML

name: SOPS decrypt
description: |
Decrypt one or more SOPS-encrypted files using the host's age key.
Each line of `decrypt` is a `src -> dst` pair. The destination's extension
determines the output format (`.env`/dotenv, `.json`, `.yaml|.yml`, `.ini`,
anything else = raw/binary). Plaintext files are written with mode 0600.
Cleanup is the caller's responsibility. The list of decrypted destinations
is exported via `SOPS_DECRYPT_CLEANUP` (path to a newline-separated file)
and as the step output `cleanup-list`. The reusable workflow
`deploy-stack.yml` already wires an `if: always()` cleanup step around it;
if you call this action directly, do the same.
inputs:
decrypt:
description: |
Multi-line list of `src -> dst` pairs. Example:
secrets/env.sops.yaml -> .env
secrets/config.sops.yaml -> config/app.yaml
secrets/credentials.sops.json -> credentials.json
Blank lines and `#` comments are ignored.
required: true
age-key-file:
description: Path to the host's age private key.
required: false
default: /etc/charlie/age.key
workdir:
description: Working directory the `src` and `dst` paths are resolved against.
required: false
default: ${{ github.workspace }}
outputs:
cleanup-list:
description: Path to a newline-separated file listing decrypted destinations.
value: ${{ steps.decrypt.outputs.cleanup-list }}
runs:
using: composite
steps:
- id: decrypt
shell: bash
env:
SOPS_AGE_KEY_FILE: ${{ inputs.age-key-file }}
DECRYPT_INPUT: ${{ inputs.decrypt }}
WORKDIR: ${{ inputs.workdir }}
run: |
set -euo pipefail
command -v sops >/dev/null 2>&1 || { echo "::error::sops binary not on PATH"; exit 1; }
if [ ! -r "$SOPS_AGE_KEY_FILE" ]; then
echo "::error::age key not readable at $SOPS_AGE_KEY_FILE (need root or appropriate perms)"
exit 1
fi
cd "$WORKDIR"
cleanup_list="${RUNNER_TEMP:-/tmp}/sops-decrypt-cleanup.$$.list"
: > "$cleanup_list"
while IFS= read -r raw; do
line="${raw%%#*}"
line="$(printf '%s' "$line" | awk '{$1=$1;print}')"
[ -z "$line" ] && continue
case "$line" in
*"->"*) ;;
*) echo "::error::invalid decrypt entry (missing '->'): $raw"; exit 1 ;;
esac
src="$(printf '%s' "${line%%->*}" | awk '{$1=$1;print}')"
dst="$(printf '%s' "${line#*->}" | awk '{$1=$1;print}')"
if [ -z "$src" ] || [ -z "$dst" ]; then
echo "::error::invalid decrypt entry: $raw"
exit 1
fi
if [ ! -f "$src" ]; then
echo "::error::source not found: $src"
exit 1
fi
case "$dst" in
*.env|.env) out_format="dotenv" ;;
*.json) out_format="json" ;;
*.yaml|*.yml) out_format="yaml" ;;
*.ini) out_format="ini" ;;
*) out_format="binary" ;;
esac
dst_dir="$(dirname -- "$dst")"
[ "$dst_dir" = "." ] || mkdir -p "$dst_dir"
umask 077
sops --decrypt --output-type "$out_format" --output "$dst" "$src"
chmod 600 "$dst"
printf '%s\n' "$dst" >> "$cleanup_list"
echo "decrypted: $src -> $dst"
done <<< "$DECRYPT_INPUT"
echo "cleanup-list=$cleanup_list" >> "$GITHUB_OUTPUT"
echo "SOPS_DECRYPT_CLEANUP=$cleanup_list" >> "$GITHUB_ENV"