From dfcf4245b8ef48c22cca6fca1ca9c9e11444fc4d Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 21 Oct 2026 14:20:50 +0200 Subject: [PATCH] feat(scripts): add patch script for security fix releases --- scripts/patch.sh | 115 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100755 scripts/patch.sh diff --git a/scripts/patch.sh b/scripts/patch.sh new file mode 100755 index 0000000000..94e757a1b7 --- /dev/null +++ b/scripts/patch.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +# Patches Coolify 4.0 to 4.4 to the security fix release of its own line. +# Only the coolify container is recreated, the compose files and the other containers stay as they are. +# Usage: curl -fsSL https://cdn.coollabs.io/coolify/patch.sh | sudo bash +set -euo pipefail + +SOURCE_DIR=/data/coolify/source +ENV_FILE="$SOURCE_DIR/.env" +TIMESTAMP="$(date +%Y-%m-%d-%H-%M-%S)" +LOG_FILE="$SOURCE_DIR/patch-$TIMESTAMP.log" +BACKUP_FILE="$ENV_FILE-$TIMESTAMP" +PATCH_VERSIONS=(4.0.1 4.1.3 4.2.1 4.3.24 4.4.7) + +BOLD=$'\033[1m' GREEN=$'\033[32m' BLUE=$'\033[34m' RED=$'\033[31m' RESET=$'\033[0m' + +heading() { printf '\n %s%s%s\n' "$BOLD" "$*" "$RESET"; } +info() { printf ' %sℹ%s %s\n' "$BLUE" "$RESET" "$*"; } +ok() { printf ' %s✔%s %s\n' "$GREEN" "$RESET" "$*"; } +die() { + printf ' %s✖%s %s\n' "$RED" "$RESET" "$*" >&2 + exit 1 +} + +main() { + check_installation + read_version + select_patch_version + check_compose_files + heading "Patching Coolify $current_version to $patch_version" + pull_image + update_env_file + restart_coolify + wait_for_coolify +} + +check_installation() { + heading "Checking the installation" + [ "$EUID" = 0 ] || die "run this script as root, for example with sudo" + [ -f "$ENV_FILE" ] && [ -f "$SOURCE_DIR/docker-compose.prod.yml" ] || die "Coolify is not installed in $SOURCE_DIR" + docker compose version > /dev/null 2>&1 || die "the Docker Compose plugin is not installed" + compose=(docker compose --env-file "$ENV_FILE" -f "$SOURCE_DIR/docker-compose.yml" -f "$SOURCE_DIR/docker-compose.prod.yml") + if [ -f "$SOURCE_DIR/docker-compose.custom.yml" ]; then + compose+=(-f "$SOURCE_DIR/docker-compose.custom.yml") + fi + if [ -f "$SOURCE_DIR/docker-compose.postgres-upgrade.yml" ]; then + compose+=(-f "$SOURCE_DIR/docker-compose.postgres-upgrade.yml") + fi + ok "Coolify is installed in $SOURCE_DIR" +} + +read_version() { + current_version="$(docker exec -w /var/www/html coolify php bootstrap/getVersion.php 2> /dev/null)" || die "the coolify container is not running" + ok "Coolify $BOLD$current_version$RESET is running" +} + +select_patch_version() { + [[ "$current_version" =~ ^4\.([0-4])\.([0-9]+)$ ]] || die "Coolify $current_version has no security fix release, upgrade to the latest version from the dashboard instead" + patch_version="${PATCH_VERSIONS[${BASH_REMATCH[1]}]}" + if [ "${BASH_REMATCH[2]}" -ge "${patch_version##*.}" ]; then + ok "Coolify $current_version already includes the security fixes" + exit 0 + fi +} + +check_compose_files() { + local has_realtime=false needs_realtime=true + if grep -q 'coollabsio/coolify-realtime:' "$SOURCE_DIR/docker-compose.prod.yml"; then + has_realtime=true + fi + if [[ "$patch_version" == 4.4.* ]]; then + needs_realtime=false + fi + [ "$has_realtime" = "$needs_realtime" ] || die "the compose files in $SOURCE_DIR do not belong to Coolify $current_version, upgrade to the latest version from the dashboard instead" + ok "the compose files belong to Coolify $current_version" +} + +pull_image() { + LATEST_IMAGE="$patch_version" "${compose[@]}" pull coolify < /dev/null >> "$LOG_FILE" 2>&1 || die "could not pull Coolify $patch_version, nothing was changed (log: $LOG_FILE)" + ok "pulled Coolify $patch_version" +} + +update_env_file() { + local key updated + cp "$ENV_FILE" "$BACKUP_FILE" + for key in LATEST_IMAGE COOLIFY_VERSION; do + if grep -q "^$key=" "$ENV_FILE"; then + updated="$(sed "s|^$key=.*|$key=$patch_version|" "$ENV_FILE")" + printf '%s\n' "$updated" > "$ENV_FILE" + fi + done + ok "backed up .env to $BACKUP_FILE" +} + +restart_coolify() { + # Detached, because the Coolify 4.4 terminal runs inside the coolify container and closes when it stops + LATEST_IMAGE="$patch_version" nohup "${compose[@]}" up -d --no-deps coolify < /dev/null >> "$LOG_FILE" 2>&1 & + wait $! || die "could not restart the coolify container (log: $LOG_FILE)" + ok "restarted the coolify container" +} + +wait_for_coolify() { + local status + for _ in $(seq 60); do + status="$(docker inspect --format '{{.State.Health.Status}}' coolify 2> /dev/null || true)" + if [ "$status" = healthy ]; then + ok "Coolify $BOLD$patch_version$RESET is healthy and runs PHP $(docker exec coolify php -r 'echo PHP_VERSION;')" + return + fi + sleep 5 + done + info "roll back with: cp $BACKUP_FILE $ENV_FILE && LATEST_IMAGE=$current_version ${compose[*]} up -d --no-deps coolify" + die "Coolify $patch_version is not healthy after 5 minutes, check it with: docker logs coolify" +} + +main