Files
3x-ui/.github/workflows/ci.yml
T
25f2002654 e8feb9d641 ci: run the Postgres suites that never ran and lint the shell (#6917)
Eight Postgres-only tests skipped everywhere because no workflow named
them. The durable-first job now runs the service and outbound ones, and a
scratch-database job runs the migration tests that drop their own tables.
Every Postgres step counts top-level passes, so a renamed test cannot
leave a step green.

A shell-lint job runs actionlint on the workflows and shellcheck on the
shipped scripts (the legacy installers at error severity), golangci-lint
is pinned, and both the codegen job and make verify fail when
docs/public/openapi.json drifts from the panel's spec; docs-ci also fails
on a stale API reference. npm audit moves to its own script outside make
verify, since it depends on the network.
2026-10-24 19:55:23 +02:00

326 lines
12 KiB
YAML

name: CI
on:
pull_request:
paths:
- "**.go"
- "go.mod"
- "go.sum"
- "frontend/**"
- ".nvmrc"
- "Makefile"
- ".github/workflows/ci.yml"
push:
branches:
- main
paths:
- "**.go"
- "go.mod"
- "go.sum"
- "frontend/**"
- ".nvmrc"
- "Makefile"
- ".github/workflows/ci.yml"
permissions:
contents: read
jobs:
go-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache: true
- name: Stub internal/web/dist for go:embed
run: mkdir -p internal/web/dist && touch internal/web/dist/.gitkeep
- name: Test
run: |
go list ./... | grep -v '/frontend/node_modules/' > /tmp/go-packages.txt
go test -shuffle=on -count=1 $(cat /tmp/go-packages.txt)
postgres-durable-first:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: xui_durable
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres -d xui_durable"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
XUI_DB_TYPE: postgres
XUI_DB_DSN: "host=127.0.0.1 port=5432 user=postgres password=postgres dbname=xui_durable sslmode=disable"
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache: true
- name: Stub internal/web/dist for go:embed
run: mkdir -p internal/web/dist && touch internal/web/dist/.gitkeep
- name: PostgreSQL durable-first tests
run: |
set -o pipefail
go test ./internal/web/service -run 'PostgresCommitFailure|^TestSyncInboundClientsOnPostgres$|^TestSetClientLimitHwidIsSerializedWithSyncInbound$' -count=1 -v | tee /tmp/postgres-durable-first.log
# Count passes rather than assert no SKIP: a renamed or deleted test
# prints "no tests to run" and exits 0, leaving the step green for nothing.
passed=$(grep -c -- '^--- PASS' /tmp/postgres-durable-first.log || true)
if [ "$passed" -lt 4 ]; then
echo "expected at least 4 passing durable-first tests, got $passed" >&2
exit 1
fi
- name: PostgreSQL deferred-commit test
run: |
set -o pipefail
go test ./internal/web/service/outbound -run '^TestAddTrafficReturnsDeferredCommitFailure$' -count=1 -v | tee /tmp/postgres-outbound.log
passed=$(grep -c -- '^--- PASS' /tmp/postgres-outbound.log || true)
if [ "$passed" -lt 1 ]; then
echo "expected at least 1 passing deferred-commit test, got $passed" >&2
exit 1
fi
- name: PostgreSQL schema and migration tests
run: |
set -o pipefail
go test ./internal/database -run '^(TestHostAutoMigrateCreatesColumns_Postgres|TestMigrate_Postgres|TestClientWeeklyRenewMigration_Postgres|TestMigrate_LegacyBooleanVerifyPeerCertByNameIsBlanked_Postgres|TestClientKeepAlivePostgresMigrationPreservesLegacyScalars|TestPostgresModelSettled_TracksSchemaPresence)$' -count=1 -v | tee /tmp/postgres-schema.log
# All must pass. Counting, not SKIP-matching: renaming a test would
# otherwise leave this step green while testing nothing.
passed=$(grep -c -- '^--- PASS' /tmp/postgres-schema.log || true)
if [ "$passed" -lt 6 ]; then
echo "expected at least 6 passing PostgreSQL schema tests, got $passed" >&2
exit 1
fi
- name: PostgreSQL subscription Host ordering tests
run: |
set -o pipefail
go test ./internal/sub -run '^TestSubHostOrder_Postgres$' -count=1 -v | tee /tmp/postgres-host-order.log
passed=$(grep -c -- '^--- PASS' /tmp/postgres-host-order.log || true)
if [ "$passed" -ne 1 ]; then
echo "expected passing PostgreSQL Host ordering coverage, got $passed" >&2
exit 1
fi
- name: PostgreSQL Telegram lookup and legacy migration tests
run: |
set -o pipefail
go test ./internal/web/service -run '^(TestGetClientTrafficTgBot_IndexedPostgres|TestGetClientTrafficTgBot_SettingsSerializationStyles)$' -count=1 -v | tee /tmp/postgres-tgbot.log
passed=$(grep -c -- '^--- PASS' /tmp/postgres-tgbot.log || true)
if [ "$passed" -ne 2 ]; then
echo "expected 2 passing PostgreSQL Telegram lookup tests, got $passed" >&2
exit 1
fi
# The migration tests write and drop their own tables, so they need a
# scratch database instead of the durable one the panel tests use.
postgres-migration:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: xui_scratch
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres -d xui_scratch"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
XUI_TEST_PG_DSN: "host=127.0.0.1 port=5432 user=postgres password=postgres dbname=xui_scratch sslmode=disable"
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache: true
- name: Stub internal/web/dist for go:embed
run: mkdir -p internal/web/dist && touch internal/web/dist/.gitkeep
- name: PostgreSQL migration tests
run: |
set -o pipefail
go test ./internal/database -run '^(TestMigrateData_CompositeKeyTableLargerThanBatch|TestMigrateData_PreservesFalseDefaultedColumns|TestMigrateData_FailedCopyLeavesDestinationUntouched|TestClientHwidSchemaPostgres)$' -count=1 -v | tee /tmp/postgres-migration.log
# Count passes rather than assert no SKIP: a renamed or deleted test
# prints "no tests to run" and exits 0, leaving the step green for nothing.
passed=$(grep -c -- '^--- PASS' /tmp/postgres-migration.log || true)
if [ "$passed" -lt 4 ]; then
echo "expected at least 4 passing PostgreSQL migration tests, got $passed" >&2
exit 1
fi
codegen:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache: true
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
- name: Regenerate schemas, examples and OpenAPI
run: npm run gen
working-directory: frontend
- name: Fail if generated files are stale (run 'npm run gen' and commit)
run: git diff --exit-code -- frontend/src/generated frontend/public/openapi.json
- name: Fail if the docs spec is not the panel's
run: cmp frontend/public/openapi.json docs/public/openapi.json
govulncheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache: true
- name: Stub internal/web/dist for go:embed
run: mkdir -p internal/web/dist && touch internal/web/dist/.gitkeep
- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
- name: Run govulncheck
run: govulncheck ./...
# Race + shuffle hygiene gate: data races and order-dependent tests fail the build.
race:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache: true
- name: Stub internal/web/dist for go:embed
run: mkdir -p internal/web/dist && touch internal/web/dist/.gitkeep
- name: Race + shuffle
run: |
go list ./... | grep -v '/frontend/node_modules/' > /tmp/go-packages.txt
# internal/web/service runs ~10x slower under -race and overruns the 10m default.
go test -race -shuffle=on -count=1 -timeout 25m $(cat /tmp/go-packages.txt)
# A real master and node panel, each its own process, driven through node sync.
# A SKIP here means the binary was never handed over, so it fails the job.
node-e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache: true
- name: Master + node end to end
run: |
set -o pipefail
make node-e2e 2>&1 | tee /tmp/node-e2e.log
if grep -q -- '--- SKIP' /tmp/node-e2e.log; then echo "node-e2e skipped"; exit 1; fi
# Brief native-fuzz smoke on the security-/parser-critical decoders. Each runs the
# generated corpus plus 30s of exploration; a crash here is a real input-handling bug.
fuzz-smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache: true
- name: Stub internal/web/dist for go:embed
run: mkdir -p internal/web/dist && touch internal/web/dist/.gitkeep
- name: Fuzz critical parsers (smoke)
run: |
go test -run '^$' -fuzz 'FuzzParseLink$' -fuzztime=30s ./internal/util/link/
go test -run '^$' -fuzz 'FuzzDecodeCertPin$' -fuzztime=30s ./internal/web/runtime/
golangci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache: true
- name: Stub internal/web/dist for go:embed
run: mkdir -p internal/web/dist && touch internal/web/dist/.gitkeep
- name: golangci-lint
uses: golangci/golangci-lint-action@v9
with:
# Pinned: "latest" let the lint result drift between runs, so the same
# commit could pass today and fail tomorrow on a new upstream release.
version: v2.15.0
# Lint the shell scripts and the workflows themselves. The three legacy
# scripts carry a warning backlog (unquoted expansions, masked return codes)
# and are gated on errors only; the scripts that are clean today are gated on
# everything.
shell-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache: true
- name: actionlint
# The shellcheck integration is off: it reports the stylistic rules
# (SC2086, SC2046) that main's own workflows trip. The shell scripts
# the panel ships are linted by the shellcheck step below.
run: go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 -color -shellcheck=
- name: Install shellcheck
run: sudo apt-get update && sudo apt-get install -y shellcheck
- name: shellcheck
run: |
shellcheck --severity=style DockerInit.sh DockerEntrypoint.sh deploy/test/smoke-noninteractive.sh
shellcheck --severity=error install.sh update.sh x-ui.sh
frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Install
run: npm ci
working-directory: frontend
- name: Lint
run: npm run lint
working-directory: frontend
- name: Format check
run: npm run format:check
working-directory: frontend
- name: Typecheck
run: npm run typecheck
working-directory: frontend
- name: Install Playwright Chromium (Storybook story tests)
run: npx playwright install --with-deps chromium
working-directory: frontend
- name: Test
run: npm test
working-directory: frontend
- name: Build
run: npm run build
working-directory: frontend
- name: Build Storybook
run: npm run build-storybook
working-directory: frontend
- name: Audit
run: npm run audit
working-directory: frontend