mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-04 10:42:23 +00:00
ci(release): build and release a nightly twice a day
- Remove other/nightly, the nightly channel uses the production files, so there are no longer two copies to keep in sync - sync-cdn.sh --nightly syncs the files of the commit the nightly is built from to /coolify/nightly on the CDN, with the nightly version in its versions.json and in /coolify/release.json, and pushes them straight to main of coollabs-cdn - version.sh picks the branch the nightly comes from, the release branch of the next version after the newest stable one or next, and names the version <preview>.<YYYYMMDD>.<N> - The image is labeled with its version and commit, the nightly workflow reads both from the image nightly points at to count up N and to skip a run without a new commit
This commit is contained in:
29 files changed
+232
-2355
No files matched your search
@@ -5,6 +5,15 @@ on:
|
||||
- cron: "50 7 * * 1"
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
inputs:
|
||||
ref:
|
||||
description: The commit to build, the commit of the run when empty
|
||||
type: string
|
||||
default: ""
|
||||
coolify:
|
||||
description: The Coolify version to build, the version release.json sets for the branch when empty
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
permissions: {}
|
||||
|
||||
@@ -26,10 +35,11 @@ jobs:
|
||||
runs-on: ubuntu-26.04 # https://github.com/actions/runner-images
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
coolify: ${{ steps.version.outputs.coolify }}
|
||||
coolify: ${{ inputs.coolify || steps.version.outputs.coolify }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: false
|
||||
|
||||
- id: version
|
||||
@@ -60,11 +70,16 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set the build arguments
|
||||
id: args
|
||||
run: echo "epoch=$(git log -1 --format=%ct)" >> "$GITHUB_OUTPUT"
|
||||
run: |
|
||||
{
|
||||
echo "epoch=$(git log -1 --format=%ct)"
|
||||
echo "sha=$(git rev-parse HEAD)"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # https://github.com/docker/setup-buildx-action/releases/tag/v4.4.1
|
||||
with:
|
||||
@@ -80,6 +95,7 @@ jobs:
|
||||
build-args: |
|
||||
COOLIFY_VERSION=${{ needs.version.outputs.coolify }}
|
||||
SOURCE_DATE_EPOCH=${{ steps.args.outputs.epoch }}
|
||||
GIT_SHA=${{ steps.args.outputs.sha }}
|
||||
|
||||
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # https://github.com/actions/upload-artifact/releases/tag/v7.0.2
|
||||
with:
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
name: Nightly
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "10 9,21 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
cache-mode: none
|
||||
|
||||
concurrency:
|
||||
group: nightly
|
||||
cancel-in-progress: false
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
version:
|
||||
name: Select the nightly
|
||||
if: ${{ github.repository == 'coollabsio/coolify' }}
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-26.04 # https://github.com/actions/runner-images
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
nightly: ${{ steps.version.outputs.nightly }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1
|
||||
with:
|
||||
ref: next
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- id: version
|
||||
env:
|
||||
BRANCH: next
|
||||
run: |
|
||||
labels="$(docker buildx imagetools inspect ghcr.io/coollabsio/coolify:nightly --format '{{ json (index .Image "linux/amd64").Config.Labels }}')" || labels='{}'
|
||||
NIGHTLY_VERSION="$(jq -r '.["org.opencontainers.image.version"] // empty' <<< "$labels")"
|
||||
NIGHTLY_REVISION="$(jq -r '.["org.opencontainers.image.revision"] // empty' <<< "$labels")"
|
||||
export NIGHTLY_VERSION NIGHTLY_REVISION
|
||||
nightly="$(scripts/version.sh --nightly)"
|
||||
echo "nightly=$nightly" | tee -a "$GITHUB_OUTPUT"
|
||||
|
||||
build:
|
||||
name: Build
|
||||
needs: version
|
||||
if: ${{ needs.version.outputs.nightly != '' }}
|
||||
permissions:
|
||||
contents: read
|
||||
uses: $/.github/workflows/build.yaml
|
||||
with:
|
||||
ref: ${{ fromJSON(needs.version.outputs.nightly).revision }}
|
||||
coolify: ${{ fromJSON(needs.version.outputs.nightly).image }}
|
||||
|
||||
publish:
|
||||
name: Publish ${{ matrix.line.tag }}
|
||||
needs: [version, build]
|
||||
permissions:
|
||||
packages: write # push the built nightly to ghcr.io and move the nightly tag
|
||||
environment:
|
||||
name: release
|
||||
deployment: false
|
||||
runs-on: ubuntu-26.04 # https://github.com/actions/runner-images
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
line: ${{ fromJSON(needs.version.outputs.nightly).lines }}
|
||||
steps:
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # https://github.com/actions/download-artifact/releases/tag/v8.0.2
|
||||
with:
|
||||
pattern: image-*
|
||||
path: ${{ runner.temp }}/images
|
||||
|
||||
- uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # https://github.com/docker/login-action/releases/tag/v4.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # https://github.com/docker/login-action/releases/tag/v4.7.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Push the built nightly
|
||||
env:
|
||||
TAG: ${{ matrix.line.tag }}
|
||||
LINE: ${{ matrix.line.name }}
|
||||
run: |
|
||||
sources=()
|
||||
for index in "$RUNNER_TEMP"/images/*/index.json; do
|
||||
sources+=("oci-layout://${index%/index.json}@$(jq -r '.manifests[0].digest' "$index")")
|
||||
done
|
||||
for repository in ghcr.io/coollabsio/coolify docker.io/coollabsio/coolify; do
|
||||
tags=(--tag "$repository:${TAG#v}")
|
||||
[ "$LINE" != latest ] || tags+=(--tag "$repository:nightly")
|
||||
docker buildx imagetools create "${tags[@]}" "${sources[@]}"
|
||||
done
|
||||
|
||||
cdn:
|
||||
name: Sync the nightly channel of the CDN
|
||||
needs: [version, publish]
|
||||
permissions:
|
||||
contents: read
|
||||
environment:
|
||||
name: release
|
||||
deployment: false
|
||||
runs-on: ubuntu-26.04 # https://github.com/actions/runner-images
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # https://github.com/actions/checkout/releases/tag/v7.0.1
|
||||
with:
|
||||
ref: ${{ fromJSON(needs.version.outputs.nightly).revision }}
|
||||
persist-credentials: false
|
||||
|
||||
- env:
|
||||
GH_TOKEN: ${{ secrets.CDN_TOKEN }}
|
||||
IMAGE_VERSION: ${{ fromJSON(needs.version.outputs.nightly).image }}
|
||||
run: |
|
||||
gh auth setup-git
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
scripts/sync-cdn.sh --nightly "$IMAGE_VERSION"
|
||||
+23
-2
@@ -118,9 +118,29 @@ Every release is tagged `v<version>`, such as `v4.6.0-beta.1`, and publishes the
|
||||
> [!IMPORTANT]
|
||||
> Cut `v4.x` from `main`, or bring an existing one up to date with `main`, before `release/v5.0` is merged into `main`. Afterwards `main` is v5 and v4 lives on `v4.x` alone.
|
||||
|
||||
## Nightlies
|
||||
|
||||
The nightly workflow publishes a nightly twice a day when its branch has a new commit, versioned `<base>.<YYYYMMDD>.<N>`:
|
||||
|
||||
| State | Nightly |
|
||||
| ------------------------------ | -------------------------- |
|
||||
| nothing bumped since 5.0.4 | `5.1.0-alpha.0.20261014.1` |
|
||||
| after 5.1.0-alpha.1 | `5.1.0-alpha.1.20261014.1` |
|
||||
| after 5.1.0-beta.1 | `5.1.0-beta.1.20261014.1` |
|
||||
| second nightly on the same day | `5.1.0-beta.1.20261014.2` |
|
||||
| after 5.1.0-rc.1 | `5.1.0-rc.1.20261015.1` |
|
||||
| after 5.1.0 | `5.2.0-alpha.0.20261016.1` |
|
||||
|
||||
- It comes from `next`, or from the lowest release branch while one is in progress: `release/v5.1` takes it over once it is cut and hands it back once `5.1.0` is released
|
||||
- The base is the prerelease `preview` of that branch sets in `release.json`, else its next version with `-alpha.0`, which is never released
|
||||
- N counts up per day and base
|
||||
- No git tag and no GitHub release, only its image tag, the moving `nightly` tag and the nightly channel of the CDN
|
||||
|
||||
Print what the next run publishes with `scripts/version.sh --nightly`.
|
||||
|
||||
## Pull requests
|
||||
|
||||
Every push to a pull request from a branch of this repository publishes `ghcr.io/coollabsio/coolify:pr-<number>`, versioned `<version of the base branch>-pr.<number>`. Pull request 1234 into `next` is `pr-1234` with the version `4.6.0-beta.1-pr.1234`, which sorts after `4.6.0-beta.1` and before `4.6.0-beta.2`.
|
||||
Every push to a pull request from a branch of this repository publishes `ghcr.io/coollabsio/coolify:pr-<number>`, versioned `<version of the base branch>-pr.<number>`. Pull request 1234 into `next` is `pr-1234` with the version `4.6.0-beta.1-pr.1234`, which sorts after `4.6.0-beta.1` and its nightlies and before `4.6.0-beta.2`.
|
||||
|
||||
- Pull requests from forks and Dependabot are built, not published
|
||||
- Pull request builds are published to ghcr.io only, as the Docker Hub credentials are in the `release` environment, which no pull request run can use
|
||||
@@ -134,8 +154,9 @@ Every push to a pull request from a branch of this repository publishes `ghcr.io
|
||||
| --------- | ------------------- | --------------------------------------------------------- |
|
||||
| `stable` | `/coolify/` | the release workflow when `latest` moves, a pull request |
|
||||
| `preview` | `/coolify/preview/` | the release workflow when `preview` moves, pushed to main |
|
||||
| `nightly` | `/coolify/nightly/` | the nightly workflow after every nightly, pushed to main |
|
||||
|
||||
- The preview channel takes betas and release candidates, no alphas
|
||||
- The preview channel takes betas and release candidates, alphas reach the nightly channel only
|
||||
- `CDN_TOKEN` in the environment `release` is a fine-grained token for `coollabsio/coollabs-cdn` alone, with read and write access to contents and pull requests
|
||||
- `versions.json` of a channel names its Coolify version and the helper, Sentinel and Traefik versions of `upstream.json`
|
||||
- `/coolify/release.json` lists the Coolify version of every channel, each sync sets the version of its channel
|
||||
|
||||
@@ -220,5 +220,10 @@ RUN mkdir -p /etc/nginx/conf.d && \
|
||||
COPY --from=minio-client /usr/bin/mc /usr/bin/mc
|
||||
RUN chmod +x /usr/bin/mc
|
||||
|
||||
ARG GIT_SHA
|
||||
|
||||
LABEL org.opencontainers.image.version="${COOLIFY_VERSION}" \
|
||||
org.opencontainers.image.revision="${GIT_SHA}"
|
||||
|
||||
# Switch to non-root user
|
||||
USER www-data
|
||||
@@ -1,17 +0,0 @@
|
||||
APP_ID=
|
||||
APP_NAME=Coolify
|
||||
APP_KEY=
|
||||
|
||||
DB_USERNAME=coolify
|
||||
DB_PASSWORD=
|
||||
|
||||
REDIS_PASSWORD=
|
||||
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_APP_KEY=
|
||||
PUSHER_APP_SECRET=
|
||||
ROOT_USERNAME=
|
||||
ROOT_USER_EMAIL=
|
||||
ROOT_USER_PASSWORD=
|
||||
|
||||
REGISTRY_URL=docker.io
|
||||
@@ -1,89 +0,0 @@
|
||||
services:
|
||||
# Placeholder for "soketi" overrides in docker-compose.custom.yml from versions before 4.4.
|
||||
# Realtime runs inside the coolify container now. Without this entry, such an override is an
|
||||
# incomplete service and every upgrade fails. Zero replicas means it never starts, and
|
||||
# "docker compose up" removes the old coolify-realtime container (an inactive profile keeps it).
|
||||
# It uses the coolify image, so install and upgrade do not pull another image.
|
||||
soketi:
|
||||
image: "${REGISTRY_URL:-docker.io}/coollabsio/coolify:${LATEST_IMAGE:-latest}"
|
||||
deploy:
|
||||
replicas: 0
|
||||
coolify:
|
||||
image: "${REGISTRY_URL:-docker.io}/coollabsio/coolify:${LATEST_IMAGE:-latest}"
|
||||
volumes:
|
||||
- type: bind
|
||||
source: /data/coolify/source/.env
|
||||
target: /var/www/html/.env
|
||||
read_only: true
|
||||
- /data/coolify/ssh:/var/www/html/storage/app/ssh
|
||||
- /data/coolify/applications:/var/www/html/storage/app/applications
|
||||
- /data/coolify/databases:/var/www/html/storage/app/databases
|
||||
- /data/coolify/services:/var/www/html/storage/app/services
|
||||
- /data/coolify/backups:/var/www/html/storage/app/backups
|
||||
- /data/coolify/images:/var/www/html/storage/app/images
|
||||
environment:
|
||||
- APP_ENV=${APP_ENV:-production}
|
||||
- PHP_MEMORY_LIMIT=${PHP_MEMORY_LIMIT:-256M}
|
||||
- PHP_FPM_PM_CONTROL=${PHP_FPM_PM_CONTROL:-dynamic}
|
||||
- PHP_FPM_PM_START_SERVERS=${PHP_FPM_PM_START_SERVERS:-1}
|
||||
- PHP_FPM_PM_MIN_SPARE_SERVERS=${PHP_FPM_PM_MIN_SPARE_SERVERS:-1}
|
||||
- PHP_FPM_PM_MAX_SPARE_SERVERS=${PHP_FPM_PM_MAX_SPARE_SERVERS:-10}
|
||||
- PUSHER_BACKEND_PORT=${PUSHER_BACKEND_PORT:-6001}
|
||||
env_file:
|
||||
- /data/coolify/source/.env
|
||||
ports:
|
||||
- "${APP_PORT:-8000}:8080"
|
||||
- "${REVERB_PORT:-${SOKETI_PORT:-6001}}:6001"
|
||||
- "6002:6002"
|
||||
expose:
|
||||
- "${APP_PORT:-8000}"
|
||||
- "6001"
|
||||
- "6002"
|
||||
networks:
|
||||
coolify:
|
||||
# Realtime ran in the coolify-realtime container before 4.4; keep the name for existing proxy upstreams.
|
||||
aliases:
|
||||
- coolify-realtime
|
||||
healthcheck:
|
||||
test: curl --fail http://127.0.0.1:8080/api/health && curl --fail http://127.0.0.1:${PUSHER_BACKEND_PORT:-6001}/up || exit 1
|
||||
interval: 5s
|
||||
retries: 24
|
||||
start_period: 1m
|
||||
timeout: 2s
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
postgres:
|
||||
volumes:
|
||||
- coolify-db:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_USER: "${DB_USERNAME}"
|
||||
POSTGRES_PASSWORD: "${DB_PASSWORD}"
|
||||
POSTGRES_DB: "${DB_DATABASE:-coolify}"
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "pg_isready -U ${DB_USERNAME}", "-d", "${DB_DATABASE:-coolify}" ]
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
redis:
|
||||
command: redis-server --save 20 1 --loglevel warning --requirepass ${REDIS_PASSWORD}
|
||||
environment:
|
||||
REDIS_PASSWORD: "${REDIS_PASSWORD}"
|
||||
volumes:
|
||||
- coolify-redis:/data
|
||||
healthcheck:
|
||||
test: redis-cli ping
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
volumes:
|
||||
coolify-db:
|
||||
name: coolify-db
|
||||
coolify-redis:
|
||||
name: coolify-redis
|
||||
|
||||
networks:
|
||||
coolify:
|
||||
external: true
|
||||
@@ -1,110 +0,0 @@
|
||||
services:
|
||||
coolify-testing-host:
|
||||
init: true
|
||||
image: "docker.io/coollabsio/coolify-testing-host:latest"
|
||||
pull_policy: always
|
||||
container_name: coolify-testing-host
|
||||
volumes:
|
||||
- //var/run/docker.sock://var/run/docker.sock
|
||||
- ./:/data/coolify
|
||||
coolify:
|
||||
image: "docker.io/coollabsio/coolify:latest"
|
||||
pull_policy: always
|
||||
container_name: coolify
|
||||
restart: always
|
||||
working_dir: /var/www/html
|
||||
extra_hosts:
|
||||
- 'host.docker.internal:host-gateway'
|
||||
volumes:
|
||||
- type: bind
|
||||
source: .env
|
||||
target: /var/www/html/.env
|
||||
read_only: true
|
||||
- ./ssh:/var/www/html/storage/app/ssh
|
||||
- ./applications:/var/www/html/storage/app/applications
|
||||
- ./databases:/var/www/html/storage/app/databases
|
||||
- ./services:/var/www/html/storage/app/services
|
||||
- ./backups:/var/www/html/storage/app/backups
|
||||
- ./images:/var/www/html/storage/app/images
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- APP_ID
|
||||
- APP_ENV=production
|
||||
- APP_NAME
|
||||
- APP_KEY
|
||||
- DB_PASSWORD
|
||||
- REDIS_PASSWORD
|
||||
- SSL_MODE=off
|
||||
- PHP_PM_CONTROL=dynamic
|
||||
- PHP_PM_START_SERVERS=1
|
||||
- PHP_PM_MIN_SPARE_SERVERS=1
|
||||
- PHP_PM_MAX_SPARE_SERVERS=10
|
||||
- PUSHER_APP_ID
|
||||
- PUSHER_APP_KEY
|
||||
- PUSHER_APP_SECRET
|
||||
- PUSHER_PORT
|
||||
- PUSHER_BACKEND_PORT
|
||||
- AUTOUPDATE=true
|
||||
- SELF_HOSTED=true
|
||||
- SSH_MUX_ENABLED=false
|
||||
- IS_WINDOWS_DOCKER_DESKTOP=true
|
||||
ports:
|
||||
- "${APP_PORT:-8000}:8080"
|
||||
- "${REVERB_PORT:-${SOKETI_PORT:-6001}}:6001"
|
||||
- "6002:6002"
|
||||
expose:
|
||||
- "${APP_PORT:-8000}"
|
||||
- "6001"
|
||||
- "6002"
|
||||
healthcheck:
|
||||
test: curl --fail http://localhost:8080/api/health && curl --fail http://localhost:${PUSHER_BACKEND_PORT:-6001}/up || exit 1
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
pull_policy: always
|
||||
container_name: coolify-db
|
||||
restart: always
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- coolify-db:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_USER: "${DB_USERNAME}"
|
||||
POSTGRES_PASSWORD: "${DB_PASSWORD}"
|
||||
POSTGRES_DB: "${DB_DATABASE:-coolify}"
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "pg_isready -U ${DB_USERNAME}", "-d", "${DB_DATABASE:-coolify}" ]
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
pull_policy: always
|
||||
container_name: coolify-redis
|
||||
restart: always
|
||||
command: redis-server --save 20 1 --loglevel warning --requirepass ${REDIS_PASSWORD}
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
REDIS_PASSWORD: "${REDIS_PASSWORD}"
|
||||
volumes:
|
||||
- coolify-redis:/data
|
||||
healthcheck:
|
||||
test: redis-cli ping
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
|
||||
volumes:
|
||||
coolify-db:
|
||||
name: coolify-db
|
||||
coolify-redis:
|
||||
name: coolify-redis
|
||||
@@ -1,32 +0,0 @@
|
||||
services:
|
||||
coolify:
|
||||
container_name: coolify
|
||||
restart: always
|
||||
working_dir: /var/www/html
|
||||
extra_hosts:
|
||||
- host.docker.internal:host-gateway
|
||||
expose:
|
||||
- "6001"
|
||||
- "6002"
|
||||
networks:
|
||||
- coolify
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: coolify-db
|
||||
restart: always
|
||||
networks:
|
||||
- coolify
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: coolify-redis
|
||||
restart: always
|
||||
networks:
|
||||
- coolify
|
||||
networks:
|
||||
coolify:
|
||||
name: coolify
|
||||
driver: bridge
|
||||
external: false
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,405 +0,0 @@
|
||||
#!/bin/bash
|
||||
## Explicit Coolify internal PostgreSQL major-version migrator.
|
||||
## This script is intentionally not run by upgrade.sh automatically.
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
SOURCE_DIR="/data/coolify/source"
|
||||
ENV_FILE="${SOURCE_DIR}/.env"
|
||||
BACKUP_DIR="/data/coolify/backups/internal-postgres"
|
||||
OVERRIDE_FILE="${SOURCE_DIR}/docker-compose.postgres-upgrade.yml"
|
||||
ROLLBACK_FILE="${SOURCE_DIR}/postgres-upgrade-rollback.env"
|
||||
DATE=$(date +%Y-%m-%d-%H-%M-%S)
|
||||
LOGFILE="${SOURCE_DIR}/postgres-upgrade-${DATE}.log"
|
||||
COMMAND="${1:-upgrade}"
|
||||
TARGET_MAJOR="${1:-18}"
|
||||
|
||||
if [ "$COMMAND" = "rollback" ]; then
|
||||
TARGET_MAJOR=""
|
||||
else
|
||||
COMMAND="upgrade"
|
||||
fi
|
||||
|
||||
TARGET_IMAGE="${COOLIFY_POSTGRES_TARGET_IMAGE:-postgres:${TARGET_MAJOR}-alpine}"
|
||||
TARGET_VOLUME="${COOLIFY_POSTGRES_TARGET_VOLUME:-coolify-db-pg${TARGET_MAJOR}}"
|
||||
TEMP_CONTAINER="coolify-db-pg${TARGET_MAJOR:-rollback}-restore-${DATE}"
|
||||
DUMP_FILE="${BACKUP_DIR}/postgres-upgrade-${DATE}.sql.gz"
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOGFILE"
|
||||
}
|
||||
|
||||
fail() {
|
||||
log "ERROR: $1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage:
|
||||
$0 <target-major>
|
||||
$0 rollback
|
||||
|
||||
Examples:
|
||||
$0 18
|
||||
$0 rollback
|
||||
|
||||
Environment overrides:
|
||||
COOLIFY_POSTGRES_TARGET_IMAGE=postgres:18-alpine
|
||||
COOLIFY_POSTGRES_TARGET_VOLUME=coolify-db-pg18
|
||||
EOF
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
docker rm -f "$TEMP_CONTAINER" >/dev/null 2>&1 || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
get_env_var() {
|
||||
local key="$1"
|
||||
local fallback="${2:-}"
|
||||
local value
|
||||
|
||||
value=$(grep -E "^${key}=" "$ENV_FILE" 2>/dev/null | tail -n 1 | cut -d '=' -f 2- || true)
|
||||
value="${value%\"}"
|
||||
value="${value#\"}"
|
||||
value="${value%\'}"
|
||||
value="${value#\'}"
|
||||
|
||||
if [ -z "$value" ]; then
|
||||
printf '%s' "$fallback"
|
||||
else
|
||||
printf '%s' "$value"
|
||||
fi
|
||||
}
|
||||
|
||||
wait_for_postgres() {
|
||||
local container="$1"
|
||||
local user="$2"
|
||||
local database="$3"
|
||||
local attempts=60
|
||||
|
||||
for _ in $(seq 1 "$attempts"); do
|
||||
if docker exec "$container" pg_isready -U "$user" -d "$database" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
compose_files() {
|
||||
printf -- '-f %s/docker-compose.yml -f %s/docker-compose.prod.yml ' "$SOURCE_DIR" "$SOURCE_DIR"
|
||||
|
||||
if [ -f "${SOURCE_DIR}/docker-compose.custom.yml" ]; then
|
||||
printf -- '-f %s/docker-compose.custom.yml ' "$SOURCE_DIR"
|
||||
fi
|
||||
|
||||
if [ -f "$OVERRIDE_FILE" ]; then
|
||||
printf -- '-f %s ' "$OVERRIDE_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
validate_target_major() {
|
||||
case "$TARGET_MAJOR" in
|
||||
''|*[!0-9]*)
|
||||
usage
|
||||
fail "Target major version must be numeric. Example: $0 18"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$TARGET_MAJOR" -lt 10 ]; then
|
||||
fail "Target major version must be 10 or higher."
|
||||
fi
|
||||
}
|
||||
|
||||
mount_path_for_major() {
|
||||
local major="$1"
|
||||
|
||||
if [ "$major" -ge 18 ]; then
|
||||
printf '%s' '/var/lib/postgresql'
|
||||
else
|
||||
printf '%s' '/var/lib/postgresql/data'
|
||||
fi
|
||||
}
|
||||
|
||||
current_postgres_mount_name() {
|
||||
docker inspect coolify-db --format '{{range .Mounts}}{{if or (eq .Destination "/var/lib/postgresql/data") (eq .Destination "/var/lib/postgresql")}}{{.Name}}{{end}}{{end}}' 2>/dev/null
|
||||
}
|
||||
|
||||
current_postgres_mount_path() {
|
||||
docker inspect coolify-db --format '{{range .Mounts}}{{if or (eq .Destination "/var/lib/postgresql/data") (eq .Destination "/var/lib/postgresql")}}{{.Destination}}{{end}}{{end}}' 2>/dev/null
|
||||
}
|
||||
|
||||
current_postgres_image() {
|
||||
docker inspect coolify-db --format '{{.Config.Image}}' 2>/dev/null
|
||||
}
|
||||
|
||||
current_coolify_image_tag() {
|
||||
local image
|
||||
local image_without_digest
|
||||
local last_segment
|
||||
|
||||
image=$(docker inspect coolify --format '{{.Config.Image}}' 2>/dev/null || true)
|
||||
image_without_digest="${image%@*}"
|
||||
last_segment="${image_without_digest##*/}"
|
||||
|
||||
if [[ "$last_segment" == *:* ]]; then
|
||||
printf '%s' "${last_segment##*:}"
|
||||
fi
|
||||
}
|
||||
|
||||
write_override_file() {
|
||||
local image="$1"
|
||||
local volume="$2"
|
||||
local mount_path="$3"
|
||||
|
||||
cat > "$OVERRIDE_FILE" <<YAML
|
||||
services:
|
||||
postgres:
|
||||
image: "${image}"
|
||||
volumes:
|
||||
- coolify-db:${mount_path}
|
||||
volumes:
|
||||
coolify-db:
|
||||
name: "${volume}"
|
||||
external: true
|
||||
YAML
|
||||
}
|
||||
|
||||
write_rollback_file() {
|
||||
local previous_image="$1"
|
||||
local previous_volume="$2"
|
||||
local previous_mount_path="$3"
|
||||
local previous_override_present="$4"
|
||||
local upgraded_image="$5"
|
||||
local upgraded_volume="$6"
|
||||
|
||||
cat > "$ROLLBACK_FILE" <<EOF
|
||||
PREVIOUS_IMAGE='${previous_image}'
|
||||
PREVIOUS_VOLUME='${previous_volume}'
|
||||
PREVIOUS_MOUNT_PATH='${previous_mount_path}'
|
||||
PREVIOUS_OVERRIDE_PRESENT='${previous_override_present}'
|
||||
UPGRADED_IMAGE='${upgraded_image}'
|
||||
UPGRADED_VOLUME='${upgraded_volume}'
|
||||
CREATED_AT='${DATE}'
|
||||
EOF
|
||||
chmod 600 "$ROLLBACK_FILE"
|
||||
}
|
||||
|
||||
start_stack() {
|
||||
local coolify_image_tag="${1:-${LATEST_IMAGE:-latest}}"
|
||||
local files
|
||||
files=$(compose_files)
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
LATEST_IMAGE="$coolify_image_tag" docker compose --env-file "$ENV_FILE" $files up -d --remove-orphans --wait --wait-timeout 120
|
||||
}
|
||||
|
||||
print_rollback_instructions() {
|
||||
cat <<EOF | tee -a "$LOGFILE"
|
||||
|
||||
Rollback command if the upgraded database does not work:
|
||||
${SOURCE_DIR}/upgrade-postgres.sh rollback
|
||||
|
||||
Rollback metadata was saved to:
|
||||
${ROLLBACK_FILE}
|
||||
|
||||
The previous active Docker volume was '${PREVIOUS_VOLUME}'.
|
||||
The new Docker volume is '${TARGET_VOLUME}'.
|
||||
The dump file is '${DUMP_FILE}'.
|
||||
EOF
|
||||
}
|
||||
|
||||
validate_common_requirements() {
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
touch "$LOGFILE"
|
||||
chmod 700 "$BACKUP_DIR"
|
||||
|
||||
[ -f "$ENV_FILE" ] || fail "Missing ${ENV_FILE}. Run this on a self-hosted Coolify server."
|
||||
command -v docker >/dev/null 2>&1 || fail "Docker is required."
|
||||
docker info >/dev/null 2>&1 || fail "Docker daemon is not reachable."
|
||||
}
|
||||
|
||||
rollback_postgres() {
|
||||
validate_common_requirements
|
||||
|
||||
[ -f "$ROLLBACK_FILE" ] || fail "Missing rollback metadata file: ${ROLLBACK_FILE}"
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
. "$ROLLBACK_FILE"
|
||||
|
||||
[ -n "${PREVIOUS_IMAGE:-}" ] || fail "Rollback metadata is missing PREVIOUS_IMAGE."
|
||||
[ -n "${PREVIOUS_VOLUME:-}" ] || fail "Rollback metadata is missing PREVIOUS_VOLUME."
|
||||
[ -n "${PREVIOUS_MOUNT_PATH:-}" ] || fail "Rollback metadata is missing PREVIOUS_MOUNT_PATH."
|
||||
[ -n "${PREVIOUS_OVERRIDE_PRESENT:-}" ] || fail "Rollback metadata is missing PREVIOUS_OVERRIDE_PRESENT."
|
||||
|
||||
CURRENT_COOLIFY_IMAGE_TAG=$(current_coolify_image_tag)
|
||||
|
||||
log "Rolling back Coolify internal PostgreSQL."
|
||||
log "Previous image: ${PREVIOUS_IMAGE}"
|
||||
log "Previous volume: ${PREVIOUS_VOLUME}"
|
||||
log "Previous mount path: ${PREVIOUS_MOUNT_PATH}"
|
||||
log "Current Coolify image tag: ${CURRENT_COOLIFY_IMAGE_TAG:-latest}"
|
||||
|
||||
docker volume inspect "$PREVIOUS_VOLUME" >/dev/null 2>&1 || fail "Previous volume '${PREVIOUS_VOLUME}' does not exist."
|
||||
|
||||
log "Stopping Coolify application container before rollback."
|
||||
docker stop coolify >>"$LOGFILE" 2>&1 || true
|
||||
|
||||
log "Removing current coolify-db container. Current upgraded volume is kept untouched."
|
||||
docker rm -f coolify-db >>"$LOGFILE" 2>&1 || true
|
||||
|
||||
if [ "$PREVIOUS_OVERRIDE_PRESENT" = "true" ]; then
|
||||
log "Restoring previous PostgreSQL compose override."
|
||||
write_override_file "$PREVIOUS_IMAGE" "$PREVIOUS_VOLUME" "$PREVIOUS_MOUNT_PATH"
|
||||
else
|
||||
log "Removing PostgreSQL compose override to restore base compose configuration."
|
||||
rm -f "$OVERRIDE_FILE"
|
||||
fi
|
||||
|
||||
log "Starting Coolify stack with rollback database volume."
|
||||
start_stack "$CURRENT_COOLIFY_IMAGE_TAG" >>"$LOGFILE" 2>&1 || fail "Could not start Coolify stack after rollback. See ${LOGFILE}."
|
||||
|
||||
log "Rollback completed successfully."
|
||||
cat <<EOF | tee -a "$LOGFILE"
|
||||
|
||||
Rollback completed.
|
||||
Current active PostgreSQL volume should be '${PREVIOUS_VOLUME}'.
|
||||
The upgraded volume '${UPGRADED_VOLUME:-unknown}' was left untouched for inspection or manual cleanup.
|
||||
EOF
|
||||
}
|
||||
|
||||
upgrade_postgres() {
|
||||
validate_target_major
|
||||
TARGET_MOUNT_PATH=$(mount_path_for_major "$TARGET_MAJOR")
|
||||
validate_common_requirements
|
||||
|
||||
log "Starting Coolify internal PostgreSQL major upgrade."
|
||||
log "Target major: ${TARGET_MAJOR}"
|
||||
log "Target image: ${TARGET_IMAGE}"
|
||||
log "Target volume: ${TARGET_VOLUME}"
|
||||
log "Target mount path: ${TARGET_MOUNT_PATH}"
|
||||
|
||||
DB_USERNAME=$(get_env_var DB_USERNAME coolify)
|
||||
DB_DATABASE=$(get_env_var DB_DATABASE coolify)
|
||||
|
||||
if ! docker ps -a --format '{{.Names}}' | grep -qx 'coolify-db'; then
|
||||
fail "Container 'coolify-db' was not found. Start Coolify before running this script."
|
||||
fi
|
||||
|
||||
if ! docker ps --format '{{.Names}}' | grep -qx 'coolify-db'; then
|
||||
log "Starting existing coolify-db container for version detection and dump."
|
||||
docker start coolify-db >>"$LOGFILE" 2>&1 || fail "Could not start coolify-db."
|
||||
fi
|
||||
|
||||
wait_for_postgres coolify-db "$DB_USERNAME" "$DB_DATABASE" || fail "Existing coolify-db is not ready."
|
||||
|
||||
SERVER_VERSION_NUM=$(docker exec coolify-db psql -U "$DB_USERNAME" -d "$DB_DATABASE" -Atc 'SHOW server_version_num;' | tr -d '[:space:]')
|
||||
CURRENT_MAJOR=$((SERVER_VERSION_NUM / 10000))
|
||||
PREVIOUS_VOLUME=$(current_postgres_mount_name)
|
||||
PREVIOUS_MOUNT_PATH=$(current_postgres_mount_path)
|
||||
PREVIOUS_IMAGE=$(current_postgres_image)
|
||||
CURRENT_COOLIFY_IMAGE_TAG=$(current_coolify_image_tag)
|
||||
|
||||
[ -n "$PREVIOUS_VOLUME" ] || fail "Could not detect current PostgreSQL Docker volume."
|
||||
[ -n "$PREVIOUS_MOUNT_PATH" ] || fail "Could not detect current PostgreSQL mount path."
|
||||
[ -n "$PREVIOUS_IMAGE" ] || fail "Could not detect current PostgreSQL image."
|
||||
|
||||
if [ -f "$OVERRIDE_FILE" ]; then
|
||||
PREVIOUS_OVERRIDE_PRESENT=true
|
||||
else
|
||||
PREVIOUS_OVERRIDE_PRESENT=false
|
||||
fi
|
||||
|
||||
log "Current PostgreSQL major: ${CURRENT_MAJOR}"
|
||||
log "Current active volume: ${PREVIOUS_VOLUME}"
|
||||
log "Current image: ${PREVIOUS_IMAGE}"
|
||||
log "Current mount path: ${PREVIOUS_MOUNT_PATH}"
|
||||
log "Current Coolify image tag: ${CURRENT_COOLIFY_IMAGE_TAG:-latest}"
|
||||
|
||||
if [ "$CURRENT_MAJOR" -eq "$TARGET_MAJOR" ]; then
|
||||
log "PostgreSQL is already on major ${TARGET_MAJOR}. Nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$CURRENT_MAJOR" -gt "$TARGET_MAJOR" ]; then
|
||||
fail "Downgrade from ${CURRENT_MAJOR} to ${TARGET_MAJOR} is not supported. Use '$0 rollback' to restore the previous upgrade state."
|
||||
fi
|
||||
|
||||
if docker volume inspect "$TARGET_VOLUME" >/dev/null 2>&1; then
|
||||
fail "Target volume '${TARGET_VOLUME}' already exists. Set COOLIFY_POSTGRES_TARGET_VOLUME to a new name or remove the old failed target volume."
|
||||
fi
|
||||
|
||||
log "Stopping Coolify application container to prevent writes during dump."
|
||||
docker stop coolify >>"$LOGFILE" 2>&1 || true
|
||||
|
||||
log "Creating compressed dump at ${DUMP_FILE}."
|
||||
docker exec coolify-db pg_dumpall -U "$DB_USERNAME" | gzip -c > "$DUMP_FILE"
|
||||
chmod 600 "$DUMP_FILE"
|
||||
|
||||
if [ ! -s "$DUMP_FILE" ]; then
|
||||
fail "Dump file is empty. Aborting."
|
||||
fi
|
||||
|
||||
log "Creating target Docker volume '${TARGET_VOLUME}'."
|
||||
docker volume create "$TARGET_VOLUME" >>"$LOGFILE" 2>&1
|
||||
|
||||
log "Pulling ${TARGET_IMAGE}."
|
||||
docker pull "$TARGET_IMAGE" >>"$LOGFILE" 2>&1
|
||||
|
||||
log "Starting temporary PostgreSQL ${TARGET_MAJOR} container."
|
||||
docker run -d \
|
||||
--name "$TEMP_CONTAINER" \
|
||||
--network coolify \
|
||||
-e POSTGRES_HOST_AUTH_METHOD=trust \
|
||||
-v "${TARGET_VOLUME}:${TARGET_MOUNT_PATH}" \
|
||||
"$TARGET_IMAGE" >>"$LOGFILE" 2>&1
|
||||
|
||||
wait_for_postgres "$TEMP_CONTAINER" postgres postgres || fail "Temporary PostgreSQL ${TARGET_MAJOR} container did not become ready."
|
||||
|
||||
log "Restoring dump into target volume."
|
||||
gunzip -c "$DUMP_FILE" | docker exec -i "$TEMP_CONTAINER" psql -U postgres -d postgres >>"$LOGFILE" 2>&1
|
||||
|
||||
log "Smoke-checking restored Coolify database."
|
||||
docker exec "$TEMP_CONTAINER" psql -U "$DB_USERNAME" -d "$DB_DATABASE" -Atc 'SELECT 1;' | grep -qx '1' || fail "Restored database smoke check failed."
|
||||
|
||||
log "Saving rollback metadata to ${ROLLBACK_FILE}."
|
||||
write_rollback_file "$PREVIOUS_IMAGE" "$PREVIOUS_VOLUME" "$PREVIOUS_MOUNT_PATH" "$PREVIOUS_OVERRIDE_PRESENT" "$TARGET_IMAGE" "$TARGET_VOLUME"
|
||||
|
||||
log "Writing Docker Compose override to ${OVERRIDE_FILE}."
|
||||
write_override_file "$TARGET_IMAGE" "$TARGET_VOLUME" "$TARGET_MOUNT_PATH"
|
||||
|
||||
log "Stopping temporary restore container."
|
||||
docker rm -f "$TEMP_CONTAINER" >>"$LOGFILE" 2>&1 || true
|
||||
|
||||
log "Stopping old coolify-db container. Previous volume '${PREVIOUS_VOLUME}' will be kept for rollback."
|
||||
docker rm -f coolify-db >>"$LOGFILE" 2>&1 || true
|
||||
|
||||
log "Starting Coolify stack with PostgreSQL ${TARGET_MAJOR}."
|
||||
start_stack "$CURRENT_COOLIFY_IMAGE_TAG" >>"$LOGFILE" 2>&1 || fail "Could not start Coolify stack with upgraded PostgreSQL. See ${LOGFILE}."
|
||||
|
||||
log "Coolify internal PostgreSQL upgrade completed successfully."
|
||||
print_rollback_instructions
|
||||
}
|
||||
|
||||
if [ "${COOLIFY_POSTGRES_UPGRADE_SOURCE_ONLY:-false}" = "true" ] && [ "${BASH_SOURCE[0]}" != "$0" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "$COMMAND" in
|
||||
rollback)
|
||||
rollback_postgres
|
||||
;;
|
||||
upgrade)
|
||||
upgrade_postgres
|
||||
;;
|
||||
-h|--help|help)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
fail "Unknown command: ${COMMAND}"
|
||||
;;
|
||||
esac
|
||||
@@ -1,427 +0,0 @@
|
||||
#!/bin/bash
|
||||
## Do not modify this file. You will lose the ability to autoupdate!
|
||||
|
||||
CDN="https://cdn.coollabs.io/coolify-nightly"
|
||||
LATEST_IMAGE=${1:-latest}
|
||||
LATEST_HELPER_VERSION=${2:-latest}
|
||||
ENV_FILE="/data/coolify/source/.env"
|
||||
if [ -n "${3+x}" ]; then
|
||||
REGISTRY_URL="$3"
|
||||
elif [ -f "$ENV_FILE" ] && grep -q "^REGISTRY_URL=" "$ENV_FILE"; then
|
||||
REGISTRY_URL=$(grep "^REGISTRY_URL=" "$ENV_FILE" | cut -d '=' -f2- | head -n1)
|
||||
else
|
||||
REGISTRY_URL="docker.io"
|
||||
fi
|
||||
SKIP_BACKUP=${4:-false}
|
||||
STATUS_FILE="/data/coolify/source/.upgrade-status"
|
||||
|
||||
DATE=$(date +%Y-%m-%d-%H-%M-%S)
|
||||
LOGFILE="/data/coolify/source/upgrade-${DATE}.log"
|
||||
|
||||
# Helper function to log with timestamp
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >>"$LOGFILE"
|
||||
}
|
||||
|
||||
# Helper function to log section headers
|
||||
log_section() {
|
||||
echo "" >>"$LOGFILE"
|
||||
echo "============================================================" >>"$LOGFILE"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >>"$LOGFILE"
|
||||
echo "============================================================" >>"$LOGFILE"
|
||||
}
|
||||
|
||||
# Helper function to write upgrade status for API polling
|
||||
write_status() {
|
||||
local step="$1"
|
||||
local message="$2"
|
||||
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}"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Initialize log file with header
|
||||
echo "============================================================" >>"$LOGFILE"
|
||||
echo "Coolify Upgrade Log" >>"$LOGFILE"
|
||||
echo "Started: $(date '+%Y-%m-%d %H:%M:%S')" >>"$LOGFILE"
|
||||
echo "Target Version: ${LATEST_IMAGE}" >>"$LOGFILE"
|
||||
echo "Helper Version: ${LATEST_HELPER_VERSION}" >>"$LOGFILE"
|
||||
echo "Registry URL: ${REGISTRY_URL}" >>"$LOGFILE"
|
||||
echo "============================================================" >>"$LOGFILE"
|
||||
|
||||
log_section "Step 1/6: Downloading configuration files"
|
||||
write_status "1" "Downloading configuration files"
|
||||
echo "1/6 Downloading latest configuration files..."
|
||||
log "Downloading docker-compose.yml from ${CDN}/docker-compose.yml"
|
||||
curl -fsSL -L $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml
|
||||
log "Downloading docker-compose.prod.yml from ${CDN}/docker-compose.prod.yml"
|
||||
curl -fsSL -L $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml
|
||||
log "Downloading .env.production from ${CDN}/.env.production"
|
||||
curl -fsSL -L $CDN/.env.production -o /data/coolify/source/.env.production
|
||||
log "Downloading upgrade-postgres.sh from ${CDN}/upgrade-postgres.sh"
|
||||
curl -fsSL -L $CDN/upgrade-postgres.sh -o /data/coolify/source/upgrade-postgres.sh
|
||||
chmod +x /data/coolify/source/upgrade-postgres.sh
|
||||
log "Configuration files downloaded successfully"
|
||||
echo " Done."
|
||||
|
||||
# Extract all images from docker-compose configuration
|
||||
log "Extracting all images from docker-compose configuration..."
|
||||
COMPOSE_FILES="-f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml"
|
||||
|
||||
# Check if custom compose file exists
|
||||
if [ -f /data/coolify/source/docker-compose.custom.yml ]; then
|
||||
COMPOSE_FILES="$COMPOSE_FILES -f /data/coolify/source/docker-compose.custom.yml"
|
||||
log "Including custom docker-compose.yml in image extraction"
|
||||
fi
|
||||
|
||||
# Check if PostgreSQL upgrade override exists
|
||||
if [ -f /data/coolify/source/docker-compose.postgres-upgrade.yml ]; then
|
||||
COMPOSE_FILES="$COMPOSE_FILES -f /data/coolify/source/docker-compose.postgres-upgrade.yml"
|
||||
log "Including PostgreSQL upgrade compose override in image extraction"
|
||||
fi
|
||||
|
||||
# Get all unique images from docker compose config
|
||||
# LATEST_IMAGE env var is needed for image substitution in compose files
|
||||
IMAGES=$(REGISTRY_URL=${REGISTRY_URL} LATEST_IMAGE=${LATEST_IMAGE} docker compose --env-file "$ENV_FILE" $COMPOSE_FILES config --images 2>/dev/null | sort -u)
|
||||
|
||||
if [ -z "$IMAGES" ]; then
|
||||
log "ERROR: Failed to extract images from docker-compose files"
|
||||
write_status "error" "Failed to parse docker-compose configuration"
|
||||
echo " ERROR: Failed to parse docker-compose configuration. Aborting upgrade."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Images to pull:"
|
||||
echo "$IMAGES" | while read img; do log " - $img"; done
|
||||
|
||||
# Backup existing .env file before making any changes
|
||||
if [ "$SKIP_BACKUP" != "true" ]; then
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
echo " Creating backup of .env file..."
|
||||
log "Creating backup of .env file to .env-$DATE"
|
||||
cp "$ENV_FILE" "$ENV_FILE-$DATE"
|
||||
log "Backup created: ${ENV_FILE}-${DATE}"
|
||||
else
|
||||
log "WARNING: No existing .env file found to backup"
|
||||
fi
|
||||
fi
|
||||
|
||||
log_section "Step 2/6: Updating environment configuration"
|
||||
write_status "2" "Updating environment configuration"
|
||||
echo ""
|
||||
echo "2/6 Updating environment configuration..."
|
||||
log "Merging .env.production values into .env"
|
||||
awk -F '=' '!seen[$1]++' "$ENV_FILE" /data/coolify/source/.env.production > "$ENV_FILE.tmp" && mv "$ENV_FILE.tmp" "$ENV_FILE"
|
||||
log "Environment file merged successfully"
|
||||
|
||||
update_env_var() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
|
||||
# If variable "key=" exists but has no value, update the value of the existing line
|
||||
if grep -q "^${key}=$" "$ENV_FILE"; then
|
||||
sed -i "s|^${key}=$|${key}=${value}|" "$ENV_FILE"
|
||||
log "Updated ${key} (was empty)"
|
||||
# If variable "key=" doesn't exist, append it to the file with value
|
||||
elif ! grep -q "^${key}=" "$ENV_FILE"; then
|
||||
printf '%s=%s\n' "$key" "$value" >>"$ENV_FILE"
|
||||
log "Added ${key} (was missing)"
|
||||
fi
|
||||
}
|
||||
|
||||
set_env_var() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
|
||||
if grep -q "^${key}=" "$ENV_FILE"; then
|
||||
sed -i "s|^${key}=.*|${key}=${value}|" "$ENV_FILE"
|
||||
log "Updated ${key}"
|
||||
else
|
||||
printf '%s=%s\n' "$key" "$value" >>"$ENV_FILE"
|
||||
log "Added ${key}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Quote a value so that Docker Compose and phpdotenv both read it back unchanged (same as install.sh).
|
||||
quote_env_value() {
|
||||
local value="$1"
|
||||
case "$value" in
|
||||
*\'*)
|
||||
value=${value//\\/\\\\}
|
||||
value=${value//\"/\\\"}
|
||||
value=${value//\$/\\\$}
|
||||
printf '"%s"' "$value"
|
||||
;;
|
||||
*)
|
||||
printf "'%s'" "$value"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Installers before 4.5.1 (and manual edits) wrote root user credentials with spaces without quotes.
|
||||
# phpdotenv cannot parse such a line, so the recreated Coolify container returns HTTP 500 for every page.
|
||||
# Only quote lines that phpdotenv rejects: unquoted values that contain whitespace but no comment.
|
||||
quote_root_user_env_values() {
|
||||
local key value
|
||||
for key in ROOT_USERNAME ROOT_USER_EMAIL ROOT_USER_PASSWORD; do
|
||||
value=$(grep -m1 "^${key}=" "$ENV_FILE" | cut -d '=' -f2-) || continue
|
||||
# phpdotenv ignores whitespace around an unquoted value (also the \r of CRLF line endings)
|
||||
value="${value#"${value%%[![:space:]]*}"}"
|
||||
value="${value%"${value##*[![:space:]]}"}"
|
||||
case "$value" in
|
||||
\"* | \'* | *[[:space:]]\#*) continue ;;
|
||||
*[[:space:]]*) ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
# awk reads the value from the environment, so characters such as | & \ in it stay literal
|
||||
KEY="$key" VALUE="$(quote_env_value "$value")" awk '!done && index($0, ENVIRON["KEY"] "=") == 1 { $0 = ENVIRON["KEY"] "=" ENVIRON["VALUE"]; done = 1 } { print }' "$ENV_FILE" >"$ENV_FILE.tmp" && mv "$ENV_FILE.tmp" "$ENV_FILE"
|
||||
log "Quoted ${key} (its value has spaces)"
|
||||
done
|
||||
}
|
||||
|
||||
log "Checking environment variables..."
|
||||
quote_root_user_env_values
|
||||
set_env_var "REGISTRY_URL" "$REGISTRY_URL"
|
||||
update_env_var "PUSHER_APP_ID" "$(openssl rand -hex 32)"
|
||||
update_env_var "PUSHER_APP_KEY" "$(openssl rand -hex 32)"
|
||||
update_env_var "PUSHER_APP_SECRET" "$(openssl rand -hex 32)"
|
||||
update_env_var "PUSHER_BACKEND_PORT" "6001"
|
||||
# The realtime container no longer exists; point older installs at the bundled Reverb server
|
||||
if grep -q '^PUSHER_BACKEND_HOST=coolify-realtime$' "$ENV_FILE"; then
|
||||
set_env_var "PUSHER_BACKEND_HOST" "127.0.0.1"
|
||||
fi
|
||||
log "Environment variables check complete"
|
||||
echo " Done."
|
||||
|
||||
ensure_coolify_network || exit 1
|
||||
|
||||
mkdir -p /data/coolify/images/{avatars,project-icons}
|
||||
chown -R 9999:root /data/coolify/images
|
||||
chmod -R 700 /data/coolify/images
|
||||
|
||||
# Fix SSH directory ownership if not owned by container user UID 9999 (fixes #6621)
|
||||
# Only changes owner — preserves existing group to respect custom setups
|
||||
SSH_OWNER=$(stat -c '%u' /data/coolify/ssh 2>/dev/null || echo "unknown")
|
||||
if [ "$SSH_OWNER" != "9999" ]; then
|
||||
log "Fixing SSH directory ownership (was owned by UID $SSH_OWNER)"
|
||||
chown -R 9999 /data/coolify/ssh
|
||||
chmod -R 700 /data/coolify/ssh
|
||||
fi
|
||||
|
||||
# Check if Docker config file exists
|
||||
DOCKER_CONFIG_MOUNT=""
|
||||
if [ -f /root/.docker/config.json ]; then
|
||||
DOCKER_CONFIG_MOUNT="-v /root/.docker/config.json:/root/.docker/config.json"
|
||||
log "Docker config mount enabled: /root/.docker/config.json"
|
||||
fi
|
||||
|
||||
log_section "Step 3/6: Pulling Docker images"
|
||||
write_status "3" "Pulling Docker images"
|
||||
echo ""
|
||||
echo "3/6 Pulling Docker images..."
|
||||
echo " This may take a few minutes depending on your connection."
|
||||
|
||||
# Also pull the helper image (not in compose files but needed for upgrade)
|
||||
HELPER_IMAGE="${REGISTRY_URL:-docker.io}/coollabsio/coolify-helper:${LATEST_HELPER_VERSION}"
|
||||
echo " - Pulling $HELPER_IMAGE..."
|
||||
log "Pulling image: $HELPER_IMAGE"
|
||||
if pull_image "$HELPER_IMAGE"; then
|
||||
log "Successfully pulled $HELPER_IMAGE"
|
||||
else
|
||||
log "ERROR: Failed to pull $HELPER_IMAGE"
|
||||
write_status "error" "Failed to pull $HELPER_IMAGE"
|
||||
echo " ERROR: Failed to pull $HELPER_IMAGE. Aborting upgrade."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Pull all images from compose config
|
||||
# Using a for loop to avoid subshell issues with exit
|
||||
for IMAGE in $IMAGES; do
|
||||
if [ -n "$IMAGE" ]; then
|
||||
echo " - Pulling $IMAGE..."
|
||||
log "Pulling image: $IMAGE"
|
||||
if pull_image "$IMAGE"; then
|
||||
log "Successfully pulled $IMAGE"
|
||||
else
|
||||
log "ERROR: Failed to pull $IMAGE"
|
||||
write_status "error" "Failed to pull $IMAGE"
|
||||
echo " ERROR: Failed to pull $IMAGE. Aborting upgrade."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
log "All images pulled successfully"
|
||||
echo " All images pulled successfully."
|
||||
|
||||
set_env_var "LATEST_IMAGE" "$LATEST_IMAGE"
|
||||
set_env_var "COOLIFY_VERSION" "$LATEST_IMAGE"
|
||||
|
||||
log_section "Step 4/6: Stopping and restarting containers"
|
||||
write_status "4" "Stopping containers"
|
||||
echo ""
|
||||
echo "4/6 Stopping containers and starting new ones..."
|
||||
echo " This step will restart all Coolify containers."
|
||||
echo " Check the log file for details: ${LOGFILE}"
|
||||
|
||||
# From this point forward, we need to ensure the script continues even if
|
||||
# the SSH connection is lost (which happens when coolify container stops)
|
||||
# We use a subshell with nohup to ensure completion
|
||||
log "Starting container restart sequence (detached)..."
|
||||
|
||||
nohup bash -c "
|
||||
LOGFILE='$LOGFILE'
|
||||
STATUS_FILE='$STATUS_FILE'
|
||||
DOCKER_CONFIG_MOUNT='$DOCKER_CONFIG_MOUNT'
|
||||
REGISTRY_URL='$REGISTRY_URL'
|
||||
LATEST_HELPER_VERSION='$LATEST_HELPER_VERSION'
|
||||
LATEST_IMAGE='$LATEST_IMAGE'
|
||||
|
||||
log() {
|
||||
echo \"[\$(date '+%Y-%m-%d %H:%M:%S')] \$1\" >>\"\$LOGFILE\"
|
||||
}
|
||||
|
||||
write_status() {
|
||||
echo \"\$1|\$2|\$(date -Iseconds)\" > \"\$STATUS_FILE\"
|
||||
}
|
||||
|
||||
# Stop and remove containers
|
||||
# coolify-realtime is kept for upgrades from versions that still ran the separate realtime container
|
||||
for container in coolify coolify-db coolify-redis coolify-realtime; do
|
||||
if docker ps -a --format '{{.Names}}' | grep -q \"^\${container}\$\"; then
|
||||
log \"Stopping container: \${container}\"
|
||||
docker stop \"\$container\" >>\"\$LOGFILE\" 2>&1 || true
|
||||
log \"Removing container: \${container}\"
|
||||
docker rm \"\$container\" >>\"\$LOGFILE\" 2>&1 || true
|
||||
log \"Container \${container} stopped and removed\"
|
||||
else
|
||||
log \"Container \${container} not found (skipping)\"
|
||||
fi
|
||||
done
|
||||
log \"Container cleanup complete\"
|
||||
|
||||
# Start new containers
|
||||
echo '' >>\"\$LOGFILE\"
|
||||
echo '============================================================' >>\"\$LOGFILE\"
|
||||
log 'Step 5/6: Starting new containers'
|
||||
echo '============================================================' >>\"\$LOGFILE\"
|
||||
write_status '5' 'Starting new containers'
|
||||
|
||||
COMPOSE_FILES='-f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml'
|
||||
if [ -f /data/coolify/source/docker-compose.custom.yml ]; then
|
||||
log 'Using custom docker-compose.yml'
|
||||
COMPOSE_FILES=\"\$COMPOSE_FILES -f /data/coolify/source/docker-compose.custom.yml\"
|
||||
fi
|
||||
if [ -f /data/coolify/source/docker-compose.postgres-upgrade.yml ]; then
|
||||
log 'Using PostgreSQL upgrade compose override'
|
||||
COMPOSE_FILES=\"\$COMPOSE_FILES -f /data/coolify/source/docker-compose.postgres-upgrade.yml\"
|
||||
fi
|
||||
|
||||
log 'Running docker compose up...'
|
||||
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\"
|
||||
echo '============================================================' >>\"\$LOGFILE\"
|
||||
log 'Step 6/6: Upgrade complete'
|
||||
echo '============================================================' >>\"\$LOGFILE\"
|
||||
write_status '6' 'Upgrade complete'
|
||||
log 'Coolify upgrade completed successfully'
|
||||
log \"Version: \${LATEST_IMAGE}\"
|
||||
echo '' >>\"\$LOGFILE\"
|
||||
echo '============================================================' >>\"\$LOGFILE\"
|
||||
echo \"Upgrade completed: \$(date '+%Y-%m-%d %H:%M:%S')\" >>\"\$LOGFILE\"
|
||||
echo '============================================================' >>\"\$LOGFILE\"
|
||||
|
||||
# Clean up status file after a short delay to allow frontend to read completion
|
||||
sleep 10
|
||||
rm -f \"\$STATUS_FILE\"
|
||||
log 'Status file cleaned up'
|
||||
" >>"$LOGFILE" 2>&1 &
|
||||
|
||||
# Give the background process a moment to start
|
||||
sleep 2
|
||||
log "Container restart sequence started in background (PID: $!)"
|
||||
echo ""
|
||||
echo "5/6 Containers are being restarted in the background..."
|
||||
echo "6/6 Upgrade process initiated!"
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " Coolify upgrade to ${LATEST_IMAGE} in progress"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo " The upgrade will continue in the background."
|
||||
echo " Coolify will be available again shortly."
|
||||
echo " Log file: ${LOGFILE}"
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"coolify": {
|
||||
"v4": {
|
||||
"version": "4.5.4"
|
||||
},
|
||||
"nightly": {
|
||||
"version": "4.6-rc.1"
|
||||
},
|
||||
"helper": {
|
||||
"version": "1.0.19"
|
||||
},
|
||||
"sentinel": {
|
||||
"version": "1.1.3"
|
||||
},
|
||||
"sentinel-host": {
|
||||
"version": "1.1.3",
|
||||
"digest": "sha256:bca77d22355c9005185b438dad406747b4b776018215a0a4a75c88618d681ff7"
|
||||
}
|
||||
},
|
||||
"traefik": {
|
||||
"v3.7": "3.7.16",
|
||||
"v3.6": "3.6.25",
|
||||
"v3.5": "3.5.6",
|
||||
"v3.4": "3.4.5",
|
||||
"v3.3": "3.3.7",
|
||||
"v3.2": "3.2.5",
|
||||
"v3.1": "3.1.7",
|
||||
"v3.0": "3.0.4",
|
||||
"v2.11": "2.11.60"
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -22,8 +22,8 @@ cd "$(dirname "$0")/.."
|
||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||
|
||||
main() {
|
||||
[[ "$OPTION" =~ ^(--stable [0-9]+\.[0-9]+\.[0-9]+|--preview [0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta|rc)\.[0-9]+)?|--purge)$ ]] \
|
||||
|| die "'$OPTION' is not an option, expected --stable or --preview with the version of the channel, or --purge"
|
||||
[[ "$OPTION" =~ ^(--stable [0-9]+\.[0-9]+\.[0-9]+|--preview [0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta|rc)\.[0-9]+)?|--nightly [0-9]+\.[0-9]+\.[0-9]+-(alpha|beta|rc)\.[0-9]+\.[0-9]{8}\.[0-9]+|--purge)$ ]] \
|
||||
|| die "'$OPTION' is not an option, expected --stable, --preview or --nightly with the version of the channel, or --purge"
|
||||
if [ "$OPTION" = --purge ]; then
|
||||
purge_cache
|
||||
return
|
||||
@@ -44,7 +44,7 @@ select_channel() {
|
||||
channel_path+="/$channel" channel_url+="/$channel"
|
||||
fi
|
||||
if [[ "$channel:$VERSION" == preview:*-alpha.* ]]; then
|
||||
info "the preview channel takes no alphas, Coolify $VERSION is not synced"
|
||||
info "the preview channel takes no alphas, Coolify $VERSION reaches the nightly channel only"
|
||||
exit 0
|
||||
fi
|
||||
info "syncing $channel_url to Coolify $BOLD$VERSION$RESET"
|
||||
@@ -68,7 +68,7 @@ clone_cdn() {
|
||||
write_release() {
|
||||
local release="$WORK_DIR/cdn/json/coolify/release.json"
|
||||
[ -f "$release" ] || echo '{}' > "$release"
|
||||
jq --tab --arg channel "$channel" --arg version "$VERSION" '.[$channel].coolify = $version | {stable, preview} | with_entries(select(.value))' \
|
||||
jq --tab --arg channel "$channel" --arg version "$VERSION" '.[$channel].coolify = $version | {stable, preview, nightly} | with_entries(select(.value))' \
|
||||
"$release" > "$WORK_DIR/release.json"
|
||||
cp "$WORK_DIR/release.json" "$release"
|
||||
ok "release.json sets the $channel version $BOLD$VERSION$RESET"
|
||||
|
||||
+43
-3
@@ -4,6 +4,8 @@ set -euo pipefail
|
||||
OPTION="$*"
|
||||
BRANCH="${BRANCH:-}"
|
||||
PULL_REQUEST="${PULL_REQUEST:-}"
|
||||
NIGHTLY_VERSION="${NIGHTLY_VERSION:-}"
|
||||
NIGHTLY_REVISION="${NIGHTLY_REVISION:-}"
|
||||
VERSION_FILE=release.json
|
||||
ARTIFACT=coolify
|
||||
UPSTREAM_FILE=upstream.json
|
||||
@@ -22,10 +24,11 @@ die() {
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
main() {
|
||||
[[ "$OPTION" =~ ^(--build|--release|--tags)?$ ]] || die "'$OPTION' is not an option, expected --build, --release, --tags or none"
|
||||
[[ "$OPTION" =~ ^(--build|--release|--tags|--nightly)?$ ]] || die "'$OPTION' is not an option, expected --build, --release, --tags, --nightly or none"
|
||||
read_version
|
||||
fetch_origin
|
||||
print_tags
|
||||
print_next_nightly
|
||||
check_branch
|
||||
check_version
|
||||
select_releases
|
||||
@@ -66,6 +69,43 @@ print_tags() {
|
||||
exit 0
|
||||
}
|
||||
|
||||
print_next_nightly() {
|
||||
[ "$OPTION" = --nightly ] || return 0
|
||||
select_nightly_branch
|
||||
[ "$revision" != "$NIGHTLY_REVISION" ] || exit 0
|
||||
select_nightly_version
|
||||
upstream_lines <(git show "$revision:$UPSTREAM_FILE") | while read -r name upstream; do
|
||||
jq -nc --arg name "$name" --arg version "$upstream" --arg tag "${upstream:+$upstream-}v$nightly" '{$name, $version, $tag}'
|
||||
done | jq -sc --arg branch "$branch" --arg revision "$revision" --arg image "$nightly" '{$branch, $revision, $image, lines: .}'
|
||||
exit 0
|
||||
}
|
||||
|
||||
select_nightly_branch() {
|
||||
stable="$(tag_versions <<< "$tags" | awk '!/-/' | newest)"
|
||||
stable="${stable:-$(artifact_version stable)}"
|
||||
line="$(git for-each-ref --format='%(refname:lstrip=4)' 'refs/remotes/origin/release/v*' | sed -n 's/^v\([0-9]*\.[0-9]*\)$/\1/p' | sort -V \
|
||||
| while read -r release; do ! newer "$release.0" "$stable" || { echo "$release" && break; }; done)"
|
||||
branch="${line:+release/v$line}"
|
||||
branch="${branch:-next}"
|
||||
revision="$(git rev-parse "origin/$branch")"
|
||||
}
|
||||
|
||||
select_nightly_version() {
|
||||
local base number=1
|
||||
version="$(artifact_version preview <(git show "$revision:$VERSION_FILE"))"
|
||||
if [ -n "$line" ]; then
|
||||
base="$line.0-alpha.0"
|
||||
[[ "$version" != "$line.0-"* ]] || base="$version"
|
||||
elif [[ "$version" == *-* ]] && newer "$version" "$stable"; then
|
||||
base="$version"
|
||||
else
|
||||
base="$(next_version "$stable" minor)-alpha.0"
|
||||
fi
|
||||
nightly="$base.$(date -u +%Y%m%d)"
|
||||
[[ "$NIGHTLY_VERSION" != "$nightly".* ]] || number=$((${NIGHTLY_VERSION##*.} + 1))
|
||||
nightly+=".$number"
|
||||
}
|
||||
|
||||
check_branch() {
|
||||
[[ "$branch" =~ ^(main|next|v([0-9]+(\.[0-9]+)?)\.x|release/v([0-9]+\.[0-9]+))$ ]] || die "${branch:-a detached HEAD} releases nothing"
|
||||
line="${BASH_REMATCH[2]}${BASH_REMATCH[4]}"
|
||||
@@ -176,7 +216,7 @@ print_releases() {
|
||||
}
|
||||
|
||||
artifact_version() {
|
||||
jq -r --arg channel "$1" --arg artifact "$ARTIFACT" '.[$channel][$artifact] // empty' "$VERSION_FILE"
|
||||
jq -r --arg channel "$1" --arg artifact "$ARTIFACT" '.[$channel][$artifact] // empty' "${2:-$VERSION_FILE}"
|
||||
}
|
||||
|
||||
upstream_lines() {
|
||||
@@ -184,7 +224,7 @@ upstream_lines() {
|
||||
echo latest
|
||||
return
|
||||
fi
|
||||
jq -r --arg upstream "$UPSTREAM" '.[$upstream] | to_entries[] | "\(.key) \(.value.version)"' "$UPSTREAM_FILE"
|
||||
jq -r --arg upstream "$UPSTREAM" '.[$upstream] | to_entries[] | "\(.key) \(.value.version)"' "${1:-$UPSTREAM_FILE}"
|
||||
}
|
||||
|
||||
next_version() {
|
||||
|
||||
@@ -14,7 +14,6 @@ it('persists images in stable and nightly production compose files', function (s
|
||||
->toContain('/data/coolify/images:/var/www/html/storage/app/images');
|
||||
})->with([
|
||||
'stable' => 'docker-compose.prod.yml',
|
||||
'nightly' => 'other/nightly/docker-compose.prod.yml',
|
||||
]);
|
||||
|
||||
it('persists images in the Windows compose file', function () {
|
||||
|
||||
@@ -49,9 +49,6 @@ it('detects the current Coolify image tag from the running container image', fun
|
||||
expect($exitCode)->toBe(0)
|
||||
->and($output)->toBe($expectedTag);
|
||||
})->with([
|
||||
'nightly standard registry image' => ['other/nightly/upgrade-postgres.sh', 'ghcr.io/coollabsio/coolify:4.0.0-beta.420', '4.0.0-beta.420'],
|
||||
'nightly registry with port' => ['other/nightly/upgrade-postgres.sh', 'registry.example.com:5000/coollabsio/coolify:4.0.1', '4.0.1'],
|
||||
'nightly digest suffix' => ['other/nightly/upgrade-postgres.sh', 'ghcr.io/coollabsio/coolify:4.0.2@sha256:abcdef', '4.0.2'],
|
||||
'scripts standard registry image' => ['scripts/upgrade-postgres.sh', 'ghcr.io/coollabsio/coolify:4.0.0-beta.420', '4.0.0-beta.420'],
|
||||
'scripts registry with port' => ['scripts/upgrade-postgres.sh', 'registry.example.com:5000/coollabsio/coolify:4.0.1', '4.0.1'],
|
||||
'scripts digest suffix' => ['scripts/upgrade-postgres.sh', 'ghcr.io/coollabsio/coolify:4.0.2@sha256:abcdef', '4.0.2'],
|
||||
@@ -66,6 +63,5 @@ it('passes the preserved Coolify image tag to docker compose when starting the s
|
||||
->and($output)->toBe('')
|
||||
->and($latestImage)->toBe('4.0.0-beta.420');
|
||||
})->with([
|
||||
'nightly script' => 'other/nightly/upgrade-postgres.sh',
|
||||
'release script' => 'scripts/upgrade-postgres.sh',
|
||||
]);
|
||||
@@ -70,7 +70,6 @@ it('rewrites the legacy realtime backend host during upgrades', function (string
|
||||
->toContain('set_env_var "PUSHER_BACKEND_HOST" "127.0.0.1"');
|
||||
})->with([
|
||||
'upgrade script' => ['scripts/upgrade.sh'],
|
||||
'nightly upgrade script' => ['other/nightly/upgrade.sh'],
|
||||
]);
|
||||
|
||||
it('includes Reverb but not the terminal server in the Coolify container healthcheck', function (string $composeFile) {
|
||||
@@ -82,9 +81,7 @@ it('includes Reverb but not the terminal server in the Coolify container healthc
|
||||
->not->toContain('6002/ready');
|
||||
})->with([
|
||||
'production compose' => ['docker-compose.prod.yml'],
|
||||
'nightly production compose' => ['other/nightly/docker-compose.prod.yml'],
|
||||
'windows compose' => ['docker-compose.windows.yml'],
|
||||
'nightly windows compose' => ['other/nightly/docker-compose.windows.yml'],
|
||||
]);
|
||||
|
||||
it('removes the legacy realtime container during upgrades', function (string $script) {
|
||||
@@ -92,7 +89,6 @@ it('removes the legacy realtime container during upgrades', function (string $sc
|
||||
->toContain('for container in coolify coolify-db coolify-redis coolify-realtime; do');
|
||||
})->with([
|
||||
'upgrade script' => ['scripts/upgrade.sh'],
|
||||
'nightly upgrade script' => ['other/nightly/upgrade.sh'],
|
||||
]);
|
||||
|
||||
it('runs Reverb and terminal websocket services inside the Coolify containers', function (string $dockerfile, string $dependencyService) {
|
||||
@@ -195,11 +191,6 @@ it('uses Pusher environment keys for self-hosted Reverb compatibility', function
|
||||
'docker-compose.windows.yml',
|
||||
'scripts/install.sh',
|
||||
'scripts/upgrade.sh',
|
||||
'other/nightly/.env.production',
|
||||
'other/nightly/docker-compose.prod.yml',
|
||||
'other/nightly/docker-compose.windows.yml',
|
||||
'other/nightly/install.sh',
|
||||
'other/nightly/upgrade.sh',
|
||||
];
|
||||
|
||||
foreach ($files as $file) {
|
||||
@@ -230,9 +221,7 @@ it('does not use the browser websocket port as the Docker host port', function (
|
||||
->not->toContain('"${PUSHER_PORT:-6001}:6001"');
|
||||
})->with([
|
||||
'production compose' => ['docker-compose.prod.yml'],
|
||||
'nightly production compose' => ['other/nightly/docker-compose.prod.yml'],
|
||||
'windows compose' => ['docker-compose.windows.yml'],
|
||||
'nightly windows compose' => ['other/nightly/docker-compose.windows.yml'],
|
||||
]);
|
||||
|
||||
it('preserves existing public websocket port overrides during install and upgrade', function (string $script) {
|
||||
@@ -244,13 +233,10 @@ it('preserves existing public websocket port overrides during install and upgrad
|
||||
})->with([
|
||||
'install script' => ['scripts/install.sh'],
|
||||
'upgrade script' => ['scripts/upgrade.sh'],
|
||||
'nightly install script' => ['other/nightly/install.sh'],
|
||||
'nightly upgrade script' => ['other/nightly/upgrade.sh'],
|
||||
]);
|
||||
|
||||
it('stops publishing or preserving the obsolete realtime image', function () {
|
||||
$productionInstallScript = file_get_contents(base_path('scripts/install.sh'));
|
||||
$nightlyInstallScript = file_get_contents(base_path('other/nightly/install.sh'));
|
||||
|
||||
expect(file_get_contents(config_path('constants.php')))
|
||||
->not->toContain('realtime_version')
|
||||
@@ -263,8 +249,6 @@ it('stops publishing or preserving the obsolete realtime image', function () {
|
||||
->and(file_exists(base_path('.github/workflows/coolify-realtime.yml')))->toBeFalse()
|
||||
->and(file_exists(base_path('.github/workflows/coolify-realtime-next.yml')))->toBeFalse()
|
||||
->and($productionInstallScript)->not->toContain('LATEST_REALTIME_VERSION')
|
||||
->not->toContain('| Realtime')
|
||||
->and($nightlyInstallScript)->not->toContain('LATEST_REALTIME_VERSION')
|
||||
->not->toContain('| Realtime');
|
||||
});
|
||||
|
||||
@@ -278,7 +262,6 @@ it('keeps 4.3.23 realtime setups working with the production compose files', fun
|
||||
->and(implode("\n", [...$coolify['ports'], ...$coolify['expose']]))->not->toContain('TERMINAL_PORT');
|
||||
})->with([
|
||||
'production compose' => ['docker-compose.prod.yml'],
|
||||
'nightly production compose' => ['other/nightly/docker-compose.prod.yml'],
|
||||
]);
|
||||
|
||||
it('keeps TERMINAL_PORT browser-only in the Windows compose files', function (string $composeFile) {
|
||||
@@ -288,7 +271,6 @@ it('keeps TERMINAL_PORT browser-only in the Windows compose files', function (st
|
||||
->and(implode("\n", [...$coolify['ports'], ...$coolify['expose']]))->not->toContain('TERMINAL_PORT');
|
||||
})->with([
|
||||
'windows compose' => ['docker-compose.windows.yml'],
|
||||
'nightly windows compose' => ['other/nightly/docker-compose.windows.yml'],
|
||||
]);
|
||||
|
||||
it('resolves the browser websocket port like 4.3.23', function (string $pageUrl, ?string $pusherPort, ?string $expectedPort) {
|
||||
@@ -335,7 +317,6 @@ it('accepts a soketi override from 4.3.23 in docker-compose.custom.yml with zero
|
||||
})->with([
|
||||
'production compose, partial override' => ['docker-compose.prod.yml', "services:\n soketi:\n environment:\n SOKETI_DEBUG: 'true'\n ports:\n - '6001:6001'\n"],
|
||||
'production compose, full service' => ['docker-compose.prod.yml', "services:\n soketi:\n image: quay.io/soketi/soketi:1.6-16-alpine\n container_name: coolify-realtime\n ports:\n - '6001:6001'\n"],
|
||||
'nightly production compose, partial override' => ['other/nightly/docker-compose.prod.yml', "services:\n soketi:\n environment:\n SOKETI_DEBUG: 'true'\n"],
|
||||
]);
|
||||
|
||||
it('does not pull an extra image for the soketi placeholder', function (string $composeFile) {
|
||||
@@ -362,5 +343,4 @@ it('does not pull an extra image for the soketi placeholder', function (string $
|
||||
->toBe(['postgres:15-alpine', 'redis:7-alpine', 'registry.example.com/coollabsio/coolify:4.5.1']);
|
||||
})->with([
|
||||
'production compose' => ['docker-compose.prod.yml'],
|
||||
'nightly production compose' => ['other/nightly/docker-compose.prod.yml'],
|
||||
]);
|
||||
@@ -38,5 +38,4 @@ it('removes only previous Coolify keys from authorized_keys', function (string $
|
||||
exec('rm -rf '.escapeshellarg($home));
|
||||
})->with([
|
||||
'stable install script' => ['scripts/install.sh'],
|
||||
'nightly install script' => ['other/nightly/install.sh'],
|
||||
]);
|
||||
@@ -46,7 +46,6 @@ function installScriptFileMode(string $path): string
|
||||
|
||||
$installScripts = [
|
||||
'stable install' => ['scripts/install.sh', ['source', 'ssh', 'images']],
|
||||
'nightly install' => ['other/nightly/install.sh', ['source', 'ssh', 'images']],
|
||||
];
|
||||
$resourceDirectories = ['applications', 'databases', 'backups', 'services', 'proxy', 'sentinel'];
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ function runInstallScriptDockerDaemonStep(string $scriptPath, ?string $daemonJso
|
||||
return ['config' => $config, 'restarted' => str_contains($output, 'RESTARTED'), 'output' => $output];
|
||||
}
|
||||
|
||||
$installScripts = ['stable install' => 'scripts/install.sh', 'nightly install' => 'other/nightly/install.sh'];
|
||||
$installScripts = ['stable install' => 'scripts/install.sh'];
|
||||
|
||||
it('does not create daemon.json or restart Docker when containers are running', function (string $scriptPath) {
|
||||
$result = runInstallScriptDockerDaemonStep($scriptPath, null, runningContainers: 30);
|
||||
|
||||
@@ -22,7 +22,3 @@ function expectRockyInstallScriptToUseRhelRepo(string $path): void
|
||||
it('uses the rocky linux documented docker install flow in the stable install script', function () {
|
||||
expectRockyInstallScriptToUseRhelRepo('scripts/install.sh');
|
||||
});
|
||||
|
||||
it('uses the rocky linux documented docker install flow in the nightly install script', function () {
|
||||
expectRockyInstallScriptToUseRhelRepo('other/nightly/install.sh');
|
||||
});
|
||||
@@ -41,7 +41,7 @@ function writeRootUserCredentialsWithInstallScript(string $scriptPath, array $cr
|
||||
return Dotenv::parse($contents);
|
||||
}
|
||||
|
||||
$installScripts = ['stable install' => 'scripts/install.sh', 'nightly install' => 'other/nightly/install.sh'];
|
||||
$installScripts = ['stable install' => 'scripts/install.sh'];
|
||||
|
||||
$credentials = [
|
||||
'username with a space' => ['ROOT_USERNAME', 'Coolify Lab'],
|
||||
|
||||
@@ -25,7 +25,6 @@ it('shows every installer step with the terminal UI and sends command output to
|
||||
->and(preg_match_all('/^step_done(?!\()/m', $script))->toBe(9);
|
||||
})->with([
|
||||
'stable install script' => ['scripts/install.sh'],
|
||||
'nightly install script' => ['other/nightly/install.sh'],
|
||||
]);
|
||||
|
||||
it('renders aligned step lines, warnings and failures without a terminal', function (string $path) {
|
||||
@@ -68,5 +67,4 @@ it('renders aligned step lines, warnings and failures without a terminal', funct
|
||||
->and(array_map(fn (string $line) => mb_strlen($line), $stepLines))->each->toBe(80);
|
||||
})->with([
|
||||
'stable install script' => ['scripts/install.sh'],
|
||||
'nightly install script' => ['other/nightly/install.sh'],
|
||||
]);
|
||||
@@ -11,5 +11,4 @@ it('allows Coolify enough time to start before counting healthcheck failures', f
|
||||
->retries->toBe(24);
|
||||
})->with([
|
||||
'stable' => 'docker-compose.prod.yml',
|
||||
'nightly' => 'other/nightly/docker-compose.prod.yml',
|
||||
]);
|
||||
@@ -26,7 +26,6 @@ function assertBashSyntaxIsValid(string $path): void
|
||||
|
||||
it('ships postgres upgrade scripts with valid bash syntax', function () {
|
||||
assertBashSyntaxIsValid('scripts/upgrade-postgres.sh');
|
||||
assertBashSyntaxIsValid('other/nightly/upgrade-postgres.sh');
|
||||
});
|
||||
|
||||
it('downloads postgres upgrade script during install and upgrade without auto-running it', function (string $path) {
|
||||
@@ -39,9 +38,7 @@ it('downloads postgres upgrade script during install and upgrade without auto-ru
|
||||
->not->toContain('bash /data/coolify/source/upgrade-postgres.sh');
|
||||
})->with([
|
||||
'stable install' => 'scripts/install.sh',
|
||||
'nightly install' => 'other/nightly/install.sh',
|
||||
'stable upgrade' => 'scripts/upgrade.sh',
|
||||
'nightly upgrade' => 'other/nightly/upgrade.sh',
|
||||
]);
|
||||
|
||||
it('uses the selected registry url when extracting upgrade images', function (string $path) {
|
||||
@@ -50,7 +47,6 @@ it('uses the selected registry url when extracting upgrade images', function (st
|
||||
expect($script)->toContain('IMAGES=$(REGISTRY_URL=${REGISTRY_URL} LATEST_IMAGE=${LATEST_IMAGE} docker compose --env-file "$ENV_FILE" $COMPOSE_FILES config --images');
|
||||
})->with([
|
||||
'stable upgrade' => 'scripts/upgrade.sh',
|
||||
'nightly upgrade' => 'other/nightly/upgrade.sh',
|
||||
]);
|
||||
|
||||
it('persists the selected registry url during upgrades', function (string $path) {
|
||||
@@ -59,7 +55,6 @@ it('persists the selected registry url during upgrades', function (string $path)
|
||||
expect($script)->toContain('set_env_var "REGISTRY_URL" "$REGISTRY_URL"');
|
||||
})->with([
|
||||
'stable upgrade' => 'scripts/upgrade.sh',
|
||||
'nightly upgrade' => 'other/nightly/upgrade.sh',
|
||||
]);
|
||||
|
||||
it('persists the target image and runtime version before recreating containers', function (string $path) {
|
||||
@@ -88,7 +83,6 @@ it('persists the target image and runtime version before recreating containers',
|
||||
->and($coolifyVersionPosition)->toBeLessThan($composeUpPosition);
|
||||
})->with([
|
||||
'stable upgrade' => 'scripts/upgrade.sh',
|
||||
'nightly upgrade' => 'other/nightly/upgrade.sh',
|
||||
]);
|
||||
|
||||
it('uses the existing env registry url when old callers do not pass a registry argument', function (string $path) {
|
||||
@@ -102,7 +96,6 @@ it('uses the existing env registry url when old callers do not pass a registry a
|
||||
->toContain('REGISTRY_URL="docker.io"');
|
||||
})->with([
|
||||
'stable upgrade' => 'scripts/upgrade.sh',
|
||||
'nightly upgrade' => 'other/nightly/upgrade.sh',
|
||||
]);
|
||||
|
||||
it('keeps postgres upgrade compose override in future upgrade compose commands', function (string $path) {
|
||||
@@ -114,7 +107,6 @@ it('keeps postgres upgrade compose override in future upgrade compose commands',
|
||||
->toContain('Using PostgreSQL upgrade compose override');
|
||||
})->with([
|
||||
'stable upgrade' => 'scripts/upgrade.sh',
|
||||
'nightly upgrade' => 'other/nightly/upgrade.sh',
|
||||
]);
|
||||
|
||||
it('uses postgres 18 compatible mount path in generated override and restore container', function () {
|
||||
|
||||
@@ -46,18 +46,18 @@ it('reports an error when the new Coolify containers do not start', function (st
|
||||
expect($result['exitCode'])->toBe(1)
|
||||
->and($result['status'])->toStartWith('error|')
|
||||
->toContain('upgrade log');
|
||||
})->with(['scripts/upgrade.sh', 'other/nightly/upgrade.sh']);
|
||||
})->with(['scripts/upgrade.sh']);
|
||||
|
||||
it('completes the upgrade when the coolify container runs but is not healthy within the wait timeout', function (string $script) {
|
||||
$result = runUpgradeScriptContainerStart($script, composeExitCode: 1, coolifyRunning: true);
|
||||
|
||||
expect($result['exitCode'])->toBe(0)
|
||||
->and($result['status'])->toBe('');
|
||||
})->with(['scripts/upgrade.sh', 'other/nightly/upgrade.sh']);
|
||||
})->with(['scripts/upgrade.sh']);
|
||||
|
||||
it('completes the upgrade when docker compose up succeeds', function (string $script) {
|
||||
$result = runUpgradeScriptContainerStart($script, composeExitCode: 0, coolifyRunning: true);
|
||||
|
||||
expect($result['exitCode'])->toBe(0)
|
||||
->and($result['status'])->toBe('');
|
||||
})->with(['scripts/upgrade.sh', 'other/nightly/upgrade.sh']);
|
||||
})->with(['scripts/upgrade.sh']);
|
||||
@@ -64,19 +64,19 @@ it('stops the upgrade before any container changes when the coolify network has
|
||||
->and($result['status'])->toStartWith('error|')
|
||||
->toContain('fd00:1::1/64')
|
||||
->toContain('systemctl restart docker');
|
||||
})->with(['scripts/upgrade.sh', 'other/nightly/upgrade.sh']);
|
||||
})->with(['scripts/upgrade.sh']);
|
||||
|
||||
it('keeps a readable coolify network', function (string $script) {
|
||||
$result = runUpgradeScriptEnsureCoolifyNetwork($script, existingGateways: '172.19.0.1 fd00:1::1 ', createdIpv6Gateway: 'unused');
|
||||
|
||||
expect($result)->toBe(['exitCode' => 0, 'calls' => [], 'status' => '']);
|
||||
})->with(['scripts/upgrade.sh', 'other/nightly/upgrade.sh']);
|
||||
})->with(['scripts/upgrade.sh']);
|
||||
|
||||
it('creates the coolify network with IPv6', function (string $script) {
|
||||
$result = runUpgradeScriptEnsureCoolifyNetwork($script, existingGateways: null, createdIpv6Gateway: 'fd00:1::1');
|
||||
|
||||
expect($result)->toBe(['exitCode' => 0, 'calls' => ['network create --attachable --ipv6 coolify'], 'status' => '']);
|
||||
})->with(['scripts/upgrade.sh', 'other/nightly/upgrade.sh']);
|
||||
})->with(['scripts/upgrade.sh']);
|
||||
|
||||
it('recreates a new coolify network without IPv6 when Docker reports its gateway with a prefix length', function (string $script) {
|
||||
$result = runUpgradeScriptEnsureCoolifyNetwork($script, existingGateways: null, createdIpv6Gateway: 'fd00:1::1/64');
|
||||
@@ -90,4 +90,4 @@ it('recreates a new coolify network without IPv6 when Docker reports its gateway
|
||||
],
|
||||
'status' => '',
|
||||
]);
|
||||
})->with(['scripts/upgrade.sh', 'other/nightly/upgrade.sh']);
|
||||
})->with(['scripts/upgrade.sh']);
|
||||
@@ -35,8 +35,8 @@ function runUpgradeScriptPullImage(string $script, int $failures): array
|
||||
|
||||
it('retries a rate limited image pull until it succeeds', function (string $script) {
|
||||
expect(runUpgradeScriptPullImage($script, failures: 2))->toBe(['exitCode' => 0, 'pulls' => 3]);
|
||||
})->with(['scripts/upgrade.sh', 'other/nightly/upgrade.sh']);
|
||||
})->with(['scripts/upgrade.sh']);
|
||||
|
||||
it('fails the image pull after five attempts', function (string $script) {
|
||||
expect(runUpgradeScriptPullImage($script, failures: 10))->toBe(['exitCode' => 1, 'pulls' => 5]);
|
||||
})->with(['scripts/upgrade.sh', 'other/nightly/upgrade.sh']);
|
||||
})->with(['scripts/upgrade.sh']);
|
||||
@@ -33,7 +33,7 @@ function repairRootUserEnvWithUpgradeScript(string $scriptPath, string $initialE
|
||||
return $contents;
|
||||
}
|
||||
|
||||
$upgradeScripts = ['stable upgrade' => 'scripts/upgrade.sh', 'nightly upgrade' => 'other/nightly/upgrade.sh'];
|
||||
$upgradeScripts = ['stable upgrade' => 'scripts/upgrade.sh'];
|
||||
|
||||
it('quotes unquoted root user credentials with spaces so that .env parses again', function (string $scriptPath) {
|
||||
$env = repairRootUserEnvWithUpgradeScript($scriptPath, implode("\n", [
|
||||
|
||||
Reference in new issue
Block a user