Command reference

Every proofroomctl command and every option of the installer, install.sh: what it does, its options and their defaults, exit codes and examples. The same text is on the server: sudo proofroomctl help [command] and man proofroomctl.

proofroomctl

proofroomctl looks after the Proofroom stacks on one server: each client’s own containers, database, files, keys, domain and licence. Run it with sudo (or as root) on Ubuntu 24.04 or Debian 12 with Docker (and its compose plugin), curl and openssl. The commands below leave sudo out.

It touches only proofroom-* containers, networks and volumes, its root folder /srv/proofroom, /etc/caddy/proofroom.d/, its manual page, and the units and scripts of the daily patch report and updates by button (/usr/local/lib/proofroom/ and proofroom-* units in /etc/systemd/system/). Other applications on the server are left alone.

One changing command runs at a time on a server: a second one stops at once and says so.

sudo proofroomctl [-s | --simulate | --dry-run] COMMAND [ARGUMENTS] [OPTIONS]
Command What it does
help List the commands, or describe one.
version Print proofroomctl’s version.
init Prepare this server for stacks.
create Start a new client stack.
admin-link Print a new one-time setup link for an admin.
update Move a stack to another release, with a backup and a rollback.
resize Change a stack’s memory limits, with a restart.
images List the Proofroom images on this server, or remove the ones no stack needs.
backup Take a verified backup now.
restore Replace a stack’s data with a backup, or start a new stack from one.
backup-decrypt Decrypt an off-server copy downloaded from the client’s bucket.
list Every stack, in one table.
status One stack in detail, and the server’s patch report.
logs The app’s logs.
restart Restart the app, after changing its settings.
licence Show the stack’s licence, or install a licence file.
usage Licensed and active users per stack.
remove Delete a stack, keeping a final export, its keys and its backups.
check-update Run by systemd: check for a newer release. Not run by hand.
apply-update Run by systemd: Update now for stack NAME. Not run by hand.
refresh-units Run by update: the release’s units. Not run by hand.

Options before the command

Option What it does
-s, --simulate, --dry-run Simulate: print exactly what the command would do, as numbered steps, and change nothing. Read-only checks still run for real (disk, memory, DNS, the licence file, versions, the server’s lock), so the plan is the one for this server. Secrets are masked. The exit code is the one the command would end with. Put it before the command.
-h, --help The list of commands; after a command (proofroomctl create –help), that command’s help.
--version proofroomctl’s version (also: proofroomctl version).

Simulate mode

proofroomctl -s COMMAND … (or –simulate, or –dry-run as in install.sh) prints what COMMAND would do on this server without doing it. Each change it would make (a file written or removed, a systemd unit, a Docker container, image or volume, the database, a Caddy site, a call to the licence server that changes something) is printed as a numbered step, in order. Values that are secrets (passwords, keys, tokens, install secrets) are shown as ****.

Checks that only read run for real: the operating system and Docker, free memory and disk, where the domain points, the licence file (checked with the release image, offline), the stack’s and the release’s versions, and whether another proofroomctl command holds the server’s lock. A check that fails ends the simulation with the same message and exit code as the command itself.

The last line says how many steps the command would take and whether it would succeed. Nothing is changed: no file under /srv/proofroom, /etc or /usr/local, no unit, no container and no database row. The step count is kept in one temporary file, removed before proofroomctl exits.

sudo proofroomctl -s create acme --domain compliance.acme.example --licence ~/lic_1.proofroom-licence.json
sudo proofroomctl --simulate update acme --version 1.14

Exit codes

Code Meaning
0 done (simulated: it would succeed)
1 failed, with a message saying why and what to do (simulated: it would fail)
2 wrong usage: an unknown command or option, or a missing value
75 apply-update only: refused because another proofroomctl command holds the server’s lock

Commands

help

sudo proofroomctl help [COMMAND]

