Skip to content

kici-admin: configuration & database

Terminal window
kici-admin config init [--output <path>] # Generate starter orchestrator.yaml
kici-admin config seed --file <path> [--description <desc>] [--format json|yaml|table]
kici-admin config get [path] [--format json|yaml|table]
kici-admin config set <path> <value> [--description <desc>] [--format json|yaml|table]
kici-admin config delete <path> [--description <desc>] [--format json|yaml|table]
kici-admin config export [--format json|yaml]
kici-admin config validate --file <path> [--type local|shared|full (default: shared)] [--offline] [--format json|yaml|table]
kici-admin config diff [--format json|yaml|table]
kici-admin config history [--limit <n> (default: 20)] [--format json|yaml|table]
kici-admin config rollback --to <version> [--format json|yaml|table]
kici-admin config reload [--drain] [--target <instance-id>] [--format json|yaml|table]
  • seed imports a YAML file as the shared config. Sensitive values can be injected from environment variables (KICI_PLATFORM_TOKEN, KICI_SECRET_KEY, KICI_BOOTSTRAP_ADMIN_TOKEN, KICI_CLUSTER_JOIN_TOKEN).
  • get returns the effective config (merged local YAML + shared DB + env vars). Pass a dotted path to get a single field.
  • validate --offline works without a running orchestrator (validates against local schemas).
  • diff compares local YAML config vs shared DB config.
  • reload triggers a hot reload across the cluster. Use --drain to drain in-flight work first.
  • init generates a commented orchestrator.yaml template.

For full configuration details, see Configuration management.

