diff --git a/README.md b/README.md index d6fff6d..ba2b87e 100644 --- a/README.md +++ b/README.md @@ -124,10 +124,13 @@ Here is the matrix of deployed services: | | DNSCrypt Proxy | 5300 | `dnscrypt-proxy` | Secure DNS (DoH) | | | Wireguard | 51820 (UDP) | - | VPN | | **Media** | Plex | 32400 | `http://IP:32400` | Streaming Server | +| | Jellyfin | 8096 | `http://IP:8096` | Streaming Server (Open Source) | | | Tautulli | 8181 | `http://IP:8181` | Plex Stats | -| | Overseerr | 5055 | `http://IP:5055` | Media Requests | +| | Overseerr | 5055 | `http://IP:5055` | Plex Requests | +| | Jellyseerr | 5056 | `http://IP:5056` | Jellyfin Requests | | **ArrStack** | Sonarr | 8989 | `http://IP:8989` | TV Shows | | | Radarr | 7878 | `http://IP:7878` | Movies | +| | Bazarr | 6767 | `http://IP:6767` | Subtitles | | | Prowlarr | 9696 | `http://IP:9696` | Torrent Indexers | | | Jackett | 9117 | `http://IP:9117` | Indexer Proxy | | **Download** | QBittorrent | 8080 | `http://IP:8080` | Torrent Client | @@ -242,10 +245,13 @@ Voici la matrice des services déployés : | | DNSCrypt Proxy | 5300 | `dnscrypt-proxy` | DNS Sécurisé (DoH) | | | Wireguard | 51820 (UDP) | - | VPN | | **Média** | Plex | 32400 | `http://IP:32400` | Serveur Streaming | +| | Jellyfin | 8096 | `http://IP:8096` | Serveur Streaming (Open Source) | | | Tautulli | 8181 | `http://IP:8181` | Stats Plex | -| | Overseerr | 5055 | `http://IP:5055` | Demandes de Média | +| | Overseerr | 5055 | `http://IP:5055` | Demandes Plex | +| | Jellyseerr | 5056 | `http://IP:5056` | Demandes Jellyfin | | **ArrStack** | Sonarr | 8989 | `http://IP:8989` | Séries TV | | | Radarr | 7878 | `http://IP:7878` | Films | +| | Bazarr | 6767 | `http://IP:6767` | Sous-titres | | | Prowlarr | 9696 | `http://IP:9696` | Indexeurs Torrent | | | Jackett | 9117 | `http://IP:9117` | Proxy Indexeurs | | **Download** | QBittorrent | 8080 | `http://IP:8080` | Client Torrent | diff --git a/server_manager/src/services/arr.rs b/server_manager/src/services/arr.rs index 4906270..551a2ff 100644 --- a/server_manager/src/services/arr.rs +++ b/server_manager/src/services/arr.rs @@ -51,3 +51,4 @@ define_arr_service!(SonarrService, "sonarr", "lscr.io/linuxserver/sonarr:latest" define_arr_service!(RadarrService, "radarr", "lscr.io/linuxserver/radarr:latest", 7878); define_arr_service!(ProwlarrService, "prowlarr", "lscr.io/linuxserver/prowlarr:latest", 9696); define_arr_service!(JackettService, "jackett", "lscr.io/linuxserver/jackett:latest", 9117); +define_arr_service!(BazarrService, "bazarr", "lscr.io/linuxserver/bazarr:latest", 6767); diff --git a/server_manager/src/services/media.rs b/server_manager/src/services/media.rs index e1f4486..5f80752 100644 --- a/server_manager/src/services/media.rs +++ b/server_manager/src/services/media.rs @@ -72,6 +72,90 @@ impl Service for PlexService { } } +pub struct JellyfinService; + +impl Service for JellyfinService { + fn name(&self) -> &'static str { "jellyfin" } + fn image(&self) -> &'static str { "lscr.io/linuxserver/jellyfin:latest" } + + fn ports(&self) -> Vec { + vec!["8096:8096".to_string()] + } + + fn env_vars(&self, hw: &HardwareInfo, _secrets: &Secrets) -> HashMap { + let mut vars = HashMap::new(); + vars.insert("PUID".to_string(), hw.user_id.clone()); + vars.insert("PGID".to_string(), hw.group_id.clone()); + + if hw.has_nvidia { + vars.insert("NVIDIA_VISIBLE_DEVICES".to_string(), "all".to_string()); + vars.insert("NVIDIA_DRIVER_CAPABILITIES".to_string(), "compute,video,utility".to_string()); + } + + vars + } + + fn volumes(&self, hw: &HardwareInfo) -> Vec { + let mut vols = vec![ + "./config/jellyfin:/config".to_string(), + "./media/tv:/data/tvshows".to_string(), + "./media/movies:/data/movies".to_string(), + ]; + + // Optimization: RAM Transcoding for High Profile + match hw.profile { + HardwareProfile::High => vols.push("/dev/shm:/transcode".to_string()), + _ => vols.push("./transcode_jellyfin:/transcode".to_string()), + } + + vols + } + + fn devices(&self, hw: &HardwareInfo) -> Vec { + if hw.has_intel_quicksync { + return vec!["/dev/dri:/dev/dri".to_string()]; + } + vec![] + } + + fn healthcheck(&self) -> Option { + Some("curl -f http://localhost:8096/health || exit 1".to_string()) + } + + fn resources(&self, hw: &HardwareInfo) -> Option { + let memory_limit = match hw.profile { + HardwareProfile::High => "8G", + HardwareProfile::Standard => "4G", + HardwareProfile::Low => "2G", + }; + Some(ResourceConfig { + memory_limit: Some(memory_limit.to_string()), + memory_reservation: None, + cpu_limit: None, + cpu_reservation: None, + }) + } +} + +pub struct JellyseerrService; +impl Service for JellyseerrService { + fn name(&self) -> &'static str { "jellyseerr" } + fn image(&self) -> &'static str { "fallenbagel/jellyseerr:latest" } + // Internal port 5055, exposed as 5056 to avoid conflict with Overseerr + fn ports(&self) -> Vec { vec!["127.0.0.1:5056:5055".to_string()] } + fn volumes(&self, _hw: &HardwareInfo) -> Vec { + vec!["./config/jellyseerr:/app/config".to_string()] + } + fn resources(&self, _hw: &HardwareInfo) -> Option { + Some(ResourceConfig { + memory_limit: Some("1G".to_string()), + memory_reservation: None, + cpu_limit: None, + cpu_reservation: None, + }) + } +} + pub struct TautulliService; impl Service for TautulliService { fn name(&self) -> &'static str { "tautulli" } diff --git a/server_manager/src/services/mod.rs b/server_manager/src/services/mod.rs index 3a6bc67..d761d97 100644 --- a/server_manager/src/services/mod.rs +++ b/server_manager/src/services/mod.rs @@ -69,10 +69,13 @@ pub fn get_all_services() -> Vec> { Box::new(media::PlexService), Box::new(media::TautulliService), Box::new(media::OverseerrService), + Box::new(media::JellyfinService), + Box::new(media::JellyseerrService), Box::new(arr::SonarrService), Box::new(arr::RadarrService), Box::new(arr::ProwlarrService), Box::new(arr::JackettService), + Box::new(arr::BazarrService), Box::new(download::QBittorrentService), Box::new(infra::MariaDBService), Box::new(infra::RedisService), diff --git a/server_manager/tests/integration_tests.rs b/server_manager/tests/integration_tests.rs index baa0a6f..79c6a4b 100644 --- a/server_manager/tests/integration_tests.rs +++ b/server_manager/tests/integration_tests.rs @@ -42,12 +42,14 @@ fn test_generate_compose_structure() { let networks = mapping.get(&serde_yaml::Value::from("networks")).unwrap().as_mapping().unwrap(); assert!(networks.contains_key(&serde_yaml::Value::from("server_manager_net"))); - // 5. Verify Services Count (Should be 24) + // 5. Verify Services Count (Should be 27) let services = mapping.get(&serde_yaml::Value::from("services")).unwrap().as_mapping().unwrap(); - assert_eq!(services.len(), 24, "Expected 24 services"); + assert_eq!(services.len(), 27, "Expected 27 services"); - // 6. Verify specific service (Plex) + // 6. Verify specific service (Plex, Jellyfin, Bazarr) assert!(services.contains_key(&serde_yaml::Value::from("plex"))); + assert!(services.contains_key(&serde_yaml::Value::from("jellyfin"))); + assert!(services.contains_key(&serde_yaml::Value::from("bazarr"))); let plex = services.get(&serde_yaml::Value::from("plex")).unwrap().as_mapping().unwrap(); assert_eq!(plex.get(&serde_yaml::Value::from("image")).unwrap().as_str().unwrap(), "lscr.io/linuxserver/plex:latest");