List the commands, or describe one. Without COMMAND: every command, with one line each. With COMMAND: its usage, options with their defaults, exit codes and examples. COMMAND –help does the same. Changes nothing.

Also: -h, --help.

sudo proofroomctl help
sudo proofroomctl help resize

version

sudo proofroomctl version

Print proofroomctl’s version. Prints proofroomctl VERSION. Each release carries its own proofroomctl; a healthy update puts the release’s copy in place.

Also: --version.

sudo proofroomctl version

init

sudo proofroomctl init [--registry R] [--registry-user U] [--registry-token-file F|-] [--own-proxy] [--licence-server URL] [--update-url URL] | init --refresh

Prepare this server for stacks. Checks the server (operating system, Docker, memory), logs in to the registry when given a token, installs Caddy from its own package repository unless –own-proxy, writes /srv/proofroom/ctl.env, and installs the daily patch report (it only reads; it never changes the server), updates by button and this manual page. Run it again at any time: it changes only what is missing or different. If Caddy is already installed, init adds one import line to /etc/caddy/Caddyfile, keeps a copy from before as Caddyfile.before-proofroom, and undoes the change if Caddy refuses it.

Option Default What it does
--refresh On a server init already prepared: install or refresh the patch report, updates by button and the manual page, and nothing else. Takes no other option.
--registry R ghcr.io/proofroom-team/proofroom The image repository the release images come from.
--registry-user U proofroom The user for –registry-token-file.
--registry-token-file F Log in to the registry with the token in file F; - reads it from standard input. The token never appears on a command line.
--own-proxy Do not install or touch Caddy. For each stack, proxy its domain to http://127.0.0.1:PORT (create prints it) with a request body limit of at least 110 MB, and terminate TLS yourself.
--licence-server URL https://licence.proofroom.com Where stacks check in. “” for a server without internet access: check-ins are then off.
--update-url URL https://proofroom.com/releases/latest.json Where root reads the newest release’s version every 4 hours. “” turns the check, the dashboard notice and Update now off on this server.
sudo proofroomctl init
sudo proofroomctl -s init --own-proxy
sudo proofroomctl init --refresh

create

sudo proofroomctl create NAME --domain D --licence FILE [--version V] [--admin-email E] [--app-memory M] [--db-memory M] [--master-key-from APP_ENV]

Start a new client stack. Checks the licence file (signature and domain) with the release’s own image, generates the stack’s passwords, AUTH_SECRET and MASTER_KEY, picks a port from 41000, starts the stack, waits until it is healthy, adds the Caddy site and, with –admin-email, prints the first admin’s one-time setup link (valid 24 hours). A firm licence file first registers the site with the licence server, which gives it its own install secret. NAME is 2 to 31 lower-case letters, digits or dashes, starting with a letter. The domain needs a DNS record pointing at this server first: Caddy asks for the certificate as soon as the site is added.

Option Default What it does
--domain D The client’s domain, as named on the licence. Required.
--licence FILE The licence file from the licence server. Required.
--version V proofroomctl’s own version The release to run.
--admin-email E Create the first admin and print their one-time setup link.
--app-memory M 1g The app’s memory limit, like 1g or 768m (resize changes it later).
--db-memory M 512m The database’s memory limit, like 512m or 1g (resize changes it later).
--master-key-from APP_ENV Take MASTER_KEY from another stack’s app.env, to restore its backups here.
sudo proofroomctl -s create acme --domain compliance.acme.example --licence ~/lic_1.proofroom-licence.json
sudo proofroomctl create acme --domain compliance.acme.example --licence ~/lic_1.proofroom-licence.json --admin-email it@acme.example
sudo proofroomctl admin-link NAME --email E [--name "FULL NAME"]

Print a new one-time setup link for an admin. Creates a local admin, or recovers an existing one (a lost or expired link, a lost authenticator), and prints a new one-time setup link, valid 24 hours. Send it securely.

