Oblive Docs

Installed CLI Architecture

Technical contract for the portable Oblive operator CLI, deployment bundle, runtime generation, updates, and compatibility tests.

The installed oblive CLI is host operator tooling for a portable Oblive deployment. It owns the installation lifecycle around the complete Docker Compose stack: configuration generation, authentication preflight, image selection, service startup, inspection, coordinated updates, and explicit data removal.

It is not an application control-plane client. oblivectl is the authenticated, agent-only CLI for durable product transitions, while contextctl verify validates local Context and contextctl integration accesses live integration context. Keep deployment operations in oblive; do not add application mutations or agent capabilities to it.

Source and Artifact Ownership

ConcernCanonical sourceReleased artifact
Operator command implementationscripts/release/obliveoblive
Typed config/runtime generatorscripts/portable-stack.tslib/portable-stack.js
Base topologycompose.yamlcompose.yaml
Local Codex auth topologycompose.codex-local.yamlcompose.codex-local.yaml
Configuration contractconfig/local-stack.schema.jsonlocal-stack.schema.json
Bundle assembly.github/scripts/release/build-deployment-bundle.shoblive-deployment.tar.gz
Public bootstrapsites/landing/public/install.shhttps://oblive.dev/install.sh

The CLI is POSIX sh so the installed deployment does not require a host Bun or a repository checkout. Bundle construction still requires Bun because it compiles the TypeScript generator into one dependency-bundled JavaScript file.

The release archive has a deliberately small, exact file set:

IMAGE_REPOSITORY
VERSION
compose.cloudflare.yaml
compose.codex-local.yaml
compose.yaml
docker/garage.toml
docker/postgres/001-vector.sql
lib/portable-stack.js
local-stack.schema.json
oblive

Do not add source-only files, package-manager state, build directives, or floating image references to the portable artifact.

Installation Layout and Root Resolution

The default bootstrap creates:

~/.local/bin/oblive -> ~/.oblive/oblive
~/.config/oblive/install-root
~/.oblive/

XDG_CONFIG_HOME changes the pointer directory, OBLIVE_BIN_DIR changes the bootstrap symlink directory, and --install-dir changes the installed root. A named installation defaults to ~/.oblive-instances/<name> and registers its root at $XDG_CONFIG_HOME/oblive/instances/<name> (using ~/.config when XDG is unset). Named roots must not overlap other registered roots. The CLI resolves its root in this order:

  1. An explicit leading --instance <name>, resolving its registration; default uses the legacy pointer.
  2. OBLIVE_HOME, when set.
  3. The first line of $XDG_CONFIG_HOME/oblive/install-root.
  4. ~/.oblive.

An unknown named instance fails without falling back to default. The shared PATH symlink may point to any instance-aware release: the command dispatches to the selected installation’s own CLI before running it. Installing a named release never replaces the default pointer. Purging the instance owning that symlink repoints it to a surviving installation. oblive instances reads the registrations and version files without invoking Docker.

The root must not be empty, /, the user’s home directory, or contain :. OBLIVE_HOME is useful for tests and deliberate custom invocations, but the install pointer is the normal runtime contract for a non-default installation.

Release-owned and machine-owned files remain separate:

File or directoryOwnerUpdate behavior
VERSIONReleaseReplaced with the coordinated semantic version
IMAGE_REPOSITORYReleaseReplaced with the release repository
Compose and lib/ReleaseReplaced as one bundle
.channelInstaller/CLITracks latest or pinned
.download-base-urlInstaller/CLIPreserved into the staged release
.instanceInstaller/CLIStable name; preserved unchanged across updates
.cli-linkInstaller/CLIShared command location; preserved across updates
local-stack.jsonHuman operatorCopied unchanged into a staged update
.runtime/Generator outputRegenerated from config and the installed release

local-stack.json and generated environment files are mode 0600. The generator runs as the invoking host UID and GID so mounted output does not become root-owned on Linux.

Bootstrap Boundary

The public installer performs the one-time host bootstrap:

  1. Validate macOS or Linux on AMD64 or ARM64 and require curl, tar, awk, sed, and mktemp.
  2. Verify Docker, the Compose plugin, and the selected Codex or API-key authentication prerequisite.
  3. Resolve latest to an exact stable vX.Y.Z, or normalize the explicitly selected version.
  4. Download the versioned archive and checksum over HTTPS.
  5. Verify the SHA-256 digest, reject traversal paths, and reject links or special archive entries.
  6. Extract into the installation root, write release-channel metadata, create the CLI symlink and install pointer, and initialize the machine-local configuration.
  7. Start the stack unless --no-start was selected.

Re-running the installer against a valid existing root delegates release movement to oblive update, then reinitializes without replacing the existing configuration. A directory that exists but is not a valid Oblive installation fails closed.

A new named instance requires --port-base, an integer from 1024 through 65530. The typed generator initializes six consecutive ports, ordered PostgreSQL, Redis, Garage S3, Garage admin, backend, frontend. The operator chooses non-overlapping ranges. Reinitialization preserves the existing config and rejects a supplied base that differs from its saved ports.

Instance-aware bundles answer the internal --instance-format capability probe with 1 before root resolution. The installer and updater reject a named-instance target without that capability before invoking its lifecycle commands. Legacy bundles remain usable as the default installation.

Runtime Generation and Compose Invocation

The portable generator is bundled with the release, but it executes with the pinned Bun binary in the release’s semantic backend image:

host oblive script
  -> pull <repository>:backend-vX.Y.Z
  -> mount installation root at /oblive
  -> run bun /oblive/lib/portable-stack.js as host UID:GID
  -> write validated .runtime files
  -> invoke Docker Compose

This keeps the generator coordinated with the bundle without requiring a host runtime or making the backend image the owner of generator source. The CLI always invokes Compose with:

  • project name oblive for the default instance or oblive-<name> for a named instance;
  • the installation root as the project directory;
  • .runtime/compose.env as the environment file;
  • compose.yaml; and
  • compose.codex-local.yaml only when authMode is local.

The instance’s fixed project name stabilizes container and named-volume identity across bundle replacement, including update staging. It never includes the release version or temporary directory. Existing installations without .instance metadata retain project oblive. PostgreSQL, Redis, Garage, agent workspaces, and container-owned Codex state use named volumes. Normal stop, restart, update, and uninstall operations omit --volumes and therefore preserve that state.

prepare_runtime pulls the semantic backend image, initializes missing default-instance configuration, validates the configured authentication source, and generates release-specific environment files. start then pulls every selected image and runs compose up --detach --wait --remove-orphans. The migration container is a one-shot dependency; PostgreSQL remains the durable owner of operational state. Missing named-instance configuration fails with instructions to rerun the installer with a port base.

The instance work changes only host deployment tooling. oblivectl, application HTTP contracts, OpenAPI, and database schemas are unaffected. The regression suite exercises three installations, release dispatch, isolated update/reset/purge, preserved default data/configuration, legacy-bundle rejection, and Docker-rendered network, volume, and port separation.

Authentication Boundary

Local mode reads the configured host Codex auth.json only as an input to the dedicated codex-auth-sync service. Chat and worker services mount container-owned Codex state; they do not mount the host Codex home.

API-key mode resolves the configured host environment-variable name at invocation time. The CLI passes that variable only to the generator container. The secret is absent from local-stack.json, release metadata, and Compose interpolation output, and is materialized only in private generated agent environment files.

oblive auth login is valid only for local mode. API-key mode supports status validation but never owns a provider login flow.

Coordinated Update Transaction

start is intentionally version-stable. Only update can change the installed release.

Plain oblive update advances an installation whose .channel is latest. A pinned installation requires --version vX.Y.Z or --latest. The update transaction is:

  1. Create a private staging directory next to the installed root.
  2. Resolve and download the requested stable bundle and checksum.
  3. Verify checksum, archive safety, embedded version, and semantic ordering.
  4. Reject a downgrade before touching the running installation.
  5. Copy local-stack.json, channel metadata, and the download base URL into the staged root.
  6. Run generation and compose config --quiet against the staged root.
  7. If the old stack is running, stop it without removing volumes.
  8. Move the old root to a private backup and atomically move the staged root into its place.
  9. Restart only when the old stack had been running. Retain the private previous installation and print its location after success.

Validation happens before downtime. If startup fails after replacement, the newly installed release remains active and the previous files remain in the printed backup directory. The CLI does not automatically roll back: the new startup may already have applied irreversible migrations. Docker volumes remain intact throughout the transaction.

