Refactor bootstrap for multi-server support and add srs-develop skill. v7.0.142 (#4657)

Summary
- Extract proxy bootstrap implementation from bootstrap.go into
internal/bootstrap/proxy.go, keeping only the Bootstrap interface in the
shared file. This prepares for origin/edge servers
  to have their own bootstrap implementations.
- Rename NewBootstrap() → NewProxyBootstrap() to follow the explicit
factory naming convention.
- Rebrand signature from SRSProxy to SRSX and update logger context key
accordingly.
- Add srs-develop skill with task router, module routing workflow, proxy
unit test script, and RTMP E2E test script.
- Remove st-develop skill (superseded by srs-develop).
- Add srs-support eval #21 for HLS AnnexB decode error scenario.
Test plan
- go build ./cmd/proxy/... compiles successfully
  - go test ./cmd/... ./internal/... passes
- E2E RTMP proxy test (proxy-e2e-test.sh) passes
  - Verify proxy starts and logs SRSX-Proxy/<version> started

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Winlin 2026-04-06 08:28:00 -04:00 committed by GitHub
parent 5f47cee19c
commit 7c17c93b70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 564 additions and 247 deletions

View File

@ -1,6 +1,9 @@
{
"permissions": {
"allow": [
"Read",
"Glob",
"Grep",
"Bash(find:*)",
"Bash(ls:*)",
"Bash(cat:*)",
@ -19,9 +22,8 @@
"Bash(realpath:*)",
"Bash(dirname:*)",
"Bash(basename:*)",
"Read",
"Glob",
"Grep"
"Bash(echo \"PID=$!\")",
"Bash(make)"
]
},
"hooks": {

View File

@ -10,3 +10,4 @@
/personal*
/support*
/*srs-consults*
/memory/202*.md

View File

@ -8,7 +8,7 @@ This file helps AI navigate the SRS project. Instead of grepping blindly, AI sho
SRS has three codebases:
### C++ Media Server
### C++ Media Server Code
The C++ media server (`trunk/src/`) serves as both origin server and edge server. It uses the ST (State Threads) coroutine library and is the full-featured server with all SRS capabilities.
@ -192,7 +192,7 @@ Config (`trunk/conf/`) is only for the C++ media server. The next-generation Go
- `full.conf` — Full reference config with all options documented
- Feature-specific configs: `rtc.conf`, `srt.conf`, `hls.conf`, `dvr.*.conf`, `edge.conf`, `forward.*.conf`, `gb28181.conf`, `dash.conf`, etc.
### State Threads
### State Threads Code
State Threads (`trunk/3rdparty/st-srs/`) is the coroutine library used by the C++ media server only. The Go server does not use ST.
@ -207,7 +207,7 @@ State Threads (`trunk/3rdparty/st-srs/`) is the coroutine library used by the C+
- `md.h` — Platform detection and context switch macros
- `md_linux.S`, `md_linux2.S`, `md_darwin.S`, `md_cygwin64.S` — Assembly context switch per platform
### Next-Generation Server
### Next-Generation Server Code
The next-generation server (`cmd/` + `internal/`) is written in Go and maintained by AI. It is the future of SRS. Currently it only supports proxy, but work is ongoing to support the full feature set including origin, edge, and proxy servers.
@ -247,9 +247,13 @@ The knowledge base (`memory/srs-*.md`) captures William's knowledge about SRS
## Documentation Structure
### Tracking Docs
**Tracking** — Project changelog and version history:
- `trunk/doc/CHANGELOG.md` — Full changelog of all SRS versions, one entry per merged PR with version bump
### C++ Media Server Docs
**C++ Media Server Docs** (`trunk/3rdparty/srs-docs/doc/`) — User-facing documentation:
- `introduction.md` — SRS overview: what it is, supported protocols (RTMP/WebRTC/HLS/SRT/etc.), features list, ST coroutine architecture, and learning path
- `getting-started.md` — Quick start with Docker: live streaming (RTMP publish, HTTP-FLV/HLS play), WebRTC publish/play, RTMP-to-WebRTC conversion, HTTPS for WebRTC on remote servers, SRT publishing, and multiple streams with flexible URL patterns
@ -288,6 +292,8 @@ The knowledge base (`memory/srs-*.md`) captures William's knowledge about SRS
- `product-en.md` — Release history: milestones from v1.0 (2013) to v7.0 (2025+), codenames, key achievements per release, AI contributor era (2025+)
- `security-advisories-en.md` — CVE reports: CVE-2024-29882 (JSONP XSS), CVE-2023-34105 (command injection in demo api-server), with patch versions and links
### Next-Generation Server Docs
**Next-Generation Server Docs** (`docs/proxy/`) — Documentation for the Go server:
- `proxy-design.md` — Architecture: stateless proxy with built-in load balancing, single-proxy vs multi-proxy (Redis) deployment modes, horizontal scaling behind NLB
- `proxy-protocol.md` — Backend registration protocol: default backend for debugging, automatic registration via SRS system API, heartbeat mechanism, env vars

View File

@ -0,0 +1,126 @@
---
name: srs-develop
description: Develop, modify, debug, and maintain the next-generation SRS media server written in Go — including the proxy, origin, and edge servers. This is the AI-maintained successor to the first-generation C++ SRS server. Use for all development tasks, for example, adding features, fixing bugs, refactoring code, understanding code architecture, reviewing changes, and writing tests for the Go codebase. NOT for end-user support, usage questions, configuration help, or learning how to use SRS — use the srs-support skill for those. Only activate when the task is explicitly about developing or modifying the Go SRS codebase.
---
# SRS Development
## Core Principle
**Code and documents are the only truth.** Issue descriptions may be inaccurate. Pull requests may be misleading. Feature descriptions may be insufficient. Always ground your understanding in the actual source code and project documentation. Documents capture design intent, architecture rationale, and complex background that code alone cannot express — they are another form of code. When code and documents conflict, investigate rather than assume one is wrong.
---
## Task Router
⚠️ **MANDATORY — Always execute this step first.** Never skip the Task Router. Never jump directly to a task. Every request must be routed through this table before any work begins.
Route the user's request to exactly ONE task type. Follow that task only. Do not combine tasks.
| Task | When | Route To | Status |
|---|---|---|---|
| **Develop Code** | User wants to add, modify, refactor code, or update docs — any planned change | → [Develop Code](#task-develop-code) | ✅ Supported |
| **Fix a Bug** | User reports something broken, unexpected behavior, or an error | → [Fix a Bug](#task-fix-a-bug) | ❌ Not yet supported |
| **Learn Code** | User wants to understand how code works — no changes intended | → [Learn Code](#task-learn-code) | ❌ Not yet supported |
| **Review a PR** | User wants to review an existing pull request | → [Review a PR](#task-review-a-pr) | ❌ Not yet supported |
**If the routed task is not yet supported**, stop and tell the user:
- What task type you routed to
- That this task type is not supported yet
- That support will be added in the future
Do NOT attempt unsupported tasks.
---
## Task: Fix a Bug
**Prerequisite:** You must arrive here via the [Task Router](#task-router). Do not execute this task directly — always complete the Task Router first to confirm this is the correct task type.
**Not yet supported.** Will be added in a future update.
---
## Task: Learn Code
**Prerequisite:** You must arrive here via the [Task Router](#task-router). Do not execute this task directly — always complete the Task Router first to confirm this is the correct task type.
**Not yet supported.** Will be added in a future update.
---
## Task: Review a PR
**Prerequisite:** You must arrive here via the [Task Router](#task-router). Do not execute this task directly — always complete the Task Router first to confirm this is the correct task type.
**Not yet supported.** Will be added in a future update.
---
## Task: Develop Code
**Prerequisite:** You must arrive here via the [Task Router](#task-router). Do not execute this task directly — always complete the Task Router first to confirm this is the correct task type.
**Scope:** This task covers any planned code or documentation change — adding new features, modifying existing functionality, refactoring code, and updating documentation.
**Important:** The C++ media server (origin + edge) is in **maintenance mode** — only bug fixes are accepted, no new features. All new feature development happens in the **next-generation Go server**. You may reference the C++ server's code to understand how things were done before, but do not add features to it.
**Service Router** — Determine which Go service the feature targets. Route to exactly ONE service. Do not guess — if unclear, ask the user to clarify.
| Service | Route To | Status |
|---|---|---|
| **Proxy server** | → [Proxy Server](#proxy-server) | ✅ Supported |
| **Origin server** | → [Origin Server](#origin-server) | ✅ Supported |
| **Edge server** | → [Edge Server](#edge-server) | ❌ Not yet supported |
**If the routed service is not yet supported**, stop and tell the user:
- What service you routed to
- That this service is not supported yet
### Proxy Server
The proxy server is a complex, growing product — not a small app. It has many modules, and more will be added over time. You cannot load all the code into context at once. The key to working on it is **routing to the correct module first**.
**Step 1: Module Routing (MANDATORY)**
1. Read the codebase map: `memory/srs-codebase-map.md` — both the **Next-Generation Server Code** section (code modules: `cmd/` + `internal/`) and the **Next-Generation Server Docs** section (documentation: `docs/proxy/`).
2. Study the module descriptions and doc descriptions. Understand what each covers and its boundaries.
3. Reason about which module(s) and which doc(s) are relevant to the user's request. Consider:
- Which module owns the functionality being changed?
- Which modules might be affected as dependencies?
- Which docs cover the design/architecture of this area?
- Is this a new module or a change to an existing one?
4. **Present your reasoning to the user — both the module(s) and doc(s) you identified — and ask for confirmation.** Even if you are confident, you MUST ask. Do not proceed without confirmation.
5. If you are unsure, stop and ask the user to clarify. Do not guess.
Only after the user confirms the routing do you proceed to Step 2.
**Step 2: Understand the Module**
1. **Read the confirmed docs** (if any were identified) — understand the design intent, architecture rationale, and how the module is organized internally. This is the *why*.
2. **Based on doc understanding, identify the specific file(s)** within the module that are relevant to the feature. Not the whole module — only the files that matter.
3. **Read only those specific files.** Code gives you the implementation details: function signatures, patterns, conventions, edge cases. This is the *how*.
4. If no relevant docs exist, scan the module directory listing (filenames only) to locate the right files, then read them.
**Step 3: Implement and Verify**
1. Implement the code change.
2. Run the proxy unit tests to verify:
```
bash scripts/proxy-utest.sh
```
3. Run the proxy E2E test (starts proxy + SRS origin, publishes RTMP, verifies playback):
```
bash scripts/proxy-e2e-test.sh
```
4. If any tests fail, fix the issues and re-run until all tests pass.
All script paths are relative to this skill's directory.
### Origin Server
*(workflow steps to be defined)*
### Edge Server
**Not yet supported.** Will be added in a future update.

View File

@ -0,0 +1,184 @@
#!/bin/bash
# E2E test for RTMP proxy: starts proxy + SRS origin, publishes an RTMP stream,
# verifies playback via ffprobe, then cleans up all processes.
set -e
SCRIPT_DIR="$(cd -P "$(dirname "$0")" && pwd)"
# Navigate: scripts/ -> srs-develop/ -> skills/ -> .openclaw/ -> srs
WORKSPACE="$(cd -P "$SCRIPT_DIR/../../../.." && pwd)"
if [[ ! -f "$WORKSPACE/go.mod" ]]; then
echo "Error: go.mod not found in WORKSPACE: $WORKSPACE" >&2
exit 1
fi
# Ports — use high ports to avoid conflicts with running services.
# The proxy starts ALL servers, so we must assign unique ports for each.
PROXY_RTMP_PORT=11935
PROXY_HTTP_API_PORT=11985
PROXY_HTTP_SERVER_PORT=18080
PROXY_WEBRTC_PORT=18000
PROXY_SRT_PORT=20080
PROXY_SYSTEM_API_PORT=12025
SOURCE_FLV="$WORKSPACE/trunk/doc/source.flv"
SRS_BINARY="$WORKSPACE/trunk/objs/srs"
ORIGIN_CONF="$WORKSPACE/trunk/conf/origin1-for-proxy.conf"
# PIDs to clean up on exit.
PROXY_PID=""
ORIGIN_PID=""
FFMPEG_PID=""
cleanup() {
echo ""
echo "=== Cleaning up ==="
for pid in $PROXY_PID $ORIGIN_PID $FFMPEG_PID; do
if kill -0 "$pid" 2>/dev/null; then
kill "$pid" 2>/dev/null || true
fi
done
sleep 1
for pid in $PROXY_PID $ORIGIN_PID $FFMPEG_PID; do
if kill -0 "$pid" 2>/dev/null; then
kill -9 "$pid" 2>/dev/null || true
fi
done
echo "Cleanup done."
}
trap cleanup EXIT
echo "=== E2E RTMP Proxy Test ==="
echo "Workspace: $WORKSPACE"
echo ""
# --- Pre-checks ---
if [[ ! -f "$SOURCE_FLV" ]]; then
echo "Error: test source not found: $SOURCE_FLV" >&2
exit 1
fi
if ! command -v ffmpeg &>/dev/null; then
echo "Error: ffmpeg not found in PATH" >&2
exit 1
fi
if ! command -v ffprobe &>/dev/null; then
echo "Error: ffprobe not found in PATH" >&2
exit 1
fi
# Origin ports (from origin1-for-proxy.conf).
ORIGIN_RTMP_PORT=19351
ORIGIN_HTTP_PORT=8081
ORIGIN_API_PORT=19851
ORIGIN_RTC_PORT=8001
ORIGIN_SRT_PORT=10081
# --- Step 0: Clean up stale state ---
# Remove stale SRS PID file that prevents restart.
rm -f "$WORKSPACE/trunk/objs/origin1.pid"
# Kill any leftover processes on our ports (proxy + origin).
ALL_PORTS="$PROXY_RTMP_PORT $PROXY_HTTP_API_PORT $PROXY_HTTP_SERVER_PORT $PROXY_WEBRTC_PORT $PROXY_SRT_PORT $PROXY_SYSTEM_API_PORT $ORIGIN_RTMP_PORT $ORIGIN_HTTP_PORT $ORIGIN_API_PORT $ORIGIN_RTC_PORT $ORIGIN_SRT_PORT"
for port in $ALL_PORTS; do
lsof -ti :"$port" 2>/dev/null | xargs kill 2>/dev/null || true
done
sleep 1
# --- Step 1: Build proxy ---
echo "=== Step 1: Building proxy ==="
cd "$WORKSPACE"
make -s 2>&1
echo "Proxy built: $WORKSPACE/srs-proxy"
# --- Step 2: Build SRS origin (if not already built) ---
if [[ ! -f "$SRS_BINARY" ]]; then
echo "=== Step 2: Building SRS origin ==="
cd "$WORKSPACE/trunk"
./configure && make 2>&1 | tail -3
echo "SRS origin built: $SRS_BINARY"
else
echo "=== Step 2: SRS origin already built ==="
fi
# --- Step 3: Start proxy ---
echo "=== Step 3: Starting proxy (RTMP :$PROXY_RTMP_PORT, System API :$PROXY_SYSTEM_API_PORT) ==="
cd "$WORKSPACE"
env PROXY_RTMP_SERVER=$PROXY_RTMP_PORT \
PROXY_HTTP_API=$PROXY_HTTP_API_PORT \
PROXY_HTTP_SERVER=$PROXY_HTTP_SERVER_PORT \
PROXY_WEBRTC_SERVER=$PROXY_WEBRTC_PORT \
PROXY_SRT_SERVER=$PROXY_SRT_PORT \
PROXY_SYSTEM_API=$PROXY_SYSTEM_API_PORT \
PROXY_LOAD_BALANCER_TYPE=memory \
./srs-proxy >/tmp/srs-proxy-e2e.log 2>&1 &
PROXY_PID=$!
echo "Proxy PID: $PROXY_PID"
sleep 1
if ! kill -0 "$PROXY_PID" 2>/dev/null; then
echo "Error: proxy failed to start. Logs:" >&2
cat /tmp/srs-proxy-e2e.log >&2
exit 1
fi
echo "Proxy started."
# --- Step 4: Start SRS origin ---
echo "=== Step 4: Starting SRS origin ==="
ulimit -n 10000 2>/dev/null || true
cd "$WORKSPACE/trunk"
./objs/srs -c conf/origin1-for-proxy.conf >/tmp/srs-origin-e2e.log 2>&1 &
ORIGIN_PID=$!
echo "SRS origin PID: $ORIGIN_PID"
# Wait for SRS to start and register with proxy (heartbeat interval is 9s).
echo "Waiting for SRS origin to register with proxy (up to 15s)..."
sleep 12
if ! kill -0 "$ORIGIN_PID" 2>/dev/null; then
echo "Error: SRS origin failed to start. Logs:" >&2
cat /tmp/srs-origin-e2e.log >&2
exit 1
fi
echo "SRS origin started and registered."
# --- Step 5: Publish RTMP stream ---
echo "=== Step 5: Publishing RTMP stream to proxy ==="
ffmpeg -stream_loop -1 -re -i "$SOURCE_FLV" -c copy -f flv \
"rtmp://localhost:$PROXY_RTMP_PORT/live/livestream" >/tmp/srs-ffmpeg-e2e.log 2>&1 &
FFMPEG_PID=$!
echo "FFmpeg publisher PID: $FFMPEG_PID"
# Wait for stream to stabilize.
sleep 5
if ! kill -0 "$FFMPEG_PID" 2>/dev/null; then
echo "Error: FFmpeg publisher failed. Logs:" >&2
cat /tmp/srs-ffmpeg-e2e.log >&2
exit 1
fi
echo "Stream publishing."
# --- Step 6: Verify RTMP playback ---
echo "=== Step 6: Verifying RTMP playback via proxy ==="
PROBE_OUTPUT=$(ffprobe -v error -show_streams \
"rtmp://localhost:$PROXY_RTMP_PORT/live/livestream" 2>&1 || true)
if echo "$PROBE_OUTPUT" | grep -q "codec_type=video"; then
echo "PASS: Video stream detected."
else
echo "FAIL: No video stream detected." >&2
echo "ffprobe output:" >&2
echo "$PROBE_OUTPUT" >&2
exit 1
fi
if echo "$PROBE_OUTPUT" | grep -q "codec_type=audio"; then
echo "PASS: Audio stream detected."
else
echo "FAIL: No audio stream detected." >&2
echo "ffprobe output:" >&2
echo "$PROBE_OUTPUT" >&2
exit 1
fi
echo ""
echo "=== E2E RTMP Proxy Test PASSED ==="

View File

@ -0,0 +1,17 @@
#!/bin/bash
# Run unit tests for the proxy server (cmd/ and internal/ packages).
set -e
SCRIPT_DIR="$(cd -P "$(dirname "$0")" && pwd)"
# Navigate: scripts/ -> srs-develop/ -> skills/ -> .openclaw/ -> srs
WORKSPACE="$(cd -P "$SCRIPT_DIR/../../../.." && pwd)"
if [[ ! -f "$WORKSPACE/go.mod" ]]; then
echo "Error: go.mod not found in WORKSPACE: $WORKSPACE" >&2
exit 1
fi
cd "$WORKSPACE"
echo "Running proxy unit tests in: $WORKSPACE"
go test ./cmd/... ./internal/... -v

View File

@ -838,6 +838,64 @@
"type": "absence"
}
]
},
{
"id": 21,
"prompt": "I'm getting this error from SRS: [ERROR][1][48960q0o][4] serve error code=3001(HlsDecode)... avc demux annexb : not annexb. My camera pushes RTMP to SRS and I play via HLS. What causes this and how do I fix it?",
"expected_output": "Should explain the error comes from H.264 AnnexB format detection in the HLS muxing path, caused by the camera encoder producing malformed frames. Should NOT blame network packet loss — RTMP uses TCP which guarantees delivery. Should suggest camera firmware update, FFmpeg relay, or camera restart.",
"files": [],
"assertions": [
{
"name": "annexb_format_detection",
"description": "Answer explains SRS detects AnnexB format (start code 00 00 00 01 or 00 00 01) on the first H.264 frame and locks in that format for subsequent frames",
"type": "contains_concept"
},
{
"name": "annexb_vs_ibmf_both_supported",
"description": "Answer mentions SRS supports both AnnexB and IBMF (AVCC) formats, auto-detecting from the first frame",
"type": "contains_concept"
},
{
"name": "camera_encoder_is_root_cause",
"description": "Answer identifies the camera encoder as the root cause — it produces a frame that doesn't conform to the AnnexB format after running for some time",
"type": "contains_concept"
},
{
"name": "hls_muxing_path",
"description": "Answer explains the error surfaces during HLS segment generation (error code HlsDecode/3001), when SRS demuxes H.264 data to remux into TS segments",
"type": "contains_concept"
},
{
"name": "not_network_packet_loss",
"description": "Answer does NOT blame network packet loss or TCP packet loss as a cause — RTMP runs over TCP which guarantees ordered, complete delivery. If TCP fails, the connection drops entirely rather than delivering corrupted data",
"type": "absence"
},
{
"name": "not_srs_bug",
"description": "Answer clarifies this is not an SRS bug — SRS correctly detects and reports the malformed data",
"type": "contains_concept"
},
{
"name": "fix_camera_firmware",
"description": "Answer suggests upgrading camera firmware as the most direct fix",
"type": "contains_concept"
},
{
"name": "fix_ffmpeg_relay",
"description": "Answer suggests using FFmpeg as a relay between camera and SRS to repackage the stream",
"type": "contains_concept"
},
{
"name": "fix_camera_restart",
"description": "Answer suggests periodic camera restart as a workaround if firmware can't be updated",
"type": "contains_concept"
},
{
"name": "no_hallucination",
"description": "Answer does not hallucinate SRS config options to fix this, does not claim network issues cause this error, and does not invent workarounds that don't exist",
"type": "absence"
}
]
}
]
}

View File

@ -1,66 +0,0 @@
---
name: st-develop
description: Anything related to coroutines, State Threads (ST), or SRS's concurrency model. Use when discussing coroutine concepts, updating coroutine knowledge (srs-coroutines.md), developing/debugging/porting ST source code, porting ST to new CPU architectures or OSes, debugging coroutine context switching, analyzing ST scheduler behavior, adding new platform assembly, fixing ASAN/Valgrind/SEH issues, or understanding ST internals (sched, stk, sync, key, io, event, context switch ASM).
---
# ST Development
State Threads (ST) is a C coroutine library.
## Setup
All files are in the current working directory. Find everything from here — no discovery logic needed.
Available directories: `trunk/`, `cmd/`, `internal/`, `cmake/`, `docs/`, `memory/`
All AI tools — OpenClaw, Codex, Claude Code, Kiro CLI — see the same relative paths.
## Load Knowledge Base (MANDATORY)
Before any ST work, use the `read` tool to load the knowledge base. Do NOT use memory_search — read the full file directly.
- `memory/srs-coroutines.md`
- `memory/srs-codebase-map.md` — Load this when you need to find or navigate source files. Always use the codebase map to reason about which files are relevant before searching. Never grep from the repository root blindly.
## Loading ST Source Code (ON REQUEST)
When the user asks to load the ST codebase (or needs you to work directly with the source), load **ALL** ST source files — no partial loads.
All under `trunk/3rdparty/st-srs/`:
Headers: `public.h`, `common.h`, `md.h`
Core C: `sched.c`, `stk.c`, `sync.c`, `key.c`, `io.c`, `event.c`, `common.c`
Platform ASM: `md_darwin.S`, `md_linux.S`, `md_linux2.S`, `md_cygwin64.S`
Build: `Makefile`
**Load every single file listed above — no shortcuts, no skipping.**
## Unit Tests (utest)
ST has a Google Test-based unit test suite in `trunk/3rdparty/st-srs/utest/`:
- `st_utest.cpp` / `st_utest.hpp` — Test main and shared helpers
- `st_utest_coroutines.cpp` — Coroutine tests (start, params, multiple coroutines, addition across yields)
- `st_utest_tcp.cpp` — TCP connection test
- `gtest-fit/` — Embedded Google Test framework
**Build targets** (in the ST Makefile):
- `darwin-debug-utest` — macOS debug build + utest
- `linux-debug-utest` — Linux debug build + utest
- `cygwin64-debug-utest` — Cygwin64 debug build + utest
Coverage variants: `darwin-debug-gcov`, `linux-debug-gcov` (adds `-fprofile-arcs -ftest-coverage`).
The build compiles ST as a static library first, then builds and links the utest binary at `obj/st_utest`.
## Verifying Changes
After any ST change (including utest-only changes), run the verifier script in this skill folder (not in the ST codebase):
- `scripts/verify.sh`
This script runs unit tests in `trunk/3rdparty/st-srs`.
Always run verification before considering a change complete.

View File

@ -1,26 +0,0 @@
#!/bin/bash
# Verify ST changes by building and running unit tests.
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# Navigate: scripts/ -> st-develop/ -> skills/ -> .openclaw/
WORKSPACE="$(cd "$SCRIPT_DIR/../../.." && pwd)"
ST_DIR="$WORKSPACE/trunk/3rdparty/st-srs"
if [[ ! -d "$ST_DIR" ]]; then
echo "Error: ST_DIR does not exist: $ST_DIR" >&2
exit 1
fi
echo "ST source: $ST_DIR"
CMAKE_DIR="$WORKSPACE/cmake"
BUILD_DIR="$WORKSPACE/cmake/build"
mkdir -p "$BUILD_DIR"
cd "$BUILD_DIR"
cmake .. -DCMAKE_BUILD_TYPE=Debug
cmake --build . --target st_utest
./st-build/st_utest

View File

@ -11,8 +11,9 @@ import (
)
func main() {
bs := bootstrap.NewBootstrap()
bs := bootstrap.NewProxyBootstrap()
if err := bs.Start(context.Background()); err != nil {
// Error already logged in bootstrap.Start().
os.Exit(-1)
}
}

9
cmd/proxy/main_test.go Normal file
View File

@ -0,0 +1,9 @@
// Copyright (c) 2025 Winlin
//
// SPDX-License-Identifier: MIT
package main
import "testing"
func TestExample(t *testing.T) {
}

View File

@ -65,13 +65,13 @@ Both commands should successfully detect the stream and display video/audio code
## Code Conventions
## Factory Functions
- Factory functions should use explicit interface names: `NewBootstrap()`, `NewMemoryLoadBalancer()`, etc.
- Factory functions should use explicit interface names: `NewProxyBootstrap()`, `NewMemoryLoadBalancer()`, etc.
- **Do not** use generic `New()` function names
- This improves code clarity and makes the constructed type explicit at the call site
- Example:
```go
// Good
bs := bootstrap.NewBootstrap()
bs := bootstrap.NewProxyBootstrap()
// Avoid
bs := bootstrap.New()

View File

@ -5,16 +5,6 @@ package bootstrap
import (
"context"
"time"
"srsx/internal/debug"
"srsx/internal/env"
"srsx/internal/errors"
"srsx/internal/lb"
"srsx/internal/logger"
"srsx/internal/protocol"
"srsx/internal/signal"
"srsx/internal/version"
)
// Bootstrap defines the interface for application bootstrap operations.
@ -22,137 +12,4 @@ type Bootstrap interface {
// Start initializes the context with logger and signal handlers, then runs the bootstrap.
// Returns any error encountered during startup.
Start(ctx context.Context) error
// Run initializes and starts all proxy servers and the load balancer.
// It blocks until the context is cancelled.
Run(ctx context.Context) error
}
// bootstrapImpl implements the Bootstrap interface.
type bootstrapImpl struct{}
// NewBootstrap creates a new Bootstrap instance.
func NewBootstrap() Bootstrap {
return &bootstrapImpl{}
}
// Start initializes the context with logger and signal handlers, then runs the bootstrap.
// Returns any error encountered during startup.
func (b *bootstrapImpl) Start(ctx context.Context) error {
ctx = logger.WithContext(ctx)
logger.Df(ctx, "%v/%v started", version.Signature(), version.Version())
// Install signals.
ctx, cancel := context.WithCancel(ctx)
signal.InstallSignals(ctx, cancel)
// Run the main loop, ignore the user cancel error.
err := b.Run(ctx)
if err != nil && ctx.Err() != context.Canceled {
logger.Ef(ctx, "main: %+v", err)
return err
}
logger.Df(ctx, "%v done", version.Signature())
return nil
}
// Run initializes and starts all proxy servers and the load balancer.
// It blocks until the context is cancelled.
func (b *bootstrapImpl) Run(ctx context.Context) error {
// Setup the environment variables.
environment, err := env.NewEnvironment(ctx)
if err != nil {
return errors.Wrapf(err, "create environment")
}
// When cancelled, the program is forced to exit due to a timeout. Normally, this doesn't occur
// because the main thread exits after the context is cancelled. However, sometimes the main thread
// may be blocked for some reason, so a forced exit is necessary to ensure the program terminates.
if err := signal.InstallForceQuit(ctx, environment); err != nil {
return errors.Wrapf(err, "install force quit")
}
// Start the Go pprof if enabled.
debug.HandleGoPprof(ctx, environment)
// Initialize the load balancer.
if err := b.initializeLoadBalancer(ctx, environment); err != nil {
return err
}
// Parse the gracefully quit timeout.
gracefulQuitTimeout, err := time.ParseDuration(environment.GraceQuitTimeout())
if err != nil {
return errors.Wrapf(err, "parse gracefully quit timeout")
}
// Start all servers and block until context is cancelled.
return b.startServers(ctx, environment, gracefulQuitTimeout)
}
// initializeLoadBalancer sets up the load balancer based on configuration.
func (b *bootstrapImpl) initializeLoadBalancer(ctx context.Context, environment env.Environment) error {
switch environment.LoadBalancerType() {
case "redis":
lb.SrsLoadBalancer = lb.NewRedisLoadBalancer(environment)
default:
lb.SrsLoadBalancer = lb.NewMemoryLoadBalancer(environment)
}
if err := lb.SrsLoadBalancer.Initialize(ctx); err != nil {
return errors.Wrapf(err, "initialize srs load balancer")
}
return nil
}
// startServers initializes and starts all protocol servers.
func (b *bootstrapImpl) startServers(ctx context.Context, environment env.Environment, gracefulQuitTimeout time.Duration) error {
// Start the RTMP server.
srsRTMPServer := protocol.NewSRSRTMPServer(environment)
if err := srsRTMPServer.Run(ctx); err != nil {
return errors.Wrapf(err, "rtmp server")
}
defer srsRTMPServer.Close()
// Start the WebRTC server.
srsWebRTCServer := protocol.NewSRSWebRTCServer(environment)
if err := srsWebRTCServer.Run(ctx); err != nil {
return errors.Wrapf(err, "rtc server")
}
defer srsWebRTCServer.Close()
// Start the HTTP API server.
srsHTTPAPIServer := protocol.NewSRSHTTPAPIServer(environment, gracefulQuitTimeout, srsWebRTCServer)
if err := srsHTTPAPIServer.Run(ctx); err != nil {
return errors.Wrapf(err, "http api server")
}
defer srsHTTPAPIServer.Close()
// Start the SRT server.
srsSRTServer := protocol.NewSRSSRTServer(environment)
if err := srsSRTServer.Run(ctx); err != nil {
return errors.Wrapf(err, "srt server")
}
defer srsSRTServer.Close()
// Start the System API server.
systemAPI := protocol.NewSystemAPI(environment, gracefulQuitTimeout)
if err := systemAPI.Run(ctx); err != nil {
return errors.Wrapf(err, "system api server")
}
defer systemAPI.Close()
// Start the HTTP web server.
srsHTTPStreamServer := protocol.NewSRSHTTPStreamServer(environment, gracefulQuitTimeout)
if err := srsHTTPStreamServer.Run(ctx); err != nil {
return errors.Wrapf(err, "http server")
}
defer srsHTTPStreamServer.Close()
// Wait for the main loop to quit.
<-ctx.Done()
return nil
}

147
internal/bootstrap/proxy.go Normal file
View File

@ -0,0 +1,147 @@
// Copyright (c) 2025 Winlin
//
// SPDX-License-Identifier: MIT
package bootstrap
import (
"context"
"time"
"srsx/internal/debug"
"srsx/internal/env"
"srsx/internal/errors"
"srsx/internal/lb"
"srsx/internal/logger"
"srsx/internal/protocol"
"srsx/internal/signal"
"srsx/internal/version"
)
// NewProxyBootstrap creates a new Bootstrap instance for the proxy server.
func NewProxyBootstrap() Bootstrap {
return &proxyBootstrap{}
}
// proxyBootstrap implements the Bootstrap interface for the proxy server.
type proxyBootstrap struct{}
// Start initializes the context with logger and signal handlers, then runs the bootstrap.
// Returns any error encountered during startup.
func (b *proxyBootstrap) Start(ctx context.Context) error {
ctx = logger.WithContext(ctx)
logger.Df(ctx, "%v-Proxy/%v started", version.Signature(), version.Version())
// Install signals.
ctx, cancel := context.WithCancel(ctx)
signal.InstallSignals(ctx, cancel)
// Run the main loop, ignore the user cancel error.
err := b.run(ctx)
if err != nil && ctx.Err() != context.Canceled {
logger.Ef(ctx, "main: %+v", err)
return err
}
logger.Df(ctx, "%v done", version.Signature())
return nil
}
// Run initializes and starts all proxy servers and the load balancer.
// It blocks until the context is cancelled.
func (b *proxyBootstrap) run(ctx context.Context) error {
// Setup the environment variables.
environment, err := env.NewEnvironment(ctx)
if err != nil {
return errors.Wrapf(err, "create environment")
}
// When cancelled, the program is forced to exit due to a timeout. Normally, this doesn't occur
// because the main thread exits after the context is cancelled. However, sometimes the main thread
// may be blocked for some reason, so a forced exit is necessary to ensure the program terminates.
if err := signal.InstallForceQuit(ctx, environment); err != nil {
return errors.Wrapf(err, "install force quit")
}
// Start the Go pprof if enabled.
debug.HandleGoPprof(ctx, environment)
// Initialize the load balancer.
if err := b.initializeLoadBalancer(ctx, environment); err != nil {
return err
}
// Parse the gracefully quit timeout.
gracefulQuitTimeout, err := time.ParseDuration(environment.GraceQuitTimeout())
if err != nil {
return errors.Wrapf(err, "parse gracefully quit timeout")
}
// Start all servers and block until context is cancelled.
return b.startServers(ctx, environment, gracefulQuitTimeout)
}
// initializeLoadBalancer sets up the load balancer based on configuration.
func (b *proxyBootstrap) initializeLoadBalancer(ctx context.Context, environment env.Environment) error {
switch environment.LoadBalancerType() {
case "redis":
lb.SrsLoadBalancer = lb.NewRedisLoadBalancer(environment)
default:
lb.SrsLoadBalancer = lb.NewMemoryLoadBalancer(environment)
}
if err := lb.SrsLoadBalancer.Initialize(ctx); err != nil {
return errors.Wrapf(err, "initialize srs load balancer")
}
return nil
}
// startServers initializes and starts all protocol servers.
func (b *proxyBootstrap) startServers(ctx context.Context, environment env.Environment, gracefulQuitTimeout time.Duration) error {
// Start the RTMP server.
srsRTMPServer := protocol.NewSRSRTMPServer(environment)
if err := srsRTMPServer.Run(ctx); err != nil {
return errors.Wrapf(err, "rtmp server")
}
defer srsRTMPServer.Close()
// Start the WebRTC server.
srsWebRTCServer := protocol.NewSRSWebRTCServer(environment)
if err := srsWebRTCServer.Run(ctx); err != nil {
return errors.Wrapf(err, "rtc server")
}
defer srsWebRTCServer.Close()
// Start the HTTP API server.
srsHTTPAPIServer := protocol.NewSRSHTTPAPIServer(environment, gracefulQuitTimeout, srsWebRTCServer)
if err := srsHTTPAPIServer.Run(ctx); err != nil {
return errors.Wrapf(err, "http api server")
}
defer srsHTTPAPIServer.Close()
// Start the SRT server.
srsSRTServer := protocol.NewSRSSRTServer(environment)
if err := srsSRTServer.Run(ctx); err != nil {
return errors.Wrapf(err, "srt server")
}
defer srsSRTServer.Close()
// Start the System API server.
systemAPI := protocol.NewSystemAPI(environment, gracefulQuitTimeout)
if err := systemAPI.Run(ctx); err != nil {
return errors.Wrapf(err, "system api server")
}
defer systemAPI.Close()
// Start the HTTP web server.
srsHTTPStreamServer := protocol.NewSRSHTTPStreamServer(environment, gracefulQuitTimeout)
if err := srsHTTPStreamServer.Run(ctx); err != nil {
return errors.Wrapf(err, "http server")
}
defer srsHTTPStreamServer.Close()
// Wait for the main loop to quit.
<-ctx.Done()
return nil
}

View File

@ -12,7 +12,7 @@ import (
type key string
var cidKey key = "cid.proxy.ossrs.org"
var cidKey key = "cid.srsx.ossrs.org"
// generateContextID generates a random context id in string.
func GenerateContextID() string {

View File

@ -23,5 +23,5 @@ func Version() string {
}
func Signature() string {
return "SRSProxy"
return "SRSX"
}

View File

@ -7,6 +7,7 @@ The changelog for SRS.
<a name="v7-changes"></a>
## SRS 7.0 Changelog
* v7.0, 2026-04-06, Merge [#4657](https://github.com/ossrs/srs/pull/4657): Proxy: Refactor bootstrap for multi-server support and rebrand to SRSX. v7.0.142 (#4657)
* v7.0, 2026-03-26, Merge [#4654](https://github.com/ossrs/srs/pull/4654): OpenClaw: Restructure workspace with symlinks, add codebase map, and rewrite AI docs. v7.0.141 (#4654)
* v7.0, 2026-03-22, Merge [#4653](https://github.com/ossrs/srs/pull/4653): OpenClaw: unify AI agent configs with shared persona symlinks. v7.0.140 (#4653)
* v7.0, 2026-03-22, Merge [#4652](https://github.com/ossrs/srs/pull/4652): Proxy: restructure repo as Go project with proxy as first module. v7.0.139 (#4652)

View File

@ -9,6 +9,6 @@
#define VERSION_MAJOR 7
#define VERSION_MINOR 0
#define VERSION_REVISION 141
#define VERSION_REVISION 142
#endif