Option Default What it does
--email E The admin’s email. Required.
--name "FULL NAME" The person’s display name, on a new or an existing account.
sudo proofroomctl admin-link acme --email it@acme.example --name "Jo Smith"

update

sudo proofroomctl update NAME [--version V]

Move a stack to another release, with a backup and a rollback. Pulls the release, takes a backup, moves the stack to it and waits until it is healthy. If it does not come up, the stack goes back to the release it ran and the data from that backup. After a healthy update, proofroomctl puts the release’s own proofroomctl, units and manual page in place, and removes the Proofroom images no stack needs (each stack keeps the image it runs and the one before it; see images). Before anything changes, it stops when the disk is clearly short of the new image’s download (its size in the registry) and 512 MB, after removing old Proofroom images no stack uses if that makes the room; when the size can’t be read, it says so and goes on. Copy the backup and app.env off the server before relying on the update: the backup is on the same server.

Option Default What it does
--version V proofroomctl’s own version The release to move to. X.Y moves to the newest X.Y.Z.
sudo proofroomctl -s update acme --version 1.14
sudo proofroomctl update acme

resize

sudo proofroomctl resize NAME [--app-memory M] [--db-memory M]

Change a stack’s memory limits, with a restart. Sets the app’s and the database’s memory limits (and the app’s heap, three quarters of its limit) and restarts the stack’s containers on them, which takes about a minute. It refuses, changing nothing, when the extra memory asked for is more than this server has free (MemAvailable, less 256 MB kept for the system). If the stack does not come back healthy, the previous limits are put back. Give at least one of the options; the app needs at least 512m and the database at least 256m.

Option Default What it does
--app-memory M unchanged The app’s new memory limit, like 2g or 1536m.
--db-memory M unchanged The database’s new memory limit, like 1g or 768m.
sudo proofroomctl -s resize acme --app-memory 2g --db-memory 1g
sudo proofroomctl resize acme --app-memory 2g

images

sudo proofroomctl images [--clean]

List the Proofroom images on this server, or remove the ones no stack needs. Lists the Proofroom images on this server (the release repository, the pre-1.1.1 name ghcr.io/hriday/proofroom and the server’s own registry) with their sizes, and whether each is kept: every stack keeps the image it runs and the one before it, for a rollback. With –clean, removes the others one by one, by tag or by ID; an image Docker will not remove (a container still uses it) is left with a note. Never removes images of other repositories (the database’s, anything else on the server) and never prunes. update does the same after a healthy update.

Option Default What it does
--clean Remove the Proofroom images no stack needs.
sudo proofroomctl images
sudo proofroomctl -s images --clean
sudo proofroomctl images --clean

backup

sudo proofroomctl backup NAME

Take a verified backup now. Takes a backup into /srv/proofroom/stacks/NAME/backups/ (the database, the evidence files and a manifest), checks it, prints its folder and keeps the newest 14. The app also takes one every night, and can copy it to the client’s own S3-compatible bucket (Admin → Backups). A backup needs the stack’s MASTER_KEY (in app.env) to be restored: keep a copy of app.env off the server too.

sudo proofroomctl backup acme

restore

sudo proofroomctl restore NAME BACKUP [--yes] | restore SOURCE BACKUP --into NEW --domain D --licence FILE

Replace a stack’s data with a backup, or start a new stack from one. BACKUP is a name in the stack’s backups/ or a folder: a backup, or an off-server copy downloaded from the client’s bucket, which is decrypted first with the stack’s MASTER_KEY (as backup-decrypt does) into /srv/proofroom/restored/. Restoring into the same stack asks you to type its name (or –yes), takes a safety backup first, stops the app, restores and starts it again; if the restore fails, the stack keeps the data it had. With –into, a new stack NEW is created with SOURCE’s version and MASTER_KEY, and the backup is restored into it. On a firm licence, restoring another site’s backup counts as an import.

