summaryrefslogtreecommitdiff
path: root/man
diff options
context:
space:
mode:
authorDawid Rycerz <dawid@rycerz.xyz>2026-01-22 22:07:32 +0100
committerDawid Rycerz <dawid@rycerz.xyz>2026-02-10 18:44:26 +0100
commit064a1d01c5c14f5ecc032fa9b8346a4a88b893f6 (patch)
treea2023f9ccd297ed8a41a3a0cc5699c2add09244d /man
witryna 0.1.0 — initial releasev0.1.0
Minimalist Git-based static site deployment orchestrator. Webhook-triggered builds in Podman/Docker containers with atomic symlink publishing, SIGHUP hot-reload, and zero-downtime deploys. See README.md for usage, CHANGELOG.md for details.
Diffstat (limited to 'man')
-rw-r--r--man/witryna.toml.5490
1 files changed, 490 insertions, 0 deletions
diff --git a/man/witryna.toml.5 b/man/witryna.toml.5
new file mode 100644
index 0000000..29c0331
--- /dev/null
+++ b/man/witryna.toml.5
@@ -0,0 +1,490 @@
+.TH WITRYNA.TOML 5 "2026-02-10" "witryna 0.1.0" "Witryna Configuration"
+.SH NAME
+witryna.toml \- configuration file for \fBwitryna\fR(1)
+.SH DESCRIPTION
+\fBwitryna.toml\fR is a TOML file that configures the \fBwitryna\fR static site
+deployment orchestrator.
+It defines the HTTP listen address, container runtime, directory layout,
+logging, rate limiting, and zero or more site definitions with optional build
+overrides, polling intervals, cache volumes, and post\-deploy hooks.
+.PP
+The file is read at startup and can be reloaded at runtime by sending
+\fBSIGHUP\fR to the process (see \fBHOT RELOAD\fR below).
+.SH GLOBAL OPTIONS
+.TP
+\fBlisten_address\fR = "\fIhost:port\fR" (required)
+Socket address the HTTP server binds to.
+Must be a valid \fIip:port\fR pair (e.g., "127.0.0.1:8080").
+.TP
+\fBcontainer_runtime\fR = "\fIname\fR" (required)
+Container runtime executable used to run build commands.
+Typically "podman" or "docker".
+Must not be empty or whitespace\-only.
+.TP
+\fBbase_dir\fR = "\fI/path\fR" (required)
+Root directory for clones, builds, and cache.
+Default layout:
+.RS
+.nf
+<base_dir>/clones/<site>/
+<base_dir>/builds/<site>/<timestamp>/
+<base_dir>/builds/<site>/current -> <timestamp>
+<base_dir>/cache/<site>/
+.fi
+.RE
+.TP
+\fBlog_dir\fR = "\fI/path\fR" (optional, default: "/var/log/witryna")
+Directory for per\-build log files.
+Layout: \fI<log_dir>/<site>/<timestamp>.log\fR
+.TP
+\fBlog_level\fR = "\fIlevel\fR" (required)
+Tracing verbosity.
+Valid values: "trace", "debug", "info", "warn", "error" (case\-insensitive).
+Can be overridden at runtime with the \fBRUST_LOG\fR environment variable.
+.TP
+\fBrate_limit_per_minute\fR = \fIn\fR (optional, default: 10)
+Maximum webhook requests per token per minute.
+Exceeding this limit returns HTTP 429.
+.PP
+\fBNote:\fR The server enforces a hard 1\ MB request body size limit.
+This is not configurable and applies to all endpoints.
+.TP
+\fBmax_builds_to_keep\fR = \fIn\fR (optional, default: 5)
+Number of timestamped build directories to retain per site.
+Older builds and their corresponding log files are removed after each
+successful publish.
+Set to 0 to disable cleanup (keep all builds).
+.TP
+\fBgit_timeout\fR = "\fIduration\fR" (optional, default: "1m")
+Maximum time allowed for each git operation (clone, fetch, reset, submodule update).
+Accepts a \fBhumantime\fR duration string (e.g., "30s", "2m", "5m").
+.RS
+Minimum is 5 seconds, maximum is 1 hour.
+Applies globally to all sites.
+Repositories with many or large submodules may need a longer timeout (e.g., "5m").
+.RE
+.SH SITE DEFINITIONS
+Sites are defined as TOML array\-of\-tables entries under \fB[[sites]]\fR.
+Each site represents a Git repository that witryna can build and publish.
+Site names must be unique.
+The list may be empty (\fBsites = []\fR); witryna will start but serve
+only the health\-check endpoint.
+.TP
+\fBname\fR = "\fIsite\-name\fR" (required)
+Unique identifier for the site.
+Used in the webhook URL path (\fIPOST /<name>\fR) and directory names.
+.RS
+Validation rules:
+.IP \(bu 2
+Alphanumeric characters, hyphens, and underscores only.
+.IP \(bu 2
+Cannot start or end with a hyphen or underscore.
+.IP \(bu 2
+Cannot contain consecutive hyphens or consecutive underscores.
+.IP \(bu 2
+No path traversal characters (\fI..\fR, \fI/\fR, \fI\\\fR).
+.RE
+.TP
+\fBrepo_url\fR = "\fIurl\fR" (required)
+Git repository URL to clone.
+Any URL that \fBgit clone\fR accepts (HTTPS, SSH, local path).
+.TP
+\fBbranch\fR = "\fIref\fR" (required)
+Git branch to track.
+This branch is checked out after clone and fetched on each build trigger.
+.TP
+\fBwebhook_token\fR = "\fItoken\fR" (optional)
+Bearer token for webhook endpoint authentication.
+If omitted or set to an empty string, webhook authentication is disabled for
+this site \(em all POST requests to the site endpoint will be accepted without
+token validation.
+Use this when the endpoint is protected by other means (reverse proxy, VPN,
+firewall).
+A warning is logged at startup for sites without authentication.
+.PP
+When set, the token is validated using constant\-time comparison to prevent
+timing attacks.
+Sent as: \fIAuthorization: Bearer <token>\fR.
+.RS
+The token can be provided in three ways:
+.IP \(bu 2
+\fBLiteral value:\fR \fBwebhook_token = "my\-secret"\fR
+.IP \(bu 2
+\fBEnvironment variable:\fR \fBwebhook_token = "${VAR_NAME}"\fR \- resolved from
+the process environment at config load time.
+The variable name must consist of ASCII uppercase letters, digits, and underscores,
+and must start with a letter or underscore.
+Only full\-value substitution is supported; partial interpolation
+(e.g., "prefix\-${VAR}") is treated as a literal token.
+.IP \(bu 2
+\fBFile:\fR Use \fBwebhook_token_file\fR (see below).
+.PP
+The \fB${VAR}\fR syntax and \fBwebhook_token_file\fR are mutually exclusive.
+If the referenced environment variable is not set or the file cannot be read,
+config loading fails with an error.
+.RE
+.TP
+\fBwebhook_token_file\fR = "\fI/path/to/file\fR" (optional)
+Path to a file containing the webhook token.
+The file contents are read and trimmed of leading/trailing whitespace.
+Compatible with Docker secrets (\fI/run/secrets/\fR) and Kubernetes secret volumes.
+.RS
+When set, \fBwebhook_token\fR should be omitted (it defaults to empty).
+Cannot be combined with the \fB${VAR}\fR substitution syntax.
+.PP
+\fBSecurity note:\fR Ensure the token file has restrictive permissions
+(e.g., 0400 or 0600) and is readable only by the witryna user.
+.RE
+.SH REPOSITORY CONFIG FILE
+.TP
+\fBconfig_file\fR = "\fIpath\fR" (optional)
+Path to a custom build config file in the repository, relative to the repo root
+(e.g., ".witryna.yaml", "build/config.yml").
+Must be a relative path with no path traversal (\fI..\fR).
+.PP
+If not set, witryna searches the repository root in order:
+\fI.witryna.yaml\fR, \fI.witryna.yml\fR, \fIwitryna.yaml\fR, \fIwitryna.yml\fR.
+The first file found is used.
+.SH BUILD OVERRIDES
+Build parameters can optionally be specified directly in the site definition,
+overriding values from the repository's build config file
+(\fI.witryna.yaml\fR / \fIwitryna.yaml\fR).
+When all three fields (\fBimage\fR, \fBcommand\fR, \fBpublic\fR) are set,
+the repository config file becomes optional.
+.TP
+\fBimage\fR = "\fIcontainer:tag\fR" (optional)
+Container image to use for the build (e.g., "node:20\-alpine").
+Required unless provided in \fIwitryna.yaml\fR.
+Must not be blank.
+.TP
+\fBcommand\fR = "\fIshell command\fR" (optional)
+Build command executed via \fBsh \-c\fR inside the container.
+Must not be blank.
+.TP
+\fBpublic\fR = "\fIrelative/path\fR" (optional)
+Directory containing built static assets, relative to the repository root.
+Must be a relative path with no path traversal (\fI..\fR).
+.SH RESOURCE LIMITS
+Optional resource limits for container builds.
+These flags are passed directly to the container runtime.
+.TP
+\fBcontainer_memory\fR = "\fIsize\fR" (optional)
+Memory limit for the build container (e.g., "512m", "2g", "1024k").
+Must be a number followed by a unit suffix: \fBk\fR, \fBm\fR, or \fBg\fR
+(case\-insensitive).
+Passed as \fB\-\-memory\fR to the container runtime.
+.TP
+\fBcontainer_cpus\fR = \fIn\fR (optional)
+CPU limit for the build container (e.g., 0.5, 2.0).
+Must be greater than 0.
+Passed as \fB\-\-cpus\fR to the container runtime.
+.TP
+\fBcontainer_pids_limit\fR = \fIn\fR (optional)
+Maximum number of PIDs inside the build container (e.g., 100).
+Must be greater than 0.
+Passed as \fB\-\-pids\-limit\fR to the container runtime.
+Helps prevent fork bombs during builds.
+.SH NETWORK ISOLATION
+.TP
+\fBcontainer_network\fR = "\fImode\fR" (optional, default: "bridge")
+Network mode for the build container.
+Passed as \fB\-\-network=\fImode\fR to the container runtime.
+.RS
+Allowed values:
+.IP \(bu 2
+\fB"bridge"\fR (default) \- Standard container networking (NAT).
+Works out of the box for builds that download dependencies (e.g., \fBnpm install\fR).
+.IP \(bu 2
+\fB"none"\fR \- No network access.
+Most secure option; use for builds that don't need to download anything.
+.IP \(bu 2
+\fB"host"\fR \- Use the host network namespace directly.
+.IP \(bu 2
+\fB"slirp4netns"\fR \- User\-mode networking (Podman rootless).
+.PP
+Set \fBcontainer_network = "none"\fR for maximum isolation when your build
+does not require network access.
+.RE
+.SH CONTAINER WORKING DIRECTORY
+.TP
+\fBcontainer_workdir\fR = "\fIpath\fR" (optional, default: repo root)
+Working directory inside the build container, relative to the repository root.
+Useful for monorepo projects where the build runs from a subdirectory.
+.PP
+The value is a relative path (e.g., "packages/frontend") appended to the
+default \fB/workspace\fR mount point, resulting in
+\fB\-\-workdir /workspace/packages/frontend\fR.
+.PP
+Must be a relative path with no path traversal (\fI..\fR) and no leading slash.
+.SH POLLING
+.TP
+\fBpoll_interval\fR = "\fIduration\fR" (optional, default: disabled)
+If set, witryna periodically fetches the remote branch and triggers a build
+when new commits are detected.
+Accepts a \fBhumantime\fR duration string (e.g., "30m", "1h", "2h30m").
+.RS
+Minimum interval is 1 minute.
+Polling respects the concurrent build lock; if a build is already in progress,
+the poll cycle is skipped.
+Initial poll delays are staggered across sites to avoid a thundering herd.
+.RE
+.SH GIT CLONE DEPTH
+.TP
+\fBgit_depth\fR = \fIn\fR (optional, default: 1)
+Git clone depth for the repository.
+.RS
+.IP \(bu 2
+\fB1\fR (default) \- Shallow clone with only the latest commit.
+Fast and minimal disk usage.
+.IP \(bu 2
+\fB0\fR \- Full clone with complete history.
+Required for \fBgit describe\fR or monorepo change detection.
+.IP \(bu 2
+\fBn > 1\fR \- Shallow clone with \fIn\fR commits of history.
+.RE
+.PP
+Submodules are detected and initialized automatically regardless of clone depth.
+.SH BUILD TIMEOUT
+.TP
+\fBbuild_timeout\fR = "\fIduration\fR" (optional, default: "10m")
+Maximum time a build command is allowed to run before being killed.
+Accepts a \fBhumantime\fR duration string (e.g., "5m", "30m", "1h").
+.RS
+Minimum is 10 seconds, maximum is 24 hours.
+.RE
+.SH CACHE VOLUMES
+.TP
+\fBcache_dirs\fR = ["\fI/container/path\fR", ...] (optional)
+List of absolute container paths to persist as cache volumes across builds.
+Each path gets a dedicated host directory under \fI<base_dir>/cache/<site>/\fR.
+.RS
+Validation rules:
+.IP \(bu 2
+Each entry must be an absolute path.
+.IP \(bu 2
+No path traversal (\fI..\fR) allowed.
+.IP \(bu 2
+No duplicates after normalization.
+.IP \(bu 2
+Maximum 20 entries per site.
+.PP
+Common cache paths:
+.TS
+l l.
+Node.js /root/.npm
+Python pip /root/.cache/pip
+Go modules /root/.cache/go
+Rust cargo /usr/local/cargo/registry
+Maven /root/.m2/repository
+.TE
+.PP
+Cache directories are \fBnever cleaned automatically\fR by witryna.
+Administrators should monitor disk usage under
+\fI<base_dir>/cache/\fR and prune manually when needed.
+.RE
+.SH ENVIRONMENT VARIABLES
+.TP
+\fB[sites.env]\fR (optional)
+TOML table of environment variables passed to builds and post\-deploy hooks.
+Each key\-value pair becomes a \fB\-\-env KEY=VALUE\fR flag for the container
+runtime.
+Variables are also passed to post\-deploy hooks.
+.RS
+Validation rules:
+.IP \(bu 2
+Keys must not be empty.
+.IP \(bu 2
+Keys must not contain \fB=\fR.
+.IP \(bu 2
+Neither keys nor values may contain null bytes.
+.IP \(bu 2
+Keys starting with \fBWITRYNA_\fR (case\-insensitive) are reserved and rejected.
+.IP \(bu 2
+Maximum 64 entries per site.
+.PP
+In post\-deploy hooks, user\-defined variables are set \fBbefore\fR the reserved
+variables (PATH, HOME, LANG, WITRYNA_*), so they cannot override system or
+Witryna\-internal values.
+.RE
+.SH POST-DEPLOY HOOKS
+.TP
+\fBpost_deploy\fR = ["\fIcmd\fR", "\fIarg\fR", ...] (optional)
+Command to execute after a successful symlink switch.
+Uses array form (no shell interpolation) for safety.
+.RS
+The hook receives context exclusively via environment variables:
+.TP
+\fBWITRYNA_SITE\fR
+The site name.
+.TP
+\fBWITRYNA_BUILD_DIR\fR
+Absolute path to the new build directory (also the working directory).
+.TP
+\fBWITRYNA_PUBLIC_DIR\fR
+Absolute path to the stable current symlink
+(e.g. /var/lib/witryna/builds/my\-site/current).
+Use this as the web server document root.
+.TP
+\fBWITRYNA_BUILD_TIMESTAMP\fR
+Build timestamp (YYYYmmdd-HHMMSS-ffffff).
+.PP
+The hook runs with a minimal environment: any user\-defined variables
+from \fB[sites.env]\fR, followed by PATH, HOME, LANG,
+and the WITRYNA_* variables above (which take precedence).
+Its working directory is the build output directory.
+It is subject to a 30\-second timeout and killed if exceeded.
+Output is streamed to disk and logged to
+\fI<log_dir>/<site>/<timestamp>\-hook.log\fR.
+.PP
+Hook failure is \fBnon\-fatal\fR: the deployment is already live,
+and a warning is logged.
+The exit code is recorded in the hook log.
+A log file is written for every hook invocation (success, failure,
+timeout, or spawn error).
+.PP
+Validation rules:
+.IP \(bu 2
+The array must not be empty.
+.IP \(bu 2
+The first element (executable) must not be empty or whitespace\-only.
+.IP \(bu 2
+No element may contain null bytes.
+.IP \(bu 2
+Maximum 64 elements.
+.RE
+.SH HOT RELOAD
+Sending \fBSIGHUP\fR to the witryna process causes it to re\-read
+\fIwitryna.toml\fR.
+Sites can be added, removed, or reconfigured without downtime.
+Polling tasks are stopped and restarted to reflect the new configuration.
+.PP
+The following fields are \fBnot reloadable\fR and require a full restart:
+.IP \(bu 2
+\fBlisten_address\fR
+.IP \(bu 2
+\fBbase_dir\fR
+.IP \(bu 2
+\fBlog_dir\fR
+.IP \(bu 2
+\fBlog_level\fR
+.PP
+If any of these fields differ after reload, a warning is logged but the new
+values are ignored until the process is restarted.
+.PP
+If the reloaded configuration is invalid, the existing configuration remains
+active and an error is logged.
+.SH EXAMPLES
+A complete annotated configuration:
+.PP
+.nf
+.RS 4
+# Network
+listen_address = "127.0.0.1:8080"
+
+# Container runtime ("podman" or "docker")
+container_runtime = "podman"
+
+# Data directory (clones, builds, cache)
+base_dir = "/var/lib/witryna"
+
+# Log directory (per\-build logs)
+log_dir = "/var/log/witryna"
+
+# Tracing verbosity
+log_level = "info"
+
+# Webhook rate limit (per token, per minute)
+rate_limit_per_minute = 10
+
+# Keep the 5 most recent builds per site
+max_builds_to_keep = 5
+
+# Git operation timeout (clone, fetch, reset)
+# git_timeout = "2m"
+
+# A site that relies on witryna.yaml in the repo
+[[sites]]
+name = "my\-blog"
+repo_url = "https://github.com/user/my\-blog.git"
+branch = "main"
+webhook_token = "s3cret\-tok3n"
+# Or from environment: webhook_token = "${MY_BLOG_TOKEN}"
+# Or from file: webhook_token_file = "/run/secrets/blog\-token"
+poll_interval = "1h"
+
+# A site with full build overrides (no witryna.yaml needed)
+# Custom build timeout (default: 10 minutes)
+[[sites]]
+name = "docs\-site"
+repo_url = "https://github.com/user/docs.git"
+branch = "main"
+webhook_token = "an0ther\-t0ken"
+image = "node:20\-alpine"
+command = "npm ci && npm run build"
+public = "dist"
+build_timeout = "30m"
+cache_dirs = ["/root/.npm"]
+post_deploy = ["curl", "\-sf", "https://status.example.com/deployed"]
+
+# Environment variables for builds and hooks
+[sites.env]
+DEPLOY_TOKEN = "abc123"
+NODE_ENV = "production"
+.fi
+.RE
+.SH SECURITY CONSIDERATIONS
+.TP
+\fBContainer isolation\fR
+Build commands run inside ephemeral containers with \fB\-\-cap\-drop=ALL\fR.
+When the runtime is Podman, \fB\-\-userns=keep\-id\fR is added so the
+container user maps to the host UID, avoiding permission issues without
+any extra capabilities.
+When the runtime is Docker, \fBDAC_OVERRIDE\fR is re\-added because
+Docker runs as root (UID\ 0) inside the container while the workspace is
+owned by the host UID; without this capability, the build cannot read or
+write files.
+The container filesystem is isolated; the build has no direct access to
+the host beyond the mounted workspace directory.
+.SH SYSTEMD OVERRIDES
+The deb and rpm packages install example systemd override templates to
+\fI/usr/share/doc/witryna/examples/systemd/\fR.
+The post\-install script copies the appropriate template to
+\fI/etc/systemd/system/witryna.service.d/10\-runtime.conf\fR based on the
+detected container runtime.
+.TP
+\fBdocker.conf\fR
+.nf
+.RS 4
+[Service]
+SupplementaryGroups=docker
+ReadWritePaths=/var/run/docker.sock
+.fi
+.RE
+.TP
+\fBpodman.conf\fR
+.nf
+.RS 4
+[Service]
+RestrictNamespaces=no
+Environment="XDG_RUNTIME_DIR=/run/user/%U"
+.fi
+.RE
+.PP
+\fB%U\fR resolves to the numeric UID of the service user at runtime.
+To add custom overrides, create a separate file (e.g.,
+\fI20\-custom.conf\fR) in the same directory; do not edit
+\fI10\-runtime.conf\fR as it will be overwritten on package reinstall.
+.SH FILES
+When no \fB\-\-config\fR flag is given, \fBwitryna\fR searches for the
+configuration file in the following order:
+.IP 1. 4
+\fI./witryna.toml\fR (current working directory)
+.IP 2. 4
+\fI$XDG_CONFIG_HOME/witryna/witryna.toml\fR (default: \fI~/.config/witryna/witryna.toml\fR)
+.IP 3. 4
+\fI/etc/witryna/witryna.toml\fR
+.PP
+The first file found is used.
+If none exists, an error is printed listing all searched paths.
+.SH SEE ALSO
+\fBwitryna\fR(1)