.TH WITRYNA.TOML 5 "2026-02-15" "witryna 0.2.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, 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 /clones// /builds/// /builds//current -> /cache// .fi .RE .TP \fBlog_dir\fR = "\fI/path\fR" (optional, default: "/var/log/witryna") Directory for per\-build log files. Layout: \fI//.log\fR .TP \fBlog_level\fR = "\fIlevel\fR" (required) Log verbosity. Valid values: "trace", "debug", "info", "warn", "error" (case\-insensitive). Can be overridden at runtime with the \fBRUST_LOG\fR environment variable. .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). See also: \fBwitryna cleanup\fR for manual pruning. .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 /\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 \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/cache//\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/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 every build completion (success or failure). 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). .TP \fBWITRYNA_BUILD_STATUS\fR Build outcome: "success" or "failed". .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//\-hook.log\fR. .PP Hook failure is \fBnon\-fatal\fR and a warning is logged. On successful builds the deployment is already live; on failed builds the hook still runs but no assets were published. 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" # 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)