Terminal window
kici-admin db migrate # Run pending migrations (HTTP — orchestrator must be up)
kici-admin db migrate --database-url <url> # Run pending migrations directly (orchestrator need not run)
kici-admin db migrate --status # Show migration status without applying
kici-admin db migrate --to <migration> # Revert to a named migration (before a rollback)
# Infrastructure operations (direct DB — use --database-url or KICI_DATABASE_URL).
# These cannot go through HTTP because the target DB may not exist yet or is about to be dropped.
kici-admin db fresh --confirm [--yes] # DROP + CREATE + migrate + record content hash
kici-admin db ensure <name> [--owner <role>] [--revoke-connect-public] [--grant-connect-role <role>...] # CREATE DATABASE IF NOT EXISTS
kici-admin db create-role --user <name> --password <pw> [--createdb]
kici-admin db create-readonly-user --user <name> --password <pw>
kici-admin db check-schema [--json] # Exit 2 on migration drift
kici-admin db schema-diff [--json] # Exit 2 when the live schema differs from this release
kici-admin db collation-check [--database-url <url>] [--json] # Exit 2 on collation drift
kici-admin db reindex --confirm --reason <text> [--database-url <url>]
kici-admin db refresh-collation-version --reason <text> [--database-url <url>]
kici-admin db stats-check [--database-url <url>] [--json] # Exit 2 when a table needs ANALYZE
kici-admin db analyze --table <schema.table> [--table ...] --reason <text> [--dry-run] [--database-url <url>]
# Backup / restore (direct DB — wraps pg_dump / pg_restore).
kici-admin db backup [--output <path> | --output-dir <dir> [--keep <n>]] [--database-url <url>]
kici-admin db backup --install-timer | --uninstall-timer [--schedule <spec>] [--output-dir <dir>] [--keep <n>] [--name <name>] [--instance-dir <path>]
kici-admin db restore --input <path> [--yes] [--database-url <url>]
  • migrate goes through the orchestrator HTTP admin API (orchestrator auto-migrates on startup by default; set KICI_AUTO_MIGRATE=false to disable and run manually). migrate --database-url <url> runs the same migrator directly against the database, so the orchestrator does not need to run; it cannot be combined with --to or --status, and it records a db.migrate access-log row. migrate --to <migration> reverts every migration newer than the named one. Run it before a rollback, while the newer version still serves — only that version carries the down() functions of its own migrations.
  • ensure creates the database when it is missing. --owner sets a separate owner role, --revoke-connect-public revokes CONNECT from PUBLIC, and --grant-connect-role (repeatable) grants CONNECT to a named role. Every successful migration run records the bundled-migration content hash in _migration_content_hash — including warm runs that apply zero migrations — so check-schema reports the schema as current on a long-lived database whose migrations are already up to date.
  • fresh / ensure / create-role / create-readonly-user / check-schema / schema-diff / collation-check / reindex / refresh-collation-version / stats-check / analyze open their own pool and run SQL directly — needed for deploy / bootstrap / DR workflows.
  • fresh prompts for the target database name as a confirmation. Pass --yes to skip the prompt (scripted use).
  • create-role and create-readonly-user send the password inside a SQL statement. On a superuser connection they turn off statement logging and statement tracking for that statement. The password then does not reach the PostgreSQL server log or the pg_stat_statements view. A connection that is not a superuser cannot change those settings. The server then logs the statement when its log_statement is ddl or all.
  • check-schema compares the bundled migration manifest (names + body hash) against the live schema and the stored _migration_content_hash marker. Exit code 2 means drift — call fresh or run migrate depending on intent. Exit code 1 means the database could not be read. --json prints {"current", "kind", "reason"}; kind is one of current, missing, pending, unknown-applied, hash-missing or hash-mismatch.
  • schema-diff reads the live schema from the PostgreSQL catalog — columns, constraints, indexes, functions, triggers, sequences and extensions — and compares it with the schema this release builds from its migrations. It prints one line per difference: missing (the release expects it, the database does not have it), extra (the database has it, the release does not expect it) or changed. --json prints {"differences": [...]}. Exit code 0 means the schema matches, 2 means at least one difference, and 1 means an error. The indexes the orchestrator builds in the background after startup are not compared. An extension that the database has and the release does not use is not a difference, because your database host can install its own extensions. Use it after an upgrade, or after a restore, to find a schema that check-schema reports as current but that a manual change or an edited migration made different.
  • collation-check compares pg_database.datcollversion against the running libc collation version. Exit code 2 means the stamped and actual collation versions differ — a libc upgrade changed sort order out from under existing indexes.
  • reindex runs REINDEX DATABASE CONCURRENTLY, rebuilding every index under the current libc collation rules. Non-blocking but takes minutes and roughly 2× temporary disk. Requires --confirm and --reason.
  • refresh-collation-version runs ALTER DATABASE … REFRESH COLLATION VERSION — a metadata-only bump that clears the drift warning. Pair it with db reindex after a libc-base image rebuild so the indexes match the new collation. Requires --reason.
  • stats-check finds every table that has a GIN index and checks its row estimate, pg_class.reltuples. PostgreSQL 18.6 fixed a defect where a parallel GIN index build could write a wrong estimate, such as NaN or Infinity. Autovacuum then does not process the table again, so the value stays wrong until you run ANALYZE. The check reports a value as invalid when it is NaN, Infinity or -Infinity, when it is negative other than -1 (the “never analyzed” marker), or when it is larger than the table’s pages can hold. Exit code 0 means every value is valid, 2 means at least one table needs ANALYZE, and 1 means an error. The text output prints the db analyze command that fixes the reported tables. --json prints {"database", "clean", "tables", "needsAnalyze"}; each tables entry has table, reltuples, relpages, ginIndexes and problem (nan, infinite, negative, exceeds_page_capacity, or null when the value is valid). Run it after you upgrade PostgreSQL to a new minor version.
  • analyze runs ANALYZE on the tables you name with --table (repeat the option for more tables). Give each name with its schema, for example public.access_log. The command checks every name first. If one name is not a table, it stops with exit code 1 and analyzes nothing. --dry-run prints the statements and does not run them. Requires --reason, and records a db.analyze access-log row.
  • backup wraps pg_dump -Fc into a custom-format dump (default ./kici-orchestrator-backup-<timestamp>.dump, override with --output) and writes a sidecar <dump>.manifest.json recording the creation time, byte size, secret-key version, Postgres server version, migrations hash, cluster id, and hostname. Each run is also recorded in the backup_runs table, which is what the backup-freshness WARN threshold reads. --output-dir writes a timestamped dump into a directory and keeps the newest --keep dumps (default 7).
  • backup --install-timer installs a scheduled backup service and timer for the named orchestrator instance, then exits; --uninstall-timer removes it. See Schedule backups for the per-platform behavior of --schedule.
  • restore wraps pg_restore --clean --if-exists --no-owner and is destructive — it drops and recreates the target’s objects. It prompts before proceeding; pass --yes for scripted use. Afterwards it reports whether the dump carried encrypted secrets, because those only decrypt under the same KICI_SECRET_KEY the source orchestrator used.
  • Both require a postgresql-client on PATH whose major version is at least the server’s, and both refuse rather than produce a half-usable dump when it is older. Credentials ride in PG* environment variables, never in argv, so the DB password never appears in the world-readable /proc/<pid>/cmdline.

