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 is a local read-only context helper. 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.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. The CLI resolves its root in this order:

  1. OBLIVE_HOME, when set;
  2. the first line of $XDG_CONFIG_HOME/oblive/install-root; or
  3. ~/.oblive.

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
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.

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;
  • 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 fixed project name stabilizes container and named-volume identity across installation-directory changes and bundle replacement. 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 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.

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, then remove the backup 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.