From df7d00ad99a55b17e9cf18cb57f3ce8dd2fb8050 Mon Sep 17 00:00:00 2001 From: wjbeckett Date: Fri, 18 Jul 2025 12:54:08 +1000 Subject: [PATCH] feat: improve container setup and fix deployment issues - Move images directory from /data/images to /app/images to separate app data from user media - Implement config folder approach instead of direct file mounting - Add automatic config initialization with example config on first boot - Remove hardcoded media directory environment variables from Dockerfile - Update startup script to handle config folder setup and validation - Only create application-managed directories, not user media directories - Update docker-compose.yaml to use config folder volume mapping Fixes container startup failures when config.toml doesn't exist and improves separation between application data and user media directories. --- Dockerfile | 8 +- SETUP_IMPROVEMENTS.md | 101 ++++++++++++++++++++ config.example.toml | 164 ++++++++++++++++++++++++++++++++ config.toml | 7 +- docker-compose.yaml | 8 +- media_manager/config.py | 6 +- media_manager/main.py | 13 ++- mediamanager-backend-startup.sh | 30 ++++++ 8 files changed, 321 insertions(+), 16 deletions(-) create mode 100644 SETUP_IMPROVEMENTS.md create mode 100644 config.example.toml diff --git a/Dockerfile b/Dockerfile index b633ebd..5ed1550 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,12 +16,9 @@ LABEL author="github.com/maxdorninger" LABEL version=${VERSION} LABEL description="Docker image for MediaManager" -ENV MISC__IMAGE_DIRECTORY=/data/images \ - MISC__TV_DIRECTORY=/data/tv \ - MISC__MOVIE_DIRECTORY=/data/movies \ - MISC__TORRENT_DIRECTORY=/data/torrents \ +ENV MISC__IMAGE_DIRECTORY=/app/images \ PUBLIC_VERSION=${VERSION} \ - CONFIG_FILE="/app/config.toml"\ + CONFIG_DIR="/app/config"\ BASE_PATH=${BASE_PATH}\ FRONTEND_FILES_DIR="/app/web/build" @@ -37,6 +34,7 @@ COPY pyproject.toml uv.lock ./ RUN uv sync --locked COPY --chmod=755 mediamanager-backend-startup.sh . +COPY config.example.toml . COPY media_manager ./media_manager COPY alembic ./alembic COPY alembic.ini . diff --git a/SETUP_IMPROVEMENTS.md b/SETUP_IMPROVEMENTS.md new file mode 100644 index 0000000..3bc65a9 --- /dev/null +++ b/SETUP_IMPROVEMENTS.md @@ -0,0 +1,101 @@ +# MediaManager Setup Improvements + +This document outlines the improvements made to the MediaManager container setup to address common deployment issues. + +## Changes Made + +### 1. Automatic Directory Creation +- **Problem**: The application expected certain directories to exist but only created them in development mode +- **Solution**: The application now creates only the directories it manages (like `/app/images` for application data). Media directories are user-configured and mounted as volumes, so the application doesn't need to create them. + +### 2. Images Directory Relocated +- **Problem**: Images were stored in `/data/images` which should be reserved for user media +- **Solution**: Images are now stored in `/app/images` within the application context, keeping user data separate from application data + +### 3. Config Folder Approach +- **Problem**: The container required users to create a `config.toml` file before first boot, causing startup failures +- **Solution**: + - Changed from direct file mapping to config folder mapping + - Added automatic config initialization on first boot + - Example config is copied to the config folder if no config exists + - Container can now start successfully without pre-existing configuration + +## New Volume Structure + +### Before: +```yaml +volumes: + - ./data/:/data/ + - ./config.toml:/app/config.toml # Required file that users had to create +``` + +### After: +```yaml +volumes: + - ./data/:/data/ + - ./config/:/app/config/ # Config folder that gets auto-initialized +``` + +## First Boot Process + +1. Container starts and checks for config directory +2. If config directory doesn't exist, it's created +3. If `config.toml` doesn't exist in the config directory, the example config is copied +4. Application-managed directories (like images) are created automatically +5. Database migrations run +6. Application starts successfully + +Note: Media directories are NOT created by the application - they should be mounted from your host system. + +## User Experience Improvements + +- **No pre-setup required**: Users can now run `docker-compose up` immediately +- **Clear configuration path**: Config file location is clearly communicated during startup +- **Example configuration**: Users get a working example config with helpful comments +- **Separation of concerns**: User data (`/data/`) is separate from app data (`/app/images`) + +## Migration for Existing Users + +If you have an existing setup: + +1. Create a `config` folder in your docker-compose directory +2. Move your existing `config.toml` file into the `config` folder +3. Update your `docker-compose.yaml` to use the new volume mapping +4. Remove any existing `/data/images` folder (images will now be stored in the container) + +## Directory Management + +### Application-Managed Directories +- `/app/images`: Created automatically by the application for storing metadata images +- `/app/config`: Config folder mounted as volume, auto-initialized on first boot + +### User-Managed Directories +- Media directories (TV shows, movies, torrents): These are defined in your `config.toml` and should be mounted as volumes in your `docker-compose.yaml` +- The application does NOT create these directories - they should exist on your host system and be properly mounted + +### Example Volume Mapping +```yaml +volumes: + # Your actual media directories + - /path/to/your/tv/shows:/data/tv + - /path/to/your/movies:/data/movies + # Config folder (auto-initialized) + - ./config/:/app/config/ +``` + +Then in your `config.toml`: +```toml +[[misc.tv_libraries]] +name = "TV Shows" +path = "/data/tv" # This matches the container path from volume mount + +[[misc.movie_libraries]] +name = "Movies" +path = "/data/movies" # This matches the container path from volume mount +``` + +## Environment Variables + +- `CONFIG_DIR`: Path to config directory (default: `/app/config`) +- `MISC__IMAGE_DIRECTORY`: Path to images directory (default: `/app/images`) +- Media directory environment variables removed - these should be configured in `config.toml` \ No newline at end of file diff --git a/config.example.toml b/config.example.toml new file mode 100644 index 0000000..5ca15ba --- /dev/null +++ b/config.example.toml @@ -0,0 +1,164 @@ +# MediaManager Example Configuration File +# This file contains all available configuration options for MediaManager +# Documentation: https://maxdorninger.github.io/MediaManager/introduction.html +# +# 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. + +[misc] +# it's very likely that you need to change this for MediaManager to work +frontend_url = "http://localhost:3000/" # note the trailing slash +cors_urls = ["http://localhost:3000", "http://localhost:8000"] # note the lack of a trailing slash + +# you probaly don't need to change this +development = false + +# Custom Media Libraries +# These paths should match your volume mounts in docker-compose.yaml +# Example: if you mount "./movies:/media/movies" then use path = "/media/movies/subdirectory" +[[misc.tv_libraries]] +name = "Live Action" +path = "/data/tv/live-action" # Change this to match your actual TV shows location + +[[misc.movie_libraries]] +name = "Documentary" +path = "/data/movies/documentary" # Change this to match your actual movies location + +[database] +host = "localhost" +port = 5432 +user = "MediaManager" +password = "MediaManager" +dbname = "MediaManager" + +[auth] +email_password_resets = false # if true, you also need to set up SMTP (notifications.smtp_config) + +token_secret = "CHANGE_ME_GENERATE_RANDOM_STRING" # generate a random string with "openssl rand -hex 32", e.g. here https://www.cryptool.org/en/cto/openssl/ +session_lifetime = 86400 # this is how long you will be logged in after loggin in, in seconds +admin_emails = ["admin@example.com", "admin2@example.com"] + + # OpenID Connect settings + [auth.openid_connect] + enabled = false + client_id = "" + client_secret = "" + configuration_endpoint = "https://openid.example.com/.well-known/openid-configuration" + name = "OpenID" + +[notifications] + # SMTP settings for email notifications and email password resets + [notifications.smtp_config] + smtp_host = "smtp.example.com" + smtp_port = 587 + smtp_user = "admin" + smtp_password = "admin" + from_email = "mediamanager@example.com" + use_tls = true + + # Email notification settings + [notifications.email_notifications] + enabled = false + emails = ["admin@example.com", "admin2@example.com"] # List of email addresses to send notifications to + + # Gotify notification settings + [notifications.gotify] + enabled = false + api_key = "" + url = "https://gotify.example.com" + + # Ntfy notification settings + [notifications.ntfy] + enabled = false + url = "https://ntfy.sh/your-topic" + + # Pushover notification settings + [notifications.pushover] + enabled = false + api_key = "" + user = "" + +[torrents] + # qBittorrent settings + [torrents.qbittorrent] + enabled = false + host = "http://localhost" + port = 8080 + username = "admin" + password = "admin" + + # Transmission settings + [torrents.transmission] + enabled = false + username = "admin" + password = "admin" + https_enabled = true + host = "localhost" + port = 9091 + path = "/transmission/rpc" # RPC request path target, usually "/transmission/rpc" + + # SABnzbd settings + [torrents.sabnzbd] + enabled = false + host = "localhost" + port = 8080 + api_key = "" + +[indexers] + # Prowlarr settings + [indexers.prowlarr] + enabled = false + url = "http://localhost:9696" + api_key = "" + + # Jackett settings + [indexers.jackett] + enabled = false + url = "http://localhost:9117" + api_key = "" + indexers = ["1337x","torrentleech"] # List of indexer names to use + + # Title-based scoring rules + [[indexers.title_scoring_rules]] + name = "prefer_h265" + keywords = ["h265", "hevc", "x265", "h.265","x.265"] + score_modifier = 100 + negate = false + + [[indexers.title_scoring_rules]] + name = "avoid_cam" + keywords = ["cam", "ts"] + score_modifier = -10000 + negate = false + + # Indexer flag-based scoring rules + [[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 + + # Scoring rulesets + [[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"] + +# its very unlikely that you need to change this +[metadata] + [metadata.tmdb] + tmdb_relay_url = "https://metadata-relay.maxid.me/tmdb" + + [metadata.tvdb] + tvdb_relay_url = "https://metadata-relay.maxid.me/tvdb" \ No newline at end of file diff --git a/config.toml b/config.toml index 86b4fc7..b5a3d0f 100644 --- a/config.toml +++ b/config.toml @@ -1,6 +1,9 @@ -# MediaManager Complete Configuration File +# MediaManager Example Configuration File # This file contains all available configuration options for MediaManager # Documentation: https://maxdorninger.github.io/MediaManager/introduction.html +# +# 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. [misc] # it's very likely that you need to change this for MediaManager to work @@ -29,7 +32,7 @@ dbname = "MediaManager" [auth] email_password_resets = false # if true, you also need to set up SMTP (notifications.smtp_config) -token_secret = "" # generate a random string with "openssl rand -hex 32", e.g. here https://www.cryptool.org/en/cto/openssl/ +token_secret = "CHANGE_ME_GENERATE_RANDOM_STRING" # generate a random string with "openssl rand -hex 32", e.g. here https://www.cryptool.org/en/cto/openssl/ session_lifetime = 86400 # this is how long you will be logged in after loggin in, in seconds admin_emails = ["admin@example.com", "admin2@example.com"] diff --git a/docker-compose.yaml b/docker-compose.yaml index 15c5517..056598f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -4,10 +4,12 @@ services: ports: - "8000:8000" environment: - - CONFIG_FILE=/app/config.toml + - CONFIG_DIR=/app/config volumes: - - ./data/:/data/ - - ./config.toml:/app/config.toml + # Mount your actual media directories here - these paths should match your config.toml + - ./data/:/data/ # Example: change ./data/ to your actual media root + # Config folder for application configuration + - ./config/:/app/config/ db: image: postgres:latest restart: unless-stopped diff --git a/media_manager/config.py b/media_manager/config.py index f263acd..eb5b1a0 100644 --- a/media_manager/config.py +++ b/media_manager/config.py @@ -20,7 +20,9 @@ from media_manager.torrent.config import TorrentConfig config_path = os.getenv("CONFIG_FILE") if config_path is None: - config_path = Path(__file__).parent.parent / "config.toml" + # Default to config folder approach + config_dir = os.getenv("CONFIG_DIR", "/app/config") + config_path = Path(config_dir) / "config.toml" else: config_path = Path(config_path) @@ -31,7 +33,7 @@ class LibraryItem(BaseSettings): class BasicConfig(BaseSettings): - image_directory: Path = Path(__file__).parent.parent / "data" / "images" + image_directory: Path = Path(__file__).parent.parent / "images" tv_directory: Path = Path(__file__).parent.parent / "data" / "tv" movie_directory: Path = Path(__file__).parent.parent / "data" / "movies" torrent_directory: Path = Path(__file__).parent.parent / "data" / "torrents" diff --git a/media_manager/main.py b/media_manager/main.py index 61f6f3c..edb0b84 100644 --- a/media_manager/main.py +++ b/media_manager/main.py @@ -100,11 +100,16 @@ from apscheduler.triggers.cron import CronTrigger # noqa: E402 init_db() log.info("Database initialized") config = AllEncompassingConfig() + +# Create application-specific directories that the app manages +log.info("Creating application directories...") +config.misc.image_directory.mkdir(parents=True, exist_ok=True) +log.info("Application directories created successfully") + +# Note: Media directories (tv, movies, torrents) are user-configured and mounted as volumes +# The application doesn't create these as they should be managed by the user + if config.misc.development: - config.misc.torrent_directory.mkdir(parents=True, exist_ok=True) - config.misc.tv_directory.mkdir(parents=True, exist_ok=True) - config.misc.movie_directory.mkdir(parents=True, exist_ok=True) - config.misc.image_directory.mkdir(parents=True, exist_ok=True) log.warning("Development Mode activated!") else: log.info("Development Mode not activated!") diff --git a/mediamanager-backend-startup.sh b/mediamanager-backend-startup.sh index 82de876..708ce26 100644 --- a/mediamanager-backend-startup.sh +++ b/mediamanager-backend-startup.sh @@ -12,6 +12,36 @@ echo " /____/ " echo "Buy me a coffee at https://buymeacoffee.com/maxdorninger" + +# Initialize config if it doesn't exist +CONFIG_DIR=${CONFIG_DIR:-/app/config} +CONFIG_FILE="$CONFIG_DIR/config.toml" +EXAMPLE_CONFIG="/app/config.example.toml" + +echo "Checking configuration setup..." + +# Create config directory if it doesn't exist +if [ ! -d "$CONFIG_DIR" ]; then + echo "Creating config directory: $CONFIG_DIR" + mkdir -p "$CONFIG_DIR" +fi + +# Copy example config if config.toml doesn't exist +if [ ! -f "$CONFIG_FILE" ]; then + echo "Config file not found. Copying example config to: $CONFIG_FILE" + if [ -f "$EXAMPLE_CONFIG" ]; then + cp "$EXAMPLE_CONFIG" "$CONFIG_FILE" + echo "Example config copied successfully!" + echo "Please edit $CONFIG_FILE to configure MediaManager for your environment." + echo "Important: Make sure to change the token_secret value!" + else + echo "ERROR: Example config file not found at $EXAMPLE_CONFIG" + exit 1 + fi +else + echo "Config file found at: $CONFIG_FILE" +fi + echo "Running DB migrations..." uv run alembic upgrade head