Runtime and deployment¶
Backend validation and native smoke run on the owner-managed Linux ARM64 runner, without a GitHub-hosted fallback. Pull requests can reach these self-hosted jobs only when their head belongs to the same repository; fork pull requests do not run on the owner machine. Native smoke explicitly selects HTTP so its readiness probe and browser base URL cannot disagree with an inherited development certificate. The six-minute readiness deadline and the browser tests remain unchanged.
Shared CI bounds Python dependency setup with UV_CONCURRENT_DOWNLOADS=4,
UV_CONCURRENT_INSTALLS=2, UV_HTTP_TIMEOUT=120 (seconds per HTTP read) and
UV_HTTP_RETRIES=3.
This dependency-only policy retains frozen locks, isolated job caches and existing
test/startup deadlines. Runner disconnections remain infrastructure failures;
these settings do not make an offline runner pass.
This page records reviewed source contracts as of the verification date. Docker is a supported optional deployment target; native development remains the default. Neither source review nor a configured release target proves platform acceptance. See the operations runbook for commands, data preservation and diagnostics.
Native runtime¶
Start the two repository wrappers from terminals. macOS LaunchAgents are an optional host arrangement, not a prerequisite:
| Process | Wrapper under scripts/runtime/ |
Default address | Source reload |
|---|---|---|---|
| Backend | run_native_dev.sh 5002 |
127.0.0.1:5002 |
uvicorn watches backend/. |
| Frontend | run_native_frontend.sh --config vite.config.js --host 127.0.0.1 |
HTTP(S) 127.0.0.1:5173 |
Vite reloads source. |
The backend uses uv run --project "$BASE" --frozen --no-sync with the
existing root Python environment. Its only environment/data authorities are
Python's load_env() and resolve_data_dir(), not shell dotenv parsing.
Per-variable precedence is process environment, repository .env, then the
shared file explicitly selected with GNOSI_SHARED_ENV_FILE; no parent
.env_shared is inferred. The data resolver selects GNOSI_DATA_DIR, then
GNOSI_LOCAL_DATA, then LOCAL_DATA_DIR, then the platform default.
The wrapper does not choose a vault when none is configured or force OneDrive,
a provider, HOME_HOST_PATH, timezone, model or translation endpoint.
The frontend sets COREPACK_ENABLE_NETWORK=0 and executes
corepack pnpm --filter @gnosi/frontend dev. The example passes Vite's config
and loopback host explicitly; otherwise the configured Vite host applies.
The wrapper preserves explicit VITE_BACKEND_HOST and VITE_BACKEND_PORT
(defaults: localhost and 5002). Vite owns its dotenv files; the wrapper
leaves an unset VITE_FRONTEND_PORT unset so it cannot shadow them. It also
preserves explicit checkout labels and can warn when a served checkout is an
already-merged ancestor of origin/main.
Both wrappers validate supplied ports from 1 to 65535, forward arguments and
propagate exits. They do not install or synchronize dependencies; the pinned
package manager and locked environments must already be prepared. Source
reload does not refresh dependencies. uv.lock is authoritative, but its
platform-specific dependency choices do not certify the ML stack on every OS
or architecture.
Docker self-hosting¶
The base docker-compose.yml provides backend, frontend and Zotero's
translation-server without requiring host vault paths or private tooling:
| Storage | Named volume | Backend path |
|---|---|---|
| Per-device state | gnosi_local_data (existing key) |
/data; GNOSI_DATA_DIR=/data |
| Vaults | gnosi_vaults (new) |
/vaults; GNOSI_VAULTS_ROOT=/vaults, DIGITAL_BRAIN_VAULT_PATH=/vaults/default |
Preserve the existing Compose project name and both data volumes on upgrades;
the project name determines named-volume identity. A new vault volume does
not import existing host vaults. Never use docker compose down -v or broad
volume pruning to repair dependencies; preserve databases, credentials and
vault content before migration.
Ports publish on loopback by default: 127.0.0.1:5002 and 127.0.0.1:5173.
GNOSI_BIND_ADDRESS, GNOSI_BACKEND_PORT and GNOSI_FRONTEND_PORT control
host publication. Internal ports remain 5002/5173; the frontend uses HTTP and
proxies API/WebSocket traffic to backend:5002. Review access and TLS before
exposing another bind address. A strong private GNOSI_JWT_SECRET is required
at Compose interpolation time through the shell or local .env; a service
env_file alone cannot supply it. GNOSI_REQUIRE_AUTH=1 is explicit.
Compose optionally reads the shared file selected by GNOSI_SHARED_ENV_FILE
(fallback .env.shared.disabled), then optional local .env. Local entries
win over shared entries; explicit service environment wins over both.
Arbitrary host shell values are not automatically container variables.
These files are neither mounted nor baked into images. Compose clears
GNOSI_SHARED_ENV_FILE inside the backend after loading their values.
Zotero's translation-server stays internal on 1969. GNOSI_TRANSLATION_IMAGE
selects its image; TRANSLATION_SERVER_URL defaults to
http://translation-server:1969 only when unset, preserving an explicit empty
value. Translation is optional to the application; the current Compose file
includes the sidecar without an optional profile.
The explicit compose.vaults.yml override requires both existing host paths:
VAULT_HOST_PATH for the active vault and VAULTS_ROOT_HOST_PATH for its
parent. Both binds use create_host_path: false. Merging by container target
replaces the /vaults volume, adds /vault, sets
DIGITAL_BRAIN_VAULT_PATH=/vault and preserves gnosi_local_data:/data.
The two host paths are forwarded explicitly for file actions. Relative paths
resolve from the base Compose directory; prefer absolute paths. This override
neither migrates data nor provisions host helpers.
There are no implicit home, private .antigravity, secrets-directory, Docker
socket, source-code or host-dependency mounts. Only the explicit override adds
its two vault binds. A Docker CLI inside the backend image does not provide
host-engine access without an explicitly configured socket or endpoint.
Source and dependencies belong to the images: no host-source hot reload or
anonymous node_modules volumes. Rebuild after code or lock changes.
The frontend image pins Node 22.22.2 and pnpm 11.19.0, installs with
--frozen-lockfile and runs Vite on strict port 5173. The image installs the
pinned JavaScript distribution of pnpm with npm's bounded network retries and
checks its version. A clean checkout is sufficient; no runner-owned pnpm files
are copied into the image. Registry access is still required. The native Linux
smoke job likewise installs this distribution after setting up Node 22.22.2,
avoiding the standalone executable download used by the action's self-updater.
The backend exports
uv.lock with --frozen, installs the pinned CPU-only Torch wheel before
the exported requirements, and runs uvicorn without --reload. Wheel
availability and actual build/startup remain platform acceptance requirements.
Static contract tests do not replace actual Compose merging, image builds,
container smoke tests or platform acceptance.
Native smoke starts ordinary Uvicorn without a background traceback thread.
The external scripts/ci/wait_native_services.py probe requires HTTP 200
from both /api/health and the frontend in the same polling cycle. Each request
has a two-second maximum, within a six-minute monotonic deadline; the startup
step has a seven-minute outer limit. Failures report each endpoint's last
result and retain both server logs. Startup banners or wrapper process IDs do
not count as readiness, and the browser smoke suite still runs afterwards.
Electron packages¶
The frontend CI contract check has a ten-minute step timeout. OpenAPI generation does not start a background traceback watchdog: cancellation of that diagnostic thread was observed blocking under translated Python on macOS. The step timeout bounds the entire contract check without altering the generated schema.
Electron owns the packaged application lifecycle. It starts the bundled Python backend, exposes a narrow IPC surface through preload, opens the renderer, and manages manual update state. The renderer subscribes to updates and can query the latest state to avoid missing events emitted before React mounts.
The desktop process installs an explicit native application menu instead of Electron's default development menu. React remains the source of truth for translated labels: once the configured interface language is resolved, the renderer sends a validated label set through preload and repeats that handshake when the language changes. Native Settings commands return to the existing Global Settings modal. Production menus exclude reload and developer tools.
Gnosi main windows are tracked independently. File → New Window creates another renderer against the same bundled backend, closing one window removes only that window, and macOS Dock activation recreates a main window after the last one has closed. Renderer-bound menu commands focus an existing Gnosi window or wait for a newly created renderer before delivery.
Candidate jobs produce platform installers plus the update metadata required
by electron-updater, after shared CI succeeds at the exact candidate commit.
They retain an Actions artifact for five days, not a GitHub draft or public
release. Publication is disabled pending complete acceptance and a separately
reviewed publication path; see candidate distribution.
Configured targets and static
contracts do not prove clean installation, first launch, update, rollback,
signing or data preservation; every platform requires independent evidence.
Auxiliary host services¶
Host-open helpers can provide file opening, Spotlight search, native pickers and Trash actions. Cloud-file helpers can hydrate online-only files; vendor recovery belongs to the selected adapter. These are optional integrations requiring explicit provisioning, not portable startup prerequisites.
The 15 historical host-runtime scripts (installers, watchdogs and host tools),
plus the obsolete run_brain.sh and run_prod.sh launchers, have been retired
from the public repository. Host operations belong in private WorkspaceTools.
The historical install_native_startup.sh stops listeners on 5002/5173 and
reloads LaunchAgents. A preserved native_watchdog.sh can kill broadly matching
multiprocessing workers and restart via launchd; do not execute either script
as a generic diagnostic. Review the actual installed configuration and private
procedures. This checkout cleanup does not change, migrate or uninstall
installed host services. Portable wrappers remain the native startup contract.
Port and process invariants¶
- Only one listener may own each selected address/port; 5002/5173 are defaults, not permission for native and Docker instances to share a binding.
- Vite uses
strictPort; silently falling back to another port is a QA failure. - Native source reload does not update dependencies or startup-injected versions; container source changes require rebuilding the image.
- Browser QA follows the active Vite protocol. HTTP applies without readable
local certificates; automatic HTTPS uses those certificates,
VITE_DEV_HTTPS=falseforces HTTP andVITE_DEV_HTTPS=truerequires them.
Health and acceptance gates¶
/api/health reports process state, mode, effective authentication policy and
vault configuration. Verify /api/config and /api/vault/pages with an
authorized session; liveness alone cannot establish vault readability.
Native acceptance must test real registration, workspace and first-vault
creation, login, /api/auth/me, HttpOnly cookies and Playwright authentication
setup, with clean startup and shutdown. Browser checks must create/edit a
disposable page, reload/reopen it to verify title/body persistence, inspect
the console and verify logout. Setup requires explicit
GNOSI_TEST_EMAIL and GNOSI_TEST_PASSWORD for an existing disposable
account, derives identity and workspace membership from the verified session,
and never registers an account or invents admin privileges.
GNOSI_TEST_WORKSPACE_ID must match membership; without it exactly one
membership is required. Optional GNOSI_TEST_VAULT_ID grants no access.
Keep credentials, cookies and GNOSI_TEST_STORAGE_STATE private.
backend/tests/test_vault_creation_membership.py covers authorized first-vault
bootstrap, authentication/role/cross-workspace denial, path confinement and
organization listings without registering personal storage. These scoped
checks do not certify the full E2E suite, Docker/Electron matrix or release. The
integration owner performs the remaining real browser, CI, SOP, documentation
generation and platform acceptance checks.