From 88afcc113d1e960db85b084b29a506441eb16154 Mon Sep 17 00:00:00 2001 From: DataBitz <5953303+DataBitz@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:08:48 +1300 Subject: [PATCH 01/23] docs: media server support clarification [skip ci] (#2498) --- gen-docs/blog/2026-02-10/seerr-release.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gen-docs/blog/2026-02-10/seerr-release.md b/gen-docs/blog/2026-02-10/seerr-release.md index fda587b33..a3d02a794 100644 --- a/gen-docs/blog/2026-02-10/seerr-release.md +++ b/gen-docs/blog/2026-02-10/seerr-release.md @@ -19,7 +19,7 @@ Please check how to migrate to Seerr in our [migration guide](https://docs.seerr Seerr brings several features that were previously available in Jellyseerr but missing from Overseerr. These additions improve flexibility, performance, and overall control for admins and power users: -* **Alternative media solution:** Added support for Jellyfin and Emby in addition to the existing Plex integration. +* **Alternative media solution:** Added support for Jellyfin and Emby as alternatives to Plex. Only one integration can be used at a time. * **PostgreSQL support**: In addition to SQLite, you can now opt in to using a PostgreSQL database. * **Blocklist for movies, series, and tags**: Allows permitted users to hide movies, series, or tags from regular users. * **Override rules**: Adjust default request settings based on conditions such as user, tag, or other criteria. From 06e5eb0704d995fdfc14738e8a085c27a20e2e11 Mon Sep 17 00:00:00 2001 From: Ludovic Ortega Date: Wed, 18 Feb 2026 13:22:16 +0100 Subject: [PATCH 02/23] ci: add create-tag workflow to streamline release process (#2493) Signed-off-by: Ludovic Ortega --- .github/workflows/ci.yml | 4 +- .github/workflows/create-tag.yml | 87 ++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/create-tag.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c7f797465..674b466b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -129,7 +129,7 @@ jobs: build: name: Build (per-arch, native runners) - if: github.ref == 'refs/heads/develop' && !contains(github.event.head_commit.message, '[skip ci]') + if: github.ref == 'refs/heads/develop' strategy: matrix: include: @@ -237,7 +237,7 @@ jobs: discord: name: Send Discord Notification needs: publish - if: always() && github.event_name != 'pull_request' && !contains(github.event.head_commit.message, '[skip ci]') + if: always() && github.event_name != 'pull_request' runs-on: ubuntu-24.04 steps: - name: Determine Workflow Status diff --git a/.github/workflows/create-tag.yml b/.github/workflows/create-tag.yml new file mode 100644 index 000000000..e532d6be1 --- /dev/null +++ b/.github/workflows/create-tag.yml @@ -0,0 +1,87 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: Create tag + +on: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + determine-tag-version: + name: Determine tag version + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-24.04 + permissions: + contents: read + outputs: + tag_version: ${{ steps.git-cliff.outputs.tag_version }} + steps: + - name: Checkout + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Install git-cliff + uses: taiki-e/install-action@cede0bb282aae847dfa8aacca3a41c86d973d4d7 # v2.68.1 + with: + tool: git-cliff + + - name: Get tag version + id: git-cliff + run: | + tag_version=$(git-cliff -c .github/cliff.toml --bumped-version --unreleased) + echo "Next tag version is ${tag_version}" + echo "tag_version=${tag_version}" >> "$GITHUB_OUTPUT" + + create-tag: + name: Create tag + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-24.04 + permissions: + contents: write + needs: determine-tag-version + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG_VERSION: ${{ needs.determine-tag-version.outputs.tag_version }} + steps: + - name: Checkout + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + ssh-key: '${{ secrets.COMMIT_KEY }}' + + - name: Pnpm Setup + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 + + - name: Set up Node.js + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 + with: + node-version-file: 'package.json' + # For workflows with elevated privileges we recommend disabling automatic caching. + # https://github.com/actions/setup-node + package-manager-cache: false + + - name: Configure git + run: | + git config --global user.name "${{ github.actor }}" + git config --global user.email "${{ github.actor }}@users.noreply.github.com" + + - name: Bump package.json + run: npm version ${TAG_VERSION} --no-commit-hooks --no-git-tag-version + + - name: Commit updated files + run: | + git add package.json + git commit -m 'chore(release): prepare ${TAG_VERSION}' + git push + + - name: Create git tag + run: | + git tag ${TAG_VERSION} + git push origin ${TAG_VERSION} From dc1734d41fcd7a66f835e5f14538254fa8f8b2c2 Mon Sep 17 00:00:00 2001 From: Ludovic Ortega Date: Wed, 18 Feb 2026 13:30:25 +0100 Subject: [PATCH 03/23] docs(truenas): update install/migration guide (#2491) Signed-off-by: Ludovic Ortega --- .../getting-started/third-parties/truenas.mdx | 11 ++---- docs/migration-guide.mdx | 39 ++++++++++++++++++- gen-docs/docusaurus.config.ts | 7 +++- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/docs/getting-started/third-parties/truenas.mdx b/docs/getting-started/third-parties/truenas.mdx index b0e261ccc..aa03e7266 100644 --- a/docs/getting-started/third-parties/truenas.mdx +++ b/docs/getting-started/third-parties/truenas.mdx @@ -4,12 +4,6 @@ description: Install Seerr using TrueNAS sidebar_position: 4 --- # TrueNAS -:::danger -This method has not yet been updated for Seerr and is currently a work in progress. -You can follow the ongoing work on this issue https://github.com/truenas/apps/issues/3374. -::: - - + +## Installation + +Go to the 'Apps' menu, click the 'Discover Apps' button in the top right, search for 'Seerr' in the search bar, and install the app. diff --git a/docs/migration-guide.mdx b/docs/migration-guide.mdx index 2bc039eb7..ff0ec0c6e 100644 --- a/docs/migration-guide.mdx +++ b/docs/migration-guide.mdx @@ -210,7 +210,42 @@ See https://aur.archlinux.org/packages/seerr ### TrueNAS -Waiting for https://github.com/truenas/apps/issues/3374 +Refer to [Seerr TrueNAS Documentation](/getting-started/third-parties/truenas), all of our examples have been updated to reflect the below change. + + + + **This guide describes how to migrate from Host Path storage (not ixVolume).** + 1. Stop Jellyseerr/Overseerr + 2. Install Seerr and use the same Host Path storage that was used by Jellyseerr/Overseerr + 3. Start Seerr app + 4. Delete Jellyseerr/Overseerr app + + + **This guide describes how to migrate from ixVolume storage (not Host Path).** + 1. Stop Jellyseerr/Overseerr + 2. Create a dataset for Seerr + If your apps normally store data under something like: + ``` + /mnt/storage/ + ``` + then create a dataset named: + ``` + storage/seerr + ``` + resulting in: + ``` + /mnt/storage/seerr + ``` + 3. Copy ixVolume Data + Open System Settings → Shell, or SSH into your TrueNAS server as root and run : + ```bash + rsync -av /mnt/.ix-apps/app_mounts/jellyseerr/ /mnt/storage/seerr/ + ``` + 4. Install Seerr and use the same Host Path storage that was created before (`/mnt/storage/seerr/config` in our example) + 5. Start Seerr app + 6. Delete Jellyseerr/Overseerr app + + ### Unraid @@ -277,4 +312,4 @@ For Jellyseerr users, use `/mnt/user/appdata/jellyseerr`. :::tip If you are using a reverse proxy (such as SWAG or Nginx Proxy Manager), update your proxy configuration to point to the new container name `seerr`. The default port remains `5055`. -::: \ No newline at end of file +::: diff --git a/gen-docs/docusaurus.config.ts b/gen-docs/docusaurus.config.ts index b50624119..94b7d8828 100644 --- a/gen-docs/docusaurus.config.ts +++ b/gen-docs/docusaurus.config.ts @@ -16,7 +16,12 @@ const config: Config = { deploymentBranch: 'gh-pages', onBrokenLinks: 'throw', - onBrokenMarkdownLinks: 'warn', + + markdown: { + hooks: { + onBrokenMarkdownLinks: 'warn', + }, + }, i18n: { defaultLocale: 'en', From e0e4b6f512d2efe717d1065ab5a85aa5c717dea4 Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+fallenbagel@users.noreply.github.com> Date: Wed, 18 Feb 2026 19:23:20 +0500 Subject: [PATCH 04/23] fix(watchlist-sync): correct permission typo for TV auto requests (#2488) --- server/lib/watchlistsync.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/lib/watchlistsync.ts b/server/lib/watchlistsync.ts index be73a0dfd..7ec976b97 100644 --- a/server/lib/watchlistsync.ts +++ b/server/lib/watchlistsync.ts @@ -45,7 +45,7 @@ class WatchlistSync { [ Permission.AUTO_REQUEST, Permission.AUTO_REQUEST_MOVIE, - Permission.AUTO_APPROVE_TV, + Permission.AUTO_REQUEST_TV, ], { type: 'or' } ) From 0e636a3f9905df66124a29a60fc7a01a78821016 Mon Sep 17 00:00:00 2001 From: Michael Reid <943378+mreid-tt@users.noreply.github.com> Date: Wed, 18 Feb 2026 11:49:51 -0400 Subject: [PATCH 05/23] docs(synology): add installation guide via SynoCommunity (#2503) Co-authored-by: Ludovic Ortega --- .../third-parties/synology.mdx | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 docs/getting-started/third-parties/synology.mdx diff --git a/docs/getting-started/third-parties/synology.mdx b/docs/getting-started/third-parties/synology.mdx new file mode 100644 index 000000000..6a5e6d2af --- /dev/null +++ b/docs/getting-started/third-parties/synology.mdx @@ -0,0 +1,111 @@ +--- +title: Synology (Advanced) +description: Install Seerr on Synology NAS using SynoCommunity +sidebar_position: 5 +--- + +# Synology + +:::warning +Third-party installation methods are maintained by the community. The Seerr team is not responsible for these packages. +::: + +:::warning +This method is not recommended for most users. It is intended for advanced users who are using Synology NAS. +::: + +## Prerequisites + +- Synology NAS running **DSM 7.2** or later +- 64-bit architecture (x86_64 or ARMv8) +- [SynoCommunity package source](https://synocommunity.com/) added to Package Center + +## Adding the SynoCommunity Package Source + +If you haven't already added SynoCommunity to your Package Center: + +1. Open **Package Center** in DSM +2. Click **Settings** in the top-right corner +3. Go to the **Package Sources** tab +4. Click **Add** +5. Enter the following: + - **Name**: `SynoCommunity` + - **Location**: `https://packages.synocommunity.com` +6. Click **OK** + +## Installation + +1. In **Package Center**, search for **Seerr** +2. Click **Install** +3. Follow the installation wizard prompts +4. Package Center will automatically install any required dependencies (Node.js v22) + +### Access Seerr + +Once installed, access Seerr at: + +``` +http://:5055 +``` + +You can also click the **Open** button in Package Center or find Seerr in the DSM main menu. + +## Configuration + +Seerr's configuration files are stored at: + +``` +/var/packages/seerr/var/config +``` + +:::info +The Seerr package runs as a dedicated service user managed by DSM. No manual permission configuration is required. +::: + +## Managing the Service + +You can start, stop, and restart Seerr from **Package Center** → Find Seerr → Use the action buttons. + +## Updating + +When a new version is available: + +1. Open **Package Center** +2. Go to **Installed** packages +3. Find **Seerr** and click **Update** if available + +:::tip +Enable automatic updates in Package Center settings to keep Seerr up to date. +::: + +## Troubleshooting + +### Viewing Logs + +Seerr logs are located at `/var/packages/seerr/var/config/logs` and can be accessed using: + +- **File Browser** package (recommended for most users) +- SSH (advanced users) + +### Port Conflicts + +Seerr uses port 5055. If this port is already in use: + +- **Docker containers**: Remap the conflicting container to a different port +- **Other packages**: The conflicting package will need to be uninstalled as Seerr's port cannot be changed + +SynoCommunity ensures there are no port conflicts with other SynoCommunity packages or official Synology packages. + +### Package Won't Start + +Ensure Node.js v22 is installed and running by checking its status in **Package Center**. + +## Uninstallation + +1. Open **Package Center** +2. Find **Seerr** in your installed packages +3. Click **Uninstall** + +:::caution +Uninstalling will remove the application but preserve your configuration data by default. Select "Remove data" during uninstallation if you want a complete removal. +::: From 9da8bb6deaca09a93d89a2ec1df3e42f3c1f6bcd Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+fallenbagel@users.noreply.github.com> Date: Thu, 19 Feb 2026 01:23:50 +0500 Subject: [PATCH 06/23] fix: preserve blocklist on media deletion & optimise watchlist-sync (#2478) --- server/entity/Media.ts | 13 +++++++++++++ server/lib/watchlistsync.ts | 24 ++++++++++++++++++++++-- server/routes/media.ts | 7 ++++++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/server/entity/Media.ts b/server/entity/Media.ts index 25609c58b..8737ad0e0 100644 --- a/server/entity/Media.ts +++ b/server/entity/Media.ts @@ -206,6 +206,19 @@ class Media { Object.assign(this, init); } + public resetServiceData(): void { + this.serviceId = null; + this.serviceId4k = null; + this.externalServiceId = null; + this.externalServiceId4k = null; + this.externalServiceSlug = null; + this.externalServiceSlug4k = null; + this.ratingKey = null; + this.ratingKey4k = null; + this.jellyfinMediaId = null; + this.jellyfinMediaId4k = null; + } + @AfterLoad() public setPlexUrls(): void { const { machineId, webAppUrl } = getSettings().plex; diff --git a/server/lib/watchlistsync.ts b/server/lib/watchlistsync.ts index 7ec976b97..8afea0df5 100644 --- a/server/lib/watchlistsync.ts +++ b/server/lib/watchlistsync.ts @@ -70,13 +70,33 @@ class WatchlistSync { response.items.map((i) => i.tmdbId) ); + const watchlistTmdbIds = response.items.map((i) => i.tmdbId); + + const requestRepository = getRepository(MediaRequest); + const existingAutoRequests = await requestRepository + .createQueryBuilder('request') + .leftJoinAndSelect('request.media', 'media') + .where('request.requestedBy = :userId', { userId: user.id }) + .andWhere('request.isAutoRequest = true') + .andWhere('media.tmdbId IN (:...tmdbIds)', { tmdbIds: watchlistTmdbIds }) + .getMany(); + + const autoRequestedTmdbIds = new Set( + existingAutoRequests + .filter((r) => r.media != null) + .map((r) => `${r.media.mediaType}:${r.media.tmdbId}`) + ); + const unavailableItems = response.items.filter( - // If we can find watchlist items in our database that are also available, we should exclude them (i) => + !autoRequestedTmdbIds.has( + `${i.type === 'show' ? MediaType.TV : MediaType.MOVIE}:${i.tmdbId}` + ) && !mediaItems.find( (m) => m.tmdbId === i.tmdbId && - ((m.status !== MediaStatus.UNKNOWN && m.mediaType === 'movie') || + (m.status === MediaStatus.BLOCKLISTED || + (m.status !== MediaStatus.UNKNOWN && m.mediaType === 'movie') || (m.mediaType === 'tv' && m.status === MediaStatus.AVAILABLE)) ) ); diff --git a/server/routes/media.ts b/server/routes/media.ts index 8f52efae8..b83f9dd0c 100644 --- a/server/routes/media.ts +++ b/server/routes/media.ts @@ -174,7 +174,12 @@ mediaRoutes.delete( where: { id: Number(req.params.id) }, }); - await mediaRepository.remove(media); + if (media.status === MediaStatus.BLOCKLISTED) { + media.resetServiceData(); + await mediaRepository.save(media); + } else { + await mediaRepository.remove(media); + } return res.status(204).send(); } catch (e) { From fa905be0026c91d55e3fe632d9110ed679455761 Mon Sep 17 00:00:00 2001 From: nova-api Date: Thu, 19 Feb 2026 08:56:25 +0000 Subject: [PATCH 07/23] docs: remove double quotes (") from DB_HOST environment variable (#2514) --- docs/extending-seerr/database-config.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/extending-seerr/database-config.mdx b/docs/extending-seerr/database-config.mdx index e2003b19b..59684161c 100644 --- a/docs/extending-seerr/database-config.mdx +++ b/docs/extending-seerr/database-config.mdx @@ -30,7 +30,7 @@ If your PostgreSQL server is configured to accept TCP connections, you can speci ```dotenv DB_TYPE=postgres # Which DB engine to use, either sqlite or postgres. The default is sqlite. -DB_HOST="localhost" # (optional) The host (URL) of the database. The default is "localhost". +DB_HOST=localhost # (optional) The host (URL) of the database. The default is "localhost". DB_PORT="5432" # (optional) The port to connect to. The default is "5432". DB_USER= # (required) Username used to connect to the database. DB_PASS= # (required) Password of the user used to connect to the database. From fba20c1b39b428a68bd09893ebba9cbd8d4e2d4e Mon Sep 17 00:00:00 2001 From: Ludovic Ortega Date: Thu, 19 Feb 2026 22:47:26 +0100 Subject: [PATCH 08/23] ci: remove discord notification from release (#2501) Signed-off-by: Ludovic Ortega --- .github/workflows/release.yml | 39 ----------------------------------- 1 file changed, 39 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b79496b7d..9bf72c7ec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -304,42 +304,3 @@ jobs: run: gh release edit "${{ env.VERSION }}" --draft=false --repo "${{ github.repository }}" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - discord: - name: Send Discord Notification - needs: publish-release - if: always() - runs-on: ubuntu-24.04 - steps: - - name: Determine status - id: status - run: | - case "${{ needs.publish-release.result }}" in - success) echo "status=Success" >> $GITHUB_OUTPUT; echo "colour=3066993" >> $GITHUB_OUTPUT ;; - failure) echo "status=Failure" >> $GITHUB_OUTPUT; echo "colour=15158332" >> $GITHUB_OUTPUT ;; - cancelled) echo "status=Cancelled" >> $GITHUB_OUTPUT; echo "colour=10181046" >> $GITHUB_OUTPUT ;; - *) echo "status=Skipped" >> $GITHUB_OUTPUT; echo "colour=9807270" >> $GITHUB_OUTPUT ;; - esac - - - name: Send notification - run: | - WEBHOOK="${{ secrets.DISCORD_WEBHOOK }}" - - PAYLOAD=$(cat < Date: Fri, 20 Feb 2026 00:43:00 +0100 Subject: [PATCH 09/23] chore: update contributing guide regarding Automated AI Agent (#2518) Signed-off-by: Ludovic Ortega --- CONTRIBUTING.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 022b97bd5..733eefb76 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,6 +6,12 @@ All help is welcome and greatly appreciated! If you would like to contribute to > [!IMPORTANT] > +> Automated AI-generated contributions without human review are not allowed and will be rejected. +> This is an open-source project maintained by volunteers. +> We do not have the resources to review pull requests that could have been avoided with proper human oversight. +> While we have no issue with contributors using AI tools as an aid, it is your responsibility as a contributor to ensure that all submissions are carefully reviewed and meet our quality standards. +> Submissions that appear to be unreviewed AI output will be considered low-effort and may result in a ban. +> > If you are using **any kind of AI assistance** to contribute to Seerr, > it must be disclosed in the pull request. From cecdd631086232daaf73b3ba8a8c220315340d8c Mon Sep 17 00:00:00 2001 From: Ludovic Ortega Date: Fri, 20 Feb 2026 11:27:50 +0100 Subject: [PATCH 10/23] chore: disable nextjs telemetry (#2517) Signed-off-by: Ludovic Ortega --- .prettierignore | 3 ++- .prettierrc.js | 6 ++++++ package.json | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.prettierignore b/.prettierignore index efeeb712d..d6464bf14 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,6 +2,7 @@ .next/ dist/ config/ +cache/config.json pnpm-lock.yaml cypress/config/settings.cypress.json .github @@ -17,4 +18,4 @@ public/* **/charts # Prettier breaks GitHub alert syntax in markdown -*.md \ No newline at end of file +*.md diff --git a/.prettierrc.js b/.prettierrc.js index 625c1665b..035c9a84f 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -33,5 +33,11 @@ module.exports = { rangeEnd: 0, }, }, + { + files: 'cache/config.json', + options: { + rangeEnd: 0, // default: Infinity + }, + }, ], }; diff --git a/package.json b/package.json index d38b779d8..d68daad5d 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "packageManager": "pnpm@10.24.0", "scripts": { "preinstall": "npx only-allow pnpm", + "postinstall": "next telemetry disable", "dev": "nodemon -e ts --watch server --watch seerr-api.yml -e .json,.ts,.yml -x ts-node -r tsconfig-paths/register --files --project server/tsconfig.json server/index.ts", "build:server": "tsc --project server/tsconfig.json && copyfiles -u 2 server/templates/**/*.{html,pug} dist/templates && tsc-alias -p server/tsconfig.json", "build:next": "next build", From 7e9dff3a1b7055d598edaa0cd3d2425a03edd306 Mon Sep 17 00:00:00 2001 From: Ludovic Ortega Date: Fri, 20 Feb 2026 11:44:47 +0100 Subject: [PATCH 11/23] docs: add a warning in migration-guide for third party installation (#2527) Signed-off-by: Ludovic Ortega --- docs/migration-guide.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/migration-guide.mdx b/docs/migration-guide.mdx index ff0ec0c6e..3a2893675 100644 --- a/docs/migration-guide.mdx +++ b/docs/migration-guide.mdx @@ -200,6 +200,11 @@ Summary of changes : ## Third-party installation methods + +:::warning +Third-party installation methods are maintained by the community. The Seerr team is not responsible for these packages. +::: + ### Nix Waiting for https://github.com/NixOS/nixpkgs/pull/450096 and https://github.com/NixOS/nixpkgs/pull/450093 From a0d0eb185800a1444f5402b15bdd1047b05b04aa Mon Sep 17 00:00:00 2001 From: mjonkus Date: Fri, 20 Feb 2026 21:12:27 +0200 Subject: [PATCH 12/23] docs: update Unraid install and migration guides with dual permission methods (#2532) Co-authored-by: Mindaugas Jonkus Co-authored-by: Claude Opus 4.6 Co-authored-by: Ludovic Ortega --- docs/getting-started/third-parties/unraid.mdx | 195 +++++++++++++----- docs/migration-guide.mdx | 66 ++---- 2 files changed, 154 insertions(+), 107 deletions(-) diff --git a/docs/getting-started/third-parties/unraid.mdx b/docs/getting-started/third-parties/unraid.mdx index bb53b9b78..e07dcf367 100644 --- a/docs/getting-started/third-parties/unraid.mdx +++ b/docs/getting-started/third-parties/unraid.mdx @@ -4,6 +4,9 @@ description: Install Seerr using Unraid sidebar_position: 3 --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + # Unraid :::warning @@ -14,76 +17,160 @@ Third-party installation methods are maintained by the community. The Seerr team This method is not recommended for most users. It is intended for advanced users who are using Unraid. ::: +## Template installation -If an official Unraid Community Applications template for Seerr isn't available in your catalog, you can install Seerr manually using Unraid's Docker UI. +Several templates from Unraid Community are available right now +- [Unraid Community Apps](https://unraid.net/community/apps#community-apps-iframe) -## Fresh Installation +In case you still want to do it manually, please follow the instructions below. -### 1. Create the config directory +## Manual Installation -:::note -Seerr is now rootless. Unraid typically runs Docker containers as `nobody:users` (UID 99, GID 100), but Seerr now runs internally as UID 1000, GID 1000. This creates a permission mismatch. -::: +Before proceeding, choose which installation method best suits your server: -:::info -**If migrating**: Copy your existing Jellyseerr/Overseerr config files (e.g., from `/mnt/user/appdata/overseerr/` or `/mnt/user/appdata/jellyseerr`) to `/mnt/user/appdata/seerr`, then apply the permissions below -::: +**Seerr Default** (`UID 1000:1000`) — Runs with the container's native `node` user. Matches the official image, but may break SMB share access, trigger "Fix Common Problems" warnings, and cause issues with backup plugins (such as CA Appdata Backup) and network integrations (such as Tailscale). -Open the Unraid terminal and run: +**Unraid Default** (`UID 99:100`) — Runs with Unraid's native `nobody:users` ownership. Follows Unraid best practices, but overrides Seerr's built-in rootless user and may cause compatibility issues with future Seerr updates. -```bash -mkdir -p /mnt/user/appdata/seerr -chown -R 1000:1000 /mnt/user/appdata/seerr -``` + + -### 2. Add the Docker container + ### 1. Create the config directory -Navigate to the **Docker** tab in Unraid and click **Add Container**. Fill in the following: + Open the Unraid terminal and run: -| Field | Value | -|---|---| -| **Name** | `seerr` | -| **Repository** | `ghcr.io/seerr-team/seerr:latest` | -| **Registry URL** (optional) | `https://ghcr.io` | -| **Icon URL** | `https://raw.githubusercontent.com/seerr-team/seerr/develop/public/android-chrome-512x512.png` | -| **WebUI** | `http://[IP]:[PORT:5055]` | -| **Extra Parameters** | `--init` | -| **Network Type** | `bridge` | -| **Privileged** | `Off` | + ```bash + mkdir -p /mnt/user/appdata/seerr + ``` -Then click **Add another Path, Port, Variable** to add: + ### 2. Set folder permissions -**Port:** -| Field | Value | -|---|---| -| Container Port | `5055` | -| Host Port | `5055` | -| Connection Type | `TCP` | + ```bash + chown -R 1000:1000 /mnt/user/appdata/seerr + ``` -**Path:** -| Field | Value | -|---|---| -| Container Path | `/app/config` | -| Host Path | `/mnt/user/appdata/seerr` | + ### 3. Add the Docker container -**Variable:** -| Field | Value | -|---|---| -| Key | `TZ` | -| Value | Your [TZ database name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) (e.g., `America/New_York`) | + :::warning + The **Extra Parameters** field is critical. You **must** add `--init --restart=unless-stopped` (see table below). Without `--init`, the container will not handle signals properly or shut down cleanly. Without `--restart=unless-stopped`, the container will not automatically restart after a reboot. -**Variable (optional):** -| Field | Value | -|---|---| -| Key | `LOG_LEVEL` | -| Value | `info` | + To see the **Extra Parameters** field, click **Basic View** in the top-right corner of the template page to switch to the advanced editor. + ::: -Click **Apply** to create and start the container. + Navigate to the **Docker** tab in Unraid and click **Add Container**. Fill in the following: -### 3. Access Seerr + | Field | Value | + |---|---| + | **Name** | `seerr` | + | **Repository** | `ghcr.io/seerr-team/seerr:latest` | + | **Icon URL** | `https://raw.githubusercontent.com/seerr-team/seerr/develop/public/android-chrome-512x512.png` | + | **WebUI** | `http://[IP]:[PORT:5055]` | + | **Extra Parameters** | `--init --restart=unless-stopped` | + | **Network Type** | `bridge` | + | **Privileged** | `Off` | -Open the WebUI at `http://:5055` and follow the setup wizard. + Then click **Add another Path, Port, Variable** to add: -:::info -The `--init` flag in **Extra Parameters** is required. Seerr does not include its own init process, so `--init` ensures proper signal handling and clean container shutdowns. -::: + **Port:** + | Field | Value | + |---|---| + | Container Port | `5055` | + | Host Port | `5055` | + | Connection Type | `TCP` | + + **Path:** + | Field | Value | + |---|---| + | Container Path | `/app/config` | + | Host Path | `/mnt/user/appdata/seerr` | + + **Variable:** + | Field | Value | + |---|---| + | Key | `TZ` | + | Value | Your [TZ database name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) (e.g., `America/New_York`) | + + **Variable (optional):** + | Field | Value | + |---|---| + | Key | `LOG_LEVEL` | + | Value | `info` | + + Click **Apply** to create and start the container. + + ### 4. Access Seerr + + Open the WebUI at `http://:5055` and follow the setup wizard. + + + + + ### 1. Create the config directory + + Open the Unraid terminal and run: + + ```bash + mkdir -p /mnt/user/appdata/seerr + ``` + + ### 2. Set folder permissions + + ```bash + chown -R 99:100 /mnt/user/appdata/seerr + ``` + + ### 3. Add the Docker container + + :::warning + The **Extra Parameters** field is critical. You **must** add `--init --restart=unless-stopped --user 99:100` (see table below). Without `--init`, the container will not handle signals properly or shut down cleanly. Without `--restart=unless-stopped`, the container will not automatically restart after a reboot. The `--user 99:100` parameter runs the container process with Unraid's default UID/GID and avoids permission errors accessing your shares without changing host folder ownership. + + To see the **Extra Parameters** field, click **Basic View** in the top-right corner of the template page to switch to the advanced editor. + ::: + + Navigate to the **Docker** tab in Unraid and click **Add Container**. Fill in the following: + + | Field | Value | + |---|---| + | **Name** | `seerr` | + | **Repository** | `ghcr.io/seerr-team/seerr:latest` | + | **Icon URL** | `https://raw.githubusercontent.com/seerr-team/seerr/develop/public/android-chrome-512x512.png` | + | **WebUI** | `http://[IP]:[PORT:5055]` | + | **Extra Parameters** | `--init --restart=unless-stopped --user 99:100` | + | **Network Type** | `bridge` | + | **Privileged** | `Off` | + + Then click **Add another Path, Port, Variable** to add: + + **Port:** + | Field | Value | + |---|---| + | Container Port | `5055` | + | Host Port | `5055` | + | Connection Type | `TCP` | + + **Path:** + | Field | Value | + |---|---| + | Container Path | `/app/config` | + | Host Path | `/mnt/user/appdata/seerr` | + + **Variable:** + | Field | Value | + |---|---| + | Key | `TZ` | + | Value | Your [TZ database name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) (e.g., `America/New_York`) | + + **Variable (optional):** + | Field | Value | + |---|---| + | Key | `LOG_LEVEL` | + | Value | `info` | + + Click **Apply** to create and start the container. + + ### 4. Access Seerr + + Open the WebUI at `http://:5055` and follow the setup wizard. + + + diff --git a/docs/migration-guide.mdx b/docs/migration-guide.mdx index 3a2893675..e99d95a2a 100644 --- a/docs/migration-guide.mdx +++ b/docs/migration-guide.mdx @@ -258,63 +258,23 @@ Refer to [Seerr Unraid Documentation](/getting-started/third-parties/unraid), al Seerr will automatically migrate your existing Overseerr or Jellyseerr data on first startup. No manual database migration is needed. -1. Stop and remove the old Overseerr (or Jellyseerr) container from the Unraid **Docker** tab. Click the container icon, then **Stop**, then **Remove**. **⚠️ Do not delete the appdata folder ⚠️** +**1. Stop the existing container** +In the Unraid **Docker** tab, stop your Overseerr (or Jellyseerr) container. +**⚠️ Do not remove the container or delete the appdata folder yet ⚠️** -2. Back up your existing appdata folder: +**2. Copy existing data to Seerr appdata** +Open the Unraid terminal and copy your existing appdata folder into the new Seerr appdata directory: ```bash -cp -a /mnt/user/appdata/overseerr /mnt/user/appdata/overseerr-backup +cp -a /mnt/user/appdata/overseerr /mnt/user/appdata/seerr ``` -3. Fix config folder permissions — Seerr runs as the `node` user (UID 1000) instead of root: -```bash -chown -R 1000:1000 /mnt/user/appdata/overseerr -``` -For Jellyseerr users, replace `overseerr` with `jellyseerr` in the path above. +*(For Jellyseerr users, replace `overseerr` with `jellyseerr` in the paths).* -4. Add a new container in the Unraid **Docker** tab. Click **Add Container** and fill in the following: +**3. Set permissions and install Seerr** +Follow the [Unraid Installation Guide](/getting-started/third-parties/unraid#2-set-folder-permissions), **starting from step 2** — this covers setting the correct folder permissions and adding the Docker container. The guide offers two permission methods (**Seerr Default** and **Unraid Default**), each with trade-offs — read the descriptions before choosing. -| Field | Value | -|---|---| -| **Name** | `seerr` | -| **Repository** | `ghcr.io/seerr-team/seerr:latest` | -| **Registry URL** (optional) | `https://ghcr.io` | -| **Icon URL** | `https://raw.githubusercontent.com/seerr-team/seerr/develop/public/android-chrome-512x512.png` | -| **WebUI** | `http://[IP]:[PORT:5055]` | -| **Extra Parameters** | `--init` | -| **Network Type** | `bridge` | -| **Privileged** | `Off` | +**4. Start the new Seerr app** +Start the newly created Seerr container. Check the container logs to confirm the automatic migration completed successfully. -Then click **Add another Path, Port, Variable** to add: - -**Port:** -| Field | Value | -|---|---| -| Container Port | `5055` | -| Host Port | `5055` | -| Connection Type | `TCP` | - -**Path** — point this to your existing config folder: -| Field | Value | -|---|---| -| Container Path | `/app/config` | -| Host Path | `/mnt/user/appdata/overseerr` | - -For Jellyseerr users, use `/mnt/user/appdata/jellyseerr`. - -**Variable:** -| Field | Value | -|---|---| -| Key | `TZ` | -| Value | Your [TZ database name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) (e.g., `America/New_York`) | - -**Variable (optional):** -| Field | Value | -|---|---| -| Key | `LOG_LEVEL` | -| Value | `info` | - -5. Click **Apply** to start the container. Check the container logs to confirm the automatic migration completed successfully. - -:::tip -If you are using a reverse proxy (such as SWAG or Nginx Proxy Manager), update your proxy configuration to point to the new container name `seerr`. The default port remains `5055`. -::: +**5. Remove the old app** +Once you have confirmed Seerr is working properly and your data has successfully migrated, you can safely **Remove** the old Overseerr (or Jellyseerr) container from Unraid. \ No newline at end of file From 7c60a5c3c5209116506de6f590a6b0f428610871 Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+fallenbagel@users.noreply.github.com> Date: Sat, 21 Feb 2026 02:48:52 +0500 Subject: [PATCH 13/23] fix(plex-scanner): add TVDb to TMDB fallback in plex scanner (#2537) --- server/lib/scanners/plex/index.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/lib/scanners/plex/index.ts b/server/lib/scanners/plex/index.ts index 24862e558..b464008e8 100644 --- a/server/lib/scanners/plex/index.ts +++ b/server/lib/scanners/plex/index.ts @@ -430,6 +430,13 @@ class PlexScanner mediaIds.tmdbId = tmdbMedia.id; } + if (mediaIds.tvdbId && !mediaIds.tmdbId) { + const show = await this.tmdb.getShowByTvdbId({ + tvdbId: mediaIds.tvdbId, + }); + mediaIds.tmdbId = show.id; + } + // Cache GUIDs guidCache.data.set(plexitem.ratingKey, mediaIds); From 68f56d2035d05827d47ffd9a4e374b19043146b5 Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+fallenbagel@users.noreply.github.com> Date: Sat, 21 Feb 2026 06:51:02 +0500 Subject: [PATCH 14/23] fix(jellyfin-scanner): include unmatched seasons in processable seasons (#2538) --- server/lib/scanners/jellyfin/index.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/lib/scanners/jellyfin/index.ts b/server/lib/scanners/jellyfin/index.ts index bb3456c7e..f44159af4 100644 --- a/server/lib/scanners/jellyfin/index.ts +++ b/server/lib/scanners/jellyfin/index.ts @@ -397,6 +397,13 @@ class JellyfinScanner episodes: totalStandard, episodes4k: total4k, }); + } else { + processableSeasons.push({ + seasonNumber: season.season_number, + totalEpisodes: season.episode_count, + episodes: 0, + episodes4k: 0, + }); } } From b843be0cad895c5f974d47dc44c85557fdbcce36 Mon Sep 17 00:00:00 2001 From: Pierre Spring Date: Sun, 22 Feb 2026 12:41:37 +0100 Subject: [PATCH 15/23] fix(link-account): fix error-message override (#2547) --- .../UserLinkedAccountsSettings/index.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsx b/src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsx index ae7e63425..329da894f 100644 --- a/src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsx +++ b/src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsx @@ -100,14 +100,16 @@ const UserLinkedAccountsSettings = () => { ); await revalidateUser(); } catch (e) { - if (e?.response?.status === 401) { - setError(intl.formatMessage(messages.plexErrorUnauthorized)); - } else if (e?.response?.status === 422) { - setError(intl.formatMessage(messages.plexErrorExists)); - } else { - setError(intl.formatMessage(messages.errorUnknown)); + switch (e?.response?.status) { + case 401: + setError(intl.formatMessage(messages.plexErrorUnauthorized)); + break; + case 422: + setError(intl.formatMessage(messages.plexErrorExists)); + break; + default: + setError(intl.formatMessage(messages.errorUnknown)); } - setError(intl.formatMessage(messages.errorUnknown)); } }; From 5013d1d54db5787469e030493fc628f5c2db57d9 Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+fallenbagel@users.noreply.github.com> Date: Sun, 22 Feb 2026 22:13:39 +0500 Subject: [PATCH 16/23] fix(sonarr): use configured metadata provider for season filtering (#2516) --- server/lib/scanners/sonarr/index.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/server/lib/scanners/sonarr/index.ts b/server/lib/scanners/sonarr/index.ts index 7a6e95c0a..3c39519d2 100644 --- a/server/lib/scanners/sonarr/index.ts +++ b/server/lib/scanners/sonarr/index.ts @@ -1,6 +1,12 @@ +import { getMetadataProvider } from '@server/api/metadata'; import type { SonarrSeries } from '@server/api/servarr/sonarr'; import SonarrAPI from '@server/api/servarr/sonarr'; -import type { TmdbTvDetails } from '@server/api/themoviedb/interfaces'; +import TheMovieDb from '@server/api/themoviedb'; +import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants'; +import type { + TmdbKeyword, + TmdbTvDetails, +} from '@server/api/themoviedb/interfaces'; import { getRepository } from '@server/datasource'; import Media from '@server/entity/Media'; import type { @@ -102,6 +108,15 @@ class SonarrScanner } const tmdbId = tvShow.id; + const metadataProvider = tvShow.keywords.results.some( + (keyword: TmdbKeyword) => keyword.id === ANIME_KEYWORD_ID + ) + ? await getMetadataProvider('anime') + : await getMetadataProvider('tv'); + + if (!(metadataProvider instanceof TheMovieDb)) { + tvShow = await metadataProvider.getTvShow({ tvId: tmdbId }); + } const settings = getSettings(); const filteredSeasons = sonarrSeries.seasons.filter( From 3bcb4da1e5048a7b2141716d9bf0387a75909ee6 Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+fallenbagel@users.noreply.github.com> Date: Mon, 23 Feb 2026 04:32:31 +0500 Subject: [PATCH 17/23] feat(servarr-api): make Servarr API request timeout configurable (#2556) --- server/api/servarr/base.ts | 6 +-- server/api/servarr/radarr.ts | 12 +----- server/api/servarr/sonarr.ts | 12 +----- server/lib/settings/index.ts | 2 + .../Settings/SettingsNetwork/index.tsx | 38 +++++++++++++++++++ src/i18n/locale/en.json | 3 ++ 6 files changed, 50 insertions(+), 23 deletions(-) diff --git a/server/api/servarr/base.ts b/server/api/servarr/base.ts index cdbb2203e..5e18f5b49 100644 --- a/server/api/servarr/base.ts +++ b/server/api/servarr/base.ts @@ -1,7 +1,7 @@ import ExternalAPI from '@server/api/externalapi'; import type { AvailableCacheIds } from '@server/lib/cache'; import cacheManager from '@server/lib/cache'; -import type { DVRSettings } from '@server/lib/settings'; +import { getSettings, type DVRSettings } from '@server/lib/settings'; export interface SystemStatus { version: string; @@ -92,14 +92,14 @@ class ServarrBase extends ExternalAPI { apiKey, cacheName, apiName, - timeout = 10000, }: { url: string; apiKey: string; cacheName: AvailableCacheIds; apiName: string; - timeout?: number; }) { + const timeout = getSettings().network.apiRequestTimeout; + super( url, { diff --git a/server/api/servarr/radarr.ts b/server/api/servarr/radarr.ts index 29f492d73..3d0cf53ad 100644 --- a/server/api/servarr/radarr.ts +++ b/server/api/servarr/radarr.ts @@ -64,16 +64,8 @@ export interface RadarrMovie { } class RadarrAPI extends ServarrBase<{ movieId: number }> { - constructor({ - url, - apiKey, - timeout, - }: { - url: string; - apiKey: string; - timeout?: number; - }) { - super({ url, apiKey, cacheName: 'radarr', apiName: 'Radarr', timeout }); + constructor({ url, apiKey }: { url: string; apiKey: string }) { + super({ url, apiKey, cacheName: 'radarr', apiName: 'Radarr' }); } public getMovies = async (): Promise => { diff --git a/server/api/servarr/sonarr.ts b/server/api/servarr/sonarr.ts index e8b39c6f7..01b429ba5 100644 --- a/server/api/servarr/sonarr.ts +++ b/server/api/servarr/sonarr.ts @@ -111,16 +111,8 @@ class SonarrAPI extends ServarrBase<{ episodeId: number; episode: EpisodeResult; }> { - constructor({ - url, - apiKey, - timeout, - }: { - url: string; - apiKey: string; - timeout?: number; - }) { - super({ url, apiKey, apiName: 'Sonarr', cacheName: 'sonarr', timeout }); + constructor({ url, apiKey }: { url: string; apiKey: string }) { + super({ url, apiKey, apiName: 'Sonarr', cacheName: 'sonarr' }); } public async getSeries(): Promise { diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index 0aa9da159..1e659cd29 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -171,6 +171,7 @@ export interface NetworkSettings { trustProxy: boolean; proxy: ProxySettings; dnsCache: DnsCacheSettings; + apiRequestTimeout: number; } interface PublicSettings { @@ -593,6 +594,7 @@ class Settings { forceMinTtl: 0, forceMaxTtl: -1, }, + apiRequestTimeout: 10000, }, migrations: [], }; diff --git a/src/components/Settings/SettingsNetwork/index.tsx b/src/components/Settings/SettingsNetwork/index.tsx index 21dc6a00d..3adffd632 100644 --- a/src/components/Settings/SettingsNetwork/index.tsx +++ b/src/components/Settings/SettingsNetwork/index.tsx @@ -56,6 +56,10 @@ const messages = defineMessages('components.Settings.SettingsNetwork', { 'Do NOT enable this if you are experiencing issues with DNS lookups', dnsCacheForceMinTtl: 'DNS Cache Minimum TTL', dnsCacheForceMaxTtl: 'DNS Cache Maximum TTL', + apiRequestTimeout: 'API Request Timeout', + apiRequestTimeoutTip: + 'Maximum time (in seconds) to wait for responses from external services like Radarr/Sonarr. Set to 0 for no timeout.', + validationApiRequestTimeout: 'You must provide a valid timeout value', }); const SettingsNetwork = () => { @@ -91,6 +95,10 @@ const SettingsNetwork = () => { .max(65535, intl.formatMessage(messages.validationProxyPort)) .required(intl.formatMessage(messages.validationProxyPort)), }), + apiRequestTimeout: Yup.number() + .typeError(intl.formatMessage(messages.validationApiRequestTimeout)) + .required(intl.formatMessage(messages.validationApiRequestTimeout)) + .min(0, intl.formatMessage(messages.validationApiRequestTimeout)), }); if (!data && !error) { @@ -130,6 +138,10 @@ const SettingsNetwork = () => { proxyPassword: data?.proxy?.password, proxyBypassFilter: data?.proxy?.bypassFilter, proxyBypassLocalAddresses: data?.proxy?.bypassLocalAddresses, + apiRequestTimeout: + data?.apiRequestTimeout !== undefined + ? data.apiRequestTimeout / 1000 + : 10, }} enableReinitialize validationSchema={NetworkSettingsSchema} @@ -154,6 +166,7 @@ const SettingsNetwork = () => { bypassFilter: values.proxyBypassFilter, bypassLocalAddresses: values.proxyBypassLocalAddresses, }, + apiRequestTimeout: Number(values.apiRequestTimeout) * 1000, }); mutate('/api/v1/settings/public'); mutate('/api/v1/status'); @@ -341,6 +354,31 @@ const SettingsNetwork = () => { )} +
+ +
+ +
+ {errors.apiRequestTimeout && + touched.apiRequestTimeout && + typeof errors.apiRequestTimeout === 'string' && ( +
{errors.apiRequestTimeout}
+ )} +