152 lines
4.2 KiB
Bash
Executable File
152 lines
4.2 KiB
Bash
Executable File
#!/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"
|