fix: made migartion idempotent and fixed docker-compose

This commit is contained in:
Anish Sarkar 2026-01-15 13:26:23 +05:30
parent 32b8bb33f9
commit 35392c8e62
4 changed files with 65 additions and 7 deletions

View file

@ -8,10 +8,13 @@ services:
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
- ./scripts/docker/postgresql.conf:/etc/postgresql/postgresql.conf:ro - ./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: environment:
- POSTGRES_USER=${POSTGRES_USER:-postgres} - POSTGRES_USER=${POSTGRES_USER:-postgres}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres}
- POSTGRES_DB=${POSTGRES_DB:-surfsense} - 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 command: postgres -c config_file=/etc/postgresql/postgresql.conf
pgadmin: pgadmin:
@ -123,6 +126,8 @@ services:
- ELECTRIC_INSECURE=true - ELECTRIC_INSECURE=true
- ELECTRIC_WRITE_TO_PG_MODE=direct - ELECTRIC_WRITE_TO_PG_MODE=direct
restart: unless-stopped restart: unless-stopped
# depends_on:
# - db
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/v1/health"] test: ["CMD", "curl", "-f", "http://localhost:3000/v1/health"]
interval: 10s interval: 10s

View file

@ -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"

View file

@ -6,6 +6,11 @@ Revises: 65
Creates notifications table and sets up Electric SQL replication Creates notifications table and sets up Electric SQL replication
(user, publication, REPLICA IDENTITY FULL) for notifications, (user, publication, REPLICA IDENTITY FULL) for notifications,
search_source_connectors, and documents tables. 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 from collections.abc import Sequence
@ -46,11 +51,11 @@ def upgrade() -> None:
""" """
) )
# Create indexes # Create indexes (using IF NOT EXISTS for idempotency)
op.create_index("ix_notifications_user_id", "notifications", ["user_id"]) op.execute("CREATE INDEX IF NOT EXISTS ix_notifications_user_id ON notifications (user_id);")
op.create_index("ix_notifications_read", "notifications", ["read"]) op.execute("CREATE INDEX IF NOT EXISTS ix_notifications_read ON notifications (read);")
op.create_index("ix_notifications_created_at", "notifications", ["created_at"]) op.execute("CREATE INDEX IF NOT EXISTS ix_notifications_created_at ON notifications (created_at);")
op.create_index("ix_notifications_user_read", "notifications", ["user_id", "read"]) op.execute("CREATE INDEX IF NOT EXISTS ix_notifications_user_read ON notifications (user_id, read);")
# ===================================================== # =====================================================
# Electric SQL Setup - User and Publication # Electric SQL Setup - User and Publication

View file

@ -44,7 +44,7 @@ export function NotificationPopup({
switch (status) { switch (status) {
case "in_progress": case "in_progress":
return <Loader2 className="h-4 w-4 text-white animate-spin" />; return <Loader2 className="h-4 w-4 text-foreground animate-spin" />;
case "completed": case "completed":
return <CheckCircle2 className="h-4 w-4 text-green-500" />; return <CheckCircle2 className="h-4 w-4 text-green-500" />;
case "failed": case "failed":
@ -73,7 +73,7 @@ export function NotificationPopup({
<ScrollArea className="h-[400px]"> <ScrollArea className="h-[400px]">
{loading ? ( {loading ? (
<div className="flex items-center justify-center py-8"> <div className="flex items-center justify-center py-8">
<Loader2 className="h-5 w-5 animate-spin text-white" /> <Loader2 className="h-5 w-5 animate-spin text-foreground" />
</div> </div>
) : notifications.length === 0 ? ( ) : notifications.length === 0 ? (
<div className="flex flex-col items-center justify-center py-8 px-4 text-center"> <div className="flex flex-col items-center justify-center py-8 px-4 text-center">