Skip to content

Commit d58bf1f

Browse files
authored
Merge pull request #11 from Sydney-Elvis/alpha.3
Alpha.3 - UI and Endpoint Security
2 parents 16205cd + 8d39040 commit d58bf1f

88 files changed

Lines changed: 8555 additions & 1337 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,24 @@ M3UNDLE_ENCRYPTION_KEY=your-base64-32-byte-key-here
2323
# MY_HOST=provider.example.com
2424
# MY_USER=your_username
2525
# MY_PASS=your_password
26+
27+
# ─────────────────────────────────────────────────────────────────────────────
28+
# HDHomeRun emulation (optional)
29+
# ─────────────────────────────────────────────────────────────────────────────
30+
31+
# Enable/disable HDHomeRun HTTP endpoints (/discover.json, /lineup.json, etc)
32+
# M3UNDLE_HDHR_ENABLED=true
33+
34+
# Enable UDP auto-discovery listeners (SSDP 1900 + SiliconDust 65001)
35+
# Manual add via /discover.json works even when this is false.
36+
# M3UNDLE_HDHR_DISCOVERY_ENABLED=false
37+
38+
# Optional discovery protocol toggles when discovery is enabled
39+
# M3UNDLE_HDHR_SSDP_ENABLED=true
40+
# M3UNDLE_HDHR_SILICONDUST_DISCOVERY_ENABLED=true
41+
42+
# Number of exposed tuners (current implementation reports this value)
43+
# M3UNDLE_HDHR_TUNER_COUNT=1
44+
45+
# Recommended behind Docker/reverse proxy so discovery advertises a client-reachable URL
46+
# M3UNDLE_HDHR_ADVERTISED_BASE_URL=http://192.168.1.10:8080

