cleanup: archive stale secrets/ + secrets-issuance/, add Infisical bootstrap script, update SOPS paths to archive/

This commit is contained in:
2026-07-07 22:20:58 +02:00
parent b35825c50b
commit bf250fc6a9
24 changed files with 156 additions and 20 deletions

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Snapshot secrets-issuance state to /mnt/library
After=network-online.target
[Service]
Type=oneshot
ExecStart=/opt/secrets-issuance/secrets-issuance/backup.sh
Nice=10
TimeoutStartSec=300

View File

@@ -0,0 +1,51 @@
#!/bin/bash
# secrets-issuance backup — snapshot the per-client age keys + denylist +
# admin token to /mnt/library so they survive an LXC-105-only failure.
#
# Destination is under /mnt/library/.secrets-issuance-backup/ (dot-prefix to
# stay out of any indexer's path). Mode 0700 root-owned. Privileged LXCs
# that mount /mnt/library would still be able to read it as their root maps
# to host root — accept that trade today; an encrypted-tarball variant
# (separate offline age key) is a follow-up if the LAN trust model changes.
#
# Retention: 14 daily snapshots. Each snapshot is the WHOLE /var/lib/
# secrets-issuance directory (small — ~few KiB per client), so restore is
# just `tar xf <snap> -C /`.
set -euo pipefail
SRC=/var/lib/secrets-issuance
DEST=/mnt/library/.secrets-issuance-backup
RETAIN=14
if [ ! -d "$SRC" ]; then
echo "[backup] source $SRC missing — nothing to do" >&2
exit 0
fi
if [ ! -d /mnt/library ]; then
echo "[backup] /mnt/library not mounted — can't write backup" >&2
exit 1
fi
install -d -m 0700 "$DEST"
chown root:root "$DEST"
stamp=$(date +%Y%m%d-%H%M%S)
out="$DEST/secrets-issuance-$stamp.tar.gz"
# Include the admin token too (recoverable separately, but trivial size and
# saves a step when restoring after total LXC 105 loss).
tar czf "$out" \
-C / \
var/lib/secrets-issuance \
$([ -f /etc/secrets-issuance/admin-token ] && echo etc/secrets-issuance/admin-token || true)
chmod 0600 "$out"
# Drop oldest beyond retention.
ls -1t "$DEST"/secrets-issuance-*.tar.gz 2>/dev/null \
| tail -n +$((RETAIN + 1)) \
| xargs -r rm -f
count=$(ls -1 "$DEST"/secrets-issuance-*.tar.gz 2>/dev/null | wc -l)
size=$(du -sh "$out" | cut -f1)
echo "[backup] wrote $out ($size); kept $count snapshots"

View File