See Database backup and restore for the full procedure, retention guidance, and the disaster-recovery drill.

Manage orchestrator configuration

Synopsis: kici-admin config

Remove a field from the shared config

Synopsis: kici-admin config delete <path> [options]

Arguments

ArgumentRequiredVariadicDescription
pathyesno

Options

OptionDefaultDescription
--description <desc>Change description
--format <format>jsonOutput format: json|yaml|table

Compare local YAML config vs shared DB config

Synopsis: kici-admin config diff [options]

Options

OptionDefaultDescription
--format <format>tableOutput format: json|yaml|table

Export shared config (sensitive values redacted)

Synopsis: kici-admin config export [options]

Options

OptionDefaultDescription
--format <format>yamlOutput format: json|yaml

Get current effective config (merged local + shared + env)

Synopsis: kici-admin config get [path] [options]

Arguments

ArgumentRequiredVariadicDescription
pathnono

Options

OptionDefaultDescription
--format <format>jsonOutput format: json|yaml|table

Show config version history

Synopsis: kici-admin config history [options]

Options

OptionDefaultDescription
--limit <n>20Maximum versions to show
--format <format>tableOutput format: json|yaml|table

Generate a starter orchestrator.yaml with commented defaults

Synopsis: kici-admin config init [options]

Options

OptionDefaultDescription
--output <path>./orchestrator.yamlOutput file path

Trigger config reload across the cluster. The result’s scaler field reports the reload of the scaler config: applied, or rejected with its errors.

Synopsis: kici-admin config reload [options]

Options

OptionDefaultDescription
--drainDrain in-flight work before reloading
--target <instance-id>Target specific instance
--format <format>jsonOutput format: json|yaml|table

Rollback shared config to a specific version

Synopsis: kici-admin config rollback [options]

Options

OptionDefaultDescription
--to <version>Target version number
--format <format>jsonOutput format: json|yaml|table

Bulk import shared config from a YAML file

Synopsis: kici-admin config seed [options]

Options

OptionDefaultDescription
--file <path>Path to YAML config file
--description <desc>Change description
--format <format>jsonOutput format: json|yaml|table

Set a single field in the shared config

Synopsis: kici-admin config set <path> <value> [options]

Arguments

ArgumentRequiredVariadicDescription
pathyesno
valueyesno

Options

OptionDefaultDescription
--description <desc>Change description
--format <format>jsonOutput format: json|yaml|table

Validate a config file against schema

Synopsis: kici-admin config validate [options]

Options

OptionDefaultDescription
--file <path>Path to config file
--type <type>sharedSchema type: local|shared|full
--offlineValidate locally without contacting orchestrator
--format <format>jsonOutput format: json|yaml|table

Database management

Synopsis: kici-admin db

ANALYZE the named tables, refreshing their planner statistics and pg_class.reltuples. Every table must exist.

Synopsis: kici-admin db analyze [options]

Options

OptionDefaultDescription
--database-url <url>Target DB URL (else KICI_DATABASE_URL / DATABASE_URL)
--table <schema.table>Table to analyze, schema-qualified. Repeatable.
--reason <text>Reason (recorded in the access log)
--dry-runValidate the tables and print the statements without running them

Dump the orchestrator DB to a local file (pg_dump custom format)

Synopsis: kici-admin db backup [options]

Options

OptionDefaultDescription
--database-url <url>Source DB URL (else KICI_DATABASE_URL / DATABASE_URL)
--output <path>Dump output path (default ./kici-orchestrator-backup-.dump)
--output-dir <path>Write a timestamped dump into this directory, then prune it to —keep dumps
--keep <n>7Dumps to retain in —output-dir
--install-timerInstall a scheduled backup service + timer and exit
--uninstall-timerRemove the scheduled backup service + timer and exit
--schedule <spec>dailyTimer calendar spec: a systemd OnCalendar value, or daily / HH:MM
--platform <type>Force the service platform (systemd, launchd, windows, compose). Default: the platform in the install manifest
--instance-dir <path>Deploy folder of the orchestrator instance to schedule
--name <name>Orchestrator service name to schedule (no default)
--systemOperate against the system-level timer (requires root)
--user-levelOperate against the user-level timer

Compare bundled migrations vs live schema. Exit 2 on drift.

Synopsis: kici-admin db check-schema [options]

Options

