# Knot configuration

> knot-server configuration keys, environment variables and defaults.

`knot-server` reads TOML from the file given with `--config` (or `KNOT_CONFIG`), then from `/etc/knot/config.toml`. Each key also has a `KNOT_*` environment variable, and environment variables take precedence over both files. Sizes are in bytes; durations are in the unit named by the key suffix (`_ms`, `_secs`).

## `[server]`

| Key                               | Env var                                | Default               | Description                                                                                                                                                               |
| --------------------------------- | -------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `hostname`                        | `KNOT_HOSTNAME`                        | required              | Public hostname. The knot’s identity is `did:web:<hostname>`.                                                                                                             |
| `admins`                          | `KNOT_ADMINS`                          | required              | Admin DIDs. The first entry is the knot’s service owner. Comma-separated as an env var.                                                                                   |
| `listen_addr`                     | `KNOT_LISTEN_ADDR`                     | `[::]:5555`           | HTTP listen address for XRPC, smart HTTP git, LFS and the homepage.                                                                                                       |
| `listen_header_timeout_ms`        | `KNOT_LISTEN_HEADER_TIMEOUT_MS`        | `10000`               | Time allowed to receive request headers.                                                                                                                                  |
| `listen_idle_timeout_ms`          | `KNOT_LISTEN_IDLE_TIMEOUT_MS`          | `60000`               | Idle connection timeout. Must be at least `listen_header_timeout_ms`.                                                                                                     |
| `listen_max_connections`          | `KNOT_LISTEN_MAX_CONNECTIONS`          | `1024`                | Maximum concurrent HTTP connections.                                                                                                                                      |
| `listen_rate_limit_per_second`    | `KNOT_LISTEN_RATE_LIMIT_PER_SECOND`    | `50`                  | HTTP request rate limit per client IP.                                                                                                                                    |
| `listen_rate_limit_burst`         | `KNOT_LISTEN_RATE_LIMIT_BURST`         | `200`                 | Burst size for the per-client-IP request rate limit.                                                                                                                      |
| `listen_max_inflight_requests`    | `KNOT_LISTEN_MAX_INFLIGHT_REQUESTS`    | `1024`                | Maximum HTTP requests in flight. Excess requests get `503`.                                                                                                               |
| `listen_request_timeout_ms`       | `KNOT_LISTEN_REQUEST_TIMEOUT_MS`       | `60000`               | Timeout for an HTTP request, except streaming writes.                                                                                                                     |
| `listen_body_timeout_ms`          | `KNOT_LISTEN_BODY_TIMEOUT_MS`          | `30000`               | Inactivity timeout while reading a request body.                                                                                                                          |
| `listen_write_request_timeout_ms` | `KNOT_LISTEN_WRITE_REQUEST_TIMEOUT_MS` | `1800000`             | Timeout for streaming writes: `git-receive-pack` POSTs and LFS object PUTs.                                                                                               |
| `internal_listen_addr`            | `KNOT_INTERNAL_LISTEN_ADDR`            | `[::1]:5444`          | Listen address of the mTLS admin listener. Used only when `tls.mtls_enabled` is set.                                                                                      |
| `ssh_listen_addr`                 | `KNOT_SSH_LISTEN_ADDR`                 | `[::]:2222`           | Listen address of the built-in SSH server for git and LFS.                                                                                                                |
| `ssh_host_key_file`               | `KNOT_SSH_HOST_KEY_FILE`               | required              | SSH host key path. Absolute. Generated on start if absent.                                                                                                                |
| `ssh_max_pack_bytes`              | `KNOT_SSH_MAX_PACK_BYTES`              | `8589934592`          | Largest pack accepted in one SSH push.                                                                                                                                    |
| `ssh_proxy_protocol_from`         | `KNOT_SSH_PROXY_PROTOCOL_FROM`         | `[]`                  | IPs or CIDR blocks allowed to send a PROXY protocol v1 header on SSH connections. SSH rate limits then key on the header’s source address. Comma-separated as an env var. |
| `appview_endpoint`                | `KNOT_APPVIEW_ENDPOINT`                | `https://tangled.org` | Base URL of the appview, used to build links in push messages.                                                                                                            |

## `[tls]`

| Key                   | Env var                        | Default | Description                                                                                     |
| --------------------- | ------------------------------ | ------- | ----------------------------------------------------------------------------------------------- |
| `cert_path`           | `KNOT_TLS_CERT_PATH`           | unset   | PEM certificate chain for TLS on the knot. Absolute. Set together with `key_path`.              |
| `key_path`            | `KNOT_TLS_KEY_PATH`            | unset   | PEM private key for `cert_path`. Absolute.                                                      |
| `http3`               | `KNOT_TLS_HTTP3`               | `true`  | Serve HTTP/3 over QUIC when the knot has a certificate.                                         |
| `acme_enabled`        | `KNOT_TLS_ACME_ENABLED`        | `false` | Obtain the certificate over ACME (TLS-ALPN-01). Can’t combine with `cert_path`/`key_path`.      |
| `acme_cache_dir`      | `KNOT_TLS_ACME_CACHE_DIR`      | unset   | ACME account and certificate cache directory. Absolute. Required with `acme_enabled`.           |
| `acme_contact`        | `KNOT_TLS_ACME_CONTACT`        | unset   | Contact email for the ACME account. Required with `acme_enabled`.                               |
| `acme_staging`        | `KNOT_TLS_ACME_STAGING`        | `false` | Use the Let’s Encrypt staging directory.                                                        |
| `mtls_enabled`        | `KNOT_TLS_MTLS_ENABLED`        | `false` | Serve the mTLS admin listener on `server.internal_listen_addr`. Requires a server certificate.  |
| `mtls_client_ca_path` | `KNOT_TLS_MTLS_CLIENT_CA_PATH` | unset   | CA bundle that admin client certificates must chain to. Absolute. Required with `mtls_enabled`. |
| `mtls_admin_spki_pin` | `KNOT_TLS_MTLS_ADMIN_SPKI_PIN` | unset   | Base64 SHA-256 SPKI pin of the admin client certificate. Required with `mtls_enabled`.          |

