346 lines
11 KiB
Bash
Executable File
346 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# shellcheck disable=SC2016
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
resolve_php_bin() {
|
|
if [[ -n "${PHP_BIN:-}" ]]; then
|
|
echo "$PHP_BIN"
|
|
return
|
|
fi
|
|
if command -v keyhelp-php84 >/dev/null 2>&1; then
|
|
echo "keyhelp-php84"
|
|
return
|
|
fi
|
|
if command -v php >/dev/null 2>&1; then
|
|
echo "php"
|
|
return
|
|
fi
|
|
echo "php"
|
|
}
|
|
|
|
ensure_storage_link() {
|
|
local storage_public="storage/app/public"
|
|
local public_storage="public/storage"
|
|
|
|
echo "Ensuring public storage link..."
|
|
|
|
if [[ -e "$storage_public" && ! -d "$storage_public" ]]; then
|
|
local backup_path="${storage_public}.bak.$(date +%Y%m%d_%H%M%S)"
|
|
echo "Found invalid $storage_public (not a directory). Backing up to $backup_path"
|
|
mv "$storage_public" "$backup_path"
|
|
fi
|
|
|
|
mkdir -p "$storage_public"
|
|
|
|
# If public/storage is a real directory, migrate files before converting to symlink.
|
|
if [[ -d "$public_storage" && ! -L "$public_storage" ]]; then
|
|
echo "Migrating existing files from $public_storage to $storage_public"
|
|
if command -v rsync >/dev/null 2>&1; then
|
|
rsync -a "$public_storage"/ "$storage_public"/
|
|
else
|
|
cp -a "$public_storage"/. "$storage_public"/
|
|
fi
|
|
rm -rf "$public_storage"
|
|
elif [[ -e "$public_storage" && ! -L "$public_storage" ]]; then
|
|
local public_backup="${public_storage}.bak.$(date +%Y%m%d_%H%M%S)"
|
|
echo "Found invalid $public_storage (not a directory/symlink). Backing up to $public_backup"
|
|
mv "$public_storage" "$public_backup"
|
|
fi
|
|
|
|
ln -sfn ../storage/app/public "$public_storage"
|
|
mkdir -p "$storage_public/logos" "$storage_public/favicons" "$storage_public/rank-badges"
|
|
}
|
|
|
|
resolve_configured_php_bin() {
|
|
local configured="${1:-}"
|
|
local current="${2:-php}"
|
|
local trimmed="$configured"
|
|
trimmed="${trimmed#"${trimmed%%[![:space:]]*}"}"
|
|
trimmed="${trimmed%"${trimmed##*[![:space:]]}"}"
|
|
|
|
if [[ -z "$trimmed" ]]; then
|
|
echo "$current"
|
|
return
|
|
fi
|
|
|
|
if [[ "$trimmed" == "keyhelp-php-domain" ]]; then
|
|
if command -v keyhelp-php-domain >/dev/null 2>&1; then
|
|
echo "keyhelp-php-domain"
|
|
return
|
|
fi
|
|
if [[ -x "/usr/bin/keyhelp-php-domain" ]]; then
|
|
echo "/usr/bin/keyhelp-php-domain"
|
|
return
|
|
fi
|
|
if [[ -x "/usr/local/bin/keyhelp-php-domain" ]]; then
|
|
echo "/usr/local/bin/keyhelp-php-domain"
|
|
return
|
|
fi
|
|
echo "Configured PHP binary 'keyhelp-php-domain' was not found." >&2
|
|
echo "Set ACP -> System -> CLI to a working custom binary (e.g. keyhelp-php84)." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if command -v "$trimmed" >/dev/null 2>&1; then
|
|
echo "$trimmed"
|
|
return
|
|
fi
|
|
|
|
if [[ "$trimmed" == */* && -x "$trimmed" ]]; then
|
|
echo "$trimmed"
|
|
return
|
|
fi
|
|
|
|
echo "Configured PHP binary '$trimmed' is not executable/resolvable." >&2
|
|
echo "Set ACP -> System -> CLI to a valid command or absolute executable path." >&2
|
|
exit 1
|
|
}
|
|
|
|
read_setting_php_bin() {
|
|
if [[ ! -f artisan ]]; then
|
|
echo ""
|
|
return 0
|
|
fi
|
|
echo "Using bootstrap PHP binary to read system.php_binary: $PHP_BIN" >&2
|
|
"$PHP_BIN" -r '
|
|
require "vendor/autoload.php";
|
|
$app = require "bootstrap/app.php";
|
|
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
|
|
$value = (string) \App\Models\Setting::where("key", "system.php_binary")->value("value");
|
|
echo trim($value);
|
|
'
|
|
}
|
|
|
|
enforce_php_requirement() {
|
|
local bin="${1:-php}"
|
|
echo "Validating PHP requirement from composer.json with binary: $bin"
|
|
"$bin" -r '
|
|
$composer = json_decode((string) file_get_contents("composer.json"), true);
|
|
$constraint = (string) ($composer["require"]["php"] ?? "");
|
|
$current = PHP_VERSION;
|
|
|
|
if ($constraint === "") {
|
|
fwrite(STDOUT, "No PHP requirement found in composer.json; skipping check.\n");
|
|
exit(0);
|
|
}
|
|
|
|
$normalize = static function (string $value): ?array {
|
|
if (!preg_match("/(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?/", $value, $m)) {
|
|
return null;
|
|
}
|
|
return [(int) $m[1], (int) ($m[2] ?? 0), (int) ($m[3] ?? 0)];
|
|
};
|
|
|
|
$cmp = static function (array $a, array $b): int {
|
|
for ($i = 0; $i < 3; $i++) {
|
|
if ($a[$i] > $b[$i]) {
|
|
return 1;
|
|
}
|
|
if ($a[$i] < $b[$i]) {
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
$partMin = static function (string $part) use ($normalize): ?array {
|
|
$tokens = preg_split("/\\s+/", trim($part)) ?: [];
|
|
$tokens = array_values(array_filter($tokens, static fn ($t) => $t !== ""));
|
|
|
|
foreach ($tokens as $token) {
|
|
if (str_starts_with($token, ">=")) {
|
|
$parsed = $normalize(substr($token, 2));
|
|
if ($parsed) {
|
|
return $parsed;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($tokens as $token) {
|
|
if (str_starts_with($token, "^")) {
|
|
$parsed = $normalize(substr($token, 1));
|
|
if ($parsed) {
|
|
return $parsed;
|
|
}
|
|
}
|
|
if (str_starts_with($token, "~")) {
|
|
$parsed = $normalize(substr($token, 1));
|
|
if ($parsed) {
|
|
return $parsed;
|
|
}
|
|
}
|
|
}
|
|
|
|
return isset($tokens[0]) ? $normalize($tokens[0]) : null;
|
|
};
|
|
|
|
$parts = array_values(array_filter(array_map("trim", explode("||", $constraint)), static fn ($p) => $p !== ""));
|
|
$mins = [];
|
|
foreach ($parts as $part) {
|
|
$min = $partMin($part);
|
|
if ($min) {
|
|
$mins[] = $min;
|
|
}
|
|
}
|
|
|
|
if (!$mins) {
|
|
fwrite(STDOUT, "Could not parse PHP requirement \"$constraint\"; skipping strict check.\n");
|
|
exit(0);
|
|
}
|
|
|
|
$required = array_reduce($mins, static function ($carry, $item) use ($cmp) {
|
|
if ($carry === null) {
|
|
return $item;
|
|
}
|
|
return $cmp($item, $carry) < 0 ? $item : $carry;
|
|
});
|
|
|
|
$currentParts = $normalize($current);
|
|
if (!$currentParts) {
|
|
fwrite(STDERR, "Unable to parse current PHP version: $current\n");
|
|
exit(1);
|
|
}
|
|
|
|
$requiredString = implode(".", $required);
|
|
if ($cmp($currentParts, $required) < 0) {
|
|
fwrite(STDERR, "PHP requirement check failed: composer.json requires \"$constraint\" (>= $requiredString), current is $current.\n");
|
|
exit(1);
|
|
}
|
|
|
|
fwrite(STDOUT, "PHP requirement check passed: composer.json requires \"$constraint\" (>= $requiredString), current is $current.\n");
|
|
' || return 1
|
|
}
|
|
|
|
main() {
|
|
cd "$ROOT_DIR"
|
|
|
|
git restore -q bootstrap/cache/packages.php bootstrap/cache/services.php 2>/dev/null || true
|
|
DIRTY="$(git status --porcelain)"
|
|
DIRTY_FILTERED="$(echo "$DIRTY" | grep -vE '^( M|M ) (bootstrap/cache/(packages|services)\.php|package-lock\.json)$' || true)"
|
|
if [[ -n "$DIRTY_FILTERED" ]]; then
|
|
echo "Working tree is dirty. Please commit or stash changes before updating."
|
|
echo "$DIRTY_FILTERED"
|
|
exit 1
|
|
fi
|
|
if echo "$DIRTY" | grep -qE 'package-lock\.json'; then
|
|
echo "Warning: package-lock.json is modified. Continuing anyway."
|
|
fi
|
|
|
|
echo "Fetching latest refs..."
|
|
git fetch --prune --tags
|
|
|
|
echo "Checking out stable branch..."
|
|
git checkout stable
|
|
|
|
echo "Pulling latest stable..."
|
|
git pull --ff-only
|
|
|
|
PHP_BIN="$(resolve_php_bin)"
|
|
echo "Initial fallback PHP binary: $PHP_BIN"
|
|
if command -v "$PHP_BIN" >/dev/null 2>&1; then
|
|
echo "PHP version ($PHP_BIN): $($PHP_BIN -v | head -n 1)"
|
|
else
|
|
echo "PHP binary '$PHP_BIN' not found in PATH."
|
|
fi
|
|
|
|
echo "Installing PHP dependencies..."
|
|
COMPOSER_BIN="$(command -v composer || true)"
|
|
if [[ -z "$COMPOSER_BIN" ]]; then
|
|
echo "Composer not found in PATH."
|
|
exit 1
|
|
fi
|
|
echo "Running with PHP binary: $PHP_BIN $COMPOSER_BIN install --no-dev --optimize-autoloader"
|
|
"$PHP_BIN" "$COMPOSER_BIN" install --no-dev --optimize-autoloader
|
|
|
|
if ! CONFIGURED_PHP="$(read_setting_php_bin)"; then
|
|
echo "Failed to read configured PHP binary from settings." >&2
|
|
echo "Aborting to avoid running update with the wrong PHP binary." >&2
|
|
exit 1
|
|
fi
|
|
echo "Configured PHP binary from settings: ${CONFIGURED_PHP:-<empty>}"
|
|
PHP_BIN="$(resolve_configured_php_bin "$CONFIGURED_PHP" "$PHP_BIN")"
|
|
|
|
echo "Final PHP binary: $PHP_BIN"
|
|
if command -v "$PHP_BIN" >/dev/null 2>&1; then
|
|
echo "Final PHP version ($PHP_BIN): $($PHP_BIN -v | head -n 1)"
|
|
fi
|
|
if ! enforce_php_requirement "$PHP_BIN"; then
|
|
echo "Aborting update because selected PHP binary does not satisfy composer.json requirements." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Installing JS dependencies..."
|
|
npm install
|
|
|
|
echo "Building assets..."
|
|
npm run build
|
|
|
|
echo "Running migrations..."
|
|
echo "Running with PHP binary: $PHP_BIN artisan migrate --force"
|
|
"$PHP_BIN" artisan migrate --force
|
|
|
|
ensure_storage_link
|
|
|
|
echo "Syncing version/build to settings..."
|
|
echo "Running with PHP binary: $PHP_BIN -r <read composer.json version>"
|
|
VERSION="$("$PHP_BIN" -r '$c=json_decode(file_get_contents("composer.json"), true); echo $c["version"] ?? "";')"
|
|
echo "Running with PHP binary: $PHP_BIN -r <read composer.json build>"
|
|
BUILD="$("$PHP_BIN" -r '$c=json_decode(file_get_contents("composer.json"), true); echo $c["build"] ?? "";')"
|
|
echo "Computed from composer.json: VERSION=$VERSION, BUILD=$BUILD"
|
|
|
|
if [[ -n "$VERSION" || -n "$BUILD" ]]; then
|
|
echo "Updating settings version/build (VERSION=$VERSION, BUILD=$BUILD)..."
|
|
echo "Running with PHP binary: $PHP_BIN -r <write settings version/build>"
|
|
SPEEDBB_VERSION="$VERSION" SPEEDBB_BUILD="$BUILD" "$PHP_BIN" -r '
|
|
require "vendor/autoload.php";
|
|
$app = require "bootstrap/app.php";
|
|
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
|
|
$version = getenv("SPEEDBB_VERSION");
|
|
$build = getenv("SPEEDBB_BUILD");
|
|
if ($version !== false && $version !== "") {
|
|
\Illuminate\Support\Facades\DB::table("settings")->upsert(
|
|
[[
|
|
"key" => "version",
|
|
"value" => $version,
|
|
"created_at" => now(),
|
|
"updated_at" => now(),
|
|
]],
|
|
["key"],
|
|
["value", "updated_at"]
|
|
);
|
|
echo "Upserted version setting.\n";
|
|
}
|
|
if ($build !== false && $build !== "") {
|
|
\Illuminate\Support\Facades\DB::table("settings")->upsert(
|
|
[[
|
|
"key" => "build",
|
|
"value" => $build,
|
|
"created_at" => now(),
|
|
"updated_at" => now(),
|
|
]],
|
|
["key"],
|
|
["value", "updated_at"]
|
|
);
|
|
echo "Upserted build setting.\n";
|
|
}
|
|
' \
|
|
&& echo "Running with PHP binary: $PHP_BIN -r <verify settings version/build>" \
|
|
&& "$PHP_BIN" -r '
|
|
require "vendor/autoload.php";
|
|
$app = require "bootstrap/app.php";
|
|
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
|
|
$version = \App\Models\Setting::where("key", "version")->value("value");
|
|
$build = \App\Models\Setting::where("key", "build")->value("value");
|
|
echo "Settings now: version={$version}, build={$build}\n";
|
|
'
|
|
fi
|
|
|
|
echo "Update complete."
|
|
}
|
|
|
|
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
main "$@"
|
|
fi
|