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

274 lines
10 KiB
Markdown

# 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](../man/volumen.1), and the page moves
in the same commit as the flags it documents.
## Synopsis
```sh
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`
```text
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`
```text
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`
```text
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`
```text
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`
```text
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`
```text
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`
```text
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`
```text
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`
```text
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:
```sh
volumen serve --config ./config.toml --content ./posts
```
Start a fresh per-user installation and open the wizard:
```sh
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:
```sh
volumen publish-due --dry-run
volumen publish-due
volumen validate
```
Back up a deployment and restore it into a second one:
```sh
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:
```sh
volumen doctor --config /etc/volumen/config.toml
volumen status --config /etc/volumen/config.toml --json
```