chore(ci): centralize runtime and image version constants
Some checks failed
CICD / Build CICD Image Failure Postmortem (push) Has been cancelled
CICD / Source Checks (push) Has been cancelled
CICD / Dependency Audits (Informational) (push) Has been cancelled
CICD / CICD Tests Complete (push) Has been cancelled
CICD / Build Release Images (push) Has been cancelled
CICD / Build Tester Images (push) Has been cancelled
CICD / Production Images Complete (push) Has been cancelled
CICD / Production Image Failures Postmortem (push) Has been cancelled
CICD / Source Lanes Failure Postmortem (push) Has been cancelled
CICD / Runtime Black-Box Integration Tests (push) Has been cancelled
CICD / Integration Tests Failure Postmortem (push) Has been cancelled
CICD / End-to-End Tests (push) Has been cancelled
CICD / E2E Tests Failure Postmortem (push) Has been cancelled
CICD / Promote Staging Images To Release (push) Has been cancelled
CICD / Build and Push CICD Images (push) Has been cancelled

This commit is contained in:
copilotcoder
2026-07-22 17:38:34 -04:00
parent cd19d06680
commit 3db1b0676f
7 changed files with 150 additions and 36 deletions

View File

@@ -135,9 +135,9 @@ jobs:
if retry_cmd 5 3 env GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no" \
git fetch --depth 1 origin "${HEAD_SHA}" >/dev/null 2>&1; then
git checkout FETCH_HEAD -- Dockerfile.cicd-base .dockerignore scripts/compute-cicd-base-hash.sh scripts/resolve-mirrored-image.sh
git checkout FETCH_HEAD -- Dockerfile.cicd-base .dockerignore versions/ci.env scripts/compute-cicd-base-hash.sh scripts/resolve-mirrored-image.sh
else
git checkout HEAD -- Dockerfile.cicd-base .dockerignore scripts/compute-cicd-base-hash.sh scripts/resolve-mirrored-image.sh
git checkout HEAD -- Dockerfile.cicd-base .dockerignore versions/ci.env scripts/compute-cicd-base-hash.sh scripts/resolve-mirrored-image.sh
fi
chmod +x scripts/compute-cicd-base-hash.sh
@@ -324,8 +324,13 @@ jobs:
docker_login_with_retry 5 3
PLAYWRIGHT_BROWSERS_MIRROR_TAG="${GITEA_REGISTRY}/darkhelm.org/playwright-browsers:v1.61.1-jammy"
PLAYWRIGHT_BROWSERS_UPSTREAM_TAG="mcr.microsoft.com/playwright:v1.61.1-jammy"
set -a
# shellcheck source=versions/ci.env
source versions/ci.env
set +a
PLAYWRIGHT_BROWSERS_MIRROR_TAG="${GITEA_REGISTRY}/${PLAYWRIGHT_MIRROR_IMAGE}"
PLAYWRIGHT_BROWSERS_UPSTREAM_TAG="${PLAYWRIGHT_IMAGE}"
PLAYWRIGHT_BROWSERS_LOCAL_TAG="$(./scripts/resolve-mirrored-image.sh \
--image "${PLAYWRIGHT_BROWSERS_MIRROR_TAG}" \
--mirror-image "${PLAYWRIGHT_BROWSERS_MIRROR_TAG}" \
@@ -342,6 +347,8 @@ jobs:
docker build --progress=plain -f Dockerfile.cicd-base \
--build-arg BASE_IMAGE_VERSION="v1.0.0-${BASE_HASH}" \
--build-arg BASE_IMAGE_HASH="${BASE_HASH}" \
--build-arg PLAYWRIGHT_VERSION="${PLAYWRIGHT_VERSION}" \
--build-arg PLAYWRIGHT_DISTRO="${PLAYWRIGHT_DISTRO}" \
--build-arg PLAYWRIGHT_BROWSERS_IMAGE="${PLAYWRIGHT_BROWSERS_IMAGE}" \
-t cicd-base:latest .
@@ -399,9 +406,9 @@ jobs:
if retry_cmd 5 3 env GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no" \
git fetch --depth 1 origin "${HEAD_SHA}" >/dev/null 2>&1; then
git checkout FETCH_HEAD -- .dockerignore Dockerfile.cicd Dockerfile.cicd-base scripts/compute-cicd-base-hash.sh scripts/resolve-mirrored-image.sh
git checkout FETCH_HEAD -- .dockerignore Dockerfile.cicd Dockerfile.cicd-base versions/ci.env scripts/compute-cicd-base-hash.sh scripts/resolve-mirrored-image.sh
else
git checkout HEAD -- .dockerignore Dockerfile.cicd Dockerfile.cicd-base scripts/compute-cicd-base-hash.sh scripts/resolve-mirrored-image.sh
git checkout HEAD -- .dockerignore Dockerfile.cicd Dockerfile.cicd-base versions/ci.env scripts/compute-cicd-base-hash.sh scripts/resolve-mirrored-image.sh
fi
chmod +x scripts/compute-cicd-base-hash.sh
chmod +x scripts/resolve-mirrored-image.sh
@@ -1094,11 +1101,18 @@ jobs:
FRONTEND_BASE_TAG_REF="${FRONTEND_BASE_REPO}:${HEAD_SHA}"
FRONTEND_BASE_CACHE_REF="${FRONTEND_BASE_REPO}:cache"
set -a
# shellcheck source=versions/ci.env
source versions/ci.env
set +a
retry_registry_op pull "${FRONTEND_BASE_CACHE_REF}" 5 3 || true
BUILDKIT_NO_CLIENT_TOKEN=1 DOCKER_BUILDKIT=1 docker build -f Dockerfile.frontend \
--target deps \
--cache-from "${FRONTEND_BASE_CACHE_REF}" \
--build-arg BUILDKIT_INLINE_CACHE=1 \
--build-arg NODE_ALPINE_TAG="${FRONTEND_NODE_ALPINE_TAG}" \
--build-arg YARN_VERSION="${FRONTEND_YARN_VERSION}" \
-t plex-playlist-frontend-base:"${HEAD_SHA}" .
docker tag plex-playlist-frontend-base:"${HEAD_SHA}" "${FRONTEND_BASE_TAG_REF}"
docker tag plex-playlist-frontend-base:"${HEAD_SHA}" "${FRONTEND_BASE_CACHE_REF}"
@@ -1285,12 +1299,19 @@ jobs:
DEPLOYABLE_FRONTEND_TAG_REF="${DEPLOYABLE_FRONTEND_REPO}:${HEAD_SHA}"
DEPLOYABLE_FRONTEND_CACHE_REF="${DEPLOYABLE_FRONTEND_REPO}:cache"
set -a
# shellcheck source=versions/ci.env
source versions/ci.env
set +a
docker_login_with_retry 5 3
retry_registry_op pull "${DEPLOYABLE_FRONTEND_CACHE_REF}" 5 3 || true
BUILDKIT_NO_CLIENT_TOKEN=1 DOCKER_BUILDKIT=1 docker build -f Dockerfile.frontend \
--target production \
--cache-from "${DEPLOYABLE_FRONTEND_CACHE_REF}" \
--build-arg BUILDKIT_INLINE_CACHE=1 \
--build-arg NODE_ALPINE_TAG="${FRONTEND_NODE_ALPINE_TAG}" \
--build-arg YARN_VERSION="${FRONTEND_YARN_VERSION}" \
-t plex-playlist-frontend:"${HEAD_SHA}" .
bash ./scripts/verify-deployable-image-purity.sh --image plex-playlist-frontend:"${HEAD_SHA}" --profile frontend
@@ -1442,8 +1463,13 @@ jobs:
run: |
set -euo pipefail
PLAYWRIGHT_BASE_IMAGE_MIRROR="${GITEA_REGISTRY}/darkhelm.org/playwright-browsers:v1.61.1-jammy"
PLAYWRIGHT_BASE_IMAGE_UPSTREAM="mcr.microsoft.com/playwright:v1.61.1-jammy"
set -a
# shellcheck source=versions/ci.env
source versions/ci.env
set +a
PLAYWRIGHT_BASE_IMAGE_MIRROR="${GITEA_REGISTRY}/${PLAYWRIGHT_MIRROR_IMAGE}"
PLAYWRIGHT_BASE_IMAGE_UPSTREAM="${PLAYWRIGHT_IMAGE}"
docker_login_with_retry() {
attempts="${1:-5}"
@@ -1543,6 +1569,8 @@ jobs:
DOCKER_BUILDKIT=1 docker build --pull=false -f Dockerfile.e2e-tester \
--cache-from "${E2E_TESTER_CACHE_REF}" \
--build-arg BUILDKIT_INLINE_CACHE=1 \
--build-arg PLAYWRIGHT_VERSION="${PLAYWRIGHT_VERSION}" \
--build-arg PLAYWRIGHT_DISTRO="${PLAYWRIGHT_DISTRO}" \
--build-arg PLAYWRIGHT_BASE_IMAGE="${PLAYWRIGHT_BASE_IMAGE}" \
-t plex-playlist-e2e:"${HEAD_SHA}" .
docker tag plex-playlist-e2e:"${HEAD_SHA}" "${E2E_TESTER_TAG_REF}"
@@ -1806,8 +1834,7 @@ jobs:
COMPOSE_PROJECT_NAME="plex-int-${RUN_ID}-${RUN_ATTEMPT}"
DB_PASSWORD="plex_password"
DB_URL="postgresql://plex_user:${DB_PASSWORD}@database:5432/plex_playlist"
POSTGRES_IMAGE_MIRROR="${GITEA_REGISTRY}/darkhelm.org/postgres:16-alpine"
POSTGRES_IMAGE_UPSTREAM="docker.io/library/postgres:16-alpine"
VERSION_ENV_PATH="/tmp/ci.env"
RESOLVER_PATH="/tmp/resolve-mirrored-image.sh"
if ! command -v docker >/dev/null 2>&1; then
@@ -1839,9 +1866,18 @@ jobs:
RESOLVER_SOURCE_IMAGE="${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${HEAD_SHA}"
RESOLVER_CONTAINER="$(docker create "${RESOLVER_SOURCE_IMAGE}")"
docker cp "${RESOLVER_CONTAINER}:/workspace/scripts/resolve-mirrored-image.sh" "${RESOLVER_PATH}"
docker cp "${RESOLVER_CONTAINER}:/workspace/versions/ci.env" "${VERSION_ENV_PATH}"
docker rm -f "${RESOLVER_CONTAINER}" >/dev/null 2>&1 || true
chmod +x "${RESOLVER_PATH}"
set -a
# shellcheck source=/tmp/ci.env
source "${VERSION_ENV_PATH}"
set +a
POSTGRES_IMAGE_MIRROR="${GITEA_REGISTRY}/${POSTGRES_MIRROR_IMAGE}"
POSTGRES_IMAGE_UPSTREAM="${POSTGRES_UPSTREAM_IMAGE}"
POSTGRES_IMAGE="$(${RESOLVER_PATH} \
--image "${POSTGRES_IMAGE_MIRROR}" \
--mirror-image "${POSTGRES_IMAGE_MIRROR}" \
@@ -2113,8 +2149,7 @@ jobs:
DB_PASSWORD="plex_password"
DB_URL="postgresql://plex_user:${DB_PASSWORD}@database:5432/plex_playlist"
E2E_REPO_DIR="$(mktemp -d)"
POSTGRES_IMAGE_MIRROR="${GITEA_REGISTRY}/darkhelm.org/postgres:16-alpine"
POSTGRES_IMAGE_UPSTREAM="docker.io/library/postgres:16-alpine"
VERSION_ENV_PATH="/tmp/ci.env"
RESOLVER_PATH="/tmp/resolve-mirrored-image.sh"
if ! command -v docker >/dev/null 2>&1; then
@@ -2146,9 +2181,18 @@ jobs:
RESOLVER_SOURCE_IMAGE="${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${HEAD_SHA}"
RESOLVER_CONTAINER="$(docker create "${RESOLVER_SOURCE_IMAGE}")"
docker cp "${RESOLVER_CONTAINER}:/workspace/scripts/resolve-mirrored-image.sh" "${RESOLVER_PATH}"
docker cp "${RESOLVER_CONTAINER}:/workspace/versions/ci.env" "${VERSION_ENV_PATH}"
docker rm -f "${RESOLVER_CONTAINER}" >/dev/null 2>&1 || true
chmod +x "${RESOLVER_PATH}"
set -a
# shellcheck source=/tmp/ci.env
source "${VERSION_ENV_PATH}"
set +a
POSTGRES_IMAGE_MIRROR="${GITEA_REGISTRY}/${POSTGRES_MIRROR_IMAGE}"
POSTGRES_IMAGE_UPSTREAM="${POSTGRES_UPSTREAM_IMAGE}"
POSTGRES_IMAGE="$(${RESOLVER_PATH} \
--image "${POSTGRES_IMAGE_MIRROR}" \
--mirror-image "${POSTGRES_IMAGE_MIRROR}" \

View File

@@ -1,8 +1,20 @@
# CICD Base Setup - System dependencies and language runtimes only
ARG PLAYWRIGHT_BROWSERS_IMAGE=mcr.microsoft.com/playwright:v1.61.1-jammy
ARG PLAYWRIGHT_VERSION=1.61.1
ARG PLAYWRIGHT_DISTRO=jammy
ARG PLAYWRIGHT_BROWSERS_IMAGE=mcr.microsoft.com/playwright:v${PLAYWRIGHT_VERSION}-${PLAYWRIGHT_DISTRO}
FROM ${PLAYWRIGHT_BROWSERS_IMAGE} AS playwright-browsers
FROM ubuntu:26.04
ARG UBUNTU_VERSION=26.04
FROM ubuntu:${UBUNTU_VERSION}
ARG PLAYWRIGHT_VERSION
ARG PYTHON_VERSION=3.14
ARG NODE_MAJOR=24
ARG TYPESCRIPT_VERSION=5.3.3
ARG ESLINT_VERSION=9.33.0
ARG PRETTIER_VERSION=3.6.2
ARG VITE_VERSION=7.1.10
ARG NODE_TYPES_VERSION=20.16.0
# Build args for cache busting (base dependencies change rarely)
ARG BASE_IMAGE_VERSION=v1.0.0
@@ -88,9 +100,9 @@ RUN set -eux; \
wget \
&& rm -rf /var/lib/apt/lists/*
# Install Python 3.14 with retry and fallback mechanisms
# Install Python with retry and fallback mechanisms
RUN for i in 1 2 3; do \
echo "Attempt $i: Adding deadsnakes PPA..." && \
echo "Attempt $i: Adding deadsnakes PPA..." && \
add-apt-repository -y ppa:deadsnakes/ppa && \
find /etc/apt -type f \( -name 'sources.list' -o -name '*.sources' -o -name '*.list' \) -print0 | xargs -0 sed -i 's|http://|https://|g' && \
apt-get update && \
@@ -99,21 +111,21 @@ RUN for i in 1 2 3; do \
done
RUN for i in 1 2 3; do \
echo "Attempt $i: Installing Python 3.14..." && \
echo "Attempt $i: Installing Python ${PYTHON_VERSION}..." && \
timeout 300 apt-get install -y --no-install-recommends \
python3.14 \
python3.14-venv \
python3.14-dev && \
python${PYTHON_VERSION} \
python${PYTHON_VERSION}-venv \
python${PYTHON_VERSION}-dev && \
break || \
(echo "Attempt $i failed, retrying in 15s..." && sleep 15); \
done && \
rm -rf /var/lib/apt/lists/*
# Install Node.js 24 with retry mechanism
# Install Node.js with retry mechanism
RUN for i in 1 2 3; do \
echo "Attempt $i: Installing Node.js 24..." && \
echo "Attempt $i: Installing Node.js ${NODE_MAJOR}..." && \
curl -fsSL --connect-timeout 30 --max-time 300 \
https://deb.nodesource.com/setup_24.x | bash - && \
https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - && \
find /etc/apt -type f \( -name 'sources.list' -o -name '*.sources' -o -name '*.list' \) -print0 | xargs -0 sed -i 's|http://|https://|g' && \
apt-get update && \
timeout 300 apt-get install -y --no-install-recommends nodejs && \
@@ -146,12 +158,12 @@ RUN set -eu && \
RUN echo "=== Installing Global Development Tools ===" && \
# Use npm for global installations (works better than yarn global in Berry)
npm install -g \
@playwright/test@1.56.1 \
typescript@5.3.3 \
eslint@9.33.0 \
prettier@3.6.2 \
vite@7.1.10 \
@types/node@20.16.0 && \
@playwright/test@${PLAYWRIGHT_VERSION} \
typescript@${TYPESCRIPT_VERSION} \
eslint@${ESLINT_VERSION} \
prettier@${PRETTIER_VERSION} \
vite@${VITE_VERSION} \
@types/node@${NODE_TYPES_VERSION} && \
# Verify global tools are available
which playwright && \
which tsc && \
@@ -212,7 +224,7 @@ RUN echo '#!/bin/bash' > /usr/local/bin/setup-ssh && \
# Verify that all base tools are working
RUN echo "=== Base System Verification ===" && \
python3.14 --version && \
python${PYTHON_VERSION} --version && \
node --version && \
yarn --version && \
uv --version && \

View File

@@ -1,4 +1,6 @@
ARG PLAYWRIGHT_BASE_IMAGE=mcr.microsoft.com/playwright:v1.61.1-jammy
ARG PLAYWRIGHT_VERSION=1.61.1
ARG PLAYWRIGHT_DISTRO=jammy
ARG PLAYWRIGHT_BASE_IMAGE=mcr.microsoft.com/playwright:v${PLAYWRIGHT_VERSION}-${PLAYWRIGHT_DISTRO}
FROM ${PLAYWRIGHT_BASE_IMAGE}
WORKDIR /workspace/frontend

View File

@@ -1,5 +1,9 @@
# Frontend Dockerfile for Vue/Vite TypeScript project
FROM node:24-alpine AS deps
ARG NODE_ALPINE_TAG=24-alpine
ARG YARN_VERSION=4.17.1
FROM node:${NODE_ALPINE_TAG} AS deps
ARG YARN_VERSION
WORKDIR /app
@@ -20,7 +24,7 @@ RUN set -eux; \
return 1; \
}; \
corepack enable; \
retry 5 corepack prepare yarn@4.17.1 --activate; \
retry 5 corepack prepare yarn@${YARN_VERSION} --activate; \
retry 5 yarn install --immutable
FROM deps AS build

View File

@@ -1,5 +1,23 @@
#!/usr/bin/env xonsh
from pathlib import Path
def load_ci_versions():
env_path = Path(__file__).resolve().parents[2] / "versions" / "ci.env"
values = {}
for raw_line in env_path.read_text().splitlines():
line = raw_line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
values[key.strip()] = value.strip()
return values
ci_versions = load_ci_versions()
playwright_tag = ci_versions["PLAYWRIGHT_IMAGE_TAG"]
hosts = [
"kankali.darkhelm.lan",
"zhokq.darkhelm.lan",
@@ -14,7 +32,7 @@ remote_code_template = """
mirror_pairs = [
("ACT_UBUNTU", "ghcr.io/catthehacker/ubuntu:act-latest", "kankali.darkhelm.lan:3001/darkhelm.org/act-ubuntu:act-latest"),
("RENOVATE", "ghcr.io/renovatebot/renovate:41", "kankali.darkhelm.lan:3001/darkhelm.org/renovate:41"),
("PLAYWRIGHT", "mcr.microsoft.com/playwright:v1.61.1-jammy", "kankali.darkhelm.lan:3001/darkhelm.org/playwright-browsers:v1.61.1-jammy"),
("PLAYWRIGHT", "mcr.microsoft.com/playwright:" + __PLAYWRIGHT_TAG__, "kankali.darkhelm.lan:3001/darkhelm.org/playwright-browsers:" + __PLAYWRIGHT_TAG__),
]
is_publisher = __IS_PUBLISHER__
@@ -94,5 +112,10 @@ for label, source_image, mirror_image in mirror_pairs:
for host in hosts:
print(f"\n=== {host} ===")
remote_code = remote_code_template.replace("__IS_PUBLISHER__", "True" if host == publisher_host else "False")
quoted_playwright_tag = repr(playwright_tag)
remote_code = (
remote_code_template
.replace("__IS_PUBLISHER__", "True" if host == publisher_host else "False")
.replace("__PLAYWRIGHT_TAG__", quoted_playwright_tag)
)
ssh @(host) @(remote_code)

View File

@@ -4,6 +4,17 @@ set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
COMPOSE_FILE="${ROOT_DIR}/compose.ci.e2e.yml"
PROJECT_NAME="${COMPOSE_PROJECT_NAME:-plex-e2e-local}"
VERSION_ENV_FILE="${ROOT_DIR}/versions/ci.env"
if [ ! -f "${VERSION_ENV_FILE}" ]; then
echo "❌ Missing version source file: ${VERSION_ENV_FILE}"
exit 1
fi
set -a
# shellcheck source=../versions/ci.env
source "${VERSION_ENV_FILE}"
set +a
mkdir -p "${ROOT_DIR}/frontend/.playwright-artifacts"
@@ -12,7 +23,7 @@ export DEPLOYABLE_FRONTEND_IMAGE="${DEPLOYABLE_FRONTEND_IMAGE:-plex-playlist-fro
export DB_PASSWORD="${DB_PASSWORD:-plex_password}"
export DB_URL="${DB_URL:-postgresql://plex_user:${DB_PASSWORD}@database:5432/plex_playlist}"
export E2E_ARTIFACT_DIR="${E2E_ARTIFACT_DIR:-${ROOT_DIR}/frontend/.playwright-artifacts}"
export PLAYWRIGHT_E2E_IMAGE="${PLAYWRIGHT_E2E_IMAGE:-mcr.microsoft.com/playwright:v1.61.1-jammy}"
export PLAYWRIGHT_E2E_IMAGE="${PLAYWRIGHT_E2E_IMAGE:-${PLAYWRIGHT_IMAGE}}"
cleanup() {
docker compose -p "${PROJECT_NAME}" -f "${COMPOSE_FILE}" down -v --remove-orphans >/dev/null 2>&1 || true

18
versions/ci.env Normal file
View File

@@ -0,0 +1,18 @@
# CI/runtime version source of truth for scripts and docker build args.
# shellcheck disable=SC2034
PLAYWRIGHT_VERSION=1.61.1
PLAYWRIGHT_DISTRO=jammy
PLAYWRIGHT_IMAGE_TAG=v1.61.1-jammy
PLAYWRIGHT_IMAGE=mcr.microsoft.com/playwright:${PLAYWRIGHT_IMAGE_TAG}
PLAYWRIGHT_MIRROR_IMAGE=darkhelm.org/playwright-browsers:${PLAYWRIGHT_IMAGE_TAG}
POSTGRES_IMAGE_TAG=16-alpine
POSTGRES_MIRROR_IMAGE=darkhelm.org/postgres:${POSTGRES_IMAGE_TAG}
POSTGRES_UPSTREAM_IMAGE=docker.io/library/postgres:${POSTGRES_IMAGE_TAG}
FRONTEND_NODE_ALPINE_TAG=24-alpine
FRONTEND_YARN_VERSION=4.17.1
UBUNTU_CICD_BASE_VERSION=26.04
PYTHON_VERSION=3.14
NODE_MAJOR=24