diff --git a/docs/self-hosting/architecture.mdx b/docs/self-hosting/architecture.mdx index ba660c699..bddb69509 100644 --- a/docs/self-hosting/architecture.mdx +++ b/docs/self-hosting/architecture.mdx @@ -4,8 +4,6 @@ description: Under the Hood icon: "sitemap" --- -Omnara is a control plane for managed agents. Postgres owns durable product state, while each `omnarad` machine keeps the local execution state needed to recover commands it accepted. Read this before deploying or debugging a self-hosted stack. - ```mermaid flowchart LR clients["Browsers & API clients"] -- "HTTPS + SSE" --> ingress["Public origin / ingress"] @@ -36,39 +34,25 @@ flowchart LR ## Processes -**web** serves the compiled single-page application. It handles browser-route history fallback but does not proxy API traffic. Deployment ingress sends default routes to `web` and the backend path prefixes `/api/*`, `/install/*`, and `/.well-known/*` to `api`, keeping both services under one public origin. - -**api** (`cmd/api`) serves the entire public surface on one listener: the REST API and SSE event streams under `/api/*`, browser auth routes, the WebSocket endpoint machine daemons connect to, and — optionally — the embedded web console. - -**worker** (`cmd/worker`) executes turns: it claims ready agent work, builds the model context, calls the model provider, and runs tools. Workers are global across all projects and horizontally scalable — run as many as your throughput requires. Each process creates a random UUID at startup solely to route best-effort Redis cancellation messages. - -**maintenance** (`cmd/maintenance`) is the background janitor: on a fixed interval it reaps expired locks and runtimes, times out stuck work, and reconciles machine pools toward their targets. - -**migrate** (`cmd/migrate`) is a one-shot process that applies the release's schema migrations and exits. Run it successfully before starting or updating the three long-running control-plane services. - -**omnarad** (`cmd/daemon`) runs on each machine agents execute on, not in the control plane. It dials out to `api` — no inbound port needed — and runs commands that survive daemon restarts. Official installs live under `~/.omnarad`, use a user systemd or launchd service when available, and update in the background from the installer-recorded release manifest. - -The registered daemon semantic version is the sole compatibility boundary between `omnarad` and the control plane; individual features do not invent separate protocol versions. When two released daemon versions genuinely need different behavior, the control plane branches on that one version. The current pre-release implementation instead cuts directly to one protocol and local schema rather than carrying speculative fallbacks. - -Once a daemon accepts a command, it retains physical responsibility for it until Postgres resolves it. The exact detached supervisor or recoverable evidence in a per-machine SQLite state database represents that responsibility. The database contains physical execution facts and bounded report evidence, not command packages, resolved secrets, or machine credentials. On reconnect, the daemon reconciles its local claims with Postgres before accepting more work. A command whose outcome is ambiguous after acceptance becomes `unknown`; it is never made eligible to run again merely because the daemon, supervisor, connection, or reporting path failed. +**web** serves the compiled single-page application -## Agent runtime ownership +**api** (`cmd/api`) serves the public API surface -An agent runtime lock is the sole durable record granting a worker ownership of an agent execution. Its runtime-lock ID is the ownership epoch, and owned writes require that ID, no pending cancellation, and a lease deadline still in the future. Workers use a fixed 90-second lease, while PostgreSQL derives `started_at`, `renewed_at`, and `lease_expires_at` from its own statement time. Each successful renewal also establishes a conservative local monotonic safety budget, so worker execution does not depend on clock synchronization with PostgreSQL. +**worker** (`cmd/worker`) executes agent turns: it claims ready agent work, builds the model context, calls the model provider, and runs tools. Workers are stateless and scale horizontally. -Once the stored deadline passes, the runtime lock stops authorizing writes and becomes eligible for recovery. Maintenance rechecks the lock ID and deadline while following the canonical row-lock order. A renewal that wins this race refreshes the deadline and prevents recovery. If maintenance wins, it recovers the interrupted work and deletes the lock before replacement ownership can be acquired. +**maintenance** (`cmd/maintenance`) reaps expired locks and runtimes, times out stuck work, and reconciles machine pools toward their targets -MCP calls and other worker-owned network tools release ordinary turn capacity while they wait, but they do not release agent ownership. The worker keeps the same runtime lock renewed and cancellable until every asynchronous call from that turn has committed a result or reached the fixed five-minute execution limit. Each running call records that runtime-lock ID, and completion requires the same active lease. If a call is interrupted, or cancellation or recovery wins first, its external outcome is recorded as unknown and a late network response cannot overwrite that result. +**migrate** (`cmd/migrate`) applies the release's schema migrations and exits. Run it successfully before starting or updating the long-running services. -The `worker_process_id` stored on an active lock is not a product identity or registry entry. It is only the Redis routing address for best-effort cancellation. The transactional `cancel_requested_at` flag remains authoritative, allowing a later renewal to recover cancellation when Redis delivery is missed. +**omnarad** (`cmd/daemon`) runs on each machine agents execute tool calls on. It installs under `~/.omnarad` with a user systemd or launchd service when available, and self-updates from its installer-recorded release manifest. ## Stores -**Postgres** is the canonical store: every durable noun — organizations, projects, agents, events, machines, secrets — is a row. Only the one-shot `migrate` process applies schema migrations; `api`, `worker`, and `maintenance` expect the release's migrations to have completed before they start. +**Postgres** stores all persistent data for the service, like organizations, projects, agents, events, machines, secrets. -**Redis** carries notifications and rate limits: wakeups between processes, event fan-out to SSE streams, daemon presence, and auth rate limiting. Valkey is compatible; the local Compose file uses it. +**Redis** carries notifications and rate limits, this is primarily for SSE streaming and notifications. -**Blob storage** holds artifact content — file attachments and tool outputs — in an S3-compatible bucket; events reference artifacts by ID. Local development uses the Compose MinIO service. +**Blob storage** holds artifact content, like file attachments and tool outputs. ## Next steps diff --git a/docs/self-hosting/configuration.mdx b/docs/self-hosting/configuration.mdx index 4c1e83a16..2a64c15d7 100644 --- a/docs/self-hosting/configuration.mdx +++ b/docs/self-hosting/configuration.mdx @@ -4,7 +4,7 @@ description: Environment-variable reference for every control-plane process icon: "sliders" --- -All configuration is environment variables. `.env.example` in the repo root tracks the full surface. +All configuration is via environment variables. Use `.env.example` in the repo as a starting point. **Required** refers to production. Setting `OMNARA_ALLOW_INSECURE_DEV_DEFAULTS=1` @@ -29,12 +29,6 @@ All configuration is environment variables. `.env.example` in the repo root trac | `OMNARA_WORKER_BACKGROUND_TOOL_CAPACITY` | Background tool concurrency and in-memory queue capacity per worker process | `8` | No | | `OMNARA_MAINTENANCE_INTERVAL` | Maintenance loop interval (Go duration) | `1s` | No | -Each metrics listener serves `GET /healthz` (liveness), `GET /readyz` (Postgres and Redis reachable), and Prometheus `GET /metrics`, kept off the public listener so it can stay on your internal network. - -Each worker generates a random process UUID at startup and uses it only as its Redis control-message address. Active agent runtime locks store that routing UUID, while owned writes check the runtime-lock ID and its stored lease deadline. There is no durable worker registry or operator-configured worker identity. - -Workers grant 90-second runtime-lock leases and renew them automatically. PostgreSQL records the absolute `lease_expires_at` deadline, and maintenance evaluates that stored deadline against PostgreSQL statement time. There is no runtime-lock timeout setting to coordinate across processes. - ## Database and Redis | Variable | Purpose | Default | Required | @@ -43,11 +37,6 @@ Workers grant 90-second runtime-lock leases and renew them automatically. Postgr | `OMNARA_REDIS_URL` | Redis or Valkey URL | *dev:* `redis://127.0.0.1:6379/0` | Yes | | `OMNARA_MIGRATIONS_DIR` | Directory containing schema migrations; used only by `cmd/migrate` | `migrations` | No | -The database connection's first `search_path` schema owns the Omnara tables. -If `pg_trgm` is already installed in another schema, include that schema -second, for example `search_path=agents,extensions`. Only include schemas that -do not grant `CREATE` to untrusted database roles. - ## Web serving and proxy | Variable | Purpose | Default | Required | @@ -57,7 +46,7 @@ do not grant `CREATE` to untrusted database roles. ## Email -Signup and password reset send email through `OMNARA_EMAIL_DRIVER`. In production, use `smtp` or `sendgrid` — or disable both flows with `OMNARA_AUTH_SIGNUP_ENABLED=0` and `OMNARA_AUTH_PASSWORD_RESET_ENABLED=0`. +Signup and password reset send email through `OMNARA_EMAIL_DRIVER`. In production, use `smtp` or `sendgrid` — or disable both flows with `OMNARA_AUTH_SIGNUP_ENABLED=0` and `OMNARA_AUTH_PASSWORD_RESET_ENABLED=0`. | Variable | Purpose | Default | Required | | --- | --- | --- | --- | @@ -76,7 +65,7 @@ Signup and password reset send email through `OMNARA_EMAIL_DRIVER`. In productio | `OMNARA_AUTH_PASSWORD_RESET_ENABLED` | Password reset flow | `1` | No | | `OMNARA_AUTH_CONNECTORS_JSON` | JSON array of social/SSO login connectors | — | No | -`OMNARA_AUTH_CONNECTORS_JSON` is authoritative at API startup: connectors omitted from the list are disabled. GitHub uses `kind:"github"` (endpoints default to github.com; GitHub-compatible providers must supply `issuer`, `authorization_url`, `token_url`, and `userinfo_url`). Google and generic SSO providers — Keycloak, Okta, Auth0, Azure, ZITADEL — use `kind:"oidc"` with the issuer URL and standard discovery; endpoint overrides are rejected. Production connector URLs must use HTTPS (localhost is allowed for local IdP testing). +GitHub uses `kind:"github"` (endpoints default to github.com; GitHub-compatible providers must supply `issuer`, `authorization_url`, `token_url`, and `userinfo_url`). Google and generic SSO providers — Keycloak, Okta, Auth0, Azure, ZITADEL — use `kind:"oidc"` with issuer discovery. An example is shown below: ```sh OMNARA_AUTH_CONNECTORS_JSON='[ @@ -85,11 +74,6 @@ OMNARA_AUTH_CONNECTORS_JSON='[ ]' ``` -**`email_trust_policy`** controls what a connector's email claim may do: - -- `none` (OIDC default) — the connector can only authenticate identities already linked to an Omnara user. -- `verified_email` — also allows first-time account creation and same-email linking; set it only for providers you trust to assert mailbox ownership. GitHub defaults to `verified_email` using only its primary verified email; Google email is accepted for create/link only for Gmail/Googlemail addresses or Workspace tokens with `hd`. - ## Secret encryption Secret values are envelope-encrypted with keys from this ring. @@ -103,13 +87,11 @@ Generate each key with `openssl rand -base64 32`. To rotate: add the new key to ## Skill download capabilities -Skill archive offers use a short-lived capability bound to one machine, skill, and revision. The API and worker must share the same signing key. +Skill archive offers use a short-lived capability bound to one machine, skill, and revision. | Variable | Purpose | Default | Required | | --- | --- | --- | --- | -| `OMNARA_SKILL_DOWNLOAD_SIGNING_KEY` | Base64-encoded 32-byte HMAC key for machine-bound skill download capabilities | *dev:* insecure local key | Yes | - -Generate the key with `openssl rand -base64 32` and configure the same value for API and worker processes. +| `OMNARA_SKILL_DOWNLOAD_SIGNING_KEY` | Base64-encoded 32-byte HMAC key for machine-bound skill download capabilities; generate with `openssl rand -base64 32` and set the same value on API and worker | *dev:* insecure local key | Yes | ## Blob storage @@ -125,6 +107,8 @@ Artifact content lives in S3-compatible object storage; a bucket is required. ## Machine daemon and pools +### API daemon configuration + | Variable | Purpose | Default | Required | | --- | --- | --- | --- | | `OMNARA_DAEMON_SOCKET_FALLBACK_DRAIN_INTERVAL` | API-side fallback drain floor for connected daemon sockets when Redis wakeups are missed | `30s` | No | @@ -133,11 +117,22 @@ Artifact content lives in S3-compatible object storage; a bucket is required. | `OMNARA_DEFAULT_MACHINE_POOL_TEMPLATES` | Path to a YAML file of default pool templates; when set, new orgs get cluster-managed machine pools | — | No | | `OMNARA_UNIKRAFT_DEFAULT_POOL_TOKEN` | Provider token read by the example default-pool template; default-pool provider tokens stay in env | — | No | -Daemon runtimes use a fixed two-minute lease recorded in PostgreSQL. The API renews the stored deadline, and maintenance evaluates that deadline without a matching timeout setting. +Machine pool templates automatically create a base machine pool for any new organization. The supported providers are listed below: + +| Provider | CPU fields | `provider_config` | `default_machine_provider_options` | +| --- | --- | --- | --- | +| `unikraft` | Required | Optional `api_base_url`, `allowed_images`, and `allowed_metros` | `image`, `metro`, and optional `startup_script` and `sleep_after_ms` | +| `blaxel` | Omit `default_machine_cpu`, `max_total_cpu`, and `max_machine_cpu` | Required `workspace`; optional `allowed_images` and `allowed_regions` | Blaxel sandbox `image`, `region`, and optional `startup_script` and `sleep_after_ms` | +| `daytona` | Required | Optional `api_base_url`, `allowed_snapshots`, and `allowed_targets` | Daytona snapshot name, `target`, and optional `startup_script` | + +Provider notes: -The installer resolves `/-.txt` and stores that concrete manifest URL for later updates. To use the GitHub-hosted channel instead of R2, set `OMNARA_DAEMON_RELEASE_URL=https://github.com/omnara-ai/omnara/releases/download/omnarad-stable`. This setting affects new installations only; an existing installation continues using the source stored in its installation receipt. The plain-text manifest must contain exactly one `version=MAJOR.MINOR.PATCH`, `url=`, and `sha256=<64 hex characters>` field. Release artifacts must return that exact version from `omnarad --version`. +- Custom pool images must provide a writable `HOME` or `OMNARA_HOME`, plus a POSIX shell, standard Unix utilities, `curl`, and `sha256sum` or `shasum` for the Omnara launcher (`startup_script` additionally requires `base64`). +- Blaxel images must be sandbox images built or pushed through Blaxel (custom images must include `sandbox-api`), and unlimited persistence requires quota Tier 2 or higher — Tier 0 and Tier 1 cap sandbox lifetimes at 7 and 30 days. +- Daytona snapshots are allowed by name, must be active in the selected target, must allow sandboxes to reach `OMNARA_PUBLIC_URL`, and their API-resolved CPU and memory count against the pool caps. +- For Unikraft and Blaxel, `sleep_after_ms` enables scale-to-zero after that many milliseconds of daemon inactivity; omit or `0` disables it, and enabled values must be at least `30000`. -Daemon hosts accept these installation and runtime overrides: +### Host daemon configuration | Variable | Purpose | Default | | --- | --- | --- | @@ -148,61 +143,6 @@ Daemon hosts accept these installation and runtime overrides: | `OMNARA_RUNNER_PATH` | Temporary `PATH` override for agent commands | Existing `daemon.json` value, otherwise the installation `PATH` | | `OMNARA_DAEMON_SEED_PATH` | Existing `omnarad` binary used only to seed a fresh installer-owned home | — | -`omnarad install` atomically writes validated `daemon.json` configuration. `omnarad start` and `omnarad restart` apply `OMNARA_API_URL`, `OMNARA_MACHINE_TOKEN`, `OMNARA_NO_UPDATE`, and `OMNARA_RUNNER_PATH` in memory without changing that file. When launchd or systemd is available, update `daemon.json` and run `omnarad restart` instead. Without a service manager, temporary overrides run under foreground supervision; `omnarad start --no-service` explicitly stops any managed daemon and runs with foreground supervision. - -Machine pool templates define the provider, default machine configuration, and limits used when agents request pool-backed machines. - -| Provider | CPU fields | `provider_config` | `default_machine_provider_options` | -| --- | --- | --- | --- | -| `unikraft` | Required | Optional `api_base_url`, `allowed_images`, and `allowed_metros` | `image`, `metro`, and optional `startup_script` and `sleep_after_ms` | -| `blaxel` | Omit `default_machine_cpu`, `max_total_cpu`, and `max_machine_cpu` | Required `workspace`; optional `allowed_images` and `allowed_regions` | Blaxel sandbox `image`, `region`, and optional `startup_script` and `sleep_after_ms` | -| `daytona` | Required | Optional `api_base_url`, `allowed_snapshots`, and `allowed_targets` | Daytona snapshot name, `target`, and optional `startup_script` | - -Blaxel images must be sandbox images built or pushed through Blaxel, not arbitrary registry images. Custom images must include `sandbox-api`. The Omnara launcher used by Blaxel images and Daytona snapshots requires a POSIX shell, standard Unix utilities, `curl`, and `sha256sum` or `shasum`; `startup_script` additionally requires `base64`. - -Custom pool images must provide `HOME` or `OMNARA_HOME`, and the selected location must be writable by the daemon process. - -For Unikraft and Blaxel pools, `sleep_after_ms` enables scale-to-zero after that many milliseconds of daemon inactivity. Omit it or set it to `0` to disable sleep; enabled values must be at least `30000`. - -Blaxel machine pools require quota Tier 2 or higher for unlimited persistence. Tier 0 and Tier 1 enforce maximum sandbox lifetimes of 7 and 30 days, respectively. - -Daytona snapshot CPU and memory are resolved through the Daytona API and counted against the pool caps. Snapshots are configured and allowed by name, must be active in the selected target, and must allow sandboxes to reach `OMNARA_PUBLIC_URL`. - -## Model providers - -Hosted deployments can create one cluster-managed model provider for every new organization. The control-plane API asks an optional hosted API for its credential before opening the organization transaction. A hosted API failure fails the request without creating the organization. On success, the organization, memberships, default project, default machine pools and grants, cluster-managed secret and provider, configured models, and model grants are committed together. Cluster-managed resource definitions remain visible but cannot be edited through tenant APIs; tenant-owned project grants can still reference the cluster-managed models and machine pools. - -| Variable | Purpose | Default | Required | -| --- | --- | --- | --- | -| `OMNARA_DEFAULT_MODEL_PROVIDER_TEMPLATE` | Path to the cluster-managed provider and configured-model template to create with each organization | — | No | -| `OMNARA_HOSTED_API_URL` | Base URL for optional Omnara-hosted capabilities; the control plane appends its provider-neutral API route | — | When a default model provider template is configured | -| `OMNARA_HOSTED_API_TOKEN` | Shared service-identity bearer token for control-plane-only hosted API routes; must contain at least 32 bytes | — | When a default model provider template is configured | - -Generate one high-entropy hosted API token and inject the same value only into -the control-plane and hosted-backend deployments. It is not a browser, -webhook, provider-management, or hosted-backend-to-control-plane credential. -Restricting the hosted API ingress to the control plane is additional defense; -the bearer token is still required. - -The control plane calls `POST /model-provider-credentials` beneath the configured base URL. The request contains the proposed public organization ID, the authenticated creator's public user ID, and the complete normalized provider template. The template's required `provisioner` field is an opaque adapter name interpreted by the hosted service; the control plane does not enumerate provisioners or apply provider-specific validation. The hosted service returns only the credential value. - -```json -{ "credential_value": "..." } -``` - -`Idempotency-Key` is optional on organization creation. When supplied, the control plane derives the same proposed organization ID on every retry and a completed replay returns the existing organization without another hosted call. Without the header, every request is a new creation attempt. If hosted issuance succeeds but the local organization transaction fails, the external credential can be orphaned. The hosted service must not create a second credential for that organization ID; an operator must repair the hosted issuance before the same attempt can complete. - -## Local and live-test bootstrap - -Model providers are configured through the console or API, not env vars. These keys only feed the local example CLI bootstrap and live tests. - -| Variable | Purpose | Default | Required | -| --- | --- | --- | --- | -| `OPENAI_API_KEY` | Lets the example CLI bootstrap the `openai-prod` provider config; used by live tests | — | No | -| `OPENAI_BASE_URL` | Alternative OpenAI-compatible endpoint | `https://api.openai.com/v1` | No | -| `ANTHROPIC_API_KEY` | Lets the example CLI bootstrap `anthropic-prod`; used by live tests | — | No | -| `ANTHROPIC_BASE_URL` | Alternative Anthropic endpoint | `https://api.anthropic.com` | No | - ## Web tools Web tools work out of the box: `web_fetch` has no vendor dependency, and `web_search` defaults to Exa's keyless endpoint. diff --git a/docs/self-hosting/deployment.mdx b/docs/self-hosting/deployment.mdx index 359e50447..4f973fa27 100644 --- a/docs/self-hosting/deployment.mdx +++ b/docs/self-hosting/deployment.mdx @@ -4,71 +4,66 @@ description: Steps to run Omnara on your own infrastructure icon: "cloud-arrow-up" --- -Self-hosting means running four long-lived Omnara services plus a one-shot migration job, pointed at your own Postgres, Redis, and S3-compatible bucket ([Architecture](/self-hosting/architecture)), and putting one public URL in front of them. +Self-hosting means running four long-lived Omnara services plus a one-shot migration job, pointed at your own Postgres, Redis, and S3-compatible bucket ([Architecture](/self-hosting/architecture)), with one public URL in front of them. ## Quick start (local) -The repository contains a compose file, which runs all services needed for self hosting behind the `app` profile: +The repository's compose file runs everything behind the `app` profile: ```sh git clone https://github.com/omnara-ai/omnara && cd omnara docker compose --profile app up -d --build ``` -That builds from source (a checkout auto-loads `compose.override.yaml`). To run the published release images instead, use the base file alone. Set the `OMNARA_RELEASE` env var to use images for a specific release. +That builds from source (a checkout auto-loads `compose.override.yaml`). To run published release images instead, use the base file alone, optionally pinning a release with `OMNARA_RELEASE`: ```sh docker compose -f compose.yaml --profile app up -d ``` -Open the console at `http://localhost:8000` and sign up; the local default email driver is `console`, so the verification token appears in `docker compose logs api`. Create a personal access token, then launch an agent: +Open `http://localhost:8000` and sign up — the local email driver is `console`, so the verification link will appear in `docker compose logs api`. + +## Exposing the deployment publicly + +Email links, OAuth callbacks, the machine connect command, and cloud sandboxes all build URLs from `OMNARA_PUBLIC_URL`. These features will not function without this as a publicly-accessible URL. + + + Set `OMNARA_PUBLIC_URL` on every backend service. Outside local development it is required. + + +### Cloudflare Tunnel + +One easy option to expose Omnara publicly is via a [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/). + +#### Persistent Tunnel (recommended) + +For a stable origin, add a domain to Cloudflare and create a named tunnel: ```sh -export OMNARA_API_URL=http://localhost:8000 -export OMNARA_TOKEN= -export OPENAI_API_KEY= # or ANTHROPIC_API_KEY / OPENROUTER_API_KEY -go run ./examples/testagent chat ./examples/testagent/example.yaml +brew install cloudflared # or see Cloudflare's install docs +cloudflared tunnel login +cloudflared tunnel create omnara +cloudflared tunnel route dns omnara omnara.example.com # replace with your preferred domain +cloudflared tunnel run --url http://localhost:8000 omnara ``` -## Production - - - - Stand up Postgres, Redis (or Valkey), and an S3-compatible bucket. Postgres and the bucket hold durable control-plane state such as agents, events, secrets, and artifacts; Redis provides coordination. Machine daemons separately keep the local state needed to recover accepted processes. - - - Build the web SPA plus `cmd/migrate`, `cmd/api`, `cmd/worker`, and `cmd/maintenance` from source. Official `cluster-vX.Y.Z` releases provide matching `ghcr.io/omnara-ai/omnara-web`, `ghcr.io/omnara-ai/omnara-migrations`, `ghcr.io/omnara-ai/omnara-api`, `ghcr.io/omnara-ai/omnara-worker`, and `ghcr.io/omnara-ai/omnara-maintenance` images for Linux AMD64 and ARM64. Each GitHub Release includes `cluster-release.json` with its source commit and the canonical digest reference for every image. The web image serves the SPA with browser-route fallback. The API image contains no frontend assets and runs with `OMNARA_WEB_SERVING=disabled`. For a custom single-binary deployment, `make build-api` still compiles the SPA into `cmd/api`. - - - Backend processes read their configuration from environment variables — there's no config file. Set the same `OMNARA_PUBLIC_URL`, database and Redis URLs, and secret-encryption keys on API, worker, and maintenance; configure blob storage on API and worker. The migration job needs the database URL and optionally a migrations directory. The web image requires no Omnara application environment variables because the browser uses same-origin paths. See [Configuration](/self-hosting/configuration) for the full list and which ones are required. - - - Run `cmd/migrate` to completion before starting the services. For an official release, run the exact release's `omnara-migrations` image by digest and wait for a successful exit. - - - Run `web`, `api`, one or more `worker`, and `maintenance` from the same release as the completed migration job. The backend services do not apply migrations. Run as many `worker` instances as your throughput needs; they scale horizontally. - - - Pick one public origin for the whole app (for example `https://omnara.example.com`) and set `OMNARA_PUBLIC_URL` to it. Route `/api/*`, `/install/*`, and `/.well-known/*` to `api`; route every other path to `web` on port `8080`. The web image performs SPA history fallback but does not proxy backend traffic. Preserve the incoming `Host` header when routing to API — it rejects requests for hosts it doesn't recognize. - - - API, worker, and maintenance serve `/healthz` and `/readyz` on separate metrics listeners. Keep those listeners off the public internet. Check the web service through its root route or a known static asset. - - - If agents need to run commands, open **Overview > Machines > Connect machine** in the console. Register the machine, run the generated command on it, and paste the displayed machine token when prompted. The token is shown only once. The command runs `/install/omnarad.sh` from your `OMNARA_PUBLIC_URL`. The daemon dials out to `api`, so you don't need to open any inbound ports on that machine. - - - -For `https://omnara.example.com`, the generated command has this shape: +Then, restart the stack pointed at the tunnel's URL: + +```sh +OMNARA_PUBLIC_URL=https://omnara.example.com docker compose --profile app up -d +``` + +`cloudflared service install` will keep a named tunnel running across reboots. Any other reverse proxy or tunnel (Caddy, nginx, Tailscale Funnel, ngrok) works the same way, as long as it forwards the public `Host` header — the API rejects hosts it doesn't recognize. + +#### Quick Tunnel + +For a temporary tunnel without a separate domain, you can use: ```sh -curl -fsSL https://omnara.example.com/install/omnarad.sh | sh +cloudflared tunnel --url http://localhost:8000 ``` -The installer defaults to `~/.omnarad`. It installs a user systemd or launchd -service when available; otherwise `omnarad` stays attached in the foreground. -`omnarad start --no-service` skips service-manager setup and runs with foreground -restart supervision. +This prints a random `https://.trycloudflare.com` URL that changes every run. After starting the tunnel you can set `OMNARA_PUBLIC_URL` in the same way described above. ## Next steps