Magnemite
Deployment

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.

SettingDefaultMinWhat it does
Heartbeat20s5How often a box reports in. Lives on the boxes; an agent too old to read it keeps beating at 20s.
Offline after70s30Silence before a box is marked offline. Three missed beats plus a margin.
Concurrent installs101Fleet-wide cap on boxes downloading or installing at once. A group can lower it for its own boxes.
Concurrent agent updates51How many boxes may swap their agent binary at once — the fleet reconnecting after a hub deploy.
Concurrent config rewrites41How many boxes may have their MITM config rewritten at once when their workers change. Each restarts that box's scanner.
Job stalls after900s1Silence from a box mid-job before that job is re-queued to it.
New boxes settle for120s0Wait after a box boots before its group's join installs start. 0 installs on enrolment.

Metric history

SettingDefaultMinWhat it does
Device metrics → Sample every60sheartbeatCPU, memory, storage, temperature and per-app usage, off the heartbeat.
Device metrics → Keep for7 days00 turns device recording off and drops what is stored on the next prune.
Scanner (Rotom) → Sample every60sRotom syncWorkers, request rate and the four Rotom flags, off the Rotom sync.
Scanner (Rotom) → Keep for7 days00 turns scanner recording off. The Scanner column and the rules are unaffected.
Connectivity → Keep for30 days0Every 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.

SettingDefaultMinWhat it does
Enabledoff—The master switch. Off, so upgrading a fleet never starts remediating by itself.
Ask Rotom every10s5Resolution 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 after600s2 × syncSilence from Rotom before a box counts as disconnected. Measured against the sync interval above.
After a reboot600s60No action on a box for this long after we rebooted it, so a slow boot is not rebooted again.
After a hub restart180s0Nothing acts for this long after the hub starts, while the whole fleet reconnects.
Actions per box per hour41Circuit breaker. Past it, the box is alerted about and left alone.
Reboots per box per day61The same, for the expensive half of the ladder.
Keep history for30 days0Monitor 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.

FieldDefaultWhat it does
EnabledoffNew and seeded rules both arrive off.
Applies toBOXBoxes 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.
SignalSERVICE_DOWNWhat the rule watches.
ProbenoneThe command sent to the boxes, when the signal needs one.
Groupfleet-wideA group-scoped rule replaces the fleet-wide one for that group rather than stacking.
Threshold1Consecutive bad samples before the ladder starts. 1 acts immediately.
Cooldown300sAfter an action, how long before this rule may act on that box again.
WindownoneHH:MM–HH:MM. Alerts still go out outside it; only the actions are held.
Announce itall channelsWhich channels this rule announces to. Nowhere still acts — quietly.
SeverityWARNHow loud it looks: the Discord colour, ntfy's priority, whether a browser notification sticks. Not a filter.
All-clearoffA second message on the same channels when the fault clears.
Probe timeout10sBudget on the box. A probe that runs out reads as unknown, which neither counts nor resets.
Log lines200For the log-scanning probes: how much tail is read.
Fail at1How 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 signalStatus
HOST_MEMORY_LOWFree memory plus free swap under the rule's percentage floor
HOST_DISK_LOWFree space under that floor, on docker's own filesystem
HOST_DOCKER_DOWNReads unknown — the daemon's health is not plumbed to the hub yet
REDROID_INSTANCE_DOWNReads 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.

SettingDefaultMinWhat it does
Discord webhookempty—Empty turns Discord off. Nothing else stops.
Role to pingempty—Pinged on CRITICAL monitoring alerts only. Empty pings nobody.
ntfy serverhttps://ntfy.sh—The public ntfy, or your own. The topic beside it is what decides whether ntfy is used at all.
ntfy topicempty—Empty turns ntfy off. On ntfy.sh anyone who guesses the topic reads every message, so pick a name nobody would.
ntfy access tokenempty—Only for a protected or reserved topic. Empty publishes anonymously.
Web pushoff—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 after30 min0Monitoring 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.

EventDefaultStored as
Auto-update started a rolloutonnotify.event.rolloutAutoStarted
Rollout or manual deploy startedonnotify.event.rolloutStarted
Rollout finishedonnotify.event.rolloutSucceeded
Rollout finished with failuresonnotify.event.rolloutFailed
Canary failed, rollout pausedonnotify.event.rolloutPaused
New build foundoffnotify.event.buildDiscovered
Build failed to downloadonnotify.event.buildCacheFailed
Version source failingonnotify.event.sourceFailing
Machine set uponnotify.event.machineReady
Machine setup failedonnotify.event.machineSetupFailed
Containers failed to applyonnotify.event.convergeFailed
New box failed its group installonnotify.event.joinInstallFailed
Agent update failedonnotify.event.agentUpdateFailed

Settings → Apps

Per app target. This is the auto-update policy: what happens on its own when a source publishes a build.

FieldDefaultWhat it does
EnabledonWhether the target is tracked at all.
Architecturearm64-v8aOnly versions built for this ABI are tracked.
Automatic rolloutsoffOff, so a discovered build waits for somebody.
Auto-approveoffMark new versions approved on discovery instead of by hand.
Canary devices1Updated first; the rest wait for them to succeed.
Soak30 minWait after the canary succeeds before releasing the fleet.
Max concurrencyinheritsEmpty falls back to Tuning → Concurrent installs.
Attempts3Tries per device before the job is failed.
Retry backoff60sHold on a failed job, doubling per attempt, capped at 30 minutes by the hub.
Cooldown0 minSince this app's last automatic rollout finished. 0 ships a new build as soon as it is found.
WindownoneHH:MM–HH:MM. Automatic rollouts only dispatch inside it.
Sourcesall tickedWhich 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

FieldDefaultWhat it does
EnabledonWhether the feed is polled.
Priority100Lowest wins when two feeds list the same build. The build is stored once; this picks whose URL.
Check every15 minPer feed, because the feeds are somebody else's servers and set their own polite interval.
Base URLemptyOnly 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

FieldDefaultWhat it does
Pre-install hooknoneRoot shell before the install session opens. Typically am force-stop <pkg>.
Post-install hooknoneRoot shell after the install verifies. Typically restarts the scanner.
Max concurrencyinheritsOverrides Tuning → Concurrent installs for this group's boxes.
MITM packagenoneThe MITM this group runs. Drives the MITM column and what a config write targets.
MITM labelpackage tailColumn header. Falls back to the last dotted segment of the package name.
Config pathnoneWhere the file goes on the box, e.g. /data/local/tmp/aegis_config.json.
ConfigemptyThe template written per box, with device placeholders substituted first.

Settings → Enrollment

FieldDefaultWhat it does
Auto-approveonBoxes enrolling with this token skip manual approval.
Max usesunlimitedEmpty means no cap.
ExpiresneverEmpty 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

FieldPre-filledNote
Canary devices1
Soak15 minShorter than the app target's 30, because somebody is watching this one.
Attempts3
Retry backoff60sDoubles per attempt, capped at 30 minutes.
Force a clean installoffWipes 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 versiononUncheck to force a reinstall everywhere.

Manual deploy

FieldPre-filledNote
Write the group's configonOnly 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.
Hooksworked outNORMAL 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 cleanoff
Canary · soak0 · 0A manual deploy is the boxes you picked, all at once.
Skip boxes already on this versionoffThe 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.

On this page