Skip to content
25 changes: 25 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,26 @@ OPENSEARCH_PORT=9200
TEMPORAL_PORT=7233
TEMPORAL_UI_PORT=8080

# CPB (Connecting People Bot) — Development
## PostgreSQL provisioning (used by init-db.sh to create the database and role)
POSTGRES_DB_CPB=cpb_bot
POSTGRES_USER_CPB=cpb_app
POSTGRES_PASSWORD_CPB=cpb_password
## CPB application connection (passed to the app at runtime)
CPB_POSTGRES_HOST=postgresql
CPB_POSTGRES_DB=cpb_bot
CPB_POSTGRES_USER=cpb_app
CPB_POSTGRES_PASSWORD=cpb_password
CPB_SLACK_BOT_TOKEN=xoxb-placeholder
CPB_CHANNEL_ID=CXXXXXXXXX
CPB_REPORT_CHANNEL_ID=CXXXXXXXXX
CPB_ADMIN_SLACK_ID=UXXXXXXXXX
CPB_DEV_SLACK_ID=UXXXXXXXXX
CPB_PAIRING_LAMBDA=0.0578
CPB_PAIRING_ALPHA=0.3
CPB_PAIRING_TRIALS=50
CPB_PAIRING_MIN_WEIGHT=0.01

# OAuth2 Proxy / Google OAuth
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
Expand Down Expand Up @@ -95,3 +115,8 @@ MN_SERVICE_SA_GOOGLE_TOKEN_URI=https://oauth2.googleapis.com/token
MN_SERVICE_SA_GOOGLE_AUTH_PROVIDER_X509_CERT_URL=https://www.googleapis.com/oauth2/v1/certs
MN_SERVICE_SA_GOOGLE_CLIENT_X509_CERT_URL=https://www.googleapis.com/robot/v1/metadata/x509/mock-service-account%40mock-project-id.iam.gserviceaccount.com
MN_SERVICE_SA_GOOGLE_UNIVERSE_DOMAIN=googleapis.com
# CPB (Connecting People Bot) — Production overrides
# Variables are defined in the Development section above (lines 30-48).
# For production: set real values for POSTGRES_PASSWORD_CPB, CPB_POSTGRES_HOST,
# CPB_POSTGRES_PASSWORD, CPB_SLACK_BOT_TOKEN, CPB_CHANNEL_ID,
# CPB_REPORT_CHANNEL_ID, CPB_ADMIN_SLACK_ID, and CPB_DEV_SLACK_ID in your .env file.
14 changes: 14 additions & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ services:
- MN_SERVICE_SA_GOOGLE_AUTH_PROVIDER_X509_CERT_URL=${MN_SERVICE_SA_GOOGLE_AUTH_PROVIDER_X509_CERT_URL:?MN_SERVICE_SA_GOOGLE_AUTH_PROVIDER_X509_CERT_URL is required}
- MN_SERVICE_SA_GOOGLE_CLIENT_X509_CERT_URL=${MN_SERVICE_SA_GOOGLE_CLIENT_X509_CERT_URL:?MN_SERVICE_SA_GOOGLE_CLIENT_X509_CERT_URL is required}
- MN_SERVICE_SA_GOOGLE_UNIVERSE_DOMAIN=${MN_SERVICE_SA_GOOGLE_UNIVERSE_DOMAIN:?MN_SERVICE_SA_GOOGLE_UNIVERSE_DOMAIN is required}
# CPB (Connecting People Bot) — defaults allow safe deploy before CPB is configured
- CPB_POSTGRES_HOST=${POSTGRES_HOST:-localhost}
- CPB_POSTGRES_DB=${POSTGRES_DB_CPB:-cpb_bot}
- CPB_POSTGRES_USER=${POSTGRES_USER_CPB:-cpb_app}
- CPB_POSTGRES_PASSWORD=${POSTGRES_PASSWORD_CPB:-cpb_password}
- CPB_SLACK_BOT_TOKEN=${CPB_SLACK_BOT_TOKEN:-xoxb-placeholder}
- CPB_CHANNEL_ID=${CPB_CHANNEL_ID:-CXXXXXXXXX}
- CPB_REPORT_CHANNEL_ID=${CPB_REPORT_CHANNEL_ID:-CXXXXXXXXX}
- CPB_ADMIN_SLACK_ID=${CPB_ADMIN_SLACK_ID:-UXXXXXXXXX}
- CPB_DEV_SLACK_ID=${CPB_DEV_SLACK_ID:-UXXXXXXXXX}
- CPB_PAIRING_LAMBDA=${CPB_PAIRING_LAMBDA:-0.0578}
- CPB_PAIRING_ALPHA=${CPB_PAIRING_ALPHA:-0.3}
- CPB_PAIRING_TRIALS=${CPB_PAIRING_TRIALS:-50}
- CPB_PAIRING_MIN_WEIGHT=${CPB_PAIRING_MIN_WEIGHT:-0.01}
volumes:
- n8n_data:/data/n8n
postgresql: !reset null
Expand Down
17 changes: 17 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ services:
- N8N_LOG_LEVEL=debug
- N8N_LOG_OUTPUT=console
- TZ=${TZ:-America/New_York}
# CPB (Connecting People Bot)
- CPB_POSTGRES_HOST=postgresql
- CPB_POSTGRES_DB=${POSTGRES_DB_CPB:-cpb_bot}
- CPB_POSTGRES_USER=${POSTGRES_USER_CPB:-cpb_app}
- CPB_POSTGRES_PASSWORD=${POSTGRES_PASSWORD_CPB:-cpb_password}
- CPB_SLACK_BOT_TOKEN=${CPB_SLACK_BOT_TOKEN:-xoxb-placeholder}
- CPB_CHANNEL_ID=${CPB_CHANNEL_ID:-CXXXXXXXXX}
- CPB_REPORT_CHANNEL_ID=${CPB_REPORT_CHANNEL_ID:-CXXXXXXXXX}
- CPB_ADMIN_SLACK_ID=${CPB_ADMIN_SLACK_ID:-UXXXXXXXXX}
- CPB_DEV_SLACK_ID=${CPB_DEV_SLACK_ID:-UXXXXXXXXX}
- CPB_PAIRING_LAMBDA=${CPB_PAIRING_LAMBDA:-0.0578}
- CPB_PAIRING_ALPHA=${CPB_PAIRING_ALPHA:-0.3}
- CPB_PAIRING_TRIALS=${CPB_PAIRING_TRIALS:-50}
- CPB_PAIRING_MIN_WEIGHT=${CPB_PAIRING_MIN_WEIGHT:-0.01}
- MN_SERVICE_SA_GOOGLE_TYPE=${MN_SERVICE_SA_GOOGLE_TYPE:-GOOGLE_TYPE}
- MN_SERVICE_SA_GOOGLE_PROJECT_ID=${MN_SERVICE_SA_GOOGLE_PROJECT_ID:-PROJECT_ID}
- MN_SERVICE_SA_GOOGLE_PRIVATE_KEY_ID=${MN_SERVICE_SA_GOOGLE_PRIVATE_KEY_ID:-PRIVATE_KEY_ID}
Expand Down Expand Up @@ -69,6 +83,9 @@ services:
POSTGRES_USER_TEMPORAL: ${POSTGRES_USER_TEMPORAL:-temporal}
POSTGRES_PASSWORD_TEMPORAL: ${POSTGRES_PASSWORD_TEMPORAL:-temporal}
POSTGRES_DB_TEMPORAL_VISIBILITY: ${POSTGRES_DB_TEMPORAL_VISIBILITY:-temporal_visibility}
POSTGRES_DB_CPB: ${POSTGRES_DB_CPB:-cpb_bot}
POSTGRES_USER_CPB: ${POSTGRES_USER_CPB:-cpb_app}
POSTGRES_PASSWORD_CPB: ${POSTGRES_PASSWORD_CPB:-cpb_password}
ports:
- ${POSTGRES_PORT:-5432}:5432
volumes:
Expand Down
80 changes: 80 additions & 0 deletions scripts/cpb-setup-db.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/bin/bash
set -eo pipefail

