From cf527a7c789d843da84049527a26e7f441bdfef6 Mon Sep 17 00:00:00 2001 From: Andreas Bok Andersen Date: Sun, 19 Apr 2026 13:08:20 +0200 Subject: [PATCH 01/11] fix: support docker and podman quickstart flows --- .gitignore | 4 +++- create_sync_db.sh | 22 ++++++++++++++++------ docker-compose.yml | 13 ++++++++----- install.sh | 10 ++++++---- utilities/backup-attachments.sh | 0 utilities/backup-db.sh | 0 utilities/migrate-synkronus-data.sh | 0 7 files changed, 33 insertions(+), 16 deletions(-) mode change 100644 => 100755 create_sync_db.sh mode change 100644 => 100755 install.sh mode change 100644 => 100755 utilities/backup-attachments.sh mode change 100644 => 100755 utilities/backup-db.sh mode change 100644 => 100755 utilities/migrate-synkronus-data.sh diff --git a/.gitignore b/.gitignore index ea6b986..359aa0a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ # Caddy stores TLS certs in Docker volume (caddy_data), not in the repo # Optional: add Caddyfile if you prefer not to commit the generated hostname -# Caddyfile +Caddyfile + +docker-compose.override.yml diff --git a/create_sync_db.sh b/create_sync_db.sh old mode 100644 new mode 100755 index 3ea957e..274413e --- a/create_sync_db.sh +++ b/create_sync_db.sh @@ -18,8 +18,18 @@ DB_NAME="synk_$USERNAME" PASSWORD=$(openssl rand -base64 30 | tr -d /=+ | cut -c1-40) SUGGESTED_ADMIN_PASSWORD=$(openssl rand -base64 30 | tr -d /=+ | cut -c1-40) +# Detect container runtime +if command -v podman >/dev/null 2>&1; then + RUNTIME=podman +elif command -v docker >/dev/null 2>&1; then + RUNTIME=docker +else + echo "$0: need podman or docker in PATH" >&2 + exit 1 +fi + # Check if database exists -DB_EXISTS=$(docker exec postgres psql -U postgres -tAc "SELECT 1 FROM pg_database WHERE datname='$DB_NAME';") +DB_EXISTS=$($RUNTIME exec postgres psql -U synkronus_user -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname='$DB_NAME';") if [ "$DB_EXISTS" == "1" ]; then if [ "$RECREATE" = false ]; then @@ -27,17 +37,17 @@ if [ "$DB_EXISTS" == "1" ]; then exit 0 else # Drop user and database - docker exec postgres psql -U postgres -c "DROP DATABASE IF EXISTS $DB_NAME;" - docker exec postgres psql -U postgres -c "DROP ROLE IF EXISTS $DB_USER;" + $RUNTIME exec postgres psql -U synkronus_user -d postgres -c "DROP DATABASE IF EXISTS $DB_NAME;" + $RUNTIME exec postgres psql -U synkronus_user -d postgres -c "DROP ROLE IF EXISTS $DB_USER;" echo "User and DB recreated" fi fi # Create role and database -docker exec postgres psql -U postgres -c "CREATE ROLE $DB_USER LOGIN PASSWORD '$PASSWORD';" -docker exec postgres psql -U postgres -c "CREATE DATABASE $DB_NAME OWNER $DB_USER;" +$RUNTIME exec postgres psql -U synkronus_user -d postgres -c "CREATE ROLE $DB_USER LOGIN PASSWORD '$PASSWORD';" +$RUNTIME exec postgres psql -U synkronus_user -d postgres -c "CREATE DATABASE $DB_NAME OWNER $DB_USER;" echo "Database created!" echo "Connection string:" echo "postgres://$DB_USER:$PASSWORD@db:5432/$DB_NAME?sslmode=disable" -echo "Suggested synk_admin password: $SUGGESTED_ADMIN_PASSWORD" \ No newline at end of file +echo "Suggested synk_admin password: $SUGGESTED_ADMIN_PASSWORD" diff --git a/docker-compose.yml b/docker-compose.yml index 98dd2de..b0fac42 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,16 @@ services: synkronus: image: ghcr.io/opendataensemble/synkronus:latest + container_name: synkronus ports: - "8080:8080" environment: PORT: "8080" LOG_LEVEL: "info" - DB_CONNECTION: "postgres://synkronus_user:strong_password@db:5432/synkronus?sslmode=disable" # create with ~/create_synk_db demo - JWT_SECRET: "rcN76PE4cYMYZ9D3iy71eBqHsZ4feddcdAF7Zlsfom0=" # Generate a new one with: openssl rand -base64 32 - ADMIN_USERNAME: "please_change_the_username" - ADMIN_PASSWORD: "change_the_password_as_well" + DB_CONNECTION: "postgres://synkronus_user:im97C1wauDvbHDREP51Vk2OfVeFp092@db:5432/synkronus?sslmode=disable" # create with ~/create_synk_db demo + JWT_SECRET: "ib2Xz57D7VQhkmMTWL0AVubCZblH8fvQzUKiEz6T8M" # Generate a new one with: openssl rand -base64 32 + ADMIN_USERNAME: "admin_0d16c0" + ADMIN_PASSWORD: "orK5BcLcpDPamf5OKiYFC0hw" MAX_VERSIONS_KEPT: "2" expose: - "8080" # in case you want to place a proxy, eg. nginx, in front of synkronus @@ -30,10 +31,12 @@ services: restart: unless-stopped environment: POSTGRES_USER: synkronus_user - POSTGRES_PASSWORD: strong_password # change this! + POSTGRES_PASSWORD: im97C1wauDvbHDREP51Vk2OfVeFp092 # change this! POSTGRES_DB: synkronus volumes: - pgdata:/var/lib/postgresql/data + ports: + - "5432:5432" expose: - "5432" healthcheck: diff --git a/install.sh b/install.sh old mode 100644 new mode 100755 index 3d1df60..4c417a9 --- a/install.sh +++ b/install.sh @@ -30,7 +30,7 @@ else if [ "$USER_IP" = "localhost" ] || [ "$USER_IP" = "127.0.0.1" ]; then SITE_HOST="" USE_TLS=false - echo " → Using port 80 only (no TLS). For TLS, use your server's public IP and .sslip.io next time." + echo " → Using Caddy on host port 8081 (no TLS, rootless Podman friendly). For TLS, use your server's public IP and .sslip.io next time." else # Use sslip.io so we can still get a real TLS cert (Let's Encrypt) SITE_HOST="${USER_IP}.sslip.io" @@ -127,7 +127,7 @@ volumes: OVERRIDE_EOF else cat > "$OVERRIDE_FILE" << OVERRIDE_EOF -# Generated by install.sh — Caddy (port 80 only, no TLS) +# Generated by install.sh — Caddy (host port 8081 -> container 80, no TLS) services: synkronus: ports: [] # only via Caddy @@ -136,7 +136,7 @@ services: image: docker.io/caddy:2-alpine container_name: synkronus_caddy ports: - - "80:80" + - "8081:80" volumes: - "${SCRIPT_DIR}/Caddyfile:/etc/caddy/Caddyfile:ro" - caddy_data:/data @@ -161,7 +161,9 @@ echo " JWT secret: (stored in docker-compose.yml)" echo "" echo "=== Next steps ===" echo " podman compose up -d" +echo " # or: docker compose up -d" echo " podman compose logs -f caddy" +echo " # or: docker compose logs -f caddy" echo "" echo " Upgrading from a pre-release layout (app-bundles / app-bundle-versions)?" echo " See upgrade-path.md and utilities/migrate-synkronus-data.sh (stop the stack first)." @@ -171,6 +173,6 @@ if [ -n "$SITE_HOST" ]; then echo " Synkronus will be at https://${SITE_HOST}/" echo " (Caddy obtains the TLS certificate on first request.)" else - echo " Synkronus will be at http://localhost/" + echo " Synkronus will be at http://localhost:8081/" fi echo "" diff --git a/utilities/backup-attachments.sh b/utilities/backup-attachments.sh old mode 100644 new mode 100755 diff --git a/utilities/backup-db.sh b/utilities/backup-db.sh old mode 100644 new mode 100755 diff --git a/utilities/migrate-synkronus-data.sh b/utilities/migrate-synkronus-data.sh old mode 100644 new mode 100755 From 4205ba148013f1d3b75fa966192b314b9bab590a Mon Sep 17 00:00:00 2001 From: Andreas Bok Andersen Date: Sun, 19 Apr 2026 13:08:20 +0200 Subject: [PATCH 02/11] docs: update quickstart runtime guidance --- README.md | 59 +++++++++++++++++++++++++-------------------- utilities/README.md | 10 ++++---- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 1700236..83f6cc1 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ Welcome! This repository provides a ready-to-run setup of **ODE: Synkronus**, in ## Features -* Fully containerized Synkronus server -* Includes Postgres database -* Supports local usage and GitHub Codespaces -* Single named volume for all Synkronus mutable data (`/app/data` in the container) +- Fully containerized Synkronus server +- Includes Postgres database +- Supports local usage and GitHub Codespaces +- Single named volume for all Synkronus mutable data (`/app/data` in the container) --- @@ -18,6 +18,7 @@ Welcome! This repository provides a ready-to-run setup of **ODE: Synkronus**, in We recommend **Podman** with **podman compose** (or `podman-compose`); the same steps work with Docker and Docker Compose if you prefer. > **Clean Ubuntu server?** Install the needed tools with: +> > ```bash > sudo apt update > sudo apt install -y podman podman-compose git @@ -44,31 +45,37 @@ The installer will: - **Yes** → You enter your domain. **Caddy** is placed in front and will automatically obtain and renew a TLS certificate (Let’s Encrypt). No Certbot or manual steps. - **No** → You enter this server’s **public IP** or `localhost`: - **Public IP** → The installer uses **<ip>.sslip.io** as the hostname so Caddy can still provision a real TLS certificate. You get HTTPS with no domain. - - **localhost** → Caddy serves on port 80 only (no TLS), for local testing. + - **localhost** → Caddy serves local HTTP on host port `8081` (container port `80`, no TLS), for local testing. + +The script prints admin username and password (save them). Once the server is up, log in with those credentials and you can create new users from the UI. Use **https://your-domain/** or **https://<your-ip>.sslip.io/** (with TLS), or **http://localhost:8081/** for local-only. -The script prints admin username and password (save them). Once the server is up, log in with those credentials and you can create new users from the UI. Use **https://your-domain/** or **https://<your-ip>.sslip.io/** (with TLS), or **http://localhost/** for local-only. +In localhost mode, **Caddy** listens on **http://localhost:8081/** and reverse-proxies to **Synkronus** on **http://localhost:8080/**. The Synkronus port stays published on `8080` so you can still hit the backend directly for checks like `curl http://localhost:8080/health`. -> **Note:** If you don't see the portal but get a certificate error instead, try restarting Caddy: `podman restart synkronus_caddy`. +> **Podman rootless note:** rootless Podman cannot bind privileged host ports (<1024) unless you change system settings. The installer's localhost mode uses host port `8081` to avoid this. + +> **Note:** If you don't see the portal but get a certificate error instead, try restarting Caddy: `podman restart synkronus_caddy`. > On first boot, Caddy requests a Let's Encrypt certificate; validation can occasionally fail on the first attempt if the endpoint is not yet reachable. If HTTPS still isn't ready after a minute, check the Caddy logs and restart the Caddy container once. ### Data storage layout Synkronus stores mutable files under **`/app/data`** in the container (one volume: `appdata`). You do **not** set `DATA_DIR` or `APP_BUNDLE_PATH` for the default layout. Subdirectories are: -| Path in volume | Purpose | -|----------------|---------| -| `app-bundle/active/` | Current app bundle (extracted) | +| Path in volume | Purpose | +| ---------------------- | --------------------------------------- | +| `app-bundle/active/` | Current app bundle (extracted) | | `app-bundle/versions/` | Numbered versions and `CURRENT_VERSION` | -| `attachments/` | Attachment blobs | +| `attachments/` | Attachment blobs | ### Utilities (`utilities/`) -| Script | When to use | -|--------|-------------| -| [`utilities/backup-attachments.sh`](./utilities/backup-attachments.sh) | Copy attachment blobs **from a running** Synkronus container to the host (see `--help`). | -| [`utilities/backup-db.sh`](./utilities/backup-db.sh) | **`pg_dump`** the Postgres DB to a `.sql` file while **`db` / `postgres`** is running (see `--help`). | +| Script | When to use | +| ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- | +| [`utilities/backup-attachments.sh`](./utilities/backup-attachments.sh) | Copy attachment blobs **from a running** Synkronus container to the host (see `--help`). | +| [`utilities/backup-db.sh`](./utilities/backup-db.sh) | **`pg_dump`** the Postgres DB to a `.sql` file while **`db` / `postgres`** is running (see `--help`). | | [`utilities/migrate-synkronus-data.sh`](./utilities/migrate-synkronus-data.sh) | Migrate bundle folder layout on the **volume**; run with the **stack stopped** (see [upgrade-path.md](./upgrade-path.md)). | +Run `chmod +x` on the scripts to make them executable. + --- ### Local Installation (manual) @@ -82,13 +89,13 @@ cd synkronus-quickstart 2. Adjust env variables in `docker-compose.yml`. - - In the postgres service: - - `POSTGRES_PASSWORD` - - In the synkronus service: - - `DB_CONNECTION` (update to match `POSTGRES_PASSWORD`) - - `JWT_SECRET` (generate a new one with: `openssl rand -base64 32`) - - `ADMIN_USERNAME` - - `ADMIN_PASSWORD` +- In the postgres service: + - `POSTGRES_PASSWORD` +- In the synkronus service: + - `DB_CONNECTION` (update to match `POSTGRES_PASSWORD`) + - `JWT_SECRET` (generate a new one with: `openssl rand -base64 32`) + - `ADMIN_USERNAME` + - `ADMIN_PASSWORD` Optionally map volumes to specific mount points on the host (ensure the Synkronus user can write: UID **1000** in the official image). @@ -108,13 +115,13 @@ Optionally map volumes to specific mount points on the host (ensure the Synkronu chmod +x ./create_sync_db.sh ``` - Then run the script to create the Synkronus database and user: + Then run the script to create the Synkronus database and user, providing a short name for the database (e.g. `myorg`): ```bash - ./create_sync_db.sh + ./create_sync_db.sh ``` - The script will connect to the running `db` container and set up the required database and user account. + Replace `` with a short identifier (e.g. `myorg`). The script will connect to the running `db` container and set up the required database and user account. 4. Start the services: @@ -154,7 +161,7 @@ curl /health > Notes: > -> * Perfect for experimenting or as a base for production setups. +> - Perfect for experimenting or as a base for production setups. --- diff --git a/utilities/README.md b/utilities/README.md index c5eb8e6..d735366 100644 --- a/utilities/README.md +++ b/utilities/README.md @@ -1,9 +1,9 @@ # Utilities -| Script | Purpose | -|--------|---------| -| [`backup-attachments.sh`](./backup-attachments.sh) | Copy attachment blobs **from a running** Synkronus container to the host (current `/app/data/attachments` and legacy `/app/attachments` if present). | -| [`backup-db.sh`](./backup-db.sh) | **`pg_dump`** of the Postgres database from a **running** `db` / `postgres` container to a `.sql` file on the host. | -| [`migrate-synkronus-data.sh`](./migrate-synkronus-data.sh) | Migrate **volume** layout on disk (`app-bundles/` → `app-bundle/active/`, etc.). Run with the **stack stopped**. | +| Script | Purpose | +| ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| [`backup-attachments.sh`](./backup-attachments.sh) | Copy attachment blobs **from a running** Synkronus container to the host (current `/app/data/attachments` and legacy `/app/attachments` if present). | +| [`backup-db.sh`](./backup-db.sh) | **`pg_dump`** of the Postgres database from a **running** `db` / `postgres` container to a `.sql` file on the host. | +| [`migrate-synkronus-data.sh`](./migrate-synkronus-data.sh) | Migrate **volume** layout on disk (`app-bundles/` → `app-bundle/active/`, etc.). Run with the **stack stopped**. | See **[upgrade-path.md](../upgrade-path.md)** for migration context and when to use each. From 704d51eae219b89d340171f776db31dd4377e724 Mon Sep 17 00:00:00 2001 From: Andreas Bok Andersen Date: Sun, 19 Apr 2026 13:08:20 +0200 Subject: [PATCH 03/11] ci: add runtime validation workflows --- .github/workflows/test-docker-runtime.yml | 83 +++++++++++++++++++++ .github/workflows/test-podman-runtime.yml | 87 +++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 .github/workflows/test-docker-runtime.yml create mode 100644 .github/workflows/test-podman-runtime.yml diff --git a/.github/workflows/test-docker-runtime.yml b/.github/workflows/test-docker-runtime.yml new file mode 100644 index 0000000..36d77b8 --- /dev/null +++ b/.github/workflows/test-docker-runtime.yml @@ -0,0 +1,83 @@ +name: Test Manual Install (Docker) + +on: + push: + branches: + - main + - update-docs + pull_request: + workflow_dispatch: + +permissions: + contents: read + packages: read + +jobs: + docker-manual-install: + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Verify docker runtime + run: | + set -euo pipefail + command -v docker + docker version + docker compose version + + - name: Run manual install flow with docker + run: | + set -euo pipefail + + cleanup() { + docker compose down -v || true + } + trap cleanup EXIT + + echo "=== STEP 1: start db ===" + docker compose up db -d + + echo "=== STEP 2: wait for postgres ===" + for i in $(seq 1 30); do + if docker exec postgres pg_isready -U synkronus_user -d postgres -q 2>/dev/null; then + echo "postgres ready after ${i}s" + break + fi + if [ "$i" -eq 30 ]; then + echo "postgres did not become ready" >&2 + exit 1 + fi + sleep 1 + done + + echo "=== STEP 3: create org database ===" + chmod +x ./create_sync_db.sh + ./create_sync_db.sh myorg + + echo "=== STEP 4: start full stack ===" + docker compose up -d + + echo "=== STEP 5: wait for health ===" + for i in $(seq 1 30); do + code=$(curl -sS -o /tmp/synk-health-body.txt -w '%{http_code}' --max-time 10 http://localhost:8080/health || true) + if [ "$code" = "200" ]; then + echo "health_status=$code" + cat /tmp/synk-health-body.txt + exit 0 + fi + sleep 2 + done + + echo "service did not become healthy" >&2 + docker compose logs + exit 1 diff --git a/.github/workflows/test-podman-runtime.yml b/.github/workflows/test-podman-runtime.yml new file mode 100644 index 0000000..719a795 --- /dev/null +++ b/.github/workflows/test-podman-runtime.yml @@ -0,0 +1,87 @@ +name: Test Manual Install (Podman) + +on: + push: + branches: + - main + - update-docs + pull_request: + workflow_dispatch: + +permissions: + contents: read + packages: read + +jobs: + podman-manual-install: + runs-on: ubuntu-latest + timeout-minutes: 25 + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Install podman and compose support + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y podman podman-compose + + - name: Log in to GHCR + run: | + set -euo pipefail + echo "${{ secrets.GITHUB_TOKEN }}" | podman login ghcr.io -u "${{ github.actor }}" --password-stdin + + - name: Verify podman runtime + run: | + set -euo pipefail + command -v podman + podman --version + podman compose version || podman-compose version + + - name: Run manual install flow with podman + run: | + set -euo pipefail + + cleanup() { + podman compose down -v || true + } + trap cleanup EXIT + + echo "=== STEP 1: start db ===" + podman compose up db -d + + echo "=== STEP 2: wait for postgres ===" + for i in $(seq 1 30); do + if podman exec postgres pg_isready -U synkronus_user -d postgres -q 2>/dev/null; then + echo "postgres ready after ${i}s" + break + fi + if [ "$i" -eq 30 ]; then + echo "postgres did not become ready" >&2 + exit 1 + fi + sleep 1 + done + + echo "=== STEP 3: create org database ===" + chmod +x ./create_sync_db.sh + ./create_sync_db.sh myorg + + echo "=== STEP 4: start full stack ===" + podman compose up -d + + echo "=== STEP 5: wait for health ===" + for i in $(seq 1 30); do + code=$(curl -sS -o /tmp/synk-health-body.txt -w '%{http_code}' --max-time 10 http://localhost:8080/health || true) + if [ "$code" = "200" ]; then + echo "health_status=$code" + cat /tmp/synk-health-body.txt + exit 0 + fi + sleep 2 + done + + echo "service did not become healthy" >&2 + podman compose logs + exit 1 From 268b35ce76a35640e870364e27845d24551c3c40 Mon Sep 17 00:00:00 2001 From: Andreas Bok Andersen Date: Sun, 19 Apr 2026 14:17:52 +0200 Subject: [PATCH 04/11] ci: refactor runtime workflows and add upgrade-path checks --- .github/scripts/runtime-flow.sh | 168 ++++++++++++++++++++ .github/scripts/upgrade-path-flow.sh | 96 +++++++++++ .github/workflows/reusable-runtime-flow.yml | 70 ++++++++ .github/workflows/test-docker-runtime.yml | 79 ++------- .github/workflows/test-podman-runtime.yml | 83 ++-------- .github/workflows/test-upgrade-path.yml | 81 ++++++++++ 6 files changed, 443 insertions(+), 134 deletions(-) create mode 100644 .github/scripts/runtime-flow.sh create mode 100644 .github/scripts/upgrade-path-flow.sh create mode 100644 .github/workflows/reusable-runtime-flow.yml create mode 100644 .github/workflows/test-upgrade-path.yml diff --git a/.github/scripts/runtime-flow.sh b/.github/scripts/runtime-flow.sh new file mode 100644 index 0000000..a2e7af7 --- /dev/null +++ b/.github/scripts/runtime-flow.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash +set -euo pipefail + +RUNTIME="${1:-}" +SCENARIO="${2:-}" + +if [[ "$RUNTIME" != "docker" && "$RUNTIME" != "podman" ]]; then + echo "usage: $0 " >&2 + exit 1 +fi + +if [[ "$SCENARIO" != "manual" && "$SCENARIO" != "installer" ]]; then + echo "usage: $0 " >&2 + exit 1 +fi + +if [[ "$RUNTIME" == "podman" ]]; then + export PODMAN_COMPOSE_PROVIDER=podman-compose +fi + +compose_cmd() { + "$RUNTIME" compose "$@" +} + +resolve_db_container() { + local name + + if "$RUNTIME" ps -a --format '{{.Names}}' | grep -Fxq "postgres"; then + echo "postgres" + return 0 + fi + + name=$("$RUNTIME" ps -a --filter "label=com.docker.compose.service=db" --format '{{.Names}}' | head -n1) + if [[ -n "$name" ]]; then + echo "$name" + return 0 + fi + + name=$("$RUNTIME" ps -a --filter "label=io.podman.compose.service=db" --format '{{.Names}}' | head -n1) + if [[ -n "$name" ]]; then + echo "$name" + return 0 + fi + + return 1 +} + +wait_for_postgres() { + local db_container + db_container=$(resolve_db_container || true) + if [[ -z "$db_container" ]]; then + echo "could not find db container after compose up" >&2 + exit 1 + fi + + for i in $(seq 1 30); do + if "$RUNTIME" exec "$db_container" pg_isready -U synkronus_user -d postgres -q 2>/dev/null; then + echo "postgres ready after ${i}s" + return 0 + fi + if [[ "$i" -eq 30 ]]; then + echo "postgres did not become ready" >&2 + exit 1 + fi + sleep 1 + done +} + +wait_for_health() { + local url="$1" + local fail_msg="$2" + + local healthy=0 + for i in $(seq 1 40); do + code=$(curl -sS -o /tmp/synk-health-body.txt -w '%{http_code}' --max-time 10 "$url" || true) + if [[ "$code" == "200" ]]; then + echo "health_status=$code" + cat /tmp/synk-health-body.txt + healthy=1 + break + fi + sleep 2 + done + + if [[ "$healthy" -ne 1 ]]; then + echo "$fail_msg" >&2 + compose_cmd logs + exit 1 + fi +} + +seed_attachment_probe() { + local seeded=0 + for i in $(seq 1 20); do + if "$RUNTIME" exec synkronus sh -c 'mkdir -p /app/data/attachments/ci && echo ci-probe > /app/data/attachments/ci/probe.txt'; then + seeded=1 + break + fi + sleep 2 + done + + if [[ "$seeded" -ne 1 ]]; then + echo "failed to seed attachments after service became healthy" >&2 + compose_cmd logs + exit 1 + fi +} + +run_backup_assertions() { + chmod +x ./utilities/backup-db.sh ./utilities/backup-attachments.sh + + SYNK_RUNTIME="$RUNTIME" ./utilities/backup-db.sh -o /tmp/synk-db-backup.sql + test -s /tmp/synk-db-backup.sql + + SYNK_RUNTIME="$RUNTIME" ./utilities/backup-attachments.sh -o /tmp/synk-attachments-backup + test -f /tmp/synk-attachments-backup/app-data-attachments/ci/probe.txt +} + +cleanup() { + compose_cmd down -v || true +} +trap cleanup EXIT + +if [[ "$SCENARIO" == "manual" ]]; then + echo "=== STEP 1: start db ===" + compose_cmd up db -d + "$RUNTIME" ps -a + + echo "=== STEP 2: wait for postgres ===" + wait_for_postgres + + echo "=== STEP 3: create org database ===" + chmod +x ./create_sync_db.sh + SYNK_RUNTIME="$RUNTIME" ./create_sync_db.sh myorg + + echo "=== STEP 4: start full stack ===" + compose_cmd up -d + + echo "=== STEP 5: wait for health ===" + wait_for_health "http://localhost:8080/health" "service did not become healthy" + + echo "=== STEP 5b: seed attachment data ===" + seed_attachment_probe + + echo "=== STEP 6: test backup scripts ===" + run_backup_assertions + exit 0 +fi + +echo "=== STEP 1: run installer (no domain, localhost) ===" +chmod +x ./install.sh +printf 'n\nlocalhost\n' | ./install.sh + +test -f Caddyfile +test -f docker-compose.override.yml + +echo "=== STEP 2: start stack ===" +compose_cmd up -d +compose_cmd ps + +echo "=== STEP 3: wait for caddy health endpoint ===" +wait_for_health "http://localhost:8081/health" "installer flow did not become healthy via caddy" + +echo "=== STEP 3b: seed attachment data ===" +seed_attachment_probe + +echo "=== STEP 4: test backup scripts ===" +run_backup_assertions diff --git a/.github/scripts/upgrade-path-flow.sh b/.github/scripts/upgrade-path-flow.sh new file mode 100644 index 0000000..47d1cda --- /dev/null +++ b/.github/scripts/upgrade-path-flow.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +set -euo pipefail + +RUNTIME="${1:-}" +PROJECT_NAME="${2:-upgrade-path}" + +if [[ "$RUNTIME" != "docker" && "$RUNTIME" != "podman" ]]; then + echo "usage: $0 [project_name]" >&2 + exit 1 +fi + +if [[ "$RUNTIME" == "podman" ]]; then + export PODMAN_COMPOSE_PROVIDER=podman-compose + VOL_SUFFIX=":Z" + SCRIPT_MOUNT_SUFFIX=":ro,Z" +else + VOL_SUFFIX="" + SCRIPT_MOUNT_SUFFIX=":ro" +fi + +compose_cmd() { + "$RUNTIME" compose -p "$PROJECT_NAME" "$@" +} + +cleanup() { + compose_cmd down -v || true +} +trap cleanup EXIT + +VOLUME_NAME="${PROJECT_NAME}_appdata" + +echo "=== STEP 1: create compose volumes ===" +compose_cmd up db -d +compose_cmd down + +echo "=== STEP 2: seed legacy layout ===" +"$RUNTIME" run --rm -v "${VOLUME_NAME}:/data${VOL_SUFFIX}" docker.io/library/alpine:3.21 sh -eu -c ' + mkdir -p /data/app-bundles/forms + mkdir -p /data/app-bundle-versions/0001 + printf "legacy-active" > /data/app-bundles/forms/index.txt + printf "legacy-version" > /data/app-bundle-versions/0001/bundle.zip +' + +echo "=== STEP 3: dry-run migration ===" +"$RUNTIME" run --rm \ + -v "${VOLUME_NAME}:/data${VOL_SUFFIX}" \ + -v "$PWD/utilities/migrate-synkronus-data.sh:/migrate.sh${SCRIPT_MOUNT_SUFFIX}" \ + docker.io/library/alpine:3.21 \ + sh /migrate.sh --dry-run /data | tee /tmp/migrate-dry-run.log + +grep -q "Migrating app-bundles/ -> app-bundle/active/" /tmp/migrate-dry-run.log +grep -q "Migrating app-bundle-versions/ -> app-bundle/versions/" /tmp/migrate-dry-run.log + +echo "=== STEP 4: apply migration ===" +"$RUNTIME" run --rm \ + -v "${VOLUME_NAME}:/data${VOL_SUFFIX}" \ + -v "$PWD/utilities/migrate-synkronus-data.sh:/migrate.sh${SCRIPT_MOUNT_SUFFIX}" \ + docker.io/library/alpine:3.21 \ + sh /migrate.sh /data + +echo "=== STEP 5: verify migrated files ===" +"$RUNTIME" run --rm -v "${VOLUME_NAME}:/data${VOL_SUFFIX}" docker.io/library/alpine:3.21 sh -eu -c ' + test -f /data/app-bundle/active/forms/index.txt + test -f /data/app-bundle/versions/0001/bundle.zip + test -d /data/attachments + test -f /data/app-bundles/forms/index.txt + test -f /data/app-bundle-versions/0001/bundle.zip +' + +echo "=== STEP 6: idempotence rerun ===" +"$RUNTIME" run --rm \ + -v "${VOLUME_NAME}:/data${VOL_SUFFIX}" \ + -v "$PWD/utilities/migrate-synkronus-data.sh:/migrate.sh${SCRIPT_MOUNT_SUFFIX}" \ + docker.io/library/alpine:3.21 \ + sh /migrate.sh /data + +echo "=== STEP 7: start stack and verify health ===" +compose_cmd up -d + +healthy=0 +for _ in $(seq 1 40); do + code=$(curl -sS -o /tmp/upgrade-health-body.txt -w '%{http_code}' --max-time 10 http://localhost:8080/health || true) + if [[ "$code" == "200" ]]; then + echo "health_status=$code" + cat /tmp/upgrade-health-body.txt + healthy=1 + break + fi + sleep 2 +done + +if [[ "$healthy" -ne 1 ]]; then + echo "service did not become healthy after migration" >&2 + compose_cmd logs + exit 1 +fi diff --git a/.github/workflows/reusable-runtime-flow.yml b/.github/workflows/reusable-runtime-flow.yml new file mode 100644 index 0000000..cec35c3 --- /dev/null +++ b/.github/workflows/reusable-runtime-flow.yml @@ -0,0 +1,70 @@ +name: Reusable Runtime Flow + +on: + workflow_call: + inputs: + runtime: + required: true + type: string + scenario: + required: true + type: string + timeout_minutes: + required: false + type: number + default: 30 + +permissions: + contents: read + packages: read + +jobs: + run-runtime-flow: + runs-on: ubuntu-latest + timeout-minutes: ${{ inputs.timeout_minutes }} + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Install podman and compose support + if: inputs.runtime == 'podman' + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y podman podman-compose + + - name: Log in to GHCR (Docker) + if: inputs.runtime == 'docker' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to GHCR (Podman) + if: inputs.runtime == 'podman' + run: | + set -euo pipefail + echo "${{ secrets.GITHUB_TOKEN }}" | podman login ghcr.io -u "${{ github.actor }}" --password-stdin + + - name: Verify runtime tooling + run: | + set -euo pipefail + if [ "${{ inputs.runtime }}" = "docker" ]; then + command -v docker + docker version + docker compose version + else + command -v podman + podman --version + command -v podman-compose + podman-compose version + PODMAN_COMPOSE_PROVIDER=podman-compose podman compose version + fi + + - name: Run runtime scenario flow + run: | + set -euo pipefail + chmod +x ./.github/scripts/runtime-flow.sh + ./.github/scripts/runtime-flow.sh "${{ inputs.runtime }}" "${{ inputs.scenario }}" diff --git a/.github/workflows/test-docker-runtime.yml b/.github/workflows/test-docker-runtime.yml index 36d77b8..0deff2f 100644 --- a/.github/workflows/test-docker-runtime.yml +++ b/.github/workflows/test-docker-runtime.yml @@ -14,70 +14,19 @@ permissions: jobs: docker-manual-install: - runs-on: ubuntu-latest - timeout-minutes: 20 + uses: ./.github/workflows/reusable-runtime-flow.yml + with: + runtime: docker + scenario: manual + timeout_minutes: 20 + secrets: inherit + + docker-installer-flow: + uses: ./.github/workflows/reusable-runtime-flow.yml + with: + runtime: docker + scenario: installer + timeout_minutes: 25 + secrets: inherit - steps: - - name: Check out repository - uses: actions/checkout@v4 - - name: Log in to GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Verify docker runtime - run: | - set -euo pipefail - command -v docker - docker version - docker compose version - - - name: Run manual install flow with docker - run: | - set -euo pipefail - - cleanup() { - docker compose down -v || true - } - trap cleanup EXIT - - echo "=== STEP 1: start db ===" - docker compose up db -d - - echo "=== STEP 2: wait for postgres ===" - for i in $(seq 1 30); do - if docker exec postgres pg_isready -U synkronus_user -d postgres -q 2>/dev/null; then - echo "postgres ready after ${i}s" - break - fi - if [ "$i" -eq 30 ]; then - echo "postgres did not become ready" >&2 - exit 1 - fi - sleep 1 - done - - echo "=== STEP 3: create org database ===" - chmod +x ./create_sync_db.sh - ./create_sync_db.sh myorg - - echo "=== STEP 4: start full stack ===" - docker compose up -d - - echo "=== STEP 5: wait for health ===" - for i in $(seq 1 30); do - code=$(curl -sS -o /tmp/synk-health-body.txt -w '%{http_code}' --max-time 10 http://localhost:8080/health || true) - if [ "$code" = "200" ]; then - echo "health_status=$code" - cat /tmp/synk-health-body.txt - exit 0 - fi - sleep 2 - done - - echo "service did not become healthy" >&2 - docker compose logs - exit 1 diff --git a/.github/workflows/test-podman-runtime.yml b/.github/workflows/test-podman-runtime.yml index 719a795..78bc063 100644 --- a/.github/workflows/test-podman-runtime.yml +++ b/.github/workflows/test-podman-runtime.yml @@ -14,74 +14,19 @@ permissions: jobs: podman-manual-install: - runs-on: ubuntu-latest - timeout-minutes: 25 + uses: ./.github/workflows/reusable-runtime-flow.yml + with: + runtime: podman + scenario: manual + timeout_minutes: 25 + secrets: inherit + + podman-installer-flow: + uses: ./.github/workflows/reusable-runtime-flow.yml + with: + runtime: podman + scenario: installer + timeout_minutes: 30 + secrets: inherit - steps: - - name: Check out repository - uses: actions/checkout@v4 - - name: Install podman and compose support - run: | - set -euo pipefail - sudo apt-get update - sudo apt-get install -y podman podman-compose - - - name: Log in to GHCR - run: | - set -euo pipefail - echo "${{ secrets.GITHUB_TOKEN }}" | podman login ghcr.io -u "${{ github.actor }}" --password-stdin - - - name: Verify podman runtime - run: | - set -euo pipefail - command -v podman - podman --version - podman compose version || podman-compose version - - - name: Run manual install flow with podman - run: | - set -euo pipefail - - cleanup() { - podman compose down -v || true - } - trap cleanup EXIT - - echo "=== STEP 1: start db ===" - podman compose up db -d - - echo "=== STEP 2: wait for postgres ===" - for i in $(seq 1 30); do - if podman exec postgres pg_isready -U synkronus_user -d postgres -q 2>/dev/null; then - echo "postgres ready after ${i}s" - break - fi - if [ "$i" -eq 30 ]; then - echo "postgres did not become ready" >&2 - exit 1 - fi - sleep 1 - done - - echo "=== STEP 3: create org database ===" - chmod +x ./create_sync_db.sh - ./create_sync_db.sh myorg - - echo "=== STEP 4: start full stack ===" - podman compose up -d - - echo "=== STEP 5: wait for health ===" - for i in $(seq 1 30); do - code=$(curl -sS -o /tmp/synk-health-body.txt -w '%{http_code}' --max-time 10 http://localhost:8080/health || true) - if [ "$code" = "200" ]; then - echo "health_status=$code" - cat /tmp/synk-health-body.txt - exit 0 - fi - sleep 2 - done - - echo "service did not become healthy" >&2 - podman compose logs - exit 1 diff --git a/.github/workflows/test-upgrade-path.yml b/.github/workflows/test-upgrade-path.yml new file mode 100644 index 0000000..5710ac5 --- /dev/null +++ b/.github/workflows/test-upgrade-path.yml @@ -0,0 +1,81 @@ +name: Test Upgrade Path + +on: + push: + branches: + - main + - update-docs + pull_request: + workflow_dispatch: + +permissions: + contents: read + packages: read + +jobs: + upgrade-path-docker: + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + PROJECT_NAME: upgrade-path-docker + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Verify docker runtime + run: | + set -euo pipefail + command -v docker + docker version + docker compose version + + - name: Run upgrade-path migration test (docker) + run: | + set -euo pipefail + chmod +x ./.github/scripts/upgrade-path-flow.sh + ./.github/scripts/upgrade-path-flow.sh docker "$PROJECT_NAME" + + upgrade-path-podman: + runs-on: ubuntu-latest + timeout-minutes: 35 + env: + PROJECT_NAME: upgrade-path-podman + PODMAN_COMPOSE_PROVIDER: podman-compose + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Install podman and compose support + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y podman podman-compose + + - name: Log in to GHCR + run: | + set -euo pipefail + echo "${{ secrets.GITHUB_TOKEN }}" | podman login ghcr.io -u "${{ github.actor }}" --password-stdin + + - name: Verify podman runtime + run: | + set -euo pipefail + command -v podman + podman --version + command -v podman-compose + podman-compose version + podman compose version + + - name: Run upgrade-path migration test (podman) + run: | + set -euo pipefail + chmod +x ./.github/scripts/upgrade-path-flow.sh + ./.github/scripts/upgrade-path-flow.sh podman "$PROJECT_NAME" From 9bc837578e506153aa06edd5ef00f00fc060ef12 Mon Sep 17 00:00:00 2001 From: Andreas Bok Andersen Date: Sun, 19 Apr 2026 14:17:52 +0200 Subject: [PATCH 05/11] fix: make runtime selection explicit in setup and backup scripts --- create_sync_db.sh | 82 ++++++++++++++++++++++++++++----- utilities/backup-attachments.sh | 8 +++- utilities/backup-db.sh | 10 +++- 3 files changed, 86 insertions(+), 14 deletions(-) diff --git a/create_sync_db.sh b/create_sync_db.sh index 274413e..5399212 100755 --- a/create_sync_db.sh +++ b/create_sync_db.sh @@ -18,18 +18,78 @@ DB_NAME="synk_$USERNAME" PASSWORD=$(openssl rand -base64 30 | tr -d /=+ | cut -c1-40) SUGGESTED_ADMIN_PASSWORD=$(openssl rand -base64 30 | tr -d /=+ | cut -c1-40) -# Detect container runtime -if command -v podman >/dev/null 2>&1; then - RUNTIME=podman -elif command -v docker >/dev/null 2>&1; then - RUNTIME=docker -else +find_db_container() { + local runtime="$1" + local container_name + + if $runtime ps -a --format '{{.Names}}' | grep -Fxq "postgres"; then + echo "postgres" + return 0 + fi + + container_name=$($runtime ps -a --filter "label=com.docker.compose.service=db" --format '{{.Names}}' | head -n1) + if [ -n "$container_name" ]; then + echo "$container_name" + return 0 + fi + + container_name=$($runtime ps -a --filter "label=io.podman.compose.service=db" --format '{{.Names}}' | head -n1) + if [ -n "$container_name" ]; then + echo "$container_name" + return 0 + fi + + return 1 +} + +detect_runtime() { + if [ -n "${SYNK_RUNTIME:-}" ]; then + if ! command -v "$SYNK_RUNTIME" >/dev/null 2>&1; then + echo "$0: SYNK_RUNTIME is set to '$SYNK_RUNTIME' but command was not found" >&2 + exit 1 + fi + echo "$SYNK_RUNTIME" + return 0 + fi + + if command -v docker >/dev/null 2>&1 && find_db_container docker >/dev/null 2>&1; then + echo "docker" + return 0 + fi + + if command -v podman >/dev/null 2>&1 && find_db_container podman >/dev/null 2>&1; then + echo "podman" + return 0 + fi + + if command -v podman >/dev/null 2>&1; then + echo "podman" + return 0 + fi + + if command -v docker >/dev/null 2>&1; then + echo "docker" + return 0 + fi + echo "$0: need podman or docker in PATH" >&2 exit 1 +} + +RUNTIME=$(detect_runtime) +DB_CONTAINER=$(find_db_container "$RUNTIME" || true) + +if [ -z "$DB_CONTAINER" ]; then + echo "$0: could not locate the running postgres container for runtime '$RUNTIME'" >&2 + echo "Start the database first with '$RUNTIME compose up db -d' and retry." >&2 + exit 1 fi +echo "Using runtime: $RUNTIME" +echo "Using database container: $DB_CONTAINER" + # Check if database exists -DB_EXISTS=$($RUNTIME exec postgres psql -U synkronus_user -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname='$DB_NAME';") +DB_EXISTS=$($RUNTIME exec "$DB_CONTAINER" psql -U synkronus_user -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname='$DB_NAME';") if [ "$DB_EXISTS" == "1" ]; then if [ "$RECREATE" = false ]; then @@ -37,15 +97,15 @@ if [ "$DB_EXISTS" == "1" ]; then exit 0 else # Drop user and database - $RUNTIME exec postgres psql -U synkronus_user -d postgres -c "DROP DATABASE IF EXISTS $DB_NAME;" - $RUNTIME exec postgres psql -U synkronus_user -d postgres -c "DROP ROLE IF EXISTS $DB_USER;" + $RUNTIME exec "$DB_CONTAINER" psql -U synkronus_user -d postgres -c "DROP DATABASE IF EXISTS $DB_NAME;" + $RUNTIME exec "$DB_CONTAINER" psql -U synkronus_user -d postgres -c "DROP ROLE IF EXISTS $DB_USER;" echo "User and DB recreated" fi fi # Create role and database -$RUNTIME exec postgres psql -U synkronus_user -d postgres -c "CREATE ROLE $DB_USER LOGIN PASSWORD '$PASSWORD';" -$RUNTIME exec postgres psql -U synkronus_user -d postgres -c "CREATE DATABASE $DB_NAME OWNER $DB_USER;" +$RUNTIME exec "$DB_CONTAINER" psql -U synkronus_user -d postgres -c "CREATE ROLE $DB_USER LOGIN PASSWORD '$PASSWORD';" +$RUNTIME exec "$DB_CONTAINER" psql -U synkronus_user -d postgres -c "CREATE DATABASE $DB_NAME OWNER $DB_USER;" echo "Database created!" echo "Connection string:" diff --git a/utilities/backup-attachments.sh b/utilities/backup-attachments.sh index 3a3d3a0..e3ecfaf 100755 --- a/utilities/backup-attachments.sh +++ b/utilities/backup-attachments.sh @@ -51,7 +51,13 @@ while [ $# -gt 0 ]; do esac done -if command -v podman >/dev/null 2>&1; then +if [ -n "${SYNK_RUNTIME:-}" ]; then + if ! command -v "$SYNK_RUNTIME" >/dev/null 2>&1; then + echo "$0: SYNK_RUNTIME is set to '$SYNK_RUNTIME' but command was not found" >&2 + exit 1 + fi + RUNTIME="$SYNK_RUNTIME" +elif command -v podman >/dev/null 2>&1; then RUNTIME=podman elif command -v docker >/dev/null 2>&1; then RUNTIME=docker diff --git a/utilities/backup-db.sh b/utilities/backup-db.sh index 8a99a5f..31e4c99 100755 --- a/utilities/backup-db.sh +++ b/utilities/backup-db.sh @@ -54,7 +54,13 @@ while [ $# -gt 0 ]; do esac done -if command -v podman >/dev/null 2>&1; then +if [ -n "${SYNK_RUNTIME:-}" ]; then + if ! command -v "$SYNK_RUNTIME" >/dev/null 2>&1; then + echo "$0: SYNK_RUNTIME is set to '$SYNK_RUNTIME' but command was not found" >&2 + exit 1 + fi + RUNTIME="$SYNK_RUNTIME" +elif command -v podman >/dev/null 2>&1; then RUNTIME=podman elif command -v docker >/dev/null 2>&1; then RUNTIME=docker @@ -76,7 +82,7 @@ fi # If -o pointed at a directory, write a file inside it if [ -d "$OUT" ]; then - OUT="$(CDPATH= cd -- "$OUT" && pwd)/synkronus-db-backup-$(date +%Y%m%d-%H%M%S).sql" + OUT="$(cd -- "$OUT" && pwd)/synkronus-db-backup-$(date +%Y%m%d-%H%M%S).sql" fi OUT_DIR=$(dirname "$OUT") From 65c560b7f219c1d48ced658dc3b30b238ae7e76e Mon Sep 17 00:00:00 2001 From: Andreas Bok Andersen Date: Sun, 19 Apr 2026 14:26:20 +0200 Subject: [PATCH 06/11] ci: update naming --- .github/workflows/test-docker-runtime.yml | 3 +-- .github/workflows/test-podman-runtime.yml | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-docker-runtime.yml b/.github/workflows/test-docker-runtime.yml index 0deff2f..4fa4211 100644 --- a/.github/workflows/test-docker-runtime.yml +++ b/.github/workflows/test-docker-runtime.yml @@ -1,10 +1,9 @@ -name: Test Manual Install (Docker) +name: Test Quickstart with Docker runtime on: push: branches: - main - - update-docs pull_request: workflow_dispatch: diff --git a/.github/workflows/test-podman-runtime.yml b/.github/workflows/test-podman-runtime.yml index 78bc063..4ad2b84 100644 --- a/.github/workflows/test-podman-runtime.yml +++ b/.github/workflows/test-podman-runtime.yml @@ -1,4 +1,4 @@ -name: Test Manual Install (Podman) +name: Test Quickstart with Podman on: push: @@ -28,5 +28,3 @@ jobs: scenario: installer timeout_minutes: 30 secrets: inherit - - From 689f4f7bafc4434a38a74314099c66f970866c73 Mon Sep 17 00:00:00 2001 From: Andreas Bok Andersen Date: Sun, 19 Apr 2026 14:34:49 +0200 Subject: [PATCH 07/11] ci: create action for setup of runtime --- .github/actions/setup-runtime/action.yml | 54 +++++++++++++++++++++ .github/workflows/reusable-runtime-flow.yml | 39 ++------------- .github/workflows/test-upgrade-path.yml | 42 +++++----------- 3 files changed, 70 insertions(+), 65 deletions(-) create mode 100644 .github/actions/setup-runtime/action.yml diff --git a/.github/actions/setup-runtime/action.yml b/.github/actions/setup-runtime/action.yml new file mode 100644 index 0000000..1a73ae7 --- /dev/null +++ b/.github/actions/setup-runtime/action.yml @@ -0,0 +1,54 @@ +name: Setup Runtime +description: Install (if needed), authenticate, and verify Docker or Podman tooling. + +inputs: + runtime: + description: Runtime to prepare (docker or podman) + required: true + ghcr-username: + description: Username for GHCR login + required: true + ghcr-token: + description: Token for GHCR login + required: true + +runs: + using: composite + steps: + - name: Install podman and compose support + if: inputs.runtime == 'podman' + shell: bash + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y podman podman-compose + + - name: Log in to GHCR (Docker) + if: inputs.runtime == 'docker' + shell: bash + run: | + set -euo pipefail + echo "${{ inputs.ghcr-token }}" | docker login ghcr.io -u "${{ inputs.ghcr-username }}" --password-stdin + + - name: Log in to GHCR (Podman) + if: inputs.runtime == 'podman' + shell: bash + run: | + set -euo pipefail + echo "${{ inputs.ghcr-token }}" | podman login ghcr.io -u "${{ inputs.ghcr-username }}" --password-stdin + + - name: Verify runtime tooling + shell: bash + run: | + set -euo pipefail + if [ "${{ inputs.runtime }}" = "docker" ]; then + command -v docker + docker version + docker compose version + else + command -v podman + podman --version + command -v podman-compose + podman-compose version + PODMAN_COMPOSE_PROVIDER=podman-compose podman compose version + fi diff --git a/.github/workflows/reusable-runtime-flow.yml b/.github/workflows/reusable-runtime-flow.yml index cec35c3..e8f5f4c 100644 --- a/.github/workflows/reusable-runtime-flow.yml +++ b/.github/workflows/reusable-runtime-flow.yml @@ -27,41 +27,12 @@ jobs: - name: Check out repository uses: actions/checkout@v4 - - name: Install podman and compose support - if: inputs.runtime == 'podman' - run: | - set -euo pipefail - sudo apt-get update - sudo apt-get install -y podman podman-compose - - - name: Log in to GHCR (Docker) - if: inputs.runtime == 'docker' - uses: docker/login-action@v3 + - name: Setup runtime + uses: ./.github/actions/setup-runtime with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Log in to GHCR (Podman) - if: inputs.runtime == 'podman' - run: | - set -euo pipefail - echo "${{ secrets.GITHUB_TOKEN }}" | podman login ghcr.io -u "${{ github.actor }}" --password-stdin - - - name: Verify runtime tooling - run: | - set -euo pipefail - if [ "${{ inputs.runtime }}" = "docker" ]; then - command -v docker - docker version - docker compose version - else - command -v podman - podman --version - command -v podman-compose - podman-compose version - PODMAN_COMPOSE_PROVIDER=podman-compose podman compose version - fi + runtime: ${{ inputs.runtime }} + ghcr-username: ${{ github.actor }} + ghcr-token: ${{ secrets.GITHUB_TOKEN }} - name: Run runtime scenario flow run: | diff --git a/.github/workflows/test-upgrade-path.yml b/.github/workflows/test-upgrade-path.yml index 5710ac5..4f5d1dd 100644 --- a/.github/workflows/test-upgrade-path.yml +++ b/.github/workflows/test-upgrade-path.yml @@ -23,19 +23,12 @@ jobs: - name: Check out repository uses: actions/checkout@v4 - - name: Log in to GHCR - uses: docker/login-action@v3 + - name: Setup runtime + uses: ./.github/actions/setup-runtime with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Verify docker runtime - run: | - set -euo pipefail - command -v docker - docker version - docker compose version + runtime: docker + ghcr-username: ${{ github.actor }} + ghcr-token: ${{ secrets.GITHUB_TOKEN }} - name: Run upgrade-path migration test (docker) run: | @@ -54,25 +47,12 @@ jobs: - name: Check out repository uses: actions/checkout@v4 - - name: Install podman and compose support - run: | - set -euo pipefail - sudo apt-get update - sudo apt-get install -y podman podman-compose - - - name: Log in to GHCR - run: | - set -euo pipefail - echo "${{ secrets.GITHUB_TOKEN }}" | podman login ghcr.io -u "${{ github.actor }}" --password-stdin - - - name: Verify podman runtime - run: | - set -euo pipefail - command -v podman - podman --version - command -v podman-compose - podman-compose version - podman compose version + - name: Setup runtime + uses: ./.github/actions/setup-runtime + with: + runtime: podman + ghcr-username: ${{ github.actor }} + ghcr-token: ${{ secrets.GITHUB_TOKEN }} - name: Run upgrade-path migration test (podman) run: | From b32fb706ec9aefa479098504fc9d8be091d845ca Mon Sep 17 00:00:00 2001 From: Andreas Bok Andersen Date: Sun, 19 Apr 2026 14:37:45 +0200 Subject: [PATCH 08/11] clean readme --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 83f6cc1..db5ca64 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,6 @@ Synkronus stores mutable files under **`/app/data`** in the container (one volum | [`utilities/backup-db.sh`](./utilities/backup-db.sh) | **`pg_dump`** the Postgres DB to a `.sql` file while **`db` / `postgres`** is running (see `--help`). | | [`utilities/migrate-synkronus-data.sh`](./utilities/migrate-synkronus-data.sh) | Migrate bundle folder layout on the **volume**; run with the **stack stopped** (see [upgrade-path.md](./upgrade-path.md)). | -Run `chmod +x` on the scripts to make them executable. - --- ### Local Installation (manual) From 6e8415bd0efdc3956d641497b97e649d3f70aaa8 Mon Sep 17 00:00:00 2001 From: Andreas Bok Andersen Date: Sun, 19 Apr 2026 14:38:38 +0200 Subject: [PATCH 09/11] Revert overwritten values in docker-compose file --- docker-compose.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index b0fac42..38ed453 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,10 +7,10 @@ services: environment: PORT: "8080" LOG_LEVEL: "info" - DB_CONNECTION: "postgres://synkronus_user:im97C1wauDvbHDREP51Vk2OfVeFp092@db:5432/synkronus?sslmode=disable" # create with ~/create_synk_db demo - JWT_SECRET: "ib2Xz57D7VQhkmMTWL0AVubCZblH8fvQzUKiEz6T8M" # Generate a new one with: openssl rand -base64 32 - ADMIN_USERNAME: "admin_0d16c0" - ADMIN_PASSWORD: "orK5BcLcpDPamf5OKiYFC0hw" + DB_CONNECTION: "postgres://synkronus_user:strong_password@db:5432/synkronus?sslmode=disable" # create with ~/create_synk_db demo + JWT_SECRET: "rcN76PE4cYMYZ9D3iy71eBqHsZ4feddcdAF7Zlsfom0=" # Generate a new one with: openssl rand -base64 32 + ADMIN_USERNAME: "please_change_the_username" + ADMIN_PASSWORD: "change_the_password_as_well" MAX_VERSIONS_KEPT: "2" expose: - "8080" # in case you want to place a proxy, eg. nginx, in front of synkronus @@ -31,7 +31,7 @@ services: restart: unless-stopped environment: POSTGRES_USER: synkronus_user - POSTGRES_PASSWORD: im97C1wauDvbHDREP51Vk2OfVeFp092 # change this! + POSTGRES_PASSWORD: strong_password # change this! POSTGRES_DB: synkronus volumes: - pgdata:/var/lib/postgresql/data From 62e23322a99039ce7738a37a053b4f0ad050eef2 Mon Sep 17 00:00:00 2001 From: Andreas Bok Andersen Date: Sun, 19 Apr 2026 14:39:54 +0200 Subject: [PATCH 10/11] ci: remove ports --- docker-compose.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 38ed453..7e4b349 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -35,8 +35,6 @@ services: POSTGRES_DB: synkronus volumes: - pgdata:/var/lib/postgresql/data - ports: - - "5432:5432" expose: - "5432" healthcheck: From 25124b55f01f096ea5b51346f9e890b3bb14590f Mon Sep 17 00:00:00 2001 From: Xiaoan Date: Sun, 19 Apr 2026 15:23:12 +0200 Subject: [PATCH 11/11] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- utilities/backup-db.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utilities/backup-db.sh b/utilities/backup-db.sh index 31e4c99..e59fabc 100755 --- a/utilities/backup-db.sh +++ b/utilities/backup-db.sh @@ -82,7 +82,7 @@ fi # If -o pointed at a directory, write a file inside it if [ -d "$OUT" ]; then - OUT="$(cd -- "$OUT" && pwd)/synkronus-db-backup-$(date +%Y%m%d-%H%M%S).sql" + OUT="$(CDPATH= cd -- "$OUT" && pwd)/synkronus-db-backup-$(date +%Y%m%d-%H%M%S).sql" fi OUT_DIR=$(dirname "$OUT")