WIP: openbao: add a dormant OpenBao backend alongside SOPS #129

Draft
admin_emi wants to merge 0 commits from worktree-openbao-migration into dev
Owner

Marked WIP deliberately — see "what still needs a human" at the bottom. Nothing here is cut over.

Contains the forgejo: restart the db service... commit as well, which is also up as #128 so it can merge on its own without waiting for this. Whichever lands first makes the other a no-op.

Status: dormant

TG_SECRETS_BACKEND defaults to sops for all three environments in env-topology.sh, so SOPS remains authoritative for dev, beta and prod. Flipping an environment is a one-line reviewed change and should not happen until verify-parity.sh passes.

Verified against OpenBao v2.6.1. The binary is bao.

Why bother — one specific failure class

Not encryption, and not audit, though the audit log is a real gain.

infra/CLAUDE.md requires that "vps1 must never hold prod credentials. It's the box that runs Forgejo, its CI runner, and dev's unreviewed branch — the opposite of an isolation boundary."

Today that rule is a shell flagTHERMOGRAPH_SECRETS_SKIP_COMMON — set by the caller, at three separate sites (env-topology.sh:196deploy.sh:67, deploy-dev.sh:86, infra-sync.yml:93-95). The renderer does not know that env=dev means never-common. A fourth call site, or one by-hand render_thermograph_secrets /opt/thermograph-dev/infra dev, writes both S3 keypairs (one read-write on the bucket holding prod's database backups), the VAPID private key that signs Web Push to real subscribers, REGISTRY_TOKEN, and the IndexNow and metrics tokens onto the CI-runner box — and exits 0.

policies/tg-host-dev.hcl makes that a 403. The failure class is gone, not guarded. The estate already hardened this once by hand (7583258), which is the shape of a problem that wants an ACL.

Shape

Decision Choice Why
Host vps2 only vps1 must never hold prod credentials; with a static seal the box also holds the decryption key
Process native systemd, not a container A Swarm service is circular (deploy-stack.sh renders secrets to deploy the stack). A plain docker run makes the vault depend on the daemon deploys restart, and is one prune -a from gone
Storage raft, single node 2.6.0 deprecated the file backend for removal in 2.7.0. Raft has the only backup primitive. A two-node raft would be worse than one — quorum of two loses writes if either dies
Seal static auto-unseal A sealed vault blocks every deploy. Shamir needs a human after every reboot, which is an availability regression the estate does not have today. Static seal is the same trust model as the existing 0400 age key, with ACLs/audit/short tokens layered on
TLS self-signed, on disk Must not come from Caddy/ACME — that would put DNS and the public internet in the boot chain of the secret store
Auth AppRole per env, CIDR-bound, 5m tokens Each env's secret-id owned by its own deploy user (agent vs deploy) — a boundary that does not exist today, since both reach the same root-owned age key
Consumption keep rendering a dotenv file Keeps OpenBao a deploy-time dependency, never a runtime one, so a cold boot needs nothing from it

Risk containment

The dispatch in render-secrets.sh returns early rather than refactoring the shared write path, so the SOPS path stays byte-identical. That file is on the deploy path for all three environments with no test suite in front of it. Verified after the change:

  • SOPS render of dev → 12 keys, prod → 32 keys, matching the live hosts exactly
  • unknown backend → rc=1, output file untouched at 0 bytes
  • openbao backend with no bao installed → rc=1, fails closed
  • shellcheck clean across every .sh in the tree

The duplicated write path is documented with a note to consolidate once prod has been on OpenBao for a release cycle.

Two things this fixes on the way past

A latent code-execution path. /etc/thermograph.env is raw unquoted dotenv but is sourced by bash in six places (deploy.sh:133, deploy-stack.sh:98, both daemon entrypoints, ops-cron.yml:85, terraform). Any value containing a space, quote, backtick, $( or backslash would truncate, corrupt, or execute. It works today only because every current value happens to be alphanumeric — so "rotate to a stronger password" is currently a live RCE risk on the deploy host. The OpenBao renderer rejects such values at both render and seed time.

The age key is not retired by this. ops-cron.yml:142,197 stream every off-box dump through pg_dump | age -r <recipient> | rclone rcat with 30-day retention, and restore uses /etc/thermograph/age.key. So the age keypair is the backup encryption key, not just the SOPS transport. Deleting it with the vault files would silently destroy a month of database recoverability. (This became load-bearing within hours: the Forgejo recovery in #128 depended on exactly that key.)

Honest about what this does not buy

  • No rotation of provider-issued credentials — Discord, Contabo S3, the registry token, VAPID, IndexNow. OpenBao cannot rotate any of them; Contabo exposes no IAM API.
  • Dynamic DB credentials are inapplicable — apps read THERMOGRAPH_DATABASE_URL once at import and hold a pool, and there is no reload path anywhere in the codebase.
  • Worse review than git. Today a secret change is a PR with a diff, backstopped by secrets-guard. Under OpenBao it is an out-of-band API call with no diff and no review. This is a genuine regression and a decision to make deliberately, not a detail. Partially mitigated by a committed key-name manifest plus the audit log.
  • More moving parts. SOPS needs no server; this adds a stateful service to keep alive, upgrade, back up and TLS-rotate, for ~40 rarely-changing secrets.

Not in scope, with reasons

OIDC for CI is deferioned: Forgejo Actions OIDC shipped in v15.0, but this estate runs forgejo:9-rootless (deploy/forgejo/docker-stack.yml:59). That is a six-major-version upgrade, and coupling it here would mean two large migrations at once with no way to tell which broke. CI keeps its SSH keys and REGISTRY_TOKEN as Forgejo secrets and gets no vault access at all — the SSH-in architecture already means the host renders, never the runner, which is the least-privilege topology.

Also out: HA raft, namespaces, bao agent, cert auth, PKI, transit seal (revisit later).

What still needs a human

  1. Custody of the unseal key and recovery shares. bootstrap.sh stops and waits for this on purpose. Any place a script could store them is worse custody than the age key this is meant to improve on.
  2. The PR-review-for-secrets decision above.
  3. Running seed-from-sops.sh. It reads every production secret in plaintext; infra/CLAUDE.md says the equivalent seed-from-live.sh is explicitly not for an agent to run, and this inherits that.
  4. Verifying a raft snapshot restores under a different seal key — the docs do not state it, and it must be tested before anything depends on it.

Cutover order once those are done: verify-parity.sh --all green, then dev → beta → prod, one PR per hop, 7 consecutive green parity runs before prod.

Marked WIP deliberately — see "what still needs a human" at the bottom. Nothing here is cut over. > Contains the `forgejo: restart the db service...` commit as well, which is also up as **#128** so it can merge on its own without waiting for this. Whichever lands first makes the other a no-op. ## Status: dormant `TG_SECRETS_BACKEND` defaults to `sops` for all three environments in `env-topology.sh`, so **SOPS remains authoritative** for dev, beta and prod. Flipping an environment is a one-line reviewed change and should not happen until `verify-parity.sh` passes. Verified against **OpenBao v2.6.1**. The binary is `bao`. ## Why bother — one specific failure class Not encryption, and not audit, though the audit log is a real gain. `infra/CLAUDE.md` requires that *"vps1 must never hold prod credentials. It's the box that runs Forgejo, its CI runner, and dev's unreviewed branch — the opposite of an isolation boundary."* Today that rule is a **shell flag** — `THERMOGRAPH_SECRETS_SKIP_COMMON` — set by the *caller*, at three separate sites (`env-topology.sh:196` → `deploy.sh:67`, `deploy-dev.sh:86`, `infra-sync.yml:93-95`). The renderer does not know that `env=dev` means never-common. A fourth call site, or one by-hand `render_thermograph_secrets /opt/thermograph-dev/infra dev`, writes both S3 keypairs (one read-write on the bucket holding prod's database backups), the VAPID private key that signs Web Push to real subscribers, `REGISTRY_TOKEN`, and the IndexNow and metrics tokens onto the CI-runner box — **and exits 0**. `policies/tg-host-dev.hcl` makes that a 403. The failure class is gone, not guarded. The estate already hardened this once by hand (`7583258`), which is the shape of a problem that wants an ACL. ## Shape | Decision | Choice | Why | |---|---|---| | Host | vps2 only | vps1 must never hold prod credentials; with a static seal the box also holds the decryption key | | Process | **native systemd**, not a container | A Swarm service is circular (`deploy-stack.sh` renders secrets to deploy the stack). A plain `docker run` makes the vault depend on the daemon deploys restart, and is one `prune -a` from gone | | Storage | raft, single node | 2.6.0 **deprecated the `file` backend** for removal in 2.7.0. Raft has the only backup primitive. A *two*-node raft would be worse than one — quorum of two loses writes if either dies | | Seal | static auto-unseal | A sealed vault blocks every deploy. Shamir needs a human after every reboot, which is an availability regression the estate does not have today. Static seal is the *same* trust model as the existing 0400 age key, with ACLs/audit/short tokens layered on | | TLS | self-signed, on disk | Must not come from Caddy/ACME — that would put DNS and the public internet in the boot chain of the secret store | | Auth | AppRole per env, CIDR-bound, 5m tokens | Each env's secret-id owned by its own deploy user (`agent` vs `deploy`) — a boundary that does **not** exist today, since both reach the same root-owned age key | | Consumption | keep rendering a dotenv file | Keeps OpenBao a *deploy-time* dependency, never a runtime one, so a cold boot needs nothing from it | ## Risk containment The dispatch in `render-secrets.sh` **returns early** rather than refactoring the shared write path, so the SOPS path stays byte-identical. That file is on the deploy path for all three environments with no test suite in front of it. Verified after the change: - SOPS render of dev → **12 keys**, prod → **32 keys**, matching the live hosts exactly - unknown backend → rc=1, output file untouched at 0 bytes - openbao backend with no `bao` installed → rc=1, fails closed - shellcheck clean across every `.sh` in the tree The duplicated write path is documented with a note to consolidate once prod has been on OpenBao for a release cycle. ## Two things this fixes on the way past **A latent code-execution path.** `/etc/thermograph.env` is raw unquoted dotenv but is `source`d by bash in six places (`deploy.sh:133`, `deploy-stack.sh:98`, both daemon entrypoints, `ops-cron.yml:85`, terraform). Any value containing a space, quote, backtick, `$(` or backslash would truncate, corrupt, or **execute**. It works today only because every current value happens to be alphanumeric — so "rotate to a stronger password" is currently a live RCE risk on the deploy host. The OpenBao renderer rejects such values at both render and seed time. **The age key is *not* retired by this.** `ops-cron.yml:142,197` stream every off-box dump through `pg_dump | age -r <recipient> | rclone rcat` with 30-day retention, and restore uses `/etc/thermograph/age.key`. So the age keypair is the **backup encryption key**, not just the SOPS transport. Deleting it with the vault files would silently destroy a month of database recoverability. (This became load-bearing within hours: the Forgejo recovery in #128 depended on exactly that key.) ## Honest about what this does not buy - **No rotation of provider-issued credentials** — Discord, Contabo S3, the registry token, VAPID, IndexNow. OpenBao cannot rotate any of them; Contabo exposes no IAM API. - **Dynamic DB credentials are inapplicable** — apps read `THERMOGRAPH_DATABASE_URL` once at import and hold a pool, and there is no reload path anywhere in the codebase. - **Worse review than git.** Today a secret change is a PR with a diff, backstopped by `secrets-guard`. Under OpenBao it is an out-of-band API call with no diff and no review. **This is a genuine regression and a decision to make deliberately, not a detail.** Partially mitigated by a committed key-name manifest plus the audit log. - **More moving parts.** SOPS needs no server; this adds a stateful service to keep alive, upgrade, back up and TLS-rotate, for ~40 rarely-changing secrets. ## Not in scope, with reasons OIDC for CI is deferioned: Forgejo Actions OIDC shipped in **v15.0**, but this estate runs `forgejo:9-rootless` (`deploy/forgejo/docker-stack.yml:59`). That is a six-major-version upgrade, and coupling it here would mean two large migrations at once with no way to tell which broke. CI keeps its SSH keys and `REGISTRY_TOKEN` as Forgejo secrets and gets **no vault access at all** — the SSH-in architecture already means the host renders, never the runner, which is the least-privilege topology. Also out: HA raft, namespaces, `bao agent`, cert auth, PKI, transit seal (revisit later). ## What still needs a human 1. **Custody of the unseal key and recovery shares.** `bootstrap.sh` stops and waits for this on purpose. Any place a script could store them is worse custody than the age key this is meant to improve on. 2. **The PR-review-for-secrets decision** above. 3. **Running `seed-from-sops.sh`.** It reads every production secret in plaintext; `infra/CLAUDE.md` says the equivalent `seed-from-live.sh` is explicitly not for an agent to run, and this inherits that. 4. **Verifying a raft snapshot restores under a different seal key** — the docs do not state it, and it must be tested before anything depends on it. Cutover order once those are done: `verify-parity.sh --all` green, then dev → beta → prod, one PR per hop, 7 consecutive green parity runs before prod.
admin_emi added 17 commits 2026-07-30 13:31:37 +00:00
Merge pull request 'promote: dev → main (CI consolidation, 15 workflows → 9)' (#90) from dev into main
All checks were successful
shell-lint / shellcheck (pull_request) Successful in 9s
Build + push images (Forgejo registry) / build-push (frontend) (push) Successful in 6s
Deploy / deploy (frontend) (push) Successful in 12s
Sync infra to hosts / sync-beta (push) Successful in 12s
Sync infra to hosts / sync-prod (push) Successful in 9s
secrets-guard / encrypted (push) Successful in 13s
Deploy / deploy (backend) (push) Successful in 50s
shell-lint / shellcheck (push) Successful in 14s
Build + push images (Forgejo registry) / build-push (backend) (push) Successful in 46s
secrets-guard / encrypted (pull_request) Successful in 7s
aa4ee7f4c4
Merge pull request 'promote: dev → main (alerting liveness rules onto the app heartbeat)' (#95) from dev into main
All checks were successful
secrets-guard / encrypted (push) Successful in 6s
Validate observability stack / validate (push) Successful in 19s
shell-lint / shellcheck (push) Successful in 15s
c67d22b152
Merge pull request 'promote: dev → main (alert template: omit empty value line)' (#97) from dev into main
All checks were successful
secrets-guard / encrypted (push) Successful in 10s
shell-lint / shellcheck (push) Successful in 16s
Validate observability stack / validate (push) Successful in 20s
0766fd6bcb
Merge pull request 'promote: dev → main' (#100) from dev into main
All checks were successful
secrets-guard / encrypted (push) Successful in 8s
shell-lint / shellcheck (push) Successful in 10s
Deploy / deploy (backend) (push) Successful in 52s
Build + push images (Forgejo registry) / build-push (backend) (push) Successful in 55s
Build + push images (Forgejo registry) / build-push (frontend) (push) Successful in 59s
Deploy / deploy (frontend) (push) Successful in 58s
secrets-guard / encrypted (pull_request) Successful in 6s
shell-lint / shellcheck (pull_request) Successful in 9s
ba1085bf39
Merge pull request 'promote: dev → main (Weekly UTC-today date fix)' (#105) from dev into main
All checks were successful
Build + push images (Forgejo registry) / build-push (backend) (push) Successful in 6s
Deploy / deploy (backend) (push) Successful in 6s
secrets-guard / encrypted (push) Successful in 5s
shell-lint / shellcheck (push) Successful in 14s
Deploy / deploy (frontend) (push) Successful in 21s
Build + push images (Forgejo registry) / build-push (frontend) (push) Successful in 46s
4d6ce5b70b
Merge pull request 'promote: dev → main (vps1/vps2 estate split)' (#106) from dev into main
All checks were successful
Sync infra to hosts / sync-dev (push) Has been skipped
Sync infra to hosts / sync-beta (push) Successful in 6s
Sync infra to hosts / sync-prod (push) Successful in 4s
secrets-guard / encrypted (push) Successful in 5s
Validate observability stack / validate (push) Successful in 12s
shell-lint / shellcheck (push) Successful in 13s
d5357d0670
Merge pull request 'promote: dev → main (cutover fixes: sync-dev guard, owner-db hardening, post-deploy gate)' (#110) from dev into main
All checks were successful
Sync infra to hosts / sync-dev (push) Has been skipped
shell-lint / shellcheck (push) Successful in 9s
Sync infra to hosts / sync-beta (push) Successful in 7s
Sync infra to hosts / sync-prod (push) Successful in 5s
secrets-guard / encrypted (push) Successful in 4s
e6d9b36742
Merge pull request 'promote: dev → main (dev behind basic auth at dev.thermograph.org)' (#112) from dev into main
All checks were successful
Sync infra to hosts / sync-dev (push) Has been skipped
Sync infra to hosts / sync-beta (push) Successful in 6s
Sync infra to hosts / sync-prod (push) Successful in 5s
secrets-guard / encrypted (push) Successful in 10s
shell-lint / shellcheck (push) Successful in 9s
c86489b207
Merge pull request 'promote: dev → main' (#114) from dev into main
All checks were successful
Sync infra to hosts / sync-dev (push) Has been skipped
Sync infra to hosts / sync-beta (push) Successful in 6s
Sync infra to hosts / sync-prod (push) Successful in 4s
secrets-guard / encrypted (push) Successful in 4s
shell-lint / shellcheck (push) Successful in 8s
c1ab9410d3
Merge pull request 'promote: dev → main (sign in with Discord)' (#116) from dev into main
All checks were successful
Sync infra to hosts / sync-dev (push) Has been skipped
Sync infra to hosts / sync-beta (push) Successful in 17s
Sync infra to hosts / sync-prod (push) Successful in 15s
secrets-guard / encrypted (push) Successful in 13s
shell-lint / shellcheck (push) Successful in 13s
Build + push images (Forgejo registry) / build-push (backend) (push) Successful in 1m23s
Build + push images (Forgejo registry) / build-push (frontend) (push) Successful in 1m21s
Deploy / deploy (backend) (push) Successful in 1m25s
Deploy / deploy (frontend) (push) Successful in 1m37s
efcf32defc
Merge pull request 'promote: dev → main (location bookmarks)' (#119) from dev into main
All checks were successful
secrets-guard / encrypted (push) Successful in 13s
shell-lint / shellcheck (push) Successful in 15s
Build + push images (Forgejo registry) / build-push (backend) (push) Successful in 1m7s
Build + push images (Forgejo registry) / build-push (frontend) (push) Successful in 1m5s
Deploy / deploy (backend) (push) Successful in 1m16s
Deploy / deploy (frontend) (push) Successful in 1m29s
4f21e47750
bookmarks: don't fabricate a local save when the server rejects it (#121)
All checks were successful
Build + push images (Forgejo registry) / build-push (backend) (push) Successful in 11s
Deploy / deploy (backend) (push) Successful in 13s
secrets-guard / encrypted (push) Successful in 12s
shell-lint / shellcheck (push) Successful in 14s
Build + push images (Forgejo registry) / build-push (frontend) (push) Successful in 47s
Deploy / deploy (frontend) (push) Successful in 55s
beb5fa5259
promote: dev → main
Some checks failed
Sync infra to hosts / sync-dev (push) Has been skipped
Build + push images (Forgejo registry) / build-push (frontend) (push) Successful in 7s
Deploy / deploy (frontend) (push) Successful in 8s
Sync infra to hosts / sync-beta (push) Failing after 9s
Sync infra to hosts / sync-prod (push) Successful in 8s
secrets-guard / encrypted (push) Successful in 12s
shell-lint / shellcheck (push) Successful in 12s
Build + push images (Forgejo registry) / build-push (backend) (push) Successful in 42s
Deploy / deploy (backend) (push) Successful in 1m12s
ad4d2da120
Merge pull request 'promote: dev → main (sign in with Google; shared OAuth engine)' (#127) from dev into main
All checks were successful
Sync infra to hosts / sync-dev (push) Has been skipped
Sync infra to hosts / sync-beta (push) Successful in 12s
Sync infra to hosts / sync-prod (push) Successful in 10s
secrets-guard / encrypted (push) Successful in 15s
shell-lint / shellcheck (push) Successful in 13s
Build + push images (Forgejo registry) / build-push (backend) (push) Successful in 57s
Build + push images (Forgejo registry) / build-push (frontend) (push) Successful in 55s
Deploy / deploy (backend) (push) Successful in 1m30s
Deploy / deploy (frontend) (push) Successful in 1m43s
94de933cab
Phase 1 of moving the secret store to OpenBao. Nothing is cut over:
TG_SECRETS_BACKEND defaults to sops for all three environments, so SOPS
remains authoritative until an environment is flipped in a reviewed PR.

The reason for the migration is one specific failure class. infra/CLAUDE.md
requires that vps1 never hold prod credentials, but that rule is currently
enforced by a caller-side shell flag (THERMOGRAPH_SECRETS_SKIP_COMMON) at
three call sites. A fourth call site, or one by-hand render, writes both S3
keypairs, the VAPID private key, REGISTRY_TOKEN and the IndexNow/metrics
tokens onto the Forgejo CI-runner box and exits 0. policies/tg-host-dev.hcl
makes that a 403 instead: dev's identity cannot read the common path at all.

Shape:
- vps2 only, native systemd (not a container: a Swarm service is circular,
  and a plain docker run is one `prune -a` from gone), raft storage (2.6.0
  deprecated the file backend), static auto-unseal so a reboot cannot block
  deploys, mesh-only self-signed TLS (not ACME — that would put DNS in the
  boot chain of the secret store).
- AppRole per environment, CIDR-bound, 5-minute tokens. Each environment's
  secret-id is owned by its own deploy user, which is a boundary that does
  not exist today since prod and beta both reach the same root-owned age key.
- Render still produces the same raw unquoted dotenv: Centralis compares
  /etc/thermograph.env textually and the Swarm entrypoint parses it by line.

The backend dispatch in render-secrets.sh returns early rather than
refactoring the shared write path, so the SOPS path stays byte-identical.
Verified by rendering dev (12 keys) and prod (32 keys) through it, matching
the live hosts. The duplicated write path is noted for consolidation once
prod has been on OpenBao for a release cycle.

The OpenBao renderer rejects values containing a newline or a shell
metacharacter. /etc/thermograph.env is sourced by bash in six places, so
such a value is a command-execution path on the deploy host; the SOPS path
has always had this hazard and avoids it only because every current value
happens to be alphanumeric.

Also records that the age keypair is NOT retired by this migration: it is
the backup encryption key for every off-box dump (ops-cron.yml:142,197,
30-day retention), so deleting it with the vault files would silently
destroy a month of database recoverability.

verify-parity.sh is the cutover gate — it renders both backends and diffs
them, reporting key names and counts only, never values.
forgejo: restart the db service on a clean exit, not just on failure
All checks were successful
PR build (required check) / changes (pull_request) Successful in 5s
secrets-guard / encrypted (pull_request) Successful in 4s
PR build (required check) / build-backend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 6s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 1s
7878763b66
forgejo_db has been at 0/1 replicas since 2026-07-29 01:24 UTC. Postgres hit
an invalid data-directory lock file ("could not open file postmaster.pid ...
performing immediate shutdown because data directory lock file is invalid")
and exited 0. With restart_policy.condition=on-failure, Swarm read the zero
status as successful completion, marked the task Complete, and never
rescheduled it.

The forgejo service itself stayed Up and kept serving its homepage, so the
outage presented as every repository page, the whole API and all CI returning
500 with "dial tcp: lookup db on 127.0.0.11:53: no such host" — including the
auth path, which is why API calls reported "user does not exist [uid: 0]"
rather than a database error.

on-failure cannot distinguish "finished successfully" from "shut itself down
and should be restarted", and Postgres exits 0 on several such paths, so it is
the wrong policy for an always-on stateful service.

This is the durable fix; it does not restart the currently stopped task.
admin_emi added 1 commit 2026-07-30 18:10:11 +00:00
Merge dev into openbao branch (dev moved when #128 landed)
All checks were successful
PR build (required check) / changes (pull_request) Successful in 6s
secrets-guard / encrypted (pull_request) Successful in 4s
PR build (required check) / build-backend (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 7s
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 1s
1941b184e5
All checks were successful
PR build (required check) / changes (pull_request) Successful in 6s
secrets-guard / encrypted (pull_request) Successful in 4s
PR build (required check) / build-backend (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 7s
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 1s
Required
Details
This pull request is marked as a work in progress.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin worktree-openbao-migration:worktree-openbao-migration
git checkout worktree-openbao-migration
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Jinemi/thermograph#129
No description provided.