Skip to content

Commit ba85dc9

Browse files
nazia-datainnMohsinHashmi-DataInnclaude
authored
fix(devcontainer): add postgresql password synchronization on startup (#456)
Fixes password mismatch issue that occurs when Coder workspace is rebuilt or when local devcontainer is recreated while PostgreSQL volume persists. Root cause: When workspace is rebuilt, Terraform generates a new random password but the PostgreSQL volume retains credentials from first init. Solution: - Add postgres-startup-hook.sh that runs inside PostgreSQL container on every startup and syncs password from environment variable - Add sync-db-password.sh for client-side password recovery attempts - Update Coder template to mount and run the startup hook - Update docker-compose.yml with same mechanism for local dev - Update post-start.sh to call sync script before validation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: MohsinHashmi-DataInn <108420505+MohsinHashmi-DataInn@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ad0dffa commit ba85dc9

5 files changed

Lines changed: 245 additions & 2 deletions

File tree

.coder/template.tf

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ EOF
113113
}
114114

115115
# Random password for PostgreSQL (generated once per workspace)
116+
# NOTE: This password persists in Terraform state across workspace stop/start cycles.
117+
# However, if Terraform state is reset while the volume persists, password mismatch occurs.
118+
#
119+
# The sync-db-password.sh script in post-start attempts to fix this by updating
120+
# the password in the database to match the environment variable.
121+
# If that fails, the user needs to rebuild with: coder restart --build
116122
resource "random_password" "postgres" {
117123
length = 32
118124
special = false # Avoid special chars that might cause shell escaping issues
@@ -159,7 +165,7 @@ resource "docker_container" "postgres" {
159165
"POSTGRES_USER=simpleaccounts",
160166
"POSTGRES_PASSWORD=${random_password.postgres.result}",
161167
"POSTGRES_DB=simpleaccounts",
162-
# Application database user credentials (used by init-db.sh)
168+
# Application database user credentials (used by init-db.sh and password sync)
163169
"SIMPLEACCOUNTS_DB_USER=simpleaccounts",
164170
"SIMPLEACCOUNTS_DB_PASSWORD=${random_password.postgres.result}"
165171
]
@@ -176,6 +182,13 @@ resource "docker_container" "postgres" {
176182
read_only = true
177183
}
178184

185+
# Password sync hook (runs on every startup to fix password mismatch)
186+
volumes {
187+
host_path = "/workspaces/SimpleAccounts-UAE/.devcontainer/postgres-startup-hook.sh"
188+
container_path = "/usr/local/bin/password-sync.sh"
189+
read_only = true
190+
}
191+
179192
networks_advanced {
180193
name = docker_network.workspace.name
181194
aliases = ["db", "postgres"]
@@ -188,6 +201,13 @@ resource "docker_container" "postgres" {
188201
retries = 5
189202
}
190203

204+
# Custom command: start postgres normally, then run password sync in background
205+
# This ensures passwords are synchronized on EVERY container start, not just first init
206+
command = [
207+
"bash", "-c",
208+
"docker-entrypoint.sh postgres & PG_PID=$!; sleep 5; chmod +x /usr/local/bin/password-sync.sh && /usr/local/bin/password-sync.sh || true; wait $PG_PID"
209+
]
210+
191211
restart = "unless-stopped"
192212
}
193213

.devcontainer/docker-compose.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,24 @@ services:
8282
- postgres-data:/var/lib/postgresql/data
8383
# Use shell script instead of SQL for dynamic environment variable support
8484
- ./init-db.sh:/docker-entrypoint-initdb.d/init-db.sh:ro
85+
# Password sync hook (runs on every startup to fix password mismatch)
86+
- ./postgres-startup-hook.sh:/usr/local/bin/password-sync.sh:ro
8587
environment:
8688
# PostgreSQL initialization variables (required for container startup)
8789
POSTGRES_USER: ${POSTGRES_USER}
8890
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
8991
POSTGRES_DB: ${POSTGRES_DB}
90-
# Additional variables for init script
92+
# Additional variables for init script and password sync
9193
SIMPLEACCOUNTS_DB_USER: ${SIMPLEACCOUNTS_DB_USER}
9294
SIMPLEACCOUNTS_DB_PASSWORD: ${SIMPLEACCOUNTS_DB_PASSWORD}
95+
# Custom command: start postgres normally, then run password sync in background
96+
# This ensures passwords are synchronized on EVERY container start, not just first init
97+
command: >
98+
bash -c "docker-entrypoint.sh postgres &
99+
PG_PID=$$!;
100+
sleep 5;
101+
chmod +x /usr/local/bin/password-sync.sh && /usr/local/bin/password-sync.sh || true;
102+
wait $$PG_PID"
93103
healthcheck:
94104
test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER:-simpleaccounts}']
95105
interval: 10s