.vscode/launch.json

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,18 @@
1010
"cwd": "${workspaceFolder}/src/M3Undle.Web",
1111
"console": "internalConsole",
1212
"stopAtEntry": false,
13+
"justMyCode": true,
1314
"env": {
1415
"ASPNETCORE_ENVIRONMENT": "Development",
15-
"ASPNETCORE_URLS": "http://localhost:5283",
16+
"ASPNETCORE_URLS": "http://0.0.0.0:5283",
1617
"M3UNDLE_CONFIG_DIR": "${workspaceFolder}/.config",
17-
"M3UNDLE_DATA_DIR": "${workspaceFolder}/data"
18+
"M3UNDLE_DATA_DIR": "${workspaceFolder}/data",
19+
"M3UNDLE_HDHR_ENABLED": "true",
20+
"M3UNDLE_HDHR_DISCOVERY_ENABLED": "true",
21+
"M3UNDLE_HDHR_SSDP_ENABLED": "true",
22+
"M3UNDLE_HDHR_SILICONDUST_DISCOVERY_ENABLED": "true",
23+
"M3UNDLE_HDHR_TUNER_COUNT": "1",
24+
"M3UNDLE_HDHR_ADVERTISED_BASE_URL": "${env:M3UNDLE_HDHR_ADVERTISED_BASE_URL}"
1825
},
1926
"serverReadyAction": {
2027
"action": "openExternally",
@@ -30,7 +37,8 @@
3037
"postDebugTask": "docker-stop: m3undle-debug",
3138
"platform": "netCore",
3239
"netCore": {
33-
"appProject": "${workspaceFolder}/src/M3Undle.Web/M3Undle.Web.csproj"
40+
"appProject": "${workspaceFolder}/src/M3Undle.Web/M3Undle.Web.csproj",
41+
"justMyCode": true
3442
},
3543
"dockerServerReadyAction": {
3644
"action": "openExternally",
@@ -46,7 +54,8 @@
4654
"postDebugTask": "docker-stop: m3undle-debug",
4755
"platform": "netCore",
4856
"netCore": {
49-
"appProject": "${workspaceFolder}/src/M3Undle.Web/M3Undle.Web.csproj"
57+
"appProject": "${workspaceFolder}/src/M3Undle.Web/M3Undle.Web.csproj",
58+
"justMyCode": true
5059
},
5160
"dockerServerReadyAction": {
5261
"action": "openExternally",

.vscode/tasks.json

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,24 @@
8181
"docker-ensure: dev dirs"
8282
],
8383
"dependsOrder": "sequence",
84-
"dockerRun": {
85-
"containerName": "m3undle-dev",
86-
"image": "m3undle:web-debug",
87-
"remove": false,
88-
"env": {
89-
"ASPNETCORE_ENVIRONMENT": "Development",
90-
"ASPNETCORE_URLS": "http://+:8080",
91-
"M3UNDLE_CONFIG_DIR": "/config"
92-
},
84+
"dockerRun": {
85+
"containerName": "m3undle-dev",
86+
"image": "m3undle:web-debug",
87+
"remove": false,
88+
"env": {
89+
"ASPNETCORE_ENVIRONMENT": "Development",
90+
"ASPNETCORE_URLS": "http://+:8080",
91+
"M3UNDLE_CONFIG_DIR": "/config",
92+
"M3UNDLE_ADMIN_USER": "admin",
93+
"M3UNDLE_ADMIN_PASSWORD": "dog123",
94+
"M3UNDLE_AUTH_ENABLED": "true",
95+
"M3UNDLE_HDHR_ENABLED": "true",
96+
"M3UNDLE_HDHR_DISCOVERY_ENABLED": "true",
97+
"M3UNDLE_HDHR_SSDP_ENABLED": "true",
98+
"M3UNDLE_HDHR_SILICONDUST_DISCOVERY_ENABLED": "true",
99+
"M3UNDLE_HDHR_TUNER_COUNT": "1",
100+
"M3UNDLE_HDHR_ADVERTISED_BASE_URL": "${env:M3UNDLE_HDHR_ADVERTISED_BASE_URL}"
101+
},
93102
"volumes": [
94103
{
95104
"localPath": "${workspaceFolder}/.config",
@@ -107,11 +116,11 @@
107116
"permissions": "rw"
108117
}
109118
],
110-
"customOptions": "--user root --label m3undle.run_mode=existing",
111-
"ports": [
112-
{
113-
"hostPort": 8080,
114-
"containerPort": 8080
119+
"customOptions": "--user root --label m3undle.run_mode=existing -p 1900:1900/udp -p 65001:65001/udp",
120+
"ports": [
121+
{
122+
"hostPort": 8080,
123+
"containerPort": 8080
115124
}
116125
]
117126
},
@@ -129,15 +138,24 @@
129138
"docker-build: force-debug"
130139
],
131140
"dependsOrder": "sequence",
132-
"dockerRun": {
133-
"containerName": "m3undle-dev",
134-
"image": "m3undle:web-debug",
135-
"remove": false,
136-
"env": {
137-
"ASPNETCORE_ENVIRONMENT": "Development",
138-
"ASPNETCORE_URLS": "http://+:8080",
139-
"M3UNDLE_CONFIG_DIR": "/config"
140-
},
141+
"dockerRun": {
142+
"containerName": "m3undle-dev",
143+
"image": "m3undle:web-debug",
144+
"remove": false,
145+
"env": {
146+
"ASPNETCORE_ENVIRONMENT": "Development",
147+
"ASPNETCORE_URLS": "http://+:8080",
148+
"M3UNDLE_CONFIG_DIR": "/config",
149+
"M3UNDLE_ADMIN_USER": "admin",
150+
"M3UNDLE_ADMIN_PASSWORD": "dog123",
151+
"M3UNDLE_AUTH_ENABLED": "true",
152+
"M3UNDLE_HDHR_ENABLED": "true",
153+
"M3UNDLE_HDHR_DISCOVERY_ENABLED": "true",
154+
"M3UNDLE_HDHR_SSDP_ENABLED": "true",
155+
"M3UNDLE_HDHR_SILICONDUST_DISCOVERY_ENABLED": "true",
156+
"M3UNDLE_HDHR_TUNER_COUNT": "1",
157+
"M3UNDLE_HDHR_ADVERTISED_BASE_URL": "${env:M3UNDLE_HDHR_ADVERTISED_BASE_URL}"
158+
},
141159
"volumes": [
142160
{
143161
"localPath": "${workspaceFolder}/.config",
@@ -155,11 +173,11 @@
155173
"permissions": "rw"
156174
}
157175
],
158-
"customOptions": "--user root --label m3undle.run_mode=force",
159-
"ports": [
160-
{
161-
"hostPort": 8080,
162-
"containerPort": 8080
176+
"customOptions": "--user root --label m3undle.run_mode=force -p 1900:1900/udp -p 65001:65001/udp",
177+
"ports": [
178+
{
179+
"hostPort": 8080,
180+
"containerPort": 8080
163181
}
164182
]
165183
},

README.md

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ Designed for self-hosted systems like NextPVR, Jellyfin, or any client that cons
1515
> - Database-backed provider configuration
1616
> - Provider switching with snapshot lifecycle
1717
> - Group preview (read-only catalog browsing)
18-
> - Compatibility endpoints: `/m3u/`, `/xmltv/`, `/stream/`
18+
> - Compatibility endpoints: `/m3u/`, `/xmltv/`, `/stream/`, HDHomeRun HTTP API
1919
> - Stream relay proxy (relay-only, no buffering)
2020
> - Web UI for provider management (Pre-Alpha)
21+
> - HDHomeRun tuner emulation endpoints (`/discover.json`, `/lineup.json`, `/tune/<streamKey>`)
2122
>
2223
> **Forthcoming**
2324
> - Group-based inclusion rules
2425
> - Channel numbering controls
2526
> - Advanced channel filtering workflows
26-
> - HDHomeRun emulation
2727
> - Additional Service/Web UI hardening toward Alpha
2828
2929
---
@@ -106,15 +106,46 @@ Current Pre-Alpha work includes:
106106
- Provider switching with snapshot lifecycle
107107
- Group preview (read-only catalog browse)
108108
- HTTP compatibility endpoints (`/m3u/`, `/xmltv/`, `/stream/`)
109+
- HDHomeRun HTTP endpoints (`/discover.json`, `/lineup.json`, `/lineup.xml`, `/lineup.m3u`, `/lineup_status.json`, `/device.xml`)
109110
- Stream relay proxy (relay-only, no buffering)
110111
- Web UI for provider management
111112

112-
Future releases will add: group-based inclusion rules, channel numbering, filtering, HDHomeRun emulation, and more.
113+
Future releases will add: group-based inclusion rules, channel numbering, filtering, and more.
113114

114115
See: `docs/SERVICE.md`
115116

116117
---
117118

119+
## UI Authentication
120+
121+
The web UI supports a simple local authentication model:
122+
123+
- One access level only: authenticated or not authenticated
124+
- No roles or user tiers
125+
- Endpoint authentication is configured separately in the UI
126+
127+
### Setup
128+
129+
Authentication is controlled entirely by environment variables — no UI toggle required.
130+
131+
| Variable | Default | Description |
132+
|---|---|---|
133+
| `M3UNDLE_AUTH_ENABLED` | `false` | Set to `true` to require login for the UI and management APIs |
134+
| `M3UNDLE_ADMIN_USER` | `admin` | Admin username/email (used on first startup only) |
135+
| `M3UNDLE_ADMIN_PASSWORD` | *(none)* | **Required** when `M3UNDLE_AUTH_ENABLED=true` and no account exists yet |
136+
137+
On first startup with `M3UNDLE_AUTH_ENABLED=true`, the admin account is created automatically from these variables. On subsequent startups the account already exists — changing the env vars does not affect the stored password (use **Settings → Change Password** instead).
138+
139+
### Behavior
140+
141+
- If `M3UNDLE_AUTH_ENABLED=false` (default), the UI and management APIs are open on your network.
142+
- If `M3UNDLE_AUTH_ENABLED=true`, the UI and `/api/v1/*` management APIs require login.
143+
- Compatibility endpoints can be secured independently from UI auth using **Settings → Endpoint Security**.
144+
- Endpoint credentials are stored hashed in the database and validated with stateless username/password auth.
145+
- `/status` and `/health` remain unauthenticated.
146+
147+
---
148+
118149
## Docker
119150

120151
```bash
@@ -141,6 +172,20 @@ M3Undle publishes endpoints compatible with common clients:
141172
- `/m3u/m3undle.m3u`
142173
- `/xmltv/m3undle.xml`
143174
- `/stream/<streamKey>`
175+
- `/hdhr/discover.json`
176+
- `/hdhr/lineup.json`
177+
- `/hdhr/lineup.xml`
178+
- `/hdhr/lineup.m3u`
179+
- `/hdhr/lineup_status.json`
180+
- `/hdhr/device.xml`
181+
- `/hdhr/tune/<streamKey>`
182+
183+
Legacy HDHomeRun root aliases (`/discover.json`, `/lineup.json`, etc.) are still available for compatibility.
184+
185+
Automatic discovery support:
186+
- SSDP/UPnP (`UDP 1900`)
187+
- SiliconDust discovery (`UDP 65001`)
188+
- Discovery is disabled by default; manual add works without discovery
144189

145190
See: `docs/design/HTTP_COMPATIBILITY.md`
146191

docs/DOCKER.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,16 @@ See [spec/config_spec.md](spec/config_spec.md) for the config file format.
135135
| `TZ` | host timezone | Timezone for log timestamps (e.g. `America/New_York`, `Europe/London`, `UTC`) |
136136
| `M3UNDLE_ENCRYPTION_KEY` | *(none)* | **Required for Xtream Codes providers.** Base64-encoded 32-byte AES key used to encrypt passwords at rest. Generate with `openssl rand -base64 32`. Keep this secret — treat it like a master password. |
137137
138+
### Optional — Authentication
139+
140+
| Variable | Default | Description |
141+
|---|---|---|
142+
| `M3UNDLE_AUTH_ENABLED` | `false` | Set to `true` to require login for the UI and management APIs. |
143+
| `M3UNDLE_ADMIN_USER` | `admin` | Admin username/email. Used only on first startup when no account exists. |
144+
| `M3UNDLE_ADMIN_PASSWORD` | *(none)* | **Required** when `M3UNDLE_AUTH_ENABLED=true` and no admin account exists yet. Used only for the initial seed — changing this later has no effect (use Settings → Change Password instead). |
145+
146+
Endpoint security (M3U/XMLTV/stream/HDHR username/password auth) is managed in **Settings → Endpoint Security** and stored in the database.
147+
138148
### Optional — Provider Features
139149
140150
| Variable | Default | Description |

docs/design/ARCHITECTURE_MAP.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
## Purpose
44
A single unified process (`M3Undle.Web`) provides:
55
- Web UI (configuration + status) — Blazor Server
6-
- REST API for UI communication (`/api/v1/*`)
6+
- Internal application services used directly by Blazor components (no loopback HTTP required)
7+
- REST API (`/api/v1/*`) for management/integration clients and external tooling
78
- HTTP compatibility endpoints for Media Players:
89
- M3U — `/m3u/m3undle.m3u` (output name locked in Core)
910
- XMLTV — `/xmltv/m3undle.xml`

docs/design/HTTP_COMPATIBILITY.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,27 @@
66
- The playlist and stream URL contract is stable even if internal implementation changes.
77
- Provider credentials are never exposed to clients. Stream relay is a security requirement.
88

9+
## Scope Note
10+
- This document defines the external HTTP contract for client consumption and compatibility.
11+
- Blazor Server UI internals may call application services directly instead of issuing loopback HTTP calls to `/api/v1/*`.
12+
- Internal implementation choices MUST NOT change compatibility endpoint behavior.
13+
914
## Endpoint Naming
1015

1116
The service uses lineup-scoped endpoint paths. In Core, the lineup name is fixed to `m3undle`:
1217

1318
- `/m3u/m3undle.m3u`
1419
- `/xmltv/m3undle.xml`
1520
- `/stream/<streamKey>`
21+
- `/hdhr/discover.json`
22+
- `/hdhr/lineup.json`
23+
- `/hdhr/lineup.xml`
24+
- `/hdhr/lineup.m3u`
25+
- `/hdhr/lineup_status.json`
26+
- `/hdhr/device.xml`
27+
- `/hdhr/tune/<streamKey>`
28+
29+
Legacy aliases (`/discover.json`, `/lineup.json`, `/lineup.xml`, `/lineup.m3u`, `/lineup_status.json`, `/device.xml`, `/tune/<streamKey>`) remain available for compatibility.
1630

1731
## Endpoints
1832

@@ -72,6 +86,7 @@ The service uses lineup-scoped endpoint paths. In Core, the lineup name is fixed
7286

7387
### Stream
7488
- GET /stream/<streamKey>
89+
- GET /tune/<streamKey>
7590
- Resolves streamKey -> canonical channel in active snapshot
7691
- Serves playable stream for that channel
7792
- Must be resilient:
@@ -82,5 +97,32 @@ The service uses lineup-scoped endpoint paths. In Core, the lineup name is fixed
8297
An HTTP 302 redirect would deliver raw credentials to every client that follows the stream URL.
8398
Relay is a security contract, not an implementation detail. This MUST NOT be changed to a redirect.
8499

100+
### HDHomeRun HTTP API
101+
- GET `/hdhr/discover.json`
102+
- Returns stable device identity metadata (`DeviceID`, `DeviceAuth`, `FriendlyName`, `ModelNumber`, `BaseURL`, `LineupURL`, `TunerCount`).
103+
- GET `/hdhr/lineup.json`
104+
- Returns live channels from the active snapshot with stable `GuideNumber`, `GuideName`, and M3Undle-owned tune URLs.
105+
- GET `/hdhr/lineup.xml`
106+
- XML lineup equivalent of `/lineup.json`.
107+
- GET `/hdhr/lineup.m3u`
108+
- M3U lineup equivalent of `/lineup.json`.
109+
- GET `/hdhr/lineup_status.json`
110+
- Returns lineup readiness and channel count.
111+
- GET/POST `/hdhr/lineup.post`
112+
- No-op compatibility endpoint expected by some HDHomeRun clients.
113+
- GET `/hdhr/device.xml`
114+
- UPnP device description used by SSDP/manual client probes.
115+
116+
### Discovery (optional)
117+
- SSDP / UPnP listener on UDP `1900`
118+
- SiliconDust discovery listener on UDP `65001`
119+
- Discovery uses the same device identity and base URL as manual HTTP endpoints.
120+
- Discovery is disabled by default; manual add via `/discover.json` remains available.
121+
85122
## Authentication
86-
Auth infrastructure (ASP.NET Core Identity) is present in the codebase. Whether to enable it is configured at first-run setup. Compatibility endpoints (`/m3u/`, `/xmltv/`, `/stream/`) are designed to be accessible without auth to support LAN clients. The web UI can optionally require login.
123+
UI authentication and client endpoint authentication are independent:
124+
125+
- UI auth (`M3UNDLE_AUTH_ENABLED`) controls access to the web UI and management APIs.
126+
- Endpoint auth is configured in the web UI (**Settings → Endpoint Security**) and stored in the database.
127+
- When endpoint auth is enabled, M3U/XMLTV/stream/HDHR endpoints require stateless username/password access (no redirects, no session-cookie requirements).
128+
- When endpoint auth is disabled, endpoint behavior remains open as before.

0 commit comments

Comments
 (0)