## `[acl]`

| Key                       | Env var                        | Default  | Description                                                                                                                                                                                                                                                                                                  |
| ------------------------- | ------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `admission`               | `KNOT_ADMISSION`               | `closed` | Repo creation policy. `closed`: only knot members. `open`: any account that isn’t blocked.                                                                                                                                                                                                                   |
| `legacy_admin_secret_env` | `KNOT_LEGACY_ADMIN_SECRET_ENV` | unset    | Name of an env var holding a shared admin secret. When set and non-empty, the knot accepts Basic Auth as the first DID in `server.admins` on `createRecord`/`deleteRecord` for `org.tangled.knot.member` and `org.tangled.knot.block`, and serves the invite code admin method. Used by `knot-server admin`. |

## `[repo]`

| Key                   | Env var                     | Default         | Description                                                                                        |
| --------------------- | --------------------------- | --------------- | -------------------------------------------------------------------------------------------------- |
| `scan_path`           | `KNOT_SCAN_PATH`            | required        | Root directory of all repositories and the knot’s meta-repo. Absolute. Must exist and be writable. |
| `default_branch`      | `KNOT_DEFAULT_BRANCH`       | `main`          | Default branch of new repositories.                                                                |
| `contribution_policy` | `KNOT_CONTRIBUTION_POLICY`  | `collaborators` | Contribution policy: `anyone`, `members` or `collaborators`.                                       |
| `write_burst`         | `KNOT_REPO_WRITE_BURST`     | `30`            | Per-account burst for authenticated writes.                                                        |
| `write_refill_ms`     | `KNOT_REPO_WRITE_REFILL_MS` | `500`           | Refill interval of the per-account write bucket. 1 to 60000.                                       |

## `[git]`

| Key             | Env var                  | Default  | Description                                            |
| --------------- | ------------------------ | -------- | ------------------------------------------------------ |
| `object_format` | `KNOT_GIT_OBJECT_FORMAT` | `sha256` | Object format of new repositories: `sha256` or `sha1`. |

## `[secrets]`

| Key               | Env var                | Default  | Description                                                                                           |
| ----------------- | ---------------------- | -------- | ----------------------------------------------------------------------------------------------------- |
| `sealed_key_file` | `KNOT_SEALED_KEY_FILE` | required | Sealed key store holding the knot and per-repo signing keys. Absolute. Created if absent.             |
| `master_key_env`  | `KNOT_MASTER_KEY_ENV`  | required | Name of the env var holding the master key that unseals `sealed_key_file`: base64, at least 32 bytes. |

## `[http]`

| Key                       | Env var                             | Default    | Description                                                                   |
| ------------------------- | ----------------------------------- | ---------- | ----------------------------------------------------------------------------- |
| `connect_timeout_ms`      | `KNOT_HTTP_CONNECT_TIMEOUT_MS`      | `5000`     | Connect timeout for outbound HTTP requests.                                   |
| `read_timeout_ms`         | `KNOT_HTTP_READ_TIMEOUT_MS`         | `30000`    | Read timeout for outbound HTTP requests.                                      |
| `request_timeout_ms`      | `KNOT_HTTP_REQUEST_TIMEOUT_MS`      | `60000`    | Overall timeout for outbound HTTP requests.                                   |
| `max_response_bytes`      | `KNOT_HTTP_MAX_RESPONSE_BYTES`      | `16777216` | Largest outbound HTTP response body read.                                     |
| `block_private_addresses` | `KNOT_HTTP_BLOCK_PRIVATE_ADDRESSES` | `true`     | Refuse outbound requests to loopback, private and other non-public addresses. |

## `[atproto]`

| Key                 | Env var                  | Default  | Description                                                                                                  |
| ------------------- | ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------ |
| `plc_directory`     | `KNOT_PLC_DIRECTORY`     | required | PLC directory URL. Must be `https`.                                                                          |
| `plc_sweep_width`   | `KNOT_PLC_SWEEP_WIDTH`   | `1`      | Num of concurrent PLC reconciliations the directory sweep runs, must be between 1 and 64.                    |
| `repo_signing_keys` | `KNOT_REPO_SIGNING_KEYS` | unset    | Path to a per-repo signing key archive from `knot-migrate`. Its keys are sealed into the key store on start. |

## `[xrpc]`