.devcontainer/post-start.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ done
5252
if [ $ELAPSED -lt $TIMEOUT ]; then
5353
echo "✅ PostgreSQL is ready"
5454

55+
# Synchronize database password (fixes mismatch after workspace rebuild)
56+
echo ""
57+
echo "🔄 Synchronizing database password..."
58+
if bash /workspaces/SimpleAccounts-UAE/.devcontainer/sync-db-password.sh; then
59+
echo "✅ Database password synchronized"
60+
else
61+
echo "⚠️ Database password sync failed - may need manual intervention"
62+
fi
63+
5564
# Validate database configuration
5665
echo ""
5766
echo "🔍 Validating database setup..."
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/bin/bash
2+
# =============================================================================
3+
# PostgreSQL Startup Hook
4+
# =============================================================================
5+
# This script runs INSIDE the PostgreSQL container AFTER the database is ready.
6+
# It synchronizes user passwords from environment variables.
7+
#
8+
# Mount this script and call it from a custom entrypoint or use postgres's
9+
# notify mechanism.
10+
#
11+
# Usage in docker-compose.yml:
12+
# command: >
13+
# bash -c "
14+
# docker-entrypoint.sh postgres &
15+
# sleep 5
16+
# /docker-entrypoint-initdb.d/postgres-startup-hook.sh
17+
# wait
18+
# "
19+
# =============================================================================
20+
21+
set -e
22+
23+
# Wait for PostgreSQL to be ready
24+
echo "Waiting for PostgreSQL to be ready..."
25+
until pg_isready -U "${POSTGRES_USER:-postgres}" -q; do
26+
sleep 1
27+
done
28+
echo "PostgreSQL is ready"
29+
30+
# Sync passwords from environment variables
31+
DB_USER="${SIMPLEACCOUNTS_DB_USER:-simpleaccounts}"
32+
DB_PASSWORD="${SIMPLEACCOUNTS_DB_PASSWORD}"
33+
34+
if [ -n "$DB_PASSWORD" ]; then
35+
echo "Synchronizing password for user: $DB_USER"
36+
psql -U "${POSTGRES_USER:-postgres}" -d postgres -c "
37+
DO \$\$
38+
BEGIN
39+
-- Update the user password if user exists
40+
IF EXISTS (SELECT FROM pg_roles WHERE rolname = '$DB_USER') THEN
41+
ALTER USER $DB_USER WITH PASSWORD '$DB_PASSWORD';
42+
RAISE NOTICE 'Updated password for user: $DB_USER';
43+
ELSE
44+
RAISE NOTICE 'User does not exist: $DB_USER';
45+
END IF;
46+
END
47+
\$\$;
48+
" || echo "Warning: Could not update password"
49+
fi
50+
51+
echo "Password sync complete"

.devcontainer/sync-db-password.sh

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#!/bin/bash
2+
# =============================================================================
3+
# Database Password Synchronization Script
4+
# =============================================================================
5+
# This script ensures the PostgreSQL password matches the environment variable
6+
# by updating it on each startup. This fixes the password mismatch issue that
7+
# occurs when:
8+
# - Coder workspace is rebuilt (new random password generated)
9+
# - Local devcontainer is recreated but postgres volume persists
10+
#
11+
# The script uses pg_isready to detect if PostgreSQL is available, then
12+
# attempts to connect and update the application user's password.
13+
# =============================================================================
14+
15+
set -e
16+
17+
# Colors for output
18+
RED='\033[0;31m'
19+
GREEN='\033[0;32m'
20+
YELLOW='\033[1;33m'
21+
BLUE='\033[0;34m'
22+
NC='\033[0m' # No Color
23+
24+
# Detect environment and set hostname
25+
if [ -n "$CODER_AGENT_TOKEN" ]; then
26+
DB_HOST="${POSTGRES_HOST:-db}"
27+
echo -e "${BLUE}${NC} Detected Coder environment (using '$DB_HOST' hostname)"
28+
else
29+
DB_HOST="${SIMPLEACCOUNTS_DB_HOST:-localhost}"
30+
echo -e "${BLUE}${NC} Detected local devcontainer (using '$DB_HOST' hostname)"
31+
fi
32+
33+
DB_PORT="${SIMPLEACCOUNTS_DB_PORT:-5432}"
34+
DB_USER="${SIMPLEACCOUNTS_DB_USER:-simpleaccounts}"
35+
DB_PASSWORD="${SIMPLEACCOUNTS_DB_PASSWORD:-simpleaccounts_dev}"
36+
DB_NAME="${SIMPLEACCOUNTS_DB:-simpleaccounts}"
37+
POSTGRES_USER_ENV="${POSTGRES_USER:-simpleaccounts}"
38+
POSTGRES_PASSWORD_ENV="${POSTGRES_PASSWORD:-$DB_PASSWORD}"
39+
40+
echo -e "${BLUE}🔄 Synchronizing database password...${NC}"
41+
echo " Host: $DB_HOST:$DB_PORT"
42+
echo " User: $DB_USER"
43+
echo " Database: $DB_NAME"
44+
45+
# Function to try connecting with a password
46+
try_connection() {
47+
local password="$1"
48+
local user="$2"
49+
PGPASSWORD="$password" psql -h "$DB_HOST" -p "$DB_PORT" -U "$user" -d "$DB_NAME" -c "SELECT 1" > /dev/null 2>&1
50+
return $?
51+
}
52+
53+
# Function to update user password
54+
update_password() {
55+
local admin_password="$1"
56+
local admin_user="$2"
57+
local target_user="$3"
58+
local new_password="$4"
59+
60+
PGPASSWORD="$admin_password" psql -h "$DB_HOST" -p "$DB_PORT" -U "$admin_user" -d "$DB_NAME" -c "ALTER USER $target_user WITH PASSWORD '$new_password';" > /dev/null 2>&1
61+
return $?
62+
}
63+
64+
# Wait for PostgreSQL to be ready
65+
echo -e "${BLUE}⏳ Waiting for PostgreSQL...${NC}"
66+
TIMEOUT=30
67+
ELAPSED=0
68+
until pg_isready -h "$DB_HOST" -p "$DB_PORT" -q; do
69+
sleep 1
70+
ELAPSED=$((ELAPSED + 1))
71+
if [ $ELAPSED -ge $TIMEOUT ]; then
72+
echo -e "${YELLOW}${NC} PostgreSQL not ready after ${TIMEOUT}s"
73+
exit 0 # Don't fail, let other scripts continue
74+
fi
75+
done
76+
echo -e "${GREEN}${NC} PostgreSQL is accepting connections"
77+
78+
# Test connection with current environment password
79+
if try_connection "$DB_PASSWORD" "$DB_USER"; then
80+
echo -e "${GREEN}${NC} Password is already synchronized"
81+
exit 0
82+
fi
83+
84+
echo -e "${YELLOW}${NC} Password mismatch detected, attempting to synchronize..."
85+
86+
# List of passwords to try for admin access
87+
# Order matters: try most likely passwords first
88+
ADMIN_PASSWORDS=(
89+
"$POSTGRES_PASSWORD_ENV"
90+
"$DB_PASSWORD"
91+
"simpleaccounts_dev"
92+
"postgres"
93+
""
94+
)
95+
96+
# List of admin users to try
97+
ADMIN_USERS=(
98+
"$POSTGRES_USER_ENV"
99+
"postgres"
100+
"simpleaccounts"
101+
)
102+
103+
PASSWORD_SYNCED=false
104+
105+
for admin_user in "${ADMIN_USERS[@]}"; do
106+
for admin_password in "${ADMIN_PASSWORDS[@]}"; do
107+
# Try connecting as admin
108+
if try_connection "$admin_password" "$admin_user"; then
109+
echo -e "${BLUE}${NC} Connected as '$admin_user'"
110+
111+
# Update the application user password
112+
if update_password "$admin_password" "$admin_user" "$DB_USER" "$DB_PASSWORD"; then
113+
echo -e "${GREEN}${NC} Updated password for user '$DB_USER'"
114+
115+
# Also update postgres user if different
116+
if [ "$admin_user" != "postgres" ] && [ "$DB_USER" != "postgres" ]; then
117+
update_password "$admin_password" "$admin_user" "postgres" "$POSTGRES_PASSWORD_ENV" 2>/dev/null || true
118+
fi
119+
120+
PASSWORD_SYNCED=true
121+
break 2
122+
else
123+
echo -e "${YELLOW}${NC} Failed to update password (may lack permissions)"
124+
fi
125+
fi
126+
done
127+
done
128+
129+
if [ "$PASSWORD_SYNCED" = true ]; then
130+
# Verify the new password works
131+
if try_connection "$DB_PASSWORD" "$DB_USER"; then
132+
echo -e "${GREEN}${NC} Password synchronization complete"
133+
exit 0
134+
else
135+
echo -e "${RED}${NC} Password update succeeded but verification failed"
136+
exit 1
137+
fi
138+
else
139+
echo -e "${RED}${NC} Could not synchronize password"
140+
echo ""
141+
echo "This usually means the PostgreSQL volume was initialized with a different password."
142+
echo "To fix this, you need to:"
143+
echo ""
144+
echo " Option 1: Delete the PostgreSQL volume and let it reinitialize"
145+
echo " - Coder: coder restart --build"
146+
echo " - Local: docker compose down -v && docker compose up -d"
147+
echo ""
148+
echo " Option 2: Manually reset the password"
149+
echo " - Connect to PostgreSQL as superuser"
150+
echo " - Run: ALTER USER $DB_USER WITH PASSWORD 'your-password';"
151+
echo ""
152+
exit 1
153+
fi

0 commit comments

Comments
 (0)