SOPS + age — Secrets Management for p24-infra
Overview
p24-infra infrastructure secrets (monitoring stack, n8n/bms-4, vps-h1) are managed with
SOPS (Secrets OPerationS) + age encryption. Encrypted dotenv files live in git
(secrets/*.env.sops). There is no runtime dependency on Infisical CE for these services.
Migrated 2026-06-20 from Infisical CE (monitoring/n8n-bms4/vps-h1). Migrated 2026-06-21 — Infisical CE fully decommissioned; all remaining projects (bms-servers, art-agency, radekkonarski-brand, brandpilot, github-actions) moved to SOPS+age or GH Secrets.
Encrypted files
| File | Server / scope | Services / secrets |
|---|---|---|
secrets/monitoring.env.sops | vps-i1 | Prometheus, Grafana, Alertmanager, exporters, Caddy |
secrets/n8n-bms4.env.sops | bms-4 | n8n queue-mode + workers, Redis, Traefik |
secrets/vps-h1.env.sops | vps-h1 | WAHA gateway, Traefik |
secrets/bms-servers.env.sops | operator local only | BMS-1/2/3 root passwords, MongoDB credentials |
secrets/art-agency.env.sops | Art-Agency app | Supabase, PayPal, KDP, Google Drive credentials |
Key locations
| Key | Path | Who holds it |
|---|---|---|
| Developer private key | C:\Users\konar\.age\p24-infra-keys.txt | Local dev workstation only |
| CI private key | C:\Users\konar\.age\p24-infra-ci-keys.txt | Local + GH Secret AGE_KEY_GHA |
| Standard SOPS path | ~/.sops/age/keys.txt | VPS agents (if key distributed) |
Two age public keys are listed as recipients in .sops.yaml — both can decrypt all secrets/*.env.sops files.
AGE_KEY_GHA note: The GH Secret must contain only the AGE-SECRET-KEY-1... line (74 chars),
not the full key file with comment headers. Set with:
$key = (Get-Content "C:\Users\konar\.age\p24-infra-ci-keys.txt" | Where-Object { $_ -match '^AGE-SECRET-KEY-1' })
gh secret set AGE_KEY_GHA --repo radieu/p24-infra --body $keyCommon operations
Decrypt a file locally
sops --decrypt secrets/monitoring.env.sops
# or to a temp file for docker compose:
sops --decrypt --input-type dotenv --output-type dotenv secrets/monitoring.env.sops > /tmp/monitoring.envRequires the developer private key at ~/.sops/age/keys.txt (or set SOPS_AGE_KEY_FILE).
Add a new secret
bash (Linux / CI):
export SOPS_AGE_KEY_FILE="$HOME/.age/p24-infra-keys.txt"
sops --decrypt --input-type dotenv --output-type dotenv secrets/monitoring.env.sops > /tmp/monitoring.env
echo "NEW_KEY=value" >> /tmp/monitoring.env
sops --encrypt --input-type dotenv --output-type dotenv /tmp/monitoring.env > secrets/monitoring.env.sops
rm /tmp/monitoring.env
git add secrets/monitoring.env.sops
git commit -m "feat(secrets): add NEW_KEY to monitoring"PowerShell (Windows dev workstation) — three Windows-specific gotchas:
- Temp file must live inside
secrets/and end in.env.sopsto match.sops.yamlpath_regex - Write encrypted output with LF-only —
Set-Contentadds CRLF which breaks SOPS timestamp parsing - Write plaintext temp file with
UTF8Encoding($false)(no BOM) — BOM on byte 1 corrupts the first key name when the decrypted file is later consumed by other tools
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
# Read decrypted content and write WITHOUT BOM (UTF8NoBOM)
$plain = sops --decrypt --input-type dotenv --output-type dotenv secrets\monitoring.env.sops
$plain += "`nNEW_KEY=value"
[System.IO.File]::WriteAllText("$PWD\secrets\monitoring-edit.env.sops",
($plain -join "`n") + "`n", [System.Text.UTF8Encoding]::new($false))
# Encrypt (output also written without BOM / with LF-only)
$enc = sops --encrypt --input-type dotenv --output-type dotenv secrets\monitoring-edit.env.sops
[System.IO.File]::WriteAllText("$PWD\secrets\monitoring.env.sops", ($enc -join "`n") + "`n",
[System.Text.UTF8Encoding]::new($false))
Remove-Item secrets\monitoring-edit.env.sops -Force
git add secrets/monitoring.env.sops
git commit -m "feat(secrets): add NEW_KEY to monitoring"Rotate an existing secret
Same as adding. After updating the .env.sops file, push to dev → the secrets-sync.yml
GitHub Actions workflow automatically decrypts and deploys to the affected server.
Key rotation (age keypair)
Do NOT use
sops updatekeyshere. It is broken on dotenv*.env.sopsfiles in SOPS 3.9.1 and no flag combination fixes it (issue #4601 — it picks its store from the file extension and ignores--input-type). Re-key with the no-op rewrite below instead; seeplaybooks/sops-edit-operations.md§“sops updatekeysis unusable on dotenv files”.
# 1. Generate new keypair
age-keygen -o new-key.txt
# 2. Add the new public key to the .sops.yaml recipients# 3. Re-encrypt every file under the updated recipient set (pwsh, repo root).
# -RekeyOnly = decrypt -> change nothing -> re-encrypt under the current creation_rule,
# with canary decrypt + recipient-count verification and rollback on failure.
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
Get-ChildItem secrets\*.env.sops | ForEach-Object {
.\scripts\sops-set.ps1 -SopsFile $_.FullName -RekeyOnly
}# 4. Commit the updated .env.sops files + .sops.yaml
# 5. Distribute the new private key (update AGE_KEY_GHA, VPS ~/.sops/age/keys.txt)
# 6. Remove the OLD public key from .sops.yaml, then repeat step 3 to drop it from every fileGitHub Actions workflow (secrets-sync.yml)
The secrets-sync.yml workflow:
- Checks out the repo
- Installs
ageandsops - Writes
AGE_KEY_GHAto$RUNNER_TEMP/age-key.txt - Decrypts with
sops --decrypt --input-type dotenv --output-type dotenv - SSHs to each VPS and writes the decrypted
.envfile - Restarts only affected containers via
docker compose --env-file .env up -d --no-deps
Trigger: push to main or dev when any secrets/*.env.sops file changes.
VPS deployment fallback
If the GHA workflow is unavailable, deploy manually:
# On local dev (requires developer private key)
sops --decrypt --input-type dotenv --output-type dotenv secrets/monitoring.env.sops \
| ssh root@217.154.82.162 "cat > /opt/p24-infra/monitoring/.env.bak"
ssh root@217.154.82.162 "cd /opt/p24-infra/monitoring && docker compose --env-file .env.bak up -d"The .env.bak on each server is the already-decrypted copy written at deploy time.
It is the local fallback when git+SOPS is unavailable.
Infisical CE — decommissioned 2026-06-21
All projects migrated to SOPS+age. Infisical CE containers stopped on vps-i1.
| Former project | Migrated to |
|---|---|
p24-monitoring | secrets/monitoring.env.sops |
n8n-bms4 | secrets/n8n-bms4.env.sops |
vps-h1 | secrets/vps-h1.env.sops |
radekkonarski-brand | secrets/radekkonarski-brand.env.sops (in radekkonarski-personal-brand repo) |
brandpilot | secrets/brandpilot.env.sops |
bms-servers | secrets/bms-servers.env.sops |
art-agency | secrets/art-agency.env.sops |
github-actions | GH Secrets directly (no Infisical sync) |