feat(deploy): unattended install from a single answers file
--env-file makes the whole run non-interactive, which is what a bare server needs: install git, clone, run, done. The file also carries the operational secrets (Hetzner DNS, Stripe, SMTP, the Proxmox key path); those are optional, so a first install can happen before the Stripe account exists and the features stay dark until the values are filled in. The installer warns when the file is readable by more than its owner, and clupilot.env is gitignored — it holds every secret an installation will need. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>feat/portal-design
parent
062d337dd0
commit
ac86efd9c3
|
|
@ -28,3 +28,4 @@ Thumbs.db
|
||||||
|
|
||||||
# Gitea push token (parent home dir; never track)
|
# Gitea push token (parent home dir; never track)
|
||||||
.env.gitea
|
.env.gitea
|
||||||
|
clupilot.env
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
# CluPilot — answers for an unattended install.
|
||||||
|
#
|
||||||
|
# scp this to the new server as /root/clupilot.env
|
||||||
|
# chmod 600 /root/clupilot.env
|
||||||
|
# bash install.sh --env-file /root/clupilot.env
|
||||||
|
#
|
||||||
|
# Keep the master copy in your password manager, NOT in the repository.
|
||||||
|
# After a successful install the working values live in /opt/clupilot/.env,
|
||||||
|
# so this file can be shredded: shred -u /root/clupilot.env
|
||||||
|
|
||||||
|
# ── Required ────────────────────────────────────────────────────────────────
|
||||||
|
GIT_TOKEN= # Gitea token with read access to the repo
|
||||||
|
APP_DOMAIN=app.clupilot.com # customer portal
|
||||||
|
WWW_DOMAIN=www.clupilot.com # marketing site
|
||||||
|
WS_DOMAIN=ws.clupilot.com # websockets (Reverb)
|
||||||
|
ADMIN_DOMAIN=admin.clupilot.com # operator console — must NOT resolve publicly
|
||||||
|
ADMIN_EMAIL=
|
||||||
|
ADMIN_NAME=Administrator
|
||||||
|
ADMIN_PASSWORD= # at least 12 characters
|
||||||
|
|
||||||
|
# ── Needed before the platform can actually do business ─────────────────────
|
||||||
|
# Without these the install still succeeds; the features stay dark.
|
||||||
|
HETZNER_DNS_TOKEN= # no token → no customer or host DNS names
|
||||||
|
CLUPILOT_DNS_ZONE=clupilot.com
|
||||||
|
|
||||||
|
STRIPE_KEY= # pk_… publishable
|
||||||
|
STRIPE_SECRET= # sk_… secret
|
||||||
|
STRIPE_WEBHOOK_SECRET= # whsec_… per endpoint, differs test/live
|
||||||
|
|
||||||
|
MAIL_MAILER=smtp # until this is set, mail only goes to the log
|
||||||
|
MAIL_HOST=
|
||||||
|
MAIL_PORT=587
|
||||||
|
MAIL_USERNAME=
|
||||||
|
MAIL_PASSWORD=
|
||||||
|
MAIL_FROM_ADDRESS=noreply@clupilot.com
|
||||||
|
|
||||||
|
# Private key used to onboard Proxmox hosts. Put the key file on the server
|
||||||
|
# first (chmod 600) and point at it — a PEM does not fit in a .env line.
|
||||||
|
CLUPILOT_SSH_KEY_PATH=/opt/clupilot/secrets/proxmox_ed25519
|
||||||
|
|
@ -24,6 +24,20 @@ BRANCH="${BRANCH:-main}"
|
||||||
# one in the docker group; the installer itself needs root only for apt.
|
# one in the docker group; the installer itself needs root only for apt.
|
||||||
APP_USER="${APP_USER:-clupilot}"
|
APP_USER="${APP_USER:-clupilot}"
|
||||||
|
|
||||||
|
# --env-file PATH — answers every prompt, so the whole run is unattended.
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--env-file)
|
||||||
|
ENV_FILE="${2:-}"; shift 2 ;;
|
||||||
|
--env-file=*)
|
||||||
|
ENV_FILE="${1#*=}"; shift ;;
|
||||||
|
-h|--help)
|
||||||
|
sed -n '2,20p' "$0"; exit 0 ;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
log() { printf '\n\033[1;34m==>\033[0m %s\n' "$*"; }
|
log() { printf '\n\033[1;34m==>\033[0m %s\n' "$*"; }
|
||||||
warn() { printf '\033[1;33m !\033[0m %s\n' "$*"; }
|
warn() { printf '\033[1;33m !\033[0m %s\n' "$*"; }
|
||||||
die() { printf '\033[1;31m ✗\033[0m %s\n' "$*" >&2; exit 1; }
|
die() { printf '\033[1;31m ✗\033[0m %s\n' "$*" >&2; exit 1; }
|
||||||
|
|
@ -47,6 +61,19 @@ ask_secret() {
|
||||||
rand_b64() { head -c "${1:-32}" /dev/urandom | base64 -w0; }
|
rand_b64() { head -c "${1:-32}" /dev/urandom | base64 -w0; }
|
||||||
rand_hex() { head -c "${1:-24}" /dev/urandom | od -An -tx1 | tr -d ' \n'; }
|
rand_hex() { head -c "${1:-24}" /dev/urandom | od -An -tx1 | tr -d ' \n'; }
|
||||||
|
|
||||||
|
if [[ -n "${ENV_FILE:-}" ]]; then
|
||||||
|
[[ -f "$ENV_FILE" ]] || { echo "No such file: $ENV_FILE" >&2; exit 1; }
|
||||||
|
|
||||||
|
# It holds every secret this installation will ever need. A world-readable
|
||||||
|
# copy on a shared box is worth saying something about.
|
||||||
|
perms="$(stat -c '%a' "$ENV_FILE")"
|
||||||
|
[[ "$perms" == "600" || "$perms" == "400" ]] || \
|
||||||
|
printf '\033[1;33m !\033[0m %s\n' "$ENV_FILE is mode $perms — chmod 600 it."
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090 # the path is the whole point of the flag
|
||||||
|
set -a; . "$ENV_FILE"; set +a
|
||||||
|
fi
|
||||||
|
|
||||||
# ── 0. Preflight ─────────────────────────────────────────────────────────────
|
# ── 0. Preflight ─────────────────────────────────────────────────────────────
|
||||||
[[ $EUID -eq 0 ]] || die "Please run as root (sudo bash install.sh)."
|
[[ $EUID -eq 0 ]] || die "Please run as root (sudo bash install.sh)."
|
||||||
[[ -f /etc/os-release ]] || die "Unsupported system: no /etc/os-release."
|
[[ -f /etc/os-release ]] || die "Unsupported system: no /etc/os-release."
|
||||||
|
|
@ -196,6 +223,23 @@ else
|
||||||
|
|
||||||
# The container user is mapped to the service account, so files written
|
# The container user is mapped to the service account, so files written
|
||||||
# inside the container belong to it and not to root.
|
# inside the container belong to it and not to root.
|
||||||
|
# Supplied through --env-file; skipped silently when absent, so a first
|
||||||
|
# install can happen before the Stripe account exists.
|
||||||
|
optional_env() { [[ -n "${2:-}" ]] && set_env "$1" "$2"; }
|
||||||
|
|
||||||
|
optional_env HETZNER_DNS_TOKEN "${HETZNER_DNS_TOKEN:-}"
|
||||||
|
optional_env CLUPILOT_DNS_ZONE "${CLUPILOT_DNS_ZONE:-}"
|
||||||
|
optional_env STRIPE_KEY "${STRIPE_KEY:-}"
|
||||||
|
optional_env STRIPE_SECRET "${STRIPE_SECRET:-}"
|
||||||
|
optional_env STRIPE_WEBHOOK_SECRET "${STRIPE_WEBHOOK_SECRET:-}"
|
||||||
|
optional_env MAIL_MAILER "${MAIL_MAILER:-}"
|
||||||
|
optional_env MAIL_HOST "${MAIL_HOST:-}"
|
||||||
|
optional_env MAIL_PORT "${MAIL_PORT:-}"
|
||||||
|
optional_env MAIL_USERNAME "${MAIL_USERNAME:-}"
|
||||||
|
optional_env MAIL_PASSWORD "${MAIL_PASSWORD:-}"
|
||||||
|
optional_env MAIL_FROM_ADDRESS "${MAIL_FROM_ADDRESS:-}"
|
||||||
|
optional_env CLUPILOT_SSH_KEY_PATH "${CLUPILOT_SSH_KEY_PATH:-}"
|
||||||
|
|
||||||
set_env HOST_UID "$APP_UID"
|
set_env HOST_UID "$APP_UID"
|
||||||
set_env HOST_GID "$APP_GID"
|
set_env HOST_GID "$APP_GID"
|
||||||
set_env VITE_AUTOSTART false
|
set_env VITE_AUTOSTART false
|
||||||
|
|
|
||||||
|
|
@ -27,13 +27,28 @@ 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
|
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.
|
regenerated — doing so would silently disconnect every onboarded host.
|
||||||
|
|
||||||
Every prompt has an environment variable, so it can also run unattended:
|
### Unattended, from a bare server
|
||||||
|
|
||||||
|
The only thing to install by hand is git:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
GIT_TOKEN=… ADMIN_EMAIL=… ADMIN_PASSWORD=… APP_DOMAIN=app.clupilot.com \
|
apt-get update && apt-get install -y git
|
||||||
ADMIN_DOMAIN=admin.clupilot.com bash deploy/install.sh
|
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
|
## Order of the update
|
||||||
|
|
||||||
Migrations run **before** the new containers take traffic: code that expects a
|
Migrations run **before** the new containers take traffic: code that expects a
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue