kogutdocs

Reference

kogut.toml

Configuration keys, types, defaults, and validation rules.

kogut.toml defines services, checks, heartbeats, host alert thresholds, and notification channels. File-managed objects are read-only in the panel.

File handling

The default path is $KOGUT_DATA/kogut.toml; KOGUT_CONFIG overrides it. A missing or empty file means the defaults. All top-level tables are optional. Unknown keys are errors.

kogut checks the file every 2 seconds and applies a valid file atomically. An invalid file stops startup. An invalid reload keeps the previous configuration and reports the error in the panel and log. listen needs a restart. There is no reload signal.

Removing an object from the file removes it from kogut, and deleting the file counts as an empty file. kogut never writes the file. The MCP propose_ tools return tables to add to it, judged as a reload would judge them.

Checks and heartbeats share one namespace with panel-created checks and heartbeats, and channels share another. Panel channel names cannot use the file’s naming scheme, such as ntfy or slack-2. When the file names a check, heartbeat, or channel created in the panel:

  • With the same definition, the file takes it over. It becomes file-managed and keeps its id, results, incidents, ping URL, silences, and deliveries. A channel’s secrets are removed from the database. The same definition means, for a check, the same service, type, target, from, every, timeout, method, and expect; for a heartbeat, the same service, schedule, and grace; for a channel, the same type and keys, secrets included.
  • With another definition, the file is rejected. The error names the first key that differs, such as check[2].every: a check named "api" was created in the panel with another definition. Make the two equal, or rename one.
  • A name used by another kind of object, such as a panel check for a file heartbeat, rejects the file.

A takeover is one-way: from then on only the file changes the object, and removing it from the file deletes it. A command check never rejects the file: the file’s check takes the name, and the host page names the command check that was left out.

Move panel objects into the file

kogut export prints the checks, heartbeats, and channels created in the panel, and the kogut edge and heartbeat URL set there, as kogut.toml that the next reload takes over. Settings → Move to kogut.toml in the panel copies the same text:

kogut export >> /var/lib/kogut/kogut.toml

Secrets are written as ${NAME} placeholders, listed at the top of the output. Set those variables before kogut reloads the file; until then the reload is rejected and the panel objects stay as they are.

A service or host already in the database becomes file-managed when the file names it, in [[service]] hosts or in [[host]]. A [[host]] never clashes with a panel host: the file takes it over. If the file later drops it, it stays panel-managed while panel checks use it. Sites, users, tokens, ping URLs, silences, maintenance windows, and the status page exist only in the database.

Check the file

kogut config check checks a file without starting the server. In GitHub Actions, run it from the image; --allow-unset checks the file without its secrets:

- name: Check kogut.toml
  run: docker run --rm -v "$PWD:/repo:ro" ghcr.io/piotrkowski/kogut:0.1 config check --allow-unset /repo/kogut.toml

Errors read <file>:<line>:<column>: <key>: <message>. Lines and columns start at 1, columns count characters, and array indexes start at 0:

/var/lib/kogut/kogut.toml:14:3: check[2].every: 5s is too often; checks run at most every 10s

A failed validation points at its key, a missing key at its table, such as the [[check]] line, and a syntax or type error at the exact character. A name clash with the panel gives the key path without a position. Errors about addresses omit the value, which may contain a secret. The startup error, the reload log, and the panel show the same text.

Secrets

String values support ${NAME} substitution from the environment:

secret = "${GITHUB_WEBHOOK_SECRET}"

Names contain letters, digits, and _, and cannot start with a digit. $NAME stays literal. There is no default syntax or recursive expansion. Keys, numbers, and booleans are not expanded. Durations are strings, so every = "${EVERY}" works.

If the variable is unset, kogut reads $CREDENTIALS_DIRECTORY/NAME, without one trailing newline. If neither exists, the file is invalid. Substitutions are read again on reload. File secrets, including channel tokens and check headers, never enter the database.

Durations

