From c552701b4319ce1a8b9bd834389d27c8fa741f18 Mon Sep 17 00:00:00 2001 From: Wolfgang Date: Fri, 9 Jan 2026 18:54:01 +0100 Subject: [PATCH] Installer ersetzt --- install.sh | 305 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 239 insertions(+), 66 deletions(-) diff --git a/install.sh b/install.sh index cb2c4f9..504e3f3 100755 --- a/install.sh +++ b/install.sh @@ -1,91 +1,264 @@ #!/usr/bin/env bash set -Eeuo pipefail -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -source "$SCRIPT_DIR/libsupabase.sh" +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=libsupabase.sh +source "${SCRIPT_DIR}/libsupabase.sh" setup_traps -######################################## +# ------------------------- # Defaults -######################################## +# ------------------------- +CTID="" +CORES="2" +MEMORY="4096" +SWAP="512" +DISK="50" +BRIDGE="vmbr0" +STORAGE="local-zfs" +IPCFG="dhcp" +UNPRIV="1" +TG_TOKEN="" +TG_CHAT="" +DOMAIN="userman.de" -cores=2 -memory=4096 -swap=512 -disk=50 -bridge="vmbr0" -storage="local-zfs" -ip="dhcp" -unpriv=1 -ctid="" +CT_HOSTNAME="sb-$(date +%s)" -######################################## +usage() { + cat <<'USAGE' +Usage: + bash install.sh [options] + +Core options: + --ctid Force CT ID (optional) + --cores (default: 2) + --memory (default: 4096) + --swap (default: 512) + --disk (default: 50) + --bridge (default: vmbr0) + --storage (default: local-zfs) + --ip (default: dhcp) + --privileged Create privileged CT (default: unprivileged) + --domain FQDN base (default: userman.de) + +Telegram options: + --tg-token Telegram bot token (optional) + --tg-chat Telegram chat id (optional) + + --help Show help + +Examples: + bash install.sh + bash install.sh --storage local-zfs --bridge vmbr0 --ip dhcp + bash install.sh --ip 192.168.45.171/24 --bridge vmbr90 --domain userman.de +USAGE +} + +# ------------------------- # Args -######################################## - +# ------------------------- while [[ $# -gt 0 ]]; do case "$1" in - --cores) cores="$2"; shift 2 ;; - --memory) memory="$2"; shift 2 ;; - --swap) swap="$2"; shift 2 ;; - --disk) disk="$2"; shift 2 ;; - --bridge) bridge="$2"; shift 2 ;; - --storage) storage="$2"; shift 2 ;; - --ip) ip="$2"; shift 2 ;; - --ctid) ctid="$2"; shift 2 ;; - --help) - echo "Usage: bash install.sh [options]" - exit 0 - ;; - *) - die "Unknown argument: $1" - ;; + --ctid) CTID="${2:-}"; shift 2 ;; + --cores) CORES="${2:-}"; shift 2 ;; + --memory) MEMORY="${2:-}"; shift 2 ;; + --swap) SWAP="${2:-}"; shift 2 ;; + --disk) DISK="${2:-}"; shift 2 ;; + --bridge) BRIDGE="${2:-}"; shift 2 ;; + --storage) STORAGE="${2:-}"; shift 2 ;; + --ip) IPCFG="${2:-}"; shift 2 ;; + --privileged) UNPRIV="0"; shift 1 ;; + --domain) DOMAIN="${2:-}"; shift 2 ;; + --tg-token) TG_TOKEN="${2:-}"; shift 2 ;; + --tg-chat) TG_CHAT="${2:-}"; shift 2 ;; + --help|-h) usage; exit 0 ;; + *) die "Unknown arg: $1 (use --help)" ;; esac done -info "Argument-Parsing OK" +# ------------------------- +# Validate basics +# ------------------------- +need_cmd pct pvesm pveam pvesh python3 awk grep date -######################################## -# Checks -######################################## +[[ "$CORES" =~ ^[0-9]+$ ]] || die "--cores must be integer" +[[ "$MEMORY" =~ ^[0-9]+$ ]] || die "--memory must be integer (MB)" +[[ "$SWAP" =~ ^[0-9]+$ ]] || die "--swap must be integer (MB)" +[[ "$DISK" =~ ^[0-9]+$ ]] || die "--disk must be integer (GB)" +[[ "$UNPRIV" == "0" || "$UNPRIV" == "1" ]] || die "Internal: UNPRIV must be 0/1" +[[ -n "$DOMAIN" ]] || die "--domain empty" -need_cmd pct pvesm pveam pvesh awk grep sort - -pve_bridge_exists "$bridge" || die "Bridge $bridge not found" -pve_storage_exists "$storage" || die "Storage $storage not found" - -template="$(pve_template_ensure_debian12 "$storage")" -info "Template OK: $template" - -if [[ -z "$ctid" ]]; then - ctid="$(pve_next_free_ctid)" +if [[ "$IPCFG" != "dhcp" ]]; then + [[ "$IPCFG" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$ ]] || die "--ip must be 'dhcp' or CIDR like 192.168.45.171/24" fi -[[ -n "$ctid" ]] || die "CTID empty" +info "Argument-Parsing OK" -hostname="sb-$(date +%s)" +# ------------------------- +# Step 4: Preflight Proxmox +# ------------------------- +pve_storage_exists "$STORAGE" || die "Storage not found: ${STORAGE}" +pve_bridge_exists "$BRIDGE" || die "Bridge not found or not a Linux bridge: ${BRIDGE}" -info "Using CTID=$ctid Hostname=$hostname" +TEMPLATE="$(pve_template_ensure_debian12 "$STORAGE")" +[[ -n "$TEMPLATE" ]] || die "Template selection failed (empty)" +info "Template OK: ${TEMPLATE}" -######################################## -# Create CT -######################################## +if [[ -n "$CTID" ]]; then + [[ "$CTID" =~ ^[0-9]+$ ]] || die "--ctid must be integer" +else + CTID="$(pve_next_free_ctid 2000 9999 || true)" + [[ -n "$CTID" ]] || die "CTID selection failed (empty)" +fi +info "CTID selected: ${CTID}" -net0="$(pve_build_net0 "$bridge" "$ip")" -rootfs="$storage:$disk" -features="nesting=1,keyctl=1,fuse=1" +info "SCRIPT_DIR=${SCRIPT_DIR}" +info "CT_HOSTNAME=${CT_HOSTNAME}" +info "cores=${CORES} memory=${MEMORY}MB swap=${SWAP}MB disk=${DISK}GB" +info "bridge=${BRIDGE} storage=${STORAGE} ip=${IPCFG} unprivileged=${UNPRIV}" +if [[ -n "$TG_TOKEN" && -n "$TG_CHAT" ]]; then + info "Telegram enabled (token/chat provided)" +else + info "Telegram disabled" +fi -info "Creating CT $ctid" +# ------------------------- +# Step 5: Create CT +# ------------------------- +info "Step 5: Create CT" +pve_create_ct "$CTID" "$TEMPLATE" "$CT_HOSTNAME" "$CORES" "$MEMORY" "$SWAP" "$STORAGE" "$DISK" "$BRIDGE" "$IPCFG" "$UNPRIV" -pct create "$ctid" "$template" \ - --hostname "$hostname" \ - --cores "$cores" \ - --memory "$memory" \ - --swap "$swap" \ - --net0 "$net0" \ - --rootfs "$rootfs" \ - --unprivileged "$unpriv" \ - --features "$features" \ - --start 0 +info "CT created (not started). Next step: start CT + wait for IP" +pve_start_ct "$CTID" -info "CT created successfully" +CT_IP="$(pve_wait_ct_ip "$CTID" 180 || true)" +[[ -n "$CT_IP" ]] || die "CT IP not obtained (timeout)" +info "Step 5 OK: LXC erstellt + IP ermittelt" +info "CT_HOSTNAME=${CT_HOSTNAME}" +info "CT_IP=${CT_IP}" + +# ------------------------- +# Step 6: Provision inside CT +# ------------------------- +info "Step 6: Provisioning im CT (Docker + Stack)" + +# 6.1 Base packages + Docker (Debian 12: docker.io + compose plugin) +ct_exec "$CTID" "export DEBIAN_FRONTEND=noninteractive; apt-get update -y" +ct_exec "$CTID" "export DEBIAN_FRONTEND=noninteractive; apt-get install -y ca-certificates curl gnupg lsb-release apt-transport-https" +ct_exec "$CTID" "export DEBIAN_FRONTEND=noninteractive; apt-get install -y docker.io docker-compose-plugin" + +# enable/start docker +ct_exec "$CTID" "systemctl enable --now docker" + +# 6.2 customer-stack dirs +ct_exec "$CTID" "mkdir -p /opt/customer-stack/volumes/{n8n-data,n8n-db,pgvector-db} /opt/customer-stack/sql" +ct_exec "$CTID" "chmod 700 /opt/customer-stack/volumes/n8n-db /opt/customer-stack/volumes/pgvector-db || true" + +# 6.3 Write init sql for pgvector (copy from host sql/init_pgvector.sql if exists) +if [[ -f "${SCRIPT_DIR}/sql/init_pgvector.sql" ]]; then + ct_push "$CTID" "${SCRIPT_DIR}/sql/init_pgvector.sql" "/opt/customer-stack/sql/init_pgvector.sql" +else + # minimal fallback + cat > /tmp/init_pgvector.sql <<'SQL' +CREATE EXTENSION IF NOT EXISTS vector; +SQL + ct_push "$CTID" "/tmp/init_pgvector.sql" "/opt/customer-stack/sql/init_pgvector.sql" + rm -f /tmp/init_pgvector.sql +fi + +# 6.4 Generate docker compose (real stack) +# Notes: +# - n8n runs behind reverse proxy later -> for now disable secure cookie to avoid the browser warning until TLS is in place. +# - WEBHOOK_URL is set to https://./ (works once proxy/TLS is configured) +# - For local testing without TLS you can set WEBHOOK_URL=http://:5678/ and N8N_SECURE_COOKIE=false (already false here) +N8N_BASIC_USER="$(tr -dc 'a-zA-Z0-9' temp file -> push +cat > /tmp/docker-compose.yml <