komizo

command module
v0.0.42 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 26, 2026 License: MIT Imports: 4 Imported by: 0

README

komizo

The komizo command: set up a server, add apps to it, and watch what they are doing.

The komizo service is decommissioned. Board decision: komizo-be is gone, and this CLI is the whole product. You manage your servers purely from here, over SSH, with nothing to sign in to — which is how the CLI always worked between service calls. The three commands that talked to the service now refuse, plainly and without touching the network: komizo login, and komizo enrol without --token (komizo init simply no longer files the box anywhere). komizo enrol --token and komizo enrol --remove stay — the exchange happens on the box, so they work against a service you run yourself; there is no default --api any more, because a default that points at a dead domain is a silent network call to nothing.

The supported deployment path is the app-scoped deploy-APP Compose operation. The journaled rollout/gateway experiment published in v0.0.30 through v0.0.39 was abandoned and is superseded by v0.0.40 and later; those experimental versions remain available only as historical artifacts.

One command, nothing to install first:

go run github.com/nicodes/komizo@latest init --host root@your-server

komizo carries the server agent inside itself, so that setting up a box needs nothing from the box but sshd. Those agents are build artifacts and are not in the module the Go proxy serves — so a go run build compiles one on demand, from the same module at the same version it is itself. Nothing new is fetched that was not already fetched to get here, and a warm module cache reaches the network not at all.

Or take a release binary. It carries the agents already, so it needs no Go toolchain. It connects to your server as root, so verify it before you run it:

gh release download v0.0.17 --repo nicodes/komizo \
  -p 'komizo_Linux_x86_64.tar.gz' -p checksums.txt
sha256sum -c checksums.txt --ignore-missing
gh attestation verify komizo_Linux_x86_64.tar.gz --repo nicodes/komizo
tar xzf komizo_Linux_x86_64.tar.gz && sudo install komizo /usr/local/bin/

komizo init --host root@your-server

From a checkout, make build compiles the agents first.

Every operation is a command that takes the server as a flag; komizo on its own prints the list. Watching a box — its apps, its charts, its logs — is komizo list, komizo report and komizo logs.

What it is

komizo deploys to your own server from GitHub Actions. This repository is both halves of the tool: the CLI that runs on your machine, and komizo-box, the small agent it installs on the server.

  • komizo-be — decommissioned; the docs, and how the whole thing fitted together (historical)
  • komizo-actions — the GitHub Actions a deploying repository uses

What it does to a server

Three commands, each safe to re-run:

komizo init   --host root@box      # Docker, the shared network, the agent
komizo update --host root@box      # re-run all of it, every app included
komizo proxy  --host root@box      # one Caddy, terminating TLS for every app
komizo add    --host root@box ...  # a deploy account and its two privileged commands

komizo add --scoped-env fields-postgres-v2 is a separate opt-in, and only for --app fieldsofrevik. fields-postgres-v1 is withdrawn: a recorded v1 profile or a ten-key generation is not reported ready and is not started. The profile installs a root-only provision-scoped-env-fieldsofrevik (mode 0700, not in doas) and a status-only scoped-env-status-fieldsofrevik. The deploy account may run status. It may not run provision, and set-secret is not granted for this profile. Provision takes --compose-file, a root-owned regular file that is the postgres compose candidate. It does not read or replace the live compose.yml. The candidate must map each service to its own env file, mount pg_data on postgres, and name a volume that is absent or empty. A placeholder or PocketBase compose is refused, and the PocketBase volume is left in place. Provision reads four Clerk values from a root-owned mode-0600 file (--clerk-file) or the terminal. The file must contain each fixed Clerk key once. Terminal entry turns echo off for all four reads, including CLERK_SECRET_KEY, and restores the previous terminal settings on success, refusal, and signal. Neither path accepts the secret as an argument or prints a value. The file is removed after a successful provision; a refused run leaves it in place, and the operator deletes it without printing it. Provision generates the four database passwords and WS_SECRET on the host, derives the two postgres URLs, and writes CLERK_SECRET_KEY only to api.env. That key must be a non-empty sk_live_ value within the short env-file charset and length; sk_test_ and a value already set in the environment are refused. It writes four mode-0600 files and switches secrets/current once. A second run refuses. It does not restart containers or delete a PocketBase volume. Deploy of this profile takes a fourth argument, the expected generation id, and checks the generation under the app lock before changing config. That check, status, and start read a root-only mode-0400 provenance marker written only by this provision (profile=fields-postgres-v2, schema=11, and the generation id). They do not open the env files. A recorded v1 profile, or a generation without that marker, is not ready. Other apps still take one or three arguments and reject a fourth. komizo remove deletes the commands. KEEP_DATA=1 leaves the app directory, including secrets/, and does not read those files. Clerk values that contain space, ", #, $, ', backslash, or backtick are refused: Fields compose still uses the short env_file form and does not set format: raw.

