← Back to site Loading…

rescriptum

Reference

Guide

Generated on August 30, 2026

Reference

Reference

The exhaustive counterpart to the rest of the guide. Tables and contracts, one page per surface:

Looking for the narrative instead? Start at what rescriptum is.

Configuration

Configuration

Environment variables — and, optionally, a file to read them from, in either of two shapes. There is no command line to get wrong, and the variables are the whole configuration: both file formats set exactly the same things under exactly the same rules, so nothing you can write in a file means anything the environment could not.

The variables

VariableDefaultMeaning
RESCRIPTUM_CONFIGunsetRead defaults from this TOML file — see below
RESCRIPTUM_ENV_FILEunsetRead defaults from this KEY=value file — see below
RESCRIPTUM_STOREfilesfiles (a directory) or sqlite (a database)
RESCRIPTUM_ANSWERS_DIR/srv/answersDirectory of answer documents
RESCRIPTUM_DB_PATH/srv/answers.dbDatabase path, when RESCRIPTUM_STORE=sqlite
RESCRIPTUM_LISTEN_ADDR0.0.0.0:8000Listen address. :0 picks a free port, and the bound one is printed
RESCRIPTUM_WORKERSCPU countAsync runtime threads. Not a concurrency limit
RESCRIPTUM_MAX_CONNECTIONS2048In-flight connections before shedding with 503
RESCRIPTUM_TIMEOUT_SECS10Header-read timeout and whole-connection deadline
RESCRIPTUM_ANSWER_TOKENunsetBearer token the answer endpoint requires. Unset means open
RESCRIPTUM_ADMIN_ADDRunsetAdmin API listener. Unset means the admin API is off
RESCRIPTUM_ADMIN_TOKENunsetAdmin bearer token, 16+ characters. Required with RESCRIPTUM_ADMIN_ADDR
RESCRIPTUM_CAPTURE_DIRunsetRecord request bodies here. Unset means no capture
RESCRIPTUM_LOGallall, problems or off — see below
RESCRIPTUM_LOG_FILEunsetA file to append to, or stdout / stderr. Unset means stderr
RESCRIPTUM_MEDIA_DIRunsetInstaller images. Unset means no media and no media listener
RESCRIPTUM_MEDIA_ADDR0.0.0.0:8001The media listener, when there is a media directory
RESCRIPTUM_MEDIA_TIMEOUT_SECS600Whole-transfer deadline. Deliberately not the answer listener’s 10
RESCRIPTUM_MEDIA_MAX_CONNECTIONS16Concurrent transfers. Low on purpose: each holds its permit for minutes
RESCRIPTUM_PUBLIC_HOSTderivedThe host generated URLs name. A host, never a URL
RESCRIPTUM_BOOT_ALLOWunsetClient CIDRs allowed to fetch boot media. Unset means anyone who can reach the port
RESCRIPTUM_BOOT_DIRunsetLoaders and menus, handed out over TFTP. Unset means no TFTP at all
RESCRIPTUM_TFTP_ADDR0.0.0.0:69The TFTP listener, or off for none. Port 69 is privileged; see RESCRIPTUM_USER
RESCRIPTUM_TFTP_PORT_RANGEunsetThe ports transfers answer from, as first-last. A TFTP transfer leaves port 69 immediately — the server replies from a fresh port and the client acknowledges to that — so a firewall allowing only 69 drops the acknowledgement and the machine looks like it lost interest. Pin the range so it can be opened. Unset, the kernel picks
RESCRIPTUM_TFTP_BLKSIZE1468The largest TFTP block to agree to. 1468 fills a 1500-byte path exactly — 1468 payload, 4 TFTP, 8 UDP, 20 IP — so a VLAN tag or a tunnel makes the frame too big and a PXE ROM usually just stops. Lower it (1400, or 512) when a boot stalls at the first block
RESCRIPTUM_BOOT_TIMEOUT_SECS15Seconds before the menu falls through to local boot
RESCRIPTUM_BOOT_UNCLAIMEDmenuWhat a machine no answer claims gets. local hands it back to its firmware instead, which inverts what an answer file means: present is install this one rather than leave this one alone
RESCRIPTUM_INSTALLED_TOKENunsetProxmox’s [post-installation-webhook] token. Set it and POST /installed exists, dropping a machine’s install claim when it reports success. Unset, there is no endpoint
RESCRIPTUM_BOOT_LOGObuilt-inA PNG to show behind the menu
RESCRIPTUM_BOOT_TITLEbuilt-inThe menu’s title bar
RESCRIPTUM_USER / _GROUPunsetDrop to these after binding. The other order fails on deployment

