What you are looking at
One box for the server and one for every node. A line between two boxes is a real connection: blue is the link a node already holds to the server, and green is a tunnel riding over that link. A line turns red when it cannot work right now — usually because a machine it passes through is offline.
Ports
Every circle is a port. Hollow means nothing is plugged into it; filled means it is carrying something. Each one is labelled in or out, except the uplink jacks, which are the node's link to the server. A node keeps its ports on the side facing the server, and they swap sides if you drag it past it.
Rolled up, a box shows a single dark port instead — everything it carries meets there. The server keeps one at each end, since it has nodes on both sides of it.
Finding something to tunnel
Press NET on a node to slide out the network that node can see. Click a host to reveal its open ports. If nothing has been scanned yet, use the Discover tab on the right to scan that node's network first.
Building a tunnel
Drag a discovered port onto the server. That parks it as an input: the traffic can now reach the collective over the node's existing link, so no port is opened and none is asked for. The input shows an out port marked not connected underneath it.
Drag that out port onto whichever machine you want the port to appear on, and choose the port number there. That machine can be the server itself or any node. The machine now shows an in port for it, named by the port it opened.
Changing and removing
Drag a port that already has a cable on it onto a different machine to move that tunnel there. Drop it on empty space instead and it is removed — you are asked first. Clicking a cable removes it too. Removing an input removes everything it feeds; removing one delivery leaves the input parked, ready to be sent somewhere else.
Bending a line out of the way
Drag any cable and it drops a pin where you took hold of it. Move the pin and the line runs straight through it — a pinned cable is routed by hand, so it is drawn as straight runs rather than a curve, while its two ends still curve away from their ports. A pin dropped near a port lines itself up with it, so the run into that port comes in level; otherwise it takes the grid. Drag the same cable again for a second pin, and so on. Double-click a pin to take it out. Pins snap to the grid when grid is on, and are kept and restored with the rest of the layout.
Getting around
Drag the background to pan, scroll to zoom, drag a title bar to move a box, and click a title bar to roll it up or open it again. Shift-drag on the background draws a box round several nodes and any pins among them; everything caught lights up, and dragging any one of them moves the whole group.
The buttons, top right
−, the percentage, and + zoom out, reset to 100%, and zoom in. ⤢ frames everything on screen. save keeps this layout — where each box sits, which are rolled up, and the zoom — and restore puts it back; the map restores it by itself whenever you open this view. reset lays everything out fresh without touching what you saved. grid makes boxes snap to a grid as you move them.
Needs attention
Offliners this week
Busiest CPU & GPU now · 24 h peak
Network traffic now · 24 h average
Slow responders round trip to the server
Uptime checks
Signed in
Tunnels
| Node | OS | Status | Communication | Interval | Version | Desktop | SysTray | NET | VPN | Local IP | UP | Last seen |
|---|
| Network lock on for every node until somebody turns it off · while it is on, nothing here can change this machine's route |
|
| Heartbeat interval (seconds) | |
| Random 2–30s | |
| Reconnect interval (s) 0 = stay continuously connected | |
| Auto-update | |
| Temperature interval (s) how often CPU/GPU temperature is read · 0 = off | |
| GUI / system tray run the desktop tray on this node · off when the server is unreachable |
Remote commands
Which account on which node may run commands on another node. On that node, as that account: um tell SOIDGXPPDEV03 'ls -al' — the output comes back as if it ran there. The first one sets up the tunnel (it waits a moment); later ones reuse it; um tell SOIDGXPPDEV03 --quit closes it. Commands run as the account the other node's client runs as. Only the account named here can use it.
A Claude session on that node can run um tell from its shell as it is — no MCP needed. To give Claude the tunnel tools as well: first make a token in Settings → API tokens (Create token, Tunnels read + write or read + write + delete; its secret is shown once), then load the unimatrix MCP server with it.
| From | Account | To | Tunnel |
|---|
Names under this server's domain, each with its own Let's Encrypt certificate, renewed for as long as the name is here. Every name resolves already — a wildcard record points the whole domain at this server — so a certificate normally arrives within seconds of adding one.
| Hostname | Certificate | Issued | Created |
|---|
Add one to give a service its own name and certificate — vpn, say, for the VPN to answer on.
Profiles
| Profile | Network | Clients here | May use | Clients |
|---|
Clients
| Name | Profile | Address | Enabled | Last seen — primary | Last seen — backup |
|---|
Xray — VLESS with REALITY
AmneziaWG
Obfuscation numbers
Profiles
| Profile | Network | Clients here | May use | Clients |
|---|
Clients
On the VPN applies to a node only, and it is the switch that re-routes everything that machine does. Test it first: the node brings the tunnel up, writes down the way back before it changes anything, and undoes all of it by itself unless this server confirms the link survived. If the way in it is using stops working it moves to the other one on its own; Retry starts again from the top.
| Name | Profile | Address | Enabled | On the VPN | Added |
|---|
Exit proxies
A proxy port on a node — SOCKS5 and HTTPS on the same port — whose traffic leaves the internet from its exit: unicomplex, or another node. unicomplex runs on AWS, which charges for traffic; a home or work node as the exit costs nothing. An exit node is reached directly where possible; when that cannot be set up the traffic goes through unicomplex to the same exit (the Last route column says which). If the exit is offline, connections fail — they never fall back to unicomplex.
| Node | Port | Open to | Exit | Login | Status | Last route | Today | 14 days |
|---|
A mount makes a folder on a node appear as an ordinary folder on your own computer, so you can open its files in your editor and file browser. The node serves its files itself, and the node on your computer mounts them with rclone, which it fetches from this server into its own folder the first time; nothing is installed on either machine. Your computer must be a node too: that is where the connection comes out. Where folders land is set on the Settings page. Access is full (root). For one-off transfers, use Files.
New mount
Stop the mount with Ctrl+C (or fusermount -u on the folder).
Mounts
| Files from | Mounted on | State | Password |
|---|
Each node has its own password, kept by this server. New password changes it for every mount of that node: folders already mounted stay mounted, but the next connection needs the new command.
A mount looks after itself: if rclone stops, the folder disappears, or it stops answering (checked every 30 seconds), the node mounts it again. remount does the same on demand.
Uptime checks run from a node against something on its network. Add one from the Map — pick a node, open its Ping tab. The reserved Excluded group keeps a host in the config but stops testing it.
| Name | Host | Method | Via node | Every | Status | Latency | Last online | Missed since | Group |
|---|
Claude Code running on your nodes. Click a session for everything known about it, and its last prompts.
| Node | Account | Name | State | Remote control |
|---|
Sessions that have ended, from what the nodes reported. Only these records are kept here — never what was said in them. Open opens a terminal on your computer with Claude going on where the session left off.
| Account | Name | Title | Last active |
|---|
Claude's login belongs to an account on a node, and every session that account runs shares it. Claude renews its short-lived token by itself; the login itself must be done again (/login in Claude) before the date shown.
| Node | Account | Plan | Log in again by |
|---|
Every tunnel, by the machine it comes out at. Click a tunnel for its details.
| Tunnel | Status | Created |
|---|
A start point is where a tunnel begins: a node, and the host and port it reaches from its own network. One start point can come out at many endpoints. While any tunnel uses a start point it cannot be changed or deleted. These are also the inputs parked on the Map's server box.
| Name | Node | Reaches | Tunnels |
|---|
| Timestamp | Node | Action | Result |
|---|
| Username | Role | Modules | Last seen | From | Country | Signed in | Two-factor |
|---|
Require two-factor for everyone
Account
Now
Modules
Log
| When | About | What |
|---|
Modules
Sign-in
Every platform this server can enroll, and what is built for each right now. A name listed but not built would 404 if an installer asked for it — which is why the missing ones are still shown rather than quietly left out.
| Platform | Arch | File | Version | glibc | Min distro | Size | Built | Checksum |
|---|
Download pinned certificate (unicomplex-cert.der)
Run this on any Linux machine to enroll it as a node (detects arch, installs a root systemd service):
…
The x86_64 build carries the system tray, so it links GTK at load time, and on a headless x86_64 box with no GTK it will not start. The installer handles that itself — before installing anything it runs the client once, and if it will not start it falls back to node-docker-x86_64, the same node built static with the tray compiled out, and says so while it does it. Nothing to pick: the command above is the right one on a desktop and on a headless server alike.
Debug install — writes a log file (for when an install succeeds but the node then won't run)
Same install, with the node's log turned on → /var/log/interlink-node.log. Read it with journalctl -u interlink-node.
…
For a Linux machine where you have an ordinary account but no root. Run it as that account — no sudo anywhere. It installs into ~/.local/um and the node runs as that account.
…
Short form, for typing by hand — and for a network whose firewall re-signs HTTPS (curl says self signed certificate in certificate chain). It saves that firewall's certificate for this account's curl, then runs the same install:
…
Starting at boot. Nobody needs to be logged in: the installer adds two lines to the account's own crontab — at boot, and every 5 minutes — that run ~/.local/um/run.sh. Only one copy ever runs, and it restarts the node whenever it stops. Logs go to ~/.local/um/node.log. A machine with no cron gets a systemd user service instead, which only starts at boot if someone with root has run loginctl enable-linger <account> once; the installer says when that is missing.
Works: tunnels and forwards on ports 1024 and up, hole-punched direct links, the shell
and native terminal, file manager and SFTP (all as that account), uptime checks, network scans, updates
and uninstall from this panel, folder mounts if the machine has fuse3.
Not available: acting as a VPN exit or putting the machine on the VPN, ports below 1024, other
accounts' files, and remote desktop.
From PowerShell — run it as Administrator to install for the whole machine and start at boot; run it as yourself to install for your account and start at logon:
…
From cmd.exe — cmd cannot run a script straight off a pipe, so this fetches one with the curl Windows ships with, runs it and deletes it. This one is cmd syntax: in PowerShell && is not a separator and curl means Invoke-WebRequest, so it will not run there.
…
If the machine cannot reach this server at all — the installers handle a wrong DNS answer themselves, but only once they are running, and fetching one is itself a name lookup. This form carries the address () instead of asking the local resolver. TLS is unaffected: only the route is pinned, the certificate is still checked against the real name.
…
Debug install — writes a log file (for when an install succeeds but the node then won't run)
Same install, with the node's log turned on → node.log in the install dir. Read it with Get-Content …\node.log -Tail 40.
…
Coming soon. macOS enrollment will work the same way as Linux: one command that fetches the client, installs it as a launchd service, and brings the machine into the collective at next login.
Apple silicon cannot be cross-compiled from this project's build host — the SDK is Apple's to hand out, not ours to ship — so the binary has to be built on a Mac and dropped in. The download name is reserved either way, and the Overview tab shows it as not built rather than pretending the platform is unsupported.
curl -fsSL "https://…/install.sh?token=…" | bash
The node, in a container — for a machine where installing a service is not wanted or not possible: a NAS with a read-only system partition, an immutable host, or a box that is only ever managed through its container manager.
This build is static: x86_64 musl, with the system tray compiled out. It links nothing at all, so it starts on any image — including one with no libraries. The ordinary x86_64 Linux build cannot, because the tray links GTK at load time.
On a headless machine you do not have to come here for it — the Linux installer falls back to this same binary by itself when the ordinary one will not start. This tab is for when you want it in a container.
How do you run containers on that machine?
TrueNAS SCALE
Nothing to build and nothing to download first. TrueNAS cannot build an image from its own web interface, so this YAML does not ask it to: it starts a stock Alpine container which fetches the node on first run and keeps it in the data directory, reusing it on every start after that.
Make the dataset first — before you deploy anything. It has to exist, and it has to be on one of your data pools. If the path in the YAML does not exist, Docker quietly creates it on the system filesystem instead, and TrueNAS mounts that noexec. Everything then looks like it worked — the app deploys, the node downloads — and it silently never starts, because a binary on a noexec filesystem cannot be run. The app flaps and ends up stopped, with nothing in any log to say why.
- Find your pool's real name. Storage — the name at the top of each pool. tank is only the usual example; yours is very likely called something else.
- Make the dataset. Datasets → Add Dataset, for example yourpool/apps/unimatrix. This is what keeps the node's identity — without it, it joins as a brand-new machine every redeploy.
- Apps → Discover Apps → the three dots → Install via YAML. Give it a name.
- Paste the YAML and deploy. Change the two lines marked CHANGE ME: the dataset path you just made, and the hostname you want in the roster.
Portainer, Synology, Unraid, or any YAML box
The same single paste as TrueNAS — only the menu differs. Portainer: Stacks → Add stack → Web editor. Synology: Container Manager → Project → Create. Unraid: Docker → Compose.
Make the data directory first, on real storage, and point the volume line at it. If the path does not exist Docker invents it somewhere of its own choosing — on a NAS that is often a system filesystem mounted noexec, where the node downloads perfectly and then cannot be started.
A shell on the machine
With a shell you can build a proper image, which is tidier than bootstrapping at start-up: the node is baked in, so a start needs nothing from the network and nothing is fetched twice.
Save this as Dockerfile — that is the whole recipe. It fetches the binary itself while building, so there is nothing to download first. Alpine rather than an empty image because two of the node's features want a userland: a remote shell spawns bash, and network discovery uses nmap when it is there. Nothing is needed for TLS — the certificate roots are compiled into the binary.
…
Then, in the same folder:
…
And run it with the compose file below.
Change your password
Two-factor sign-in
1. Open the authenticator app and scan this code.
Can't scan? Enter the key by hand:
2. Type the six-digit code the app now shows, to prove it worked.
What you can use
File mounts
{node} becomes the name of the node the files come from, so seg-lab1's files land in /net/seg-lab1. Used for new mounts and for the commands on the File mounts page; a mount already running keeps its folder until you mount it again.
Alerts
A whole site going offline, or a watched laptop switching to battery, is written to the Log and shown as a desktop notification on the computers ticked here (Linux for now), when it starts and again when it is over.
API tokens
A token lets a script read the panel without signing in. Send it as a header: Authorization: Bearer umx_…. It sees only the machines its owner can see, and only the parts ticked here. The token is shown once, when it is made — copy it then.
| Name | Token | Access | Owner | Created | Expires | Last used |
|---|
What a token can reach
Ping — read: GET /api/pings — every check with its status.
Ping — read + write: also POST /api/pings, DELETE /api/pings/<name>,
POST /api/pings/<name>/group.
Any token: GET /api/token — the token's own name, owner, access and expiry.
Nodes — read: GET /api/nodes — the roster with live stats (load, CPU %, memory, GPU % and VRAM, CPU/GPU temperatures, latency); GET /api/nodes/<id>/history?since=<unix>&until=<unix>&limit=<n> — heartbeat samples (the same figures; kept 30 days, up to 20000 per call); GET /api/nodes/<id>/presence — the hours each node was online.
Example: curl -H "Authorization: Bearer umx_…" https://unimatrix.sg/api/pings
Feature requests
An idea, or something you would like changed. Everyone signed in sees the list.
| Request | By | When |
|---|
A user group is a set of people that reaches one or more node groups. Somebody in a user group sees and manages only the nodes inside the node groups it reaches — everything else is not merely hidden from the roster, it is refused if asked for directly.
Granting All grants every node, now and in future — that is the plain way to say "this person sees everything". Every node is in All whatever else it is filed under, so no machine is ever invisible for want of being filed.
An account in no user group is unrestricted and sees every node, which is what every account was before this page existed. Restricting somebody is a deliberate act, so nobody is shut out by surprise.
Accounts
| Account | In user groups | Sees |
|---|
The groups nodes are arranged into — the same ones the node roster and the map use. Click a name to rename it. All is built in: every node is in it, nothing can be taken out of it, and it cannot be removed.
To put nodes in a group, tick them in the node roster and choose Move to group from Actions. Any number at once, and the ticks respect whatever you have filtered or searched for.
| Group | Nodes | Reached by |
|---|