Initial commit: VaultMesh Skills collection
Collection of operational skills for VaultMesh infrastructure including: - backup-sovereign: Backup and recovery operations - btc-anchor: Bitcoin anchoring - cloudflare-tunnel-manager: Cloudflare tunnel management - container-registry: Container registry operations - disaster-recovery: Disaster recovery procedures - dns-sovereign: DNS management - eth-anchor: Ethereum anchoring - gitea-bootstrap: Gitea setup and configuration - hetzner-bootstrap: Hetzner server provisioning - merkle-forest: Merkle tree operations - node-hardening: Node security hardening - operator-bootstrap: Operator initialization - proof-verifier: Cryptographic proof verification - rfc3161-anchor: RFC3161 timestamping - secrets-vault: Secrets management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
79
secrets-vault/SKILL.md
Normal file
79
secrets-vault/SKILL.md
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
name: secrets-vault
|
||||
description: >
|
||||
Establish a sovereign secrets vault using age + sops (GitOps-friendly).
|
||||
Generates/installs an age identity, writes .sops.yaml policy, scaffolds encrypted templates,
|
||||
and provides plan/apply/rollback/verify/report. Triggers: 'set up secrets vault',
|
||||
'age sops vault', 'encrypt secrets', 'sops policy', 'rotate age key'.
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# Secrets Vault (age + sops)
|
||||
|
||||
This skill forges a **GitOps-native secrets vault**:
|
||||
|
||||
- **age**: modern encryption keys (simple UX)
|
||||
- **sops**: encrypt YAML/JSON/ENV files for storage in git
|
||||
- `.sops.yaml`: repository policy for automatic encryption recipients
|
||||
|
||||
It is designed to be safe on **Node A** and portable to future nodes.
|
||||
|
||||
## What it produces
|
||||
|
||||
A standard layout (you can commit ciphertext to your infra repo):
|
||||
|
||||
```
|
||||
vault/
|
||||
.sops.yaml
|
||||
secrets/
|
||||
cloudflare.enc.yaml
|
||||
gitea.enc.yaml
|
||||
registry.enc.yaml
|
||||
k8s.enc.yaml
|
||||
README.md
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
cd ~/.claude/skills/secrets-vault
|
||||
|
||||
# where the vault lives (repo dir recommended)
|
||||
export VAULT_ROOT="$HOME/infrastructure/vault"
|
||||
|
||||
# safety (apply scripts require DRY_RUN=0 and confirmation)
|
||||
export DRY_RUN=1
|
||||
export REQUIRE_CONFIRM=1
|
||||
export CONFIRM_PHRASE="I UNDERSTAND THIS WILL CREATE A SECRETS VAULT"
|
||||
|
||||
./scripts/00_preflight.sh
|
||||
./scripts/10_plan.sh
|
||||
|
||||
export DRY_RUN=0
|
||||
./scripts/11_apply.sh
|
||||
|
||||
./scripts/90_verify.sh
|
||||
./scripts/99_report.sh
|
||||
```
|
||||
|
||||
## Inputs
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|---|---:|---|---|
|
||||
| VAULT_ROOT | No | ~/infrastructure/vault | Where to create the vault structure |
|
||||
| AGE_KEY_DIR | No | ~/.config/sops/age | Where age identities live |
|
||||
| AGE_KEYS_FILE | No | ~/.config/sops/age/keys.txt | age identity file (0600) |
|
||||
| RECIPIENTS_FILE | No | outputs/recipients.txt | Generated recipients list |
|
||||
| DRY_RUN | No | 1 | Apply refuses unless DRY_RUN=0 |
|
||||
| REQUIRE_CONFIRM | No | 1 | Require confirmation phrase |
|
||||
| CONFIRM_PHRASE | No | I UNDERSTAND THIS WILL CREATE A SECRETS VAULT | Safety phrase |
|
||||
|
||||
## Outputs
|
||||
|
||||
- `outputs/status_matrix.json`
|
||||
- `outputs/audit_report.md`
|
||||
- `outputs/recipients.txt`
|
||||
- `outputs/backups/*` (backups of changed files)
|
||||
|
||||
## EU Compliance
|
||||
EU (Ireland - Dublin), Irish jurisdiction. Secrets remain local-first; git stores ciphertext only.
|
||||
10
secrets-vault/checks/check_ciphertext.sh
Normal file
10
secrets-vault/checks/check_ciphertext.sh
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
: "${VAULT_ROOT:=~/infrastructure/vault}"
|
||||
vr="$(eval echo "$VAULT_ROOT")"
|
||||
for f in cloudflare gitea registry k8s; do
|
||||
p="$vr/secrets/$f.enc.yaml"
|
||||
[[ -f "$p" ]] || { echo "missing $p"; exit 1; }
|
||||
grep -q "sops:" "$p" || { echo "not encrypted: $p"; exit 1; }
|
||||
done
|
||||
echo "[OK] ciphertext"
|
||||
7
secrets-vault/checks/check_policy.sh
Normal file
7
secrets-vault/checks/check_policy.sh
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
: "${VAULT_ROOT:=~/infrastructure/vault}"
|
||||
vr="$(eval echo "$VAULT_ROOT")"
|
||||
[[ -f "$vr/.sops.yaml" ]] || { echo "missing .sops.yaml"; exit 1; }
|
||||
grep -q "creation_rules" "$vr/.sops.yaml" || { echo "invalid policy"; exit 1; }
|
||||
echo "[OK] policy"
|
||||
5
secrets-vault/checks/check_tools.sh
Normal file
5
secrets-vault/checks/check_tools.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
command -v age >/dev/null || { echo "missing age"; exit 1; }
|
||||
command -v sops >/dev/null || { echo "missing sops"; exit 1; }
|
||||
echo "[OK] tools"
|
||||
50
secrets-vault/config.json
Normal file
50
secrets-vault/config.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "secrets-vault",
|
||||
"version": "1.0.0",
|
||||
"defaults": {
|
||||
"VAULT_ROOT": "~/infrastructure/vault",
|
||||
"AGE_KEY_DIR": "~/.config/sops/age",
|
||||
"AGE_KEYS_FILE": "~/.config/sops/age/keys.txt",
|
||||
"DRY_RUN": "1",
|
||||
"REQUIRE_CONFIRM": "1",
|
||||
"CONFIRM_PHRASE": "I UNDERSTAND THIS WILL CREATE A SECRETS VAULT"
|
||||
},
|
||||
"phases": {
|
||||
"preflight": [
|
||||
"00_preflight.sh"
|
||||
],
|
||||
"vault": {
|
||||
"plan": [
|
||||
"10_plan.sh"
|
||||
],
|
||||
"apply": [
|
||||
"11_apply.sh"
|
||||
],
|
||||
"rollback": [
|
||||
"rollback/undo_last_changes.sh"
|
||||
]
|
||||
},
|
||||
"verify": [
|
||||
"90_verify.sh"
|
||||
],
|
||||
"report": [
|
||||
"99_report.sh"
|
||||
]
|
||||
},
|
||||
"checks": {
|
||||
"tools": [
|
||||
"checks/check_tools.sh"
|
||||
],
|
||||
"policy": [
|
||||
"checks/check_policy.sh"
|
||||
],
|
||||
"ciphertext": [
|
||||
"checks/check_ciphertext.sh"
|
||||
]
|
||||
},
|
||||
"eu_compliance": {
|
||||
"data_residency": "EU",
|
||||
"jurisdiction": "Ireland",
|
||||
"gdpr_applicable": true
|
||||
}
|
||||
}
|
||||
13
secrets-vault/references/recovery_notes.md
Normal file
13
secrets-vault/references/recovery_notes.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Recovery Notes (age + sops)
|
||||
|
||||
## If you lose the age identity
|
||||
You cannot decrypt existing secrets without the age private key stored in:
|
||||
|
||||
- `~/.config/sops/age/keys.txt`
|
||||
|
||||
Keep an offline recovery copy (USB/QR/printed).
|
||||
|
||||
## Rotating recipients
|
||||
Add additional recipients to `.sops.yaml` and re-encrypt:
|
||||
|
||||
- `sops updatekeys -y secrets/*.enc.yaml`
|
||||
17
secrets-vault/scripts/00_preflight.sh
Normal file
17
secrets-vault/scripts/00_preflight.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
|
||||
main() {
|
||||
need age
|
||||
need sops
|
||||
need mkdir
|
||||
need chmod
|
||||
need stat
|
||||
need grep
|
||||
need sed
|
||||
need date
|
||||
log_info "Preflight OK."
|
||||
}
|
||||
main "$@"
|
||||
26
secrets-vault/scripts/10_plan.sh
Normal file
26
secrets-vault/scripts/10_plan.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
|
||||
: "${VAULT_ROOT:=~/infrastructure/vault}"
|
||||
: "${AGE_KEY_DIR:=~/.config/sops/age}"
|
||||
: "${AGE_KEYS_FILE:=~/.config/sops/age/keys.txt}"
|
||||
|
||||
main() {
|
||||
vr="$(expand_path "$VAULT_ROOT")"
|
||||
kd="$(expand_path "$AGE_KEY_DIR")"
|
||||
kf="$(expand_path "$AGE_KEYS_FILE")"
|
||||
|
||||
echo "[PLAN] $(date -Iseconds) secrets-vault (age+sops)"
|
||||
echo "[PLAN] VAULT_ROOT: $vr"
|
||||
echo "[PLAN] AGE_KEY_DIR: $kd"
|
||||
echo "[PLAN] AGE_KEYS_FILE: $kf"
|
||||
echo
|
||||
echo "[PLAN] Will ensure age identity exists (keys.txt)."
|
||||
echo "[PLAN] Will write vault/.sops.yaml and encrypted templates under vault/secrets/."
|
||||
echo "[PLAN] Will NOT print secret plaintext."
|
||||
echo
|
||||
echo "[PLAN] Next: export DRY_RUN=0 && ./scripts/11_apply.sh"
|
||||
}
|
||||
main "$@"
|
||||
130
secrets-vault/scripts/11_apply.sh
Normal file
130
secrets-vault/scripts/11_apply.sh
Normal file
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
|
||||
: "${VAULT_ROOT:=~/infrastructure/vault}"
|
||||
: "${AGE_KEY_DIR:=~/.config/sops/age}"
|
||||
: "${AGE_KEYS_FILE:=~/.config/sops/age/keys.txt}"
|
||||
: "${RECIPIENTS_FILE:=$SKILL_ROOT/outputs/recipients.txt}"
|
||||
: "${BACKUP_DIR:=$SKILL_ROOT/outputs/backups}"
|
||||
|
||||
backup_file() {
|
||||
local f="$1"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
if [[ -f "$f" ]]; then
|
||||
ts="$(date -Iseconds | tr ':' '-')"
|
||||
cp -p "$f" "$BACKUP_DIR/$(basename "$f").${ts}.bak"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
confirm_gate
|
||||
|
||||
mkdir -p "$SKILL_ROOT/outputs" "$BACKUP_DIR"
|
||||
vr="$(expand_path "$VAULT_ROOT")"
|
||||
kd="$(expand_path "$AGE_KEY_DIR")"
|
||||
kf="$(expand_path "$AGE_KEYS_FILE")"
|
||||
|
||||
mkdir -p "$vr/secrets"
|
||||
mkdir -p "$kd"
|
||||
|
||||
# 1) ensure age identity exists
|
||||
if [[ ! -f "$kf" ]]; then
|
||||
log_info "Generating age identity: $kf"
|
||||
age-keygen -o "$kf" >/dev/null
|
||||
chmod 600 "$kf"
|
||||
else
|
||||
log_info "Using existing age identity: $kf"
|
||||
chmod 600 "$kf" || true
|
||||
fi
|
||||
|
||||
# extract recipient (public key)
|
||||
recipient="$(grep -E '^# public key: ' "$kf" | head -n1 | sed 's/^# public key: //')"
|
||||
[[ -n "$recipient" ]] || die "Could not parse public key from $kf"
|
||||
|
||||
echo "$recipient" > "$RECIPIENTS_FILE"
|
||||
|
||||
# 2) write .sops.yaml policy
|
||||
policy="$vr/.sops.yaml"
|
||||
backup_file "$policy"
|
||||
cat > "$policy" <<EOF
|
||||
creation_rules:
|
||||
- path_regex: secrets/.*\\.enc\\.(yaml|yml|json|env)$
|
||||
encrypted_regex: '^(data|stringData|secrets|values)$'
|
||||
age: ["$recipient"]
|
||||
EOF
|
||||
|
||||
# 3) scaffold encrypted templates
|
||||
# plaintext templates are generated into a temp file then immediately encrypted and removed.
|
||||
tmpdir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmpdir"' EXIT
|
||||
|
||||
# cloudflare
|
||||
cat > "$tmpdir/cloudflare.yaml" <<EOF
|
||||
data:
|
||||
account_id: "REPLACE_ME"
|
||||
tunnel_token: "REPLACE_ME"
|
||||
api_token: "REPLACE_ME"
|
||||
EOF
|
||||
sops --encrypt --age "$recipient" "$tmpdir/cloudflare.yaml" > "$vr/secrets/cloudflare.enc.yaml"
|
||||
|
||||
# gitea
|
||||
cat > "$tmpdir/gitea.yaml" <<EOF
|
||||
data:
|
||||
admin_username: "REPLACE_ME"
|
||||
admin_password: "REPLACE_ME"
|
||||
admin_email: "REPLACE_ME"
|
||||
secret_key: "REPLACE_ME"
|
||||
EOF
|
||||
sops --encrypt --age "$recipient" "$tmpdir/gitea.yaml" > "$vr/secrets/gitea.enc.yaml"
|
||||
|
||||
# registry
|
||||
cat > "$tmpdir/registry.yaml" <<EOF
|
||||
data:
|
||||
htpasswd: "REPLACE_ME"
|
||||
tls_cert_pem: "REPLACE_ME"
|
||||
tls_key_pem: "REPLACE_ME"
|
||||
EOF
|
||||
sops --encrypt --age "$recipient" "$tmpdir/registry.yaml" > "$vr/secrets/registry.enc.yaml"
|
||||
|
||||
# k8s
|
||||
cat > "$tmpdir/k8s.yaml" <<EOF
|
||||
data:
|
||||
kubeconfig: "REPLACE_ME"
|
||||
cluster_token: "REPLACE_ME"
|
||||
EOF
|
||||
sops --encrypt --age "$recipient" "$tmpdir/k8s.yaml" > "$vr/secrets/k8s.enc.yaml"
|
||||
|
||||
# 4) vault README
|
||||
readme="$vr/README.md"
|
||||
backup_file "$readme"
|
||||
cat > "$readme" <<EOF
|
||||
# Vault (age + sops)
|
||||
|
||||
This folder stores **encrypted secrets** for GitOps.
|
||||
|
||||
## Edit a secret
|
||||
\`\`\`bash
|
||||
sops secrets/cloudflare.enc.yaml
|
||||
\`\`\`
|
||||
|
||||
## Decrypt to stdout (careful)
|
||||
\`\`\`bash
|
||||
sops -d secrets/cloudflare.enc.yaml
|
||||
\`\`\`
|
||||
|
||||
## Policy
|
||||
See \`.sops.yaml\`. Only files matching \`secrets/*.enc.(yaml|json|env)\` are covered.
|
||||
|
||||
## Key location
|
||||
- age identity: \`$kf\` (0600)
|
||||
- public recipient: stored in \`outputs/recipients.txt\` by the skill
|
||||
EOF
|
||||
|
||||
echo "$vr" > "$SKILL_ROOT/outputs/vault_root.txt"
|
||||
log_info "Vault forged at: $vr"
|
||||
log_info "Recipient: $recipient"
|
||||
}
|
||||
main "$@"
|
||||
55
secrets-vault/scripts/90_verify.sh
Normal file
55
secrets-vault/scripts/90_verify.sh
Normal file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
|
||||
: "${VAULT_ROOT:=~/infrastructure/vault}"
|
||||
: "${AGE_KEYS_FILE:=~/.config/sops/age/keys.txt}"
|
||||
: "${RECIPIENTS_FILE:=$SKILL_ROOT/outputs/recipients.txt}"
|
||||
|
||||
main() {
|
||||
vr="$(expand_path "$VAULT_ROOT")"
|
||||
kf="$(expand_path "$AGE_KEYS_FILE")"
|
||||
|
||||
status="$SKILL_ROOT/outputs/status_matrix.json"
|
||||
ok_keys=false; ok_perm=false; ok_policy=false; ok_cipher=false
|
||||
|
||||
[[ -f "$kf" ]] && ok_keys=true
|
||||
if [[ -f "$kf" ]]; then
|
||||
perm="$(stat -c '%a' "$kf" 2>/dev/null || echo "")"
|
||||
[[ "$perm" == "600" ]] && ok_perm=true
|
||||
fi
|
||||
|
||||
[[ -f "$vr/.sops.yaml" ]] && ok_policy=true
|
||||
if [[ -f "$vr/secrets/cloudflare.enc.yaml" && -f "$vr/secrets/gitea.enc.yaml" && -f "$vr/secrets/registry.enc.yaml" && -f "$vr/secrets/k8s.enc.yaml" ]]; then
|
||||
ok_cipher=true
|
||||
fi
|
||||
|
||||
blockers="[]"
|
||||
if [[ "$ok_keys" != "true" ]]; then blockers='["missing_age_identity"]'
|
||||
elif [[ "$ok_policy" != "true" ]]; then blockers='["missing_sops_policy"]'
|
||||
elif [[ "$ok_cipher" != "true" ]]; then blockers='["missing_encrypted_templates"]'
|
||||
fi
|
||||
|
||||
cat > "$status" <<EOF
|
||||
{
|
||||
"skill":"secrets-vault",
|
||||
"timestamp":"$(date -Iseconds)",
|
||||
"vault_root":"$(json_escape "$vr")",
|
||||
"checks":[
|
||||
{"name":"age_identity_present", "ok": $ok_keys, "path":"$(json_escape "$kf")"},
|
||||
{"name":"age_identity_perm_600", "ok": $ok_perm},
|
||||
{"name":"sops_policy_present", "ok": $ok_policy, "path":"$(json_escape "$vr/.sops.yaml")"},
|
||||
{"name":"encrypted_templates_present", "ok": $ok_cipher}
|
||||
],
|
||||
"blockers": $blockers,
|
||||
"warnings": [],
|
||||
"next_steps": ["commit vault ciphertext to git", "cloudflare-tunnel-manager", "container-registry"]
|
||||
}
|
||||
EOF
|
||||
|
||||
log_info "Wrote $status"
|
||||
cat "$status"
|
||||
}
|
||||
main "$@"
|
||||
38
secrets-vault/scripts/99_report.sh
Normal file
38
secrets-vault/scripts/99_report.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
|
||||
main() {
|
||||
status="$SKILL_ROOT/outputs/status_matrix.json"
|
||||
report="$SKILL_ROOT/outputs/audit_report.md"
|
||||
vault_root="$(cat "$SKILL_ROOT/outputs/vault_root.txt" 2>/dev/null || echo "")"
|
||||
|
||||
cat > "$report" <<EOF
|
||||
# Secrets Vault Audit Report (age + sops)
|
||||
|
||||
**Generated:** $(date -Iseconds)
|
||||
**Vault Root:** \`$vault_root\`
|
||||
**Skill Version:** 1.0.0
|
||||
|
||||
## Status Matrix
|
||||
|
||||
$(if [[ -f "$status" ]]; then echo '```json'; cat "$status"; echo '```'; else echo "_Missing status_matrix.json_"; fi)
|
||||
|
||||
## Operating Rules
|
||||
|
||||
- Commit **only ciphertext** (\`*.enc.*\`) to git.
|
||||
- Never store plaintext secrets in the repo.
|
||||
- Keep \`~/.config/sops/age/keys.txt\` at **0600**.
|
||||
- Keep an offline recovery copy of the age identity.
|
||||
|
||||
## EU Compliance
|
||||
|
||||
EU (Ireland - Dublin), Irish jurisdiction. Secrets remain local-first; git stores ciphertext only.
|
||||
EOF
|
||||
|
||||
log_info "Wrote $report"
|
||||
cat "$report"
|
||||
}
|
||||
main "$@"
|
||||
32
secrets-vault/scripts/_common.sh
Normal file
32
secrets-vault/scripts/_common.sh
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
log_info(){ echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_warn(){ echo "[WARN] $(date -Iseconds) $*" >&2; }
|
||||
log_error(){ echo "[ERROR] $(date -Iseconds) $*" >&2; }
|
||||
die(){ log_error "$*"; exit 1; }
|
||||
need(){ command -v "$1" >/dev/null 2>&1 || die "Missing required tool: $1"; }
|
||||
|
||||
expand_path() {
|
||||
local p="$1"
|
||||
# shellcheck disable=SC2086
|
||||
eval echo "$p"
|
||||
}
|
||||
|
||||
confirm_gate() {
|
||||
: "${DRY_RUN:=1}"
|
||||
: "${REQUIRE_CONFIRM:=1}"
|
||||
: "${CONFIRM_PHRASE:=I UNDERSTAND THIS WILL CREATE A SECRETS VAULT}"
|
||||
[[ "$DRY_RUN" == "0" ]] || die "DRY_RUN=$DRY_RUN (set DRY_RUN=0)."
|
||||
if [[ "$REQUIRE_CONFIRM" == "1" ]]; then
|
||||
echo "Type to confirm:"
|
||||
echo " $CONFIRM_PHRASE"
|
||||
read -r input
|
||||
[[ "$input" == "$CONFIRM_PHRASE" ]] || die "Confirmation phrase mismatch."
|
||||
fi
|
||||
}
|
||||
|
||||
json_escape() {
|
||||
local s="$1"
|
||||
s="${s//\\/\\\\}"; s="${s//\"/\\\"}"; s="${s//$'\n'/\\n}"
|
||||
printf "%s" "$s"
|
||||
}
|
||||
22
secrets-vault/scripts/rollback/undo_last_changes.sh
Normal file
22
secrets-vault/scripts/rollback/undo_last_changes.sh
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
source "$SKILL_ROOT/scripts/_common.sh"
|
||||
|
||||
: "${BACKUP_DIR:=$SKILL_ROOT/outputs/backups}"
|
||||
|
||||
main() {
|
||||
confirm_gate
|
||||
if [[ ! -d "$BACKUP_DIR" ]]; then
|
||||
log_warn "No backups found; nothing to restore."
|
||||
exit 0
|
||||
fi
|
||||
log_warn "Backups exist in: $BACKUP_DIR"
|
||||
log_warn "This rollback restores only files we backed up (.sops.yaml, README.md)."
|
||||
log_warn "Encrypted templates are safe to keep; remove manually if desired."
|
||||
echo "Listing backups:"
|
||||
ls -1 "$BACKUP_DIR" || true
|
||||
log_info "Rollback is conservative by design. Restore manually by copying desired *.bak to target."
|
||||
}
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user