Add deploy_hvac() with dual-homed admin-VLAN support
LXC 128, hvac_db, hvac app — plus generic dual-NIC support in create_lxc() (admin VLAN bridge/tag/IP asked lazily via whiptail, persisted per-hotel in the credentials file, since these vary per site). Used now for hvac's future Modbus/Midea/Daikin direct-LAN drivers; the shared MQTT broker LXC will reuse the same ensure_admin_vlan_config()/ensure_admin_vlan_ip() helpers. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
2fd6305e4f
commit
20e0c52ea0
1 changed files with 144 additions and 2 deletions
146
install-stack.sh
146
install-stack.sh
|
|
@ -361,19 +361,30 @@ ensure_pool() {
|
|||
# container skip AppArmor via `security_opt: apparmor=unconfined` in its compose.
|
||||
# (Overriding lxc.apparmor.profile would cancel nesting — don't do that.)
|
||||
create_lxc() {
|
||||
local id=$1 ip=$2 name=$3 mem=${4:-512} cores=${5:-1}
|
||||
local id=$1 ip=$2 name=$3 mem=${4:-512} cores=${5:-1} vlan_ip=${6:-}
|
||||
if lxc_exists "$id"; then
|
||||
msg_warn "LXC $id (hotel-manage-${name}) already exists — skipping creation"
|
||||
lxc_running "$id" || pct start "$id"
|
||||
return
|
||||
fi
|
||||
local tmpl; tmpl=$(get_template)
|
||||
# Optional second NIC on the admin VLAN (dual-homed, same pattern as NPM/103) —
|
||||
# used by containers that need direct LAN reach (e.g. hvac's Modbus/Midea/Daikin
|
||||
# drivers, the shared MQTT broker). Bridge/tag are site-specific — see
|
||||
# ensure_admin_vlan_config().
|
||||
local net1_args=()
|
||||
if [[ -n "$vlan_ip" ]]; then
|
||||
local tag_part=""
|
||||
[[ -n "${ADMIN_VLAN_TAG:-}" ]] && tag_part=",tag=${ADMIN_VLAN_TAG}"
|
||||
net1_args=(--net1 "name=eth1,bridge=${ADMIN_VLAN_BRIDGE:-vmbr0}${tag_part},ip=${vlan_ip}/24")
|
||||
fi
|
||||
pct create "$id" "$tmpl" \
|
||||
--hostname "hotel-manage-${name}" \
|
||||
--memory "$mem" \
|
||||
--cores "$cores" \
|
||||
--rootfs "${STORAGE}:8" \
|
||||
--net0 "name=eth0,bridge=vmbr1,ip=${ip}/24,gw=10.10.10.1" \
|
||||
"${net1_args[@]}" \
|
||||
--features nesting=1 \
|
||||
--unprivileged 0 \
|
||||
--onboot 1 \
|
||||
|
|
@ -383,6 +394,38 @@ create_lxc() {
|
|||
sleep 5
|
||||
}
|
||||
|
||||
# ── Admin VLAN config (dual-homed containers) — asked once, lazily, per hotel ──
|
||||
# Bridge name and VLAN tag vary per hotel install (e.g. a plain vmbr0 with no tag
|
||||
# at one site, a tagged VLAN on a trunk port at another) — never hardcode either.
|
||||
ensure_admin_vlan_config() {
|
||||
[[ -n "${ADMIN_VLAN_BRIDGE:-}" ]] && return
|
||||
ADMIN_VLAN_BRIDGE=$(whiptail --title "Hotel Manage — Admin VLAN" \
|
||||
--inputbox "Proxmox bridge carrying the admin/HVAC-device VLAN (varies per hotel):" \
|
||||
8 66 "vmbr0" 3>&1 1>&2 2>&3) || exit 0
|
||||
ADMIN_VLAN_TAG=$(whiptail --title "Hotel Manage — Admin VLAN" \
|
||||
--inputbox "VLAN tag number for that bridge (leave blank if untagged/native VLAN — varies per hotel):" \
|
||||
8 66 "" 3>&1 1>&2 2>&3) || exit 0
|
||||
{
|
||||
echo "ADMIN_VLAN_BRIDGE=${ADMIN_VLAN_BRIDGE}"
|
||||
echo "ADMIN_VLAN_TAG=${ADMIN_VLAN_TAG}"
|
||||
} >> "$CREDS_FILE"
|
||||
msg_ok "Admin VLAN config saved → ${CREDS_FILE}"
|
||||
}
|
||||
|
||||
# Ask for (and persist) a static IP on the admin VLAN for one dual-homed
|
||||
# container. Kept per-consumer (not a single shared value) since each
|
||||
# dual-homed LXC needs its own address on that VLAN.
|
||||
ensure_admin_vlan_ip() {
|
||||
local var=$1 label=$2 default=$3
|
||||
[[ -n "${!var:-}" ]] && return
|
||||
local val
|
||||
val=$(whiptail --title "Hotel Manage — Admin VLAN" \
|
||||
--inputbox "${label} (varies per hotel):" 8 66 "${default}" 3>&1 1>&2 2>&3) || exit 0
|
||||
export "$var"="$val"
|
||||
echo "${var}=${val}" >> "$CREDS_FILE"
|
||||
msg_ok "${var} saved → ${CREDS_FILE}"
|
||||
}
|
||||
|
||||
create_npm_lxc() {
|
||||
local id=103
|
||||
if lxc_exists "$id"; then
|
||||
|
|
@ -1670,6 +1713,101 @@ ${build_out}"
|
|||
npm_add_location "/utilities/" "10.10.10.127" 3080
|
||||
}
|
||||
|
||||
deploy_hvac() {
|
||||
msg_step "HVAC (LXC 128 · 10.10.10.128)"
|
||||
|
||||
if [[ -z "${HVAC_DB_PASS:-}" ]]; then
|
||||
HVAC_DB_PASS=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)
|
||||
printf '\nHVAC_DB_PASS=%s\n' "$HVAC_DB_PASS" >> "$CREDS_FILE"
|
||||
msg_ok "Generated HVAC_DB_PASS → ${CREDS_FILE}"
|
||||
fi
|
||||
|
||||
ensure_admin_vlan_config
|
||||
ensure_admin_vlan_ip HVAC_ADMIN_VLAN_IP \
|
||||
"Static IP for hvac's admin-VLAN interface (net1 — direct Modbus/Midea/Daikin device access)" \
|
||||
"10.4.0.60"
|
||||
|
||||
msg_info "Creating LXC 128"
|
||||
create_lxc 128 "10.10.10.128" "hvac" 1024 1 "${HVAC_ADMIN_VLAN_IP}"
|
||||
msg_ok "LXC 128 created (dual-homed: 10.10.10.128 + ${HVAC_ADMIN_VLAN_IP} on ${ADMIN_VLAN_BRIDGE}${ADMIN_VLAN_TAG:+ tag ${ADMIN_VLAN_TAG}})"
|
||||
|
||||
msg_info "Installing Docker"
|
||||
install_docker 128
|
||||
install_mgmt_key 128
|
||||
msg_ok "Docker + SSH ready"
|
||||
|
||||
msg_info "Creating hvac database"
|
||||
pct exec 100 -- bash -c "
|
||||
docker exec hotel-manage-postgres psql -U postgres -c \
|
||||
\"CREATE USER hvac WITH PASSWORD '${HVAC_DB_PASS}';\" 2>/dev/null || true
|
||||
docker exec hotel-manage-postgres psql -U postgres -c \
|
||||
\"CREATE DATABASE hvac_db OWNER hvac;\" 2>/dev/null || true
|
||||
docker exec hotel-manage-postgres psql -U postgres -d hvac_db -c \
|
||||
\"GRANT ALL ON SCHEMA public TO hvac;\" 2>/dev/null || true
|
||||
" &>/dev/null
|
||||
msg_ok "Database hvac_db ready"
|
||||
|
||||
msg_info "Deploying hvac"
|
||||
deploy_service 128 "hvac" "${REPO_ROOT}/hvac" /opt/hvac
|
||||
|
||||
# No MQTT broker LXC exists yet (separate infra piece, not part of this deploy) —
|
||||
# hvac's mqtt.js connects out and retries with backoff, so this is safe to deploy
|
||||
# before the broker exists. NEWBOOK_LOCATION_ID matches every other NewBook app.
|
||||
push_file 128 /opt/hvac/.env <<EOF
|
||||
NODE_ENV=production
|
||||
APP_SLUG=hvac
|
||||
DATABASE_URL=postgresql://hvac:${HVAC_DB_PASS}@10.10.10.100:5432/hvac_db
|
||||
CENTRAL_AUTH_SECRET=${CENTRAL_AUTH_SECRET}
|
||||
SETTINGS_URL=http://10.10.10.116:3080
|
||||
SETTINGS_SECRET=${SETTINGS_SECRET}
|
||||
OFFICE_IP_CHECK=${OFFICE_IP_CHECK:-${OFFICE_IP:-disabled}}
|
||||
NEWBOOK_LOCATION_ID=${NEWBOOK_LOCATION_ID:-}
|
||||
FRONTEND_PORT=3080
|
||||
EOF
|
||||
|
||||
local build_out
|
||||
if ! build_out=$(pct exec 128 -- bash -c "cd /opt/hvac && docker compose up -d --build 2>&1"); then
|
||||
msg_error "docker compose build failed in LXC 128:
|
||||
${build_out}"
|
||||
fi
|
||||
|
||||
msg_info "Waiting for hvac"
|
||||
wait_healthy 128 "http://localhost:3080/hvac/health" \
|
||||
&& msg_ok "HVAC running at 10.10.10.128:3080" \
|
||||
|| msg_warn "HVAC may need extra time — check LXC 128"
|
||||
|
||||
msg_info "Seeding hvac 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 ('hvac', 'HVAC', 'Room heating control — NewBook-driven TRV scheduling, aircon and boiler (phased)', '/hvac', 'Thermometer', '#c1440e', 'Operations', '10.10.10.128', 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','View zone dashboard, device status and activity',1),
|
||||
('control','Manual Override','Force a zone''s temperature and disable auto mode',2),
|
||||
('schedule_edit','Edit Schedules','Adjust per-zone temps, offsets and auto mode',3),
|
||||
('manage_devices','Manage Devices','Discover, map, photograph devices; sync zones from NewBook',4),
|
||||
('public_area_control','Public Area Control','Central control of public-area zones (Phase 3)',5),
|
||||
('boiler_view','Boiler — View','View boiler controller status (Phase 4)',6),
|
||||
('boiler_control','Boiler — Control','Adjust boiler weather-compensation / pump disable (Phase 4)',7),
|
||||
('settings','Settings','Configure hvac app settings',8)
|
||||
) AS c(slug, name, description, sort_order)
|
||||
WHERE a.slug = 'hvac'
|
||||
ON CONFLICT (app_id, slug) DO NOTHING;
|
||||
" &>/dev/null \
|
||||
&& msg_ok "hvac seeded into auth DB" \
|
||||
|| msg_warn "Seed failed — run hvac/seed-app.js manually"
|
||||
|
||||
npm_add_location "/hvac/" "10.10.10.128" 3080
|
||||
|
||||
msg_warn "hvac deployed but the shared MQTT broker (LXC 104) is separate infra and not provisioned by this installer — device control will retry/backoff until it exists. See the hvac plan doc's 'MQTT settings & auth' section."
|
||||
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_kitchen() {
|
||||
msg_step "Kitchen (LXC 110 · 10.10.10.110)"
|
||||
|
||||
|
|
@ -2081,7 +2219,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"
|
||||
VALID="postgres auth portal npm management noticeboard settings cashup hk-planner twin-optimiser room-planner maintenance forecasting rates reports kitchen kds wages utilities hvac"
|
||||
[[ -z "$ONLY" ]] && msg_error "Usage: install-stack.sh --only <service> (one of: ${VALID})"
|
||||
grep -qw "$ONLY" <<< "$VALID" || msg_error "Unknown service '${ONLY}'. Valid: ${VALID}"
|
||||
CREDS_FILE=/root/hotel-manage-credentials.txt
|
||||
|
|
@ -2119,6 +2257,8 @@ if [[ "${1:-}" == "--only" ]]; then
|
|||
"$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)"
|
||||
[[ -z "${UTILITIES_API_KEY:-}" ]] && _append_secret UTILITIES_API_KEY \
|
||||
"$(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)"
|
||||
|
||||
USE_FORGEJO=true
|
||||
[[ -f /root/.ssh/hotel-manage_deploy.pub ]] \
|
||||
|
|
@ -2145,6 +2285,7 @@ if [[ "${1:-}" == "--only" ]]; then
|
|||
kds) deploy_kds ;;
|
||||
wages) deploy_wages ;;
|
||||
utilities) deploy_utilities ;;
|
||||
hvac) deploy_hvac ;;
|
||||
esac
|
||||
exit 0
|
||||
fi
|
||||
|
|
@ -2174,5 +2315,6 @@ deploy_reports
|
|||
deploy_kitchen
|
||||
deploy_kds
|
||||
deploy_wages
|
||||
deploy_hvac
|
||||
configure_npm_proxy_hosts
|
||||
print_summary
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue