#!/usr/bin/env bash # ===================================================================== # Create a new LogShip instance from the golden template (VM 113) # --------------------------------------------------------------------- # Runs ON prox2 (needs qm). One command: clone -> IP -> boot -> rebrand. # # clone-logship-instance.sh "" \ # [""] [""] \ # [--name ] [--memory ] [--cores ] [--domain ] # # --domain public domain (e.g. app.example.com): instance public URLs # become https:// (STRAPIPUBLIC, WebAuthn). DNS + TLS # proxy to must be set up separately. Without --domain, # public URLs use http://. # # NEWID free Proxmox VMID (e.g. 114) # IP free IP in 192.168.12.0/24 (gw .1 assumed) # ClientName tenant name (e.g. "Acme Fulfillment") # OrgName default: ClientName # CompanyLegalName default: ClientName (linked partner, e.g. "Acme GmbH") # --name VM name; default: client name slugified (acme-fulfillment) # --memory/--cores default: inherited from template (20480 MB / 8) # # After it finishes, the instance still needs (see TEMPLATE-README.md): # .env endpoints+credentials, iDempiere REST secret rotation, own users, # crons from /root/crontab.production.bak, postfix/SMTP if mail wanted. # ===================================================================== set -euo pipefail TEMPLATE_ID=113 GW=192.168.12.1 BRIDGE=vmbr1 usage() { grep '^# ' "$0" | sed 's/^# //'; exit 1; } [ $# -ge 3 ] || usage NEWID="$1"; IP="$2"; CLIENT="$3"; shift 3 ORG=""; COMPANY=""; VMNAME=""; MEMORY=""; CORES=""; DOMAIN="" while [ $# -gt 0 ]; do case "$1" in --name) VMNAME="$2"; shift 2;; --memory) MEMORY="$2"; shift 2;; --cores) CORES="$2"; shift 2;; --domain) DOMAIN="$2"; shift 2;; --*) echo "unknown option $1" >&2; usage;; *) if [ -z "$ORG" ]; then ORG="$1" elif [ -z "$COMPANY" ]; then COMPANY="$1" else echo "unexpected argument: $1" >&2; usage; fi shift;; esac done ORG="${ORG:-$CLIENT}" COMPANY="${COMPANY:-$CLIENT}" VMNAME="${VMNAME:-$(echo "$CLIENT" | tr '[:upper:] ' '[:lower:]-' | tr -cd 'a-z0-9-')}" # --- preflight ------------------------------------------------------------ qm status "$TEMPLATE_ID" >/dev/null || { echo "FATAL: template VM $TEMPLATE_ID missing" >&2; exit 1; } qm status "$TEMPLATE_ID" | grep -q stopped || { echo "FATAL: template VM $TEMPLATE_ID is running — stop it first (it must stay pristine)" >&2; exit 1; } if qm status "$NEWID" >/dev/null 2>&1; then echo "FATAL: VMID $NEWID already exists" >&2; exit 1; fi if ping -c1 -W1 "$IP" >/dev/null 2>&1; then echo "FATAL: $IP answers ping — pick a free IP" >&2; exit 1; fi if grep -rqE "ip=${IP}/" /etc/pve/qemu-server/ 2>/dev/null; then echo "FATAL: $IP already configured on another VM" >&2; exit 1 fi PUBLIC_TARGET="${DOMAIN:-$IP}" echo "== Cloning template $TEMPLATE_ID -> VM $NEWID ($VMNAME), IP $IP" echo " client='$CLIENT' org='$ORG' company='$COMPANY' public='$PUBLIC_TARGET'" # --- clone + configure ---------------------------------------------------- qm clone "$TEMPLATE_ID" "$NEWID" --name "$VMNAME" --full qm set "$NEWID" --ipconfig0 "ip=${IP}/24,gw=${GW}" --onboot 1 [ -n "$MEMORY" ] && qm set "$NEWID" --memory "$MEMORY" [ -n "$CORES" ] && qm set "$NEWID" --cores "$CORES" qm start "$NEWID" echo "== Waiting for guest agent..." for i in $(seq 1 30); do qm agent "$NEWID" ping >/dev/null 2>&1 && break [ "$i" = 30 ] && { echo "FATAL: guest agent not up after 5 min" >&2; exit 1; } sleep 10 done echo "== Waiting for PostgreSQL in guest..." for i in $(seq 1 30); do if qm guest exec "$NEWID" -- sudo -u postgres pg_isready >/dev/null 2>&1; then break; fi [ "$i" = 30 ] && { echo "FATAL: postgres not ready after 5 min" >&2; exit 1; } sleep 10 done echo "== Pointing instance .env public URLs at ${PUBLIC_TARGET}..." qm guest exec "$NEWID" --timeout 120 -- \ /root/template-cleanup/03-neutralize-env.sh "$PUBLIC_TARGET" echo "== Rebranding tenant inside the guest..." qm guest exec "$NEWID" --timeout 300 -- \ /root/template-cleanup/04-set-instance-identity.sh "$CLIENT" "$ORG" "$COMPANY" cat < public IP, and a TLS-terminating proxy vhost forwarding to ${IP}:3001 (app) and ${IP}:80 (/api/v1, /media-api, /custom-api, media root) — mirror the app.logship.de vhost. D ) Still manual (see /root/TEMPLATE-README.md in the guest): 1. /opt/erp-nuxt-frontend/.env — endpoints + credentials, then: pm2 restart erpfrontend --update-env 2. Rotate iDempiere REST secret (prod tokens work until you do!) 3. Replace staff users/emails, set own SuperUser password 4. Re-enable wanted crons from /root/crontab.production.bak 5. Mail: systemctl enable --now postfix + iDempiere SMTP config EOF