#!/bin/bash set -Eeuo pipefail echo "=========================================" echo " TerManager2 - Entrypoint" echo "=========================================" warn() { echo "[!] $*" } retry() { local attempts="$1" local delay="$2" shift 2 local n=1 until "$@"; do if [ "$n" -ge "$attempts" ]; then return 1 fi warn "Command failed (attempt ${n}/${attempts}). Retrying in ${delay}s..." sleep "$delay" n=$((n + 1)) done } upsert_env() { local key="$1" local value="$2" local env_file="$3" if grep -q "^${key}=" "$env_file"; then sed -i "s|^${key}=.*|${key}=${value}|" "$env_file" else echo "${key}=${value}" >> "$env_file" fi } read_env_value() { local key="$1" local env_file="$2" if [ ! -f "$env_file" ]; then return 0 fi local line line=$(grep -E "^${key}=" "$env_file" | tail -n 1 || true) if [ -z "$line" ]; then return 0 fi local value="${line#*=}" value="${value%\"}" value="${value#\"}" printf '%s' "$value" } # ----------------------------------------------- # 0. Application code is bind-mounted from host # ----------------------------------------------- echo "[✓] Using bind-mounted application code from host." # ----------------------------------------------- # 0b. Create required directories & fix permissions # ----------------------------------------------- mkdir -p storage/framework/{cache,sessions,views} mkdir -p storage/logs mkdir -p storage/app mkdir -p bootstrap/cache chown -R www-data:www-data storage bootstrap/cache # ----------------------------------------------- # 1. .env file (must exist before composer/artisan) # ----------------------------------------------- if [ ! -f .env ]; then echo "[*] Creating .env from .env.example..." cp .env.example .env fi # Override .env values with Docker environment variables (if set) # This ensures Dokploy env vars take precedence if [ -n "${APP_KEY:-}" ]; then upsert_env "APP_KEY" "${APP_KEY}" .env fi if [ -n "${APP_URL:-}" ]; then upsert_env "APP_URL" "${APP_URL}" .env fi if [ -n "${DB_HOST:-}" ]; then upsert_env "DB_HOST" "${DB_HOST}" .env fi if [ -n "${DB_DATABASE:-}" ]; then upsert_env "DB_DATABASE" "${DB_DATABASE}" .env fi if [ -n "${DB_USERNAME:-}" ]; then upsert_env "DB_USERNAME" "${DB_USERNAME}" .env fi if [ -n "${DB_PASSWORD:-}" ]; then upsert_env "DB_PASSWORD" "${DB_PASSWORD}" .env fi if [ -n "${REDIS_HOST:-}" ]; then upsert_env "REDIS_HOST" "${REDIS_HOST}" .env fi if [ -n "${REDIS_PASSWORD:-}" ]; then upsert_env "REDIS_PASSWORD" "${REDIS_PASSWORD}" .env fi echo "[✓] .env file ready." # ----------------------------------------------- # 2. Composer install (must run before artisan commands) # ----------------------------------------------- if [ ! -f vendor/autoload.php ]; then echo "[*] Installing Composer dependencies..." retry 3 5 composer install --no-interaction --prefer-dist --optimize-autoloader --no-progress else echo "[✓] Composer dependencies already installed." fi # ----------------------------------------------- # 3. Application key (persisted in storage volume) # ----------------------------------------------- KEY_FILE="/var/www/html/storage/app/.app_key" if [ -f "$KEY_FILE" ]; then # Restore key from persistent volume STORED_KEY=$(cat "$KEY_FILE") upsert_env "APP_KEY" "$STORED_KEY" .env echo "[✓] Application key restored from storage." elif grep -q "^APP_KEY=$" .env; then # No stored key and .env has empty key: generate new one echo "[*] Generating application key..." php artisan key:generate --ansi # Save to persistent volume grep '^APP_KEY=' .env | cut -d= -f2- > "$KEY_FILE" echo "[✓] Application key saved to persistent storage." else # .env already has a key (from env var override or .env.example), persist it grep '^APP_KEY=' .env | cut -d= -f2- > "$KEY_FILE" echo "[✓] Application key already set, saved to persistent storage." fi # ----------------------------------------------- # 4. NPM install & build assets # ----------------------------------------------- if [ ! -d node_modules ]; then echo "[*] Installing NPM dependencies..." if [ -f package-lock.json ]; then retry 3 5 npm ci --no-audit --no-fund else retry 3 5 npm install --no-audit --no-fund fi else echo "[✓] NPM dependencies already installed." fi if [ ! -f public/build/manifest.json ]; then echo "[*] Building frontend assets..." npm run build else echo "[✓] Frontend assets already built." fi # ----------------------------------------------- # 5. Storage link # ----------------------------------------------- if [ ! -L public/storage ]; then echo "[*] Creating storage symlink..." php artisan storage:link else echo "[✓] Storage symlink already exists." fi # ----------------------------------------------- # 7. Run migrations # ----------------------------------------------- echo "[*] Running database migrations..." retry 10 3 php artisan migrate --force # ----------------------------------------------- # 7b. Seed database on first container startup only # ----------------------------------------------- SEED_MARKER_FILE="/var/www/html/storage/framework/.runtime_db_seeded" RUN_DB_SEED_ON_FIRST_START="${RUN_DB_SEED_ON_FIRST_START:-true}" if [ "$RUN_DB_SEED_ON_FIRST_START" = "true" ]; then if [ ! -f "$SEED_MARKER_FILE" ]; then echo "[*] First startup detected. Running database seed..." retry 5 3 php artisan db:seed --force touch "$SEED_MARKER_FILE" echo "[✓] Database seed completed and startup marker saved." else echo "[✓] Seed already executed on a previous startup." fi else echo "[i] RUN_DB_SEED_ON_FIRST_START=false, skipping automatic seed." fi # ----------------------------------------------- # 7c. Ensure initial admin exists (first startup) # ----------------------------------------------- ENSURE_INITIAL_ADMIN_ON_EMPTY_DB="${ENSURE_INITIAL_ADMIN_ON_EMPTY_DB:-true}" INITIAL_ADMIN_NAME_VALUE="${INITIAL_ADMIN_NAME:-$(read_env_value INITIAL_ADMIN_NAME .env)}" INITIAL_ADMIN_EMAIL_VALUE="${INITIAL_ADMIN_EMAIL:-$(read_env_value INITIAL_ADMIN_EMAIL .env)}" INITIAL_ADMIN_PASSWORD_VALUE="${INITIAL_ADMIN_PASSWORD:-$(read_env_value INITIAL_ADMIN_PASSWORD .env)}" if [ "$ENSURE_INITIAL_ADMIN_ON_EMPTY_DB" = "true" ]; then echo "[*] Ensuring initial administrator account..." if ! php artisan app:create-initial-admin \ --name="$INITIAL_ADMIN_NAME_VALUE" \ --email="$INITIAL_ADMIN_EMAIL_VALUE" \ --password="$INITIAL_ADMIN_PASSWORD_VALUE" \ --no-interaction; then warn "Initial admin creation failed. Set INITIAL_ADMIN_NAME, INITIAL_ADMIN_EMAIL and INITIAL_ADMIN_PASSWORD." exit 1 fi else echo "[i] ENSURE_INITIAL_ADMIN_ON_EMPTY_DB=false, skipping initial admin creation check." fi # ----------------------------------------------- # 8. Cache config/routes/views # ----------------------------------------------- echo "[*] Caching configuration..." php artisan config:cache || warn "config:cache failed; continuing startup." php artisan route:cache || warn "route:cache failed; continuing startup." php artisan view:cache || warn "view:cache failed; continuing startup." echo "=========================================" echo " TerManager2 - Ready!" echo "=========================================" # ----------------------------------------------- # Execute CMD (default: php-fpm) # ----------------------------------------------- exec "$@"