Compare commits

...

388 Commits

Author SHA1 Message Date
dependabot[bot]
cd70ab8711 Bump actions/setup-python from 5 to 6
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-13 17:08:28 +00:00
Maximilian Dorninger
51b8794e4d Merge pull request #411 from maxdorninger/Dependabot-auto-bump-deps
Configure Dependabot for multiple package ecosystems
2026-02-13 18:07:54 +01:00
Mark Riabov
0cfd1fa724 Fix suffix formatting for with_suffix call (#408)
Fixes issue ValueError: Invalid suffix 'jpg'

Completely prevents downloading posters from metadata provider
2026-02-10 20:29:05 +01:00
Maximilian Dorninger
b5b297e99a add new sponsor syn (#405)
this PR adds the new sponsor syn
2026-02-08 20:10:06 +01:00
maxid
58414cadae update all links to docs 2026-02-08 19:47:17 +01:00
maxid
462794520e update docs workflow 2026-02-08 19:43:13 +01:00
maxid
59afba007d update docs workflow 2026-02-08 19:36:07 +01:00
Maximilian Dorninger
cfa303e4f3 Merge pull request #404 from maxdorninger/mkdocs
This PR replaces Gitbook with Mkdocs to provide documentation
2026-02-08 19:27:15 +01:00
maxid
d3dde9c7eb add docs workflow 2026-02-08 19:22:34 +01:00
maxid
9c94ef6de0 convert gitbook files to mkdocs 2026-02-08 19:16:38 +01:00
Maximilian Dorninger
2665106847 Merge pull request #401 from maxdorninger/fix-env-variables
Fix download clients config being read from env variables
2026-02-08 16:37:15 +01:00
maxid
d029177fc0 hot fix: fix search tag name for episode in jackett 2026-02-04 23:52:07 +01:00
Maximilian Dorninger
1698c404cd Merge pull request #400 from maxdorninger/add-search-by-id-support-to-jackett
Add search by id support to jackett
2026-02-04 23:00:00 +01:00
maxid
abac894a95 fix download clients config being read from env variables without the mediamanager prefix 2026-02-04 22:49:24 +01:00
maxid
12854ff661 format files 2026-02-04 21:34:37 +01:00
maxid
3d52a87302 add id search capabilities to jackett 2026-02-04 21:34:31 +01:00
Maximilian Dorninger
9ee5cc6895 make the container user configurable (#399)
This PR makes the user the container runs as configurable. Before, the
container always tried stepping down (from root) to the mediamanager
user. Now it detects if it's already running as a non-root user and
starts the server directly. Fixes #397
2026-02-04 19:01:18 +01:00
Maximilian Dorninger
c45c9e5873 add correlation id to logging (#398)
This PR adds Correlation IDs to logs and request responses.

```
2026-02-04 12:40:32,793 - [afd825081d874d6e835b5c59a6ddb371] DEBUG - media_manager.movies - get_importable_movies(): Found 5 importable movies.
2026-02-04 12:40:32,794 - [afd825081d874d6e835b5c59a6ddb371] INFO - uvicorn.access - send(): 172.19.0.1:64094 - "GET /api/v1/movies/importable HTTP/1.1" 200
2026-02-04 12:40:47,322 - [41d30b7003fd45288c6a4bb1cfba5e7a] INFO - uvicorn.access - send(): 127.0.0.1:52964 - "GET /api/v1/health HTTP/1.1" 200
2026-02-04 12:41:17,408 - [157027ea5dde472a9e620f53739ccd53] INFO - uvicorn.access - send(): 127.0.0.1:39850 - "GET /api/v1/health HTTP/1.1" 200
```
2026-02-04 13:55:05 +01:00
Sergey Khruschak
24fcba6bee Torrent file name sanitizing (#390)
Hi, I've added file names sanitization when saving the torrent file, as
previously the import was failing on torrents with special characters in
names. This fixes #367
2026-02-03 17:09:36 +01:00
Maximilian Dorninger
d5994a9037 Fix docker permission issues (#395)
This PR fixes docker permission issues by first starting as root and
then chown-ing all the volumes. This should fix #388 #389
2026-02-03 13:06:18 +01:00
just_Bri
9e0d0c03c0 feat: add links to media detail pages in requests and torrent tables (#352)
Feature Request: https://github.com/maxdorninger/MediaManager/issues/351

[feat: add links to media detail pages in requests and torrent
tables](ac376c0d6d)
2026-02-02 22:48:14 +01:00
Maximilian Dorninger
70ff8f6ace Fix the broken link to the disable ascii art page (#396)
Fix the broken link to the disable ascii art page
2026-02-02 22:22:11 +01:00
Maximilian Dorninger
e347219721 Merge pull request #394 from juandbc/fix-torznab-process-and-jackett-movies-search
Fix torznab process and jackett movies search
2026-02-02 17:42:49 +01:00
strangeglyph
72a626cb1a Add flag to disable startup ascii art (#369)
Adds an environment variable to disable the colorized splash screen.
2026-02-02 17:39:47 +01:00
Juan David Bermudez Celedon
a1f3f92c10 Enhance size validation for indexer results 2026-02-01 22:14:04 -05:00
Juan David Bermudez Celedon
caaa08fbf4 Fix typo in Jackett log for search_movie 2026-02-01 22:01:42 -05:00
Juan David Bermudez Celedon
5db60141bb Fix bug by typo in jackett log message (#387)
fix typo in the `search_season` function log, which causes an error when searching for torrents.
2026-02-01 18:09:18 +01:00
Marcel Hellwig
96b84d45db Adding some more new lints (#393)
Enable `UP` and `TRY` lint
2026-02-01 18:04:15 +01:00
Marcel Hellwig
311e625eee two hotfixes (#392)
this prevents the app from running correctly
2026-02-01 17:42:15 +01:00
maxidorninger
e22e0394bd GITBOOK-19: No subject 2026-01-09 20:13:39 +00:00
maxid
6377aa8b83 rever "add digital ocean attribution" in GitBook 2026-01-09 21:02:19 +01:00
Maximilian Dorninger
8855204930 add digital ocean attribution (#368) 2026-01-09 20:54:47 +01:00
maxidorninger
7a13326d87 GITBOOK-16: No subject 2026-01-07 19:10:20 +00:00
maxidorninger
15e9cd001f GITBOOK-15: No subject 2026-01-07 18:59:43 +00:00
maxidorninger
e52b84c3c7 GITBOOK-14: No subject 2026-01-07 18:58:37 +00:00
maxidorninger
84a430651f GITBOOK-13: No subject 2026-01-07 18:57:25 +00:00
maxidorninger
463e6914e3 GITBOOK-12: No subject 2026-01-07 18:56:20 +00:00
strangeglyph
e5e85077ae docs: add installation instructions for nix flake (#361)
Following the discussion in #329 and #115, here's a doc section on using
nix flakes to install MediaManager.

Co-authored-by: lschuetze <lschuetze@mpi-sws.org>
2026-01-07 19:45:47 +01:00
Maximilian Dorninger
a39e0d204a Ruff enable type annotations rule (#362)
This PR enables the ruff rule for return type annotations (ANN), and
adds the ty package for type checking.
2026-01-06 17:07:19 +01:00
Renan Greca
dd0b439bbe Fix logging bug in jackett indexer (#360)
fix MM trying to access non-existent attribute
2026-01-06 14:49:06 +01:00
Maximilian Dorninger
732b9c0970 make installation guides always link to files of latest release (#359)
make installation guides always link to files of latest release
2026-01-06 11:49:36 +01:00
Maximilian Dorninger
57028991df Merge pull request #341 from hellow554/ruff
enable more Ruff lints
2026-01-05 23:15:38 +01:00
maxid
d5c41430a6 add back hello word message 2026-01-05 23:05:46 +01:00
Maximilian Dorninger
5db3560e9a fix readme 2026-01-05 21:46:12 +01:00
Maximilian Dorninger
13ed291dd4 Revise MediaManager overview (#358)
Updated the description and key features of MediaManager.
2026-01-05 21:44:32 +01:00
Maximilian Dorninger
fd2befd2a1 Migrate to gitbook (#357)
This PR facilities the migration from the old Writerside documentation
platform to Gitbook.

## motivation

Gitbook supports serving multiple versions of the documentation, e.g.
v1.12.1, v1.13.0, and master.
2026-01-05 21:39:18 +01:00
maxidorninger
940c1ea2d1 GITBOOK-10: No subject 2026-01-05 19:41:27 +00:00
maxidorninger
9c7679101f GITBOOK-9: No subject 2026-01-05 19:12:55 +00:00
Marcel Hellwig
75406cbc64 ruff: add RET lint
lints about assign and immediatly returning a variable
2026-01-05 19:30:42 +01:00
Marcel Hellwig
805a6981a6 ruff: enable PTH lint 2026-01-05 19:30:42 +01:00
Marcel Hellwig
acd883df21 ruff: enable PIE lint
this just removes needless pass or ...
2026-01-05 19:30:42 +01:00
Marcel Hellwig
f2141ca8b8 ruff: enable PERF lint
this complains a lot about using manual append where a list
comprehension would be more suitable
2026-01-05 19:30:42 +01:00
Marcel Hellwig
7182344036 create list from range directly instead of using append 2026-01-05 19:30:42 +01:00
Marcel Hellwig
a34b0f11a6 use single s since we're ignoring cases anyway 2026-01-05 19:30:42 +01:00
Marcel Hellwig
40812c6040 omit return_type in computed field
it's calculated from the functions return type, so there's no need to
specifify it
2026-01-05 19:30:42 +01:00
Marcel Hellwig
29476e2008 ruff: enable INT and N lint
this renames some files to use snake_case and add Error suffix to custom
exceptions
2026-01-05 19:30:42 +01:00
Marcel Hellwig
29a0d8fe5d ruff: add INP lint
this checks for missing __init__.py files, there was one :)
2026-01-05 19:30:42 +01:00
Marcel Hellwig
55b2dd63d8 ruff: add ARG linter
this mostly either removes unused parameters, prefixes them with an
underscore or uses the @override decorator to tell the linter, that that
method comes from a superclass and can't be changed
2026-01-05 19:30:42 +01:00
Marcel Hellwig
6e46b482cb ruff: enable A lint 2026-01-05 19:30:42 +01:00
Marcel Hellwig
7824828bea ruff: enable T20 lint
and remove a print hello world :)
2026-01-05 19:30:42 +01:00
Marcel Hellwig
5368cad77a ruff: add S linter
this mostly adds a timeout=60 to all requests

this does mainly wants a timeout to all requests functions, since when
left out they hang infinitly.
I added a timeout of 60s, which is probably way too high, but since
before this there was none, I guess it's an improvement?
2026-01-05 19:30:42 +01:00
Marcel Hellwig
1857cf501c ruff: enable RUF lint 2026-01-05 19:30:42 +01:00
Marcel Hellwig
a7bb5e1e04 Make proper use of function overloading
In preparation of the RUFF lint, I rewrote the function to use
typing.overload.
This is the proper way to accept either two arguments or one argument
2026-01-05 19:30:42 +01:00
Marcel Hellwig
ff013ac76e ruff: enable I lint 2026-01-05 19:30:42 +01:00
Marcel Hellwig
42502c93fc ruff: enable ISC lint 2026-01-05 19:30:42 +01:00
Marcel Hellwig
eac58d2843 ruff: enable FAST lint
this mostly is replacing the response_model attribute with a return type
of that function since that's the more idiomatic way to do
2026-01-05 19:30:42 +01:00
Marcel Hellwig
97cb3b5c1e ruff: enable EM lint 2026-01-05 19:30:42 +01:00
Marcel Hellwig
7ef4e52c81 ruff: enable C4 lint 2026-01-05 19:30:42 +01:00
Marcel Hellwig
2c36adfd75 ruff: Enable B lint 2026-01-05 19:30:42 +01:00
Marcel Hellwig
0f272052b3 ruff: enable lints that do not complain right now 2026-01-05 19:30:42 +01:00
Marcel Hellwig
0b4b84a4aa add ruff as dev-dependency
since it is mentioned in the development doc, it makes sense to install
it as such
2026-01-05 19:30:34 +01:00
Marcel Hellwig
9ff2dc4b92 rewrite downlaod_post_image function
this now uses the proper functions instead of handling with strings
2026-01-05 19:30:00 +01:00
Marcel Hellwig
593e1828cc remove pillow-avif package
it is possible since 11.3 to use avif in the main pillow package,
therefore the avif package is no longer needed

https://github.com/python-pillow/Pillow/pull/5201#issuecomment-3023668716
2026-01-05 19:29:52 +01:00
Maximilian Dorninger
1b2d99922c Switch to quay and add docker image documentation (#356)
This PR changes all mentions of GHCR to Quay, adds docs about the
registries MM is available on, adds docs about the available docker
image tags, and adds the GHCR permission issue to the troubleshooting
page.
2026-01-05 17:14:32 +01:00
Maximilian Dorninger
e647c99722 Add Contributor Covenant Code of Conduct (#355)
This document outlines the standards of behavior expected from community
members and the consequences for violations.
2026-01-05 13:00:42 +01:00
Maximilian Dorninger
3cd750b882 fix build workflows (#354)
this PR fixes the build workflows, so the QUAY_ORG environment variable
is populated even when run by a PR from a fork
2026-01-05 11:18:51 +01:00
Maximilian Dorninger
87f10e2508 remove old docs (#353)
removes settings which were removed with the v1.12.0 release
2026-01-04 23:44:25 +01:00
Maximilian Dorninger
fc691f95bb fix bugs in MovieService
fix some small bugs in MovieService that were caused by the refactor
2026-01-04 23:37:33 +01:00
Maximilian Dorninger
7e334ffbb1 push docker images to quay.io as well (#346)
This PR adjusts the GH Workflows to also push the container images to
quay.io
2026-01-04 22:31:58 +01:00
Maximilian Dorninger
1f12a7cc3b add sponsor @seferino-fernandez (#343)
add sponsor @seferino-fernandez
2026-01-04 14:47:42 +01:00
Maximilian Dorninger
3ffc28b723 Merge pull request #342 from maxdorninger/maxdorninger-patch-1
Include show year in season search query
2026-01-04 14:35:05 +01:00
Maximilian Dorninger
21495653d2 Include show year in season search query 2026-01-04 14:20:38 +01:00
Maximilian Dorninger
2674d8e018 Merge pull request #339 from maxdorninger/deduplicate-frontend-code
deduplicate code in the download movie/season dialogs
2026-01-03 16:30:20 +01:00
maxid
30ccd0e128 fix torrents title text overflowing 2026-01-03 16:04:38 +01:00
maxid
e6daba279c fix torrents table sorting by always returning a new array 2026-01-03 16:04:21 +01:00
maxid
76d9d47a44 deduplicate code in download movie/season dialogs 2026-01-03 14:54:48 +01:00
Maximilian Dorninger
5caf9169bf Merge pull request #338 from maxdorninger/deduplicate-frontend-code
Organize frontend components into folders
2026-01-03 13:30:01 +01:00
maxid
54b99a67e5 fix import paths 2026-01-03 13:24:25 +01:00
maxid
be3f66c817 move file path suffix components to download dialogs folder 2026-01-03 12:13:42 +01:00
maxid
93711ed1d9 delete unused components 2026-01-03 12:13:13 +01:00
maxid
bd7e9090b2 create torrent components folder 2026-01-03 12:10:24 +01:00
maxid
fc79563be3 create request dialog components folder 2026-01-03 12:08:52 +01:00
maxid
4de6ab9acb create auth components folder 2026-01-03 12:03:33 +01:00
maxid
d9d6f944e8 create nav components folder 2026-01-03 11:56:01 +01:00
maxid
ea30abf1a1 create download dialogs folder 2026-01-03 11:53:29 +01:00
Maximilian Dorninger
6f8e73c90e Merge pull request #334 from hellow554/sort_by_score
allow sorting of torrent search results in web ui
2026-01-02 21:23:11 +01:00
maxid
063072f25c fix: keep column width when no sort is applied 2026-01-02 21:12:45 +01:00
maxid
4ea4275440 change sort indicators to lucid-svelte icons and clean up code 2026-01-02 20:57:40 +01:00
Maximilian Dorninger
d420a2714c Merge pull request #333 from strangeglyph/master
Allow frontend files to be followed through symlinks
2026-01-02 18:00:18 +01:00
maxid
e785b871df add docs 2026-01-02 17:56:05 +01:00
maxid
f144ce1bdf format files after merge 2026-01-02 16:25:41 +01:00
maxid
2d2afaba50 Merge branch 'master' into fork/strangeglyph/master
# Conflicts:
#	media_manager/main.py
2026-01-02 16:24:18 +01:00
maxid
a253ffd946 only accept TRUE 2026-01-02 16:21:58 +01:00
Maximilian Dorninger
3937441e84 Merge pull request #325 from maxdorninger/refactor-code
Refactor code, update dependencies, improve exception handling and more
2026-01-02 15:21:09 +01:00
maxid
4841d1ed11 fix tvdb search not working 2026-01-02 14:38:58 +01:00
Marcel Hellwig
8f6b4ec36c allow sorting of torrent results in web ui 2026-01-02 13:43:16 +01:00
strangeglyph
64f01cc919 Allow frontend files to be followed through symlinks 2026-01-02 12:33:15 +01:00
maxid
edb2001cdc fix tvdb search: use get method for safe access to image_url and overview 2026-01-02 11:13:23 +01:00
maxid
5f7aa928ed remove comment 2026-01-02 11:08:57 +01:00
maxid
88464e76dd remove debug print statement 2026-01-02 11:07:29 +01:00
maxid
02480f1420 fix: add ConflictError exception handler 2026-01-01 21:05:20 +01:00
maxid
3f500eccc6 fix: replace ValueError with ConflictError in movie route 2026-01-01 14:22:19 +01:00
maxid
dd1e5d6621 refactor: update metadata methods to include optional language parameter 2026-01-01 14:21:51 +01:00
maxid
dc7cc98a1a refactor: enhance error handling in check_if_movie_exists method 2026-01-01 14:01:44 +01:00
maxid
a695a5e3ee fix: update image endpoint 2026-01-01 14:01:14 +01:00
maxid
86130f0a6e update dependencies 2025-12-30 00:32:39 +01:00
maxid
e14f19bd45 format files 2025-12-30 00:19:28 +01:00
maxid
7c488e4fae refactor: improve logging and correct directory deletion in service.py 2025-12-30 00:17:45 +01:00
maxid
95c22fa854 Merge branch 'master' into refactor-code
# Conflicts:
#	media_manager/movies/service.py
#	media_manager/tv/service.py
2025-12-30 00:14:24 +01:00
maxid
40130c6a37 refactor: rename AllEncompassingConfig to MediaManagerConfig 2025-12-29 23:38:17 +01:00
maxid
90c2a1d65b refactor: split up main.py into multiple files 2025-12-29 23:37:22 +01:00
maxid
51560ae1e9 refactor: remove unneeded comments 2025-12-29 23:01:34 +01:00
maxid
009f743259 refactor: enhance exception handling 2025-12-29 22:54:45 +01:00
maxid
8de3a71bfd refactor: standardize parameter usage in movie router and service 2025-12-29 22:45:50 +01:00
maxid
35880231e0 refactor: standardize parameter usage in tv router and service 2025-12-29 22:39:39 +01:00
Maximilian Dorninger
d3e12579ac Merge pull request #324 from briandipalma/extra-spaces
fix: only add one space between release year and release ID and fix formatting of dev guide docs
2025-12-29 22:14:14 +01:00
Brian Di Palma
57c94dbd57 feat(importing): Only add one space between release year and release ID 2025-12-29 20:05:51 +00:00
maxid
3c67705275 refactor movie/router.py 2025-12-29 13:17:20 +01:00
maxid
11ff6cd981 reorganize tv/router.py 2025-12-29 13:06:42 +01:00
maxid
499d6a2b3f refactor: update movie handling in router.py to use movie_dep 2025-12-29 12:58:15 +01:00
Maximilian Dorninger
ef0662d584 Merge pull request #323 from maxdorninger/reduce-amount-of-logs
Reduce amount of logs and add log level variable
2025-12-29 12:18:44 +01:00
maxid
63a65c85bd add log level env variable 2025-12-29 11:57:00 +01:00
maxid
4cc4ff3629 refactor: improve logging for deleted torrents in service.py 2025-12-29 11:54:56 +01:00
maxid
9e6851d055 remove unnecessary logs in the torrents module 2025-12-29 11:43:35 +01:00
Maximilian Dorninger
a508b7dabd Merge pull request #320 from maxdorninger/fix-import-success-logic
fix: set success variable when importing movie
2025-12-29 01:01:33 +01:00
maxid
12ce02473d fix: set success variable when importing movie 2025-12-29 00:35:54 +01:00
Maximilian Dorninger
530ff01caf Merge pull request #317 from maxdorninger/improve-indexer-accuracy
Improve indexer accuracy
2025-12-29 00:17:39 +01:00
maxid
1f53f51be7 remove reject_torrents_on_url_error and follow_redirects setting from configuration files 2025-12-29 00:04:48 +01:00
maxid
c2724659d9 improve default scoring rules in config.example.toml 2025-12-28 23:42:18 +01:00
maxid
8b8559a50f format main.py 2025-12-28 23:41:52 +01:00
maxid
4f74a5bee6 improve logging of evaluation of IndexerQueryResults 2025-12-28 23:41:41 +01:00
maxid
7ea1984d6c refactor indexer search: streamline result evaluation by using raw torrent list 2025-12-28 23:41:06 +01:00
maxid
8c380da3b4 refactor logging configuration: use constant for log level 2025-12-28 23:39:14 +01:00
maxid
733c7f78de refactor indexers: add TorznabMixin for improved search result processing and use Torznab Prowlarr endpoint 2025-12-28 23:24:53 +01:00
maxid
0da3e53bcb fix api client context manager 2025-12-28 14:01:54 +01:00
maxid
6993ec8752 fix: use name attribute instead of title 2025-12-28 13:42:21 +01:00
maxid
cf7de07120 fix Dockerfile: ensure recursive ownership for app directory 2025-12-28 11:47:28 +01:00
maxid
91ed5e34dc refactor indexer service: streamline movie and season search methods 2025-12-28 11:20:51 +01:00
maxid
b05c255a53 refactor metadata providers: remove special character removal from search queries 2025-12-28 11:12:58 +01:00
maxid
a29e53bbe5 enhance Prowlarr indexer: add query and type parameters for TV and movie searches 2025-12-28 11:12:26 +01:00
maxid
db54f3b2f2 indexer service: add movie and season search methods 2025-12-28 11:12:13 +01:00
maxid
0a3e7a973f increase max limit of search results 2025-12-27 22:24:46 +01:00
maxid
09cad60673 format files 2025-12-27 22:22:32 +01:00
maxid
b510b1a6e4 enhance Prowlarr indexer: use tmdb, imdb, tvdb id, and season number for searches 2025-12-27 22:22:19 +01:00
maxid
edb6aab77d add abstract search methods for movies and seasons to generic indexer class 2025-12-27 19:59:53 +01:00
maxid
e723fdae96 close import dialog after invalidateAll 2025-12-27 15:26:31 +01:00
Maximilian Dorninger
6eb8979bed Merge pull request #314 from yangqi/fix-non-root-execution
fix: allow container to run as non-root user
2025-12-27 14:40:27 +01:00
Maximilian Dorninger
4763a5a771 Merge pull request #313 from maxdorninger/add-more-metadata-ids
Add IMDb id's
2025-12-27 12:52:03 +01:00
maxid
61fc87e577 fix bug: actually save imdb_id to database 2025-12-27 12:42:36 +01:00
maxid
4cd320a5c2 fix typo in api route 2025-12-27 12:32:11 +01:00
maxid
28e3707e47 fix order of MetadataRelay API routes 2025-12-27 11:09:07 +01:00
Qi
e405c9f8c2 fix: allow container to run as non-root user
- Create mediamanager user and group (UID/GID 1000)
- Set ownership of /app and /data to mediamanager
- Configure uv to use writable cache directory in home
- Set UV_LINK_MODE=copy for better compatibility
- Closes #96
2025-12-23 22:55:03 -08:00
maxid
e4cb7d55cb regenerate code 2025-12-23 20:31:32 +01:00
maxid
9c6676f1f3 feat: add imdb_id parameter to update movie and show methods 2025-12-23 20:29:51 +01:00
maxid
ff3797e1c4 feat: add IMDb ID retrieval for movies and TV shows 2025-12-23 20:24:16 +01:00
maxid
3b920135c2 format files 2025-12-23 19:54:27 +01:00
maxid
0c30ede537 feat: add imdb_id fields to movies and shows 2025-12-23 19:54:17 +01:00
maxid
db37e7373e feat: add external IDs endpoints for TV shows and movies 2025-12-23 19:53:48 +01:00
Maximilian Dorninger
b66410142d Merge pull request #270 from aasmoe/feat/multi-language-metadata
Add Feature: multi language metadata
2025-12-22 20:07:57 +01:00
Maximilian Dorninger
242efa744f Merge pull request #312 from maxdorninger/fix-workflows
fix: extract version number without 'v'
2025-12-22 19:12:45 +01:00
maxid
fabcc26095 fix: extract version number without 'v' prefix in build workflows in order to use correct image tag for inspecting the image 2025-12-22 19:05:11 +01:00
Maximilian Dorninger
e1a7c5fe5d fix login with oauth button not displaying (#311)
fixes oauth button not displaying
2025-12-22 18:50:29 +01:00
aasmoe
688ca8ab9e Update docs with primary_languages and default_language and remove instructions form config example 2025-12-21 21:30:31 +00:00
Maximilian Dorninger
c7c6158a3f disable caching in build and metadata relay workflows (#309)
this pr disables caching in build and metadata relay workflows because it doesn't actually make the workflows any faster.
2025-12-21 20:53:47 +01:00
Maximilian Dorninger
b87a23b5b4 Merge pull request #308 from maxdorninger/display-movie-files
display movie files
2025-12-21 19:37:05 +01:00
maxid
f184def9ad display movie files 2025-12-21 19:24:35 +01:00
Maximilian Dorninger
4e8dbdd128 Merge pull request #302 from maxdorninger/improve-frontend-code-quality
Improve frontend code quality
2025-12-21 19:11:02 +01:00
maxid
cc6e77deb7 move imports to top 2025-12-21 17:04:49 +01:00
maxid
a974a3484b refactor: data handling with derived 2025-12-21 17:03:01 +01:00
maxid
bf84cc0a06 refactor: enhance data invalidation 2025-12-21 16:54:32 +01:00
maxid
7a44463982 convert layout load function into page load function 2025-12-21 16:40:11 +01:00
maxid
0d45eae6e8 remove all occurrences of base 2025-12-21 16:32:31 +01:00
maxid
888186a084 fix handling of state 2025-12-21 16:24:16 +01:00
Maximilian Dorninger
e5efeaddf9 Merge pull request #301 from maxdorninger/maxdorninger-fix-torrent-deletion
Fix torrent deletion logic
2025-12-21 16:19:31 +01:00
maxid
4b7d5eea54 refactor: improve async data fetching in load functions 2025-12-21 16:16:34 +01:00
maxid
9187617530 refactor: improve data handling in show and movie components 2025-12-21 16:06:49 +01:00
maxid
2493b43d4f only start searching for torrents when clicking on search button 2025-12-21 16:01:08 +01:00
maxid
45208105c0 remove unused import 2025-12-21 16:01:05 +01:00
maxid
c56bf705ac use resolve instead of base on about page 2025-12-21 16:00:53 +01:00
maxid
8c584626f1 use resolve instead of base on movie pages 2025-12-21 16:00:53 +01:00
Maximilian Dorninger
2b21915b1f Fix torrent deletion logic 2025-12-21 15:25:42 +01:00
aasmoe
3574a0a08e Merge branch 'master' into feat/multi-language-metadata 2025-12-21 14:43:17 +01:00
Maximilian Dorninger
e21e74a9ea Merge pull request #299 from maxdorninger/add-contributing.md
Create CONTRIBUTING.md for project guidelines
2025-12-21 14:37:17 +01:00
Maximilian Dorninger
4fc033828e Update CONTRIBUTING.md with developer guide link
Added a link to the developer guide for setting up the dev environment.
2025-12-21 14:36:55 +01:00
Maximilian Dorninger
7d7f7b4fd5 Create CONTRIBUTING.md for project guidelines
Added a contributing guide to help users understand how to contribute to the project.
2025-12-21 14:34:47 +01:00
Maximilian Dorninger
e0fdca082c Merge pull request #297 from maxdorninger/improve-importing
Improve importing
2025-12-20 21:39:22 +01:00
maxid
1293cc692c fix: rename source directory before importing existing movies and TV shows 2025-12-20 21:16:52 +01:00
maxid
6b2f426ff9 update docs 2025-12-20 21:10:54 +01:00
maxid
e0a04bb040 fix: filter out non-directory items from import suggestions 2025-12-20 21:01:55 +01:00
Maximilian Dorninger
b854a13338 Update media_manager/torrent/utils.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-20 20:59:28 +01:00
Maximilian Dorninger
5c8cff00a9 Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-20 20:58:06 +01:00
maxid
12fe84017c fix bug: hidden directories included in import suggestions 2025-12-20 20:31:31 +01:00
maxid
ecc030238e fix bug: importable shows saves to importable movies variable 2025-12-20 20:30:33 +01:00
maxid
ed78bde604 reduce logs 2025-12-20 20:22:35 +01:00
maxid
d5c649d5bf refactor search queries to remove special characters and parentheses 2025-12-20 20:05:02 +01:00
maxid
cfe34358a0 refactor title processing to remove special characters and brackets 2025-12-20 20:00:27 +01:00
maxid
1f50b18b9f refactor media detection functions to improve accuracy 2025-12-20 19:37:34 +01:00
maxid
b20d2ed09d update api.d.ts 2025-12-20 14:55:56 +01:00
maxid
eb7a15d32d Merge branch 'refs/heads/master' into fork/aasmoe/feat/multi-language-metadata
# Conflicts:
#	metadata_relay/app/tmdb.py
#	web/src/lib/api/api.d.ts
2025-12-20 14:53:41 +01:00
Maximilian Dorninger
4bb3064779 Merge pull request #294 from maxdorninger/fix-deleted-torrents-leaving-stray-season-files
Fix deleted torrents leaving stray season files
2025-12-20 14:29:47 +01:00
Maximilian Dorninger
edcc238cdd Merge pull request #295 from maxdorninger/direct-indexer-mode
Add option to skip following redirects
2025-12-20 13:15:56 +01:00
maxid
c78bc05627 format files 2025-12-20 13:08:32 +01:00
maxid
bfd9196f5e add support for handling invalid URL schemas and following redirects for torrent downloads 2025-12-20 13:08:05 +01:00
maxid
1cfc90e4ac fix typo 2025-12-19 19:06:49 +01:00
maxid
322b868996 add configuration option to enable following redirects in prowlarr 2025-12-19 19:06:49 +01:00
maxid
2e95798e04 add configuration option to enable following redirects in prowlarr 2025-12-19 19:06:49 +01:00
maxid
1edb2cae9b format files 2025-12-19 18:42:46 +01:00
maxid
bcd1ccfd21 enhance torrent deletion to remove associated media files if the torrent was not imported 2025-12-19 18:42:38 +01:00
Maximilian Dorninger
63ff29f024 Merge pull request #293 from maxdorninger/improve-docs
Improve troubleshooting section of docs
2025-12-19 18:12:55 +01:00
maxid
bfa13762d5 add note about restarting MediaManager for config changes to take effect 2025-12-19 18:08:20 +01:00
maxid
a2462aa964 update formatting of docs and expand on hard linking issues 2025-12-19 18:06:03 +01:00
maxid
7f5363ffb2 add troubleshooting steps for qBittorrent file import issues 2025-12-19 17:58:12 +01:00
maxid
157e8ee77d format frontend code 2025-12-19 16:48:10 +01:00
maxid
c3e9eec5f7 update package-lock.json 2025-12-19 16:47:58 +01:00
Maximilian Dorninger
0443981d5f Merge pull request #278 from xNinjaKittyx/delete_shows_and_movies
Delete shows and movies (and fix lint errors)
2025-12-18 22:17:49 +01:00
Maximilian Dorninger
224644f9af Merge pull request #275 from aasmoe/remove-adult-search-results
Remove adult search results
2025-12-18 22:16:58 +01:00
maxid
32def988ad update package-lock.json 2025-12-18 22:08:43 +01:00
Maximilian Dorninger
a40077a5b1 remove unnecessary check of movie.library 2025-12-18 22:06:16 +01:00
maxid
6ed3c91bde extract delete-media-dialog from movies and shows pages 2025-12-18 22:02:57 +01:00
aasmoe
90dafbd2e5 update package-lock 2025-12-18 11:22:36 +00:00
aasmoe
1afa13a954 Merge branch 'master' into feat/multi-language-metadata 2025-12-18 12:00:54 +01:00
xNinjaKittyx
28915a5484 fix: use the right functions and also make sure the 2 checkboxes are on movies too. 2025-12-16 02:41:04 +00:00
xNinjaKittyx
380de78b51 feature: delete files and torrents. 2025-12-15 21:14:16 +00:00
xNinjaKittyx
796de41fd7 fix: lint 2025-12-15 18:27:41 +00:00
xNinjaKittyx
c1d8810614 fix: change the title 2025-12-15 18:26:07 +00:00
xNinjaKittyx
f4178a7792 fix: not sure how the str wrap came back. 2025-12-15 18:20:16 +00:00
xNinjaKittyx
ec077c7f26 fix: lint and fix wrong text. 2025-12-15 18:18:11 +00:00
xNinjaKittyx
56fa5bed17 fix: fixing some lints. 2025-12-15 18:06:04 +00:00
xNinjaKittyx
18dd20d93e feature: add basic delete capability. 2025-12-15 18:06:04 +00:00
Maximilian Dorninger
d14aae1fa0 Merge pull request #273 from aasmoe/fix/dashboard-svelte5-reactivity
Make recommendations reactive for Svelte 5
2025-12-15 19:01:58 +01:00
Maximilian Dorninger
f71f7f3efd Merge pull request #271 from xNinjaKittyx/various-fixes
Address various different fixes regarding search UI experience.
2025-12-15 19:00:25 +01:00
xNinjaKittyx
db044d9351 fix: change id to movieID/ShowID type. 2025-12-15 17:54:31 +00:00
aasmoe
e12ff1d67b remove include_adult=True from tmdb relay 2025-12-15 09:26:09 +00:00
aasmoe
c1f733dd9b Make recommendations reactive for Svelte 5 2025-12-14 20:32:17 +00:00
xNinjaKittyx
a098b172ca Address various different fixes regarding search UI experience. 2025-12-14 00:10:38 +00:00
aasmoe
5271acb51d Use previous definition of config 2025-12-13 20:53:17 +00:00
aasmoe
b3a10df70c Update config.example.toml 2025-12-13 20:46:25 +00:00
aasmoe
fda59e49b2 Pass language as a value in show and movie objects. DB migration to add original_langauge column. Removes workarounds 2025-12-13 19:30:02 +00:00
aasmoe
8695d26ad9 Removed language parameter in favour of adding original_language to movie and show object. A couple of temporary workaround while there is no original_language column in db. 2025-12-13 19:30:02 +00:00
aasmoe
68f9d71bfa feat: wire up original_language to be passed when adding shows/movies
- Add original_language field to MetaDataProviderSearchResult schema
- Update frontend to pass original_language as language parameter when adding media
- This ensures media metadata is fetched in the correct language based on original_language
2025-12-13 19:30:02 +00:00
aasmoe
1caff77412 WIP: current progress 2025-12-13 19:30:02 +00:00
aasmoe
266d81688c feat: Add multi-language metadata support
- Add primary_languages config setting (ISO 639-1 codes)
- Fetch metadata in original language when in primary_languages
- Display original titles in search results for configured languages
- Download language-specific posters when available
2025-12-13 19:30:02 +00:00
Maximilian Dorninger
53091e7204 Merge pull request #263 from maxdorninger/fix-search-torrents
Fix search torrents
2025-12-12 19:07:46 +01:00
maxid
2a8573a2ba remove wrong Table.Cell tags 2025-12-11 22:53:53 +01:00
maxid
6d025b835d improve download-movie-dialog.svelte 2025-12-11 22:53:30 +01:00
Maximilian Dorninger
c96f5b8f40 Merge pull request #255 from maxdorninger/fix-stats-card-loading
Fix stats card loading
2025-12-10 22:43:34 +01:00
Maximilian Dorninger
c0ef386905 Update web/src/lib/components/stats/animated-card.svelte
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-10 22:40:29 +01:00
maxid
45e934a260 improve stat-cards 2025-12-10 22:06:25 +01:00
maxid
014f239e19 format files 2025-12-10 21:42:30 +01:00
maxid
30a07691a2 improve formating of new version available stat card 2025-12-10 21:41:22 +01:00
maxid
a55c58da2b improve stat loading 2025-12-10 21:41:02 +01:00
maxid
23041cbd09 improve how movies and tv shows are loaded 2025-12-10 21:36:29 +01:00
Maximilian Dorninger
5a274f58b8 Merge pull request #248 from maxdorninger/fix-imports-loading
Fix imports loading
2025-12-10 20:57:08 +01:00
maxid
7fbf80d1fd update build-push-backend.yml 2025-12-10 20:50:49 +01:00
maxid
703c11271d npm update 2025-12-10 20:25:03 +01:00
maxid
4dfb0bbadb update prettier and eslint ignore patterns 2025-12-10 20:12:57 +01:00
maxid
eda1f9c13d update components to fix compile time error 2025-12-10 20:02:25 +01:00
maxid
c37f406074 update how importable media data state is managed 2025-12-10 20:02:02 +01:00
Maximilian Dorninger
2d80198d6c Merge pull request #246 from maxdorninger/increase-timeout
Add timeout config variable
2025-12-10 18:13:08 +01:00
maxid
dda9be13bb fix typos 2025-12-10 18:12:05 +01:00
maxid
c984d40fd3 format code 2025-12-10 17:50:11 +01:00
maxid
8f2f016b61 add timeout to docs 2025-12-10 17:50:04 +01:00
maxid
1ddcef0676 add timeout config value to example configs 2025-12-10 17:15:43 +01:00
maxid
fe3b77a889 add a config variable for jackett and prowlarr to change timeout time 2025-12-10 17:13:24 +01:00
Maximilian Dorninger
51f05c4a8a Fix version display format in stat-cards component 2025-12-10 14:31:11 +01:00
maxid
807ed1b17b forgot to git add new image 2025-12-09 21:07:27 +01:00
Maximilian Dorninger
b7030c4483 Merge pull request #239 from maxdorninger/update-oauth-docs
Update oauth docs
2025-12-09 20:59:50 +01:00
Maximilian Dorninger
647b211ef7 Merge pull request #238 from maxdorninger/fix-logic-for-version-update
Fix "new version available" card to always be displayed
2025-12-09 20:58:10 +01:00
maxid
509c937b0d update authentik example oauth config 2025-12-09 20:57:46 +01:00
maxid
84879600d0 update documentation for oauth 2025-12-09 20:52:31 +01:00
maxid
7f4d73ed9d remove leading 'v' from installed version 2025-12-09 20:42:19 +01:00
Maximilian Dorninger
9c529bd9d0 Replace old screenshots with new ones
Updated screenshots in the README with new images.
2025-12-08 00:49:39 +01:00
Maximilian Dorninger
06dadd29b0 Merge pull request #225 from maxdorninger/remove-unnecessary-logs
Remove unnecessary logs
2025-12-08 00:38:59 +01:00
Maximilian Dorninger
b819d3b1d9 Update media_manager/movies/repository.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-08 00:38:26 +01:00
Maximilian Dorninger
a603c65fd3 Update media_manager/tv/repository.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-08 00:38:15 +01:00
Maximilian Dorninger
d479cc84f3 Update media_manager/movies/repository.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-08 00:37:58 +01:00
Maximilian Dorninger
bed2fe8fa7 Update media_manager/tv/repository.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-08 00:37:27 +01:00
maxid
53fbc703bf reduce logs in routers 2025-12-08 00:32:47 +01:00
maxid
643d2448b1 reduce logs 2025-12-08 00:30:59 +01:00
Maximilian Dorninger
9f6d43d7de Merge pull request #224 from maxdorninger/improve-frontend-for-imports
Improve frontend for imports
2025-12-08 00:05:08 +01:00
maxid
6ab897b899 feat: add importable shows and movies count to stats cards 2025-12-07 23:55:52 +01:00
maxid
1963af4da8 bug fix: use derived instead of state 2025-12-07 23:22:08 +01:00
Maximilian Dorninger
e5c0690470 Merge pull request #220 from maxdorninger/fix-indexers
Make indexers for error resistant
2025-12-07 22:56:53 +01:00
Maximilian Dorninger
5610398e15 Merge pull request #223 from maxdorninger/add-import-of-existing-media
Add feature: import of existing media
2025-12-07 22:56:33 +01:00
Maximilian Dorninger
63f77124cb Update web/src/routes/dashboard/movies/+page.svelte
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-07 22:51:28 +01:00
Maximilian Dorninger
c17efaa306 Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-07 22:50:07 +01:00
Maximilian Dorninger
1d1e678890 Update media_manager/movies/service.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-07 22:45:11 +01:00
Maximilian Dorninger
d680099a0b Update media_manager/tv/service.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-07 22:44:43 +01:00
Maximilian Dorninger
fd457049af Update media_manager/movies/router.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-07 22:43:18 +01:00
maxid
20100441d2 bug fix: make movie importing create necessary directories 2025-12-07 22:24:33 +01:00
maxid
7aabc307c5 add good to know section to docs 2025-12-07 21:13:45 +01:00
maxid
3128c68ecb return error on failed movie import 2025-12-07 21:09:15 +01:00
maxid
417de8b289 save movie_files after successfully importing movies 2025-12-07 21:04:06 +01:00
maxid
c8347f19e9 save season_files after successfully importing tv shows 2025-12-07 21:00:52 +01:00
maxid
2a1bd7199d format frontend files 2025-12-07 20:43:49 +01:00
maxid
9bdb711195 remove unneeded imports 2025-12-07 20:43:03 +01:00
maxid
902f471917 add file criteria to documentation 2025-12-07 20:42:24 +01:00
maxid
161f9ca06a add documentation for import feature 2025-12-07 20:26:39 +01:00
maxid
4c60037a17 add frontend code for movie imports 2025-12-07 19:57:13 +01:00
maxid
d2e8d5eeb7 make movies be importable 2025-12-07 19:56:54 +01:00
maxid
ec93c1abc4 remove argument self 2025-12-07 18:54:57 +01:00
maxid
5cdfb96717 break up movie torrent import function 2025-12-07 18:44:45 +01:00
maxid
b5a7263489 fix bug that prevents notification when multiple video files were found in a movie's directory 2025-12-07 18:32:54 +01:00
maxid
dadd850524 extract functions and schemas from tv module 2025-12-07 18:30:23 +01:00
maxid
1f7dd795d1 remove year at the end of directory name in order to improve search results 2025-12-07 18:04:01 +01:00
maxid
087f4b9f15 prefix imported directories with a dot and ignore such directories 2025-12-07 17:55:32 +01:00
maxid
13b32e7104 add ability to import unknown tv shows 2025-12-07 17:38:37 +01:00
maxid
aca60cd9b7 make explicit any only warn instead of error 2025-12-07 17:37:10 +01:00
maxid
bfe4b05ca3 add spinner component 2025-12-07 17:36:45 +01:00
maxid
d65e00206d fix bug that causes saving of notifications to fail 2025-12-07 17:22:18 +01:00
maxid
247a562f7b remove stray code from notification schemas 2025-12-07 17:15:41 +01:00
maxid
9bbd6ee6df add show_id part to route 2025-12-07 17:13:56 +01:00
maxid
64bcd85287 make add_show endpoint always return the show 2025-12-07 16:05:10 +01:00
maxid
579ceb6e87 remove response model from route which doe not return anything 2025-12-07 15:31:29 +01:00
maxid
fdaaf099cb add ability to import existing media 2025-12-07 15:29:03 +01:00
Maximilian Dorninger
8e719ef94f Merge pull request #222 from maxdorninger/hotfix-fix-frontend-url
fix bug where extra slash is appended to the frontend_url
2025-12-07 00:52:45 +01:00
Maximilian Dorninger
1b03a57c3e Merge pull request #221 from maxdorninger/add-stats-to-dashboard
Add stats to dashboard
2025-12-07 00:29:40 +01:00
maxid
1f60db20f3 fix bug where extra slash is appended to the frontend_url, causing oauth to not work 2025-12-07 00:29:17 +01:00
maxid
5200821b60 update formatting of welcome message 2025-12-07 00:23:16 +01:00
maxid
92b9bff892 update package-lock.json 2025-12-06 23:49:40 +01:00
maxid
30f5ecd997 make eslint svelte resolve rule only warn 2025-12-06 23:33:53 +01:00
maxid
e117bed738 make counters animated with animejs 2025-12-06 23:33:03 +01:00
maxid
961c89e4e2 format frontend files 2025-12-06 22:39:09 +01:00
maxid
d1eee14b79 add card to inform user of new versions 2025-12-06 22:38:28 +01:00
maxid
e522fa9801 add stats to dashboard 2025-12-06 21:05:13 +01:00
maxid
11cafa800a add route which returns the total count of all episodes 2025-12-06 21:04:58 +01:00
maxid
bdeb60ab30 add default node url to cors urls of dev config 2025-12-06 19:49:47 +01:00
maxid
24b735439c move logfile to /dev/null to stop backend from reloading because of changes in the logfile 2025-12-06 19:37:44 +01:00
maxid
4eb78bd495 add healthcheck to db container in docker-compose.dev.yaml 2025-12-06 19:00:22 +01:00
maxid
751c4558a2 refactor and improve prowlarr search result processing 2025-12-06 15:57:51 +01:00
maxid
d8948e4462 make jackett torrent search more error resistant 2025-12-06 15:48:50 +01:00
maxid
0554fee85f make prowlarr torrent search more error resistant 2025-12-06 15:48:50 +01:00
Maximilian Dorninger
ffb86324c4 Merge pull request #219 from maxdorninger/copilot/add-user-delete-feature
Add ability to delete users
2025-11-29 18:22:34 +01:00
copilot-swe-agent[bot]
83932d6c99 Add delete button for user management in admin settings
Co-authored-by: maxdorninger <97409287+maxdorninger@users.noreply.github.com>
2025-11-29 16:59:45 +00:00
copilot-swe-agent[bot]
9e76fc9b8b Initial plan 2025-11-29 16:51:12 +00:00
Maximilian Dorninger
a36b161a38 Merge pull request #218 from maxdorninger/add-sponsor
add new sponsor @PuppiestDoggo
2025-11-29 17:38:43 +01:00
maxDorninger
3ac8b7588a add new sponsor @PuppiestDoggo 2025-11-29 17:34:23 +01:00
Maximilian Dorninger
c477e3d656 Merge pull request #217 from maxdorninger/improve-gh-workflows
stop saving of docker build cache when run by PRs from forks and build x86 and arm64 image in parallel
2025-11-29 17:19:23 +01:00
maxDorninger
ff0eae31dc use arm gh workflow runner instead of qemu with x86 runner 2025-11-29 17:13:00 +01:00
maxDorninger
5e3afee676 fix workflow file paths for metadata relay 2025-11-29 17:01:30 +01:00
maxDorninger
606109d629 stop saving of docker build cache when run by PRs from forks and build x86 and arm64 image in parallel 2025-11-29 16:57:23 +01:00
Maximilian Dorninger
cc9fecc09a Merge pull request #211 from colinmarc/basepath
improve ergonomics of `BASE_PATH`
2025-11-29 16:26:51 +01:00
Maximilian Dorninger
a85cf9f8af Clarify frontend_url trailing slash requirement
Update documentation to specify that the frontend URL must not include a trailing slash.
2025-11-29 16:26:24 +01:00
Maximilian Dorninger
85738a13e6 Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-29 16:09:53 +01:00
maxDorninger
bb5c61ac79 remove /web prefix from the frontend_url variable 2025-11-29 15:54:01 +01:00
maxDorninger
9b0fac3a8b improve format of documentation 2025-11-29 15:52:29 +01:00
Maximilian Dorninger
af13c0eb59 Revert changes to redirect URLs in main.py
The BASE_PATH is already configured when the FastAPI object is created.
2025-11-29 15:32:08 +01:00
Colin Marc
e62f6d4511 docs: clarify usage of frontend_url and BASE_PATH
I initially thought that `frontend_url` would control the prefix. The
docs on `BASE_PATH` weren't enough to get started, either.
2025-11-17 15:11:18 +01:00
Colin Marc
31a23dd781 fix: redirect correctly when using a base path
FastAPI doesn't automatically do this for you.
2025-11-17 14:53:20 +01:00
Maximilian Dorninger
4ea70a4f2e Merge pull request #199 from maxdorninger/rework-metadata-relay
remove valkey caching functionality from metadata relay
2025-11-02 21:53:51 +01:00
maxDorninger
252f8c2f65 remove valkey caching functionality from metadata relay in favour of caching by a cdn like cloudflare 2025-11-02 21:24:41 +01:00
Maximilian Dorninger
f9bceffa18 Merge pull request #198 from maxdorninger/fix-faulty-gh-workflow
fix the gh container build workflows
2025-11-02 20:55:52 +01:00
Maximilian Dorninger
5871400986 Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-02 20:53:50 +01:00
Maximilian Dorninger
a741baded5 Add step to set repository name to lowercase 2025-11-02 20:53:20 +01:00
maxDorninger
c38995579c fix the gh container build workflows 2025-11-02 20:44:19 +01:00
Maximilian Dorninger
ecdfc38470 Merge pull request #197 from maxdorninger/improve-file-logging
update logfile config and add env var to configure logfile location
2025-11-02 20:37:12 +01:00
maxDorninger
e8563b25e2 extend .gitignore; update logfile config and add env var to configure the location of the log file; add accompanying docs 2025-11-02 20:31:12 +01:00
Maximilian Dorninger
70ac88b2c2 Merge pull request #195 from maxdorninger/improve-container-startup-behaviour
Improve container startup behaviour and DB handling
2025-11-02 15:16:33 +01:00
Maximilian Dorninger
ea8c72ed8e Merge branch 'master' into improve-container-startup-behaviour 2025-11-02 14:29:57 +01:00
maxDorninger
780edf1aff update startup script to properly create a development mode variable 2025-11-02 14:19:26 +01:00
maxDorninger
b89320f25d prevent circular imports 2025-11-02 14:19:01 +01:00
Maximilian Dorninger
bc59eac490 remove log related to the removed init_db call in media_manager/main.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-02 13:41:17 +01:00
Maximilian Dorninger
92fdac1f7f Merge pull request #194 from JasperJuergensen/193-build-database-url
Build database URL using sqlalchemy URL create instead of string concatination
2025-11-02 13:23:35 +01:00
maxDorninger
a5c55cc591 remove database initialization function which conflicts with alembic 2025-11-02 13:22:07 +01:00
Jasper Jürgensen
45fc771c16 Build database URL using sqlalchemy URL create
String concatination can cause issues with special characters and can be
a risk for URL parameter injection.

fixes https://github.com/maxdorninger/MediaManager/issues/193
2025-11-02 12:20:35 +01:00
maxDorninger
faee405c8d improve startup script error handling 2025-11-02 11:56:24 +01:00
maxDorninger
2f35cebce3 add health check dependency for database service in docker-compose files 2025-11-02 11:49:22 +01:00
maxDorninger
8e9947652d add new sponsors 2025-11-01 20:42:02 +01:00
Maximilian Dorninger
9932331123 Merge pull request #184 from tylersatre/master
Improve local development experience
2025-11-01 19:16:33 +01:00
Maximilian Dorninger
910b49a66d Merge pull request #187 from maxdorninger/improve-github-workflows
Improve GitHub workflows
2025-11-01 19:16:16 +01:00
Maximilian Dorninger
c68462396b Remove unneeded docker image tags
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-01 19:15:06 +01:00
Maximilian Dorninger
568eaa88fe wrap version variable to prevent script injection
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-01 18:58:53 +01:00
maxDorninger
e2f3cfd173 only tag docker images as latest when they are created as part of a release; replace the hash value in the dev tag with the current date and the workflow run number; fix bug which would cause the build-and-push step to run even though the lint-backend-step failed 2025-11-01 18:41:31 +01:00
maxDorninger
221a4c3506 only push docker images and docs when a pr does not come from a fork 2025-11-01 16:29:05 +01:00
maxDorninger
8a04a7aa41 reformat docs for consistency 2025-11-01 13:28:20 +01:00
Tyler Satre
c71ba85c1c Merge branch 'master' of github.com:maxdorninger/MediaManager 2025-10-31 12:20:24 -04:00
Tyler Satre
106190d7ac Improve local dev experience 2025-10-31 12:19:23 -04:00
Maximilian Dorninger
32aae83875 Remove roadmap section from README
Removed the roadmap section detailing planned features and improvements.
2025-10-31 11:35:55 +01:00
363 changed files with 11669 additions and 7616 deletions

View File

@@ -53,5 +53,5 @@ YOUR CONFIG HERE
``` ```
- [ ] I understand, that without logs and/or screenshots and a detailed description of the problem, it is very hard to fix bugs. - [ ] I understand, that without logs and/or screenshots and a detailed description of the problem, it is very hard to fix bugs.
- [ ] I have checked the [documentation](https://maxdorninger.github.io/MediaManager/introduction.html) for help. - [ ] I have checked the [documentation](https://maxdorninger.github.io/MediaManager/) for help.
- [ ] I have searched the [issues](https://github.com/maxdorninger/MediaManager/issues) for similar issues and found none. - [ ] I have searched the [issues](https://github.com/maxdorninger/MediaManager/issues) for similar issues and found none.

25
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
- package-ecosystem: "npm"
directory: "/web"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5

View File

@@ -1,137 +0,0 @@
name: Build documentation
on:
push:
branches: [ "master" ]
paths:
- 'Writerside/**'
- '.github/workflows/build-docs.yml'
workflow_dispatch:
permissions:
contents: read
id-token: write
pages: write
env:
INSTANCE: 'Writerside/mm'
DOCKER_VERSION: '2025.04.8412'
ALGOLIA_APP_NAME: '5SXJTW5J6S'
ALGOLIA_INDEX_NAME: 'MediaManagerDocs'
CONFIG_JSON_PRODUCT: 'MM'
jobs:
build:
runs-on: ubuntu-latest
outputs:
algolia_artifact: ${{ steps.define-ids.outputs.algolia_artifact }}
artifact: ${{ steps.define-ids.outputs.artifact }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Define instance id and artifacts
id: define-ids
run: |
INSTANCE=${INSTANCE#*/}
INSTANCE_ID_UPPER=$(echo "$INSTANCE" | tr '[:lower:]' '[:upper:]')
ARTIFACT="webHelp${INSTANCE_ID_UPPER}2-all.zip"
ALGOLIA_ARTIFACT="algolia-indexes-${INSTANCE_ID_UPPER}.zip"
# Print the values
echo "INSTANCE_ID_UPPER: $INSTANCE_ID_UPPER"
echo "ARTIFACT: $ARTIFACT"
echo "ALGOLIA_ARTIFACT: $ALGOLIA_ARTIFACT"
# Set the environment variables and outputs
echo "INSTANCE_ID_UPPER=$INSTANCE_ID_UPPER" >> $GITHUB_ENV
echo "ARTIFACT=$ARTIFACT" >> $GITHUB_ENV
echo "ALGOLIA_ARTIFACT=$ALGOLIA_ARTIFACT" >> $GITHUB_ENV
echo "artifact=$ARTIFACT" >> $GITHUB_OUTPUT
echo "algolia_artifact=$ALGOLIA_ARTIFACT" >> $GITHUB_OUTPUT
- name: Build docs using Writerside Docker builder
uses: JetBrains/writerside-github-action@v4
with:
instance: ${{ env.INSTANCE }}
docker-version: ${{ env.DOCKER_VERSION }}
- name: Save artifact with build results
uses: actions/upload-artifact@v4
with:
name: docs
path: |
artifacts/${{ steps.define-ids.outputs.artifact }}
artifacts/report.json
artifacts/${{ steps.define-ids.outputs.algolia_artifact }}
retention-days: 7
test:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: docs
path: artifacts
- name: Test documentation
uses: JetBrains/writerside-checker-action@v1
with:
instance: ${{ env.INSTANCE }}
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
needs: [ build, test ]
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: docs
path: artifacts
- name: Unzip artifact
run: unzip -O UTF-8 -qq "artifacts/${{ needs.build.outputs.artifact }}" -d dir
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Package and upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: dir
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
publish-indexes:
needs: [ build, test, deploy ]
runs-on: ubuntu-latest
container:
image: registry.jetbrains.team/p/writerside/builder/algolia-publisher:2.0.32-3
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: docs
path: artifacts
- name: Unzip Algolia artifact
run: unzip -O UTF-8 -qq "artifacts/${{ needs.build.outputs.algolia_artifact }}" -d algolia-indexes
- name: Update Algolia Index
run: |
if [ -z "${{ secrets.ALGOLIA_KEY }}" ]; then
echo "ALGOLIA_KEY secret is not set in GitHub Secrets"
exit 1
else
env "algolia-key=${{ secrets.ALGOLIA_KEY }}" java -jar /opt/builder/help-publication-agent.jar \
update-index \
--application-name ${{ env.ALGOLIA_APP_NAME }} \
--index-name ${{ env.ALGOLIA_INDEX_NAME }} \
--product ${{ env.CONFIG_JSON_PRODUCT }} \
--index-directory algolia-indexes/ \
2>&1 | tee algolia-update-index-log.txt
fi

View File

@@ -4,8 +4,6 @@ on:
push: push:
branches: branches:
- master - master
tags:
- 'v*.*.*'
paths: paths:
- 'media_manager/**' - 'media_manager/**'
- 'alembic/**' - 'alembic/**'
@@ -29,13 +27,15 @@ on:
- 'tests/**' - 'tests/**'
- 'web/**' - 'web/**'
release:
types: [published]
workflow_dispatch: workflow_dispatch:
jobs: jobs:
lint-backend: lint-backend:
name: Lint Python Code name: Lint Python Code
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3 - uses: astral-sh/ruff-action@v3
@@ -50,7 +50,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: '24'
cache: 'npm' cache: 'npm'
cache-dependency-path: './web/package-lock.json' cache-dependency-path: './web/package-lock.json'
- name: Install dependencies - name: Install dependencies
@@ -60,14 +60,22 @@ jobs:
run: npm run lint run: npm run lint
working-directory: ./web working-directory: ./web
build-and-push: build-and-push:
needs: [lint-frontend] needs: [lint-frontend, lint-backend]
runs-on: ubuntu-latest strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: ubuntu-24.04
- arch: arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
permissions: permissions:
contents: read contents: read
packages: write packages: write
env:
QUAY_ORG: ${{ secrets.QUAY_ORG || github.repository_owner }}
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -76,8 +84,91 @@ jobs:
id: repo_name id: repo_name
run: echo "name=$(echo '${{ github.event.repository.name }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT run: echo "name=$(echo '${{ github.event.repository.name }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
- name: Set up QEMU - name: Set up Docker Buildx
uses: docker/setup-qemu-action@v3 uses: docker/setup-buildx-action@v3
with:
install: true
driver-opts: image=moby/buildkit:rootless
- name: Log in to GitHub Container Registry
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to Quay
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
uses: docker/login-action@v3
with:
registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_PASSWORD }}
- name: Extract version
id: version
run: |
if [[ "${{ github.event_name }}" == "release" ]]; then
VERSION="${{ github.event.release.tag_name }}"
elif [[ "${{ github.ref_type }}" == "tag" ]]; then
VERSION="${{ github.ref_name }}"
else
DATE_STAMP=$(date -u +'%Y.%m.%d')
VERSION="dev-${DATE_STAMP}-${{ github.run_number }}"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
VERSION_NO_V=${VERSION#v}
echo "version_no_v=$VERSION_NO_V" >> $GITHUB_OUTPUT
- name: Extract metadata (tags + labels)
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ github.repository_owner }}/${{ steps.repo_name.outputs.name }}/mediamanager
quay.io/${{ env.QUAY_ORG }}/mediamanager
tags: |
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
type=raw,value=${{ steps.version.outputs.version }},enable=${{ github.event_name != 'release' }}
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
flavor: |
suffix=-${{ matrix.arch }},onlatest=true
- name: Build and push arch image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
platforms: linux/${{ matrix.arch }}
push: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VERSION=${{ steps.version.outputs.version }}
BASE_PATH=
publish-manifests:
needs: build-and-push
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write
env:
QUAY_ORG: ${{ secrets.QUAY_ORG }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set repository name to lowercase
id: repo_name
run: echo "name=$(echo '${{ github.event.repository.name }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
@@ -89,14 +180,38 @@ jobs:
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker - name: Log in to Quay
uses: docker/login-action@v3
with:
registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_PASSWORD }}
- name: Extract version
id: version
run: |
if [[ "${{ github.event_name }}" == "release" ]]; then
VERSION="${{ github.event.release.tag_name }}"
elif [[ "${{ github.ref_type }}" == "tag" ]]; then
VERSION="${{ github.ref_name }}"
else
DATE_STAMP=$(date -u +'%Y.%m.%d')
VERSION="dev-${DATE_STAMP}-${{ github.run_number }}"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
VERSION_NO_V=${VERSION#v}
echo "version_no_v=$VERSION_NO_V" >> $GITHUB_OUTPUT
- name: Extract metadata (tags + labels)
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
with: with:
images: ghcr.io/${{ github.repository_owner }}/${{ steps.repo_name.outputs.name }}/mediamanager images: |
ghcr.io/${{ github.repository_owner }}/${{ steps.repo_name.outputs.name }}/mediamanager
quay.io/${{ env.QUAY_ORG }}/mediamanager
tags: | tags: |
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'master') && github.event_name != 'pull_request' }} type=raw,value=latest,enable=${{ github.event_name == 'release' }}
type=ref,event=tag type=raw,value=${{ steps.version.outputs.version }},enable=${{ github.event_name != 'release' }}
type=ref,event=branch type=ref,event=branch
type=ref,event=pr type=ref,event=pr
type=semver,pattern={{version}} type=semver,pattern={{version}}
@@ -104,28 +219,21 @@ jobs:
type=semver,pattern={{major}} type=semver,pattern={{major}}
type=sha type=sha
- name: Extract version - name: Create and push multi-arch manifests
id: version
run: | run: |
if [[ "${{ github.ref }}" == refs/tags/* ]]; then set -euo pipefail
VERSION=${GITHUB_REF#refs/tags/} mapfile -t TAGS <<< "${{ steps.meta.outputs.tags }}"
else for TAG in "${TAGS[@]}"; do
VERSION="dev-${GITHUB_SHA::7}" docker buildx imagetools create \
fi --tag "${TAG}" \
echo "version=$VERSION" >> $GITHUB_OUTPUT "${TAG}-amd64" \
"${TAG}-arm64"
done
- name: Build and push Docker image - name: Inspect image (GHCR)
uses: docker/build-push-action@v6 run: |
with: docker buildx imagetools inspect ghcr.io/${{ github.repository_owner }}/${{ steps.repo_name.outputs.name }}/mediamanager:${{ steps.version.outputs.version_no_v }}
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VERSION=${{ steps.version.outputs.version }}
BASE_PATH=
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/${{ steps.repo_name.outputs.name }}/mediamanager:buildcache
cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/${{ steps.repo_name.outputs.name }}/mediamanager:buildcache,mode=max
- name: Inspect image (Quay)
run: |
docker buildx imagetools inspect quay.io/${{ env.QUAY_ORG }}/mediamanager:${{ steps.version.outputs.version_no_v }}

View File

@@ -4,60 +4,178 @@ on:
push: push:
branches: branches:
- master - master
tags:
- 'v*.*.*'
paths: paths:
- 'metadata_relay/**' - 'metadata_relay/**'
- '.github/workflows/python-lint_metadata_relay.yaml' - '.github/workflows/build-push-metadata_relay.yml'
pull_request: pull_request:
paths: paths:
- 'metadata_relay/**' - 'metadata_relay/**'
- '.github/workflows/python-lint_metadata_relay.yaml' - '.github/workflows/build-push-metadata_relay.yml'
release:
types: [ published ]
workflow_dispatch: workflow_dispatch:
jobs: jobs:
lint-code: lint-code:
name: Lint Python Code name: Lint Python Code
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3 - uses: astral-sh/ruff-action@v3
with: with:
src: "./metadata_relay" src: "./metadata_relay"
build-and-push: build-and-push:
needs: lint-code needs: lint-code
runs-on: ubuntu-latest strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: ubuntu-24.04
- arch: arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
permissions: permissions:
contents: read contents: read
packages: write packages: write
env:
QUAY_ORG: ${{ secrets.QUAY_ORG || github.repository_owner }}
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up QEMU - name: Set repository name to lowercase
uses: docker/setup-qemu-action@v3 id: repo_name
run: echo "name=$(echo '${{ github.event.repository.name }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
install: true
driver-opts: image=moby/buildkit:rootless
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to Quay
uses: docker/login-action@v3
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
with:
registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_PASSWORD }}
- name: Extract version
id: version
run: |
if [[ "${{ github.event_name }}" == "release" ]]; then
VERSION="${{ github.event.release.tag_name }}"
elif [[ "${{ github.ref_type }}" == "tag" ]]; then
VERSION="${{ github.ref_name }}"
else
DATE_STAMP=$(date -u +'%Y.%m.%d')
VERSION="dev-${DATE_STAMP}-${{ github.run_number }}"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
VERSION_NO_V=${VERSION#v}
echo "version_no_v=$VERSION_NO_V" >> $GITHUB_OUTPUT
- name: Extract metadata (tags + labels)
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ github.repository_owner }}/${{ steps.repo_name.outputs.name }}/metadata_relay
quay.io/${{ env.QUAY_ORG }}/metadata_relay
tags: |
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
type=raw,value=${{ steps.version.outputs.version }},enable=${{ github.event_name != 'release' }}
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
flavor: |
suffix=-${{ matrix.arch }},onlatest=true
- name: Build and push arch image
uses: docker/build-push-action@v6
with:
context: ./metadata_relay
file: ./metadata_relay/Dockerfile
platforms: linux/${{ matrix.arch }}
push: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VERSION=${{ steps.version.outputs.version }}
publish-manifests:
needs: build-and-push
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write
env:
QUAY_ORG: ${{ secrets.QUAY_ORG || github.repository_owner }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set repository name to lowercase
id: repo_name
run: echo "name=$(echo '${{ github.event.repository.name }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry - name: Log in to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker - name: Log in to Quay
id: meta uses: docker/login-action@v3
uses: docker/metadata-action@v5
with: with:
images: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}/metadata_relay registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_PASSWORD }}
- name: Extract version
id: version
run: |
if [[ "${{ github.event_name }}" == "release" ]]; then
VERSION="${{ github.event.release.tag_name }}"
elif [[ "${{ github.ref_type }}" == "tag" ]]; then
VERSION="${{ github.ref_name }}"
else
DATE_STAMP=$(date -u +'%Y.%m.%d')
VERSION="dev-${DATE_STAMP}-${{ github.run_number }}"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
VERSION_NO_V=${VERSION#v}
echo "version_no_v=$VERSION_NO_V" >> $GITHUB_OUTPUT
- name: Extract metadata (tags + labels)
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ github.repository_owner }}/${{ steps.repo_name.outputs.name }}/metadata_relay
quay.io/${{ env.QUAY_ORG }}/metadata_relay
tags: | tags: |
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'master') && github.event_name != 'pull_request' }} type=raw,value=latest,enable=${{ github.event_name == 'release' }}
type=ref,event=tag type=raw,value=${{ steps.version.outputs.version }},enable=${{ github.event_name != 'release' }}
type=ref,event=branch type=ref,event=branch
type=ref,event=pr type=ref,event=pr
type=semver,pattern={{version}} type=semver,pattern={{version}}
@@ -65,14 +183,21 @@ jobs:
type=semver,pattern={{major}} type=semver,pattern={{major}}
type=sha type=sha
- name: Build and push Docker image - name: Create and push multi-arch manifests
uses: docker/build-push-action@v6 run: |
with: set -euo pipefail
context: ./metadata_relay mapfile -t TAGS <<< "${{ steps.meta.outputs.tags }}"
file: ./metadata_relay/Dockerfile for TAG in "${TAGS[@]}"; do
platforms: linux/amd64,linux/arm64 docker buildx imagetools create \
push: true --tag "${TAG}" \
tags: ${{ steps.meta.outputs.tags }} "${TAG}-amd64" \
labels: ${{ steps.meta.outputs.labels }} "${TAG}-arm64"
build-args: | done
VERSION=${{ steps.meta.outputs.version }}
- name: Inspect image (GHCR)
run: |
docker buildx imagetools inspect ghcr.io/${{ github.repository_owner }}/${{ steps.repo_name.outputs.name }}/metadata_relay:${{ steps.version.outputs.version_no_v }}
- name: Inspect image (Quay)
run: |
docker buildx imagetools inspect quay.io/${{ env.QUAY_ORG }}/metadata_relay:${{ steps.version.outputs.version_no_v }}

62
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,62 @@
name: Publish docs via GitHub Pages
on:
push:
branches:
- master
tags:
- v*
workflow_dispatch:
inputs:
set_default_alias:
description: 'Alias to set as default (e.g. latest, master)'
required: false
default: 'latest'
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure Git Credentials
run: |
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
- uses: actions/setup-python@v6
with:
python-version: 3.x
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
- uses: actions/cache@v4
with:
key: mkdocs-material-${{ env.cache_id }}
path: .cache
restore-keys: |
mkdocs-material-
- name: Install dependencies
run: pip install mkdocs-material mike
- name: Deploy (master)
if: github.ref == 'refs/heads/master'
run: |
mike deploy --push --update-aliases master
- name: Deploy (tag)
if: startsWith(github.ref, 'refs/tags/v')
run: |
version=${GITHUB_REF#refs/tags/}
mike deploy --push --update-aliases $version latest --title "$version"
mike set-default --push latest
- name: Set Default (Manual)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.set_default_alias != ''
run: |
mike set-default --push ${{ github.event.inputs.set_default_alias }}

9
.gitignore vendored
View File

@@ -11,6 +11,11 @@ res/*
media_manager/indexer/indexers/prowlarr.http media_manager/indexer/indexers/prowlarr.http
*.egg-info *.egg-info
.env .env
config
images
data
res
web/cache/ web/cache/
@@ -44,5 +49,5 @@ __pycache__
# Postgres # Postgres
/postgres /postgres
# Node modules # MkDocs
/node_modules/* site/

128
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
97409287+maxdorninger@users.noreply.github.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

33
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,33 @@
# Contributing to MediaManager
First off, thank you for considering contributing to MediaManager.
## Why
Following this guide helps me merge your PRs faster, prevents unnecessary back-and-forth and wasted effort.
## How to suggest a feature or enhancement
Open an issue which describes the feature you would like to
see, why you need it, and how it should work.
There we can discuss its scope and implementation.
## How to contribute
Generally, if you have any questions or need help on the implementation side of MediaManager,
just ask in the issue, or in a draft PR.
Also, see the contribution guide in the docs for information on how to setup the dev environment:
https://maxdorninger.github.io/MediaManager/
### For something that is a one or two line fix:
Make the change, and open a PR with a short description of what you changed and why.
### For something that is bigger than a one or two line fix:
Explain why you are making the change.
Be sure to give a rough overview on how your implementation works, and maybe any design decisions you made.
Also include any relevant limitations or trade-offs you made.
It's best to also open an issue first to discuss larger changes before you start working on them.

View File

@@ -1,31 +1,19 @@
FROM node:24-alpine AS frontend-build FROM node:24-alpine AS frontend-build
WORKDIR /frontend WORKDIR /frontend
ARG VERSION
ARG BASE_PATH=""
COPY web/package*.json ./ COPY web/package*.json ./
RUN npm ci && npm cache clean --force RUN npm ci && npm cache clean --force
COPY web/ ./ COPY web/ ./
RUN env PUBLIC_VERSION=${VERSION} PUBLIC_API_URL=${BASE_PATH} BASE_PATH=${BASE_PATH}/web npm run build
FROM ghcr.io/astral-sh/uv:python3.13-trixie-slim
ARG VERSION ARG VERSION
ARG BASE_PATH="" ARG BASE_PATH=""
LABEL author="github.com/maxdorninger" RUN env PUBLIC_VERSION=${VERSION} PUBLIC_API_URL=${BASE_PATH} BASE_PATH=${BASE_PATH}/web npm run build
LABEL version=${VERSION}
LABEL description="Docker image for MediaManager"
ENV PUBLIC_VERSION=${VERSION} \ FROM ghcr.io/astral-sh/uv:python3.13-trixie-slim AS base
CONFIG_DIR="/app/config"\
BASE_PATH=${BASE_PATH}\
FRONTEND_FILES_DIR="/app/web/build"
WORKDIR /app
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y ca-certificates bash libtorrent21 gcc bc locales postgresql media-types mailcap curl gzip unzip tar 7zip bzip2 unar && \ apt-get install -y ca-certificates bash libtorrent21 gcc bc locales postgresql media-types mailcap curl gzip unzip tar 7zip bzip2 unar gosu && \
apt-get clean && \ apt-get clean && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
@@ -34,17 +22,46 @@ RUN locale-gen
ENV LANG=en_US.UTF-8 ENV LANG=en_US.UTF-8
ENV LC_ALL=en_US.UTF-8 ENV LC_ALL=en_US.UTF-8
COPY pyproject.toml uv.lock ./ # Create a non-root user and group
RUN uv sync --locked RUN groupadd -g 1000 mediamanager && \
useradd -m -u 1000 -g mediamanager mediamanager
COPY --chmod=755 mediamanager-startup.sh . FROM base AS dependencies
COPY config.example.toml . WORKDIR /app
COPY media_manager ./media_manager # Ensure mediamanager owns /app
COPY alembic ./alembic RUN chown -R mediamanager:mediamanager /app
COPY alembic.ini .
COPY --from=frontend-build /frontend/build /app/web/build USER mediamanager
ENV UV_CACHE_DIR=/home/mediamanager/.cache/uv \
UV_LINK_MODE=copy
COPY --chown=mediamanager:mediamanager pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/home/mediamanager/.cache/uv,uid=1000,gid=1000 \
uv sync --locked
FROM dependencies AS app
ARG VERSION
ARG BASE_PATH=""
LABEL author="github.com/maxdorninger"
LABEL version=${VERSION}
LABEL description="Docker image for MediaManager"
USER root
ENV PUBLIC_VERSION=${VERSION} \
CONFIG_DIR="/app/config" \
BASE_PATH=${BASE_PATH} \
FRONTEND_FILES_DIR="/app/web/build"
COPY --chown=mediamanager:mediamanager --chmod=755 mediamanager-startup.sh .
COPY --chown=mediamanager:mediamanager config.example.toml .
COPY --chown=mediamanager:mediamanager media_manager ./media_manager
COPY --chown=mediamanager:mediamanager alembic ./alembic
COPY --chown=mediamanager:mediamanager alembic.ini .
HEALTHCHECK CMD curl -f http://localhost:8000${BASE_PATH}/api/v1/health || exit 1 HEALTHCHECK CMD curl -f http://localhost:8000${BASE_PATH}/api/v1/health || exit 1
EXPOSE 8000 EXPOSE 8000
CMD ["/app/mediamanager-startup.sh"] CMD ["/app/mediamanager-startup.sh"]
FROM app AS production
COPY --chown=mediamanager:mediamanager --from=frontend-build /frontend/build /app/web/build

49
Makefile Normal file
View File

@@ -0,0 +1,49 @@
SHELL := /bin/bash
# Docker Compose command (override if needed)
COMPOSE ?= docker compose
DC := $(COMPOSE) -f docker-compose.dev.yaml
# Log args passthrough, e.g.:
# make logs ARGS="--follow --tail=100"
ARGS ?=
# Service names (override if your compose uses different names)
APP_SVC ?= mediamanager
FRONTEND_SVC ?= frontend
.PHONY: help up down logs ps restartapp frontend
help:
@echo "Usage:"
@echo " All commands run using the dev docker compose file ($(DEV_FILE))"
@echo ""
@echo " make up # Development environment up, runs with --build flag to rebuild if necessary"
@echo " make down # Development environment down"
@echo " make logs ARGS=\"...\" # (Optional) Set ARGS like \"--follow --tail=100\""
@echo " make ps | restart # Check status or restart containers"
@echo " make app # Shell into $(APP_SVC) container"
@echo " make frontend # Shell into $(FRONTEND_SVC) container"
# Core lifecycle
up:
$(DC) up -d --build
down:
$(DC) down
logs:
$(DC) logs $(ARGS)
ps:
$(DC) ps
restart:
$(DC) restart
# Interactive shells (prefer bash, fallback to sh)
app:
@$(DC) exec -it $(APP_SVC) bash 2>/dev/null || $(DC) exec -it $(APP_SVC) sh
frontend:
@$(DC) exec -it $(FRONTEND_SVC) bash 2>/dev/null || $(DC) exec -it $(FRONTEND_SVC) sh

View File

@@ -1,7 +1,7 @@
<br /> <br />
<div align="center"> <div align="center">
<a href="https://maxdorninger.github.io/MediaManager"> <a href="https://maxdorninger.github.io/MediaManager/">
<img src="https://raw.githubusercontent.com/maxdorninger/MediaManager/refs/heads/master/Writerside/images/logo.svg" alt="Logo" width="260" height="260"> <img src="https://raw.githubusercontent.com/maxdorninger/MediaManager/refs/heads/master/web/static/logo.svg" alt="Logo" width="260" height="260">
</a> </a>
<h3 align="center">MediaManager</h3> <h3 align="center">MediaManager</h3>
@@ -9,7 +9,7 @@
<p align="center"> <p align="center">
Modern management system for your media library Modern management system for your media library
<br /> <br />
<a href="https://maxdorninger.github.io/MediaManager/introduction.html"><strong>Explore the docs »</strong></a> <a href="https://maxdorninger.github.io/MediaManager/"><strong>Explore the docs »</strong></a>
<br /> <br />
<a href="https://github.com/maxdorninger/MediaManager/issues/new?labels=bug&template=bug_report.md">Report Bug</a> <a href="https://github.com/maxdorninger/MediaManager/issues/new?labels=bug&template=bug_report.md">Report Bug</a>
&middot; &middot;
@@ -18,26 +18,24 @@
</div> </div>
MediaManager is modern software to manage your TV and movie library. It is designed to be a replacement for Sonarr, MediaManager is the modern, easy-to-use successor to the fragmented "Arr" stack. Manage, discover, and automate your TV and movie collection in a single, simple interface.
Radarr, Overseer, and Jellyseer.
It supports TVDB and TMDB for metadata, supports OIDC and OAuth 2.0 for authentication and supports Prowlarr and
Jackett.
MediaManager is built first and foremost for deployment with Docker, making it easy to set up.
It also provides an API to interact with the software programmatically, allowing for automation and integration with Key features:
other services. - support for OAuth/OIDC
- support for TVDB and TMDB
- made to be deployed with Docker
## Quick Start ## Quick Start
```sh ```sh
wget -O docker-compose.yaml https://raw.githubusercontent.com/maxdorninger/MediaManager/refs/heads/master/docker-compose.yaml wget -O docker-compose.yaml https://github.com/maxdorninger/MediaManager/releases/latest/download/docker-compose.yaml
mkdir config mkdir config
wget -O ./config/config.toml https://raw.githubusercontent.com/maxdorninger/MediaManager/refs/heads/master/config.example.toml wget -O ./config/config.toml https://github.com/maxdorninger/MediaManager/releases/latest/download/config.example.toml
# you probably need to edit the config.toml file in the ./config directory, for more help see the documentation # you probably need to edit the config.toml file in the ./config directory, for more help see the documentation
docker compose up -d docker compose up -d
``` ```
### [View the docs for installation instructions and more](https://maxdorninger.github.io/MediaManager/configuration-overview.html#configuration-overview) ### [View the docs for installation instructions and more](https://maxdorninger.github.io/MediaManager/)
## Support MediaManager ## Support MediaManager
@@ -56,56 +54,34 @@ docker compose up -d
<a href="https://github.com/brandon-dacrib"><img src="https://github.com/brandon-dacrib.png" width="80px" alt="Brandon P." /></a>&nbsp;&nbsp; <a href="https://github.com/brandon-dacrib"><img src="https://github.com/brandon-dacrib.png" width="80px" alt="Brandon P." /></a>&nbsp;&nbsp;
<a href="https://github.com/SeimusS"><img src="https://github.com/SeimusS.png" width="80px" alt="SeimusS" /></a>&nbsp;&nbsp; <a href="https://github.com/SeimusS"><img src="https://github.com/SeimusS.png" width="80px" alt="SeimusS" /></a>&nbsp;&nbsp;
<a href="https://github.com/HadrienKerlero"><img src="https://github.com/HadrienKerlero.png" width="80px" alt="HadrienKerlero" /></a>&nbsp;&nbsp; <a href="https://github.com/HadrienKerlero"><img src="https://github.com/HadrienKerlero.png" width="80px" alt="HadrienKerlero" /></a>&nbsp;&nbsp;
<a href="https://github.com/keyxmakerx"><img src="https://github.com/keyxmakerx.png" width="80px" alt="keyxmakerx" /></a>&nbsp;&nbsp;
<a href="https://github.com/LITUATUI"><img src="https://github.com/LITUATUI.png" width="80px" alt="LITUATUI" /></a>&nbsp;&nbsp;
<a href="https://buymeacoffee.com/maxdorninger"><img src="https://cdn.buymeacoffee.com/uploads/profile_pictures/default/v2/B6CDBD/NI.png" width="80px" alt="Nicolas" /></a>&nbsp;&nbsp;
<a href="https://buymeacoffee.com/maxdorninger"><img src="https://cdn.buymeacoffee.com/uploads/profile_pictures/default/v2/DEBBB9/JO.png" width="80px" alt="Josh" /></a>&nbsp;&nbsp;
<a href="https://buymeacoffee.com/maxdorninger"><img src="https://cdn.buymeacoffee.com/uploads/profile_pictures/2025/11/2VeQ8sTGPhj4tiLy.jpg" width="80px" alt="PuppiestDoggo" /></a>&nbsp;&nbsp;
<a href="https://github.com/seferino-fernandez"><img src="https://avatars.githubusercontent.com/u/5546622" width="80px" alt="Seferino" /></a>&nbsp;&nbsp;
<a href="https://buymeacoffee.com/maxdorninger"><img src="https://cdn.buymeacoffee.com/uploads/profile_pictures/default/v2/EC9689/SY.png" width="80px" alt="syn" /></a>&nbsp;&nbsp;
## Star History ## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=maxdorninger/MediaManager&type=Date)](https://www.star-history.com/#maxdorninger/MediaManager&Date) [![Star History Chart](https://api.star-history.com/svg?repos=maxdorninger/MediaManager&type=Date)](https://www.star-history.com/#maxdorninger/MediaManager&Date)
## Roadmap
- [x] support for more torrent indexers
- [x] fully automatic downloads
- [x] add tests
- [x] add more logs/errors
- [x] make API return proper error codes
- [x] optimize images for web in the backend
- [x] responsive ui
- [x] automatically update metadata of shows
- [x] automatically download new seasons/episodes of shows
- [x] add fallback to just copy files if hardlinks don't work
- [x] add check at startup if hardlinks work
- [x] create separate metadata relay service, so that api keys for TMDB and TVDB are not strictly needed
- [x] support for movies
- [x] expand README with more information and a quickstart guide
- [x] improve reliability of scheduled tasks
- [x] add notification system
- [x] add sequence diagrams to the documentation
- [x] add usenet support
- [x] provide example configuration files
- [x] make media sorting algorithm configurable
- [x] add support for transmission
- [x] add support for configuration via toml/yaml config file
- [ ] add in-depth documentation on the architecture of the codebase
- [ ] make indexer module multithreaded
- [ ] add delete button for movies/TV shows
- [ ] rework prowlarr module (select which indexers to use, etc.)
- [ ] _maybe_ rework the logo
See the [open issues](hhttps://maxdorninger.github.io/MediaManager/issues) for a full list of proposed features (and known issues).
## Screenshots ## Screenshots
![Screenshot 2025-07-02 174732](https://github.com/user-attachments/assets/49fc18aa-b471-4be8-983e-c0ab240dfb73) <img width="1902" height="887" alt="Screenshot 2025-12-08 at 00 44 26" src="https://github.com/user-attachments/assets/4c3fbc73-6358-44ac-a948-0eb35ab116fd" />
![Screenshot 2025-07-02 174342](https://github.com/user-attachments/assets/3a38953d-d0fa-4a7e-83d0-dd6e6427681c) <img width="1875" height="882" alt="Screenshot 2025-12-08 at 00 46 39" src="https://github.com/user-attachments/assets/75645cb6-236b-4bef-9a21-76bfd6d530f9" />
![Screenshot 2025-07-02 174616](https://github.com/user-attachments/assets/c3af4be8-b873-448c-8a4d-0d5db863aec7) ![Screenshot 2025-07-02 174616](https://github.com/user-attachments/assets/c3af4be8-b873-448c-8a4d-0d5db863aec7)
![Screenshot 2025-07-02 174416](https://github.com/user-attachments/assets/0d50f53b-64da-4243-8408-1d6fc85fe81b)
<img width="1883" height="890" alt="Screenshot 2025-12-08 at 00 48 23" src="https://github.com/user-attachments/assets/c5789559-5aa7-4c26-9442-a2b156e99b1c" />
![Screenshot 2025-06-28 222908](https://github.com/user-attachments/assets/193e1afd-dabb-42a2-ab28-59f2784371c7) ![Screenshot 2025-06-28 222908](https://github.com/user-attachments/assets/193e1afd-dabb-42a2-ab28-59f2784371c7)
## Developer Quick Start ## Developer Quick Start
For the developer guide see the [Developer Guide](https://maxdorninger.github.io/MediaManager/developer-guide.html). For the developer guide see the [Developer Guide](https://maxdorninger.github.io/MediaManager/).
<!-- LICENSE --> <!-- LICENSE -->
@@ -118,5 +94,9 @@ Distributed under the AGPL 3.0. See `LICENSE.txt` for more information.
## Acknowledgments ## Acknowledgments
Thanks to DigitalOcean for sponsoring the project!
[![DigitalOcean Referral Badge](https://web-platforms.sfo2.cdn.digitaloceanspaces.com/WWW/Badge%201.svg)](https://www.digitalocean.com/?refcode=4edf05429dca&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge)
* [Thanks to Pawel Czerwinski for the image on the login screen](https://unsplash.com/@pawel_czerwinski) * [Thanks to Pawel Czerwinski for the image on the login screen](https://unsplash.com/@pawel_czerwinski)

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE instance-profile
SYSTEM "https://resources.jetbrains.com/writerside/1.0/product-profile.dtd">
<instance-profile id="Writerside_libraries"
name="Writerside_libraries" is-library="true" start-page="notes.topic">
<toc-element topic="notes.topic"/>
</instance-profile>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE categories
SYSTEM "https://resources.jetbrains.com/writerside/1.0/categories.dtd">
<categories>
<category id="wrs" name="Writerside documentation" order="1"/>
</categories>

View File

@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE buildprofiles SYSTEM "https://resources.jetbrains.com/writerside/1.0/build-profiles.dtd">
<buildprofiles xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://resources.jetbrains.com/writerside/1.0/build-profiles.xsd">
<sitemap priority="0.35" change-frequency="monthly"/>
<variables>
</variables>
<build-profile instance="mm">
<variables>
<noindex-content>false</noindex-content>
<custom-favicons>favicon.ico</custom-favicons>
<header-logo>logo.svg</header-logo>
<download-title>Get MediaManager</download-title>
<download-page>https://maxdorninger.github.io/MediaManager/configuration-overview.html</download-page>
<showDownloadButton>true</showDownloadButton>
<product-web-url>https://github.com/maxdorninger/MediaManager</product-web-url>
<algolia-id>5SXJTW5J6S</algolia-id>
<algolia-api-key>ed89153df615ded46c214c9390539f3b</algolia-api-key>
<algolia-show-logo>true</algolia-show-logo>
<algolia-index>MediaManagerDocs</algolia-index>
</variables>
<footer>
<copyright>Maximilian Dorninger 2025</copyright>
<social type="github" href="https://github.com/maxdorninger/MediaManager" />
<social type="reddit" href="https://www.reddit.com/r/MediaManager/" />
<link href="https://github.com/maxdorninger/MediaManager/issues">Report an Issue</link>
<link href="https://github.com/maxdorninger">Maximilian Dorninger</link>
<link href="https://github.com/sponsors/maxdorninger">Donate</link>
</footer>
</build-profile>
</buildprofiles>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

View File

@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE instance-profile
SYSTEM "https://resources.jetbrains.com/writerside/1.0/product-profile.dtd">
<instance-profile id="mm"
name="MediaManager" start-page="Introduction.topic">
<toc-element topic="Introduction.topic"/>
<toc-element topic="configuration-overview.md"/>
<toc-element topic="User-Guide.md"/>
<toc-element topic="Configuration.md">
<toc-element topic="configuration-backend.md"/>
<toc-element topic="authentication-setup.md"/>
<toc-element topic="database-configuration.md"/>
<toc-element topic="download-client-configuration.md"/>
<toc-element topic="Indexer-Settings.md"/>
<toc-element topic="Scoring-Rulesets.md"/>
<toc-element topic="Notifications.md"/>
<toc-element topic="Custom-Libraries.md"/>
</toc-element>
<toc-element topic="Advanced-Features.md">
<toc-element topic="qBittorrent-Category.md"/>
<toc-element topic="Base-Path.md"/>
<toc-element topic="metadata-provider-configuration.md"/>
<toc-element topic="Custom-port.md"/>
</toc-element>
<toc-element topic="troubleshooting.md"/>
<toc-element topic="developer-guide.md"/>
<toc-element topic="api-reference.md"/>
<toc-element topic="Screenshots.md"/>
</instance-profile>

View File

@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE rules SYSTEM "https://resources.jetbrains.com/writerside/1.0/redirection-rules.dtd">
<rules>
<!-- format is as follows
<rule id="<unique id>">
<accepts>page.html</accepts>
</rule>
-->
<rule id="63c34f28">
<description>Created after removal of "Installation" from MediaManager</description>
<accepts>installation.html</accepts>
</rule>
<rule id="440cb672">
<description>Created after removal of "About MediaManager" from MediaManager</description>
<accepts>starter-topic.html</accepts>
</rule>
<rule id="666c8c31">
<description>Created after removal of "User Guide" from MediaManager</description>
<accepts>user-guide.html</accepts>
</rule>
<rule id="6822e619">
<description>Created after removal of "Quick Start Guide" from MediaManager</description>
<accepts>Quick-Start-Guide.html</accepts>
</rule>
<rule id="570cb9d1">
<description>Created after removal of "user-guide" from MediaManager</description>
<accepts>user-guide.html</accepts>
</rule>
<rule id="7cb7d801">
<description>Created after removal of "MetadataRelay" from MediaManager</description>
<accepts>MetadataRelay.html</accepts>
</rule>
<rule id="52a1fe89">
<description>Created after removal of "Frontend" from MediaManager</description>
<accepts>configuration-frontend.html</accepts>
</rule>
<rule id="6242074">
<description>Created after removal of "Reverse Proxy" from MediaManager</description>
<accepts>Reverse-Proxy.html</accepts>
</rule>
</rules>

View File

@@ -1,4 +0,0 @@
# Advanced Features
The features in this section are not required to run MediaManager and serve their purpose in very specific environments,
but they can enhance your experience and provide additional functionality.

View File

@@ -1,20 +0,0 @@
# Base Path
Unfortunately you need to build the docker container, to configure a url base path. This is because
SvelteKit needs to know the base path at build time.
## Docker Build Arguments
When building the Docker image for the frontend, you can pass build arguments to set the base path and version.
### `BASE_PATH`
Sets the base url path, it must begin with a slash and not end with one. Example (in build command):
- clone the repo
- cd into the repo's root directory
- `docker build --build-arg BASE_URL=/media -f Dockerfile .`
### `VERSION`
Sets the version variable in the container. This isn't strictly necessary, but it can be useful for debugging or versioning purposes.
Example: `docker build --build-arg VERSION=locally-built -f web/Dockerfile .`

View File

@@ -1,73 +0,0 @@
# Configuration
MediaManager uses a TOML configuration file (`config.toml`) for all backend settings.
This centralized configuration approach makes it easier to manage, backup, and share your MediaManager setup.
Frontend settings are configured through environment variables in your `docker-compose.yaml` file.
## Configuration File Location
Your `config.toml` file should be in the directory that's mounted to `/app/config/config.toml` inside the container:
```yaml
volumes:
- ./config:/app/config
```
Though you can change the location of the configuration file's directory by setting the `CONFIG_DIR` env variable to
another path,
e.g. `/etc/mediamanager/`.
## Configuration Sections
The configuration is organized into the following sections:
- `[misc]` - General settings
- `[database]` - Database settings
- `[auth]` - Authentication settings
- `[notifications]` - Notification settings (Email, Gotify, Ntfy, Pushover)
- `[torrents]` - Download client settings (qBittorrent, Transmission, SABnzbd)
- `[indexers]` - Indexer settings (Prowlarr and Jackett )
- `[metadata]` - TMDB and TVDB settings
## Configuring Secrets
For sensitive information like API keys, passwords, and secrets, you _should_ use environment variables.
You can actually set every configuration value through environment variables.
For example, to set the `token_secret` value for authentication, with a .toml file you would use:
```toml
[auth]
token_secret = "your_super_secret_key_here"
```
But you can also set it through an environment variable:
```
MEDIAMANAGER_AUTH__TOKEN_SECRET = "your_super_secret_key_here"
```
or another example with the OIDC client secret:
```toml
[auth]
...
[auth.openid_connect]
client_secret = "your_client_secret_from_provider"
```
env variable:
```
MEDIAMANAGER_AUTH__OPENID_CONNECT__CLIENT_SECRET = "your_client_secret_from_provider"
```
So for every config "level", you basically have to take the name of the value and prepend it with the section names in
uppercase with 2 underscores as delimiters and `MEDIAMANAGER_` as the prefix.
<warning>Note that not every env variable starts with <code>MEDIAMANAGER_</code>,
this prefix only applies to env variables which replace/overwrite values in the config file.
Variables like the <code>CONFIG_DIR</code> env variable must not be prefixed.
</warning>

View File

@@ -1,4 +0,0 @@
# Custom port
With the env variable `PORT`, you can change the port that MediaManager listens on. The default port is `8000`.
Note that this setting only works if you are using the Docker Image.

View File

@@ -1,60 +0,0 @@
# Indexers
Indexer settings are configured in the `[indexers]` section of your `config.toml` file. MediaManager supports both
Prowlarr and Jackett as indexer providers.
## Prowlarr (`[indexers.prowlarr]`)
- `enabled`
Set to `true` to enable Prowlarr. Default is `false`.
- `url`
Base URL of your Prowlarr instance.
- `api_key`
API key for Prowlarr. You can find this in Prowlarr's settings under General.
- `reject_torrents_on_url_error`
Set to `true` to reject torrents if there is a URL error when fetching from Prowlarr. Until MediaManager v1.9.0 the
default behavior was `false`, but from v1.9.0 onwards the default is `true`. It's recommended to set this to `true` to
avoid adding possibly invalid torrents.
## Jackett (`[indexers.jackett]`)
- `enabled`
Set to `true` to enable Jackett. Default is `false`.
- `url`
Base URL of your Jackett instance.
- `api_key`
API key for Jackett. You can find this in Jackett's dashboard.
- `indexers`
List of indexer names to use with Jackett. You can specify which indexers Jackett should search through.
## Example Configuration
Here's a complete example of the indexers section in your `config.toml`:
```toml
[indexers]
[indexers.prowlarr]
enabled = true
url = "http://prowlarr:9696"
api_key = "your_prowlarr_api_key"
[indexers.jackett]
enabled = false
url = "http://jackett:9117"
api_key = "your_jackett_api_key"
indexers = ["1337x", "rarbg"]
```

View File

@@ -1,62 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE topic
SYSTEM "https://resources.jetbrains.com/writerside/1.0/xhtml-entities.dtd">
<topic xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://resources.jetbrains.com/writerside/1.0/topic.v2.xsd"
title="Introduction" id="Introduction">
<section-starting-page>
<title>MediaManager</title>
<description>
MediaManager is modern software to manage your TV and movie library. It is designed to be a replacement for Sonarr,
Radarr, Overseer, Unpackerr and Jellyseer.
It supports TVDB and TMDB for metadata, supports OIDC and OAuth 2.0 for authentication and supports Prowlarr and
Jackett.
</description>
<spotlight>
<card href="configuration-overview.md" badge="start"
summary="Installation Guide"/>
<card href="Configuration.md" badge="settings"
summary="Configuration Overview"/>
</spotlight>
<primary>
<title>Other Topics</title>
<card href="developer-guide.md" badge="development"
summary="Developer Guide"/>
<card href="troubleshooting.md" badge="support"
summary="Troubleshooting Guide"/>
<card href="Advanced-Features.md"
summary="Advanced Features"/>
<card href="Screenshots.md"
summary="Screenshots"/>
</primary>
<secondary>
<title>Support MediaManager &amp; Maximilian Dorninger</title>
<card href="https://github.com/sponsors/maxdorninger"
summary="Sponsor me on Github Sponsors :)"/>
<card href="https://buymeacoffee.com/maxdorninger"
summary="Buy me a coffee :)"/>
</secondary>
<misc>
<cards narrow="true">
<title>MediaManager Sponsors</title>
<card href="https://github.com/ldrrp"
summary="@ldrrp" image="https://github.com/ldrrp.png" />
<card href="https://fosstodon.org/@aljazmerzen"
summary="@aljamerzen" image="https://cdn.fosstodon.org/accounts/avatars/107/626/197/378/907/137/original/e93602705171fd0a.jpg"/>
<card href="https://github.com/SeimusS"
summary="@SeimusS" image="https://github.com/SeimusS.png" />
<card href="https://github.com/HadrienKerlero"
summary="@HadrienKerlero" image="https://github.com/HadrienKerlero.png" />
<card href="https://github.com/brandon-dacrib"
summary="@brandon-dacrib" image="https://github.com/brandon-dacrib.png" />
</cards>
<links>
<group>
<title>MediaManager Repository</title>
<a href="https://github.com/maxdorninger/MediaManager"
summary="MediaManager Repository"/>
</group>
</links>
</misc>
</section-starting-page>
</topic>

View File

@@ -1,141 +0,0 @@
# Scoring Rulesets
Scoring rulesets in MediaManager allow you to flexibly control which releases are preferred or avoided when searching
for media. Each ruleset is a collection of scoring rules that can be assigned to one or more libraries. When
MediaManager evaluates releases, it applies the relevant ruleset(s) to adjust the score of each result, influencing
which releases are selected for download.
## How Rulesets Work
- **Rulesets** are defined in the configuration and contain a list of rule names and the libraries they apply to.
- **Scoring rules** can target keywords in release titles or specific indexer flags.
- When searching for a release, MediaManager checks which library the media belongs to and applies the corresponding
ruleset.
## Rules
Rules define how MediaManager scores releases based on their titles or indexer flags. You can create rules that:
- Prefer releases with specific codecs (e.g., H.265 over H.264).
- Avoid releases with certain keywords (e.g., "CAM", "TS", "Nuked").
- Reject releases that do not meet certain criteria (e.g., non-freeleech releases).
- and more.
<note>
The keywords and flags are compared case-insensitively.
</note>
### Title Rules
Title rules allow you to adjust the score of a release based on the presence (or absence) of specific keywords in the release title. This is useful for preferring or avoiding certain encodings, sources, or other characteristics that are typically included in release names.
Each title rule consists of:
- `name`: A unique identifier for the rule.
- `keywords`: A list of keywords to search for in the release title.
- `score_modifier`: The amount to add or subtract from the score if a keyword matches.
- `negate`: If true, the rule applies when none of the keywords are present.
#### Examples for Title Rules
```toml
[[indexers.title_scoring_rules]]
name = "prefer_h265"
keywords = ["h265", "hevc", "x265"]
score_modifier = 100
negate = false
[[indexers.title_scoring_rules]]
name = "avoid_cam"
keywords = ["cam", "ts"]
score_modifier = -10000
negate = false
```
- The first rule increases the score for releases containing "h265", "hevc", or "x265".
- The second rule heavily penalizes releases containing "cam" or "ts".
If `negate` is set to `true`, the `score_modifier` is applied only if none of the keywords are found in the title.
### Indexer Flag Rules
Indexer flag rules adjust the score based on flags provided by the indexer (such as `freeleech`, `nuked`, etc). These flags are often used to indicate special properties or warnings about a release.
Each indexer flag rule consists of:
- `name`: A unique identifier for the rule.
- `flags`: A list of indexer flags to match.
- `score_modifier`: The amount to add or subtract from the score if a flag matches.
- `negate`: If true, the rule applies when none of the flags are present.
#### Examples for Indexer Flag Rules
```toml
[[indexers.indexer_flag_scoring_rules]]
name = "reject_non_freeleech"
flags = ["freeleech", "freeleech75"]
score_modifier = -10000
negate = true
[[indexers.indexer_flag_scoring_rules]]
name = "reject_nuked"
flags = ["nuked"]
score_modifier = -10000
negate = false
```
- The first rule penalizes releases that do **not** have the "freeleech" or "freeleech75" flag.
- The second rule penalizes releases that are marked as "nuked".
If `negate` is set to `true`, the `score_modifier` is applied only if none of the flags are present on the release.
## Example
```toml
[[indexers.scoring_rule_sets]]
name = "default"
libraries = ["ALL_TV", "ALL_MOVIES"]
rule_names = ["prefer_h265", "avoid_cam", "reject_nuked"]
[[indexers.scoring_rule_sets]]
name = "strict_quality"
libraries = ["ALL_MOVIES"]
rule_names = ["prefer_h265", "avoid_cam", "reject_non_freeleech"]
```
## Libraries
The libraries that are mentioned in the preceding example are explained in greater detail in
the [Library config section](Custom-Libraries.md).
### Special Libraries
You can use special library names in your rulesets:
- `ALL_TV`: Applies the ruleset to all TV libraries.
- `ALL_MOVIES`: Applies the ruleset to all movie libraries.
- `Default`: Applies the ruleset to all media that is not part of a custom library.
This allows you to set global rules for all TV or movie content, or provide fallback rules for uncategorized media.
<tip>
You don't need to create lots of libraries with different directories, multiple libraries can share the same directory.
You can set multiple (unlimited) libraries to the default directory `/data/movies` or `/data/tv` and use different
rulesets with them.
</tip>
## Relation to Sonarr/Radarr Profiles
MediaManager's scoring rules and rulesets system is an alternative to Sonarr's Quality, Custom, and Release Profiles. I
designed this system with the goal of being more intuitive and flexible, since I noticed that a lot of people are
overwhelmed by Sonarrs/Radarrs system.
- **Quality Profiles**: Use scoring rules to prefer or avoid certain codecs, resolutions, or other quality indicators.
- **Custom/Release Profiles**: Use title or flag-based rules to match or exclude releases based on keywords or indexer
flags.
This approach provides a powerful and transparent way to fine-tune your automation.

View File

@@ -1,17 +0,0 @@
# Screenshots
<note> MediaManager also supports darkmode!</note>
![screenshot-dashboard.png](screenshot-dashboard.png)
![screenshot-tv-dashboard.png](screenshot-tv-dashboard.png)
![screenshot-download-season.png](screenshot-download-season.png)
![screenshot-request-season.png](screenshot-request-season.png)
![screenshot-tv-torrents.png](screenshot-tv-torrents.png)
![screenshot-settings.png](screenshot-settings.png)
![screenshot-login.png](screenshot-login.png)

View File

@@ -1,51 +0,0 @@
# Usage
If you are coming from Radarr or Sonarr you will find that MediaManager does things a bit differently.
Instead of completely automatically downloading and managing your media, MediaManager focuses on providing an
easy-to-use interface to guide you through the process of finding and downloading media. Advanced features like multiple
qualities of a show/movie necessitate such a paradigm shift.
__So here is a quick step-by-step guide to get you started:__
<tabs>
<tab id="as-a-user" title="as a user">
<procedure title="Downloading/Requesting a show" id="request-show-user">
<step>Add a show on the "Add Show" page</step>
<step>After adding the show you will be redirected to the show's page.</step>
<step>There you can click the "Request Season" button.</step>
<step>Select one or more seasons that you want to download</step>
<step>Then select the "Min Quality", this will be the minimum resolution of the content to download.</step>
<step>Then select the "Wanted Quality", this will be the <strong>maximum</strong> resolution of the content to download.</step>
<step>Finally click Submit request, though this is not the last step!</step>
<step>An administrator first has to approve your request for download, only then will the requested content be downloaded.</step>
<p>Congratulation! You've downloaded a show.</p>
</procedure>
</tab>
<tab id="as-an-admin" title="as an admin">
<procedure title="Requesting a show" id="request-show-admin">
<step>Add a show on the "Add Show" page</step>
<step>After adding the show you will be redirected to the show's page.</step>
<step>There you can click the "Request Season" button.</step>
<step>Select one or more seasons that you want to download</step>
<step>Then select the "Min Quality", this will be the minimum resolution of the content to download.</step>
<step>Then select the "Wanted Quality", this will be the <strong>maximum</strong> resolution of the content to download.</step>
<step>Finally click Submit request, as you are an admin, your request will be automatically approved.</step>
<p>Congratulation! You've downloaded a show.</p>
</procedure>
<procedure title="Downloading a show" id="download-show-admin">
<p>You can only directly download a show if you are an admin!</p>
<step>Go to a show's page.</step>
<step>There you can click the "Download Season" button.</step>
<step>Enter the season's number that you want to download</step>
<step>Then optionally select the "File Path Suffix", <strong>it needs to be unique per season per show!</strong> </step>
<step>Then click "Download" on a torrent that you want to download.</step>
<p>Congratulation! You've downloaded a show.</p>
</procedure>
<procedure title="Managing requests" id="approving-request-admin">
<p>Users need their requests to be approved by an admin, to do this follow these steps:</p>
<step>Go to the "Requests" page.</step>
<step>There you can approve, delete or modify a user's request.</step>
</procedure>
</tab>
</tabs>

View File

@@ -1,8 +0,0 @@
# API Reference
Media Manager's backend is built with FastAPI, which automatically generates interactive API documentation.
* **Swagger UI** (typically available at `http://localhost:8000/docs`).
* **ReDoc** (typically available at `http://localhost:8000/redoc`).

View File

@@ -1,109 +0,0 @@
# Authentication
MediaManager supports multiple authentication methods. Email/password authentication is the default, but you can also
enable OpenID Connect (OAuth 2.0) for integration with external identity providers.
All authentication settings are configured in the `[auth]` section of your `config.toml` file.
## General Authentication Settings (`[auth]`)
- `token_secret`
Strong secret key for signing JWTs (create with `openssl rand -hex 32`). This is a required field.
- `session_lifetime`
Lifetime of user sessions in seconds. Default is `86400` (1 day).
- `admin_emails`
A list of email addresses for administrator accounts. This is a required field.
- `email_password_resets`
Toggle for enabling password resets via email. If users request a password reset because they forgot their password,
they will be sent an email with a link to reset it. Default is `false`.
<note>
To use email password resets, you must also configure SMTP settings in the <code>[notifications.smtp_config]</code> section.
</note>
<include from="notes.topic" element-id="auth-admin-emails"></include>
## OpenID Connect Settings (`[auth.openid_connect]`)
OpenID Connect allows you to integrate with external identity providers like Google, Microsoft Azure AD, Keycloak, or
any other OIDC-compliant provider.
- `enabled`
Set to `true` to enable OpenID Connect authentication. Default is `false`.
- `client_id`
Client ID provided by your OpenID Connect provider.
- `client_secret`
Client secret provided by your OpenID Connect provider.
- `configuration_endpoint`
OpenID Connect configuration endpoint URL. Note the lack of a trailing slash - this is important. It usually ends with
`.well-known/openid-configuration`.
- `name`
Display name for the OpenID Connect provider that will be shown on the login page.
### Configuration for your OpenID Connect Provider
#### Redirect URI
The OpenID server will likely require a redirect URI. The exact path depends on the `name` of the OIDC provider. Note
that the `name` is case-sensitive.
```
{FRONTEND_URL}/api/v1/auth/cookie/{OPENID_NAME}/callback
```
<warning>It is very important that you set the correct callback URI, otherwise it won't work!</warning>
E.g.: I set `MyAuthProvider` as the `name` in the `[auth.openid_connect]` config section, thus the redirect URI would
be:
```
https://mediamanager.example.com/api/v1/auth/cookie/MyAuthProvider/callback
```
#### Authentik Example {collapsible="true"}
Here is an example configuration for the OpenID Connect provider for Authentik.
![authentik-example-config.png](authentik-example-config.png)
#### PocketID Example {collapsible="true"}
Here is an example configuration for the OpenID Connect provider for PocketID.
![pocketid-example-config.png](pocketid-example-config.png)
## Example Configuration
Here's a complete example of the authentication section in your `config.toml`:
```toml
[auth]
token_secret = "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6"
session_lifetime = 604800 # 1 week
admin_emails = ["admin@example.com", "manager@example.com"]
email_password_resets = true
[auth.openid_connect]
enabled = true
client_id = "mediamanager-client"
client_secret = "your-secret-key-here"
configuration_endpoint = "https://auth.example.com/.well-known/openid-configuration"
name = "Authentik"
```

View File

@@ -1,38 +0,0 @@
# Backend
These settings configure the core backend application through the `config.toml` file. All backend configuration is now
centralized in this TOML file instead of environment variables.
## General Settings (`[misc]`)
- `frontend_url`
The URL the frontend will be accessed from. This is a required field and must include the trailing slash. The default
path is `http://localhost:8000/web/`. Make sure to change this to match your actual frontend URL.
- `cors_urls`
A list of origins you are going to access the API from. Note the lack of trailing slashes.
- `development`
Set to `true` to enable development mode. Default is `false`.
## Example Configuration
Here's a complete example of the general settings section in your `config.toml`:
```toml
[misc]
# REQUIRED: Change this to match your actual frontend URL
frontend_url = "http://localhost:8000/web/"
cors_urls = ["http://localhost:8000"]
# Optional: Development mode (set to true for debugging)
development = false
```
<note>
The <code>frontend_url</code> is the most important settings to configure correctly. Make sure it matches your actual deployment URLs.
</note>

View File

@@ -1,33 +0,0 @@
# Installation Guide
The recommended way to install and run Media Manager is using Docker and Docker Compose.
## Prerequisites
* Ensure Docker and Docker Compose are installed on your system.
* If you plan to use OAuth 2.0 / OpenID Connect for authentication, you will need an account and client credentials
from an OpenID provider (e.g., Authentik, Pocket ID).
## Setup
* Download the `docker-compose.yaml` from the MediaManager repo with the following command:
```bash
wget -O docker-compose.yaml https://raw.githubusercontent.com/maxdorninger/MediaManager/refs/heads/master/docker-compose.yaml
mkdir config
wget -O ./config/config.toml https://raw.githubusercontent.com/maxdorninger/MediaManager/refs/heads/master/config.example.toml
# you probably need to edit the config.toml file in the ./config directory, for more help see the documentation
docker compose up -d
```
* Upon first run, MediaManager will create a default `config.toml` file in the `./config` directory.
* You can edit this file to configure MediaManager according to your needs.
* Upon first run, MediaManager will also create a default admin user with the email, it's recommended to change the
password of this user after the first login. The credentials of the default admin user will be printed in the logs of
the container.
* For more information on the available configuration options, see the [Configuration section](Configuration.md) of the
documentation.
<include from="notes.topic" element-id="auth-admin-emails"></include>

View File

@@ -1 +0,0 @@
# Contributing

View File

@@ -1,42 +0,0 @@
# Database
Database settings are configured in the `[database]` section of your `config.toml` file. MediaManager uses PostgreSQL as its database backend.
## Database Settings (`[database]`)
- `host`
Hostname or IP of the PostgreSQL server. Default is `localhost`.
- `port`
Port number of the PostgreSQL server. Default is `5432`.
- `user`
Username for PostgreSQL connection. Default is `MediaManager`.
- `password`
Password for the PostgreSQL user. Default is `MediaManager`.
- `dbname`
Name of the PostgreSQL database. Default is `MediaManager`.
## Example Configuration
Here's a complete example of the database section in your `config.toml`:
```toml
[database]
host = "db"
port = 5432
user = "MediaManager"
password = "your_secure_password"
dbname = "MediaManager"
```
<tip>
In docker-compose deployments the containers name is simultaneously its hostname, so you can use "db" or "postgres" as host.
</tip>

View File

@@ -1,184 +0,0 @@
# Developer Guide
This section is for those who want to contribute to Media Manager or understand its internals.
## Source Code directory structure
- `media_manager/`: Backend FastAPI application
- `web/`: Frontend SvelteKit application
- `Writerside/`: Documentation
- `metadata_relay/`: Metadata relay service, also FastAPI
## Special Dev Configuration
#### Env Variables
- `BASE_PATH`: this sets the base path for the app (can be set for both backend and frontend)
- `PUBLIC_VERSION`: this sets the version variable, it is displayed in the frontend (requires rebuilding of the
frontend) and in the /api/v1/health endpoint (can be set for both backend and frontend)
- `FRONTEND_FILES_DIR`: directory for frontend files, e.g. in Docker container it is `/app/web/build` (only backend)
- `MEDIAMANAGER_MISC__DEVELOPMENT`: If set to `TRUE`, enables hot reloading of FastAPI (only when using the docker container)
## Contributing
- Consider opening an issue to discuss changes before starting work
## Setting up the Development Environment
I use IntellijIdea with the Pycharm and Webstorm plugins to develop this, but this guide should also work with VSCode.
Normally I'd recommend Intellij, but unfortunately only Intellij Ultimate has support for FastAPI and some other
features.
### Recommended VSCode Plugins:
- Python
- Svelte for VSCode
- and probably more, but I don't use VSCode myself, so I can't recommend anymore.
### Recommended Intellij/Pycharm Plugins:
- Python
- Svelte
- Pydantic
- Ruff
- VirtualKit
- Writerside (for writing documentation)
### Other recommendations
I recommend developing using Docker, i.e. you can use the provided `docker-compose.dev.yaml` file. This dev
docker-compose file has the `./media_manager` directory mounted at `/app/media_manager` in the container, meaning you
can run the code using the container in exactly the environment it will be running in.
Additionally, to develop the frontend I use a locally installed Node.js server. So basically a hybrid approach, where
the backend runs in a container and the frontend runs on Windows. To make this work, you need to make sure the
`cors_urls` and `frontend_url` are set correctly in the backend's config file.
Unfortunately, a side effect of this setup is that you have to rebuild the Docker image every time when you change the
python dependencies in any way or at least restart the container if you change the code. For a fast-paced development it
may be more convenient to run the backend locally too, because then it supports hot reloading.
### Setting up the basic development environment with Docker
- Copy the `config.dev.toml` file to `config.toml` in the `./res` directory and edit it to your needs.
- Use the following command to start the development environment with Docker:
```bash
docker compose -f docker-compose.dev.yaml up -d
```
### Setting up the backend development environment
1. Clone the repository
2. cd into repo root
3. [Install `uv`.](https://docs.astral.sh/uv/getting-started/installation/)
4. run `uv --version` to verify that `uv` is installed correctly
5. Install python if you haven't already:
```bash
uv python install 3.13
```
6. Create a virtual environment with uv
```bash
uv venv --python 3.13
```
7. Install dependencies:
```bash
uv sync
```
8. run db migrations with
```bash
uv run alembic upgrade head
```
9. run the backend with
```bash
uv run ./media_manager/main.py --reload --port 8000
```
- format code with `uvx ruff format`
- lint code with `uvx ruff check`
### Setting up the frontend development environment
1. Clone the repository
2. cd into repo root
3. cd into `web` directory
4. install Node.js and npm if you haven't already, I
used [nvm-windows](https://github.com/coreybutler/nvm-windows?tab=readme-ov-file):
```powershell
nvm install 24.1.0
nvm use 24.1.0
```
I also needed to run the following command to be able to use `npm`:
```powershell
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
```
5. Install the dependencies with npm: `npm install`
6. Start the frontend development server: `npm run dev`
- format the code with `npm run format`
- lint the code with `npm run lint`
## Sequence Diagrams
```mermaid
sequenceDiagram
title Step-by-step: going from adding a show to importing a torrent of one of its seasons
User->>TV Router: Add a show (POST /tv/shows)
TV Router->>TV Service: Receive Show Request
TV Service->>MetadataProviderService: Get Metadata for Show
MetadataProviderService->>File System: Save Poster Image
TV Service->>Database: Store show information
User->>TV Router: Get Available Torrents for a Season (GET /tv/torrents)
TV Router->>TV Service: Receive Request
TV Service->>Indexer Service: Search for torrents
TV Service->>User: Returns Public Indexer Results
User->>TV Router: Download Torrent (POST /tv/torrents)
TV Router->>TV Service: Receive Request
Note over Database: This associates a season with a torrent id and the file_path_suffix
TV Service->>Database: Saves a SeasonFile object
TV Service->>Torrent Service: Download Torrent
Torrent Service->>File System: Save Torrentfile
Torrent Service->>QBittorrent: Download Torrent
Note over Scheduler: Hourly scheduler trigger
Scheduler->>TV Service: auto_import_all_show_torrents()
TV Service->>Database: Get all Shows and seasons which are associated with a torrent
TV Service->>Torrent Service: Update Torrent download statuses
Note over TV Service: if a torrent is finished downloading it will be imported
TV Service->>Torrent Service: get all files in the torrents directory
Note over Torrent Service: Extracts archives, guesses mimetype (Video/Subtitle/Other)
Note over TV Service: filters files based on some regex and renames them
TV Service->>File System: Move/Hardlink video and subtitle files
Note over User: User can now access the show in e.g. Jellyfin
```
## Tech Stack
### Backend
- Python
- FastAPI
- SQLAlchemy
- Pydantic and Pydantic-Settings
- Alembic
### Frontend
- TypeScript
- SvelteKit
- Tailwind CSS
- shadcn-svelte
- openapi-ts
- openapi-fetch
### CI/CD
- GitHub Actions

View File

@@ -1,69 +0,0 @@
# Metadata Provider Configuration
Metadata provider settings are configured in the `[metadata]` section of your `config.toml` file. These settings control how MediaManager retrieves information about movies and TV shows.
## TMDB Settings (`[metadata.tmdb]`)
TMDB (The Movie Database) is the primary metadata provider for MediaManager. It provides detailed information about movies and TV shows.
<tip>
Other software like Jellyfin use TMDB as well, so there won't be any metadata discrepancies.
</tip>
### `tmdb_relay_url`
If you want to use your own TMDB relay service, set this to the URL of your own MetadataRelay. Otherwise, use the default relay.
- **Default:** `https://metadata-relay.dorninger.co/tmdb`
- **Example:** `https://your-own-relay.example.com/tmdb`
## TVDB Settings (`[metadata.tvdb]`)
<warning>
The TVDB might provide false metadata and doesn't support some features of MediaManager like showing overviews. Therefore, TMDB is the preferred metadata provider.
</warning>
### `tvdb_relay_url`
If you want to use your own TVDB relay service, set this to the URL of your own MetadataRelay. Otherwise, use the default relay.
- **Default:** `https://metadata-relay.dorninger.co/tvdb`
- **Example:** `https://your-own-relay.example.com/tvdb`
## MetadataRelay
<note>
To use MediaManager <strong>you don't need to set up your own MetadataRelay</strong>, as the default relay hosted by the developer should be sufficient for most purposes.
</note>
The MetadataRelay is a service that provides metadata for MediaManager. It acts as a proxy for TMDB and TVDB, allowing you to use your own API keys if needed, but the default relay means you don't need to create accounts for API keys yourself.
You might want to use your own relay if you want to avoid rate limits, protect your privacy, or for other reasons. If you know Sonarr's Skyhook, this is similar to that.
### Where to get API keys
- Get a TMDB API key from [The Movie Database](https://www.themoviedb.org/settings/api)
- Get a TVDB API key from [The TVDB](https://thetvdb.com/auth/register)
<tip>
If you want to use your own MetadataRelay, you can set the <code>tmdb_relay_url</code> and/or <code>tvdb_relay_url</code> to your own relay service.
</tip>
## Example Configuration
Here's a complete example of the metadata section in your `config.toml`:
```toml
[metadata]
# TMDB configuration
[metadata.tmdb]
tmdb_relay_url = "https://metadata-relay.dorninger.co/tmdb"
# TVDB configuration
[metadata.tvdb]
tvdb_relay_url = "https://metadata-relay.dorninger.co/tvdb"
```
<note>
In most cases, you can simply use the default values and don't need to specify these settings in your config file at all.
</note>

View File

@@ -1,19 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE topic SYSTEM "https://resources.jetbrains.com/writerside/1.0/html-entities.dtd">
<topic id="notes"
is-library="true" title="notes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://resources.jetbrains.com/writerside/1.0/topic.v2.xsd">
<snippet id="list-format">
Lists have to be formatted like this: <code>["item1", "item2", "item3"]</code>. Note the double quotes.
</snippet>
<snippet id="auth-admin-emails">
<tip>
<p>When setting up MediaManager for the first time, you should add your email to <code>admin_emails</code>
in the <code>[auth]</code> config
section, MediaManager will then use this email instead of the default admin email. Your account will automatically be
created as an admin account, allowing you to manage other
users, media and
settings.</p>
</tip>
</snippet>
</topic>

View File

@@ -1,12 +0,0 @@
# qBittorrent Category
qBittorrent supports saving Torrents to subdirectories based on the category of the Torrent.
The default category name that MediaManager uses is `MediaManager`.
With the variable `torrents.qbittorrent.category_name` you can change the category name that MediaManager uses when
adding Torrents to qBittorrent.
With the variable `torrents.qbittorrent.category_save_path` you can change the path where the Torrents are saved to. By
default, no subdirectory is used. Note that qBittorrent saves torrents to this path, so it must be a
valid path that qBittorrent can write to. Example value: `/data/torrents/MediaManager`. Note that for MediaManager to be
able to successfully import torrents, you must add the subdirectory to the `misc.torrent_directory` variable.

View File

@@ -1,31 +0,0 @@
# Troubleshooting
<note>
Note the lack of a trailing slash in some env vars like FRONTEND_URL. This is important.
</note>
<tip>
Always check the container and browser logs for more specific error messages
</tip>
<procedure title="I can't log in with OAuth/OIDC?" id="procedure-i-cannot-log-in-with-oauth">
<step>Verify your OAuth provider's configuration. <a href="authentication-setup.md" anchor="openid-connect-settings-auth-openid-connect">See the OAuth documentation</a></step>
<step>Check if the callback URI you set in your OIDC providers settings is correct. <a href="authentication-setup.md" anchor="redirect-uri">See the callback URI documentation</a> </step>
<step>Check the frontend url in your config file. It should match the URL you use to access MediaManager.</step>
</procedure>
<procedure title="I cannot log in?" id="procedure-i-cannot-log-in">
<step>Make sure you are logging in, not signing up.</step>
<step>Try logging in with the following credentials:
<list>
<li>Email: admin@mediamanager.local or admin@example.com</li>
<li>Password: admin</li>
</list>
</step>
</procedure>
<procedure title="My hardlinks don't work?" id="procedure-my-hardlinks-dont-work">
<step>Make sure you are using only one volumes for TV, Movies and Downloads. <a href="https://raw.githubusercontent.com/maxdorninger/MediaManager/refs/heads/master/docker-compose.yaml"> See the configuration in the example <code>docker-compose.yaml</code> file.</a></step>
</procedure>
<note>If it still doesn't work, <a href="https://github.com/maxdorninger/MediaManager/issues">please open an Issue.</a> It is possible that a bug is causing the issue.</note>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE vars SYSTEM "https://resources.jetbrains.com/writerside/1.0/vars.dtd">
<vars>
<var name="product" value="Writerside"/>
</vars>

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ihp SYSTEM "https://resources.jetbrains.com/writerside/1.0/ihp.dtd">
<ihp version="2.0">
<topics dir="topics" web-path="topics"/>
<images dir="images" web-path="MediaManager"/>
<instance src="mm.tree"/>
<instance src="Writerside_libraries.tree"/>
</ihp>

View File

@@ -1,13 +1,16 @@
import sys import sys
sys.path = ["", ".."] + sys.path[1:] sys.path = ["", "..", *sys.path[1:]]
from logging.config import fileConfig # noqa: E402 from logging.config import fileConfig # noqa: E402
from sqlalchemy import ( # noqa: E402
engine_from_config,
pool,
)
from alembic import context # noqa: E402 from alembic import context # noqa: E402
from sqlalchemy import engine_from_config # noqa: E402
from sqlalchemy import pool # noqa: E402
# this is the Alembic Config object, which provides # this is the Alembic Config object, which provides
# access to the values within the .ini file in use. # access to the values within the .ini file in use.
@@ -23,34 +26,40 @@ if config.config_file_name is not None:
# from myapp import mymodel # from myapp import mymodel
# target_metadata = mymodel.Base.metadata # target_metadata = mymodel.Base.metadata
from media_manager.auth.db import User, OAuthAccount # noqa: E402 from media_manager.auth.db import OAuthAccount, User # noqa: E402
from media_manager.config import MediaManagerConfig # noqa: E402
from media_manager.database import Base # noqa: E402
from media_manager.indexer.models import IndexerQueryResult # noqa: E402 from media_manager.indexer.models import IndexerQueryResult # noqa: E402
from media_manager.torrent.models import Torrent # noqa: E402
from media_manager.tv.models import Show, Season, Episode, SeasonFile, SeasonRequest # noqa: E402
from media_manager.movies.models import Movie, MovieFile, MovieRequest # noqa: E402 from media_manager.movies.models import Movie, MovieFile, MovieRequest # noqa: E402
from media_manager.notification.models import Notification # noqa: E402 from media_manager.notification.models import Notification # noqa: E402
from media_manager.database import Base # noqa: E402 from media_manager.torrent.models import Torrent # noqa: E402
from media_manager.config import AllEncompassingConfig # noqa: E402 from media_manager.tv.models import ( # noqa: E402
Episode,
Season,
SeasonFile,
SeasonRequest,
Show,
)
target_metadata = Base.metadata target_metadata = Base.metadata
# this is to keep pycharm from complaining about/optimizing unused imports # this is to keep pycharm from complaining about/optimizing unused imports
# noinspection PyStatementEffect # noinspection PyStatementEffect
( __all__ = [
User, "Episode",
OAuthAccount, "IndexerQueryResult",
IndexerQueryResult, "Movie",
Torrent, "MovieFile",
Show, "MovieRequest",
Season, "Notification",
Episode, "OAuthAccount",
SeasonFile, "Season",
SeasonRequest, "SeasonFile",
Movie, "SeasonRequest",
MovieFile, "Show",
MovieRequest, "Torrent",
Notification, "User",
) ]
# other values from the config, defined by the needs of env.py, # other values from the config, defined by the needs of env.py,
@@ -59,20 +68,8 @@ target_metadata = Base.metadata
# ... etc. # ... etc.
db_config = AllEncompassingConfig().database db_config = MediaManagerConfig().database
db_url = ( db_url = f"postgresql+psycopg://{db_config.user}:{db_config.password}@{db_config.host}:{db_config.port}/{db_config.dbname}"
"postgresql+psycopg"
+ "://"
+ db_config.user
+ ":"
+ db_config.password
+ "@"
+ db_config.host
+ ":"
+ str(db_config.port)
+ "/"
+ db_config.dbname
)
config.set_main_option("sqlalchemy.url", db_url) config.set_main_option("sqlalchemy.url", db_url)
@@ -109,7 +106,13 @@ def run_migrations_online() -> None:
""" """
def include_object(object, name, type_, reflected, compare_to): def include_object(
_object: object | None,
name: str | None,
type_: str | None,
_reflected: bool | None,
_compare_to: object | None,
) -> bool:
if type_ == "table" and name == "apscheduler_jobs": if type_ == "table" and name == "apscheduler_jobs":
return False return False
return True return True

View File

@@ -0,0 +1,37 @@
"""add original_language columns to show and movie tables
Revision ID: 16e78af9e5bf
Revises: eb0bd3cc1852
Create Date: 2025-12-13 18:47:02.146038
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "16e78af9e5bf"
down_revision: Union[str, None] = "eb0bd3cc1852"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# Add original_language column to show table
op.add_column("show", sa.Column("original_language", sa.String(10), nullable=True))
# Add original_language column to movie table
op.add_column("movie", sa.Column("original_language", sa.String(10), nullable=True))
def downgrade() -> None:
"""Downgrade schema."""
# Remove original_language column from movie table
op.drop_column("movie", "original_language")
# Remove original_language column from show table
op.drop_column("show", "original_language")

View File

@@ -8,9 +8,9 @@ Create Date: 2025-07-16 01:09:44.045395
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = "1801d9f5a275" revision: str = "1801d9f5a275"

View File

@@ -8,9 +8,9 @@ Create Date: 2025-06-22 13:46:01.973406
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = "1f340754640a" revision: str = "1f340754640a"

View File

@@ -8,9 +8,9 @@ Create Date: 2025-07-06 10:49:08.814496
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = "21a19f0675f9" revision: str = "21a19f0675f9"

View File

@@ -0,0 +1,35 @@
"""add imdb_id fields
Revision ID: 2c61f662ca9e
Revises: 16e78af9e5bf
Create Date: 2025-12-23 19:42:09.593945
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "2c61f662ca9e"
down_revision: Union[str, None] = "16e78af9e5bf"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("movie", sa.Column("imdb_id", sa.String(), nullable=True))
op.add_column("show", sa.Column("imdb_id", sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("show", "imdb_id")
op.drop_column("movie", "imdb_id")
# ### end Alembic commands ###

View File

@@ -8,9 +8,10 @@ Create Date: 2025-07-09 20:55:42.338629
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = "333866afcd2c" revision: str = "333866afcd2c"
down_revision: Union[str, None] = "aa4689f80796" down_revision: Union[str, None] = "aa4689f80796"

View File

@@ -8,9 +8,9 @@ Create Date: 2025-07-16 23:24:37.931188
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = "5299dfed220b" revision: str = "5299dfed220b"

View File

@@ -8,9 +8,9 @@ Create Date: 2025-06-10 21:25:27.871064
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = "7508237d5bc2" revision: str = "7508237d5bc2"

View File

@@ -8,10 +8,11 @@ Create Date: 2025-05-27 21:36:18.532068
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy.dialects import postgresql from sqlalchemy.dialects import postgresql
from alembic import op
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = "93fb07842385" revision: str = "93fb07842385"
down_revision: Union[str, None] = None down_revision: Union[str, None] = None

View File

@@ -8,9 +8,9 @@ Create Date: 2025-07-06 10:54:19.714809
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = "aa4689f80796" revision: str = "aa4689f80796"

View File

@@ -8,9 +8,9 @@ Create Date: 2025-10-28 21:39:24.480466
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = "eb0bd3cc1852" revision: str = "eb0bd3cc1852"

View File

@@ -1,14 +1,14 @@
# MediaManager Dev Configuration File # MediaManager Dev Configuration File
# This file contains all available configuration options for MediaManager # This file contains all available configuration options for MediaManager
# Documentation: https://maxdorninger.github.io/MediaManager/introduction.html # Documentation: https://maxdorninger.github.io/MediaManager/
# #
# This is an example configuration file that gets copied to your config folder # This is an example configuration file that gets copied to your config folder
# on first boot. You should modify the values below to match your setup. # on first boot. You should modify the values below to match your setup.
[misc] [misc]
# it's very likely that you need to change this for MediaManager to work # it's very likely that you need to change this for MediaManager to work
frontend_url = "http://localhost:5173/" # note the trailing slash frontend_url = "http://localhost:5173" # note the lack of a trailing slash
cors_urls = ["http://localhost:8000", "http://localhost:5173"] # note the lack of a trailing slash cors_urls = ["http://localhost:8000", "http://localhost:5173", "http://mediamanager:8000"] # note the lack of a trailing slash
image_directory = "/data/images" image_directory = "/data/images"
tv_directory = "/data/tv" tv_directory = "/data/tv"
@@ -119,7 +119,7 @@ base_path = "/api"
enabled = false enabled = false
url = "http://localhost:9696" url = "http://localhost:9696"
api_key = "" api_key = ""
reject_torrents_on_url_error = true timeout_seconds = 60
# Jackett settings # Jackett settings
[indexers.jackett] [indexers.jackett]
@@ -127,6 +127,7 @@ enabled = false
url = "http://localhost:9117" url = "http://localhost:9117"
api_key = "" api_key = ""
indexers = ["1337x", "torrentleech"] # List of indexer names to use indexers = ["1337x", "torrentleech"] # List of indexer names to use
timeout_seconds = 60
# Title-based scoring rules # Title-based scoring rules
[[indexers.title_scoring_rules]] [[indexers.title_scoring_rules]]

View File

@@ -1,13 +1,13 @@
# MediaManager Example Configuration File # MediaManager Example Configuration File
# This file contains all available configuration options for MediaManager # This file contains all available configuration options for MediaManager
# Documentation: https://maxdorninger.github.io/MediaManager/introduction.html # Documentation: https://maxdorninger.github.io/MediaManager/
# #
# This is an example configuration file that gets copied to your config folder # This is an example configuration file that gets copied to your config folder
# on first boot. You should modify the values below to match your setup. # on first boot. You should modify the values below to match your setup.
[misc] [misc]
# it's very likely that you need to change this for MediaManager to work # it's very likely that you need to change this for MediaManager to work
frontend_url = "http://localhost:8000/web/" # note the trailing slash frontend_url = "http://localhost:8000" # note the lack of a trailing slash
cors_urls = ["http://localhost:8000"] # note the lack of a trailing slash cors_urls = ["http://localhost:8000"] # note the lack of a trailing slash
image_directory = "/data/images" image_directory = "/data/images"
@@ -119,7 +119,7 @@ base_path = "/api"
enabled = false enabled = false
url = "http://localhost:9696" url = "http://localhost:9696"
api_key = "" api_key = ""
reject_torrents_on_url_error = true timeout_seconds = 60
# Jackett settings # Jackett settings
[indexers.jackett] [indexers.jackett]
@@ -127,6 +127,7 @@ enabled = false
url = "http://localhost:9117" url = "http://localhost:9117"
api_key = "" api_key = ""
indexers = ["1337x", "torrentleech"] # List of indexer names to use indexers = ["1337x", "torrentleech"] # List of indexer names to use
timeout_seconds = 60
# Title-based scoring rules # Title-based scoring rules
[[indexers.title_scoring_rules]] [[indexers.title_scoring_rules]]
@@ -143,10 +144,10 @@ negate = false
# Indexer flag-based scoring rules # Indexer flag-based scoring rules
[[indexers.indexer_flag_scoring_rules]] [[indexers.indexer_flag_scoring_rules]]
name = "reject_non_freeleech" name = "prefer_freeleech"
flags = ["freeleech", "freeleech75"] flags = ["freeleech", "freeleech75"]
score_modifier = -10000 score_modifier = 100
negate = true negate = false
[[indexers.indexer_flag_scoring_rules]] [[indexers.indexer_flag_scoring_rules]]
name = "reject_nuked" name = "reject_nuked"
@@ -158,17 +159,14 @@ negate = false
[[indexers.scoring_rule_sets]] [[indexers.scoring_rule_sets]]
name = "default" name = "default"
libraries = ["ALL_TV", "ALL_MOVIES"] libraries = ["ALL_TV", "ALL_MOVIES"]
rule_names = ["prefer_h265", "avoid_cam", "reject_nuked"] rule_names = ["prefer_h265", "avoid_cam", "reject_nuked", "prefer_freeleech"]
[[indexers.scoring_rule_sets]]
name = "strict_quality"
libraries = ["ALL_MOVIES"]
rule_names = ["prefer_h265", "avoid_cam", "reject_non_freeleech"]
# its very unlikely that you need to change this # its very unlikely that you need to change this
[metadata] [metadata]
[metadata.tmdb] [metadata.tmdb]
tmdb_relay_url = "https://metadata-relay.dorninger.co/tmdb" tmdb_relay_url = "https://metadata-relay.dorninger.co/tmdb"
primary_languages = [""]
default_language = "en"
[metadata.tvdb] [metadata.tvdb]
tvdb_relay_url = "https://metadata-relay.dorninger.co/tvdb" tvdb_relay_url = "https://metadata-relay.dorninger.co/tvdb"

View File

@@ -11,10 +11,16 @@ services:
POSTGRES_PASSWORD: MediaManager POSTGRES_PASSWORD: MediaManager
ports: ports:
- "5432:5432" - "5432:5432"
healthcheck:
test: [ "CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}" ]
interval: 10s
timeout: 5s
retries: 5
mediamanager: mediamanager:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
target: app
args: args:
- VERSION=locally-built - VERSION=locally-built
- BASE_PATH= - BASE_PATH=
@@ -24,44 +30,73 @@ services:
- "8000:8000" - "8000:8000"
environment: environment:
- CONFIG_DIR=/app/config - CONFIG_DIR=/app/config
- MEDIAMANAGER_MISC__DEVELOPMENT=TRUE
- MEDIAMANAGER_MISC__CORS_URLS=["http://localhost:5173"]
- DISABLE_FRONTEND_MOUNT=TRUE
- LOG_FILE=/dev/null
- MEDIAMANAGER_LOG_LEVEL=DEBUG
volumes: volumes:
#- ./web/build:/app/web/build # this is only needed to test built frontend when developing frontend #- ./web/build:/app/web/build # this is only needed to test built frontend when developing frontend
- ./res/images/:/data/images/ - ./res/images/:/data/images/
- ./res/:/data/ - ./res/:/data/
- ./res/config/:/app/config/ - ./res/config/:/app/config/
- ./media_manager:/app/media_manager - ./media_manager:/app/media_manager
depends_on:
db:
condition: service_healthy
frontend:
image: node:24-alpine
container_name: mediamanager-frontend-dev
working_dir: /app
command: sh -c "npm install && npm run dev -- --host 0.0.0.0"
ports:
- "5173:5173"
- "24678:24678"
volumes:
- ./web:/app
depends_on:
- mediamanager
docs:
image: squidfunk/mkdocs-material:9
container_name: mediamanager-docs
volumes:
- .:/docs
ports:
- "9000:9000"
command: serve -w /docs -a 0.0.0.0:9000
# ---------------------------- # ----------------------------
# Additional services can be uncommented and configured as needed # Additional services can be uncommented and configured as needed
# ---------------------------- # ----------------------------
# prowlarr: prowlarr:
# image: lscr.io/linuxserver/prowlarr:latest image: lscr.io/linuxserver/prowlarr:latest
# container_name: prowlarr container_name: prowlarr
# environment: environment:
# - PUID=1000 - PUID=1000
# - PGID=1000 - PGID=1000
# - TZ=Etc/UTC - TZ=Etc/UTC
# volumes: volumes:
# - ./res/prowlarr:/config - ./res/prowlarr:/config
# restart: unless-stopped restart: unless-stopped
# ports: ports:
# - "9696:9696" - "9696:9696"
# qbittorrent: qbittorrent:
# image: lscr.io/linuxserver/qbittorrent:latest image: lscr.io/linuxserver/qbittorrent:latest
# container_name: qbittorrent container_name: qbittorrent
# environment: environment:
# - TZ=Etc/UTC - TZ=Etc/UTC
# - WEBUI_PORT=8080 - WEBUI_PORT=8080
# - TORRENTING_PORT=6881 - TORRENTING_PORT=6881
# ports: ports:
# - 8080:8080 - 8080:8080
# - 6881:6881 - 6881:6881
# - 6881:6881/udp - 6881:6881/udp
# restart: unless-stopped restart: unless-stopped
# volumes: volumes:
# - ./res/torrents:/download - ./res/torrents:/download
# - ./res/qbittorrent:/config - ./res/qbittorrent:/config
# transmission: # transmission:
# image: lscr.io/linuxserver/transmission:latest # image: lscr.io/linuxserver/transmission:latest
# container_name: transmission # container_name: transmission
@@ -104,17 +139,17 @@ services:
# ports: # ports:
# - 8081:8080 # - 8081:8080
# restart: unless-stopped # restart: unless-stopped
# jackett: jackett:
# image: lscr.io/linuxserver/jackett:latest image: lscr.io/linuxserver/jackett:latest
# container_name: jackett container_name: jackett
# environment: environment:
# - PUID=1000 - PUID=1000
# - PGID=1000 - PGID=1000
# - TZ=Etc/UTC - TZ=Etc/UTC
# - AUTO_UPDATE=true - AUTO_UPDATE=true
# volumes: volumes:
# - ./res/jackett/data:/config - ./res/jackett/data:/config
# - ./res/jackett/torrents:/downloads - ./res/jackett/torrents:/downloads
# ports: ports:
# - 9117:9117 - 9117:9117
# restart: unless-stopped restart: unless-stopped

View File

@@ -1,6 +1,7 @@
services: services:
mediamanager: mediamanager:
image: ghcr.io/maxdorninger/mediamanager/mediamanager:latest container_name: mediamanager_server
image: quay.io/maxdorninger/mediamanager:latest
ports: ports:
- "8000:8000" - "8000:8000"
environment: environment:
@@ -12,7 +13,11 @@ services:
- ./config/:/app/config/ - ./config/:/app/config/
# Image folder # Image folder
- ./images/:/data/images/ - ./images/:/data/images/
depends_on:
db:
condition: service_healthy
db: db:
container_name: mediamanager_postgres
image: postgres:17 image: postgres:17
restart: unless-stopped restart: unless-stopped
volumes: volumes:

View File

@@ -0,0 +1,4 @@
# Custom port
* `PORT`\
Port that MediaManager listens on. Default is `8000`. This only works if you are using the Docker image. Configured as environment variable.

View File

@@ -0,0 +1,4 @@
# Disable Startup Ascii Art
* `MEDIAMANAGER_NO_STARTUP_ART`: Set this environment variable (to any value) \
to disable the colorized startup splash screen. Unset to reenable.

View File

@@ -0,0 +1,12 @@
# Follow symlinks in frontend files
MediaManager can be configured to follow symlinks when serving frontend files. This is useful if you have a setup where your frontend files are stored in a different location, and you want to symlink them into the MediaManager frontend directory.
* `FRONTEND_FOLLOW_SYMLINKS`\
Set this environment variable to `true` to follow symlinks when serving frontend files. Default is `false`.
```bash title=".env"
FRONTEND_FOLLOW_SYMLINKS=true
```

View File

@@ -0,0 +1,65 @@
# Metadata Provider Configuration
## Metadata Provider Configuration
Metadata provider settings are configured in the `[metadata]` section of your `config.toml` file. These settings control how MediaManager retrieves information about movies and TV shows.
### TMDB Settings (`[metadata.tmdb]`)
TMDB (The Movie Database) is the primary metadata provider for MediaManager. It provides detailed information about movies and TV shows.
!!! info
Other software like Jellyfin use TMDB as well, so there won't be any metadata discrepancies.
* `tmdb_relay_url`\
URL of the TMDB relay (MetadataRelay). Default is `https://metadata-relay.dorninger.co/tmdb`. Example: `https://your-own-relay.example.com/tmdb`.
* `primary_languages`\
If the original language of a show/movie is in this list, metadata is fetched in that language. Otherwise, `default_language` is used. Default is `[]`. Example: `["no", "de", "es"]`. Format: ISO 639-1 (2 letters). Full list: https://en.wikipedia.org/wiki/List\_of\_ISO\_639\_language\_codes
* `default_language`\
TMDB language parameter used when searching and adding. Default is `en`. Format: ISO 639-1 (2 letters).
!!! warning
`default_language` sets the TMDB `language` parameter when searching and adding TV shows and movies. If TMDB does not find a matching translation, metadata in the original language will be fetched with no option for a fallback language. It is therefore highly advised to only use "broad" languages. For most use cases, the default setting is safest.
### TVDB Settings (`[metadata.tvdb]`)
!!! warning
The TVDB might provide false metadata and doesn't support some features of MediaManager like showing overviews. Therefore, TMDB is the preferred metadata provider.
* `tvdb_relay_url`\
URL of the TVDB relay (MetadataRelay). Default is `https://metadata-relay.dorninger.co/tvdb`. Example: `https://your-own-relay.example.com/tvdb`.
### MetadataRelay
!!! info
To use MediaManager you don't need to set up your own MetadataRelay, as the default relay hosted by the developer should be sufficient for most purposes.
The MetadataRelay is a service that provides metadata for MediaManager. It acts as a proxy for TMDB and TVDB, allowing you to use your own API keys if needed, but the default relay means you don't need to create accounts for API keys yourself.
You might want to use your own relay if you want to avoid rate limits, protect your privacy, or for other reasons. If you know Sonarr's Skyhook, this is similar to that.
#### Where to get API keys
* Get a TMDB API key from [The Movie Database](https://www.themoviedb.org/settings/api)
* Get a TVDB API key from [The TVDB](https://thetvdb.com/auth/register)
!!! info
If you want to use your own MetadataRelay, you can set the `tmdb_relay_url` and/or `tvdb_relay_url` to your own relay service.
### Example Configuration
Here's a complete example of the metadata section in your `config.toml`:
```toml title="config.toml"
[metadata]
# TMDB configuration
[metadata.tmdb]
tmdb_relay_url = "https://metadata-relay.dorninger.co/tmdb"
# TVDB configuration
[metadata.tvdb]
tvdb_relay_url = "https://metadata-relay.dorninger.co/tvdb"
```
!!! info
In most cases, you can simply use the default values and don't need to specify these settings in your config file at all.

View File

@@ -0,0 +1,16 @@
# qBittorrent Category
qBittorrent supports saving Torrents to subdirectories based on the category of the Torrent. The default category name that MediaManager uses is `MediaManager`.
Use the following variables to customize behavior:
* `torrents.qbittorrent.category_name`\
Category name MediaManager uses when adding torrents to qBittorrent. Default is `MediaManager`.
* `torrents.qbittorrent.category_save_path`\
Save path for the category in qBittorrent. By default, no subdirectory is used. Example: `/data/torrents/MediaManager`.
!!! info
qBittorrent saves torrents to the path specified by `torrents.qbittorrent.category_save_path`, so it must be a valid path that qBittorrent can write to.
!!! warning
For MediaManager to successfully import torrents, you must add the subdirectory to the `misc.torrent_directory` variable.

View File

@@ -0,0 +1,36 @@
# URL Prefix
MediaManager, by default, expects to run at the base of a domain, e.g. `maxdorninger.github.io`.
In order to run it on a prefixed path, like `maxdorninger.github.io/media`, the docker image must be built with a special build argument. That's because SvelteKit needs to know the base URL at build time.
In short, clone the repository, then run:
```none title="Build Docker image"
docker build \
--build-arg BASE_PATH=/media \
--build-arg VERSION=my-custom-version \
-t MediaManager:my-custom-version \
-f Dockerfile .
```
You also need to set the `BASE_PATH` environment variable at runtime in `docker-compose.yaml`:
* `BASE_PATH`\
Base path prefix MediaManager is served under. Example: `/media`. This must match the `BASE_PATH` build arg.
```yaml title="docker-compose.yaml (excerpt)"
services:
mediamanager:
image: MediaManager:my-custom-version
ports:
- "8000:8000"
environment:
BASE_PATH: /media
...
```
!!! info
Make sure to include the base path in the `frontend_url` field in the config file. See [Backend](../configuration/backend.md).
Finally, ensure that whatever reverse proxy you're using leaves the incoming path unchanged; that is, you should not strip the `/media` from `/media/web/`.

7
docs/api-reference.md Normal file
View File

@@ -0,0 +1,7 @@
# API Reference
!!! info
Media Manager's backend is built with FastAPI, which automatically generates interactive API documentation.
* Swagger UI (typically available at `http://localhost:8000/docs`)
* ReDoc (typically available at `http://localhost:8000/redoc`)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

BIN
docs/assets/assets/5546622 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
docs/assets/assets/JO.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
docs/assets/assets/NI.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 8.9 MiB

After

Width:  |  Height:  |  Size: 8.9 MiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 MiB

After

Width:  |  Height:  |  Size: 5.5 MiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

Before

Width:  |  Height:  |  Size: 7.6 MiB

After

Width:  |  Height:  |  Size: 7.6 MiB

View File

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 123 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -0,0 +1,69 @@
# Configuration
MediaManager uses a TOML configuration file (`config.toml`) for all backend settings. This centralized configuration approach makes it easier to manage, backup, and share your MediaManager setup.
Frontend settings are configured through environment variables in your `docker-compose.yaml` file.
## Configuration File Location
!!! warning
Note that MediaManager may need to be restarted for changes in the config file to take effect.
Your `config.toml` file should be in the directory that's mounted to `/app/config/config.toml` inside the container:
```yaml
volumes:
- ./config:/app/config
```
You can change the configuration directory with the following environment variable:
* `CONFIG_DIR`\
Directory that contains `config.toml`. Default is `/app/config`. Example: `/etc/mediamanager/`.
## Configuration Sections
The configuration is organized into the following sections:
* `[misc]` - General settings
* `[database]` - Database settings
* `[auth]` - Authentication settings
* `[notifications]` - Notification settings (Email, Gotify, Ntfy, Pushover)
* `[torrents]` - Download client settings (qBittorrent, Transmission, SABnzbd)
* `[indexers]` - Indexer settings (Prowlarr and Jackett )
* `[metadata]` - TMDB and TVDB settings
## Configuring Secrets
For sensitive information like API keys, passwords, and secrets, you should use environment variables. You can actually set every configuration value through environment variables. For example, to set the `token_secret` value for authentication, with a .toml file you would use:
```toml
[auth]
token_secret = "your_super_secret_key_here"
```
But you can also set it through an environment variable:
```none
MEDIAMANAGER_AUTH__TOKEN_SECRET = "your_super_secret_key_here"
```
or another example with the OIDC client secret:
```toml
[auth]
...
[auth.openid_connect]
client_secret = "your_client_secret_from_provider"
```
env variable:
```none
MEDIAMANAGER_AUTH__OPENID_CONNECT__CLIENT_SECRET = "your_client_secret_from_provider"
```
So for every config "level", you basically have to take the name of the value and prepend it with the section names in uppercase with 2 underscores as delimiters and `MEDIAMANAGER_` as the prefix.
!!! warning
Note that not every env variable starts with `MEDIAMANAGER_`; this prefix only applies to env variables which replace/overwrite values in the config file. Variables like the `CONFIG_DIR` env variable must not be prefixed.

View File

@@ -0,0 +1,81 @@
---
description: >-
MediaManager supports multiple authentication methods. Email/password
authentication is the default, but you can also enable OpenID Connect (OAuth
2.0) for integration with external identity providers
---
# Authentication
All authentication settings are configured in the `[auth]` section of your `config.toml` file.
## General Authentication Settings (`[auth]`)
* `token_secret`\
Strong secret key for signing JWTs (create with `openssl rand -hex 32`). This is required.
* `session_lifetime`\
Lifetime of user sessions in seconds. Default is `86400` (1 day).
* `admin_emails`\
A list of email addresses for administrator accounts. This is required.
* `email_password_resets`\
Enables password resets via email. Default is `false`.
!!! info
To use email password resets, you must also configure SMTP settings in the `[notifications.smtp_config]` section.
!!! info
When setting up MediaManager for the first time, you should add your email to `admin_emails` in the `[auth]` config section. MediaManager will then use this email instead of the default admin email. Your account will automatically be created as an admin account, allowing you to manage other users, media and settings.
## OpenID Connect Settings (`[auth.openid_connect]`)
OpenID Connect allows you to integrate with external identity providers like Google, Microsoft Azure AD, Keycloak, or any other OIDC-compliant provider.
* `enabled`\
Set to `true` to enable OpenID Connect authentication. Default is `false`.
* `client_id`\
Client ID provided by your OpenID Connect provider.
* `client_secret`\
Client secret provided by your OpenID Connect provider.
* `configuration_endpoint`\
OpenID Connect configuration endpoint URL. Do not include a trailing slash. Usually ends with `/.well-known/openid-configuration`.
* `name`\
Display name for the OpenID Connect provider shown on the login page.
### Configuration for your OpenID Connect Provider
#### Redirect URI
The OpenID server will likely require a redirect URI. This URL will usually look something like this:
```none
{MEDIAMANAGER_URL}/api/v1/auth/oauth/callback
```
!!! warning
It is very important that you set the correct callback URI, otherwise it won't work!
#### Authentik Example
Here is an example configuration for the OpenID Connect provider for Authentik.
![authentik-redirect-url-example](<../assets/assets/authentik redirect url example.png>)
## Example Configuration
Here's a complete example of the authentication section in your `config.toml`:
```toml title="config.toml"
[auth]
token_secret = "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6"
session_lifetime = 604800 # 1 week
admin_emails = ["admin@example.com", "manager@example.com"]
email_password_resets = true
[auth.openid_connect]
enabled = true
client_id = "mediamanager-client"
client_secret = "your-secret-key-here"
configuration_endpoint = "https://auth.example.com/.well-known/openid-configuration"
name = "Authentik"
```

View File

@@ -0,0 +1,42 @@
---
description: >-
These settings configure the core backend application through the config.toml
file. All backend configuration is now centralized in this TOML file instead
of environment variables.
---
# Backend
## General Settings (`[misc]`)
* `frontend_url`\
The URL the frontend will be accessed from. This is required. Do not include a trailing slash. Default is `http://localhost:8000`.
Example: if you are accessing MediaManager at `http://example.com/media`, set this to `http://example.com/media`.
If you are accessing MediaManager at the root of a domain, e.g. `https://mediamanager.example.com`, set this to `https://mediamanager.example.com`.
`frontend_url` does not affect where the server binds. It also does not configure a base path prefix. For prefixes, see [URL Prefix](../advanced-features/url-prefix.md).
* `cors_urls`\
A list of origins you are going to access the API from. Do not include trailing slashes.
* `development`\
Set to `true` to enable development mode. Default is `false`.
## Example Configuration
Here's a complete example of the general settings section in your `config.toml`:
```toml title="config.toml"
[misc]
# REQUIRED: Change this to match your actual frontend domain.
frontend_url = "http://mediamanager.dev"
cors_urls = ["http://localhost:8000"]
# Optional: Development mode (set to true for debugging)
development = false
```
!!! info
The `frontend_url` is the most important setting to configure correctly. Make sure it matches your actual deployment URLs.

View File

@@ -4,22 +4,20 @@ MediaManager supports custom libraries, allowing you to add multiple folders for
## Configuration ## Configuration
Custom libraries are configured in the `mis` section in the `config.toml` file. You can add as many libraries as you need. Custom libraries are configured in the `misc` section in the `config.toml` file. You can add as many libraries as you need.
<note> !!! info
You are not limited to `/data/tv` or `/data/movies`, you can choose the entire path freely!
You are not limited to `/data/tv` or `/data/movies`, you can choose the entire path freely!
</note>
### Movie Libraries ### Movie Libraries
To add custom movie libraries, you need to add a `[[misc.movie_libraries]]` section for each library. Each library requires a `name` and a `path`. To add custom movie libraries, add a `[[misc.movie_libraries]]` section for each library. Each library requires a `name` and a `path`.
Here is an example of how to configure two movie libraries: Example — configuring two movie libraries:
```toml ```toml
[misc] [misc]
# ... other misc settings # ... other misc settings
[[misc.movie_libraries]] [[misc.movie_libraries]]
@@ -35,12 +33,13 @@ In this example, MediaManager will scan both `/data/movies/action` and `/data/mo
### TV Show Libraries ### TV Show Libraries
Similarly, to add custom TV show libraries, you need to add a `[[misc.tv_libraries]]` section for each library. Each library requires a `name` and a `path`. Similarly, to add custom TV show libraries, add a `[[misc.tv_libraries]]` section for each library. Each library requires a `name` and a `path`.
Here is an example of how to configure two TV show libraries: Example — configuring two TV show libraries:
```toml ```toml
[misc] [misc]
# ... other misc settings # ... other misc settings
[[misc.tv_libraries]] [[misc.tv_libraries]]
@@ -50,5 +49,4 @@ path = "/data/tv/live-action"
[[misc.tv_libraries]] [[misc.tv_libraries]]
name = "Animation" name = "Animation"
path = "/data/tv/animation" path = "/data/tv/animation"
``` ```

View File

@@ -0,0 +1,32 @@
# Database
Database settings are configured in the `[database]` section of your `config.toml` file. MediaManager uses PostgreSQL as its database backend.
## Database Settings (`[database]`)
* `host`\
Hostname or IP of the PostgreSQL server. Default is `localhost`.
* `port`\
Port number of the PostgreSQL server. Default is `5432`.
* `user`\
Username for the PostgreSQL connection. Default is `MediaManager`.
* `password`\
Password for the PostgreSQL user. Default is `MediaManager`.
* `dbname`\
Name of the PostgreSQL database. Default is `MediaManager`.
## Example Configuration
Here's a complete example of the database section in your `config.toml`:
```toml title="config.toml"
[database]
host = "db"
port = 5432
user = "MediaManager"
password = "your_secure_password"
dbname = "MediaManager"
```
!!! info
In docker-compose deployments the container name is simultaneously its hostname, so you can use "db" or "postgres" as host.

View File

@@ -6,93 +6,59 @@ Download client settings are configured in the `[torrents]` section of your `con
qBittorrent is a popular BitTorrent client that MediaManager can integrate with for downloading torrents. qBittorrent is a popular BitTorrent client that MediaManager can integrate with for downloading torrents.
- `enabled` * `enabled`\
Set to `true` to enable qBittorrent integration. Default is `false`.
Set to `true` to enable qBittorrent integration. Default is `false`. * `host`\
Hostname or IP of the qBittorrent server. Include the protocol (http/https).
- `host` * `port`\
Port of the qBittorrent Web UI/API. Default is `8080`.
Hostname or IP of the qBittorrent server. Include the protocol (http/https). * `username`\
Username for qBittorrent Web UI authentication. Default is `admin`.
- `port` * `password`\
Password for qBittorrent Web UI authentication. Default is `admin`.
Port of the qBittorrent Web UI/API. Default is `8080`.
- `username`
Username for qBittorrent Web UI authentication. Default is `admin`.
- `password`
Password for qBittorrent Web UI authentication. Default is `admin`.
## Transmission Settings (`[torrents.transmission]`) ## Transmission Settings (`[torrents.transmission]`)
<note> !!! info
The downloads path in Transmission and MediaManager must be the same, i.e. the path `/data/torrents` must link to the same volume for both containers.
The downloads path in Transmission and MediaManager __must__ be the same, i.e. the path `/data/torrents` must link to the same volume for both containers.
</note>
Transmission is a BitTorrent client that MediaManager can integrate with for downloading torrents. Transmission is a BitTorrent client that MediaManager can integrate with for downloading torrents.
- `enabled` * `enabled`\
Set to `true` to enable Transmission integration. Default is `false`.
Set to `true` to enable Transmission integration. Default is `false`. * `username`\
Username for Transmission RPC authentication.
- `username` * `password`\
Password for Transmission RPC authentication.
Username for Transmission RPC authentication. * `https_enabled`\
Set to `true` if your Transmission RPC endpoint uses HTTPS. Default is `true`.
- `password` * `host`\
Hostname or IP of the Transmission server (without protocol).
Password for Transmission RPC authentication. * `port`\
Port of the Transmission RPC endpoint. Default is `9091`.
- `https_enabled` * `path`\
RPC request path target. Usually `/transmission/rpc`.
Set to `true` if your Transmission RPC endpoint uses HTTPS. Default is `true`.
- `host`
Hostname or IP of the Transmission server (without protocol).
- `port`
Port of the Transmission RPC endpoint. Default is `9091`.
- `path`
RPC request path target. Usually `/transmission/rpc`.
## SABnzbd Settings (`[torrents.sabnzbd]`) ## SABnzbd Settings (`[torrents.sabnzbd]`)
SABnzbd is a Usenet newsreader that MediaManager can integrate with for downloading NZB files. SABnzbd is a Usenet newsreader that MediaManager can integrate with for downloading NZB files.
- `enabled` * `enabled`\
Set to `true` to enable SABnzbd integration. Default is `false`.
Set to `true` to enable SABnzbd integration. Default is `false`. * `host`\
Hostname or IP of the SABnzbd server, it needs to include `http(s)://`.
- `host` * `port`\
Port of the SABnzbd API. Default is `8080`.
Hostname or IP of the SABnzbd server, it needs to include `http(s)://`. * `api_key`\
API key for SABnzbd. You can find this in SABnzbd's configuration under "General" → "API Key".
- `port` * `base_path`\
API base path for SABnzbd. It usually ends with `/api`, the default is `/api`.
Port of the SABnzbd API. Default is `8080`.
- `api_key`
API key for SABnzbd. You can find this in SABnzbd's configuration under "General" → "API Key".
- `base_path`
API base path for SABnzbd. It usually ends with `/api`, the default is `/api`.
## Example Configuration ## Example Configuration
Here's a complete example of the download clients section in your `config.toml`: Here's a complete example of the download clients section in your `config.toml`:
```toml ```toml title="config.toml"
[torrents] [torrents]
# qBittorrent configuration # qBittorrent configuration
[torrents.qbittorrent] [torrents.qbittorrent]
@@ -124,7 +90,7 @@ Here's a complete example of the download clients section in your `config.toml`:
When using Docker Compose, make sure your download clients are accessible from the MediaManager backend: When using Docker Compose, make sure your download clients are accessible from the MediaManager backend:
```yaml ```yaml title="docker-compose.yml"
services: services:
# MediaManager backend # MediaManager backend
backend: backend:
@@ -152,10 +118,8 @@ services:
# ... other configuration ... # ... other configuration ...
``` ```
<note> !!! warning
You should enable only one BitTorrent and only one Usenet Download Client at any time. You should enable only one BitTorrent and only one Usenet Download Client at any time.
</note>
<tip> !!! info
Make sure the download directories in your download clients are accessible to MediaManager for proper file management and organization. Make sure the download directories in your download clients are accessible to MediaManager for proper file management and organization.
</tip>

View File

@@ -0,0 +1,67 @@
# Indexers
Indexer settings are configured in the `[indexers]` section of your `config.toml` file. MediaManager supports both Prowlarr and Jackett as indexer providers.
## Prowlarr (`[indexers.prowlarr]`)
* `enabled`\
Set to `true` to enable Prowlarr. Default is `false`.
* `url`\
Base URL of your Prowlarr instance.
* `api_key`\
API key for Prowlarr. You can find this in Prowlarr's settings under General.
* `timeout_seconds`\
Timeout in seconds for requests to Prowlarr. Default is `60`.
!!! warning
Symptoms of timeouts are typically no search results ("No torrents found!") in conjunction with logs showing read timeouts.
<details>
<summary>Example timeout log</summary>
```none
DEBUG - media_manager.indexer.utils -
follow_redirects_to_final_torrent_url():
An error occurred during the request for <some-url>:
HTTPConnectionPool(host='<some-host>', port=<some-port>):
Read timed out. (read timeout=10)
```
</details>
***
## Jackett (`[indexers.jackett]`)
* `enabled`\
Set to `true` to enable Jackett. Default is `false`.
* `url`\
Base URL of your Jackett instance.
* `api_key`\
API key for Jackett. You can find this in Jackett's dashboard.
* `indexers`\
List of indexer names to use with Jackett.
* `timeout_seconds`\
Timeout in seconds for requests to Jackett. Refer to the Prowlarr section for details.
***
## Example Configuration
```toml title="config.toml"
[indexers]
[indexers.prowlarr]
enabled = true
url = "http://prowlarr:9696"
api_key = "your_prowlarr_api_key"
timeout_seconds = 60
[indexers.jackett]
enabled = false
url = "http://jackett:9117"
api_key = "your_jackett_api_key"
indexers = ["1337x", "rarbg"]
timeout_seconds = 60
```

View File

@@ -0,0 +1,12 @@
# Logging
MediaManager automatically logs events and errors to help with troubleshooting and monitoring. These logs are emitted to the console (stdout) by default, and to a json-formatted log file.
## Configuring Logging
The following are configured as environment variables.
* `LOG_FILE`\
Path to the JSON log file. Default is `/app/config/media_manager.log`. The directory must exist and be writable.
* `MEDIAMANAGER_LOG_LEVEL`\
Logging level. Default is `INFO`. Supported values: `DEBUG`, `INFO`, `WARNING`, `ERROR`.

View File

@@ -1,88 +1,63 @@
# Notifications # Notifications
These settings are configured in the `[notifications]` section of your `config.toml` file. These settings are configured in the `[notifications]` section of your `config.toml` file.
### SMTP Configuration (`[notifications.smtp_config]`) ## SMTP Configuration (`[notifications.smtp_config]`)
For sending emails, MediaManager uses the SMTP protocol. You can use any SMTP server, like Gmail or SMTP2GO. For sending emails, MediaManager uses the SMTP protocol. You can use any SMTP server, like Gmail or SMTP2GO.
- `smtp_host` * `smtp_host`\
Hostname of the SMTP server.
* `smtp_port`\
Port of the SMTP server.
* `smtp_user`\
Username for the SMTP server.
* `smtp_password`\
Password (or app password) for the SMTP server.
* `from_email`\
From-address used when sending emails.
* `use_tls`\
Set to `true` to use TLS for the SMTP connection. Default is `true`.
Hostname of the SMTP server. ## Email Notifications (`[notifications.email_notifications]`)
- `smtp_port` Controls which emails receive notifications.
Port of the SMTP server. * `enabled`\
Set to `true` to enable email notifications. Default is `false`.
* `emails`\
List of email addresses to send notifications to.
- `smtp_user` ## Gotify Notifications (`[notifications.gotify]`)
Username for the SMTP server. * `enabled`\
Set to `true` to enable Gotify notifications. Default is `false`.
* `api_key`\
API key for Gotify.
* `url`\
Base URL of your Gotify instance. Do not include a trailing slash.
- `smtp_password` ## Ntfy Notifications (`[notifications.ntfy]`)
Password or app password for the SMTP server. * `enabled`\
Set to `true` to enable Ntfy notifications. Default is `false`.
* `url`\
URL of your ntfy instance plus the topic.
- `from_email` ## Pushover Notifications (`[notifications.pushover]`)
Email address from which emails will be sent. * `enabled`\
Set to `true` to enable Pushover notifications. Default is `false`.
- `use_tls` * `api_key`\
API key for Pushover.
Set to `true` to use TLS for the SMTP connection. Default is `true`. * `user`\
User key for Pushover.
### Email Notifications (`[notifications.email_notifications]`)
- `enabled`
Set to `true` to enable email notifications. Default is `false`.
- `emails`
List of email addresses to send notifications to.
### Gotify Notifications (`[notifications.gotify]`)
- `enabled`
Set to `true` to enable Gotify notifications. Default is `false`.
- `api_key`
API key for Gotify.
- `url`
Base URL of your Gotify instance. Note the lack of a trailing slash.
### Ntfy Notifications (`[notifications.ntfy]`)
- `enabled`
Set to `true` to enable Ntfy notifications. Default is `false`.
- `url`
URL of your ntfy instance plus the topic.
### Pushover Notifications (`[notifications.pushover]`)
- `enabled`
Set to `true` to enable Pushover notifications. Default is `false`.
- `api_key`
API key for Pushover.
- `user`
User key for Pushover.
## Example Configuration ## Example Configuration
Here's a complete example of the notifications section in your `config.toml`: Here's a complete example of the notifications section in your `config.toml`:
```toml ```toml title="config.toml"
[notifications] [notifications]
# SMTP settings for email notifications and password resets # SMTP settings for email notifications and password resets
[notifications.smtp_config] [notifications.smtp_config]
@@ -116,6 +91,6 @@ Here's a complete example of the notifications section in your `config.toml`:
user = "your_pushover_user_key" user = "your_pushover_user_key"
``` ```
<note>
!!! info
You can enable multiple notification methods simultaneously. For example, you could have both email and Gotify notifications enabled at the same time. You can enable multiple notification methods simultaneously. For example, you could have both email and Gotify notifications enabled at the same time.
</note>

View File

@@ -0,0 +1,133 @@
# Scoring Rulesets
Scoring rulesets in MediaManager allow you to flexibly control which releases are preferred or avoided when searching for media. Each ruleset is a collection of scoring rules that can be assigned to one or more libraries. When MediaManager evaluates releases, it applies the relevant ruleset(s) to adjust the score of each result, influencing which releases are selected for download.
## How Rulesets Work
* Rulesets are defined in the configuration and contain a list of rule names and the libraries they apply to.
* Scoring rules can target keywords in release titles or specific indexer flags.
* When searching for a release, MediaManager checks which library the media belongs to and applies the corresponding ruleset.
## Rules
Rules define how MediaManager scores releases based on their titles or indexer flags. You can create rules that:
* Prefer releases with specific codecs (e.g., H.265 over H.264).
* Avoid releases with certain keywords (e.g., "CAM", "TS", "Nuked").
* Reject releases that do not meet certain criteria (e.g., non-freeleech releases).
* and more.
!!! info
The keywords and flags are compared case-insensitively.
### Title Rules
Title rules allow you to adjust the score of a release based on the presence (or absence) of specific keywords in the release title. This is useful for preferring or avoiding certain encodings, sources, or other characteristics that are typically included in release names.
Each title rule consists of:
* `name`\
A unique identifier for the rule.
* `keywords`\
List of keywords to search for in the release title.
* `score_modifier`\
Amount to add or subtract from the score if a keyword matches.
* `negate`\
If `true`, the rule applies when none of the keywords are present.
Examples for Title Rules
```toml title="config.toml"
[[indexers.title_scoring_rules]]
name = "prefer_h265"
keywords = ["h265", "hevc", "x265"]
score_modifier = 100
negate = false
[[indexers.title_scoring_rules]]
name = "avoid_cam"
keywords = ["cam", "ts"]
score_modifier = -10000
negate = false
```
* The first rule increases the score for releases containing "h265", "hevc", or "x265".
* The second rule heavily penalizes releases containing "cam" or "ts".
If `negate` is set to `true`, the `score_modifier` is applied only if none of the keywords are found in the title.
### Indexer Flag Rules
Indexer flag rules adjust the score based on flags provided by the indexer (such as `freeleech`, `nuked`, etc). These flags are often used to indicate special properties or warnings about a release.
Each indexer flag rule consists of:
* `name`\
A unique identifier for the rule.
* `flags`\
List of indexer flags to match.
* `score_modifier`\
Amount to add or subtract from the score if a flag matches.
* `negate`\
If `true`, the rule applies when none of the flags are present.
Examples for Indexer Flag Rules
```toml title="config.toml"
[[indexers.indexer_flag_scoring_rules]]
name = "reject_non_freeleech"
flags = ["freeleech", "freeleech75"]
score_modifier = -10000
negate = true
[[indexers.indexer_flag_scoring_rules]]
name = "reject_nuked"
flags = ["nuked"]
score_modifier = -10000
negate = false
```
* The first rule penalizes releases that do not have the "freeleech" or "freeleech75" flag.
* The second rule penalizes releases that are marked as "nuked".
If `negate` is set to `true`, the `score_modifier` is applied only if none of the flags are present on the release.
## Example
```toml title="config.toml"
[[indexers.scoring_rule_sets]]
name = "default"
libraries = ["ALL_TV", "ALL_MOVIES"]
rule_names = ["prefer_h265", "avoid_cam", "reject_nuked"]
[[indexers.scoring_rule_sets]]
name = "strict_quality"
libraries = ["ALL_MOVIES"]
rule_names = ["prefer_h265", "avoid_cam", "reject_non_freeleech"]
```
## Libraries
The libraries that are mentioned in the preceding example are explained in greater detail in the [Library config section](custom-libraries.md).
### Special Libraries
You can use special library names in your rulesets:
* `ALL_TV`: Applies the ruleset to all TV libraries.
* `ALL_MOVIES`: Applies the ruleset to all movie libraries.
* `Default`: Applies the ruleset to all media that is not part of a custom library.
This allows you to set global rules for all TV or movie content, or provide fallback rules for uncategorized media.
!!! info
You don't need to create lots of libraries with different directories, multiple libraries can share the same directory. You can set multiple (unlimited) libraries to the default directory `/data/movies` or `/data/tv` and use different rulesets with them.
## Relation to Sonarr/Radarr Profiles
MediaManager's scoring rules and rulesets system is an alternative to Sonarr's Quality, Custom, and Release Profiles. This system is designed to be more intuitive and flexible.
* Quality Profiles: Use scoring rules to prefer or avoid certain codecs, resolutions, or other quality indicators.
* Custom/Release Profiles: Use title or flag-based rules to match or exclude releases based on keywords or indexer flags.
This approach provides a powerful and transparent way to fine-tune your automation.

View File

@@ -0,0 +1,449 @@
---
description: >-
This section is for those who want to contribute to Media Manager or
understand its internals.
---
# Developer Guide
## Source Code directory structure
* `media_manager/`: Backend FastAPI application
* `web/`: Frontend SvelteKit application
* `docs/`: Documentation (MkDocs)
* `metadata_relay/`: Metadata relay service, also FastAPI
## Special Dev Configuration
### Environment Variables
MediaManager uses various environment variables for configuration. In the Docker development setup (`docker-compose.dev.yaml`), most of these are automatically configured for you.
#### Backend Variables
* `BASE_PATH`\
Base path for the app (for subdirectory deployments).
* `PUBLIC_VERSION`\
Version string displayed in `/api/v1/health`.
* `FRONTEND_FILES_DIR`\
Directory for built frontend files (e.g. `/app/web/build` in Docker).
* `MEDIAMANAGER_MISC__DEVELOPMENT`\
When set to `TRUE`, enables FastAPI hot-reloading in Docker.
#### Frontend Variables
* `PUBLIC_API_URL`\
API URL for backend communication (auto-configured via Vite proxy in Docker).
* `PUBLIC_VERSION`\
Version string displayed in the frontend UI.
* `BASE_PATH`\
Base path for frontend routing (matches backend `BASE_PATH`).
#### Docker Development Variables
* `DISABLE_FRONTEND_MOUNT`\
When `TRUE`, disables mounting built frontend files (allows separate frontend container).
!!! info
This is automatically set in `docker-compose.dev.yaml` to enable the separate frontend development container
#### Configuration Files
* Backend: `res/config/config.toml` (created from `config.dev.toml`)
* Frontend: `web/.env` (created from `.env.example`)
## Contributing
* Consider opening an issue to discuss changes before starting work
## Setting up the Development Environment
I use IntellijIdea with the Pycharm and Webstorm plugins to develop this, but this guide should also work with VSCode. Normally I'd recommend Intellij, but unfortunately only Intellij Ultimate has support for FastAPI and some other features.
### Recommended VSCode Plugins
* Python
* Svelte for VSCode
### Recommended Intellij/Pycharm Plugins
* Python
* Svelte
* Pydantic
* Ruff
* VirtualKit
### Recommended Development Workflow
The recommended way to develop MediaManager is using the fully Dockerized setup with `docker-compose.dev.yaml`. This ensures you're working in the same environment as production and makes it easy for new contributors to get started without installing Python, Node.js, or other dependencies locally.
The development environment includes:
* Backend (FastAPI) with automatic hot-reloading for Python code changes
* Frontend (SvelteKit/Vite) with Hot Module Replacement (HMR) for instant updates
* Database (PostgreSQL) pre-configured and ready to use
#### What supports hot reloading and what does not
* Python code changes (.py files), Frontend code changes (.svelte, .ts, .css) and configuration changes (config.toml) reload automatically.
* Changing the backend dependencies (pyproject.toml) requires rebuilding: `docker compose -f docker-compose.dev.yaml build mediamanager`
* Changing the frontend dependencies (package.json) requires restarting the frontend container: `docker compose -f docker-compose.dev.yaml restart frontend`
* Database migrations: Automatically run on backend container startup
This approach eliminates the need for container restarts during normal development and provides the best developer experience with instant feedback for code changes.
#### How the Frontend Connects to the Backend
In the Docker development setup, the frontend and backend communicate through Vite's proxy configuration:
* Frontend runs on: `http://localhost:5173` (exposed from Docker)
* Backend runs on: `http://mediamanager:8000` (Docker internal network)
* Vite proxy: Automatically forwards all `/api/*` requests from frontend to backend
This means when your browser makes a request to `http://localhost:5173/api/v1/tv/shows`, Vite automatically proxies it to `http://mediamanager:8000/api/v1/tv/shows`. The `PUBLIC_API_URL` environment variable is set to use this proxy, so you don't need to configure anything manually.
### Setting up the full development environment with Docker (Recommended)
### Prepare config files
Create config directory (only needed on first run) and copy example config files:
```bash
mkdir -p res/config # Only needed on first run
cp config.dev.toml res/config/config.toml
cp web/.env.example web/.env
```
### Start all services
Recommended: Use make commands for easy development
```bash
# Recommended: Use make commands for easy development
make up
```
Alternative: Use docker compose directly (if make is not available)
```bash
docker compose -f docker-compose.dev.yaml up
```
### Access the application
* Frontend (with HMR): http://localhost:5173
* Backend API: http://localhost:8000
* Database: localhost:5432
The default user email is `admin@example.com` and password is `admin`, these are printed out in the logs accessible with `make logs`.
Now you can edit code and see changes instantly:
* Edit Python files → Backend auto-reloads
* Edit Svelte/TypeScript files → Frontend HMR updates in browser
* Edit config.toml → Changes apply immediately
!!! info
Run `make help` to see all available development commands including `make down`, `make logs`, `make app` (shell into backend), and more.
## Setting up the backend development environment (Local)
### Clone & prerequisites
1. Clone the repository
2. cd into repo root
3. Install `uv`: https://docs.astral.sh/uv/getting-started/installation/
4. Verify installation:
```bash
uv --version
```
### Install Python with uv
```bash
uv python install 3.13
```
### Create virtual environment
```bash
uv venv --python 3.13
```
### Install dependencies
```bash
uv sync
```
### Run database migrations
```bash
uv run alembic upgrade head
```
### Run the backend (development mode)
```bash
uv run fastapi run media_manager/main.py --reload --port 8000
```
### Formatting & linting
* Format code:
```bash
ruff format .
```
* Lint code:
```bash
ruff check .
```
## Setting up the frontend development environment (Local, Optional)
### Clone & change dir
1. Clone the repository
2. cd into repo root
3. cd into `web` directory
### Install Node.js (example using nvm-windows)
I used nvm-windows:
```powershell
nvm install 24.1.0
nvm use 24.1.0
```
If using PowerShell you may need:
```powershell
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
```
### Create .env for frontend
```bash
cp .env.example .env
```
Update `PUBLIC_API_URL` if your backend is not at `http://localhost:8000`
### Install dependencies and run dev server
```bash
npm install
npm run dev
```
### Format & lint
* Format:
```bash
npm run format
```
* Lint:
```bash
npm run lint
```
!!! info
If running frontend locally, make sure to add `http://localhost:5173` to the `cors_urls` in your backend config file.
## Troubleshooting
### Common Docker Development Issues
<details>
<summary>Port already in use errors</summary>
* Check if ports 5173, 8000, or 5432 are already in use:
* macOS/Linux: `lsof -i :5173`
* Windows: `netstat -ano | findstr :5173`
* Stop conflicting services or change ports in `docker-compose.dev.yaml`
</details>
<details>
<summary>Container not showing code changes</summary>
* Verify volume mounts are correct in `docker-compose.dev.yaml`
* For backend: Ensure `./media_manager:/app/media_manager` is mounted
* For frontend: Ensure `./web:/app` is mounted
* On Windows: Check that file watching is enabled in Docker Desktop settings
</details>
<details>
<summary>Frontend changes not updating</summary>
* Check that the frontend container is running: `make ps` or `docker compose -f docker-compose.dev.yaml ps`
* Verify Vite's file watching is working (should see HMR updates in browser console)
* Try restarting the frontend container:
```bash
docker compose -f docker-compose.dev.yaml restart frontend
```
</details>
<details>
<summary>Backend changes not reloading</summary>
* Verify `MEDIAMANAGER_MISC__DEVELOPMENT=TRUE` is set in `docker-compose.dev.yaml`
* Check backend logs:
```bash
make logs ARGS="--follow mediamanager"
# or
docker compose -f docker-compose.dev.yaml logs -f mediamanager
```
* If dependencies changed, rebuild:
```bash
docker compose -f docker-compose.dev.yaml build mediamanager
```
</details>
<details>
<summary>Database migration issues</summary>
* Migrations run automatically on container startup
* To run manually:
```bash
make app
uv run alembic upgrade head
```
* To create new migration:
```bash
make app
uv run alembic revision --autogenerate -m "description"
```
</details>
<details>
<summary>Viewing logs</summary>
* All services: `make logs`
* Follow logs in real-time: `make logs ARGS="--follow"`
* Specific service: `make logs ARGS="mediamanager --follow"`
</details>
<details>
<summary>Interactive debugging (shell into containers)</summary>
* Shell into backend:
```bash
make app
# or
docker compose -f docker-compose.dev.yaml exec -it mediamanager bash
```
* Shell into frontend:
```bash
make frontend
# or
docker compose -f docker-compose.dev.yaml exec -it frontend sh
```
* Once inside, you can run commands like `uv run alembic upgrade head`, `npm install`, etc.
</details>
<details>
<summary>Volume permission issues (Linux)</summary>
* Docker containers may create files as root, causing permission issues, which can make the login page fail to show up.
Solution:
```bash
sudo chown -R $USER:$USER res/
```
* Alternatively: Run containers with your user ID or use Docker's `user:` directive (may fail in some setups).
</details>
<details>
<summary>Complete reset</summary>
If all else fails, you can completely reset your development environment:
```bash
make down
docker compose -f docker-compose.dev.yaml down -v # Remove volumes
docker compose -f docker-compose.dev.yaml build --no-cache # Rebuild without cache
make up
```
</details>
## Tech Stack
### Backend
* Python
* FastAPI
* SQLAlchemy
* Pydantic and Pydantic-Settings
* Alembic
### Frontend
* TypeScript
* SvelteKit
* Tailwind CSS
* shadcn-svelte
* openapi-ts
* openapi-fetch
### CI/CD
* GitHub Actions

View File

@@ -0,0 +1,14 @@
# Documentation
MediaManager uses [MkDocs](https://www.mkdocs.org/) with
the [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) theme for documentation.
The files for the documentation are in the `/docs` directory.
To preview the documentation locally, you need to have mkdocs or Docker installed.
## How to preview the documentation locally with docker
1. Run the mkdocs container in `docker-compose.dev.yaml`
2. Open `http://127.0.0.1:9000/` in your browser.

Some files were not shown because too many files have changed in this diff Show More