# =============================================================================
# CPB Production Database Setup
# =============================================================================
# Creates the cpb_bot database and cpb_app user on an external PostgreSQL server.
# Idempotent — safe to re-run. Does not modify existing data.
#
# Usage:
# POSTGRES_PASSWORD_CPB="<password>" CPB_POSTGRES_HOST="<host>" ./scripts/cpb-setup-db.sh
#
# Required env vars:
# CPB_POSTGRES_HOST — PostgreSQL server hostname or IP
# POSTGRES_PASSWORD_CPB — Password for the cpb_app user
#
# Optional env vars:
# CPB_POSTGRES_PORT — PostgreSQL port (default: 5432)
# CPB_POSTGRES_ADMIN_USER — Admin user for psql connection (default: postgres)
# POSTGRES_DB_CPB — Database name (default: cpb_bot)
# POSTGRES_USER_CPB — Username (default: cpb_app)
# =============================================================================

PGHOST="${CPB_POSTGRES_HOST:?CPB_POSTGRES_HOST is required}"
PGPORT="${CPB_POSTGRES_PORT:-5432}"
PGADMIN_USER="${CPB_POSTGRES_ADMIN_USER:-postgres}"
CPB_DB="${POSTGRES_DB_CPB:-cpb_bot}"
CPB_USER="${POSTGRES_USER_CPB:-cpb_app}"
CPB_PASS="${POSTGRES_PASSWORD_CPB:?POSTGRES_PASSWORD_CPB is required}"

# Validate PostgreSQL identifiers (prevent SQL injection via crafted names)
validate_pg_identifier() {
local value="$1" name="$2"
if [[ -z "$value" ]]; then
echo "ERROR: ${name} cannot be empty" >&2; exit 1
fi
if [[ ${#value} -gt 63 ]]; then
echo "ERROR: ${name} exceeds PostgreSQL's 63-char identifier limit" >&2; exit 1
fi
if [[ ! "$value" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
echo "ERROR: ${name} contains invalid characters (must match ^[a-zA-Z_][a-zA-Z0-9_]*$)" >&2; exit 1
fi
return 0
}
validate_pg_identifier "$CPB_USER" "POSTGRES_USER_CPB"
validate_pg_identifier "$CPB_DB" "POSTGRES_DB_CPB"

# Escape single quotes in password for SQL string literal safety
CPB_PASS_SQL="${CPB_PASS//\'/''}"

echo "Setting up CPB database on ${PGHOST}:${PGPORT}..."
echo " Database: ${CPB_DB}"
echo " User: ${CPB_USER}"

psql -v ON_ERROR_STOP=1 -h "$PGHOST" -p "$PGPORT" -U "$PGADMIN_USER" <<-EOSQL
-- Create role if it doesn't exist
DO \$\$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '${CPB_USER}') THEN
CREATE ROLE "${CPB_USER}" WITH LOGIN ENCRYPTED PASSWORD '${CPB_PASS_SQL}';
RAISE NOTICE 'Created role: ${CPB_USER}';
ELSE
RAISE NOTICE 'Role already exists: ${CPB_USER}';
END IF;
END
\$\$;

-- Create database if it doesn't exist
SELECT 'CREATE DATABASE "${CPB_DB}" OWNER "${CPB_USER}"'
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${CPB_DB}')\gexec

-- Grant privileges (idempotent)
GRANT ALL PRIVILEGES ON DATABASE "${CPB_DB}" TO "${CPB_USER}";
EOSQL

echo ""
echo "CPB database setup complete."
echo " Database: ${CPB_DB}"
echo " User: ${CPB_USER}"
echo " Host: ${PGHOST}:${PGPORT}"
36 changes: 34 additions & 2 deletions scripts/init-db.sh
Original file line number Diff line number Diff line change
@@ -1,16 +1,48 @@
#!/bin/bash
set -e

# Validate PostgreSQL identifiers (prevent SQL injection via crafted names)
validate_pg_identifier() {
local value="$1" name="$2"
if [[ -z "$value" ]]; then
echo "ERROR: ${name} cannot be empty" >&2; exit 1
fi
if [[ ${#value} -gt 63 ]]; then
echo "ERROR: ${name} exceeds PostgreSQL's 63-char identifier limit" >&2; exit 1
fi
if [[ ! "$value" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
echo "ERROR: ${name} contains invalid characters (must match ^[a-zA-Z_][a-zA-Z0-9_]*$)" >&2; exit 1
fi
return 0
}

validate_pg_identifier "$POSTGRES_USER_N8N" "POSTGRES_USER_N8N"
validate_pg_identifier "$POSTGRES_DB_N8N" "POSTGRES_DB_N8N"
validate_pg_identifier "$POSTGRES_USER_TEMPORAL" "POSTGRES_USER_TEMPORAL"
validate_pg_identifier "$POSTGRES_DB_TEMPORAL" "POSTGRES_DB_TEMPORAL"
validate_pg_identifier "$POSTGRES_DB_TEMPORAL_VISIBILITY" "POSTGRES_DB_TEMPORAL_VISIBILITY"
validate_pg_identifier "$POSTGRES_USER_CPB" "POSTGRES_USER_CPB"
validate_pg_identifier "$POSTGRES_DB_CPB" "POSTGRES_DB_CPB"

# Escape single quotes in passwords for SQL safety
ESCAPED_POSTGRES_PASSWORD_N8N="${POSTGRES_PASSWORD_N8N//\'/''}"
ESCAPED_POSTGRES_PASSWORD_TEMPORAL="${POSTGRES_PASSWORD_TEMPORAL//\'/''}"
ESCAPED_POSTGRES_PASSWORD_CPB="${POSTGRES_PASSWORD_CPB//\'/''}"

psql -v ON_ERROR_STOP=1 --username "postgres" <<-EOSQL
CREATE USER "$POSTGRES_USER_N8N" WITH ENCRYPTED PASSWORD '$POSTGRES_PASSWORD_N8N';
CREATE USER "$POSTGRES_USER_N8N" WITH ENCRYPTED PASSWORD '$ESCAPED_POSTGRES_PASSWORD_N8N';
CREATE DATABASE "$POSTGRES_DB_N8N" OWNER "$POSTGRES_USER_N8N";
GRANT ALL PRIVILEGES ON DATABASE "$POSTGRES_DB_N8N" TO "$POSTGRES_USER_N8N";

CREATE USER "$POSTGRES_USER_TEMPORAL" WITH ENCRYPTED PASSWORD '$POSTGRES_PASSWORD_TEMPORAL';
CREATE USER "$POSTGRES_USER_TEMPORAL" WITH ENCRYPTED PASSWORD '$ESCAPED_POSTGRES_PASSWORD_TEMPORAL';
CREATE DATABASE "$POSTGRES_DB_TEMPORAL" OWNER "$POSTGRES_USER_TEMPORAL";
GRANT ALL PRIVILEGES ON DATABASE "$POSTGRES_DB_TEMPORAL" TO "$POSTGRES_USER_TEMPORAL";

ALTER USER "$POSTGRES_USER_TEMPORAL" CREATEDB;
CREATE DATABASE "$POSTGRES_DB_TEMPORAL_VISIBILITY" OWNER "$POSTGRES_USER_TEMPORAL";
GRANT ALL PRIVILEGES ON DATABASE "$POSTGRES_DB_TEMPORAL_VISIBILITY" TO "$POSTGRES_USER_TEMPORAL";

CREATE USER "$POSTGRES_USER_CPB" WITH ENCRYPTED PASSWORD '$ESCAPED_POSTGRES_PASSWORD_CPB';
CREATE DATABASE "$POSTGRES_DB_CPB" OWNER "$POSTGRES_USER_CPB";
GRANT ALL PRIVILEGES ON DATABASE "$POSTGRES_DB_CPB" TO "$POSTGRES_USER_CPB";
EOSQL
Loading