Files
OwnCord/.github/workflows/ci.yml
T
J3vbandClaude Fable 5 f3a89e0e09 fix(updater): make client auto-update work end-to-end and Linux server self-update verifiable
- client: update endpoint now sends {{target}}-{{arch}}-{{bundle_type}} so the
  server-echoed platforms key matches the updater plugin's
  {os}-{arch}-{installer} lookup (previously bare {{target}} produced a key
  the plugin never matches, so no update was ever surfaced)
- client: TOFU cert pin is scoped to the OwnCord server host via
  HostScopedVerifier; the GitHub installer download validates against web PKI
  instead of failing the pinned-fingerprint check on every install
- client: check/install share one build_updater helper so the two paths cannot
  diverge; tauri-plugin-updater minor-pinned per its configure_client guidance
- server: client-update endpoint serves target-specific artifacts (NSIS,
  per-arch AppImage) and returns 204 for targets without a published updater
  artifact (deb, darwin) instead of always serving the Windows NSIS installer
- release: server-update-manifest.json now binds both OS assets (legacy
  top-level pair kept pointing at the Windows binary so deployed servers still
  verify); VerifyReleaseManifest resolves the entry matching the downloaded
  asset, fixing Linux server self-update
- release: ARM64 staging renames installer, tar.gz and .sig consistently so
  signatures keep pairing and arch-less names cannot collide with x86_64 assets
- ci: run cargo test --lib (Rust #[cfg(test)] code was never compiled in CI);
  merge the two ptt tests that raced on the global PTT_VKEY atomic

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 18:38:22 +02:00

290 lines
9.7 KiB
YAML

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main, dev]
# Cancel in-progress runs for the same branch/PR
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
server-build-test:
name: Server Build & Test (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
binary: chatserver.exe
- os: ubuntu-latest
binary: chatserver
runs-on: ${{ matrix.os }}
defaults:
run:
working-directory: Server/
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: "1.26"
cache-dependency-path: Server/go.sum
- name: Build server
run: go build -o ${{ matrix.binary }} -ldflags "-s -w" .
# Phase B + C build-tag matrix. Each tag variant must compile so the
# tag boundaries don't drift.
- name: Build with -tags otel (Phase B Step 8)
run: go build -tags otel ./...
- name: Build with -tags wazero (Phase C Step 9)
run: go build -tags wazero ./...
- name: Build with -tags otel,wazero (full community-hub build)
run: go build -tags otel,wazero ./...
- name: Go vulnerability check
run: go install golang.org/x/vuln/cmd/govulncheck@v1.1.4 && govulncheck ./...
# Generated sqlc output must never drift from db/queries/. One leg of
# the matrix is enough; make is not guaranteed on the Windows runner.
- name: Verify generated sqlc output (make sqlc-verify)
if: matrix.os == 'ubuntu-latest'
run: make sqlc-install sqlc-verify
# Protocol message-type constants (Go + TS) must never drift from
# docs/protocol-schema.json — the single source of truth.
- name: Verify generated protocol constants (make protocol-verify)
if: matrix.os == 'ubuntu-latest'
run: make protocol-verify
- name: Run tests with race detection and coverage
run: go test -race -timeout 20m ./... -coverprofile=coverage.out -cover
- name: Run tests with deadlock detection
run: go test -tags deadlock -count=1 ./...
- name: Upload Go coverage
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: go-coverage-${{ matrix.os }}
path: Server/coverage.out
retention-days: 7
- name: Lint
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
with:
version: v2.11.3
working-directory: Server/
client-check:
name: Client Static Checks
runs-on: windows-latest
defaults:
run:
working-directory: Client/tauri-client/
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20
cache: npm
cache-dependency-path: Client/tauri-client/package-lock.json
- name: Install npm dependencies
run: npm ci
- name: Patch auto-generated Tauri TypeScript bindings
working-directory: Client/tauri-client/
# tauri-typegen generates an Event type that is intentionally unused in app code.
# Rename it to _Event so @typescript-eslint/no-unused-vars does not fail.
run: |
node -e "
const fs = require('fs');
const p = 'src/generated/events.ts';
if (fs.existsSync(p)) {
let c = fs.readFileSync(p, 'utf8');
c = c.replace(/^type Event\b/gm, 'type _Event').replace(/^interface Event\b/gm, 'interface _Event');
c = c.replace(/,\s*type Event\s*(?=\})/g, ' '); // unused named import from @tauri-apps/api/event
fs.writeFileSync(p, c);
console.log('Patched: renamed Event -> _Event in generated/events.ts');
} else {
console.log('src/generated/events.ts not found, skipping patch.');
}
"
- name: Security audit (npm)
run: npm audit --audit-level=high
- name: Oxlint (fast correctness checks)
run: npx oxlint src/
- name: TypeScript check
run: npx tsc --noEmit
- name: ESLint (type-aware rules)
run: npx eslint src/
- name: Prettier format check
run: npx prettier --check "src/**/*.ts" "tests/**/*.ts"
- name: Knip (unused code & deps)
run: npx knip || true
# Unit tests live in their own job so a suite failure is visible as exactly one
# failing check instead of masking the static gates above. The suite is GREEN
# and must stay green — never "fix" a failing test by editing its assertions.
client-tests:
name: Client Unit Tests
runs-on: windows-latest
defaults:
run:
working-directory: Client/tauri-client/
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20
cache: npm
cache-dependency-path: Client/tauri-client/package-lock.json
- name: Install npm dependencies
run: npm ci
- name: Run unit tests with coverage
run: npx vitest run --coverage --reporter=default
- name: Upload client coverage
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: client-coverage
path: Client/tauri-client/coverage/
retention-days: 7
server-docker-build:
name: Server Docker Build (verify)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Build image (no push)
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
with:
context: Server/
push: false
build-args: VERSION=ci
cache-from: type=gha
cache-to: type=gha,mode=max
# Full Tauri build only on PRs to main (expensive: ~15 min x2 multiplier)
tauri-build:
name: Tauri Full Build (${{ matrix.os }})
needs: client-check
if: github.event_name == 'pull_request' && github.base_ref == 'main'
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
- os: ubuntu-22.04
- os: ubuntu-22.04-arm
runs-on: ${{ matrix.os }}
defaults:
run:
working-directory: Client/tauri-client/
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20
cache: npm
cache-dependency-path: Client/tauri-client/package-lock.json
- name: Install Linux system dependencies
if: startsWith(matrix.os, 'ubuntu')
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
libsecret-1-dev \
libasound2-dev \
libssl-dev \
patchelf \
librsvg2-dev \
xdg-utils
- name: Install Rust
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
components: clippy
- name: Rust cache
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
with:
workspaces: Client/tauri-client/src-tauri
- name: Install npm dependencies
run: npm ci
- name: Install tauri-typegen
run: cargo install tauri-typegen@0.5.0 --quiet
- name: Generate TypeScript IPC bindings
working-directory: Client/tauri-client/
run: cargo tauri-typegen generate
- name: Fix generated TypeScript bindings (tauri-typegen 0.5.0 workaround)
working-directory: Client/tauri-client/
# tauri-typegen 0.5.0 cannot map serde_json::Value to a TS type — patch post-generation.
# Duplicate events are avoided at source by using one emit() call site per event name.
run: |
node -e "
const fs = require('fs');
const tp = fs.readFileSync('src/generated/types.ts', 'utf8');
if (!tp.includes('export type Value')) {
fs.writeFileSync('src/generated/types.ts', tp.replace(
'export interface CredentialData',
'export type Value = unknown;\n\nexport interface CredentialData'
));
}
console.log('Generated bindings patched.');
"
- name: Clippy lint (Rust)
working-directory: Client/tauri-client/src-tauri/
run: cargo clippy -- -D warnings
# Without this, #[cfg(test)] code is never compiled or run in CI
# (clippy above skips test targets), so Rust unit tests would rot.
- name: Rust unit tests
working-directory: Client/tauri-client/src-tauri/
run: cargo test --lib
- name: Security audit (Rust dependencies)
working-directory: Client/tauri-client/src-tauri/
run: |
cargo install cargo-audit@0.22.1 --quiet
cargo audit
- name: Build Tauri app
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: npm run tauri build