diff --git a/README.md b/README.md index 1ca8bc1..6df4d51 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Proxmox Helpers — HNF Stack Installer +# Hotel Manage — Proxmox Stack Installer tteck-style helper scripts that provision the stack onto a fresh Proxmox host — no local copy of the repo required on the host. @@ -64,7 +64,7 @@ target. It then provisions the six foundation LXCs (postgres, auth, portal, npm, management, noticeboard), health-checks each, and configures the NPM proxy routes. (Leave the Forgejo token blank — the repos are public.) -Secrets are written to `/root/hnf-credentials.txt` (chmod 600) — copy this +Secrets are written to `/root/hotel-manage-credentials.txt` (chmod 600) — copy this offsite. ## Add an app later @@ -75,7 +75,7 @@ On the Proxmox host (again, because it creates an LXC): bash <(curl -fsSL https://git.pterois.co.uk/hotel-manage-stack/stack-init/raw/branch/main/add-app.sh) ``` -Reads `/root/hnf-credentials.txt` for the shared secret and office IP, +Reads `/root/hotel-manage-credentials.txt` for the shared secret and office IP, provisions a new LXC, optionally creates a dedicated postgres DB, clones the app repo, and prints the NPM route / Uptime Kuma / webhook / deploy-map lines to finish wiring it in. After that, ongoing updates flow through the management @@ -88,6 +88,18 @@ Same command on the new host. Only the wizard answers differ per site: The internal `10.10.10.0/24` network and all service IPs are identical everywhere, so the repos are reused unchanged. +## LXC organisation + +Every LXC is created with: +- a `hotel-manage-` **hostname** (e.g. `hotel-manage-postgres`, + `hotel-manage-auth`, `hotel-manage-noticeboard`), +- membership of a Proxmox **resource pool** `hotel-manage`, and +- a **tag** `hotel-manage`, + +so the whole stack groups and filters together in the Proxmox UI and is easy to +tell apart from any other containers on the host. `add-app.sh` puts new app LXCs +in the same pool/tag. + ## Notes - All repos are public, so no Forgejo token is needed to install or update. diff --git a/add-app.sh b/add-app.sh index 464e60a..f8595b9 100755 --- a/add-app.sh +++ b/add-app.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # ┌─────────────────────────────────────────────────────────────────────────┐ -# │ HNF Manage — Add App LXC │ +# │ Hotel Manage — Add App LXC │ # │ Provisions a single app container and wires it into the stack. │ # │ │ # │ Usage: bash add-app.sh (or curl-bootstrap, see install/README.md) │ @@ -23,7 +23,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" # Load secrets from credentials file if present -CREDS_FILE=/root/hnf-credentials.txt +CREDS_FILE=/root/hotel-manage-credentials.txt if [[ -f "$CREDS_FILE" ]]; then # shellcheck disable=SC1090 set -a; source <(grep -v '^#' "$CREDS_FILE" | grep '='); set +a @@ -104,12 +104,18 @@ fi # ── Get mgmt public key ─────────────────────────────────────────────────────── MGMT_PUBKEY="" -[[ -f /root/.ssh/hnf_management.pub ]] && MGMT_PUBKEY=$(cat /root/.ssh/hnf_management.pub) +[[ -f /root/.ssh/hotel-manage_deploy.pub ]] && MGMT_PUBKEY=$(cat /root/.ssh/hotel-manage_deploy.pub) + +# ── Resource pool (created by install-stack.sh; reuse if present) ────────────── +POOL="hotel-manage" +POOL_OPT="" +pvesh get "/pools/${POOL}" &>/dev/null || pvesh create /pools --poolid "${POOL}" &>/dev/null || true +pvesh get "/pools/${POOL}" &>/dev/null && POOL_OPT="--pool ${POOL}" # ── Create LXC ──────────────────────────────────────────────────────────────── -msg_info "Creating LXC ${LXC_ID} (hnf-${APP_NAME} at 10.10.10.${LAST_OCTET})" +msg_info "Creating LXC ${LXC_ID} (hotel-manage-${APP_NAME} at 10.10.10.${LAST_OCTET})" pct create "$LXC_ID" "$TEMPLATE" \ - --hostname "hnf-${APP_NAME}" \ + --hostname "hotel-manage-${APP_NAME}" \ --memory 512 \ --cores 1 \ --rootfs "${STORAGE}:8" \ @@ -117,6 +123,8 @@ pct create "$LXC_ID" "$TEMPLATE" \ --features nesting=1 \ --unprivileged 0 \ --onboot 1 \ + --tags "${POOL}" \ + ${POOL_OPT} \ --start 1 &>/dev/null sleep 5 msg_ok "LXC ${LXC_ID} created" @@ -150,17 +158,17 @@ if [[ "$APP_DB" == "yes" ]]; then msg_info "Creating database ${APP_DB_NAME}" # Run SQL via postgres LXC pct exec 100 -- bash -c " - docker exec hnf-postgres psql -U postgres -c \ + docker exec hotel-manage-postgres psql -U postgres -c \ \"CREATE USER ${APP_DB_USER} WITH PASSWORD '${APP_DB_PASS}';\" 2>/dev/null || true - docker exec hnf-postgres psql -U postgres -c \ + docker exec hotel-manage-postgres psql -U postgres -c \ \"CREATE DATABASE ${APP_DB_NAME} OWNER ${APP_DB_USER};\" 2>/dev/null || true - docker exec hnf-postgres psql -U postgres -d ${APP_DB_NAME} -c \ + docker exec hotel-manage-postgres psql -U postgres -d ${APP_DB_NAME} -c \ \"GRANT ALL ON SCHEMA public TO ${APP_DB_USER};\" 2>/dev/null || true " &>/dev/null msg_ok "Database ${APP_DB_NAME} created" # Append DB creds to credentials file - cat >> /root/hnf-credentials.txt <> /root/hotel-manage-credentials.txt </dev/null else - tmp=$(mktemp /tmp/hnf-app-XXXX.tar.gz) + tmp=$(mktemp /tmp/hotel-manage-app-XXXX.tar.gz) tar czf "$tmp" -C "$(dirname "$LOCAL_SRC")" "$(basename "$LOCAL_SRC")" 2>/dev/null - pct push "$LXC_ID" "$tmp" /tmp/hnf-app.tar.gz 2>/dev/null + pct push "$LXC_ID" "$tmp" /tmp/hotel-manage-app.tar.gz 2>/dev/null pct exec "$LXC_ID" -- bash -c " - mkdir -p /opt && tar xzf /tmp/hnf-app.tar.gz -C /opt + mkdir -p /opt && tar xzf /tmp/hotel-manage-app.tar.gz -C /opt mv /opt/$(basename "$LOCAL_SRC") /opt/${APP_NAME} 2>/dev/null || true - rm -f /tmp/hnf-app.tar.gz + rm -f /tmp/hotel-manage-app.tar.gz " &>/dev/null rm -f "$tmp" fi @@ -192,7 +200,7 @@ msg_ok "App files deployed to /opt/${APP_NAME}" # ── Write .env ──────────────────────────────────────────────────────────────── msg_info "Writing .env" -ENV_TMP=$(mktemp /tmp/hnf-env-XXXX) +ENV_TMP=$(mktemp /tmp/hotel-manage-env-XXXX) { echo "NODE_ENV=production" echo "APP_SLUG=${APP_NAME}" @@ -209,7 +217,7 @@ msg_ok ".env written" # ── Register app in auth service ────────────────────────────────────────────── msg_warn "Remember to register '${APP_NAME}' in the auth service DB:" -printf " docker exec hnf-auth-1 node -e \"\n" +printf " docker exec hotel-manage-auth-1 node -e \"\n" printf " const db = require('./src/db.js');\n" printf " db.query(\\\"INSERT INTO apps (slug,name,base_path) VALUES ('%s','%s','%s') ON CONFLICT DO NOTHING\\\");\n" \ "$APP_NAME" "$APP_NAME" "$APP_PATH" @@ -233,7 +241,7 @@ cat <&1 1>&2 2>&3) || exit 0 - DOMAIN=$(whiptail --title "HNF Stack — Site Config" \ + DOMAIN=$(whiptail --title "Hotel Manage — Site Config" \ --inputbox "Public domain (e.g. manage.hotelnumberfour.com):" 8 64 "manage.hotelnumberfour.com" \ 3>&1 1>&2 2>&3) || exit 0 - NPM_LAN_IP=$(whiptail --title "HNF Stack — Site Config" \ + NPM_LAN_IP=$(whiptail --title "Hotel Manage — Site Config" \ --inputbox "NPM LXC static LAN IP (from your hotel LAN pool):" 8 64 "10.4.0.50" \ 3>&1 1>&2 2>&3) || exit 0 - LAN_GW=$(whiptail --title "HNF Stack — Site Config" \ + LAN_GW=$(whiptail --title "Hotel Manage — Site Config" \ --inputbox "LAN gateway IP:" 8 52 "10.4.0.1" 3>&1 1>&2 2>&3) || exit 0 - OFFICE_IP=$(whiptail --title "HNF Stack — Site Config" \ + OFFICE_IP=$(whiptail --title "Hotel Manage — Site Config" \ --inputbox \ "Office IP / CIDR / DDNS hostname for offsite restriction. Examples: 203.0.113.5 10.4.0.0/22 hotel.dyndns.org Type 'disabled' to allow access from anywhere:" \ 11 64 "disabled" 3>&1 1>&2 2>&3) || exit 0 - ADMIN_EMAIL=$(whiptail --title "HNF Stack — Admin Account" \ + ADMIN_EMAIL=$(whiptail --title "Hotel Manage — Admin Account" \ --inputbox "Admin user email:" 8 52 "" 3>&1 1>&2 2>&3) || exit 0 - ADMIN_PASS=$(whiptail --title "HNF Stack — Admin Account" \ + ADMIN_PASS=$(whiptail --title "Hotel Manage — Admin Account" \ --passwordbox "Admin user password:" 8 52 3>&1 1>&2 2>&3) || exit 0 # Deploy source — per-service Forgejo repos (default) or a local copy on this host - FORGEJO_BASE=$(whiptail --title "HNF Stack — Forgejo" \ + FORGEJO_BASE=$(whiptail --title "Hotel Manage — Forgejo" \ --inputbox \ "Forgejo org/base URL hosting the per-service repos. Each service is cloned from /.git @@ -135,7 +135,7 @@ Example: https://git.pterois.co.uk/hotel-manage-stack" \ 13 66 "https://git.pterois.co.uk/hotel-manage-stack" 3>&1 1>&2 2>&3) || exit 0 FORGEJO_BASE="${FORGEJO_BASE%/}" - FORGEJO_TOKEN=$(whiptail --title "HNF Stack — Forgejo Token" \ + FORGEJO_TOKEN=$(whiptail --title "Hotel Manage — Forgejo Token" \ --passwordbox \ "The repos are PUBLIC — leave this blank. @@ -144,7 +144,7 @@ from Forgejo → Settings → Applications, scope read:repository, which gets embedded in each LXC's git remote for the updater.)" \ 12 66 3>&1 1>&2 2>&3) || exit 0 - if whiptail --title "HNF Stack — Deploy Source" --yesno \ + if whiptail --title "Hotel Manage — Deploy Source" --yesno \ "Deploy services from Forgejo? (recommended)\n\nNo = copy from a local repo at ${REPO_ROOT}\n(only works if you already copied the repo to this host)" \ 11 62; then USE_FORGEJO=true @@ -152,24 +152,24 @@ which gets embedded in each LXC's git remote for the updater.)" \ USE_FORGEJO=false fi - BACKUP_REMOTE=$(whiptail --title "HNF Stack — Backup" \ + BACKUP_REMOTE=$(whiptail --title "Hotel Manage — Backup" \ --inputbox \ "Backup rsync target (leave blank to skip backup config). -Example: backup@192.168.1.10:/backups/hnf" \ +Example: backup@192.168.1.10:/backups/hotel-manage" \ 10 64 "" 3>&1 1>&2 2>&3) || exit 0 # Confirm LXC allocation - whiptail --title "HNF Stack — Confirm" --yesno \ + whiptail --title "Hotel Manage — Confirm" --yesno \ "LXCs to be created (storage: ${STORAGE}): ID Hostname IP ────────────────────────────────────────── - 100 hnf-postgres 10.10.10.100 - 101 hnf-auth 10.10.10.101 - 102 hnf-portal 10.10.10.102 - 103 hnf-npm 10.10.10.103 / ${NPM_LAN_IP} (dual-homed) - 105 hnf-management 10.10.10.105 - 112 hnf-noticeboard 10.10.10.112 + 100 hotel-manage-postgres 10.10.10.100 + 101 hotel-manage-auth 10.10.10.101 + 102 hotel-manage-portal 10.10.10.102 + 103 hotel-manage-npm 10.10.10.103 / ${NPM_LAN_IP} (dual-homed) + 105 hotel-manage-management 10.10.10.105 + 112 hotel-manage-noticeboard 10.10.10.112 Domain: ${DOMAIN} Admin: ${ADMIN_EMAIL} @@ -187,8 +187,8 @@ gen_secrets() { WEBHOOK_SECRET=$(openssl rand -hex 24) NPM_ADMIN_PASS=$(openssl rand -base64 12 | tr -dc 'a-zA-Z0-9' | head -c 12) - cat > /root/hnf-credentials.txt < /root/hotel-manage-credentials.txt </dev/null - msg_ok "SSH keypair generated → /root/.ssh/hnf_management" + ssh-keygen -t ed25519 -f /root/.ssh/hotel-manage_deploy -N "" -C "hotel-manage-management-deploy" &>/dev/null + msg_ok "SSH keypair generated → /root/.ssh/hotel-manage_deploy" else - msg_ok "Using existing SSH keypair at /root/.ssh/hnf_management" + msg_ok "Using existing SSH keypair at /root/.ssh/hotel-manage_deploy" fi - MGMT_PUBKEY=$(cat /root/.ssh/hnf_management.pub) - echo "${MGMT_PUBKEY}" >> /root/hnf-credentials.txt + MGMT_PUBKEY=$(cat /root/.ssh/hotel-manage_deploy.pub) + echo "${MGMT_PUBKEY}" >> /root/hotel-manage-credentials.txt } # ── LXC lifecycle helpers ───────────────────────────────────────────────────── @@ -244,16 +244,30 @@ lxc_running() { pct status "$1" 2>/dev/null | grep -q "running" } +# Group the stack in a Proxmox resource pool + tag for tidy UI organisation. +POOL="hotel-manage" +POOL_OPT="" +ensure_pool() { + if ! pvesh get "/pools/${POOL}" &>/dev/null; then + msg_info "Creating Proxmox resource pool '${POOL}'" + pvesh create /pools --poolid "${POOL}" --comment "Hotel Manage stack" &>/dev/null || true + pvesh get "/pools/${POOL}" &>/dev/null && msg_ok "Pool '${POOL}' ready" \ + || msg_warn "Could not create pool '${POOL}' — continuing without it" + fi + # Only pass --pool if it actually exists (else pct create would fail) + pvesh get "/pools/${POOL}" &>/dev/null && POOL_OPT="--pool ${POOL}" +} + create_lxc() { local id=$1 ip=$2 name=$3 mem=${4:-512} cores=${5:-1} if lxc_exists "$id"; then - msg_warn "LXC $id (hnf-${name}) already exists — skipping creation" + msg_warn "LXC $id (hotel-manage-${name}) already exists — skipping creation" lxc_running "$id" || pct start "$id" return fi local tmpl; tmpl=$(get_template) pct create "$id" "$tmpl" \ - --hostname "hnf-${name}" \ + --hostname "hotel-manage-${name}" \ --memory "$mem" \ --cores "$cores" \ --rootfs "${STORAGE}:8" \ @@ -261,6 +275,8 @@ create_lxc() { --features nesting=1 \ --unprivileged 0 \ --onboot 1 \ + --tags "${POOL}" \ + ${POOL_OPT} \ --start 1 &>/dev/null sleep 5 # let systemd start } @@ -268,13 +284,13 @@ create_lxc() { create_npm_lxc() { local id=103 if lxc_exists "$id"; then - msg_warn "LXC $id (hnf-npm) already exists — skipping creation" + msg_warn "LXC $id (hotel-manage-npm) already exists — skipping creation" lxc_running "$id" || pct start "$id" return fi local tmpl; tmpl=$(get_template) pct create "$id" "$tmpl" \ - --hostname "hnf-npm" \ + --hostname "hotel-manage-npm" \ --memory 512 \ --cores 1 \ --rootfs "${STORAGE}:8" \ @@ -283,6 +299,8 @@ create_npm_lxc() { --features nesting=1 \ --unprivileged 0 \ --onboot 1 \ + --tags "${POOL}" \ + ${POOL_OPT} \ --start 1 &>/dev/null sleep 5 } @@ -320,7 +338,7 @@ install_mgmt_key() { push_file() { # Write content to a temp file, push into LXC, remove temp local id=$1 dest=$2; shift 2 - local tmp; tmp=$(mktemp /tmp/hnf-push-XXXX) + local tmp; tmp=$(mktemp /tmp/hotel-manage-push-XXXX) cat > "$tmp" # reads stdin pct push "$id" "$tmp" "$dest" 2>/dev/null rm -f "$tmp" @@ -329,14 +347,14 @@ push_file() { push_dir() { # tar local dir → push tarball → extract in LXC at parent of dest local id=$1 src=$2 dest=$3 - local tmp; tmp=$(mktemp /tmp/hnf-dir-XXXX.tar.gz) + local tmp; tmp=$(mktemp /tmp/hotel-manage-dir-XXXX.tar.gz) tar czf "$tmp" -C "$(dirname "$src")" "$(basename "$src")" 2>/dev/null - pct push "$id" "$tmp" /tmp/hnf-deploy.tar.gz 2>/dev/null + pct push "$id" "$tmp" /tmp/hotel-manage-deploy.tar.gz 2>/dev/null pct exec "$id" -- bash -c " mkdir -p '$(dirname "$dest")' - tar xzf /tmp/hnf-deploy.tar.gz -C '$(dirname "$dest")' + tar xzf /tmp/hotel-manage-deploy.tar.gz -C '$(dirname "$dest")' mv '$(dirname "$dest")/$(basename "$src")' '${dest}' 2>/dev/null || true - rm -f /tmp/hnf-deploy.tar.gz + rm -f /tmp/hotel-manage-deploy.tar.gz " &>/dev/null rm -f "$tmp" } @@ -380,7 +398,7 @@ wait_healthy() { wait_pg() { local max=30 i=0 while ! pct exec 100 -- bash -c \ - "docker exec hnf-postgres pg_isready -U postgres" &>/dev/null; do + "docker exec hotel-manage-postgres pg_isready -U postgres" &>/dev/null; do sleep 3; ((i++)) [[ $i -ge $max ]] && { msg_warn "Postgres not ready after 90s"; return 1; } done @@ -407,7 +425,7 @@ deploy_postgres() { push_file 100 /opt/postgres/docker-compose.yml <<'EOF' services: postgres: - container_name: hnf-postgres + container_name: hotel-manage-postgres image: postgres:16-alpine environment: - POSTGRES_USER=postgres @@ -548,7 +566,7 @@ deploy_npm() { push_file 103 /opt/npm/docker-compose.yml <<'EOF' services: npm: - container_name: hnf-npm + container_name: hotel-manage-npm image: jc21/nginx-proxy-manager:latest ports: - "80:80" @@ -591,8 +609,8 @@ deploy_management() { # Copy the management SSH private key into management container pct exec 105 -- mkdir -p /root/.ssh - pct push 105 /root/.ssh/hnf_management /root/.ssh/hnf_management &>/dev/null - pct exec 105 -- chmod 600 /root/.ssh/hnf_management + pct push 105 /root/.ssh/hotel-manage_deploy /root/.ssh/hotel-manage_deploy &>/dev/null + pct exec 105 -- chmod 600 /root/.ssh/hotel-manage_deploy msg_info "Deploying management stack" deploy_service 105 "management" "${REPO_ROOT}/management" /opt/management @@ -742,7 +760,7 @@ print_summary() { printf "\n${GN}" cat <