Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ jobs:
docker buildx build \
--platform linux/amd64 \
--target builder \
--build-arg BUILDER_IMAGE=public.ecr.aws/docker/library/alpine@sha256:19b4bcc4f60e99dd5ebdca0cbce22c503bbcff197549d7e19dab4f22254dc864 \
--build-arg BUILDER_IMAGE=docker.io/library/alpine@sha256:19b4bcc4f60e99dd5ebdca0cbce22c503bbcff197549d7e19dab4f22254dc864 \
--output type=local,dest=out \
.

Expand Down
184 changes: 139 additions & 45 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -207,59 +207,36 @@ jobs:
docker rm -fv "$container"
rm "$RUNTIME_E2E_CONTAINER_FILE"

candidate:
name: Probe ${{ matrix.variant }} / ${{ matrix.arch }} / PG${{ matrix.postgres }}
needs: [resolve, media-fixtures, linux-bundle]
candidate-image:
name: Build ${{ matrix.variant }} / ${{ matrix.arch }}
needs: [resolve, linux-bundle]
if: ${{ always() && needs.resolve.result == 'success' && needs.linux-bundle.result == 'success' }}
permissions:
contents: read
packages: write
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
strategy: &native_strategy
strategy:
fail-fast: false
max-parallel: 8
max-parallel: 4
matrix:
include:
- variant: linuxserver
dockerfile: Dockerfile
arch: amd64
runner: ubuntu-24.04
postgres: 15
- variant: linuxserver
dockerfile: Dockerfile
arch: amd64
runner: ubuntu-24.04
postgres: 18
- variant: linuxserver
dockerfile: Dockerfile
arch: arm64
runner: ubuntu-24.04-arm
postgres: 15
- variant: linuxserver
dockerfile: Dockerfile
arch: arm64
runner: ubuntu-24.04-arm
postgres: 18
- variant: plexinc
dockerfile: Dockerfile.standalone
arch: amd64
runner: ubuntu-24.04
postgres: 15
- variant: plexinc
dockerfile: Dockerfile.standalone
arch: amd64
runner: ubuntu-24.04
postgres: 18
- variant: plexinc
dockerfile: Dockerfile.standalone
arch: arm64
runner: ubuntu-24.04-arm
postgres: 15
- variant: plexinc
dockerfile: Dockerfile.standalone
arch: arm64
runner: ubuntu-24.04-arm
postgres: 18
steps:
- name: Use an isolated Docker client configuration
run: |
Expand All @@ -273,10 +250,6 @@ jobs:
with:
ref: ${{ needs.resolve.outputs.source_sha }}
persist-credentials: false
- uses: actions/download-artifact@v4
with:
name: ${{ inputs.profile_id && format('{0}-pinned-real-media', inputs.profile_id) || 'pinned-real-media' }}
path: media-fixture-cache
- uses: actions/download-artifact@v4
with:
name: ${{ inputs.profile_id && format('{0}-upstream-candidate', inputs.profile_id) || 'upstream-candidate' }}
Expand All @@ -293,13 +266,12 @@ jobs:
REPOSITORY_OWNER: ${{ github.repository_owner }}
VARIANT: ${{ matrix.variant }}
ARCH: ${{ matrix.arch }}
POSTGRES_MAJOR: ${{ matrix.postgres }}
PROFILE_ID: ${{ inputs.profile_id }}
run: |
set -eu
IMAGE="ghcr.io/${REPOSITORY_OWNER,,}/plex-postgresql-${VARIANT}"
echo "repository=$IMAGE" >> "$GITHUB_OUTPUT"
echo "tag=$IMAGE:candidate-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-${PROFILE_ID:-legacy}-${ARCH}-pg${POSTGRES_MAJOR}" >> "$GITHUB_OUTPUT"
echo "tag=$IMAGE:candidate-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-${PROFILE_ID:-legacy}-${ARCH}" >> "$GITHUB_OUTPUT"
- uses: actions/download-artifact@v4
with:
name: ${{ (inputs.payload_profile_id || inputs.profile_id) && format('{0}-', inputs.payload_profile_id || inputs.profile_id) || '' }}runtime-payload-${{ matrix.arch }}
Expand All @@ -320,13 +292,128 @@ jobs:
RUNTIME_PAYLOAD=certified_payload
build-contexts: |
certified_payload=./certified-payload
- name: Preserve immutable build identity
env:
CANDIDATE_REPOSITORY: ${{ steps.image.outputs.repository }}
CANDIDATE_DIGEST: ${{ steps.build.outputs.digest }}
BUILD_METADATA: ${{ steps.build.outputs.metadata }}
SOURCE_SHA: ${{ needs.resolve.outputs.source_sha }}
run: |
printf '%s\n' "$CANDIDATE_REPOSITORY@$CANDIDATE_DIGEST" > evidence/candidate-ref.txt
printf '%s\n' "$BUILD_METADATA" > evidence/build-metadata.json
printf '%s\n' "$SOURCE_SHA" > evidence/source-sha.txt
- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.profile_id && format('{0}-', inputs.profile_id) || '' }}built-image-${{ matrix.variant }}-${{ matrix.arch }}
path: evidence
if-no-files-found: error

