From 69481b87a9aba07e5041fd61e9d349eb61826daf Mon Sep 17 00:00:00 2001 From: Lucian Behind The Scenes Date: Thu, 5 Feb 2026 18:13:34 +0200 Subject: [PATCH] refactor: migrate Deployer from hooks to script-based architecture Replace hook-based deployment with centralized shell scripts for better reusability and maintainability. The new structure separates deployment logic (deploy.sh), scheduled tasks (cron.sh), and queue workers (supervisor.sh) into dedicated scripts. BREAKING CHANGE: deployer.yml moved to .deployer/inventory.yml and hook files removed. Users must update their deployment configuration. --- .deployer/hooks/1-building.sh | 36 ------ .deployer/hooks/3-finishing.sh | 24 ---- deployer.yml => .deployer/inventory.yml | 0 .deployer/scripts/cron.sh | 48 ++++++++ .../2-releasing.sh => scripts/deploy.sh} | 108 ++++++++++++------ .deployer/scripts/supervisor.sh | 67 +++++++++++ 6 files changed, 186 insertions(+), 97 deletions(-) delete mode 100644 .deployer/hooks/1-building.sh delete mode 100644 .deployer/hooks/3-finishing.sh rename deployer.yml => .deployer/inventory.yml (100%) create mode 100644 .deployer/scripts/cron.sh rename .deployer/{hooks/2-releasing.sh => scripts/deploy.sh} (51%) create mode 100644 .deployer/scripts/supervisor.sh diff --git a/.deployer/hooks/1-building.sh b/.deployer/hooks/1-building.sh deleted file mode 100644 index 66a0287..0000000 --- a/.deployer/hooks/1-building.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# -# Building - Runs immediately after updating the code -# ---- -# -# Environment variables provided by Deployer PHP: -# DEPLOYER_RELEASE_PATH - Absolute path to the new release directory -# DEPLOYER_SHARED_PATH - Absolute path to the shared/ directory -# DEPLOYER_CURRENT_PATH - Absolute path to the current/ symlink -# DEPLOYER_REPO_PATH - Path to the git cache (bare) repository -# DEPLOYER_DOMAIN - Site domain (example.com) -# DEPLOYER_BRANCH - Git branch being deployed -# DEPLOYER_PHP_VERSION - PHP version selected for this site (e.g. 8.4) -# DEPLOYER_PHP - Absolute path to the PHP binary (e.g. /usr/bin/php8.4) -# -# You're automatically in the DEPLOYER_RELEASE_PATH directory at this point: - -echo "→ Building release..." - -if [[ -f composer.json ]]; then - echo "→ Installing Composer dependencies..." - - composer_bin="$(command -v composer || true)" - "${DEPLOYER_PHP}" "${composer_bin}" install --no-interaction --no-dev --optimize-autoloader -fi - -if [[ -f package.json ]]; then - echo "→ Installing frontend dependencies..." - bun install --frozen-lockfile - - echo "→ Building frontend assets..." - bun run build -fi diff --git a/.deployer/hooks/3-finishing.sh b/.deployer/hooks/3-finishing.sh deleted file mode 100644 index 258afb9..0000000 --- a/.deployer/hooks/3-finishing.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# -# Finishing - Runs after the new release is activated (at this point we're done) -# ---- -# -# Environment variables provided by Deployer PHP: -# DEPLOYER_RELEASE_PATH - Absolute path to the new release directory -# DEPLOYER_SHARED_PATH - Absolute path to the shared/ directory -# DEPLOYER_CURRENT_PATH - Absolute path to the current/ symlink -# DEPLOYER_REPO_PATH - Path to the git cache (bare) repository -# DEPLOYER_DOMAIN - Site domain (example.com) -# DEPLOYER_BRANCH - Git branch being deployed -# DEPLOYER_PHP_VERSION - PHP version selected for this site (e.g. 8.4) -# DEPLOYER_PHP - Absolute path to the PHP binary (e.g. /usr/bin/php8.4) -# -# You're automatically in the DEPLOYER_RELEASE_PATH directory at this point. -# -# Also, PHP-FPM is automatically reloaded for you so you don't have to do it manually: -# sudo systemctl reload "php${DEPLOYER_PHP_VERSION}-fpm" -# - -echo "→ Finished releasing..." diff --git a/deployer.yml b/.deployer/inventory.yml similarity index 100% rename from deployer.yml rename to .deployer/inventory.yml diff --git a/.deployer/scripts/cron.sh b/.deployer/scripts/cron.sh new file mode 100644 index 0000000..1a0d678 --- /dev/null +++ b/.deployer/scripts/cron.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# +# Cron Script - Run scheduled tasks for supported frameworks +# ---- +# +# This should be executed by cron on the remote server. +# +# Environment variables provided by runner script: +# DEPLOYER_RELEASE_PATH - Absolute path to the current release directory +# DEPLOYER_SHARED_PATH - Absolute path to the shared/ directory +# DEPLOYER_CURRENT_PATH - Absolute path to the current/ symlink +# DEPLOYER_DOMAIN - Site domain (example.com) +# DEPLOYER_BRANCH - Git branch currently deployed +# DEPLOYER_PHP - Absolute path to the PHP binary (e.g. /usr/bin/php8.4) +# + +cd "${DEPLOYER_CURRENT_PATH}" + +# ---- +# Framework Detection +# ---- + +framework="" + +if [[ -f artisan ]]; then + framework="laravel" +elif [[ -f bin/console ]]; then + framework="symfony" +elif [[ -f spark ]]; then + framework="codeigniter" +fi + +# ---- +# Scheduled Tasks +# ---- + +if [[ $framework == "laravel" ]]; then + "${DEPLOYER_PHP}" artisan schedule:run --no-interaction +elif [[ $framework == "symfony" ]]; then + # Process messages for up to 55 seconds then exit (allows cron to restart fresh) + "${DEPLOYER_PHP}" bin/console messenger:consume async --time-limit=55 --no-interaction +else + echo "Unsupported framework for cron.sh. Customize this script for your app." >&2 + exit 1 +fi diff --git a/.deployer/hooks/2-releasing.sh b/.deployer/scripts/deploy.sh similarity index 51% rename from .deployer/hooks/2-releasing.sh rename to .deployer/scripts/deploy.sh index 6bc54cf..5ce4b4d 100644 --- a/.deployer/hooks/2-releasing.sh +++ b/.deployer/scripts/deploy.sh @@ -3,7 +3,7 @@ set -euo pipefail # -# Releasing - Runs immediately after building and right before the new release is activated +# Deploy Script - Link shared resources, build, and prepare for release activation # ---- # # Environment variables provided by Deployer PHP: @@ -18,8 +18,6 @@ set -euo pipefail # # You're automatically in the DEPLOYER_RELEASE_PATH directory at this point: -echo "→ Preparing release..." - # ---- # Framework Detection # ---- @@ -35,34 +33,86 @@ elif [[ -f spark ]]; then fi # ---- -# Laravel +# Shared Resources # ---- +# Laravel if [[ $framework == "laravel" ]]; then - - # - # Ensure shared data - # ---- - - echo "→ Ensuring shared data..." - + echo "→ Ensuring shared storage directories..." mkdir -p "${DEPLOYER_SHARED_PATH}/storage/"{app,framework,logs} mkdir -p "${DEPLOYER_SHARED_PATH}/storage/framework/"{cache,sessions,views} - "${DEPLOYER_PHP}" artisan storage:link + + echo "→ Linking shared storage..." + rm -rf "${DEPLOYER_RELEASE_PATH}/storage" + ln -sfn "${DEPLOYER_SHARED_PATH}/storage" "${DEPLOYER_RELEASE_PATH}/storage" echo "→ Ensuring shared sqlite database..." mkdir -p "${DEPLOYER_SHARED_PATH}/database" + touch "${DEPLOYER_SHARED_PATH}/database/database.sqlite" + + echo "→ Linking shared database.sqlite..." + ln -sf "${DEPLOYER_SHARED_PATH}/database/database.sqlite" "${DEPLOYER_RELEASE_PATH}/database/database.sqlite" +fi + +# Symfony (uncomment as needed) +# if [[ $framework == "symfony" ]]; then +# echo "→ Ensuring shared var directories..." +# mkdir -p "${DEPLOYER_SHARED_PATH}/var/"{log,sessions} +# +# echo "→ Linking shared var..." +# rm -rf "${DEPLOYER_RELEASE_PATH}/var" +# ln -sfn "${DEPLOYER_SHARED_PATH}/var" "${DEPLOYER_RELEASE_PATH}/var" +# fi + +# CodeIgniter (uncomment as needed) +# if [[ $framework == "codeigniter" ]]; then +# echo "→ Ensuring shared writable directories..." +# mkdir -p "${DEPLOYER_SHARED_PATH}/writable/"{cache,logs,session,uploads} +# +# echo "→ Linking shared writable..." +# rm -rf "${DEPLOYER_RELEASE_PATH}/writable" +# ln -sfn "${DEPLOYER_SHARED_PATH}/writable" "${DEPLOYER_RELEASE_PATH}/writable" +# fi + +# .env (framework-agnostic) +if [[ -f "${DEPLOYER_SHARED_PATH}/.env" ]]; then + echo "→ Linking shared .env..." + ln -sf "${DEPLOYER_SHARED_PATH}/.env" "${DEPLOYER_RELEASE_PATH}/.env" +fi + +# ---- +# Build +# ---- + +echo "→ Building release..." + +if [[ -f composer.json ]]; then + echo "→ Installing Composer dependencies..." + + composer_bin="$(command -v composer || true)" + "${DEPLOYER_PHP}" "${composer_bin}" install --no-interaction --no-dev --optimize-autoloader +fi + +if [[ -f package.json ]]; then + echo "→ Installing frontend dependencies..." + bun install --frozen-lockfile + + echo "→ Building frontend assets..." + bun run build +fi + +# ---- +# Release Preparation +# ---- - shared_db="${DEPLOYER_SHARED_PATH}/database/database.sqlite" - touch "${shared_db}" +# Laravel +if [[ $framework == "laravel" ]]; then - release_db="${DEPLOYER_RELEASE_PATH}/database/database.sqlite" - if [ ! -e "${release_db}" ] && [ ! -L "${release_db}" ]; then - ln -s "${shared_db}" "${release_db}" - fi + echo "→ Creating storage symlink..." + "${DEPLOYER_PHP}" artisan storage:link echo "→ Ensuring app key exists..." - php artisan key:generate || true + "${DEPLOYER_PHP}" artisan key:generate || true # # Run migrations @@ -80,16 +130,8 @@ if [[ $framework == "laravel" ]]; then fi -# ---- -# Symfony -# ---- -# Uncomment as needed - +# Symfony (uncomment as needed) # if [[ $framework == "symfony" ]]; then -# # Ensure shared data -# echo "→ Ensuring shared data..." -# mkdir -p "${DEPLOYER_SHARED_PATH}/var/"{cache,log,sessions} -# # # Run migrations # echo "→ Running migrations..." # "${DEPLOYER_PHP}" bin/console doctrine:migrations:migrate --no-interaction @@ -99,16 +141,8 @@ fi # "${DEPLOYER_PHP}" bin/console cache:clear # fi -# ---- -# CodeIgniter -# ---- -# Uncomment as needed - +# CodeIgniter (uncomment as needed) # if [[ $framework == "codeigniter" ]]; then -# # Ensure shared data -# echo "→ Ensuring shared data..." -# mkdir -p "${DEPLOYER_SHARED_PATH}/writable/"{cache,logs,session,uploads} -# # # Run migrations # echo "→ Running migrations..." # "${DEPLOYER_PHP}" spark migrate --all diff --git a/.deployer/scripts/supervisor.sh b/.deployer/scripts/supervisor.sh new file mode 100644 index 0000000..759951b --- /dev/null +++ b/.deployer/scripts/supervisor.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# +# Supervisor Script - Run long-lived workers for supported frameworks +# ---- +# +# This should be executed by supervisord on the remote server. +# +# Environment variables provided by runner script: +# DEPLOYER_RELEASE_PATH - Absolute path to the current release directory +# DEPLOYER_SHARED_PATH - Absolute path to the shared/ directory +# DEPLOYER_CURRENT_PATH - Absolute path to the current/ symlink +# DEPLOYER_DOMAIN - Site domain (example.com) +# DEPLOYER_BRANCH - Git branch currently deployed +# DEPLOYER_PHP - Absolute path to the PHP binary (e.g. /usr/bin/php8.4) +# +# The --max-time/--time-limit flags ensure graceful restart +# (matches default stopwaitsecs=3600). +# + +cd "${DEPLOYER_CURRENT_PATH}" + +# ---- +# Framework Detection +# ---- + +framework="" + +if [[ -f artisan ]]; then + framework="laravel" +elif [[ -f bin/console ]]; then + framework="symfony" +elif [[ -f spark ]]; then + framework="codeigniter" +fi + +# ---- +# Workers +# ---- + +if [[ $framework == "laravel" ]]; then + # + # Why exec is required: + # Without exec, the process tree looks like: + # supervisord -> bash (tracked) -> php (actual worker) + # Supervisord only tracks bash. When it sends SIGTERM to stop the program, + # the signal goes to bash, not PHP. PHP never gets a chance to gracefully + # finish the current message before shutting down. + # + # With exec, bash is replaced by PHP: + # supervisord -> php (tracked directly) + # Now SIGTERM goes directly to PHP, allowing graceful shutdown within + # the stopwaitsecs window (default 3600s). + # + exec "${DEPLOYER_PHP}" artisan queue:work --sleep=3 --tries=3 --max-time=3600 +elif [[ $framework == "symfony" ]]; then + # + # Why exec is required: + # See Laravel notes above. + # + exec "${DEPLOYER_PHP}" bin/console messenger:consume async --time-limit=3600 +else + echo "Unsupported framework for supervisor.sh. Customize this script for your app." >&2 + exit 1 +fi