Troubleshooting
Android's missing resolv.conf, small /data partitions, and 35 GB of egress.
DNS on Android
Symptom
The agent never connects, and its log repeats one line:
lookup magnemite.example.com: no such hostMeanwhile the box browses the web perfectly, and ping from adb shell
resolves the same name without trouble.
Why it happens
The agent is built with CGO_ENABLED=0 so it is one static binary that needs no
NDK to cross-compile and no shared libraries on the box. The cost is that Go
stops calling the system resolver and uses its own, written in Go — and that
resolver learns which nameservers to use by reading /etc/resolv.conf.
Android does not have that file. Bionic, Android's libc, asks the netd daemon
over a socket instead, and publishes the current servers as system properties:
adb shell getprop net.dns1So everything on the box resolves names except a CGO-free Go program, which finds no config, has no servers to ask, and fails every lookup.
The fix
It lives in agent/internal/netfix. At startup the agent checks for
/etc/resolv.conf; if the file is missing it installs a net.Resolver whose
dialer walks this list, rotating to the next entry whenever a server refuses a
connection — common on a LAN where the router's resolver black-holes.
| Order | Nameserver | Where it comes from |
|---|---|---|
| 1-4 | net.dns1 … net.dns4 | getprop, i.e. what netd is using |
| 5 | 1.1.1.1 | Fallback |
| 6 | 8.8.8.8 | Fallback |
Consequences worth knowing
On a box whose DNS is genuinely broken, set serverUrl to a bare IP and skip
resolution entirely.
And if you ever rebuild the agent with CGO enabled, this code becomes dead weight — but so does the single-binary cross-compile.
What version is actually running
The Status page carries the version of every piece.
| Card | Reports |
|---|---|
| Dashboard | Its own web= version, Next.js and Node |
| Hub | Its own hub= version, Node, and the agent= it is handing out |
| Database | The Postgres server version and the Prisma client's |
| Public edge | Whatever answers on the boxes' URL, from its Server header |
| Rotom | The scanner versions across the fleet, counted |
Each number comes from the matching line of the repo's
VERSIONfile.
Reading what the box actually printed
The ⋯ menu on a device page has Logcat (.zip) for a snapshot you can keep and Live logs for logcat as it happens. What each contains, and what to look for, is in reading a box's logs.
An agent that will not update
The hub only sends agent_update to a box whose reported version differs from
its target, that is not mid-job, and whose ABI it has a build for. Check
the Status page for the target version and AGENT_AUTO_UPDATE, the
device page for the version that box reports, and the box's own log for
self-update: lines.
Check the full flow in updating the agent.
A MITM installed without its config
The app is on the box and starts, then says its config is missing: Aegis
prints The configuration file aegis_config.json is invalid or inexistent.
The MITM's job on Rollouts says why the hub sent none, as a warning:
| Line on the job | Means |
|---|---|
config not sent: {{device.…}} is empty on <box> | A placeholder had nothing to put in on that box. |
config not written: this box's agent is too old | The agent does not claim write_config. It is told to update itself on its next hello. |
| neither | The group's MITM package is another package, the group has no config path or JSON, or the deploy had the config switched off. |
A box already in that state: Execute → Rewrite MITM config on its device page writes the group's config and restarts the MITM, with nothing reinstalled (an agent of 1.0.5 or later). On an older agent, a Manual deploy of the same MITM build with the config switch left on does it by reinstalling in place.
Small /data
Boxes need roughly 450 MB free: a 172 MB bundle unpacks to ~250 MB and both sit on disk at once. The agent refuses to start an install below that rather than filling the partition. Devices table shows free space per device and warns under 500 MB, when things are already tight.
Egress
A full rollout to 200 boxes is ~35 GB off your server. Settings → Tuning → Max concurrent jobs paces it
Check your traffic allowance
If your ISP or cloud provider has a cap, check it before you start a rollout.
A box enrolled but never appears online?
I know the feeling, such a headache. Check the following:
| Check | How |
|---|---|
| Right URL? | serverUrl must be the hub domain (MAGNEMITE_PUBLIC_URL), not the dashboard's. |
| Right port on Coolify? | The edge domain's port in the Domains tab must be 8090, or the proxy misses Caddy. |
| Approved? | With auto-approve off, a box sits as pending approval and receives nothing. |
| Token still valid? | A revoked enrollment token blocks new enrollments; a revoked device is disconnected. |
| DNS? | See DNS on Android above. |
When the table settles nothing, read the agent's own log. It is written to
/data/adb/magnemite/agent.log — by the Magisk service on a box, by
magnemite-start.sh in a Redroid container — rotated to
agent.log.1 at 2 MB, and it is where enrollment and connection failures
actually print:
# the whole log, and the rotated previous one
adb shell su -c 'cat /data/adb/magnemite/agent.log'
adb shell su -c 'cat /data/adb/magnemite/agent.log.1'
# follow it live while you retry
adb shell su -c 'tail -f /data/adb/magnemite/agent.log'
# just the enrollment and connection lines
adb shell su -c 'grep -E "enroll|connect|reconnecting|connection lost" /data/adb/magnemite/agent.log'| Line | Means |
|---|---|
enrolling with <url> | It read serverUrl — check it is the hub's, not the dashboard |
enrolled as "<name>" | Enrollment worked; the device row exists |
connected to <ws url> | The socket is up — the box should be online |
connection lost / reconnecting in | It reaches the hub but the socket keeps dropping, usually the edge port or proxy |
No log file at all means the module never started. Check it is enabled and that the binary is there:
adb shell su -c 'ls -l /data/adb/modules/magnemite-agent/'
adb shell su -c 'ps -A | grep magnemite'
adb logcat -d | grep -i magnemiteRedroid boxes
Three failures particular to containers:
A new device row on every restart is the serial. A container has no
ro.serialno of its own, so the agent falls back to a MAC that Docker
reassigns on recreate, and the hub — which keys a device on its serial alone —
sees a box it has never met. Give each container an explicit, unique
ro.serialno.
No agent process means init never started the service. Redroid normally
runs SELinux permissive and magnemite.rc sets no seclabel for that reason;
an image that enforces denies the service, and the denial is in logcat:
adb shell 'ps -A | grep magnemite-agent'
adb shell logcat -b all | grep -i magnemite
adb shell 'ls -l /system/etc/init/magnemite.rc /data/adb/magnemite/'Identical load and temperature across boxes is correct. Linux has no
per-container load average and a container has no thermal sensor, so both are
the host's. Uptime and storage are the box's own; memory is too, but only when
the container has a mem_limit.
Downloads 401 at the edge
/files/* is gated by forward_auth against the hub's /internal/authz. A 401
means the device token the box presented is not one the hub recognises, most
often a box restored from a backup image whose device row was deleted. Delete
its config.json device token and let it enroll again.