/srv is where the filesystem hierarchy standard puts data served by the system, which is what an answers directory is. Both defaults live there so that a bare rescriptum does something plausible on any Linux host. Nothing creates the directory for you, and the server says so at startup if it is missing.

Logging

One line per event, on stderr by default. Two knobs, because the two questions are different.

WhatRESCRIPTUM_LOG:

ValueKeeps
all (default)every request, plus startup, warnings and errors
problemsstartup, warnings, errors, and only the requests that did not succeed
off / nonenothing at all

A successful answer is one line, and at thirteen thousand requests a second that is the only thing here with any volume. problems is what you want once a rollout is routine and the disk is not. Everything else is low-volume and diagnostic, so it survives both.

A request that never reached a status at all — a connection that timed out mid-body — counts as a problem. An unrecognised value falls back to all with a warning: a typo must not be the reason nobody can see why a rollout failed. The level is named in the startup line (log=problems), so an empty log explains itself.

WhereRESCRIPTUM_LOG_FILE:

ValueGoes to
unset, or stderrstderr, which is what a supervisor reads
stdoutstdout
any other valuethat file, appended to; parent directories are created

A file that cannot be opened is a startup error, not a fall back to stderr — that would be a silent surprise discovered much later. A write that fails once the server is running is dropped instead: a provisioning server that died because its log disk filled up would fail every install in flight in order to report that it could not report something.

Rotation is yours. Under systemd there is nothing to do, since the log goes to the journal; with a file, point logrotate at it with copytruncate.

The TOML file

RESCRIPTUM_CONFIG names a file in TOML that sets the same variables in a shape meant to be read. Reach for it when a person edits the file by hand — on a NAS, in File Station or over SMB — which is exactly where RESCRIPTUM_ANSWERS_DIR=… on every line reads poorly and where the word “environment” sends people looking for a shell that is not there.

# /etc/rescriptum.toml   (chmod 600, owned by root)
answers_dir = "/srv/answers"
listen_addr = "0.0.0.0:8000"
log         = "problems"          # all | problems | off

[store]
kind    = "sqlite"
db_path = "/srv/answers.db"

[server]
workers         = 2
max_connections = 2048
timeout_secs    = 10

[admin]
addr  = "127.0.0.1:8001"
token = "…"

[answer]
token       = "…"
capture_dir = "/var/lib/rescriptum/captures"
$ RESCRIPTUM_CONFIG=/etc/rescriptum.toml rescriptum
2026-08-29T12:42:02Z - reading configuration defaults from /etc/rescriptum.toml (8 set)

Every rule the env file has, this one has too: never discovered, only named (there is no ./rescriptum.toml); the real environment wins; and a file that was asked for and cannot be read is a startup error, never a warning.

Put it outside the answers directory. Every servable .toml at the top of that directory is an answer document, and this format shares the extension — a configuration file dropped in there is reported by check as a misplaced answer, and migrate offers to move it. /etc is the obvious home; on a packaged install the package chooses one.

The names

The prefix goes away and tables do the grouping. Nothing else changes: each line below is the variable of the same name, and rescriptum config prints both spellings.

