Executable
+151
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
required_variables=(
|
||||
SOURCE_DIR
|
||||
DEPLOY_ROOT
|
||||
CONTAINER_DEPLOY_ROOT
|
||||
PHP_CONTAINER
|
||||
HEALTHCHECK_URL
|
||||
BACKUP_COMMAND
|
||||
)
|
||||
|
||||
for variable_name in "${required_variables[@]}"; do
|
||||
test -n "${!variable_name:-}" || {
|
||||
printf 'ERROR: %s is required.\n' "$variable_name" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
release_id="${RELEASE_ID:-$(date -u +%Y%m%d%H%M%S)}"
|
||||
keep_releases="${KEEP_RELEASES:-5}"
|
||||
php_user="${PHP_RUNTIME_USER:-1000:1000}"
|
||||
dry_run="${DRY_RUN:-false}"
|
||||
host_release="$DEPLOY_ROOT/releases/$release_id"
|
||||
container_release="$CONTAINER_DEPLOY_ROOT/releases/$release_id"
|
||||
previous_release=""
|
||||
maintenance_enabled=false
|
||||
|
||||
run() {
|
||||
if test "$dry_run" = "true"; then
|
||||
printf 'DRY-RUN:'
|
||||
printf ' %q' "$@"
|
||||
printf '\n'
|
||||
return 0
|
||||
fi
|
||||
"$@"
|
||||
}
|
||||
|
||||
artisan() {
|
||||
run docker exec --user "$php_user" --workdir "$container_release" \
|
||||
"$PHP_CONTAINER" php artisan "$@"
|
||||
}
|
||||
|
||||
restore_previous_release() {
|
||||
if test "$dry_run" != "true" && test -n "$previous_release"; then
|
||||
ln -s "$previous_release" "$DEPLOY_ROOT/.current.rollback"
|
||||
mv -Tf "$DEPLOY_ROOT/.current.rollback" "$DEPLOY_ROOT/current"
|
||||
fi
|
||||
}
|
||||
|
||||
recover() {
|
||||
exit_code=$?
|
||||
if test "$exit_code" -ne 0; then
|
||||
printf 'Deployment failed; restoring the previous code release.\n' >&2
|
||||
restore_previous_release
|
||||
fi
|
||||
if test "$maintenance_enabled" = "true" && test -L "$DEPLOY_ROOT/current"; then
|
||||
docker exec --user "$php_user" \
|
||||
--workdir "$CONTAINER_DEPLOY_ROOT/current" \
|
||||
"$PHP_CONTAINER" php artisan up >/dev/null 2>&1 || true
|
||||
fi
|
||||
exit "$exit_code"
|
||||
}
|
||||
trap recover EXIT
|
||||
trap 'exit 143' TERM
|
||||
trap 'exit 130' INT
|
||||
|
||||
test -d "$SOURCE_DIR" || {
|
||||
printf 'ERROR: Source directory does not exist: %s\n' "$SOURCE_DIR" >&2
|
||||
exit 1
|
||||
}
|
||||
test -f "$DEPLOY_ROOT/shared/.env" || {
|
||||
printf 'ERROR: Shared production .env does not exist.\n' >&2
|
||||
exit 1
|
||||
}
|
||||
test -d "$DEPLOY_ROOT/shared/storage" || {
|
||||
printf 'ERROR: Shared storage directory does not exist.\n' >&2
|
||||
exit 1
|
||||
}
|
||||
command -v flock >/dev/null 2>&1 || {
|
||||
printf 'ERROR: flock is required on the production host.\n' >&2
|
||||
exit 1
|
||||
}
|
||||
exec 9>"$DEPLOY_ROOT/.deploy.lock"
|
||||
flock -w 600 9 || {
|
||||
printf 'ERROR: Another deployment still holds the production lock.\n' >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
if test -L "$DEPLOY_ROOT/current"; then
|
||||
previous_release="$(readlink "$DEPLOY_ROOT/current")"
|
||||
if test "$dry_run" != "true"; then
|
||||
docker exec --user "$php_user" \
|
||||
--workdir "$CONTAINER_DEPLOY_ROOT/current" \
|
||||
"$PHP_CONTAINER" php artisan down --retry=30
|
||||
maintenance_enabled=true
|
||||
fi
|
||||
fi
|
||||
|
||||
run mkdir -p "$host_release"
|
||||
run rsync -a --delete \
|
||||
--exclude='.env' \
|
||||
--exclude='node_modules' \
|
||||
--exclude='storage' \
|
||||
--exclude='vendor' \
|
||||
"$SOURCE_DIR/" "$host_release/"
|
||||
run ln -s ../../shared/.env "$host_release/.env"
|
||||
run ln -s ../../shared/storage "$host_release/storage"
|
||||
|
||||
run docker exec --user "$php_user" --workdir "$container_release" \
|
||||
"$PHP_CONTAINER" composer install \
|
||||
--no-dev --no-interaction --prefer-dist --optimize-autoloader
|
||||
|
||||
if test "$dry_run" != "true"; then
|
||||
bash -c "$BACKUP_COMMAND"
|
||||
fi
|
||||
|
||||
artisan migrate --force
|
||||
artisan storage:link
|
||||
artisan optimize:clear
|
||||
artisan config:cache
|
||||
artisan route:cache
|
||||
artisan view:cache
|
||||
|
||||
run ln -s "releases/$release_id" "$DEPLOY_ROOT/.current.next"
|
||||
run mv -Tf "$DEPLOY_ROOT/.current.next" "$DEPLOY_ROOT/current"
|
||||
|
||||
if test "$dry_run" != "true"; then
|
||||
docker exec "$PHP_CONTAINER" kill -USR2 1
|
||||
docker exec --user "$php_user" \
|
||||
--workdir "$CONTAINER_DEPLOY_ROOT/current" \
|
||||
"$PHP_CONTAINER" php artisan up
|
||||
maintenance_enabled=false
|
||||
curl --fail --silent --show-error \
|
||||
--retry 6 --retry-delay 5 --retry-connrefused \
|
||||
"$HEALTHCHECK_URL" >/dev/null
|
||||
|
||||
active_target="$(readlink "$DEPLOY_ROOT/current")"
|
||||
mapfile -t old_releases < <(
|
||||
ls -1dt "$DEPLOY_ROOT"/releases/* 2>/dev/null |
|
||||
awk -v keep="$keep_releases" -v active="$DEPLOY_ROOT/$active_target" '
|
||||
$0 != active { seen++; if (seen >= keep) print }
|
||||
'
|
||||
)
|
||||
if test "${#old_releases[@]}" -gt 0; then
|
||||
rm -rf -- "${old_releases[@]}"
|
||||
fi
|
||||
fi
|
||||
|
||||
trap - EXIT
|
||||
printf 'Deployment succeeded: %s\n' "$release_id"
|
||||
Reference in New Issue
Block a user