Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f939f24
WIP playback token auth
Thiritin Aug 2, 2026
2c6f328
Remove hardcoded EF branding
Thiritin Aug 2, 2026
a34e10a
Expand S3 port banner
Thiritin Aug 2, 2026
2ca1383
Update DVR archive plan
Thiritin Aug 2, 2026
e57085a
Remove Filament, port admin to manage
Thiritin Aug 2, 2026
7849c61
Prevent session id collision
Thiritin Aug 2, 2026
d315ed8
Finalise DVR archive plan
Thiritin Aug 2, 2026
ad11729
Drop stream control, regroup rail
Thiritin Aug 2, 2026
be7516d
Add HLS archive uploader
Thiritin Aug 2, 2026
d8aacf5
Sync roles by external id
Thiritin Aug 2, 2026
380936b
Add built-in swatch to accent picker
Thiritin Aug 3, 2026
349cfe0
Preview accent colour live in settings
Thiritin Aug 3, 2026
ce2dbe9
Tint admin chrome from accent colour
Thiritin Aug 3, 2026
218dd5b
Stamp observed time in index
Thiritin Aug 3, 2026
ca89f84
Branding CLI and logo sizing
Thiritin Aug 3, 2026
0987c55
Cap and guard upload rate
Thiritin Aug 3, 2026
7ce88ab
Cut recordings from archive
Thiritin Aug 3, 2026
95da464
Serve recordings via presigned urls
Thiritin Aug 3, 2026
9b4a03a
Add cut editor and fix marker timezone
Thiritin Aug 3, 2026
90b223a
Fix migration for sqlite tests
Thiritin Aug 3, 2026
d9438e5
Rebuild cut editor on hls.js
Thiritin Aug 3, 2026
d5b98d4
Fix cut editor playback and layout
Thiritin Aug 3, 2026
fd5db13
Freeze timeline window while dragging
Thiritin Aug 3, 2026
c4ea9f3
Make cut timeline a real ruler
Thiritin Aug 3, 2026
24c6c7a
Scrub by dragging the track
Thiritin Aug 3, 2026
d64ad0a
Trim verbose ui copy
Thiritin Aug 3, 2026
dbbbcf2
mega dump
Thiritin Aug 3, 2026
cd3959e
Start archive uploader with stack
Thiritin Aug 3, 2026
295e1f3
Normalise cut markers to app timezone
Thiritin Aug 3, 2026
28c7c7d
Add readme and screenshots
Thiritin Aug 3, 2026
dc99677
Move screenshots, drop artefacts
Thiritin Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions .env.build
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
VITE_APP_NAME="Eurofurence Stream"
VITE_APP_NAME="Streaming"

# Reverb WebSocket configuration for production build
VITE_REVERB_APP_KEY=stream
VITE_REVERB_HOST=stream.eurofurence.org
VITE_REVERB_HOST=
VITE_REVERB_PORT=443
VITE_REVERB_SCHEME=https
2 changes: 1 addition & 1 deletion .env.dvr-process.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ TEMP_DIR=/tmp/dvr-processing # Temporary directory for processing
DVR_SOURCE_DIR=/var/dvr # Path to DVR storage directory

# Optional: Override for specific environments
# API_BASE_URL=https://streaming.eurofurence.org/api
# API_BASE_URL=https://streaming.example.org/api
# DVR_SOURCE_DIR=/mnt/dvr-storage
71 changes: 55 additions & 16 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,37 @@ APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_URL=http://streaming.test

LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

DB_CONNECTION=mysql
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PORT=5432
DB_DATABASE=ef_streaming
DB_USERNAME=postgres
DB_PASSWORD=

BROADCAST_DRIVER=reverb
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
QUEUE_CONNECTION=database
SESSION_DRIVER=file
SESSION_LIFETIME=120

MEMCACHED_HOST=127.0.0.1
# 4 weeks, in minutes. Matches AUTH_REMEMBER_LIFETIME so attendees stay signed in.
SESSION_LIFETIME=40320
AUTH_REMEMBER_LIFETIME=40320

# Valkey/Redis (production uses this for cache, queue via Horizon, and Reverb scaling)
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_PASSWORD=null

MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
Expand All @@ -57,6 +59,19 @@ DVR_AWS_URL=https://s3.your-server.com/recording
# Generate a secure random string for production
STREAM_SYSTEM_STREAMKEY=

# Playback token secrets, shared with every edge server so it can verify tokens
# locally without calling back into Laravel. Two separate secrets so a leak of
# the viewer secret cannot mint long-lived embed keys.
# Generate with: openssl rand -hex 32
HLS_VIEWER_SECRET=
HLS_EMBED_SECRET=

# Viewer token lifetime in seconds, the grace window edges allow past expiry,
# and how long before expiry a fresh token is pushed to the player.
HLS_TOKEN_TTL=900
HLS_TOKEN_LEEWAY=60
HLS_TOKEN_REFRESH_MARGIN=180

