Skip to content

Commit 952b021

Browse files
SashkoMarchukclaude
andcommitted
infra(cpb): rewrite DB setup script and add schema for Connecting People Bot
Replace the old setup script that required postgres admin access and CREATEROLE with a version that uses temporal user (CREATEDB) and grants to existing n8n user. Add complete 6-table schema (cycles, opt_in_responses, pairings, pair_history, interactions, admin_reports) with idempotent IF NOT EXISTS, triggers, and indexes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 404d9ed commit 952b021

3 files changed

Lines changed: 324 additions & 80 deletions

File tree

‎scripts/cpb-setup-db.sh‎

Lines changed: 0 additions & 80 deletions
This file was deleted.

‎scripts/cpb/setup-db.sh‎

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#!/bin/bash
2+
set -eo pipefail
3+
4+
# =============================================================================
5+
# CPB Production Database Setup
6+
# =============================================================================
7+
# Creates the cpb_bot database on an external PostgreSQL (RDS) server using the
8+
# temporal user (which has CREATEDB privilege). Grants access to the n8n user
9+
# so the CPB application (running inside n8n) can manage its tables.
10+
#
11+
# Ownership model:
12+
# - temporal creates and owns the database (only user with CREATEDB)
13+
# - n8n gets ALL PRIVILEGES on the database to create/manage tables
14+
# - Later, when DevOps provides the master password, ownership can be
15+
# migrated to a dedicated cpb_app user
16+
#
17+
# PostgreSQL version notes:
18+
# - PG14: public schema grants CREATE to PUBLIC by default — n8n can create
19+
# tables without explicit schema grants
20+
# - PG15+: CREATE on public schema is revoked by default — the script
21+
# attempts to grant schema privileges and warns if it cannot (the DB owner
22+
# can do this on PG15+ since public schema is owned by pg_database_owner)
23+
#
24+
# Usage:
25+
# CPB_POSTGRES_HOST="<host>" POSTGRES_PASSWORD_TEMPORAL="<pw>" \
26+
# POSTGRES_USER_N8N="n8n" ./scripts/cpb-setup-db.sh
27+
#
28+
# Required env vars:
29+
# CPB_POSTGRES_HOST — PostgreSQL server hostname or IP
30+
# POSTGRES_PASSWORD_TEMPORAL — Password for the temporal user
31+
#
32+
# Optional env vars:
33+
# CPB_POSTGRES_PORT — PostgreSQL port (default: 5432)
34+
# POSTGRES_USER_TEMPORAL — Temporal user name (default: temporal)
35+
# POSTGRES_USER_N8N — n8n user to grant access to (default: n8n)
36+
# POSTGRES_DB_CPB — Database name (default: cpb_bot)
37+
# =============================================================================
38+
39+
PGHOST="${CPB_POSTGRES_HOST:?CPB_POSTGRES_HOST is required}"
40+
PGPORT="${CPB_POSTGRES_PORT:-5432}"
41+
TEMPORAL_USER="${POSTGRES_USER_TEMPORAL:-temporal}"
42+
N8N_USER="${POSTGRES_USER_N8N:-n8n}"
43+
CPB_DB="${POSTGRES_DB_CPB:-cpb_bot}"
44+
45+
TEMPORAL_PASS="${POSTGRES_PASSWORD_TEMPORAL:?POSTGRES_PASSWORD_TEMPORAL is required}"
46+
47+
# Validate PostgreSQL identifiers (prevent SQL injection via crafted names)
48+
validate_pg_identifier() {
49+
local value="$1" name="$2"
50+
if [[ -z "$value" ]]; then
51+
echo "ERROR: ${name} cannot be empty" >&2; exit 1
52+
fi
53+
if [[ ${#value} -gt 63 ]]; then
54+
echo "ERROR: ${name} exceeds PostgreSQL's 63-char identifier limit" >&2; exit 1
55+
fi
56+
if [[ ! "$value" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
57+
echo "ERROR: ${name} contains invalid characters (must match ^[a-zA-Z_][a-zA-Z0-9_]*$)" >&2; exit 1
58+
fi
59+
return 0
60+
}
61+
validate_pg_identifier "$TEMPORAL_USER" "POSTGRES_USER_TEMPORAL"
62+
validate_pg_identifier "$N8N_USER" "POSTGRES_USER_N8N"
63+
validate_pg_identifier "$CPB_DB" "POSTGRES_DB_CPB"
64+
65+
echo "Setting up CPB database on ${PGHOST}:${PGPORT}..."
66+
echo " Database: ${CPB_DB}"
67+
echo " Owner: ${TEMPORAL_USER} (interim — migrate to dedicated user later)"
68+
echo " Grantee: ${N8N_USER}"
69+
70+
# --- Step 1: Create database and grant database-level privileges -----------
71+
# Connect to the 'postgres' maintenance database to run DDL.
72+
# CREATE DATABASE cannot run inside a transaction, so we use \gexec.
73+
PGPASSWORD="${TEMPORAL_PASS}" psql -v ON_ERROR_STOP=1 \
74+
-h "$PGHOST" -p "$PGPORT" -U "$TEMPORAL_USER" -d postgres <<-EOSQL
75+
-- Create database if it doesn't exist (temporal becomes owner)
76+
SELECT 'CREATE DATABASE "${CPB_DB}"'
77+
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${CPB_DB}')\gexec
78+
79+
-- Grant all database-level privileges to n8n (idempotent)
80+
GRANT ALL PRIVILEGES ON DATABASE "${CPB_DB}" TO "${N8N_USER}";
81+
EOSQL
82+
83+
# --- Step 2: Grant schema-level privileges ---------------------------------
84+
# On PG14, this is unnecessary (PUBLIC has CREATE on public schema by default)
85+
# but we attempt it for forward-compatibility with PG15+ where it IS required.
86+
# temporal cannot grant on public schema in PG14 (owned by postgres), so we
87+
# handle the error gracefully.
88+
SCHEMA_GRANT_ERR=$(PGPASSWORD="${TEMPORAL_PASS}" psql -v ON_ERROR_STOP=1 \
89+
-h "$PGHOST" -p "$PGPORT" -U "$TEMPORAL_USER" -d "$CPB_DB" \
90+
-c "GRANT ALL ON SCHEMA public TO \"${N8N_USER}\";" 2>&1) && \
91+
echo " Schema grant on public: OK" || {
92+
if echo "$SCHEMA_GRANT_ERR" | grep -qi "permission denied\|must be owner"; then
93+
echo " Schema grant on public: skipped (not needed on PG14 — PUBLIC has CREATE by default)"
94+
echo " NOTE: After upgrading to PG15+, re-run this script or grant manually:"
95+
echo " GRANT ALL ON SCHEMA public TO \"${N8N_USER}\";"
96+
else
97+
echo "ERROR: Schema grant failed unexpectedly:" >&2
98+
echo " $SCHEMA_GRANT_ERR" >&2
99+
exit 1
100+
fi
101+
}
102+
103+
echo ""
104+
echo "CPB database setup complete."
105+
echo " Database: ${CPB_DB}"
106+
echo " Owner: ${TEMPORAL_USER}"
107+
echo " Access: ${N8N_USER} (all privileges)"
108+
echo " Host: ${PGHOST}:${PGPORT}"

0 commit comments

Comments
 (0)