#!/usr/bin/env bash # Create a Debian LXC container on a Proxmox host and install LLeMbas in it. # # A **wrapper around what already works**, not a second install path. It makes a # container, puts the dependencies in it, and runs `deploy/install.sh` inside -- # which is the same script, doing the same things, so a fix to the installer # reaches this without anybody remembering. A parallel installer would be two # things to keep correct and one of them would rot. # # Run this on the Proxmox host, as root: # # CTID=140 SITE_HOST=chat.example ./deploy/lxc-install.sh # # Everything is overridable: # # CTID next free id the container's id # CT_HOSTNAME lembas hostname inside it # CT_STORAGE local-lvm where the rootfs goes # CT_TEMPLATE debian-12 template, matched against pveam list # CT_DISK 12 GB # CT_CORES 2 # CT_MEMORY 4096 MB # CT_BRIDGE vmbr0 # CT_IP dhcp or 192.168.1.50/24 # CT_GATEWAY (unset) required when CT_IP is static # REPO_URL this checkout's origin # SITE_HOST lembas.local # # **Unprivileged, and that is not a default to change lightly.** Nothing LLeMbas # does needs privilege: agent chats run their commands over SSH on some *other* # machine, which is the whole isolation story. A privileged container would give # up the host's protection to buy nothing. set -euo pipefail CT_HOSTNAME="${CT_HOSTNAME:-lembas}" CT_STORAGE="${CT_STORAGE:-local-lvm}" CT_TEMPLATE="${CT_TEMPLATE:-debian-12}" CT_DISK="${CT_DISK:-12}" CT_CORES="${CT_CORES:-2}" CT_MEMORY="${CT_MEMORY:-4096}" CT_BRIDGE="${CT_BRIDGE:-vmbr0}" CT_IP="${CT_IP:-dhcp}" CT_GATEWAY="${CT_GATEWAY:-}" SITE_HOST="${SITE_HOST:-lembas.local}" BRANCH="${LEMBAS_BRANCH:-main}" HERE="$(dirname "$(readlink -f "$0")")" REPO_URL="${REPO_URL:-$(git -C "$HERE" remote get-url origin 2>/dev/null || true)}" if ! command -v pct >/dev/null; then echo "pct not found. Run this on a Proxmox host." >&2 exit 1 fi if [[ -z "$REPO_URL" ]]; then echo "Could not determine REPO_URL. Set it explicitly." >&2 exit 1 fi CTID="${CTID:-$(pvesh get /cluster/nextid)}" # The template has to be on the host before a container can be made from it. # Matched by prefix rather than pinned to a filename, because the point release # in it moves and a hard-coded name would break on a host that downloaded a # different one. echo "== template ==" template=$(pveam list local 2>/dev/null | awk -v want="$CT_TEMPLATE" '$1 ~ want {print $1}' | head -1) if [[ -z "$template" ]]; then available=$(pveam available --section system | awk -v want="$CT_TEMPLATE" '$2 ~ want {print $2}' | tail -1) if [[ -z "$available" ]]; then echo "No template matching '$CT_TEMPLATE'. Try: pveam available --section system" >&2 exit 1 fi echo " downloading $available" pveam download local "$available" template="local:vztmpl/$available" fi echo " $template" echo "== container $CTID ==" if pct status "$CTID" >/dev/null 2>&1; then echo " $CTID already exists, using it" else net="name=eth0,bridge=$CT_BRIDGE,ip=$CT_IP" [[ -n "$CT_GATEWAY" ]] && net="$net,gw=$CT_GATEWAY" pct create "$CTID" "$template" \ --hostname "$CT_HOSTNAME" \ --cores "$CT_CORES" \ --memory "$CT_MEMORY" \ --rootfs "$CT_STORAGE:$CT_DISK" \ --net0 "$net" \ --unprivileged 1 \ --features nesting=1 \ --onboot 1 echo " created" fi pct start "$CTID" 2>/dev/null || true # `pct exec` returns before the container's own network is up, and the very next # thing this does is apt-get. Waiting on DNS resolving rather than on a fixed # sleep, because a fixed sleep is either too short on a slow host or wasted on a # fast one. echo "== waiting for the network ==" for _ in $(seq 1 30); do pct exec "$CTID" -- getent hosts deb.debian.org >/dev/null 2>&1 && break sleep 2 done echo "== dependencies ==" pct exec "$CTID" -- bash -lc ' set -e export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y -qq --no-install-recommends \ git python3 python3-venv python3-pip nginx openssl sudo ca-certificates ' echo "== checkout ==" pct exec "$CTID" -- bash -lc " set -e rm -rf /tmp/lembas-src git clone --quiet --branch '$BRANCH' '$REPO_URL' /tmp/lembas-src " # The same installer this repository ships, run inside. Everything it decides -- # the service user, the prefix, the unit, the vhost, the self-signed certificate # -- it decides there, so this script has no opinions to keep in step with it. echo "== install ==" pct exec "$CTID" -- bash -lc " set -e SITE_HOST='$SITE_HOST' LEMBAS_BRANCH='$BRANCH' REPO_URL='$REPO_URL' \ bash /tmp/lembas-src/deploy/install.sh " address=$(pct exec "$CTID" -- hostname -I 2>/dev/null | awk '{print $1}') echo echo "LLeMbas is installed in container $CTID." echo " address : ${address:-unknown}" echo " site : https://$SITE_HOST (self-signed; accept the warning)" echo echo "Point '$SITE_HOST' at ${address:-the container} in your DNS or hosts file," echo "then create the first account -- it becomes the administrator."