@@ -0,0 +1,11 @@
[Unit]
Description=Daily secrets-issuance backup
[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=10min
Persistent=true
Unit=secrets-issuance-backup.service
[Install]
WantedBy=timers.target

View File

@@ -0,0 +1,66 @@
#!/bin/bash
# Install/update secrets-issuance on LXC 105 (apps). Idempotent.
set -euo pipefail
REPO_DIR=${REPO_DIR:-/opt/secrets-issuance}
cd "$REPO_DIR"
echo "[deploy] git pull"
git pull --ff-only
echo "[deploy] ensure python venv + deps"
if [ ! -d /opt/secrets-issuance/.venv ]; then
python3 -m venv /opt/secrets-issuance/.venv
fi
/opt/secrets-issuance/.venv/bin/pip install --quiet --upgrade pip
/opt/secrets-issuance/.venv/bin/pip install --quiet pyyaml
echo "[deploy] verify dependencies (age, shred)"
command -v age-keygen >/dev/null || { echo "age-keygen not installed: apt install -y age" >&2; exit 1; }
command -v shred >/dev/null || { echo "shred not installed: apt install -y coreutils" >&2; exit 1; }
echo "[deploy] state dir"
install -d -m 700 /var/lib/secrets-issuance
install -d -m 700 /var/lib/secrets-issuance/keys
echo "[deploy] admin token dir"
install -d -m 700 /etc/secrets-issuance
if [ ! -s /etc/secrets-issuance/admin-token ]; then
head -c 32 /dev/urandom | base64 > /etc/secrets-issuance/admin-token
chmod 600 /etc/secrets-issuance/admin-token
echo "[deploy] generated admin token at /etc/secrets-issuance/admin-token"
fi
echo "[deploy] install systemd units"
install -m 644 secrets-issuance/server.service \
/etc/systemd/system/secrets-issuance.service
install -m 644 secrets-issuance/deploy/webhook/secrets-issuance-deploy.service \
/etc/systemd/system/secrets-issuance-deploy.service
install -m 755 secrets-issuance/backup.sh /usr/local/bin/secrets-issuance-backup
install -m 644 secrets-issuance/backup.service \
/etc/systemd/system/secrets-issuance-backup.service
install -m 644 secrets-issuance/backup.timer \
/etc/systemd/system/secrets-issuance-backup.timer
if [ ! -d /opt/homelab-context/.git ]; then
echo " /opt/homelab-context is not a git clone — run bootstrap.sh first." >&2
exit 1
fi
systemctl daemon-reload
if systemctl is-active --quiet secrets-issuance.service; then
systemctl restart secrets-issuance.service
fi
if systemctl is-active --quiet secrets-issuance-deploy.service; then
systemctl restart secrets-issuance-deploy.service
fi
echo "[deploy] daemon-reload + enable backup timer"
systemctl daemon-reload
systemctl enable --now secrets-issuance-backup.timer
echo "[deploy] done"
echo "First-time enable:"
echo " systemctl enable --now secrets-issuance.service secrets-issuance-deploy.service"
echo "Webhook first-time setup:"
echo " $REPO_DIR/secrets-issuance/deploy/webhook/install.sh"

View File

@@ -0,0 +1,30 @@
#!/bin/bash
# First-time setup for the secrets-issuance deploy webhook. Generates a
# secret, installs the systemd unit, and starts it.
set -euo pipefail
SECRET_DIR=/etc/secrets-issuance-deploy
SECRET=$SECRET_DIR/secret
UNIT=secrets-issuance-deploy.service
install -d -m 700 "$SECRET_DIR"
if [ ! -s "$SECRET" ]; then
head -c 32 /dev/urandom | base64 > "$SECRET"
chmod 600 "$SECRET"
echo "[install] generated webhook secret at $SECRET"
fi
systemctl daemon-reload
systemctl enable --now "$UNIT"
systemctl status "$UNIT" --no-pager | head -10
cat <<EOF
[install] webhook listening on :9821/deploy.
Configure Gitea (dtoro/Homelab-Docs → Settings → Webhooks → Add Webhook → Gitea):
Target URL: http://<lxc-105-mesh-ip>:9821/deploy
HTTP Method: POST
Content-Type: application/json
Secret: $(cat $SECRET)
Trigger: Push events
EOF

View File

@@ -0,0 +1,13 @@
[Unit]
Description=Gitea deploy webhook for dtoro/Homelab-Docs → secrets-issuance
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /opt/secrets-issuance/secrets-issuance/deploy/webhook/webhook.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,91 @@
#!/usr/bin/env python3
"""Deploy webhook for dtoro/Homelab-Docs → secrets-issuance on LXC 105.
Listens on 0.0.0.0:9821/deploy. Validates Gitea HMAC, runs deploy.sh.
"""
from __future__ import annotations
import hashlib
import hmac
import json
import logging
import os
import subprocess
import sys
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
BIND_HOST = "0.0.0.0"
BIND_PORT = 9821
SECRET_PATH = "/etc/secrets-issuance-deploy/secret"
DEPLOY_CMD = ["/opt/secrets-issuance/secrets-issuance/deploy/deploy.sh"]
TARGET_REF = "refs/heads/main"
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("secrets-issuance-deploy")
_deploy_lock = threading.Lock()
def load_secret() -> bytes:
with open(SECRET_PATH, "rb") as f:
return f.read().strip()
class Handler(BaseHTTPRequestHandler):
def log_message(self, fmt, *args):
log.info("%s - %s", self.address_string(), fmt % args)
def _reply(self, status: int, msg: str = "") -> None:
self.send_response(status)
self.send_header("Content-Type", "text/plain")
self.end_headers()
if msg:
self.wfile.write(msg.encode())
def do_POST(self) -> None:
if self.path != "/deploy":
self._reply(404, "not found")
return
length = int(self.headers.get("Content-Length", "0"))
body = self.rfile.read(length) if length else b""
sig = self.headers.get("X-Gitea-Signature", "")
secret = load_secret()
expected = hmac.new(secret, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, sig):
log.warning("signature mismatch")
self._reply(403, "bad signature")
return
try:
payload = json.loads(body)
except json.JSONDecodeError:
self._reply(400, "bad json")
return
if payload.get("ref", "") != TARGET_REF:
self._reply(204)
return
if not _deploy_lock.acquire(blocking=False):
self._reply(202, "already running")
return
try:
result = subprocess.run(DEPLOY_CMD, capture_output=True, text=True, timeout=180)
if result.returncode != 0:
log.error("deploy failed: %s\n%s", result.stdout, result.stderr)
self._reply(500, "deploy failed")
return
self._reply(204)
finally:
_deploy_lock.release()
def main() -> int:
if not os.path.exists(SECRET_PATH):
log.error("secret file missing: %s", SECRET_PATH)
return 1
server = HTTPServer((BIND_HOST, BIND_PORT), Handler)
log.info("listening on %s:%d", BIND_HOST, BIND_PORT)
server.serve_forever()
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,266 @@
#!/usr/bin/env python3
"""
Secrets-issuance HTTP service.
Lives on LXC 105 alongside the MCP server. Mesh-bound (nftables restricts to
Netbird + Tailscale subnets). Identifies callers by source mesh IP and returns
the per-client age private key.
State:
/var/lib/secrets-issuance/keys/<hostname>.key (private, 0600 root)
/var/lib/secrets-issuance/keys/<hostname>.pub (public)
/var/lib/secrets-issuance/denylist.txt (one hostname per line)
Inventory lookup:
/opt/homelab-context/inventory.yaml — the issuance server is itself a
homelab-context client, so it sees inventory updates within 5 min.
Endpoints:
POST /issue body: {"hostname": "..."} -> raw age private key
POST /revoke body: {"hostname": "..."} -> shred key + add to denylist
(called by 'homelab client remove')
GET /health
"""
from __future__ import annotations
import ipaddress
import json
import logging
import os
import secrets as sysrandom
import subprocess
import sys
from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path
import yaml
BIND_HOST = "0.0.0.0"
BIND_PORT = 9820
CONTEXT_DIR = Path(os.environ.get("HOMELAB_CONTEXT_DIR", "/opt/homelab-context"))
STATE_DIR = Path(os.environ.get("SECRETS_ISSUANCE_STATE", "/var/lib/secrets-issuance"))
KEYS_DIR = STATE_DIR / "keys"
DENYLIST = STATE_DIR / "denylist.txt"
ADMIN_TOKEN_PATH = Path(os.environ.get("SECRETS_ISSUANCE_ADMIN_TOKEN",
"/etc/secrets-issuance/admin-token"))
MESH_SUBNETS = [
ipaddress.ip_network(s.strip())
for s in os.environ.get("MESH_SUBNETS", "100.64.0.0/10").split(",")
if s.strip()
]
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("secrets-issuance")
def load_inventory() -> dict:
return yaml.safe_load((CONTEXT_DIR / "inventory.yaml").read_text())
def known_hosts() -> dict[str, dict]:
return load_inventory().get("hosts", {})
def host_by_mesh_ip(ip: str) -> str | None:
"""Resolve a mesh source IP to a hostname per inventory."""
for name, entry in known_hosts().items():
mesh = entry.get("mesh", {})
for proto in ("netbird", "tailscale"):
p = mesh.get(proto, {})
if not isinstance(p, dict):
continue
if p.get("ip") == ip:
return name
return None
def in_mesh(ip: str) -> bool:
addr = ipaddress.ip_address(ip)
return any(addr in subnet for subnet in MESH_SUBNETS)
def is_denied(hostname: str) -> bool:
if not DENYLIST.exists():
return False
return hostname in {line.strip() for line in DENYLIST.read_text().splitlines() if line.strip()}
def deny(hostname: str) -> None:
DENYLIST.parent.mkdir(parents=True, exist_ok=True)
existing = set()
if DENYLIST.exists():
existing = {line.strip() for line in DENYLIST.read_text().splitlines() if line.strip()}
existing.add(hostname)
DENYLIST.write_text("\n".join(sorted(existing)) + "\n")
def generate_key(hostname: str) -> tuple[Path, str]:
"""Generate an age keypair for hostname; return (priv_path, pubkey)."""
KEYS_DIR.mkdir(parents=True, exist_ok=True)
priv = KEYS_DIR / f"{hostname}.key"
pub_path = KEYS_DIR / f"{hostname}.pub"
proc = subprocess.run(["age-keygen"], capture_output=True, text=True, check=True)
priv.write_text(proc.stdout)
priv.chmod(0o600)
# Extract the public key. age-keygen writes "# public key: ageXXX" line.
pubkey = ""
for line in proc.stdout.splitlines():
if line.startswith("# public key:"):
pubkey = line.split(":", 1)[1].strip()
if not pubkey:
priv.unlink(missing_ok=True)
raise RuntimeError("age-keygen did not emit a public key")
pub_path.write_text(pubkey + "\n")
log.info("generated new age key for %s (pubkey: %s)", hostname, pubkey)
return priv, pubkey
def existing_pubkey(hostname: str) -> str | None:
pub_path = KEYS_DIR / f"{hostname}.pub"
if pub_path.exists():
return pub_path.read_text().strip()
return None
def read_admin_token() -> str | None:
if ADMIN_TOKEN_PATH.exists():
return ADMIN_TOKEN_PATH.read_text().strip()
return None
class Handler(BaseHTTPRequestHandler):
def log_message(self, fmt, *args):
log.info("%s - %s", self.address_string(), fmt % args)
def _reply(self, status: int, body: bytes | str = b"",
content_type: str = "text/plain") -> None:
self.send_response(status)
self.send_header("Content-Type", content_type)
if isinstance(body, str):
body = body.encode()
self.send_header("Content-Length", str(len(body)))
self.end_headers()
if body:
self.wfile.write(body)
def _read_json(self) -> dict | None:
length = int(self.headers.get("Content-Length", "0"))
if not length:
return {}
body = self.rfile.read(length)
try:
return json.loads(body)
except json.JSONDecodeError:
return None
def do_GET(self) -> None:
if self.path == "/health":
self._reply(200, "ok")
return
self._reply(404, "not found")
def do_POST(self) -> None:
client_ip = self.client_address[0]
if not in_mesh(client_ip):
log.warning("rejecting non-mesh source %s", client_ip)
self._reply(403, "non-mesh source")
return
if self.path == "/issue":
self._handle_issue(client_ip)
elif self.path == "/revoke":
self._handle_revoke()
else:
self._reply(404, "not found")
def _handle_issue(self, client_ip: str) -> None:
payload = self._read_json()
if payload is None:
self._reply(400, "bad json")
return
hostname = (payload.get("hostname") or "").strip()
if not hostname:
self._reply(400, "hostname required")
return
# Cross-check: does inventory list this hostname, and does its mesh IP
# match the caller (if known)?
hosts = known_hosts()
if hostname not in hosts:
log.warning("unknown hostname in /issue: %s (from %s)", hostname, client_ip)
self._reply(403, f"unknown hostname: {hostname} (run 'homelab client add {hostname}' first)")
return
if is_denied(hostname):
log.warning("denied hostname %s tried to issue (from %s)", hostname, client_ip)
self._reply(403, "hostname on denylist (was removed); operator must clear before re-enrolling")
return
inv_ip = host_by_mesh_ip(client_ip)
if inv_ip is not None and inv_ip != hostname:
log.warning("source %s maps to inventory host %s but body claims %s",
client_ip, inv_ip, hostname)
self._reply(403, "source IP / hostname mismatch")
return
if inv_ip is None:
# Inventory has no IP for this hostname yet (first-bootstrap state).
# Accept the call but log it loudly so the operator backfills.
log.info("issuing for %s (source %s) — inventory has no mesh IP yet",
hostname, client_ip)
priv = KEYS_DIR / f"{hostname}.key"
existing_pub = existing_pubkey(hostname)
if priv.exists() and existing_pub:
# Re-issue: client lost its key but is still authorized. Return
# the existing key.
log.info("returning existing key for %s", hostname)
self._reply(200, priv.read_text())
return
# Fresh provisioning.
priv, pubkey = generate_key(hostname)
# Tell the operator (via response body comment) which pubkey to commit.
body = priv.read_text()
body += f"\n# operator: add this pubkey to inventory.yaml under hosts.{hostname}.age_pubkey:\n"
body += f"# {pubkey}\n"
self._reply(200, body)
def _handle_revoke(self) -> None:
token = self.headers.get("X-Admin-Token", "")
expected = read_admin_token()
if not expected or token != expected:
self._reply(403, "admin token required")
return
payload = self._read_json()
if payload is None:
self._reply(400, "bad json")
return
hostname = (payload.get("hostname") or "").strip()
if not hostname:
self._reply(400, "hostname required")
return
priv = KEYS_DIR / f"{hostname}.key"
pub = KEYS_DIR / f"{hostname}.pub"
for p in (priv, pub):
if p.exists():
# shred-then-unlink
subprocess.run(["shred", "-u", str(p)], check=False)
if p.exists():
p.unlink(missing_ok=True)
deny(hostname)
log.warning("revoked %s (shredded keys, added to denylist)", hostname)
self._reply(200, f"revoked {hostname}\n")
def main() -> int:
KEYS_DIR.mkdir(parents=True, exist_ok=True)
server = HTTPServer((BIND_HOST, BIND_PORT), Handler)
log.info("listening on %s:%d (context=%s, state=%s)",
BIND_HOST, BIND_PORT, CONTEXT_DIR, STATE_DIR)
server.serve_forever()
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,23 @@
[Unit]
Description=Homelab secrets-issuance (per-client age key provisioning)
After=network-online.target homelab-context-sync.service
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/opt/secrets-issuance
Environment=HOMELAB_CONTEXT_DIR=/opt/homelab-context
Environment=SECRETS_ISSUANCE_STATE=/var/lib/secrets-issuance
Environment=MESH_SUBNETS=100.122.0.0/16,100.64.0.0/10,192.168.8.0/24
ExecStart=/opt/secrets-issuance/.venv/bin/python /opt/secrets-issuance/secrets-issuance/server.py
Restart=on-failure
RestartSec=5
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
NoNewPrivileges=true
ReadOnlyPaths=/opt/homelab-context /opt/secrets-issuance
ReadWritePaths=/var/lib/secrets-issuance
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,63 @@
# secrets/
SOPS-encrypted YAML files. The plaintext lives only in transit and in the
operator's head — committed files are always ciphertext.
## Conventions
- One file per logical grouping (e.g. `gitea-tokens.yaml`, `webhook-hmacs.yaml`,
`api-keys.yaml`).
- Recipients are declared in `../.sops.yaml` by path-regex, not per-file.
- The plaintext schema inside each file is free-form YAML; the consumer code
decides what it expects (e.g. `gitea-tokens.yaml` contains
`{"<host>": "ghp_xxx"}`).
## How to add a secret
```bash
# 1. Decide which clients should be able to decrypt it; edit ../.sops.yaml to
# list their age public keys for the new path_regex.
# 2. Create the plaintext, encrypt in place:
sops -e --in-place secrets/my-thing.yaml
# 3. Commit + push. The 5-min sync propagates to every recipient.
```
## How to consume a secret
```bash
# On any client that's a recipient:
homelab secret my-thing # prints plaintext
# Or programmatically:
sops -d /opt/homelab-context/secrets/my-thing.yaml
```
The `mcp` tool `list_my_secrets(caller_pubkey)` returns the names of secrets
the caller can decrypt. The MCP server never reads plaintext — decryption
stays client-side.
## Granting / revoking access
To grant a new recipient: edit `../.sops.yaml` to add their age pubkey, then
re-key every affected file:
```bash
sops updatekeys -y secrets/my-thing.yaml
```
To revoke: remove the recipient from `../.sops.yaml` and `sops updatekeys`
but remember this only protects future ciphertext. Past plaintext the client
already decrypted is gone from your control. Rotate the underlying credential
if compromise is suspected.
`homelab client remove <name>` does the recipient removal + `updatekeys` for
you, and prints the rotation checklist as a follow-up.
## hello.yaml — bootstrap decrypt test
`secrets/hello.yaml` is encrypted to every enrolled client. Used by Phase 3a
verification to confirm the end-to-end decrypt path works on a freshly-
bootstrapped machine. Content is intentionally trivial:
```yaml
greeting: hello from the homelab
```

View File

@@ -0,0 +1,80 @@
user: ENC[AES256_GCM,data:e6Dj7CE=,iv:Bf4tvdWQr0vdF3PShG2cRTryxqRC5rN6UdGd1qwFg7g=,tag:22rrPMAF97C2GUhRGgutOw==,type:str]
token: ENC[AES256_GCM,data:98bMZO2t0qgOO/rTYmyDBZdRTHu8iPgUsctS4dc0B2BCML6tBxBNhA==,iv:oXehq2FJy1pQxzK8M3LqDtjpoFbdfAPvYcKhygwf51A=,tag:RMYdimBwxw247OARznBu3g==,type:str]
scopes:
- ENC[AES256_GCM,data:S3nz8buLTp1PCHpf8F3Y,iv:IkHU+QwIlsxrNrGEu3tRS0zlnuCyp9Fs8ILxUaZrabw=,tag:Ufd+MVYXAAtbSbfvRi3jCg==,type:str]
- ENC[AES256_GCM,data:/UG/8LQD21l/Ca0wfEodKw==,iv:f3tW1ZNLLkzE5Oy8acQ6GfyDvzEH3++kp8duBOI8+Is=,tag:IgfhvEaX9EGyPHhr6FSAjQ==,type:str]
note: ENC[AES256_GCM,data:vRDjHXh5fYlhwJ4LyS/hcqMGdS+Z/D1W2oH6NaJak/XRUr+nFVQqcXSH/zUgqEBWNrMv6j8n2Ouxex/q2mr+p3Ds1jfThJTobX/MFqMkxvxk0TmB7ucui7CDa4mcVgSo3wyhcaba2o3erObByZ3o2uay3mmqzAUdo/wy2bCgQTkTG9/xqVSf3EYuR1j/A+SAzSvpsOORMNUjC5NRP1DUL+3hZkPXsfkXuy7sV6T2vF+oOrbEI0WxR8o4/JcF1B/4ezFAJ+f+nukCMz6eSCwur/wHffbcYa8mtD6BIRuV1pTd/Zob8arhe2ir19MB0fR27E3UkcCwyCkQ+QWuGzisvciCPtwR4JidH7xOpPW+4AK3vqEjmpEeMSWlyxbZadQFuSi8f3bPH+8eB2h/UFvT0BU6xw==,iv:r2dUnKyUWhyafIeDIjHOdqh+PNFzGtVZfcHc2TAhvvI=,tag:Cmg6esU4mQ8/9UbIQQL/gw==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIa05CSXpkcUsvbFYxcnNM
b2o1ejFFRVdUaVhzYURPRm5oTnBxUXgwYWtVCjY4L3hvcy9NMHpDM1Q0WGJMZ3Mv
aXhVek5uWkIrdEI3cFl2N2NUUjc2ckkKLS0tIFpSRDZqeE5VM3ZsNGdYTHRWcUQw
QkhSSVZkVm05TEp1RmFNdXFydVhpQXMKgpF5MlP8wkOLkbCxozmtG+axD32rs/SA
Oc1Eo/ggRT54EGCFoMPITnzsC0ELqZOnOjpZi2FU/Vh1pEhBXCOwJA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1duyl8mkpgu80uv934dy8q7enqjms6yvdz264hme8uryuxmvvqesq6rusq0
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxeXFyQW8zb09QRm94M1da
ejBkbCtzMkQyaVF3RkxscWk1cDBiQW5SdmxrCjRCa1UvZ2VhQ3Rqb3pXcjN2d2dS
VlROVHh0ZnRWbGVhdDYvY0ZDWUUxNE0KLS0tIE9zT2VvZ2ZjWnBNYWxTaWtFVjkv
S3VIdUgydit1Wk9kMnd0ZDB5NE1iaVEKLvVToIX8zc6lXi0Ro59copkeCl19Nv3H
obbUoW6v7tVnM/Khiw0DhHzzhLbgLFfSm/e6h/8o4Wok8CSHvp35iQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age1vf8h7s8mqsn2q5eadgpdupsj4mwn8zguc77d85ws3xj40sl9rgksx2rxw6
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlMy9nek80M3k2cTNPQlRG
WE42aGtsWllkaHZjOFg2b3ROdmZuWUNLQXhBCmNZZC9mSXNkU2xLUnp3MDNVZ1hn
YTA5TzRHNHJMN0xqMDhhZGxxSytpZDQKLS0tIHVwdHNRczZaLzRrK3QyLy9qU0E2
anExUUFlRzdiY3dFbU5VMW83K2I0a1kKFaQVQH6mjh7I4r9UNk1Uvw2hyYnwLGSJ
Z9PxVOucYfODHoWMq1jWcW2vQQO5G8dWtE3S97yoqNVteAf8nmH4xw==
-----END AGE ENCRYPTED FILE-----
- recipient: age1z62ff2ak9zj5ctcvaxwyyhedwjvlwgm2dkn9nk3wrwk8fkavcpmsqwc2vs
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDaHZmTzkrTjhETkttOWho
azcxb0lTNVd3aW5iZzBxUkNZQjE4T1EvblYwCnBSdFN3WFhBYWRFeTlSQk1MbkQx
bm9ORkJRbHhJLzJva1pvSDF0SU5pZlUKLS0tIEZLSmxBdmE0RHQ1Qk05MnBnUVlL
aWtDcTZHZ1BWWmtLNHEveEE2K3F0eVUKO5Yi/DEmFL5wJDL3XVFlRfWTjPTNVFKz
G6NPwO5qeC0kFCOZWtKU8+6f0m3tw2ZsNylntnCZbRScbshR9QhVEg==
-----END AGE ENCRYPTED FILE-----
- recipient: age1s07zs83ehtlg8jtwvr75ltc3c4cdlemfwjuxrwjtwkqxkl9tpggsyrzn2h
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvSkEvNm1NZFgxRlJaeVZu
TCs0L3c0UEpTWmlDRnV3QnkxUWYxTnFIZjJnCnlOMm5lOS9MbmJwekNoczZsZFFa
R3JKYWZoNjNLTnhscXp2UGVrOHBsUkUKLS0tIEx1K1lYODBuczllRkl5U0JTTGpC
RC9Mc2NyTUhPekJnY0VWSDVpZzlhVjgK1bFDQaqJf0Nz+IjTPPMgi5DxbW/ln5Po
SABlg3INzGh2pyAgOroBEKr0Dl2dU23/hX4uixP2YxkJRkbamU3+6w==
-----END AGE ENCRYPTED FILE-----
- recipient: age1rtwvdct6avjkr3cyxv3vue3vqx4d524fjfr3vk7xrnvyrylnry5sm54sn4
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHQUE3RHhGS01SYzMrZ0lK
elR4UEQvT0w1anVjeHNKN1JERGxwcUoxc0c0CmNLVEFVMWFDdGNlM3VLcDFueDNa
SW10cWhlWk5KMWtYMnFXTmJYbFQ1RU0KLS0tIGNHVWloaEtIeEVoREMvT3lqT3dS
ejZwcVhWS1NmNVByRGY0ekM4V2lKQUEKOWxj1HmtSPoungbzek2Z0okRS59A2QwY
/zXoEXgIskP9Rjr4zf29cE4RYSYW2UVU4aeE9fVA3Y+NrylGi5I3ng==
-----END AGE ENCRYPTED FILE-----
- recipient: age1pwtdws2thdh7vzp2dzttl3zxgcs2tgpcsjsqgw3q04nyml4kvuqq467u4x
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzYWQ4RHpNWEpKNWpMRGll
blpTUWpmV1lOZ3lKdUwwL2o4TzNMY3lnV0NjCmRUNEQ2OU93QXdhRURveXY1bE5S
alAwNzVGRVZtaXg0TU9xclJZMGNvSlUKLS0tIEM1SmY3TUhkN0FRb1JUMDFKVndN
a0dIZlZ5ODZBMGtEZmxiTFRNUEdKQ1EKXYAaezVE0H1viKEo49D60i/zqHv8xZsg
8c4oVRGVW3wLzb4Dd4O2MvLpNpy1yRgd1pN+qUB/Pft2G3IwWWw3BA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-05-20T16:24:18Z"
mac: ENC[AES256_GCM,data:d3qcjEqpPIFeut634ImvISJLdit0MQWcdFYomrsqriqJ0NUKdyq3XCk85+vvPvDWikB4WEApk6HMXssSY8mhDci95q5Ssmr+JRAhLebMZjs9yXUf9A7vXpNFswmdldu3CKBiDrhm2GE08qUfsCkcf3jjihEqsfHhxMuWSd5fZpU=,iv:VZgvZ3bBArRChgRX38U6/43XoX5aTnts7Kd4S9spBPk=,tag:/5oOw5yvulvFT6KXv2zj0g==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.9.4

View File

@@ -0,0 +1,76 @@
greeting: ENC[AES256_GCM,data:eNMtZcpv04PHl/lGBZGBp7xAcO/5Sw==,iv:A4jtwePX0p/eDTP5+ztfV6gege9RwdPfLBY2N+DPfx0=,tag:MU8HpvLt/PUrNMw7BNUVIg==,type:str]
phase: ENC[AES256_GCM,data:oIeo+kmkwaMDQrf10iY2PqOXJnq+hCIwOROQ86xgpkU7EJEeJKFssrNgbvQPHhIRZUZblQg9ZPamiZixQbYSTOXQSTWWu2FOnu6XFb24wCay,iv:TYKqoHcN+V324/roNopYhkwRpmv3cPrTFcmni5TajYQ=,tag:g8DXqEyORbQZ9B7PaFWgOQ==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzUFNydDVhMEpvMVNyMTk5
N3ZiTGZibmNhWDBMajlOQW5BVGllQ0lWcnlVCmJsQVZ1Z0c0WTNIYWFYbFk2R2lZ
RkUyR0M1Tjg0SUxhY0RrU1FnYlNrbFUKLS0tIFdSWlllREpVUnVOVTh6dTRxenhk
WndBSEdid1hlRUZmMTl0a3d5N2lRbncKIkcQC2JTSxGES+DOzLCT3G8KrrIO2nUK
jjovwCLP1XxoTwXfv85nMPNwsJkCddg5sh/FXGTDglSBHfZ94RFeYQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age1duyl8mkpgu80uv934dy8q7enqjms6yvdz264hme8uryuxmvvqesq6rusq0
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJZEptakpiYnRoaDNhek9E
c1NKSVNsZDBlbWhabTJ4NkM4RmNNTXVTS1ZNCmJ1RHUzNlZ3aGdsUHZCaXNEeTBM
TTIzNHY0Nk1sOGRCQkVyRlc5Y0s1VHcKLS0tIGx3NDJDRmZiYzdTTUc1clRkamtm
b2gxTGpZbEdVUS9oMVUxODkrVERqUncKTsngnt9pySyKqxhBOlDFbtqaO72Bq3Z4
CNVVigYh73xWK8CHVU+nj0QG+VFnRr8FJhRxpxJw/ZfFpHvpSKSxtg==
-----END AGE ENCRYPTED FILE-----
- recipient: age1vf8h7s8mqsn2q5eadgpdupsj4mwn8zguc77d85ws3xj40sl9rgksx2rxw6
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5eDY2V2U1YkJhc1ZwSVRM
d1NnQ1ZvUHpVbG5tWkoxT2N2M3ZRYml4NlJJCnJDVmV5bzdvTS9FZGlxY3pheDBu
VFRWZ3pCdUFFV1R1dUtTSmMzcHdrUlkKLS0tIGRHcThteTlOUUMxOXZSS2tlVVpt
ZGl0VkJpSlVxQ2pzaEdmQnpmY254L3cKQ51TCwCzDpN8T5qsozW2ScSiBvvERLg6
p2128rIityffiCCUoOCUt8gHdqDBBivruVnofINlp7fN7m3xss6NcQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age1z62ff2ak9zj5ctcvaxwyyhedwjvlwgm2dkn9nk3wrwk8fkavcpmsqwc2vs
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNbHBLU1pJRXlIWFN0MzAr
bnZUd3NoYXVsMmxKNkR0ODBlcFlDRkdTUm1zCks4Q042Y2dCTVV5OEJTS2REQm41
b3M4RVM5Vk8zSEIyYTF4aDdJWDNKbHMKLS0tIGE1Tzcrb1VTUExieFRxNDJPSFFa
SkI2NTMxdUhraWduSHpLMmh3SDdZaGsKzjZvj1Y9UlEoGZp6FJoOxW6r7sX7g7Jj
FpK4QiUhiKztNeUIdx7abte2rKSFqyjLdq45SKdqi4Bg1iiuqh6Umw==
-----END AGE ENCRYPTED FILE-----
- recipient: age1s07zs83ehtlg8jtwvr75ltc3c4cdlemfwjuxrwjtwkqxkl9tpggsyrzn2h
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtNEhuY2Rrb2hzWTVFQlZM
VHJURUJxQSt2dVhqbTVZNVBBWVg3ekpBeFJ3Cnd6bmNEL0RTNjc3UXEvOFA2ZVhF
dThNNkd5R3c5L2JKRTA1NDhNRXYxU2MKLS0tIFFnVU9TK1JHQnhRRy9CNWpzZjdy
OFZYb3E4YlB5VlcvRVA0TFM0bFFLeTgKSFvNi+X2CI2zvWBdcctv26ocG07Xvtw1
mstp3VQUhwMYe63YVl/liU/JnBSC9EN0Q0/8WdsVUycm2HEIRBnIUg==
-----END AGE ENCRYPTED FILE-----
- recipient: age1rtwvdct6avjkr3cyxv3vue3vqx4d524fjfr3vk7xrnvyrylnry5sm54sn4
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1Z3FRVzZWUU1UN0JUMGdw
T05yaU05Q2wzc0xpTGNkSThOY2U2YkNSQnl3CllYbUlwN3FxSHpaM2Ewa04vR09j
RlF5a25NSW8wc1ZteEpyR2NJdWl0ZWMKLS0tIFBQQmJEYWpNaTlTMXdqdFh0OCt1
eDF0U3oxdXdQdFpNMjBBRGNFZnY5ZWcKvYjG2p6/MEd7VV5/K7tnNTqZC3R1BsVd
oAHqKo4+mcqyRaS0UYoSfiDo6XyTUXNk8n3zRGMlKuSaTqGVpv/PMA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1pwtdws2thdh7vzp2dzttl3zxgcs2tgpcsjsqgw3q04nyml4kvuqq467u4x
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPU3BuZ2ZERWJDZU0vdHVi
cmxwTFc2WXE1SjhBdmhnSUJIZVRrb1FHRnpzCkdiOHdFZ3NCVjM0SVN4cVZ3MkJn
NzFiV0tFMzJPUDB1Y21mSDZGOGVzOUkKLS0tIDFUVEpUd0NETy94KzlCSU5UTG5G
K1Blb2EySlhnQnFxUm1zNXNOMDRES28KXeFuHSpm3VrdVdzK1cMiFQQEzvjko/Xq
Bsfu3mZ7fNlHW5szZtAFsyxPyvaELCPGprKmQFzUn68GFtphJxmXww==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-05-20T14:39:14Z"
mac: ENC[AES256_GCM,data:CAoIW1sJkIbNoXMaVlXwfHdREzATNqDKpSN5ecaSiRARThB0/tcLNbkazXKyV1Rx5b+b3s+yCpBzwmNnH6ytaCzG5pKPcnfd6YJq7C5rm/EQHyaVGrPdBsSfqiz4yBXKClej//v3+qkD5Ls2PMppv5KmqigOKNQC/ot3ht2c7lM=,iv:eOmDDclksr9f9CDu6dJprSFwlWuoggzVFjyfqWf5Uhg=,tag:IU3sMlIi92ohM86zOIJdAg==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.9.4

View File

@@ -0,0 +1,50 @@
members:
"+491726924525": ENC[AES256_GCM,data:RA==,iv:FLOi3HmBPFtS8BAqPR9Epihs868OwAWs1t2LGjg9kGU=,tag:bf8PGNipOeepfnzHUE5KMA==,type:str]
"+4917622791635": ENC[AES256_GCM,data:Mw==,iv:onlvwM9nlAlXnL6f+BTRZlOi4+C6pryyiay421GY9WA=,tag:KiYrTcRlBNbB5mYYlb7GHg==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmYW9VS2FLb1BTVTk2OFZZ
Wnd1U2tnanR5N2d6SmNqdFYrekpzZTRMRGpZCjZhREcrY29qc2pFZUhnQnc3U1dE
aU5Kd1Robkk4OVd2M0ZkalBWMlQzR0EKLS0tIDJOTjkwR2htaDhDR0hqN0R4cCtB
dldLSHBOVjZxNVFZVUtkZm1INkdvM1kK2iaqr75MZ+4QlWaNOjH1X11zHbi4Ahy0
ely7Fakx6wSoQtl803q6UyNJyIqCOavJKoLQw48FhAl2yGmv9KsvrQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age1vf8h7s8mqsn2q5eadgpdupsj4mwn8zguc77d85ws3xj40sl9rgksx2rxw6
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4eXFvMjZXVWkvZnNLSGhS
eWJ3Q0M0QzlnYnJ2dTVhZGRtZ3Y4Ylh5NHc0CkVTRnNLa0NzK2czcjFSSmgxQ081
ZVo2MzR4S2ZlY1AxZC8rWTJ4d25vekUKLS0tIEFnK2gvTVBUWk5jOW1NaUtXTmRL
U1FCaHZjaTUwZ1RGVlZkZ0JzT1NOek0KceYGFDlpcpAQte0yqebyuQBxr00/Rurh
McQ607wCQWbGKkwkHCDi1VGqqokvbU7MRT9iOBLLBlxEF+KS9UiYuA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1z62ff2ak9zj5ctcvaxwyyhedwjvlwgm2dkn9nk3wrwk8fkavcpmsqwc2vs
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6aXRkd3d4ZTV0RmltR1JN
bFg4VU0vMDVuR0cxc2I0b0NRRUQyQlE0RHk4CnpPaUpGMEh0Unk4d3BDeE84Q0px
RnZnUHRQa3BJQUpGWUFZd2dLczhmc3MKLS0tIE9NOUNacDM5dktuWWZ2WWRxajZu
Uk5Gb216dmdvUkJzYkoyWE5yQ044eWMKSGR5dDya0gyBWDMB+NSudXK38VQYP88y
M6lyeZUAJ3cFydjyE1PjyllBehX9tYh/rh9RS67y/zRSmkXH5e7eAg==
-----END AGE ENCRYPTED FILE-----
- recipient: age1s07zs83ehtlg8jtwvr75ltc3c4cdlemfwjuxrwjtwkqxkl9tpggsyrzn2h
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkM2I2MkV6NlhydURwYk8w
ajB1cEtiQjlLYkRGbDZXTHkzTWxuNW53NTJVCmJuMzJZSlNtU1BxVFNpOWpSVWp3
TmxOeTRhRmZlanp4QmVmRjRSOElHL3cKLS0tIHp1MzZQUlUxNXFlS1B6TWhQdzhE
ekFibGF3ZE95K2FjZmZmVGkzek54R1UK7Au0Op4P+JZjHIiJJ5effjxjeAJItXw1
Wrq8Z6D9NZTAZ2TBUGpdqIZcZhBiOuqxdNV88ZHOu8sjT1SLwm17GQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-06-28T14:50:45Z"
mac: ENC[AES256_GCM,data:7gWwrXY9q7zpiDZgFidEMa4VbxJ0uM7HdG6Ea8P4fbISgRnOx+aoAwznEQ+K/fIBsa1qtiR82lIxsNr6b44W4jmFF3+rPAm9DMs6ImX1KIVCW3GVptM9fMqOxnCI7RGMKKwWYxkvfE2lwl5EsBljqqlsuxDfX26etfqukIhbOIY=,iv:6iI7Q8wFKguICPtFvZWshok7BkEUUITp5XcSYt9hwFM=,tag:jIOzsy4jiSvuHh5GNQsjpQ==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.9.4

View File

@@ -0,0 +1,66 @@
client_secret: ENC[AES256_GCM,data:p8csBIMAuDb1BYLLL9aYIifShpw9uIWm1Z0c59SlzBDP3SJD4I8Z6ipYBETisgxcECr+78zs2wtuebtcHZ3DBqF5PZaG87TJEGotTkjOVSjDLKgelMq0TDfnpWyR1yedsi6/SiRhm4ggTz0Vwwo+TLDNkw3Gv7EP7fZQphYdMyA=,iv:MZcvhiBwDc8FWcNqQvCiTL+9OVEfHpZrzt1kmcKcxdA=,tag:FmEIrw7u2wYiF3ksymC5lA==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhYmFWOGllS0ZTeU5yUkdO
QUpHMHdXUDZsY3VFaFJjbkQ5aDJVUk5RSG5vCmJ5RWNhZXZhdkJwdUk3anNjcWlt
WG5WSjA2UzN5TmloUm81VVo4eGdCdDAKLS0tIGtpclRlcjhlSDJnSFBmUkluM3Rz
bm9NK3Q1NEhMSStjM0hSSXlBVUlab0EKeR1I/tAHV0ivIeaJkr0OC8W54Wvwpyuy
qc5QCjXeD9tpMltakMFqk+tDNtItRebWO33RD9TpSkryPhiYxywF2g==
-----END AGE ENCRYPTED FILE-----
- recipient: age1duyl8mkpgu80uv934dy8q7enqjms6yvdz264hme8uryuxmvvqesq6rusq0
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZelpTaWIwbFN6bUJRemZu
blF6VnlqZDdKNFpaY2ZXTTZCVVZETmtxUGlZCjlEaVIzc3cyVmxXQmNDRm5JdVpJ
bm9hbjNZNGVLSldnak8wWDhqZUpqRGMKLS0tIDhoUTJjU3FDRjdYcFNmekJ3dXAv
QW1tWElyWGJPSldhdWF0ZkFTekVoVDQKlBub868JsuRRrPrHkopMyD+GkeQo7lSn
s7WoYd2q0oKeF4JDgO9yNZbH1zGg+5Qa76wnOqk7z2zphGIpADmyaA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1vf8h7s8mqsn2q5eadgpdupsj4mwn8zguc77d85ws3xj40sl9rgksx2rxw6
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1Zzc5NUJZbjNVQVAxUDdi
WVIvUXZnK21uWmlWVUVLWDBrdmlnZFJlbnlvCjhsRG41NTdRU1NrOTRlMStzZHBl
M3ZZUkpPNFBaRERLUGQ1cGZKTVAxdGMKLS0tIHYzdytHdmJWWHc1eDdHUUxNWnBl
TzJBTFhxZy8vWkFGNnNTNVZjK0ZIaUkKpRokUUB7W1Xt9jP7eVQw9Hch7Fn2oEQM
cuWcayD3w/DliXsEuS3ZGwn7bjzbJKLpDZmcxCNTwd600UdokAc/rQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age1z62ff2ak9zj5ctcvaxwyyhedwjvlwgm2dkn9nk3wrwk8fkavcpmsqwc2vs
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYRUdQZ2k2T1AydGlBRWg2
YUtVS29BV2VDWGVzV0xuYkF3U2NvbzdtYzFRCkRkK08vaDlOdEFsVHhCaEJ5cXZz
bEZ4cUxjem5oM3RlZm1JZXdUcStGZk0KLS0tIHM1a1VYS1krUlg2OGVNN1pCdTdT
TVVLK2FBdlRWTjdqWEc0WXlibytJczQKUoAigx2KB4JYy+LxbpAdUcuTW8afL7hz
9eTizEzZRphfoEioIvIes+sLAraTj1hQH/jSFYaM9iS3sE2Zz8u35Q==
-----END AGE ENCRYPTED FILE-----
- recipient: age1s07zs83ehtlg8jtwvr75ltc3c4cdlemfwjuxrwjtwkqxkl9tpggsyrzn2h
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1cXRIWk54U2VyNVNuRGl1
WkxlbFBMa24xdStmcW0rTUJFcUdDTUNqdmhnCms0cmdIUWtUYXNpTHBOVHB3czZn
Qmh1UFh5RkxXdlpPa2sxK2c1M3RmRFUKLS0tIHFYQU9jUzdqQlVRZi9RNmVzM1ZN
OStmN2VQWHhXMjczRjdtYU9wUUg1WUUKrkjJBSPWowCpNFtPm4kvQKTiKBAquilo
fwV5/ds2gvsidoTeC6aSNIP279TZu+iT6n/K6vdt7h+Gz0Xj5YbyNQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age1pwtdws2thdh7vzp2dzttl3zxgcs2tgpcsjsqgw3q04nyml4kvuqq467u4x
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArbFZkV1VmWXFvNE5pMXRq
a0ZDZUVsRXJvVUVNTENDbG15MmV1ZDJ4eWdJCnJWY1dhTlRkUUd5anl2c1hYajhJ
NmNzai9GSFVralRnaHg1NElIeFQxQVkKLS0tIGROK2pGMS9mVTJpanFxQURPNDYr
ZzUwdDBEdS9kNSs5bDFYV0FUT2xuaDgKOwWcRF2vk82imiho3y8RliyBYzhG7AP8
NIham/3UzYOLViPTHmMhNY3fEwUf7pv07Z1tsAglnPm3iaxbBl0Gog==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-05-21T20:13:20Z"
mac: ENC[AES256_GCM,data:bi4o17skIOtZoxJGzLFJ7IAv+X265qIhgnQz6wr4bch2xofwPZZfzECck0I0xDhcwxcDtx3VZ5E2VBdvHWk+iSan+clRq7735k9+DIpjdWaGxIHuylmelZl6aE5kE36UO7fQdnwmiqCPHxiVq/sKuqknFXkl1+FvO1ryrtSHQsU=,iv:xxm+5rK7o2c43iGS1j2Q274QCRsbDn+k+wskpp5tzeU=,tag:sZHk9eth/TwqM8tIWSrv3w==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.9.4

View File

@@ -0,0 +1,36 @@
#ENC[AES256_GCM,data:yB1EyrABkGl4G+Mq67djNXCuySBS1TDZjKwtko8hfZi15br6+L9dlwhVTHrzvHAdGYOvkZu/dCssii0kKXC+3QeJws+SfwUV,iv:r7bb8QjkyfeaATPKN3VzO4H+ykQTyUKRMxoE9bmGo+E=,tag:TA0actRKQmYsgCMAcvQkjA==,type:comment]
#ENC[AES256_GCM,data:oVH2uanu0pyv7B0SKiaSKfAXWmKAimXKR2mHKi/+hvFIQGzf3mYTZ5zGMQdBCLoy,iv:pgQrEqMZmynEHfIIHHi52xSNwn0/uQ2ccnK0T9T/Z7U=,tag:8hdflKb6l7HlqHTto8eEyQ==,type:comment]
netbird_pat: ENC[AES256_GCM,data:cz9pdu7aLR+vKV9QbQ2PtKWf5j5PWxcHEWZ8IEseHS/qPdR4M8282g==,iv:3MCOU0q/7nqQCD6+7qWq++XR9uHi5QHZoenCIT2aYIk=,tag:m4ZE4pDycjwGxoLi1Li1bg==,type:str]
sops:
age:
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpYVlocHFacWxtTnhPeTV1
SnZqVUUwbXVId0dKNTdmaVQzSUcrT3gxYTA0CloyYm1oQlU3QXVUNVIxTXpGQkN6
cTBxTHViZ0E5bEF0dmx4S3l5WVJZOW8KLS0tIEQ5UmJGZG5aVDhGblFDREc5M1dR
ZlJ1UEVLeWFsQm0zQ0Q1Z2pTbEpFenMKsw+gfYZ+sby1crIyfoy+yGIaQ2WZ2KYU
YgsXY4Vt5b5DC0u/sHKaqXH3bw8ooBQ7tR4WX3JvU6emIAJU0rAuwg==
-----END AGE ENCRYPTED FILE-----
recipient: age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaM3NXTExiRzZ0VWdRR1RH
RUlBVVBTWFpUQWtCTnc2T0VqMDJITVFiOFNnClZRbElxRFlGb1orbThmdGI3dFRM
V1Jqck9pRzVjWGhVQlIvVUFLMXkyT1EKLS0tIGY5K3g3VDJjUHpjSTNrOTRBUEpz
dHBmQ0FpeHRSc1ZyMjJHdU1pM1FzbFEKqIJ0qC0SY0aSceh0ZhGVDMb/WCkh1JZZ
E3cP7r4Sa6rMZXRR4u23wTHnrckdHgwxSSXYYymy4ilXhgiFQ3ZTBA==
-----END AGE ENCRYPTED FILE-----
recipient: age1duyl8mkpgu80uv934dy8q7enqjms6yvdz264hme8uryuxmvvqesq6rusq0
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhSElYV3dkMU5hR1Vnb0tl
L1R0SG0zeFRuZUd2aTVVaU1leU14bHJjVWl3CjhibVp4NWFsazRYWkl4R1NWTUdk
TFdrOXc2OWY0aWxUb3NqM3R1T2xVVEkKLS0tIHlUZGUrRmM5VVVTOEZNYTBSZ2s0
VWVTdXlZcmJqeU4xc2NLSDY3MWNUUVkKix4IZEbRI7icZciI7bYzIvDiW4M3Nb7O
uopkLc9bC/E+qK9H7jfySkk/zIeW1s/s7fBEKB5utUVpvmKzUB24pA==
-----END AGE ENCRYPTED FILE-----
recipient: age1vf8h7s8mqsn2q5eadgpdupsj4mwn8zguc77d85ws3xj40sl9rgksx2rxw6
lastmodified: "2026-06-03T19:57:46Z"
mac: ENC[AES256_GCM,data:zez2CaP1NPJJnUbEWT58If6jKxFiAudRRPKVJCzbjtp0loFohvOHt1nFY3g0XldqkNngsSZ83GOeJ9g+CvdD7/plkmjz0C04uHWahnNhjAwNRiN32xt31XFqEejScOHW4dbOunX8wXuQ0Q4SrSLTl5G9O/qbLwlwEiqp8KxwRWA=,iv:ctqQ9TDUM65POxn4v1iILubygU2lloCLz9u0bAP6gsY=,tag:05FpkqIhPRMX5hYopFNK2Q==,type:str]
unencrypted_suffix: _unencrypted
version: 3.13.1

View File

@@ -0,0 +1,25 @@
hmac_key: ENC[AES256_GCM,data:KEvRp+N/NYHlTJZoehf8cehYMpShnP43s+KWX3OR6p9udA4d1B2lMqXFhrXHxEuWDzLdfOP97AG19DDYgjemPw==,iv:SH8cyI9HP974Af54v0ecwN3ICnX+eBudP6XwB/9nwMU=,tag:VzyLoHy5LRa/5hjL43O2eQ==,type:str]
sops:
age:
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyQ2s4QVJQY1VSZG5JN1NL
Vll1MGZNSGIxQ0NJZEFpYUJ4OWxweXFOUmtRCitsODBCTVhMempzYVFpNHpmNWds
Qk5SL2pOUGVkbzBtanRmM3BVT1JwYmcKLS0tIGpTV3k5OVF2aXZFRk1JZDVNNk1C
VkY1SXpQS2YyM2g3TGpDWG9HdEZqTDAKYhb7yKywJ+jj7ChWCDRmhHsYCASMxBgd
hLZV9pmIYCC4jse8q4wJK+tbT+Za5mklXt+j8nZoqm+sA0+MNlnV6g==
-----END AGE ENCRYPTED FILE-----
recipient: age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHV0F6RzFiVHF0bS9pNER3
elQ1YUorYUsvVC9zWXVBeXYzcThOU3JBbEVZCkxMQlVnMVNtNElibGhUaHhJM0hO
cXhFUU5KYzAzQUREMVNJTjhmYlpXekkKLS0tIGhxb3Q3blQ2a29YM3VLdWY2aFgw
Qm9HUEl4ZEd0WDVOZG96WXdORjdGVmMKNDNwkB4IF1xcFqG79dnUpHamOzlwQZJt
3qfpdXquDeOOomnk7FLlnF/IqyHnhXNGqg+W1XBbZpwvqcAGtTFxQA==
-----END AGE ENCRYPTED FILE-----
recipient: age1duyl8mkpgu80uv934dy8q7enqjms6yvdz264hme8uryuxmvvqesq6rusq0
lastmodified: "2026-07-05T21:11:20Z"
mac: ENC[AES256_GCM,data:yz52Q1+49ntOFMESD3QHb5yGE7LB9SfSnHIWeMmYllaYTqR/rcNSXAb/b04ZWvLHqESGLLfZif7gNQYx3Fes0AFnAccowec7IWk/wNY0QoHLA8tZOw2Y1J1Hm32uVOWLl+G6MQybHGt9vHPlz33CLDeB/V07j7WjORqtYjVl8A4=,iv:zvx/bV3WX48OzN3Hi+moL+cELOHaw1SzW/eSCjJ25bg=,tag:DiIZfLyljK+xB3NjZsrwlA==,type:str]
unencrypted_suffix: _unencrypted
version: 3.13.1

View File

@@ -0,0 +1,16 @@
webhook_secret: ENC[AES256_GCM,data:+/+IOmg+PC1IvItSOEl9PJZ53HhTBC1Qg6NGTQEW5K1mC/7YE1CQEpAr0w==,iv:rv9+eE1OhkudGtBB6VELcHWeDIQz6IvH3nl6H1y8nks=,tag:vzkMN8lfL3Sw1a/OA28i3w==,type:str]
sops:
age:
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsNnhBVnpJaUFNcWZYZW5C
Sml0SkI1SVFVQ0hsSDkzMXBWNlZmQmRmRjNBCjZqNWRUQk45elp3VFRjUEpqVHFs
cDNBVmM0aEtJZGplT0EwU01RNGNxZkUKLS0tIFFJMXh6N2Q3Ymg4UXdoTExFalVp
dmpPWXlKT0tuUnhkc0pEUWFuS3l3OXcKCaSTvIPguiHnxPquk9AiT/zKBRNWvgxK
L4Iawa12jTsRSIM6Z+T8ow2eCz0P+dtrUmxc40WXmKR9OI9mPpZQOg==
-----END AGE ENCRYPTED FILE-----
recipient: age1duyl8mkpgu80uv934dy8q7enqjms6yvdz264hme8uryuxmvvqesq6rusq0
lastmodified: "2026-07-06T12:00:57Z"
mac: ENC[AES256_GCM,data:wGYgH6R6jReR2DUSyBQbs3xxF6tvD/conjODOri85ups9hMqxZUXGsxBBbshjwIZrs1LDOoDqq4tYtWcnLPa6awbx0gZdTe6AJji2ApFDf9Ah1MkysvHio7Qr7GdyxrJbdt80bfP/tF740m9iovv2eEUNJosQOnCz6NQmwd2nZQ=,iv:caYOD2gm596LrJjnFVG8UpD8x9TQaa2ixXreoYKUc3U=,tag:GYx6NpG/lWcuANhtlY3uLA==,type:str]
unencrypted_suffix: _unencrypted
version: 3.13.1

View File

@@ -0,0 +1,48 @@
api_key: ENC[AES256_GCM,data:TbPkuLCidS7cg52DYb0MOCui3TsaiwkOWyYLhp6gcvXI8TqvXej1tNsCF0TNAIr9vvnFaYno/UTEPiOKe/EQqR8uQMyWuvsmMg==,iv:bahy9ae4Qxvkv1OAjx6LwzK5ggqDh2GVHDj95PVP1Mc=,tag:J7PajLaeqFOdz3FESYZmSQ==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4TFVsb3VYWnVFcnhRL2Vz
SVp6cG9CYzdqSFNkTGhHSmtlRDJjVWwyK0RNCkdEcEJtNjB0aHBZQVZBbTlHMTla
cHduT1lyTWlESmQrVHB6NU9LTmU2MTQKLS0tIHkwQXM5TzZCRXFsOHdjYThZQ0Z2
bS9wOTBDenRMb2hGcmgrcTl5R3dnTG8KZfSI1pnfpcqtD0Z5N6hONSeuAaggCkZI
VJgh6TF7soOHbvddcNc5RINHQI6XLOZAeSOzvKm3zT8y5xe48Ff+3g==
-----END AGE ENCRYPTED FILE-----
- recipient: age1vf8h7s8mqsn2q5eadgpdupsj4mwn8zguc77d85ws3xj40sl9rgksx2rxw6
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkb3ZmZmpPZDAzZzB5SmNx
SHFKT3JENnVwZUttcFNYd3RoTHI2MCttQUNFCllZc2Z5Y1dSSnV0bW1LeGJvaURh
TlZGWjhseUdjU1FRWE41aTlMczI0U28KLS0tIHpzajRWWmlUU3FCaEFWMlBsVjBT
bGVRMGF2YjVwcVNCYkNEZVM1TmsyUncKYLscCS6BD3fwVmK3JnkISsEhXvhuRBnr
tJ+v7mcEXj4pz8FjIyY6MG3T+EXx6+HwuWtPyxbmEfqnxu2Ocfg5Dg==
-----END AGE ENCRYPTED FILE-----
- recipient: age1z62ff2ak9zj5ctcvaxwyyhedwjvlwgm2dkn9nk3wrwk8fkavcpmsqwc2vs
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUSCtGaUdaR2pVekZCUUkz
am1pRTJSdzgvUjJsTlpXVE0rVlczOFZ3aGpzClJNVXRJV2JqbHB5ZEUvUFJXR2cy
MUpad0ZMYUI2MmtoUm8zcG1RS01WcmMKLS0tIGsvVGhITGRsQkJXcUdmQllOVEVr
QnN5QVZXcFR0cDVab09hcEFiWjRoMmsKYo5ThhNgdp9GUXZkyF6JGGdWnmHmZK5c
QIqk5L5fYq+Wif8bKUJBEv2fGyJJZD74AOCFjQUIDgBnLx2tK3Kf4Q==
-----END AGE ENCRYPTED FILE-----
- recipient: age1s07zs83ehtlg8jtwvr75ltc3c4cdlemfwjuxrwjtwkqxkl9tpggsyrzn2h
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiVHRnMTZXK1FNWmFJZlF5
MWk5cjRHQ2NuRWJrMlhQVXhPL094elF1MW5VCkx5TklkR1VQSXc1OTljaXBFR2FQ
WEtudU5DNUxTWGNpbUFESjBoSkh6bVUKLS0tIFFiV2NkcktOdnF1djY2UC93OVdF
Unhpa0h5RU9xUHNic0hXVGhqSUthN3MKMAOF3gxkJVFU9aKqeUaViDs+Ka8NI4YQ
Bb8wPf9BcQe0NPjVMrxxP7L9c5oACiiNuA/46YxMYu7K1dOj0KapGw==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-06-01T21:23:34Z"
mac: ENC[AES256_GCM,data:DXvUZUOMKQA0aDpN1foKy+aHhBh6daGSRJM1FagTh76qutk4XF9Een9iyy9sQ7olabCjf3oUezvU7XWB83bSDD3e23CFuweOl0RreRFqIsbXBi55Dbcxw8+9fvMyOK4PXu6mkT/PRao/XR5sSMqiHt3FoJbjazSaJlI5teeVXbM=,iv:IICO5ay+KqkTikfxW4kqB2qcSRpbsOP1NjGWtCB9uqI=,tag:MX2TqUxre+ff3RszCfXeFg==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.9.4

View File

@@ -0,0 +1,100 @@
# Secret rotation runbook (Phase 5)
Rotation cadences per secret type. All rotation is automated via Infisical;
this runbook covers the manual verification and DR procedures.
## Rotation schedule
| Secret | Cadence | Method |
|--------|---------|--------|
| OpenRouter API key | 90 days | Infisical rotation policy → update `OPENROUTER_API_KEY` env |
| MCP bearer token | 30 days | Infisical random password generation |
| Approval HMAC secret | 90 days | Infisical random password generation |
| Matrix access token | 90 days | Manual (Matrix does not support automated rotation) |
| Age DR key (SOPS fallback) | Never | Static — stored offline for DR only |
## How to rotate a secret
### Automated (Infisical)
```bash
# Secrets managed by Infisical rotate automatically per the policy above.
# To force an immediate rotation:
infisical secrets rotate --project-id $INFISICAL_PROJECT_ID \
--secret-name <secret-key> --env dev
# Verify the new value is available:
oikos secret list
```
### Manual (SOPS fallback)
```bash
# If Infisical is unavailable, use the SOPS DR fallback:
sops -d secrets/<name>.yaml
# To rotate a SOPS secret:
sops -e --in-place secrets/<name>.yaml # edit in place
```
## Rotation verification
After any rotation, verify the consuming services still work:
```bash
# 1. OpenRouter key: test Hermes query
curl -s -X POST http://localhost:8092/query \
-H "Content-Type: application/json" \
-d '{"query":"fleet health"}'
# 2. MCP bearer token: test MCP connection
curl -s -X POST http://localhost:8092/query \
-H "Content-Type: application/json" \
-d '{"tool":"list_entities","args":{"limit":1}}'
# 3. Approval HMAC: create a test execution
curl -s -X POST http://localhost:8092/query \
-H "Content-Type: application/json" \
-d '{"query":"restart caddy"}'
```
## Disaster recovery
If Infisical is completely unavailable:
```bash
# 1. Export SOPS DR fallback
oikos secret export-sops > /tmp/sops-dr-backup.txt
# 2. Configure services to use SOPS fallback
# Set OIKOS_SECRETS_DIR=/opt/homelab-context/secrets
# This switches the secrets manager to SOPS-only mode.
# 3. Restart services
docker compose restart api hermes notifier scheduler
```
## Restore drill
Run monthly:
```bash
# 1. Export all secrets from Infisical
oikos secret list
# 2. Simulate Infisical outage: stop the container
docker compose stop infisical
# 3. Verify SOPS fallback works
OIKOS_SECRETS_DIR=./secrets oikos secret list
# 4. Restore Infisical
docker compose start infisical
sleep 5
# 5. Verify Infisical primary works again
oikos secret list
```
## Changelog
### 2026-07-07 — initial rotation runbook
Phase 5 rotation cadences, verification steps, and DR restore drill.

View File

@@ -0,0 +1,66 @@
password: ENC[AES256_GCM,data:C9uA7LHAc/SmWJsICGMNLu2OOYyowIhLq/sTfuNo/w==,iv:211oFm7MdphnXwYszRxWrHLeKw7qeh0BNzP0yomTUTk=,tag:zfhfkzLm0AivKwD7TZTEqw==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3S0pWdGZGSFcyTG1pMnMr
ZWpOQ1RpR1R4T2tDYTNMdmNtcnFwVzlCRHhFCkpJQm8zaDNnWS80OHA4dksyTE1P
c1BnenJ3ZWRESlRNajltWXgwUSszYzQKLS0tIDcvdk5CaHgwWlBQanIzYmJ4V1Z2
V2pEb0M1WmxGZFF3TmZacXFBakNpZjgKyv7Z3XvyLYTGoyS2e8ptPgIALQWKi18t
b3qO9TTY98HGUwTLBU7Tf3IcHy8aO+u67sCVMbDhfFTd4g0bfdrNOw==
-----END AGE ENCRYPTED FILE-----
- recipient: age1duyl8mkpgu80uv934dy8q7enqjms6yvdz264hme8uryuxmvvqesq6rusq0
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByZGI0ZmhWMVlUT0x2MkpM
dUk4cFBZSXFYcHd4dWt1d3hCYlFzUkJEdEJnCkdIaFBDcHJyS0p0aUZXb2xKckIw
Ky9CT2lLWXp4dkUwNEpoRmd3YWNDaGsKLS0tIHdROUx1Uzc4NEVrZnBxMHVmUi95
SXdZaUZGZklIalYzd2xZdjZtMFQ0MnMKxEKbyLvlwdrDx4G1CmUs2qs1nJbdIlSu
coXVNnkbt6FIzNO08DxTGBP7Q2XQWQKMvRpBFDYYUssOGBHL3JPVgQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age1vf8h7s8mqsn2q5eadgpdupsj4mwn8zguc77d85ws3xj40sl9rgksx2rxw6
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWU243SmlKVHRwKzMvaFk1
U2ZpcnJxK0RwZXhzWFpQOUNyS0JOZlljbFgwCitQU1hTbzMwNlltcUZaSUxXK01T
VGFxLzZCZGZsUEtXWjY5b2dBZmxDcGcKLS0tIDM4S0Z2MW1rVmlyZjJ6N0NZQytq
d0xLUlNHdE1mZVhOZjVManA1OXhBV2cKgoo6S6kVypzCljJSqSRQP2fSYHizdZQF
H6GNSOCj5fYbkZQwa3WUyun8YRl0BMgdkg1Uym++b2EGGCOFoHSuyg==
-----END AGE ENCRYPTED FILE-----
- recipient: age1z62ff2ak9zj5ctcvaxwyyhedwjvlwgm2dkn9nk3wrwk8fkavcpmsqwc2vs
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrWU5PKzk2R1h0VVIwMFFl
MzBCN1BXUGdvS3FobkNiTGZWRnVkckVWUGs0ClVSaVEwclBDdnkwalllUFF3T3ln
V1dHL2Q0MnR0Nm9BUTdIbTRVUlJQZk0KLS0tIDRjd1hjT1lja3hPS3FyQndnNXJi
SzczZU1pVG5xM05TMUdtcHNNbHE3WjAKITW1sBP9TqZ6+cKNMnvQPzuNAdawaX8m
H4aAjlaR+BCrJPNJmv3J+VT3NZxw8rFIKQBu+c5h9RaU3RX1o/A2PA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1s07zs83ehtlg8jtwvr75ltc3c4cdlemfwjuxrwjtwkqxkl9tpggsyrzn2h
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3d0p3Z3J6VXBvVjZpMXVa
TDZ6V1BEdTQzYzErMitDeHpRT3NFQ3FVRURzCnlWaVkzYjhXbStVSmp5eThBcXZY
RG5MQXpWYXB1aVhISmNJZ0p3K0k4akUKLS0tIFJWa3NkQ0tEc1NscHRjR2FmalpL
SVBrem8yT1FqRjI2dlpQTzB1N3JSWXMK08Gj8cRPmImMVPbS2/0W2qHqJCZ9Gy9S
VgvSaI1PX1ieOkpqvl6nNUfWLzykZkdcAu9DhHC9MSwDDYy8vOBHPw==
-----END AGE ENCRYPTED FILE-----
- recipient: age1pwtdws2thdh7vzp2dzttl3zxgcs2tgpcsjsqgw3q04nyml4kvuqq467u4x
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSdVBpMmFEcUZkYjdKY1Qz
aCtWOXFoc3FGd0dZUHNwNExuRFF3cDhXNEhBCnQxZWNkbmxQMnNSaGhjS0E3TS82
a25OVytyK2lnUkpwYmRIUEJmZG1yLzQKLS0tIFA2U1VVUzdNb3B1SUFnZmF3VndQ
R0NuY0NWRjU0MmVEWGhyYWN3dk9nbmMKbN0hg8Sd8/TJxTSvhMYicpwLnR7LPD7P
HeH+hwrqzT/3CRDpL7DRpILommAEQ+3j1dbaLTdbE1xhBp7jegfD3w==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-05-21T20:13:20Z"
mac: ENC[AES256_GCM,data:PXXEpDbJIoIwWuNwMetpALtxrcB7yJhDYqp6LSKV8WFQI2TJSDgDHiNQ2cngfB9PXKmUNNhlvhmB52Hi6WQSWQdXjiy79T1fOjMhbUAKmykTaZDrwvlrDGkPgiZ1cFSb8+hx/5aZw7YhdQO9+7LAmymeIldZIFEpEO2pvuPZ6MM=,iv:mJb3K8TnsnY183OX7o9pF7oVMDcwVU/rOgX74KTRpr4=,tag:f+M+FT0Vjozm7a8LFDI8lA==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.9.4

View File

@@ -0,0 +1,48 @@
token: ENC[AES256_GCM,data:LgiLFEsli412rVxSo9YU69wx9Lh7eDGBw6nmROh6Qbw7bYN8lylNjSDWkjNJ/UJvDg==,iv:vNmiV/D+SQaMtgwTK/mFDQIwSQ/L0JRatmoj5AWfBiw=,tag:W5P98TlmevQ0j051044LPQ==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQYVBwS250T0ZFa2VGbGow
R0MwMzNsY0t2N1ZBVnlnbkF6UEIyUStaUEhJCnBuNWNZT1VxRFlKNFBjOTZHNEd0
cCtxN1p3TjkzWUZNZkdZeXRJVEhaQzgKLS0tIFVSWUpVQ2k3VE83eHU4VmR4SnVH
RVgxdDNnKzMwM2c2bm1YS1ZSRDdsMVkK8PVncSpesaxIEz7a39rVBzB0V3RphmAA
ANV8fY4W4tLuxo+EhB90ciy2dnAHaKEbxHBWMMjN4u22yZRT4lPWQw==
-----END AGE ENCRYPTED FILE-----
- recipient: age1vf8h7s8mqsn2q5eadgpdupsj4mwn8zguc77d85ws3xj40sl9rgksx2rxw6
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4Sk1hdENFQWIvZXhIK0U0
TmhCWkw5TWhSVXdIbDN5SmVNdHkwYm1aOGxvCmlnQmZYWitxUmdURzNYRDBFUUVM
TjdIN2FEd2ptWG5UVlFCb1IyVzFjMmsKLS0tIDJMZHlXVHFNUlY2bnhlS3hFT3FY
U2szdVB4MGxxUnpJN1BBaUhKVE5WQkEKz7BkFIfs2RSyAww73dH5+PyDNjo4Ocor
mX5pLhSRGGf13bfA5lMQiGvqtd3jw1c4zKVZD9BcthIk5H/oqallNQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age1z62ff2ak9zj5ctcvaxwyyhedwjvlwgm2dkn9nk3wrwk8fkavcpmsqwc2vs
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmVWI2L05GbjdLR1k3a280
N2RRWmZsZWZPeWdmd3I3aThrV1ppOEw4VTJRCit6aXgwTVIwQldkSmpRNkM4UTh4
UEcyQjkrMFk2UGVBL1JZdGg2WDI3c0EKLS0tIGV5VkRWYWpXZXZGN1FXNmhTSmR5
cjQ0TWg0TkFmMnIyMDNBNVZXa1l0REUKVHdvbXFwLgMECi2JLg8aoYIkOWHwYyZh
v3mVVaaki5KceuyUhcGsdthNadrM0RDi89uUw8O2cfADdDgLlha/iQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age1s07zs83ehtlg8jtwvr75ltc3c4cdlemfwjuxrwjtwkqxkl9tpggsyrzn2h
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnemkvTUxHZDNxYmJ4eGRK
Y09laWNnN0pXN1VHQzUzSWlJZGFqcmkwTWdJCjkxZG9pVVVzWXBTU0lpL0JFR2w3
TWhPTDNFSXhPTkVMVUpOY0xHMlN6RkkKLS0tIDB3d0ZyRVpGT3YzSXIrM1pPQ1Uz
QkpQYXRWaGtabnU5c1RpeE9vb0MwMWcKVRnpepDJ8a2ECD5uaK6O3fMKwWcLJP8a
uFWds5rvwDfrM1aNLXYbPIfXtqLx4fbxkoRZvHcuZ2/lYLDBFBrFlQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-06-28T14:02:13Z"
mac: ENC[AES256_GCM,data:NDjBNtSzGdm0+SqAvaUlltljXcyXzLQx3b9RUWZIlS/D3ox1BTO6t3t4WT8szbSnKcT51mJGJ8EIqppNWkmOpEaMNGdMw70DmGnf/IJAzScTpRyvhfsGlohCVaekMBjF+wg1uoB91vSKcTFq/wfUW79KICmxMF6woavRQ94yfQs=,iv:1f9sGKyTITSslqDXB3Khk/OGiPaJM2BreSEG6aTbZDs=,tag:UulDl/JQamg/p/q4esvtIA==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.9.4