#!/usr/bin/env bash # # generalize-golden.sh — strip ALL instance state from a paas VM so it can be # turned into a Proxmox template (qm template). Run as root on a THROWAWAY # clone, never on a live server. The VM shuts down at the end. # # sudo PREINSTALL_LARAVEL=1 ./infra/generalize-golden.sh # # PREINSTALL_LARAVEL=1 (recommended for the single "all features" template): # downloads the PHP-FPM/composer/MariaDB packages now and leaves their # services DISABLED, so a clone bootstrapped with WITH_LARAVEL=1 needs no # apt downloads, while a clone bootstrapped without the flag stays a plain # Node server (services off, nothing configured — identical to production). # # What survives (generic): OS + packages, Node/PM2, custom Caddy binary + # systemd override, Varnish, chromium, Checkmk agent, GeoIP mmdb, the paas # CLI + panel code with node_modules, all timers, sudoers, the deploy user # (with the workstation key), the repo checkout at /home/younes/dotnews-paas. # What is removed: apps, paas.db, panel .env/secrets, TLS certs + ACME # account, deploy SSH keypair, logs/snapshots/builds, DB credentials + dumps, # PHP pools, machine-id, SSH host keys, cloud-init state, shell histories. set -euo pipefail [[ "$(id -u)" -eq 0 ]] || { echo "run as root" >&2; exit 1; } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" log() { printf '\n[generalize] %s\n' "$*"; } read -rp "[generalize] This wipes every app, database, secret and key on $(hostname) and shuts it down. Type GENERALIZE to continue: " a [[ "$a" == "GENERALIZE" ]] || { echo "aborted"; exit 1; } # ------------------------------------------------ optional laravel preinstall ---- if [[ "${PREINSTALL_LARAVEL:-0}" == "1" ]]; then log "pre-installing PHP/composer/MariaDB packages (services stay disabled)" PHP_VERSIONS="${PHP_VERSIONS:-8.3 8.4}" if [[ ! -f /etc/apt/sources.list.d/sury-php.list ]]; then # shellcheck disable=SC1091 . /etc/os-release curl -fsSL https://packages.sury.org/php/apt.gpg | gpg --dearmor --yes -o /usr/share/keyrings/deb.sury.org-php.gpg echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $VERSION_CODENAME main" \ >/etc/apt/sources.list.d/sury-php.list fi export DEBIAN_FRONTEND=noninteractive apt-get update -qq for V in $PHP_VERSIONS; do apt-get install -y -qq "php$V-fpm" "php$V-cli" "php$V-mysql" "php$V-mbstring" "php$V-xml" \ "php$V-curl" "php$V-zip" "php$V-bcmath" "php$V-intl" "php$V-gd" "php$V-opcache" \ "php$V-readline" "php$V-sqlite3" "php$V-redis" >/dev/null systemctl disable --now "php$V-fpm" >/dev/null 2>&1 || true done apt-get install -y -qq mariadb-server mariadb-client >/dev/null systemctl disable --now mariadb >/dev/null 2>&1 || true if ! command -v composer >/dev/null; then FIRST="${PHP_VERSIONS%% *}" EXPECTED="$(curl -fsSL https://composer.github.io/installer.sig)" curl -fsSL https://getcomposer.org/installer -o /tmp/composer-setup.php ACTUAL="$("/usr/bin/php$FIRST" -r "echo hash_file('sha384', '/tmp/composer-setup.php');")" [[ "$EXPECTED" == "$ACTUAL" ]] || { echo "composer installer checksum mismatch" >&2; exit 1; } "/usr/bin/php$FIRST" /tmp/composer-setup.php --quiet --install-dir=/usr/local/bin --filename=composer rm -f /tmp/composer-setup.php fi fi # --------------------------------------------------------- stop everything ---- log "stopping services" sudo -u deploy -H bash -c 'cd /srv/paas && pm2 delete all >/dev/null 2>&1; pm2 save --force >/dev/null 2>&1; pm2 kill >/dev/null 2>&1' || true systemctl stop caddy varnish 2>/dev/null || true systemctl stop 'php*-fpm' mariadb 2>/dev/null || true systemctl stop paas-traffic-rollup.timer paas-clicks-writeback.timer paas-geoip-refresh.timer \ paas-google-crawlers-refresh.timer paas-laravel-scheduler.timer paas-db-backup.timer 2>/dev/null || true # ------------------------------------------------------------ apps + paas ---- log "removing apps, registry, generated configs, database, panel secrets" rm -rf /srv/apps/* rm -rf /srv/paas/apps.d/* /srv/paas/caddy/app-*.caddy /srv/paas/caddy/panel.caddy rm -f /srv/paas/paas.db /srv/paas/paas.db-wal /srv/paas/paas.db-shm rm -rf /srv/paas/builds/* /srv/paas/logs/* /srv/paas/snapshots/* rm -f /srv/paas/panel/.env /srv/paas/panel/run.sh rm -rf /srv/paas/php/*/paas-*.conf /srv/paas/php/*/.staged /srv/paas/php/*/.bak rm -f /srv/paas/db-admin.cnf rm -rf /srv/paas/backups # varnish back to "no backends" echo "backend b_paas_none none;" >/srv/paas/vcl/paas-backends.vcl echo 'return (synth(421, "Misdirected Request"));' >/srv/paas/vcl/paas-routing.vcl chown deploy:deploy /srv/paas/vcl/paas-*.vcl # platform conf: keep SYSTEM_DOMAIN_BASE, drop runtime capability flags if [[ -f /srv/paas/paas.conf ]]; then sed -i '/^RUNTIMES=/d;/^PHP_VERSIONS=/d;/^PHP_DEFAULT=/d' /srv/paas/paas.conf fi # laravel sudoers/timers are (re)written by bootstrap WITH_LARAVEL=1 rm -f /etc/sudoers.d/paas-laravel rm -f /etc/systemd/system/paas-laravel-scheduler.* /etc/systemd/system/paas-db-backup.* systemctl daemon-reload # mariadb: drop anything a bootstrap may have created (app DBs + paas_admin) if command -v mariadbd >/dev/null; then systemctl start mariadb 2>/dev/null || true mariadb -e "SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME NOT IN ('mysql','information_schema','performance_schema','sys')" -N -B 2>/dev/null \ | while read -r d; do [[ -n "$d" ]] && mariadb -e "DROP DATABASE \`$d\`"; done mariadb -e "SELECT CONCAT(user,'@',host) FROM mysql.user WHERE user NOT IN ('root','mariadb.sys','mysql')" -N -B 2>/dev/null \ | while read -r u; do [[ -n "$u" ]] && mariadb -e "DROP USER IF EXISTS ${u%@*}@'${u#*@}'"; done systemctl stop mariadb 2>/dev/null || true systemctl disable mariadb >/dev/null 2>&1 || true rm -f /etc/mysql/mariadb.conf.d/60-paas.cnf fi for V in /etc/php/*/; do [[ -d "$V" ]] || continue systemctl disable "php$(basename "$V")-fpm" >/dev/null 2>&1 || true done # ------------------------------------------------- first-boot wizard + banner ---- log "installing paas-init + login banner" install -m 755 "$SCRIPT_DIR/bin/paas-init" /usr/local/bin/paas-init cat >/etc/motd <<'EOF' ┌──────────────────────────────────────────────────────────────┐ │ dotnews-paas golden clone — NOT SET UP YET │ │ │ │ Run: sudo paas-init │ │ It asks for the panel domain, admin password and flavour │ │ (Node.js like production, or Laravel with PHP-FPM/MariaDB) │ │ and then configures everything. │ └──────────────────────────────────────────────────────────────┘ EOF # ------------------------------------------------------- certs, keys, ids ---- log "removing TLS state, deploy key, machine identity" rm -rf /var/lib/caddy/.local/share/caddy /var/lib/caddy/.config/caddy rm -f /home/deploy/.ssh/id_ed25519 /home/deploy/.ssh/id_ed25519.pub /home/deploy/.ssh/known_hosts rm -f /etc/ssh/ssh_host_* truncate -s 0 /etc/machine-id rm -f /var/lib/dbus/machine-id; ln -s /etc/machine-id /var/lib/dbus/machine-id command -v cloud-init >/dev/null && cloud-init clean --logs --seed 2>/dev/null || true # ------------------------------------------------------------- housekeeping ---- log "housekeeping" apt-get clean journalctl --rotate >/dev/null 2>&1 || true; journalctl --vacuum-time=1s >/dev/null 2>&1 || true rm -rf /tmp/* /var/tmp/* /root/.cache /home/*/.cache /home/deploy/.pm2/logs/* 2>/dev/null || true rm -f /root/.bash_history /home/*/.bash_history /root/.mysql_history /home/*/.mysql_history find /var/log -type f \( -name '*.gz' -o -name '*.[0-9]' \) -delete 2>/dev/null || true find /var/log -type f -exec truncate -s 0 {} \; 2>/dev/null || true # repo checkout: make sure it is at the commit we want in the template if [[ -d "$SCRIPT_DIR/.git" ]]; then log "repo checkout at $(git -C "$SCRIPT_DIR" rev-parse --short HEAD) ($(git -C "$SCRIPT_DIR" log -1 --format=%s))" fi log "DONE — powering off. On Proxmox: qm template " sync poweroff