A whole number and a unit: ms, s, m, h, or d, such as "500ms", "30s", "5m", "1h", or "7d". Units cannot be combined: write "90m", not "1h30m".

Example

kogut.toml
[server]
url = "https://kogut.acme.com"

[[service]]
name = "api"
hosts = ["vps-1"]
depends_on = ["postgres"]

[[service]]
name = "postgres"
hosts = ["vps-1"]

[[check]]
name = "API"
service = "api"
url = "https://api.acme.com/health"
expect = [{ json = "$.db", equals = "ok" }]

[[host]]
name = "vps-1"
disk = 95

[[alert]]
type = "ntfy"
url = "https://ntfy.sh/acme-ops"

[server]

Environment variables override listen and url.

KeyTypeDefaultDescription
listenstring"0.0.0.0:8484"Listen address and port for all HTTP endpoints. Bare ports are invalid. Read once at startup.
urlstringnonePublic panel URL, starting with http:// or https://. Used for generated links, snippets, agent commands, and panel Origin checks. HTTPS marks session cookies Secure. Without a URL, kogut uses the request’s Host.
timezonestringthe machine’s zoneIANA zone for notification times, the daily summary at 07:00, and visitor salt rotation at 04:00. Defaults to TZ, then /etc/localtime. An unreadable configured zone logs an error and uses UTC. Heartbeat cron has its own zone.
trusted_proxieslist of strings[]Proxy IPs or networks trusted for visitor addresses in X-Forwarded-For and X-Real-IP. Loopback and [auth.proxy] trusted_proxies are also trusted. Used for rate limits and visitor counts, never sign-in.
connectivity_hostslist of strings["1.1.1.1:443", "9.9.9.9:443"]host:port connectivity probes, every 30 seconds while a server-run check targets a public address. Two consecutive rounds without a response mark kogut offline. [] disables probes. See connectivity handling.
heartbeat_urlstringnoneHTTP or HTTPS watchdog URL. Requested with GET at startup and every minute, with a 10-second timeout. Overrides the panel setting while present. Often contains a secret. See Heartbeat URL.

[brand]

Branding for the status page, not the panel. Without [brand], the page and browser tab read “Status”.

KeyTypeDefaultDescription
namestringrequiredPage heading. The browser title adds “status” unless the name already ends with it.
logostringnoneA URL starting with http:// or https://, or a file path relative to the data directory (no ..). A file is served as /status-logo on the status domain. Supports SVG, PNG, WebP, and JPEG.
accentstringnoneA colour as #rgb or #rrggbb. Colours the name in the status page header when there is no logo.
status_domainstringnoneStatus hostname, without a scheme or path. Serves the status page and subscription routes, not the panel or API.
subscriptionsbooleanfalseEmail subscriptions, sent through the first email notification channel. Links use https://<status_domain>, or the panel’s public URL. See Email updates.

[[service]]

A service named by a check or heartbeat but not declared is created with defaults.

KeyTypeDefaultDescription
namestringrequiredUnique name, such as api. Up to 100 characters, no spaces at the start or end.
hostsarray of strings[]Agent hosts the service runs on. Each is created in kogut; connect its agent from the panel.
sitesarray of strings[]Domains of existing sites linked to this service. Does not create sites. Each domain belongs to at most one service.
depends_onarray of strings[]Dependencies. When a dependency is down, this service’s alerts join its incident instead of opening their own. A failing shared host takes precedence over a dependency as the root. Cycles are invalid. See alert grouping and likely causes.
tierstring"standard""critical", "standard", or "low". Orders services in the panel and in the daily summary.

[[host]]

Alert thresholds of one agent host. A key you leave out keeps its default. A host named here is created in kogut and becomes file-managed, and its thresholds are read-only in the panel. Removing the table restores the defaults.

