Navigace
ELOWEN · Dokumentace

Poslední aktualizace: 20. října 2026

Procházet dokumentaci · Installer and setup ownership

Tento článek zatím není přeložen. Je dostupný v angličtině.

Developer reference

Installer and setup ownership

Installer orchestration and first-run setup

src/cli/install/index.ts orchestrates preparation, licence gating, activation and recap. installArgs.ts owns the shared InstallPlan, unattended flag validation and help text; both installer front ends execute the same plan. The module parses flags without host I/O. assertInstallArgs runs first in install() for both front ends and refuses any argument outside its value and switch lists, so a mistyped flag fails instead of selecting a default. systemdUnits.ts owns the SYSTEMD_UNITS inventory after its path constants, alongside the pure unit renderers. Install, privileged refresh and uninstall import that same inventory.

install/updateCoordinator.ts owns provisionSudoers and convergeUpdateCoordinator, used by installer preparation and privileged refresh. It remains at the install module's package depth so executable resolution is unchanged. Refresh reads the elowen-update.service unit that the update timer starts, including its identity and literal environment, validates the staged sudoers drop-in with visudo -cf before installation, then updates the coordinator and queue watcher. Identical trusted files are skipped; only a changed watcher is restarted. There are no contributions or alternative inventory paths. These operations keep their existing root trust checks, file bytes, command order and cost.

src/cli/setup.ts owns probeSetup(fetchFn, base), the single decoder of the daemon's /setup response. A successful response must contain boolean needsSetup and licenceLocked fields. HTTP, JSON and shape failures throw; transport and JSON errors retain their identity. isFirstRun and the setup command propagate/refuse failures instead of assuming an admin exists. Installer licence readiness catches failures and polls within its existing deadline; it accepts only a valid unlocked response. Each attempt uses one HTTP request and one schema validation, with no persistent state.

The installer's readiness wait is waitForActiveLicence(base) in src/cli/install/index.ts. It defaults to a 20-second budget and probes /setup every 500 ms, and it returns true only when the probe succeeds with licenceLocked false. elowen setup (src/cli/setup/command.ts) calls assertLicenceUnlocked before the wizard. A failed probe or a locked licence stops it with exit code 1. Probes are made through probeSetup in three places: the installer, isFirstRun and the setup command.

The rest of src/cli/setup.ts holds the wizard's HTTP helpers. login decodes the /auth/login discriminated issuance envelope from the shared wire contract, omitting the unused account projection. HTTP 200 must be authenticated with a nonempty token and expiry metadata; HTTP 202 must be totp_required with challenge/expiry, never a bearer. Malformed or status-mismatched responses throw a content-free error. One password request does not retry or store credentials. The installer's post-bootstrap login smoke check uses the same decoder. createAdmin posts to /users and then requires a final login. saveConfig sends a PUT /config with a brain section only, and does nothing when the plan has no provider. buildSetupPlan maps the answers to that plan, storing a blank API key as absent, and applySetup runs the account creation and config save together. Setup writes through the daemon's API, not the database.

Existing protected-account password sign-in in the interactive Account step stops with browser-approval guidance, clears its in-memory credential and skips account authentication; it never asks for an OTP or marks a challenge signed in. Run elowen login for the same instance, approve the normal browser device flow, then use elowen setup --non-interactive with the desired flags, without --admin-password or ELOWEN_ADMIN_PASSWORD. Headless setup uses explicit ELOWEN_TOKEN first, an explicitly supplied admin password next, and the existing per-origin CLI credential when no password was supplied. An unrelated cached identity never replaces a requested password account. A protected password challenge fails before configuration writes. Password sign-in without TOTP and first-admin bootstrap retain their existing behavior. No web-session token is printed or written to another file; only normal pairing owns credential storage.

The shipped bootstrap install.sh runs its own guard, guard_existing_install, before it changes anything. On a machine with no elowen command the guard does nothing. A global npm link that does not point into a managed release (a linked checkout) is refused in every case, and ELOWEN_FORCE does not override that refusal. On a machine that already runs Elowen, the script asks for confirmation when it has a terminal. Without a terminal it refuses, unless ELOWEN_FORCE is set, which reinstalls and re-provisions without asking.

PostgreSQL provisioning

src/cli/install/database.ts owns host provisioning behind the existing Runner. install/index.ts calls provisionDatabase(runner, options) after licence gating. Its record(database) callback persists ownership before creating the local cluster; its verify(connectionUrl) callback checks the target without changing it. Provisioning does not activate a release; the daemon applies the schema migrations on its first boot.

With no --database-url, local mode adds the official PGDG repository and installs PostgreSQL 18, its matching pgvector package and PgBouncer. It reads pg_lsclusters --json and TCP listeners before selecting the first free cluster port from 5432. Only the recorded 18/elowen cluster is configured. An existing cluster with that name but no ownership record is refused, as are a changed owner, data directory or port. A receipt whose cluster was not created yet resumes on the reserved port unless another cluster has taken it. No cluster is dropped.

The local database record contains {mode:'local', version:18, cluster:'elowen', port, socketDir:'/run/postgresql-elowen', poolSocketDir:'/run/elowen-pgbouncer', poolPort:6432}. The shared InstallInfo contract requires both instanceRoot and database; the installer passes them to buildInstallInfo together with the deployment facts. The explicit root is independent of the service HOME and connection URL. Installer paths for the licence, plugin data and update queue use the shared instancePaths(instanceRoot) contract. Fresh provisioning creates and starts only the recorded cluster and dedicated pool. Already-owned installations use read-only preparation followed by explicit policy reconciliation; neither PostgreSQL nor PgBouncer restarts automatically. Reloadable changes are verified live; restart-only changes remain staged as restart-required. Other clusters and the host's default pool configuration are not targets. Package installation temporarily disables automatic creation of an unrelated default cluster and restores the previous policy even on failure; an unset create_main_cluster, as stock Ubuntu ships it, is restored to unset. During PostgreSQL package installation it wraps policy-rc.d to deny package-triggered PostgreSQL/PgBouncer actions, preserves the original policy by rename, and suppresses needrestart actions. A rerun recognizes its interrupted wrapper; a conflicting backup is refused. Explicit service operations still target only the recorded cluster and dedicated pool.

Both database processes listen only on Unix sockets. The dedicated elowen-pgbouncer.service runs as OS user postgres, uses transaction pooling, max_prepared_statements=100 and auth_type=hba. Its HBA accepts the service role by peer authentication. Its administrative database separately accepts OS postgres by peer authentication. auth_file (/etc/elowen/pgbouncer_users.txt) names both users with empty passwords so the HBA can select them. The admin path verifies SHOW CONFIG and drains old server connections through RECONNECT/WAIT_CLOSE; the application role gains no PostgreSQL superuser privilege. The cluster HBA uses local elowen <service-user> peer map=pgbouncer; the ident map maps both OS postgres and the service user to that database role. The default application URL points to PgBouncer, never directly to the cluster.

databasePolicy.ts::renderDatabasePolicy is the deterministic renderer used by fresh provisioning and database.ts::reconcileDatabasePolicy. Updates reuse approved root-recorded database.policy.resources and decisions, not subsequently observed capacity. Fresh sizing respects constrained memory, affinity and cgroup-v2 ancestor CPU quotas; automatic CPU sizing on cgroup v1 requires an explicit approved budget instead. Desired, written and effective SHA-256 identities are separate; the pool identity covers configuration, HBA and auth-user bytes separated by NUL. pendingRestart names settings not yet effective. A local installation without database.policy remains operator-controlled: update reconciliation returns operator-required without database commands or writes, and privileged refresh warns on stderr without blocking the core release. Existing owned installations must explicitly record approved resources, decisions and managed-file identities before automated policy convergence; updates never recompute over manual limits. Unexpected file-byte drift or an invalid recorded policy still fails. Explicit reprovisioning also continues to require approved policy inputs.

Unfinished first provisioning has neither written nor effective receipts; cluster presence alone does not establish completion. A rerun verifies the recorded cluster identity and approved desired profile, rejects drift in present managed policy files, writes only missing policy files and resumes initial starts. PostgreSQL starts only after status explicitly reports not running. Once a written receipt exists, stopped PostgreSQL or pool services are errors, not startup instructions; enabling units does not start them.

With R as approved RAM in MiB and C as approved CPUs, the owned PG18 profile uses:

Resource Policy
Shared buffers / estimated cache 25% / 70% of RAM
Connections min(64, floor(R/32))
Parallel workers / gather workers min(C,8,floor(R/512)) / at most 2
work_mem 15% divided across connections, gather workers and two operations, bounded 1–64 MiB per operation; not a total RAM cap
Maintenance memory 5% of RAM, capped at 1 GiB
Vacuum workers / memory each min(3,C) / 5% divided among workers, bounded 1–256 MiB
Maximum WAL 25% of RAM, bounded 1–8 GiB
I/O workers / concurrency min(3,C) / 16; io_method is not written, so the PG18 default stays
Pool server capacity min(40, connections-8), including at most four reserve connections

Fresh decisions use WAL compression off and a 300-second checkpoint timeout; updates preserve recorded decisions. random_page_cost=4, huge_pages=try, durability remains fully enabled and pg_stat_statements.track=top. Slow statements start at 500 ms; both parameter-length logging limits are zero. I/O/WAL timing, one-second autovacuum logs, 16-MiB temp-file logs and lock waits are enabled. This suppresses bound parameters, not literal SQL or arbitrary error text. Table-local vacuum/analyze policy belongs to schema migrations, not global installer scale factors. The application role retains statement, lock and idle-transaction timeouts.

Apply verifies native PostgreSQL parsing and trusted ownership before writes, then checks pg_settings values and sources, pg_file_settings, role/database overrides and pending_restart, including the actual reload generation. A masked requested setting is not convergence. Repeated converged or unchanged restart-required runs neither rewrite nor reload policy files. pendingRestart belongs to the last verified written profile. A new file generation clears effective state and old pending names before replacing files; an interrupted reload resumes without rewriting bytes, then records the new pending names.

