CluPilotCloud/docs/deployment.md

3.6 KiB

Deploying CluPilot on a fresh server

Developed locally, deployed by pulling. Two scripts do the work:

# once, on a fresh Debian/Ubuntu server — as root, because of apt
bash deploy/install.sh

# afterwards, as the service account — never as root
sudo -u clupilot bash -c 'cd /opt/clupilot && bash deploy/update.sh'

The installer needs root only to install packages and open the firewall. It creates a clupilot account that owns the checkout and runs Docker; the application never runs as root, and update.sh refuses to start if you try. That account has no password login — membership in the docker group is root-equivalent on the host, which is inherent to Docker and the reason it is reachable only by sudo -u or an SSH key.

What the installer does

Docker, git and wireguard-tools; clone into /opt/clupilot; generate every secret (APP_KEY, VPN_CONFIG_KEY, database password, Reverb keys); start the stack; migrate; create the WireGuard hub and print its public key; create your Owner account; close the firewall down to 22, 80, 443 and 51820/udp.

It is safe to re-run: an existing .env is kept and the hub key is never regenerated — doing so would silently disconnect every onboarded host.

Unattended, from a bare server

The only thing to install by hand is git:

apt-get update && apt-get install -y git
git clone https://git.bave.dev/boban/CluPilotCloud.git /tmp/clupilot
bash /tmp/clupilot/deploy/install.sh --env-file /root/clupilot.env

deploy/clupilot.env.example is the template. Put the real file on the server as /root/clupilot.env with mode 600 before starting — the installer warns if it is readable by anyone else. Keep the master copy in a password manager, never in the repository (clupilot.env is gitignored for that reason).

After a successful run the working values live in /opt/clupilot/.env, so the bootstrap copy can go: shred -u /root/clupilot.env.

The file also carries the operational secrets — Hetzner DNS, Stripe, SMTP, the Proxmox SSH key path. Those are optional: the install succeeds without them and the corresponding features simply stay dark until they are filled in.

Order of the update

Migrations run before the new containers take traffic: code that expects a column the database does not have yet answers with 500s. The image is rebuilt only when docker/, composer or npm manifests actually changed. Queue workers are restarted at the end — they are long-running PHP processes and keep the old classes in memory otherwise. That one bit us during development: a fixed job kept failing until the worker was restarted.

What the installer deliberately leaves to you

  • DNS and TLS. Put a reverse proxy in front (Zoraxy, Caddy, nginx). The console hostname must not be publicly resolvable: /admin answers only on the hostnames in ADMIN_HOSTS and 404s everywhere else, but keeping it out of public DNS is the layer above that.
  • STRIPE_WEBHOOK_SECRET. Stripe dashboard → Developers → Webhooks → https://<app-domain>/webhooks/stripe, events checkout.session.completed and checkout.session.async_payment_succeeded. The secret starts with whsec_ and differs between test and live mode.
  • Backups. At minimum the MariaDB volume and /etc/wireguard in the wireguard volume — losing the hub key means re-peering every host.

Hiding the site while you build

Settings → Website visibility. Outsiders then get a placeholder (503, noindex), while anyone on the management VPN and any signed-in operator keeps seeing the real thing. The console stays reachable either way, so the switch can always be flipped back.