# Local streaming server override for specific IP subnets
# When client IPs match these subnets, force use of LOCAL_STREAMING_HOSTNAME server
# Use CIDR notation, e.g., 192.168.1.0/24 for IPv4, 2001:db8::/64 for IPv6
Expand All @@ -68,11 +83,11 @@ REVERB_APP_ID=my-app-id
REVERB_APP_KEY=my-app-key
REVERB_APP_SECRET=my-app-secret
REVERB_HOST=localhost
REVERB_PORT=8080
REVERB_PORT=8081
REVERB_SCHEME=http

REVERB_SERVER_HOST=0.0.0.0
REVERB_SERVER_PORT=8080
REVERB_SERVER_PORT=8081
REVERB_MAX_REQUEST_SIZE=250000
REVERB_APP_MAX_MESSAGE_SIZE=100000
REVERB_SCALING_ENABLED=false
Expand All @@ -98,12 +113,36 @@ VITE_PUSHER_SCHEME="${REVERB_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

# DNS Configuration for dynamic updates
DNS_SERVER=85.199.154.53
DNS_ZONE=stream.eurofurence.org
DNS_SERVER=
DNS_ZONE=stream.example.org
DNS_KEY_NAME=stream-ddns
DNS_KEY_ALGORITHM=hmac-sha256
DNS_KEY_SECRET=
DNS_TTL=60

# Recording API Authentication
RECORDING_API_KEY=your-secure-api-key-here

# Branding (name, copy, links, logo, accent colour) is not configured here.
# It lives in /manage > Settings, stored in the branding_settings table, and
# applies without a deploy or a rebuild. config/branding.php holds the neutral
# defaults a fresh install boots with. For scripted setup:
# php artisan branding:set primary_color=#0e7490 site_name="My Con"

# Is signing in mandatory? true puts every page behind the identity provider.
# false opens browse, schedule, archive and the player to guests, and leaves
# login mandatory only for chat, which needs an identity to attribute and
# moderate. Role-restricted shows and recordings stay hidden from guests either
# way.
AUTH_REQUIRED=true

# Chat on or off for the whole installation. false 404s every chat endpoint and
# hides the panel, pop-out and emote pages; streams keep playing.
CHAT_ENABLED=true

# Chat: comma separated domains whose links stay clickable. Empty strips them all.
CHAT_ALLOWED_DOMAINS=

# Container images for the generated provisioning scripts, built from docker/.
#STREAM_IMAGE_FFMPEG_HLS=
#STREAM_IMAGE_DVR_UPLOADER=
Binary file added .github/screenshots/archive.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/browse.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/login.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/manage-cut-editor.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/manage-dashboard.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/manage-planner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/manage-settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/manage-shows.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/manage-sources.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/mobile-player.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/player-chat.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshots/schedule.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 17 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,20 @@ frankenphp
frankenphp-worker.php

# Logs
logs
logs
# Local dev stream loops (scripts/dev-streams.sh)
/public/dev-streams

# Generated per-server install script (contains a live shared secret)
/install.sh

# Python bytecode from the DVR uploader image build
__pycache__/

# Playwright MCP session artefacts (console logs, snapshots, screenshots)
.playwright-mcp/

