Skip to content

Repository files navigation

🦅 Hyperion

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.

Rust License Debian Tests API Status

Install · Features · Remote API · Migrate in · Multi-node · Architecture · Status


Warning

🧪 Young project — not yet battle-tested in production

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.


Contents


Screenshots

Dashboard — overview card with KPIs, sparklines, recent activity feed
Dashboard — KPI tiles, load + bandwidth sparklines, audit feed.
Stats — cluster-wide KPIs, per-node breakdown, load/memory/bandwidth/requests sparklines
Stats — cluster + per-node, sampled every 5 min, 200-min charts.

Why Hyperion?

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 bash improv, 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) ⚠️ partial ✅ LIFO rollback on every step
Scriptable HTTP API (OpenAPI) + CLI ⚠️ partial / unofficial /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 ⚠️ partial ✅ in core
WP install + plugin manage + Redis cache ⚠️ via plugins ✅ 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) ⚠️ FTP only ✅ S3 + age (multi-target)
One-click cross-node migration + clone
Import from HestiaCP / CloudPanel ✅ in-place or remote (SSH)

Install

One-liner — fresh Debian 12+ VPS, as root

curl -fsSL https://raw.githubusercontent.com/nechodom/hyperion/main/packaging/install/install-master.sh \
  | sudo bash

Yes, 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 URL

Configurator & port pre-flight

Hyperion 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 pickernginx + 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 bash
All 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.

Adding a worker node (cluster mode)

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.

In-place updates

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.

Local development (macOS / dev VPS)

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.

Versioning & releases

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.


Features

Hosting

  • 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, PHP memory_limit per 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.

Per-hosting controls

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 to wp-config.php), per-hosting php.ini override 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 -t never 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.

WordPress

  • 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 deactivate needs a WordPress that loads.
  • Object-cache drop-in controlwp-content/object-cache.php is 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.php and a world-readable PHP session directory. It performs a real mkdir as 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.

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.php and 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.

File manager

  • 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.

Backups

  • 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; tar exit 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.

Security

  • #![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.php floods and auto-bans via an nftables set (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 chain button), 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_basedir and 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 --off is 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.

Multi-node cluster

  • 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.

Operator UI

  • axum + askama + HTMX, no JS build step, single binary; themed confirm modals (type-the-domain for destructive actions); live service-install progress; a /jobs page 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-data form whose CSRF token is in a hidden field (never read — the upload 403s every time), a data-confirm-* on a <button> instead of the <form> (the dialog never opens), an hx-get with no matching route (the panel spins forever), and a page with no way to reach it from the navigation.

RBAC + multi-tenancy

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.


Remote API

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_all accounts). 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).
  • ContractGET /api/v1/openapi.json is a generated OpenAPI 3 document (kept in lockstep with the routes); GET /api/v1/docs renders 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 return 202 {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.

hctl remote — the CLI over the API

# 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 contract

Every 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.


CLI

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 off

hctl 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 -f

Migrate in (panel import)

Move 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 with rsync, DBs dumped over ssh.
  • Dry-run first — a plan shows created / skipped / conflict before anything is touched; an existing domain is skipped, never overwritten.
  • Sites + databases, WordPress includedwp-config.php auto-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".

Multi-node cookbook

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 nodeTransfer → 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 panelImport (sidebar) → source + mode → PreviewApply.

Architecture

Two layers per box:

  • hyperion-agent runs 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, group hyperion-admin). On worker nodes also on 0.0.0.0:9443 for signed RPC from the master.
  • hyperion-web (master only) — axum + askama + HTMX, runs unprivileged in the hyperion-admin group. Owns the audit log, web users, sessions ledger, node registry, Ed25519 master signing key, and the /api/v1 Bearer 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).


Project layout

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)

Project status

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.

Shipped — single node

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

Shipped — multi-node cluster

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

Roadmap

  • 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.

Testing

cargo test --workspace                    # 800+ tests, green, runs in seconds
cargo fmt --all
cargo clippy --workspace --all-targets    # clean under -D warnings

Integration 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.


Documentation


Contributing

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.


License

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.


⬆ back to top

Built with ☕ in Czechia by @nechodom, one cargo build at a time — contributions (and bug reports) welcome.

About

Rust hosting control panel for Debian. Multi-node clustering, kernel-enforced quotas, tamper-evident audit log, and live progress bars on every operation. The Cloudpanel / HestiaCP alternative that doesn't shell-template PHP at root.

Topics

Resources

Contributing

Security policy

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages