Skip to content

feat: OpenBao-backed secrets, vps target support - #7

Merged
BKJN1 merged 14 commits into
feat/configure-up-splitfrom
feat/openbao-secrets
Aug 15, 2026
Merged

feat: OpenBao-backed secrets, vps target support#7
BKJN1 merged 14 commits into
feat/configure-up-splitfrom
feat/openbao-secrets

Conversation

@BKJN1

@BKJN1 BKJN1 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

CLI-side counterpart to the versola repo's OpenBao PR: auth/central/edge's
generated configs now reference secrets as ${?VAR} placeholders instead
of literal values, and this is what actually resolves them before the
stack starts. Companion PR: (versolauth/versola#176).

  • internal/openbao: AppRole client (login, read/write against OpenBao's
    KV v2 API) and ~/.versola/openbao/<target>.json credential storage.
  • versola secrets login <target> <address> <role-id> <secret-id> /
    secrets test <target>: stores and verifies AppRole credentials for a
    target, so configure has something to authenticate with.
  • internal/deploy/secrets.go: for each of auth/central/edge, reads the
    *.generated-secrets.env candidates versola-tools wrote, resolves each
    key against OpenBao (an existing value wins over the fresh candidate,
    which gets stored if it's new), and writes the result as
    *.secrets.env for Compose's env_file: to load.
  • Configure creates the external versola-openbao-file volume and
    starts (or reuses an already-running) openbao service before
    resolving secrets against it.
  • vps target support: Configure/Up accept "vps" alongside
    "local", pass TARGET=vps through to versola-tools, skip the
    local-only port check and postgres/nginx compose services, and ask for
    explicit confirmation before Up touches the real VPS (central runs
    migrations against the live database as a side effect of starting, and
    there's no separate migrate step yet to review first).

Verified locally: versola bootstrap local still works unchanged (secrets
now resolve through OpenBao instead of being written literally). vps
target not yet exercised against the real VPS — pending OpenBao being set
up there (see versola's develop.md).

@augmentcode

augmentcode Bot commented Aug 15, 2026

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary: This PR adds OpenBao-backed secret resolution and a production VPS deployment target to the CLI.

Changes:

  • Adds an AppRole OpenBao client with per-target credential storage under ~/.versola/openbao.
  • Introduces secrets login and secrets test commands to store and validate OpenBao access.
  • Generates candidate service secrets, resolves existing or new values through OpenBao, and writes protected Compose env files.
  • Starts and waits for OpenBao during configuration, using an external persistent data volume.
  • Adds vps target generation, VPS-specific service startup, and an explicit migration confirmation prompt.
  • Updates local port ownership checks to tolerate the CLI's existing nginx container during redeploys.
  • Refactors active deployment state to use per-run bundle directories and finalizes state only after configuration succeeds.
  • Preserves per-target credentials during uninstall and handles OpenBao volume cleanup according to target.
Technical Notes: Secret files are written with restrictive permissions; OpenBao remains intentionally manually initialized and unsealed as documented.

🤖 Was this summary useful? React with 👍 or 👎

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

}
// 0o600, not the 0o644 state.go uses for state.json: this file holds
// SecretID, which state.json never holds anything equivalent to.
if err := os.WriteFile(path, b, 0o600); err != nil {

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal/openbao/credentials.go:112: target is accepted verbatim by secrets login and reaches filepath.Join; for example, versola secrets login ../active/state ... writes ~/.versola/active/state.json instead of a credential file. A successful login can therefore corrupt deployment state (and similarly address other files beneath ~/.versola) even though Configure supports only local and vps.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

// of it, and up.go's own docker volume create call is idempotent, so
// doing it again there once Up runs doesn't conflict with this one.
fragmentPath := filepath.Join(dir, "compose.fragment.yml")
if err := docker.Run("volume", "create", "versola-openbao-file"); err != nil {

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal/deploy/configure.go:96: This external volume is not removed by the existing docker compose down --volumes uninstall path, because Compose never removes external: true volumes. Consequently versola uninstall leaves the OpenBao data and all resolved secrets on disk, and a later fresh install silently reuses them.

Severity: medium

Other Locations
  • internal/deploy/up.go:77

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

@BKJN1

BKJN1 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 1 suggestion posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread internal/cmd/uninstall.go
// same way ComposeFile already treats them internally: no state means
// nothing to know a target for, not a reason to fail uninstall itself.
var target string
if st, loadErr := state.Load(); loadErr == nil {

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal/cmd/uninstall.go:54: Although this determines the active target, the later removal of ~/.versola also removes ~/.versola/openbao/*.json; uninstalling a local stack therefore deletes the cached VPS AppRole credentials (and vice versa). This defeats the new per-target credential persistence and can leave an otherwise intact target unable to configure until its credentials are reissued.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

@BKJN1

BKJN1 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread internal/cmd/secrets.go Outdated
}

func runSecretsLogin(cmd *cobra.Command, args []string) error {
target, address, roleID, secretID := args[0], args[1], args[2], args[3]

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal/cmd/secrets.go:43: secretID is an AppRole password, but accepting it as a positional argument exposes it in the invoking shell's history and, while this command is running, to users able to inspect process arguments. This undermines the package's stated handling of SecretID as a value that must not be exposed.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread internal/checks/checks.go Outdated

if owner, used := dockerPortInUse(port); used {
if owner == ownContainer {
return Result{Name: name, OK: true}

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal/checks/checks.go:112: Returning success here skips the raw host bind check. On the Docker Desktop/WSL2 case described above, another host process can bind this port while the current nginx container is published; replacing or restarting nginx then needs Docker to publish the port again and can fail despite this prerequisite passing.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

BKJN1 added 2 commits August 15, 2026 18:47
…rtFree does a raw bind check even for our own container
…ment state across a failed redeploy; don't drop unknown OpenBao keys on write; prompt for secret-id instead of passing it as an arg; raw bind check for our own container's port
@BKJN1

BKJN1 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 3 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread internal/checks/checks.go
// on it — a real conflict, not a false alarm from our own container.
ln, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
if used {

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal/checks/checks.go:139: On native Docker, the deployment's own published port normally makes this net.Listen fail, so this branch rejects every local redeploy while versola-nginx is running despite the earlier own-container exception. The WSL2 behavior described in the comment does not make a bind failure distinguish an external owner on Linux/macOS.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

fmt.Println("OpenBao is already running.")
} else {
fmt.Println("Starting OpenBao...")
if err := docker.Run("compose", "-f", fragmentPath, "up", "-d", "openbao"); err != nil {

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal/deploy/configure.go:124: If a first configuration later fails (for example, missing credentials or a sealed OpenBao), this starts OpenBao without recording deployment state; uninstall then sees deployed == false and never runs compose down, orphaning the container and its volume after it deletes the generated bundle.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread internal/cmd/secrets.go Outdated
creds, err := openbao.LoadCredentials(target)
if err != nil {
if errors.Is(err, openbao.ErrNoCredentials) {
return fmt.Errorf("no OpenBao credentials stored for %q — run `versola secrets login %s <address> <role-id> <secret-id>` first", target, target)

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal/cmd/secrets.go:97: This recovery command still shows a fourth positional secret-id, although login now accepts only three arguments and prompts for it; copying the guidance both fails and exposes the Secret ID in shell history/process arguments.

Severity: medium

Other Locations
  • internal/deploy/secrets.go:36

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

… orphaned OpenBao container on uninstall; fix stale secrets-login usage hint
@BKJN1

BKJN1 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 1 suggestion posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread internal/cmd/secrets.go Outdated
target, address, roleID := args[0], args[1], args[2]

fmt.Print("Secret ID: ")
secretID := readLine()

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal/cmd/secrets.go:50: readLine leaves terminal echo enabled, so the AppRole SecretID is displayed in the terminal (and commonly retained in scrollback or session recordings). This still exposes the password even though moving it out of positional arguments avoids shell history and process-list exposure.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

@BKJN1

BKJN1 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

fmt.Println("OpenBao is already running.")
} else {
fmt.Println("Starting OpenBao...")
if err := docker.Run("compose", "-f", fragmentPath, "up", "-d", "openbao"); err != nil {

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal/deploy/configure.go:124: down removes versola-openbao through the current Compose file; the next bootstrap reaches this line and starts a fresh container, which the code below notes is sealed on every fresh start. Because resolveSecrets immediately authenticates and there is no CLI unseal path, the documented down→bootstrap reuse flow (and a Docker daemon restart) fails until an operator manually unseals OpenBao.

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional — see develop.md's OpenBao one-time-setup section, single-operator unseal is by design for this internal tool

// entries expect these files to already exist) -- Configure is where
// both of those are true.
func resolveSecrets(dir, target string) error {
creds, err := openbao.LoadCredentials(target)

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal/deploy/secrets.go:33: On a clean machine, Configure starts a brand-new uninitialized local OpenBao volume before this call, so no local AppRole credential can exist. secrets login local cannot prepare it beforehand because its login requires that still-uninitialized server, making the advertised fresh bootstrap local workflow fail unless OpenBao is manually initialized and configured outside the CLI.

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional — see develop.md's OpenBao one-time-setup section, single-operator unseal is by design for this internal tool

@BKJN1

BKJN1 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread internal/state/state.go
}
if err := os.MkdirAll(bundleDir, 0o755); err != nil {
return "", fmt.Errorf("couldn't create %s: %w", bundleDir, err)
if err := s.Save(); err != nil {

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal/state/state.go:206: This replacement is not failure-safe: Save uses os.WriteFile, which truncates an existing state.json before a later write failure. For example, a full disk makes Configure return an error but destroys the previous active-deployment record that this new sequencing is intended to preserve, leaving its running containers without a usable down/uninstall handle.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread internal/deploy/secrets.go Outdated
}
}

return writeDotenv(filepath.Join(dir, service+".secrets.env"), final)

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal/deploy/secrets.go:101: Only the resolved file is written with mode 0600; the *.generated-secrets.env source read above is left in the 0755 bundle directory. Those candidate values are secret material (and become live values on first setup), so the tools' normal readable output exposes them to other local users despite the protection applied to *.secrets.env.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

…nerated-secrets.env candidates after resolving (world-readable, held real secret material)
@BKJN1

BKJN1 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

// develop.md's OpenBao section for the manual `bao operator unseal`
// step.
fmt.Println("Resolving secrets (OpenBao)...")
if err := resolveSecrets(dir, target); err != nil {

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal/deploy/configure.go:149: If resolution fails (for example, because credentials are missing or OpenBao is sealed), the generated *.generated-secrets.env files remain in the 0755 bundle directory because removal only occurs after a successful per-service resolution. Those candidates become live values on first setup, so this leaves secret material readable to other local users on a shared VPS host.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread internal/deploy/configure.go
@BKJN1

BKJN1 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 1 suggestion posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

func restrictGeneratedSecretsPerms(dir string) error {
for _, service := range secretServices {
path := filepath.Join(dir, service+".generated-secrets.env")
if err := os.Chmod(path, 0o600); err != nil {

@augmentcode augmentcode Bot Aug 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal/deploy/secrets.go:31: pullAndRunTools starts the tools container without --user, so on a Linux VPS its bind-mounted output is normally owned by the image user (commonly root), not by the CLI user. That user can remove the file through the caller-owned directory but cannot chmod it, causing every Configure to abort here before secret resolution unless the image happens to arrange matching ownership.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

@BKJN1

BKJN1 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 1 suggestion posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread internal/deploy/up.go
@BKJN1

BKJN1 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

augment review

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. No suggestions at this time.

Comment augment review to trigger a new review at any time.

@BKJN1
BKJN1 merged commit 7c5ef49 into feat/configure-up-split Aug 15, 2026
1 check passed
BKJN1 added a commit that referenced this pull request Aug 18, 2026
up.go's completion message always said https://id.versola.kz
regardless of what --auth-url was actually passed -- any deployment
using a different domain would be told to visit the wrong endpoint
after a successful deploy (goshacodes/augment review on
#7).
BKJN1 added a commit that referenced this pull request Aug 18, 2026
…Configure

- OpenbaoVolumeName (tools.go) gives local/vps distinct volume names
  instead of one shared name across both compose templates -- was
  already referenced by configure.go/up.go in the previous commit
  without this definition existing yet, which would have broken a
  fresh checkout at that commit
- uninstall.go updated to match
- vps confirmation moved from Up to before Configure runs at all --
  Configure's Finalize was already irreversible by the time Up asked
  (goshacodes/augment review on #7)
BKJN1 added a commit to versolauth/versola that referenced this pull request Aug 18, 2026
Volume separation alone wasn't enough -- the container_name was still
shared (versola-openbao), so switching targets on one machine could
find the OTHER target's container already running, leave it alone
(Configure's own optimization), and never mount the freshly created
target-specific volume at all -- silently resolving secrets against
the wrong target's OpenBao entirely (augment review on
versolauth/versola-cli#7).
BKJN1 added a commit that referenced this pull request Aug 18, 2026
Both targets' compose fragments bind OpenBao to the same host port --
separate container names/volumes don't change that. Previously this
surfaced as a raw Docker 'port already allocated' failure; now it says
which container to stop (augment review on #7).
BKJN1 added a commit to versolauth/versola that referenced this pull request Aug 18, 2026
* tools: add OpenBao to the local docker-local compose stack

* gen-env: placeholder secrets (incl. paired public keys) for docker-local, resolved via OpenBao

* feat: vps env branch in gen-env.scala (host-network defaults, OpenBao secrets)

* feat: vps compose template + TARGET-aware entrypoint in versola-tools

* docs: document OpenBao one-time setup for docker-local and vps

* fix: restrict OpenBao's listener to loopback (was reachable on all host/VPS interfaces)

* fix: bind auth/central/edge to loopback on vps (was reachable on VPS's public interface, bypassing nginx)

* fix: keep edge key-id paired with its OpenBao-resolved private key; document seeding real crypto material for vps migration

* fix: keep edge key-id paired with its OpenBao-resolved private key; document seeding real crypto material for vps migration

* docs: warn about container-name conflict on first vps configure after a pre-existing deployment

* fix: address goshacodes review on #176 - decouple target/env, require --auth-url

- unify central's default port to 8090 (was 9001 in interactive mode)
- split gen-env.scala's env into target (picks network defaults) and a
  separate env value: docker-local stays fixed, vps now reads it from
  ENV_NAME instead of always hardcoding prod
- vps no longer defaults its public URL to our own domain -- AUTH_URL
  is now required, read from the environment, fails loudly if missing
- Dockerfile.tools' build-time smoke test passes a placeholder
  AUTH_URL for the vps check

* fix: require --postgres-host for vps too, same as --auth-url

Postgres URL was still hardcoded to 127.0.0.1:5432 for vps -- not
company-specific like the domain was, but still an infra assumption
that won't hold for every future deployment (goshacodes' review on
#176: 'user should provide this URL, we should not set defaults').
One POSTGRES_HOST value now drives all three services' currentSchema
URLs instead of three separate hardcoded defaults.

* fix: revert central's default port back to 9001

Broke ci-cd.yml's e2e job -- it hardcodes PORT=9001 when starting
central directly for gen-env.scala's 'local' branch, so auth couldn't
reach it after the port-unification commit changed this default to
8090 (retried until OOM). Reverted; the port-consistency comment this
was addressing was about the general discrepancy, not this specific
CI-load-bearing value.

* fix: quote EDGE_PUBLIC_JWK in vps seeding example, matches JWKS_JSON

Unquoted JSON in the docs example is subject to shell word-splitting
on commas/braces -- would seed a corrupted value if copy-pasted as-is.

* fix: separate OpenBao container_name per target too, not just the volume

Volume separation alone wasn't enough -- the container_name was still
shared (versola-openbao), so switching targets on one machine could
find the OTHER target's container already running, leave it alone
(Configure's own optimization), and never mount the freshly created
target-specific volume at all -- silently resolving secrets against
the wrong target's OpenBao entirely (augment review on
versolauth/versola-cli#7).

* fix: quote real secret placeholders in vps seeding examples

Same class of bug as EDGE_PUBLIC_JWK earlier -- unquoted values
containing \$, whitespace, *, or ! would be mangled by the shell before
bao receives them.

* fix: drop -t from the policy-write docker exec, heredoc stdin isn't a TTY

docker exec -it fails to allocate a pseudo-TTY when stdin is actually
a heredoc, not a real terminal -- step 5 (policy write) would fail
before ever reaching the policy content, leaving the AppRole role
created in step 6 with no working policy attached.
BKJN1 added a commit that referenced this pull request Aug 18, 2026
* refactor(bootstrap): split into configure/up, add state.json

- internal/deploy: Configure (checks + versola-tools + compose.yml) and
  Up (start stack, wait readiness) as separate steps, bootstrap.local
  calls both in sequence -- external behavior unchanged.
- internal/state: replace bare 'version' file with state.json
  (target/version/configuredAt/migratedAt), keep reading the old format
  for deployments made by earlier CLI builds.
- each configure run gets its own bundle-<timestamp> directory instead
  of reusing one fixed path -- works around a Docker Desktop bug where
  bind-mounting a wiped-and-recreated path can make cp fail with
  'File exists' for a file that doesn't exist.
- internal/docker: docker.Run/Cmd extracted out of bootstrap.go, reused
  by uninstall.go.

* feat: OpenBao-backed secrets, vps target support (#7)

* deploy: create the external openbao-file volume before compose up

* secrets: OpenBao AppRole client + secrets login/test commands

* deploy: resolve auth/central/edge secrets against OpenBao before starting the stack

* feat: support vps target in configure/up (TARGET passthrough, confirmation before touching prod)

* fix: stable compose project name across configure runs (was failing on redeploy)

* fix: validate target in openbao credentials path, remove OpenBao volume on local uninstall

* fix: uninstall only clears ~/.versola/active (was wiping all targets' OpenBao credentials)

* fix: prompt for OpenBao secret-id instead of passing it as an arg; PortFree does a raw bind check even for our own container

* fix: stop echoing vps admin password on every deploy; preserve deployment state across a failed redeploy; don't drop unknown OpenBao keys on write; prompt for secret-id instead of passing it as an arg; raw bind check for our own container's port

* fix: don't over-check own container's port on native Docker; clean up orphaned OpenBao container on uninstall; fix stale secrets-login usage hint

* fix: mask secret-id input at the terminal (was visible in scrollback/session recordings)

* fix: write state.json atomically (was truncate-then-write); remove generated-secrets.env candidates after resolving (world-readable, held real secret material)

* fix: restrict generated-secrets.env permissions immediately, not only after successful resolve

* fix: don't abort configure when chmod on generated-secrets.env fails (root-owned files on Linux VPS)

* fix: create bundle directory 0700 instead of 0755 — closes the secret-exposure gap chmod couldn't (root-owned files on Linux)

* feat: add --auth-url flag, pass ENV_NAME/AUTH_URL to versola-tools

vps deploys now require --auth-url explicitly instead of versola's
gen-env.scala hardcoding our domain -- see versolauth/versola#176.
ENV_NAME=prod is now passed explicitly too, decoupled from target the
same way.

* feat: add --postgres-host flag, required for vps

Same reasoning as --auth-url -- see versolauth/versola#176.

* fix: record --auth-url in state, print it instead of hardcoded domain

up.go's completion message always said https://id.versola.kz
regardless of what --auth-url was actually passed -- any deployment
using a different domain would be told to visit the wrong endpoint
after a successful deploy (goshacodes/augment review on
#7).

* fix: separate OpenBao volume per target, ask vps confirmation before Configure

- OpenbaoVolumeName (tools.go) gives local/vps distinct volume names
  instead of one shared name across both compose templates -- was
  already referenced by configure.go/up.go in the previous commit
  without this definition existing yet, which would have broken a
  fresh checkout at that commit
- uninstall.go updated to match
- vps confirmation moved from Up to before Configure runs at all --
  Configure's Finalize was already irreversible by the time Up asked
  (goshacodes/augment review on #7)

* fix: use target-specific OpenBao container name, not shared

Matches the openbao-<target> container_name compose now uses. Configure
checks the right one before deciding to leave it running; uninstall's
orphan-cleanup checks both, since an untracked orphan's target is
unknown by definition.

* fix: clear error when the other target's OpenBao already holds port 8200

Both targets' compose fragments bind OpenBao to the same host port --
separate container names/volumes don't change that. Previously this
surfaced as a raw Docker 'port already allocated' failure; now it says
which container to stop (augment review on #7).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant