From 2088cb5577a57090c561a0637c6693a5efe1a1d4 Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Thu, 6 Aug 2026 14:45:07 +0000 Subject: [PATCH] deploy_kds: scoped DB role instead of sharing kitchen's credential - Generate KDS_DB_PASS (matching KITCHEN_DB_PASS's pattern), add to --only dispatch's _append_secret list - Create a scoped `kds` Postgres role: SELECT/INSERT/UPDATE/DELETE on kds_tickets/kds_course_bumps, read-only on resos_bookings/resos_opening_hours, column-scoped SELECT+UPDATE on just the kds_* columns of kitchen_settings (not the NewBook/ResOS/Nextcloud/Dext/SambaPOS/Azure/Anthropic credentials that live in the same table). Runs in two passes since kds_tickets/ kds_course_bumps don't exist until KDS's own migrations create them. - kds .env now carries DATABASE_URL (scoped `kds` role, runtime) and MIGRATION_DATABASE_URL (privileged `kitchen` role, migrations only) - Fix KDS theme_color seed (teal -> orange, matched kitchen's tile before) See kitchen-port-log.md E17. Co-Authored-By: Claude Sonnet 5 --- install-stack.sh | 63 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/install-stack.sh b/install-stack.sh index 5fbf955..9bbdcf1 100755 --- a/install-stack.sh +++ b/install-stack.sh @@ -2189,6 +2189,12 @@ deploy_kds() { msg_error "KITCHEN_DB_PASS not set — deploy kitchen first, or add it to ${CREDS_FILE}" fi + if [[ -z "${KDS_DB_PASS:-}" ]]; then + KDS_DB_PASS=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24) + printf '\nKDS_DB_PASS=%s\n' "$KDS_DB_PASS" >> "$CREDS_FILE" + msg_ok "Generated KDS_DB_PASS → ${CREDS_FILE}" + fi + msg_info "Creating LXC 125" create_lxc 125 "10.10.10.125" "kds" 1024 1 msg_ok "LXC 125 created" @@ -2198,12 +2204,54 @@ deploy_kds() { install_mgmt_key 125 msg_ok "Docker + SSH ready" + # Scoped `kds` DB role — E17: KDS used to share the `kitchen` role wholesale + # (full read/write on all 74 kitchen_db tables). Grant only what KDS + # actually touches: its own tables, read-only ResOS bookings, and the + # kds_* columns on kitchen_settings — not NewBook/ResOS/Nextcloud/Dext/ + # SambaPOS-sales/Azure/Anthropic credentials that live in the same table. + # kds_tickets/kds_course_bumps don't exist yet at this point (KDS's own + # migrations create them on first boot, run via the privileged connection + # below) — that grant runs in a second pass after the container is up. + msg_info "Scoping kds DB role" + pct exec 100 -- docker exec hotel-manage-postgres psql -U postgres -d kitchen_db -c " + DO \$\$ BEGIN + CREATE ROLE kds LOGIN PASSWORD '${KDS_DB_PASS}'; + EXCEPTION WHEN duplicate_object THEN + ALTER ROLE kds WITH PASSWORD '${KDS_DB_PASS}'; + END \$\$; + GRANT CONNECT ON DATABASE kitchen_db TO kds; + GRANT USAGE ON SCHEMA public TO kds; + GRANT SELECT ON resos_bookings, resos_opening_hours TO kds; + GRANT SELECT ( + id, kitchen_id, kds_enabled, kds_graphql_url, kds_graphql_username, + kds_graphql_password, kds_graphql_client_id, kds_poll_interval_seconds, + kds_timer_green_seconds, kds_timer_amber_seconds, kds_timer_red_seconds, + kds_course_order, kds_show_completed_for_seconds, + kds_away_timer_green_seconds, kds_away_timer_amber_seconds, + kds_away_timer_red_seconds, kds_bookings_refresh_seconds + ) ON kitchen_settings TO kds; + GRANT UPDATE ( + kds_enabled, kds_graphql_url, kds_graphql_username, kds_graphql_password, + kds_graphql_client_id, kds_poll_interval_seconds, kds_timer_green_seconds, + kds_timer_amber_seconds, kds_timer_red_seconds, kds_course_order, + kds_show_completed_for_seconds, kds_away_timer_green_seconds, + kds_away_timer_amber_seconds, kds_away_timer_red_seconds, + kds_bookings_refresh_seconds + ) ON kitchen_settings TO kds; + " &>/dev/null \ + && msg_ok "kds DB role scoped" \ + || msg_warn "kds role setup failed — check LXC 100 postgres logs" + msg_info "Deploying kds" deploy_service 125 "kds" "${REPO_ROOT}/kds" /opt/kds + # DATABASE_URL is the scoped `kds` role (runtime queries). MIGRATION_DATABASE_URL + # is the privileged `kitchen` role, used only at startup to create KDS's own + # tables and ALTER kitchen_settings — never touched by request handling. push_file 125 /opt/kds/.env </dev/null \ + && msg_ok "kds table grants applied" \ + || msg_warn "kds table grants failed — check kds_tickets/kds_course_bumps exist, then re-run" + msg_info "Waiting for kds" wait_healthy 125 "http://localhost:3080/kds/health" 200 \ && msg_ok "KDS running at 10.10.10.125:3080" \ @@ -2222,7 +2279,7 @@ ${build_out}" msg_info "Seeding kds 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 ('kds', 'Kitchen Display', 'SambaPOS ticket feed and course flow display', '/kds', 'Monitor', '#0d9488', 'Kitchen', '10.10.10.125', 3080) + VALUES ('kds', 'Kitchen Display', 'SambaPOS ticket feed and course flow display', '/kds', 'Monitor', '#ea580c', 'Kitchen', '10.10.10.125', 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, @@ -2529,6 +2586,8 @@ if [[ "${1:-}" == "--only" ]]; then "$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)" [[ -z "${KITCHEN_DB_PASS:-}" ]] && _append_secret KITCHEN_DB_PASS \ "$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)" + [[ -z "${KDS_DB_PASS:-}" ]] && _append_secret KDS_DB_PASS \ + "$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)" [[ -z "${WAGES_DB_PASS:-}" ]] && _append_secret WAGES_DB_PASS \ "$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)" [[ -z "${UTILITIES_DB_PASS:-}" ]] && _append_secret UTILITIES_DB_PASS \