1010 GO_VERSION : " 1.24.x"
1111 NODE_VERSION : " 18"
1212 SINGBOX_VERSION : " 1.12.12"
13+ SINGBOX_LINUX_AMD64_SHA256 : " 7c103cb2f9a7dc54cb82962043596718ed27989a478d6405f0939a9b775f889f"
1314
1415jobs :
1516 lint-backend :
@@ -62,11 +63,24 @@ jobs:
6263 - name : Download dependencies
6364 run : go mod download
6465
66+ - name : Install pinned sing-box test kernel
67+ run : |
68+ archive="sing-box-${SINGBOX_VERSION}-linux-amd64.tar.gz"
69+ curl --fail --location --silent --show-error \
70+ --output "${RUNNER_TEMP}/${archive}" \
71+ "https://github.com/SagerNet/sing-box/releases/download/v${SINGBOX_VERSION}/${archive}"
72+ echo "${SINGBOX_LINUX_AMD64_SHA256} ${RUNNER_TEMP}/${archive}" | sha256sum --check --strict
73+ tar --extract --gzip --file "${RUNNER_TEMP}/${archive}" --directory "${RUNNER_TEMP}"
74+ binary="${RUNNER_TEMP}/sing-box-${SINGBOX_VERSION}-linux-amd64/sing-box"
75+ chmod +x "${binary}"
76+ "${binary}" version
77+ echo "SINGBOX_TEST_BINARY=${binary}" >> "${GITHUB_ENV}"
78+
6579 - name : Build backend
6680 run : CGO_ENABLED=0 go build -o singbox-proxy-manager ./backend
6781
6882 - name : Run backend tests
69- run : go test ./backend/... -v
83+ run : go test -race -count=1 ./backend/... -v
7084
7185 test-frontend-build :
7286 runs-on : ubuntu-latest
@@ -84,157 +98,35 @@ jobs:
8498 run : npm ci
8599 working-directory : frontend
86100
87- - name : Build frontend
88- run : npm run build
89- working-directory : frontend
90-
91- test-frontend-e2e-dnd :
92- runs-on : ubuntu-latest
93- timeout-minutes : 15
94- steps :
95- - uses : actions/checkout@v4
96-
97- - name : Set up Node.js
98- uses : actions/setup-node@v4
99- with :
100- node-version : ${{ env.NODE_VERSION }}
101- cache : npm
102- cache-dependency-path : frontend/package-lock.json
103-
104- - name : Install frontend dependencies
105- run : npm ci
106- working-directory : frontend
107-
108- - name : Resolve browser path
109- run : |
110- if command -v google-chrome >/dev/null 2>&1; then
111- echo "PUPPETEER_EXECUTABLE_PATH=$(command -v google-chrome)" >> "$GITHUB_ENV"
112- elif command -v google-chrome-stable >/dev/null 2>&1; then
113- echo "PUPPETEER_EXECUTABLE_PATH=$(command -v google-chrome-stable)" >> "$GITHUB_ENV"
114- elif command -v chromium-browser >/dev/null 2>&1; then
115- echo "PUPPETEER_EXECUTABLE_PATH=$(command -v chromium-browser)" >> "$GITHUB_ENV"
116- elif command -v chromium >/dev/null 2>&1; then
117- echo "PUPPETEER_EXECUTABLE_PATH=$(command -v chromium)" >> "$GITHUB_ENV"
118- else
119- echo "::error::No Chrome/Chromium executable found on runner."
120- exit 1
121- fi
122-
123- - name : Run drag reorder regression e2e
124- run : npm run test:e2e:dnd
101+ - name : Verify global version consistency
102+ run : npm run test:version-sync
125103 working-directory : frontend
126104
127- test-frontend-e2e-version-refresh :
128- runs-on : ubuntu-latest
129- timeout-minutes : 15
130- steps :
131- - uses : actions/checkout@v4
132-
133- - name : Set up Node.js
134- uses : actions/setup-node@v4
135- with :
136- node-version : ${{ env.NODE_VERSION }}
137- cache : npm
138- cache-dependency-path : frontend/package-lock.json
139-
140- - name : Install frontend dependencies
141- run : npm ci
142- working-directory : frontend
143-
144- - name : Resolve browser path
145- run : |
146- if command -v google-chrome >/dev/null 2>&1; then
147- echo "PUPPETEER_EXECUTABLE_PATH=$(command -v google-chrome)" >> "$GITHUB_ENV"
148- elif command -v google-chrome-stable >/dev/null 2>&1; then
149- echo "PUPPETEER_EXECUTABLE_PATH=$(command -v google-chrome-stable)" >> "$GITHUB_ENV"
150- elif command -v chromium-browser >/dev/null 2>&1; then
151- echo "PUPPETEER_EXECUTABLE_PATH=$(command -v chromium-browser)" >> "$GITHUB_ENV"
152- elif command -v chromium >/dev/null 2>&1; then
153- echo "PUPPETEER_EXECUTABLE_PATH=$(command -v chromium)" >> "$GITHUB_ENV"
154- else
155- echo "::error::No Chrome/Chromium executable found on runner."
156- exit 1
157- fi
158-
159- - name : Run version refresh regression e2e
160- run : npm run test:e2e:version-refresh
161- working-directory : frontend
162-
163- test-frontend-e2e-remark :
164- runs-on : ubuntu-latest
165- timeout-minutes : 15
166- steps :
167- - uses : actions/checkout@v4
168-
169- - name : Set up Node.js
170- uses : actions/setup-node@v4
171- with :
172- node-version : ${{ env.NODE_VERSION }}
173- cache : npm
174- cache-dependency-path : frontend/package-lock.json
175-
176- - name : Install frontend dependencies
177- run : npm ci
178- working-directory : frontend
179-
180- - name : Resolve browser path
181- run : |
182- if command -v google-chrome >/dev/null 2>&1; then
183- echo "PUPPETEER_EXECUTABLE_PATH=$(command -v google-chrome)" >> "$GITHUB_ENV"
184- elif command -v google-chrome-stable >/dev/null 2>&1; then
185- echo "PUPPETEER_EXECUTABLE_PATH=$(command -v google-chrome-stable)" >> "$GITHUB_ENV"
186- elif command -v chromium-browser >/dev/null 2>&1; then
187- echo "PUPPETEER_EXECUTABLE_PATH=$(command -v chromium-browser)" >> "$GITHUB_ENV"
188- elif command -v chromium >/dev/null 2>&1; then
189- echo "PUPPETEER_EXECUTABLE_PATH=$(command -v chromium)" >> "$GITHUB_ENV"
190- else
191- echo "::error::No Chrome/Chromium executable found on runner."
192- exit 1
193- fi
194-
195- - name : Run remark regression e2e
196- run : npm run test:e2e:remark
197- working-directory : frontend
198-
199- test-frontend-e2e-batch-check-ip-live :
200- runs-on : ubuntu-latest
201- timeout-minutes : 15
202- steps :
203- - uses : actions/checkout@v4
204-
205- - name : Set up Node.js
206- uses : actions/setup-node@v4
207- with :
208- node-version : ${{ env.NODE_VERSION }}
209- cache : npm
210- cache-dependency-path : frontend/package-lock.json
211-
212- - name : Install frontend dependencies
213- run : npm ci
214- working-directory : frontend
215-
216- - name : Resolve browser path
217- run : |
218- if command -v google-chrome >/dev/null 2>&1; then
219- echo "PUPPETEER_EXECUTABLE_PATH=$(command -v google-chrome)" >> "$GITHUB_ENV"
220- elif command -v google-chrome-stable >/dev/null 2>&1; then
221- echo "PUPPETEER_EXECUTABLE_PATH=$(command -v google-chrome-stable)" >> "$GITHUB_ENV"
222- elif command -v chromium-browser >/dev/null 2>&1; then
223- echo "PUPPETEER_EXECUTABLE_PATH=$(command -v chromium-browser)" >> "$GITHUB_ENV"
224- elif command -v chromium >/dev/null 2>&1; then
225- echo "PUPPETEER_EXECUTABLE_PATH=$(command -v chromium)" >> "$GITHUB_ENV"
226- else
227- echo "::error::No Chrome/Chromium executable found on runner."
228- exit 1
229- fi
230-
231- - name : Run batch check IP live update regression e2e
232- run : npm run test:e2e:batch-check-ip-live
105+ - name : Build frontend
106+ run : npm run build
233107 working-directory : frontend
234108
235- test-frontend-e2e-outbound-dedup :
109+ test-frontend-e2e :
236110 runs-on : ubuntu-latest
237- timeout-minutes : 15
111+ timeout-minutes : 20
112+ strategy :
113+ fail-fast : false
114+ matrix :
115+ script :
116+ - test:e2e:dnd
117+ - test:e2e:dnd-virtual
118+ - test:e2e:preserve-ports
119+ - test:e2e:batch-import
120+ - test:e2e:batch-check-ip-live
121+ - test:e2e:outbound-dedup
122+ - test:e2e:remark
123+ - test:e2e:filter
124+ - test:e2e:tcp-reuse-ui
125+ - test:e2e:nodes-viewport
126+ - test:e2e:version-refresh
127+ - test:e2e:wireguard-ui
128+ - test:e2e:version-update
129+ - test:e2e:nodeform-config-preservation
238130 steps :
239131 - uses : actions/checkout@v4
240132
@@ -264,8 +156,8 @@ jobs:
264156 exit 1
265157 fi
266158
267- - name : Run outbound dedup regression e2e
268- run : npm run test:e2e:outbound-dedup
159+ - name : Run frontend regression
160+ run : npm run ${{ matrix.script }}
269161 working-directory : frontend
270162
271163 test-system-local-deploy :
@@ -275,11 +167,7 @@ jobs:
275167 - lint-frontend
276168 - test-backend
277169 - test-frontend-build
278- - test-frontend-e2e-dnd
279- - test-frontend-e2e-version-refresh
280- - test-frontend-e2e-remark
281- - test-frontend-e2e-batch-check-ip-live
282- - test-frontend-e2e-outbound-dedup
170+ - test-frontend-e2e
283171 steps :
284172 - uses : actions/checkout@v4
285173
@@ -300,7 +188,7 @@ jobs:
300188
301189 - name : Wait for API readiness
302190 run : |
303- for i in $(seq 1 60); do
191+ for _ in $(seq 1 60); do
304192 if curl -fsS http://127.0.0.1:38080/api/version >/dev/null; then
305193 exit 0
306194 fi
@@ -310,18 +198,109 @@ jobs:
310198 docker compose logs
311199 exit 1
312200
313- - name : Validate login + protected API
201+ - name : Validate login + import + live sing-box chain
314202 run : |
203+ mkdir -p /tmp/sbpm-live-target
204+ printf 'sbpm-live-proxy-ok\n' >/tmp/sbpm-live-target/index.html
205+ python3 -m http.server 39090 --bind 127.0.0.1 \
206+ --directory /tmp/sbpm-live-target >/tmp/sbpm-live-target.log 2>&1 &
207+ echo "$!" >/tmp/sbpm-live-target.pid
208+
209+ cat >/tmp/sbpm-ci-ss-upstream.json <<'JSON'
210+ {
211+ "log": {"level": "warn"},
212+ "inbounds": [
213+ {
214+ "type": "shadowsocks",
215+ "tag": "ss-in",
216+ "listen": "127.0.0.1",
217+ "listen_port": 39091,
218+ "method": "aes-128-gcm",
219+ "password": "ci-ss-password"
220+ }
221+ ],
222+ "outbounds": [{"type": "direct", "tag": "direct"}],
223+ "route": {"final": "direct"}
224+ }
225+ JSON
226+ docker run --detach --name sbpm-ci-ss-upstream --network host \
227+ --volume /tmp/sbpm-ci-ss-upstream.json:/tmp/config.json:ro \
228+ --entrypoint sing-box singbox-proxy-manager:local \
229+ run -c /tmp/config.json
230+ sleep 1
231+ docker ps --filter name=sbpm-ci-ss-upstream --filter status=running --format '{{.Names}}' \
232+ | grep -Fx sbpm-ci-ss-upstream
233+
315234 curl -fsS http://127.0.0.1:38080/api/auth/status | tee /tmp/auth-status.json
316235 TOKEN=$(curl -fsS -X POST http://127.0.0.1:38080/api/login \
317236 -H 'Content-Type: application/json' \
318237 -d '{"password":"system-test-password"}' | jq -r '.token')
319238 test -n "$TOKEN" && test "$TOKEN" != "null"
320239 curl -fsS -H "Authorization: Bearer $TOKEN" http://127.0.0.1:38080/api/settings >/tmp/settings.json
321240
241+ IMPORT_RESULT=$(curl -fsS -X POST http://127.0.0.1:38080/api/nodes/batch-import \
242+ -H "Authorization: Bearer $TOKEN" \
243+ -H 'Content-Type: application/json' \
244+ -d '{"content":"socks5://127.0.0.1:9#ci-system-socks","enabled":true}')
245+ echo "$IMPORT_RESULT" | tee /tmp/batch-import.json
246+ echo "$IMPORT_RESULT" | jq -e '.success == 1 and .failed == 0'
247+
248+ SS_CREDENTIALS=$(printf 'aes-128-gcm:ci-ss-password' \
249+ | base64 --wrap=0 | tr '+/' '-_' | tr -d '=')
250+ SS_LINK="ss://${SS_CREDENTIALS}@127.0.0.1:39091#ci-system-ss"
251+ SS_IMPORT=$(jq -n --arg link "$SS_LINK" '{links: [$link], enabled: true}' \
252+ | curl -fsS -X POST http://127.0.0.1:38080/api/nodes/batch-import \
253+ -H "Authorization: Bearer $TOKEN" \
254+ -H 'Content-Type: application/json' \
255+ --data-binary @-)
256+ echo "$SS_IMPORT" | tee /tmp/ss-import.json
257+ echo "$SS_IMPORT" | jq -e '.success == 1 and .failed == 0'
258+ SS_PORT=$(echo "$SS_IMPORT" | jq -er '.results[0].port | select(. > 0)')
259+ SS_USER=$(echo "$SS_IMPORT" | jq -er '.results[0].username | select(length > 0)')
260+ SS_PASS=$(echo "$SS_IMPORT" | jq -er '.results[0].password | select(length > 0)')
261+
262+ SS_RESULT=$(curl -fsS --noproxy '' \
263+ --proxy "http://127.0.0.1:${SS_PORT}" \
264+ --proxy-user "${SS_USER}:${SS_PASS}" \
265+ http://127.0.0.1:39090/)
266+ test "$SS_RESULT" = "sbpm-live-proxy-ok"
267+
268+ curl -fsS -H "Authorization: Bearer $TOKEN" \
269+ http://127.0.0.1:38080/api/nodes | tee /tmp/nodes.json
270+ jq -e '[.[] | select(.name == "ci-system-socks" and .type == "socks5" and .enabled == true)] | length == 1' \
271+ /tmp/nodes.json
272+
273+ DIRECT_NODE=$(curl -fsS -X POST http://127.0.0.1:38080/api/nodes \
274+ -H "Authorization: Bearer $TOKEN" \
275+ -H 'Content-Type: application/json' \
276+ -d '{"name":"ci-system-direct","type":"direct","config":"{}","username":"ci-live-user","password":"ci-live-pass","tcp_reuse_enabled":true,"enabled":true}')
277+ echo "$DIRECT_NODE" | tee /tmp/direct-node.json
278+ DIRECT_PORT=$(echo "$DIRECT_NODE" | jq -er '.inbound_port | select(. > 0)')
279+
280+ HTTP_RESULT=$(curl -fsS --noproxy '' \
281+ --proxy "http://127.0.0.1:${DIRECT_PORT}" \
282+ --proxy-user 'ci-live-user:ci-live-pass' \
283+ http://127.0.0.1:39090/)
284+ test "$HTTP_RESULT" = "sbpm-live-proxy-ok"
285+
286+ SOCKS_RESULT=$(curl -fsS --noproxy '' \
287+ --socks5-hostname "127.0.0.1:${DIRECT_PORT}" \
288+ --proxy-user 'ci-live-user:ci-live-pass' \
289+ http://127.0.0.1:39090/)
290+ test "$SOCKS_RESULT" = "sbpm-live-proxy-ok"
291+
292+ docker compose --profile local exec -T sb-proxy-local \
293+ sing-box check -c /app/config/config.json
294+ docker compose --profile local top sb-proxy-local | grep -E '[s]ing-box( |$)'
295+
322296 - name : Stop stack
323297 if : always()
324- run : docker compose --profile local down -v
298+ run : |
299+ if test -f /tmp/sbpm-live-target.pid; then
300+ kill "$(cat /tmp/sbpm-live-target.pid)" 2>/dev/null || true
301+ fi
302+ docker rm --force sbpm-ci-ss-upstream 2>/dev/null || true
303+ docker compose --profile local down -v
325304
326305 test-release-image-build :
327306 runs-on : ubuntu-latest
@@ -352,10 +331,10 @@ jobs:
352331 - name : Build linux release binaries for image test
353332 run : |
354333 mkdir -p release-binaries
355- for GOARCH in amd64 arm64; do
356- GOOS=linux GOARCH="${GOARCH }" CGO_ENABLED=0 \
334+ for arch in amd64 arm64; do
335+ GOOS=linux GOARCH="${arch }" CGO_ENABLED=0 \
357336 go build -tags frontend_dist -trimpath -ldflags="-s -w" \
358- -o "release-binaries/singbox-proxy-manager-linux-${GOARCH }" ./backend
337+ -o "release-binaries/singbox-proxy-manager-linux-${arch }" ./backend
359338 done
360339
361340 - name : Set up QEMU
0 commit comments