diff --git a/apps/cashup.sh b/apps/cashup.sh deleted file mode 100755 index 2b79311..0000000 --- a/apps/cashup.sh +++ /dev/null @@ -1,323 +0,0 @@ -#!/usr/bin/env bash -# ┌─────────────────────────────────────────────────────────────────────────┐ -# │ Hotel Manage — Cashup App Installer │ -# │ Provisions a new LXC for the cashup app and wires it into the stack. │ -# │ │ -# │ Run on the Proxmox host shell: │ -# │ bash <(curl -fsSL https://git.pterois.co.uk/hotel-manage-stack/stack-init/raw/branch/main/apps/cashup.sh) -# └─────────────────────────────────────────────────────────────────────────┘ -set -euo pipefail - -YW="\033[33m"; RD="\033[01;31m" -GN="\033[1;92m"; DGN="\033[32m"; BL="\033[36m"; CL="\033[m" -BFR="\\r\\033[K"; CM="${GN}✓${CL}"; CROSS="${RD}✗${CL}" - -msg_info() { printf " ◌ ${YW}%-55s${CL}" "$*"; } -msg_ok() { printf "${BFR} ${CM} ${DGN}%s${CL}\n" "$*"; } -msg_error() { printf "${BFR} ${CROSS} ${RD}%s${CL}\n" "$*"; exit 1; } -msg_warn() { printf "\n ${CROSS} ${YW}%s${CL}\n" "$*"; } -msg_step() { printf "\n${BL}── %s ────────────────────────────────────${CL}\n" "$*"; } - -header_info() { - clear - printf "${BL}" - cat <<'BANNER' - ╔══════════════════════════════════════════════════════════════╗ - ║ HOTEL MANAGE — CASHUP APP INSTALLER ║ - ║ Daily cash reconciliation & Newbook PMS integration ║ - ╚══════════════════════════════════════════════════════════════╝ -BANNER - printf "${CL}\n" -} - -# ── Pre-flight ──────────────────────────────────────────────────────────────── -[[ $EUID -ne 0 ]] && msg_error "Must run as root on the Proxmox VE host" -command -v pct &>/dev/null || msg_error "pct not found — run this on a Proxmox VE host" -command -v whiptail &>/dev/null || { apt-get install -y -qq whiptail &>/dev/null; } -command -v openssl &>/dev/null || { apt-get install -y -qq openssl &>/dev/null; } - -# When run via `bash <(curl ...)` BASH_SOURCE is a pipe -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd || echo /tmp)" - -# ── Load existing stack credentials ────────────────────────────────────────── -CREDS_FILE=/root/hotel-manage-credentials.txt -[[ -f "$CREDS_FILE" ]] || msg_error "Stack credentials not found at ${CREDS_FILE} — run install-stack.sh first" - -# Read credentials — split on first = only so values with spaces (e.g. SITE_NAME=Hotel Number Four) work correctly -while IFS= read -r line || [[ -n "$line" ]]; do - [[ "$line" =~ ^[[:space:]]*# ]] && continue - [[ -z "${line//[[:space:]]/}" ]] && continue - [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]] || continue - export "${BASH_REMATCH[1]}"="${BASH_REMATCH[2]}" -done < "$CREDS_FILE" - -[[ -z "${CENTRAL_AUTH_SECRET:-}" ]] && msg_error "CENTRAL_AUTH_SECRET missing from ${CREDS_FILE}" - -# ── App constants ───────────────────────────────────────────────────────────── -APP_NAME=cashup -APP_PORT=3083 -APP_PATH=/cashup/ -APP_DB_NAME=cashup_db -APP_DB_USER=cashup - -# ── Storage pool ────────────────────────────────────────────────────────────── -STORAGE=$(pvesm status 2>/dev/null | awk '{print $1}' | grep -E "^local-lvm$|^local-zfs$" | head -1) -STORAGE="${STORAGE:-local}" - -# ── Collect config ──────────────────────────────────────────────────────────── -header_info - -LXC_ID=$(whiptail --title "Cashup — LXC Config" \ - --inputbox "LXC ID to allocate (check Proxmox for free IDs):" 8 52 "116" \ - 3>&1 1>&2 2>&3) || exit 0 - -LAST_OCTET=$(whiptail --title "Cashup — LXC Config" \ - --inputbox "Internal IP last octet (10.10.10.X):" 8 52 "${LXC_ID}" \ - 3>&1 1>&2 2>&3) || exit 0 - -FORGEJO_BASE="${FORGEJO_BASE:-https://git.pterois.co.uk/hotel-manage-stack}" - -if whiptail --title "Cashup — Deploy Source" --yesno \ - "Deploy from Forgejo? (recommended)\n\n ${FORGEJO_BASE}/cashup.git\n\nNo = copy from local repo on this host" \ - 10 66; then - USE_FORGEJO=true -else - USE_FORGEJO=false - # When run locally the cashup dir sits two levels up from stack-init/apps/ - LOCAL_SRC="$(cd "${SCRIPT_DIR}/../../cashup" 2>/dev/null && pwd || echo '')" - LOCAL_SRC=$(whiptail --title "Cashup — Local Source" \ - --inputbox "Path to cashup app directory on this host:" 8 70 "${LOCAL_SRC}" \ - 3>&1 1>&2 2>&3) || exit 0 - [[ -d "$LOCAL_SRC" ]] || msg_error "Directory not found: ${LOCAL_SRC}" -fi - -whiptail --title "Cashup — Confirm" --yesno \ -"Create cashup LXC: - - LXC ID: ${LXC_ID} - IP: 10.10.10.${LAST_OCTET} - Port: ${APP_PORT} - Path: ${APP_PATH} - Database: ${APP_DB_NAME} - Source: $( [[ "$USE_FORGEJO" == "true" ]] && echo "Forgejo" || echo "$LOCAL_SRC" ) - -Proceed?" 18 52 || exit 0 - -# ── Generate DB password ────────────────────────────────────────────────────── -# Reuse if already in creds file (idempotent re-runs) -if grep -q "^CASHUP_DB_PASS=" "$CREDS_FILE" 2>/dev/null; then - APP_DB_PASS=$(grep "^CASHUP_DB_PASS=" "$CREDS_FILE" | cut -d= -f2) - msg_ok "Reusing existing cashup DB password" -else - APP_DB_PASS=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24) - cat >> "$CREDS_FILE" </dev/null | awk '/ubuntu-22\.04-standard/{print $1; exit}') - if [[ -z "$tmpl" ]]; then - msg_info "Downloading Ubuntu 22.04 LXC template" - pveam update &>/dev/null || true - local avail - avail=$(pveam available --section system 2>/dev/null \ - | awk '/ubuntu-22\.04-standard/{print $2}' | sort -V | tail -1) - [[ -n "$avail" ]] || msg_error "No ubuntu-22.04 template available via pveam" - pveam download local "$avail" &>/dev/null - msg_ok "Template downloaded" - tmpl="$avail" - fi - echo "local:vztmpl/${tmpl##*/}" -} - -# ── Helper: push file from stdin ────────────────────────────────────────────── -push_file() { - local id=$1 dest=$2; shift 2 - local tmp; tmp=$(mktemp /tmp/hotel-manage-push-XXXX) - cat > "$tmp" - pct push "$id" "$tmp" "$dest" 2>/dev/null - rm -f "$tmp" -} - -# ── PHASE 1 — Create LXC ────────────────────────────────────────────────────── -msg_step "1/5 LXC ${LXC_ID} (10.10.10.${LAST_OCTET})" - -TMPL=$(get_template) -POOL="hotel-manage" -POOL_OPT="" -pvesh get "/pools/${POOL}" &>/dev/null && POOL_OPT="--pool ${POOL}" - -if pct status "$LXC_ID" &>/dev/null; then - msg_warn "LXC ${LXC_ID} already exists — skipping creation" - pct status "$LXC_ID" | grep -q running || pct start "$LXC_ID" -else - msg_info "Creating LXC ${LXC_ID}" - pct create "$LXC_ID" "$TMPL" \ - --hostname "hotel-manage-${APP_NAME}" \ - --memory 1024 \ - --cores 1 \ - --rootfs "${STORAGE}:8" \ - --net0 "name=eth0,bridge=vmbr1,ip=10.10.10.${LAST_OCTET}/24,gw=10.10.10.1" \ - --features nesting=1 \ - --unprivileged 0 \ - --onboot 1 \ - --tags "${POOL}" \ - ${POOL_OPT} \ - --start 1 &>/dev/null - sleep 5 - msg_ok "LXC ${LXC_ID} created" -fi - -# ── PHASE 2 — Install Docker ────────────────────────────────────────────────── -msg_step "2/5 Docker" -msg_info "Installing Docker in LXC ${LXC_ID}" -pct exec "$LXC_ID" -- bash -s &>/dev/null <<'DOCKER' -export DEBIAN_FRONTEND=noninteractive -if [ -e /usr/sbin/apparmor_parser ]; then - mv -f /usr/sbin/apparmor_parser /usr/sbin/apparmor_parser.disabled -fi -if command -v docker >/dev/null 2>&1; then - systemctl enable --now docker ssh 2>/dev/null; exit 0 -fi -apt-get update -qq -apt-get install -y -qq ca-certificates curl gnupg git openssh-server -install -m 0755 -d /etc/apt/keyrings -rm -f /etc/apt/keyrings/docker.gpg -curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ - | gpg --batch --yes --dearmor -o /etc/apt/keyrings/docker.gpg -echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] \ -https://download.docker.com/linux/ubuntu jammy stable" \ - > /etc/apt/sources.list.d/docker.list -apt-get update -qq -apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-compose-plugin -systemctl enable --now docker ssh -DOCKER -msg_ok "Docker installed" - -# Install management SSH key so the updater can pull later -if [[ -f /root/.ssh/hotel-manage_deploy.pub ]]; then - MGMT_PUBKEY=$(cat /root/.ssh/hotel-manage_deploy.pub) - pct exec "$LXC_ID" -- bash -c " - mkdir -p /root/.ssh && chmod 700 /root/.ssh - grep -qF '${MGMT_PUBKEY}' /root/.ssh/authorized_keys 2>/dev/null || \ - echo '${MGMT_PUBKEY}' >> /root/.ssh/authorized_keys - chmod 600 /root/.ssh/authorized_keys - " &>/dev/null - msg_ok "Management SSH key installed" -fi - -# ── PHASE 3 — Create database ───────────────────────────────────────────────── -msg_step "3/5 Database" -msg_info "Creating ${APP_DB_NAME} on postgres LXC 100" -pct exec 100 -- bash -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 hotel-manage-postgres psql -U postgres -c \ - \"CREATE DATABASE ${APP_DB_NAME} OWNER ${APP_DB_USER};\" 2>/dev/null || true - 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} ready" - -# ── PHASE 4 — Deploy app ────────────────────────────────────────────────────── -msg_step "4/5 Deploy" -msg_info "Deploying cashup to LXC ${LXC_ID}" - -if [[ "$USE_FORGEJO" == "true" ]]; then - CLONE_URL="${FORGEJO_BASE}/cashup.git" - if [[ -n "${FORGEJO_TOKEN:-}" ]]; then - CLONE_URL="${CLONE_URL/https:\/\//https://oauth2:${FORGEJO_TOKEN}@}" - fi - pct exec "$LXC_ID" -- bash -c " - if [ -d /opt/cashup/.git ]; then - cd /opt/cashup && git pull -q - else - git clone -q '${CLONE_URL}' /opt/cashup - fi - " &>/dev/null -else - tmp=$(mktemp /tmp/hotel-manage-cashup-XXXX.tar.gz) - tar czf "$tmp" -C "$(dirname "$LOCAL_SRC")" "$(basename "$LOCAL_SRC")" 2>/dev/null - pct push "$LXC_ID" "$tmp" /tmp/hotel-manage-cashup.tar.gz 2>/dev/null - pct exec "$LXC_ID" -- bash -c " - mkdir -p /opt - tar xzf /tmp/hotel-manage-cashup.tar.gz -C /opt - mv /opt/$(basename "$LOCAL_SRC") /opt/cashup 2>/dev/null || true - rm -f /tmp/hotel-manage-cashup.tar.gz - " &>/dev/null - rm -f "$tmp" -fi - -# Write .env -push_file "$LXC_ID" /opt/cashup/.env </dev/null -msg_ok "Containers started" - -msg_info "Waiting for cashup to be healthy" -ATTEMPTS=0 -until pct exec "$LXC_ID" -- curl -sf --max-time 2 "http://localhost:${APP_PORT}/cashup/api/health" &>/dev/null; do - ATTEMPTS=$((ATTEMPTS + 1)) - if [[ $ATTEMPTS -ge 40 ]]; then - msg_warn "Health check timed out — app may still be starting, check: pct enter ${LXC_ID} && docker compose logs" - break - fi - sleep 3 -done -[[ $ATTEMPTS -lt 40 ]] && msg_ok "Cashup healthy at 10.10.10.${LAST_OCTET}:${APP_PORT}" - -# ── Summary ─────────────────────────────────────────────────────────────────── -printf "\n${GN}" -cat <<'BANNER' - ╔══════════════════════════════════════════════════════════════╗ - ║ Cashup — Deployed ║ - ╚══════════════════════════════════════════════════════════════╝ -BANNER -printf "${CL}" - -cat <} - Events: Push - - Credentials saved to: ${CREDS_FILE} - -SUMMARY diff --git a/install-stack.sh b/install-stack.sh index d11c3b0..b46ac0b 100755 --- a/install-stack.sh +++ b/install-stack.sh @@ -250,6 +250,7 @@ gen_secrets() { AUTH_DB_PASS=$(sed -n 's/^AUTH_DB_PASS=//p' "$CREDS_FILE" | head -1) NOTICES_DB_PASS=$(sed -n 's/^NOTICES_DB_PASS=//p' "$CREDS_FILE" | head -1) SETTINGS_DB_PASS=$(sed -n 's/^SETTINGS_DB_PASS=//p' "$CREDS_FILE" | head -1) + CASHUP_DB_PASS=$(sed -n 's/^CASHUP_DB_PASS=//p' "$CREDS_FILE" | head -1) CENTRAL_AUTH_SECRET=$(sed -n 's/^CENTRAL_AUTH_SECRET=//p' "$CREDS_FILE" | head -1) SETTINGS_SECRET=$(sed -n 's/^SETTINGS_SECRET=//p' "$CREDS_FILE" | head -1) WEBHOOK_SECRET=$(sed -n 's/^WEBHOOK_SECRET=//p' "$CREDS_FILE" | head -1) @@ -263,6 +264,7 @@ gen_secrets() { AUTH_DB_PASS=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24) NOTICES_DB_PASS=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24) SETTINGS_DB_PASS=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24) + CASHUP_DB_PASS=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24) CENTRAL_AUTH_SECRET=$(openssl rand -hex 32) SETTINGS_SECRET=$(openssl rand -hex 32) WEBHOOK_SECRET=$(openssl rand -hex 24) @@ -282,6 +284,7 @@ PG_SUPERPASS=${PG_SUPERPASS} AUTH_DB_PASS=${AUTH_DB_PASS} NOTICES_DB_PASS=${NOTICES_DB_PASS} SETTINGS_DB_PASS=${SETTINGS_DB_PASS} +CASHUP_DB_PASS=${CASHUP_DB_PASS} CENTRAL_AUTH_SECRET=${CENTRAL_AUTH_SECRET} SETTINGS_SECRET=${SETTINGS_SECRET} WEBHOOK_SECRET=${WEBHOOK_SECRET} @@ -574,6 +577,14 @@ CREATE USER settings WITH PASSWORD '${SETTINGS_DB_PASS}'; CREATE DATABASE settings_db OWNER settings; \c settings_db GRANT ALL ON SCHEMA public TO settings; +EOF + + # Init SQL — cashup DB + push_file 100 /opt/postgres/init/04-cashup.sql </dev/null @@ -813,6 +824,53 @@ EOF || msg_warn "Settings may need extra time — check LXC 116" } +# ════════════════════════════════════════════════════════════════════════════ +# PHASE 8 — CASHUP LXC 117 +# ════════════════════════════════════════════════════════════════════════════ +deploy_cashup() { + msg_step "8/8 Cashup (LXC 117 · 10.10.10.117)" + + msg_info "Creating LXC 117" + create_lxc 117 "10.10.10.117" "cashup" 1024 1 + msg_ok "LXC 117 created" + + msg_info "Installing Docker" + install_docker 117 + install_mgmt_key 117 + msg_ok "Docker + SSH ready" + + # Create DB — idempotent via || true, works on fresh installs and re-runs + msg_info "Creating cashup database" + pct exec 100 -- bash -c " + docker exec hotel-manage-postgres psql -U postgres -c \ + \"CREATE USER cashup WITH PASSWORD '${CASHUP_DB_PASS}';\" 2>/dev/null || true + docker exec hotel-manage-postgres psql -U postgres -c \ + \"CREATE DATABASE cashup_db OWNER cashup;\" 2>/dev/null || true + docker exec hotel-manage-postgres psql -U postgres -d cashup_db -c \ + \"GRANT ALL ON SCHEMA public TO cashup;\" 2>/dev/null || true + " &>/dev/null + msg_ok "Database cashup_db ready" + + msg_info "Deploying cashup" + deploy_service 117 "cashup" "${REPO_ROOT}/cashup" /opt/cashup + + push_file 117 /opt/cashup/.env </dev/null + + msg_info "Waiting for cashup" + wait_healthy 117 "http://localhost:3083/cashup/api/health" \ + && msg_ok "Cashup running at 10.10.10.117:3083" \ + || msg_warn "Cashup may need extra time — check LXC 117" +} + # ════════════════════════════════════════════════════════════════════════════ # NPM PROXY HOSTS (via API) # ════════════════════════════════════════════════════════════════════════════ @@ -912,6 +970,7 @@ SUMMARY 9000 Forgejo webhooks 112 hotel-manage-noticeboard 10.10.10.112 3080 /notices/ 116 hotel-manage-settings 10.10.10.116 3080 /settings/api/ + 117 hotel-manage-cashup 10.10.10.117 3083 /cashup/ ── Credentials ─────────────────────────────────────────────── Admin login: ${ADMIN_EMAIL} @@ -951,7 +1010,7 @@ SUMMARY # Sources existing credentials and redeploys just the named service. if [[ "${1:-}" == "--only" ]]; then ONLY="${2:-}" - VALID="postgres auth portal npm management noticeboard settings" + VALID="postgres auth portal npm management noticeboard settings cashup" [[ -z "$ONLY" ]] && msg_error "Usage: install-stack.sh --only (one of: ${VALID})" grep -qw "$ONLY" <<< "$VALID" || msg_error "Unknown service '${ONLY}'. Valid: ${VALID}" CREDS_FILE=/root/hotel-manage-credentials.txt @@ -987,6 +1046,7 @@ if [[ "${1:-}" == "--only" ]]; then management) deploy_management ;; noticeboard) deploy_noticeboard ;; settings) deploy_settings ;; + cashup) deploy_cashup ;; esac exit 0 fi @@ -1004,5 +1064,6 @@ deploy_npm deploy_management deploy_noticeboard deploy_settings +deploy_cashup configure_npm_proxy_hosts print_summary