22set -eo pipefail
33
44# =============================================================================
5- # CPB Production Database Setup
5+ # CPB Schema Setup
66# =============================================================================
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.
7+ # Connects to the cpb_bot database as the cpb_app role and applies the
8+ # init-schema.sql file to create tables, indexes, and triggers.
109#
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
10+ # Prerequisites:
11+ # - Role cpb_app and database cpb_bot must already exist.
12+ # Run create-role.sh first if they don't.
1613#
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)
14+ # Idempotency:
15+ # - Safe to re-run. All DDL uses IF NOT EXISTS / CREATE OR REPLACE.
2316#
2417# Usage:
25- # CPB_POSTGRES_HOST="<host>" POSTGRES_PASSWORD_TEMPORAL ="<pw>" \
26- # POSTGRES_USER_N8N="n8n" ./scripts/cpb/setup-db.sh
18+ # CPB_POSTGRES_HOST="<host>" POSTGRES_PASSWORD_CPB ="<pw>" \
19+ # ./scripts/cpb/setup-db.sh
2720#
2821# Required env vars:
29- # CPB_POSTGRES_HOST — PostgreSQL server hostname or IP
30- # POSTGRES_PASSWORD_TEMPORAL — Password for the temporal user
22+ # CPB_POSTGRES_HOST — PostgreSQL server hostname or IP
23+ # POSTGRES_PASSWORD_CPB — Password for the cpb_app role
3124#
3225# 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)
26+ # CPB_POSTGRES_PORT — PostgreSQL port (default: 5432)
27+ # POSTGRES_USER_CPB — CPB role name (default: cpb_app)
28+ # POSTGRES_DB_CPB — Database name (default: cpb_bot)
3729# =============================================================================
3830
31+ # --- Required env vars (fail immediately if missing) --------------------------
3932PGHOST=" ${CPB_POSTGRES_HOST:? CPB_POSTGRES_HOST is required} "
33+ CPB_PASS=" ${POSTGRES_PASSWORD_CPB:? POSTGRES_PASSWORD_CPB is required} "
34+
35+ # --- Optional env vars with defaults -----------------------------------------
4036PGPORT=" ${CPB_POSTGRES_PORT:- 5432} "
41- TEMPORAL_USER=" ${POSTGRES_USER_TEMPORAL:- temporal} "
42- N8N_USER=" ${POSTGRES_USER_N8N:- n8n} "
37+ CPB_USER=" ${POSTGRES_USER_CPB:- cpb_app} "
4338CPB_DB=" ${POSTGRES_DB_CPB:- cpb_bot} "
4439
45- TEMPORAL_PASS=" ${POSTGRES_PASSWORD_TEMPORAL:? POSTGRES_PASSWORD_TEMPORAL is required} "
40+ # --- Resolve script directory for locating SQL files --------------------------
41+ SCRIPT_DIR=" $( cd " $( dirname " ${BASH_SOURCE[0]} " ) " && pwd) "
42+ REPO_ROOT=" $( cd " $SCRIPT_DIR /../.." && pwd) "
43+ SCHEMA_FILE=" ${REPO_ROOT} /sql/cpb/init-schema.sql"
4644
47- # Validate PostgreSQL identifiers (prevent SQL injection via crafted names)
45+ # --- Validate identifiers (prevent SQL injection via crafted names) -----------
4846validate_pg_identifier () {
4947 local value=" $1 " name=" $2 "
5048 if [[ -z " $value " ]]; then
@@ -58,52 +56,63 @@ validate_pg_identifier() {
5856 fi
5957 return 0
6058}
61- validate_pg_identifier " $TEMPORAL_USER " " POSTGRES_USER_TEMPORAL"
62- validate_pg_identifier " $N8N_USER " " POSTGRES_USER_N8N"
59+ validate_pg_identifier " $CPB_USER " " POSTGRES_USER_CPB"
6360validate_pg_identifier " $CPB_DB " " POSTGRES_DB_CPB"
6461
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} "
62+ # --- Verify schema file exists ------------------------------------------------
63+ if [[ ! -f " $SCHEMA_FILE " ]]; then
64+ echo " ERROR: Schema file not found: ${SCHEMA_FILE} " >&2
65+ echo " Expected at: sql/cpb/init-schema.sql (relative to repo root)" >&2
66+ exit 1
67+ fi
6968
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
69+ echo " === CPB Schema Setup ==="
70+ echo " Host: ${PGHOST} :${PGPORT} "
71+ echo " User: ${CPB_USER} "
72+ echo " Database: ${CPB_DB} "
73+ echo " Schema: ${SCHEMA_FILE} "
74+ echo " "
7875
79- -- Grant all database-level privileges to n8n (idempotent)
80- GRANT ALL PRIVILEGES ON DATABASE "${CPB_DB} " TO "${N8N_USER} ";
81- EOSQL
76+ # --- Step 1: Verify connectivity ---------------------------------------------
77+ echo " Step 1: Verifying database connection..."
78+ if ! PGPASSWORD=" ${CPB_PASS} " psql -v ON_ERROR_STOP=1 \
79+ -h " $PGHOST " -p " $PGPORT " -U " $CPB_USER " -d " $CPB_DB " \
80+ -c " SELECT 1;" > /dev/null 2>&1 ; then
81+ echo " ERROR: Cannot connect to ${CPB_DB} as ${CPB_USER} " >&2
82+ echo " Have you run create-role.sh first?" >&2
83+ exit 1
84+ fi
85+ echo " Connection: OK"
86+
87+ # --- Step 2: Apply schema ----------------------------------------------------
88+ echo " Step 2: Applying schema from init-schema.sql..."
89+ PGPASSWORD=" ${CPB_PASS} " psql -v ON_ERROR_STOP=1 \
90+ -h " $PGHOST " -p " $PGPORT " -U " $CPB_USER " -d " $CPB_DB " \
91+ -f " $SCHEMA_FILE "
92+ echo " Schema applied: OK"
8293
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- if 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 ) ; then
91- echo " Schema grant on public: OK"
94+ # --- Step 3: Verify tables were created ---------------------------------------
95+ echo " Step 3: Verifying tables..."
96+ TABLE_COUNT=$( PGPASSWORD=" ${CPB_PASS} " psql -v ON_ERROR_STOP=1 -t -A \
97+ -h " $PGHOST " -p " $PGPORT " -U " $CPB_USER " -d " $CPB_DB " \
98+ -c " SELECT count(*) FROM information_schema.tables
99+ WHERE table_schema = 'public'
100+ AND table_type = 'BASE TABLE'
101+ AND table_name IN (
102+ 'cycles', 'opt_in_responses', 'pairings',
103+ 'pair_history', 'interactions', 'admin_reports'
104+ );" )
105+
106+ if [[ " $TABLE_COUNT " -eq 6 ]]; then
107+ echo " All 6 tables verified: OK"
92108else
93- if echo " $SCHEMA_GRANT_ERR " | grep -qi " permission denied\|must be owner" ; then
94- echo " Schema grant on public: skipped (not needed on PG14 — PUBLIC has CREATE by default)"
95- echo " NOTE: After upgrading to PG15+, re-run this script or grant manually:"
96- echo " GRANT ALL ON SCHEMA public TO \" ${N8N_USER} \" ;"
97- else
98- echo " ERROR: Schema grant failed unexpectedly:" >&2
99- echo " $SCHEMA_GRANT_ERR " >&2
100- exit 1
101- fi
109+ echo " WARNING: Expected 6 tables, found ${TABLE_COUNT} " >&2
110+ echo " Run with -v for psql debug output to investigate" >&2
102111fi
103112
104113echo " "
105- echo " CPB database setup complete. "
114+ echo " === Schema setup complete === "
106115echo " Database: ${CPB_DB} "
107- echo " Owner : ${TEMPORAL_USER }"
108- echo " Access : ${N8N_USER} (all privileges) "
116+ echo " User : ${CPB_USER }"
117+ echo " Tables : ${TABLE_COUNT} /6 "
109118echo " Host: ${PGHOST} :${PGPORT} "
0 commit comments