In the fileVariable
answers_dirRESCRIPTUM_ANSWERS_DIR
listen_addrRESCRIPTUM_LISTEN_ADDR
log, log_fileRESCRIPTUM_LOG, RESCRIPTUM_LOG_FILE
public_hostRESCRIPTUM_PUBLIC_HOST
user, groupRESCRIPTUM_USER, RESCRIPTUM_GROUP
store.kind, store.db_pathRESCRIPTUM_STORE, RESCRIPTUM_DB_PATH
server.workers, server.max_connections, server.timeout_secsRESCRIPTUM_WORKERS, RESCRIPTUM_MAX_CONNECTIONS, RESCRIPTUM_TIMEOUT_SECS
admin.addr, admin.tokenRESCRIPTUM_ADMIN_ADDR, RESCRIPTUM_ADMIN_TOKEN
answer.token, answer.capture_dirRESCRIPTUM_ANSWER_TOKEN, RESCRIPTUM_CAPTURE_DIR
media.dir, media.addr, media.timeout_secs, media.max_connectionsthe RESCRIPTUM_MEDIA_* four
boot.dir, boot.allow, boot.unclaimed, boot.timeout_secs, boot.logo, boot.titlethe RESCRIPTUM_BOOT_* six
tftp.addr, tftp.port_range, tftp.blksizethe RESCRIPTUM_TFTP_* three
installed.tokenRESCRIPTUM_INSTALLED_TOKEN

The format

Any TOML scalara number may be written as a number (workers = 2) or as a string; both reach the server as the same setting
A # commentanywhere, including at the end of a line — unlike the env file, which has no escapes and so cannot have inline comments
A value with a #, a quote or a spacefine, quoted the way TOML quotes things, and read back unchanged
""unset — the same rule as an exported-but-empty variable, which is what lets config unset empty a line instead of deleting the paragraph that documents it
The same key twicerefused by TOML itself, so the file does not load
A key this program does not reada warning naming it, so admin.tokenn is caught rather than ignored
A list or a table where a value belongsa startup error: unlike a misspelling it was aimed at a real setting, and serving the default while the file says otherwise would be silent
A file others can reada warning with its mode, because it may hold admin.token

Warnings name keys and paths, never values.

Both files at once

Naming both is a transition rather than a steady state, so nothing is refused and the order is stated at startup: the environment beats the TOML file, which beats the env file. rescriptum config shows which of the three put every value in force, and config set writes to the TOML file — the one the server reads first, so that a write cannot be a change that silently does nothing.

The env file

RESCRIPTUM_ENV_FILE names a file of the same variables. It exists for deployments with nowhere good to put a token — chiefly Synology DSM 7, which has no systemd. Under systemd, EnvironmentFile= already does this and you do not need it.

# /etc/rescriptum.env   (chmod 600, owned by root)
RESCRIPTUM_STORE=sqlite
RESCRIPTUM_DB_PATH=/srv/answers.db
RESCRIPTUM_ADMIN_ADDR=127.0.0.1:8001
RESCRIPTUM_ADMIN_TOKEN=
$ RESCRIPTUM_ENV_FILE=/etc/rescriptum.env rescriptum
2026-08-24T12:42:02Z - reading configuration defaults from /etc/rescriptum.env (4 set)

It is never discovered, only named. There is no ./.env. This binary runs as root: if it picked a file up from whatever directory it happened to be launched in, anyone who could write there would own RESCRIPTUM_ADMIN_TOKEN — and with it the root password of every machine installed afterwards.

The real environment wins. The file supplies defaults, so something exported deliberately at launch is never silently overridden. An exported-but-empty variable counts as unset, so the file still applies.

A file that was asked for and cannot be read is a startup error, not a warning. That is the whole point: the failure it replaces is a server coming up on its defaults — wrong answers directory, no admin token — without a word in the log.

The format

KEY=value, one per lineleading export is accepted, so the same file can also be sourced
# at the start of a linea comment
# anywhere elsepart of the value. There are no inline comments: truncating a token at a # it legitimately contains would be silent, and a comment landing in a value is loud
"quoted" or 'quoted'the quotes are stripped and inner whitespace is kept; unquoted values are trimmed
$HOME, ${x}not expanded. This is not a shell — no substitution, no continuation lines
the same key twicea startup error, rather than a guess about which was meant
a key this program does not reada warning naming the key — so RESCRIPTUM_ADMIN_TOKENN is caught rather than ignored
a file others can reada warning with its mode, because it may hold the admin token

Warnings name keys and paths, never values.

Reading and editing it

