README
¶
Regulus
English · Italiano
CodexCircle Server Manager, also known as Regulus, is a platform to manage one or more Linux servers from a single web dashboard.
Project status: early development. Hub, built-in and remote agents, dashboard, authentication and updates are implemented; plugins are not yet. No release has been published, and the release, the packages and the update path have not yet been tested on a real system (#81). See the roadmap and the design decisions.
How it works
Regulus is built around plugins. The base installation provides a simple dashboard, and every plugin adds features and tools.
- Dashboard: fully web-based and customizable. Users choose which widgets to show and how to arrange them.
- Agent: installed on every server to be managed. It connects the server to the user's server network.
- Hub: installed on one server per group. It manages the agents connected to it and serves the dashboard. The hub installer asks whether to include the agent as well, so the hub's own server can be managed like the others.
- Plugins: extend the platform with new server-side features and new dashboard widgets.
Browser (React dashboard)
│
▼
┌───────────┐
│ Hub │ one hub per server group
└───────────┘
▲ ▲ ▲
│ │ │ outbound connections from the agents (gRPC, mTLS)
┌────┘ │ └────┐
Agent Agent Agent one agent per managed server
Agents open the connection to the hub, so no inbound ports are needed on managed servers and everything works behind NAT or firewalls.
Tech stack
| Component | Technology |
|---|---|
| Agent and hub | Go: a single regulus executable used as agent or hub depending on the role |
| Dashboard | React with TypeScript and Vite, embedded in the binary |
| Communication | gRPC with Protobuf as the contract, mTLS between agent and hub |
| Plugins | Separate processes talking gRPC with the host, plus JavaScript modules for dashboard widgets |
| Data | SQLite by default, PostgreSQL optional |
Supported platforms
Ubuntu Server (22.04 and 24.04, amd64 and arm64) is the reference platform. Debian and RHEL-like distributions follow.
Roadmap
- Phase 1: hub with built-in agent and dashboard.
- Phase 2: remote agents on other servers.
- Phase 3: separate agent and hub, protocol versioning, updates (manual, download-only or automatic).
- Phase 4: plugins.
Third-party plugins come after the plugin system is stable. Work is tracked in the issues, grouped in milestones M1 to M6; docs/decisions.md maps them to the phases.
Installation (Ubuntu Server)
The hub installs on Ubuntu Server 22.04 and 24.04 (amd64 and arm64; other Debian-based systems with systemd should work too). One command downloads the .deb of the latest release, verifies its checksum and signature, installs it, starts the service and prints where to open the dashboard and the one-time setup token:
curl -fsSL https://github.com/CodexCircle/Regulus/releases/latest/download/install.sh | sh
The installer asks whether to include the built-in agent (so the hub's own server is managed like the others). To answer in advance, or in a script, add options after sh -s --: --builtin-agent, --no-builtin-agent, --yes (no questions), --channel beta or --channel alpha (latest release of that channel; also --beta, --alpha), --version 0.2.0, --require-signature (fail unless the signature can be verified with cosign). Without cosign the installer verifies the checksum only and says so. If you prefer not to pipe a script into a shell, download the .deb from the releases, verify it as shown under Releases and run sudo apt install ./regulus_*.deb.
What the package sets up:
| Path | Purpose |
|---|---|
/usr/bin/regulus |
the executable |
/usr/lib/systemd/system/regulus.service |
the service, enabled and started at install |
/etc/regulus/config.yaml |
configuration (kept on upgrade) |
/usr/lib/systemd/system/regulus-agent.service |
the agent service, used only on servers installed with --role agent |
/etc/regulus/regulus.env |
settings chosen by the installer, such as REGULUS_BUILTIN_AGENT; they override config.yaml |
/etc/regulus/agent.yaml, /etc/regulus/role |
agent role only: the hub address, and a marker that keeps the package from starting a hub |
/var/lib/regulus |
the database; kept when the package is removed or purged |
/var/lib/regulus-agent |
agent role only: the agent's key and certificates; kept on removal |
Privilege model. The hub runs as the system user regulus, which has no login shell, no home directory and no sudo rights, and the unit is hardened (no capabilities, read-only system, private /tmp and devices, system call filter). Monitoring needs no privileges, so this is enough for now. Actions that do need them (restarting services, package updates, Docker) will be added as explicit, opt-in grants rather than by running as root; see issue #46. Check the unit with systemd-analyze security regulus.
Installing an agent on another server. The same installer sets up the agent role. In the dashboard open Servers → Add a server, which shows the command with a one-time token and the hub's CA fingerprint, and run it on the new server (Ubuntu or Debian, as a user that can use sudo):
curl -fsSL https://github.com/CodexCircle/Regulus/releases/latest/download/install.sh | sh -s -- --role agent --hub HUB_HOST:8443 --token TOKEN --ca-fingerprint sha256:...
It verifies and installs the same package, enrolls the server as the regulus user (the token is sent only to a hub whose CA has that fingerprint), then enables and starts regulus-agent.service; the server shows as online within seconds. The hub must have the agent listener on (see Remote agents). Keep the token off the command line with REGULUS_AGENT_TOKEN=... sh -s -- --role agent .... Install the same version as the hub: add --version. If the hub revokes the agent, the service stops and is not restarted (exit status 3).
The dashboard speaks plain HTTP on port 8080. Before exposing the hub beyond a trusted network, put a reverse proxy with TLS in front of it and set listen: 127.0.0.1:8080 in /etc/regulus/config.yaml.
Development
Requirements: Go 1.26+, Node.js 22+.
make build # builds the dashboard, then bin/regulus
./bin/regulus hub # serves the dashboard on :8080
make test # go vet, go test -race, dashboard typecheck and tests
make lint # golangci-lint and ESLint
make smoke # start bin/regulus and check it end to end (after make build)
scripts/test-install.sh # tests of the install script (also part of make test)
For dashboard work, run npm run dev in dashboard/ (proxies /api to the hub on :8080).
Releases
There are three channels: stable (v0.3.0), beta (v0.3.0-beta.2, or -rc.1) and alpha (v0.3.0-alpha.1). To publish, a maintainer runs Publish release in the Actions tab on main, picking the channel (and, when a new version starts, whether to raise the patch, minor or major number). It refuses a commit on which CI has not passed, works out the next version (scripts/next-version.sh: a version goes through its alphas, then its betas, then the release; pre-releases are numbered per channel), tags the commit and starts the release workflow on the tag. Pushing a v* tag by hand does the same.
The release workflow (GoReleaser) publishes a GitHub Release with tar.gz archives, .deb and .rpm packages (linux amd64 and arm64), SBOMs, install.sh and checksums.txt, whose Sigstore signature is checksums.txt.sigstore.json. Alphas and betas are marked as pre-releases, so GitHub's Latest (and releases/latest/download/install.sh) is always the newest stable release; each release's notes start with the install command for that exact version. To verify a download:
cosign verify-blob --bundle checksums.txt.sigstore.json \
--certificate-identity-regexp 'https://github.com/CodexCircle/Regulus/.github/workflows/release.yml@.*' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com checksums.txt
sha256sum --check --ignore-missing checksums.txt
Configuration
The hub reads, in increasing priority: defaults, a YAML file (--config or REGULUS_CONFIG), environment variables, flags.
| Setting (YAML) | Flag | Environment | Default |
|---|---|---|---|
listen |
--listen |
REGULUS_LISTEN |
:8080 |
data_dir |
--data-dir |
REGULUS_DATA_DIR |
data |
builtin_agent (true, false) |
--builtin-agent |
REGULUS_BUILTIN_AGENT |
true |
metrics_interval (for example 10s, minimum 1s) |
--metrics-interval |
REGULUS_METRICS_INTERVAL |
10s |
log.level (debug, info, warn, error) |
--log-level |
REGULUS_LOG_LEVEL |
info |
log.format (text, json) |
--log-format |
REGULUS_LOG_FORMAT |
text |
updates.check (true, false) |
- | REGULUS_UPDATES_CHECK |
true |
updates.apply (true, false) |
- | REGULUS_UPDATES_APPLY |
false (the installer sets it) |
console.tui (true, false) |
--tui, --no-tui (console commands only) |
REGULUS_NO_TUI=1 (the opposite) |
true |
With builtin_agent enabled (the default) the hub runs an agent for its own server, which connects through the same contract as any other agent (agent id local).
The hub stores its data in a SQLite database (regulus.db) inside data_dir and applies schema migrations automatically at startup.
Updates
The hub looks for new releases of Regulus, keeps the newest one downloaded and checked, and can install it on its own server from the dashboard, with a rollback. The servers you manage (the agents) are updated from the hub too.
- Where from: the GitHub Releases of this repository, read without a token. The hub asks every six hours (a little later after a restart, and at most once a minute from the dashboard), with conditional requests so it stays well under GitHub's limit for anonymous use. Only the first request after a change of the list costs anything. A development build (
dev, or one made after a tag withgit describe) never asks by itself and is never told it is behind. - Channel:
stableoffers releases only;betaalso offers betas and release candidates (v0.3.0-beta.1,v0.3.0-rc.1);alphaoffers every release, alphas included (v0.3.0-alpha.1, and any other pre-release). Each channel takes whichever offered release is newest, so it moves to the beta or the release when one comes out. A hub starts on the channel of the version it runs. Versions are compared as SemVer, so a hub never "updates" to something older. - Mode, chosen in the dashboard (Updates, needs the
core.updatepermission, whichadminhas andoperatordoes not):manualonly tells you;downloadalso downloads the archive of this platform into<data_dir>/updates/and checks it, then waits for you to press Install;automaticalso installs it by itself, but only inside a maintenance window you set (two times such as 03:00 and 05:00 in the server's time zone, possibly across midnight), and never tries again a version that had to be rolled back. Every change of settings, every download and every installation is in the audit log. - What "checked" means: first the signature of
checksums.txt, then the archive. The release workflow signschecksums.txtwith Sigstore (keyless,checksums.txt.sigstore.json); the hub verifies that bundle in-process, with nocosignneeded: the certificate must name the release workflow of this repository at the tag of the release, issued by GitHub Actions, with a transparency log entry and a certificate timestamp. Only then is the archive downloaded and its SHA-256 compared with the list; a mismatch deletes it. A release with a missing or invalid signature is never kept. The Sigstore trusted root is fetched from Sigstore's TUF repository and cached in<data_dir>/tuf(a key rotation reaches the hub by itself); if that cannot be reached, a snapshot built into the binary is used. - Installing, and why no
sudo: the hub runs as the unprivileged userregulusand keeps it that way. The package shipsregulus-update.path, which the installer enables (install.sh --no-self-updateskips it) together withupdates.apply: true(REGULUS_UPDATES_APPLY). When you press Install the hub leaves a request that names a version, nothing else; systemd startsregulus-update.serviceas root, which runsregulus update-apply. That helper trusts nothing the hub's user could have written: it copies the archive into a directory of its own, verifies the Sigstore signature ofchecksums.txtand the archive against it again, refuses a version that is not newer than the installed one, extracts the executable, checks that it reports the expected version, keeps the current binary in/var/lib/regulus-update/regulus.previous, replaces/usr/bin/regulusatomically and restarts the hub. It then watches the new hub (the service must be active and report the new version and stay up for 15 seconds, within 90); if not, the previous binary is put back and restarted, and the result isrolled_back. The hub restarts in the middle, so the dashboard is unreachable for a moment; the outcome (applied,rolled_back,failed) appears on the Updates page and in the audit log. Only the executable is replaced: a release that changes the systemd units or the configuration file needs the package (install.sh) once. Because dpkg does not know about the replaced file,dpkg --verify regulusreports it as changed, and a lateraptreinstall of the package brings back the version in the package. - Updating the servers (agents): the hub is updated first, then the servers, to the version the hub runs; an agent may be one release series behind (see Versions), so there is always a window in which both can run. The Updates page lists the connected servers that are behind and updates them a wave at a time (1, 3, 10 or all), stopping at the first failure, with a result per server and an entry in the audit log (
POST /api/updates/rollout). In theautomaticmode the hub does it by itself inside the maintenance window, and does not retry a rollout that failed. How it works: the hub makes sure its cache holds the signed release with the archive of each platform in use (amd64 and arm64), downloading what is missing; it sends the agent anApplyUpdatecommand; the agent fetcheschecksums.txt, its signature and its own platform's archive from the hub over its existing mTLS connection (so it needs no internet), checks the signature and the checksum, and leaves a request for the helper of its server (regulus-agent-update.path, which the installer enables for the agent role together withupdates: trueinagent.yaml;--no-self-updateskips it). That helper works exactly like the hub's: it checks everything again as root, replaces the binary and restarts the agent. The new agent is healthy when it connects to the hub again with the new version (it writesconnected.jsonfor the helper); if it does not within 90 seconds the previous binary is put back. The hub marks a serverupdatedonly when it sees it back with the new version, andfailedif it comes back with the old one (rolled back) or does not come back. An agent installed before this feature has to be updated once withinstall.sh; the dashboard says so. A rollout lasts as long as the hub runs: restarting the hub ends it. - Turn it off: set
updates.check: false(orREGULUS_UPDATES_CHECK=false) on a hub that must not contact the internet. Nothing is requested then, and the page says why. - API:
GET /api/updates,PUT /api/updates/settings,POST /api/updates/check,POST /api/updates/download,POST /api/updates/apply,GET/POST /api/updates/rolloutandPOST /api/updates/rollout/stop(all needcore.update; seeinternal/hub/openapi.yaml). The download runs in the background; pollGET /api/updates.
Console and command line
Run regulus on the server (or regulus console) to open a full-screen console, in the spirit of the Claude Code and Gemini CLIs. It has four sections, reached with tab, the arrow keys, 1-4 or the mouse:
- Status: whether the hub runs (and the state of its systemd unit), version, uptime, the dashboard address and port (with the addresses of this machine on the network), the agent port, the data directory and database size, the configuration file and the connected agents. While no administrator exists it shows the one-time setup token, and
pmakes a one-time password reset link for the administrator (after a confirmation), so a server without a browser nearby is still manageable. - Settings: every setting with its description, default, current value and where the value comes from (
default,fileorenvironment).enteredits one; the text is checked as you type,tabcycles through the allowed choices, andenterwrites the change into the YAML file, keeping its comments. The console says whether the hub must be restarted and offers it withr(sudo systemctl restart regulus). - Logs: the last lines of the hub's log from journald, refreshed every two seconds, with a level filter on
f. - Health: the configuration is valid, the hub answers, the ports are free or served by the hub, the data directory is writable, the database exists.
The live data (agents, uptime, setup token) comes from the running hub through a Unix socket in the data directory (regulus.sock, mode 0600, owned by the user that runs the hub), not through the web login. With the package install the data directory is private to regulus, so open the console with sudo -u regulus regulus to see it all, or with sudo regulus (changes to the configuration file need root as well). Without access to the socket the console still shows the settings and tells you what is missing.
Every screen has a scriptable equivalent that uses the same code and the same file:
| Command | What it does |
|---|---|
regulus status [--json|--plain] |
the Status and Health information; exit status 0 when the hub runs, 3 when it does not, 1 when the configuration is broken |
regulus config list [--json|--plain] |
every setting with value and source |
regulus config get KEY |
the current value of one setting |
regulus config set KEY VALUE |
validates and writes one setting into the configuration file (a bare port such as 9090 becomes :9090; agent_listen off turns remote agents off) |
regulus config path |
the configuration file in use |
The file is --config, else REGULUS_CONFIG, else /etc/regulus/config.yaml when it exists. config set changes only that one line (a commented example such as # agent_listen: ":8443" becomes the setting) and refuses a value the hub would not accept. The settings are listen, agent_listen, data_dir, builtin_agent, metrics_interval, log.level, log.format and console.tui.
Turning the interactive screen off. It is never forced. regulus prints the status as text instead when any of these holds, in this order (a flag beats an environment variable, which beats the configuration file, which beats detection): the flag --no-tui (alias --notui; --tui forces the screen on), the environment (REGULUS_NO_TUI=1, CI set, TERM=dumb), console.tui: false in the configuration file, or detection (stdin or stdout is not a terminal, such as a pipe, cron, systemd or SSH without a TTY, or the terminal is smaller than 60x16). --json and --plain print instead of opening the screen, and --no-input (alias --non-interactive) means that nothing is ever asked. NO_COLOR and --no-color only remove the colours; without a UTF-8 locale the console draws with plain ASCII characters. The strings of the screen are in internal/console/strings.go, ready for a translation.
Not in the console yet: the reachability from the network, certificate validity and pending updates in Health (they need the update and TLS features), and creating the administrator (the setup token and the dashboard do it).
Authentication
The hub has local accounts; each has a role, and a role is a set of permissions (see Roles and permissions below). Three roles are built in: admin, operator and viewer. On first start, with no accounts, the hub logs a one-time setup token (the line "no accounts yet", field setup_token); the dashboard asks for it together with the username and password of the first admin, so only someone who can read the hub log can claim an unconfigured hub. A new token is generated at each start until the admin exists.
- Passwords are hashed with argon2id (at least 10 characters). Login creates a session of 7 days stored in the database (only the SHA-256 of the token); the cookie is
HttpOnly,SameSite=LaxandSecurewhen the request came over HTTPS (directly or viaX-Forwarded-Proto). POST requests must be JSON from the same origin. - After 10 failed logins from the same client address in 15 minutes, further attempts get
429for the rest of the window. Behind a reverse proxy all clients share the proxy address, so put rate limiting in the proxy as well. - Everything under
/api/except/api/auth/*needs a login (401otherwise). The dashboard files and/healthzare public: they contain no data, and the page shows the login or setup form before anything else. - Serve the hub over HTTPS (a reverse proxy is fine) outside a trusted network: sessions and passwords travel in the clear otherwise.
Endpoints: GET /api/auth/me, POST /api/auth/setup, POST /api/auth/login, POST /api/auth/logout, POST /api/auth/reset.
Users, sessions and audit. Admins manage accounts in the dashboard (Users) or through the API: GET/POST /api/users, PATCH/DELETE /api/users/{username}. What each user may do depends on the permissions of their role (403 when missing). The last enabled admin can never be demoted, disabled or deleted. Disabling or deleting an account ends its sessions at once, and a disabled account gets the same "invalid username or password" answer as a wrong password. Every user can change their own password (POST /api/account/password, which ends their other sessions and counts wrong current passwords toward the rate limit) and list or end their own sessions (GET /api/account/sessions, DELETE /api/account/sessions/{id}; each session records the address and browser it was created from). Account events (setup, logins and failed logins, user changes, password changes, ended sessions) go to an audit log in the database, newest 10,000 kept, readable with the audit.read permission at GET /api/audit?limit= and in the Users page; it never stores passwords or tokens.
Roles and permissions. Every endpoint that changes or reveals something checks a named permission, not a role name: servers.read (see the servers and their metrics), servers.manage (enrollment tokens, rename, revoke), layout.edit (arrange the dashboard), users.manage (accounts, resets, two-factor policy), roles.manage (the roles below), audit.read and core.update (see and steer the updates). The built-in roles live in the code: admin has every permission, including the ones later versions add; operator has servers.read, servers.manage and layout.edit; viewer has servers.read and layout.edit. Anyone with roles.manage can make custom roles per installation in Users → Roles (GET/POST /api/roles, PATCH/DELETE /api/roles/{name}, audited): pick a name (lowercase letters, digits, - and _) and tick permissions. A role in use cannot be deleted, and the built-in ones cannot be changed. Nobody can hand out more than they hold: a role or an account can only be given permissions its author has, and an account can only be changed, disabled, deleted or reset by someone whose permissions cover all of that account's. Changes to a role apply at the next request, with no new login. The signed-in user's permissions are in GET /api/auth/me. The OS-level privileges of the services are a separate matter (see the design decisions and #46).
Two-factor authentication. Any user can add a second step to the sign-in from Account → Two-factor authentication: after confirming the password the dashboard shows a QR code (and the key, to type by hand) for any TOTP authenticator app (RFC 6238: Google Authenticator, Aegis, 1Password, Bitwarden…). Two-factor is on only after the user enters a valid code from the app, and then ten recovery codes are shown once (stored only as hashes; each works once instead of an app code; renewable, which voids the old ones). Afterwards a correct password does not create a session: the hub answers with a short-lived pending login (5 minutes, 5 tries, not a session) and the session starts only after a valid code. Codes are accepted within one 30-second step either side of the clock, and a code works once. Wrong codes count toward the failed-login limit above, and the password step does not reset it. Using a recovery code is audited, logged as a warning and shown to the user at sign-in with the number left. Turning it off, or making new recovery codes, needs the password and a code. TOTP secrets are stored encrypted (AES-256-GCM) with a key derived from secret.key in the data directory (created at the first start, readable by the owner only): a copy of the database alone does not reveal them, so back both up. In Users → Security an admin can require two-factor from administrators or from everyone; a user it applies to must set it up before anything else (the API answers 403 with code: two_factor_setup_required until then) and cannot turn it off. An admin can remove the second factor of a user who lost the app and the codes (Reset two-factor, audited; it ends their sessions). Endpoints: POST /api/auth/login/two-factor, GET /api/account/two-factor, POST /api/account/two-factor/{setup,enable,disable,recovery-codes}, DELETE /api/users/{username}/two-factor, GET/PUT /api/settings/security.
Lost credentials. The hub does not need email for recovery.
- A user forgot the password: an admin opens Users, picks Reset password and hands the link to the user (
POST /api/users/{username}/reset). The link ishttp://<hub>/#/reset/<token>: the token is random, stored only as a hash, works once, expires after one hour and replaces any earlier link of that account; it sits in the URL fragment, so it never reaches server logs. The user opens it, chooses a new password (POST /api/auth/reset) and signs in; the admin never sees the password. Making the link ends all sessions of the account, and so does using it. Disabled accounts cannot be reset. Wrong tokens count toward the same limit as failed logins (10 per 15 minutes per address), and every token that is unknown, expired or used gets the same answer. - The admin is locked out: on the machine that runs the hub, as the user that owns the data directory, run
sudo -u regulus regulus hub reset-admin --config /etc/regulus/config.yaml(with the package install; otherwise pass the same--configor--data-diras the hub, and--user NAMEif there are several admins). It prints a one-time reset link like the one above for that admin, enables the account if it was disabled and ends its sessions; the hub can keep running. Whoever can write to the data directory owns the hub anyway, so that access is the proof of ownership. The password is chosen in the browser, so it never shows up in the terminal or the shell history. The command is recorded in the audit log asconsole. - The admin lost the authenticator app too:
reset-adminalso removes the admin's two-factor authentication (console access is the proof of ownership), so the new password is enough to sign in; set it up again from Account. For other users, an admin uses Reset two-factor. Passkeys will be cleared by a reset when that feature exists.
Remote agents
Besides the built-in agent, a server can run regulus agent and be managed from the hub. The agent always connects out to the hub (gRPC over TLS 1.3), so the managed server needs no open port.
- Turn on the listener. Remote agents are off by default. Set
agent_listen: ":8443"(or--agent-listen,REGULUS_AGENT_LISTEN) and restart the hub. Open that port in the firewall only towards the servers you manage. - Make a token. An admin opens Servers → Add a server in the dashboard, optionally names the server and gets the command to run, with a one-time token valid for one hour, the port and the CA fingerprint of the hub (the same thing
POST /api/enrollment-tokensreturns). The token is shown once. - Enroll the agent on the other server, once. On Ubuntu or Debian the easiest way is the installer command shown by the dashboard (see Installation); by hand, with the binary already there:
The agent first checks that the hub's certificate is signed by a CA with that fingerprint (so the token is never sent to an impostor), creates its own key, and the hub signs its certificate. The key stays on the agent. From then on the agent only needsregulus agent --hub HUB_HOST:8443 --token TOKEN --ca-fingerprint sha256:... --data-dir /var/lib/regulus-agent--huband the saved credentials; the same options can go in a YAML file (--config:hub,data_dir,log) or inREGULUS_AGENT_*variables. - Revoke an agent from its server page or with
DELETE /api/agents/{id}: its connection is closed and it can never connect again; the process stops with an error. To use that server again, delete its data directory and enroll with a new token.
How it is protected: the hub creates its own certificate authority at the first start (pki/ in the data directory, ca.key readable by the owner only). Tokens are random, stored only as a hash, single use, expire after an hour and are rate limited (10 wrong attempts per 15 minutes per address). Every connection needs the client certificate of an enrolled, not revoked agent, and the agent id comes from the certificate, not from what the agent says. Client certificates last five years; there is no renewal yet. Creating tokens, renaming and revoking agents need servers.manage and go to the audit log.
Versions. An agent and the hub agree on a protocol version when the agent connects (the agent sends the range it speaks, the hub picks the highest both understand). On top of that the hub accepts agents of its own release series and the previous one: the same minor version, one minor behind (hub 0.3.x takes 0.3.x and 0.2.x), or any release of the previous major. Updating the hub first is therefore safe: its agents keep working until they are updated too. The Servers page marks agents that are behind the hub, or ahead of it (update the hub). An agent further behind is refused with a message that says so and the server is listed as incompatible with the reason. A refusal because of versions does not stop the agent service: it asks again every few seconds, so it connects by itself once the agent or the hub is updated. A revoked agent still stops for good. Development builds are never judged.
HTTP API
GET /api/agents returns the connected agents as JSON (id, version, system info, status of connected or stale, last heartbeat, uptime). GET /api/agents/{id}/metrics returns the recent metrics of an agent (CPU, load, memory, swap, disks, network counters and rates, uptime), oldest first; ?since=<RFC 3339> returns only newer points. The hub keeps the last hour of metrics in memory (up to 3600 reports per agent), so the history is lost when the hub restarts. GET /api/agents/{id} returns one agent. GET /api/agents?all=true also lists the enrolled agents that are offline or revoked, with the time the hub last heard from them (last_seen_at); PATCH /api/agents/{id} (servers.manage) gives an agent a display name. GET /api/layout and PUT /api/layout read and save the dashboard layout of the logged-in user (any JSON up to 64 KiB, stored per user). GET /api/stream upgrades to a WebSocket that pushes a snapshot of the agents and then agent.connected, agent.disconnected, agent.heartbeat and agent.metrics events as JSON; it needs the session cookie and refuses pages from other origins, and a client that reads too slowly is disconnected and should reconnect. All of this is described in internal/hub/openapi.yaml, also served at /api/openapi.yaml; a test keeps the file in sync with the routes. The endpoints need a login and GETs are read-only; the dashboard shows the servers on the Servers page.
Dashboard
After the login the dashboard shows a header with the sections Dashboard, Servers, (for those who may manage users or roles or read the audit log) Users and (with core.update) Updates, and a link to your Account page (change password, your sessions). The Dashboard page is a grid of widgets: Customize unlocks dragging and resizing, Add widget picks from the registered widgets, and Reset to default restores the initial layout. The layout is saved per user through /api/layout (with debouncing, and flushed when leaving the page). Widgets are registered in dashboard/src/widgets/ with registerWidget (id, title, default size, component); a widget that crashes only breaks its own frame, and a layout that refers to an unknown widget (for example from a removed plugin) shows a placeholder that can be removed.
The base installation ships these widgets, fed live by the hub (the dashboard follows /api/stream and reconnects by itself if the connection drops): System (host, OS, kernel, memory, load, uptime), CPU and Memory (current value plus a chart of the last 10 minutes), Network (received and sent rate, in total and per interface), Disk usage (one bar per mounted filesystem), Uptime, and Servers. The first five (without Uptime) are in the default layout. When several agents are connected, each widget has a selector and remembers the choice; by default it shows the hub's built-in agent.
The Servers page lists every enrolled server, online or not, with its system, agent version, status (online, stale, offline, revoked) and last contact. A row opens the page of that server: its facts and the System, CPU, Memory, Network and Disk usage widgets bound to it. Those with servers.manage rename or revoke a server there and add new ones with Add a server. An offline server shows when it was last seen; a revoked one cannot connect again.
Repository layout
cmd/regulus/ single executable (role chosen with a subcommand)
internal/config/ configuration (defaults, YAML file, environment, flags) and the editable settings
internal/console/ the interactive console (Bubble Tea) and the status and settings output
internal/localstatus/ the Unix socket where a running hub reports to local tools
internal/logging/ structured logging
internal/storage/ SQLite database and migrations
internal/auth/ local accounts, sessions and the HTTP login endpoints
internal/hub/ hub: HTTP server, agents API and the agent service (registry of connected agents)
internal/update/ looking for new releases, the download cache and its checks, installing with a rollback (hub and agent)
internal/agent/ agent runtime (client side of the contract) and Linux metrics collector, also used as the built-in agent
internal/protocol/ protocol version of the agent-hub contract
internal/inproc/ in-memory gRPC transport (used by the built-in agent)
internal/gen/ generated Go code for proto/ (do not edit)
internal/webui/ embeds the compiled dashboard
dashboard/ React + TypeScript dashboard
packaging/ systemd unit, default config, maintainer scripts and install.sh
proto/ Protobuf contract between agents and the hub
Also: docs/ (design decisions and the plugin design draft), AGENTS.md (notes for AI coding agents). Planned: plugins/ (internal plugins).
Contributing
See CONTRIBUTING.md and the design decisions. Security problems: SECURITY.md.
License
Apache License 2.0. See LICENSE.
Italiano
CodexCircle Server Manager, detto anche Regulus, è una piattaforma per gestire uno o più server Linux da un'unica dashboard web.
Stato del progetto: sviluppo iniziale. Hub, agent integrato e remoti, dashboard, autenticazione e aggiornamenti sono implementati; i plug-in non ancora. Non è stata pubblicata nessuna release, e release, pacchetti e aggiornamenti non sono ancora stati provati su un sistema reale (#81). Vedi la roadmap e le decisioni di progetto.
Come funziona
Regulus lavora per plug-in. L'installazione base fornisce una dashboard semplice, e ogni plug-in aggiunge funzioni e strumenti.
- Dashboard: interamente web e personalizzabile. L'utente sceglie quali widget mostrare e come disporli.
- Agent: va installato su ogni server da gestire. Collega il server alla rete di server dell'utente.
- Hub: va installato su un server per gruppo. Gestisce gli agent a lui collegati e serve la dashboard. Durante l'installazione dell'hub viene chiesto se includere anche l'agent, così il server che ospita l'hub può essere gestito come gli altri.
- Plug-in: estendono la piattaforma con nuove funzioni lato server e nuovi widget lato dashboard.
Gli agent aprono la connessione verso l'hub, quindi non servono porte in ingresso sui server gestiti e il sistema funziona anche dietro NAT o firewall.
Stack tecnologico
| Componente | Tecnologia |
|---|---|
| Agent e hub | Go: un unico eseguibile regulus, usato come agent o come hub a seconda del ruolo |
| Dashboard | React con TypeScript e Vite, incorporata nel binario |
| Comunicazione | gRPC con Protobuf come contratto, mTLS tra agent e hub |
| Plug-in | Processi separati che parlano gRPC con l'host, più moduli JavaScript per i widget della dashboard |
| Dati | SQLite di default, PostgreSQL opzionale |
Piattaforme supportate
Ubuntu Server (22.04 e 24.04, amd64 e arm64) è la piattaforma di riferimento. Seguiranno Debian e le distribuzioni RHEL-like.
Roadmap
- Fase 1: hub con agent integrato e dashboard.
- Fase 2: agent remoti su altri server.
- Fase 3: agent e hub separati, versionamento del protocollo, aggiornamenti (manuali, solo download o automatici).
- Fase 4: plug-in.
I plug-in di terze parti arriveranno dopo la stabilizzazione del sistema di plug-in. Il lavoro è tracciato nelle issue, raggruppate nelle milestone da M1 a M6; docs/decisions.md le collega alle fasi.
Installazione (Ubuntu Server)
L'hub si installa su Ubuntu Server 22.04 e 24.04 (amd64 e arm64; dovrebbe funzionare anche su altri sistemi basati su Debian con systemd). Un solo comando scarica il .deb dell'ultima release, ne verifica checksum e firma, lo installa, avvia il servizio e mostra dove aprire la dashboard e il token di setup monouso:
curl -fsSL https://github.com/CodexCircle/Regulus/releases/latest/download/install.sh | sh
L'installer chiede se includere l'agent integrato (così il server dell'hub è gestito come gli altri). Per rispondere in anticipo, o in uno script, aggiungi opzioni dopo sh -s --: --builtin-agent, --no-builtin-agent, --yes (nessuna domanda), --channel beta o --channel alpha (ultima release di quel canale; anche --beta, --alpha), --version 0.2.0, --require-signature (fallisce se la firma non si può verificare con cosign). Senza cosign l'installer verifica solo il checksum e lo dice. Se preferisci non passare uno script a una shell, scarica il .deb dalle release, verificalo come descritto in Rilasci ed esegui sudo apt install ./regulus_*.deb.
Cosa prepara il pacchetto:
| Percorso | Scopo |
|---|---|
/usr/bin/regulus |
l'eseguibile |
/usr/lib/systemd/system/regulus.service |
il servizio, abilitato e avviato all'installazione |
/etc/regulus/config.yaml |
configurazione (conservata negli aggiornamenti) |
/usr/lib/systemd/system/regulus-agent.service |
il servizio dell'agent, usato solo sui server installati con --role agent |
/etc/regulus/regulus.env |
impostazioni scelte dall'installer, come REGULUS_BUILTIN_AGENT; hanno la precedenza su config.yaml |
/etc/regulus/agent.yaml, /etc/regulus/role |
solo ruolo agent: l'indirizzo dell'hub e un segnaposto che impedisce al pacchetto di avviare un hub |
/var/lib/regulus |
il database; resta se il pacchetto viene rimosso o purgato |
/var/lib/regulus-agent |
solo ruolo agent: chiave e certificati dell'agent; restano se si rimuove il pacchetto |
Modello dei privilegi. L'hub gira con l'utente di sistema regulus, senza shell di login, senza home e senza diritti sudo, e l'unità è rinforzata (nessuna capability, sistema in sola lettura, /tmp e dispositivi privati, filtro delle system call). Il monitoraggio non richiede privilegi, quindi per ora basta. Le azioni che li richiedono (riavvio servizi, aggiornamenti dei pacchetti, Docker) arriveranno come permessi espliciti e facoltativi, non eseguendo come root; vedi l'issue #46. Controlla l'unità con systemd-analyze security regulus.
Installare un agent su un altro server. Lo stesso installer prepara il ruolo agent. Nella dashboard apri Servers → Add a server, che mostra il comando con un token monouso e la fingerprint della CA dell'hub, ed eseguilo sul nuovo server (Ubuntu o Debian, con un utente che può usare sudo):
curl -fsSL https://github.com/CodexCircle/Regulus/releases/latest/download/install.sh | sh -s -- --role agent --hub HOST_HUB:8443 --token TOKEN --ca-fingerprint sha256:...
Verifica e installa lo stesso pacchetto, registra il server come utente regulus (il token va solo a un hub la cui CA ha quella fingerprint), poi abilita e avvia regulus-agent.service; il server risulta online in pochi secondi. L'hub deve avere il listener degli agent attivo (vedi Agent remoti). Per tenere il token fuori dalla riga di comando usa REGULUS_AGENT_TOKEN=... sh -s -- --role agent .... Installa la stessa versione dell'hub: aggiungi --version. Se l'hub revoca l'agent, il servizio si ferma e non viene riavviato (exit status 3).
La dashboard usa HTTP in chiaro sulla porta 8080. Prima di esporre l'hub oltre una rete fidata, metti davanti un reverse proxy con TLS e imposta listen: 127.0.0.1:8080 in /etc/regulus/config.yaml.
Sviluppo
Requisiti: Go 1.26+, Node.js 22+.
make build # compila la dashboard, poi bin/regulus
./bin/regulus hub # serve la dashboard su :8080
make test # go vet, go test -race, typecheck e test della dashboard
make lint # golangci-lint ed ESLint
make smoke # avvia bin/regulus e lo verifica end to end (dopo make build)
scripts/test-install.sh # test dello script di installazione (incluso in make test)
Per lavorare sulla dashboard, esegui npm run dev in dashboard/ (inoltra /api all'hub su :8080).
Rilasci
Ci sono tre canali: stable (v0.3.0), beta (v0.3.0-beta.2, o -rc.1) e alpha (v0.3.0-alpha.1). Per pubblicare, un maintainer avvia Publish release dalla scheda Actions su main, scegliendo il canale (e, quando inizia una nuova versione, se alzare il numero di patch, minor o major). Rifiuta un commit su cui la CI non è passata, calcola la versione successiva (scripts/next-version.sh: una versione passa dalle sue alpha, poi dalle beta, poi dalla release; le pre-release sono numerate per canale), crea il tag e avvia il workflow di release sul tag. Fare il push di un tag v* a mano ha lo stesso effetto.
Il workflow di release (GoReleaser) pubblica una GitHub Release con archivi tar.gz, pacchetti .deb e .rpm (linux amd64 e arm64), SBOM, install.sh e checksums.txt, la cui firma Sigstore è checksums.txt.sigstore.json. Alpha e beta sono segnate come pre-release, quindi la Latest di GitHub (e releases/latest/download/install.sh) è sempre l'ultima release stabile; le note di ogni release iniziano con il comando di installazione di quella versione. Per verificare un download usa i comandi riportati nella sezione inglese.
Configurazione
L'hub legge, in ordine di priorità crescente: valori predefiniti, file YAML (--config o REGULUS_CONFIG), variabili d'ambiente, flag. Le impostazioni e i nomi sono nella tabella in inglese qui sopra.
Con builtin_agent attivo (predefinito) l'hub esegue un agent per il proprio server, che si collega con lo stesso contratto di qualsiasi altro agent (id local).
L'hub salva i dati in un database SQLite (regulus.db) dentro data_dir e applica automaticamente le migrazioni dello schema all'avvio.
Aggiornamenti
L'hub cerca nuove release di Regulus, tiene scaricata e verificata la più recente e può installarla sul proprio server dalla dashboard, con rollback. Anche i server che gestisci (gli agent) si aggiornano dall'hub.
- Da dove: le GitHub Releases di questa repository, lette senza token. L'hub interroga ogni sei ore (un po' dopo un riavvio, e al massimo una volta al minuto dalla dashboard), con richieste condizionali per restare ben sotto il limite di GitHub per l'uso anonimo. Un build di sviluppo (
dev, o fatto dopo un tag congit describe) non interroga mai da solo e non risulta mai in ritardo. - Canale:
stableoffre solo le release;betaanche beta e release candidate (v0.3.0-beta.1,v0.3.0-rc.1);alphaogni release, alpha comprese (v0.3.0-alpha.1, e qualsiasi altra pre-release). Ogni canale prende la più recente tra quelle che offre, quindi passa alla beta o alla release quando esce. Un hub parte sul canale della versione che esegue. Le versioni si confrontano come SemVer, quindi l'hub non "aggiorna" mai a qualcosa di più vecchio. - Modalità, scelta nella dashboard (Updates, serve il permesso
core.update, che haadmine non haoperator):manualti avvisa soltanto;downloadscarica anche l'archivio di questa piattaforma in<data_dir>/updates/e lo verifica, poi aspetta che tu prema Install;automaticlo installa anche da solo, ma solo dentro una finestra di manutenzione che scegli (due orari come 03:00 e 05:00, nel fuso del server, anche a cavallo della mezzanotte) e non riprova mai una versione che ha dovuto fare rollback. Ogni cambio di impostazioni, ogni download e ogni installazione finisce nell'audit. - Cosa significa "verificato": prima la firma di
checksums.txt, poi l'archivio. Il workflow di release firmachecksums.txtcon Sigstore (keyless,checksums.txt.sigstore.json); l'hub verifica quel bundle al suo interno, senza bisogno dicosign: il certificato deve indicare il workflow di release di questa repository al tag della release, emesso da GitHub Actions, con una voce nel registro di trasparenza e il timestamp del certificato. Solo allora l'archivio viene scaricato e il suo SHA-256 confrontato con l'elenco; se non coincide viene cancellato. Una release con firma mancante o non valida non viene mai conservata. La trusted root di Sigstore viene presa dal repository TUF di Sigstore e conservata in<data_dir>/tuf(una rotazione delle chiavi arriva da sola all'hub); se non è raggiungibile si usa uno snapshot incluso nel binario. - Installare, e perché senza
sudo: l'hub gira come utente senza privilegireguluse resta tale. Il pacchetto includeregulus-update.path, che l'installer abilita (install.sh --no-self-updatelo salta) insieme aupdates.apply: true(REGULUS_UPDATES_APPLY). Quando premi Install l'hub lascia una richiesta che indica una versione e nient'altro; systemd avvia come rootregulus-update.service, che esegueregulus update-apply. Quell'helper non si fida di nulla che l'utente dell'hub possa aver scritto: copia l'archivio in una cartella sua, verifica di nuovo la firma Sigstore dichecksums.txte l'archivio rispetto ad essa, rifiuta una versione non più recente di quella installata, estrae l'eseguibile, controlla che dichiari la versione attesa, conserva il binario attuale in/var/lib/regulus-update/regulus.previous, sostituisce/usr/bin/regulusin modo atomico e riavvia l'hub. Poi osserva il nuovo hub (il servizio deve essere attivo, dichiarare la nuova versione e restare su per 15 secondi, entro 90); altrimenti rimette il binario precedente e lo riavvia, e l'esito èrolled_back. L'hub si riavvia nel mezzo, quindi la dashboard è irraggiungibile per un attimo; l'esito (applied,rolled_back,failed) compare nella pagina Updates e nell'audit. Viene sostituito solo l'eseguibile: una release che cambia le unit systemd o il file di configurazione richiede una volta il pacchetto (install.sh). Poiché dpkg non sa del file sostituito,dpkg --verify reguluslo segnala come modificato, e una successiva reinstallazione del pacchetto conaptriporta la versione contenuta nel pacchetto. - Aggiornare i server (agent): prima si aggiorna l'hub, poi i server, alla versione che l'hub esegue; un agent può essere indietro di una serie di release (vedi Versioni), quindi esiste sempre una finestra in cui convivono. La pagina Updates elenca i server connessi in ritardo e li aggiorna a ondate (1, 3, 10 o tutti), fermandosi al primo errore, con un esito per server e una voce nell'audit (
POST /api/updates/rollout). Nella modalitàautomaticl'hub lo fa da solo dentro la finestra di manutenzione e non ripete un rollout fallito. Come funziona: l'hub si assicura che la sua cache contenga la release firmata con l'archivio di ogni piattaforma in uso (amd64 e arm64), scaricando ciò che manca; manda all'agent un comandoApplyUpdate; l'agent prende dall'hubchecksums.txt, la sua firma e l'archivio della propria piattaforma sulla connessione mTLS già esistente (quindi non serve internet), controlla firma e checksum e lascia una richiesta all'helper del suo server (regulus-agent-update.path, che l'installer abilita per il ruolo agent insieme aupdates: trueinagent.yaml;--no-self-updatelo salta). Quell'helper funziona come quello dell'hub: ricontrolla tutto come root, sostituisce il binario e riavvia l'agent. Il nuovo agent è sano quando si riconnette all'hub con la nuova versione (scriveconnected.jsonper l'helper); se non succede entro 90 secondi il binario precedente viene rimesso. L'hub segna un serverupdatedsolo quando lo rivede con la nuova versione, efailedse torna con la vecchia (rollback) o non torna. Un agent installato prima di questa funzione va aggiornato una volta coninstall.sh; la dashboard lo dice. Un rollout dura quanto l'hub: riavviare l'hub lo interrompe. - Spegnerlo: imposta
updates.check: false(oREGULUS_UPDATES_CHECK=false) su un hub che non deve contattare internet. Non viene richiesto nulla e la pagina ne spiega il motivo. - API:
GET /api/updates,PUT /api/updates/settings,POST /api/updates/check,POST /api/updates/download,POST /api/updates/apply,GET/POST /api/updates/rolloutePOST /api/updates/rollout/stop(tutte richiedonocore.update; vediinternal/hub/openapi.yaml). Il download gira in background; interrogaGET /api/updates.
Console e riga di comando
Esegui regulus sul server (o regulus console) per aprire una console a schermo intero, nello spirito delle CLI di Claude Code e Gemini. Ha quattro sezioni, raggiungibili con tab, le frecce, 1-4 o il mouse:
- Status: se l'hub è in esecuzione (e lo stato dell'unità systemd), versione, uptime, indirizzo e porta della dashboard (con gli indirizzi di questa macchina in rete), la porta degli agent, la directory dati e la dimensione del database, il file di configurazione e gli agent connessi. Finché non esiste un amministratore mostra il token di setup monouso, e
pcrea (dopo conferma) un link monouso per reimpostare la password dell'amministratore, così un server senza browser a portata di mano è comunque gestibile. - Settings: ogni impostazione con descrizione, valore predefinito, valore attuale e origine (
default,fileoenvironment).invione modifica una; il testo è controllato mentre si scrive,tabscorre le scelte ammesse, einvioscrive la modifica nel file YAML mantenendone i commenti. La console dice se serve riavviare l'hub e lo offre conr(sudo systemctl restart regulus). - Logs: le ultime righe del log dell'hub da journald, aggiornate ogni due secondi, con filtro per livello su
f. - Health: la configurazione è valida, l'hub risponde, le porte sono libere o servite dall'hub, la directory dati è scrivibile, il database esiste.
I dati in tempo reale (agent, uptime, token di setup) arrivano dall'hub in esecuzione tramite un socket Unix nella directory dati (regulus.sock, modo 0600, di proprietà dell'utente che esegue l'hub), non tramite il login web. Con l'installazione da pacchetto la directory dati è privata per regulus, quindi apri la console con sudo -u regulus regulus per vedere tutto, o con sudo regulus (anche le modifiche al file di configurazione richiedono root). Senza accesso al socket la console mostra comunque le impostazioni e dice cosa manca.
Ogni schermata ha un equivalente da script che usa lo stesso codice e lo stesso file:
| Comando | Cosa fa |
|---|---|
regulus status [--json|--plain] |
le informazioni di Status e Health; exit status 0 se l'hub è in esecuzione, 3 se non lo è, 1 se la configurazione è rotta |
regulus config list [--json|--plain] |
ogni impostazione con valore e origine |
regulus config get CHIAVE |
il valore attuale di un'impostazione |
regulus config set CHIAVE VALORE |
valida e scrive un'impostazione nel file di configurazione (una porta sola come 9090 diventa :9090; agent_listen off disattiva gli agent remoti) |
regulus config path |
il file di configurazione in uso |
Il file è --config, altrimenti REGULUS_CONFIG, altrimenti /etc/regulus/config.yaml se esiste. config set cambia solo quella riga (un esempio commentato come # agent_listen: ":8443" diventa l'impostazione) e rifiuta un valore che l'hub non accetterebbe. Le impostazioni sono listen, agent_listen, data_dir, builtin_agent, metrics_interval, log.level, log.format e console.tui.
Disattivare lo schermo interattivo. Non è mai imposto. regulus stampa lo stato come testo quando vale una di queste condizioni, in quest'ordine (un flag prevale su una variabile d'ambiente, che prevale sul file di configurazione, che prevale sul rilevamento): il flag --no-tui (alias --notui; --tui forza lo schermo), l'ambiente (REGULUS_NO_TUI=1, CI impostata, TERM=dumb), console.tui: false nel file di configurazione, o il rilevamento (stdin o stdout non sono un terminale, come una pipe, cron, systemd o SSH senza TTY, oppure il terminale è più piccolo di 60x16). --json e --plain stampano invece di aprire lo schermo, e --no-input (alias --non-interactive) significa che non viene mai fatta una domanda. NO_COLOR e --no-color tolgono solo i colori; senza un locale UTF-8 la console disegna con caratteri ASCII semplici. Le stringhe dello schermo sono in internal/console/strings.go, pronte per una traduzione.
Non ancora nella console: raggiungibilità dalla rete, validità dei certificati e aggiornamenti in sospeso in Health (richiedono le funzioni di aggiornamento e TLS), e la creazione dell'amministratore (la fanno il token di setup e la dashboard).
Autenticazione
L'hub ha account locali; ognuno ha un ruolo, e un ruolo è un insieme di permessi (vedi Ruoli e permessi sotto). Tre ruoli sono predefiniti: admin, operator e viewer. Al primo avvio, senza account, l'hub scrive nel log un token di setup monouso (la riga "no accounts yet", campo setup_token); la dashboard lo chiede insieme a nome utente e password del primo admin, così solo chi può leggere il log dell'hub può prendere possesso di un hub non configurato. A ogni avvio viene generato un nuovo token finché l'admin non esiste.
- Le password usano argon2id (almeno 10 caratteri). Il login crea una sessione di 7 giorni salvata nel database (solo lo SHA-256 del token); il cookie è
HttpOnly,SameSite=LaxeSecurese la richiesta è arrivata in HTTPS (direttamente o conX-Forwarded-Proto). Le richieste POST devono essere JSON dalla stessa origine. - Dopo 10 login falliti dallo stesso indirizzo in 15 minuti, i tentativi successivi ricevono
429fino a fine finestra. Dietro un reverse proxy tutti i client condividono l'indirizzo del proxy, quindi limita le richieste anche nel proxy. - Tutto sotto
/api/tranne/api/auth/*richiede il login (altrimenti401). I file della dashboard e/healthzsono pubblici: non contengono dati e la pagina mostra login o setup prima di tutto. - Fuori da una rete fidata servi l'hub in HTTPS (va bene un reverse proxy): altrimenti sessioni e password viaggiano in chiaro.
Endpoint: GET /api/auth/me, POST /api/auth/setup, POST /api/auth/login, POST /api/auth/logout, POST /api/auth/reset.
Utenti, sessioni e audit. Gli admin gestiscono gli account dalla dashboard (Users) o con l'API: GET/POST /api/users, PATCH/DELETE /api/users/{username}. Cosa può fare ogni utente dipende dai permessi del suo ruolo (403 se mancano). L'ultimo admin attivo non può mai essere declassato, disabilitato o eliminato. Disabilitare o eliminare un account ne chiude subito le sessioni, e un account disabilitato riceve la stessa risposta "invalid username or password" di una password errata. Ogni utente può cambiare la propria password (POST /api/account/password, che chiude le altre sue sessioni e conta le password attuali errate nel limite di tentativi) ed elencare o chiudere le proprie sessioni (GET /api/account/sessions, DELETE /api/account/sessions/{id}; ogni sessione registra indirizzo e browser di creazione). Gli eventi degli account (setup, login e login falliti, modifiche agli utenti, cambi password, sessioni chiuse) vanno in un log di audit nel database, con gli ultimi 10.000 conservati, leggibile con il permesso audit.read da GET /api/audit?limit= e nella pagina Users; non salva mai password o token.
Ruoli e permessi. Ogni endpoint che modifica o rivela qualcosa controlla un permesso con nome, non il nome di un ruolo: servers.read (vedere i server e le loro metriche), servers.manage (token di registrazione, rinominare, revocare), layout.edit (organizzare la dashboard), users.manage (account, reset, politica 2FA), roles.manage (i ruoli qui sotto), audit.read e core.update (vedere e gestire gli aggiornamenti). I ruoli predefiniti stanno nel codice: admin ha ogni permesso, compresi quelli che aggiungeranno le versioni future; operator ha servers.read, servers.manage e layout.edit; viewer ha servers.read e layout.edit. Chi ha roles.manage può creare ruoli personalizzati per ogni installazione da Users → Roles (GET/POST /api/roles, PATCH/DELETE /api/roles/{name}, con audit): si sceglie un nome (lettere minuscole, cifre, - e _) e si spuntano i permessi. Un ruolo in uso non si può eliminare e quelli predefiniti non si possono modificare. Nessuno può dare più di quanto ha: a un ruolo o a un account si possono assegnare solo permessi che l'autore possiede, e un account può essere modificato, disabilitato, eliminato o azzerato solo da chi ha tutti i suoi permessi. Le modifiche a un ruolo valgono dalla richiesta successiva, senza nuovo login. I permessi dell'utente autenticato sono in GET /api/auth/me. I privilegi del sistema operativo dei servizi sono un'altra questione (vedi le decisioni di progetto e la #46).
Autenticazione a due fattori. Ogni utente può aggiungere un secondo passaggio al login da Account → Two-factor authentication: dopo aver confermato la password la dashboard mostra un QR code (e la chiave, da digitare a mano) per qualsiasi app TOTP (RFC 6238: Google Authenticator, Aegis, 1Password, Bitwarden…). Il secondo fattore si attiva solo dopo aver inserito un codice valido dell'app, e a quel punto vengono mostrati una sola volta dieci codici di recupero (salvati solo come hash; ognuno vale una volta al posto di un codice dell'app; rinnovabili, il che invalida i vecchi). Da allora una password corretta non crea una sessione: l'hub risponde con un login in sospeso di breve durata (5 minuti, 5 tentativi, non è una sessione) e la sessione nasce solo dopo un codice valido. I codici sono accettati entro un passo di 30 secondi prima o dopo l'orologio e ognuno vale una volta. I codici errati contano nel limite dei login falliti qui sopra, e il passaggio della password non lo azzera. Usare un codice di recupero viene registrato nell'audit e nel log come avviso, e all'accesso l'utente vede quanti ne restano. Disattivarlo o rifare i codici di recupero richiede password e un codice. I segreti TOTP sono salvati cifrati (AES-256-GCM) con una chiave derivata da secret.key nella directory dati (creato al primo avvio, leggibile solo dal proprietario): una copia del solo database non li rivela, quindi fai il backup di entrambi. In Users → Security un admin può rendere obbligatoria la 2FA per gli amministratori o per tutti; chi è interessato deve configurarla prima di tutto (l'API risponde 403 con code: two_factor_setup_required fino ad allora) e non può disattivarla. Un admin può rimuovere il secondo fattore a chi ha perso app e codici (Reset two-factor, registrato nell'audit; chiude le sue sessioni). Endpoint: POST /api/auth/login/two-factor, GET /api/account/two-factor, POST /api/account/two-factor/{setup,enable,disable,recovery-codes}, DELETE /api/users/{username}/two-factor, GET/PUT /api/settings/security.
Credenziali perse. Il recupero non richiede email.
- Un utente ha dimenticato la password: un admin apre Users, sceglie Reset password e consegna il link all'utente (
POST /api/users/{username}/reset). Il link èhttp://<hub>/#/reset/<token>: il token è casuale, salvato solo come hash, vale una volta sola, scade dopo un'ora e sostituisce ogni link precedente dello stesso account; sta nel frammento dell'URL, quindi non arriva mai nei log del server. L'utente lo apre, sceglie la nuova password (POST /api/auth/reset) e accede; l'admin non vede mai la password. Creare il link chiude tutte le sessioni dell'account, e lo stesso fa usarlo. Gli account disabilitati non si possono azzerare. I token sbagliati contano nello stesso limite dei login falliti (10 ogni 15 minuti per indirizzo), e ogni token sconosciuto, scaduto o già usato riceve la stessa risposta. - L'admin è bloccato fuori: sulla macchina che esegue l'hub, come l'utente proprietario della directory dati, esegui
sudo -u regulus regulus hub reset-admin --config /etc/regulus/config.yaml(con l'installazione da pacchetto; altrimenti passa gli stessi--configo--data-dirdell'hub, e--user NOMEse ci sono più admin). Stampa un link di reset monouso come sopra per quell'admin, riabilita l'account se era disabilitato e ne chiude le sessioni; l'hub può restare in esecuzione. Chi può scrivere nella directory dati è comunque il padrone dell'hub, quindi quell'accesso è la prova di proprietà. La password si sceglie nel browser, quindi non compare mai nel terminale né nella cronologia della shell. Il comando è registrato nell'audit comeconsole. - L'admin ha perso anche l'app di autenticazione:
reset-adminrimuove anche la 2FA dell'admin (l'accesso alla console è la prova di proprietà), quindi basta la nuova password per entrare; riattivala da Account. Per gli altri utenti un admin usa Reset two-factor. Le passkey saranno cancellate da un reset quando questa funzione esisterà.
Agent remoti
Oltre all'agent integrato, un server può eseguire regulus agent ed essere gestito dall'hub. L'agent si collega sempre in uscita verso l'hub (gRPC su TLS 1.3), quindi il server gestito non deve aprire nessuna porta.
- Attiva il listener. Gli agent remoti sono spenti di default. Imposta
agent_listen: ":8443"(oppure--agent-listen,REGULUS_AGENT_LISTEN) e riavvia l'hub. Apri quella porta nel firewall solo verso i server che gestisci. - Crea un token. Un admin apre Servers → Add a server nella dashboard, dà se vuole un nome al server e ottiene il comando da eseguire, con un token monouso valido un'ora, la porta e la fingerprint della CA dell'hub (le stesse cose che restituisce
POST /api/enrollment-tokens). Il token viene mostrato una sola volta. - Registra l'agent sull'altro server, una volta sola. Su Ubuntu o Debian il modo più semplice è il comando dell'installer mostrato dalla dashboard (vedi Installazione); a mano, con il binario già presente:
L'agent controlla prima che il certificato dell'hub sia firmato da una CA con quella fingerprint (così il token non va mai a un impostore), crea la propria chiave e l'hub firma il suo certificato. La chiave resta sull'agent. Da quel momento servono soloregulus agent --hub HOST_HUB:8443 --token TOKEN --ca-fingerprint sha256:... --data-dir /var/lib/regulus-agent--hube le credenziali salvate; le stesse opzioni possono stare in un file YAML (--config:hub,data_dir,log) o nelle variabiliREGULUS_AGENT_*. - Revoca un agent dalla pagina del suo server o con
DELETE /api/agents/{id}: la connessione si chiude e non potrà più collegarsi; il processo si ferma con un errore. Per usare di nuovo quel server, cancella la sua directory dati e registralo con un nuovo token.
Come è protetto: l'hub crea una propria autorità di certificazione al primo avvio (pki/ nella directory dati, ca.key leggibile solo dal proprietario). I token sono casuali, salvati solo come hash, monouso, scadono dopo un'ora e hanno un limite di tentativi (10 errori ogni 15 minuti per indirizzo). Ogni connessione richiede il certificato client di un agent registrato e non revocato, e l'id dell'agent viene dal certificato, non da ciò che l'agent dichiara. I certificati client durano cinque anni; il rinnovo non c'è ancora. Creare token, rinominare e revocare agent richiede servers.manage e finisce nell'audit.
Versioni. Agent e hub concordano una versione del protocollo alla connessione (l'agent invia l'intervallo che parla, l'hub sceglie la più alta che entrambi capiscono). In più l'hub accetta agent della propria serie di release e della precedente: stessa versione minore, una minore indietro (l'hub 0.3.x accetta 0.3.x e 0.2.x) o qualsiasi release della major precedente. Aggiornare prima l'hub è quindi sicuro: i suoi agent continuano a funzionare finché non vengono aggiornati. La pagina Servers segnala gli agent indietro rispetto all'hub o avanti (aggiorna l'hub). Un agent più indietro viene rifiutato con un messaggio che lo dice e il server compare come incompatible con il motivo. Un rifiuto dovuto alle versioni non ferma il servizio dell'agent: riprova ogni pochi secondi, quindi si collega da solo appena l'agent o l'hub vengono aggiornati. Un agent revocato si ferma comunque per sempre. I build di sviluppo non vengono mai giudicati.
API HTTP
GET /api/agents restituisce in JSON gli agent connessi (id, versione, info di sistema, status connected o stale, ultimo heartbeat, uptime). GET /api/agents/{id}/metrics restituisce le metriche recenti di un agent (CPU, load, memoria, swap, dischi, contatori e velocità di rete, uptime), dalla più vecchia; ?since=<RFC 3339> restituisce solo i punti più recenti. L'hub tiene in memoria l'ultima ora di metriche (fino a 3600 report per agent), quindi lo storico si perde al riavvio dell'hub. GET /api/agents/{id} restituisce un solo agent. GET /api/agents?all=true elenca anche gli agent registrati offline o revoked, con l'ultimo momento in cui l'hub li ha sentiti (last_seen_at); PATCH /api/agents/{id} (servers.manage) assegna un nome visualizzato. GET /api/layout e PUT /api/layout leggono e salvano il layout della dashboard dell'utente autenticato (qualsiasi JSON fino a 64 KiB, per utente). GET /api/stream passa a WebSocket e invia in JSON uno snapshot degli agent e poi gli eventi agent.connected, agent.disconnected, agent.heartbeat e agent.metrics; richiede il cookie di sessione, rifiuta le pagine di altre origini e disconnette il client che legge troppo lentamente (deve riconnettersi). Tutto è descritto in internal/hub/openapi.yaml, servito anche su /api/openapi.yaml; un test mantiene il file allineato alle route. Gli endpoint richiedono il login e le GET sono in sola lettura; la dashboard mostra i server nella pagina Servers.
Dashboard
Dopo il login la dashboard mostra un'intestazione con le sezioni Dashboard, Servers, (per chi può gestire utenti o ruoli o leggere l'audit) Users e (con core.update) Updates, e un link alla pagina Account (cambio password, le tue sessioni). La pagina Dashboard è una griglia di widget: Customize abilita trascinamento e ridimensionamento, Add widget sceglie tra i widget registrati e Reset to default ripristina il layout iniziale. Il layout è salvato per utente tramite /api/layout (con debounce, e salvato anche quando si lascia la pagina). I widget si registrano in dashboard/src/widgets/ con registerWidget (id, titolo, dimensione predefinita, componente); un widget che va in errore rompe solo il proprio riquadro, e un layout che cita un widget sconosciuto (ad esempio di un plugin rimosso) mostra un segnaposto che si può rimuovere.
L'installazione base include questi widget, alimentati in tempo reale dall'hub (la dashboard segue /api/stream e si riconnette da sola se la connessione cade): System (host, sistema operativo, kernel, memoria, load, uptime), CPU e Memory (valore attuale e grafico degli ultimi 10 minuti), Network (velocità in ricezione e invio, totale e per interfaccia), Disk usage (una barra per filesystem montato), Uptime e Servers. Tutti tranne Uptime sono nel layout predefinito. Con più agent collegati ogni widget ha un selettore e ricorda la scelta; di default mostra l'agent integrato dell'hub.
La pagina Servers elenca tutti i server registrati, online o no, con sistema, versione dell'agent, stato (online, stale, offline, revoked) e ultimo contatto. Una riga apre la pagina di quel server: i suoi dati e i widget System, CPU, Memory, Network e Disk usage legati a lui. Chi ha servers.manage lo rinomina o lo revoca da lì e ne aggiunge di nuovi con Add a server. Un server offline mostra quando è stato visto l'ultima volta; uno revocato non può più collegarsi.
Contribuire
Vedi CONTRIBUTING.md e le decisioni di progetto. Problemi di sicurezza: SECURITY.md.
Licenza
Apache License 2.0. Vedi LICENSE.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
regulus
command
Command regulus is the single Regulus executable.
|
Command regulus is the single Regulus executable. |
|
internal
|
|
|
agent
Package agent is the client side of the agent-hub contract.
|
Package agent is the client side of the agent-hub contract. |
|
auth
Package auth implements the hub's local accounts: first-run setup of the admin, password hashing, login sessions and the HTTP middleware that protects the API.
|
Package auth implements the hub's local accounts: first-run setup of the admin, password hashing, login sessions and the HTTP middleware that protects the API. |
|
config
Package config loads the hub configuration.
|
Package config loads the hub configuration. |
|
console
Package console is the interactive terminal console of Regulus and the plain-text and JSON output that takes its place when no terminal UI is wanted: what the hub is doing, its settings, its logs and health checks.
|
Package console is the interactive terminal console of Regulus and the plain-text and JSON output that takes its place when no terminal UI is wanted: what the hub is doing, its settings, its logs and health checks. |
|
fleet
Package fleet manages the remote agents of the hub: the single-use tokens that let a new agent enroll, the registry of the agents that did, and the revocation of an agent.
|
Package fleet manages the remote agents of the hub: the single-use tokens that let a new agent enroll, the registry of the agents that did, and the revocation of an agent. |
|
hub
Package hub implements the Regulus hub: the HTTP server that serves the dashboard and, in later phases, the API and the agent connections.
|
Package hub implements the Regulus hub: the HTTP server that serves the dashboard and, in later phases, the API and the agent connections. |
|
inproc
Package inproc is an in-memory gRPC transport.
|
Package inproc is an in-memory gRPC transport. |
|
localstatus
Package localstatus is the small channel between a running hub and the tools on the same machine (the console and `regulus status`): a Unix socket in the data directory, readable only by the user that runs the hub.
|
Package localstatus is the small channel between a running hub and the tools on the same machine (the console and `regulus status`): a Unix socket in the data directory, readable only by the user that runs the hub. |
|
logging
Package logging builds the structured logger used across Regulus.
|
Package logging builds the structured logger used across Regulus. |
|
pki
Package pki is the small certificate authority of the hub.
|
Package pki is the small certificate authority of the hub. |
|
protocol
Package protocol holds the version of the agent-hub contract defined in proto/regulus/agent/v1.
|
Package protocol holds the version of the agent-hub contract defined in proto/regulus/agent/v1. |
|
storage
Package storage opens the hub database (SQLite) and applies its migrations.
|
Package storage opens the hub database (SQLite) and applies its migrations. |
|
update
Package update finds, downloads and verifies new releases of Regulus.
|
Package update finds, downloads and verifies new releases of Regulus. |
|
webui
Package webui embeds the compiled dashboard so the hub can serve it.
|
Package webui embeds the compiled dashboard so the hub can serve it. |