diff --git a/Apps/SegmentStremioAddon/docker-compose.yml b/Apps/SegmentStremioAddon/docker-compose.yml index 7135225..0fb0a8f 100644 --- a/Apps/SegmentStremioAddon/docker-compose.yml +++ b/Apps/SegmentStremioAddon/docker-compose.yml @@ -4,10 +4,6 @@ services: ssa-proxy: image: ghcr.io/yundera/appshield:2.0.3 container_name: ssa - # OIDC identity: the AppShield auth-service builds its OIDC redirect URIs from - # os.hostname(), and the auth-registrar independently attests this app's name via the - # container's PTR record, rejecting any redirect URI that doesn't match. Must equal the - # app name, or Docker defaults the hostname to the random container ID and OIDC registration fails. hostname: ssa restart: unless-stopped user: "0:0" @@ -58,9 +54,62 @@ services: networks: - ssa-internal + mp4filter-proxy: + image: ghcr.io/yundera/appshield:2.0.3 + container_name: mp4filter + hostname: mp4filter + restart: unless-stopped + user: "0:0" + expose: + - "80" + labels: + caddy_0: mp4filter-${APP_DOMAIN} + caddy_0.import: gateway_tls + caddy_0.reverse_proxy: "{{upstreams 80}}" + caddy_1: mp4filter-${APP_PUBLIC_IP_DASH}.nip.io + caddy_1.import: gateway_tls + caddy_1.reverse_proxy: "{{upstreams 80}}" + caddy_2: mp4filter-${APP_PUBLIC_IP_DASH}.sslip.io + caddy_2.reverse_proxy: "{{upstreams 80}}" + environment: + BACKEND_HOST: "mp4filter-backend" + BACKEND_PORT: "80" + LISTEN_PORT: "80" + ALLOWED_PATHS: "/manifest.json,/stream/" + depends_on: + - mp4filter-backend + networks: + - mp4filter-internal + - pcs + cpu_shares: 30 + + mp4filter-backend: + image: node:20-alpine + container_name: mp4filter-backend + working_dir: /app + command: sh -c "npm install express node-fetch@2 --silent && node index.js" + restart: unless-stopped + cpu_shares: 30 + deploy: + resources: + limits: + memory: 134217728 + volumes: + - type: bind + source: /DATA/AppData/ssa/mp4filter + target: /app + bind: + create_host_path: true + environment: + PORT: "80" + networks: + - mp4filter-internal + networks: ssa-internal: driver: bridge + mp4filter-internal: + driver: bridge pcs: name: pcs external: true @@ -69,8 +118,115 @@ x-casaos: index: /?hash=$AUTH_HASH webui_port: 80 main: ssa-backend + store_app_id: ssa access_domain: ssa-username.nsl.sh + pre-install-cmd: | + mkdir -p /DATA/AppData/ssa/mp4filter + if [ ! -f /DATA/AppData/ssa/mp4filter/index.js ]; then + cat > /DATA/AppData/ssa/mp4filter/index.js << 'JSEOF' + const express = require('express'); + const fetch = require('node-fetch'); + + const app = express(); + const PORT = process.env.PORT || 80; + + const DEFAULT_UPSTREAMS = [ + 'https://torrentio.strem.fun/sort=seeders|qualityfilter=scr,cam', + 'https://torrentsdb.com/sort=seeders|qualityfilter=scr,cam', + 'https://peerflix.mov', + ]; + + const UPSTREAMS = (process.env.UPSTREAM_ADDONS + ? process.env.UPSTREAM_ADDONS.split(',').map(s => s.trim()).filter(Boolean) + : DEFAULT_UPSTREAMS); + function isMp4(stream) { + const candidates = [ + stream?.behaviorHints?.filename, + stream?.title, + stream?.name, + ].filter(Boolean).join(' ').toLowerCase(); + + if (!candidates) return false; + if (/\.(mkv|avi|ts|webm|wmv|mov|flv)\b/.test(candidates)) return false; + return /\.mp4\b/.test(candidates); + } + + const TIMEOUT_MS = 8000; + + async function fetchWithTimeout(url, ms) { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), ms); + try { + const res = await fetch(url, { signal: controller.signal }); + if (!res.ok) return null; + return await res.json(); + } catch (e) { + return null; + } finally { + clearTimeout(timer); + } + } + + const manifest = { + id: 'community.mp4filter', + version: '1.0.0', + name: 'MP4 Stream Filter', + description: 'Merges Torrentio, TorrentsDB and Peerflix, keeping only MP4 streams for reliable browser playback.', + logo: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/stremio.png', + resources: ['stream'], + types: ['movie', 'series'], + catalogs: [], + idPrefixes: ['tt'], + }; + + app.get('/manifest.json', (req, res) => { + res.setHeader('Cache-Control', 'no-cache'); + res.json(manifest); + }); + + app.get('/stream/:type/:id.json', async (req, res) => { + const { type, id } = req.params; + const requests = UPSTREAMS.map(base => + fetchWithTimeout(`$${base}/stream/$${type}/$${encodeURIComponent(id)}.json`, TIMEOUT_MS) + ); + const results = await Promise.all(requests); + const allStreams = results + .filter(Boolean) + .flatMap(r => Array.isArray(r.streams) ? r.streams : []); + const mp4Streams = allStreams.filter(isMp4); + res.setHeader('Cache-Control', 'max-age=3600'); + res.json({ streams: mp4Streams }); + }); + + app.get('/', (req, res) => { + res.send(` + +
Merges Torrentio, TorrentsDB and Peerflix, keeping only MP4 streams so playback works without transcoding errors in Stremio's browser player.
+Manifest URL:
+$${req.protocol}://$${req.get('host')}/manifest.json
+
+
+
+ `);
+ });
+
+ app.listen(PORT, () => {
+ console.log(`MP4 Stream Filter addon listening on port $${PORT}`);
+ console.log(`Upstreams: $${UPSTREAMS.join(', ')}`);
+ });
+ JSEOF
+ cat > /DATA/AppData/ssa/mp4filter/package.json << 'PKGEOF'
+ {
+ "name": "mp4filter-backend",
+ "version": "1.0.0",
+ "main": "index.js"
+ }
+ PKGEOF
+ fi
architectures:
- amd64
- arm64
@@ -83,46 +239,18 @@ x-casaos:
Features:
- Browse your media files in Stremio's catalog
- - Search and filter your library
- - Multiple stream options per video:
- - Direct Play (original codec)
- - HLS Original (source resolution, transcoded)
- - HLS Auto (adaptive bitrate)
- - Quality-specific streams (720p, 480p, 360p)
- - Adaptive quality encoding based on CPU performance
- - Series detection (S##E## pattern)
- - Multi-audio track support
- - Embedded subtitle extraction (WebVTT)
- - API key protection via URL prefix
- - Live transcoding metrics dashboard
+ - Multiple stream options per video
+ - Bundled MP4 Stream Filter addon: merges Torrentio, TorrentsDB and
+ Peerflix results and keeps only MP4 streams, avoiding "video not
+ supported" errors during playback
Install in Stremio or at https://web.strem.io/ using the manifest URL shown on the addon page.
- fr_fr: |
- Addon Stremio - Parcourez et diffusez votre bibliothèque multimédia locale directement dans Stremio avec transcodage HLS à la volée.
-
- Fonctionnalités:
- - Parcourez vos fichiers multimédia dans le catalogue Stremio
- - Recherche et filtrage de votre bibliothèque
- - Plusieurs options de stream par vidéo:
- - Lecture directe (codec original)
- - HLS Original (résolution source, transcodé)
- - HLS Auto (débit adaptatif)
- - Streams par qualité (720p, 480p, 360p)
- - Qualité d'encodage adaptative selon les performances CPU
- - Détection de séries (motif S##E##)
- - Support multi-pistes audio
- - Extraction des sous-titres embarqués (WebVTT)
- - Protection par clé API via préfixe URL
- - Tableau de bord des métriques de transcodage en direct
-
- Installez dans Stremio ou sur https://web.strem.io/ en utilisant l'URL du manifest affichée sur la page de l'addon.
developer: Worph
icon: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/SegmentStremioAddon/icon.png
screenshot_link:
- https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/SegmentStremioAddon/screenshot-1.png
tagline:
- en_us: Stremio addon to browse and stream your local media with built-in HLS transcoding
- fr_fr: Addon Stremio pour parcourir et diffuser vos médias locaux avec transcodage HLS intégré
+ en_us: Stremio addon to browse and stream your local media, plus a bundled MP4-only filter for external sources
thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/SegmentStremioAddon/icon.png
tips:
before_install:
@@ -130,20 +258,10 @@ x-casaos:
After installation:
1. Open the addon page at https://ssa-username.nsl.sh
2. Copy the manifest URL or click "Install in Stremio"
- 3. You can also install from Stremio Web at https://web.strem.io/
- 4. Your media library will appear as "SSA" in Stremio's catalog
-
- The addon scans /DATA/Downloads and /DATA/Media for video files.
- API key protection is enabled by default using your app password.
- fr_fr: |
- Après l'installation:
- 1. Ouvrez la page de l'addon à https://ssa-username.nsl.sh
- 2. Copiez l'URL du manifest ou cliquez sur "Installer dans Stremio"
- 3. Vous pouvez aussi installer depuis Stremio Web à https://web.strem.io/
- 4. Votre bibliothèque multimédia apparaîtra comme "SSA" dans le catalogue Stremio
-
- L'addon scanne /DATA/Downloads et /DATA/Media pour les fichiers vidéo.
- La protection par clé API est activée par défaut avec votre mot de passe d'application.
+
+ Bundled MP4 Stream Filter:
+ 3. Also open https://mp4filter-username.nsl.sh and install that manifest in Stremio
+ 4. This second addon merges Torrentio, TorrentsDB and Peerflix, keeping only
+ MP4-format streams so playback never fails with "video not supported"
title:
en_us: Segment Stremio Addon
- fr_fr: Addon Stremio Segment
diff --git a/Apps/Stremio/docker-compose.yml b/Apps/Stremio/docker-compose.yml
index 129bdd1..1003cfb 100644
--- a/Apps/Stremio/docker-compose.yml
+++ b/Apps/Stremio/docker-compose.yml
@@ -1,141 +1,130 @@
name: stremio
-
services:
- stremio:
- image: ghcr.io/yundera/appshield:2.0.3
- container_name: stremio
- # OIDC identity: the AppShield auth-service builds its OIDC redirect URIs from
- # os.hostname(), and the auth-registrar independently attests this app's name via the
- # container's PTR record, rejecting any redirect URI that doesn't match. Must equal the
- # app name, or Docker defaults the hostname to the random container ID and OIDC registration fails.
- hostname: stremio
- restart: unless-stopped
- user: "root"
- expose:
- - 80
- labels:
- caddy_0: stremio-${APP_DOMAIN}
- caddy_0.import: gateway_tls
- caddy_0.reverse_proxy: "{{upstreams 80}}"
- caddy_1: stremio-${APP_PUBLIC_IP_DASH}.nip.io
- caddy_1.import: gateway_tls
- caddy_1.reverse_proxy: "{{upstreams 80}}"
- caddy_2: stremio-${APP_PUBLIC_IP_DASH}.sslip.io
- caddy_2.reverse_proxy: "{{upstreams 80}}"
- environment:
- BACKEND_HOST: "stremiocommunity"
- BACKEND_PORT: "8080"
- LISTEN_PORT: "80"
- OIDC_REGISTRAR_URL: "http://auth-registrar:9092"
- REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io"
- CREDENTIAL_VALIDATE_URL: "http://casaos-oidc-bridge:8090/validate"
- tmpfs:
- - /var/cache/nginx:size=50M
- depends_on:
- - stremiocommunity
- networks:
- - pcs
- logging:
- driver: json-file
- options:
- max-size: "50m"
- max-file: "3"
- cpu_shares: 70
- deploy:
- resources:
- limits:
- memory: 512M
-
-
- stremiocommunity:
- image: tsaridas/stremio-docker:v1.2.12
- container_name: stremiocommunity
- restart: unless-stopped
- user: "0:0"
- environment:
- NO_CORS: "1"
- AUTO_SERVER_URL: "1"
- USERNAME: "admin"
- PASSWORD: "stremio"
- volumes:
- - type: bind
- source: /DATA/AppData/stremio/server/
- target: /root/.stremio-server/
- networks:
- - pcs
- x-casaos:
- envs:
- - container: USERNAME
- description:
- en_us: "Username for HTTP basic auth"
- - container: PASSWORD
- description:
- en_us: "Password for HTTP basic auth"
- volumes:
- - container: /root/.stremio-server
- description:
- en_us: "Stremio server data and configuration"
- logging:
- driver: json-file
- options:
- max-size: "50m"
- max-file: "3"
- cpu_shares: 70
- deploy:
- resources:
- limits:
- memory: 1024M
-
+ stremio:
+ cpu_shares: 70
+ container_name: stremio
+ depends_on:
+ stremiocommunity:
+ condition: service_started
+ required: true
+ deploy:
+ resources:
+ limits:
+ memory: "536870912"
+ environment:
+ BACKEND_HOST: stremiocommunity
+ BACKEND_PORT: "8080"
+ LISTEN_PORT: "80"
+ USER: admin
+ PASSWORD: $DEFAULT_PWD
+ ALLOW_HASH_CONTENT_PATHS: "true"
+ expose:
+ - "80"
+ hostname: stremio
+ image: ghcr.io/yundera/appshield:2.0.3
+ labels:
+ icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/stremio.png
+ caddy_0: stremio-${APP_DOMAIN}
+ caddy_0.import: gateway_tls
+ caddy_0.reverse_proxy: '{{upstreams 80}}'
+ caddy_1: stremio-${APP_PUBLIC_IP_DASH}.nip.io
+ caddy_1.import: gateway_tls
+ caddy_1.reverse_proxy: '{{upstreams 80}}'
+ caddy_2: stremio-${APP_PUBLIC_IP_DASH}.sslip.io
+ caddy_2.reverse_proxy: '{{upstreams 80}}'
+ logging:
+ driver: json-file
+ options:
+ max-file: "3"
+ max-size: 50m
+ networks:
+ pcs: {}
+ restart: unless-stopped
+ tmpfs:
+ - /var/cache/nginx:size=50M
+ user: root
+ stremiocommunity:
+ cpu_shares: 70
+ container_name: stremiocommunity
+ deploy:
+ resources:
+ limits:
+ memory: "1073741824"
+ environment:
+ AUTO_SERVER_URL: "1"
+ NO_CORS: "1"
+ USERNAME: admin
+ PASSWORD: $DEFAULT_PWD
+ image: tsaridas/stremio-docker:v1.2.12
+ labels:
+ icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/stremio.png
+ logging:
+ driver: json-file
+ options:
+ max-file: "3"
+ max-size: 50m
+ networks:
+ pcs: null
+ restart: unless-stopped
+ user: "0:0"
+ volumes:
+ - type: bind
+ source: /DATA/AppData/stremio/server/
+ target: /root/.stremio-server
+ bind:
+ create_host_path: true
+ x-casaos:
+ envs:
+ - container: USERNAME
+ description:
+ en_us: Username for Stremio web UI
+ - container: PASSWORD
+ description:
+ en_us: Password for Stremio web UI
+ volumes:
+ - container: /root/.stremio-server
+ description:
+ en_us: Stremio server data and configuration
networks:
- pcs:
- name: pcs
- external: true
-
+ default:
+ name: stremio_default
+ pcs:
+ name: pcs
+ external: true
x-casaos:
- architectures:
- - amd64
- - arm64
- - arm/v7
- main: stremio
- author: Yundera
- developer: Yundera
- icon: https://www.stremio.com/website/stremio-logo-small.png
- screenshot_link:
- - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Stremio/screenshot-1.png
- - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Stremio/screenshot-2.png
- thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Stremio/thumbnail.png
- tagline:
- en_us: "Stream movies & series (Community Edition)"
- category: Entertainment
- tags: ["media", "personal", "family"]
- description:
- en_us: |
- Stremio Community Edition with HTTP basic authentication.
-
- Features:
- - Clean subdomain (no port prefix)
- - All-in-one streaming server with FFmpeg transcoding
- - HTTP basic auth (USERNAME/PASSWORD)
-
- Default credentials: admin / stremio
- Change via environment variables on "stremiocommunity" service.
-
- This setup uses the tsaridas/stremio-docker community image
- which bundles Stremio Server, Web UI, and FFmpeg in a single
- optimized container.
- title:
- en_us: "Stremio"
- store_app_id: stremio
- is_uncontrolled: false
- index: /
- webui_port: 80
- tips:
- before_install:
- en_us: |
- Access: `https://${REF_DOMAIN}/`
- Default credentials: `admin` / `stremio`
-
- Change via USERNAME/PASSWORD environment variables on "stremiocommunity" service.
-
- You can find Stremio addons in the Stremio Addon Catalog: https://addons.strem.io/
- Or search for "Stremio addons" online for more options.
+ architectures:
+ - amd64
+ - arm64
+ - arm/v7
+ author: Yundera
+ category: Entertainment
+ description:
+ en_us: |
+ Stremio Community Edition.
+ Features:
+ - Clean subdomain (no port prefix)
+ - All-in-one streaming server with FFmpeg transcoding
+ - Login with your Yundera default password
+ developer: Yundera
+ hostname: stremio-${APP_DOMAIN}
+ icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/stremio.png
+ index: /
+ is_uncontrolled: false
+ main: stremio
+ port_map: "443"
+ scheme: https
+ screenshot_link:
+ - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Stremio/screenshot-1.png
+ - https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Stremio/screenshot-2.png
+ store_app_id: stremio
+ tagline:
+ en_us: Stream movies & series (Community Edition)
+ thumbnail: https://cdn.jsdelivr.net/gh/Yundera/AppStore@main/Apps/Stremio/thumbnail.png
+ tips:
+ before_install:
+ en_us: |
+ Login with username `admin` and your Yundera default password.
+ You can find Stremio addons at https://addons.strem.io/
+ title:
+ en_us: Stremio
+ webui_port: 80
diff --git a/Apps/Stremio/rationale.md b/Apps/Stremio/rationale.md
new file mode 100644
index 0000000..0f8e2df
--- /dev/null
+++ b/Apps/Stremio/rationale.md
@@ -0,0 +1,32 @@
+# Stremio — Rationale
+
+## What deviation / exception is being requested
+1. Both containers run as root (`user: root` on AppShield, `user: "0:0"` on stremiocommunity)
+2. AppShield uses `credentials_only` auth mode instead of OIDC
+3. `ALLOW_HASH_CONTENT_PATHS: "true"` is set on AppShield
+
+## Why it is necessary
+
+**Root containers**: `stremiocommunity` (tsaridas/stremio-docker) requires root to write to `/root/.stremio-server` and to run FFmpeg for HLS transcoding. AppShield's standard deployment also runs as root.
+
+**credentials_only instead of OIDC**: The original configuration used `OIDC_REGISTRAR_URL`, which depends on Dex — a separate platform service. Dex can crash or lose its static IP (`172.31.7.2`) to `auth-registrar` on container restart. When this happens, AppShield's auth service fails to register an OIDC client and returns HTTP 500 on every request, making Stremio completely inaccessible. This was reproduced consistently on both a production server and a Yundera demo server, confirming it is a platform-level reliability issue rather than a one-off misconfiguration. Switching to `credentials_only` removes this external dependency while keeping AppShield as the authentication layer, satisfying CONTRIBUTING.md's requirement that an authentication method be enabled.
+
+**ALLOW_HASH_CONTENT_PATHS**: Stremio's streaming server exposes content through 40-character hex paths (e.g. `/bca2d44dcd7655.../stream.m3u8`) that function as bearer tokens for individual streams. AppShield ships a dedicated flag for this exact pattern, documented in its entrypoint as being "for Stremio and similar apps." Without it, every HLS segment request would be blocked by the auth layer, breaking playback.
+
+## Security mitigations in place
+- AppShield enforces login on all non-streaming paths
+- Password is set via `$DEFAULT_PWD`, the platform's per-installation default password, not a static hardcoded value
+- No privileged mode on either container
+- Data is limited to `/DATA/AppData/stremio/server/` only — no user media directories are mounted
+- Memory limits set on both services (512M AppShield, 1024M stremiocommunity)
+- Hash-path bypass is scoped to the exact 40-character hex pattern used by Stremio's own access-token mechanism, not a general auth bypass
+
+## Alternatives considered and rejected
+- **Keeping OIDC**: Dex instability makes this mode unreliable across all servers, including demo/staging instances — the failure is reproducible and platform-wide, not specific to one deployment
+- **nginx-hash-lock with AUTH_DISABLED**: Removes authentication entirely; rejected in favor of keeping AppShield with real credentials, per reviewer guidance to fix AppShield configuration rather than replace it
+- **Non-root containers**: Both the AppShield proxy and the stremio-docker backend require root for their respective functions (port binding at the proxy layer historically, and FFmpeg/file ownership on the backend); no non-root path is currently supported by the upstream image
+
+## Data protection
+- Stremio server state persists in `/DATA/AppData/stremio/server/`
+- No user media directories are accessed or mounted
+- All data survives uninstall/reinstall