An update to the already installed version changes the channel metadata when requested, but does not replace release files or restart services.

Command and Error Contract

The CLI uses three process-status classes:

StatusMeaning
0Help, a successful operation, or an intentional no-op
1Runtime, dependency, authentication, validation, or I/O failure
2Unknown command, invalid option, or invalid command usage

Help is available through oblive, oblive --help, oblive help <command>, and <command> --help. Human-readable color appears only on a terminal and is disabled when NO_COLOR is set. Keep errors actionable and send them to standard error. Preserve the service allowlist in logs so user input cannot become an arbitrary Compose service or option.

Destructive Boundaries

Data removal must stay syntactically explicit:

  • reset --confirm stops the stack and removes named volumes but retains configuration and the CLI;
  • plain uninstall stops services and removes the recognized CLI symlink without removing volumes or the installation root; and
  • uninstall --purge --confirm removes volumes, the recognized CLI symlink, the installation root, and its pointer.

Never make stop, restart, update, reinstall, or plain uninstall imply volume deletion. Never widen a recursive deletion target without resolving and validating the exact installation root first.

Change and Test Contract

Treat the bootstrap, archive, CLI, generator, Compose topology, semantic image tags, and release workflow as one compatibility boundary. A change to one may require coordinated updates to all of them.

Run these focused suites after changing the installed CLI:

.github/scripts/tests/deployment-bundle.test.sh
.github/scripts/tests/deployment-bundle-container.test.sh
.github/scripts/tests/portable-install.test.sh
bun test scripts/tests/portable-stack.test.ts
bun run --cwd sites/docs check

The tests cover the exact archive contents and checksum, build-free Compose topology, POSIX shell syntax, generated-file ownership and permissions, install and PATH behavior, configuration preservation, help and exit statuses, authentication modes, semantic updates, downgrade rejection, and corrupt-download handling.

Use a bundle-only release only when existing application images remain compatible with the CLI, generator, schema, and Compose change. Otherwise publish a full coordinated release. See Container Releases for the publication and smoke-test boundary.

Upgrade acceptance and recovery

Before upgrading, record the installed version, image IDs/digests, effective non-secret configuration, active work IDs, schedules and profile catalog pins. Run oblive doctor and validate the staged configuration. Download and verify the target bundle and images before downtime. Dispatch GitHub CD with expected_sha set to the exact commit whose CI passed; a different source fails before release.

For a release without typed deployment shutdown, first stop new work dispatch through the supported organization setting and let existing runs finish. Preserve the previous dispatch flags and restore them after acceptance. This avoids interrupting old processes that cannot classify a restart.

Stop agent containers first with a 60-second timeout, keeping backend, PostgreSQL, Redis and object storage available while partial files and receipts synchronize. New bundles set both agent services’ stop_grace_period to 60 seconds. For an older bundle, explicitly use docker stop --time 60 with the agent container IDs discovered from the oblive Compose project. Do not remove volumes.

After agent shutdown, stop the remaining services and create one private, consistent recovery set:

  • The complete installed directory, including local-stack.json and its credential encryption key.
  • Every named volume mounted by the installation: PostgreSQL, Redis, Garage metadata/data, agent workspaces and Codex state. Discover mounts from the actual containers, rather than assuming names.
  • The image identities and installed release/bundle checksum. Keep the previous images available.

Treat the recovery set as credentials: restrict its directory to the owner, keep it outside Git, and verify archive checksums. A configuration copy alone is not a data backup. Restore tests must use a disposable Compose project and matching database/object-store volumes; never overwrite live volumes as a test. A rollback restores the previous installation and its matching pre-migration durable state. Starting old images against a migrated database is unsupported. Retain the recovery set until post-upgrade acceptance finishes and according to the owner’s backup policy.

After upgrading, verify service health, migration completion, immutable image revisions and automatic built-in instruction pins. Preserve explicit/custom pins, existing task identities, schedules and action ownership. Deployment interruption uses the existing bounded retry path; it does not replay an uncertain external write. Check action receipts before recovery. Confirm retained files and at least one real permitted provider read separately from mocked platform tests. Reconnect expired provider grants only when the provider requires consent.