forked from m3ue/m3u-editor
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstart-container
More file actions
executable file
·625 lines (545 loc) · 24.4 KB
/
start-container
File metadata and controls
executable file
·625 lines (545 loc) · 24.4 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
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
#!/usr/bin/env bash
# Get/set runtime environment variables
export PUID="${PUID:-1000}" # NOT CURRENTLY USED...
export PGID="${PGID:-1000}" # NOT CURRENTLY USED...
# Set Redis server host, port and password
export REDIS_ENABLED="${REDIS_ENABLED:-true}"
export REDIS_HOST="${REDIS_HOST:-localhost}"
export REDIS_SERVER_PORT="${REDIS_SERVER_PORT:-36790}" # Default 36790
export REDIS_PASSWORD="${REDIS_PASSWORD}" # May be set later if empty
# Run NGINX/FPM as root
export NGINX_ENABLED="${NGINX_ENABLED:-true}"
export NGINX_USER="root"
export FPMUSER="root"
export FPMGROUP="root"
export FPMPORT="${FPMPORT:-9000}" # Default 9000
# Check if PostgreSQL enabled
export PGDATA="/var/lib/postgresql/data"
export ENABLE_POSTGRES="${ENABLE_POSTGRES:-false}"
export PG_DATABASE="${PG_DATABASE:-m3ue}"
export PG_USER="${PG_USER:-root}"
export PG_PASSWORD="${PG_PASSWORD:-root}"
export PG_PORT="${PG_PORT:-5432}" # Default 5432
export PG_MAX_WAIT_SECONDS="${PG_MAX_WAIT_SECONDS:-60}" # Default 60
# Network Broadcast Worker
# Enables continuous broadcasting for Networks (pseudo-TV channels)
# When enabled, supervisor runs `php artisan network:broadcast` continuously
export NETWORK_BROADCAST_ENABLED="${NETWORK_BROADCAST_ENABLED:-false}"
# M3U Proxy configuration
# If M3U_PROXY_ENABLED=true, enable embedded proxy
# If M3U_PROXY_ENABLED=false/unset, disable embedded proxy
export M3U_PROXY_ENABLED="${M3U_PROXY_ENABLED:-true}"
export M3U_PROXY_START_EMBEDDED="true" # Will be set to false if external proxy enabled
export M3U_PROXY_HOST="${M3U_PROXY_HOST:-127.0.0.1}" # Bind to localhost for embedded
export M3U_PROXY_PORT="${M3U_PROXY_PORT:-8085}"
export M3U_PROXY_LOG_LEVEL="${M3U_PROXY_LOG_LEVEL:-ERROR}" # Default ERROR
export M3U_PROXY_NGINX_TARGET="" # Will be set based on mode for nginx proxy_pass
# Run PHP as root (or m3ue)
export SUPERVISOR_PHP_USER="root"
# Application URL and port
export APP_URL="${APP_URL:-http://localhost}"
export APP_PORT="${APP_PORT:-36400}" # Default 36400
# Xtream API endpoint port (separate nginx instance for Xtream-only access)
export XTREAM_PORT="${XTREAM_PORT:-36401}" # Default 36401
export XTREAM_ONLY_ENABLED="${XTREAM_ONLY_ENABLED:-false}" # Default disabled
# Set proxy reverse proxy target and token based on mode
if [ "${M3U_PROXY_ENABLED}" = "true" ]; then
# Embedded mode - proxy through nginx to localhost
export M3U_PROXY_NGINX_TARGET="127.0.0.1:${M3U_PROXY_PORT}"
# M3U Proxy API key (for embedded proxy only)
# Generate if not set
if [ -z "$M3U_PROXY_TOKEN" ]; then
export M3U_PROXY_TOKEN=$(openssl rand -hex 24)
echo "🔑 Generated random m3u proxy API token: $M3U_PROXY_TOKEN"
else
export M3U_PROXY_TOKEN="${M3U_PROXY_TOKEN}"
echo "🔑 Using provided m3u proxy API token."
fi
if [ "$NGINX_ENABLED" = "true" ]; then
echo "🔗 Embedded m3u-proxy mode enabled. Nginx will proxy /m3u-proxy/ to ${M3U_PROXY_NGINX_TARGET}"
fi
else
# External mode - proxy through nginx to external container
export M3U_PROXY_START_EMBEDDED="false"
# Should be in the format: m3u-proxy:38085
export M3U_PROXY_NGINX_TARGET="${M3U_PROXY_HOST}:${M3U_PROXY_PORT}"
# For external proxy, use provided token
export M3U_PROXY_TOKEN="${M3U_PROXY_TOKEN}"
if [ "$NGINX_ENABLED" = "true" ]; then
echo "🔗 External m3u-proxy mode enabled. Nginx will proxy /m3u-proxy/ to ${M3U_PROXY_NGINX_TARGET}"
fi
fi
# Check if embedded mode is enabled but HOST is not localhost
if [ "${M3U_PROXY_ENABLED}" = "true" ] && [[ "$M3U_PROXY_HOST" != "localhost" ]] && [[ "$M3U_PROXY_HOST" != "127.0.0.1" ]]; then
echo "⚠️ WARNING: Embedded mode enabled but M3U_PROXY_HOST is not localhost"
echo " Current value: ${M3U_PROXY_HOST}"
echo " For AIO/embedded mode, M3U_PROXY_HOST should be 'localhost' or '127.0.0.1'"
fi
# Setup Redis password
# Default to M3U_PROXY_TOKEN if not set
# Generate Redis password if not set (for external connections)
if [ "${REDIS_ENABLED}" = "true" ]; then
if [ -z "$REDIS_PASSWORD" ]; then
if [ -z "$M3U_PROXY_TOKEN" ]; then
export REDIS_PASSWORD=$(openssl rand -hex 24)
echo "🔑 Generated random Redis password: $REDIS_PASSWORD"
else
export REDIS_PASSWORD="$M3U_PROXY_TOKEN"
echo "🔑 Using M3U_PROXY_TOKEN as Redis password"
fi
else
echo "🔑 Using provided Redis password."
fi
fi
# Configure HLS segment storage (persistent location)
# Default to storage/app/hls-segments for persistence across container restarts
export HLS_TEMP_DIR="${HLS_TEMP_DIR:-/var/www/html/storage/app/hls-segments}"
export HLS_GC_ENABLED="${HLS_GC_ENABLED:-true}"
export HLS_GC_INTERVAL="${HLS_GC_INTERVAL:-600}" # 10 minutes
export HLS_GC_AGE_THRESHOLD="${HLS_GC_AGE_THRESHOLD:-7200}" # 2 hours (increased from default 1 hour)
# Configure broadcast segment storage (RAM disk for low-latency live TV)
# Network broadcasts write short-lived .ts segments that benefit from in-memory storage.
export HLS_BROADCAST_DIR="${HLS_BROADCAST_DIR:-${HLS_TEMP_DIR}}"
# Broadcast HLS garbage collection
# Cleans orphaned .ts segments left behind during programme transitions
export BROADCAST_GC_ENABLED="${BROADCAST_GC_ENABLED:-true}"
if [ "${M3U_PROXY_ENABLED}" = "true" ]; then
# Only output configure HLS storage for embedded proxy mode
# For external proxy, HLS storage is handled by the proxy container
echo ""
echo "🎬 Configuring HLS segment storage..."
echo " HLS_TEMP_DIR: ${HLS_TEMP_DIR}"
echo " HLS_GC_ENABLED: ${HLS_GC_ENABLED}"
echo " HLS_GC_INTERVAL: ${HLS_GC_INTERVAL}s"
echo " HLS_GC_AGE_THRESHOLD: ${HLS_GC_AGE_THRESHOLD}s"
echo " BROADCAST_GC_ENABLED: ${BROADCAST_GC_ENABLED}"
# Create HLS storage directory if it doesn't exist
if [ ! -d "$HLS_TEMP_DIR" ]; then
echo " Creating HLS storage directory..."
mkdir -p "$HLS_TEMP_DIR"
chown -R $WWWUSER:$WWWGROUP "$HLS_TEMP_DIR"
chmod 755 "$HLS_TEMP_DIR"
echo " ✅ Created: $HLS_TEMP_DIR"
else
echo " ✅ Directory exists: $HLS_TEMP_DIR"
# Ensure correct permissions
chown -R $WWWUSER:$WWWGROUP "$HLS_TEMP_DIR"
chmod 755 "$HLS_TEMP_DIR"
fi
# Check available disk space for HLS storage
AVAILABLE_SPACE_KB=$(df -k "$HLS_TEMP_DIR" | tail -1 | awk '{print $4}')
AVAILABLE_SPACE_MB=$((AVAILABLE_SPACE_KB / 1024))
AVAILABLE_SPACE_GB=$((AVAILABLE_SPACE_MB / 1024))
echo " Available disk space: ${AVAILABLE_SPACE_GB}GB (${AVAILABLE_SPACE_MB}MB)"
# Warn if less than 2GB available
if [ "$AVAILABLE_SPACE_MB" -lt 2048 ]; then
echo " ⚠️ WARNING: Low disk space for HLS segments!"
echo " Available: ${AVAILABLE_SPACE_MB}MB"
echo " Recommended: At least 2GB for HLS streaming"
echo " HLS streams may fail if disk fills up!"
fi
# Warn if less than 500MB available (critical)
if [ "$AVAILABLE_SPACE_MB" -lt 512 ]; then
echo " 🔴 CRITICAL: Very low disk space!"
echo " HLS streaming will likely fail!"
echo " Free up disk space immediately!"
fi
echo "✅ HLS storage configured"
echo ""
# If network broadcast enabled, configure broadcast segment storage
if [ "${NETWORK_BROADCAST_ENABLED}" = "true" ]; then
echo "📡 Configuring broadcast segment storage..."
echo " HLS_BROADCAST_DIR: ${HLS_BROADCAST_DIR}"
# Create broadcast storage directory if it doesn't exist
if [ ! -d "$HLS_BROADCAST_DIR" ]; then
echo " Creating broadcast storage directory..."
mkdir -p "$HLS_BROADCAST_DIR"
chown -R $WWWUSER:$WWWGROUP "$HLS_BROADCAST_DIR"
chmod 755 "$HLS_BROADCAST_DIR"
echo " ✅ Created: $HLS_BROADCAST_DIR"
else
echo " ✅ Directory exists: $HLS_BROADCAST_DIR"
chmod 755 "$HLS_BROADCAST_DIR"
fi
# Check available space for broadcast storage
BROADCAST_SPACE_KB=$(df -k "$HLS_BROADCAST_DIR" | tail -1 | awk '{print $4}')
BROADCAST_SPACE_MB=$((BROADCAST_SPACE_KB / 1024))
BROADCAST_SPACE_GB=$((BROADCAST_SPACE_MB / 1024))
echo " Available space: ${BROADCAST_SPACE_GB}GB (${BROADCAST_SPACE_MB}MB)"
if [ "$BROADCAST_SPACE_MB" -lt 256 ]; then
echo " ⚠️ WARNING: Low space for broadcast segments!"
echo " Available: ${BROADCAST_SPACE_MB}MB"
echo " Recommended: At least 256MB for network broadcasts"
echo " Consider setting HLS_BROADCAST_DIR to a larger tmpfs or disk path."
fi
echo "✅ Broadcast storage configured"
echo ""
fi
fi
# Redis pooling settings for embedded proxy
# Variables are ignored if using external proxy
export M3U_PROXY_REDIS_HOST="${REDIS_HOST}"
export M3U_PROXY_REDIS_PORT="${REDIS_SERVER_PORT}"
export M3U_PROXY_REDIS_DB="6" # Default 6 (1-5 used by app)
export M3U_REDIS_ENABLED="true"
export M3U_ENABLE_TRANSCODING_POOLING="true"
# Websockets
export REVERB_PORT="${REVERB_PORT:-36800}" # Default 36800
export REVERB_HOST="${REVERB_HOST:-localhost}"
export REVERB_SCHEME="${REVERB_SCHEME:-http}"
# REVERB_APP_KEY is fixed for now so we can pre-compile assets on build
# See resources/js/echo.js and config/broadcasting.php
# Using reverse proxy, port not needed in client
if [ -z "$REVERB_APP_ID" ]; then
# Generate random app id if not set
export REVERB_APP_ID=$(openssl rand -hex 12)
fi
if [ -z "$REVERB_APP_SECRET" ]; then
# Generate random app secret if not set
export REVERB_APP_SECRET=$(openssl rand -hex 24)
fi
# Set timezone, default to UTC
export TZ="${TZ:-UTC}"
export APP_TIMEZONE="${TZ}"
# Define the php-fpm command (add "-R" flag to allow php-fpm to run as root)
export SUPERVISOR_PHP_COMMAND="/usr/sbin/php-fpm84 -F -R"
# The queue and websockets
export QUEUE_PHP_COMMAND="/usr/bin/php /var/www/html/artisan horizon"
export WEBSOCKET_PHP_COMMAND="/usr/bin/php /var/www/html/artisan reverb:start --host=0.0.0.0 --port=$REVERB_PORT --no-interaction --no-ansi"
# Make sure the laravel project is installed
if [ ! -f "artisan" ]; then
echo "Laravel artisan not found! Make sure project is installed."
exit 1
fi
# Update template files with variable values
if [ -f /etc/php84/php-fpm.d/www.tmpl ]; then
# Variables set in container
envsubst '${FPMUSER} ${FPMGROUP} ${FPMPORT}' < /etc/php84/php-fpm.d/www.tmpl > /etc/php84/php-fpm.d/www.conf
fi
if [ -f /etc/nginx/nginx.tmpl ]; then
envsubst '${NGINX_USER}' < /etc/nginx/nginx.tmpl > /etc/nginx/nginx.conf
fi
if [ -f /etc/redis/redis.tmpl ]; then
envsubst '${REDIS_SERVER_PORT}' < /etc/redis/redis.tmpl > /etc/redis/redis.conf
if [ -n "$REDIS_PASSWORD" ]; then
echo "requirepass ${REDIS_PASSWORD}" >> /etc/redis/redis.conf
fi
fi
if [ -f /etc/nginx/conf.d/laravel.tmpl ]; then
# If app url contains https, don't show port
if [[ "$APP_URL" == *"https"* ]]; then
echo "⚡️ Starting app at: $APP_URL"
else
echo "⚡️ Starting app at: $APP_URL:$APP_PORT"
fi
envsubst '${APP_PORT} ${APP_URL} ${FPMPORT} ${M3U_PROXY_PORT} ${REVERB_PORT} ${M3U_PROXY_NGINX_TARGET}' < /etc/nginx/conf.d/laravel.tmpl > /etc/nginx/conf.d/laravel.conf
fi
if [ -f /etc/nginx/conf.d/xtream.tmpl ]; then
if [ "${XTREAM_ONLY_ENABLED}" = "true" ]; then
echo "⚡️ Starting Xtream API only endpoint at port: $XTREAM_PORT (proxying to 127.0.0.1:$APP_PORT)"
envsubst '${APP_PORT} ${XTREAM_PORT}' < /etc/nginx/conf.d/xtream.tmpl > /etc/nginx/conf.d/xtream.conf
else
echo "⚠️ Xtream API only endpoint disabled (set XTREAM_ONLY_ENABLED=true to enable)"
# Remove any existing xtream config if disabled
rm -f /etc/nginx/conf.d/xtream.conf
fi
fi
# Check for configuration directories and files
config_dir="/var/www/config"
env_file="${config_dir}/env"
db_dir="${config_dir}/database"
db_file="${db_dir}/database.sqlite"
db_file_jobs="${db_dir}/jobs.sqlite"
epg_dir="${config_dir}/epg"
epg_cache_dir="${config_dir}/epg-cache"
logo_cache_dir="${config_dir}/cached-logos"
playlist_dir="${config_dir}/playlist"
playlist_epg_dir="${config_dir}/playlist-epg-files"
backup_dir="${config_dir}/m3u-editor-backups"
log_dir="${config_dir}/logs"
user_plugins_dir="${config_dir}/plugins"
plugin_data_dir="${config_dir}/plugin-data"
queue_log_file="${log_dir}/queue.log"
websockets_log_file="${log_dir}/websocket.log"
laravel_log_file="${log_dir}/laravel.log"
ffmpeg_log_file="${log_dir}/ffmpeg.log"
nginx_log_file="${log_dir}/nginx.log"
postgres_log_file="${log_dir}/postgress.log"
startup_log_file="${log_dir}/startup.log"
# Set log directory variable
export LOG_DIR="${log_dir}"
# Invalid log files, or files that will be created by the app
swoole_log_file="${log_dir}/swoole_http.log"
reverb_log_file="${log_dir}/reverb.log"
horizon_log_file="${log_dir}/horizon.log"
# Setup timezone
if [ -f "/etc/localtime" ]; then
rm /etc/localtime
fi
cp "/usr/share/zoneinfo/$TZ" /etc/localtime
echo "$TZ" > /etc/timezone
echo "📅 Timezone set to $TZ. Current date/time: $(date)"
echo ""
# Create config directories if needed
if [ ! -d "${config_dir}" ]; then
echo "-- Missing config directory - please link a director to '/var/www/config' in the container for data persitence"
exit 0
fi
[ ! -d "${db_dir}" ] && mkdir -p "${db_dir}"
[ ! -d "${epg_dir}" ] && mkdir -p "${epg_dir}"
[ ! -d "${epg_cache_dir}" ] && mkdir -p "${epg_cache_dir}"
[ ! -d "${logo_cache_dir}" ] && mkdir -p "${logo_cache_dir}"
[ ! -d "${playlist_dir}" ] && mkdir -p "${playlist_dir}"
[ ! -d "${playlist_epg_dir}" ] && mkdir -p "${playlist_epg_dir}"
[ ! -d "${backup_dir}" ] && mkdir -p "${backup_dir}"
[ ! -d "${log_dir}" ] && mkdir -p "${log_dir}"
[ ! -d "${user_plugins_dir}" ] && mkdir -p "${user_plugins_dir}"
[ ! -d "${plugin_data_dir}" ] && mkdir -p "${plugin_data_dir}"
# Create environment file if missing
if [ ! -f "${env_file}" ]; then
echo "-- Missing environment file, creating now..."
cp /var/www/html/.env.example "${env_file}"
fi
# Create database files if missing
[ ! -f "${db_file}" ] && touch "${db_file}"
[ ! -f "${db_file_jobs}" ] && touch "${db_file_jobs}"
# Create log files
[ ! -f "${queue_log_file}" ] && touch "${queue_log_file}"
[ ! -f "${websockets_log_file}" ] && touch "${websockets_log_file}"
[ ! -f "${nginx_log_file}" ] && touch "${nginx_log_file}"
# Clear out the log content (could build up over time, clean on container reboot)
echo "" > "/var/log/cron.out.log"
echo "" > "/var/log/cron.err.log"
echo "" > "${queue_log_file}"
echo "" > "${websockets_log_file}"
echo "" > "${nginx_log_file}"
# Cleanup
[ -f "${swoole_log_file}" ] && rm -f "${swoole_log_file}"
[ -f "${reverb_log_file}" ] && rm -f "${reverb_log_file}"
[ -f "${horizon_log_file}" ] && rm -f "${horizon_log_file}"
[ -f "${laravel_log_file}" ] && rm -f "${laravel_log_file}"
[ -f "${ffmpeg_log_file}" ] && rm -f "${ffmpeg_log_file}"
[ -f "${startup_log_file}" ] && rm -f "${startup_log_file}"
# Link environment file
ln -sf "${env_file}" .env
# Remove any stale Laravel bootstrap cache files that might reference dev-only
# service providers (e.g. beyondcode/laravel-dump-server). These files can be
# created during build stages and copied into the image; remove them here so
# the runtime doesn't try to load providers that are not present in vendor.
[ -d "bootstrap/cache" ] && rm -f bootstrap/cache/*.php || true
# Link db files
ln -sf "${db_file}" "database/database.sqlite"
ln -sf "${db_file_jobs}" "database/jobs.sqlite"
# Link logs
rm -rf storage/logs
ln -sf "${log_dir}" storage/
# Link EPG, Playlist, Backups
[ ! -d "storage/app/private/epg" ] && ln -sf "${epg_dir}" storage/app/private/epg
[ ! -d "storage/app/private/epg-cache" ] && ln -sf "${epg_cache_dir}" storage/app/private/epg-cache
[ ! -d "storage/app/private/cached-logos" ] && ln -sf "${logo_cache_dir}" storage/app/private/cached-logos
[ ! -d "storage/app/private/playlist" ] && ln -sf "${playlist_dir}" storage/app/private/playlist
[ ! -d "storage/app/private/playlist-epg-files" ] && ln -sf "${playlist_epg_dir}" storage/app/private/playlist-epg-files
[ ! -d "storage/app/private/m3u-editor-backups" ] && ln -sf "${backup_dir}" storage/app/private/m3u-editor-backups
# Link user-installed plugins and plugin runtime data (persisted across container rebuilds)
# Note: bundled (first-party) plugins live in plugins-bundled/ inside the image and are NOT symlinked.
[ ! -d "plugins" ] && ln -sf "${user_plugins_dir}" plugins
[ ! -d "storage/app/private/plugin-data" ] && ln -sf "${plugin_data_dir}" storage/app/private/plugin-data
# Link Laravel storage
if [ ! -d "public/storage" ]; then
php artisan storage:link
fi
# Set app key if not set
php artisan app:generate-key
# Check for updates
php artisan app:update-check
# Optimize Laravel
php artisan optimize
# Optimize Filament
php artisan filament:optimize
# Make sure app is "up"
php artisan up
# Set Sqlite WAL mode
echo "🗂️ Enabling SQLite WAL mode..."
if [ "${ENABLE_POSTGRES}" != "true" ]; then
# If postgres not enabled, enable WAL on main SQLite database
php artisan sqlite:wal-enable sqlite
fi
# Always enable WAL on jobs database
php artisan sqlite:wal-enable jobs
echo ""
# If postgres is enabled, start it
if [ "${ENABLE_POSTGRES}" = "true" ]; then
# Check if DB_HOST is set from the environment (passed by Docker)
# and is not localhost or 127.0.0.1, indicating an external DB.
# Note: Ensure DB_HOST is actually passed to the container's environment.
# Your docker-compose.yml shows it is.
if [ -n "${DB_HOST}" ] && [ "${DB_HOST}" != "localhost" ] && [ "${DB_HOST}" != "127.0.0.1" ]; then
echo "📡 Using external Postgres server configured at ${DB_HOST}. Skipping local Postgres setup."
# Even if external, ensure the log file directory exists for other potential logs
[ ! -f "${postgres_log_file}" ] && touch "${postgres_log_file}"
echo "" > "${postgres_log_file}" # Clear it or add a note about external DB
echo "INFO: Configured to use external PostgreSQL at ${DB_HOST}:${DB_PORT:-5432}" >> "${postgres_log_file}"
chown $WWWUSER:$WWWGROUP "${postgres_log_file}"
else
echo "📡 Starting local Postgres server setup..."
# Create log file for local Postgres
[ ! -f "${postgres_log_file}" ] && touch "${postgres_log_file}"
echo "" > "${postgres_log_file}"
chown $WWWUSER:$WWWGROUP "${postgres_log_file}"
# Make sure permissions are correct for $PGDATA *if it exists*
# These lines were causing issues if $PGDATA (e.g. /var/lib/postgresql/data) didn't exist.
# initdb is responsible for creating $PGDATA if it's not there.
# The parent directory /var/lib/postgresql is created in Dockerfile and chowned.
if [ -d "$PGDATA" ]; then
echo "- $PGDATA directory exists. Ensuring correct permissions..."
chown -R $WWWUSER:$WWWGROUP "$PGDATA"
chmod -R 700 "$PGDATA"
else
echo "- $PGDATA does not exist. 'initdb' will attempt to create it."
fi
# Ensure data & run initdb on first-run
if [ ! -f "$PGDATA/PG_VERSION" ]; then
pwfile="/tmp/.pg_pwfile"
echo "$PG_PASSWORD" > "$pwfile"
chown $WWWUSER:$WWWGROUP "$pwfile" && chmod 600 "$pwfile"
echo "- Initializing local Postgres database in $PGDATA (User: $WWWUSER, DB: $PG_DATABASE)..."
# Ensure the parent directory of $PGDATA exists and is writable by $WWWUSER
# Dockerfile creates and chowns /var/lib/postgresql to $WWWUSER:$WWWGROUP
if ! su-exec $WWWUSER initdb --username=postgres --pwfile="$pwfile" -D "$PGDATA"; then
echo "FATAL: Failed to initialize local Postgres database. Check permissions on /var/lib/postgresql and logs at ${postgres_log_file}."
exit 1
fi
rm -f "$pwfile"
echo "- Local Postgres database initialized."
else
echo "- Local Postgres database already initialized at $PGDATA."
fi
# Create socket dir and start Postgres
mkdir -p /run/postgresql && chown $WWWUSER:$WWWGROUP /run/postgresql
echo "- Starting local Postgres process..."
su-exec $WWWUSER postgres -D "$PGDATA" -h 0.0.0.0 -p "$PG_PORT" >> "${postgres_log_file}" 2>&1 &
# Wait until it's ready
dots=""
max_wait_seconds=${PG_MAX_WAIT_SECONDS}
wait_interval=1
elapsed_wait=0
echo "- Waiting for local Postgres to be ready (max ${max_wait_seconds}s)..."
until su-exec $WWWUSER pg_isready -h localhost -p "$PG_PORT" --quiet; do
dots+="."
echo "⏳ Waiting for local Postgres${dots}"
sleep $wait_interval
elapsed_wait=$((elapsed_wait + wait_interval))
if [ $elapsed_wait -ge $max_wait_seconds ]; then
echo "FATAL: Local Postgres failed to start within ${max_wait_seconds} seconds. Check logs: ${postgres_log_file}"
exit 1
fi
done
echo "✅ Local Postgres is ready."
echo ""
# Idempotent role + database creation for local postgres
echo "- Configuring local Postgres roles and database (User: ${PG_USER}, DB: ${PG_DATABASE})..."
# Note: PG_USER, PG_PASSWORD, PG_DATABASE are used here for the local instance.
# For external DB, these would be DB_USER, DB_PASSWORD, DB_DATABASE from .env
su-exec $WWWUSER psql \
--quiet \
--tuples-only \
--no-align \
-U postgres \
-h localhost -p "$PG_PORT" \
<<-EOSQL
DO \$\$
BEGIN
-- ensure your app user exists and has the right password
IF NOT EXISTS (
SELECT FROM pg_catalog.pg_roles WHERE rolname = '${PG_USER}'
) THEN
CREATE ROLE "${PG_USER}" LOGIN PASSWORD '${PG_PASSWORD}';
ELSE
ALTER ROLE "${PG_USER}" WITH PASSWORD '${PG_PASSWORD}';
END IF;
-- ensure the postgres superuser also has the password (using PG_PASSWORD for local setup)
ALTER ROLE postgres WITH PASSWORD '${PG_PASSWORD}';
END
\$\$;
EOSQL
# If the database exists but is owned by someone else, reassign it (for local postgres)
if su-exec $WWWUSER psql -U postgres -h localhost -p "$PG_PORT" --quiet -tAc "SELECT 1 FROM pg_database WHERE datname='${PG_DATABASE}'" | grep -q 1; then
echo "- Database '${PG_DATABASE}' exists. Ensuring correct owner ('${PG_USER}')."
su-exec $WWWUSER psql -U postgres -h localhost -p "$PG_PORT" --quiet <<-EOSQL
-- change the database owner
ALTER DATABASE "${PG_DATABASE}" OWNER TO "${PG_USER}";
-- change ownership of all schemas and tables in that DB
\c "${PG_DATABASE}" postgres
DO \$\$
DECLARE
tbl record;
BEGIN
-- first change schema owner
EXECUTE 'ALTER SCHEMA public OWNER TO "${PG_USER}"';
-- then change each table
FOR tbl IN
SELECT table_schema, table_name
FROM information_schema.tables
WHERE table_schema = 'public'
LOOP
EXECUTE format(
'ALTER TABLE %I.%I OWNER TO "${PG_USER}"',
tbl.table_schema, tbl.table_name
);
END LOOP;
END
\$\$;
EOSQL
else
echo "- Database '${PG_DATABASE}' does not exist. Creating it with owner '${PG_USER}'."
su-exec $WWWUSER psql -U postgres -h localhost -p "$PG_PORT" --quiet -v ON_ERROR_STOP=1 <<-EOSQL
CREATE DATABASE "${PG_DATABASE}"
OWNER = "${PG_USER}"
ENCODING = 'UTF8'
LC_COLLATE = 'C'
LC_CTYPE = 'C.UTF-8'
TEMPLATE = template0;
EOSQL
fi
# Create extensions in the database (after database is ensured to exist)
echo "- Creating PostgreSQL extensions..."
su-exec $WWWUSER psql \
--quiet \
-U postgres \
-h localhost -p "$PG_PORT" \
-d "${PG_DATABASE}" \
<<-EOSQL
-- Create extensions if they don't exist
CREATE EXTENSION IF NOT EXISTS fuzzystrmatch;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
EOSQL
echo "✅ Local Postgres setup and configuration complete."
fi
fi
# Run migrations
echo "💾 Running migrations..."
php artisan migrate --force
# Jobs database uses a separate SQLite file for queue job tracking (see config/database.php)
php artisan migrate --database=jobs --force
# Register webhook for real-time cache invalidation
# This works for both embedded and external m3u-proxy modes
# The command will check if m3u-proxy is configured before attempting registration
# Command will be run automatically via `supervisor` since it's included in the supervisord.conf as a one-time command
# Note: This command is also scheduled to run every 5 minutes in app/Console/Kernel.php to handle proxy restarts, delayed startup, etc.
# Ensure composer dir is writable
[ ! -d /.composer ] && mkdir /.composer
chmod -R ugo+rw /.composer
# If passed custom commands, execute them, else run supervisord
if [ $# -gt 0 ]; then
if [ "$SUPERVISOR_PHP_USER" = "root" ]; then
exec "$@"
else
su-exec $WWWUSER "$@"
fi
else
echo ""
echo "🚀 Starting services..."
echo ""
# Start supervisord
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
fi