External mode validates the URL and checks prerequisites before host provisioning. TCP requires sslmode=verify-full; an explicit Unix socket in the host query parameter needs no TLS. Duplicate parameters and conflicting TLS overrides are refused. The external record is {mode:'external'}. Elowen installs no PostgreSQL packages and writes no server configuration in this mode. The operator owns pooling, server tuning, role timeouts and extension provisioning. Host-policy reconciliation returns operator-required without host commands. It does not inspect an actual external GUC diff or claim that the external server matches the owned-host profile.

databaseProbe.ts invokes the shared elowen-db.checkServerFloor contract and checks database CONNECT/CREATE, public-schema creation and ownership of existing application schemas. PostgreSQL 16+, vector 0.7+, pg_trgm and installed/preloaded pg_stat_statements are required. It does not migrate or modify an external database. Probe credentials travel to the child on stdin, and failure diagnostics omit URLs and server error text.

installSpec accepts --instance-root and --database-url. Units always load root-owned mode-0600 /etc/elowen/instance.env after an optional operator environment file. It pins ELOWEN_INSTANCE_ROOT, ELOWEN_LOG_DIR and ELOWEN_DATABASE_URL. URLs never enter unit text, progress output or install.json. An existing operator environment file with conflicting pins is refused without echoing its values. An external reinstall also checks the existing protected file, so changing only the command-line URL cannot repoint a recorded instance. The protected file is persisted before the ownership record, allowing interrupted installations to resume. The licence, plugin data and update queue all follow the explicit state root. --adopt requires a previously provisioned database, verifies it and prepares application units without starting database or application services.

Focused installation tests run all host operations against recorded commands. The scripts/install-smoke/postgres-recorded.mjs host writes only into a caller-supplied new empty disposable directory and exercises first provision plus rerun. It starts no database, apt process or service. Real PostgreSQL 18 installation acceptance is a separate disposable-container gate. databasePolicyPostgres.test.ts additionally requires native PostgreSQL 18 and /usr/sbin/pgbouncer (verified with 1.22.0), using only private ephemeral sockets/processes. It checks parsing, effective settings, pending restart, parameter suppression and pool reconnect. Its unprivileged pool fixture adapts authentication to the harness user; root peer-auth/systemd equivalence remains the container gate. The onboarding harness under scripts/tests/installer-e2e now provisions a PostgreSQL 18 fixture inside its own container, supplies both explicit instance-root and database-URL environment values to the installed daemon, and stops that owned fixture during teardown. Its fixture refuses execution outside the dedicated container.

Shared installation contracts

src/shared/installInfo.ts owns ProxyKind, server installation-record sanitization, serialization, reads and webBaseUrl. CLI lifecycle, provisioning, privileged refresh and brain construction import it directly. The PostgreSQL installer records an explicit instanceRoot and database ownership, without connection credentials; absent-record behavior remains unchanged.

The same module owns ClientInstallInfo and readClientInstallInfo(path) for a standalone client's explicit <prefix>/install.json. The strict record contains exactly { kind: 'client', prefix, instanceOrigin }. clientUpdate.ts passes that path during bootstrap and installed startup and checks that the recorded prefix matches the resolved installation. A missing file returns null; malformed JSON, invalid fields and other read failures throw. Reading costs one file read and schema validation, with no discovery or service access. Client records do not change the default system server-record path. See the shared CLI owner for addressing and bootstrap, and update transactions for publication, proof and recovery.

src/cli/install/hostConvergence.ts owns root unit convergence, installed core inventory/record writes and refreshAfterUpdate. Installer preparation, privileged refresh and update coordination import this owner; update.ts retains transaction orchestration and its injected refresh collaborator. Root refresh has no dependency on update.ts or install/index.ts. convergeRecordedDatabasePolicy validates the root-owned record, serializes policy work under the instance kernel lock, revalidates database identity and preserves unrelated record fields. An absent source-checkout record is a no-op; an existing malformed or untrusted record fails.

replaceUnitFile retains its content, lstat, root-owner, regular-file, symlink and 0644 checks. Required replacements use writeTextAtomic with stagingMode: 0o600, mode: 0o644 and forceMode: true. Staging is exclusive, final mode is applied on the open descriptor, and rename replaces the path. Failures propagate and owned staging files are cleaned up.

Headless setup requires a successful provider probe, validates unknown replies with providerProbeResponseSchema and selects the first chatModelCatalog entry. Missing output metadata remains selectable; explicit non-text outputs, failed/malformed replies and empty chat lists yield no model. Explicit-model, Anthropic and keyless paths retain their policy. Provider upserts preserve settings and order; PUT /config stores one provider/model pair. readinessLines in doctor.ts renders checks and failing hints for doctor, wizard and headless setup, without I/O; only doctor adds its summary.