refactor(bootstrap): split into configure/up, add state.json - #6
Conversation
- 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.
🤖 Augment PR SummarySummary: This PR separates deployment preparation from stack startup to support safer server deployments. Changes:
Technical Notes: The refactor establishes the configuration/startup boundary needed for a future explicit migration step while retaining the existing local bootstrap flow. 🤖 Was this summary useful? React with 👍 or 👎 |
|
augment review |
* 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)
|
augment review |
| func restrictGeneratedSecretsPerms(dir string) { | ||
| for _, service := range secretServices { | ||
| path := filepath.Join(dir, service+".generated-secrets.env") | ||
| if err := os.Chmod(path, 0o600); err != nil { |
There was a problem hiding this comment.
internal/deploy/secrets.go:44: On Linux, the tools container normally writes these files as root, so this chmod failure is expected; because Prepare creates a mode-0755 bundle directory, their usual 0644 mode leaves generated passwords readable to every local user. A failing configuration, including the documented initial VPS path, then leaves the candidate files exposed indefinitely.
Severity: high
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
…-exposure gap chmod couldn't (root-owned files on Linux)
|
augment review |
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.
Same reasoning as --auth-url -- see versolauth/versola#176.
|
augment review |
| // gen-env.scala's vps branch), is a fact about the one real VPS | ||
| // this deploys to, not a general "vps" concept — hardcoded here | ||
| // for the same reason it's hardcoded there. | ||
| fmt.Printf("\nVersola %s is running at https://id.versola.kz\n", st.Version) |
There was a problem hiding this comment.
internal/deploy/up.go:150: The VPS address is supplied through the required --auth-url and passed into the generated configuration, but this completion message always reports https://id.versola.kz. Any deployment using the documented custom domain will be told to visit the wrong endpoint after a successful deploy.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| ConfiguredAt: time.Now().UTC(), | ||
| BundleDir: filepath.Base(bundleDir), | ||
| } | ||
| if err := s.Save(); err != nil { |
There was a problem hiding this comment.
internal/state/state.go:220 — Configure reaches this save (and then deletes the previous bundle) before Up asks for VPS confirmation or starts anything. If an operator declines that prompt, or Up fails, the old containers can still be serving while status now reports the new version as deployed and the old deployment record has been discarded.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| // gen-env.scala's vps branch), is a fact about the one real VPS | ||
| // this deploys to, not a general "vps" concept — hardcoded here | ||
| // for the same reason it's hardcoded there. | ||
| fmt.Printf("\nVersola %s is running at https://id.versola.kz\n", st.Version) |
There was a problem hiding this comment.
internal/deploy/up.go:150 — VPS configuration accepts an arbitrary required --auth-url and passes it to versola-tools, but the successful deployment message always directs the operator to https://id.versola.kz. Any deployment using the documented https://id.example.com-style domain is therefore reported with an incorrect login URL.
Severity: low
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
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).
…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)
|
augment review |
| if err != nil { | ||
| return "", err | ||
| } | ||
| if running { |
There was a problem hiding this comment.
When switching targets, a running versola-openbao may still be mounted on the previous target's external volume, but this branch leaves it in place; resolveSecrets then authenticates against that previous OpenBao instance while the newly created target-specific volume is never mounted. As a result, local→vps (and the reverse) cannot reliably use the target's stored AppRole/secrets and defeats the intended volume isolation.
Severity: high
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
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.
|
augment review |
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).
|
augment review |
bootstrapused to be one function: check the machine, generate configs,start everything — no way to stop in between. That's fine locally, where
the CLI owns the database it just created, but won't work for a real
server, where the database already exists and "change the schema" has to
be a decision someone makes on purpose, not a side effect of starting a
service. Groundwork for a separate
versola migratestep ahead ofdeploying to the VPS.
internal/deploy: split intoConfigure(prerequisite checks, runsversola-tools, writes the compose file) and
Up(starts the stack,waits for readiness).
bootstrapstill calls both in sequence, soexternal behavior is unchanged — this is a pure refactor, verified by a
local deployment behaving exactly as before.
internal/state: replaces the old bareversionfile withstate.json(target, version, configuredAt, migratedAt, bundleDir), so later commands
(status/migrate/up) can tell what's actually been deployed and how far
along it got, instead of inferring it from which files happen to exist.
Deployments made by earlier CLI builds still read correctly (loadLegacy).
configurerun now gets its ownbundle-<timestamp>directoryinstead of reusing one fixed path — works around a Docker Desktop bug
where bind-mounting a wiped-and-recreated path can make a container's own
cpfail with "File exists" for a file that doesn't exist.internal/docker:Run/Cmdextracted out of bootstrap.go, reused byuninstall.go.
No behavior change for
versola bootstrap local— verified locally.