Skip to content

CLI surface

The contract for how iiirisd is invoked: what flags exist, what they do, what they print, and what they exit with. This is the surface 1.0 freezes — after 1.0, removing or renaming a flag is a major bump (releases.md "Versioning").

Operator how-to lives in configuration.md "Command-line flags" and deployment.md.

Invocation

iiirisd [flags]

One binary, no subcommands, stdlib flag parsing. Flags may be written -flag or --flag; -flag=value and -flag value are both accepted, per stdlib.

Flags

Flag Effect
(none) Serve with built-in defaults + IIIRIS_* env overrides. The zero-config path.
-config <path> Serve, overlaying the YAML file at path between defaults and env vars. A missing file is an error.
-version Print build identity, exit.
-check-config Load + validate the configuration, print the verdict, exit.
-print-config Print the effective configuration as YAML, exit.
-h / -help stdlib usage listing, exit 0.

-version, -check-config and -print-config are one-shot: they report and exit without binding a listener, building a subsystem, initialising libvips, or entering the Windows service dispatcher. -check-config and -print-config honour -config; -version ignores it. When more than one is given, the first of -version, -check-config, -print-config wins — defined, not an error.

Exit codes

Code Meaning
0 Success. Graceful shutdown for a serving run; the requested output for a one-shot flag.
1 Runtime failure — listener failed, or a subsystem that must work (source, store) failed to build.
2 Configuration failure — unreadable file, unparseable YAML, or a value Validate rejects. Also unparseable flags (stdlib's own exit code).

Operators script against these; they are part of the contract.

Output

  • -version — one line to stdout: iiirisd <version> (<goos>/<goarch>, <go version>, libvips <version>). The version string is the -ldflags build stamp, dev in an unstamped build. libvips is reported because it is a runtime dependency resolved differently in every shipping form (distroless, debian-slim, Windows DLL bundle, local build), and is otherwise not answerable from outside the process.
  • -check-config — on success, one line to stdout naming what was checked; on failure, config: <error> to stderr and nothing to stdout.
  • -print-config — YAML to stdout, one document, no leading ---. Errors go to stderr with an empty stdout.

Contracts

  • Flags describe the binary, never the configuration. There is no -addr, no -fs-root, no flag mirroring a YAML field. Configuration has three layers with defined precedence (defaults → YAML → IIIRIS_*) and an admin editor; a fourth layer would fork that contract. A new flag is justified only when it changes what the process does, not what the server is.
  • Zero-config is untouched. iiirisd with no flags, no file, and no env vars serves the bundled testdata/ on :8080. No flag is required for any code path.
  • -check-config runs exactly what startup runs, and no more. Both call the same load-and-validate path in cmd/iiirisd (config.LoadConfig.Validate), so anything the preflight rejects would have stopped a start with the same message and exit code. The converse does not hold: it does not build subsystems, so cache-backend, auth-wiring and hook errors — today raised inside cache.Build* / auth.Build / hook.Build rather than in Validate — still surface only at startup (also exit 2). Widening Validate to cover the pure-config half of those checks would tighten the preflight without giving it side effects; it has not been done.
  • -print-config never prints a secret. Output passes through config.Config.Redacted(), which masks sentry.dsn, metrics.password, content_state.store.write_token, hook.webhook.auth_header, every redis.password, and the inline auth.profiles.*.users hashes, replacing each non-empty value with ***. An empty secret stays empty, so a dump never implies a credential exists when none does. A new secret-bearing config field must be added to Redacted(); the enumeration in internal/config/redact_test.go is the guard.
  • -print-config does not validate. A config that parses but wouldn't start still prints — that is precisely when reading the effective values matters. -check-config is the flag that judges.
  • A -print-config dump is diagnostic, not a config file. Feeding it back in would install *** as a literal secret. It is for reading, pasting into an issue, and diffing between hosts.
  • Windows service mode is unaffected. The one-shot flags are handled before service-context detection, and the Service Control Manager passes none of them.

Out of scope

  • Subcommands (iiirisd serve, iiirisd config check). A subcommand grammar is a bigger contract to freeze at 1.0 than this binary needs, and it would break every existing invocation and service definition. Rejected in favour of flags.
  • A third-party CLI framework. stdlib flag covers a surface this small; a dependency here buys help-text polish at the cost of another vendored contract.
  • Flags for config fields. See the first contract above.
  • A -check-config that builds subsystems. Connecting to Redis, creating filesystem cache directories and compiling hook scripts would make the preflight authoritative — and give it side effects on the host it is meant to be inspecting. The cheaper half of that win (moving pure-config checks into Validate) stays available.
  • -print-config output as an input format. Round-tripping a redacted dump is not supported and not intended.
  • Machine-readable -version (--version=json). Nothing consumes it yet; the one-line form is greppable. Additive later if a need appears.