Self-hosted, multi-node hosting control panel written in Rust.
One binary per server, one web UI on the master. It provisions PHP / static / Node.js sites end-to-end — Linux user, nginx vhost, FPM pool, database, TLS, WordPress — in one transaction that rolls back cleanly if any step trips, so you never inherit a half-built site at 2am. Runs a fleet of VPSes from one screen, drives it all from a scriptable HTTP API + CLI, and imports what you've already got on HestiaCP or CloudPanel.
Install · Features · Remote API · Migrate in · Multi-node · Architecture · Status
Warning
Hyperion is a free-time project. It compiles, 700+ tests are green, and it's
been driven end-to-end across throwaway VMs — and it has never once seen real
production traffic. Schrödinger's hosting panel: probably fine, definitely
unobserved. Run it on boxes you'd be happy to dd into oblivion, keep
backups, and don't point anything you can't afford to lose at it — yet.
🙏 Testers & reviewers wanted. Kick the tyres and tell me what breaks —
feedback on functionality, security, or just design / UX is worth its weight
in cargo build minutes right now. Open an issue, however rough.
- Screenshots
- Why Hyperion?
- Install
- Features
- Hosting · Per-hosting controls · WordPress · Reinstall · File manager · Backups · Security · Multi-node · Operator UI · RBAC
- Remote API — scriptable HTTP +
hctl remote - CLI — local
hctl - Migrate in (panel import)
- Multi-node cookbook
- Architecture
- Project layout
- Project status
- Testing · Documentation · Contributing · License
Dashboard — KPI tiles, load + bandwidth sparklines, audit feed. |
Stats — cluster + per-node, sampled every 5 min, 200-min charts. |
The honest pitch: most open-source panels are PHP wrappers around shell templating. They work — right up until a domain with a funny character turns a config write into accidental
bashimprov, and you realise you've been trusting ~10 000 lines of string-concatenated shell running as root. Hyperion does the same job from a small, security-first Rust core where the worst a typo earns you is a compile error instead of a 2am page — and it scales across servers out of the box.
| HestiaCP / Vesta / aapanel | Hyperion | |
|---|---|---|
| Memory-safe language | ❌ PHP + bash | ✅ Rust, #![forbid(unsafe_code)] |
| Multi-node cluster | ❌ single-node | ✅ master + N workers, signed RPC |
| Atomic provisioning (no half-creates) | ✅ LIFO rollback on every step | |
| Scriptable HTTP API (OpenAPI) + CLI | ✅ /api/v1 + hctl remote |
|
| Live progress for long operations | ❌ | ✅ HTMX-polled bar on every job |
| Tamper-evident audit log | ❌ | ✅ BLAKE3 hash chain |
| TOTP 2FA + per-session revocation | ✅ in core | |
| WP install + plugin manage + Redis cache | ✅ first-class | |
| Zero-config Let's Encrypt + auto-renewal | ✅ | ✅ HTTP-01 + DNS-01 wildcard |
| Per-hosting disk quota (kernel-enforced) | ✅ | ✅ |
| Off-site backups (S3 + age encryption) | ✅ S3 + age (multi-target) | |
| One-click cross-node migration + clone | ❌ | ✅ |
| Import from HestiaCP / CloudPanel | ❌ | ✅ in-place or remote (SSH) |
curl -fsSL https://raw.githubusercontent.com/nechodom/hyperion/main/packaging/install/install-master.sh \
| sudo bashYes, it's curl | sudo bash piping a script straight into root. Read it
first — I would.
In ~3–5 minutes the script runs a port pre-flight and a configurator
(below), then apt-installs the chosen packages + PHP 8.3, builds Hyperion from
source, lays down /etc/hyperion/{agent,web}.toml, installs systemd units,
starts both services, and prompts for an admin password.
============================================================
✓ Hyperion master installed
----------------------------------------
Web UI: https://<your-host>:8443
CLI: hctl info
============================================================
sudo usermod -aG hyperion-admin "$USER" # log out / in, then visit the URLHyperion drives host services (nginx, PHP-FPM, and optionally MariaDB / PostgreSQL / vsftpd), so before touching anything the installer checks it can actually run there — handy on a box that already runs docker or another stack.
Port pre-flight — for every port it needs (80, 443, panel, FTP, DB, node
RPC) it finds the holder (ss + docker ps) and, on a conflict, prints exactly
what holds it and offers to [s]top it or [a]bort. A port already owned by
the right service (a re-run) is not a conflict.
Component + port picker — nginx + PHP-FPM are always installed;
MariaDB / PostgreSQL / vsftpd are opt-out via an interactive [Y/n]; and
ports are adjustable. Whatever you skip is neither installed, started, nor
pre-flight-checked.
# Dry-run: check a candidate box WITHOUT installing anything
curl -fsSL <installer-url> | sudo HYPERION_PREFLIGHT_ONLY=1 bash
# Non-interactive: no local DBs (using one in docker), custom FTP + panel ports
curl -fsSL <installer-url> | sudo \
HYPERION_WITH_POSTGRES=0 HYPERION_WITH_MARIADB=0 \
HYPERION_FTP_PORT=2121 HYPERION_LISTEN=0.0.0.0:9000 bashAll installer env knobs
| Env | Default | Effect |
|---|---|---|
HYPERION_ADMIN_USER / HYPERION_ADMIN_PASS |
admin / prompt |
bootstrap admin |
HYPERION_LISTEN |
0.0.0.0:8443 |
panel bind addr:port |
HYPERION_ACME_EMAIL |
— | Let's Encrypt contact |
HYPERION_WITH_MARIADB / _POSTGRES / _VSFTPD |
1 (install) |
component on/off |
HYPERION_FTP_PORT |
21 |
vsftpd control port |
HYPERION_RPC_PORT |
9443 |
master→node RPC port (node installer) |
HYPERION_NONINTERACTIVE |
— | accept defaults, no prompts |
HYPERION_PREFLIGHT_ONLY |
— | run checks + exit, install nothing |
HYPERION_STOP_CONFLICTS / HYPERION_ALLOW_SHARED |
— | auto-stop port holders / ignore conflicts |
HYPERION_REF · HYPERION_INSTALL_DIR · HYPERION_GIT_URL · HYPERION_GIT_TOKEN · HYPERION_LOCAL_TARBALL · HYPERION_SKIP_CLONE |
— | source acquisition (branch/tag, private repo, offline) |
See docs/RUNBOOK.md for private-repo and air-gapped installs.
Web UI → Nodes → fill label → Generate invite → copy the printed
curl … | sudo bash --token=… --master=… command and paste it on a fresh
Debian 12+ VPS (it runs the same pre-flight + configurator). The node enrolls
within ~30 s and shows up in the Nodes table; from then on you provision
hostings on it straight from the master UI.
sudo /opt/hyperion/packaging/install/update.sh
# or, from the web UI: /install → row for the node → Update…Stops services, fast-forwards /opt/hyperion, rebuilds, reinstalls binaries,
refreshes systemd units only if they changed, and tails journalctl on health
failure. While hyperion-web is briefly down mid-update, the panel vhost serves
a self-refreshing "Hyperion is updating…" page (HTTP 503), not a bare nginx
502, and returns automatically once the service is back.
git clone https://github.com/nechodom/hyperion
cd hyperion
cargo build --release --workspace # → target/release/{hyperion-agent,hyperion-web,hctl}See docs/RUNBOOK.md for a minimal rootless
agent.toml + web.toml.
Every binary stamps its own version at build time (build.rs), so --version
always tells you exactly what's running:
hyperion-agent --version
# hyperion-agent v0.42.0-2-gf718fd1 (f718fd1a…full 40-char SHA…)The human part is git describe against the nearest vX.Y.Z tag. There's
nothing to bump by hand — cut a milestone with git tag vX.Y.Z && git push --tags and CI builds a named GitHub release. A rolling release ships on every
push to main (what update.sh pulls); both contain byte-identical binaries
for the same commit.
- One-click create — Linux user, PHP-FPM pool, MariaDB / Postgres DB, nginx vhost, self-signed cert, all in one transaction. Failure at any step rolls back the rest — no orphan rows, no zombie users.
- PHP 8.1 / 8.2 / 8.3 / 8.4 side by side via deb.sury.org. Static-only sites, and a reverse-proxy mode for Node.js / Python / Docker.
- Suspend / resume — 503 page, FPM stop, DB lock, user processes killed. Fully reversible.
- Hosting profiles — stamp the same limits + WP plugins + DB engine onto a hundred sites in one click.
- Every slow action is a background job — create, migration, clone, backup, restore, cert issue, WP install, staging push, panel import, and bulk actions all spawn a detached server-side job and redirect to a live HTMX-polled
/jobs/<id>page. Close the tab and the work keeps running; an orphan reaper fails anything a restart interrupts. - Cross-node hosting migration with version preflight, and clone to a new domain on the same or different node.
- Expiration + grace with scheduled notifications and auto-suspend.
- Quota enforcement — kernel-level disk quota via
setquota, PHPmemory_limitper FPM pool, monthly bandwidth alerts. - Let's Encrypt certs — HTTP-01 one-click with automatic renewal, working through a Cloudflare/CDN proxy as-is. DNS-01 wildcards (
*.domain) are available via guided manual TXT records, but cannot renew themselves — Hyperion holds no DNS credentials by design. The DNS pre-check asks the domain's authoritative nameservers, not just this box's resolver, so a domain repointed minutes ago is not reported as wrong because of a stale cache. - Standalone or cluster. A single box is a first-class deployment, not a one-node cluster: cluster chrome is hidden until a second node enrols.
Operator knobs mapping straight to nginx / FPM / WordPress. Every save runs
nginx -t before commit; failures surface the verbatim daemon error.
- HTTP basic auth (bcrypt htpasswd + ACME bypass), HSTS presets + force-HTTPS, custom nginx snippet (validated, 32 KiB cap), maintenance mode, per-hosting FastCGI page cache.
- WP debug toggle + rotate
debug.log, per-hosting Redis object cache (dedicated ACL user, written towp-config.php), per-hostingphp.inioverride via.user.ini(no FPM restart). - Live log tail + search (access/error, no SSH), health score + notes / tags, vhost auto-heal (missing cert → fresh self-signed bootstrap so
nginx -tnever breaks). - Bot + country blocking — switch off AI crawlers, SEO scrapers and price-comparison bots by family, or refuse whole countries via a GeoIP map. Both rules exempt the ACME challenge path, so a block can never stop a renewal.
- Traffic by country per hosting, from the access log against a local GeoLite2 database.
Raw nginx snippets are admin-only (
HostingEditNginxRaw). nginx runs as root, so an arbitrary directive is control of the box, not a hosting setting — see Security.
- Plugin + theme manager via
wp-cli— list / install / activate / update / delete, bulk "update all", per-plugin auto-update toggle, upload a.zip. - Keyless "defender" — outdated plugin/theme detection against the public WordPress.org version data (no API key, cached on the node), severity-sorted, with a one-click minor/patch auto-update. "Couldn't check" is never reported as "all clear".
- Staging → push-to-prod — one click spins up a
staging.<domain>copy (files + DB, WP URLs rewritten); another pushes it back over production after a pre-push safety backup of prod first. - Site health — probes the site over loopback, and when it 5xxs, reproduces the fatal through wp-cli and names the plugin or theme in the stack trace. Offers to park the culprit by renaming its folder, which is the only remedy that works once WordPress will not boot:
wp plugin deactivateneeds a WordPress that loads. - Object-cache drop-in control —
wp-content/object-cache.phpis loaded before plugins and regardless of whether its plugin is installed, so an unreachable Redis takes the site down and blocks every wp-cli remedy. Park or restore it directly from Site health. - File-permission self-check — proves nginx can actually reach the docroot, finds ownership drift, unreadable files, world-writable entries, setuid files, an exposed
wp-config.phpand a world-readable PHP session directory. It performs a realmkdiras the site user rather than inferring from modes, so an exhausted disk quota or an out-of-inodes filesystem is named as itself instead of looking like a permission bug. - Repair core files (
wp core download --force --skip-content, pinned to the installed version) and a full reinstall behind a backup that has to succeed first — see Reinstall.
Two operations, kept apart because they are not the same risk:
- Repair core files replaces WordPress core at the version already installed and
leaves
wp-content,wp-config.phpand the database alone. This is what fixes most broken sites. - Reinstall deletes the document tree and every table in the database, then installs
fresh. It is collapsed behind a disclosure, gated on
HostingDelete, requires the domain typed in full — verified on the node, not only in the browser — and takes a backup first. If that backup fails, nothing is deleted and the job says so: a failed backup followed by a wipe is the worst outcome the feature can produce, so the control flow is built around preventing exactly that.
- Browse / upload / download / delete / mkdir / rename under
htdocs/, plus an inline editor for text files (no SCP round-trip). - Symlinks refused at the adapter layer; path traversal refused after canonicalisation; 64 MB write cap; type-the-name delete confirmation.
- Local — tar.gz of htdocs +
mysqldump/pg_dump+ JSON manifest on/var/lib/hyperion/backups. - Off-site S3 + age encryption — multi-target (Wasabi / Backblaze B2 / Minio / AWS), per-target retention (daily / weekly / monthly), client-side age encryption (operator keeps the private key off the node). Legacy FTP/FTPS/SFTP push still supported.
- Retention (max age + minimum N per hosting, pruned hourly), granular restore (full / DB-only / files-only), chunked download from the browser, and restore as a new domain (mirrors PHP/DB/kind; WP URLs auto-rewritten).
- Size-independent. Database dumps stream to disk rather than through memory;
tarexit 1 ("a file changed while being read") is treated as the success it is, which on a live site is the normal outcome once the tree is large; and long backups heartbeat so the stale-job reaper cannot fail work that is still running.
#![forbid(unsafe_code)]in every crate. Argon2id passwords, Ed25519-signed session cookies with a DB-backed revocation ledger.- TOTP 2FA with backup codes — enforced for admin+ roles, "remember this device 30 days" option.
- Native brute-force protection (fail2ban) — the agent scans each site's access log for
wp-login.php/xmlrpc.phpfloods and auto-bans via annftablesset (auto-expiring, survives reboots); manual ban/unban per hosting. - WAF-lite + wp-admin IP allowlist per hosting, key-only chrooted SFTP (gated by
sshd -t), per-IP rate limit on login + 2FA verify. - CSP + HSTS + X-Frame-Options + Permissions-Policy + Referrer-Policy on every response, per-form CSRF tokens, tamper-evident audit log (BLAKE3 hash chain +
Verify chainbutton), constant-time secret compares. - The tenant is a local user. Every hosting has a real Linux account, so isolation is enforced with uids and modes, not convention: PHP-FPM runs as the site's own user with
open_basedirand a private session directory, and privileged file operations inside a tenant tree resolve symlinks component-by-component before acting — the agent is root, and a path under a customer's webroot is attacker-controlled input. - Admin-only blast radius. Actions whose reach exceeds one hosting are gated separately from per-hosting rights: raw nginx config, node-wide vsftpd/FTPS changes, and reinstall (gated as
HostingDelete, since the outcome is the same class of loss). - FTPS with enforced TLS, available per node. Off by default and deliberately so — the FTP password is the site's Linux password, but enabling TLS restarts a shared daemon and locks out clients that cannot negotiate it.
hctl ftp ftps --offis the way back without a browser. Prefer key-only chrooted SFTP. - Applied migrations are immutable, enforced by a checksum lock in CI: sqlx refuses to start against a migration whose bytes changed, so an edited comment takes the agent down on every box that already ran it.
- Master + worker model. Master holds the web UI, audit log, and node registry; workers run an agent the master drives over a signed RPC channel (Ed25519 envelope over self-signed HTTPS, port 9443, IP-based — no DNS dependency).
- Per-page node switcher, hosting auto-placement (scores load + memory + hosting count), one-click migration / clone across nodes with live progress + version preflight, remote node update (apt + Hyperion, log streams into the panel).
- Test-node mode (auto-subdomains,
blog_public=0, prod refuses to land), cluster-wide stats + monitoring, master-as-control-plane-only toggle.
- axum + askama + HTMX, no JS build step, single binary; themed confirm modals (type-the-domain for destructive actions); live service-install progress; a
/jobspage for every long op (survives browser disconnect, reaper for orphans); role-aware navigation; dark + light themes. - Live stats with a configurable refresh interval, per-hosting traffic maps, signed-in device list with "sign out everywhere", and automatic redirect to the login page when a session expires rather than a silent 403.
- Template lints in CI catch the failures that compile and render perfectly and then fail for every user: a
multipart/form-dataform whose CSRF token is in a hidden field (never read — the upload 403s every time), adata-confirm-*on a<button>instead of the<form>(the dialog never opens), anhx-getwith no matching route (the panel spins forever), and a page with no way to reach it from the navigation.
Five roles: super_admin (god mode + user management), admin (sees everything, no user management), operator (CRUD on assigned hostings), customer (slim nav, only their own), viewer (read-only on granted hostings). Per-hosting access grants for the bottom three, plus custom roles built from a granular capability set.
A first-class, scriptable HTTP API at /api/v1 — provision and operate the
whole cluster from CI, cron, or your own tooling, with a machine-readable
contract and an official CLI.
- Auth — a Bearer key (
Authorization: Bearer hyp_…), minted in Settings → API keys (admin/scope_allaccounts). The key carries a capability set clamped to its owner and re-checked live on every call (lock or demote the owner → the key loses power immediately). - Per-key hardening — optional IP allowlist (CIDRs, matched against the real client IP behind nginx via
X-Forwarded-For) and rate limit (req/min →429 Retry-After). - Contract —
GET /api/v1/openapi.jsonis a generated OpenAPI 3 document (kept in lockstep with the routes);GET /api/v1/docsrenders it in a self-hosted, no-CDN viewer. Generate a client in any language. - Conventions — list endpoints paginate (
{items, next_cursor, total}+?limit&cursor&state&node&q); slow mutations return202 {job_id}you poll at/api/v1/jobs/:id; errors are a JSON envelope with the right status (400/401/403/404/409/429/502).
Endpoint map
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/me |
the key's identity + caps |
| GET | /api/v1/hostings |
list (paginated + filtered) |
| GET | /api/v1/hostings/:id |
detail |
| POST | /api/v1/hostings |
create → 201 / 409 |
| PATCH | /api/v1/hostings/:id/{limits,php,vhost,expiry,quota} |
config |
| POST | /api/v1/hostings/:id/{suspend,resume} |
lifecycle |
| DELETE | /api/v1/hostings/:id |
delete → 202 {job_id} |
| POST | /api/v1/hostings/:id/{backup,cert,wp/install,restore} |
ops → 202 {job_id} |
| GET | /api/v1/hostings/:id/backups |
list backups |
| POST | /api/v1/certs/renew-all |
cluster cert renew → 202 {job_id} |
| GET | /api/v1/nodes · /api/v1/jobs/:id |
cluster nodes · job status |
All writes enforce the same per-hosting access checks as the UI.
# one-time: save url + key to ~/.config/hyperion/remote.toml (0600)
hctl remote --url https://panel.example.com --key hyp_… login
hctl remote list --state active | jq '.items[].domain'
hctl remote create --domain new.example.com --php v8_3
hctl remote backup new.example.com --wait # polls the job to completion
hctl remote suspend new.example.com
hctl remote openapi > openapi.json # dump the contractEvery command prints the API's JSON verbatim (pipes into jq); a non-2xx status
exits non-zero. Connection resolves flags → HYPERION_API_URL/HYPERION_API_KEY
env → the config file. --insecure skips TLS verify for self-signed panels.
hctl is also a thin client over the same Unix socket as the web UI — the
"ssh in and poke" path when the node is too broken for the web to help. (For the
HTTP API from any host, see hctl remote.)
$ hctl info
agent: master.example.com version=v0.42.0-2-gf718fd1 schema=61 hostings=12
$ hctl hosting create example.com --php 8.3 --db mariadb
✓ created example_com (id=01K4Z…)
root: /home/example_com/example.com/htdocs
db: lm_a8c_examplecz (user=lm_a8c_u, password=Hx9k…RnG2)
$ hctl hosting suspend example.com --reason="payment overdue"
✓ suspended
$ hctl audit --limit 3
ID TS ACTOR ACTION RESULT
42 2026-06-08 14:42 agent hosting.backup ok
41 2026-06-08 14:42 agent hosting.suspend ok
40 2026-06-08 14:42 cli:root hosting.set_limits ok
$ hctl ftp check example.com # diagnose FTP on the owning node
$ hctl ftp perms example.com # file-permission report
$ hctl ftp ftps --off # turn node-wide FTPS back offhctl ftp ftps --off exists for one reason: enabling FTPS can lock out a client,
and recovering from that must not need a working browser session.
There is also a short front door, installed alongside the binaries:
hyperion update # same as packaging/install/update.sh
hyperion status # both units at a glance
hyperion logs # journalctl -u hyperion-agent -u hyperion-web -fMove existing sites off another panel without a weekend of manual work. Wizard
at /import (admin) or hctl hosting import-panel.
- Sources: HestiaCP and CloudPanel — reads the source panel's own state directly (Hestia's
*.conf, CloudPanel's SQLite), no scraping. - In-place or remote over SSH — remote imports from another machine with a host + private key (used for that one run,
0600, deleted after — never stored); files come across withrsync, DBs dumped overssh. - Dry-run first — a plan shows created / skipped / conflict before anything is touched; an existing domain is skipped, never overwritten.
- Sites + databases, WordPress included —
wp-config.phpauto-repointed at the new credentials. Mail & DNS are reported, never migrated (Hyperion runs neither). Runs as a background job. - Self-service — the wizard hands the source box a one-liner that downloads a static-musl exporter, reports its sites, waits for you to tick the ones you want, then streams only those back. The exporter is built for x86_64; the bootstrap refuses with a clear message on any other architecture rather than leaving the kernel to say "Exec format error".
Recipes — all doable from the master web UI; hctl / the API work too.
- Move a hosting to a worker —
/hostings/<domain>→ Transfer → Move → pick target. Full backup on the source, worker restores it, source is suspended (not deleted) so you can verify. Live progress on/jobs/<id>. - Clone prod → staging on another node — Transfer → Copy →
staging.example.com+ target node. Source keeps serving; the copy comes up with the same PHP / DB / files. - Update a worker —
/install→ worker row → Update… → tick → Start; the job runs on the worker, log tails into the panel. - Operate on one worker — every node-aware page has a "View on node:" dropdown.
- Import an existing panel — Import (sidebar) → source + mode → Preview → Apply.
Two layers per box:
hyperion-agentruns as root, owns all system state — users, dirs, nginx vhosts, FPM pools, DBs, certs, FTP, cron, backups. Listens on a local Unix socket (/run/hyperion.sock, mode 0660, grouphyperion-admin). On worker nodes also on0.0.0.0:9443for signed RPC from the master.hyperion-web(master only) — axum + askama + HTMX, runs unprivileged in thehyperion-admingroup. Owns the audit log, web users, sessions ledger, node registry, Ed25519 master signing key, and the/api/v1Bearer edge.
web browser (cookies) API client (Bearer hyp_…)
│ │
▼ ▼
┌───────────────────────────────────────────────────┐
│ hyperion-web (master only) │
│ axum + askama + HTMX · /api/v1 (OpenAPI) │
│ └─ holds master-rpc.key (Ed25519 signer) │
└────┬───────────────────────┬──────────────────────┘
│ local Unix socket │ signed RPC over HTTPS
│ /run/hyperion.sock │ (port 9443, IP-based)
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ hyperion-agent │ │ hyperion-agent │
│ (master) │ │ (each worker) │
│ HostingService │ │ HostingService │
│ State DB (SQLite)│ │ State DB (SQLite)│
│ Adapters: │ │ Adapters: │
│ fs/users nginx │ │ fs/users nginx │
│ php mysql/pg │ │ php mysql/pg │
│ acme wp ftp │ │ acme wp ftp │
└──────────────────┘ └──────────────────┘
also: web UI + API loops: 60s heartbeat,
audit chain, registry scheduler/5min, cert
master signer renewal, retention, reaper
Every adapter takes pre-validated typed arguments and shells out only via
Command::new(..).arg(..) — never format!() into a shell, the one rule that
turns "oops, an unescaped quote in a domain name" from an RCE into a boring type
error. Wire protocol is u32be length || JSON; the RPC envelope is
Ed25519-signed Canonical-JSON over self-signed HTTPS (integrity by signature,
not TLS).
hyperion/
├── crates/
│ ├── hyperion-types/ # newtype IDs + DTOs (no I/O)
│ ├── hyperion-validate/ # Domain + SystemUserName parsers
│ ├── hyperion-rpc/ # trait + wire types + codec
│ ├── hyperion-rpc-server/ # Unix-socket + mTLS server
│ ├── hyperion-rpc-client/ # RPC client
│ ├── hyperion-state/ # SQLite + 54 migrations + audit chain + api_keys
│ ├── hyperion-adapters/ # system tool wrappers (nginx/php/db/acme/ftp/…)
│ ├── hyperion-core/ # orchestration + secrets + RealAdapter
│ ├── hyperion-import/ # HestiaCP / CloudPanel importers
│ └── hyperion-auth/ # argon2id + Ed25519 sessions + CSRF
├── bin/
│ ├── hyperion-agent/ # privileged daemon (+ background scheduler)
│ ├── hyperion-web/ # axum admin UI + /api/v1 (single binary)
│ ├── hyperion-export/ # static-musl exporter served by the import wizard
│ └── hctl/ # CLI (local socket + `remote` HTTP)
├── packaging/install/ # install-master.sh · install-node.sh · update.sh
└── docs/
├── RUNBOOK.md
└── superpowers/specs/ # design specs (incl. remote-API + HA)
Beta, now with production mileage. Everything below is unit-tested, and since v0.10 the panel has been run against live customer sites — most of the fixes in recent releases came from that rather than from the test suite. Read the lists as "shipped and exercised on a real box", not "proven at scale": it has not been run across a large fleet, and the multi-node path in particular has had far less real traffic than the single-node one.
| Capability | Surface |
|---|---|
| Hosting CRUD (PHP / static / reverse-proxy + DB + TLS) | UI · CLI · API · RPC |
| Multi-version PHP (8.1–8.4), MariaDB / PostgreSQL | UI · CLI · API · RPC |
| Suspend / resume, limits, kernel disk quotas | UI · CLI · API · RPC |
| Hosting profiles, clone, expiration + scheduler | UI · API · RPC |
| Local + off-site backups (S3 + age, legacy FTP/SFTP) | UI · CLI · API · RPC |
| Granular restore, chunked download, restore-as-new | UI · CLI · API · RPC |
| Let's Encrypt HTTP-01 + manual DNS-01 wildcard | UI · CLI · API · RPC |
| WordPress install + plugin/theme manage + admin reset | UI · API · RPC |
| Keyless WP defender (outdated scan + auto-update) | UI · CLI · RPC |
WP staging → push-to-prod, Redis cache, .user.ini |
UI · API · RPC |
| Per-hosting cron, log tail + search, monitor probes | UI · RPC |
| FTP (vsftpd chroot) + key-only chrooted SFTP | UI · CLI · RPC |
| WAF-lite + wp-admin allowlist, fail2ban (nftables) | UI · CLI · RPC |
| Audit log (BLAKE3 chain + verify), 2FA, session revoke | UI · CLI · RPC |
| Panel import — HestiaCP + CloudPanel (in-place + SSH) | UI · CLI · RPC |
| Remote API — keys + OpenAPI 3 + IP allowlist + rate limit | UI · API · CLI |
| Background jobs page with live progress bars | UI · CLI · API · RPC |
| Site health — fatal detection + park the culprit plugin | UI · RPC |
| File-permission self-check + repair (real write probe) | UI · CLI · RPC |
| FTP self-check + repair, node-wide FTPS (enforced TLS) | UI · CLI · RPC |
| WP core repair + full reinstall behind a required backup | UI · RPC |
| Bot + country blocking, GeoIP, per-hosting traffic map | UI · RPC |
| Per-hosting DKIM signing, per-site SPF, mail path checks | UI · CLI · RPC |
| Care packages (paid entitlements) + customer reports | UI · RPC |
| Monitoring — live gauges, PSI, OOM, per-hosting RSS | UI · RPC |
| Custom roles from a granular capability set | UI · RPC |
| Read-only-rootfs watchdog with automatic remount | agent |
| Capability | Surface |
|---|---|
| Node enrollment (mint / list / revoke invite tokens) | UI · CLI · RPC |
| Master → worker signed RPC channel (Ed25519 envelope) | transport |
| Per-page node switcher, cluster stats aggregation | UI |
| Hosting auto-placement, migration + clone across nodes | UI · API · RPC |
| Remote node update (apt + hyperion) with live log tail | UI · RPC |
| Test-node mode + auto-subdomain, control-plane-only toggle | UI · agent.toml |
| Installer port pre-flight + component/port configurator | installer |
- HA control plane — warm standby + continuous state replication (design; an S3-free variant is possible — see the doc).
- Per-tenant API keys — tenant read-scoping so non-admin accounts can mint scoped keys.
- Restic / borg backup targets, SSO / OIDC login, ModSecurity / OWASP CRS over the built-in WAF-lite.
cargo test --workspace # 800+ tests, green, runs in seconds
cargo fmt --all
cargo clippy --workspace --all-targets # clean under -D warningsIntegration tests that want a real Debian (useradd, mariadb-dump,
systemctl reload nginx) are gated #[ignore]; run them on a node with
cargo test --workspace -- --ignored. bin/hyperion-web/tests/web_e2e.rs
drives the whole stack — login, CSRF, hosting create, the /api/v1 surface —
against a real socket-backed agent with mocked adapters.
docs/RUNBOOK.md— manual production deploy (apt deps, configs, systemd, hardening, troubleshooting, backup, updates, removal).docs/superpowers/specs/— design specs, including the remote API and HA control plane.
Trying it out is the most valuable contribution right now. Honest reports on functionality, security, or design / UX are worth as much as code. Open an issue, even a rough one.
When adding a new system effect: adapter (typed args, no shell interpolation) →
AdapterPort trait method (mockable) → orchestration in HostingService with a
LIFO rollback step → RPC variant + handler → CLI / UI / API surface → tests
at every layer. For multi-node features, never hit the local socket from a
per-hosting handler — always dispatcher::dispatch_to_node.
AGPL-3.0-only. Built as the open-source alternative to CloudPanel / HestiaCP / Plesk — Rust instead of templated PHP, multi-node and API-driven from day one, and a security model that doesn't rely on trusting shell-script templating. If you use it commercially or want to fund a feature, get in touch.
Built with ☕ in Czechia by @nechodom, one cargo build at a time — contributions (and bug reports) welcome.