| Key                            | Env var                                  | Default      | Description                                                                                                                                                                                                                  |
| ------------------------------ | ---------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `max_body_bytes`               | `KNOT_XRPC_MAX_BODY_BYTES`               | `65536`      | XRPC request body limit.                                                                                                                                                                                                     |
| `max_response_bytes`           | `KNOT_XRPC_MAX_RESPONSE_BYTES`           | `5242880`    | XRPC response size limit.                                                                                                                                                                                                    |
| `max_archive_bytes`            | `KNOT_XRPC_MAX_ARCHIVE_BYTES`            | `1073741824` | Upper bound on one archive across `org.tangled.git.getArchive`, `git archive --remote` over SSH and the smart HTTP archive route.                                                                                            |
| `tree_last_commit_budget_ms`   | `KNOT_XRPC_TREE_LAST_COMMIT_BUDGET_MS`   | `300`        | Time budget for last-commit lookup in tree listings.                                                                                                                                                                         |
| `blob_last_commit_budget_ms`   | `KNOT_XRPC_BLOB_LAST_COMMIT_BUDGET_MS`   | `2000`       | Time budget for last-commit lookup for a blob.                                                                                                                                                                               |
| `languages_budget_ms`          | `KNOT_XRPC_LANGUAGES_BUDGET_MS`          | `1000`       | Time budget for language statistics.                                                                                                                                                                                         |
| `max_patch_bytes`              | `KNOT_XRPC_MAX_PATCH_BYTES`              | `16777216`   | Body limit for the `seedRepo` procedure.                                                                                                                                                                                     |
| `max_patch_decompressed_bytes` | `KNOT_XRPC_MAX_PATCH_DECOMPRESSED_BYTES` | `134217728`  | Limit on the total decompressed size of a patch the merge procedures parse.                                                                                                                                                  |
| `preauth_burst`                | `KNOT_XRPC_PREAUTH_BURST`                | `20`         | Per-peer burst for unauthenticated requests.                                                                                                                                                                                 |
| `preauth_refill_ms`            | `KNOT_XRPC_PREAUTH_REFILL_MS`            | `100`        | Refill interval of the per-peer pre-auth bucket.                                                                                                                                                                             |
| `per_peer_inflight`            | `KNOT_XRPC_PER_PEER_INFLIGHT`            | `8`          | Concurrent pre-auth requests per peer.                                                                                                                                                                                       |
| `global_inflight`              | `KNOT_XRPC_GLOBAL_INFLIGHT`              | `64`         | Concurrent pre-auth requests across all peers. Must be at least `per_peer_inflight`.                                                                                                                                         |
| `max_pending_reservations`     | `KNOT_XRPC_MAX_PENDING_RESERVATIONS`     | `256`        | Knot-wide limit on reserved repository keys awaiting creation.                                                                                                                                                               |
| `per_actor_reservations`       | `KNOT_XRPC_PER_ACTOR_RESERVATIONS`       | `32`         | Per-account limit on reserved repository keys awaiting creation.                                                                                                                                                             |
| `reservation_ttl_secs`         | `KNOT_XRPC_RESERVATION_TTL_SECS`         | `3600`       | Time a reserved repository key is held before it lapses and its sealed key is reclaimed.                                                                                                                                     |
| `fork_max_pack_bytes`          | `KNOT_XRPC_FORK_MAX_PACK_BYTES`          | `1073741824` | Largest pack fetched when creating a fork.                                                                                                                                                                                   |
| `import_max_pack_bytes`        | `KNOT_XRPC_IMPORT_MAX_PACK_BYTES`        | `268435456`  | Largest pack fetched when importing a repository.                                                                                                                                                                            |
| `fork_fetch_timeout_ms`        | `KNOT_XRPC_FORK_FETCH_TIMEOUT_MS`        | `600000`     | Request timeout for the fork fetch.                                                                                                                                                                                          |
| `trusted_proxy_header`         | `KNOT_XRPC_TRUSTED_PROXY_HEADER`         | unset        | Header a reverse proxy appends the client address to, such as `x-forwarded-for`. Read right to left; the first entry not covered by `trusted_proxies` is the client. Unset uses the socket peer address.                     |
| `trusted_proxies`              | `KNOT_XRPC_TRUSTED_PROXIES`              | `[]`         | IPs or CIDR blocks whose `trusted_proxy_header` is honored. At most 32 hops are read. Empty honors the header from every peer and takes its rightmost entry. Requires `trusted_proxy_header`. Comma-separated as an env var. |
| `events_replay_buffer`         | `KNOT_XRPC_EVENTS_REPLAY_BUFFER`         | `4096`       | Events kept for `subscribeRepos` cursor replay.                                                                                                                                                                              |
| `events_replay_bytes`          | `KNOT_XRPC_EVENTS_REPLAY_BYTES`          | `67108864`   | Byte cap on the replay buffer.                                                                                                                                                                                               |
| `events_max_subscribers`       | `KNOT_XRPC_EVENTS_MAX_SUBSCRIBERS`       | `256`        | Maximum concurrent `subscribeRepos` subscribers.                                                                                                                                                                             |
| `events_max_per_peer`          | `KNOT_XRPC_EVENTS_MAX_PER_PEER`          | `8`          | Maximum `subscribeRepos` subscriptions per peer.                                                                                                                                                                             |

## `[maintenance]`

