feat(bs): production prepping

This commit is contained in:
2026-04-29 16:14:48 +02:00
parent 728dcd9712
commit 953bc38c12
12 changed files with 177 additions and 7 deletions

View File

@@ -28,5 +28,6 @@ NEXT_PUBLIC_MEDIAMTX_WHIP_ROUTE_HQ=http://localhost:8889
# MEDIAMTX_API_ASIA=http://localhost:9999
# NEXT_PUBLIC_MEDIAMTX_INGEST_ROUTE_ASIA=localhost:8990
# idt you should change this
MEDIAMTX_PUBLISH_KEY=rjq1xdpCPA4qyt3jge
# generate with `openssl rand -base64 20`
MEDIAMTX_PUBLISH_KEY=
MEDIAMTX_API_KEY=

View File

@@ -80,6 +80,13 @@ export async function POST(request: NextRequest) {
}
return finish('authorized', 200, 'authorized_read');
}
if (parsedAction === 'api') {
if (password === process.env.MEDIAMTX_API_KEY) {
return finish('authorized api', 200, 'authorized_api');
}
return finish('unauthorized api', 401, 'unauthorized_api');
}
return finish('uhh', 401, 'unauthorized');
}

View File

@@ -259,7 +259,11 @@ export default function Page() {
</SelectTrigger>
<SelectContent>
{serverOptions.map((server) => (
<SelectItem key={server.value} value={server.value}>
<SelectItem
key={server.value}
value={server.value}
disabled={!server.whipEnabled}
>
{server.label} {server.emoji}
</SelectItem>
))}

View File

@@ -15,7 +15,18 @@ import { logout } from '@/lib/auth/actions';
import { useSession } from '@/lib/providers/SessionProvider';
import Link from 'next/link';
import { ThemeSwitcher } from '../ThemeSwitcher/ThemeSwitcher';
import { IdCard, Shield, Settings, Users, PenSquare, LogOut, Code, Github, Heart } from 'lucide-react';
import {
IdCard,
Shield,
Settings,
Users,
PenSquare,
LogOut,
Code,
Github,
Heart,
Radio,
} from 'lucide-react';
import { SidebarTrigger } from '@/components/ui/sidebar';
import Image from 'next/image';
import Logo from '@/lib/assets/logo.webp';
@@ -52,6 +63,16 @@ export default function Navbar(props: Props) {
{/* Right Side Items */}
<div className="flex items-center gap-1 md:gap-3 shrink-0">
{user && (
<Link href="/stream">
<Button variant="outline" size="sm" className="gap-1 md:gap-2 text-xs md:text-sm">
<Radio className="w-3 h-3 md:w-4 md:h-4" />
<span className="hidden sm:inline">Go live</span>
<span className="sm:hidden">Live</span>
</Button>
</Link>
)}
{props.editLivestream && <div className="hidden sm:block">{props.editLivestream}</div>}
{user ? (

View File

@@ -90,7 +90,15 @@ export async function syncStream() {
for (const r of regions) {
const region = MEDIAMTX_SERVER_REGIONS[r];
const response = await fetch(`${region.apiUrl}/v3/paths/list?itemsPerPage=1000`);
if (!region.apiAuthHeader) {
throw new Error('MEDIAMTX_API_KEY is required when querying the MediaMTX API');
}
const response = await fetch(`${region.apiUrl}/v3/paths/list?itemsPerPage=1000`, {
headers: {
Authorization: region.apiAuthHeader,
},
});
if (!response.ok) {
recordStreamSyncScrape(r, 'error');

View File

@@ -5,6 +5,7 @@ export interface MediaMTXClientEnvs {
publicUrl: string;
ingestRoute: string;
whip: string;
whipEnabled: boolean;
emoji: string;
string: string;
}
@@ -13,6 +14,7 @@ export interface MediaMTXClientRegionOption {
value: MediaMTXRegion;
emoji: string;
label: string;
whipEnabled: boolean;
}
export function getMediamtxClientEnvs(region: MediaMTXRegion = 'hq'): MediaMTXClientEnvs {
@@ -21,9 +23,18 @@ export function getMediamtxClientEnvs(region: MediaMTXRegion = 'hq'): MediaMTXCl
publicUrl: getEnv('NEXT_PUBLIC_MEDIAMTX_URL_HQ')!,
ingestRoute: getEnv('NEXT_PUBLIC_MEDIAMTX_INGEST_ROUTE_HQ')!,
whip: getEnv('NEXT_PUBLIC_MEDIAMTX_WHIP_ROUTE_HQ')!,
whipEnabled: false,
emoji: '🇺🇸',
string: 'HQ Server A',
},
ethande: {
publicUrl: getEnv('NEXT_PUBLIC_MEDIAMTX_URL_ETHANDE')!,
ingestRoute: getEnv('NEXT_PUBLIC_MEDIAMTX_INGEST_ROUTE_ETHANDE')!,
whip: getEnv('NEXT_PUBLIC_MEDIAMTX_WHIP_ROUTE_ETHANDE')!,
whipEnabled: true,
emoji: '🇩🇪',
string: 'eth0\'s VPS',
},
};
const regionEnvs = envs[region];
@@ -41,6 +52,7 @@ export function getMediamtxClientRegionOptions(): MediaMTXClientRegionOption[] {
value: 'hq',
emoji: '🇺🇸',
label: 'HQ Server A',
whipEnabled: false,
},
];
}

View File

@@ -1 +1 @@
export type MediaMTXRegion = 'hq';
export type MediaMTXRegion = 'hq' | 'ethande';

View File

@@ -2,11 +2,13 @@ import { MediaMTXRegion } from './regions';
export interface MediaMTXEnvs {
apiUrl: string;
apiAuthHeader?: string;
}
export const MEDIAMTX_SERVER_REGIONS: Record<MediaMTXRegion, MediaMTXEnvs> = {
hq: {
apiUrl: process.env.MEDIAMTX_API_HQ!,
apiAuthHeader: getMediamtxApiAuthHeader(),
},
};
@@ -19,3 +21,13 @@ export function getMediamtxEnvs(region: MediaMTXRegion = 'hq'): MediaMTXEnvs {
return envs;
}
function getMediamtxApiAuthHeader() {
const apiKey = process.env.MEDIAMTX_API_KEY;
if (!apiKey) {
return undefined;
}
return `Basic ${Buffer.from(`hctv-api:${apiKey}`).toString('base64')}`;
}

View File

@@ -12,10 +12,14 @@ hlsPartDuration: 1s
hlsSegmentCount: 10
webrtc: yes
webrtcAddress: :8889
webrtcLocalUDPAddress: :8189
webrtcAdditionalHosts: []
authMethod: http
authHTTPAddress: http://hctv:3000/api/mediamtx/publish
authHTTPAddress: https://hackclub.tv/api/mediamtx/publish
api: yes
apiAddress: 0.0.0.0:9997
metrics: yes
metricsAddress: :9998

View File

@@ -0,0 +1,12 @@
ACME_EMAIL=ops@hackclub.tv
# public hostnames and stuff
MEDIAMTX_HLS_HOST=hls.hackclub.tv
MEDIAMTX_WEBRTC_HOST=whip.hackclub.tv
MEDIAMTX_API_HOST=mmtxapi.hackclub.tv
# public ip for webrtc stuff
MEDIAMTX_WEBRTC_ADDITIONAL_HOSTS=203.0.113.10
# mediamtx publish route on hctv
MEDIAMTX_AUTH_HTTP_ADDRESS=https://hackclub.tv/api/mediamtx/publish

View File

@@ -0,0 +1,63 @@
services:
traefik:
image: traefik:v3.5
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.srt.address=:8890/udp
- --entrypoints.webrtc-ice.address=:8189/udp
- --certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.letsencrypt.acme.httpchallenge=true
- --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
ports:
- 80:80
- 443:443
- 8890:8890/udp
- 8189:8189/udp
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- letsencrypt:/letsencrypt
restart: unless-stopped
mediamtx:
image: bluenviron/mediamtx:1
volumes:
- ./mediamtx.yml:/mediamtx.yml:ro
environment:
MTX_WEBRTCADDITIONALHOSTS: ${MEDIAMTX_WEBRTC_ADDITIONAL_HOSTS}
MTX_AUTHHTTPADDRESS: ${MEDIAMTX_AUTH_HTTP_ADDRESS}
labels:
- traefik.enable=true
- traefik.http.routers.mediamtx-hls.rule=Host(`${MEDIAMTX_HLS_HOST}`)
- traefik.http.routers.mediamtx-hls.entrypoints=websecure
- traefik.http.routers.mediamtx-hls.tls.certresolver=letsencrypt
- traefik.http.routers.mediamtx-hls.service=mediamtx-hls
- traefik.http.services.mediamtx-hls.loadbalancer.server.port=8888
- traefik.http.routers.mediamtx-webrtc.rule=Host(`${MEDIAMTX_WEBRTC_HOST}`)
- traefik.http.routers.mediamtx-webrtc.entrypoints=websecure
- traefik.http.routers.mediamtx-webrtc.tls.certresolver=letsencrypt
- traefik.http.routers.mediamtx-webrtc.service=mediamtx-webrtc
- traefik.http.services.mediamtx-webrtc.loadbalancer.server.port=8889
- traefik.http.routers.mediamtx-api.rule=Host(`${MEDIAMTX_API_HOST}`)
- traefik.http.routers.mediamtx-api.entrypoints=websecure
- traefik.http.routers.mediamtx-api.tls.certresolver=letsencrypt
- traefik.http.routers.mediamtx-api.service=mediamtx-api
- traefik.http.services.mediamtx-api.loadbalancer.server.port=9997
- traefik.udp.routers.mediamtx-srt.entrypoints=srt
- traefik.udp.routers.mediamtx-srt.service=mediamtx-srt
- traefik.udp.services.mediamtx-srt.loadbalancer.server.port=8890
- traefik.udp.routers.mediamtx-webrtc-ice.entrypoints=webrtc-ice
- traefik.udp.routers.mediamtx-webrtc-ice.service=mediamtx-webrtc-ice
- traefik.udp.services.mediamtx-webrtc-ice.loadbalancer.server.port=8189
restart: unless-stopped
volumes:
letsencrypt:

View File

@@ -0,0 +1,26 @@
paths:
all:
source: publisher
srt: yes
srtAddress: :8890
hls: yes
hlsVariant: lowLatency
hlsSegmentDuration: 2s
hlsPartDuration: 1s
hlsSegmentCount: 10
webrtc: yes
webrtcAddress: :8889
webrtcLocalUDPAddress: :8189
webrtcAdditionalHosts: []
authMethod: http
authHTTPAddress: https://hackclub.tv/api/mediamtx/publish
api: yes
apiAddress: :9997
metrics: yes
metricsAddress: :9998