Wire admin studio SSR and search infrastructure
This commit is contained in:
359
scripts/rollback-production.sh
Normal file
359
scripts/rollback-production.sh
Normal file
@@ -0,0 +1,359 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
remote_folder="${REMOTE_FOLDER:-/opt/www/virtual/SkinbaseNova}"
|
||||
remote_server="${REMOTE_SERVER:-klevze@server3.klevze.si}"
|
||||
remote_release_root="${REMOTE_RELEASE_ROOT:-${REMOTE_FOLDER:-/opt/www/virtual/SkinbaseNova}.releases}"
|
||||
remote_shared_root="${REMOTE_SHARED_ROOT:-${REMOTE_RELEASE_ROOT:-/opt/www/virtual/SkinbaseNova.releases}/shared}"
|
||||
php_bin="${PHP_BIN:-php}"
|
||||
composer_bin="${COMPOSER_BIN:-composer}"
|
||||
ssh_bin="${SSH_BIN:-ssh}"
|
||||
|
||||
dry_run=0
|
||||
list_only=0
|
||||
rollback_release_id=""
|
||||
use_previous=0
|
||||
skip_maintenance=0
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: bash scripts/rollback-production.sh [options]
|
||||
|
||||
Options:
|
||||
--previous Switch to the previous retained release on the production server.
|
||||
--release-id ID Switch to a specific retained release ID.
|
||||
--list List retained releases and exit.
|
||||
--dry-run Preview the release switch without changing the active release.
|
||||
--no-maintenance Skip php artisan down/up during the switch.
|
||||
--help Show this help.
|
||||
|
||||
Environment overrides:
|
||||
REMOTE_FOLDER, REMOTE_SERVER, REMOTE_RELEASE_ROOT, REMOTE_SHARED_ROOT, PHP_BIN, COMPOSER_BIN, SSH_BIN
|
||||
|
||||
Notes:
|
||||
- Rollback changes the active server release; it does not re-upload files from local.
|
||||
- Database migrations are not reversed automatically.
|
||||
EOF
|
||||
}
|
||||
|
||||
log_step() {
|
||||
printf '\n==> %s\n' "$1"
|
||||
}
|
||||
|
||||
log_info() {
|
||||
printf ' -> %s\n' "$1"
|
||||
}
|
||||
|
||||
die() {
|
||||
printf 'ERROR: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_command() {
|
||||
local command_name="$1"
|
||||
local description="$2"
|
||||
|
||||
command -v "$command_name" >/dev/null 2>&1 || die "$description is required but was not found in PATH ($command_name)."
|
||||
}
|
||||
|
||||
sanitize_release_fragment() {
|
||||
local value="$1"
|
||||
|
||||
value="${value//[^A-Za-z0-9._-]/-}"
|
||||
value="${value#-}"
|
||||
value="${value%-}"
|
||||
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--previous)
|
||||
use_previous=1
|
||||
;;
|
||||
--release-id)
|
||||
shift
|
||||
rollback_release_id="$(sanitize_release_fragment "${1:?Missing value for --release-id}")"
|
||||
;;
|
||||
--release-id=*)
|
||||
rollback_release_id="$(sanitize_release_fragment "${1#*=}")"
|
||||
;;
|
||||
--list)
|
||||
list_only=1
|
||||
;;
|
||||
--dry-run)
|
||||
dry_run=1
|
||||
;;
|
||||
--no-maintenance)
|
||||
skip_maintenance=1
|
||||
;;
|
||||
--help|-h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "Unknown option: $1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
require_command "$ssh_bin" "SSH"
|
||||
|
||||
if [[ "$list_only" -eq 0 && "$use_previous" -eq 0 && -z "$rollback_release_id" ]]; then
|
||||
die "Choose either --previous or --release-id, or use --list to inspect retained releases."
|
||||
fi
|
||||
|
||||
if [[ "$use_previous" -eq 1 && -n "$rollback_release_id" ]]; then
|
||||
die "Use either --previous or --release-id, not both."
|
||||
fi
|
||||
|
||||
if [[ "$list_only" -eq 1 ]]; then
|
||||
log_step "Retained releases on $remote_server"
|
||||
"$ssh_bin" "$remote_server" \
|
||||
REMOTE_RELEASE_ROOT="$(printf '%q' "$remote_release_root")" \
|
||||
'bash -s' <<'EOF'
|
||||
set -euo pipefail
|
||||
|
||||
current_release="unknown"
|
||||
if [[ -f "${REMOTE_RELEASE_ROOT}/current-release.txt" ]]; then
|
||||
current_release="$(cat "${REMOTE_RELEASE_ROOT}/current-release.txt")"
|
||||
fi
|
||||
|
||||
printf 'Current release: %s\n' "$current_release"
|
||||
|
||||
if [[ ! -d "${REMOTE_RELEASE_ROOT}/releases" ]]; then
|
||||
printf 'No retained releases found in %s\n' "$REMOTE_RELEASE_ROOT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
find "${REMOTE_RELEASE_ROOT}/releases" -mindepth 1 -maxdepth 1 -type d | sort -r | while read -r path; do
|
||||
release_id="$(basename "$path")"
|
||||
marker=" "
|
||||
if [[ "$release_id" == "$current_release" ]]; then
|
||||
marker="*"
|
||||
fi
|
||||
printf '%s %s\n' "$marker" "$release_id"
|
||||
done
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log_step "Switching production release on $remote_server"
|
||||
if [[ "$dry_run" -eq 1 ]]; then
|
||||
log_info "Dry-run mode enabled; remote current release will not be changed"
|
||||
fi
|
||||
|
||||
"$ssh_bin" "$remote_server" \
|
||||
REMOTE_FOLDER="$(printf '%q' "$remote_folder")" \
|
||||
REMOTE_RELEASE_ROOT="$(printf '%q' "$remote_release_root")" \
|
||||
REMOTE_SHARED_ROOT="$(printf '%q' "$remote_shared_root")" \
|
||||
PHP_BIN="$(printf '%q' "$php_bin")" \
|
||||
COMPOSER_BIN="$(printf '%q' "$composer_bin")" \
|
||||
ROLLBACK_RELEASE_ID="$(printf '%q' "$rollback_release_id")" \
|
||||
USE_PREVIOUS="$use_previous" \
|
||||
DRY_RUN="$dry_run" \
|
||||
SKIP_MAINTENANCE="$skip_maintenance" \
|
||||
'bash -s' <<'EOF'
|
||||
set -euo pipefail
|
||||
|
||||
current_link="${REMOTE_RELEASE_ROOT}/current"
|
||||
|
||||
log_step() {
|
||||
printf '\n==> %s\n' "$1"
|
||||
}
|
||||
|
||||
log_info() {
|
||||
printf ' -> %s\n' "$1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
printf 'WARN: %s\n' "$1" >&2
|
||||
}
|
||||
|
||||
die() {
|
||||
printf 'ERROR: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
current_release_id() {
|
||||
if [[ -L "$current_link" ]]; then
|
||||
basename "$(readlink "$current_link")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
printf '%s\n' ''
|
||||
}
|
||||
|
||||
resolve_target_release() {
|
||||
local current_release
|
||||
local target_release
|
||||
local -a releases=()
|
||||
|
||||
[[ -d "${REMOTE_RELEASE_ROOT}/releases" ]] || die "No retained releases exist under ${REMOTE_RELEASE_ROOT}/releases"
|
||||
|
||||
current_release="$(current_release_id)"
|
||||
|
||||
if [[ "$USE_PREVIOUS" -eq 1 ]]; then
|
||||
mapfile -t releases < <(find "${REMOTE_RELEASE_ROOT}/releases" -mindepth 1 -maxdepth 1 -type d | sort -r)
|
||||
for release_path in "${releases[@]}"; do
|
||||
target_release="$(basename "$release_path")"
|
||||
if [[ "$target_release" != "$current_release" ]]; then
|
||||
printf '%s\n' "$target_release"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
die "No previous retained release is available."
|
||||
fi
|
||||
|
||||
printf '%s\n' "$ROLLBACK_RELEASE_ID"
|
||||
}
|
||||
|
||||
ensure_dir() {
|
||||
mkdir -p "$1"
|
||||
}
|
||||
|
||||
ensure_php_runtime_dir() {
|
||||
local target_dir="$1"
|
||||
local -a privileged_cmd=()
|
||||
|
||||
if command -v sudo >/dev/null 2>&1 && sudo -n true >/dev/null 2>&1; then
|
||||
privileged_cmd=(sudo -n)
|
||||
elif [[ "$(id -u)" -eq 0 ]]; then
|
||||
privileged_cmd=()
|
||||
fi
|
||||
|
||||
if [[ ! -d "$target_dir" ]]; then
|
||||
if [[ ${#privileged_cmd[@]} -gt 0 ]]; then
|
||||
"${privileged_cmd[@]}" mkdir -p "$target_dir"
|
||||
else
|
||||
mkdir -p "$target_dir"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ${#privileged_cmd[@]} -gt 0 || "$(id -u)" -eq 0 ]]; then
|
||||
"${privileged_cmd[@]}" chown -R skinbase:skinbase "$target_dir"
|
||||
"${privileged_cmd[@]}" chmod 770 "$target_dir"
|
||||
return
|
||||
fi
|
||||
|
||||
chmod 770 "$target_dir" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
link_shared_paths() {
|
||||
local target_release="$1"
|
||||
|
||||
ensure_dir "$target_release/public" "$target_release/var"
|
||||
|
||||
rm -f "$target_release/.env"
|
||||
ln -sfn "${REMOTE_SHARED_ROOT}/.env" "$target_release/.env"
|
||||
|
||||
rm -rf "$target_release/storage"
|
||||
ln -sfn "${REMOTE_SHARED_ROOT}/storage" "$target_release/storage"
|
||||
|
||||
rm -rf "$target_release/public/files"
|
||||
ln -sfn "${REMOTE_SHARED_ROOT}/public/files" "$target_release/public/files"
|
||||
|
||||
rm -rf "$target_release/public/sitemaps"
|
||||
ln -sfn "${REMOTE_SHARED_ROOT}/public/sitemaps" "$target_release/public/sitemaps"
|
||||
|
||||
rm -rf "$target_release/var/php-tmp"
|
||||
ln -sfn "${REMOTE_SHARED_ROOT}/var/php-tmp" "$target_release/var/php-tmp"
|
||||
|
||||
rm -rf "$target_release/var/php-sessions"
|
||||
ln -sfn "${REMOTE_SHARED_ROOT}/var/php-sessions" "$target_release/var/php-sessions"
|
||||
}
|
||||
|
||||
bring_app_up() {
|
||||
if [[ "$SKIP_MAINTENANCE" -eq 0 && -f "$REMOTE_FOLDER/artisan" ]]; then
|
||||
"$PHP_BIN" "$REMOTE_FOLDER/artisan" up >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
target_release="$(resolve_target_release)"
|
||||
[[ -n "$target_release" ]] || die "Unable to resolve a target release."
|
||||
|
||||
target_release_path="${REMOTE_RELEASE_ROOT}/releases/${target_release}"
|
||||
[[ -d "$target_release_path" ]] || die "Retained release not found: ${target_release_path}"
|
||||
|
||||
current_release="$(current_release_id)"
|
||||
[[ -n "$current_release" ]] || die "No active current release is configured under ${current_link}"
|
||||
|
||||
if [[ "$target_release" == "$current_release" ]]; then
|
||||
die "Target release is already active: ${target_release}"
|
||||
fi
|
||||
|
||||
ensure_php_runtime_dir "${REMOTE_SHARED_ROOT}/var/php-tmp"
|
||||
ensure_php_runtime_dir "${REMOTE_SHARED_ROOT}/var/php-sessions"
|
||||
link_shared_paths "$target_release_path"
|
||||
|
||||
[[ -f "${REMOTE_SHARED_ROOT}/.env" ]] || die "Shared production .env is missing at ${REMOTE_SHARED_ROOT}/.env"
|
||||
[[ -f "${target_release_path}/artisan" ]] || die "Target release is missing artisan: ${target_release_path}/artisan"
|
||||
|
||||
log_info "Current release: ${current_release}"
|
||||
log_info "Target release: ${target_release}"
|
||||
log_info "Target path: ${target_release_path}"
|
||||
|
||||
if [[ "$DRY_RUN" -eq 1 ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
trap bring_app_up EXIT
|
||||
|
||||
if [[ "$SKIP_MAINTENANCE" -eq 0 && -f "$REMOTE_FOLDER/artisan" ]]; then
|
||||
log_step "Enabling maintenance mode"
|
||||
"$PHP_BIN" "$REMOTE_FOLDER/artisan" down --retry=60 || true
|
||||
fi
|
||||
|
||||
if [[ ! -f "${target_release_path}/vendor/autoload.php" ]]; then
|
||||
log_step "Installing Composer dependencies in target release"
|
||||
(
|
||||
cd "$target_release_path"
|
||||
"$COMPOSER_BIN" install --no-dev --prefer-dist --optimize-autoloader --no-interaction
|
||||
)
|
||||
fi
|
||||
|
||||
log_step "Switching current release to ${target_release}"
|
||||
ln -sfn "$target_release_path" "$current_link"
|
||||
ln -sfn "$current_link" "$REMOTE_FOLDER"
|
||||
|
||||
cd "$REMOTE_FOLDER"
|
||||
|
||||
log_step "Refreshing caches"
|
||||
"$PHP_BIN" artisan view:clear
|
||||
"$PHP_BIN" artisan optimize:clear
|
||||
"$PHP_BIN" artisan optimize
|
||||
"$PHP_BIN" artisan view:cache
|
||||
|
||||
if [[ "$SKIP_MAINTENANCE" -eq 0 ]]; then
|
||||
log_step "Bringing application back online"
|
||||
"$PHP_BIN" artisan up
|
||||
trap - EXIT
|
||||
fi
|
||||
|
||||
if ! "$PHP_BIN" artisan homepage:warm-guest-cache; then
|
||||
log_warn "Homepage guest cache warm failed during rollback."
|
||||
fi
|
||||
|
||||
if ! "$PHP_BIN" artisan posts:warm-trending; then
|
||||
log_warn "Post trending cache warm failed during rollback."
|
||||
fi
|
||||
|
||||
log_step "Restarting queue workers"
|
||||
"$PHP_BIN" artisan queue:restart || true
|
||||
|
||||
cat > "${REMOTE_RELEASE_ROOT}/current-release.json" <<JSON
|
||||
{
|
||||
"release_id": "${target_release}",
|
||||
"rolled_back_from_release_id": "${current_release}",
|
||||
"rolled_back_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||
"remote_folder": "${REMOTE_FOLDER}",
|
||||
"release_path": "${target_release_path}"
|
||||
}
|
||||
JSON
|
||||
|
||||
printf '%s\n' "${target_release}" > "${REMOTE_RELEASE_ROOT}/current-release.txt"
|
||||
|
||||
log_step "Release switch complete"
|
||||
EOF
|
||||
Reference in New Issue
Block a user