Option Default What it does
--yes Do not ask for the stack’s name.
--into NEW Start a new stack named NEW from the backup instead.
--domain D With –into: the new stack’s domain.
--licence FILE With –into: the new stack’s licence file.
sudo proofroomctl restore acme 20261021T013000Z
sudo proofroomctl restore acme /mnt/copy/20261021T013000Z --into acme2 --domain new.acme.example --licence ~/lic_2.proofroom-licence.json
sudo proofroomctl restore acme /root/copy/20261029T013000Z

backup-decrypt

sudo proofroomctl backup-decrypt SOURCE --master-key-from APP_ENV [--out DIR] [--version V]

Decrypt an off-server copy downloaded from the client’s bucket. SOURCE is one backup’s folder as downloaded from the bucket (Admin → Backups uploads each nightly backup there, encrypted with a key derived from the stack’s MASTER_KEY): the .enc files and copy.json. It checks every file’s SHA-256 and the key against copy.json, decrypts them with the release image, offline, and writes DIR/BACKUP: a backup that restore takes, with the stack’s licence file (licence.proofroom-licence.json) beside it for create on a new server. Nothing is written unless the whole copy decrypts. The key comes from APP_ENV, the stack’s app.env you kept off the server; it never appears on a command line.

Option Default What it does
--master-key-from APP_ENV The app.env holding the stack’s MASTER_KEY. Required.
--out DIR /srv/proofroom/restored Where the decrypted backup’s folder goes.
--version V proofroomctl’s own version The release image that decrypts it (1.14.2 or later).
sudo proofroomctl backup-decrypt /root/copy/20261029T013000Z --master-key-from /root/acme-app.env
sudo proofroomctl restore acme /srv/proofroom/restored/20261029T013000Z

list

sudo proofroomctl list

Every stack, in one table. For each stack: version, domain, port, health, licence state and last backup. Changes nothing.

sudo proofroomctl list

status

sudo proofroomctl status [NAME]

One stack in detail, and the server’s patch report. Image, address, health, last backup, licence and containers of stack NAME, then this server’s patch report with each warning and the command that fixes it. Without NAME, only the patch report. Changes nothing.

sudo proofroomctl status acme
sudo proofroomctl status

logs

sudo proofroomctl logs NAME [--since D] [--follow]

The app’s logs. The last 200 lines of the app’s log. Changes nothing.

Option Default What it does
--since D Only lines since D: a time, or a duration like 1h or 30m.
--follow, -f Keep printing new lines until you press Ctrl-C.
sudo proofroomctl logs acme --since 1h
sudo proofroomctl logs acme -f

restart

sudo proofroomctl restart NAME

Restart the app, after changing its settings. Starts a new app container, so it reads /srv/proofroom/stacks/NAME/app.env again (single sign-on, email and the other settings), and waits until it is healthy.

sudo proofroomctl restart acme

licence

sudo proofroomctl licence NAME [FILE]

Show the stack’s licence, or install a licence file. Without FILE: the licence’s state, users and expiry. With FILE: checks it with the stack’s image, installs it and restarts the app. For renewals by hand, stacks without internet access, and moving a site onto a firm licence.

sudo proofroomctl licence acme
sudo proofroomctl licence acme ~/lic_1.proofroom-licence.json

usage

sudo proofroomctl usage [--csv]

Licensed and active users per stack. For each stack: its domain, licence, licensed users, active users and licence state. Changes nothing.

Option Default What it does
--csv Comma-separated values with a header line, for a spreadsheet.
sudo proofroomctl usage --csv > usage.csv

remove

sudo proofroomctl remove NAME --confirm NAME [--no-export]

Delete a stack, keeping a final export, its keys and its backups. Takes a final export, then copies it, .env, app.env and the backups into /srv/proofroom/removed/NAME-TIME/, and deletes the stack’s containers, volumes, folder and Caddy site. On a firm licence, release the site first (Admin → Licence) or its users keep counting.

