diff --git a/docker-compose.yml b/docker-compose.yml index 85ae51c43..9c50173be 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,10 +8,13 @@ services: volumes: - postgres_data:/var/lib/postgresql/data - ./scripts/docker/postgresql.conf:/etc/postgresql/postgresql.conf:ro + - ./scripts/docker/init-electric-user.sh:/docker-entrypoint-initdb.d/init-electric-user.sh:ro environment: - POSTGRES_USER=${POSTGRES_USER:-postgres} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres} - POSTGRES_DB=${POSTGRES_DB:-surfsense} + - ELECTRIC_DB_USER=${ELECTRIC_DB_USER:-electric} + - ELECTRIC_DB_PASSWORD=${ELECTRIC_DB_PASSWORD:-electric_password} command: postgres -c config_file=/etc/postgresql/postgresql.conf pgadmin: @@ -123,6 +126,8 @@ services: - ELECTRIC_INSECURE=true - ELECTRIC_WRITE_TO_PG_MODE=direct restart: unless-stopped + # depends_on: + # - db healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/v1/health"] interval: 10s diff --git a/scripts/docker/init-electric-user.sh b/scripts/docker/init-electric-user.sh new file mode 100755 index 000000000..fcd31b2e2 --- /dev/null +++ b/scripts/docker/init-electric-user.sh @@ -0,0 +1,48 @@ +#!/bin/sh +# ============================================================================ +# Electric SQL User Initialization Script (Docker deployments) +# ============================================================================ +# Creates the Electric SQL replication user for Docker deployments. +# +# For local PostgreSQL users (non-Docker), this is handled by Alembic +# migration 66 (66_add_notifications_table_and_electric_replication.py). +# +# Both approaches are idempotent (use IF NOT EXISTS), so running both +# will not cause conflicts. +# ============================================================================ + +set -e + +# Use environment variables with defaults +ELECTRIC_DB_USER="${ELECTRIC_DB_USER:-electric}" +ELECTRIC_DB_PASSWORD="${ELECTRIC_DB_PASSWORD:-electric_password}" + +echo "Creating Electric SQL replication user: $ELECTRIC_DB_USER" + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + DO \$\$ + BEGIN + IF NOT EXISTS (SELECT FROM pg_user WHERE usename = '$ELECTRIC_DB_USER') THEN + CREATE USER $ELECTRIC_DB_USER WITH REPLICATION PASSWORD '$ELECTRIC_DB_PASSWORD'; + END IF; + END + \$\$; + + GRANT CONNECT ON DATABASE $POSTGRES_DB TO $ELECTRIC_DB_USER; + GRANT USAGE ON SCHEMA public TO $ELECTRIC_DB_USER; + GRANT SELECT ON ALL TABLES IN SCHEMA public TO $ELECTRIC_DB_USER; + GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO $ELECTRIC_DB_USER; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO $ELECTRIC_DB_USER; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON SEQUENCES TO $ELECTRIC_DB_USER; + + -- Create the publication for Electric SQL (if not exists) + DO \$\$ + BEGIN + IF NOT EXISTS (SELECT FROM pg_publication WHERE pubname = 'electric_publication_default') THEN + CREATE PUBLICATION electric_publication_default; + END IF; + END + \$\$; +EOSQL + +echo "Electric SQL user '$ELECTRIC_DB_USER' and publication created successfully" diff --git a/surfsense_backend/alembic/versions/66_add_notifications_table_and_electric_replication.py b/surfsense_backend/alembic/versions/66_add_notifications_table_and_electric_replication.py index 2dde10871..e07cf6de4 100644 --- a/surfsense_backend/alembic/versions/66_add_notifications_table_and_electric_replication.py +++ b/surfsense_backend/alembic/versions/66_add_notifications_table_and_electric_replication.py @@ -6,6 +6,11 @@ Revises: 65 Creates notifications table and sets up Electric SQL replication (user, publication, REPLICA IDENTITY FULL) for notifications, search_source_connectors, and documents tables. + +NOTE: Electric SQL user creation is idempotent (uses IF NOT EXISTS). +- Docker deployments: user is pre-created by scripts/docker/init-electric-user.sh +- Local PostgreSQL: user is created here during migration +Both approaches are safe to run together without conflicts as this migraiton is idempotent """ from collections.abc import Sequence @@ -46,11 +51,11 @@ def upgrade() -> None: """ ) - # Create indexes - op.create_index("ix_notifications_user_id", "notifications", ["user_id"]) - op.create_index("ix_notifications_read", "notifications", ["read"]) - op.create_index("ix_notifications_created_at", "notifications", ["created_at"]) - op.create_index("ix_notifications_user_read", "notifications", ["user_id", "read"]) + # Create indexes (using IF NOT EXISTS for idempotency) + op.execute("CREATE INDEX IF NOT EXISTS ix_notifications_user_id ON notifications (user_id);") + op.execute("CREATE INDEX IF NOT EXISTS ix_notifications_read ON notifications (read);") + op.execute("CREATE INDEX IF NOT EXISTS ix_notifications_created_at ON notifications (created_at);") + op.execute("CREATE INDEX IF NOT EXISTS ix_notifications_user_read ON notifications (user_id, read);") # ===================================================== # Electric SQL Setup - User and Publication diff --git a/surfsense_web/components/notifications/NotificationPopup.tsx b/surfsense_web/components/notifications/NotificationPopup.tsx index 129ff97db..74e2f1e31 100644 --- a/surfsense_web/components/notifications/NotificationPopup.tsx +++ b/surfsense_web/components/notifications/NotificationPopup.tsx @@ -44,7 +44,7 @@ export function NotificationPopup({ switch (status) { case "in_progress": - return ; + return ; case "completed": return ; case "failed": @@ -73,7 +73,7 @@ export function NotificationPopup({ {loading ? (
- +
) : notifications.length === 0 ? (