candidate:
name: Probe ${{ matrix.variant }} / ${{ matrix.arch }} / PG${{ matrix.postgres }}
needs: [resolve, media-fixtures, candidate-image]
if: ${{ always() && needs.resolve.result == 'success' && needs.media-fixtures.result == 'success' && needs.candidate-image.result == 'success' }}
permissions:
contents: read
packages: read
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
strategy: &native_strategy
fail-fast: false
max-parallel: 8
matrix:
include:
- variant: linuxserver
dockerfile: Dockerfile
arch: amd64
runner: ubuntu-24.04
postgres: 15
- variant: linuxserver
dockerfile: Dockerfile
arch: amd64
runner: ubuntu-24.04
postgres: 18
- variant: linuxserver
dockerfile: Dockerfile
arch: arm64
runner: ubuntu-24.04-arm
postgres: 15
- variant: linuxserver
dockerfile: Dockerfile
arch: arm64
runner: ubuntu-24.04-arm
postgres: 18
- variant: plexinc
dockerfile: Dockerfile.standalone
arch: amd64
runner: ubuntu-24.04
postgres: 15
- variant: plexinc
dockerfile: Dockerfile.standalone
arch: amd64
runner: ubuntu-24.04
postgres: 18
- variant: plexinc
dockerfile: Dockerfile.standalone
arch: arm64
runner: ubuntu-24.04-arm
postgres: 15
- variant: plexinc
dockerfile: Dockerfile.standalone
arch: arm64
runner: ubuntu-24.04-arm
postgres: 18
steps:
- name: Use an isolated Docker client configuration
run: |
set -eu
docker_config=$(mktemp -d "$RUNNER_TEMP/plex-docker-config.XXXXXX")
printf '{}\n' > "$docker_config/config.json"
chmod 700 "$docker_config"
chmod 600 "$docker_config/config.json"
echo "DOCKER_CONFIG=$docker_config" >> "$GITHUB_ENV"
- uses: actions/checkout@v4
with:
ref: ${{ needs.resolve.outputs.source_sha }}
persist-credentials: false
- uses: actions/download-artifact@v4
with:
name: ${{ inputs.profile_id && format('{0}-pinned-real-media', inputs.profile_id) || 'pinned-real-media' }}
path: media-fixture-cache
- uses: actions/download-artifact@v4
with:
name: ${{ inputs.profile_id && format('{0}-', inputs.profile_id) || '' }}built-image-${{ matrix.variant }}-${{ matrix.arch }}
path: evidence
- name: Require the shared immutable image on the resolved source
env:
SOURCE_SHA: ${{ needs.resolve.outputs.source_sha }}
REPOSITORY_OWNER: ${{ github.repository_owner }}
VARIANT: ${{ matrix.variant }}
run: |
python3 - <<'PYCODE'
import os, re
from pathlib import Path
root = Path('evidence')
image = (root / 'candidate-ref.txt').read_text().strip()
prefix = 'ghcr.io/' + os.environ['REPOSITORY_OWNER'].lower() + '/plex-postgresql-' + os.environ['VARIANT'] + '@'
if not image.startswith(prefix) or not re.fullmatch(r'sha256:[0-9a-f]{64}', image[len(prefix):]):
raise SystemExit('Shared image must be an immutable candidate digest')
if (root / 'source-sha.txt').read_text().strip() != os.environ['SOURCE_SHA']:
raise SystemExit('Shared image source mismatch')
with open(os.environ['GITHUB_ENV'], 'a') as output:
output.write('CANDIDATE_IMAGE=' + image + '\n')
PYCODE
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/download-artifact@v4
with:
name: ${{ inputs.profile_id && format('{0}-linux-release-zip', inputs.profile_id) || 'linux-release-zip' }}
path: release-bundle
- name: Bind the container payload to exact certified bundle bytes
env:
CANDIDATE: ${{ steps.image.outputs.repository }}@${{ steps.build.outputs.digest }}
CANDIDATE: ${{ env.CANDIDATE_IMAGE }}
ARCH: ${{ matrix.arch }}
run: |
docker pull "$CANDIDATE"
Expand All @@ -335,6 +422,13 @@ jobs:
docker cp "$container:/usr/local/lib/plex-postgresql" container-payload
bundle=$(find release-bundle -maxdepth 1 -name '*.zip' -type f)
python3 scripts/verify_candidate_payload.py --bundle "$bundle" --directory container-payload --arch "$ARCH" --output evidence/payload-binding.json
python3 - <<'PY'
import json, os, re
checksum = json.load(open('evidence/payload-binding.json'))['bundle_sha256']
assert re.fullmatch('[0-9a-f]{64}', checksum)
with open(os.environ['GITHUB_ENV'], 'a') as output:
output.write('PLEX_E2E_BUNDLE_SHA256=' + checksum + '\n')
PY
- name: Prepare offline crash diagnostics
run: |
if ! command -v gdb >/dev/null; then
Expand All @@ -348,7 +442,7 @@ jobs:
coredumpctl --version
- name: Verify the exact candidate digest
env:
CANDIDATE_IMAGE: ${{ steps.image.outputs.repository }}@${{ steps.build.outputs.digest }}
SOURCE_SHA: ${{ needs.resolve.outputs.source_sha }}
BASE_PLEX_IMAGE: ${{ matrix.variant == 'linuxserver' && needs.resolve.outputs.linuxserver_image || needs.resolve.outputs.plexinc_image }}
POSTGRES_IMAGE: ${{ needs.resolve.outputs[format('postgres{0}_image', matrix.postgres)] }}
EXPECTED_PLEX_VERSION: ${{ needs.resolve.outputs.plex_version }}
Expand All @@ -358,20 +452,13 @@ jobs:
PLEX_E2E_MEDIA_CACHE_DIR: ${{ github.workspace }}/media-fixture-cache
PLEX_E2E_RESTART_CYCLES: ${{ inputs.mode == 'probe' && '1' || '100' }}
run: bash scripts/plex-container-e2e.sh
- name: Preserve candidate digest and build metadata
- name: Preserve PostgreSQL lane identity
if: always()
env:
CANDIDATE_REPOSITORY: ${{ steps.image.outputs.repository }}
CANDIDATE_DIGEST: ${{ steps.build.outputs.digest }}
BUILD_METADATA: ${{ steps.build.outputs.metadata }}
SOURCE_SHA: ${{ needs.resolve.outputs.source_sha }}
POSTGRES_MAJOR: ${{ matrix.postgres }}
POSTGRES_IMAGE: ${{ needs.resolve.outputs[format('postgres{0}_image', matrix.postgres)] }}
run: |
mkdir -p evidence
printf '%s\n' "$CANDIDATE_REPOSITORY@$CANDIDATE_DIGEST" > evidence/candidate-ref.txt
printf '%s\n' "$BUILD_METADATA" > evidence/build-metadata.json
printf '%s\n' "$SOURCE_SHA" > evidence/source-sha.txt
printf '%s\n' "$POSTGRES_MAJOR" > evidence/postgres-major.txt
printf '%s\n' "$POSTGRES_IMAGE" > evidence/postgres-ref.txt
- uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -440,8 +527,13 @@ jobs:
expected['restart_cycles_completed'] = int(os.environ['REQUIRED_CYCLES'])
if any(result.get(key) != value for key, value in expected.items()):
raise SystemExit('Prior native smoke failed or is incomplete')
binding = json.loads((root / 'payload-binding.json').read_text())
checksum = binding['bundle_sha256']
if not re.fullmatch('[0-9a-f]{64}', checksum) or result.get('bundle_sha256') != checksum:
raise SystemExit('Prior native bundle binding mismatch')
with open(os.environ['GITHUB_ENV'], 'a') as output:
output.write('CANDIDATE_IMAGE=' + image + '\n')
output.write('PLEX_E2E_BUNDLE_SHA256=' + checksum + '\n')
PY
- uses: docker/login-action@v3
with:
Expand All @@ -461,6 +553,7 @@ jobs:
coredumpctl --version
- name: Continuous native mixed fixture workload for 21000 seconds
env:
SOURCE_SHA: ${{ needs.resolve.outputs.source_sha }}
POSTGRES_IMAGE: ${{ needs.resolve.outputs[format('postgres{0}_image', matrix.postgres)] }}
EXPECTED_PLEX_VERSION: ${{ needs.resolve.outputs.plex_version }}
EXPECTED_ARCH: ${{ matrix.arch }}
Expand Down Expand Up @@ -535,6 +628,7 @@ jobs:
native-bundle-install:
name: Native ZIP / ${{ matrix.variant }} / ${{ matrix.arch }}
needs: [resolve, linux-bundle]
if: ${{ always() && needs.resolve.result == 'success' && needs.linux-bundle.result == 'success' }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
strategy:
Expand Down
19 changes: 11 additions & 8 deletions .github/workflows/publish-certified-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,17 @@ jobs:
runs-on: ubuntu-24.04
outputs:
enabled: ${{ steps.trust.outputs.enabled }}
trust_hash: ${{ steps.trust.outputs.hash }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
persist-credentials: false
- id: trust
run: |
if [[ ! -f .github/release-trust.json ]]; then
echo 'enabled=false' >> "$GITHUB_OUTPUT"
echo 'release-publication-unconfigured: genuine certificate bootstrap and reviewed release-trust.json are required.' >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
python3 -c 'import json; assert json.load(open(".github/release-trust.json")).get("enabled") is True'
echo 'enabled=true' >> "$GITHUB_OUTPUT"
env:
RELEASE_TRUST_JSON: ${{ vars.RELEASE_TRUST_JSON }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: python3 scripts/load_release_trust.py
publish:
needs: configuration
if: needs.configuration.outputs.enabled == 'true'
Expand All @@ -56,6 +53,12 @@ jobs:
[[ "$RUN_ID" =~ ^[0-9]+$ ]]
gh run download "$RUN_ID" --repo "$GITHUB_REPOSITORY" --name release-certificate --dir certificate
echo "CERTIFICATE_RUN_ID=$RUN_ID" >> "$GITHUB_ENV"
- name: Recheck operator-reviewed release configuration
env:
RELEASE_TRUST_JSON: ${{ vars.RELEASE_TRUST_JSON }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
EXPECTED_TRUST_HASH: ${{ needs.configuration.outputs.trust_hash }}
run: python3 scripts/load_release_trust.py --expected-hash "$EXPECTED_TRUST_HASH"
- name: Independently authenticate provenance and publish existing bytes
env:
GH_TOKEN: ${{ github.token }}
Expand Down
17 changes: 16 additions & 1 deletion .github/workflows/release-certification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,28 @@ jobs:
persist-credentials: false
- uses: actions/download-artifact@v4
with:
# Docker build records are gzip data, not ZIP evidence archives.
pattern: '!*.dockerbuild'
path: certification-evidence
- name: Validate every cell and bind final artifact bytes
env:
GH_TOKEN: ${{ github.token }}
PROFILE_JSON: ${{ inputs.upstream_profile_json }}
SOURCE_SHA: ${{ inputs.source_sha }}
run: python3 scripts/issue_release_certificate.py --evidence certification-evidence --profile-json "$PROFILE_JSON" --source-sha "$SOURCE_SHA" --policy .github/plex-compatibility-policy.json --output certificate/certificate.json
run: python3 scripts/issue_release_certificate.py --evidence certification-evidence --profile-json "$PROFILE_JSON" --source-sha "$SOURCE_SHA" --policy .github/plex-compatibility-policy.json --assets certified-assets --output certificate/draft.json
- uses: actions/upload-artifact@v4
id: certified-assets
with:
name: certified-linux-release-zip-${{ github.run_attempt }}
path: certified-assets/*
if-no-files-found: error
retention-days: 90
- name: Bind fresh publication artifact
env:
GH_TOKEN: ${{ github.token }}
ARTIFACT_ID: ${{ steps.certified-assets.outputs.artifact-id }}
ARTIFACT_DIGEST: ${{ steps.certified-assets.outputs.artifact-digest }}
run: python3 scripts/issue_release_certificate.py --draft certificate/draft.json --artifact-id "$ARTIFACT_ID" --artifact-digest "$ARTIFACT_DIGEST" --assets certified-assets --output certificate/certificate.json
- uses: actions/upload-artifact@v4
with:
name: release-certificate
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/runtime-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ jobs:
RUNTIME_E2E_ADMIN_DATABASE: runtime_e2e_admin
# Pinned alpine 3.15 (musl 1.2.2, the release builder base) via the
# ECR mirror to stay independent of Docker Hub auth outages.
ALPINE_BUILDER_IMAGE: public.ecr.aws/docker/library/alpine@sha256:19b4bcc4f60e99dd5ebdca0cbce22c503bbcff197549d7e19dab4f22254dc864
ALPINE_BUILDER_IMAGE: docker.io/library/alpine@sha256:19b4bcc4f60e99dd5ebdca0cbce22c503bbcff197549d7e19dab4f22254dc864
run: |
set -eu
docker run --rm --network host \
Expand Down
Loading
Loading