#!/usr/bin/env bash # lib/build.func — shared host-side helpers for ct/*.sh # # Sourced via: # source <(curl -fsSL https://gitea.luki-net.org/luki-net/proxmox-scripts/raw/branch/main/lib/build.func) # # Caller (ct/.sh) is expected to set: # APP short app name (used for filenames, banner) # APP_DESCRIPTION one-line description (shown in banner) # INSTALL_SCRIPT_URL URL of the matching install/-install.sh # and may override the DEFAULT_* values below. # # Caller may define print_app_summary() to customize the trailing summary. # ── colors / logging ───────────────────────────────────────────────────────── if [[ -z "${_COLORS_LOADED:-}" ]]; then RED="\033[0;31m"; GREEN="\033[0;32m"; YELLOW="\033[1;33m"; BLUE="\033[0;34m"; NC="\033[0m" _COLORS_LOADED=1 fi msg_info() { echo -e "${BLUE}[i]${NC} $*"; } msg_ok() { echo -e "${GREEN}[✓]${NC} $*"; } msg_warn() { echo -e "${YELLOW}[!]${NC} $*"; } msg_err() { echo -e "${RED}[✗]${NC} $*" >&2; } # ── defaults (per-script overridable before sourcing or via env) ───────────── DEFAULT_HOSTNAME="${DEFAULT_HOSTNAME:-lxc}" DEFAULT_DISK="${DEFAULT_DISK:-8}" DEFAULT_CORES="${DEFAULT_CORES:-2}" DEFAULT_RAM="${DEFAULT_RAM:-1024}" DEFAULT_BRIDGE="${DEFAULT_BRIDGE:-vmbr0}" DEFAULT_TEMPLATE_STORAGE="${DEFAULT_TEMPLATE_STORAGE:-local}" DEFAULT_ROOTFS_STORAGE="${DEFAULT_ROOTFS_STORAGE:-local-lvm}" DEFAULT_TEMPLATE_PATTERN="${DEFAULT_TEMPLATE_PATTERN:-debian-12-standard}" # ── error trap ─────────────────────────────────────────────────────────────── _on_error() { local exit_code=$? msg_err "Installation failed (exit $exit_code)" if [[ -n "${CTID:-}" ]] && pct status "$CTID" >/dev/null 2>&1; then msg_warn "LXC $CTID was created. To clean up: pct stop $CTID && pct destroy $CTID" fi exit "$exit_code" } # ── checks ─────────────────────────────────────────────────────────────────── preflight_pve() { [[ "$EUID" -eq 0 ]] || { msg_err "Must run as root"; exit 1; } command -v pveversion >/dev/null 2>&1 || { msg_err "Not a Proxmox VE host (pveversion not found)"; exit 1; } } show_header() { local app="$1" desc="${2:-}" clear echo "═══════════════════════════════════════════════════════════════" echo " ${app} · Proxmox LXC installer" [[ -n "$desc" ]] && echo " ${desc}" echo " luki-net/proxmox-scripts" echo "═══════════════════════════════════════════════════════════════" echo } # ── prompts ────────────────────────────────────────────────────────────────── # Each prompt is skipped if the corresponding variable is already set in env. prompt_lxc_config() { if [[ -z "${CTID:-}" ]]; then read -rp "Container ID [auto]: " CTID [[ -z "${CTID:-}" ]] && CTID=$(pvesh get /cluster/nextid) fi echo " → CTID: $CTID" if [[ -z "${HOSTNAME:-}" ]]; then read -rp "Hostname [$DEFAULT_HOSTNAME]: " HOSTNAME HOSTNAME="${HOSTNAME:-$DEFAULT_HOSTNAME}" fi if [[ -z "${DISK_SIZE:-}" ]]; then read -rp "Disk size in GB [$DEFAULT_DISK]: " DISK_SIZE DISK_SIZE="${DISK_SIZE:-$DEFAULT_DISK}" fi if [[ -z "${CORES:-}" ]]; then read -rp "vCPU cores [$DEFAULT_CORES]: " CORES CORES="${CORES:-$DEFAULT_CORES}" fi if [[ -z "${RAM:-}" ]]; then read -rp "RAM in MB [$DEFAULT_RAM]: " RAM RAM="${RAM:-$DEFAULT_RAM}" fi if [[ -z "${BRIDGE:-}" ]]; then read -rp "Bridge [$DEFAULT_BRIDGE]: " BRIDGE BRIDGE="${BRIDGE:-$DEFAULT_BRIDGE}" fi if [[ -z "${TEMPLATE_STORAGE:-}" ]]; then read -rp "Template storage [$DEFAULT_TEMPLATE_STORAGE]: " TEMPLATE_STORAGE TEMPLATE_STORAGE="${TEMPLATE_STORAGE:-$DEFAULT_TEMPLATE_STORAGE}" fi if [[ -z "${ROOTFS_STORAGE:-}" ]]; then read -rp "Rootfs storage [$DEFAULT_ROOTFS_STORAGE]: " ROOTFS_STORAGE ROOTFS_STORAGE="${ROOTFS_STORAGE:-$DEFAULT_ROOTFS_STORAGE}" fi if [[ -z "${IPCFG:-}" ]]; then read -rp "Network: IP/CIDR or 'dhcp' [dhcp]: " IPCFG IPCFG="${IPCFG:-dhcp}" fi GATEWAY="${GATEWAY:-}" if [[ "$IPCFG" != "dhcp" && -z "$GATEWAY" ]]; then read -rp "Gateway: " GATEWAY fi } # ── template ───────────────────────────────────────────────────────────────── resolve_debian_template() { msg_info "Resolving template ($DEFAULT_TEMPLATE_PATTERN)..." pveam update >/dev/null 2>&1 || true TEMPLATE=$(pveam available --section system \ | awk -v t="$DEFAULT_TEMPLATE_PATTERN" '$2 ~ t {print $2}' \ | sort -V | tail -n1) [[ -n "$TEMPLATE" ]] || { msg_err "No template matching '$DEFAULT_TEMPLATE_PATTERN' found"; exit 1; } if ! pveam list "$TEMPLATE_STORAGE" 2>/dev/null | grep -q "$TEMPLATE"; then msg_info "Downloading template $TEMPLATE to $TEMPLATE_STORAGE..." pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" fi msg_ok "Template: $TEMPLATE" } # ── LXC ────────────────────────────────────────────────────────────────────── create_lxc() { ROOT_PASSWORD=$(openssl rand -base64 18) local net_opts="name=eth0,bridge=$BRIDGE" if [[ "$IPCFG" == "dhcp" ]]; then net_opts+=",ip=dhcp" else net_opts+=",ip=$IPCFG,gw=$GATEWAY" fi msg_info "Creating unprivileged LXC $CTID ($HOSTNAME)..." pct create "$CTID" "$TEMPLATE_STORAGE:vztmpl/$TEMPLATE" \ --hostname "$HOSTNAME" \ --cores "$CORES" \ --memory "$RAM" \ --swap 512 \ --rootfs "$ROOTFS_STORAGE:$DISK_SIZE" \ --net0 "$net_opts" \ --features nesting=1 \ --unprivileged 1 \ --onboot 1 \ --ostype debian \ --password "$ROOT_PASSWORD" msg_ok "LXC $CTID created" msg_info "Starting LXC..." pct start "$CTID" sleep 5 msg_info "Waiting for network..." local i for i in {1..30}; do if pct exec "$CTID" -- getent hosts deb.debian.org >/dev/null 2>&1; then break fi sleep 2 done msg_ok "Network up" } # ── bootstrap installer inside the container ───────────────────────────────── bootstrap_install_script() { local url="$1" msg_info "Installing curl in container..." pct exec "$CTID" -- bash -c "apt-get update -qq && apt-get install -y -qq curl ca-certificates >/dev/null" msg_info "Running installer ($url)..." pct exec "$CTID" -- bash -c "curl -fsSL '$url' -o /root/${APP}-install.sh && bash /root/${APP}-install.sh" } # ── summary ────────────────────────────────────────────────────────────────── get_container_ip() { pct exec "$CTID" -- bash -c "hostname -I | awk '{print \$1}'" | tr -d '\r\n' } # Override in ct/.sh for app-specific output. print_app_summary() { echo " (no app-specific summary defined)" } print_summary() { IP_CT=$(get_container_ip) echo msg_ok "Installation complete!" echo echo "─────────────────────────────────────────────────────────────" echo " $APP LXC #$CTID — $HOSTNAME" echo "─────────────────────────────────────────────────────────────" print_app_summary echo echo " LXC root password: $ROOT_PASSWORD" echo "─────────────────────────────────────────────────────────────" echo } # ── orchestrator ───────────────────────────────────────────────────────────── run_installer() { trap _on_error ERR preflight_pve show_header "$APP" "${APP_DESCRIPTION:-}" prompt_lxc_config resolve_debian_template create_lxc bootstrap_install_script "$INSTALL_SCRIPT_URL" print_summary }