update environment configuration, enhance setup guide, and align documentation with new domain structure

This commit is contained in:
zwitschi committed 2026-08-03 14:09:51 +02:00
1 parent c484b463e3
commit 2e061dff28
13 files changed
+267 -46

No files matched your search

+11 -2
View File
@@ -12,7 +12,16 @@ R2_REGION=auto
# Cloudflare (for custom domain, cache rules, Workers if used) # Cloudflare (for custom domain, cache rules, Workers if used)
CLOUDFLARE_API_TOKEN=your-cloudflare-api-token CLOUDFLARE_API_TOKEN=your-cloudflare-api-token
CLOUDFLARE_ACCOUNT_ID=your-account-id CLOUDFLARE_ACCOUNT_ID=your-account-id
MEDIA_DOMAIN=media.example.com MEDIA_DOMAIN=media.linearvoid.tv
# Public frontend URLs (Vite reads these from the repository root via frontend/vite.config.ts)
# Local defaults keep development on localhost. Production should use:
# VITE_PUBLIC_APP_URL=https://linearvoid.tv
# VITE_PUBLIC_WS_URL=wss://linearvoid.tv/ws
# VITE_PUBLIC_HLS_URL=https://media.linearvoid.tv/hls/playlist.m3u8
VITE_PUBLIC_APP_URL=http://localhost:8080
VITE_PUBLIC_WS_URL=ws://localhost:3000/ws
VITE_PUBLIC_HLS_URL=http://localhost:8080/hls/playlist.m3u8
# Gitea Container Registry (for self-hosted Docker images) # Gitea Container Registry (for self-hosted Docker images)
GITEA_REGISTRY_URL=git.allucanget.biz GITEA_REGISTRY_URL=git.allucanget.biz
@@ -34,7 +43,7 @@ CHAT_PORT=3000
NGINX_PORT=80 NGINX_PORT=80
# Playout configuration # Playout configuration
PLAYLIST_PATH=/app/playlist.txt PLAYLIST_PATH=/app/playlist/playlist.txt
HLS_SEGMENT_DURATION=6 HLS_SEGMENT_DURATION=6
HLS_WINDOW_SIZE=24 HLS_WINDOW_SIZE=24
+10 -1
View File
@@ -1,8 +1,17 @@
# Change Log for linearvoid # Change Log for linearvoid
## [1.0.1] - 2026-08-03
### Changed
- Expanded the setup guide with current `.env.example` variables, development prerequisites, Cloudflare R2 setup, Gitea registry setup, and a production deployment guide.
- Aligned frontend, nginx, and deployment docs with the `linearvoid.tv` app domain and `media.linearvoid.tv` HLS domain model.
## [1.0.0] - 2026-08-02 ## [1.0.0] - 2026-08-02
**Full Backend and Frontend Implementation Complete** ### Added
Full backend and frontend implementation complete.
- **Playout Service** (`backend/playout/`): Go-based playlist management, ffprobe normalization validation, continuous HLS with immutable segment naming, automatic R2 sync, health check endpoint, and Docker integration (ADR-001, ADR-003). - **Playout Service** (`backend/playout/`): Go-based playlist management, ffprobe normalization validation, continuous HLS with immutable segment naming, automatic R2 sync, health check endpoint, and Docker integration (ADR-001, ADR-003).
- **Chat Backend** (`backend/chat/`): Go + Gorilla WebSocket, Redis for presence/rate limiting/pub/sub, SQLite persistence for message history and ModerationLog (ADR-006/ADR-007), public rooms, private DM routing, moderation commands with immutable audit logging, health check endpoint, and docker-compose integration. - **Chat Backend** (`backend/chat/`): Go + Gorilla WebSocket, Redis for presence/rate limiting/pub/sub, SQLite persistence for message history and ModerationLog (ADR-006/ADR-007), public rooms, private DM routing, moderation commands with immutable audit logging, health check endpoint, and docker-compose integration.
+2 -1
View File
@@ -1,6 +1,6 @@
# Linear Void # Linear Void
A self-managed **24/7 linear video channel** for a primarily US audience. It runs a continuous playlist of self-owned videos with true/near-true synchronization, a custom lightweight viewer SPA (hls.js player + chat), and full independence from third-party platforms (YouTube, Twitch, etc.). [Linear Void](https://linearvoid.tv) is a self-managed **24/7 linear video channel** for a primarily US audience. It runs a continuous playlist of self-owned videos with true/near-true synchronization, a custom lightweight viewer SPA (hls.js player + chat), and full independence from third-party platforms (YouTube, Twitch, etc.).
**Repository**: [git.allucanget.biz/zwitschi/linearvoid](https://git.allucanget.biz/zwitschi/linearvoid) **Repository**: [git.allucanget.biz/zwitschi/linearvoid](https://git.allucanget.biz/zwitschi/linearvoid)
@@ -107,6 +107,7 @@ See the [architecture docs](docs/architecture/README.md) for detailed architectu
## Development ## Development
- Use Docker Compose for local development (playout + chat + frontend). - Use Docker Compose for local development (playout + chat + frontend).
- Public production host model: `https://linearvoid.tv` for viewer/admin/chat and `https://media.linearvoid.tv` for HLS delivery.
- Normalize all source videos before adding to playlist (see ADR-001 for exact codec/fps/keyframe requirements). - Normalize all source videos before adding to playlist (see ADR-001 for exact codec/fps/keyframe requirements).
- Monitor cache hit ratio and R2 operations closely — they determine real cost. - Monitor cache hit ratio and R2 operations closely — they determine real cost.
+175 -14
View File
@@ -5,12 +5,24 @@ This guide covers prerequisites, environment setup, running the application loca
## Prerequisites ## Prerequisites
- **Docker & Docker Compose**: Required for running the full stack (playout, chat, nginx, redis, db, prometheus, grafana). Install from [docker.com](https://www.docker.com). - **Docker & Docker Compose**: Required for running the full stack (playout, chat, nginx, redis, db, prometheus, grafana). Install from [docker.com](https://www.docker.com).
- **Node.js 20+**: For frontend development (`cd frontend && npm run dev`). - **Node.js 20+**: Required for frontend development and production builds (`cd frontend && npm install && npm run build`).
- **npm**: Installed with Node.js and used for the frontend dependency installation and Vite workflow.
- **Go 1.23+**: Recommended when working on the Go services outside Docker (`backend/chat`, `backend/playout`).
- **FFmpeg**: Used by the playout service for HLS generation. Install via `apt install ffmpeg` (Linux), Homebrew (macOS), or Winget (Windows). - **FFmpeg**: Used by the playout service for HLS generation. Install via `apt install ffmpeg` (Linux), Homebrew (macOS), or Winget (Windows).
- **Git**: For cloning and version control. - **Git**: For cloning and version control.
- **Cloudflare Account**: For R2 storage and CDN (free tier sufficient for most use cases). - **Cloudflare Account**: For R2 storage and CDN (free tier sufficient for most use cases).
- **Gitea Account**: Required if you want to push images to the self-hosted registry or use the repository's CI/CD flow.
- **Hetzner Cloud Account** (optional for production): For the US-East VPS (Ashburn, VA recommended per ADR-001). - **Hetzner Cloud Account** (optional for production): For the US-East VPS (Ashburn, VA recommended per ADR-001).
## Development Prerequisites
- **Frontend dependencies**: Run `cd frontend && npm install` before `npm run dev` or `npm run build`.
- **Docker build support**: Local development expects Docker to be able to build the `chat`, `playout`, and `nginx` images referenced in `docker-compose.yml`.
- **Data directories**: Create `data/playlist`, `data/media`, `data/hls`, and `data/chat` before the first run so bind mounts resolve cleanly.
- **Database choice**: The stack defaults to PostgreSQL in `docker-compose.yml`. If you want SQLite for local-only development, update `DATABASE_URL` in `.env` accordingly.
- **Redis availability**: Chat presence, rate limiting, and pub/sub require Redis. The compose stack provides it automatically.
- **Video inputs**: Source videos should be normalized before playout. If the playlist contains invalid files, the playout container will fail its validation flow.
## Environment Setup ## Environment Setup
1. **Clone the Repository** (if not already done): 1. **Clone the Repository** (if not already done):
@@ -34,10 +46,17 @@ This guide covers prerequisites, environment setup, running the application loca
R2_SECRET_ACCESS_KEY=your-r2-secret-key R2_SECRET_ACCESS_KEY=your-r2-secret-key
R2_BUCKET_NAME=linearvoid-media R2_BUCKET_NAME=linearvoid-media
R2_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com R2_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com
R2_REGION=auto
# Cloudflare (for custom domain and cache rules) # Cloudflare (for custom domain and cache rules)
CLOUDFLARE_API_TOKEN=your-cloudflare-api-token CLOUDFLARE_API_TOKEN=your-cloudflare-api-token
MEDIA_DOMAIN=media.example.com CLOUDFLARE_ACCOUNT_ID=your-account-id
MEDIA_DOMAIN=media.linearvoid.tv
# Public frontend URLs
VITE_PUBLIC_APP_URL=http://localhost:8080
VITE_PUBLIC_WS_URL=ws://localhost:3000/ws
VITE_PUBLIC_HLS_URL=http://localhost:8080/hls/playlist.m3u8
# Gitea Container Registry (for self-hosted Docker images) # Gitea Container Registry (for self-hosted Docker images)
GITEA_REGISTRY_URL=git.allucanget.biz GITEA_REGISTRY_URL=git.allucanget.biz
@@ -46,33 +65,112 @@ This guide covers prerequisites, environment setup, running the application loca
GITEA_REGISTRY_NAMESPACE=zwitschi/linearvoid GITEA_REGISTRY_NAMESPACE=zwitschi/linearvoid
# Chat / Database # Chat / Database
REDIS_URL=redis://redis:6379/0 DATABASE_URL=postgres://user:password@localhost:5432/linearvoid
DATABASE_URL=sqlite://./data/chat.db # or postgres://user:pass@db:5432/linearvoid # or for SQLite in local development:
# DATABASE_URL=sqlite://./data/chat.db
REDIS_URL=redis://localhost:6379/0
# Ports (for local development) # Ports (for local development)
PLAYOUT_PORT=8080
CHAT_PORT=3000 CHAT_PORT=3000
NGINX_PORT=8080 NGINX_PORT=80
PLAYOUT_HEALTH_PORT=8081
# Playout Configuration # Playout Configuration
PLAYLIST_PATH=/app/playlist/playlist.txt PLAYLIST_PATH=/app/playlist.txt
HLS_SEGMENT_DURATION=6 HLS_SEGMENT_DURATION=6
HLS_WINDOW_SIZE=24 HLS_WINDOW_SIZE=24
# Logging # Logging / Monitoring
LOG_LEVEL=info LOG_LEVEL=info
PROMETHEUS_PORT=9090
GRAFANA_PORT=3001
# Development flags
NODE_ENV=development
GO_ENV=development
# Gitea API access (optional, for CI/release automation)
GITEA_SERVER_URL=https://git.allucanget.biz
GITEA_TOKEN=your-gitea-token
GITEA_USERNAME=your-gitea-username
``` ```
4. **Create Data Directories**: 4. **Check local value overrides for Docker Compose**:
- `docker-compose.yml` builds images locally and maps the frontend build output from `frontend/dist` into nginx.
- `docker-compose.prod.yml` pulls prebuilt images from the Gitea registry.
- For production on the VPS, change hostnames in `.env` so containers talk to each other over the compose network, for example `REDIS_URL=redis://redis:6379/0` and `DATABASE_URL=postgres://linearvoid:<password>@db:5432/linearvoid`.
- For production public routing, set `VITE_PUBLIC_APP_URL=https://linearvoid.tv`, `VITE_PUBLIC_WS_URL=wss://linearvoid.tv/ws`, and `VITE_PUBLIC_HLS_URL=https://media.linearvoid.tv/hls/playlist.m3u8`.
5. **Create Data Directories**:
```bash ```bash
mkdir -p data/playlist data/media data/hls data/chat mkdir -p data/playlist data/media data/hls data/chat
``` ```
5. **Prepare Sample Videos** (optional): 6. **Prepare Sample Videos** (optional):
- Place normalized MP4 files in `data/media/`. - Place normalized MP4 files in `data/media/`.
- Create a `data/playlist/playlist.txt` with paths (see ADR-001 for normalization requirements). - Create a `data/playlist/playlist.txt` with paths (see ADR-001 for normalization requirements).
## Cloudflare R2 Setup
1. **Create an R2 bucket**:
- Create a bucket such as `linearvoid-media` in Cloudflare R2.
- Keep the bucket name aligned with `R2_BUCKET_NAME` in `.env`.
2. **Generate R2 API credentials**:
- Create an access key pair with read/write permissions for the bucket.
- Set `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, and `R2_REGION=auto` in `.env`.
3. **Find the S3 endpoint**:
- Use the account-scoped endpoint format `https://<account-id>.r2.cloudflarestorage.com`.
- Store the account ID in both `R2_ENDPOINT` and `CLOUDFLARE_ACCOUNT_ID` where applicable.
4. **Bind a custom media domain**:
- Attach a domain such as `media.linearvoid.tv` to the bucket.
- Set `MEDIA_DOMAIN` to that hostname.
5. **Configure app-domain DNS and TLS**:
- Point `linearvoid.tv` to the public entrypoint serving nginx.
- Point `www.linearvoid.tv` to the same target and redirect it to `https://linearvoid.tv`.
- Keep `media.linearvoid.tv` on the R2 custom domain path so HLS delivery stays split from the app origin.
- Use Cloudflare proxied DNS with Full (strict) TLS so browser traffic uses `https://linearvoid.tv`, `wss://linearvoid.tv/ws`, and `https://media.linearvoid.tv`.
6. **Apply cache behavior**:
- Configure the `.ts` and `.m3u8` cache rules from `infrastructure/cloudflare/cache-rules.md`.
- Enable Smart Tiered Cache for the R2-backed hostname.
- If you need header normalization for immutable segment caching, deploy `infrastructure/cloudflare/worker.js` to the same zone.
7. **Verify access**:
- Confirm uploads with an S3-compatible client such as `aws s3 ls --endpoint-url <R2_ENDPOINT>`.
- After deployment, check Cloudflare analytics for cache hit ratio and origin request volume.
## Gitea Setup
1. **Create or verify a Gitea user**:
- The user should have access to the repository and permission to read from the container registry.
2. **Create an access token**:
- Generate a token with the scopes needed for package/registry access and, if applicable, repository automation.
- Store it as `GITEA_TOKEN` in `.env`.
3. **Configure registry variables**:
- Set `GITEA_REGISTRY_URL` to the Gitea host.
- Set `GITEA_REGISTRY_NAMESPACE` to the image namespace, for example `zwitschi/linearvoid`.
- Set `GITEA_USERNAME` to the account that will push or pull images.
4. **Authenticate Docker to the registry**:
```bash
docker login git.allucanget.biz
```
5. **Build and publish images when needed**:
- Local development uses `docker-compose.yml`, which builds from source.
- Production uses `docker-compose.prod.yml`, which expects the `playout`, `chat`, and `nginx` images to exist in the configured Gitea registry namespace.
6. **Validate image availability**:
- Before a production rollout, verify that the expected `:latest` tags can be pulled from the registry on the target server.
## Running the Application ## Running the Application
### Local Development ### Local Development
@@ -85,7 +183,7 @@ This guide covers prerequisites, environment setup, running the application loca
2. **Access the Application**: 2. **Access the Application**:
- Viewer: <http://localhost:8080> - Viewer: <http://localhost:8080>
- Chat WebSocket: ws://localhost:3000/ws - Chat WebSocket: `ws://localhost:3000/ws` when using the local env defaults, or `ws://localhost:8080/ws` if you switch to same-origin local routing.
- Admin Interface: <http://localhost:8080/admin> (use the credentials from `.env` or default admin/admin) - Admin Interface: <http://localhost:8080/admin> (use the credentials from `.env` or default admin/admin)
- Health Checks: - Health Checks:
- Playout: <http://localhost:8081/health> - Playout: <http://localhost:8081/health>
@@ -98,23 +196,85 @@ This guide covers prerequisites, environment setup, running the application loca
```bash ```bash
cd frontend cd frontend
npm install
npm run dev npm run dev
``` ```
The dev server runs on <http://localhost:5173>. It proxies API calls to the backend services. The dev server runs on <http://localhost:5173>. It proxies API calls to the backend services.
4. **Stop Services**: 4. **Build frontend assets for nginx-backed local runs**:
```bash
cd frontend
npm install
npm run build
```
The local nginx container serves `frontend/dist`, so rebuilding the frontend is required before validating production-like static output.
5. **Stop Services**:
```bash ```bash
docker compose down docker compose down
``` ```
### Production Setup Guide
1. **Prepare the target host**:
- Provision a Linux VPS with Docker Engine, Docker Compose, Git, and enough disk for media, HLS output, PostgreSQL, Redis persistence, and monitoring data.
- Open the ports required for HTTP/HTTPS and any restricted monitoring access you intend to expose.
2. **Clone the repository and create the environment file**:
```bash
git clone https://git.allucanget.biz/zwitschi/linearvoid.git
cd linearvoid
cp .env.example .env
```
3. **Set production-safe `.env` values**:
- Replace placeholder secrets.
- Use compose-network hostnames for internal services, for example `redis` and `db`.
- Set strong values for `POSTGRES_PASSWORD` and `GRAFANA_PASSWORD`.
- Keep `MEDIA_DOMAIN`, R2 credentials, and Gitea registry values consistent with the external infrastructure.
- Set `MEDIA_DOMAIN=media.linearvoid.tv`.
- Set `VITE_PUBLIC_APP_URL=https://linearvoid.tv`, `VITE_PUBLIC_WS_URL=wss://linearvoid.tv/ws`, and `VITE_PUBLIC_HLS_URL=https://media.linearvoid.tv/hls/playlist.m3u8`.
4. **Create runtime directories on the server**:
```bash
mkdir -p data/playlist data/media data/hls data/chat
```
5. **Publish or verify required images in Gitea**:
- Ensure the `playout`, `chat`, and `nginx` images exist under `${GITEA_REGISTRY_URL}/${GITEA_REGISTRY_NAMESPACE}`.
- Log in to the registry from the VPS before pulling.
6. **Deploy the production stack**:
```bash
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d
```
7. **Run post-deploy verification**:
- Check `docker compose -f docker-compose.prod.yml ps`.
- Confirm nginx, chat, and playout health endpoints respond.
- Verify that HLS output is being written to `data/hls` and that the media domain serves manifests and segments through Cloudflare.
8. **Harden and operate the deployment**:
- Put HTTPS in front of nginx using Cloudflare or another reverse proxy, with `linearvoid.tv` as canonical app host and `www.linearvoid.tv` redirected.
- Restrict Grafana and Prometheus exposure to trusted IPs or a private network.
- Run the stack under systemd or another service manager if you need auto-start behavior outside Docker restart policies.
### Production Deployment (Hetzner VPS + Cloudflare) ### Production Deployment (Hetzner VPS + Cloudflare)
1. **Provision VPS**: Use Hetzner Cloud CX22 in Ashburn, VA (per ADR-001). 1. **Provision VPS**: Use Hetzner Cloud CX22 in Ashburn, VA (per ADR-001).
2. **Configure Cloudflare**: 2. **Configure Cloudflare**:
- Create R2 bucket with `enam` location hint. - Create R2 bucket with `enam` location hint.
- Set up custom domain (`media.example.com`) with the cache rules from `infrastructure/cloudflare/cache-rules.md`. - Set up custom media domain (`media.linearvoid.tv`) with the cache rules from `infrastructure/cloudflare/cache-rules.md`.
- Point `linearvoid.tv` and `www.linearvoid.tv` at the app entrypoint, then redirect `www` to the apex domain.
- Use Full (strict) TLS for both app and media hostnames.
- Apply the Worker from `infrastructure/cloudflare/worker.js` for advanced header management. - Apply the Worker from `infrastructure/cloudflare/worker.js` for advanced header management.
3. **Deploy**: 3. **Deploy**:
- Copy the project to the VPS. - Copy the project to the VPS.
@@ -133,6 +293,7 @@ See `docs/architecture/README.md` for the full arc42 documentation and the ADRs
- **Cache Not Working**: Verify Cloudflare cache rules (`.ts` = long TTL immutable, `.m3u8` = short TTL). Check cache hit ratio in Cloudflare dashboard. - **Cache Not Working**: Verify Cloudflare cache rules (`.ts` = long TTL immutable, `.m3u8` = short TTL). Check cache hit ratio in Cloudflare dashboard.
- **Docker Issues**: Run `docker compose logs` for service-specific errors. Ensure ports are not in use. - **Docker Issues**: Run `docker compose logs` for service-specific errors. Ensure ports are not in use.
- **Admin Interface**: Ensure the protected route is configured in nginx and authentication is set up (default is open for local development). - **Admin Interface**: Ensure the protected route is configured in nginx and authentication is set up (default is open for local development).
- **Production Pull Fails**: Confirm `docker login` to Gitea succeeded on the server and the image tags referenced by `docker-compose.prod.yml` exist.
## Further Reading ## Further Reading
@@ -143,4 +304,4 @@ See `docs/architecture/README.md` for the full arc42 documentation and the ADRs
The system is ready for use. For production, review ADR-001 for cost estimates and scaling guidance. The system is ready for use. For production, review ADR-001 for cost estimates and scaling guidance.
**Last Updated**: 2026-08-02 **Last Updated**: 2026-08-03
+8 -8
View File
@@ -1,17 +1,17 @@
FROM golang:1.23-alpine AS builder FROM golang:1.23-alpine AS builder
WORKDIR /app RUN apk add --no-cache gcc musl-dev
COPY src/ ./src/
COPY go.mod go.sum ./
RUN go mod download WORKDIR /app
RUN CGO_ENABLED=0 GOOS=linux go build -o /chat-server ./src/main.go COPY go.mod ./
COPY src/ ./src/
RUN go mod tidy
RUN CGO_ENABLED=1 GOOS=linux go build -o /chat-server ./src/main.go
FROM alpine:3.20 FROM alpine:3.20
RUN apk --no-cache add ca-certificates RUN apk --no-cache add ca-certificates
WORKDIR /app WORKDIR /app
COPY --from=builder /chat-server . COPY --from=builder /chat-server /usr/local/bin/chat-server
COPY config/ ./config/
EXPOSE 3000 EXPOSE 3000
CMD ["./chat-server"] CMD ["/usr/local/bin/chat-server"]
+1 -2
View File
@@ -1,8 +1,7 @@
FROM golang:1.23-alpine AS builder FROM golang:1.25-alpine AS builder
WORKDIR /app WORKDIR /app
COPY src/ ./src/ COPY src/ ./src/
COPY go.mod go.sum ./
# Initialize Go module if not present # Initialize Go module if not present
RUN go mod init playout || true RUN go mod init playout || true
+1 -1
View File
@@ -75,7 +75,7 @@ func ValidateVideo(videoPath string) (VideoFile, error) {
v := VideoFile{Path: videoPath} v := VideoFile{Path: videoPath}
cmd := exec.Command("ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", videoPath) cmd := exec.Command("ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", videoPath)
output, err := cmd.Output() _, err := cmd.Output()
if err != nil { if err != nil {
v.Validation = "ffprobe failed: " + err.Error() v.Validation = "ffprobe failed: " + err.Error()
return v, err return v, err
+1 -1
View File
@@ -49,7 +49,7 @@ services:
ports: ports:
- "${NGINX_PORT:-8080}:80" - "${NGINX_PORT:-8080}:80"
volumes: volumes:
- ./data/hls:/usr/share/nginx/html/hls:ro - ./data/hls:/srv/hls:ro
networks: networks:
- linearvoid-net - linearvoid-net
depends_on: depends_on:
+2 -4
View File
@@ -1,5 +1,3 @@
version: "3.9"
services: services:
playout: playout:
image: ${GITEA_REGISTRY_URL:-git.allucanget.biz}/${GITEA_REGISTRY_NAMESPACE:-zwitschi/linearvoid}/playout:latest image: ${GITEA_REGISTRY_URL:-git.allucanget.biz}/${GITEA_REGISTRY_NAMESPACE:-zwitschi/linearvoid}/playout:latest
@@ -60,8 +58,8 @@ services:
ports: ports:
- "${NGINX_PORT:-8080}:80" - "${NGINX_PORT:-8080}:80"
volumes: volumes:
- ./frontend/dist:/usr/share/nginx/html:ro - ./frontend/dist:/srv/frontend:ro
- ./data/hls:/usr/share/nginx/html/hls:ro - ./data/hls:/srv/hls:ro
networks: networks:
- linearvoid-net - linearvoid-net
depends_on: depends_on:
+26 -5
View File
@@ -16,13 +16,34 @@ let ws: WebSocket | null = null;
let currentRoom = "main"; let currentRoom = "main";
let username = "viewer" + Math.floor(Math.random() * 1000); let username = "viewer" + Math.floor(Math.random() * 1000);
function resolveWebSocketBaseUrl() {
const configuredUrl = import.meta.env.VITE_PUBLIC_WS_URL?.trim();
if (configuredUrl) {
return configuredUrl;
}
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
return `${protocol}//${window.location.host}/ws`;
}
function resolveManifestUrl() {
const configuredUrl = import.meta.env.VITE_PUBLIC_HLS_URL?.trim();
if (configuredUrl) {
return configuredUrl;
}
return new URL("/hls/playlist.m3u8", window.location.origin).toString();
}
function connectWebSocket(room: string) { function connectWebSocket(room: string) {
if (ws) ws.close(); if (ws) ws.close();
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; const socketUrl = new URL(resolveWebSocketBaseUrl());
ws = new WebSocket( socketUrl.searchParams.set("username", username);
`${protocol}//localhost:3000/ws?username=${username}&room=${room}`, socketUrl.searchParams.set("room", room);
); ws = new WebSocket(socketUrl);
ws.onopen = () => { ws.onopen = () => {
console.log(`Connected to chat room: ${room}`); console.log(`Connected to chat room: ${room}`);
@@ -113,7 +134,7 @@ window.app = () => {
// Initialize HLS Player // Initialize HLS Player
const video = document.getElementById("player") as HTMLVideoElement; const video = document.getElementById("player") as HTMLVideoElement;
const manifestUrl = "http://localhost:8080/hls/playlist.m3u8"; // from nginx/pla yout const manifestUrl = resolveManifestUrl();
if (Hls.isSupported()) { if (Hls.isSupported()) {
hls = new Hls(); hls = new Hls();
+5
View File
@@ -0,0 +1,5 @@
import { defineConfig } from "vite";
export default defineConfig({
envDir: "..",
});
+1 -1
View File
@@ -3,7 +3,7 @@ FROM nginx:alpine
COPY nginx.conf /etc/nginx/nginx.conf COPY nginx.conf /etc/nginx/nginx.conf
# Create directories for HLS and frontend # Create directories for HLS and frontend
RUN mkdir -p /usr/share/nginx/html/hls /usr/share/nginx/html/admin RUN mkdir -p /srv/frontend /srv/hls /srv/frontend/admin
EXPOSE 80 EXPOSE 80
CMD ["nginx", "-g", "daemon off;"] CMD ["nginx", "-g", "daemon off;"]
+24 -6
View File
@@ -3,11 +3,25 @@ events {
} }
http { http {
resolver 127.0.0.11 ipv6=off valid=30s;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server { server {
listen 80; listen 80;
server_name localhost; server_name www.linearvoid.tv;
root /usr/share/nginx/html; return 301 https://linearvoid.tv$request_uri;
}
server {
listen 80 default_server;
server_name linearvoid.tv localhost 127.0.0.1;
root /srv/frontend;
index index.html; index index.html;
# Serve static frontend # Serve static frontend
@@ -17,16 +31,20 @@ http {
# Proxy chat WebSocket # Proxy chat WebSocket
location /ws { location /ws {
proxy_pass http://chat:3000; set $chat_upstream http://chat:3000;
proxy_pass $chat_upstream;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
} }
# Serve HLS from playout (with cache headers for Cloudflare) # Local/dev fallback HLS origin. Production media should use MEDIA_DOMAIN.
location /hls/ { location /hls/ {
alias /usr/share/nginx/html/hls/; alias /srv/hls/;
add_header Cache-Control "public, max-age=3600"; add_header Cache-Control "public, max-age=3600";
add_header Access-Control-Allow-Origin "*"; add_header Access-Control-Allow-Origin "*";
} }