mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 21:26:15 -08:00
docs(self-hosted): server storage mode, sign-in and the release note
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ced58e3ac0
commit
8714b3b445
7 files changed
+104
-29
No files matched your search
@@ -1,7 +0,0 @@
|
||||
---
|
||||
type: feature
|
||||
area: self-hosted
|
||||
issues: [305]
|
||||
---
|
||||
|
||||
The Docker image now keeps a server database on a `/data` volume, the groundwork for sharing playlists and watch progress across devices. Mount a volume to keep it; nothing changes in the browser yet.
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
type: feature
|
||||
area: self-hosted
|
||||
issues: [305]
|
||||
highlight: Playlists on your server
|
||||
---
|
||||
|
||||
The Docker version now keeps playlists, favorites and watch progress on the server, so every phone, TV and computer that opens it shares one library. Playlists already in a browser move over with one click, and an optional access token keeps the library private.
|
||||
@@ -135,6 +135,10 @@ The application is available at <http://localhost:4333>. See
|
||||
compose file and [`docker/README.md`](./docker/README.md) for environment
|
||||
variables, reverse proxy notes, PWA limitations, and build details.
|
||||
|
||||
The image keeps playlists, favorites and watch progress on the server, so
|
||||
every browser that opens it shares one library. The first browser that opens
|
||||
an updated instance offers to move the playlists it already has.
|
||||
|
||||
The self-hosted image runs the browser PWA rather than the Electron desktop app:
|
||||
EPG/XMLTV panels, Embedded MPV, managed MPV/VLC launching, the download manager,
|
||||
and Electron remote-control features are not available there. If browser
|
||||
|
||||
+20
-12
@@ -18,9 +18,12 @@ The Docker image runs the browser PWA, not the Electron desktop application.
|
||||
That means:
|
||||
|
||||
- EPG/XMLTV panels and multi-EPG views are not available in the self-hosted PWA.
|
||||
- Playlist metadata is stored in the browser through IndexedDB, and Xtream user
|
||||
data is stored by the PWA data source in localStorage. The PWA does not use
|
||||
the Electron SQLite database or DB worker.
|
||||
- With server storage (the image default) playlists, M3U favorites and
|
||||
history, and Xtream favorites, recently viewed items and playback positions
|
||||
live on the server, and every browser that opens the instance shares them.
|
||||
Settings, parental locks, the global favorites order and UI preferences stay
|
||||
per browser. With `IPTVNATOR_STORAGE=none` each browser keeps its own data
|
||||
in IndexedDB and localStorage.
|
||||
- Docker cannot launch local desktop players or Embedded MPV. If a stream does
|
||||
not play inline in the browser, use the in-app copy URL action and open the
|
||||
stream manually in an external player such as MPV, VLC, or IINA.
|
||||
@@ -100,7 +103,7 @@ safe local defaults shown below.
|
||||
| `NODE_EXTRA_CA_CERTS` | unset | Optional Node.js CA bundle path for providers using private certificate authorities. Mount the CA file into the container and set this path. |
|
||||
| `IPTVNATOR_STORAGE` | `server` in the image | `server` opens the server storage database under `IPTVNATOR_DATA_DIR` and its `/api/v1` routes; `none` keeps the backend stateless. |
|
||||
| `IPTVNATOR_DATA_DIR` | `/data` in the image | Data root; the database is `<root>/databases/iptvnator.db`. Mount a volume here. |
|
||||
| `IPTVNATOR_ACCESS_TOKEN` | unset | When set, every route except `/`, `/health`, `/config.js` and `/v1/instance` requires `Authorization: Bearer <token>`, proxy routes included. The browser app cannot send a token yet: leave it unset until it can. |
|
||||
| `IPTVNATOR_ACCESS_TOKEN` | unset | When set, every route except `/`, `/health`, `/config.js` and `/v1/instance` requires `Authorization: Bearer <token>`, proxy routes included. Each browser asks for the token once and keeps it. |
|
||||
| `IPTVNATOR_MAX_DOCUMENT_MB` | `64` | Largest playlist document the backend accepts. Keep the nginx `client_max_body_size` in step. |
|
||||
| `IPTVNATOR_BACKEND_START_TIMEOUT` | `300` | Seconds the entrypoint waits for the backend's `/health` before giving up. Database migrations run before the backend listens. |
|
||||
|
||||
@@ -136,16 +139,21 @@ the `restart: unless-stopped` policy.
|
||||
|
||||
The image opens a server storage database on the `/data` volume
|
||||
(`IPTVNATOR_STORAGE=server`, `IPTVNATOR_DATA_DIR=/data`) and serves its API
|
||||
under `/api/v1`. It is the backend half of keeping playlists, favorites,
|
||||
history and playback positions on the server for every device; the PWA
|
||||
starts using it in a follow-up change. Until then the volume holds an empty
|
||||
database and each browser keeps its own data.
|
||||
under `/api/v1`. The PWA finds it on start and keeps playlists, favorites,
|
||||
history and playback positions there, so every phone, TV and computer that
|
||||
opens the instance sees the same library. Settings → Server shows the
|
||||
connection, the database size and path, and exports a backup.
|
||||
|
||||
Provider credentials will be stored unencrypted in that database, as the
|
||||
The first browser that opens an instance with an empty database offers to
|
||||
move the playlists it already kept, with their Xtream favorites and
|
||||
progress; nothing is deleted from the browser. Later browsers can merge
|
||||
theirs from Settings → Server.
|
||||
|
||||
Provider credentials are stored unencrypted in that database, as the
|
||||
desktop app does. Do not expose an instance with server storage to the
|
||||
internet without authentication in your reverse proxy. The backend already
|
||||
supports `IPTVNATOR_ACCESS_TOKEN`, but the browser app cannot send the
|
||||
token yet, so setting it now makes the app unusable. Back up the volume like any database: stop the container, or copy
|
||||
internet without authentication. Set `IPTVNATOR_ACCESS_TOKEN` to require a
|
||||
token: each browser then asks for it once on a sign-in page. Back up the
|
||||
volume like any database: stop the container, or copy
|
||||
`databases/iptvnator.db` together with its `-wal` file.
|
||||
|
||||
## IPv6, VPNs, And Connection Fallback
|
||||
|
||||
@@ -16,6 +16,8 @@ services:
|
||||
# Server storage database on the /data volume. Set to "none" to keep
|
||||
# the backend stateless.
|
||||
IPTVNATOR_STORAGE: server
|
||||
# Ask every browser for this token once before it shows the library.
|
||||
# IPTVNATOR_ACCESS_TOKEN: change-me
|
||||
volumes:
|
||||
- iptvnator-data:/data
|
||||
healthcheck:
|
||||
|
||||
@@ -73,8 +73,8 @@ The current self-hosted PWA uses these `apps/web-backend` routes:
|
||||
- `GET /xtream?targetId=<id>&username=<u>&password=<p>&action=<action>`
|
||||
- `GET /stalker?targetId=<id>&macAddress=<mac>&action=<action>`
|
||||
|
||||
Server storage adds versioned routes under `/v1`; the PWA does not call them
|
||||
yet:
|
||||
Server storage adds versioned routes under `/v1`, which the PWA uses in
|
||||
[server storage mode](#server-storage-mode):
|
||||
|
||||
- `GET /v1/instance` (always public): `{ service, apiVersion, instanceId,
|
||||
storage: 'server' | 'none', auth: 'none' | 'token' }`
|
||||
@@ -102,8 +102,8 @@ its database. Storage errors are logged by error name and code only, because
|
||||
Drizzle messages can carry bound values such as portal credentials. With
|
||||
`IPTVNATOR_ACCESS_TOKEN`, every route except `/`, `/health`, `/config.js`
|
||||
and `/v1/instance` requires `Authorization: Bearer <token>`, the proxy
|
||||
routes included. The PWA cannot send that token yet, so the variable must
|
||||
stay unset until the browser side lands. Browser app state written through
|
||||
routes included; the PWA sends it once its sign-in page has stored it.
|
||||
Browser app state written through
|
||||
`/v1/app-state` is stored under a `client:` key prefix, apart from
|
||||
server-owned keys such as the instance id.
|
||||
|
||||
@@ -119,7 +119,11 @@ reached only `pnpm`/`nx`, and the orphaned server kept port 3333 after the run.
|
||||
launches the backend through Nx or its `env` drifts from the `serve` target.
|
||||
`WEB_BACKEND_PORT` (default 3333) relocates the E2E backend and the
|
||||
`self-hosted.e2e.ts` URLs, like `MOCK_PORT` does for the mocks. Use it when
|
||||
another worktree holds 3333. The `web:serve` entry can stay on Nx:
|
||||
another worktree holds 3333. `server-storage.e2e.ts` talks to a second
|
||||
backend from the same config, launched with `IPTVNATOR_STORAGE=server` on
|
||||
`STORAGE_BACKEND_PORT` (default 3334) and a data directory named after the
|
||||
Playwright process, so every run starts with an empty store; the spec
|
||||
accepts at most that one extra launch. The `web:serve` entry can stay on Nx:
|
||||
`@angular/build:dev-server` runs inside the Nx process, so the group kill
|
||||
stops it. Playwright launches that command itself even outside CI: the
|
||||
web-e2e targets carry no Nx `serve` dependency, because Nx refuses a
|
||||
@@ -160,6 +164,55 @@ MPV and VLC preload launch methods (`openInMpv` and `openInVlc`), and
|
||||
(`setMpvPlayerPath` and `setVlcPlayerPath`); a partial Electron bridge must not
|
||||
expose desktop-only actions in the PWA/shared UI.
|
||||
|
||||
## Server Storage Mode
|
||||
|
||||
`main.ts` probes `GET /v1/instance` before bootstrapping, and only when the
|
||||
deployment wrote a runtime `BACKEND_URL` into `window.__IPTVNATOR_CONFIG__`
|
||||
(the Docker entrypoint does; hosted builds and Electron never probe). On
|
||||
`storage: 'server'` it imports `apps/web/src/app/server-storage/` lazily and
|
||||
merges `provideServerStorage()` into the app config. Everything else of the
|
||||
client lives behind the `@iptvnator/services/server-storage` sub-entry, so
|
||||
other builds never load it; `ServerStorageStatusService` and the token
|
||||
helpers stay in the main `@iptvnator/services` barrel because the rail,
|
||||
settings and guards read them in every build.
|
||||
|
||||
- **Playlists.** `ServerStorageSession.start()` (an app initializer) plugs
|
||||
`ServerPlaylistDocumentStore` into `PlaylistStorageBackend` (kinds
|
||||
`electron`, `server`, `indexeddb`), so `PlaylistsService` is unchanged. The
|
||||
store caches full documents by ETag and uploads a document whose payload it
|
||||
served unchanged with `keepPayload=1`, so a favorite or a rename does not
|
||||
resend a large M3U.
|
||||
- **Xtream user data.** `USER_DATA_STORAGE` becomes `ServerUserDataStorage`:
|
||||
the four synced namespaces are cached under the `iptvnator-server:` key
|
||||
prefix (the legacy unprefixed keys are never written in server mode) and
|
||||
each change sends only the playlist entries that differ, debounced, with a
|
||||
`keepalive` flush when the page hides. Start-up waits up to 3 s for the
|
||||
first read.
|
||||
- **Connection state.** `ServerStorageStatusService` is `online`, `offline`
|
||||
(a request did not reach the server; retried every 30 s and when the tab
|
||||
returns) or
|
||||
`unauthorized` (a 401 clears the stored token). The rail shows a status
|
||||
shortcut, an offline banner sits above the workspace content, and failed
|
||||
writes raise a snackbar (`SERVER_STORAGE.WRITE_FAILED`, or `TOO_LARGE` for
|
||||
a 413).
|
||||
- **Access token.** With `IPTVNATOR_ACCESS_TOKEN` the instance reports
|
||||
`auth: 'token'`; without a stored token, or after a 401, the guards route to
|
||||
`/server-sign-in`, which checks the token against `/v1/instance/status`
|
||||
before it stores it under `iptvnator-server:access-token` and reloads.
|
||||
`ServerAccessTokenInterceptor` adds the header to `HttpClient` calls to the
|
||||
backend, and `PwaService` adds it to its two raw `fetch` calls.
|
||||
- **Moving browser data.** `ServerMigrationService` offers, once per server
|
||||
instance (marker `iptvnator-server:migration:<instanceId>`), to move the
|
||||
playlists this browser kept in IndexedDB. An empty server receives them
|
||||
with their ids plus the legacy Xtream maps for those ids; a server that
|
||||
already has playlists goes through the backup export and import, whose
|
||||
fingerprints merge duplicates. Browser data is never deleted. Settings →
|
||||
Server repeats the offer while the browser has playlists that were not
|
||||
moved.
|
||||
|
||||
What stays per device: settings, parental lock PINs and locked groups,
|
||||
the global favorites order and other UI preferences in browser storage.
|
||||
|
||||
## Runtime Limitations
|
||||
|
||||
The self-hosted build is the browser PWA, not the Electron desktop app. Keep
|
||||
@@ -172,7 +225,9 @@ these limitations explicit in UI, troubleshooting, and release notes:
|
||||
metadata uses `PlaylistsService` with IndexedDB, which
|
||||
`PlaylistStorageBackend` selects when no SQLite bridge exists; Xtream
|
||||
favorites, recently viewed items, playback positions, and cached collection
|
||||
snapshots use `PwaXtreamDataSource` browser storage.
|
||||
snapshots use `PwaXtreamDataSource` browser storage. In
|
||||
[server storage mode](#server-storage-mode) both live on the server
|
||||
instead.
|
||||
- Browser playlist deletion must go through `PlaylistsService`, not
|
||||
`DatabaseService`. `PlaylistsService.deletePlaylist()` runs registered
|
||||
cleanup hooks such as the PWA Xtream cleanup so localStorage sidecar data does
|
||||
@@ -271,8 +326,8 @@ and sidecar state in localStorage:
|
||||
|
||||
`PwaXtreamDataSource` and `PwaCollectionStorage` reach these keys through the
|
||||
`USER_DATA_STORAGE` token from `@iptvnator/services` (default:
|
||||
`localStorage`), never through `localStorage` directly, so a deployment can
|
||||
substitute a server-synced storage.
|
||||
`localStorage`), never through `localStorage` directly, so server storage
|
||||
mode can substitute `ServerUserDataStorage`.
|
||||
|
||||
Entries should include a content snapshot when the item is added. Global
|
||||
collection routes and the dashboard can then restore titles, posters, content
|
||||
|
||||
@@ -89,7 +89,10 @@ The shell is intentionally split into four persistent regions:
|
||||
recently viewed. The routed global-search rail link is Electron-only
|
||||
because its data source is the SQLite worker bridge.
|
||||
2. Provider-aware context links derived from the active or current playlist.
|
||||
3. Settings remains a persistent footer shortcut in the rail.
|
||||
3. Settings remains a persistent footer shortcut in the rail. In
|
||||
[server storage mode](pwa-self-hosted.md#server-storage-mode) a server
|
||||
status shortcut sits above it: its dot shows online, offline or
|
||||
sign-in needed, and it opens Settings → Server.
|
||||
4. No brand mark: it only repeated the first workspace link (Dashboard,
|
||||
or Sources when the dashboard is off).
|
||||
2. Top header:
|
||||
@@ -103,7 +106,9 @@ The shell is intentionally split into four persistent regions:
|
||||
6. Downloads shortcut in Electron.
|
||||
3. Main body:
|
||||
1. Optional left context panel.
|
||||
2. Main router outlet content.
|
||||
2. Main router outlet content. In server storage mode an offline banner
|
||||
with Retry takes the first row of `.workspace-content-frame`, above
|
||||
the scroller, so full-height pages keep their height.
|
||||
4. Optional footer:
|
||||
1. External playback session bar when a docked session is visible.
|
||||
|
||||
|
||||
Reference in new issue
Block a user