| Key                         | Env var                                      | Default    | Description                                                                      |
| --------------------------- | -------------------------------------------- | ---------- | -------------------------------------------------------------------------------- |
| `enabled`                   | `KNOT_MAINTENANCE_ENABLED`                   | `true`     | Run background git maintenance.                                                  |
| `commit_graph`              | `KNOT_MAINTENANCE_COMMIT_GRAPH`              | `true`     | Write commit-graph files.                                                        |
| `multi_pack_index`          | `KNOT_MAINTENANCE_MULTI_PACK_INDEX`          | `true`     | Write multi-pack indexes.                                                        |
| `bitmap`                    | `KNOT_MAINTENANCE_BITMAP`                    | `true`     | Write reachability bitmaps.                                                      |
| `interval_secs`             | `KNOT_MAINTENANCE_INTERVAL_SECS`             | `21600`    | Interval between maintenance passes.                                             |
| `repack_max_objects`        | `KNOT_MAINTENANCE_REPACK_MAX_OBJECTS`        | `16000000` | A full repack is skipped for a repository with more reachable objects than this. |
| `repack_geometric_factor`   | `KNOT_MAINTENANCE_REPACK_GEOMETRIC_FACTOR`   | `2`        | Geometric factor for pack roll-up. At least 2.                                   |
| `repack_loose_threshold`    | `KNOT_MAINTENANCE_REPACK_LOOSE_THRESHOLD`    | `6700`     | A repack is due when a repository has more loose objects than this.              |
| `prune_grace_secs`          | `KNOT_MAINTENANCE_PRUNE_GRACE_SECS`          | `1209600`  | Unreachable objects younger than this are kept.                                  |
| `full_repack_interval_secs` | `KNOT_MAINTENANCE_FULL_REPACK_INTERVAL_SECS` | `432000`   | Minimum interval between full repacks and reflog expiry of a repository.         |
| `reflog_expire_secs`        | `KNOT_MAINTENANCE_REFLOG_EXPIRE_SECS`        | `7776000`  | Reflog retention.                                                                |
| `large_push_bytes`          | `KNOT_MAINTENANCE_LARGE_PUSH_BYTES`          | `52428800` | A push pack of at least this size triggers maintenance of that repository.       |

## `[pack_cache]`

| Key               | Env var                           | Default      | Description                                                     |
| ----------------- | --------------------------------- | ------------ | --------------------------------------------------------------- |
| `enabled`         | `KNOT_PACK_CACHE_ENABLED`         | `true`       | Cache generated packs for repeated identical fetches.           |
| `ttl_secs`        | `KNOT_PACK_CACHE_TTL_SECS`        | `60`         | Lifetime of a cached pack.                                      |
| `max_entry_bytes` | `KNOT_PACK_CACHE_MAX_ENTRY_BYTES` | `67108864`   | Largest single cached pack.                                     |
| `max_total_bytes` | `KNOT_PACK_CACHE_MAX_TOTAL_BYTES` | `2147483648` | Total pack cache size, clamped to a share of the memory budget. |

## `[pack]`

| Key                          | Env var                                | Default       | Description                                         |
| ---------------------------- | -------------------------------------- | ------------- | --------------------------------------------------- |
| `max_objects`                | `KNOT_PACK_MAX_OBJECTS`                | `16000000`    | Most objects in one generated pack.                 |
| `max_total_bytes`            | `KNOT_PACK_MAX_TOTAL_BYTES`            | `68719476736` | Largest generated pack.                             |
| `selection_max_objects`      | `KNOT_PACK_SELECTION_MAX_OBJECTS`      | `16000000`    | Most objects walked when selecting objects to send. |
| `selection_time_budget_secs` | `KNOT_PACK_SELECTION_TIME_BUDGET_SECS` | `600`         | Time budget for object selection.                   |

## `[lfs]`

| Key                      | Env var                           | Default      | Description                                                                                                                        |
| ------------------------ | --------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| `store_path`             | `KNOT_LFS_STORE_PATH`             | required     | LFS objects and atproto record attachments; must be absolute path, must exist + be writable, must be outside the `repo.scan_path`. |
| `max_object_bytes`       | `KNOT_LFS_MAX_OBJECT_BYTES`       | `5368709120` | Largest LFS object.                                                                                                                |
| `free_space_floor_bytes` | `KNOT_LFS_FREE_SPACE_FLOOR_BYTES` | `1073741824` | Uploads are refused when free disk space is below this.                                                                            |
| `gc_grace_secs`          | `KNOT_LFS_GC_GRACE_SECS`          | `1209600`    | Unreferenced LFS objects younger than this are kept.                                                                               |
| `gc_interval_secs`       | `KNOT_LFS_GC_INTERVAL_SECS`       | `21600`      | Interval between LFS garbage collection passes.                                                                                    |
| `max_ssh_transfers`      | `KNOT_LFS_MAX_SSH_TRANSFERS`      | `16`         | Concurrent LFS transfers over SSH.                                                                                                 |
| `max_http_downloads`     | `KNOT_LFS_MAX_HTTP_DOWNLOADS`     | `64`         | Concurrent LFS downloads over HTTP.                                                                                                |

## `[social]`

| Key                 | Env var                         | Default                          | Description                                                                                                                    |
| ------------------- | ------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `repo_budget_bytes` | `KNOT_SOCIAL_REPO_BUDGET_BYTES` | `1073741824`                     | Byte budget over the live record versions of one repository, across all collections. A write past the budget gets `429`.       |
| `foster_url`        | `KNOT_SOCIAL_FOSTER_URL`        | `https://knot2.mock.tangled.org` | Foster knot the knot pulls shadowed social records from at boot, for the one-time 2026-10-28 upgrade. Empty disables the pull. |
| `foster_boot_width` | `KNOT_SOCIAL_FOSTER_BOOT_WIDTH` | `4`                              | Num of concurrent handovers the boot-time foster pull will do, must be between 1 and 256.                                      |

## `[keyfill]`

