---
id: w4/m5/t003
title: "scripts/bao-init.sh — out-of-band init/unseal via .env + KV v2 mount tenants/"
worker: worker4
status: done
estimate: 40m
depends_on: [w4/m5/t002]
---

## Objective

An idempotent out-of-band bootstrap script in the mold of `scripts/auth-secrets.sh`: initialize OpenBao (first run: capture unseal key(s) + root token into `.env`-style vars, never echo values, never touch git), unseal it on subsequent runs, and enable the KV v2 secrets engine at `tenants/`.

## Context

- Pattern: `scripts/auth-secrets.sh` — validates env presence, never echoes values, `DRY_RUN=1` prints resource names only.
- Env vars (names mirrored to `.env.example` + `.env.template` per repo rule): `BAO_UNSEAL_KEY_1..N`, `BAO_ROOT_TOKEN` (or as the ADR decides).
- Runs via port-forward or in-cluster exec against the cluster-internal service; kubeconfig at `infra/local/bex.kubeconfig` locally.
- Must be safe to run on every deploy (prod path added in t006): already-initialized ⇒ unseal only; already-unsealed ⇒ no-op; mount exists ⇒ no-op.

## Steps

1. Write `scripts/bao-init.sh`: detect init status → `operator init` (capture to `.env` guidance) or unseal from env; enable KV v2 at `tenants/` if absent.
2. Support `DRY_RUN=1` (print intended actions, no mutations, no values).
3. Update `.env.example` and `.env.template` with the new var names + comments (never values).
4. Run against the mock cluster: fresh init, then re-run proving idempotency.

## Files

- `scripts/bao-init.sh` (new)
- `.env.example`, `.env.template`

## Acceptance criteria

- [ ] Fresh cluster: one run leaves OpenBao initialized, unsealed, `tenants/` KV v2 mounted.
- [ ] Second run is a no-op (idempotent); pod restart + re-run re-unseals.
- [ ] No secret value is ever printed or written to a tracked file; `DRY_RUN=1` supported.

## Out of scope

- Kubernetes auth method and policies (t004); prod CI wiring (t006).