rescriptum config prints every variable, its value, and which of the files and the environment put it there — the distinction that matters, because the files supply defaults and the real environment wins. config set edits the file the way you would want it edited, in either format: comments kept, a commented-out setting uncommented in place rather than duplicated (in the env file) or the value replaced where it stands (in TOML), and a change that would leave a server unable to start refused before anything is written. It is documented in the command line reference, and it is what the DSM application drives underneath.

Invalid values

CaseWhat happens
Exported but empty (RESCRIPTUM_LISTEN_ADDR=)treated as unset — an empty value is a mistake, not an instruction
Whitespace-onlysame, and values are trimmed
A zero or unparseable numberfalls back to the default, rather than starting a server that accepts connections and never answers
RESCRIPTUM_STORE set to anything elsea warning, and files is used
RESCRIPTUM_ENV_FILE or RESCRIPTUM_CONFIG naming a missing, unreadable or malformed filea startup error
A TOML setting given a list or a tablea startup error, unlike a misspelled key, which warns
RESCRIPTUM_STORE=sqlite on a binary built without the featurea startup error

Startup errors

These stop the server rather than warning, because starting anyway would be worse:

ConditionWhy it is fatal
RESCRIPTUM_ADMIN_ADDR set with RESCRIPTUM_STORE not sqlitetwo ways to change the same configuration, racing
RESCRIPTUM_ADMIN_ADDR set with no RESCRIPTUM_ADMIN_TOKENan open API that rewrites root credentials
RESCRIPTUM_ADMIN_TOKEN under 16 charactersshort enough to guess
The listen address cannot be boundnothing to do
The store cannot be openednothing to serve
RESCRIPTUM_MEDIA_ADDR set with no RESCRIPTUM_MEDIA_DIRa listener with nothing to serve
RESCRIPTUM_MEDIA_ADDR equal to the answer or admin addressthe second bind loses, and which one depends on start order
RESCRIPTUM_PUBLIC_HOST carrying a scheme, a port or a pathit is written into URLs for two listeners; one port in the value pins every generated script to one of them
RESCRIPTUM_TFTP_ADDR set with no RESCRIPTUM_BOOT_DIRa listener with no loaders to hand out
The boot directory cannot be resolvedevery path check compares against it
RESCRIPTUM_USER names an account that does not existnothing to become

Startup warnings

These are printed and the server carries on:

ConditionLine
Answers directory missingwarning: … does not exist yet — every request will 404 until it does
Answers path exists but is not a directorywarning: … is not a directory — every request will 404 until it is
Answers directory present but unreadablewarning: … cannot be read: … — every request will 404 until that is fixed. The likeliest cause is the server running as a user that is not the directory’s owner
Admin API not on loopbackwarning: the admin API is not bound to loopback — …
RESCRIPTUM_ANSWER_TOKEN under 16 charactersa warning, not an error — refusing to start would leave a fleet unable to install
Any problem in the answer setone warning: line each, the same set check reports
RESCRIPTUM_PUBLIC_HOST unsetThe routing table’s answer, or the sole interface address when there is no default route. Logged either way, as a warning naming the other addresses when there are any. A NAT host still gets it wrong silently
TFTP cannot bindwarning: cannot bind TFTP on … the one listener whose failed bind is not fatal. Port 69 is the only privileged port in the design, so it is the only bind that can fail for something nobody configured; answers are the product, and dying would fail every install in flight to report that a second port could not be opened. boot check exits non-zero and the message names the ways to have the port
Media directory missing or unlistableone warning: media: … line — a fleet must never be unable to install because one image is odd

Compile-time options

FeatureDefaultEffect
sqliteonThe SQLite store and the admin API
bootonThe media catalogue, the ISO reader and the media listener

Measured on ARMv7 (gnueabihf, glibc floor 2.17), all four in one sitting on 2026-08-29. Re-measure rather than quoting these: they moved by about 375 KB when that target changed from musl, and the set they replace here had drifted about 200 KB out of date.

BuildBytes
both (default)2,813,712
sqlite only2,557,592
boot only1,649,048
neither1,392,544