| Key                    | Env var                             | Default | Description                                                                               |
| ---------------------- | ----------------------------------- | ------- | ----------------------------------------------------------------------------------------- |
| `key_budget_mib`       | `KNOT_KEYFILL_KEY_BUDGET_MIB`       | `64`    | Memory budget for cached SSH keys, in MiB. 1 to 1048576.                                  |
| `ttl_secs`             | `KNOT_KEYFILL_TTL_SECS`             | `3600`  | Time a fetched SSH key set stays valid.                                                   |
| `reprieve_retry_secs`  | `KNOT_KEYFILL_REPRIEVE_RETRY_SECS`  | `300`   | Retry interval while a key refresh fails.                                                 |
| `reprieve_budget_secs` | `KNOT_KEYFILL_REPRIEVE_BUDGET_SECS` | `21600` | Time cached keys stay usable while refresh fails. Must be at least `reprieve_retry_secs`. |

## `[resources]`

| Key                | Env var                 | Default | Description                                                              |
| ------------------ | ----------------------- | ------- | ------------------------------------------------------------------------ |
| `max_threads`      | `KNOT_MAX_THREADS`      | `0`     | Worker thread cap. `0` uses the available parallelism.                   |
| `max_memory_bytes` | `KNOT_MAX_MEMORY_BYTES` | `0`     | Memory budget. `0` derives it from the cgroup limit or available memory. |

## `[homepage]`

| Key       | Env var                 | Default | Description                                                                 |
| --------- | ----------------------- | ------- | --------------------------------------------------------------------------- |
| `enabled` | `KNOT_HOMEPAGE_ENABLED` | `true`  | Serve an HTML homepage at `/`.                                              |
| `path`    | `KNOT_HOMEPAGE_PATH`    | unset   | HTML file served as the homepage. Absolute. Unset serves the built-in page. |

## `[links]`

| Key                | Env var                       | Default | Description                                                      |
| ------------------ | ----------------------------- | ------- | ---------------------------------------------------------------- |
| `privacy_policy`   | `KNOT_LINKS_PRIVACY_POLICY`   | unset   | Privacy policy URL, served by `org.tangled.knot.describeKnot`.   |
| `terms_of_service` | `KNOT_LINKS_TERMS_OF_SERVICE` | unset   | Terms of service URL, served by `org.tangled.knot.describeKnot`. |

## `[contact]`

| Key     | Env var              | Default  | Description                                                        |
| ------- | -------------------- | -------- | ------------------------------------------------------------------ |
| `email` | `KNOT_CONTACT_EMAIL` | required | Operator contact email, served by `org.tangled.knot.describeKnot`. |

## `[messages.*]`

Text the knot sends to git clients. Placeholders in braces are filled in at runtime. Multi-line keys take a list of lines or one newline-separated string.

### `[messages.push]`

| Key              | Env var                             | Default                                                       | Description                                   |
| ---------------- | ----------------------------------- | ------------------------------------------------------------- | --------------------------------------------- |
| `ack`            | `KNOT_MESSAGES_PUSH_ACK`            | `["{knot} received {refs}."]`                                 | Multi-line. Placeholders: `{knot}`, `{refs}`. |
| `pull_request`   | `KNOT_MESSAGES_PUSH_PULL_REQUEST`   | `["", "-> Open pull request for this branch:", " {url}", ""]` | Multi-line. Placeholders: `{url}`.            |
| `pipeline_clean` | `KNOT_MESSAGES_PUSH_PIPELINE_CLEAN` | `["pipeline compiled with no diagnostics"]`                   | Multi-line. Placeholders: none.               |
| `pipeline_none`  | `KNOT_MESSAGES_PUSH_PIPELINE_NONE`  | `["no pipelines to compile"]`                                 | Multi-line. Placeholders: none.               |

### `[messages.fetch]`

| Key           | Env var                           | Default                                   | Description                          |
| ------------- | --------------------------------- | ----------------------------------------- | ------------------------------------ |
| `motd`        | `KNOT_MESSAGES_FETCH_MOTD`        | `["Thanks for using {knot}!"]`            | Multi-line. Placeholders: `{knot}`.  |
| `enumerating` | `KNOT_MESSAGES_FETCH_ENUMERATING` | `["Enumerating objects: {count}, done."]` | Multi-line. Placeholders: `{count}`. |
| `total`       | `KNOT_MESSAGES_FETCH_TOTAL`       | `["Total {count}, done."]`                | Multi-line. Placeholders: `{count}`. |
| `fatal`       | `KNOT_MESSAGES_FETCH_FATAL`       | `knot: {error}`                           | Placeholders: `{error}`.             |

### `[messages.reject]`

