install.sh manager
The self-contained bash lifecycle manager — install, upgrade, status, service, config, env, and uninstall for both binary+systemd and Docker Compose deployments.
scripts/install.sh is a single self-contained bash 4+ script that
covers the whole lifecycle of a Portunus node:
install · uninstall · upgrade · status · service · config · envIt manages both deploy forms — a release binary + systemd unit and a
Docker Compose stack — across the server, client, and
standalone roles, and it is bilingual (English / 中文). For a server it
can also provision a Caddy reverse proxy with automatic Let's
Encrypt HTTPS for the Web UI. The non-interactive flag interface that
CI, automation, and the documented curl … | bash one-liner depend on
is unchanged; the interactive menu is purely additive.
The same script is reachable two ways:
# Always-current copy, piped (interactive menu or non-interactive verbs):
curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/Portunus/main/scripts/install.sh | bash
# A checked-out / downloaded copy, run directly:
bash scripts/install.sh <verb> [flags]Requires bash 4.0+. The script hard-errors below that. macOS ships
bash 3.2 — brew install bash and run it with that interpreter (the
piped curl … | bash form uses whichever bash is first on PATH).
Two modes
Interactive — no actionable verb/role and a terminal is reachable
(stdin is a TTY, or /dev/tty is readable when piped). You get a menu:
Portunus Manager
[1] Install
[2] Uninstall
[3] Upgrade
[4] Status
[5] Service (start/stop/restart)
[6] Config
[7] Env
[0] Exit
Select [0-7]:Because it reads from /dev/tty when piped, curl … | bash reaches the
menu. Pick 1 for a guided install wizard: role (server / client /
standalone) → deploy form → (server only) HTTPS domain → advertised
endpoint → plan preview → confirm.
Non-interactive — any verb/role/recognized flag, or --yes, or no
usable terminal. This is the contract for CI and automation; behaviour
matches the historical downloader plus the new verbs.
Synopsis
install.sh <client|server|standalone|install|uninstall|upgrade|status|service|config|env|domain>
[start|stop|restart] [get|set <key> [value]] [<fqdn>]
[--version V] [--deploy binary|docker]
[--bin-dir DIR] [--compose-dir DIR]
[--advertised-endpoint HOST:PORT] [--data-dir DIR]
[--operator-http-listen ADDR]
[--domain FQDN] [--acme-email ADDR] [--skip-dns-check]
[--systemd] [--lang en|zh] [--reset-lang]
[--yes] [--purge] [--dry-run]A bare role is shorthand for install: client ≡ install client,
server ≡ install server, standalone ≡ install standalone.
Verbs
| Verb | What it does |
|---|---|
install <client|server|standalone> | Guided or flag-driven install. Default deploy form is binary. |
uninstall [client|server|standalone] | Remove the binary + unit + drop-in, or docker compose down. Confirms first. |
upgrade [client|server|standalone] | Resolve latest, compare to recorded version, reuse recorded config. No-op when current. |
status | Recorded metadata + a live probe (systemctl is-active / docker compose ps). Read-only. |
service <start|stop|restart> | Dispatch to systemctl or docker compose by the recorded deploy form. |
config <get|set> <key> [value] | Read/write one scoped key (see below). |
env | Print all scoped keys and their persisted values. |
domain <fqdn> | Server-only. Provision/refresh the Caddy HTTPS front for an existing install (see Domain & HTTPS). |
Flags
| Flag | Meaning |
|---|---|
--version <X.Y.Z|vX.Y.Z> | Pin a release. Omitted ⇒ latest, resolved at run time. |
--deploy <binary|docker> | Deploy form. Default binary. |
--bin-dir <DIR> | Binary install dir (default /usr/local/bin). |
--compose-dir <DIR> | Docker compose directory (default: current directory). |
--advertised-endpoint <HOST:PORT> | Server reach address (see below). Blank ⇒ runtime auto. |
--data-dir <DIR> | Server data dir (default /var/lib/portunus). |
--operator-http-listen <ADDR> | Operator HTTP listen address override. |
--domain <FQDN> | Server-only. Provision a Caddy HTTPS front for the Web UI; also derives the advertised endpoint as <FQDN>:7443 when --advertised-endpoint is unset. |
--acme-email <ADDR> | ACME / Let's Encrypt contact email for the Caddy domain. |
--skip-dns-check | Skip the pre-flight check that the domain's DNS resolves to this host. |
--systemd | Also install the hardened systemd unit (Linux only). |
--lang <en|zh> | Force UI language (also via PORTUNUS_LANG). |
--reset-lang | Clear the cached language preference and exit; the next interactive run re-prompts. |
--yes | Assume yes for ordinary confirmations. Does not bypass the --purge typed token. |
--purge | With uninstall, also delete the data dir / compose volume. Requires a typed confirmation. |
--dry-run | Print the plan; perform zero network and zero filesystem changes. |
--version, --bin-dir, --systemd, --yes, --dry-run and the bare
client / server form are the original downloader interface and behave
exactly as before.
Deploy forms
Binary + systemd
# binary only
curl -fsSL .../scripts/install.sh | bash -s -- server --version 1.4.2
# binary + hardened unit + service user + drop-in
curl -fsSL .../scripts/install.sh | sudo bash -s -- server --systemd \
--advertised-endpoint host.example:7443Downloads the release archive, verifies its SHA-256, and installs
portunus-<role> to --bin-dir. With --systemd it fetches the
hardened unit, creates the portunus-server / portunus-client service
user and directories, and (for the server) writes a systemd drop-in
at /etc/systemd/system/portunus-server.service.d/10-portunus.conf with
the advertised endpoint. The shipped unit is never edited — all
server config goes through the drop-in.
Docker Compose
mkdir -p ~/portunus && cd ~/portunus
curl -fsSL .../scripts/install.sh | bash -s -- server --deploy docker \
--compose-dir ~/portunus --advertised-endpoint host.example:7443Writes compose.yml (kept if it already exists) and a sibling .env
holding the scoped variables, then docker compose pull && up -d.
Docker Compose v2 is required (legacy docker-compose is detected
but v2 is preferred). Server config goes through the .env sidecar; an
existing compose file is never rewritten.
Install metadata
Each install records an .install-meta file (shell key=value) so later
subcommands know how to act:
| Deploy form / role | Path |
|---|---|
| binary server | <data-dir>/.install-meta (default /var/lib/portunus/.install-meta) |
| binary client / standalone | /etc/portunus/.install-meta |
| docker | <compose-dir>/.install-meta |
Fields include role, deploy, version, lang,
advertised_endpoint_set, domain, installed_at, installer_version.
Lifecycle verbs resolve the metadata in this order: an explicit
--compose-dir, then the current directory, then
/var/lib/portunus, then /etc/portunus. For a Docker deployment, run
lifecycle commands from the compose directory (or pass
--compose-dir) so a stale binary install does not shadow it.
Scoped config & env
config get/set does not apply to the standalone role — edit
/etc/portunus/standalone.toml directly. For server and client,
config and env accept only these four keys (anything else is a hard
error):
| Key | Maps to env var |
|---|---|
advertised-endpoint | PORTUNUS_ADVERTISED_ENDPOINT |
data-dir | PORTUNUS_DATA_DIR |
operator-http-listen | PORTUNUS_OPERATOR_HTTP_LISTEN |
version-pin | PORTUNUS_VERSION_PIN |
set writes the value into the systemd drop-in (binary) or the compose
.env (docker), merge-preserving the other keys, then offers a
service restart. get prints the persisted value (<unset> if none).
env dumps all four.
# binary server (run anywhere — meta is at /var/lib/portunus)
sudo bash install.sh config set advertised-endpoint host.example:7443
bash install.sh config get advertised-endpoint
bash install.sh env
# docker (run from the compose dir, or pass --compose-dir)
cd ~/portunus
bash install.sh config set operator-http-listen 0.0.0.0:7080
bash install.sh envServer advertised endpoint
When installing a server, the wizard prompts for the advertised
endpoint — the public host:port that Clients dial. The server
certificate SAN must cover that host. Leave it blank to let the server
auto-resolve it at run time. The installer only does a light host:port
sanity check and persists the value (systemd drop-in or compose .env
as PORTUNUS_ADVERTISED_ENDPOINT); authoritative SAN/grammar validation
is performed by the server itself at startup.
Domain & HTTPS
Server-only. When you supply a --domain (or answer the wizard's HTTPS
prompt), the installer fronts the Web UI with Caddy and automatic
Let's Encrypt HTTPS:
# At install time
curl -fsSL .../scripts/install.sh | sudo bash -s -- server --systemd \
--domain portunus.example.com --acme-email admin@example.com
# Or attach / refresh HTTPS on an existing server install
sudo bash install.sh domain portunus.example.comWhat it does:
- Pre-flight checks that the domain's DNS A record resolves to this
host (skip with
--skip-dns-check). - Installs Caddy if absent (apt / dnf / yum), then writes a managed
block — delimited by
# >>> portunus >>>/# <<< portunus <<<— into/etc/caddy/Caddyfilereverse-proxying the domain to the operator HTTP port. An existing Caddyfile is backed up and only the managed block is rewritten. --acme-emailseeds the ACME contact for certificate issuance.- Reloads Caddy and polls
https://<domain>/until issuance completes (~30 s typical).
Supplying --domain also derives the advertised endpoint as
<domain>:7443 when --advertised-endpoint is not set. After domain
re-aligns the advertised endpoint, the server refreshes its gRPC
certificate SAN on restart and existing client bundles must be
re-issued (portunus-server enroll-client <name>).
The Web UI becomes publicly reachable over HTTPS but stays protected by operator login / token.
Lifecycle examples
# Status (read-only): recorded meta + live probe
bash install.sh status
# Service control (dispatches systemctl or docker compose)
sudo bash install.sh service restart
bash install.sh service stop
# Upgrade in place, reusing recorded config (no-op when already current)
sudo bash install.sh upgrade
# Uninstall (keeps data) — confirms first
sudo bash install.sh uninstall
# Uninstall and delete data — requires typing the token 'purge'
sudo bash install.sh uninstall --purgeLanguage
Resolution order: --lang / PORTUNUS_LANG → LC_ALL / LANG
autodetect (zh* ⇒ 中文, else English) → first-run prompt when
interactive → otherwise English. The chosen language is stored in
.install-meta and reused by later subcommands.
PORTUNUS_LANG=zh curl -fsSL .../scripts/install.sh | bash
bash install.sh --lang en statusSafety
--dry-runprints the plan and performs zero network and zero filesystem changes — for every verb.--purgeadditionally deletes the data dir / compose volume and requires you to type the literal tokenpurge.--yesnever bypasses that typed token.- Destructive actions always confirm;
uninstalldefaults to[y/N]. - The shipped systemd unit and an existing base compose file are never
edited — server config is applied through the drop-in /
.envsidecar. - Re-running
installover an existing node is detected via.install-meta; useupgradeto move versions while reusing the recorded configuration.
See also
- Installation — quick start and the other install paths.
- Docker deployment · systemd deployment.