Wire admin studio SSR and search infrastructure
This commit is contained in:
160
scripts/check_inertia.php
Normal file
160
scripts/check_inertia.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
function usage(): void
|
||||
{
|
||||
$script = basename(__FILE__);
|
||||
|
||||
fwrite(STDERR, <<<TXT
|
||||
Usage:
|
||||
php scripts/{$script} --base=https://skinbase.top /@gregor /categories /wallpapers
|
||||
php scripts/{$script} https://skinbase.top/@gregor https://skinbase.top/categories
|
||||
|
||||
Classifications:
|
||||
INERTIA_SSR id="app" + data-page + non-empty app HTML
|
||||
INERTIA_NO_SSR id="app" + data-page + empty app HTML
|
||||
NOT_INERTIA missing id="app" or missing data-page
|
||||
|
||||
TXT);
|
||||
}
|
||||
|
||||
function isAbsoluteUrl(string $value): bool
|
||||
{
|
||||
return (bool) preg_match('~^https?://~i', $value);
|
||||
}
|
||||
|
||||
function joinUrl(string $baseUrl, string $path): string
|
||||
{
|
||||
return rtrim($baseUrl, '/') . '/' . ltrim($path, '/');
|
||||
}
|
||||
|
||||
function resolveTargets(array $arguments): array
|
||||
{
|
||||
$baseUrl = null;
|
||||
$targets = [];
|
||||
|
||||
foreach ($arguments as $argument) {
|
||||
if (str_starts_with($argument, '--base=')) {
|
||||
$baseUrl = substr($argument, 7);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($argument === '--help' || $argument === '-h') {
|
||||
usage();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$targets[] = $argument;
|
||||
}
|
||||
|
||||
if ($targets === []) {
|
||||
usage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return array_map(static function (string $target) use ($baseUrl): string {
|
||||
if (isAbsoluteUrl($target)) {
|
||||
return $target;
|
||||
}
|
||||
|
||||
if ($baseUrl === null || $baseUrl === '') {
|
||||
fwrite(STDERR, "Relative path '{$target}' requires --base=...\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return joinUrl($baseUrl, $target);
|
||||
}, $targets);
|
||||
}
|
||||
|
||||
function fetchUrl(string $url): array
|
||||
{
|
||||
if (function_exists('curl_init')) {
|
||||
$handle = curl_init($url);
|
||||
curl_setopt_array($handle, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_MAXREDIRS => 5,
|
||||
CURLOPT_TIMEOUT => 20,
|
||||
CURLOPT_CONNECTTIMEOUT => 10,
|
||||
CURLOPT_USERAGENT => 'Skinbase Inertia Checker/1.0',
|
||||
CURLOPT_HEADER => true,
|
||||
]);
|
||||
|
||||
$raw = curl_exec($handle);
|
||||
if ($raw === false) {
|
||||
$error = curl_error($handle);
|
||||
curl_close($handle);
|
||||
|
||||
throw new RuntimeException($error !== '' ? $error : 'Unknown cURL error');
|
||||
}
|
||||
|
||||
$status = (int) curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
|
||||
$headerSize = (int) curl_getinfo($handle, CURLINFO_HEADER_SIZE);
|
||||
curl_close($handle);
|
||||
|
||||
return [
|
||||
'status' => $status,
|
||||
'body' => substr($raw, $headerSize),
|
||||
];
|
||||
}
|
||||
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'GET',
|
||||
'follow_location' => 1,
|
||||
'max_redirects' => 5,
|
||||
'timeout' => 20,
|
||||
'header' => "User-Agent: Skinbase Inertia Checker/1.0\r\n",
|
||||
'ignore_errors' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$body = @file_get_contents($url, false, $context);
|
||||
if ($body === false) {
|
||||
$error = error_get_last();
|
||||
throw new RuntimeException($error['message'] ?? 'HTTP request failed');
|
||||
}
|
||||
|
||||
$headers = $http_response_header ?? [];
|
||||
$status = 0;
|
||||
|
||||
foreach ($headers as $header) {
|
||||
if (preg_match('~^HTTP/\S+\s+(\d{3})~', $header, $matches)) {
|
||||
$status = (int) $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => $status,
|
||||
'body' => $body,
|
||||
];
|
||||
}
|
||||
|
||||
function classifyHtml(string $html): string
|
||||
{
|
||||
$hasApp = str_contains($html, 'id="app"');
|
||||
$hasDataPage = str_contains($html, 'data-page="');
|
||||
|
||||
if (! $hasApp || ! $hasDataPage) {
|
||||
return 'NOT_INERTIA';
|
||||
}
|
||||
|
||||
if (! preg_match('~<div id="app"[^>]*>(.*?)</div>~si', $html, $matches)) {
|
||||
return 'INERTIA_UNKNOWN';
|
||||
}
|
||||
|
||||
return trim($matches[1]) === '' ? 'INERTIA_NO_SSR' : 'INERTIA_SSR';
|
||||
}
|
||||
|
||||
$targets = resolveTargets(array_slice($argv, 1));
|
||||
|
||||
foreach ($targets as $url) {
|
||||
try {
|
||||
$response = fetchUrl($url);
|
||||
$classification = classifyHtml($response['body']);
|
||||
printf("%d\t%s\t%s\n", $response['status'], $classification, $url);
|
||||
} catch (Throwable $exception) {
|
||||
printf("ERROR\t%s\t%s\n", $exception->getMessage(), $url);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -44,7 +44,7 @@ DB::connection('legacy')
|
||||
foreach ($rows as $r) {
|
||||
$id = (int) ($r->user_id ?? 0);
|
||||
$hash = trim((string) ($r->password2 ?: $r->password ?: ''));
|
||||
if ($id === 0 || $hash === '') {
|
||||
if ($id === 0 || $hash === '' || $hash === 'abc123') {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
18
scripts/parse-a11y.cjs
Normal file
18
scripts/parse-a11y.cjs
Normal file
@@ -0,0 +1,18 @@
|
||||
const fs = require('fs')
|
||||
const html = fs.readFileSync('D:/Sites/Skinbase26/skinbase.top-20260429T075355.html', 'utf8')
|
||||
const idx = html.indexOf('"requestedUrl"')
|
||||
let start = idx; while(start>0 && html[start]!=='{') start--
|
||||
let depth=0,end=0
|
||||
const slice = html.slice(start)
|
||||
for(let i=0;i<slice.length;i++){if(slice[i]==='{')depth++;else if(slice[i]==='}'){depth--;if(depth===0){end=i+1;break}}}
|
||||
const lhr = JSON.parse(slice.slice(0,end))
|
||||
const audits = ['link-name','color-contrast','label-content-name-mismatch','link-in-text-block','forced-reflow-insight','image-delivery-insight']
|
||||
for (const id of audits) {
|
||||
const a = lhr.audits[id]
|
||||
if (!a) continue
|
||||
console.log(`\n=== ${id} (score ${a.score}) ===`)
|
||||
;(a.details?.items || []).forEach(item => {
|
||||
if (item.type === 'node') console.log(' node:', item.snippet?.slice(0,200))
|
||||
else console.log(' item:', JSON.stringify(item).slice(0,300))
|
||||
})
|
||||
}
|
||||
10
scripts/parse-lcp.cjs
Normal file
10
scripts/parse-lcp.cjs
Normal file
@@ -0,0 +1,10 @@
|
||||
const fs = require('fs')
|
||||
const html = fs.readFileSync('D:/Sites/Skinbase26/skinbase.top-20260429T075355.html', 'utf8')
|
||||
const idx = html.indexOf('"requestedUrl"')
|
||||
let start = idx; while(start>0 && html[start]!=='{') start--
|
||||
let depth=0,end=0
|
||||
const slice = html.slice(start)
|
||||
for(let i=0;i<slice.length;i++){if(slice[i]==='{')depth++;else if(slice[i]==='}'){depth--;if(depth===0){end=i+1;break}}}
|
||||
const lhr = JSON.parse(slice.slice(0,end))
|
||||
const lcp = lhr.audits['lcp-discovery-insight']
|
||||
console.log(JSON.stringify(lcp, null, 2))
|
||||
75
scripts/parse-lighthouse.js
Normal file
75
scripts/parse-lighthouse.js
Normal file
@@ -0,0 +1,75 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const html = fs.readFileSync('D:/Sites/Skinbase26/skinbase.top-20260429T075355.html', 'utf8')
|
||||
|
||||
// The LH report embeds JSON like: const __json__ = ... or as a compressed string
|
||||
// Try to find the JSON blob by locating "requestedUrl"
|
||||
const idx = html.indexOf('"requestedUrl"')
|
||||
if (idx === -1) {
|
||||
console.log('No requestedUrl found in file')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Walk backwards to find the opening { of the top-level object
|
||||
let start = idx
|
||||
while (start > 0 && html[start] !== '{') start--
|
||||
|
||||
// Walk forward to find the matching close — take a large slice and try to parse
|
||||
const slice = html.slice(start)
|
||||
|
||||
// Find the end by counting braces
|
||||
let depth = 0
|
||||
let end = 0
|
||||
for (let i = 0; i < slice.length; i++) {
|
||||
if (slice[i] === '{') depth++
|
||||
else if (slice[i] === '}') {
|
||||
depth--
|
||||
if (depth === 0) {
|
||||
end = i + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let lhr
|
||||
try {
|
||||
lhr = JSON.parse(slice.slice(0, end))
|
||||
} catch {
|
||||
// Try taking the slice up to the first </script> after requestedUrl
|
||||
const scriptEnd = html.indexOf('</script>', idx)
|
||||
const scriptSlice = html.slice(start, scriptEnd).replace(/;?\s*$/, '')
|
||||
lhr = JSON.parse(scriptSlice)
|
||||
}
|
||||
|
||||
// Print category scores
|
||||
console.log('=== CATEGORY SCORES ===')
|
||||
for (const [id, cat] of Object.entries(lhr.categories || {})) {
|
||||
console.log(` ${id}: ${Math.round((cat.score || 0) * 100)}`)
|
||||
}
|
||||
|
||||
// Print failed/low audits (score < 1 and not null)
|
||||
console.log('\n=== FAILED / LOW AUDITS ===')
|
||||
for (const [id, audit] of Object.entries(lhr.audits || {})) {
|
||||
if (audit.score === null || audit.score === undefined) continue
|
||||
if (audit.score >= 1) continue
|
||||
const items = audit.details?.items?.length || 0
|
||||
const displayValue = audit.displayValue || ''
|
||||
console.log(` [${(audit.score * 100).toFixed(0).padStart(3)}] ${id} ${displayValue}`)
|
||||
if (items > 0 && audit.score < 0.9) {
|
||||
const heads = (audit.details?.items || []).slice(0, 4)
|
||||
heads.forEach((item) => {
|
||||
const label = item.node?.snippet || item.url || item.description || item.label || JSON.stringify(item).slice(0, 120)
|
||||
console.log(` • ${label}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Print all opportunity audits
|
||||
console.log('\n=== OPPORTUNITY AUDITS ===')
|
||||
for (const [id, audit] of Object.entries(lhr.audits || {})) {
|
||||
if (audit.details?.type !== 'opportunity') continue
|
||||
const savings = audit.details?.overallSavingsMs || 0
|
||||
if (savings < 100) continue
|
||||
console.log(` ${id}: ${savings}ms savings`)
|
||||
}
|
||||
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