| Key                         | Env var                                          | Default                                                                                                               | Description              |
| --------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| `reserved_refs`             | `KNOT_MESSAGES_REJECT_RESERVED_REFS`             | `refs/cobs/*, refs/cob-checkpoints/*, refs/hidden/*, refs/atproto/* and refs/tngl/* are reserved and can't be pushed` | Placeholders: none.      |
| `cob_create_only`           | `KNOT_MESSAGES_REJECT_COB_CREATE_ONLY`           | `existing refs/cobs/* object cannot be modified or deleted over the wire`                                             | Placeholders: none.      |
| `cob_delete`                | `KNOT_MESSAGES_REJECT_COB_DELETE`                | `refs/cobs/* stores append-only collaborative objects and cannot be deleted`                                          | Placeholders: none.      |
| `hidden_reserved`           | `KNOT_MESSAGES_REJECT_HIDDEN_RESERVED`           | `refs/hidden/* is reserved and can't be pushed`                                                                       | Placeholders: none.      |
| `atproto_reserved`          | `KNOT_MESSAGES_REJECT_ATPROTO_RESERVED`          | `refs/atproto/* is reserved for the knot's atproto commit chain and can't be pushed`                                  | Placeholders: none.      |
| `tngl_reserved`             | `KNOT_MESSAGES_REJECT_TNGL_RESERVED`             | `refs/tngl/* is reserved for knot-written keep refs and can't be pushed`                                              | Placeholders: none.      |
| `checkpoint_reserved`       | `KNOT_MESSAGES_REJECT_CHECKPOINT_RESERVED`       | `refs/cob-checkpoints/* is reserved for knot-written collaborative-object snapshots and can't be pushed`              | Placeholders: none.      |
| `cob_verification`          | `KNOT_MESSAGES_REJECT_COB_VERIFICATION`          | `collaborative-object verification failed: {error}`                                                                   | Placeholders: `{error}`. |
| `ref_exists`                | `KNOT_MESSAGES_REJECT_REF_EXISTS`                | `reference already exists`                                                                                            | Placeholders: none.      |
| `stale_old_value`           | `KNOT_MESSAGES_REJECT_STALE_OLD_VALUE`           | `stale info: old value doesn't match`                                                                                 | Placeholders: none.      |
| `missing_objects`           | `KNOT_MESSAGES_REJECT_MISSING_OBJECTS`           | `missing necessary objects`                                                                                           | Placeholders: none.      |
| `missing_objects_for`       | `KNOT_MESSAGES_REJECT_MISSING_OBJECTS_FOR`       | `missing necessary objects for {ref}`                                                                                 | Placeholders: `{ref}`.   |
| `atomic_failed`             | `KNOT_MESSAGES_REJECT_ATOMIC_FAILED`             | `atomic transaction failed`                                                                                           | Placeholders: none.      |
| `atomic_aborted`            | `KNOT_MESSAGES_REJECT_ATOMIC_ABORTED`            | `atomic push aborted`                                                                                                 | Placeholders: none.      |
| `authorization_unavailable` | `KNOT_MESSAGES_REJECT_AUTHORIZATION_UNAVAILABLE` | `authorization unavailable`                                                                                           | Placeholders: none.      |
| `unpacker_error`            | `KNOT_MESSAGES_REJECT_UNPACKER_ERROR`            | `unpacker error`                                                                                                      | Placeholders: none.      |
| `ref_snapshot_unavailable`  | `KNOT_MESSAGES_REJECT_REF_SNAPSHOT_UNAVAILABLE`  | `ref snapshot unavailable`                                                                                            | Placeholders: none.      |
| `object_migration_failed`   | `KNOT_MESSAGES_REJECT_OBJECT_MIGRATION_FAILED`   | `object migration failed`                                                                                             | Placeholders: none.      |

### `[messages.ssh]`