Provisioning is shell, piped down the connection that is already open, run once and thrown away. It is the half that CHANGES a machine, and it runs as root exactly as long as it takes.

The local web app

komizo ui, run ON the box as root, serves the local web app: a SolidJS + Tailwind app (the RN-port screens from the archived reference remain the design reference), embedded in the binary as a static export (ui/ is the source, built with make ui; internal/ui/dist is the committed export CI byte-verifies). It shows this server — status, problems, system facts and usage, apps and their services, routes, proxy and network, and the events the box was told (the daemon's command results). The only actions are start, stop and restart for an app, enforced server-side in the CLI: the allowlist is the boundary, not the page.

There is no sign-in. The listener is the boundary: it binds loopback by default, and --bind widens it to the tailnet interface address, where the network is the identity. It never listens on every interface by accident — 0.0.0.0 only arrives when typed, and it says so when it does.

The data is the box's own files, read with the same box package the daemon reads them with. The daemon's unix socket is NOT used: every route on it requires a read token signed by the decommissioned registry and an envelope signed by a planted device key, and extending the daemon is out of bounds — the files are the store, so the UI reads them directly. v1 defers two things, for the same reason: backups visibility and run-backup. No box-local backup state exists and there is no clean box-local trigger, so the screen shows the absence honestly rather than inventing a shape nothing writes.

Reading is not. The inventory, the request counts and the cgroup reads come from komizo-box — a 2.6MB Go binary that init installs and runs on a timer as root, writing /run/komizo/report.json and nothing else.

That split is the whole design. Root writes a file; something with no privileges at all reads it. Everything komizo grows next — a dashboard, a phone, alerts — reads that same file, and none of it needs a way in. See design/architecture.md.

The trade is real and worth stating: the old shell arrived fresh on every poll, so a newer komizo read new things off an untouched box. An agent has to be updated to learn anything new, and komizo report says when one is behind.

Rebuilding a box

A fresh machine is brought back in a fixed order, and every step is safe to re-run:

komizo init      --host root@box                          # Docker, network, agent
komizo proxy     --host root@box                          # the shared Caddy
komizo add       --host root@box --app NAME --config REF  # once per app
komizo reconcile --host root@box --inventory expected-apps.json

The order matters: add needs the network and agent that init installs, and a deploy needs the proxy route. reconcile is last and is the proof the rebuild worked — after the reachability preflight every komizo command runs, it fetches the box's report exactly once and compares every registered app and route against the inventory, exiting nonzero on any missing, unexpected, duplicate or mismatched entry. The box is only ever read: reconcile provisions nothing, deploys nothing and rotates no key, so run it as often as you like, as the operator (root) login. Locally, connecting can create or tighten ~/.ssh to 0700 (for the SSH control socket), and --accept-host-key against a box never seen before appends its host key to ~/.ssh/known_hosts (trust-on-first-use) — those are the only local files it can ever touch (SSH is run with UpdateHostKeys=no, so the box cannot quietly add keys to known_hosts either). The inventory must be a regular file — on unix a symlink as the final component is refused, and a FIFO or device is rejected on the opened descriptor; on Windows a link is followed. That refusal covers the link itself, not the directory around it: someone who can write the inventory's parent directory can rename a different file over the path outright, and no open flag prevents that. Keep the inventory and its parent directories owned and writable only by the operator, like every other file a check's answer depends on. It holds only app names, pinned config-image references and public hostnames (wildcards like *.api.example.com included, matched exactly). The loader enforces the boundary mechanically where it can: unknown fields are rejected (a member named for a secret, token or key cannot ride along), repeated object members are rejected, values must fit the app/config/route syntax, and literal PEM (-----BEGIN) material is rejected. It cannot judge meaning — a token-shaped string that fits the syntax is accepted — so keeping the values non-sensitive stays the operator's job.

The deploy-key and known-hosts handoff

Two values per app have to reach its repository before CI can deploy, and a rebuild changes exactly one of them:

  • KOMIZO_DEPLOY_KEY (secret) — the app's deploy key. komizo add generates a fresh pair; on a rebuild where the old key is still in the repo and still intended, komizo add --keep-key regenerates everything else and leaves the account's authorized key alone. The private half is printed once, held in memory and written nowhere unless --key PATH says so.
  • KOMIZO_KNOWN_HOSTS (variable) — the box's host keys against the names this app's CI dials. A rebuilt box has NEW host keys, so this value always changes on a rebuild. komizo report --host root@box --known-hosts prints each app's value without touching the box — reading it costs no rotation.

Both go to the app's repository under Settings → Secrets and variables → Actions. Values are never written down here, in the inventory, or in any log komizo keeps.

Four things live on a server: komizo-box and its OpenRC service, the two per-app scripts, and the shared proxy. komizo update renews all of them -- including the per-app scripts, which are regenerated from the record komizo already holds for each app, so no deploy key is rotated, no setting is changed and an app somebody deliberately stopped stays stopped.

Layout

main.go            the CLI
cmd/komizo-box/    the agent, which runs on the server
internal/app/      the subcommands, and everything they do to a server
box/               the probes and the report -- shared by both binaries AND the service
internal/agent/    the compiled agents, embedded into the CLI
scripts/           the provisioning shell, embedded with go:embed

box/ is imported by three programs that are not upgraded together: the agent on a server writes a report, a CLI on a laptop reads one, and the komizo service receives them from every box it knows about — possibly months apart, each on a different version. It is public rather than internal/ for that third reader, which is a different module. The schema rule is in report.go: add fields, never repurpose one.

scripts/ is the half that runs as root on somebody else's machine, so it is tested by being executed against a fake box rather than by being read. See internal/app/deploy_script_test.go.

Building

make            # the agents, then the CLI
make check      # what CI runs

make, not a bare go build: the agents are embedded into the CLI, and //go:embed reads the filesystem at build time rather than invoking a compiler, so they have to exist first. A CLI built without them works for everything except installing one, and says so.

MIT licensed.

Documentation

Overview

Command komizo sets up and inspects servers that deploy from GitHub Actions.

Directories

Path Synopsis
Package box is what runs ON a server: the probes that describe a machine, and the report they produce.
Package box is what runs ON a server: the probes that describe a machine, and the report they produce.
cmd
komizo-box command
Command komizo-box is what komizo installs on a server.
Command komizo-box is what komizo installs on a server.
internal
agent
Package agent carries the compiled komizo-box binaries, so that installing one is something `komizo init` can do over a plain SSH connection.
Package agent carries the compiled komizo-box binaries, so that installing one is something `komizo init` can do over a plain SSH connection.
app
Package app is komizo: the commands, and everything they do to a server.
Package app is komizo: the commands, and everything they do to a server.
ui
Package ui carries the local web app into the binary.
Package ui carries the local web app into the binary.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL