Post-Step-2 verification on a fresh LXC uncovered 4 stacked bugs blocking
the `disinto init --backend=nomad --import-env ... --with forgejo` hero
command. Root cause is #1; #2-#4 surface as the operator walks past each.
1. kv/ secret engine never enabled — every policy, role, import write,
and template read references kv/disinto/* and 403s without the mount.
Adds lib/init/nomad/vault-engines.sh (idempotent POST sys/mounts/kv)
wired into `_disinto_init_nomad` before vault-apply-policies.sh.
2. VAULT_ADDR/VAULT_TOKEN not exported in the init process. Extracts the
5-line default-and-resolve block into `_hvault_default_env` in
lib/hvault.sh and sources it from vault-engines.sh, vault-nomad-auth.sh,
vault-apply-policies.sh, vault-apply-roles.sh, and vault-import.sh. One
definition, zero copies — avoids the 5-line sliding-window duplicate
gate that failed PRs #917/#918.
3. vault-import.sh required --sops; spec (#880) says --env alone must
succeed. Flag validation now: --sops requires --age-key, --age-key
requires --sops, --env alone imports only the plaintext half.
4. forgejo.hcl template blocks forever when kv/disinto/shared/forgejo is
absent or missing a key. Adds `error_on_missing_key = false` so the
existing `with ... else ...` fallback emits placeholders instead of
hanging on template-pending.
vault-engines.sh parser uses a while/shift shape distinct from
vault-apply-policies.sh (flat case) and vault-apply-roles.sh (if/elif
ladder) so the three sibling flag parsers hash differently under the
repo-wide duplicate detector.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the `|`-delimited string accumulators with bash associative and
indexed arrays so any byte may appear in a secret value.
Two sites used `|` as a delimiter over data that includes user secrets:
1. ops_data["path:key"]="value|status" — extraction via `${data%%|*}`
truncated values at the first `|` (silently corrupting writes).
2. paths_to_write["path"]="k1=v1|k2=v2|..." — split back via
`IFS='|' read -ra` at write time, so a value containing `|` was
shattered across kv pairs (silently misrouting writes).
Fix:
- Split ops_data into two assoc arrays (`ops_value`, `ops_status`) keyed
on "vault_path:vault_key" — value and status are stored independently
with no in-band delimiter. (`:` is safe because both vault_path and
vault_key are identifier-safe.)
- Track distinct paths in `path_seen` and, for each path, collect its
kv pairs into a fresh indexed `pairs_array` by filtering ops_value.
`_kv_put_secret` already splits each entry on the first `=` only, so
`=` and `|` inside values are both preserved.
Added a bats regression that imports values like `abc|xyz`, `p1|p2|p3`,
and `admin|with|pipes` and asserts they round-trip through Vault
unmodified. Values are single-quoted in the .env so they survive
`source` — the accumulator is what this test exercises.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>