Settings reference
Every value the dashboard lets you set, and what it is before you touch it.
The counterpart to the environment reference. Nothing here is an env var: it is stored in the database and changed from Settings, or from the form that starts the thing it governs. A fresh install already has every value below, the defaults are what the fleet runs on until you decide otherwise.
Two kinds of default live on this page, and the difference matters when a number surprises you:
- Stored defaults: held in the database. Changing one changes the fleet.
- Form defaults: what a field is pre-filled with. They apply to the one rollout you are starting, and the next form opens pre-filled the same way again.
Settings → Tuning
Fleet-wide, read by the hub on every scheduler tick. A save takes effect within
seconds: the dashboard tells the hub to drop its cached copy. The heartbeat
is the one that does not live on the server — it rides the welcome, so a save
that changes it pushes a fresh welcome to every connected box, and a box that
was offline adopts it when it comes back.
| Setting | Default | Min | What it does |
|---|---|---|---|
| Heartbeat | 20s | 5 | How often a box reports in. Lives on the boxes; an agent too old to read it keeps beating at 20s. |
| Offline after | 70s | 30 | Silence before a box is marked offline. Three missed beats plus a margin. |
| Concurrent installs | 10 | 1 | Fleet-wide cap on boxes downloading or installing at once. A group can lower it for its own boxes. |
| Concurrent agent updates | 5 | 1 | How many boxes may swap their agent binary at once — the fleet reconnecting after a hub deploy. |
| Concurrent config rewrites | 4 | 1 | How many boxes may have their MITM config rewritten at once when their workers change. Each restarts that box's scanner. |
| Job stalls after | 900s | 1 | Silence from a box mid-job before that job is re-queued to it. |
| New boxes settle for | 120s | 0 | Wait after a box boots before its group's join installs start. 0 installs on enrolment. |
Metric history
| Setting | Default | Min | What it does |
|---|---|---|---|
| Device metrics → Sample every | 60s | heartbeat | CPU, memory, storage, temperature and per-app usage, off the heartbeat. |
| Device metrics → Keep for | 7 days | 0 | 0 turns device recording off and drops what is stored on the next prune. |
| Scanner (Rotom) → Sample every | 60s | Rotom sync | Workers, request rate and the four Rotom flags, off the Rotom sync. |
| Scanner (Rotom) → Keep for | 7 days | 0 | 0 turns scanner recording off. The Scanner column and the rules are unaffected. |
| Connectivity → Keep for | 30 days | 0 | Every stretch a box was out of contact, and what it was. One row per episode rather than one per beat, so months of it is cheap. 0 turns it off. |
The heartbeat is what the others are measured in
Offline after ≥ 3 × heartbeat is refused on the card above, at the default 20s beat that is 60s, and anything tighter flaps a box offline on one dropped beat.
Settings → Monitoring
Stored under a monitor. prefix and read by the evaluation pass. The master
switch and the rules are on this tab; the numbers are the How it watches
card on Settings → Tuning, beside the heartbeat they are measured against.
Most are ceilings rather than features — monitoring
has the why for each.
| Setting | Default | Min | What it does |
|---|---|---|---|
| Enabled | off | — | The master switch. Off, so upgrading a fleet never starts remediating by itself. |
| Ask Rotom every | 10s | 5 | Resolution of everything Rotom-shaped, at one request per interval for the whole fleet. It floors Metric history → Scanner (Rotom) → Sample every, which is what decides how much is kept. Ignored when the Rotom integration is off. |
| Rotom stale after | 600s | 2 × sync | Silence from Rotom before a box counts as disconnected. Measured against the sync interval above. |
| After a reboot | 600s | 60 | No action on a box for this long after we rebooted it, so a slow boot is not rebooted again. |
| After a hub restart | 180s | 0 | Nothing acts for this long after the hub starts, while the whole fleet reconnects. |
| Actions per box per hour | 4 | 1 | Circuit breaker. Past it, the box is alerted about and left alone. |
| Reboots per box per day | 6 | 1 | The same, for the expensive half of the ladder. |
| Keep history for | 30 days | 0 | Monitor history retention. 0 keeps it forever. |
The pass itself has no interval to set: it runs once per heartbeat, because it reads what the boxes last said and they say it once per beat.
Where the alerts go — the channels, and how often the same alert may repeat — is Settings → Notifications, with everything else the fleet announces. Which channels a rule uses is picked on the rule.
A monitoring rule
From Settings → Monitoring → Add rule. Seeded rules are written disabled, and only onto a fleet that has none.
| Field | Default | What it does |
|---|---|---|
| Enabled | off | New and seeded rules both arrive off. |
| Applies to | BOX | Boxes or machines, never both. A box rule evaluated against a VPS would read a missing scanner as a fault on a machine that never ran one, and act on it. |
| Signal | SERVICE_DOWN | What the rule watches. |
| Probe | none | The command sent to the boxes, when the signal needs one. |
| Group | fleet-wide | A group-scoped rule replaces the fleet-wide one for that group rather than stacking. |
| Threshold | 1 | Consecutive bad samples before the ladder starts. 1 acts immediately. |
| Cooldown | 300s | After an action, how long before this rule may act on that box again. |
| Window | none | HH:MM–HH:MM. Alerts still go out outside it; only the actions are held. |
| Announce it | all channels | Which channels this rule announces to. Nowhere still acts — quietly. |
| Severity | WARN | How loud it looks: the Discord colour, ntfy's priority, whether a browser notification sticks. Not a filter. |
| All-clear | off | A second message on the same channels when the fault clears. |
| Probe timeout | 10s | Budget on the box. A probe that runs out reads as unknown, which neither counts nor resets. |
| Log lines | 200 | For the log-scanning probes: how much tail is read. |
| Fail at | 1 | How many pattern matches count as a failure. |
Applies to also decides which signals the rule can carry. Nine are about a
box — the five it reports about itself, AGENT_OFFLINE, and the three Rotom
ones — and four are about a machine:
| Host signal | Status |
|---|---|
HOST_MEMORY_LOW | Free memory plus free swap under the rule's percentage floor |
HOST_DISK_LOW | Free space under that floor, on docker's own filesystem |
HOST_DOCKER_DOWN | Reads unknown — the daemon's health is not plumbed to the hub yet |
REDROID_INSTANCE_DOWN | Reads unknown — per-instance container state is not read by rules yet |
AGENT_OFFLINE works on either side with no extra signal, because a machine is
a Device and its socket goes quiet the same way a box's does. The one action
only a host rule has is RESTART_INSTANCE, which restarts a single container
from the machine that runs it — REBOOT is deliberately not overloaded to mean
"reboot the VPS".
Not selectable from the form yet
The four host signals and RESTART_INSTANCE are in the schema, and the hub evaluates the two with
data behind them. The vocabulary the rule dialog is built from has not learned them, so there is
no way to write one from Settings → Monitoring today.
Settings → Notifications
Stored under a notify. prefix, and read by the hub whenever it has something
to say — a monitoring alert, or one of the fleet events below.
Notifications has the why for each.
| Setting | Default | Min | What it does |
|---|---|---|---|
| Discord webhook | empty | — | Empty turns Discord off. Nothing else stops. |
| Role to ping | empty | — | Pinged on CRITICAL monitoring alerts only. Empty pings nobody. |
| ntfy server | https://ntfy.sh | — | The public ntfy, or your own. The topic beside it is what decides whether ntfy is used at all. |
| ntfy topic | empty | — | Empty turns ntfy off. On ntfy.sh anyone who guesses the topic reads every message, so pick a name nobody would. |
| ntfy access token | empty | — | Only for a protected or reserved topic. Empty publishes anonymously. |
| Web push | off | — | Notifications in the dashboard's own browsers. Needs a VAPID keypair in the hub's environment; without one the card says so and the switch is unavailable. |
| Repeat an alert after | 30 min | 0 | Monitoring alerts only: the same signal on the same box is not re-announced inside this. Fleet events are said once each and need no window. |
The three channels are independent: each has its own credential, and one being broken says nothing about the other two. What they share is Repeat an alert after — the point of that window is that a flapping box does not announce itself thirty times, and a window per channel would let it do exactly that by taking turns.
None of them has a minimum severity. Which monitoring rules reach which channel is picked on each rule, in Announce it, and which fleet events do is the matrix below. There used to be a floor per channel as well, and two filters in series meant a rule set to announce could silently not — with nothing on the rule to say why. One filter, where the person looking for the reason is already looking.
The VAPID keypair for web push is the one notification credential that is
not here. A push service only accepts a message signed by the key it saw
when the browser subscribed, so it is deployment identity rather than a knob:
it goes in the hub's environment as VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY and
VAPID_SUBJECT, generated once with npx web-push generate-vapid-keys.
Regenerating the pair silently unsubscribes every browser, which is exactly why
there is no button for it.
Fleet events
One row per event, each stored as notify.event.<id>: the list of channels it
goes to, empty for off. A fresh install has every event on every channel except
New build found, which is off — a channel nobody has set up sends nothing
anyway, so "on" costs a fleet with no channels nothing.
| Event | Default | Stored as |
|---|---|---|
| Auto-update started a rollout | on | notify.event.rolloutAutoStarted |
| Rollout or manual deploy started | on | notify.event.rolloutStarted |
| Rollout finished | on | notify.event.rolloutSucceeded |
| Rollout finished with failures | on | notify.event.rolloutFailed |
| Canary failed, rollout paused | on | notify.event.rolloutPaused |
| New build found | off | notify.event.buildDiscovered |
| Build failed to download | on | notify.event.buildCacheFailed |
| Version source failing | on | notify.event.sourceFailing |
| Machine set up | on | notify.event.machineReady |
| Machine setup failed | on | notify.event.machineSetupFailed |
| Containers failed to apply | on | notify.event.convergeFailed |
| New box failed its group install | on | notify.event.joinInstallFailed |
| Agent update failed | on | notify.event.agentUpdateFailed |
Settings → Apps
Per app target. This is the auto-update policy: what happens on its own when a source publishes a build.
| Field | Default | What it does |
|---|---|---|
| Enabled | on | Whether the target is tracked at all. |
| Architecture | arm64-v8a | Only versions built for this ABI are tracked. |
| Automatic rollouts | off | Off, so a discovered build waits for somebody. |
| Auto-approve | off | Mark new versions approved on discovery instead of by hand. |
| Canary devices | 1 | Updated first; the rest wait for them to succeed. |
| Soak | 30 min | Wait after the canary succeeds before releasing the fleet. |
| Max concurrency | inherits | Empty falls back to Tuning → Concurrent installs. |
| Attempts | 3 | Tries per device before the job is failed. |
| Retry backoff | 60s | Hold on a failed job, doubling per attempt, capped at 30 minutes by the hub. |
| Cooldown | 0 min | Since this app's last automatic rollout finished. 0 ships a new build as soon as it is found. |
| Window | none | HH:MM–HH:MM. Automatic rollouts only dispatch inside it. |
| Sources | all ticked | Which feeds are polled for this package. A target with none selected is never polled. |
A target created by a manual upload is marked manual and has none of
this: no source to poll, no policy. The Fleet and Versions pages skip it, while
the boxes still report its installed version like any other.
Settings → Version sources
| Field | Default | What it does |
|---|---|---|
| Enabled | on | Whether the feed is polled. |
| Priority | 100 | Lowest wins when two feeds list the same build. The build is stored once; this picks whose URL. |
| Check every | 15 min | Per feed, because the feeds are somebody else's servers and set their own polite interval. |
| Base URL | empty | Only needed when the index lists bare filenames. The UnownHash mirror wants https://mirror.unownhash.com/apks/ — with that path, or every download 404s. |
A feed can be working perfectly and still show zero versions, because the other feed won every build on priority. That is what this page is for.
Settings → Groups
| Field | Default | What it does |
|---|---|---|
| Pre-install hook | none | Root shell before the install session opens. Typically am force-stop <pkg>. |
| Post-install hook | none | Root shell after the install verifies. Typically restarts the scanner. |
| Max concurrency | inherits | Overrides Tuning → Concurrent installs for this group's boxes. |
| MITM package | none | The MITM this group runs. Drives the MITM column and what a config write targets. |
| MITM label | package tail | Column header. Falls back to the last dotted segment of the package name. |
| Config path | none | Where the file goes on the box, e.g. /data/local/tmp/aegis_config.json. |
| Config | empty | The template written per box, with device placeholders substituted first. |
Settings → Enrollment
| Field | Default | What it does |
|---|---|---|
| Auto-approve | on | Boxes enrolling with this token skip manual approval. |
| Max uses | unlimited | Empty means no cap. |
| Expires | never | Empty means no expiry. |
A device is approved: false until a token says otherwise or somebody approves
it, and a new user account is a VIEWER.
The deploy forms
Per rollout, not stored. These are deliberately not the app target's policy — a rollout started by hand, watched, is a different decision from one that fires at 4am.
Fleet → Deploy
| Field | Pre-filled | Note |
|---|---|---|
| Canary devices | 1 | |
| Soak | 15 min | Shorter than the app target's 30, because somebody is watching this one. |
| Attempts | 3 | |
| Retry backoff | 60s | Doubles per attempt, capped at 30 minutes. |
| Force a clean install | off | Wipes app data on every targeted box. The agent already falls back to it when Android rejects an in-place upgrade. |
| Skip devices already on this version | on | Uncheck to force a reinstall everywhere. |
Manual deploy
| Field | Pre-filled | Note |
|---|---|---|
| Write the group's config | on | Only offered when the app being installed is some group's MITM. Writing the config is the point of deploying one, and there is no other way for an edited config to reach the boxes — so it is on, and it asks for confirmation before the install starts. |
| Hooks | worked out | NORMAL when the group's MITM is already on a picked box, POST_ONLY when this deploy is what puts it there, NONE otherwise. A guess, and overridable. |
| Force clean | off | |
| Canary · soak | 0 · 0 | A manual deploy is the boxes you picked, all at once. |
| Skip boxes already on this version | off | The opposite of the fleet form, and offered rather than forced: the same version going out again by hand usually means the first build was wrong, and the version string cannot tell a rebuild from a repeat. |
Where a value lives
.env is what the stack needs to boot and connect — database, secrets,
ports, URLs. Everything on this page is what is tuned while it runs, wants
changing without a deploy, and is the same in every environment.
Several values moved from the first to the second, and the old variables are
now ignored in silence: a leftover AGENT_UPDATE_CONCURRENCY or
DEVICE_OFFLINE_TIMEOUT in .env does nothing at all.
Changing a setting straight in the database
The hub caches these and is told to drop its copy when the dashboard saves. Nothing polls. A value
written directly with psql or mysql therefore needs a hub restart, or one Save from the
dashboard, before the hub runs on it.