| Key                    | Env var                                  | Default                                                                                                                                                                                                                                                                                                                                                                     | Description                                   |
| ---------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| `greeting`             | `KNOT_MESSAGES_SSH_GREETING`             | `["Hi {user}! You're authenticated to {knot} knot.", "This knot serves git over ssh, so there's no shell here. :P", "Clone repo with: git clone {knot}:<repoDID>"]`                                                                                                                                                                                                         | Multi-line. Placeholders: `{user}`, `{knot}`. |
| `greeting_unknown`     | `KNOT_MESSAGES_SSH_GREETING_UNKNOWN`     | `["Hi there! This is the {knot} knot.", "This knot serves git over ssh, so there's no shell here. :P", "Clone repo with: git clone {knot}:<repoDID>", "Publish your ssh key to your atproto account so this knot can identify your pushes.", "Put your handle in the url, as in yourhandle@{knot}:<repoDID>, so your ssh client can find your registered key on its own."]` | Multi-line. Placeholders: `{knot}`.           |
| `unsupported_command`  | `KNOT_MESSAGES_SSH_UNSUPPORTED_COMMAND`  | `knot: unsupported command`                                                                                                                                                                                                                                                                                                                                                 | Placeholders: none.                           |
| `too_many_operations`  | `KNOT_MESSAGES_SSH_TOO_MANY_OPERATIONS`  | `knot: too many concurrent operations from your address, try again shortly`                                                                                                                                                                                                                                                                                                 | Placeholders: none.                           |
| `repo_not_found`       | `KNOT_MESSAGES_SSH_REPO_NOT_FOUND`       | `knot: repository not found`                                                                                                                                                                                                                                                                                                                                                | Placeholders: none.                           |
| `index_warming`        | `KNOT_MESSAGES_SSH_INDEX_WARMING`        | `knot: repository index is warming, retry shortly`                                                                                                                                                                                                                                                                                                                          | Placeholders: none.                           |
| `key_not_registered`   | `KNOT_MESSAGES_SSH_KEY_NOT_REGISTERED`   | `knot: this ssh key doesn't match any key published by the accounts that may push here. Authorized: {authorized}. If your agent offers several keys, add -o IdentitiesOnly=yes so it offers your registered key.`                                                                                                                                                           | Placeholders: `{authorized}`.                 |
| `identity_unavailable` | `KNOT_MESSAGES_SSH_IDENTITY_UNAVAILABLE` | `knot: couldn't read the account records needed to check your ssh key, retry shortly`                                                                                                                                                                                                                                                                                       | Placeholders: none.                           |
| `push_denied`          | `KNOT_MESSAGES_SSH_PUSH_DENIED`          | `knot: you aren't authorized to push to this repository.`                                                                                                                                                                                                                                                                                                                   | Placeholders: none.                           |
| `shutting_down`        | `KNOT_MESSAGES_SSH_SHUTTING_DOWN`        | `knot: server is shutting down`                                                                                                                                                                                                                                                                                                                                             | Placeholders: none.                           |
| `archive_malformed`    | `KNOT_MESSAGES_SSH_ARCHIVE_MALFORMED`    | `knot: malformed upload-archive request`                                                                                                                                                                                                                                                                                                                                    | Placeholders: none.                           |
| `archive_timeout`      | `KNOT_MESSAGES_SSH_ARCHIVE_TIMEOUT`      | `knot: upload-archive request timed out`                                                                                                                                                                                                                                                                                                                                    | Placeholders: none.                           |
| `archive_failed`       | `KNOT_MESSAGES_SSH_ARCHIVE_FAILED`       | `knot: upload-archive failed`                                                                                                                                                                                                                                                                                                                                               | Placeholders: none.                           |
| `advertise_failed`     | `KNOT_MESSAGES_SSH_ADVERTISE_FAILED`     | `knot: cannot advertise refs`                                                                                                                                                                                                                                                                                                                                               | Placeholders: none.                           |
| `push_too_large`       | `KNOT_MESSAGES_SSH_PUSH_TOO_LARGE`       | `knot: push exceeds configured size limit`                                                                                                                                                                                                                                                                                                                                  | Placeholders: none.                           |
| `receive_deadline`     | `KNOT_MESSAGES_SSH_RECEIVE_DEADLINE`     | `knot: receive exceeded its time budget`                                                                                                                                                                                                                                                                                                                                    | Placeholders: none.                           |
| `malformed_pack`       | `KNOT_MESSAGES_SSH_MALFORMED_PACK`       | `knot: malformed pack stream`                                                                                                                                                                                                                                                                                                                                               | Placeholders: none.                           |
| `receive_read_error`   | `KNOT_MESSAGES_SSH_RECEIVE_READ_ERROR`   | `knot: receive read error`                                                                                                                                                                                                                                                                                                                                                  | Placeholders: none.                           |
| `receive_ended_early`  | `KNOT_MESSAGES_SSH_RECEIVE_ENDED_EARLY`  | `knot: receive stream ended early`                                                                                                                                                                                                                                                                                                                                          | Placeholders: none.                           |
| `receive_failed`       | `KNOT_MESSAGES_SSH_RECEIVE_FAILED`       | `knot: receive-pack failed`                                                                                                                                                                                                                                                                                                                                                 | Placeholders: none.                           |

### `[messages.http]`

| Key                   | Env var                                  | Default                                            | Description              |
| --------------------- | ---------------------------------------- | -------------------------------------------------- | ------------------------ |
| `push_denied`         | `KNOT_MESSAGES_HTTP_PUSH_DENIED`         | `you aren't authorized to push to this repository` | Placeholders: none.      |
| `repo_not_found`      | `KNOT_MESSAGES_HTTP_REPO_NOT_FOUND`      | `repository not found`                             | Placeholders: none.      |
| `push_too_large`      | `KNOT_MESSAGES_HTTP_PUSH_TOO_LARGE`      | `push exceeds the configured size limit`           | Placeholders: none.      |
| `malformed_pack`      | `KNOT_MESSAGES_HTTP_MALFORMED_PACK`      | `malformed pack stream: {error}`                   | Placeholders: `{error}`. |
| `receive_ended_early` | `KNOT_MESSAGES_HTTP_RECEIVE_ENDED_EARLY` | `receive stream ended early`                       | Placeholders: none.      |

### `[messages.lfs]`

| Key                   | Env var                                 | Default                                                   | Description                               |
| --------------------- | --------------------------------------- | --------------------------------------------------------- | ----------------------------------------- |
| `invalid_oid`         | `KNOT_MESSAGES_LFS_INVALID_OID`         | `invalid LFS oid {value}`                                 | Placeholders: `{value}`.                  |
| `hash_mismatch`       | `KNOT_MESSAGES_LFS_HASH_MISMATCH`       | `oid mismatch, declared {declared}, computed {computed}`  | Placeholders: `{declared}`, `{computed}`. |
| `size_mismatch`       | `KNOT_MESSAGES_LFS_SIZE_MISMATCH`       | `size mismatch, declared {declared}, received {received}` | Placeholders: `{declared}`, `{received}`. |
| `size_limit_exceeded` | `KNOT_MESSAGES_LFS_SIZE_LIMIT_EXCEEDED` | `object size {declared} exceeds limit {limit}`            | Placeholders: `{declared}`, `{limit}`.    |
| `free_space_denied`   | `KNOT_MESSAGES_LFS_FREE_SPACE_DENIED`   | `free space {free} below floor {floor}`                   | Placeholders: `{free}`, `{floor}`.        |
| `not_found`           | `KNOT_MESSAGES_LFS_NOT_FOUND`           | `object {oid} not found`                                  | Placeholders: `{oid}`.                    |
| `framing`             | `KNOT_MESSAGES_LFS_FRAMING`             | `protocol framing fault: {detail}`                        | Placeholders: `{detail}`.                 |
| `too_many`            | `KNOT_MESSAGES_LFS_TOO_MANY`            | `too many {what} in one message, limit {limit}`           | Placeholders: `{what}`, `{limit}`.        |
| `unknown_command`     | `KNOT_MESSAGES_LFS_UNKNOWN_COMMAND`     | `unknown command {command}`                               | Placeholders: `{command}`.                |
| `unsupported_version` | `KNOT_MESSAGES_LFS_UNSUPPORTED_VERSION` | `unsupported version {version}`                           | Placeholders: `{version}`.                |
| `unsupported_hash`    | `KNOT_MESSAGES_LFS_UNSUPPORTED_HASH`    | `unsupported hash algorithm {algorithm}`                  | Placeholders: `{algorithm}`.              |
| `put_on_download`     | `KNOT_MESSAGES_LFS_PUT_ON_DOWNLOAD`     | `put-object isn't allowed on a download channel`          | Placeholders: none.                       |
| `verify_on_download`  | `KNOT_MESSAGES_LFS_VERIFY_ON_DOWNLOAD`  | `verify-object isn't allowed on a download channel`       | Placeholders: none.                       |
| `get_on_upload`       | `KNOT_MESSAGES_LFS_GET_ON_UPLOAD`       | `get-object isn't allowed on an upload channel`           | Placeholders: none.                       |
| `put_no_body`         | `KNOT_MESSAGES_LFS_PUT_NO_BODY`         | `put-object is missing its object body`                   | Placeholders: none.                       |