Fixed limits

Not configurable, and deliberately so:

LimitValueWhere
Request body1 MBanswer endpoint — an aberrant Content-Length is refused from the header
Document size256 KBadmin API PUT
Captured requests1000 capturescounted from the directory at startup, so a restart does not start again
Admin failures before a block5 within 60 sblock doubles to a maximum of 900 s
Addresses tracked by the guard4096so the guard cannot be turned into a memory leak
Listing reload backstop1 sforces a re-read even when the directory mtime looks unchanged

HTTP surface

HTTP surface

Two listeners, and they never share a port. The answer endpoint is what installers talk to; the admin API is off unless configured.

The answer endpoint

RequestResponse
POST any paththe answer, content-typed by its format
GET any paththe same
GET /health200 OK, body OK\n — no token needed, never rate-limited
any other method405

Any path, because the URL is baked into an ISO and this server does not get to choose it. The path is not ignored, though: segments naming a format alias restrict which documents may answer, and the path also contributes the path, file and segment facts.

Status codes

CodeWhen
200an answer applied
400the body could not be read
401RESCRIPTUM_ANSWER_TOKEN is set and the request did not present it
404nothing claimed the request and there is no default for the format asked for
405a method other than GET or POST
413body over 1 MB, or a Content-Length claiming one
500a document would not parse, a group is missing, a template could not be filled, or the lookup panicked
503at RESCRIPTUM_MAX_CONNECTIONS — written promptly, then the connection closes

Response headers

HeaderValue
Content-Typefrom the answer’s format — see the table below
Content-Lengthalways set
Connectionclose
WWW-AuthenticateBearer, on 401
FormatContent-Type
toml, and every text format (ks, preseed, cfg, seed, ipxe)text/plain; charset=utf-8
yaml, ymltext/yaml; charset=utf-8
json, ignapplication/json
xml, autoyast, unattendapplication/xml; charset=utf-8

TOML is served as text/plain rather than application/toml because that is what the Proxmox installer expects.

Request handling limits

Body cap1 MB. An implausible Content-Length is refused from the header, before the body is read at all — rather than allocating for it and tripping a limit later
Header-read timeoutRESCRIPTUM_TIMEOUT_SECS, default 10 s
Whole-connection deadlinethe same value. Both are needed: the header timeout stops at the end of the headers, so a client that promises a body and sends nothing would otherwise park a connection indefinitely
ConcurrencyRESCRIPTUM_MAX_CONNECTIONS in flight; over that, a 503 and close rather than queueing
Authenticationonly when RESCRIPTUM_ANSWER_TOKEN is set. Compared in constant time. Failures are logged and never rate-limited

Authentication

Authorization: Bearer <RESCRIPTUM_ANSWER_TOKEN>

Proxmox sends this when its ISO was prepared with --answer-auth-token. Nothing else can, which is why it is off by default. See Security.

The admin API

A separate listener, RESCRIPTUM_ADMIN_ADDR, over SQLite only. Full details on its own page.

RequestDoes
GET /machines, GET /groupslist identifiers
GET /machines/{id}, GET /groups/{name}, GET /defaultthe stored document, as written
PUT /machines/{id}, PUT /groups/{name}, PUT /defaultstore a document
DELETE /machines/{id}, DELETE /groups/{name}, DELETE /defaultremove one
GET /resolve/{id}the merged answer that machine would receive
GET /checkcurrent problems
GET /healthliveness — no token, never blocked

All document endpoints take ?format=<ext>, defaulting to toml.

CodeWhen
200done
400malformed document, invalid identifier, or a non-UTF-8 body
401missing or wrong token
404no such document or endpoint; nothing resolves for that identifier
409the write would have broken the answer set (rolled back), or a resolve that could not render
413document over 256 KB
429this address is blocked; Retry-After says for how long
500the store could not be read or written

Every admin response sets Connection: close. Successful GET /resolve also sets X-Answer-Source, carrying the same description the log line uses.

Logging

One line per request, on stderr by default. RESCRIPTUM_LOG chooses what is kept and RESCRIPTUM_LOG_FILE chooses where it goes — see logging.

