The installer
install.sh — its three modes, every variable it reads, and every file it touches.
install.sh install, or repair an existing install
install.sh --upgrade pull newer images and apply migrations
install.sh --uninstall stop everything and remove the platform
install.sh --help print thisRun as root. Idempotent and re-runnable: every step checks before it acts, so a second run repairs rather than duplicates.
Variables it reads
| Variable | Default | Used for |
|---|---|---|
STHORA_HOSTNAME | localhost | The portal's hostname; goes into PUBLIC_BASE_URL, AUTH_TRUSTED_ORIGINS, and the Traefik router rule |
STHORA_ACME_EMAIL | Empty | PROXY_ACME_EMAIL |
STHORA_BASE_DOMAIN | Empty | PROXY_BASE_DOMAIN |
STHORA_EMAIL_FROM | Empty | EMAIL_FROM_ADDRESS |
STHORA_EMAIL_FROM_NAME | Empty | EMAIL_FROM_NAME |
STHORA_SMTP_HOST | Empty | EMAIL_SMTP_HOST |
STHORA_SMTP_PORT | 587 | EMAIL_SMTP_PORT |
STHORA_SMTP_SECURE | false | EMAIL_SMTP_SECURE — implicit TLS. true for port 465; 587 upgrades with STARTTLS and wants false |
STHORA_SMTP_USER | Empty | EMAIL_SMTP_USER |
STHORA_SMTP_PASSWORD | Empty | EMAIL_SMTP_PASSWORD |
STHORA_VERSION | latest | The image tag |
STHORA_IMAGE_REGISTRY | ghcr.io/asynccoders | Where images are pulled from |
STHORA_REGISTRY_HOST | ghcr.io | The registry to sign in to, when a token is given |
STHORA_REGISTRY_USER | Empty | Registry username. Required when a token is set |
STHORA_REGISTRY_TOKEN | Empty | Registry token. Unset means the pull is anonymous, which is what a public registry wants |
STHORA_MODE | self-hosted | self-hosted or cloud — see below |
STHORA_DEFAULT_PLAN | Mode-dependent | BILLING_DEFAULT_PLAN |
Distribution mode
STHORA_MODE moves three settings together, because a deployment with two of
the three set is in a state neither mode intends.
self-hosted | cloud | |
|---|---|---|
SIGNUP_ENABLED | false | true |
BILLING_ENFORCE_LIMITS | false | true |
AGENT_CHANNEL_ENABLED | false | true |
BILLING_DEFAULT_PLAN | self-hosted | starter |
STHORA_DEFAULT_PLAN overrides the last row. In cloud mode the installer
refuses self-hosted for it: that plan's limits are null everywhere, null means
unlimited rather than zero, and open registration on top of it hands an
unlimited account to anyone who finds the URL.
Both modes also get TRUSTED_PROXY_HOPS=1, which is what makes req.ip — the
rate limiter's key, and every audit record's requester address — the client's
address rather than Traefik's. The schema defaults it to 0 because guessing a
hop count is worse than not throttling. The installer is not guessing; it wrote
the topology.
Preflight
It refuses, rather than warns, on any of:
| Floor | |
|---|---|
| Distribution | Ubuntu or Debian — it reads /etc/os-release |
| Architecture | amd64 or arm64 |
| RAM | 2048 MB |
Free disk on / | 20 GB |
It warns below 4096 MB of RAM. Builds are the memory spike, and the warning is about builds rather than about the platform sitting idle.
What it changes on the machine
Installed from get.docker.com and enabled, unless both the engine and the Compose v2 plugin
are already present.
A 2 GB swapfile at /swapfile, mode 0600, added to /etc/fstab — unless swap already
exists.
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" },
"live-restore": true,
"default-address-pools": [
{ "base": "10.100.0.0/16", "size": 24 }
]
}Then Docker is restarted. Skipped if the file already mentions live-restore.
ufw is installed if missing. Inbound 22, 80, and 443 are allowed before default-deny is
set and the firewall is enabled — doing it the other way round over SSH locks you out of the
box you are installing on, with no way back in.
It warns that Docker's published ports bypass ufw's chains.
The PostgreSQL password, the Redis password, and AUTH_SECRET are generated with
openssl rand -hex 32 and written to /etc/sthora/.env at 0600. The master encryption
key is generated into /etc/sthora/master.key at 0600.
An existing .env is kept. Delete it to regenerate every credential.
/etc/sthora/docker-compose.yml and /etc/sthora/compose.env are written, and the
external sthora-proxy bridge network is created if it does not exist.
Startup
Pulls the images (falling back to what is on the box if a pull fails), brings the stack up,
polls docker exec sthora-api curl -fsS localhost:4600/health for up to two minutes, then
runs prisma migrate deploy.
If the API never becomes healthy it stops and tells you to read
docker compose -p sthora logs api.
Upgrade mode
Dumps the database first, rewrites the compose file, pulls, brings up, waits for health, migrates, and prunes dangling images. See upgrading.
Uninstall mode
Asks you to type remove, then runs compose down. Data volumes and
/var/lib/sthora are kept, and it prints the commands to remove them.
Building from source
Multi-arch images for backend, dashboard, agent, and website are published to GHCR by
CI after the suite passes on main. Building the platform from source on the target box takes
five to eight minutes and a lot of the RAM budget, which is why the installer pulls.
Not yet verified end to end
The installer has been syntax-checked and everything it orchestrates is exercised by the test suite, but it has not been run end to end on a fresh machine.