OptionDefaultDescription
--database-url <url>Target DB URL (else KICI_DATABASE_URL / DATABASE_URL)
--jsonfalseEmit JSON instead of a human-readable line

Compare pg_database.datcollversion against the running libc collation version. Exit 2 on drift.

Synopsis: kici-admin db collation-check [options]

Options

OptionDefaultDescription
--database-url <url>Target DB URL (else KICI_DATABASE_URL / DATABASE_URL)
--jsonfalseEmit JSON instead of a human-readable line

Create a read-only role with SELECT on all tables + default privileges

Synopsis: kici-admin db create-readonly-user [options]

Options

OptionDefaultDescription
--database-url <url>Target DB URL (must connect as owner)
--user <name>Read-only role name
--password <password>Role password

CREATE / ALTER ROLE with LOGIN [+ CREATEDB] (idempotent)

Synopsis: kici-admin db create-role [options]

Options

OptionDefaultDescription
--database-url <url>Admin DB URL (else KICI_DATABASE_URL / DATABASE_URL)
--user <name>Role name to create or update
--password <password>Role password (raw — quote as needed)
--createdbfalseGrant CREATEDB to the new role

CREATE DATABASE IF NOT EXISTS (idempotent)

Synopsis: kici-admin db ensure <name> [options]

Arguments

ArgumentRequiredVariadicDescription
nameyesno

Options

OptionDefaultDescription
--database-url <url>Admin DB URL (else KICI_DATABASE_URL / DATABASE_URL)
--owner <role>DB owner role (default: URL user). Pass when the admin connection is privileged but the new DB should be owned by a separate non-privileged role.
--revoke-connect-publicAfter ensure, REVOKE CONNECT ON DATABASE "" FROM PUBLIC (recommended on shared clusters).
--grant-connect-role <role>After ensure (and any —revoke-connect-public), GRANT CONNECT ON DATABASE "" TO "". Repeatable.

DROP + CREATE the orchestrator DB, run migrations, record content hash

Synopsis: kici-admin db fresh [options]

Options

OptionDefaultDescription
--database-url <url>Target database URL (else KICI_DATABASE_URL / DATABASE_URL)
--confirmExplicit confirmation (destructive)
--yesSkip interactive confirmation (for scripted use)

Run pending database migrations (via the orchestrator HTTP admin API, or directly with —database-url)

Synopsis: kici-admin db migrate [options]

Options

OptionDefaultDescription
--statusShow migration status without applying
--database-url <url>Migrate this database directly instead of through the orchestrator HTTP API
--to <migration>Migrate to a named migration, reverting newer ones. Run this BEFORE a rollback, while the newer binary is still serving — it is the only one carrying their down() functions.

ALTER DATABASE REFRESH COLLATION VERSION. Metadata-only bump; pair with db reindex after a libc-base image rebuild.

Synopsis: kici-admin db refresh-collation-version [options]

Options

OptionDefaultDescription
--database-url <url>Target DB URL (else KICI_DATABASE_URL / DATABASE_URL)
--reason <text>Reason (recorded in stderr banner)

REINDEX DATABASE CONCURRENTLY . Rebuilds every index under the running libc collation rules. Non-blocking but takes minutes + ~2× temp disk.

Synopsis: kici-admin db reindex [options]

Options

OptionDefaultDescription
--database-url <url>Target DB URL (else KICI_DATABASE_URL / DATABASE_URL)
--confirmExplicit confirmation (destructive — long-running)
--reason <text>Reason (recorded in stderr banner)

Restore the orchestrator DB from a pg_dump file (DESTRUCTIVE)

Synopsis: kici-admin db restore [options]

Options

OptionDefaultDescription
--input <path>Path to a .dump file produced by db backup
--database-url <url>Target DB URL (else KICI_DATABASE_URL / DATABASE_URL)
--yesSkip interactive confirmation (for scripted use)

Compare the live schema with the schema this release expects. Exit 2 on any difference.

Synopsis: kici-admin db schema-diff [options]

Options

OptionDefaultDescription
--database-url <url>Target DB URL (else KICI_DATABASE_URL)
--jsonfalseEmit JSON instead of one line per difference

Report tables with a GIN index whose planner row estimate (pg_class.reltuples) is NaN, infinite or impossible. Exit 2 when a table needs ANALYZE.

Synopsis: kici-admin db stats-check [options]

Options

OptionDefaultDescription
--database-url <url>Target DB URL (else KICI_DATABASE_URL / DATABASE_URL)
--jsonfalseEmit JSON instead of human-readable lines