#!/usr/bin/env bash # # paas-init — first-boot wizard for a server spawned from the golden template. # # sudo paas-init # interactive: asks everything, then runs bootstrap.sh # sudo paas-init --panel-domain deploy2.example.com --flavour laravel \ # --password-file /root/pw.txt --yes # non-interactive # # Asks for: panel domain (management UI), admin password, flavour (Node.js as # in production, or Laravel = + PHP-FPM/composer/MariaDB), system-domain base, # ACME e-mail — then pulls the repo (optional) and calls bootstrap.sh with the # matching environment. Safe to re-run (bootstrap is idempotent; re-running # with the Laravel flavour upgrades a Node server in place). set -euo pipefail REPO_DIR="${PAAS_REPO_DIR:-}" PANEL_DOMAIN=""; FLAVOUR=""; PASSWORD=""; PASSWORD_FILE="" SYSTEM_DOMAIN_BASE_IN=""; ACME_EMAIL_IN=""; PHP_VERSIONS_IN=""; DO_PULL=""; YES=0 a=""; f=""; go="" # answers filled by ask() usage() { cat <<'EOF' paas-init — set up this server (run as root) --panel-domain management UI domain (DNS must point at this server) --flavour node|laravel node = Caddy+Varnish+PM2 (production); laravel = + PHP-FPM, composer, MariaDB --password-file admin password for the panel (first line); asked interactively otherwise --system-domain-base base for auto-generated app domains (default: younex.de) --acme-email Let's Encrypt contact (default: info@logyou.de) --php-versions "8.3 8.4" laravel only (default: 8.3 8.4) --pull | --no-pull git pull the repo checkout before bootstrapping (default: ask / pull) --repo repo checkout to use (default: auto-detect) --yes no confirmation prompt EOF exit "${1:-0}" } while [[ $# -gt 0 ]]; do case "$1" in --panel-domain) PANEL_DOMAIN="$2"; shift 2 ;; --flavour|--flavor) FLAVOUR="$2"; shift 2 ;; --password-file) PASSWORD_FILE="$2"; shift 2 ;; --system-domain-base) SYSTEM_DOMAIN_BASE_IN="$2"; shift 2 ;; --acme-email) ACME_EMAIL_IN="$2"; shift 2 ;; --php-versions) PHP_VERSIONS_IN="$2"; shift 2 ;; --pull) DO_PULL=1; shift ;; --no-pull) DO_PULL=0; shift ;; --repo) REPO_DIR="$2"; shift 2 ;; --yes|-y) YES=1; shift ;; -h|--help) usage 0 ;; *) echo "unknown option: $1" >&2; usage 1 ;; esac done [[ "$(id -u)" -eq 0 ]] || { echo "run as root: sudo paas-init" >&2; exit 1; } # ---- locate the repo checkout ---- if [[ -z "$REPO_DIR" ]]; then for d in "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." 2>/dev/null && pwd)" /home/younes/dotnews-paas /home/*/dotnews-paas /root/dotnews-paas /opt/dotnews-paas; do [[ -f "$d/bootstrap.sh" && -f "$d/bin/paas" ]] && { REPO_DIR="$d"; break; } done fi [[ -n "$REPO_DIR" && -f "$REPO_DIR/bootstrap.sh" ]] || { echo "cannot find the dotnews-paas checkout (use --repo )" >&2; exit 1; } REPO_OWNER="$(stat -c %U "$REPO_DIR")" bold() { printf '\033[1m%s\033[0m' "$*"; } hr() { printf '%s\n' "──────────────────────────────────────────────────────────────"; } ask() { # $1 var, $2 prompt, $3 default local v if [[ -n "$3" ]]; then read -rp "$2 [$3]: " v; else read -rp "$2: " v; fi printf -v "$1" '%s' "${v:-$3}" } valid_domain() { [[ "$1" =~ ^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$ ]]; } # current state (re-runs) CUR_FLAVOUR=node grep -qs '^RUNTIMES=.*laravel' /srv/paas/paas.conf 2>/dev/null && CUR_FLAVOUR=laravel CUR_PANEL="" [[ -f /srv/paas/panel/.env ]] && CUR_PANEL="$(sed -n 's#^PANEL_BASE_URL=https\?://##p' /srv/paas/panel/.env | head -1)" CUR_BASE="$(sed -n 's/^SYSTEM_DOMAIN_BASE=//p' /srv/paas/paas.conf 2>/dev/null | head -1)" echo hr; echo " $(bold 'dotnews-paas — server setup wizard') host: $(hostname) ip: $(hostname -I 2>/dev/null | awk '{print $1}')"; hr if [[ -n "$CUR_PANEL" ]]; then echo " This server is already set up (panel: $CUR_PANEL, flavour: $CUR_FLAVOUR)." echo " Re-running is safe: bootstrap is idempotent and can add the Laravel flavour." echo fi echo " Repo checkout: $REPO_DIR ($(git -C "$REPO_DIR" log -1 --format='%h %s' 2>/dev/null || echo 'not a git checkout'))" echo # ---- 1. update code ---- if [[ -z "$DO_PULL" ]]; then if [[ "$YES" -eq 1 ]]; then DO_PULL=1; else ask a "Pull the latest platform code first? (y/n)" y [[ "$a" =~ ^[Yy] ]] && DO_PULL=1 || DO_PULL=0 fi fi if [[ "$DO_PULL" -eq 1 && -d "$REPO_DIR/.git" ]]; then echo " pulling…" if sudo -u "$REPO_OWNER" -H git -C "$REPO_DIR" pull --ff-only -q 2>&1 | sed 's/^/ /'; then echo " code now at $(git -C "$REPO_DIR" log -1 --format='%h %s')" else echo " WARNING: git pull failed (no network / no access?) — continuing with the checked-out code" >&2 fi # the wizard itself may have changed — re-exec once from the fresh checkout if [[ -z "${PAAS_INIT_REEXEC:-}" && -f "$REPO_DIR/bin/paas-init" ]] && ! cmp -s "$REPO_DIR/bin/paas-init" "${BASH_SOURCE[0]}"; then install -m 755 "$REPO_DIR/bin/paas-init" /usr/local/bin/paas-init export PAAS_INIT_REEXEC=1 args=(--no-pull --repo "$REPO_DIR") [[ -n "$PANEL_DOMAIN" ]] && args+=(--panel-domain "$PANEL_DOMAIN") [[ -n "$FLAVOUR" ]] && args+=(--flavour "$FLAVOUR") [[ -n "$PASSWORD_FILE" ]] && args+=(--password-file "$PASSWORD_FILE") [[ -n "$SYSTEM_DOMAIN_BASE_IN" ]] && args+=(--system-domain-base "$SYSTEM_DOMAIN_BASE_IN") [[ -n "$ACME_EMAIL_IN" ]] && args+=(--acme-email "$ACME_EMAIL_IN") [[ -n "$PHP_VERSIONS_IN" ]] && args+=(--php-versions "$PHP_VERSIONS_IN") [[ "$YES" -eq 1 ]] && args+=(--yes) exec "$REPO_DIR/bin/paas-init" "${args[@]}" fi fi echo # ---- 2. questions ---- while [[ -z "$PANEL_DOMAIN" ]] || ! valid_domain "$PANEL_DOMAIN"; do [[ -n "$PANEL_DOMAIN" ]] && echo " invalid domain: $PANEL_DOMAIN" [[ "$YES" -eq 1 && -z "$PANEL_DOMAIN" ]] && { echo "--panel-domain is required" >&2; exit 1; } echo " 1) $(bold 'Panel domain') — the management UI (login) for THIS server." echo " Each server needs its own (production uses deploy.younex.de). Point its DNS" echo " A-record at this server's WAN alias; Caddy fetches the certificate itself." ask PANEL_DOMAIN " panel domain" "$CUR_PANEL" PANEL_DOMAIN="${PANEL_DOMAIN,,}" done if [[ -z "$FLAVOUR" ]]; then echo echo " 2) $(bold 'Flavour') — what this server can run:" echo " [1] Node.js — Caddy + Varnish + PM2, exactly like production today" echo " [2] Laravel — the above + PHP-FPM 8.3/8.4, composer, MariaDB ('paas db');" echo " Node apps still work; Laravel/Database options appear in the panel" def=1; [[ "$CUR_FLAVOUR" == "laravel" ]] && def=2 ask f " choose" "$def" case "$f" in 2|laravel|l) FLAVOUR=laravel ;; *) FLAVOUR=node ;; esac fi [[ "$FLAVOUR" =~ ^(node|laravel)$ ]] || { echo "--flavour must be node or laravel" >&2; exit 1; } if [[ -z "$PASSWORD" && -n "$PASSWORD_FILE" ]]; then PASSWORD="$(head -n1 "$PASSWORD_FILE")" fi if [[ -z "$PASSWORD" && ! -f /srv/paas/panel/.env ]]; then echo echo " 3) $(bold 'Admin password') for the panel login (user: admin)" while :; do read -rsp " password: " p1; echo read -rsp " repeat: " p2; echo [[ "$p1" == "$p2" ]] || { echo " passwords differ"; continue; } [[ ${#p1} -ge 8 ]] || { echo " at least 8 characters"; continue; } PASSWORD="$p1"; break done elif [[ -z "$PASSWORD" ]]; then echo echo " 3) Admin password: keeping the existing one (panel already configured)" fi echo echo " 4) $(bold 'System domain base') — every app also gets -. (wildcard DNS → this server)" if [[ -z "$SYSTEM_DOMAIN_BASE_IN" ]]; then if [[ "$YES" -eq 1 ]]; then SYSTEM_DOMAIN_BASE_IN="${CUR_BASE:-younex.de}"; else ask SYSTEM_DOMAIN_BASE_IN " base" "${CUR_BASE:-younex.de}"; fi fi valid_domain "$SYSTEM_DOMAIN_BASE_IN" || { echo "invalid base domain" >&2; exit 1; } echo echo " 5) $(bold "ACME e-mail") — Let's Encrypt contact for certificates" if [[ -z "$ACME_EMAIL_IN" ]]; then if [[ "$YES" -eq 1 ]]; then ACME_EMAIL_IN="info@logyou.de"; else ask ACME_EMAIL_IN " e-mail" "info@logyou.de"; fi fi if [[ "$FLAVOUR" == "laravel" && -z "$PHP_VERSIONS_IN" ]]; then echo echo " 6) $(bold 'PHP versions') to install (selectable per app)" if [[ "$YES" -eq 1 ]]; then PHP_VERSIONS_IN="8.3 8.4"; else ask PHP_VERSIONS_IN " versions" "8.3 8.4"; fi fi # ---- 3. summary + confirm ---- echo hr; echo " $(bold 'Summary')"; hr printf ' %-22s %s\n' "panel domain:" "https://$PANEL_DOMAIN" printf ' %-22s %s\n' "flavour:" "$FLAVOUR$([[ "$FLAVOUR" == laravel ]] && echo " (PHP $PHP_VERSIONS_IN, MariaDB)")" printf ' %-22s %s\n' "admin password:" "$([[ -n "$PASSWORD" ]] && echo '(set)' || echo '(unchanged)')" printf ' %-22s %s\n' "system domain base:" "$SYSTEM_DOMAIN_BASE_IN" printf ' %-22s %s\n' "ACME e-mail:" "$ACME_EMAIL_IN" printf ' %-22s %s\n' "code:" "$(git -C "$REPO_DIR" log -1 --format='%h %s' 2>/dev/null || echo "$REPO_DIR")" hr if [[ "$YES" -ne 1 ]]; then ask go "Run the setup now? (y/n)" y [[ "$go" =~ ^[Yy] ]] || { echo "aborted — nothing changed"; exit 1; } fi # ---- 4. run bootstrap ---- echo ENV=(PANEL_DOMAIN="$PANEL_DOMAIN" SYSTEM_DOMAIN_BASE="$SYSTEM_DOMAIN_BASE_IN" ACME_EMAIL="$ACME_EMAIL_IN") [[ -n "$PASSWORD" ]] && ENV+=(PANEL_ADMIN_PASSWORD="$PASSWORD") if [[ "$FLAVOUR" == "laravel" ]]; then ENV+=(WITH_LARAVEL=1 PHP_VERSIONS="$PHP_VERSIONS_IN"); fi LOG=/var/log/paas-init.log echo " running bootstrap.sh (log: $LOG) — a few minutes on first run…" if ! ( cd "$REPO_DIR" && env "${ENV[@]}" ./bootstrap.sh ) 2>&1 | tee -a "$LOG"; then echo echo " bootstrap FAILED — see $LOG. Fix the cause and re-run: sudo paas-init" >&2 exit 1 fi # an existing SYSTEM_DOMAIN_BASE is not rewritten by bootstrap — apply the answer if [[ -f /srv/paas/paas.conf ]]; then if grep -q '^SYSTEM_DOMAIN_BASE=' /srv/paas/paas.conf; then sed -i "s|^SYSTEM_DOMAIN_BASE=.*|SYSTEM_DOMAIN_BASE=$SYSTEM_DOMAIN_BASE_IN|" /srv/paas/paas.conf else echo "SYSTEM_DOMAIN_BASE=$SYSTEM_DOMAIN_BASE_IN" >>/srv/paas/paas.conf fi fi grep -qs 'paas-init' /etc/motd && : >/etc/motd # drop the "not set up yet" banner install -m 755 "$REPO_DIR/bin/paas-init" /usr/local/bin/paas-init 2>/dev/null || true # ---- 5. what's left ---- CMK_TOKEN="$(sed -n 's/^CMK_AGENT_TOKEN=//p' /srv/paas/panel/.env 2>/dev/null | head -1)" WAN_HINT="$(hostname -I 2>/dev/null | awk '{print $1}')" echo hr; echo " $(bold 'Setup complete')"; hr cat </dev/null || echo '(missing?)') Still to do outside this VM: 1. OPNsense: NAT 80/tcp, 443/tcp, 443/udp from a free WAN alias -> $WAN_HINT 2. DNS: $PANEL_DOMAIN (and later each app domain) -> that WAN alias; *.$SYSTEM_DOMAIN_BASE_IN wildcard -> this server if it should serve system domains 3. Checkmk: host with datasource https://$PANEL_DOMAIN/cmk-agent${CMK_TOKEN:+, token: $CMK_TOKEN} 4. Git hosts: add the deploy key above for private repositories Then: open the panel -> New project$([[ "$FLAVOUR" == laravel ]] && echo ' (Runtime: Node.js or Laravel)') Re-run 'sudo paas-init' any time to change the panel domain or add the Laravel flavour. EOF