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 ? (