kogut.toml
[[host]]
name = "nas"
disk = 95
swap = false
temperature = 80
ignore_mounts = ["/mnt/backup"]
KeyTypeDefaultDescription
namestringrequiredThe host, as in [[service]] hosts. Each host has at most one [[host]].
cpuinteger or false90Percent. CPU above it for 5 min degrades CPU on <host>.
memoryinteger or false90Percent. Memory in use above it for 5 min degrades Memory on <host>.
swapinteger or false50Percent. Swap in use above it for 5 min degrades Swap on <host>.
diskinteger or false90Percent. The fullest mount above it degrades Disk on <host> at once, with the mount named, such as /srv 93%, above 90%.
temperatureinteger or falsefalseDegrees Celsius, 1 to 150. The hottest sensor above it for 5 min degrades Temperature on <host>. A host without sensors never alerts.
ignore_mountsarray of strings[]Mount points the disk alert leaves out, as the host page lists them, such as "/mnt/nas". Each is an absolute path. The disk chart still shows every mount.

Percentages are whole numbers from 1 to 100. false turns that alert off; true is an error. A changed limit applies from the host’s next sample; after a raised limit, the 5 min start again from the first sample above it. Turning off an open alert closes it. The 5 min are fixed.

For hosts added in the panel, set the same values under Alerts → Edit alerts on the host page.

[[check]]

Each check has exactly one target, url, tcp, dns, ping, or domain, which sets its kind. See Checks.

KeyTypeDefaultDescription
namestringrequiredShown in alerts and the panel. Unique among checks and heartbeats. Up to 100 characters.
servicestringrequiredThe service the check belongs to.
urlstringnoneAn HTTP or HTTPS URL. Makes an HTTP check. HTTPS checks also watch the certificate: degraded 14 days before it expires, down 7 days before. A user name or password in the URL is an error; send Basic authentication in headers.
tcpstringnonehost:port, such as "db.internal:5432" or "[2001:db8::1]:22". Makes a TCP check.
dnsstringnoneA name to resolve, such as "acme.com". Makes a DNS check.
pingstringnoneA host name or IP address, such as "router.lan" or "10.0.0.1", without a port. Makes a ping check: three ICMP echo requests per run.
domainstringnoneA registered domain, such as "acme.com". Makes a domain check, which reads the registration expiry from the registry once a day. One check per domain.
methodstring"GET"HTTP method, in capitals: GET, HEAD, POST, PUT, PATCH, or DELETE. Only with url. OPTIONS is accepted but the check fails when it runs. Checks send no body.
headersinline tablenoneRequest headers, such as { Authorization = "Bearer ${API_TOKEN}" }. Only with url. See below.
everyduration"1m" for HTTP, TCP, and ping, "5m" for DNSInterval from "10s" to "1d". Domain checks run daily and reject this key.
timeoutduration"10s"How long to wait. Between "100ms" and "1m", and not longer than every. At least "1s" for ping checks.
fromstringthe serverAgent host, listed in a service’s hosts. Domain checks always run on the server. See Internal services.
expectarray of inline tables2xx statusAssertions about the response. Only with url, and with ping, where only response_time_below is allowed.

headers

Request headers for an HTTP check, as an inline table of names and string values. Values can use ${NAME}.

  • Names are HTTP header names. The same name twice in different case is an error.
  • Values allow printable ASCII and tabs, up to 8 KiB, without line breaks. At most 50 headers.
  • Connection, Content-Length, Keep-Alive, Proxy-Connection, TE, Trailer, Transfer-Encoding, and Upgrade are set by kogut and are errors here. Host and User-Agent replace kogut’s own.
  • The headers go only to the origin of url. A redirect to another scheme, host, or port is followed without them.
  • Header values are never logged, shown in the panel, or written to the database. Error messages about a value name the header, not the value.

expect

Each item is one assertion. All must pass for the check to be up.

