diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9223f3b..cd42707 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest services: postgres: - image: postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24 + image: postgres:18-alpine@sha256:77f585114c32fbca283dc835b0596f4e52b51b4c6662d7810b2f4084f60a1873 env: POSTGRES_PASSWORD: test-password ports: ["5432:5432"] @@ -51,10 +51,37 @@ jobs: exit 1 fi - name: Test - run: go test -race ./... + run: go test -race -count=1 ./... + + compose: + name: compose postgres + runs-on: ubuntu-latest + env: + POSTGRES_PASSWORD: compose-test-password + RENDERCASE_PUBLIC_URL: https://rendercase.example.com + RENDERCASE_CONTENT_URL: https://content.rendercase.example.com + RENDERCASE_COOKIE_SECRET: compose-test-cookie-secret-not-for-production + RENDERCASE_OIDC_ISSUER: https://id.example.com + RENDERCASE_OAUTH_AUDIENCE: https://rendercase.example.com/mcp + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Start PostgreSQL from compose.yaml on a fresh volume + run: docker compose up -d --wait --wait-timeout 120 postgres + - name: Check server version and data directory + run: | + docker compose exec -T postgres psql -U rendercase -d rendercase -Atc 'show server_version' | tee version + docker compose exec -T postgres psql -U rendercase -d rendercase -Atc 'show data_directory' + image_major=$(sed -n 's/.*image: postgres:\([0-9]*\)-.*/\1/p' compose.yaml) + grep -q "^${image_major}\." version + - name: Show PostgreSQL logs + if: failure() + run: docker compose logs postgres + - name: Clean up + if: always() + run: docker compose down --volumes docker: - needs: test + needs: [test, compose] runs-on: ubuntu-latest if: github.event_name != 'pull_request' permissions: diff --git a/compose.yaml b/compose.yaml index f441808..f6201fd 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,13 +1,13 @@ services: postgres: - image: postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24 + image: postgres:18-alpine@sha256:77f585114c32fbca283dc835b0596f4e52b51b4c6662d7810b2f4084f60a1873 restart: unless-stopped environment: POSTGRES_DB: rendercase POSTGRES_USER: rendercase POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env} volumes: - - postgres-data:/var/lib/postgresql/data + - postgres-data:/var/lib/postgresql healthcheck: test: ["CMD-SHELL", "pg_isready -U rendercase"] interval: 5s diff --git a/docs/operations.md b/docs/operations.md index f068b8e..7e97f30 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -50,6 +50,83 @@ Do not edit `schema_version` or mark a schema change complete by hand. Roll back to the recorded application revision only with a database and object-store state that it supports. +## Upgrade PostgreSQL to a new major version + +A PostgreSQL major version cannot open data files written by an earlier major +version, so a major upgrade in Compose is a dump and restore. The bundled +`compose.yaml` uses PostgreSQL 18. Its image keeps data in +`/var/lib/postgresql/18/docker` and mounts the `postgres-data` volume at +`/var/lib/postgresql`; PostgreSQL 17 and earlier used `/var/lib/postgresql/data`. + +If you start the PostgreSQL 18 revision on an existing PostgreSQL 17 volume, the +`postgres` container exits with `Error: in 18+, these Docker images are +configured to store database data in a format which is compatible with +"pg_ctlcluster"` and Rendercase does not start. Nothing is written to the old +data. Upgrade it as follows. + +The commands below assume the Compose project is named `rendercase` (the +default for a checkout in a `rendercase` directory), so the volume is +`rendercase_postgres-data`. Check yours with +`docker volume ls --filter name=postgres-data`. + +1. While still on the old revision, stop Rendercase, then dump the database. + Also back up artifact storage and `.env` as described in [Back up](#back-up). + + ```sh + docker compose stop rendercase + docker compose exec -T postgres \ + pg_dump -U rendercase -d rendercase -Fc > rendercase-pg17.dump + ``` + +2. Stop the stack. This keeps the volumes. + + ```sh + docker compose down + ``` + +3. Copy the old data into a separate rollback volume, then remove the original + so PostgreSQL 18 can initialize it: + + ```sh + docker volume create rendercase-pg17-data + docker run --rm \ + -v rendercase_postgres-data:/from:ro -v rendercase-pg17-data:/to \ + alpine cp -a /from/. /to/ + docker volume rm rendercase_postgres-data + ``` + +4. Update the checkout to the new revision and start only PostgreSQL. Do not + start Rendercase yet: it would create an empty schema that the restore then + collides with. + + ```sh + docker compose up -d --wait postgres + ``` + +5. Restore the dump: + + ```sh + docker compose exec -T postgres \ + pg_restore --exit-on-error --single-transaction \ + -U rendercase -d rendercase < rendercase-pg17.dump + ``` + +6. Start Rendercase and run the checks from step 5 of [Upgrade](#upgrade): + + ```sh + docker compose up -d --build rendercase + ``` + +Keep `rendercase-pg17.dump` and the `rendercase-pg17-data` volume until the +upgraded installation has been verified, then remove the volume with +`docker volume rm rendercase-pg17-data`. + +To roll back before then, run `docker compose down`, remove +`rendercase_postgres-data`, copy `rendercase-pg17-data` back into a new +`rendercase_postgres-data` volume with the same `docker run ... cp -a` command +(source and destination swapped), check out the previous revision and run +`docker compose up -d`. Any changes made after the upgrade are lost. + ## Back up PostgreSQL contains identities, authorization, artifact/version metadata,