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
167 changes: 167 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
name: build

on:
schedule:
- cron: '23 5 * * *'
workflow_dispatch:
inputs:
force:
description: Rebuild even if the latest upstream release was already built
type: boolean
default: false

permissions:
contents: write
packages: write
issues: write
actions: write

concurrency:
group: build
cancel-in-progress: false

env:
UPSTREAM_REPO: seerr-team/seerr
PR_NUMBER: '1855'
IMAGE: ghcr.io/${{ github.repository }}
GH_TOKEN: ${{ github.token }}

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Stop if PR 1855 was merged or closed upstream
run: |
pr="$(gh api "repos/$UPSTREAM_REPO/pulls/$PR_NUMBER")"
merged="$(jq -r .merged <<<"$pr")"
state="$(jq -r .state <<<"$pr")"
if [ "$merged" = "true" ]; then
title="Upstream PR #$PR_NUMBER was MERGED — time to switch back to the official image"
body="The hide-requested feature was merged into seerr upstream.

**What to do now:**
1. Wait for the next seerr release that includes it (check https://github.com/$UPSTREAM_REPO/releases — the release notes will mention hiding requested media, or PR #$PR_NUMBER).
2. In Unraid, change the container repository back to \`ghcr.io/seerr-team/seerr:latest\`.
3. In seerr Settings → General, enable **Hide Requested Media**.
4. Archive or delete this repo.

Until that release ships, your current image keeps working — it already contains this feature. This workflow has disabled itself and will not build again."
elif [ "$state" = "closed" ]; then
title="Upstream PR #$PR_NUMBER was CLOSED without merging"
body="The hide-requested PR was closed upstream without being merged. Your current image keeps working, but no new upstream releases will be picked up until you decide what to do (find a replacement PR and update PR_NUMBER in build.yml, or retire this repo). This workflow has disabled itself."
else
echo "PR #$PR_NUMBER is still open — continuing."
exit 0
fi
existing="$(gh issue list --repo "$GITHUB_REPOSITORY" --state open --search "\"$title\" in:title" --json number --jq length)"
if [ "$existing" = "0" ]; then
gh issue create --repo "$GITHUB_REPOSITORY" --title "$title" --body "$body"
fi
gh workflow disable build.yml --repo "$GITHUB_REPOSITORY" || true
echo "::error::$title"
exit 1

- name: Determine latest upstream release
id: rel
env:
FORCE: ${{ inputs.force }}
run: |
tag="$(gh api "repos/$UPSTREAM_REPO/releases/latest" --jq .tag_name)"
if ! grep -qE '^v[0-9][A-Za-z0-9._-]*$' <<<"$tag"; then
echo "::error::Upstream release tag has unexpected format: $tag"
exit 1
fi
echo "tag=$tag" >> "$GITHUB_OUTPUT"
last="$(cat .last-built 2>/dev/null || echo none)"
if [ "$tag" = "$last" ] && [ "$FORCE" != "true" ]; then
echo "Already built $tag — nothing to do."
echo "build=false" >> "$GITHUB_OUTPUT"
else
echo "New upstream release $tag (last built: $last)."
echo "build=true" >> "$GITHUB_OUTPUT"
fi

- name: Keepalive commit if repo is going stale
if: steps.rel.outputs.build == 'false'
run: |
age_days=$(( ( $(date +%s) - $(git log -1 --format=%ct) ) / 86400 ))
if [ "$age_days" -ge 45 ]; then
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git commit --allow-empty -m "chore: keepalive so the schedule is not auto-disabled"
git push
fi

- name: Fetch upstream source at release tag
if: steps.rel.outputs.build == 'true'
env:
TAG: ${{ steps.rel.outputs.tag }}
run: git clone --depth 1 --branch "$TAG" "https://github.com/$UPSTREAM_REPO.git" upstream

- name: Apply vendored patches
if: steps.rel.outputs.build == 'true'
env:
TAG: ${{ steps.rel.outputs.tag }}
run: |
cd upstream
for patch in ../patches/*.diff; do
if ! git apply --check "$patch"; then
title="Vendored patch $(basename "$patch") no longer applies to $TAG"
existing="$(gh issue list --repo "$GITHUB_REPOSITORY" --state open --search "\"$title\" in:title" --json number --jq length)"
if [ "$existing" = "0" ]; then
gh issue create --repo "$GITHUB_REPOSITORY" --title "$title" \
--body "The vendored patch conflicts with the new upstream release. Your current image keeps working, but it is now behind upstream. Re-download the updated diff from its source PR, review it, replace the file in patches/, and commit — see patches/README.md."
fi
echo "::error::$title"
exit 1
fi
git apply "$patch"
echo "Applied $(basename "$patch")"
done

- name: Set up Docker Buildx
if: steps.rel.outputs.build == 'true'
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
if: steps.rel.outputs.build == 'true'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}

- name: Build and push image
if: steps.rel.outputs.build == 'true'
uses: docker/build-push-action@v6
with:
context: upstream
push: true
platforms: linux/amd64
build-args: COMMIT_TAG=${{ steps.rel.outputs.tag }}-pr1855
tags: |
${{ env.IMAGE }}:latest
${{ env.IMAGE }}:${{ steps.rel.outputs.tag }}-pr1855
labels: |
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.description=seerr ${{ steps.rel.outputs.tag }} + PR 1855 (hide requested media)
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Record built tag
if: steps.rel.outputs.build == 'true'
env:
TAG: ${{ steps.rel.outputs.tag }}
run: |
echo "$TAG" > .last-built
if git diff --quiet -- .last-built; then
echo "Forced rebuild of $TAG — .last-built unchanged, nothing to commit."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add .last-built
git commit -m "chore: built $TAG"
git push
1 change: 1 addition & 0 deletions .last-built
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
none
102 changes: 100 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,101 @@
# seerr-hide-requested
# seerr + Hide Requested Media

Setup PR incoming — see the open pull request for the full build pipeline and docs.
**[seerr](https://github.com/seerr-team/seerr), with the ability to hide already-requested titles from Discover — available today, before it lands upstream.**

```text
ghcr.io/danieldjupvik/seerr:latest
```

Seerr has long had a *Hide Available Media* setting, but nothing to hide titles you've already **requested** — so Discover pages fill up with things you've already asked for. That feature has been requested for years across [Overseerr #3681](https://github.com/sct/overseerr/issues/3681), Jellyseerr, and [seerr #1048](https://github.com/seerr-team/seerr/issues/1048), and an implementation exists in [PR #1855](https://github.com/seerr-team/seerr/pull/1855) — it just hasn't been merged yet.

This repo publishes the **latest official seerr release with that PR applied**, plus two small reviewed fixes to it (documented in [patches/](patches/README.md)). Nothing else is changed. You get a new **Hide Requested Media** checkbox in *Settings → General*, right next to *Hide Available Media*:

- Titles with an **active** request (pending or approved-and-processing) disappear from Discover pages and sliders; declined requests don't hide anything
- They stay visible in **search**, so you can still find them and see their request status
- Global admin setting, off by default — same behavior the upstream PR will ship, with its review findings already fixed

## Quick start

The image is a **drop-in replacement** for `ghcr.io/seerr-team/seerr:latest` — same app, same ports, same config format. Switching in either direction is just changing the image name; your existing config and database are untouched.

### Docker Compose

```yaml
services:
seerr:
image: ghcr.io/danieldjupvik/seerr:latest
container_name: seerr
environment:
- TZ=Europe/Oslo
ports:
- 5055:5055
volumes:
- ./config:/app/config
restart: unless-stopped
```

### Docker CLI

```bash
docker run -d \
--name seerr \
-e TZ=Europe/Oslo \
-p 5055:5055 \
-v /path/to/config:/app/config \
--restart unless-stopped \
ghcr.io/danieldjupvik/seerr:latest
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

### Unraid

Edit your existing seerr container and change the **Repository** field to `ghcr.io/danieldjupvik/seerr:latest`. Everything else stays the same.

### Enable the feature

After starting: **Settings → General → Hide Requested Media** → check it, save.

## Tags

| Tag | Meaning |
| --- | --- |
| `latest` | Newest upstream release + the patch (mirrors upstream's `latest`) |
| `vX.Y.Z-pr1855` | Pinned build of a specific upstream release, for rollback |

## How it stays up to date

A [scheduled GitHub Actions workflow](.github/workflows/build.yml) runs daily:

1. Checks that [PR #1855](https://github.com/seerr-team/seerr/pull/1855) is still open upstream.
2. Checks for a new upstream release. If there's nothing new, it exits — no rebuild, no image churn.
3. On a new release: clones upstream at the release tag, applies the **vendored, reviewed patches** (the PR diff pinned to a specific PR commit, plus small reviewed fixes — see [patches/README.md](patches/README.md)), builds with upstream's own unmodified Dockerfile, and pushes. Typically live within 24 hours of an upstream release.

There is no fork being maintained here. Every build starts from pristine upstream source; the only delta is the vendored patches, applied at build time. The patch content is never fetched from the network during a build — it can only change through a reviewed commit in this repo.

## Can I trust this image?

Reasonable question for any third-party image. You don't have to take anyone's word:

- **Everything is built in public.** Every image comes from a GitHub Actions run in this repo — the [full build logs](../../actions) show exactly which upstream tag was cloned and which diff was applied. There are no manual pushes and no secrets involved beyond the repo's own `GITHUB_TOKEN`.
- **The only changes are pinned, vendored diffs** ([patches/](patches/)): the public upstream PR written by [@0xSysR3ll](https://github.com/0xSysR3ll), verbatim, plus a small documented fixup. You can read every line here, and diff the PR copy against the upstream PR yourself. Builds never pull patch content from the network, so an update to the upstream PR branch cannot silently change this image.
- **Don't want to trust it anyway? Run your own.** Fork this repo, enable Actions, and you'll build and publish the identical image into your own GHCR namespace in ~20 minutes.

## Limitations

- **`linux/amd64` only.** No ARM builds (keeps CI fast; open an issue if you'd genuinely use one).
- **Up to 24h behind upstream releases** (daily schedule).
- **Global setting, not per-user** — same as the upstream PR and the existing Hide Available Media setting.
- **If a new upstream release conflicts with the patch**, no image is published for it: the pipeline fails loudly and the previous version stays available until the vendored diff is updated (reviewed, then committed). You keep a working seerr; you're just temporarily behind.
- **Not affiliated with the seerr team.** If you hit a seerr bug while on this image, please reproduce it on the official image before reporting it upstream — don't send the maintainers chasing ghosts from a patched build.

## What happens when the PR merges upstream?

This repo retires itself. The daily workflow detects the merge, opens an issue here with switch-back instructions, and disables itself. At that point: wait for the next official release containing the feature, change your image back to `ghcr.io/seerr-team/seerr:latest`, and keep the checkbox — it'll be the real one. Your config carries over cleanly, including the setting itself.

If you want this to happen sooner, go 👍 [PR #1855](https://github.com/seerr-team/seerr/pull/1855) and [issue #1048](https://github.com/seerr-team/seerr/issues/1048).

## Credits

- The [seerr team](https://github.com/seerr-team) — the actual application (formerly Jellyseerr/Overseerr)
- [@0xSysR3ll](https://github.com/0xSysR3ll) — author of the hide-requested feature in PR #1855

This repo is just plumbing that connects the two a little earlier than the release cycle would.
21 changes: 21 additions & 0 deletions patches/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Vendored patches

Applied by the build workflow on top of the upstream release source, in lexicographic filename order.

| File | Source | Pinned at |
| --- | --- | --- |
| `pr1855.diff` | [seerr-team/seerr#1855](https://github.com/seerr-team/seerr/pull/1855), verbatim | head `cfeeb8ce8416f9775e503cfa8a03845d8f9f56f4` (2026-06-18) |
| `pr1855.fixups.diff` | This repo (review findings on the PR code) | — |

## What the fixups change

Two issues found in review of the PR code, fixed server-side in `Media.getRelatedMedia`:

1. **Only active requests hide a title.** The PR hides any title with request rows, but declined request rows persist forever — a declined title would never reappear in Discover. The join now excludes `DECLINED` and `COMPLETED` requests, matching how the rest of seerr defines active requests.
2. **Minimal payload instead of full request rows.** The PR's `leftJoinAndSelect` serialized entire `MediaRequest` rows into every discover/search/related-media response. The join now selects only `id` and `status` — all the frontend needs for its length check.

Both fixes live in one server-side join, so the PR's frontend code works unmodified.

## Updating a patch

To pick up a rebased/updated upstream PR: re-download the diff, **review it**, replace `pr1855.diff`, re-verify the fixups still apply on top, and commit — the content shipped into the image only ever changes through a reviewed commit in this repo.
Loading