diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69534372..2a046f88 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,12 +32,11 @@ on: workflow_dispatch: env: - # Canonical Docker Hub image name. The deprecated mirror below keeps - # publishing alongside this one until 2026-10-29 so users on the old - # name keep getting updates while they migrate. + # Canonical Docker Hub image name. The old mirror + # (stevezzau/plex_generate_vid_previews) was retired — a one-shot tombstone + # (retire-deprecated-image.yml) now sits on its tags telling users to switch. # Change DOCKER_IMAGE for forks; see CONTRIBUTING.md. DOCKER_IMAGE: stevezzau/media_preview_generator - DOCKER_IMAGE_DEPRECATED: stevezzau/plex_generate_vid_previews concurrency: # Cancel superseded PR runs; never cancel in-flight pushes to main/dev/tags @@ -366,7 +365,6 @@ jobs: head_sha: ${{ steps.ctx.outputs.head_sha }} is_tag: ${{ steps.ctx.outputs.is_tag }} docker_tags_canonical: ${{ steps.ctx.outputs.docker_tags_canonical }} - docker_tags_deprecated: ${{ steps.ctx.outputs.docker_tags_deprecated }} steps: - name: Compute build context id: ctx @@ -380,12 +378,10 @@ jobs: if [[ "$REF_TYPE" == "tag" ]]; then echo "is_tag=true" >> "$GITHUB_OUTPUT" echo "docker_tags_canonical=-t ${{ env.DOCKER_IMAGE }}:$HEAD_BRANCH -t ${{ env.DOCKER_IMAGE }}:latest" >> "$GITHUB_OUTPUT" - echo "docker_tags_deprecated=-t ${{ env.DOCKER_IMAGE_DEPRECATED }}:$HEAD_BRANCH -t ${{ env.DOCKER_IMAGE_DEPRECATED }}:latest" >> "$GITHUB_OUTPUT" else # dev branch push or manual workflow_dispatch echo "is_tag=false" >> "$GITHUB_OUTPUT" echo "docker_tags_canonical=-t ${{ env.DOCKER_IMAGE }}:dev" >> "$GITHUB_OUTPUT" - echo "docker_tags_deprecated=-t ${{ env.DOCKER_IMAGE_DEPRECATED }}:dev" >> "$GITHUB_OUTPUT" fi build: @@ -394,24 +390,16 @@ jobs: fail-fast: true matrix: include: - # 4 jobs total: 2 platforms × 2 image flavours. The deprecated - # mirror and the canonical image differ only in the published - # name + the DOCKER_IMAGE_NAME build-arg baked in (drives the - # in-app deprecation banner). Layer cache is shared across - # flavours via cache-scope so the second flavour's build is - # near-instant. + # 2 jobs: amd64 + arm64 of the canonical image. The deprecated + # mirror (stevezzau/plex_generate_vid_previews) was retired — a + # one-shot tombstone image (see retire-deprecated-image.yml) now + # sits on its tags telling users to switch to the canonical name. - platform: linux/amd64 runner: ubuntu-latest image_flavour: canonical - - platform: linux/amd64 - runner: ubuntu-latest - image_flavour: deprecated - platform: linux/arm64 runner: ubuntu-24.04-arm image_flavour: canonical - - platform: linux/arm64 - runner: ubuntu-24.04-arm - image_flavour: deprecated runs-on: ${{ matrix.runner }} steps: @@ -437,14 +425,9 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - - name: Resolve image name for this flavour + - name: Resolve image name id: image - run: | - if [[ "${{ matrix.image_flavour }}" == "canonical" ]]; then - echo "name=${{ env.DOCKER_IMAGE }}" >> "$GITHUB_OUTPUT" - else - echo "name=${{ env.DOCKER_IMAGE_DEPRECATED }}" >> "$GITHUB_OUTPUT" - fi + run: echo "name=${{ env.DOCKER_IMAGE }}" >> "$GITHUB_OUTPUT" - name: Generate bundled release notes # Fetches GitHub Releases into media_preview_generator/release_notes.json @@ -512,13 +495,6 @@ jobs: pattern: digest-*-canonical merge-multiple: true - - name: Download deprecated digests - uses: actions/download-artifact@v4 - with: - path: /tmp/digests/deprecated - pattern: digest-*-deprecated - merge-multiple: true - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -535,13 +511,6 @@ jobs: ${{ needs.context.outputs.docker_tags_canonical }} \ $(printf '${{ env.DOCKER_IMAGE }}@sha256:%s ' *) - - name: Create deprecated mirror manifest list and push - working-directory: /tmp/digests/deprecated - run: | - docker buildx imagetools create \ - ${{ needs.context.outputs.docker_tags_deprecated }} \ - $(printf '${{ env.DOCKER_IMAGE_DEPRECATED }}@sha256:%s ' *) - update-dockerhub-description: runs-on: ubuntu-latest needs: context @@ -558,11 +527,3 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} repository: stevezzau/media_preview_generator readme-filepath: ./DOCKERHUB_README.md - - - name: Update deprecated mirror Docker Hub description - uses: peter-evans/dockerhub-description@v5 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - repository: stevezzau/plex_generate_vid_previews - readme-filepath: ./DOCKERHUB_DEPRECATED_README.md diff --git a/.github/workflows/retire-deprecated-image.yml b/.github/workflows/retire-deprecated-image.yml new file mode 100644 index 00000000..45c421fe --- /dev/null +++ b/.github/workflows/retire-deprecated-image.yml @@ -0,0 +1,54 @@ +name: Retire deprecated Docker image + +# One-shot, manually triggered. Builds the tombstone image (a static "this +# image moved" page) and pushes it OVER the deprecated repo's :latest and :dev +# tags, so anyone still pulling stevezzau/plex_generate_vid_previews gets a +# clear "switch to stevezzau/media_preview_generator" notice. Run this ONCE, +# then the deprecated build is already removed from ci.yml. +on: + workflow_dispatch: + inputs: + confirm: + description: 'Type "retire" to confirm pushing the tombstone over the old image' + required: true + default: '' + +env: + DEPRECATED_IMAGE: stevezzau/plex_generate_vid_previews + +jobs: + tombstone: + runs-on: ubuntu-latest + if: ${{ github.event.inputs.confirm == 'retire' }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Validate Docker secrets + run: | + if [ -z "${{ secrets.DOCKER_USERNAME }}" ] || [ -z "${{ secrets.DOCKER_PASSWORD }}" ]; then + echo "::error::DOCKER_USERNAME and DOCKER_PASSWORD must be set in repository secrets" + exit 1 + fi + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build & push tombstone (multi-arch) over :latest and :dev + uses: docker/build-push-action@v6 + with: + context: ./tombstone + platforms: linux/amd64,linux/arm64 + push: true + tags: | + ${{ env.DEPRECATED_IMAGE }}:latest + ${{ env.DEPRECATED_IMAGE }}:dev diff --git a/DOCKERHUB_DEPRECATED_README.md b/DOCKERHUB_DEPRECATED_README.md deleted file mode 100644 index f76be7a4..00000000 --- a/DOCKERHUB_DEPRECATED_README.md +++ /dev/null @@ -1,43 +0,0 @@ - - -# ⚠️ This image has been renamed - -This Docker Hub repository — `stevezzau/plex_generate_vid_previews` — is now a **mirror** of the canonical image: - -> **➡ [`stevezzau/media_preview_generator`](https://hub.docker.com/r/stevezzau/media_preview_generator)** - -The project supports **Plex, Emby, and Jellyfin** today, so the old "Plex" name is misleading. We renamed the image (and the upstream repository) to match the broader scope. - -## What you need to do - -Update your `docker-compose.yml` (or `docker run` script): - -```diff - services: - media-preview-generator: -- image: stevezzau/plex_generate_vid_previews:latest -+ image: stevezzau/media_preview_generator:latest - # ... everything else stays the same -``` - -Then `docker compose pull && docker compose up -d`. Existing volumes, settings, jobs, schedules, and configuration are unchanged — only the image name moves. - -## Timeline - -- **Now → 2026-10-29**: Both image names mirror the same builds. Watchtower / `:latest` users on the old name keep getting updates automatically. -- **After 2026-10-29**: Only `stevezzau/media_preview_generator` receives updates. The old name stops being published. - -## Why the rename? - -The app started life as a Plex-only tool. Phases 0–L of the multi-server refactor (PR #225) added Emby and Jellyfin adapters with full per-server libraries, path mappings, exclude paths, webhooks, and dispatch fan-out. The "Plex Generate Previews" name no longer reflected what the project does. - -## Where to read more - -- Canonical Docker Hub: -- GitHub: (auto-redirects from the old URL) -- Documentation: - -If you have any issues migrating, open an issue on GitHub — the import path inside the container is unchanged, so any third-party scripts hitting `/api/...` keep working without modification. diff --git a/DOCKERHUB_README.md b/DOCKERHUB_README.md index 4bfcd428..eebe91c0 100644 --- a/DOCKERHUB_README.md +++ b/DOCKERHUB_README.md @@ -6,7 +6,7 @@ GPU-accelerated video preview thumbnail generation for **Plex, Emby, and Jellyfin**. **Web UI only** — no CLI. -> Previously named **Plex Generate Previews** at `stevezzau/plex_generate_vid_previews`. That image keeps mirroring updates until **2026-10-29**; after that, only this repo (`stevezzau/media_preview_generator`) is published. Update your `compose` to the new name when convenient — settings and volumes carry over unchanged. +> Previously named **Plex Generate Previews** at `stevezzau/plex_generate_vid_previews`. **That image has been retired and no longer receives updates** — this repo (`stevezzau/media_preview_generator`) is the only one published. If you're still on the old name, update your `compose` file's `image:` line to `stevezzau/media_preview_generator` and re-pull — settings and volumes carry over unchanged. **The Problem:** Built-in preview generation has gaps depending on which server you run: diff --git a/media_preview_generator/web/app.py b/media_preview_generator/web/app.py index 02d22349..252599ee 100644 --- a/media_preview_generator/web/app.py +++ b/media_preview_generator/web/app.py @@ -520,22 +520,17 @@ def _log_image_deprecation_warning() -> None: rather than open the dashboard. Silent when the env var is unset (local dev) or set to the canonical name. """ - from .notifications import ( - CANONICAL_IMAGE_NAME, - DEPRECATED_IMAGE_NAME, - DEPRECATED_IMAGE_SUNSET_DATE, - ) + from .notifications import CANONICAL_IMAGE_NAME, DEPRECATED_IMAGE_NAME image_name = (os.environ.get("DOCKER_IMAGE_NAME") or "").strip() if image_name != DEPRECATED_IMAGE_NAME: return logger.warning( - "Running deprecated Docker image {!r} — switch your compose to {!r} " - "before {} to keep getting updates. (Both image names mirror the same " - "builds until then; only the canonical name receives updates after.)", + "Running RETIRED Docker image {!r} — it no longer receives updates. " + "Switch your compose 'image:' line to {!r} and re-pull to keep getting " + "updates. Your volumes and settings are unchanged.", DEPRECATED_IMAGE_NAME, CANONICAL_IMAGE_NAME, - DEPRECATED_IMAGE_SUNSET_DATE, ) diff --git a/media_preview_generator/web/notifications.py b/media_preview_generator/web/notifications.py index 065c00d5..ae2f2b09 100644 --- a/media_preview_generator/web/notifications.py +++ b/media_preview_generator/web/notifications.py @@ -30,12 +30,12 @@ DEPRECATED_IMAGE_ID = "deprecated_docker_image_name" MEDIA_MOUNT_UNHEALTHY_ID = "media_mount_unhealthy" -# Image names recognised by the deprecation banner. The deprecated image -# keeps publishing alongside the canonical name until 2026-10-29 (six months -# after the rename); after that, only the canonical name receives updates. +# Image names recognised by the deprecation banner. The deprecated image was +# retired — a one-shot tombstone image now sits on its tags. This banner still +# fires for anyone running the last real build of the old image (no auto-update +# yet) to push them to switch to the canonical name. DEPRECATED_IMAGE_NAME = "stevezzau/plex_generate_vid_previews" CANONICAL_IMAGE_NAME = "stevezzau/media_preview_generator" -DEPRECATED_IMAGE_SUNSET_DATE = "2026-10-29" _SESSION_DISMISSED: set[str] = set() @@ -201,21 +201,21 @@ def _build_deprecated_image_notification() -> dict[str, Any] | None: body = ( f"

