<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.wilsoz.com/index.php?action=history&amp;feed=atom&amp;title=Services%2FOpenBao</id>
	<title>Services/OpenBao - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.wilsoz.com/index.php?action=history&amp;feed=atom&amp;title=Services%2FOpenBao"/>
	<link rel="alternate" type="text/html" href="https://wiki.wilsoz.com/index.php?title=Services/OpenBao&amp;action=history"/>
	<updated>2026-07-06T07:32:28Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.9</generator>
	<entry>
		<id>https://wiki.wilsoz.com/index.php?title=Services/OpenBao&amp;diff=9&amp;oldid=prev</id>
		<title>Wikibot: Migrated from services/OPENBAO.md</title>
		<link rel="alternate" type="text/html" href="https://wiki.wilsoz.com/index.php?title=Services/OpenBao&amp;diff=9&amp;oldid=prev"/>
		<updated>2026-07-05T18:16:03Z</updated>

		<summary type="html">&lt;p&gt;Migrated from services/OPENBAO.md&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;&amp;#039;&amp;#039;As of: 2026-07-05 — migrated from `services/OPENBAO.md`&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
= OpenBao Secret Store =&lt;br /&gt;
&lt;br /&gt;
Self-hosted Vault-API-compatible secret store. The canonical record for credentials used by Claude Code (MCP), shell scripts, and n8n workflows.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Vaultwarden remains the human-facing store.&amp;#039;&amp;#039;&amp;#039; OpenBao is the machine-facing one. They mirror each other; on rotation, update both.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Container &amp;amp; Files ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Item&lt;br /&gt;
! Value&lt;br /&gt;
|-&lt;br /&gt;
| Container&lt;br /&gt;
| &amp;lt;code&amp;gt;openbao&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Compose&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/openbao-app/docker-compose.yml&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Config&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/openbao-app/config/openbao.hcl&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Storage&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/openbao-app/data/&amp;lt;/code&amp;gt; (Raft)&lt;br /&gt;
|-&lt;br /&gt;
| Init output&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/openbao-app/.init-output.json&amp;lt;/code&amp;gt; (chmod 600 — root token + unseal key)&lt;br /&gt;
|-&lt;br /&gt;
| Scripts token&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/openbao-app/.scripts-token&amp;lt;/code&amp;gt; (chmod 600 — used by &amp;lt;code&amp;gt;bao-get.sh&amp;lt;/code&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
| Claude token&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/openbao-app/.claude-token&amp;lt;/code&amp;gt; (chmod 600 — also embedded in &amp;lt;code&amp;gt;~/.claude.json&amp;lt;/code&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
| Auto-unseal&lt;br /&gt;
| &amp;lt;code&amp;gt;/usr/local/bin/openbao-unseal.sh&amp;lt;/code&amp;gt; + &amp;lt;code&amp;gt;openbao-unseal.service&amp;lt;/code&amp;gt; (systemd)&lt;br /&gt;
|-&lt;br /&gt;
| Helper script&lt;br /&gt;
| &amp;lt;code&amp;gt;/usr/local/bin/bao-get.sh&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| n8n cred sync&lt;br /&gt;
| &amp;lt;code&amp;gt;/library/openbao-app/sync-n8n-creds.py&amp;lt;/code&amp;gt; (timer: &amp;lt;code&amp;gt;sync-n8n-creds.timer&amp;lt;/code&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
| MCP binary&lt;br /&gt;
| &amp;lt;code&amp;gt;/usr/local/bin/vault-mcp-server&amp;lt;/code&amp;gt; (HashiCorp v0.2.0)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;#039;&amp;#039;&amp;#039;VC sources&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
| &amp;lt;code&amp;gt;scripts/openbao/&amp;lt;/code&amp;gt; in this repo — see &amp;lt;code&amp;gt;scripts/openbao/README.md&amp;lt;/code&amp;gt; for deploy&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /library/openbao-app &amp;amp;&amp;amp; docker compose up -d&lt;br /&gt;
docker logs --tail=50 openbao 2&amp;gt;&amp;amp;1 | grep -v &amp;quot;^chown\|Could not chown&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Ports &amp;amp; Endpoints ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Port&lt;br /&gt;
! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| 8200&lt;br /&gt;
| API + UI (HTTP, no TLS — internal only)&lt;br /&gt;
|-&lt;br /&gt;
| 8201&lt;br /&gt;
| Cluster (Raft consensus, single-node here)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Endpoint&lt;br /&gt;
! URL&lt;br /&gt;
|-&lt;br /&gt;
| API (host)&lt;br /&gt;
| &amp;lt;code&amp;gt;http://localhost:8200&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| API (LAN)&lt;br /&gt;
| &amp;lt;code&amp;gt;http://192.168.0.109:8200&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| API (Tailscale)&lt;br /&gt;
| &amp;lt;code&amp;gt;http://100.64.207.155:8200&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| API (n8n container)&lt;br /&gt;
| &amp;lt;code&amp;gt;http://172.29.0.1:8200&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Web UI&lt;br /&gt;
| &amp;lt;code&amp;gt;http://192.168.0.109:8200/ui&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Not exposed publicly&amp;#039;&amp;#039;&amp;#039; — no VPS nginx route, intentionally. UI is LAN/Tailscale only.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Credentials ==&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;All in Vaultwarden → &amp;quot;Stronghold Infrastructure&amp;quot; folder.&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Vaultwarden item&lt;br /&gt;
! Contents&lt;br /&gt;
|-&lt;br /&gt;
| OpenBao Unseal&lt;br /&gt;
| root token (&amp;lt;code&amp;gt;s.TqAL...&amp;lt;/code&amp;gt;) + unseal key (&amp;lt;code&amp;gt;dHIJ...=&amp;lt;/code&amp;gt;) in notes&lt;br /&gt;
|-&lt;br /&gt;
| OpenBao Tokens&lt;br /&gt;
| claude-mcp token (login.password) + scripts-n8n token (custom field)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The init-output.json on stronghold has the same data — that&amp;#039;s how &amp;lt;code&amp;gt;openbao-unseal.service&amp;lt;/code&amp;gt; reads the unseal key at boot.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Reading Secrets ==&lt;br /&gt;
&lt;br /&gt;
=== From Claude Code (MCP) ===&lt;br /&gt;
&lt;br /&gt;
Just ask: &amp;#039;&amp;#039;&amp;quot;fetch secret/ntfy from vault&amp;quot;&amp;#039;&amp;#039; or &amp;#039;&amp;#039;&amp;quot;what&amp;#039;s the mxroute api-key?&amp;quot;&amp;#039;&amp;#039; — the &amp;lt;code&amp;gt;vault&amp;lt;/code&amp;gt; MCP server (16 tools) handles it.&lt;br /&gt;
&lt;br /&gt;
=== From shell scripts ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
bao-get.sh PATH FIELD&lt;br /&gt;
# e.g.&lt;br /&gt;
bao-get.sh ntfy token&lt;br /&gt;
bao-get.sh mxroute api-key&lt;br /&gt;
bao-get.sh authentik api-token&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Reads from &amp;lt;code&amp;gt;/library/openbao-app/.scripts-token&amp;lt;/code&amp;gt;. Outputs the field value to stdout, no newline.&lt;br /&gt;
&lt;br /&gt;
=== From curl (any host with LAN access) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
TOKEN=$(cat /library/openbao-app/.scripts-token)&lt;br /&gt;
curl -sf http://localhost:8200/v1/secret/data/PATH \&lt;br /&gt;
  -H &amp;quot;X-Vault-Token: $TOKEN&amp;quot; \&lt;br /&gt;
  | jq -r &amp;#039;.data.data.FIELD&amp;#039;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== From n8n ===&lt;br /&gt;
&lt;br /&gt;
HTTP Request node:&lt;br /&gt;
* Method: &amp;lt;code&amp;gt;GET&amp;lt;/code&amp;gt;&lt;br /&gt;
* URL: &amp;lt;code&amp;gt;http://172.29.0.1:8200/v1/secret/data/PATH&amp;lt;/code&amp;gt;&lt;br /&gt;
* Headers: &amp;lt;code&amp;gt;X-Vault-Token: &amp;lt;scripts-n8n token&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
* Response field: &amp;lt;code&amp;gt;data.data.&amp;lt;field&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or in a Code node:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
const resp = await this.helpers.httpRequest({&lt;br /&gt;
  method: &amp;#039;GET&amp;#039;,&lt;br /&gt;
  url: &amp;#039;http://172.29.0.1:8200/v1/secret/data/mxroute&amp;#039;,&lt;br /&gt;
  headers: { &amp;#039;X-Vault-Token&amp;#039;: &amp;#039;&amp;lt;scripts-n8n token&amp;gt;&amp;#039; },&lt;br /&gt;
});&lt;br /&gt;
const apiKey = resp.data.data[&amp;#039;api-key&amp;#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Recommended:&amp;#039;&amp;#039;&amp;#039; store the scripts-n8n token as an &amp;lt;code&amp;gt;httpHeaderAuth&amp;lt;/code&amp;gt; credential in n8n (header name &amp;lt;code&amp;gt;X-Vault-Token&amp;lt;/code&amp;gt;) so workflows reference it by credential ID.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Writing Secrets (via root token) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
ROOT_TOKEN=$(jq -r .root_token /library/openbao-app/.init-output.json)&lt;br /&gt;
&lt;br /&gt;
# kv put REPLACES the entire record — pass all fields you want kept&lt;br /&gt;
docker exec -e BAO_ADDR=http://127.0.0.1:8200 -e BAO_TOKEN=&amp;quot;$ROOT_TOKEN&amp;quot; openbao \&lt;br /&gt;
  bao kv put secret/PATH field1=value1 field2=value2&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For multiline values (private keys, certs), use the API directly:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
python3 - &amp;lt;&amp;lt; &amp;#039;PY&amp;#039;&lt;br /&gt;
import json, urllib.request&lt;br /&gt;
payload = {&amp;quot;data&amp;quot;: {&amp;quot;private-key&amp;quot;: open(&amp;#039;/path/to/key&amp;#039;).read()}}&lt;br /&gt;
req = urllib.request.Request(&lt;br /&gt;
  &amp;quot;http://localhost:8200/v1/secret/data/PATH&amp;quot;,&lt;br /&gt;
  method=&amp;quot;POST&amp;quot;, data=json.dumps(payload).encode(),&lt;br /&gt;
  headers={&amp;quot;X-Vault-Token&amp;quot;: &amp;quot;&amp;lt;ROOT_TOKEN&amp;gt;&amp;quot;, &amp;quot;Content-Type&amp;quot;: &amp;quot;application/json&amp;quot;})&lt;br /&gt;
print(urllib.request.urlopen(req).read())&lt;br /&gt;
PY&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Secret Inventory ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Path&lt;br /&gt;
! Fields&lt;br /&gt;
! Source-of-truth status&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;secret/n8n&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;api-key&amp;lt;/code&amp;gt;&lt;br /&gt;
| OpenBao canonical; n8n&amp;#039;s own auth is separate&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;secret/mxroute&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;server&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;username&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;api-key&amp;lt;/code&amp;gt;&lt;br /&gt;
| OpenBao canonical&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;secret/homeassistant&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;token&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;host&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;port&amp;lt;/code&amp;gt;&lt;br /&gt;
| OpenBao canonical; mirrored in n8n credential &amp;lt;code&amp;gt;R9LGxX...&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;4RowxM...&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;secret/ntfy&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;token&amp;lt;/code&amp;gt;&lt;br /&gt;
| OpenBao canonical; mirrored in n8n credential &amp;lt;code&amp;gt;w0gXpM...&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;secret/authentik&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;api-token&amp;lt;/code&amp;gt;&lt;br /&gt;
| OpenBao canonical&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;secret/matrix&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;zordon-token&amp;lt;/code&amp;gt;&lt;br /&gt;
| OpenBao canonical; mirrored in n8n credential &amp;lt;code&amp;gt;x5tv5a...&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;secret/stalwart&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;admin-password&amp;lt;/code&amp;gt;&lt;br /&gt;
| OpenBao canonical&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;secret/immich&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;admin-api-key&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;user-api-key&amp;lt;/code&amp;gt;&lt;br /&gt;
| OpenBao canonical&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;secret/bitwarden&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;client-id&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;client-secret&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;master-password&amp;lt;/code&amp;gt;&lt;br /&gt;
| For &amp;lt;code&amp;gt;bw&amp;lt;/code&amp;gt; CLI; also in &amp;lt;code&amp;gt;~/.config/claude/vaultwarden.env&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;secret/openai&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;api-key&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;bearer-header&amp;lt;/code&amp;gt;&lt;br /&gt;
| OpenBao canonical; mirrored in n8n credentials &amp;lt;code&amp;gt;NIQrbn...&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;XB0V2U...&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;secret/anthropic&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;api-key&amp;lt;/code&amp;gt;&lt;br /&gt;
| OpenBao canonical; mirrored in n8n credential &amp;lt;code&amp;gt;bEPlqP...&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;secret/audiobookshelf&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;bearer-header&amp;lt;/code&amp;gt;&lt;br /&gt;
| OpenBao canonical; mirrored in n8n credential &amp;lt;code&amp;gt;5MCzns...&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;secret/ssh-stronghold&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;username&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;host&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;port&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;private-key&amp;lt;/code&amp;gt;&lt;br /&gt;
| OpenBao canonical; mirrored in n8n credential &amp;lt;code&amp;gt;2ugS1i...&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
To list paths live: &amp;lt;code&amp;gt;bao-get.sh&amp;lt;/code&amp;gt; doesn&amp;#039;t support list — use the UI or:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
ROOT_TOKEN=$(jq -r .root_token /library/openbao-app/.init-output.json)&lt;br /&gt;
docker exec -e BAO_ADDR=http://127.0.0.1:8200 -e BAO_TOKEN=&amp;quot;$ROOT_TOKEN&amp;quot; openbao bao kv list secret/&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Operational ==&lt;br /&gt;
&lt;br /&gt;
=== Restart ===&lt;br /&gt;
&lt;br /&gt;
OpenBao re-seals on every restart. The systemd service handles auto-unseal:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /library/openbao-app &amp;amp;&amp;amp; docker compose restart&lt;br /&gt;
sudo systemctl start openbao-unseal.service&lt;br /&gt;
docker exec -e BAO_ADDR=http://127.0.0.1:8200 openbao bao status | grep Sealed&lt;br /&gt;
# Expected: Sealed: false&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Manual unseal (if the systemd unit fails):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
/usr/local/bin/openbao-unseal.sh&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Backups ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;/library/openbao-app/&amp;lt;/code&amp;gt; is mounted into &amp;lt;code&amp;gt;restic-backup&amp;lt;/code&amp;gt; as &amp;lt;code&amp;gt;/data/openbao:ro&amp;lt;/code&amp;gt; and included in the nightly Hetzner restic backup. Both the Raft data &amp;#039;&amp;#039;&amp;#039;and&amp;#039;&amp;#039;&amp;#039; &amp;lt;code&amp;gt;.init-output.json&amp;lt;/code&amp;gt; are backed up — restoring requires both (the unseal key would be lost otherwise).&lt;br /&gt;
&lt;br /&gt;
=== n8n credential sync ===&lt;br /&gt;
&lt;br /&gt;
n8n credentials are mirrored from OpenBao by &amp;lt;code&amp;gt;/library/openbao-app/sync-n8n-creds.py&amp;lt;/code&amp;gt;. The script reads each managed credential&amp;#039;s spec (n8n ID + OpenBao path + field mapping) from a &amp;lt;code&amp;gt;MAPPINGS&amp;lt;/code&amp;gt; table, diffs against &amp;lt;code&amp;gt;n8n export:credentials --decrypted&amp;lt;/code&amp;gt;, and runs &amp;lt;code&amp;gt;n8n import:credentials&amp;lt;/code&amp;gt; only on drift. &amp;#039;&amp;#039;&amp;#039;Match is by credential ID — never creates new credentials.&amp;#039;&amp;#039;&amp;#039; Posts to &amp;lt;code&amp;gt;#notifications:wilsoz.com&amp;lt;/code&amp;gt; on change.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
/library/openbao-app/sync-n8n-creds.py --dry-run          # preview drift&lt;br /&gt;
/library/openbao-app/sync-n8n-creds.py                    # sync all&lt;br /&gt;
/library/openbao-app/sync-n8n-creds.py --only ntfy_auth   # one mapping&lt;br /&gt;
/library/openbao-app/sync-n8n-creds.py --quiet            # cron-friendly&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Daily timer: &amp;lt;code&amp;gt;sync-n8n-creds.timer&amp;lt;/code&amp;gt; (04:30 + 0–5 min jitter). Logs in &amp;lt;code&amp;gt;journalctl -u sync-n8n-creds.service&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Rotation flow:&amp;#039;&amp;#039;&amp;#039; update OpenBao → next nightly sync (or manual run) propagates to n8n. No workflow edits.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Adding a new mapping:&amp;#039;&amp;#039;&amp;#039; append to &amp;lt;code&amp;gt;MAPPINGS&amp;lt;/code&amp;gt; in the script. Must specify &amp;lt;code&amp;gt;n8n_id&amp;lt;/code&amp;gt; (existing credential — create the credential in n8n UI first), &amp;lt;code&amp;gt;n8n_name&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;n8n_type&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;bao_path&amp;lt;/code&amp;gt;, and a &amp;lt;code&amp;gt;build&amp;lt;/code&amp;gt; lambda that returns the n8n &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; dict from the OpenBao field dict.&lt;br /&gt;
&lt;br /&gt;
=== Token rotation ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
ROOT_TOKEN=$(jq -r .root_token /library/openbao-app/.init-output.json)&lt;br /&gt;
# Revoke old token&lt;br /&gt;
docker exec -e BAO_ADDR=http://127.0.0.1:8200 -e BAO_TOKEN=&amp;quot;$ROOT_TOKEN&amp;quot; openbao \&lt;br /&gt;
  bao token revoke &amp;lt;OLD_TOKEN&amp;gt;&lt;br /&gt;
# Mint new with same policy&lt;br /&gt;
docker exec -e BAO_ADDR=http://127.0.0.1:8200 -e BAO_TOKEN=&amp;quot;$ROOT_TOKEN&amp;quot; openbao \&lt;br /&gt;
  bao token create -policy=readonly-secrets -no-default-policy -ttl=0 -display-name=&amp;quot;...&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Update &amp;lt;code&amp;gt;~/.claude.json&amp;lt;/code&amp;gt; (claude-mcp token) and &amp;lt;code&amp;gt;/library/openbao-app/.scripts-token&amp;lt;/code&amp;gt; (scripts-n8n token) to the new values, then update Vaultwarden.&lt;br /&gt;
&lt;br /&gt;
=== Adding a new secret path ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
ROOT_TOKEN=$(jq -r .root_token /library/openbao-app/.init-output.json)&lt;br /&gt;
docker exec -e BAO_ADDR=http://127.0.0.1:8200 -e BAO_TOKEN=&amp;quot;$ROOT_TOKEN&amp;quot; openbao \&lt;br /&gt;
  bao kv put secret/NEWPATH field1=value1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;readonly-secrets&amp;lt;/code&amp;gt; policy uses &amp;lt;code&amp;gt;path &amp;quot;secret/data/*&amp;quot;&amp;lt;/code&amp;gt; so new paths are immediately readable by claude-mcp and scripts-n8n tokens — no policy update needed.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Gotchas (hard-won) ==&lt;br /&gt;
&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Container runs as &amp;lt;code&amp;gt;user: &amp;quot;1000:1000&amp;quot;&amp;lt;/code&amp;gt; (mnw).&amp;#039;&amp;#039;&amp;#039; Without this, files on &amp;lt;code&amp;gt;/library/openbao-app/data&amp;lt;/code&amp;gt; end up owned by UID 100 (&amp;lt;code&amp;gt;messagebus&amp;lt;/code&amp;gt; on host) which is confusing.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Must set &amp;lt;code&amp;gt;SKIP_CHOWN=1&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;SKIP_SETCAP=1&amp;lt;/code&amp;gt; env vars.&amp;#039;&amp;#039;&amp;#039; Without them, the entrypoint&amp;#039;s &amp;lt;code&amp;gt;chown /openbao/logs&amp;lt;/code&amp;gt; fails under &amp;lt;code&amp;gt;set -e&amp;lt;/code&amp;gt; and the container restart-loops.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;&amp;lt;code&amp;gt;docker exec&amp;lt;/code&amp;gt; to bao CLI needs &amp;lt;code&amp;gt;-e BAO_ADDR=http://127.0.0.1:8200&amp;lt;/code&amp;gt;.&amp;#039;&amp;#039;&amp;#039; The CLI defaults to https else and fails with &amp;quot;server gave HTTP response to HTTPS client&amp;quot;.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Sealed on every restart.&amp;#039;&amp;#039;&amp;#039; Raft + Shamir means the box re-seals on container start. Auto-unseal via the systemd service. Don&amp;#039;t delete &amp;lt;code&amp;gt;.init-output.json&amp;lt;/code&amp;gt;.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;&amp;lt;code&amp;gt;bao kv put&amp;lt;/code&amp;gt; replaces the whole record.&amp;#039;&amp;#039;&amp;#039; Always pass all fields you want kept, not just the changed one.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Multi-line values fail with &amp;lt;code&amp;gt;bao kv put&amp;lt;/code&amp;gt; shell escaping.&amp;#039;&amp;#039;&amp;#039; Use the HTTP API (curl/python urllib) directly for private keys, certs, etc.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;vault-mcp-server release binaries.&amp;#039;&amp;#039;&amp;#039; GitHub releases page shows zero assets — download from &amp;lt;code&amp;gt;releases.hashicorp.com&amp;lt;/code&amp;gt; instead (&amp;lt;code&amp;gt;https://releases.hashicorp.com/vault-mcp-server/0.2.0/vault-mcp-server_0.2.0_linux_amd64.zip&amp;lt;/code&amp;gt;).&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;&amp;lt;code&amp;gt;bao kv put field=@/path/to/file&amp;lt;/code&amp;gt; reads from container fs, not host.&amp;#039;&amp;#039;&amp;#039; Either copy the file into the container first or use the HTTP API.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Related ==&lt;br /&gt;
&lt;br /&gt;
* Setup plan (preserved for archaeology): &amp;lt;code&amp;gt;plans/openbao-setup.md&amp;lt;/code&amp;gt;&lt;br /&gt;
* Memory note: &amp;lt;code&amp;gt;~/.claude/projects/.../memory/project_openbao.md&amp;lt;/code&amp;gt;&lt;br /&gt;
* CLAUDE.md &amp;quot;OpenBao Secret Store&amp;quot; section&lt;/div&gt;</summary>
		<author><name>Wikibot</name></author>
	</entry>
</feed>