-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathdocker-entrypoint.sh
More file actions
executable file
·281 lines (230 loc) · 9.03 KB
/
docker-entrypoint.sh
File metadata and controls
executable file
·281 lines (230 loc) · 9.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
#!/bin/bash
# ViTransfer Docker Entrypoint Script
# Universal compatibility: Works on Unraid, TrueNAS, Docker Desktop, Ubuntu, Podman
# Handles PUID/PGID remapping AND docker-compose user: directive
# This script runs automatically on container start - no manual intervention required
set -e
echo "ViTransfer starting..."
echo ""
# ========================================
# SMART USER DETECTION
# ========================================
# Detects how the container is running and adapts automatically:
# - Via docker-compose user: directive → Already correct user
# - Via PUID/PGID env vars → Remap user
# - Default → Use default UID 911
RUNNING_UID=$(id -u)
RUNNING_GID=$(id -g)
PUID=${PUID:-911}
PGID=${PGID:-911}
echo "[INFO] User Configuration:"
echo " Container running as: UID=$RUNNING_UID GID=$RUNNING_GID"
echo " Target (PUID/PGID): UID=$PUID GID=$PGID"
echo ""
# ========================================
# CASE 1: Already running as target user
# ========================================
if [ "$RUNNING_UID" = "$PUID" ] && [ "$RUNNING_GID" = "$PGID" ]; then
echo "[OK] Already running as target user UID:$PUID GID:$PGID"
echo " (Detected docker-compose 'user:' directive or matching PUID/PGID)"
echo ""
# Fix ownership of app files if needed (from build-time UID 911)
if [ "$RUNNING_UID" != "911" ]; then
echo "[SETUP] Fixing ownership of app files..."
# Only fix files still owned by build-time user (911)
# Don't touch mounted volumes!
find /app -maxdepth 2 \( -name '.next' -o -name 'public' -o -name 'node_modules' -o -name 'src' \) -user 911 \
-exec chown -R $RUNNING_UID:$RUNNING_GID {} + 2>/dev/null || true
echo "[OK] File ownership updated"
echo ""
fi
SKIP_SU_EXEC=true
# ========================================
# CASE 2: Running as non-root, but different UID
# ========================================
elif [ "$RUNNING_UID" != "0" ]; then
echo "[OK] Running as non-root user UID:$RUNNING_UID GID:$RUNNING_GID"
echo " (Container already secured, using current user)"
echo ""
# Fix ownership if possible (may fail without root, that's ok)
echo "[SETUP] Attempting to fix app file ownership..."
find /app -maxdepth 2 \( -name '.next' -o -name 'public' -o -name 'src' \) -user 911 \
-exec chown -R $RUNNING_UID:$RUNNING_GID {} + 2>/dev/null || true
echo "[OK] Ownership fix attempted (errors ignored)"
echo ""
SKIP_SU_EXEC=true
# ========================================
# CASE 3: Running as root - need to remap
# ========================================
else
echo "[SETUP] Running as root, remapping to UID:$PUID GID:$PGID..."
echo ""
# Get current app user IDs
CURRENT_UID=$(id -u app 2>/dev/null || echo "911")
CURRENT_GID=$(id -g app 2>/dev/null || echo "911")
# Only remap if needed
if [ "$CURRENT_UID" != "$PUID" ] || [ "$CURRENT_GID" != "$PGID" ]; then
# Temporarily move home dir away from /app before usermod.
# usermod recursively chowns the home directory which includes
# node_modules (100k+ files) and takes minutes.
USERHOME=$(grep "^app:" /etc/passwd | cut -d ":" -f6)
usermod -d /tmp app 2>/dev/null || true
# Update group ID if needed
if [ "$CURRENT_GID" != "$PGID" ]; then
groupmod -o -g "$PGID" app 2>/dev/null || true
fi
# Update user ID (fast — home dir is /tmp, not /app)
if [ "$CURRENT_UID" != "$PUID" ]; then
usermod -o -u "$PUID" app 2>/dev/null || true
fi
# Restore home directory
usermod -d "${USERHOME}" app 2>/dev/null || true
echo "[OK] User permissions updated"
else
echo "[OK] User permissions already correct"
fi
# Fix ownership of top-level app files (package.json, config, etc.)
# Uses maxdepth 1 to avoid slow recursive chown of node_modules
find /app -maxdepth 1 -exec chown app:app {} + 2>/dev/null || true
chown -R app:app /app/.next /app/public /app/src /app/prisma 2>/dev/null || true
# Ensure uploads volume is owned by app user (Docker mounts as root)
if [ -d /app/uploads ]; then
chown app:app /app/uploads 2>/dev/null || true
fi
echo ""
SKIP_SU_EXEC=false
fi
# ========================================
# SERVICE READINESS CHECKS
# ========================================
# Function to wait for postgres to be ready
wait_for_postgres() {
echo "[WAIT] Waiting for PostgreSQL to be ready..."
max_attempts=30
attempt=0
while [ $attempt -lt $max_attempts ]; do
if node -e "
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
prisma.\$connect()
.then(() => { console.log('Connected'); process.exit(0); })
.catch(() => { process.exit(1); });
" 2>/dev/null; then
echo "[OK] PostgreSQL is ready!"
return 0
fi
attempt=$((attempt + 1))
echo " Attempt $attempt/$max_attempts - waiting..."
sleep 2
done
echo "[ERROR] PostgreSQL is not ready after $max_attempts attempts"
return 1
}
# Function to wait for Redis to be ready
wait_for_redis() {
echo "[WAIT] Waiting for Redis to be ready..."
max_attempts=30
attempt=0
while [ $attempt -lt $max_attempts ]; do
if node -e "
const Redis = require('ioredis');
const redis = new Redis({
host: process.env.REDIS_HOST || 'redis',
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD || undefined,
maxRetriesPerRequest: 1,
retryStrategy: () => null
});
redis.ping()
.then(() => { redis.disconnect(); process.exit(0); })
.catch(() => { redis.disconnect(); process.exit(1); });
" 2>/dev/null; then
echo "[OK] Redis is ready!"
return 0
fi
attempt=$((attempt + 1))
echo " Attempt $attempt/$max_attempts - waiting..."
sleep 2
done
echo "[ERROR] Redis is not ready after $max_attempts attempts"
return 1
}
# Function to wait for app to be fully ready
wait_for_app() {
echo "[WAIT] Waiting for application to be fully ready..."
# Configurable hostname and port for different deployment scenarios
# APP_HOST: Container/service name (default: vitransfer-app)
# APP_PORT: Application port (default: 4321)
APP_HOST=${APP_HOST:-vitransfer-app}
APP_PORT=${APP_PORT:-4321}
max_attempts=150 # 5 minutes (150 attempts * 2 seconds)
attempt=0
echo " Checking: http://${APP_HOST}:${APP_PORT}/api/health"
while [ $attempt -lt $max_attempts ]; do
if curl -s -f http://${APP_HOST}:${APP_PORT}/api/health > /dev/null 2>&1; then
echo "[OK] Application is ready!"
return 0
fi
attempt=$((attempt + 1))
echo " Attempt $attempt/$max_attempts - waiting..."
sleep 2
done
echo "[ERROR] Application is not ready after $max_attempts attempts"
echo " Tried: http://${APP_HOST}:${APP_PORT}"
return 1
}
# ========================================
# DATABASE SETUP (MAIN APP ONLY)
# ========================================
# Only run migrations and initialization for the main app, not the worker
if [ "$1" = "npm" ] && [ "$2" = "start" ]; then
echo "[SETUP] Running database setup..."
echo ""
# Wait for services to be ready
wait_for_postgres
wait_for_redis
echo ""
echo "[DB] Running Prisma migrations..."
# Run migrations automatically
# - On first run: Creates all tables from scratch (initial_schema migration)
# - On updates: Applies only new migrations (e.g., when upgrading to v1.1, v1.2, etc.)
# - Idempotent: Safe to run multiple times, only applies pending migrations
if npx prisma migrate deploy; then
echo "[OK] Database migrations completed"
else
echo "[ERROR] Database migration failed"
exit 1
fi
echo ""
echo "[INIT] Database setup complete"
echo " Admin initialization will run automatically via instrumentation.ts"
echo ""
elif [[ "$@" == *"npm run worker"* ]] || [[ "$@" == *"worker"* ]]; then
echo "[SETUP] Worker initialization..."
echo ""
# Workers need to wait for database, Redis, AND the main app to be ready
wait_for_postgres
wait_for_redis
wait_for_app
echo ""
echo "[OK] All services ready for worker"
echo ""
fi
# ========================================
# START APPLICATION
# ========================================
echo "[START] Starting application..."
if [ "$SKIP_SU_EXEC" = "true" ]; then
echo " Running as: UID:$RUNNING_UID GID:$RUNNING_GID (direct)"
else
echo " Running as: UID:$PUID GID:$PGID (via su-exec app)"
fi
echo ""
# Execute the main command
if [ "$SKIP_SU_EXEC" = "true" ]; then
# Already running as correct user, no need for su-exec
exec "$@"
else
# Running as root, switch to app user
exec su-exec app "$@"
fi