Requirements: one machine running a supported distribution (table below), root, and ports 80 and 443 reachable. For a real certificate, a DNS name pointing at the machine. The installer brings PostgreSQL and Caddy itself.
Support matrix
| Distribution | Architectures | PostgreSQL | Notes |
|---|---|---|---|
| Ubuntu 26.04 | x86_64, aarch64 | 18 (distribution) | |
| Ubuntu 24.04 | x86_64, aarch64 | 16 (distribution) | |
| Ubuntu 22.04 | x86_64, aarch64 | 16 (PGDG apt repository) | nftables from the distribution |
| Debian 12 | x86_64, aarch64 | 16 (PGDG apt repository) | sudo installed if absent |
| Debian 13 | x86_64, aarch64 | 17 (distribution) |
Any other distribution is refused with unsupported distribution <id> <version>, unless --force-distro is given: the installer then uses the Debian 12 plan and prints that it does.
The script
The repository and its releases are private until the owner decides otherwise (spec §12); with access, as root:
Options:
| Flag | Meaning |
|---|---|
--panel-host <dns> | the panel's name; Caddy gets a certificate for it (ACME) |
--tls internal | Caddy issues from its own internal CA instead of ACME; alone, the panel host is the machine's primary IP |
--version <v> | release to install (default: the latest) |
--archive-url <url>, --sums-url <url> | install from these URLs instead of the GitHub release (tests; both or neither) |
One of --panel-host and --tls internal is required; both together give an internal-CA certificate for a name (what scripts/test-install.sh does with setu.test).
What it does, in order (spec §8):
- Checks root, Linux, the machine type (x86_64 or aarch64) and the distribution (the matrix above).
- Installs
sudo gnupg ca-certificates curl, adds the PGDG repository where the plan needs it (the key is checked against a pinned SHA-256 before apt sees it), thenapt-get install postgresql-<N> zstd nftables. - Downloads
setu-<ver>-<arch>-unknown-linux-musl.tar.gzandSHA256SUMSfrom the release, verifies the archive, installs/opt/setu/setu(root:root 0755) and links/usr/local/bin/setu. - Creates the system user
setu,/var/lib/setu(setu 0750) and/etc/setu(root 0755). - Creates the
seturole (a generated password, handed topsqlon stdin with\set, never on a command line) and database,REVOKE CONNECT ON DATABASE setu FROM PUBLICandGRANT CONNECT … TO setu. - Writes
/etc/setu/setu.toml(setu:setu 0600):listen = "unix:/run/setu/http.sock",database_url, a generatedsecret_key(64 hex),public_url = "https://<panel host>",trusted_proxies = ["127.0.0.1/32"],data_dir = "/var/lib/setu". setu agent install-system --caddy-only --panel-host <host> --upstream unix//run/setu/http.sock [--tls internal]: installs thecaddypackage when missing (Caddy's apt repository, else Caddy's checksum-pinned release.deb), writes/etc/caddy/setu.jsonwith the panel route, runscaddy-api.serviceon its admin socket.- Puts
setuin groupcaddy(the socket is handed to that group) and writessetu.service(User=setu,WorkingDirectory=/etc/setu,RuntimeDirectory=setu0755 holding the socket 0660setu:caddy,Restart=always). setu migrate,setu setting set tls_internal true|false, starts the service and waits forhttps://<host>/healthzthrough Caddy.- Prints the setup URL and the one-time setup token from
/var/lib/setu/setup-token(written bysetu serveon its first boot, 0600, removed once the owner exists).
Re-running the script is safe: an existing setu.toml is kept (its database password and secret key are reused; a file it cannot read back, or one whose public_url differs from the flags, stops the script with an error rather than being rewritten); the service restarts only when the binary or the unit changed.
The setup wizard
Open https://<host>/setup, paste the setup token, create the owner (works exactly once), then let the wizard register this machine: it shows a setu agent install --control-plane https://<host> --panel-host <host> [--tls internal --ca …] line and a separate join token. Run the line as root with the token in SETU_JOIN_TOKEN (or paste it on stdin when asked):
SETU_JOIN_TOKEN=<token>
The agent joins through Caddy (the panel has no TCP listener), and the server shows online within seconds. With --tls internal, browsers need Caddy's root certificate, /var/lib/caddy/.local/share/caddy/pki/authorities/local/root.crt.
Commands on the host
The control plane reads setu.toml from its working directory, so run its commands as setu from /etc/setu:
SETU_OPERATOR_PASSWORD=…
setu backup hands the database password to pg_dump in its environment (PGPASSWORD), never on argv; restoring it, and keeping setu.toml (the key to every sealed secret) with it, is in OPERATIONS.md. operator create takes the password from SETU_OPERATOR_PASSWORD or the first line of stdin.
Manual install
- Fetch the archive and
SHA256SUMSfrom the release,sha256sum -c --ignore-missing SHA256SUMS, unpacksetuto/opt/setu/setu. - Provide a Postgres 16 database and either a
setu.tomlin the service's working directory orSETU_*variables (prefixSETU_, nested keys__):SETU_DATABASE_URL,SETU_SECRET_KEY(64 hex),SETU_PUBLIC_URL; optionalSETU_LISTEN(<ip>:<port>, default127.0.0.1:7000, orunix:<path>: created 0660 and handed to groupcaddy),SETU_TRUSTED_PROXIES(comma-separated, default127.0.0.1/32),SETU_DATA_DIR(default/var/lib/setu). setu migrate, thensetu serveunder systemd, behind Caddy.
Upgrading
Run setu upgrade as root (0.1.5; OPERATIONS.md), or re-run install.sh --version <new> … with the same flags: it verifies the new archive, replaces the binary and restarts setu.service (migrations run on boot) and, when the local agent is installed, setu-agent.service (only when the binary changed; R139). Upgrade other servers' binaries by hand (OPERATIONS.md). Back up first (setu backup); migrations are forward-only. Site updates are separate: each site moves to a newer app version from the UI with automatic rollback.
Uninstalling
systemctl disable --now setu setu-agent; then remove /opt/setu, /usr/local/bin/setu, /etc/setu, /var/lib/setu, /srv/setu, the setu role and database, and the setu user as needed.