Files
petrbalvin f8ed33df83
Test / test (push) Successful in 7m5s
Release / gates (push) Successful in 7m28s
Release / build (amd64, freebsd) (push) Successful in 2m52s
Release / build (amd64, linux) (push) Successful in 2m46s
Release / build (arm64, freebsd) (push) Successful in 2m22s
Release / build (arm64, linux) (push) Successful in 2m38s
Release / build (loong64, linux) (push) Successful in 2m7s
Release / build (riscv64, linux) (push) Successful in 2m17s
Release / release (push) Successful in 1m0s
Initial commit
Assisted-by: GLM 5.3
2026-09-29 10:03:32 +02:00

10 KiB

Command line

The reference below is taken from the program's own --help. If the two disagree, the program is right and this file is a defect. The same reference ships as a manual page, man/volumen.1, and the page moves in the same commit as the flags it documents.

Synopsis

volumen <command> [options]

Commands

Command Purpose
serve Start the publishing server
status Show installation status
doctor Check installation health
check-update Compare with the latest Gitea release
export Export posts, media, and users to a tar.gz
import Import a backup archive
publish-due Publish scheduled posts whose date arrived
validate Validate the content directory
version Show version

volumen serve

volumen serve [--config <path>] [--content <dir>] [--host <addr>] [--port <n>]
Flag Default Meaning
--config /etc/volumen/config.toml, then ~/.config/volumen/config.toml Path to config.toml
--content from config Override the posts directory
--host from config (::) Override the bind address
--port from config (9091) Override the port

A --port outside 1..65535 is rejected rather than ignored, and a positional argument is an error.

Starts the HTTP server: the public API under /api/volumen, the admin under /admin, uploaded media under /media, plus /healthz, /robots.txt, /sitemap.xml and /favicon.ico. The configuration is validated at startup; invalid values abort with exit code 1, before the socket is bound.

With no --config the server reads /etc/volumen/config.toml if it exists, then ~/.config/volumen/config.toml, and when neither exists it runs on the built-in defaults. Those defaults put the state under the user's home (~/.local/share/volumen, honouring XDG_DATA_HOME), so a plain volumen serve on a fresh machine works without root and without any configuration file. On the first start the account file is empty and /admin shows the first-run wizard: it creates the administrator account, the interface language and the colour scheme, and signs the operator in. The server keeps the session secret it generates in secret.key beside the account file; [admin].session_key overrides it.

The server sets a 10 second read-header timeout, a 60 second read timeout, a 120 second write timeout and a 120 second idle timeout, and bounds a request line and its headers at 1 MiB. SIGINT and SIGTERM drain in-flight requests for up to 15 seconds and then exit 0.

When [scheduler].enabled = true, an internal goroutine publishes the due posts once at start-up and then every [scheduler].interval seconds. A background release check fills the admin update banner without blocking requests. Logging goes to stderr, either as text or as JSON lines when [server].log_format = "json"; the server's own protocol errors go there too, through log/slog, rather than to a bare stderr line. Every request carries a 16-character id: it is answered in X-Request-Id, it is attached to every line the handlers write for that request, and the request's own access line records the method, the path, the status and the duration under it.

volumen status

volumen status [--config <path>] [--data <dir>] [--users-file <path>] [--json]

Reports whether the config exists, parses and validates, the number of posts (with the draft count), and the number of users; with no accounts yet the user count carries the hint open /admin to run the setup wizard. --json prints {"ok": true, "checks": {…}}, with a config_validate entry naming the first invalid value, a posts_unreadable entry when a post file cannot be parsed, and users: "unreadable" when the accounts file cannot be read. Exit code 1 when the config is missing, unparseable or invalid, when content cannot be parsed, or when the users file cannot be read.

Read-only: it inspects the content directory without creating or changing anything, so a mistyped content_dir is reported rather than turned into an empty tree.

volumen doctor

volumen doctor [--config <path>] [--json]

Runs the health checks: config presence and validation, whether every post file can be parsed, whether every post renders, whether the users file can be read, whether the installation has its first account, and whether any stored scrypt parameters are below the policy floor. The checks carry two levels: a missing or broken config, an unreadable content directory and an unreadable users file are fail and exit code 1; posts that fail to render, an installation still waiting for the first-run wizard and weak stored hashes are warn, reported without changing the exit code. --json prints {"ok": bool, "checks": [{"name", "status", "detail"}]}.

volumen check-update

volumen check-update [--json]

Compares the running version with the latest release published on the Gitea instance (https://sourcedock.dev/petrbalvin/volumen/releases).

Exit code Meaning
0 Up to date (or the local build is newer)
1 A newer release is available, or the release check failed (offline, timeout, malformed response)
2 The arguments were wrong

--json prints {"current": …, "latest": …, "available": bool} and keeps the same exit-code contract, so it works as a CI or monitoring probe.

volumen export

volumen export [--config <path>] [--out <archive>]

Writes a .tar.gz containing posts/ (including .revisions/ and media/), users.toml, templates.toml and tokens.toml. Default output: volumen-backup.tar.gz, written mode 0600.

The config file is deliberately not included: it may hold the session signing key override, and volumen import ignores it anyway. A file that exists but cannot be read aborts the export rather than producing an archive that looks complete: the archive is written to a temporary file and renamed into place, so a failure leaves the previous backup untouched. Exit code 1 on I/O errors.

volumen import

volumen import [--config <path>] <archive>

Restores an archive produced by volumen export or the admin Backup panel into the configured locations. An archive entry named users.toml, templates.toml or tokens.toml is written to the path the deployment configures for that file, which may be a different name; the post files under posts/ are written into the content directory.

Extraction is confined to the content directory, so an entry that tries to escape it is refused, and only post files, revision archives and images with an allowed extension are written: the media directory is served from a public route, so an archive can never plant a document there. The decompressed total is bounded. Exit code 1 on a malformed archive or one that holds no file the layout recognises.

volumen publish-due

volumen publish-due [--config <path>] [--dry-run] [--json]

Publishes every post whose publish_at date is today or earlier: removes the publish_at key and defaults date to it when the post has no date. This is the cron and systemd-timer counterpart to the in-app [scheduler].

  • --dry-run lists the due slugs without touching files.
  • --json prints {"published": […], "failed": n} (or {"due": […], "dry_run": true}).
  • The configured webhooks receive post.published for every post this publishes, the same event the admin delivers.
  • Exit code 1 when a post could not be written, so a timer notices.

volumen validate

volumen validate [--config <path>] [--json]

Scans the content directory and reports: files that cannot be parsed, posts that fail to render, duplicate slugs, slug values that do not match the slug format, missing titles, a publish_at that is not a date (which withholds the post), and alias conflicts (an alias used twice or shadowing a live slug). Prints content OK when clean; exit code 1 with a problem list otherwise. --json prints {"problems": [{"slug", "path", "error"}]}, where path names the file when there is no slug to name.

A file that cannot be parsed is invisible to every other command, which is why it is named here first.

volumen version

volumen version

Prints volumen <version>: the release the toolchain recorded in the binary's build information. A release binary reports its tag (v1.0.0); a build from a plain checkout reports a pseudo-version naming the commit; a build outside version control reports (devel), and a dirty tree appends +dirty. Nothing injects the version, so the reported value cannot go stale.

Global flags

Flag Effect
-h, --help, help prints the usage block and exits 0
-v, --version prints volumen <version> and exits 0
-h on a subcommand prints that subcommand's flags and exits 0; version prints the version instead

An unknown command and an unknown flag exit 2. Every subcommand rejects a stray positional argument with exit code 2; import is the one command whose positional argument (<archive>) is required.

Exit codes

Code Meaning
0 success
1 a failure the program detected: an unreadable config, a failed write, a content directory with a problem, a release check that could not run
2 the arguments were wrong

check-update is the exception that proves the rule: it exits 1 both when a newer release exists and when the release check failed, so a monitoring probe reads its --json output rather than its status alone.

Examples

Serve a checkout against its own content, with the scheduler on:

volumen serve --config ./config.toml --content ./posts

Start a fresh per-user installation and open the wizard:

volumen serve

The server comes up on http://localhost:9091; /admin shows the first-run wizard, which creates the administrator account and signs the operator in. The state lives under ~/.local/share/volumen, and the session secret in secret.key beside it.

Publish what a timer missed, then check the content directory:

volumen publish-due --dry-run
volumen publish-due
volumen validate

Back up a deployment and restore it into a second one:

volumen export --config /etc/volumen/config.toml --out /backup/volumen.tar.gz
volumen import --config /srv/staging/config.toml /backup/volumen.tar.gz

Find out why a fresh install will not start:

volumen doctor --config /etc/volumen/config.toml
volumen status --config /etc/volumen/config.toml --json