#!/usr/bin/env bash # Fast on-host release deploy (same model as climpme/web/deploy/deploy.sh). set -Eeuo pipefail APP_ROOT="${LADILL_APP_ROOT:-/var/www/ladill-woo-manager}" RELEASES_DIR="$APP_ROOT/releases" SHARED_DIR="$APP_ROOT/shared" CURRENT_LINK="$APP_ROOT/current" RELEASE_ARCHIVE="${LADILL_RELEASE_ARCHIVE:-/tmp/ladill-release.tgz}" KEEP_RELEASES="${LADILL_KEEP_RELEASES:-5}" STAMP="$(date +%Y%m%d%H%M%S)" NEW_RELEASE="$RELEASES_DIR/$STAMP" log() { printf '[%s] %s\n' "$(date '+%F %T')" "$*" } bootstrap_shared_env() { local candidate="" if [ -f "$SHARED_DIR/.env" ]; then return 0 fi for candidate in "$CURRENT_LINK/.env" "$APP_ROOT/.env"; do if [ -f "$candidate" ]; then cp -fL "$candidate" "$SHARED_DIR/.env" log "Bootstrapped shared .env from $candidate" return 0 fi done return 1 } sync_smtp_env_from_platform() { local platform_env="${LADILL_PLATFORM_ENV:-/var/www/ladill.com/shared/.env}" local target="$SHARED_DIR/.env" local key_pattern='^(MAIL_MAILER|MAIL_SCHEME|MAIL_ENCRYPTION|MAIL_HOST|MAIL_PORT|MAIL_USERNAME|MAIL_PASSWORD|MAIL_URL|MAIL_EHLO_DOMAIN|MAIL_FROM_ADDRESS)=' if [ ! -f "$platform_env" ] || [ ! -f "$target" ]; then log "Skipping SMTP env sync (platform or app .env missing)" return 0 fi local tmp merged tmp="$(mktemp)" merged="$(mktemp)" grep -v -E "$key_pattern" "$target" > "$tmp" || true grep -E "$key_pattern" "$platform_env" > "$merged" || true if [ ! -s "$merged" ]; then rm -f "$tmp" "$merged" log "No MAIL_* transport vars in $platform_env — skipping SMTP sync" return 0 fi cat "$tmp" "$merged" > "${target}.new" mv "${target}.new" "$target" rm -f "$tmp" "$merged" log "Synced SMTP settings from $platform_env" } ensure_writable_paths() { local paths=("$@") [ "${#paths[@]}" -gt 0 ] || return 0 chmod -R ug+rwX "${paths[@]}" 2>/dev/null || true if command -v find >/dev/null 2>&1; then find "${paths[@]}" -type d -exec chmod ug+rwx {} + 2>/dev/null || true find "${paths[@]}" -type d -exec chmod g+s {} + 2>/dev/null || true fi } normalize_shared_permissions() { local owner="${LADILL_DEPLOY_USER:-deploy}" local group="${LADILL_DEPLOY_GROUP:-www-data}" local shared_paths=("$SHARED_DIR/storage" "$SHARED_DIR/bootstrap-cache") id -u "$owner" >/dev/null 2>&1 || return 0 getent group "$group" >/dev/null 2>&1 || return 0 if chown -R "$owner:$group" "${shared_paths[@]}" 2>/dev/null; then : elif command -v sudo >/dev/null 2>&1; then sudo -n chown -R "$owner:$group" "${shared_paths[@]}" 2>/dev/null || true fi ensure_writable_paths "${shared_paths[@]}" } run_artisan() { local command="$1" if [ -f "$NEW_RELEASE/artisan" ]; then (cd "$NEW_RELEASE" && php artisan ${command}) fi } uses_sqlite() { [ -f "$SHARED_DIR/.env" ] && grep -Eq '^DB_CONNECTION=sqlite([[:space:]]*)$' "$SHARED_DIR/.env" } sqlite_database_setting() { [ -f "$SHARED_DIR/.env" ] || return 1 grep -E '^DB_DATABASE=' "$SHARED_DIR/.env" | tail -n 1 | cut -d= -f2- | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//" } prepare_sqlite_database() { local configured_path="" local db_name="" local shared_sqlite_path="" local current_sqlite_path="" local release_sqlite_path="" configured_path="$(sqlite_database_setting || true)" [ -n "$configured_path" ] || configured_path="database/database.sqlite" if [[ "$configured_path" = /* ]]; then shared_sqlite_path="$configured_path" mkdir -p "$(dirname "$shared_sqlite_path")" else db_name="$(basename "$configured_path")" shared_sqlite_path="$SHARED_DIR/database/$db_name" current_sqlite_path="$CURRENT_LINK/$configured_path" release_sqlite_path="$NEW_RELEASE/$configured_path" mkdir -p "$SHARED_DIR/database" "$(dirname "$release_sqlite_path")" if [ ! -f "$shared_sqlite_path" ]; then if [ -f "$current_sqlite_path" ]; then cp -fL "$current_sqlite_path" "$shared_sqlite_path" log "Bootstrapped shared sqlite database from $current_sqlite_path" elif [ -f "$APP_ROOT/$configured_path" ]; then cp -fL "$APP_ROOT/$configured_path" "$shared_sqlite_path" log "Bootstrapped shared sqlite database from $APP_ROOT/$configured_path" else touch "$shared_sqlite_path" log "Created shared sqlite database at $shared_sqlite_path" fi fi rm -f "$release_sqlite_path" ln -sfn "$shared_sqlite_path" "$release_sqlite_path" ensure_writable_paths "$SHARED_DIR/database" return 0 fi if [ ! -f "$shared_sqlite_path" ]; then touch "$shared_sqlite_path" log "Created shared sqlite database at $shared_sqlite_path" fi ensure_writable_paths "$(dirname "$shared_sqlite_path")" } log "Preparing release $STAMP" [ -f "$RELEASE_ARCHIVE" ] || { echo "Release archive not found: $RELEASE_ARCHIVE" >&2; exit 1; } mkdir -p "$RELEASES_DIR" "$SHARED_DIR" "$NEW_RELEASE" tar -xzf "$RELEASE_ARCHIVE" -C "$NEW_RELEASE" chmod -R u+rwX "$NEW_RELEASE" 2>/dev/null || true log "Linking shared paths" mkdir -p "$SHARED_DIR/storage/"{app/public,app/private,framework/{cache/data,sessions,testing,views},logs} mkdir -p "$SHARED_DIR/bootstrap-cache" normalize_shared_permissions if bootstrap_shared_env; then sync_smtp_env_from_platform ln -sfn "$SHARED_DIR/.env" "$NEW_RELEASE/.env" else echo "Missing deployment environment file. Expected $SHARED_DIR/.env or an existing $CURRENT_LINK/.env / $APP_ROOT/.env to bootstrap from." >&2 exit 1 fi rm -rf "$NEW_RELEASE/storage" ln -sfn "$SHARED_DIR/storage" "$NEW_RELEASE/storage" mkdir -p "$NEW_RELEASE/bootstrap" rm -rf "$NEW_RELEASE/bootstrap/cache" ln -sfn "$SHARED_DIR/bootstrap-cache" "$NEW_RELEASE/bootstrap/cache" normalize_shared_permissions if uses_sqlite; then log "Preparing shared sqlite database" prepare_sqlite_database if id -u www-data >/dev/null 2>&1; then chgrp www-data "$SHARED_DIR/database" 2>/dev/null || true find "$SHARED_DIR/database" -type f -exec chgrp www-data {} + 2>/dev/null || true find "$SHARED_DIR/database" -type d -exec chgrp www-data {} + 2>/dev/null || true fi fi log "Installing PHP dependencies" if [ -f "$NEW_RELEASE/composer.json" ]; then if [ "$(id -u)" -eq 0 ]; then export COMPOSER_ALLOW_SUPERUSER=1 fi export COMPOSER_HOME="${COMPOSER_HOME:-/home/deploy/.composer}" ( cd "$NEW_RELEASE" composer install \ --no-dev \ --prefer-dist \ --no-interaction \ --no-progress \ --optimize-autoloader \ --classmap-authoritative ) fi log "Running migrations" if ! run_artisan "migrate --force"; then log "Migration failed — clearing shared bootstrap cache" run_artisan "optimize:clear" || true exit 1 fi switch_current_release() { if ln -sfnT "$NEW_RELEASE" "$CURRENT_LINK" 2>/dev/null; then return 0 fi if command -v sudo >/dev/null 2>&1 && sudo -n ln -sfnT "$NEW_RELEASE" "$CURRENT_LINK"; then return 0 fi echo "Unable to update current release symlink at $CURRENT_LINK (run as root: ln -sfnT $NEW_RELEASE $CURRENT_LINK)" >&2 return 1 } log "Switching current release" if ! switch_current_release; then echo "Failed to point $CURRENT_LINK at $NEW_RELEASE" >&2 exit 1 fi LIVE_REV="$(cat "$CURRENT_LINK/REVISION" 2>/dev/null || echo unknown)" log "Live release: $STAMP (revision $LIVE_REV)" log "Optimizing Laravel" if [ -L "$CURRENT_LINK" ] && [ -f "$CURRENT_LINK/artisan" ]; then ( cd "$CURRENT_LINK" php artisan storage:link || true php artisan optimize:clear || true php artisan config:cache || true php artisan route:cache || true php artisan view:cache || true ) fi log "Reloading services" if command -v systemctl >/dev/null 2>&1; then systemctl reload php8.4-fpm 2>/dev/null || sudo -n systemctl reload php8.4-fpm systemctl reload nginx 2>/dev/null || sudo -n systemctl reload nginx fi log "Restarting queues" if [ -L "$CURRENT_LINK" ] && [ -f "$CURRENT_LINK/artisan" ]; then (cd "$CURRENT_LINK" && php artisan queue:restart) || true fi if command -v supervisorctl >/dev/null 2>&1; then supervisorctl restart ladill-woo-manager-worker:* 2>/dev/null || true fi log "Cleaning old releases" mapfile -t OLD_RELEASES < <(ls -1dt "$RELEASES_DIR"/* 2>/dev/null | tail -n "+$((KEEP_RELEASES + 1))" || true) if [ "${#OLD_RELEASES[@]}" -gt 0 ]; then chmod -R u+rwX "${OLD_RELEASES[@]}" 2>/dev/null || true rm -rf "${OLD_RELEASES[@]}" 2>/dev/null || true fi log "Deploy completed: $STAMP"