#!/usr/bin/env bash # Homelable — Proxmox VE LXC creator # Run this on the Proxmox HOST (not inside a container): # bash <(curl -fsSL https://raw.githubusercontent.com/Pouzor/homelable/main/scripts/install-proxmox.sh) set -euo pipefail RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m' info() { echo -e "${GREEN}[homelable]${NC} $*"; } warn() { echo -e "${YELLOW}[homelable]${NC} $*"; } error() { echo -e "${RED}[homelable]${NC} $*"; exit 1; } step() { echo -e "\n${CYAN}▶ $*${NC}"; } # ── Must run on a Proxmox VE host ───────────────────────────────────────────── [[ $EUID -ne 0 ]] && error "Run as root on the Proxmox host" command -v pct &>/dev/null || error "pct not found — run this on a Proxmox VE host, not inside a container" # ── Detect available storages for LXC rootfs ────────────────────────────────── mapfile -t STORAGES < <(pvesm status --content rootdir 2>/dev/null | awk 'NR>1 && $3=="active" {print $1}') [[ ${#STORAGES[@]} -eq 0 ]] && error "No active storage found that supports LXC rootfs (rootdir content type)" if [[ ${#STORAGES[@]} -eq 1 ]]; then DEFAULT_STORAGE="${STORAGES[0]}" else echo "" echo "Available storages:" for i in "${!STORAGES[@]}"; do echo " $((i+1))) ${STORAGES[$i]}" done read -rp "Select storage [1]: " STORAGE_IDX STORAGE_IDX="${STORAGE_IDX:-1}" DEFAULT_STORAGE="${STORAGES[$((STORAGE_IDX-1))]}" fi # ── Settings (override via env vars) ────────────────────────────────────────── CT_HOSTNAME="${CT_HOSTNAME:-homelable}" STORAGE="${STORAGE:-$DEFAULT_STORAGE}" DISK_SIZE="${DISK_SIZE:-8}" # GB RAM="${RAM:-1024}" # MB CORES="${CORES:-2}" BRIDGE="${BRIDGE:-vmbr0}" RAW="https://raw.githubusercontent.com/Pouzor/homelable/main" # ── Interactive prompts ──────────────────────────────────────────────────────── DEFAULT_CTID="$(pvesh get /cluster/nextid 2>/dev/null || echo 200)" if [[ -z "${CTID:-}" ]]; then read -rp "Container ID [${DEFAULT_CTID}]: " CTID_INPUT CTID="${CTID_INPUT:-$DEFAULT_CTID}" fi if [[ -z "${ROOT_PASSWORD:-}" ]]; then while true; do read -rsp "Root password for LXC container: " ROOT_PASSWORD echo "" [[ -z "$ROOT_PASSWORD" ]] && warn "Password cannot be empty, try again." && continue read -rsp "Confirm root password: " ROOT_PASSWORD_CONFIRM echo "" [[ "$ROOT_PASSWORD" == "$ROOT_PASSWORD_CONFIRM" ]] && break warn "Passwords do not match, try again." done fi step "Creating Homelable LXC (CTID=$CTID, hostname=$CT_HOSTNAME, storage=$STORAGE)" # ── Download Debian 12 template if needed ───────────────────────────────────── TEMPLATE_STORAGE=$(pvesm status --content vztmpl | awk 'NR>1 {print $1; exit}') TEMPLATE=$(pveam list "$TEMPLATE_STORAGE" 2>/dev/null | grep "debian-12" | tail -1 | awk '{print $1}') if [[ -z "$TEMPLATE" ]]; then info "Downloading Debian 12 LXC template..." pveam update TEMPLATE_NAME=$(pveam available --section system | grep "debian-12" | tail -1 | awk '{print $2}') [[ -z "$TEMPLATE_NAME" ]] && error "Could not find a Debian 12 template" pveam download "$TEMPLATE_STORAGE" "$TEMPLATE_NAME" TEMPLATE="$TEMPLATE_STORAGE:vztmpl/$TEMPLATE_NAME" fi info "Using template: $TEMPLATE" # ── Create the container ─────────────────────────────────────────────────────── pct create "$CTID" "$TEMPLATE" \ --hostname "$CT_HOSTNAME" \ --storage "$STORAGE" \ --rootfs "${STORAGE}:${DISK_SIZE}" \ --memory "$RAM" \ --cores "$CORES" \ --net0 "name=eth0,bridge=${BRIDGE},ip=dhcp${VLAN_TAG:+,tag=${VLAN_TAG}}" \ --ostype debian \ --unprivileged 1 \ --features "nesting=1" \ --password "$ROOT_PASSWORD" \ --start 1 info "Container $CTID created and started" # ── Wait for container to be ready ──────────────────────────────────────────── info "Waiting for container to be ready..." for i in $(seq 1 30); do if pct exec "$CTID" -- test -x /usr/bin/apt-get &>/dev/null; then break fi [[ $i -eq 30 ]] && error "Container did not become ready after 30s" sleep 1 done # Wait a bit more for network (DHCP lease) info "Waiting for network (DHCP)..." for i in $(seq 1 20); do if pct exec "$CTID" -- sh -c "ip route | grep -q default" &>/dev/null; then break fi [[ $i -eq 20 ]] && error "Container has no default route after 20s — check bridge $BRIDGE" sleep 1 done # ── Grant NET_RAW for nmap (ping-based checks) ───────────────────────────────── echo "lxc.cap.keep = net_raw net_bind_service" >> "/etc/pve/lxc/${CTID}.conf" 2>/dev/null || true # ── Bootstrap curl then run the installer ───────────────────────────────────── step "Running Homelable installer inside container $CTID..." pct exec "$CTID" -- apt-get install -y -qq curl pct exec "$CTID" -- bash -c "curl -fsSL ${RAW}/scripts/lxc-install.sh | bash" # ── Done ────────────────────────────────────────────────────────────────────── IP=$(pct exec "$CTID" -- hostname -I 2>/dev/null | awk '{print $1}' || echo "") echo "" echo -e " ${GREEN}✓ Homelable installed in LXC $CTID${NC}" echo -e " ${GREEN}✓ Open http://${IP}${NC}" echo -e " Homelable login: ${YELLOW}admin / admin${NC}" echo -e " LXC root SSH: ${YELLOW}root / ${NC}" echo -e " ${YELLOW}⚠ Change the Homelable password after first login${NC}" echo -e " ${YELLOW} - edit /opt/homelable/backend/.env (AUTH_PASSWORD_HASH)${NC}" echo ""