## NixOS module

`services.tangled.knot-rs` renders `settings` to `/etc/knot/config.toml`, checks it with `knot-server validate --config-only` at build time when the build platform can run the binary, and runs `knot-server --config /etc/knot/config.toml`. Keys under `settings` not listed below pass through unchanged.

| Option                                 | Default                      | Description                                                                                               |
| -------------------------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------- |
| `enable`                               | `false`                      | Enable the knot service.                                                                                  |
| `package`                              | none                         | Package providing `knot-server`.                                                                          |
| `user`                                 | `"knot"`                     | User the knot runs as and owner of the repositories.                                                      |
| `group`                                | value of `user`              | Group the knot runs as.                                                                                   |
| `stateDir`                             | `"/var/lib/knot"`            | State directory; base for the path defaults below.                                                        |
| `openFirewall`                         | `true`                       | Open the TCP port of each non-loopback listen address, and the UDP port for HTTP/3 when TLS is on.        |
| `environmentFile`                      | `null`                       | systemd `EnvironmentFile`. Must be set; holds the master key. Its `KNOT_*` variables override `settings`. |
| `settings.server.hostname`             | none                         | `server.hostname`.                                                                                        |
| `settings.server.admins`               | none                         | `server.admins`. Non-empty list.                                                                          |
| `settings.server.listen_addr`          | `"127.0.0.1:5555"`           | `server.listen_addr`.                                                                                     |
| `settings.server.internal_listen_addr` | `"[::1]:5444"`               | `server.internal_listen_addr`.                                                                            |
| `settings.server.ssh_listen_addr`      | `"[::]:2222"`                | `server.ssh_listen_addr`. Must not be a port `services.openssh` listens on.                               |
| `settings.server.ssh_host_key_file`    | `"${stateDir}/ssh_host_key"` | `server.ssh_host_key_file`.                                                                               |
| `settings.acl.admission`               | `"closed"`                   | `acl.admission`: `closed` or `open`.                                                                      |
| `settings.repo.scan_path`              | `"${stateDir}/repos"`        | `repo.scan_path`.                                                                                         |
| `settings.git.object_format`           | `"sha256"`                   | `git.object_format`: `sha1` or `sha256`.                                                                  |
| `settings.secrets.sealed_key_file`     | `"${stateDir}/sealed-keys"`  | `secrets.sealed_key_file`.                                                                                |
| `settings.secrets.master_key_env`      | `"KNOT_MASTER_KEY"`          | `secrets.master_key_env`.                                                                                 |
| `settings.atproto.plc_directory`       | none                         | `atproto.plc_directory`.                                                                                  |
| `settings.contact.email`               | none                         | `contact.email`.                                                                                          |
| `settings.xrpc.trusted_proxy_header`   | `null`                       | `xrpc.trusted_proxy_header`.                                                                              |
| `settings.lfs.store_path`              | none                         | `lfs.store_path`.                                                                                         |
| `settings.tls.cert_path`               | `null`                       | `tls.cert_path`.                                                                                          |
| `settings.tls.key_path`                | `null`                       | `tls.key_path`.                                                                                           |
| `settings.tls.http3`                   | `true`                       | `tls.http3`.                                                                                              |
| `settings.tls.acme_enabled`            | `false`                      | `tls.acme_enabled`. Requires a non-loopback `listen_addr`.                                                |
| `settings.tls.acme_cache_dir`          | `"${stateDir}/acme"`         | `tls.acme_cache_dir`.                                                                                     |
| `settings.tls.acme_contact`            | `null`                       | `tls.acme_contact`.                                                                                       |
| `settings.tls.acme_staging`            | `false`                      | `tls.acme_staging`.                                                                                       |
| `settings.tls.mtls_enabled`            | `false`                      | `tls.mtls_enabled`.                                                                                       |
| `settings.tls.mtls_client_ca_path`     | `null`                       | `tls.mtls_client_ca_path`.                                                                                |
| `settings.tls.mtls_admin_spki_pin`     | `null`                       | `tls.mtls_admin_spki_pin`.                                                                                |