You're running the Docker image " - f"{DEPRECATED_IMAGE_NAME}, which has been renamed to " - f"{CANONICAL_IMAGE_NAME} to reflect that this app now " - f"supports Plex, Emby, and Jellyfin.

" - f"

Both image names mirror the same builds until " - f"{DEPRECATED_IMAGE_SUNSET_DATE}; after that, only " - f"{CANONICAL_IMAGE_NAME} receives updates. Update your " - f"compose file's image: line and pull " - f"the new image to keep getting updates.

" + f"{DEPRECATED_IMAGE_NAME}, which has been retired " + f"and renamed to {CANONICAL_IMAGE_NAME} (this app now supports " + f"Plex, Emby, and Jellyfin).

" + f"

The old image name no longer receives updates. " + f"Update your compose file's image: line to " + f"{CANONICAL_IMAGE_NAME} and run " + f"docker compose pull && docker compose up -d to keep " + f"getting updates.

" f"

Existing volumes, settings, and " f"configuration are unchanged — only the image name moves.

" ) return { "id": DEPRECATED_IMAGE_ID, "severity": "warning", - "title": "Update your Docker image", + "title": "Your Docker image has been retired", "body_html": body, "dismissable": True, "source": "image_deprecation", diff --git a/tests/test_notifications_api.py b/tests/test_notifications_api.py index 45a7e8dc..04a0f8f9 100644 --- a/tests/test_notifications_api.py +++ b/tests/test_notifications_api.py @@ -311,8 +311,9 @@ def test_fires_when_running_deprecated_image(self, monkeypatch): # The body names both the old and the new image so users can copy-paste. assert "stevezzau/plex_generate_vid_previews" in deprecated["body_html"] assert "stevezzau/media_preview_generator" in deprecated["body_html"] - # And carries the sunset date. - assert "2026-10-29" in deprecated["body_html"] + # And makes clear the old image is retired (no longer updated). + assert "retired" in deprecated["body_html"].lower() + assert deprecated["title"] == "Your Docker image has been retired" class TestSettingsManagerDismissedNotifications: diff --git a/tombstone/Dockerfile b/tombstone/Dockerfile new file mode 100644 index 00000000..d467df10 --- /dev/null +++ b/tombstone/Dockerfile @@ -0,0 +1,10 @@ +# Minimal "this image was retired" tombstone for the old Docker Hub repo +# (stevezzau/plex_generate_vid_previews). Built + pushed ONCE by +# .github/workflows/retire-deprecated-image.yml, then the deprecated build is +# removed from ci.yml. Anyone who pulls the old image after that gets a page +# telling them to switch to stevezzau/media_preview_generator. +FROM nginx:1.27-alpine +COPY index.html /usr/share/nginx/html/index.html +COPY default.conf /etc/nginx/conf.d/default.conf +EXPOSE 8080 +HEALTHCHECK CMD wget -qO- http://127.0.0.1:8080/ >/dev/null 2>&1 || exit 1 diff --git a/tombstone/default.conf b/tombstone/default.conf new file mode 100644 index 00000000..1b8a21be --- /dev/null +++ b/tombstone/default.conf @@ -0,0 +1,8 @@ +# Tombstone server: every request returns the "this image moved" page on the +# app's normal port (8080) so existing compose port-maps still hit it. +server { + listen 8080 default_server; + server_name _; + root /usr/share/nginx/html; + location / { try_files /index.html =404; } +} diff --git a/tombstone/index.html b/tombstone/index.html new file mode 100644 index 00000000..514b3433 --- /dev/null +++ b/tombstone/index.html @@ -0,0 +1,36 @@ + + + + + + This image moved — Media Preview Generator + + + +
+ Image retired +

This Docker image has moved 🚚

+

stevezzau/plex_generate_vid_previews is no longer published. + The project was renamed (it now supports Plex, Emby & Jellyfin).

+

Switch your image: line to the new name — your config, volumes and settings carry over unchanged:

+
+
image: stevezzau/plex_generate_vid_previews:latest
+
image: stevezzau/media_preview_generator:latest
+
+

Then docker compose pull && docker compose up -d. Same app, same data — just the new image name. + Docs: github.com/stevezau/media_preview_generator

+
+ +