2026-08-24T08:43:37Z 127.0.0.1:61721 POST /answer body=102 200 format=toml machine=98fa9b50d810 group=example-rack bytes=431

Server-level lines carry - where the peer address would be. See troubleshooting.

Formats and endpoint aliases

Formats and endpoint aliases

Three tables. The narrative version is in one document per operating system.

Document extensions

The allowlist of extensions rescriptum will pick up from a store. Anything else is ignored — txt is deliberately not on the list, so a stray notes file next to your answers never becomes a candidate.

ExtensionFamilyLayeringContent-Type
tomlTOMLstructural mergetext/plain; charset=utf-8
yaml, ymlYAMLstructural mergetext/yaml; charset=utf-8
json, ignJSONstructural mergeapplication/json
xml, autoyast, unattendXMLstructural merge, by elementapplication/xml; charset=utf-8
ks, cfg, preseed, seed, ipxetextconcatenation in layer ordertext/plain; charset=utf-8

The family is what the log line’s format= field reports, so ks and preseed both appear as format=text. The extension is what an endpoint filters on, and what check needs in order to pick the right validator.

Endpoint aliases

A path segment naming one of these restricts the answer to documents with the listed extensions. Any segment of the path may name it, so /rhel/ks, /ks and /provision/rhel/node.cfg all restrict to kickstart.

SegmentServesTypical use
proxmox, pve, toml.tomlProxmox VE
debian, preseed.preseed, .seedDebian preseed
rhel, centos, fedora, alma, rocky, kickstart, ks.kskickstart
ubuntu, autoinstall, cloudinit, nocloud, yaml, yml.yaml, .ymlUbuntu autoinstall, cloud-init
flatcar, coreos, ignition, ign.ign, .jsonIgnition
suse, opensuse, autoyast.autoyast, .xmlAutoYaST
windows, unattend.unattend, .xmlWindows unattend.xml
json.json, .ign
xml.xml
cfg.cfg
ipxe.ipxe

A segment naming none of these constrains nothing, which is why /answer keeps working exactly as it always has.

Two traps in this table

  • Filtering is on the extension, not the family. .ks and .preseed are both text documents; filtering by family would let a preseed answer /rhel/ks.
  • seed is deliberately not an alias. s=http://server/seed/ is an ordinary NoCloud seed URL, and it serves YAML. An alias has to be specific enough that nobody reaches it by accident. (The .seed extension still exists, and /debian/ serves it.)

Control keys, per format

Stripped before the answer is sent.

FormatSpelling
TOMLtop-level extends = "base", members = […], [match] table
YAMLtop-level extends:, members:, match:
JSONtop-level "extends", "members", "match"
XML<answer-meta extends="base"><member>…</member><match k="v" /></answer-meta>
Text# answer: extends <name> · # answer: member a, b · # answer: match k=v k2=v2

Text directives also accept // as the comment marker. match takes space-separated key=pattern pairs; member a comma-separated list. Ordinary comments in a text document are served — only # answer: lines are removed.

Merge semantics

Structural formatsText formats
Maps / objects / elementsmerge recursively
Scalarshigher layer replaces
Arrays / listsreplace, never append
Whole documentconcatenated in layer order

XML pairs siblings by element name plus a discriminating attribute — name, id, key, alias, pass — and honours config:type="list". Declarations, doctypes, namespaces and attributes survive a merge; original indentation and comment placement do not.

Validators check can call

FormatToolInvoked as
tomlproxmox-auto-install-assistantvalidate-answer <file>
xml, autoyast, unattendxmllint--noout <file>
ksksvalidator<file>
everything elsenone exists

A tool that is not on PATH is reported once as a note, never as a failure.

Command line

Command line

With no arguments, rescriptum runs the server. Everything else is a subcommand.

CommandDoes
rescriptumrun the server
rescriptum render <id>print the answer that identifier would receive
rescriptum render --body FILE…for a captured request body
rescriptum render --query Q…for labels, e.g. "mac=aa:bb&serial=7ABC1"
rescriptum checkrender everything in the configured store and report what breaks
rescriptum import <dir>copy a directory of documents into the configured store
rescriptum export <dir>write the configured store out as a directory of documents
rescriptum migrate [<dir>]show what a flat answers directory would become
rescriptum migrate --applymove those documents into a directory each
rescriptum configshow the configuration, and where each value comes from
rescriptum config --jsonthe same, for a settings panel
rescriptum config --value KEYone value, for a script — never a credential
rescriptum config set K=V …edit the file RESCRIPTUM_CONFIG or RESCRIPTUM_ENV_FILE names
rescriptum config unset KEY …take a setting back out of it
rescriptum --helpusage and the environment variables

All of them read the same environment variables, including RESCRIPTUM_CONFIG and RESCRIPTUM_ENV_FILE — which are resolved first, so a file that cannot be read stops any command that needs configuration. --help and --version are answered before it is read, because they are what you reach for when something is wrong. There are no global flags.

render

$ rescriptum render 98:fa:9b:50:d8:10
$ rescriptum render --query "serial=7ABC123&mac=98:fa:9b:50:d8:10"
$ rescriptum render --query "path=/rhel/ks&serial=7ABC123"
$ rescriptum render --body /var/log/rescriptum-captures/2026…-0000.body
FormFacts it supplies
<id>the identifier as a haystack, and nothing else — enough to match by name, not enough for a selector on serial
--query "k=v&k2=v2"those labels, percent-decoded. path= also yields file and segment, and constrains the format the way a real URL would
--body FILEthe file verbatim: haystack, plus flattened JSON if it parses as JSON
  • The document goes to stdout; the # format=… machine=… group=… line explaining how it was reached goes to stderr. So render … > answer.toml gives you just the document.
  • Load-time problems are printed as warning: lines first.
  • Exit 0 when something resolved, 1 when nothing applied (the server would have returned 404) or rendering failed.

check

$ rescriptum check

Reports load-time problems, renders every machine document and every group member, names the groups that select on a match block (which it cannot try without a real request), and calls the installer’s own validator where one is on PATH.

Exit 0 when everything renders, 1 when anything failed — so it drops into CI as is. See validating.

import / export

$ RESCRIPTUM_STORE=sqlite RESCRIPTUM_DB_PATH=/srv/answers.db rescriptum import /srv/answers
$ RESCRIPTUM_STORE=sqlite RESCRIPTUM_DB_PATH=/srv/answers.db rescriptum export /tmp/backup

import reads a directory and writes into the configured store; export does the reverse. The round trip is byte-identical, paths included. Neither runs check for you — the output says to.

migrate

Answers used to be files at the top of the answers directory — 98fa9b50d810.toml beside 98fa9b50d810.preseed. They are now a directory each, and a document left flat is reported and not served. This moves them:

$ rescriptum migrate
migrating /srv/answers
  98fa9b50d810.toml -> 98fa9b50d810/proxmox.toml
  98fa9b50d810.ipxe -> 98fa9b50d810/boot.ipxe
  groups/rack-a.toml -> groups/rack-a/proxmox.toml
  default.toml -> default/proxmox.toml
  4 document(s) to move — nothing has been changed. Re-run with --apply.

It shows by default and moves only when told to. The answers directory is what a rack installs from; typing the command to find out what it would do must not rearrange it.

--apply performs the moves, each a rename within the same directory, so no document is ever rewritten. If any destination is already taken, nothing moves at all — including the documents that could have — and the collisions are named: a half-migrated directory is the state nobody can reason about. It takes a directory as an argument, defaulting to RESCRIPTUM_ANSWERS_DIR, and running it on an already-migrated directory says there is nothing to move.

config

The configuration is environment variables, and on a deployment that reads them from a file — a packaged install, mainly — this is how to see and change them without opening an editor. It is also what the DSM application runs underneath.

$ rescriptum config
env file: /var/packages/rescriptum/etc/rescriptum.env

  RESCRIPTUM_STORE            files                             default
  RESCRIPTUM_ANSWERS_DIR      /volume1/netboot/answers          file
  RESCRIPTUM_LISTEN_ADDR      0.0.0.0:9000                      environment
  RESCRIPTUM_ADMIN_TOKEN      (set)                             file