AssertionExamplePasses when
status{ status = 200 }, { status = "2xx" }The status is that code (100 to 599), or in that class (1xx to 5xx).
body_contains{ body_contains = "Welcome" }The body contains the text.
body_not_contains{ body_not_contains = "Maintenance" }The body does not contain the text.
json{ json = "$.version" }The body is JSON and the path exists. A path starts with $, followed by .key and [index] steps, such as $.a.b[0].c.
json with equals{ json = "$.db", equals = "ok" }The value at the path equals the given value. Any TOML value works: equals = true, equals = 3.
response_time_below{ response_time_below = "800ms" }The response arrived faster than the limit.
response_time_below with else{ response_time_below = "800ms", else = "degraded" }As above. A slower response makes the check degraded instead of down. else is "degraded" or "down" (the default).
  • Without a status assertion, the response must be 2xx.
  • A 429 or 503 that fails the status assertion makes the check degraded, not down, and the body assertions are skipped.
  • GET and HEAD checks follow up to 5 redirects. Other methods follow none. kogut reads at most 1 MiB of the body.

When an assertion fails, the alert shows what kogut saw:

status 503, expected 2xx
$.db is "degraded", expected "ok"

[[heartbeat]]

A heartbeat alerts when a scheduled task misses a ping, starting from its first ping. Ping URLs are created in the panel or through the API. To keep a token your jobs already send, set ping_token; see Keep an existing push URL.

KeyTypeDefaultDescription
namestringrequiredUnique among checks and heartbeats.
servicestringrequiredThe service the heartbeat belongs to.
everydurationnoneThe longest time between pings. Between "1m" and "31d".
cronstringnoneA five-field cron expression (minute, hour, day of month, month, and day of week), such as "0 3 * * *". Lists, ranges, steps, and names such as mon and jan work. An expression that can never fire is an error. Give either every or cron.
timezonestring"UTC"An IANA time zone for cron, such as "Europe/Warsaw". Only with cron. [server] timezone does not apply.
gracedurationa fifth of every (between 1 min and 1 h), or "1h" with cronHow late a ping may be before it is missed. Between "1m" and "7d".
ping_tokenstringnoneA token that pings this heartbeat at /ping/<token> and /api/push/<token>. 10 to 128 letters, digits, -, or _, unique among heartbeats, and not the token of another heartbeat’s panel ping URL. Give it as a secret, such as "${BACKUP_PUSH_TOKEN}". kogut keeps it in memory only, not in the database. A ping URL made in the panel works next to it.

[[alert]]

A notification channel. type selects its keys. See Notification channels and How alerts work.

typeKeys
"ntfy"url (required, the topic URL), token (an access token for protected topics), allow_private (default false, true to reach a server on a private or loopback address)
"slack"url (required, an incoming webhook URL)
"discord"url (required, a channel webhook URL)
"telegram"bot_token (required), chat_id (required)
"webhook"url (required), secret (signs the timestamp and body of each request with HMAC-SHA256, see Verify the signature), allow_private (default false, true to reach a private or loopback address)
"email"smtp (required, host:port), tls ("starttls", "implicit", or "none", default "implicit" on port 465 and "starttls" on any other), username and password (together or not at all, never with tls = "none"), from (required), to (required, at least one address), allow_private (default false, true to reach a relay on a private or loopback address)

Without allow_private, kogut delivers only to public addresses. Slack, Discord, and Telegram always need one. For other services, such as Microsoft Teams or Pushover, use a webhook to an Apprise API server.

name (optional) names the channel: lowercase letters, digits, and hyphens, such as ops-phone, unique among [[alert]] tables. Without it, the channel is named after its type in file order: ntfy, then ntfy-2, and so on, counting named channels too. A name, or a panel channel’s name, cannot have that form. A named channel with the same name and settings as a panel channel takes it over (see File handling).

[auth]

Sign-in to the panel: passwords, OpenID Connect, and forward auth. Several can be on at once.

KeyTypeDefaultDescription
passwordbooleantrueLocal accounts with passwords. false is accepted only with [[auth.oidc]] or [auth.proxy].

[[auth.oidc]]

One table per provider, each a button on the sign-in page. Needs the public URL (KOGUT_URL or [server] url). See OpenID Connect.

