Argus Fleet

Which machine needs me?

One tile per machine: which are up, what is running on each, and which one is waiting for you — sorted to the top, in the only colour on the board that means something. Click a session name and you are in that terminal, on that machine. It is the board over your Argus instances.

curl -fsSLO https://raw.githubusercontent.com/andreaderuvo/panoptes/master/install.sh less install.sh && bash install.sh panoptes # prints a URL with a token in it

MIT · Python + vanilla JS · no build step · no database · self-hosted

Six machines on one board: five working, one out of disk, one that has stopped calling in, and one asking for you. 1 2 3 4
  1. 1The one asking you something. It sorts to the top and it is the only tile wearing the accent.
  2. 2Sessions, one chip each. Green is asking, amber has finished — click one and you are in it.
  3. 3The fullest disk, and only that one. A board says there is a problem; it does not inventory filesystems.
  4. 4A machine that has stopped calling in. Still there, still red — gone and never-existed must not look alike.
Six machines, one screen. The footer counts what is really there — 4 argus on 2 machines, when three of them are on one box.

When one machine becomes three

Without

  • Three tabs, and you are the polling loop.
  • The agent that finished forty minutes ago finished into an empty room.
  • Which box was the one with the full disk? You open all of them to find out.
  • A machine that is off looks exactly like a machine you forgot you had.

With Panoptes

  • One page worth glancing at, because the whole point is that you are not looking.
  • The one that wants you is at the top, wearing the only accent on the board.
  • Load and the fullest disk on every tile, amber over 74%, red over 90%.
  • A machine that stops answering goes red and stays on the board, saying when it last called in.

It cannot get into anything

This is a page that reads six numbers off each machine, and it is built so that losing it costs you a list of session names.

One read-only key per machine

A watcher token opens GET /api/overview on that machine's Argus and nothing else: no shell, no files, no writes, no proxy.

No token ever reaches a browser

The board holds them; the page never sees one. Clicking through to a machine opens that machine's own Argus, in its own origin, with its own token.

No shell of its own

Panoptes has no filesystem view, no terminal and no way to run a command it was not told about by name — see below.

Two directions, because networks are not symmetrical

The board asks (pull). Write the machine down and it gets polled. Better wherever it works: a request that fails is the signal that a machine is down, and one file says what is being watched.

# ~/.config/panoptes/config.yaml
machines:
  - name: hetzner
    url: http://hetzner.internal:8090
    token: <a watcher token from that machine's Argus>

The machine calls in (push). Some networks only go one way — two boxes on the same wire, each with the other's MAC in its ARP table, and one refusing everything inbound. No configuration on the board fixes that, so the machine opens the connection instead, in the direction that already works.

# argus config, on the machine that cannot be reached
report_to:
  url: http://board.internal:8070
  token: <the registration token>
  name: gpu2
  every: 10

Nothing invented here: it is what Prometheus does in agent mode, and what every dial-out agent does — Tailscale, Cloudflare Tunnel, Teleport, the kubelet registering with its API server. Local pull, remote push.

Three rules keep the weak keys weak. Announcing and looking are different tokens: the registration key cannot read the board, and the board's key cannot invent machines. The list you wrote by hand wins, so nothing holding the registration key can replace a real machine with one of its own. And silence is the only signal there is — no request fails when nobody is asking, so a machine that stops calling in goes cold rather than staying green for ever.

Starting and stopping, without a command in a request

The ⋯ on a tile can start and stop things on that machine, and stop its Argus. No command ever arrives in a request: each machine publishes named things in its own config, and the board may ask for one of those names and nothing else. That is the whole design, and it is what keeps a shared key harmless.

# argus config, on the machine
runnable:
  - name: nightly
    run: python3 nightly.py

watchers:
  - name: panoptes
    token: …
    may_run: true            # otherwise the token stays read-only
    may_stop_argus: false    # the one thing this board cannot undo

An ordinary board shows nothing in that section, which is correct: pressing these has to be granted on the machine, not assumed by the page. Stopping an Argus asks twice and says what it costs — every tmux session keeps running, but nothing here can start it again; that takes a shell on that machine.

Yours to read at a glance

The same board in the light theme.

Light or dark, following the device, and switchable.

The same board on a phone.

On a phone it is one column, and it installs as an app where the certificate allows it.

A colour per machine

Taken from the name, so it is the same on every device you open the board from — and overridable, with the tint strength in your hands.

A line of your own

A note per machine, written with the pencil on the tile: what the box is for, what it was doing. Everything else on a tile, the machine said. This is the part only you know.

A journal

Who pressed stop, who tried to get in, and who invented a machine — with the address it came from. The board is the only thing that knows which browser a click came from.

Run it in a minute

curl -fsSLO https://raw.githubusercontent.com/andreaderuvo/panoptes/master/install.sh
less install.sh && bash install.sh
panoptes                       # prints a URL with a token in it
panoptes --qr                  # and a code to photograph

No sudo, nothing outside your home, and the same line again updates it. Or a container — and here a container is not a compromise but the right shape, since a board has no tmux, no filesystem and no terminal to reach into, so it needs no host namespaces and works anywhere Docker does, Windows included:

git clone https://github.com/andreaderuvo/panoptes && cd panoptes
docker compose up -d

Versioned amd64/arm64 images are also published at ghcr.io/andreaderuvo/panoptes; the Compose file documents the one-line switch from a local build.

Or git clone and pip install -r requirements.txt, as before. All three are checked on every change, on Linux and macOS.

That is the board. Then tell it about your machines, or tell your machines about it — both directions work, and which one you want depends on your network. The machine guide has both, with the exact configuration on each side.

Before you try it

Do I need Argus for this to be any use?

Yes — Panoptes reads each machine's Argus and links back into it. It is the second half of the same idea: Argus is one machine in a browser tab, this is every machine on one page.

What does it actually hold?

Per machine: a name, a URL, a read-only watcher token, an optional colour and your own note. Per sweep: hostname, uptime, load, memory, the fullest disk and the session names with which of them is ringing. No file, no path, no command.

What happens when I restart the board?

The machines that announced themselves are still there. They used to be forgotten, which meant a machine you had stopped did not go red — it vanished, and for the one question this exists to answer that is the worst possible answer. Forgetting one is now something you do on purpose.

Several people watching one board?

It works, and the journal is why it is worth mentioning: the machine records that "panoptes" pressed stop, which is true and not enough. The board is the only thing that knows which browser the click came from.

What is it written in?

Python on the server, vanilla JavaScript in the browser. No build step, no bundler, no database — a file for the config, one for the machines that announced themselves and one for the journal.