chore: merge main into release/v4.5 (#12192)

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Furox <177975472+Furox-Art@users.noreply.github.com>
Co-authored-by: johandenhollander <johan.den.hollander@finalist.nl>
Co-authored-by: João Cruz <161638790+SrJooJ@users.noreply.github.com>
Co-authored-by: Saim Hashmi <hashmisaim037@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
github-actions[bot]
2026-10-18 23:43:20 +02:00
committed by GitHub
co-authored by Andras Bacsai Claude Opus 5.5 Furox johandenhollander João Cruz Saim Hashmi dependabot[bot]
parent 5dddcfd276
commit e3d4411ff2
246 changed files with 32148 additions and 1316 deletions
+44 -126
View File
@@ -7,7 +7,7 @@
## ROOT_USER_PASSWORD - Predefined root user password
## DOCKER_ADDRESS_POOL_BASE - Custom Docker address pool base (default: 10.0.0.0/8)
## DOCKER_ADDRESS_POOL_SIZE - Custom Docker address pool size (default: 24)
## DOCKER_POOL_FORCE_OVERRIDE - Force override Docker address pool configuration (default: false)
## DOCKER_POOL_FORCE_OVERRIDE - Force override Docker address pool configuration, also when Docker must restart with running containers (default: false)
## AUTOUPDATE - Set to "false" to disable auto-updates
## REGISTRY_URL - Custom registry URL for Docker images (default: docker.io)
@@ -31,14 +31,21 @@ fi
# Resource directories hold data whose owner and mode the resource images or a non-root SSH user need.
# Set their owner and mode only when they are created, never on an upgrade (#11945).
for COOLIFY_DIRECTORY in /data/coolify /data/coolify/{applications,databases,backups,services,proxy,proxy/dynamic,sentinel}; do
if [ ! -d "$COOLIFY_DIRECTORY" ]; then
mkdir -p "$COOLIFY_DIRECTORY"
chown 9999:root "$COOLIFY_DIRECTORY"
chmod 700 "$COOLIFY_DIRECTORY"
create_data_directory() {
if [ ! -d "$1" ]; then
mkdir -p "$1"
chown 9999:root "$1"
chmod 700 "$1"
fi
done
mkdir -p /data/coolify/source /data/coolify/images /data/coolify/ssh/{keys,mux}
}
# Run only after the checks and downloads succeed, so a failed run does not change the data directories (#11973).
create_coolify_directories() {
for COOLIFY_DIRECTORY in /data/coolify/{applications,databases,backups,services,proxy,proxy/dynamic,sentinel}; do
create_data_directory "$COOLIFY_DIRECTORY"
done
mkdir -p /data/coolify/images /data/coolify/ssh/{keys,mux}
}
# Coolify's own directories, used by the coolify container (UID 9999)
set_coolify_directory_permissions() {
@@ -46,7 +53,9 @@ set_coolify_directory_permissions() {
chmod -R 700 /data/coolify/{source,ssh,images}
}
set_coolify_directory_permissions
# The installation log and the downloaded files go to /data/coolify/source, so only it is created before the checks.
create_data_directory /data/coolify
mkdir -p /data/coolify/source
INSTALLATION_LOG_WITH_DATE="/data/coolify/source/installation-${DATE}.log"
@@ -828,131 +837,39 @@ step_done "${DOCKER_SERVER_VERSION:+v$DOCKER_SERVER_VERSION}"
step_start "Configuring Docker daemon" "log rotation · address pools"
echo " - Network pool configuration: ${DOCKER_ADDRESS_POOL_BASE}/${DOCKER_ADDRESS_POOL_SIZE}"
echo " - To override existing configuration: DOCKER_POOL_FORCE_OVERRIDE=true"
mkdir -p /etc/docker
# Backup original daemon.json if it exists
if [ -f /etc/docker/daemon.json ]; then
cp /etc/docker/daemon.json /etc/docker/daemon.json.original-"$DATE"
fi
# Create coolify configuration with or without address pools based on whether they were explicitly provided
# Changing daemon.json needs a Docker restart, which stops every running container.
# Coolify sets log rotation and the network pool only when no pool is configured or DOCKER_POOL_FORCE_OVERRIDE=true,
# keeps all other keys, and does not restart a Docker daemon with running containers unless DOCKER_POOL_FORCE_OVERRIDE=true.
if [ "$DOCKER_POOL_FORCE_OVERRIDE" = true ] || [ "$EXISTING_POOL_CONFIGURED" = false ]; then
# First check if the configuration would actually change anything
if [ -f /etc/docker/daemon.json ]; then
CURRENT_POOL_BASE=$(jq -r '.["default-address-pools"][0].base' /etc/docker/daemon.json 2>/dev/null)
CURRENT_POOL_SIZE=$(jq -r '.["default-address-pools"][0].size' /etc/docker/daemon.json 2>/dev/null)
if [ "$CURRENT_POOL_BASE" = "$DOCKER_ADDRESS_POOL_BASE" ] && [ "$CURRENT_POOL_SIZE" = "$DOCKER_ADDRESS_POOL_SIZE" ]; then
echo " - Network pool configuration unchanged, skipping update"
NEED_MERGE=false
mkdir -p /etc/docker
CURRENT_DAEMON_CONFIG='{}'
if [ -s /etc/docker/daemon.json ] && ! CURRENT_DAEMON_CONFIG=$(jq -S . /etc/docker/daemon.json 2>/dev/null); then
CURRENT_DAEMON_CONFIG=""
fi
if [ -z "$CURRENT_DAEMON_CONFIG" ]; then
warn "Docker daemon configuration not changed: /etc/docker/daemon.json is not valid JSON."
else
NEW_DAEMON_CONFIG=$(jq -S --arg base "$DOCKER_ADDRESS_POOL_BASE" --argjson size "$DOCKER_ADDRESS_POOL_SIZE" \
'. * {"log-driver": "json-file", "log-opts": {"max-size": "10m", "max-file": "3"}, "default-address-pools": [{"base": $base, "size": $size}]}' <<<"$CURRENT_DAEMON_CONFIG")
RUNNING_CONTAINERS=$({ docker ps -q 2>/dev/null || true; } | wc -l)
if [ "$NEW_DAEMON_CONFIG" = "$CURRENT_DAEMON_CONFIG" ]; then
echo " - Configuration is up to date"
elif [ "$RUNNING_CONTAINERS" -gt 0 ] && [ "$DOCKER_POOL_FORCE_OVERRIDE" != true ]; then
warn "Docker daemon configuration not changed: log rotation and the ${DOCKER_ADDRESS_POOL_BASE} (size ${DOCKER_ADDRESS_POOL_SIZE}) network pool need a Docker restart, which stops ${RUNNING_CONTAINERS} running container(s) and can change the docker0 bridge IP. To apply it, run the installer again with DOCKER_POOL_FORCE_OVERRIDE=true."
UI_STEP_DETAIL="skipped · containers running"
else
# If force override is enabled or no existing configuration exists,
# create a new configuration with the specified address pools
echo " - Creating new Docker configuration with network pool: ${DOCKER_ADDRESS_POOL_BASE}/${DOCKER_ADDRESS_POOL_SIZE}"
cat >/etc/docker/daemon.json <<EOL
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"default-address-pools": [
{"base":"${DOCKER_ADDRESS_POOL_BASE}","size":${DOCKER_ADDRESS_POOL_SIZE}}
]
}
EOL
NEED_MERGE=true
fi
else
# No existing configuration, create new one
echo " - Creating new Docker configuration with network pool: ${DOCKER_ADDRESS_POOL_BASE}/${DOCKER_ADDRESS_POOL_SIZE}"
cat >/etc/docker/daemon.json <<EOL
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"default-address-pools": [
{"base":"${DOCKER_ADDRESS_POOL_BASE}","size":${DOCKER_ADDRESS_POOL_SIZE}}
]
}
EOL
NEED_MERGE=true
fi
else
# Check if we need to update log settings
if [ -f /etc/docker/daemon.json ] && jq -e '.["log-driver"] == "json-file" and .["log-opts"]["max-size"] == "10m" and .["log-opts"]["max-file"] == "3"' /etc/docker/daemon.json >/dev/null 2>&1; then
echo " - Log configuration is up to date"
NEED_MERGE=false
else
# Create a configuration without address pools to preserve existing ones
cat >/etc/docker/daemon.json.coolify <<EOL
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
EOL
NEED_MERGE=true
fi
fi
# Remove the duplicate daemon.json creation since we handle it above
if ! [ -f /etc/docker/daemon.json ]; then
# If no daemon.json exists, create it with default settings
cat >/etc/docker/daemon.json <<EOL
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"default-address-pools": [
{"base":"${DOCKER_ADDRESS_POOL_BASE}","size":${DOCKER_ADDRESS_POOL_SIZE}}
]
}
EOL
NEED_MERGE=false
fi
if [ -s /etc/docker/daemon.json.original-"$DATE" ]; then
DIFF=$(diff <(jq --sort-keys . /etc/docker/daemon.json) <(jq --sort-keys . /etc/docker/daemon.json.original-"$DATE") || true)
if [ "$DIFF" != "" ]; then
echo " - Checking configuration changes..."
# Check if address pools were changed
if echo "$DIFF" | grep -q "default-address-pools"; then
if [ "$DOCKER_POOL_BASE_PROVIDED" = true ] || [ "$DOCKER_POOL_SIZE_PROVIDED" = true ]; then
echo " - Network pool updated per user request"
else
echo " - Warning: Network pool modified without explicit request"
if [ -f /etc/docker/daemon.json ]; then
cp /etc/docker/daemon.json /etc/docker/daemon.json.original-"$DATE"
fi
fi
# Remove this redundant restart since we already restarted when writing the config
echo " - Configuration changes confirmed"
if [ "$NEED_MERGE" = true ]; then
printf '%s\n' "$NEW_DAEMON_CONFIG" >/etc/docker/daemon.json
echo " - Network pool configuration: ${DOCKER_ADDRESS_POOL_BASE} (size ${DOCKER_ADDRESS_POOL_SIZE})"
echo " - Configuration updated - restarting Docker daemon..."
restart_docker_service
else
echo " - Configuration is up to date"
fi
else
echo " - Configuration is up to date"
fi
else
if [ "$NEED_MERGE" = true ]; then
echo " - Configuration updated - restarting Docker daemon..."
restart_docker_service
else
echo " - Configuration is up to date"
fi
echo " - Using the existing network pool ${DOCKER_ADDRESS_POOL_BASE} (size ${DOCKER_ADDRESS_POOL_SIZE})"
echo " - To override it: DOCKER_POOL_FORCE_OVERRIDE=true"
fi
step_done
@@ -1070,6 +987,7 @@ step_done
step_start "Checking SSH key for localhost access"
SSH_KEY_DETAIL="existing key"
create_coolify_directories
if [ ! -f ~/.ssh/authorized_keys ]; then
mkdir -p ~/.ssh
chmod 700 ~/.ssh
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env bash
# Show how a code change alters the service parser output for every template and the reference fixture.
#
# Usage:
# ./scripts/parser-snapshot-diff save # 1. before you change code: save the current output
# ./scripts/parser-snapshot-diff baseline # 2. after the change: compare with the saved output
# ./scripts/parser-snapshot-diff [git-ref] # or compare with a git ref (default: main)
# ./scripts/parser-snapshot-diff baseline -- --filter='"template appwrite"' # extra Pest arguments
#
# Each run parses everything with tests/Feature/ServiceParserReferenceTest.php (about 90 seconds).
# A git ref is exported with git archive to a temporary directory and parsed with the current test,
# fixtures and templates, so the diff shows only the effect of the code change.
# The terminal shows one line per changed case; the full diff is written to storage/framework/testing/.
set -euo pipefail
repo="$(git -C "$(dirname "$0")/.." rev-parse --show-toplevel)"
mode="${1:-main}"
shift || true
[[ "${1:-}" == "--" ]] && shift
pest_args=("$@")
testing="$repo/storage/framework/testing" # gitignored, same file system as the repository
saved="$testing/parser-snapshots-baseline"
report="$testing/parser-snapshot-diff.diff"
work="$(mktemp -d "$testing/parser-snapshot-diff.XXXXXX")"
trap 'rm -rf "$work"' EXIT
# Run the golden test in <dir> with UPDATE_PARSER_SNAPSHOTS=1 and copy its expected files to <target>.
parse_into() {
local dir="$1" target="$2" label="$3" expected="$1/tests/Fixtures/parser/expected" backup="$work/expected-backup" status=0
echo "==> Parsing on $label (about 90 seconds)"
# The run rewrites the expected files: put the previous ones back afterwards, so the repository is unchanged.
rm -rf "$backup"
cp -r "$expected" "$backup"
rm -rf "$expected/templates"
(cd "$dir" && UPDATE_PARSER_SNAPSHOTS=1 php -d memory_limit=2G artisan test --compact \
tests/Feature/ServiceParserReferenceTest.php "${pest_args[@]}" >"$work/run.log" 2>&1) || status=$?
rm -rf "$target"
cp -r "$expected" "$target"
rm -rf "$expected"
mv "$backup" "$expected"
if [[ $status -ne 0 ]]; then
echo "Test run on $label failed:" >&2
tail -30 "$work/run.log" >&2
exit 1
fi
}
case "$mode" in
save)
parse_into "$repo" "$saved" "working tree"
echo "Saved baseline: $saved"
echo "Change the code, then run: ./scripts/parser-snapshot-diff baseline"
exit 0
;;
baseline)
[[ -d "$saved" ]] || { echo "No saved baseline. Run ./scripts/parser-snapshot-diff save first." >&2; exit 1; }
base_label="saved baseline"
cp -r "$saved" "$work/base-expected"
;;
*)
base_label="$mode"
base="$work/base"
mkdir -p "$base"
echo "==> Exporting $mode ($(git -C "$repo" rev-parse --short "$mode"))"
git -C "$repo" archive "$mode" | tar -x -C "$base"
# Hard links: fast and no extra disk space. A symlink would make Composer load the classes of $repo.
cp -al "$repo/vendor" "$base/vendor"
[[ -f "$repo/.env" ]] && cp "$repo/.env" "$base/.env"
rm -rf "$base/templates/compose" "$base/tests/Fixtures/parser"
cp -r "$repo/templates/compose" "$base/templates/compose"
mkdir -p "$base/tests/Fixtures/parser"
cp "$repo"/tests/Fixtures/parser/*.yaml "$base/tests/Fixtures/parser/"
cp "$repo/tests/Feature/ServiceParserReferenceTest.php" "$base/tests/Feature/"
parse_into "$base" "$work/base-expected" "$mode"
;;
esac
parse_into "$repo" "$work/current-expected" "working tree"
echo
if diff -ru "$work/base-expected" "$work/current-expected" | sed -E "s#$work/(base|current)-expected/##g" >"$report"; then
echo "No differences from $base_label: the parser output of every case is the same."
exit 0
fi
# Line 2 is the first step (1_deploy): an error there means the case did not parse at all on that side.
first_step_error() { sed -n 2p "$1" | grep '^ error:' | cut -c10-130 || true; }
summary() {
local file="$1" base_file="$work/base-expected/$1" current_file="$work/current-expected/$1" base_error current_error
[[ -f "$base_file" ]] || { echo " new case $file"; return; }
[[ -f "$current_file" ]] || { echo " removed case $file"; return; }
base_error="$(first_step_error "$base_file")"
current_error="$(first_step_error "$current_file")"
if [[ -n "$base_error" && -z "$current_error" ]]; then
echo " base error $file: $base_error"
elif [[ -n "$current_error" && -z "$base_error" ]]; then
echo " CURRENT ERROR $file: $current_error"
else
echo " changed $file (+$(diff "$base_file" "$current_file" | grep -c '^>' || true) -$(diff "$base_file" "$current_file" | grep -c '^<' || true))"
fi
}
changed_cases=()
while IFS= read -r file; do
cmp -s "$work/base-expected/$file" "$work/current-expected/$file" || changed_cases+=("$file")
done < <( (cd "$work/base-expected" && find . -name '*.yaml'; cd "$work/current-expected" && find . -name '*.yaml') | sed 's|^\./||' | sort -u)
lines="$(for file in "${changed_cases[@]}"; do summary "$file"; done)"
echo "${#changed_cases[@]} case(s) differ from $base_label:"
echo "$lines"
echo
echo "Full diff: $report"
if grep -q '^ base error' <<<"$lines"; then
echo "Note: 'base error' cases did not parse on $base_label, so there is no base output to compare with."
fi
exit 1
+72 -19
View File
@@ -38,6 +38,65 @@ write_status() {
echo "${step}|${message}|$(date -Iseconds)" > "$STATUS_FILE"
}
# Print the first gateway of a network that Docker reports with a prefix length (fd00::1/64).
# Docker Engine 27.0 to 28.0.0 does this for IPv6 until the daemon restarts (https://github.com/moby/moby/pull/49520).
# The Docker CLI and Compose in the helper image fail on such a network with a ParseAddr error.
network_gateway_with_prefix() {
docker network inspect "$1" --format '{{range .IPAM.Config}}{{.Gateway}} {{end}}' 2>&1 | grep -oE '[0-9A-Fa-f:.]+/[0-9]+' | head -n1
}
# Make sure the coolify network exists and the helper image can read it.
# It is created when starting Coolify with docker compose
ensure_coolify_network() {
log "Checking Docker network 'coolify'..."
if ! docker network inspect coolify >/dev/null 2>&1; then
log "Network 'coolify' does not exist, creating..."
if ! docker network create --attachable --ipv6 coolify 2>/dev/null; then
log "Failed to create network with IPv6, trying without IPv6..."
docker network create --attachable coolify 2>/dev/null
log "Network 'coolify' created without IPv6"
elif [ -n "$(network_gateway_with_prefix coolify)" ]; then
# No container uses the new network yet, so it is safe to recreate it.
log "Docker reports the IPv6 gateway of network 'coolify' with a prefix length, recreating it without IPv6..."
docker network rm coolify >/dev/null 2>&1
docker network create --attachable coolify 2>/dev/null
log "Network 'coolify' created without IPv6"
else
log "Network 'coolify' created with IPv6 support"
fi
return 0
fi
log "Network 'coolify' already exists"
local gateway
gateway=$(network_gateway_with_prefix coolify)
if [ -n "$gateway" ]; then
local message="Docker Engine $(docker version --format '{{.Server.Version}}' 2>/dev/null) reports the gateway of the 'coolify' network as ${gateway}, which Coolify cannot read (Docker Engine bug, fixed in 28.0.1). Run 'systemctl restart docker' (this restarts all containers) or upgrade Docker Engine, then upgrade Coolify again."
log "ERROR: ${message}"
write_status "error" "$message"
echo " ERROR: ${message}"
return 1
fi
}
# Pull an image. Retry with backoff, because the registry can rate limit many instances that pull at the same time.
pull_image() {
local image="$1"
local attempt=1
local max_attempts=5
local delay=15
until docker pull "$image" >>"$LOGFILE" 2>&1; do
if [ "$attempt" -ge "$max_attempts" ]; then
return 1
fi
log "Pull of $image failed (attempt $attempt/$max_attempts). Retrying in ${delay}s..."
echo " Pull failed (attempt $attempt/$max_attempts). Retrying in ${delay}s..."
sleep "$((delay + RANDOM % 10))"
attempt=$((attempt + 1))
delay=$((delay * 2))
done
}
echo ""
echo "=========================================="
echo " Coolify Upgrade - ${DATE}"
@@ -159,21 +218,7 @@ fi
log "Environment variables check complete"
echo " Done."
# Make sure coolify network exists
# It is created when starting Coolify with docker compose
log "Checking Docker network 'coolify'..."
if ! docker network inspect coolify >/dev/null 2>&1; then
log "Network 'coolify' does not exist, creating..."
if ! docker network create --attachable --ipv6 coolify 2>/dev/null; then
log "Failed to create network with IPv6, trying without IPv6..."
docker network create --attachable coolify 2>/dev/null
log "Network 'coolify' created without IPv6"
else
log "Network 'coolify' created with IPv6 support"
fi
else
log "Network 'coolify' already exists"
fi
ensure_coolify_network || exit 1
mkdir -p /data/coolify/images/{avatars,project-icons}
chown -R 9999:root /data/coolify/images
@@ -205,7 +250,7 @@ echo " This may take a few minutes depending on your connection."
HELPER_IMAGE="${REGISTRY_URL:-docker.io}/coollabsio/coolify-helper:${LATEST_HELPER_VERSION}"
echo " - Pulling $HELPER_IMAGE..."
log "Pulling image: $HELPER_IMAGE"
if docker pull "$HELPER_IMAGE" >>"$LOGFILE" 2>&1; then
if pull_image "$HELPER_IMAGE"; then
log "Successfully pulled $HELPER_IMAGE"
else
log "ERROR: Failed to pull $HELPER_IMAGE"
@@ -220,7 +265,7 @@ for IMAGE in $IMAGES; do
if [ -n "$IMAGE" ]; then
echo " - Pulling $IMAGE..."
log "Pulling image: $IMAGE"
if docker pull "$IMAGE" >>"$LOGFILE" 2>&1; then
if pull_image "$IMAGE"; then
log "Successfully pulled $IMAGE"
else
log "ERROR: Failed to pull $IMAGE"
@@ -298,8 +343,16 @@ nohup bash -c "
fi
log 'Running docker compose up...'
docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock \${DOCKER_CONFIG_MOUNT} --rm \${REGISTRY_URL:-docker.io}/coollabsio/coolify-helper:\${LATEST_HELPER_VERSION} bash -c \"LATEST_IMAGE=\${LATEST_IMAGE} docker compose --env-file /data/coolify/source/.env \${COMPOSE_FILES} up -d --remove-orphans --wait --wait-timeout 60\" >>\"\$LOGFILE\" 2>&1
log 'Docker compose up completed'
if docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock \${DOCKER_CONFIG_MOUNT} --rm \${REGISTRY_URL:-docker.io}/coollabsio/coolify-helper:\${LATEST_HELPER_VERSION} bash -c \"LATEST_IMAGE=\${LATEST_IMAGE} docker compose --env-file /data/coolify/source/.env \${COMPOSE_FILES} up -d --remove-orphans --wait --wait-timeout 60\" >>\"\$LOGFILE\" 2>&1; then
log 'Docker compose up completed'
elif [ \"\$(docker inspect --format '{{.State.Running}}' coolify 2>/dev/null)\" = 'true' ]; then
# A slow first start (for example long migrations) can pass the wait timeout. Coolify is still starting.
log 'Docker compose up did not finish waiting, but the coolify container is running'
else
log 'ERROR: Docker compose up failed and the coolify container is not running'
write_status 'error' \"Coolify did not start. Check the upgrade log: \$LOGFILE\"
exit 1
fi
# Final log entry
echo '' >>\"\$LOGFILE\"