The third column is the point. The files supply defaults and the real environment wins, so a value marked environment cannot be changed by editing a file — and config set says so rather than letting you write something the running server will ignore. With a TOML file the column reads toml file, and naming both files prints both paths plus the order they win in.

config set writes the TOML file when both are named, because it is the one the server reads first: writing the other would be a change that silently does nothing.

A credential is never printed, by any form of this command. A token shows as (set) or (not set); --value refuses outright.

$ rescriptum config set RESCRIPTUM_LOG=problems RESCRIPTUM_CAPTURE_DIR=/srv/captures
wrote /var/packages/rescriptum/etc/rescriptum.env

Writing keeps the file as it is otherwise: comments stay, a setting is replaced where it stands, and one that is commented out is uncommented in place rather than appended below — which matters when the comment above it is the only documentation the file has. In a TOML file the same care applies to the document: the value is replaced where it stands, its trailing comment survives, and config unset empties the value rather than deleting the line, so the paragraph explaining the setting stays where it was.

Two refusals are deliberate:

  • A change that would leave a server unable to start is refused, whole, before anything is written. Turning on the admin API without a token, or with a token shorter than 16 characters, gets the reason instead of a broken next boot.
  • A misspelled variable is refused. Written, it would be read back as a stranger and warned about at the next start, by which time nobody connects the two.

Unlike every other subcommand, this one works when the configuration is too broken to start a server — a file that will not parse, a token one character short. That is the state people run it to get out of.

media

Boot media: the installer images this server holds. Every one of these needs RESCRIPTUM_MEDIA_DIR; without it they say so and exit 1. See Serving boot media.

$ rescriptum media list                    # what is held: family, architecture, version, digest
$ rescriptum media add FILE [--sha256 D]   # register one already in the directory
$ rescriptum media add URL --sha256 D      # fetch one into it, then register it
$ rescriptum media check                   # re-verify every recorded digest
$ rescriptum media ipxe ID                 # print the .ipxe answer that boots one image
$ rescriptum media prepare ID [--url URL]  # a Proxmox image with its answer URL inside
$ rescriptum media export ID FILE          # materialise a prepared entry, for a stick

media add takes a file already inside the media directory — nothing is downloaded and nothing is copied. It hashes it with progress, probes it, and writes a .media sidecar beside it. --sha256 is checked before anything is recorded: a mismatch writes nothing and exits 1.

media check’s exit status is a contract, like check’s. deploy.sh keys on it.

media ipxe prints to stdout and puts everything else on stderr, so rescriptum media ipxe pve-8.4 > groups/rack-a/boot.ipxe produces a usable answer document — which is all it is. It prints a script; it does not install one.

boot

The netboot half: TFTP’s loaders, the generated DHCP configuration, and the two scripts a machine executes. See Netbooting a machine.

$ rescriptum boot dhcp-snippet [--format F] [--one-loader]
$ rescriptum boot check        # are the loaders a snippet names actually on disk?
$ rescriptum boot bootstrap    # print the stage-two script
$ rescriptum boot menu         # print the built-in menu

--format is dnsmasq (the default), isc, kea, powershell, pfsense or mikrotik. The snippet goes to stdout and warnings to stderr, so boot dhcp-snippet > dhcpd.conf produces a file that can be included as-is.

boot check’s exit status is a contract, like check’s. What it catches is the least diagnosable failure in the chain: a snippet naming a loader that is not on disk fails silently at the ROM, with nothing on any console. It also warns when the media listener has moved off the port shipped loaders embed.

boot bootstrap and boot menu print what a machine will execute, for the same reason render prints an answer: everything a rack runs should be readable by a human first.

Exit statuses

StatusMeans
0success
1the command failed — nothing resolved, a document would not parse, the store could not be opened

config is the one with a second meaning: 0 says the configuration is one the server would start on, 1 that it is not — or that a write was refused. That makes it usable from a script the way check is.

The server itself exits 0 on SIGTERM or Ctrl-C, and 1 if it cannot bind or cannot open the store.