Vault — Setup
HashiCorp Vault for secrets management. Raft storage (no external DB). TLS terminated by Caddy. All service secrets are stored here and injected via Ansible at deploy time.
Infrastructure
| Host | LXC ID | Internal IP | URL |
|---|---|---|---|
| Alpine LXC | 106 | 192.168.1.106:8200 | https://vault.eva-00.network |
IaC
| Artifact | Path |
|---|---|
| Playbook | ansible/playbooks/vault.yml |
| Workflow | .forgejo/workflows/vault.yml |
First-time initialization (run once only)
ssh [email protected]
export VAULT_ADDR=http://127.0.0.1:8200
vault operator init
This outputs 5 unseal keys and a root token.
- Save the root token in Vaultwarden immediately
- The unseal keys are stored in:
- Forgejo Actions secrets:
VAULT_UNSEAL_KEY_1,VAULT_UNSEAL_KEY_2,VAULT_UNSEAL_KEY_3 - On chizuru:
/etc/vault/unseal-keys(root:root, mode 0400)
Root token
The root token is not stored in the repo or on any host. It must be saved externally (Vaultwarden) at first init time. The Forgejo Actions secret VAULT_TOKEN contains a working token used by CI workflows.
Auto-unseal (Proxmox hook script)
Vault unseals automatically when LXC 106 starts. A Proxmox hook script on chizuru fires on post-start and calls the Vault unseal API three times using keys stored in /etc/vault/unseal-keys.
To reprovision: Forgejo Actions → Setup Vault Auto-Unseal → Run workflow (requires VAULT_UNSEAL_KEY_{1,2,3} Actions secrets).
Recovering a lost root token
If the root token is lost, generate a new one using the unseal keys (stored on chizuru at /etc/vault/unseal-keys):
VAULT_ADDR=http://192.168.1.106:8200
OTP_JSON=$(curl -sf -X PUT $VAULT_ADDR/v1/sys/generate-root/attempt -H 'Content-Type: application/json' -d '{"pgp_key":""}')
NONCE=$(echo $OTP_JSON | python3 -c "import json,sys; print(json.load(sys.stdin)['nonce'])")
OTP=$(echo $OTP_JSON | python3 -c "import json,sys; print(json.load(sys.stdin)['otp'])")
while IFS= read -r key; do
result=$(curl -sf -X PUT $VAULT_ADDR/v1/sys/generate-root/update \
-H 'Content-Type: application/json' \
-d "{\"key\":\"$key\",\"nonce\":\"$NONCE\"}")
echo $result
done < /etc/vault/unseal-keys
# Decode the encoded_root_token from the last result using the OTP
python3 -c "
import base64
encoded = base64.b64decode('<encoded_root_token from last result>==')
otp = '<OTP>'.encode()
print(bytes(a ^ b for a,b in zip(encoded, otp)).decode())
"