Option Default What it does
--confirm NAME The stack’s name again. Required.
--no-export Remove without the final export (for a stack that never started).
sudo proofroomctl remove acme --confirm acme

check-update

sudo proofroomctl check-update

Run by systemd: check for a newer release. Run every 4 hours by proofroom-update-check.timer: one GET of the latest-release file (nothing is sent), recorded in /srv/proofroom/host/update.json for every stack’s Admin → Updates. Also finishes the status of an update a reboot interrupted. Always exits 0.

sudo proofroomctl -s check-update

apply-update

sudo proofroomctl apply-update NAME [--clear]

Run by systemd: Update now for stack NAME. Run by proofroom-update@NAME.service when an admin presses Update now: takes the request, refuses (changing nothing) unless the latest release is newer than the stack and can be reached from it, and otherwise runs update with each phase in the stack’s update status.

Option Default What it does
--clear Only take a request and finish the status of a run that stopped (the unit’s stop step).

Exit code 75: refused: another proofroomctl command holds the server’s lock.

sudo proofroomctl -s apply-update acme

refresh-units

sudo proofroomctl refresh-units IMAGE

Run by update: the release’s units. Run by update with the release image it has just moved a stack to: installs or refreshes the patch report, updates by button and the manual page as that release writes them, like init –refresh.

sudo proofroomctl -s refresh-units ghcr.io/proofroom-team/proofroom:1.14.2

Files

Path What it is
/srv/proofroom/ctl.env the server’s settings, written by init: registry, proxy, licence server, update address
/srv/proofroom/stacks/NAME/ a stack: compose.yml (from the release), .env (image, port, memory, database passwords), app.env (the app’s settings, including MASTER_KEY), licence/, backups/
/srv/proofroom/removed/ what remove keeps: the final export, the keys and the backups
/srv/proofroom/host/ the daily patch report and the latest-release check, read by every stack
/etc/caddy/proofroom.d/NAME.caddy a stack’s site in Caddy (not with init –own-proxy)
/usr/local/lib/proofroom/ root’s copy of proofroomctl and the patch report script, run by systemd
/usr/local/share/man/man8/proofroomctl.8 this manual page

install.sh

The installer: install Proofroom on this server with one command.

install.sh --code CODE [OPTIONS]
install.sh --licence FILE --email EMAIL [OPTIONS]

install.sh checks the server and stops at the first problem, saying what to do; fetches the licence with the install code (and saves it, so running it again needs no code); installs Docker if it is missing, then proofroomctl; runs proofroomctl init and create; and prints the first admin’s one-time setup link. It is safe to run twice. It sends the install code to the licence server and nothing else, and never asks for a password or a key.

Each release’s installer is also published as https://proofroom.com/install-X.Y.Z.sh with its SHA-256 in install-X.Y.Z.sh.sha256. Those files never change; https://proofroom.com/install.sh is always the newest.

Option Default What it does
--code CODE The install code from your licence email or download page. It works once.
--licence FILE A licence file you copied to this server, instead of a code.
--email EMAIL The first admin’s email (with –licence; a code already names it).
--name NAME from your company’s name The stack’s name.
--yes Do not ask before installing (needed when there is no terminal, as under cloud-init).
--dry-run Check the server and print the commands; nothing is changed and the code is not used.
--skip-dns-check For a server behind a proxy or NAT: the HTTPS certificate then fails unless that proxy sends the domain’s traffic here.
--help, -h The options.
Exit code Meaning
0 installed, or the stack was already there and is healthy (dry run: the checks passed)
1 a check or a step failed; the message says what to do
2 wrong usage
curl -fsSL https://proofroom.com/install.sh | sudo bash -s -- --code XXXX-XXXX-XXXX
sudo bash install-X.Y.Z.sh --licence /root/compliance.acme.example.proofroom-licence.json --email it@acme.example --dry-run