Files
ladill-events/deploy/deploy.sh
T
isaaccladandCursor 2c1c3cdd3e
Deploy Ladill Events / deploy (push) Successful in 30s
Enforce Events favicon everywhere and fix deploy pipeline failures.
Add favicon partial to all standalone pages, regenerate the Events icon asset, and stop marking deploys failed when the nginx vhost step cannot sudo.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 11:56:27 +00:00

250 lines
7.3 KiB
Bash
Executable File

#!/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-events}"
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
}
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
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" >&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-events-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"