Compare commits
34 Commits
v0.0.2
...
renovate/f
| Author | SHA1 | Date | |
|---|---|---|---|
| baacb09d1b | |||
|
|
6db9232f1f | ||
|
|
a93bd3c274 | ||
|
|
ae0c0ac857 | ||
|
|
a6180a187d | ||
|
|
28bfd275e0 | ||
|
|
06e5b2e6c2 | ||
|
|
f937f6d3e5 | ||
|
|
ab0ec0887d | ||
|
|
3db1b0676f | ||
|
|
cd19d06680 | ||
|
|
f71025ee1c | ||
|
|
e61b70c4a0 | ||
|
|
6c98ee7018 | ||
|
|
87f977280c | ||
|
|
ff53dec2c2 | ||
| 2bb73a3f6b | |||
| 8ba9d5f5d0 | |||
| 329663ccdf | |||
| 1ba01171a1 | |||
| 9d97f4bec8 | |||
| 81ff99fe59 | |||
| a51bb70ba0 | |||
| 5fa44e2bec | |||
| 640d3bb767 | |||
| 75fdff75cf | |||
| 4d1069767b | |||
| 9d4db9eaa4 | |||
| 0e76925c90 | |||
| c4f59e4d05 | |||
|
|
e4925e2023 | ||
|
|
f037e7138d | ||
|
|
93b4000b20 | ||
|
|
996a018dad |
@@ -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 backend/pyproject.toml frontend/package.json 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 backend/pyproject.toml frontend/package.json scripts/compute-cicd-base-hash.sh scripts/resolve-mirrored-image.sh
|
||||
fi
|
||||
|
||||
chmod +x scripts/compute-cicd-base-hash.sh
|
||||
@@ -324,8 +324,17 @@ jobs:
|
||||
|
||||
docker_login_with_retry 5 3
|
||||
|
||||
PLAYWRIGHT_BROWSERS_MIRROR_TAG="${GITEA_REGISTRY}/darkhelm.org/playwright-browsers:v1.56.1-jammy"
|
||||
PLAYWRIGHT_BROWSERS_UPSTREAM_TAG="mcr.microsoft.com/playwright:v1.56.1-jammy"
|
||||
set -a
|
||||
# shellcheck source=versions/ci.env
|
||||
source versions/ci.env
|
||||
set +a
|
||||
|
||||
PYTHON_VERSION="$(python3 -c "from pathlib import Path; import re, tomllib, sys; pyproject = tomllib.loads(Path('backend/pyproject.toml').read_text()); requires_python = pyproject['project']['requires-python']; match = re.search(r'([0-9]+[.][0-9]+)', requires_python); sys.exit('could not derive backend Python version') if not match else None; print(match.group(1))")"
|
||||
|
||||
NODE_MAJOR="$(node -p "const pkg = require('./frontend/package.json'); const match = String(pkg.engines?.node ?? '').match(/(\\d+)/); if (!match) throw new Error('could not derive frontend Node major'); match[1]")"
|
||||
|
||||
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 +351,10 @@ 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 PYTHON_VERSION="${PYTHON_VERSION}" \
|
||||
--build-arg NODE_MAJOR="${NODE_MAJOR}" \
|
||||
--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 +412,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 +1107,16 @@ jobs:
|
||||
FRONTEND_BASE_TAG_REF="${FRONTEND_BASE_REPO}:${HEAD_SHA}"
|
||||
FRONTEND_BASE_CACHE_REF="${FRONTEND_BASE_REPO}:cache"
|
||||
|
||||
NODE_ALPINE_TAG="$(node -p "const pkg = require('./frontend/package.json'); const match = String(pkg.engines?.node ?? '').match(/(\\d+)/); if (!match) throw new Error('could not derive frontend Node major'); match[1] + '-alpine'")"
|
||||
YARN_VERSION="$(node -p "const pkg = require('./frontend/package.json'); const match = String(pkg.packageManager ?? '').match(/^yarn@(.*)$/); if (!match) throw new Error('could not derive frontend Yarn version'); match[1]")"
|
||||
|
||||
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 +1303,17 @@ jobs:
|
||||
DEPLOYABLE_FRONTEND_TAG_REF="${DEPLOYABLE_FRONTEND_REPO}:${HEAD_SHA}"
|
||||
DEPLOYABLE_FRONTEND_CACHE_REF="${DEPLOYABLE_FRONTEND_REPO}:cache"
|
||||
|
||||
NODE_ALPINE_TAG="$(node -p "const pkg = require('./frontend/package.json'); const match = String(pkg.engines?.node ?? '').match(/(\\d+)/); if (!match) throw new Error('could not derive frontend Node major'); match[1] + '-alpine'")"
|
||||
YARN_VERSION="$(node -p "const pkg = require('./frontend/package.json'); const match = String(pkg.packageManager ?? '').match(/^yarn@(.*)$/); if (!match) throw new Error('could not derive frontend Yarn version'); match[1]")"
|
||||
|
||||
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 +1465,13 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
PLAYWRIGHT_BASE_IMAGE_MIRROR="${GITEA_REGISTRY}/darkhelm.org/playwright-browsers:v1.56.1-jammy"
|
||||
PLAYWRIGHT_BASE_IMAGE_UPSTREAM="mcr.microsoft.com/playwright:v1.56.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 +1571,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 +1836,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 +1868,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 +2151,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 +2183,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}" \
|
||||
|
||||
@@ -180,7 +180,7 @@ jobs:
|
||||
RENOVATE_NODE_ARGS: --max-old-space-size=768
|
||||
RENOVATE_OSV_VULNERABILITY_ALERTS: "false"
|
||||
RENOVATE_FETCH_CHANGELOGS: "off"
|
||||
LOG_LEVEL: info
|
||||
LOG_LEVEL: debug
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -400,10 +400,10 @@ jobs:
|
||||
|
||||
# Manual workflow dispatch should execute immediately, regardless of repository schedule windows.
|
||||
if [ "${GITHUB_EVENT_NAME:-}" = "workflow_dispatch" ]; then
|
||||
export RENOVATE_IGNORE_SCHEDULE="true"
|
||||
echo "✓ Manual run detected: ignoring Renovate schedule constraints"
|
||||
export RENOVATE_FORCE='{"schedule":["at any time"]}'
|
||||
echo "✓ Manual run detected: forcing Renovate outside schedule windows"
|
||||
else
|
||||
unset RENOVATE_IGNORE_SCHEDULE
|
||||
unset RENOVATE_FORCE
|
||||
fi
|
||||
|
||||
# Multi-batch runs must not prune branches created by earlier batches.
|
||||
@@ -412,22 +412,16 @@ jobs:
|
||||
run_renovate_with_endpoint() {
|
||||
endpoint="$1"
|
||||
enabled_managers="$2"
|
||||
batch_label="$3"
|
||||
batch_node_args="${RENOVATE_NODE_ARGS}"
|
||||
batch_pr_limit="${RENOVATE_PR_CONCURRENT_LIMIT}"
|
||||
batch_branch_limit="${RENOVATE_BRANCH_CONCURRENT_LIMIT}"
|
||||
|
||||
if [ "${batch_label}" = "npm" ]; then
|
||||
batch_node_args="--max-old-space-size=1536"
|
||||
batch_pr_limit="1"
|
||||
batch_branch_limit="1"
|
||||
fi
|
||||
single_pass_node_args="${RENOVATE_NODE_ARGS}"
|
||||
single_pass_pr_limit="${RENOVATE_PR_CONCURRENT_LIMIT}"
|
||||
single_pass_branch_limit="${RENOVATE_BRANCH_CONCURRENT_LIMIT}"
|
||||
|
||||
export RENOVATE_ENDPOINT="${endpoint}"
|
||||
echo "Running Renovate batch '${batch_label}' with endpoint: ${RENOVATE_ENDPOINT}"
|
||||
echo "Enabled managers for '${batch_label}': ${enabled_managers}"
|
||||
echo "Node args for '${batch_label}': ${batch_node_args}"
|
||||
echo "PR/branch limits for '${batch_label}': ${batch_pr_limit}/${batch_branch_limit}"
|
||||
echo "Running Renovate single pass with endpoint: ${RENOVATE_ENDPOINT}"
|
||||
echo "Enabled managers: ${enabled_managers}"
|
||||
echo "Node args: ${single_pass_node_args}"
|
||||
echo "PR/branch limits: ${single_pass_pr_limit}/${single_pass_branch_limit}"
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
docker run --rm ${DOCKER_ADD_HOST_ARG} \
|
||||
@@ -448,12 +442,12 @@ jobs:
|
||||
-e RENOVATE_GIT_AUTHOR \
|
||||
-e RENOVATE_ONBOARDING \
|
||||
-e RENOVATE_REQUIRE_CONFIG \
|
||||
-e RENOVATE_PR_CONCURRENT_LIMIT="${batch_pr_limit}" \
|
||||
-e RENOVATE_BRANCH_CONCURRENT_LIMIT="${batch_branch_limit}" \
|
||||
-e RENOVATE_NODE_ARGS="${batch_node_args}" \
|
||||
-e RENOVATE_PR_CONCURRENT_LIMIT="${single_pass_pr_limit}" \
|
||||
-e RENOVATE_BRANCH_CONCURRENT_LIMIT="${single_pass_branch_limit}" \
|
||||
-e RENOVATE_NODE_ARGS="${single_pass_node_args}" \
|
||||
-e RENOVATE_OSV_VULNERABILITY_ALERTS \
|
||||
-e RENOVATE_FETCH_CHANGELOGS \
|
||||
-e RENOVATE_IGNORE_SCHEDULE \
|
||||
-e RENOVATE_FORCE \
|
||||
-e RENOVATE_PRUNE_STALE_BRANCHES \
|
||||
-e RENOVATE_ENABLED_MANAGERS="${enabled_managers}" \
|
||||
--entrypoint /bin/sh \
|
||||
@@ -469,30 +463,27 @@ jobs:
|
||||
renovate --platform "${RENOVATE_PLATFORM}" --endpoint "${RENOVATE_ENDPOINT}" --git-url "${RENOVATE_GIT_URL}" --git-author "${RENOVATE_GIT_AUTHOR}" --onboarding="${RENOVATE_ONBOARDING}" --pr-concurrent-limit "${RENOVATE_PR_CONCURRENT_LIMIT}" --branch-concurrent-limit "${RENOVATE_BRANCH_CONCURRENT_LIMIT}" --osv-vulnerability-alerts="${RENOVATE_OSV_VULNERABILITY_ALERTS}" --enabled-managers "${RENOVATE_ENABLED_MANAGERS}" DarkHelm.org/plex-playlist'
|
||||
}
|
||||
|
||||
run_batch_with_fallback() {
|
||||
run_single_pass_with_fallback() {
|
||||
enabled_managers="$1"
|
||||
batch_label="$2"
|
||||
|
||||
if run_renovate_with_endpoint "${BASE_ENDPOINT}" "${enabled_managers}" "${batch_label}" 2>&1 | tee -a /tmp/renovate.log; then
|
||||
if run_renovate_with_endpoint "${BASE_ENDPOINT}" "${enabled_managers}" 2>&1 | tee -a /tmp/renovate.log; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
primary_status=$?
|
||||
if [ "${primary_status}" -eq 137 ] || tail -n 400 /tmp/renovate.log | grep -qE '(^|[[:space:]])Killed($|[[:space:]])|exitcode '\''137'\''' ; then
|
||||
echo "❌ Renovate batch '${batch_label}' terminated by OOM/kill signal on primary endpoint; skipping endpoint retry"
|
||||
echo "❌ Renovate single pass terminated by OOM/kill signal on primary endpoint; skipping endpoint retry"
|
||||
return "${primary_status}"
|
||||
fi
|
||||
|
||||
echo "⚠ Renovate batch '${batch_label}' failed with primary endpoint. Retrying with API endpoint form..."
|
||||
run_renovate_with_endpoint "${API_ENDPOINT}" "${enabled_managers}" "${batch_label}" 2>&1 | tee -a /tmp/renovate.log
|
||||
echo "⚠ Renovate single pass failed with primary endpoint. Retrying with API endpoint form..."
|
||||
run_renovate_with_endpoint "${API_ENDPOINT}" "${enabled_managers}" 2>&1 | tee -a /tmp/renovate.log
|
||||
}
|
||||
|
||||
: > /tmp/renovate.log
|
||||
|
||||
# Run low-memory batches sequentially to keep peak usage below constrained runner limits.
|
||||
run_batch_with_fallback "docker-compose,dockerfile" "containers"
|
||||
run_batch_with_fallback "github-actions,pep621" "python-and-actions"
|
||||
run_batch_with_fallback "npm" "npm"
|
||||
# Run Renovate once across all enabled managers.
|
||||
run_single_pass_with_fallback "docker-compose,dockerfile,github-actions,npm,pep621"
|
||||
|
||||
echo "✓ Renovate execution completed"
|
||||
|
||||
|
||||
@@ -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}
|
||||
ARG UBUNTU_VERSION=26.04
|
||||
FROM ${PLAYWRIGHT_BROWSERS_IMAGE} AS playwright-browsers
|
||||
|
||||
FROM ubuntu:22.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
|
||||
@@ -59,6 +71,11 @@ RUN set -eux; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
done; \
|
||||
if apt-cache show libasound2t64 >/dev/null 2>&1; then \
|
||||
asound_package=libasound2t64; \
|
||||
else \
|
||||
asound_package=libasound2; \
|
||||
fi; \
|
||||
apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
curl \
|
||||
@@ -77,15 +94,15 @@ RUN set -eux; \
|
||||
libxrandr2 \
|
||||
libgbm1 \
|
||||
libxkbcommon0 \
|
||||
libasound2 \
|
||||
"$asound_package" \
|
||||
tzdata \
|
||||
gnupg \
|
||||
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 && \
|
||||
@@ -94,28 +111,38 @@ 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
|
||||
RUN for i in 1 2 3; do \
|
||||
echo "Attempt $i: Installing Node.js 24..." && \
|
||||
curl -fsSL --connect-timeout 30 --max-time 300 \
|
||||
https://deb.nodesource.com/setup_24.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 && \
|
||||
break || \
|
||||
(echo "Attempt $i failed, retrying in 15s..." && sleep 15); \
|
||||
done && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
# Install Node.js with retry mechanism
|
||||
RUN node_arch="$(dpkg --print-architecture)"; \
|
||||
case "$node_arch" in \
|
||||
amd64) node_tarball_arch=linux-x64 ;; \
|
||||
arm64) node_tarball_arch=linux-arm64 ;; \
|
||||
*) echo "unsupported architecture for Node.js tarball: $node_arch" >&2; exit 1 ;; \
|
||||
esac; \
|
||||
for i in 1 2 3; do \
|
||||
echo "Attempt $i: Installing Node.js ${NODE_MAJOR}..."; \
|
||||
node_version="$(python3 -c "import json, urllib.request; versions = json.load(urllib.request.urlopen('https://nodejs.org/dist/index.json')); print(next(item['version'].lstrip('v') for item in versions if item['version'].startswith('v${NODE_MAJOR}.')))" )"; \
|
||||
if curl -fsSLO "https://nodejs.org/dist/v${node_version}/node-v${node_version}-${node_tarball_arch}.tar.xz" && \
|
||||
tar -xJf "node-v${node_version}-${node_tarball_arch}.tar.xz" -C /usr/local --strip-components=1 && \
|
||||
rm -f "node-v${node_version}-${node_tarball_arch}.tar.xz"; then \
|
||||
break; \
|
||||
fi; \
|
||||
if [ "$i" -lt 3 ]; then \
|
||||
echo "Attempt $i failed, retrying in 15s..."; \
|
||||
sleep 15; \
|
||||
else \
|
||||
exit 1; \
|
||||
fi; \
|
||||
done
|
||||
|
||||
# Enable corepack for yarn and set up Yarn Berry
|
||||
RUN corepack enable \
|
||||
@@ -141,12 +168,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 && \
|
||||
@@ -207,7 +234,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 && \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -19,7 +23,8 @@ RUN set -eux; \
|
||||
done; \
|
||||
return 1; \
|
||||
}; \
|
||||
retry 5 npm install -g --force @yarnpkg/cli-dist@4.10.3; \
|
||||
corepack enable; \
|
||||
retry 5 corepack prepare yarn@${YARN_VERSION} --activate; \
|
||||
retry 5 yarn install --immutable
|
||||
|
||||
FROM deps AS build
|
||||
|
||||
@@ -4,23 +4,23 @@ requires = ["hatchling"]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"ruff==0.14.1",
|
||||
"pyright==1.1.410",
|
||||
"ruff==0.15.22",
|
||||
"pyright==1.1.411",
|
||||
"pydoclint==0.9.1",
|
||||
"pytest==9.1.1",
|
||||
"pytest-asyncio==1.4.0",
|
||||
"pytest-cov==7.0.0",
|
||||
"typeguard==4.4.4",
|
||||
"pytest-cov==7.1.0",
|
||||
"typeguard==4.5.2",
|
||||
"httpx==0.28.1", # For testing async HTTP calls
|
||||
"pytest-mock==3.15.1",
|
||||
# File format and linting tools
|
||||
"pre-commit==4.6.0", # For running pre-commit hooks in CI
|
||||
"pre-commit==4.6.1", # For running pre-commit hooks in CI
|
||||
"pyyaml==6.0.3",
|
||||
"yamllint==1.37.1",
|
||||
"toml-sort==0.24.3",
|
||||
"yamllint==1.38.0",
|
||||
"toml-sort==0.24.4",
|
||||
"language-formatters-pre-commit-hooks==2.16.0", # For pretty-format-toml
|
||||
"xdoctest==1.3.0", # For doctest support # Task runner for unified development workflows
|
||||
"poethepoet==0.41.0",
|
||||
"xdoctest==1.3.2", # For doctest support # Task runner for unified development workflows
|
||||
"poethepoet==0.48.0",
|
||||
"bandit>=1.9.4",
|
||||
"pip-audit>=2.9.0"
|
||||
]
|
||||
@@ -35,7 +35,7 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.14"
|
||||
]
|
||||
dependencies = [
|
||||
"fastapi==0.139.0",
|
||||
"fastapi==0.141.1",
|
||||
"sqlalchemy==2.0.51",
|
||||
"psycopg[binary]==3.3.4",
|
||||
"uvicorn==0.51.0"
|
||||
|
||||
1323
backend/uv.lock
generated
1323
backend/uv.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -433,6 +433,16 @@ Current workflow behavior includes:
|
||||
For details of base/complete image build strategy, see [CI/CD Multi-Stage Build Architecture](CICD_MULTI_STAGE_BUILD.md).
|
||||
For incident handling, see [CI/CD Troubleshooting](CICD_TROUBLESHOOTING_GUIDE.md).
|
||||
|
||||
### Renovate Manual Run Behavior
|
||||
|
||||
The Renovate workflow at `.gitea/workflows/renovate.yml` now treats manual dispatch as an explicit operator override:
|
||||
|
||||
- when triggered via `workflow_dispatch`, it forces out-of-window execution by setting `RENOVATE_IGNORE_SCHEDULE=true`
|
||||
- it also sets `RENOVATE_SCHEDULE="at any time"` for the run
|
||||
- it executes one single pass across enabled managers instead of splitting work into multiple manager batches
|
||||
|
||||
This keeps scheduled behavior unchanged while making manual runs deterministic and immediate.
|
||||
|
||||
### Operator Quick Reference
|
||||
|
||||
Use workflow dispatch when you need deterministic reruns on a specific commit:
|
||||
|
||||
@@ -94,7 +94,7 @@ export default [
|
||||
security,
|
||||
},
|
||||
rules: {
|
||||
...vue.configs['vue3-essential'].rules,
|
||||
...vue.configs['essential'].rules,
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
...securityRules,
|
||||
|
||||
@@ -22,29 +22,29 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.4.0",
|
||||
"zod": "^3.22.0"
|
||||
"zod": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.40.0",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/node": "^24.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"@vitest/coverage-v8": "^4.0.0",
|
||||
"@vue/test-utils": "^2.4.0",
|
||||
"@vue/tsconfig": "^0.5.0",
|
||||
"eslint": "^9.33.0",
|
||||
"eslint-plugin-jsdoc": "^50.0.0",
|
||||
"@vue/tsconfig": "^0.9.0",
|
||||
"eslint": "^10.0.0",
|
||||
"eslint-plugin-jsdoc": "^63.0.0",
|
||||
"eslint-plugin-security": "^4.0.1",
|
||||
"eslint-plugin-tsdoc": "^0.3.0",
|
||||
"eslint-plugin-vue": "^9.28.0",
|
||||
"jsdom": "^23.0.0",
|
||||
"prettier": "3.6.2",
|
||||
"typescript": "~5.3.0",
|
||||
"vite": "^7.1.10",
|
||||
"vitest": "^3.2.4",
|
||||
"vue-eslint-parser": "^9.4.0",
|
||||
"vue-tsc": "^2.0.0"
|
||||
"eslint-plugin-tsdoc": "^0.5.0",
|
||||
"eslint-plugin-vue": "^10.0.0",
|
||||
"jsdom": "^29.0.0",
|
||||
"prettier": "3.9.6",
|
||||
"typescript": "~6.0.0",
|
||||
"vite": "^8.0.0",
|
||||
"vitest": "^4.0.0",
|
||||
"vue-eslint-parser": "^10.0.0",
|
||||
"vue-tsc": "^3.0.0"
|
||||
},
|
||||
"packageManager": "yarn@4.10.3"
|
||||
"packageManager": "yarn@4.17.1"
|
||||
}
|
||||
|
||||
@@ -46,10 +46,7 @@ function enhancedParse<T>(this: z.ZodType<T>, data: unknown): T {
|
||||
* @param data - The data to validate against the schema
|
||||
* @returns Safe parse result with success flag and data or error
|
||||
*/
|
||||
function enhancedSafeParse<T>(
|
||||
this: z.ZodType<T>,
|
||||
data: unknown
|
||||
): z.SafeParseReturnType<unknown, T> {
|
||||
function enhancedSafeParse<T>(this: z.ZodType<T>, data: unknown) {
|
||||
const result = originalSafeParse.call(this, data);
|
||||
validationCount++;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export const UserSchema = z.object({
|
||||
theme: z.enum(['light', 'dark']).default('light'),
|
||||
notifications: z.boolean().default(true),
|
||||
})
|
||||
.default({}),
|
||||
.default({ theme: 'light', notifications: true }),
|
||||
});
|
||||
|
||||
// Inferred types (no manual type definitions needed)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"ignoreDeprecations": "6.0",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
@@ -12,6 +13,7 @@
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["node", "vite/client", "vitest/globals"],
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
|
||||
3266
frontend/yarn.lock
3266
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -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.56.1-jammy", "kankali.darkhelm.lan:3001/darkhelm.org/playwright-browsers:v1.56.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)
|
||||
|
||||
@@ -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,6 +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:-${PLAYWRIGHT_IMAGE}}"
|
||||
|
||||
cleanup() {
|
||||
docker compose -p "${PROJECT_NAME}" -f "${COMPOSE_FILE}" down -v --remove-orphans >/dev/null 2>&1 || true
|
||||
@@ -23,6 +35,7 @@ echo "Running E2E tests against runtime backend/frontend images via compose"
|
||||
echo "DEPLOYABLE_BACKEND_IMAGE=${DEPLOYABLE_BACKEND_IMAGE}"
|
||||
echo "DEPLOYABLE_FRONTEND_IMAGE=${DEPLOYABLE_FRONTEND_IMAGE}"
|
||||
echo "E2E_ARTIFACT_DIR=${E2E_ARTIFACT_DIR}"
|
||||
echo "PLAYWRIGHT_E2E_IMAGE=${PLAYWRIGHT_E2E_IMAGE}"
|
||||
|
||||
docker compose -p "${PROJECT_NAME}" -f "${COMPOSE_FILE}" up -d database backend frontend
|
||||
|
||||
@@ -34,5 +47,5 @@ docker run --rm \
|
||||
-e PLAYWRIGHT_JUNIT_OUTPUT_FILE=/tmp/playwright-artifacts/playwright-results.xml \
|
||||
-v "${ROOT_DIR}/frontend:/workspace/frontend" \
|
||||
-v "${E2E_ARTIFACT_DIR}:/tmp/playwright-artifacts" \
|
||||
mcr.microsoft.com/playwright:v1.56.1-jammy \
|
||||
"${PLAYWRIGHT_E2E_IMAGE}" \
|
||||
bash -lc "cd /workspace/frontend && corepack enable && yarn install --immutable && yarn test:e2e --reporter=list --timeout=90000"
|
||||
|
||||
12
versions/ci.env
Normal file
12
versions/ci.env
Normal file
@@ -0,0 +1,12 @@
|
||||
# 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}
|
||||
UBUNTU_CICD_BASE_VERSION=26.04
|
||||
Reference in New Issue
Block a user