From e8dea786d5e4f4f6ef0c2468e68d5ac28535d926 Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Mon, 27 Jul 2026 16:56:53 +0000 Subject: [PATCH] =?UTF-8?q?Add=20deploy=5Fcalendar()=20=E2=80=94=20LXC=201?= =?UTF-8?q?26,=20calendar=5Fdb,=20calendar=20app?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit calendar was provisioned ad hoc via add-app.sh and never got the standard installer wiring other apps have (deploy_() + --only dispatch), so it couldn't be redeployed/rebuilt the normal way. --- install-stack.sh | 188 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 187 insertions(+), 1 deletion(-) diff --git a/install-stack.sh b/install-stack.sh index 9372de3..f1e9432 100755 --- a/install-stack.sh +++ b/install-stack.sh @@ -1713,6 +1713,106 @@ ${build_out}" npm_add_location "/utilities/" "10.10.10.127" 3080 } +# ════════════════════════════════════════════════════════════════════════════ +# MQTT BROKER LXC 104 — shared infra (dual-homed), no app/frontend/auth entry. +# Same tier as postgres (100): a container other apps depend on, not a stack +# "app" itself. hvac needs it for Phase 1 TRVs; a future utility-meter app +# would reuse it too — see the hvac plan doc's 'MQTT is shared infrastructure' +# decision for why this isn't a sidecar inside hvac's own compose. +# ════════════════════════════════════════════════════════════════════════════ +deploy_mqtt_broker() { + msg_step "MQTT broker (LXC 104 · 10.10.10.104)" + + ensure_admin_vlan_config + ensure_admin_vlan_ip MQTT_ADMIN_VLAN_IP \ + "Static IP for the MQTT broker's admin-VLAN interface (net1 — this is what physical devices like TRVs publish into)" \ + "10.4.0.61" + + if [[ -z "${MQTT_ADMIN_PASS:-}" ]]; then + MQTT_ADMIN_USER="hotel-manage-admin" + MQTT_ADMIN_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c 18) + { echo "MQTT_ADMIN_USER=${MQTT_ADMIN_USER}"; echo "MQTT_ADMIN_PASS=${MQTT_ADMIN_PASS}"; } >> "$CREDS_FILE" + msg_ok "Generated MQTT broker admin credential → ${CREDS_FILE}" + fi + MQTT_ADMIN_USER="${MQTT_ADMIN_USER:-hotel-manage-admin}" + + msg_info "Creating LXC 104 (dual-homed)" + create_lxc 104 "10.10.10.104" "mqtt-broker" 512 1 "${MQTT_ADMIN_VLAN_IP}" + msg_ok "LXC 104 created (10.10.10.104 + ${MQTT_ADMIN_VLAN_IP} on ${ADMIN_VLAN_BRIDGE}${ADMIN_VLAN_TAG:+ tag ${ADMIN_VLAN_TAG}})" + + msg_info "Installing Docker" + install_docker 104 + install_mgmt_key 104 + msg_ok "Docker + SSH ready" + + msg_info "Deploying Mosquitto" + pct exec 104 -- mkdir -p /opt/mqtt-broker/config /opt/mqtt-broker/data /opt/mqtt-broker/log + + # Dynamic-security plugin, not static password/ACL files — lets per-consumer + # clients be created/revoked live later (via mosquitto_ctrl or, eventually, + # the settings app's 'MQTT Broker Clients' page) without restarting the broker. + push_file 104 /opt/mqtt-broker/config/mosquitto.conf <<'EOF' +listener 1883 0.0.0.0 +allow_anonymous false +persistence true +persistence_location /mosquitto/data/ +log_dest file /mosquitto/log/mosquitto.log +log_dest stdout + +plugin /usr/lib/mosquitto_dynamic_security.so +plugin_opts { + config_file /mosquitto/data/dynamic-security.json +} +EOF + + push_file 104 /opt/mqtt-broker/docker-compose.yml <<'EOF' +services: + mosquitto: + container_name: hotel-manage-mqtt-broker + image: eclipse-mosquitto:2 + security_opt: + - apparmor=unconfined + volumes: + - ./config:/mosquitto/config:ro + - ./data:/mosquitto/data + - ./log:/mosquitto/log + ports: + - "1883:1883" + restart: unless-stopped +EOF + + # One-time dynamic-security bootstrap — seeds a single admin identity before + # the broker ever starts against this file. Resume-safe: skipped if the file + # already exists, so re-running this deploy never resets an existing broker's + # already-provisioned clients. + if ! pct exec 104 -- test -f /opt/mqtt-broker/data/dynamic-security.json 2>/dev/null; then + msg_info "Bootstrapping dynamic-security admin identity" + if pct exec 104 -- docker run --rm -v /opt/mqtt-broker/data:/mosquitto/data \ + eclipse-mosquitto:2 \ + mosquitto_ctrl dynsec init /mosquitto/data/dynamic-security.json "${MQTT_ADMIN_USER}" "${MQTT_ADMIN_PASS}" \ + &>/dev/null; then + msg_ok "Dynamic-security admin '${MQTT_ADMIN_USER}' created" + else + msg_warn "dynsec init failed — verify manually: pct exec 104 -- docker run --rm -v /opt/mqtt-broker/data:/mosquitto/data eclipse-mosquitto:2 mosquitto_ctrl dynsec init /mosquitto/data/dynamic-security.json " + fi + else + msg_ok "dynamic-security.json already exists — skipping bootstrap" + fi + + pct exec 104 -- bash -c "cd /opt/mqtt-broker && docker compose up -d" &>/dev/null + + msg_info "Waiting for broker" + sleep 3 + if pct exec 104 -- docker exec hotel-manage-mqtt-broker sh -c "pgrep mosquitto" &>/dev/null; then + msg_ok "MQTT broker running at 10.10.10.104:1883 (internal) / ${MQTT_ADMIN_VLAN_IP}:1883 (admin VLAN)" + else + msg_warn "MQTT broker may need extra time or manual check — pct exec 104 -- docker compose -f /opt/mqtt-broker/docker-compose.yml logs" + fi + + msg_warn "Broker admin credential is installer-only, never exposed via any UI — stored in ${CREDS_FILE} as MQTT_ADMIN_USER/MQTT_ADMIN_PASS." + msg_warn "Per-consumer clients (hvac-backend, shelly-devices, etc.) are NOT created by this installer — that's self-service via the settings app's 'MQTT Broker Clients' page (not yet built). Until then, provision manually with mosquitto_ctrl dynsec createClient/addRoleToClient using the admin credential above." +} + deploy_hvac() { msg_step "HVAC (LXC 128 · 10.10.10.128)" @@ -1808,6 +1908,86 @@ ${build_out}" msg_warn "hvac's admin-VLAN NIC (${HVAC_ADMIN_VLAN_IP}) is up but unused by Phase 1 (TRVs go via the MQTT broker) — it's provisioned now for Phase 2/3 Modbus/Midea/Daikin drivers, which aren't built yet." } +deploy_calendar() { + msg_step "Calendar (LXC 126 · 10.10.10.126)" + + if [[ -z "${CALENDAR_DB_PASS:-}" ]]; then + CALENDAR_DB_PASS=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24) + printf '\nCALENDAR_DB_PASS=%s\n' "$CALENDAR_DB_PASS" >> "$CREDS_FILE" + msg_ok "Generated CALENDAR_DB_PASS → ${CREDS_FILE}" + fi + + msg_info "Creating LXC 126" + create_lxc 126 "10.10.10.126" "calendar" 1024 1 + msg_ok "LXC 126 created" + + msg_info "Installing Docker" + install_docker 126 + install_mgmt_key 126 + msg_ok "Docker + SSH ready" + + msg_info "Creating calendar database" + pct exec 100 -- bash -c " + docker exec hotel-manage-postgres psql -U postgres -c \ + \"CREATE USER calendar WITH PASSWORD '${CALENDAR_DB_PASS}';\" 2>/dev/null || true + docker exec hotel-manage-postgres psql -U postgres -c \ + \"CREATE DATABASE calendar_db OWNER calendar;\" 2>/dev/null || true + docker exec hotel-manage-postgres psql -U postgres -d calendar_db -c \ + \"GRANT ALL ON SCHEMA public TO calendar;\" 2>/dev/null || true + " &>/dev/null + msg_ok "Database calendar_db ready" + + msg_info "Deploying calendar" + deploy_service 126 "calendar" "${REPO_ROOT}/calendar" /opt/calendar + + push_file 126 /opt/calendar/.env <&1"); then + msg_error "docker compose build failed in LXC 126: +${build_out}" + fi + + msg_info "Waiting for calendar" + wait_healthy 126 "http://localhost:3080/calendar/health" \ + && msg_ok "Calendar running at 10.10.10.126:3080" \ + || msg_warn "Calendar may need extra time — check LXC 126" + + msg_info "Seeding calendar into auth DB" + pct exec 100 -- docker exec hotel-manage-postgres psql -U postgres -d auth_db -c " + INSERT INTO apps (slug, name, description, base_path, icon, theme_color, category, internal_host, internal_port) + VALUES ('calendar', 'Calendar', 'Shared events calendar — departments, staff, bank holidays, phone sync', '/calendar', 'CalendarDays', '#c9a84c', 'Operations', '10.10.10.126', 3080) + ON CONFLICT (slug) DO UPDATE SET + name=EXCLUDED.name, description=EXCLUDED.description, base_path=EXCLUDED.base_path, + icon=EXCLUDED.icon, theme_color=EXCLUDED.theme_color, category=EXCLUDED.category, + internal_host=EXCLUDED.internal_host, internal_port=EXCLUDED.internal_port; + INSERT INTO app_capabilities (app_id, slug, name, description, sort_order) + SELECT a.id, c.slug, c.name, c.description, c.sort_order + FROM apps a, (VALUES + ('view', 'View Calendar', 'View events, calendars and bank holidays', 1), + ('create', 'Create Events', 'Add new events to non-system calendars', 2), + ('edit', 'Edit Events', 'Edit, delete and attach files to events; manage own CalDAV credentials', 3), + ('manage_calendars', 'Manage Calendars', 'Create, rename, recolour and delete calendars', 4), + ('admin', 'View Activity Log & Admin', 'View the full activity/audit log', 5) + ) AS c(slug, name, description, sort_order) + WHERE a.slug = 'calendar' + ON CONFLICT (app_id, slug) DO NOTHING; + " &>/dev/null \ + && msg_ok "calendar seeded into auth DB" \ + || msg_warn "Seed failed — run calendar/seed-app.js manually" + + npm_add_location "/calendar/" "10.10.10.126" 3080 +} + deploy_kitchen() { msg_step "Kitchen (LXC 110 · 10.10.10.110)" @@ -2219,7 +2399,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 cashup hk-planner twin-optimiser room-planner maintenance forecasting rates reports kitchen kds wages utilities hvac" + VALID="postgres auth portal npm management noticeboard settings cashup hk-planner twin-optimiser room-planner maintenance forecasting rates reports kitchen kds wages utilities mqtt-broker hvac calendar" [[ -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 @@ -2259,6 +2439,8 @@ if [[ "${1:-}" == "--only" ]]; then "$(openssl rand -hex 24)" [[ -z "${HVAC_DB_PASS:-}" ]] && _append_secret HVAC_DB_PASS \ "$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)" + [[ -z "${CALENDAR_DB_PASS:-}" ]] && _append_secret CALENDAR_DB_PASS \ + "$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)" USE_FORGEJO=true [[ -f /root/.ssh/hotel-manage_deploy.pub ]] \ @@ -2285,7 +2467,9 @@ if [[ "${1:-}" == "--only" ]]; then kds) deploy_kds ;; wages) deploy_wages ;; utilities) deploy_utilities ;; + mqtt-broker) deploy_mqtt_broker ;; hvac) deploy_hvac ;; + calendar) deploy_calendar ;; esac exit 0 fi @@ -2315,6 +2499,8 @@ deploy_reports deploy_kitchen deploy_kds deploy_wages +deploy_mqtt_broker deploy_hvac +deploy_calendar configure_npm_proxy_hosts print_summary