# Throwaway screenshots dropped in the working directory. The ones the README
# uses live in .github/screenshots.
/*.png
/*.jpg
81 changes: 54 additions & 27 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,22 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

This is a Laravel-based streaming system for Eurofurence (and other conventions) that manages live video streaming infrastructure. It includes server provisioning, client management, real-time chat, and auto-scaling capabilities.
This is a Laravel-based streaming system for conventions that manages live video streaming infrastructure. It includes server provisioning, client management, real-time chat, and auto-scaling capabilities.

Nothing convention-specific is hardcoded. Names, copy, links, logo, login background and accent colour resolve through `App\Services\BrandingService`, backed by the `branding_settings` table with neutral fallbacks in `config/branding.php`. Never reintroduce a convention name, domain, or logo as a literal or a config default.

Branding has exactly one source: the `branding_settings` table, edited at `/manage` > Settings or via `php artisan branding:set key=value`. Do not add `env()` to `config/branding.php` or `BRANDING_*` vars to `.env` - a saved row always wins, so a second source could only disagree. The accent colour is applied as runtime CSS custom properties (`app.blade.php`, after `@vite`), so changing it needs no rebuild; never move it into a `VITE_` var.

## Core Architecture

### Tech Stack
- **Backend**: Laravel 12 with PHP 8.2+
- **Frontend**: Vue 3 with Inertia.js 2
- **Admin Panel**: Filament 3
- **Admin Panel**: Inertia + Vue at `/manage` (no Filament)
- **Real-time**: Pusher/Soketi for WebSockets
- **Streaming**: SRS (Simple Realtime Server) for RTMP/FLV streaming
- **Queue**: Laravel Horizon with Redis
- **Database**: MySQL 8.0
- **Queue**: Laravel Horizon with Redis (production); database queue driver locally
- **Database**: MySQL 8.0 (production), PostgreSQL locally
- **Infrastructure**: Hetzner Cloud API for server provisioning

### Key Components
Expand Down Expand Up @@ -43,6 +47,9 @@ This is a Laravel-based streaming system for Eurofurence (and other conventions)
## Development Commands

### Local Development

Not Sail/Docker. PHP, Postgres, and Valkey run natively. Yerd serves the site.

```bash
# Install dependencies
composer install
Expand All @@ -51,16 +58,25 @@ npm install
# Run migrations and seeders
php artisan migrate --seed

# Start development servers
php artisan serve # Laravel development server
# Start dev servers
npm run dev # Vite dev server for assets
php artisan horizon # Queue worker
php artisan octane:start # High-performance server (optional)

# Run with Docker Compose (includes all services)
docker-compose up
php artisan queue:work # Process queued jobs (database driver locally, no Horizon needed)
php artisan reverb:start # WebSocket server for chat/broadcasting
```

Site is served by Yerd at `http://streaming.test` (`APP_URL`); no `artisan serve` needed.

### Local Ports

| Port | Service | Notes |
|------|---------|-------|
| 80 | Yerd | serves `streaming.test`; its daemon (`yerdd`) also holds 8080 |
| 5173 | Vite | `npm run dev`; `detectTls: false` in `vite.config.js` so the plugin does not probe Yerd's valet config for certs |
| 8081 | Reverb | WebSockets; `REVERB_PORT`/`REVERB_SERVER_PORT`, 8080 is unavailable |
| 6379 | Valkey | Redis-compatible, reached via the `phpredis` extension and the `REDIS_*` env vars |

Local `CACHE_DRIVER=file` and `QUEUE_CONNECTION=database` by design, so Valkey is optional locally; production uses it for cache, Horizon queues, and Reverb scaling.

### Testing
```bash
# Run all tests
Expand Down Expand Up @@ -88,10 +104,10 @@ php artisan view:clear

### Queue Management
```bash
# Process jobs
# Process jobs (local: database driver)
php artisan queue:work

# Monitor with Horizon dashboard (visit /horizon)
# Production uses Horizon with Redis (visit /horizon)
php artisan horizon
```

Expand All @@ -104,15 +120,16 @@ Key environment variables to configure:
- `STREAM_*`: Streaming server configuration
- `CHAT_*`: Chat moderation settings

## Docker Services
## Docker Images (production/Kubernetes)

The `docker-compose.yml` includes:
- `laravel.test`: Main application container
- `mysql`: Database
- `redis`: Cache and queues
- `soketi`: WebSocket server
- `stream`: SRS edge server
- `origin`: SRS origin server
`docker/` contains Dockerfiles for services deployed to Kubernetes in production. Not used for local development:
- `docker/origin-srs`, `docker/origin-nginx`, `docker/origin-caddy`: Origin streaming stack
- `docker/edge-nginx`, `docker/edge-caddy`: Edge streaming stack
- `docker/dvr-uploader`: DVR recording uploader
- `docker/ffmpeg-hls`: HLS transcoder
- `docker/mysql`: Production MySQL init scripts

Root `Dockerfile` builds the main Laravel app image (built via `.github/workflows/docker.yml`).

## Job Queue Architecture

Expand All @@ -125,15 +142,25 @@ Critical background jobs for server management:

## Admin Interface

Filament admin panel at `/admin` provides:
- Server management and monitoring
- Client connection tracking
- User management with role-based permissions
- Real-time capacity and performance widgets
The admin panel is the Inertia panel at `/manage`. Filament is gone; `/admin` is a 301 into `/manage`.

`/manage` covers:
- Dashboard: capacity, server health, alerts, live viewers, the next few hours of programme
- Sources, Shows, the Show planner and Stream Control
- Import: pulls sessions from pretalx into shows; see docs/admin/pretalx-import.md
- Servers, including the generated install script
- Users, Roles, Emotes and Recordings
- Settings: branding, login copy, accent colour and footer links

Tables, filters, row/bulk actions and toasts are declared server-side with the
`App\Support\Manage` toolkit (`Table`, `Column`, `Filter`, `Action`, `Status`, `Toast`) and
rendered by the shared components in `resources/js/Components/Manage`. Access runs through
the `access-manage` gate plus a policy per model.

## Important Development Rules

- **NEVER use fetch() or make API calls** unless absolutely necessary. Always use Inertia.js 2 props for passing data from backend to frontend. Data should be passed through page controllers or HandleInertiaRequests middleware for global data.
- Never use -gray- for tailwind colors always use -primary- as main color
- no need t orun build i got a npm run dev running
- Always use sail instead of docker-compose
- Local dev runs natively, not Sail/Docker
- The local dev server is **Yerd** (daemon `yerdd`), not Laravel Herd. They are different tools. Never call it Herd.
Loading
Loading