.\" Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) .\" SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 .TH VOLUMEN 1 "2026-09-27" "volumen 1.0.0" "Volumen Manual" .SH NAME volumen \- lightweight publishing platform for scientists .SH SYNOPSIS .B volumen .RI [ command "] [" options ] .SH DESCRIPTION Volumen is a lightweight publishing platform for scientists: a headless JSON API plus a server-rendered admin, over posts that are files. Every post is a Markdown file with a TOML frontmatter block inside a content directory. The engine serves that directory as a JSON API under .BR /api/volumen , serves the admin under .BR /admin , and stores uploaded media under .BR /media . It has no database and no runtime dependency besides the binary itself; a public site is a separate front end that consumes the API. .PP Run .B volumen with a command named below. Each command accepts .B \-h for its own usage. An unknown command and an unknown flag exit with status 2, and every command rejects a stray positional argument. .SH COMMANDS .TP .B serve Start the publishing server. With no accounts yet, the first visit to .B /admin shows the first-run wizard, which creates the administrator account. .TP .B status Show installation status. .TP .B doctor Check installation health. .TP .B check-update Compare with the latest Gitea release. .TP .B export Export posts, media, and users to a tar.gz. .TP .B import Import a backup archive. .TP .B publish-due Publish scheduled posts whose date arrived. .TP .B validate Validate the content directory. .TP .B version Show version. .SH OPTIONS .SS serve .TP .BI "\-\-config" " path" Path to .IR config.toml . Without the flag the server reads .I /etc/volumen/config.toml if it exists, otherwise .IR ~/.config/volumen/config.toml ; when neither exists it runs on the built-in defaults, whose state paths lie under .I ~/.local/share/volumen (honouring .IR XDG_DATA_HOME ). .TP .BI "\-\-content" " dir" Override .IR content_dir . .TP .BI "\-\-host" " addr" Override .BR [server].host , an IP address such as .B :: or .BR ::1 . .TP .BI "\-\-port" " n" Override .BR [server].port , 1 to 65535. A value outside the range is rejected. .PP The server bounds every connection: a 10 second read-header timeout, a 60 second read timeout, a 120 second write timeout and a 120 second idle timeout, with request lines and headers capped at 1 MiB. .B SIGINT and .B SIGTERM drain in-flight requests for up to 15 seconds and then exit 0. When .B [scheduler].enabled is set, the due posts are published at start-up and then every .B [scheduler].interval seconds. Logging goes to standard error, as text or as JSON lines when .B [server].log_format is .BR json . Every request carries a 16-character id, answered in .B X-Request-Id and attached to the request's access line. .PP A fresh installation needs no bootstrap command: start the server, open .BR /admin , and the first-run wizard creates the administrator account, the interface language and the colour scheme, and signs the operator in. The wizard is reachable until that first account exists; on a machine reachable from the network, start the service and claim the installation right away. .SS status .TP .BI "\-\-config" " path" Config path to inspect (default as under .BR serve ). .TP .BI "\-\-data" " dir" Data directory to inspect. .TP .BI "\-\-users-file" " path" Users file to inspect. .TP .B \-\-json Machine-readable output: .BR {"ok":\ true,\ "checks":\ {\ ...} } , with a .B config_validate entry naming the first invalid value, a .B posts_unreadable entry when a post file cannot be parsed, and .B "users:\ \(dqunreadable\(dq" when the accounts file cannot be read. .PP 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 names the first-run wizard as the next step. Read-only: a mistyped .I content_dir is reported, never created. Exit 1 when the config is missing, unparseable or invalid, when content cannot be parsed, or when the users file cannot be read. .SS doctor .TP .BI "\-\-config" " path" Config path to check (default as under .BR serve ). .TP .B \-\-json Machine-readable output: .BR {"ok":\ bool,\ "checks":\ [{name,\ status,\ detail}]} . .PP Checks that the config exists, parses and validates, that every post file can be parsed, that every post renders, that the users file can be read, that the installation has its first account, and that no stored scrypt parameters sit below the policy floor. A missing account is a warning pointing at the wizard, not a failure. Exit 1 when a check fails. .SS check-update .TP .B \-\-json Machine-readable output: .BR {"current":\ ...,\ "latest":\ ...,\ "available":\ bool} , keeping the same exit-code contract. .PP Compares the running version with the latest release at .IR https://sourcedock.dev/petrbalvin/volumen/releases . Exit 0 when up to date (or the local build is newer); exit 1 when a newer release exists or the release check failed (offline, timeout, malformed response); exit 2 on a usage error. .SS export .TP .BI "\-\-config" " path" Config path (default as under .BR serve ). .TP .BI "\-\-out" " archive" Output archive path (default .IR volumen-backup.tar.gz , written mode 0600). .PP Writes a tar.gz containing .B posts/ (including .B .revisions/ and .BR media/ ), .BR users.toml , .BR templates.toml , and .BR tokens.toml . The config file is deliberately not included: it may hold the session key override. A file that exists but cannot be read aborts the export rather than writing an archive that looks complete. Exit 1 on I/O errors. .SS import .TP .BI "\-\-config" " path" Config path (default as under .BR serve ). .PP Restores an archive made by .B volumen export or the admin Backup panel. The named archive is the one required positional argument. Entries are written to the paths the deployment configures for them; extraction is confined to the content directory, only post files, revision archives and allowed image extensions are written, and the decompressed total is bounded at 512 MiB. Exit 1 on a malformed archive or one that holds no file the layout recognises. .SS publish-due .TP .BI "\-\-config" " path" Config path (default as under .BR serve ). .TP .B \-\-dry-run List the due slugs without touching files. .TP .B \-\-json Machine-readable output: .BR {"published":\ [...],\ "failed":\ n} , or .B {"due":\ [...],\ "dry_run":\ true} with .BR \-\-dry-run . .PP Publishes every post whose .B publish_at date is today or earlier: the key is removed and .B date defaults to it when the post has none. The configured webhooks receive .B post.published for every post this publishes. Exit 1 when a post could not be written, so a timer notices. .SS validate .TP .BI "\-\-config" " path" Config path (default as under .BR serve ). .TP .B \-\-json Machine-readable output: .BR {"problems":\ [{slug,\ path,\ error}]} . .PP Scans the content directory and reports files that cannot be parsed, posts that fail to render, duplicate slugs, slugs that do not match the slug format, missing titles, a .B publish_at that is not a date, and alias conflicts. Prints .B content OK when clean, exit 1 otherwise. .SS version Prints .BI "volumen " version , the release the toolchain recorded in the binary's build information. A release binary reports its tag, a checkout reports a pseudo-version, a build outside version control reports .BR "(devel)" , and a dirty tree appends .BR +dirty . .SS Global flags .TP .BR \-h , " \-\-help" ", " help Prints the usage block and exits 0. .B \-h on a subcommand prints that subcommand's flags; on .B version it prints the version. .TP .BR \-v , " \-\-version" Prints .BI "volumen " version and exits 0. .SH EXIT STATUS .TP .B 0 Success. .TP .B 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. .B check-update also exits 1 when a newer release simply exists. .TP .B 2 The arguments were wrong: an unknown command, an unknown flag, or a stray positional argument. .SH CONFIGURATION One TOML file, \fIconfig.toml\fP, copied from the commented .I config.toml.example shipped with the release. Every key, its type, its default and its validation rules are documented in .I docs/CONFIGURATION.md of the repository. Sources, strongest first: the flags of .BR serve , then the file, then the built-in defaults. A key with the wrong type or a root key swallowed by a table header stops the start with a message naming the key; an unknown key is ignored. .PP The five state files .BR users.toml , .BR secret.key , .BR templates.toml , .BR tokens.toml and .B webhooks.toml live beside the users file, are written atomically at mode 0600 by the server, and are not read from the configuration file. .B secret.key holds the session secret the server generated on first start; .B [admin].session_key overrides it. .SH ENVIRONMENT The configuration itself comes from the file and the flags of .B serve only. The XDG variables .I XDG_CONFIG_HOME and .I XDG_DATA_HOME locate the per-user config and state paths used by the defaults. .SH FILES .TP .I /etc/volumen/config.toml System configuration, tried first when .B \-\-config is absent. .TP .I ~/.config/volumen/config.toml Per-user configuration, tried when the system file does not exist. .TP .I /var/lib/volumen/posts Content directory: posts, .BR media/ , and .BR .revisions/ . .TP .I /var/lib/volumen/users.toml Admin accounts, created by the first-run wizard or the admin Settings page. .TP .IR /var/lib/volumen/secret.key , .IR /var/lib/volumen/templates.toml , .IR /var/lib/volumen/tokens.toml , .IR /var/lib/volumen/webhooks.toml Session secret generated by the server, post templates, API tokens, admin-managed webhooks. .TP .I ~/.local/share/volumen Per-user state directory: the default content and users paths when no configuration file exists. .TP .I /etc/systemd/system/volumen.service Unit written by the operator; .I docs/DEPLOYMENT.md carries the text to copy. .TP .I /media/.webp, .avif, .svg Uploaded images; WebP, AVIF and SVG are the only accepted formats, and the name is assigned by the engine. An SVG is served sandboxed, so opened at its own URL it runs as no one. .SH EXAMPLES Serve a checkout against its own content: .PP .B volumen serve \-\-config ./config.toml \-\-content ./posts .PP Start a fresh per-user installation and open the wizard in the browser at .IR http://localhost:9091/admin/ : .PP .B volumen serve .PP Publish what a timer missed, then check the content: .PP .B volumen publish-due \-\-dry-run .br .B volumen publish-due .br .B volumen validate .PP Back up one deployment and restore it into a second: .PP .B volumen export \-\-config /etc/volumen/config.toml \-\-out /backup/volumen.tar.gz .br .B volumen import \-\-config /srv/staging/config.toml /backup/volumen.tar.gz .PP Find out why a fresh install will not start: .PP .B volumen doctor \-\-config /etc/volumen/config.toml .br .B volumen status \-\-config /etc/volumen/config.toml \-\-json .SH AUTHOR Petr Balvín (https://petrbalvin.org) .SH LICENCE PolyForm Noncommercial 1.0.0, see .I LICENSE in the repository: every non-commercial use is permitted; a commercial use needs a licence from the author. The artwork in .I internal/web/static/ comes from the shared icon set and stays under Creative Commons BY-NC 4.0 alone. The licence texts of the modules the binary is compiled from are in .IR NOTICE.md . .SH "SEE ALSO" .I docs/CLI.md and .I docs/DEPLOYMENT.md in the repository; .IR docs/API.md for the JSON API; .IR https://sourcedock.dev/petrbalvin/volumen .