The Agent now owns one local view of machine health and top processes. RedFlag opens at dashboard scale, names itself plainly, and renders that evidence before updates.\n\nCI now compiles the Tauri bridge before release day.
328 lines
13 KiB
YAML
328 lines
13 KiB
YAML
name: ci
|
|
on:
|
|
push:
|
|
branches: [main, public]
|
|
pull_request:
|
|
branches: [main, public]
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
go-vet:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
|
with:
|
|
go-version-file: agent/go.mod
|
|
cache: true
|
|
- name: go vet (server)
|
|
run: cd server && go vet ./...
|
|
- name: go vet (agent)
|
|
run: cd agent && go vet ./...
|
|
|
|
go-test:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
|
with:
|
|
go-version-file: agent/go.mod
|
|
cache: true
|
|
- name: go test -race (server)
|
|
run: cd server && go test -race -count=1 ./...
|
|
- name: go test -race (agent)
|
|
run: cd agent && go test -race -count=1 ./...
|
|
|
|
rust-test:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
|
|
with:
|
|
components: clippy
|
|
- name: cargo test
|
|
run: cd helper && cargo test
|
|
- name: cargo clippy
|
|
run: cd helper && cargo clippy -- -D warnings
|
|
|
|
# Cross-compile check: does it build for every target platform?
|
|
# Tests run only on native linux-amd64 above; this catches portability
|
|
# regressions (cfg(target_os), FFI, path assumptions) without needing
|
|
# a runner per OS.
|
|
cross-compile:
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- goos: linux
|
|
goarch: arm64
|
|
rust_target: aarch64-unknown-linux-gnu
|
|
linker: gcc-aarch64-linux-gnu
|
|
use_zigbuild: false
|
|
skip_helper: false
|
|
- goos: windows
|
|
goarch: amd64
|
|
rust_target: ""
|
|
linker: gcc-mingw-w64-x86-64
|
|
use_zigbuild: false
|
|
skip_helper: true
|
|
- goos: darwin
|
|
goarch: arm64
|
|
rust_target: aarch64-apple-darwin
|
|
linker: ""
|
|
use_zigbuild: true
|
|
skip_helper: false
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
|
with:
|
|
go-version-file: agent/go.mod
|
|
cache: true
|
|
- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
|
|
with:
|
|
targets: ${{ matrix.rust_target }}
|
|
|
|
- name: Install cross-linker
|
|
if: matrix.linker != ''
|
|
run: sudo apt-get update && sudo apt-get install -y ${{ matrix.linker }}
|
|
|
|
- name: Install cargo-zigbuild
|
|
if: matrix.use_zigbuild
|
|
run: pip3 install --break-system-packages cargo-zigbuild
|
|
|
|
- name: Cross-compile Go (server)
|
|
env:
|
|
GOOS: ${{ matrix.goos }}
|
|
GOARCH: ${{ matrix.goarch }}
|
|
CGO_ENABLED: "0"
|
|
run: cd server && go build -o /dev/null ./cmd/server/
|
|
|
|
- name: Cross-compile Go (agent)
|
|
env:
|
|
GOOS: ${{ matrix.goos }}
|
|
GOARCH: ${{ matrix.goarch }}
|
|
CGO_ENABLED: "0"
|
|
run: cd agent && go build -o /dev/null ./cmd/agent/
|
|
|
|
- name: Cross-compile Rust (helper)
|
|
if: "!matrix.skip_helper"
|
|
run: |
|
|
cd helper
|
|
if [ "${{ matrix.use_zigbuild }}" = "true" ]; then
|
|
cargo zigbuild --release --target ${{ matrix.rust_target }}
|
|
else
|
|
cargo build --release --target ${{ matrix.rust_target }}
|
|
fi
|
|
|
|
web-build:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
|
with:
|
|
node-version: 20
|
|
cache: npm
|
|
cache-dependency-path: web/package-lock.json
|
|
# npm run build = tsc && vite build — type errors and bundling failures
|
|
# both surface here, not at release time.
|
|
- name: Build web UI
|
|
run: cd web && npm ci && npm run build
|
|
|
|
desktop-check:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
|
with:
|
|
node-version: 20
|
|
cache: npm
|
|
cache-dependency-path: web/package-lock.json
|
|
- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
|
|
- name: Install Tauri system dependencies
|
|
run: |
|
|
sudo apt-get update -qq
|
|
sudo apt-get install -y -qq libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev
|
|
- name: Check RedFlag desktop
|
|
run: |
|
|
cd web
|
|
npm ci
|
|
npm run build:desktop
|
|
cd ../desktop
|
|
cargo check
|
|
|
|
installer-integrity:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
|
with:
|
|
go-version-file: agent/go.mod
|
|
cache: true
|
|
- name: Install template integrity
|
|
run: cd server && go test -run 'TestInstallTemplateRenders|TestFreshInstallConfigKeys|TestInstallTemplateScriptletSyntax' -v -count=1 ./internal/services/
|
|
|
|
# Dependency vulnerability scanning — RedFlag held to the supply-chain standard
|
|
# it enforces on the fleet. Tools installed directly (no third-party actions) so
|
|
# the socket-mounted runner's surface stays small. Go is reachability-gated via a
|
|
# documented allowlist (.govulncheck-allow, mirrored in SECURITY.md); npm gates the
|
|
# production tree and treats dev-only advisories as warnings; cargo gates outright.
|
|
dep-scan:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
|
with:
|
|
go-version-file: agent/go.mod
|
|
cache: true
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
|
with:
|
|
node-version: 20
|
|
cache: npm
|
|
cache-dependency-path: web/package-lock.json
|
|
- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
|
|
|
|
# Provenance first: record the substrate even if a later scan fails the job.
|
|
# "Are we using hacked programs to build it?" — this is how we SEE the answer.
|
|
# Floor enforcement (fail on an out-of-date engine/toolchain) is the next layer.
|
|
- name: Record build substrate
|
|
run: |
|
|
{
|
|
echo "## Build substrate"
|
|
echo '```'
|
|
echo "go: $(go version)"
|
|
echo "rustc: $(rustc --version)"
|
|
echo "cargo: $(cargo --version)"
|
|
echo "node: $(node --version)"
|
|
echo "npm: $(npm --version)"
|
|
echo "docker: $(docker version --format '{{.Server.Version}}' 2>&1 || echo 'no engine reachable')"
|
|
echo "runner: ${RUNNER_NAME:-unknown} / $(uname -srm)"
|
|
echo '```'
|
|
} | tee -a "${GITHUB_STEP_SUMMARY:-/dev/stdout}"
|
|
|
|
# Scanners run latest on purpose — an old scanner misses new advisories.
|
|
- name: Install scanners
|
|
run: |
|
|
go install golang.org/x/vuln/cmd/govulncheck@latest
|
|
cargo install cargo-audit --locked
|
|
|
|
# One audited script gates Go (reachability + allowlist), web (prod tree),
|
|
# and the Rust helper. release.yml runs the same script with --posture-out
|
|
# to emit the attested posture — CI and release can't drift on the verdict.
|
|
- name: Dependency gate
|
|
run: scripts/dep-scan.sh
|
|
|
|
no-ai-attribution:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
with:
|
|
fetch-depth: 0
|
|
- name: Check commit messages for AI attribution
|
|
run: |
|
|
# Check all commits in the push/PR range.
|
|
# On push: compare against the base branch.
|
|
# On PR: compare against the PR base.
|
|
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
|
RANGE="${{ github.event.pull_request.base.sha }}..${{ github.sha }}"
|
|
else
|
|
RANGE="${{ github.event.before }}..${{ github.sha }}"
|
|
# First push — no before sha. Check last 10 commits.
|
|
if [ "${{ github.event.before }}" = "0000000000000000000000000000000000000000" ]; then
|
|
RANGE="HEAD~10..HEAD"
|
|
fi
|
|
fi
|
|
|
|
PATTERNS="Co-Authored-By:.*[Cc]laude|Co-Authored-By:.*OpenAI|Co-Authored-By:.*ChatGPT|Co-Authored-By:.*Copilot|Co-Authored-By:.*Letta|Co-Authored-By:.*Cursor|Generated by|Generated with|AI-assisted|Auto-generated by"
|
|
FAIL=0
|
|
while IFS= read -r msg; do
|
|
if echo "$msg" | grep -qiE "$PATTERNS"; then
|
|
echo "::error::AI attribution found in commit: $msg"
|
|
FAIL=1
|
|
fi
|
|
done < <(git log --format='%s%n%b' $RANGE 2>/dev/null)
|
|
|
|
if [ "$FAIL" -eq 1 ]; then
|
|
echo "::error::Commits contain AI attribution lines. Remove them before merging."
|
|
exit 1
|
|
fi
|
|
echo "No AI attribution found in commits."
|
|
|
|
action-pins:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
- name: Check for floating action refs
|
|
run: |
|
|
if grep -rE 'uses:.*@(v[0-9]+|stable|main|master)(\s|$)' .gitea/workflows/; then
|
|
echo "::error::Floating action refs found — run scripts/update-action-pins.sh"
|
|
exit 1
|
|
fi
|
|
echo "All action refs are SHA-pinned."
|
|
|
|
public-history:
|
|
runs-on: ubuntu-latest
|
|
if: github.ref == 'refs/heads/public' || github.base_ref == 'public'
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
with:
|
|
fetch-depth: 0
|
|
- name: Fetch pinned gitleaks
|
|
run: |
|
|
command -v curl || (apt-get update -qq && apt-get install -y -qq curl)
|
|
curl -fsSL -o /tmp/gitleaks.tar.gz \
|
|
https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz
|
|
echo '551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb /tmp/gitleaks.tar.gz' |
|
|
sha256sum -c -
|
|
tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks
|
|
- name: Scan complete public history
|
|
run: |
|
|
/tmp/gitleaks git . --redact --no-banner
|
|
scripts/check-public-history.sh HEAD
|
|
|
|
# The internal forge is the only writer. This job projects the exact tested
|
|
# public SHA to Forgejo, without force, then reads it back anonymously.
|
|
sync-public:
|
|
runs-on: ubuntu-latest
|
|
needs: [go-vet, go-test, rust-test, cross-compile, web-build, desktop-check, installer-integrity, dep-scan, no-ai-attribution, action-pins, public-history]
|
|
if: github.ref == 'refs/heads/public'
|
|
env:
|
|
PUBLIC_FORGE_TOKEN: ${{ secrets.PUBLIC_FORGE_TOKEN }}
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
with:
|
|
fetch-depth: 0
|
|
- name: Publish and verify exact SHA
|
|
run: |
|
|
set -euo pipefail
|
|
: "${PUBLIC_FORGE_TOKEN:?PUBLIC_FORGE_TOKEN is required}"
|
|
|
|
expected="${GITHUB_SHA}"
|
|
forge_url='https://forge.caseytunturi.com/Fimeg/RedFlag.git'
|
|
|
|
check_fast_forward() {
|
|
url="$1"
|
|
remote_sha="$(git ls-remote "$url" refs/heads/public | awk '{print $1}')"
|
|
if [ -z "$remote_sha" ]; then
|
|
return 0
|
|
fi
|
|
if ! git cat-file -e "${remote_sha}^{commit}" 2>/dev/null; then
|
|
echo "[publish] remote public SHA is not in tested history: $url" >&2
|
|
exit 1
|
|
fi
|
|
if ! git merge-base --is-ancestor "$remote_sha" "$expected"; then
|
|
echo "[publish] refusing non-fast-forward public history: $url" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
check_fast_forward "$forge_url"
|
|
|
|
forge_auth="$(printf 'publisher-redflag:%s' "$PUBLIC_FORGE_TOKEN" | base64 -w0)"
|
|
|
|
git -c "http.https://forge.caseytunturi.com/.extraheader=Authorization: Basic $forge_auth" \
|
|
push "$forge_url" public:public
|
|
|
|
forge_sha="$(git ls-remote "$forge_url" refs/heads/public | awk '{print $1}')"
|
|
test "$forge_sha" = "$expected"
|
|
echo "[publish] exact SHA is anonymous on Forgejo: $expected"
|