Repository navigation
Configuration
The bot reads /config/config.toml inside its container. Compose mounts your config.toml at that path, read-only. To read a different path, set the SHAZZZBOT_CONFIG environment variable.
The file uses top-level keys and named tables, for example [history] and [[outputs]]. Put every top-level key above the first [table] line. In TOML, a line after a table header belongs to that table. Thus a key under [tasks.rename] is a task name. When a [tasks] key is there by mistake, the bot stops with ['shown'] sit under [tasks.rename]; move them above it, into [tasks].
The bot checks the file at start. If a setting is unknown, missing, or not valid, the bot stops and the log names the setting, for example unknown setting 'intervall'; did you mean 'interval'?. Secrets never go in this file. They go in .env (see Environment variables).
config.example.toml lists every key with its default and a one-line comment. The preview site has a settings editor that checks a file with the same rules as the bot.
| Key | Type | Default | Description |
|---|---|---|---|
config_format |
integer | none, required | The format of the file. The only value is 1. A file in an older format stops the bot with run setup.sh --action migrate. A file from a newer version also stops the bot. |
guild_id |
integer | none, required | The id of your Discord server. |
audit_channel_id |
integer | none | The id of the audit channel. 0 or no value means no audit channel, and then all changes are off. |
commands_channel_id |
integer | none | One more channel where the bot answers slash commands. 0 or no value means none. |
blowup_user_id |
integer | 0 |
The user that /blowup pings. 0 turns /blowup off. |
interval |
integer | 10 |
Seconds between host readings. The minimum is 5. |
edit_every |
integer | 60 |
Seconds between edits of a message without images. The minimum is 5. An [[outputs]] block can set its own value. |
image_every |
integer | 60 |
Seconds between image uploads of a message with images. The minimum is 60. An [[outputs]] block can set its own value. |
state_file |
string | "/data/state.json" |
The file that keeps the message ids, the chart time span, the task run times, and the learned upload budget across restarts. |
timezone |
string | "UTC" |
An IANA time zone name, for example "America/New_York". The bot uses it for the time stamps on the images, the next run of each task, and the clock times of edits and uploads. |
green_ok |
boolean | false |
true shows a green dot on rows with no problem. false shows a dark square. |
order |
list of section names | all sections, in the order of Sections | The order of the sections, top to bottom, for example ["system", "services"]. Each name can appear once. Sections that are not in the list follow in the default order. |
[[outputs]] |
array of tables | none, required | One block for each status channel. See Outputs. |
Keep state_file at its default. It must be in the /data volume that compose.yaml mounts. The chart history (history.db), the Top Apps history (apps.json), and the generated preview login (preview-login.json) go in the same directory.
The other keys are tables. Each one has its own section on this page:
-
[render],[history],[system], and[host]. -
[thresholds],[perms.*], and[restarts]. -
[tasks],[services],[ups],[netdata], and[jellyfin]. -
[[forges]],[helper],[node_updates.*], and[preview].
An output is the status panel in one channel. Add one [[outputs]] block for each channel. You must have at least one block. The first output is also the one that /status shows.
[[outputs]]
channel = 123456789012345678
[outputs.render]
docker = "text"
[[outputs]]
channel = 234567890123456789
sections = ["system", "history"]
image_every = 1800
edit_every = 120| Key | Type | Default | Description |
|---|---|---|---|
channel |
integer | none, required | The channel id. Each output needs a different channel. |
sections |
list of section names | all sections | The sections to show, at least one, each name once. They appear in the order setting order, not in the order of this list. |
image_every |
integer | the top-level image_every
|
Seconds between image uploads in this channel. The minimum is 60. |
edit_every |
integer | the top-level edit_every
|
Seconds between edits in this channel. The minimum is 5. |
[outputs.render] |
table | {} |
A mode for each section in this channel. It overrides the top-level [render] for the sections that it names. |
The panel of one output can use more than one message. A Discord message holds at most 10 embeds and 6000 characters of embed text. The bot puts each section in one embed and packs the sections in order into as few messages as possible. A section that is too long for one message gets cut, and its last row shows ...and N more. When the panel needs fewer messages, the bot deletes its extra messages.
| Name | Title | Contents |
|---|---|---|
system |
the [system] title
|
Host name, Unraid version, uptime, CPU, RAM, CPU temperature, load, disk I/O, network I/O, array use, the UPS, alerts, tasks, Jellyfin streams, and the node status. In image mode, the disk usage bars follow the System summary in the same image. |
history |
History | CPU, RAM, disk I/O, and network I/O charts. In image mode, they are an image of their own. |
disks |
Disks | The array state, and the temperature, used and free space, read and write rates, busy percent, errors, and SMART health of each disk. |
services |
Services | The results of the service checks. Checks with no container go under an External Checks heading. |
docker |
Docker Containers | The engine version, the number of containers that run, and the state of each Compose stack. |
apps |
Top Apps | The ten busiest containers, VMs, and processes by CPU and by RAM, each with a trend line. |
tasks |
Tasks | The tasks that run now, with the elapsed time, and the tasks that run next. It also shows parity checks and the mover. |
forges |
the forge name | One section for each [[forges]] block. |
The Disks section needs the Unraid emhttp files. The Docker section needs [host] docker_proxy. The Services section appears only when there are service checks or a relay.
The Top Apps trend lines cover the chart time span that /charts window sets. The bot keeps the history of each app in apps.json, so the trend lines survive a restart. A (+) after a name shows that the row adds up more than one instance of that app.
The top-level [render] table sets a mode for each section. A section that the table does not name uses "image". [outputs.render] changes the mode for one output.
[render]
tasks = "embed"
[[outputs]]
channel = 123456789012345678
[outputs.render]
system = "text"The keys are the section names: system, history, disks, services, docker, apps, tasks, and forges. Each value is one of these modes:
-
"image": a PNG image. This is the default for every section. -
"text": a monospace table that fits a phone screen. -
"embed": Discord embed fields.
The Mixed preset on the preview site sets tasks = "embed" and "image" for all other sections.
A message with no image sections changes at each multiple of edit_every seconds on the clock. The bot skips the edit when nothing in the message changed.
A message that holds images changes only when its images upload. The bot sends the text and the images of that message in one request, on the image_every schedule. Because "image" is the default mode, a panel with the default [render] changes on the image_every schedule. A section in "text" or "embed" mode changes on the edit_every schedule only when its message holds no images.
Important
Discord limits the image uploads in one server and does not publish the limit. In a test on 2026-11-14, one upload every 75 seconds (48 an hour) held, and refusals began near 65 uploads in an hour, counted across all channels. Uploads by other bots in the server appear to count too. Thus the bot uploads at most one image set every 75 seconds, for all outputs together. The bot learns the limit from the Discord replies, and that budget can delay an upload more. When Discord refuses an upload, the message keeps its old content and the bot tries again later.
/panel refresh and a change of /charts window ask for new images at once, within the same upload limit.
| Key | Type | Default | Description |
|---|---|---|---|
days |
integer | 7 |
Days of chart history to keep in the data volume, from 1 to 7. It is also the longest span that /charts window and /chartgen accept. |
| Key | Type | Default | Description |
|---|---|---|---|
title |
string | "Unraid System Info" |
The title of the System section. |
subtitle |
string | "" |
Text under the title. Empty shows no text. |
| Key | Type | Default | Description |
|---|---|---|---|
root |
string | "/host" |
The directory in the container that holds the host proc and sys mounts. |
emhttp |
string | "/host/emhttp" |
The directory in the container that holds the Unraid emhttp state files. |
docker_proxy |
string | none | The URL of the Docker proxy. config.example.toml sets "http://docker-proxy:2375". With no value, the bot does not read Docker, the Docker section is gone, and the /docker commands answer Docker is not configured.
|
Keep root and emhttp at their defaults. They must agree with the mounts in compose.yaml.
Each threshold in the [thresholds] table is a pair of numbers, [warning, critical], and the warning value must not be more than the critical value. A single number sets a warning level with no critical level. A value at or above a limit gets that level. Omit a threshold to keep its default.
| Threshold | Default | Applies to |
|---|---|---|
resource |
[80, 90] |
CPU percent, RAM percent, and network percent of link speed. It also sets the bar colors of disk I/O and UPS charge. |
load |
[0.7, 1.0] |
The load average divided by the number of CPU cores. |
iowait |
[13, 24] |
The percent of time that the CPU waits for disk I/O. |
temp |
[80, 95] |
CPU temperature in degrees Celsius. |
disk_full |
[90, 95] |
The used-space percent of each disk and of the whole array. |
hdd_temp |
[45, 55] |
Hard disk temperature in degrees Celsius. |
ssd_temp |
[60, 70] |
SSD and NVMe temperature in degrees Celsius. |
disk_busy |
95 |
The busy percent of each disk and of the busiest disk. The default is a warning only. Use [95, 99] to add a critical level. |
[thresholds]
load = [0.7, 1.0]
disk_busy = [95, 99]The [perms] table holds three tier tables: [perms.view], [perms.operate], and [perms.admin]. Each tier table accepts these keys:
| Key | Type | Default | Description |
|---|---|---|---|
roles |
list of integers | [] |
Discord role ids that get the tier. |
users |
list of integers | [] |
Discord user ids that get the tier. |
[perms.operate]
roles = [123456789012345678]
users = []A higher tier includes the lower tiers. A user or role that no tier lists has no access. See Slash commands for what each tier allows.
| Key | Type | Default | Description |
|---|---|---|---|
allow |
list of strings | [] |
Container names that the /docker commands can restart, as glob patterns, for example ["jellyfin", "*arr"]. The match is case-sensitive. An empty list turns restart off. |
[restarts.groups] |
table | {} |
Named groups of container globs for /docker restart-group. |
A restart group is a name for a set of containers. /docker restart-group restarts all containers in the group.
[restarts]
allow = ["jellyfin*", "reader"]
[restarts.groups]
media = ["jellyfin*", "reader"]Each group value is a list of one or more container globs. Each container that a group selects must also match allow. If one does not match, the command restarts nothing.
The [tasks] table controls the Tasks section.
| Key | Type | Default | Description |
|---|---|---|---|
shown |
integer | 20 |
The maximum number of upcoming tasks in the Tasks section. The minimum is 1. |
ignore |
list of strings | [] |
Task names to hide, as glob patterns, for example ["boot-fim", "backup-*"]. A pattern matches the raw name or the display name. |
long_running_minutes |
number or false
|
60 |
A running task shows in yellow when it runs longer than this, or when its last run took longer. 0 or false turns this check off. |
heavy_kinds |
list of kinds | all four kinds | Kinds of task that always show in yellow while they run. Each kind can appear once. An empty list turns this check off. |
[tasks.rename] |
table | {} |
Maps a raw task name to a display name. |
The yellow color does not add an alert. The kinds are these:
-
parity: a parity check. -
file_integrity: the File Integrity plugin. -
appdata_backup_plugin: the Appdata Backup plugin. -
appdata_backup_script: a User Script that starts an appdata backup.
[tasks]
shown = 20
long_running_minutes = 60
heavy_kinds = ["parity", "file_integrity"]
[tasks.rename]
"backup-appdata.sh" = "Appdata backup"The Tasks section needs the Host helper. Without it, the section shows needs the shazzzbot helper. Watchtower schedules, read from the container command line, appear in the upcoming list without the helper.
The [services] table holds the service checks.
| Key | Type | Default | Description |
|---|---|---|---|
columns |
list of 3 strings | ["Local", "VPN", "Public"] |
The labels of the three stage columns in the Services table. No label can be empty. |
ignore |
list of strings | [] |
Check names to skip, as glob patterns. The bot does not check or show a skipped service. |
public_host |
string | "" |
The public host name or IP address of the server. With it, the bot lists the published ports that the internet can open. It needs a relay. See Relay. |
[[services.checks]] |
array of tables | none | One block for each service check. |
Each [[services.checks]] block accepts these keys:
| Key | Type | Default | Description |
|---|---|---|---|
name |
string | none, required | The name in the Services table. |
container |
string | "" |
The Docker container name. Omit it for an external check. |
url |
string | "" |
The Local stage: an http:// or https:// URL. |
tcp |
string | "" |
The Local stage: a TCP target, host:port or [IPv6]:port. |
vpn_url |
string | "" |
The VPN stage: an http:// or https:// URL. |
vpn_tcp |
string | "" |
The VPN stage: a TCP target. |
public_url |
string | "" |
The Public stage: an http:// or https:// URL. A relay does the check when one is set. |
public_tcp |
string | "" |
The Public stage: a TCP target. A relay does the check when one is set. |
expect |
string | "" |
Text that the HTTP reply must contain. |
every |
integer |
60, the global cycle |
Seconds between checks of this service. The minimum is 1 with a container, and 60 without one. |
Each block needs a container, a URL, or a TCP target. Each stage takes a URL or a TCP target, not both. The Service checks page explains the stages and the table values.
| Key | Type | Default | Description |
|---|---|---|---|
target |
string | "" |
A UPS on a NUT server (Network UPS Tools), as name@host or name@host:port. Empty means no UPS. |
[ups]
target = "ups@192.0.2.20:3493"The default port is 3493. Put an IPv6 address in brackets, for example ups@[2001:db8::1]. The bot does not log in to NUT and only reads values.
The System section then shows a UPS row with the charge, the runtime that is left, and the status. The row is critical for Low battery, Replace battery, Forced shutdown, Off, and Overloaded. It is a warning for On battery, Alarm, Bypass, Waiting for data, an unknown status, or no data.
With the Host helper and its ups_shutdown action, the row also shows the shutdown setting of the Unraid NUT plugin, for example (off at 20%).
| Key | Type | Default | Description |
|---|---|---|---|
url |
string | "" |
The URL of a Netdata agent, for example "http://192.0.2.10:19999". Empty turns Netdata off. |
ignore |
list of strings | [] |
Netdata alarms to leave out, as "chart alarm" glob patterns. The match is case-sensitive. |
When url is set, the bot adds the raised Netdata alarms to its own alerts. If Netdata does not answer, the alert list shows Netdata unreachable.
To leave out an alarm, add a glob for its chart and alarm name to ignore. That is the text before the value in the alert:
[netdata]
url = "http://192.0.2.10:19999"
ignore = ["/var/lib/netdata disk_space_usage"]| Key | Type | Default | Description |
|---|---|---|---|
url |
string | "" |
The URL of a Jellyfin server. Empty turns Jellyfin off. |
When url is set, the System section shows the number of active streams, by direct play, direct stream, and transcode. It needs JELLYFIN_KEY in .env, or the bot does not start. Get the key from the Jellyfin dashboard, under API Keys.
A forge section shows CI results, recent runs, runners, pushes, and open issues for repositories on Forgejo or GitHub. You can add up to three [[forges]] blocks, each with up to ten repositories. The bot reads each forge every 60 seconds and only reads.
[[forges]]
type = "forgejo"
name = "Home Forge"
url = "https://forge.example.com"
token_env = "FORGEJO_TOKEN"
repos = ["owner/app", "owner/site"]
show = ["ci", "runs", "pushes", "issues"]
system_row = true
pinned = ["owner/site"]
[forges.filter]
ci = ["owner/app"]
[[forges]]
type = "github"
token_env = "GITHUB_TOKEN"
repos = ["owner/repository"]| Key | Type | Default | Description |
|---|---|---|---|
type |
string | none, required |
"forgejo" or "github". |
url |
string | GitHub: "https://api.github.com"
|
The server URL, http:// or https://, without /api/v1, a login, or a query. Forgejo has no default, so a Forgejo block needs it. |
token_env |
string | none, required | The name of the environment variable that holds the API token. Put the token in .env. If the variable is not set, the bot does not start. |
repos |
list of strings | none, required | One to ten repositories, as owner/name, each once. |
show |
list of tables | all tables of the type | The tables to show, each once. See the next list. |
name |
string |
"Forgejo" or "GitHub"
|
The section title, 1 to 64 printable characters. Do not put a private host address here. |
system_row |
boolean | false |
true adds a short summary row to the System section. Only one forge can set it. |
latest |
integer | 5 |
The maximum number of items in each table, from 1 to 5. |
pinned |
list of strings | [] |
Repositories from repos that the tables show first. They count toward latest, so the list can have at most latest items. The Recent runs table ignores it. |
[forges.filter] |
table | {} |
Limits a table to some of the repositories. Each key is a table in show: ci, runs, pushes, or issues. Each value is a list of one or more repositories from repos. A table that the filter does not name uses all repositories. |
The show tables are these:
-
ci: the latest CI result of each repository. -
runs: the recent CI runs, with the result and the duration. -
pushes: the three latest commits of each repository. -
issues: the number of open issues and pull requests. -
runners: Forgejo only. This version always showsrunner API unavailablein this table, as a warning.
The [helper] table connects the bot to the Host helper. Omit the table to run without the helper.
| Key | Type | Default | Description |
|---|---|---|---|
socket |
string | none, required | The absolute path of the helper socket inside the bot container, at most 107 bytes. Compose mounts the helper socket directory at /run/shazzzbot-helper. |
key_env |
string | none, required | The name of the environment variable that holds the helper key. The key must be 32 or more letters, digits, _, or -. If the variable is not set or the key is not valid, the bot does not start. |
[helper]
socket = "/run/shazzzbot-helper/helper.sock"
key_env = "SHAZZZBOT_HELPER_KEY"/update shows the version of each node and an update command for it. A node is the bot, each relay, or the host helper. The bot only prints the command. It never runs it.
[node_updates.bot]
hostname = "server.example.com"
run_on = "workstation.example.com"
command = "cd ~/shazzzbot && ./deploy.sh server"
[node_updates.helper]
hostname = "server.example.com"
command = "bash setup.sh --upgrade"The table name is bot, helper, or the exact RELAY_URL of a relay, in quotes. Each table accepts these keys:
| Key | Type | Default | Description |
|---|---|---|---|
hostname |
string | none, required | The host name or IP address of the node. |
run_on |
string | the hostname value |
The host where you paste the command. |
command |
string | none, required | One line of ASCII text, 1 to 1000 characters, with no code fences. |
The [preview] table starts a web page in the bot container. The page shows the panel from the live data of the bot, and has the settings editor of the preview site. It needs a login. Omit the table, or set enabled = false, to turn the page off.
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | false |
true starts the page. |
bind |
string | "0.0.0.0" |
The IP address in the container that the page listens on. |
port |
integer | 8780 |
The port that the page listens on, from 1024 to 65535. |
user |
string | "" |
The login name: letters, digits, _, ., or -, at most 64 characters. |
password_hash |
string | "" |
The hash of the login password. |
Set user and password_hash together, or neither. To make the hash, run docker compose exec shazzzbot python -m shazzzbot.preview hash in the folder that holds compose.yaml, then type a password of 12 or more characters. The password does not show as you type. With neither key, the bot makes a login. It then logs the user name and the path of a file in the data volume that holds the password.
compose.yaml publishes no port for the page. To reach it, add a ports entry to the shazzzbot service, on one host address only.
The editor can save the display settings to config.toml: timezone, green_ok, order, [system], [render], [thresholds], [tasks], and [services] columns. Compose mounts config.toml read-only, so a save then fails with config.toml is read-only to the bot.
The bot reads secrets only from the environment, that is from .env. A secret in config.toml stops the bot with an unknown setting error.
| Variable | Description |
|---|---|
DISCORD_TOKEN |
Required. The bot token. |
JELLYFIN_KEY |
The Jellyfin API key. Needed when [jellyfin] url is set. |
RELAY_URL, RELAY_KEY, and RELAY_FINGERPRINT
|
The relay settings. Set all three or none. RELAY_URL must start with https://, and RELAY_FINGERPRINT must be 64 hex digits. Add _2, _3, and so on for more relays. See Relay. |
SHAZZZBOT_HELPER_KEY |
The host helper key. The [helper] table names this variable in key_env. |
the token_env of each forge |
The API token of that forge. |
SHAZZZBOT_CONFIG |
The path of config.toml in the container. The default is /config/config.toml. |
SHAZZZBOT_ROLE |
bot or relay. The default is bot. |
compose.yaml also reads these variables from .env or from the shell:
| Variable | Default | Description |
|---|---|---|
SHAZZZBOT_INSTALL_ID |
none, required | The install id that setup.sh writes. Compose labels the containers and the network with it. |
HELPER_SOCKET_DIR |
none, required | The host directory of the helper socket. The bot mounts it read-only at /run/shazzzbot-helper. Docker makes the directory when it does not exist. .env.example sets /tmp/shazzzbot-helper/socket. |
HELPER_GID |
10001 |
A group id that the bot container joins. It must agree with socket_gid in the helper configuration. |
EMHTTP_DIR |
/var/local/emhttp |
The Unraid array, disk, and SMART state on the host. |
SHAZZZBOT_DOCKER_SOCK |
/var/run/docker.sock |
The Docker socket that the docker-proxy container mounts. |