KeyTypeDefaultDescription
presetstringnone"authentik", "authelia", "keycloak", "pocket_id", "zitadel", "kanidm", "google", or "entra".
issuerstring"https://accounts.google.com" for Google, otherwise requiredThe issuer URL, https:// (plain http:// only on loopback).
client_idstringrequired
client_secretstringrequiredWrite it as ${ENV_NAME}.
scopesarray of strings["openid", "email", "profile"]Authelia, Pocket ID, and Kanidm add "groups". Zitadel adds "urn:zitadel:iam:org:projects:roles". Must include "openid".
groups_claimstring"groups"None for Google. Zitadel uses "urn:zitadel:iam:org:project:roles".
admin_groupsarray of strings[]Members are admins. When set, groups decide the role at every sign-in.
allowed_domainsarray of stringsany domain, required with GoogleEmail domains that may sign in.

[auth.proxy]

A reverse proxy passes the person’s email in a header. See Forward auth.

KeyTypeDefaultDescription
trusted_proxiesarray of stringsrequiredIP addresses or networks of the proxy. Identity headers from any other TCP peer are ignored. These addresses are also trusted to name the visitor’s address in X-Forwarded-For, like [server] trusted_proxies.
presetstringnone"authelia", "authentik", "oauth2_proxy", "pomerium", "cloudflare_access", or "tailscale".
headersinline tablenone{ email = "...", groups = "..." }, email required. Replaces preset.
admin_groupsarray of strings[]Members are admins. Requires a groups header.

[github]

Deploys from a GitHub webhook. Without this table, /hooks/github answers 404.

KeyTypeDefaultDescription
secretstringrequiredThe webhook secret, used to check X-Hub-Signature-256. Write it as ${ENV_NAME}.
repoarray of tablesnoneOne [[github.repo]] per repository and service: name as owner/repo, and the service it deploys, which must be a service known to the file. Names match without regard to case. A repository listed for several services marks a deploy of each.

Each [[github.repo]] takes these keys:

KeyTypeDefaultDescription
namestringrequiredThe repository, as owner/repo.
servicestringrequiredThe service it deploys.
environmentstringproduction deployments onlyThe only deployment environment recorded for this service, compared without regard to case. Applies to deployment_status only. See Deployments.

Pushes to the default branch, published releases, and successful production deployment statuses become deploy markers. branch is not a supported key.

[edge]

kogut sends the kogut edge watchdog a heartbeat every 30 seconds. This table overrides the panel setting and locks it. Failures retry after 5, 10, 20, and 40 seconds, then every minute.

KeyTypeDefaultDescription
urlstringrequiredThe Worker’s URL, with http:// or https://. kogut posts to <url>/heartbeat.
tokenstringrequiredThe token the Worker expects with each heartbeat, the same as its KOGUT_EDGE_TOKEN secret.

[retention]

How long data is kept. Each value is between "1h" and "3650d". Older data is deleted in small batches in the background.

KeyTypeDefaultDescription
check_resultsduration"7d"Raw results of every check run, and heartbeat pings.
host_metricsduration"1d"Raw host metrics, every 15 s: CPU, memory, disks, and containers. systemd unit states, CPU, and memory are stored when a state changes and once a minute.
minute_bucketsduration"30d"One- and five-minute rollups of checks and host metrics, and five-minute rollups of container and systemd unit CPU and memory.
hourly_bucketsduration"365d"Hourly and daily check and host rollups, hourly container and unit CPU and memory, plus hourly Web Vitals for long-range charts.
analytics_eventsduration"90d"Raw page views, visits, custom events, and Web Vitals. Minimum "3d". Daily rollups require complete days of raw page views. Older analytics uses those rollups.
analytics_rollupsduration"400d"Daily analytics totals and top dimension values. Minimum "366d" for complete 1y ranges.
error_eventsduration"30d"Single JavaScript errors. Error groups are kept.