feat: add Windows-first deployment flow

This commit is contained in:
admin committed 2026-08-20 10:21:44 +08:00
1 parent 7353d140b8
commit da01967c2e
4 files changed
+173 -28

No files matched your search

+63 -14
View File
@@ -1,20 +1,69 @@
# Deployment
All runtime paths are resolved relative to the repository by default and can be overridden with environment variables.
The maintained deployment target for this project is Windows first. Linux and macOS can still run services manually, but real end-to-end deployment testing should use the PowerShell flow below.
- `LS_DATABASE_URL`: SQLAlchemy database URL.
- `LS_REPLAY_MEDIA_DIR`: server-local directory containing replay files.
Runtime paths are resolved relative to the repository by default and can be overridden with environment variables.
- LS_DATABASE_URL: SQLAlchemy database URL.
- LS_REPLAY_MEDIA_DIR: server-local directory containing replay files. When started by start.ps1, the default is runtime\replay under the repository.
- LS_HLS_RUNTIME_DIR: backend and FFmpeg HLS output directory. When started by start.ps1, the default is runtime\hls under the repository.
## Windows deployment
Run these commands from PowerShell on the Windows node.
### 1. Install prerequisites
Required on PATH: Python 3.11+, Node.js LTS with npm, FFmpeg, and MediaMTX unless starting with -NoMediaMTX. Optional for GPU verification: NVIDIA driver and nvidia-smi.
### 2. Install application dependencies
```powershell
powershell -ExecutionPolicy Bypass -File .\start.ps1 -InstallDeps -NoMediaMTX
```
### 3. Start the full Windows stack
```powershell
powershell -ExecutionPolicy Bypass -File .\start.ps1
```
This starts MediaMTX origin, FastAPI backend on 0.0.0.0:8080, Vite frontend on 0.0.0.0:5173, HLS output under runtime\hls, and logs under runtime\logs.
Open backend docs at http://127.0.0.1:8080/docs and frontend UI at http://127.0.0.1:5173/. Publish RTMP to rtmp://127.0.0.1:1935/live/<stream_key>. MediaMTX HLS is http://127.0.0.1:8888/<stream_key>/index.m3u8.
### 4. Useful Windows variants
```powershell
powershell -ExecutionPolicy Bypass -File .\start.ps1 -NoMediaMTX
powershell -ExecutionPolicy Bypass -File .\start.ps1 -NoFrontend
powershell -ExecutionPolicy Bypass -File .\start.ps1 -HostAddress 0.0.0.0 -BackendPort 8080 -FrontendPort 5173
```
### 5. Smoke test commands
```powershell
curl.exe -I http://127.0.0.1:5173/
curl.exe http://127.0.0.1:8080/api/health
ffmpeg -version
mediamtx --version
nvidia-smi
```
If browser or network tests run from another machine, open the Windows firewall for ports 5173, 8080, 1935, and 8888.
## Edge node scripts
```cmd
mediamtx\scripts\start_origin.bat
mediamtx\scripts\start_edge.bat 1 127.0.0.1
mediamtx\scripts\check_multicast.bat
```
## Replay file safety
Replay records store paths relative to LS_REPLAY_MEDIA_DIR. Absolute paths and parent-directory traversal are rejected.
## Linux and macOS
Run `./start.sh`. The script resolves its own directory and does not depend on `/workspace`.
## Windows
1. Create a virtual environment with `py -m venv backend\.venv`.
2. Install dependencies with `backend\.venv\Scripts\pip install -r backend\requirements.txt`.
3. Optionally set `LS_REPLAY_MEDIA_DIR` to an absolute media directory.
4. Run `powershell -ExecutionPolicy Bypass -File .\start.ps1`.
5. In another terminal, run `npm install` and `npm run dev` from `frontend`.
Replay records store paths relative to `LS_REPLAY_MEDIA_DIR`. Absolute paths and parent-directory traversal are rejected.
Linux and macOS are not the primary deployment path. For local development, run the backend with uvicorn from backend, run the frontend with npm run dev from frontend, and set LS_HLS_RUNTIME_DIR to a writable directory.
+1 -1
View File
@@ -93,7 +93,7 @@ async def health():
@app.get("/hls/{path:path}")
async def serve_hls(path: str):
hls_root = "/tmp/hls"
hls_root = settings.hls_runtime_dir
fs_path = os.path.join(hls_root, path)
if os.path.isfile(fs_path):
return FileResponse(fs_path)
+3 -3
View File
@@ -24,7 +24,7 @@ class TranscodeManager:
audio_bitrate = quality.get("audio_bitrate", "128k")
keyframe_seconds = quality.get("keyframe_seconds", segment_seconds)
keyframe_frames = keyframe_seconds * fps
hls_dir = f"/tmp/hls/{stream_key}/{label}"
hls_dir = os.path.join(settings.hls_runtime_dir, stream_key, label)
os.makedirs(hls_dir, exist_ok=True)
cmd = [
"ffmpeg", "-y",
@@ -42,8 +42,8 @@ class TranscodeManager:
"-hls_time", str(segment_seconds),
"-hls_list_size", "6",
"-hls_flags", "delete_segments+independent_segments+program_date_time",
"-hls_segment_filename", f"{hls_dir}/segment_%03d.ts",
f"{hls_dir}/index.m3u8",
"-hls_segment_filename", os.path.join(hls_dir, "segment_%03d.ts"),
os.path.join(hls_dir, "index.m3u8"),
]
try:
proc = subprocess.Popen(
+106 -10
View File
@@ -1,15 +1,111 @@
param(
[switch]$NoFrontend,
[switch]$NoMediaMTX,
[switch]$InstallDeps,
[string]$HostAddress = "0.0.0.0",
[int]$BackendPort = 8080,
[int]$FrontendPort = 5173
)
$ErrorActionPreference = "Stop"
$Base = Split-Path -Parent $MyInvocation.MyCommand.Path
$BackendDir = Join-Path $Base "backend"
$FrontendDir = Join-Path $Base "frontend"
$MediaDir = Join-Path $Base "mediamtx"
$RuntimeDir = Join-Path $Base "runtime"
$HlsDir = Join-Path $RuntimeDir "hls"
$ReplayDir = Join-Path $RuntimeDir "replay"
$LogDir = Join-Path $RuntimeDir "logs"
Write-Host "[StreamCore] Starting backend..."
$Python = Join-Path $Base "backend\.venv\Scripts\python.exe"
if (-not (Test-Path $Python)) {
$Python = Join-Path $Base "backend\venv\Scripts\python.exe"
}
if (-not (Test-Path $Python)) {
throw "Python virtual environment not found. Create backend\.venv and install backend\requirements.txt."
New-Item -ItemType Directory -Force -Path $RuntimeDir, $HlsDir, $ReplayDir, $LogDir | Out-Null
if (-not $env:LS_HLS_RUNTIME_DIR) { $env:LS_HLS_RUNTIME_DIR = $HlsDir }
if (-not $env:LS_REPLAY_MEDIA_DIR) { $env:LS_REPLAY_MEDIA_DIR = $ReplayDir }
function Resolve-Python {
$candidates = @(
(Join-Path $BackendDir ".venv\Scripts\python.exe"),
(Join-Path $BackendDir "venv\Scripts\python.exe"),
"python",
"py"
)
foreach ($candidate in $candidates) {
if (Test-Path $candidate) { return $candidate }
$cmd = Get-Command $candidate -ErrorAction SilentlyContinue
if ($cmd) { return $cmd.Source }
}
throw "Python not found. Install Python 3.11+ or create backend\.venv."
}
Start-Process -FilePath $Python -ArgumentList "run.py" -WorkingDirectory (Join-Path $Base "backend")
Write-Host "[StreamCore] Backend started on http://127.0.0.1:8080"
Write-Host "Run npm install and npm run dev in frontend for the development UI."
function Assert-Command($Name, $Hint) {
if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
throw "$Name not found. $Hint"
}
}
$Python = Resolve-Python
Assert-Command "npm" "Install Node.js LTS and make npm available in PATH."
Assert-Command "ffmpeg" "Install FFmpeg and make ffmpeg.exe available in PATH."
if (-not $NoMediaMTX) {
Assert-Command "mediamtx" "Install MediaMTX and make mediamtx.exe available in PATH, or run with -NoMediaMTX."
}
if ($InstallDeps) {
Write-Host "[StreamCore] Installing backend dependencies..."
Push-Location $BackendDir
& $Python -m pip install -r requirements.txt
Pop-Location
if (-not $NoFrontend) {
Write-Host "[StreamCore] Installing frontend dependencies..."
Push-Location $FrontendDir
npm install
Pop-Location
}
}
$processes = @()
if (-not $NoMediaMTX) {
Write-Host "[StreamCore] Starting MediaMTX origin on RTMP :1935 and HLS :8888..."
$processes += Start-Process -FilePath "mediamtx" -ArgumentList "mediamtx.yml" -WorkingDirectory $MediaDir -PassThru -RedirectStandardOutput (Join-Path $LogDir "mediamtx.out.log") -RedirectStandardError (Join-Path $LogDir "mediamtx.err.log")
}
Write-Host "[StreamCore] Starting backend on ${HostAddress}:${BackendPort} ..."
$backendArgs = @("-m", "uvicorn", "main:app", "--host", $HostAddress, "--port", "$BackendPort")
$processes += Start-Process -FilePath $Python -ArgumentList $backendArgs -WorkingDirectory $BackendDir -PassThru -RedirectStandardOutput (Join-Path $LogDir "backend.out.log") -RedirectStandardError (Join-Path $LogDir "backend.err.log")
if (-not $NoFrontend) {
Write-Host "[StreamCore] Starting frontend on ${HostAddress}:${FrontendPort} ..."
$frontendArgs = @("run", "dev", "--", "--host", $HostAddress, "--port", "$FrontendPort")
$processes += Start-Process -FilePath "npm" -ArgumentList $frontendArgs -WorkingDirectory $FrontendDir -PassThru -RedirectStandardOutput (Join-Path $LogDir "frontend.out.log") -RedirectStandardError (Join-Path $LogDir "frontend.err.log")
}
Start-Sleep -Seconds 3
Write-Host ""
Write-Host "[StreamCore] Windows deployment started."
Write-Host " Backend: http://127.0.0.1:$BackendPort/docs"
if (-not $NoFrontend) { Write-Host " Frontend: http://127.0.0.1:$FrontendPort/" }
if (-not $NoMediaMTX) {
Write-Host " RTMP: rtmp://127.0.0.1:1935/live/<stream_key>"
Write-Host " HLS: http://127.0.0.1:8888/<stream_key>/index.m3u8"
}
Write-Host " Runtime: $RuntimeDir"
Write-Host " HLS dir: $env:LS_HLS_RUNTIME_DIR"
Write-Host " Logs: $LogDir"
Write-Host ""
Write-Host "Press Ctrl+C to stop child processes."
try {
while ($true) {
foreach ($proc in $processes) {
if ($proc.HasExited) {
throw "Process $($proc.Id) exited with code $($proc.ExitCode). Check logs in $LogDir."
}
}
Start-Sleep -Seconds 2
}
} finally {
Write-Host "[StreamCore] Stopping child processes..."
foreach ($proc in $processes) {
if (-not $proc.HasExited) { Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue }
}
}