diff --git a/README.md b/README.md index 3d914170..003fef2e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A structured, AI-backed desktop tool for IT incident triage, 5-Whys root cause a Built with **Tauri 2** (Rust + WebView), **React 18**, **TypeScript**, and **SQLCipher AES-256** encrypted storage. -**CI status:** ![CI](http://172.0.0.29:3000/sarman/tftsr-devops_investigation/badges/master/status.svg) — all checks green (rustfmt · clippy · 64 Rust tests · tsc · vitest) +**CI status:** ![CI](http://172.0.0.29:3000/sarman/tftsr-devops_investigation/actions/workflows/test.yml/badge.svg) — all checks green (rustfmt · clippy · 64 Rust tests · tsc · vitest) --- @@ -112,7 +112,7 @@ Pre-built installers are attached to each [tagged release](https://gogs.tftsr.co |---|---|---| | Linux amd64 | `.deb`, `.rpm`, `.AppImage` | Standard package or universal AppImage | | Windows amd64 | `.exe` (NSIS), `.msi` | From cross-compile via mingw-w64 | -| Linux arm64 | `.deb`, `.AppImage` | Built on native arm64 runner (QEMU emulation available) | +| Linux arm64 | `.deb`, `.rpm`, `.AppImage` | Built natively on arm64 runner | | macOS | — | Requires macOS runner — build locally | --- @@ -177,11 +177,11 @@ tftsr/ ├── tests/ │ ├── unit/ # Vitest unit tests (PII, session store, settings store) │ └── e2e/ # WebdriverIO + tauri-driver E2E skeletons -├── docs/wiki/ # Source of truth for Gogs wiki (auto-synced by CI) -├── .woodpecker/ -│ ├── test.yml # CI: rustfmt · clippy · cargo test · tsc · vitest (every push/PR) -│ └── release.yml # Release: linux/amd64 + windows/amd64 builds → Gogs release artifacts -└── cli/ # Standalone CLI wrapper (tftsr-cli) +├── docs/wiki/ # Source of truth for Gitea wiki +└── .gitea/ + └── workflows/ + ├── test.yml # CI: rustfmt · clippy · cargo test · tsc · vitest (every push/PR) + └── release.yml # Release: linux/amd64 + windows/amd64 + linux/arm64 → Gitea release ``` --- @@ -208,20 +208,25 @@ TAURI_BINARY_PATH=./src-tauri/target/release/tftsr npm run test:e2e --- -## CI/CD — Woodpecker CI +## CI/CD — Gitea Actions -The project uses **Woodpecker CI v0.15.4** connected to Gogs at `gogs.tftsr.com`. +The project uses **Gitea Actions** (act_runner v0.3.1) connected to the Gitea instance at `gogs.tftsr.com`. -| Pipeline | Trigger | Steps | +| Workflow | Trigger | Jobs | |---|---|---| -| `.woodpecker/test.yml` | Every push / PR | rustfmt · clippy · cargo test (64) · tsc · vitest | -| `.woodpecker/release.yml` | Tag `v*` | Build linux/amd64 + windows/amd64 → upload to Gogs release | +| `.gitea/workflows/test.yml` | Every push / PR | rustfmt · clippy · cargo test (64) · tsc · vitest (13) | +| `.gitea/workflows/release.yml` | Tag `v*` or manual dispatch | Build linux/amd64 + windows/amd64 + linux/arm64 → upload to Gitea release | -**Agents:** -- `woodpecker_agent` — linux/amd64 (native x86_64) -- `woodpecker_agent_arm64` — linux/arm64 (QEMU emulation on x86_64 host) +**Runners:** -> macOS builds require a macOS runner. See [CI/CD Pipeline wiki](https://gogs.tftsr.com/sarman/tftsr-devops_investigation/wiki/CICD-Pipeline) for full infrastructure docs. +| Runner | Platform | Host | Purpose | +|---|---|---|---| +| `amd64-docker-runner` | linux/amd64 | 172.0.0.29 (Docker) | Test pipeline + amd64/windows release builds | +| `arm64-native-runner` | linux/arm64 | Local arm64 machine | Native arm64 release builds | + +**Branch protection:** master requires a PR approved by `sarman`, with all 5 CI checks passing before merge. + +> See [CI/CD Pipeline wiki](https://gogs.tftsr.com/sarman/tftsr-devops_investigation/wiki/CICD-Pipeline) for full infrastructure docs. --- @@ -278,8 +283,8 @@ Override with the `TFTSR_DATA_DIR` environment variable. | 8 | RCA & Post-Mortem Generation | ✅ Complete | | 9 | History & Search | 🔲 Pending | | 10 | Integrations (Confluence, ServiceNow, ADO) | 🔲 v0.2 | -| 11 | CI/CD Pipeline | ✅ Complete — all checks green | -| 12 | Release Packaging | ✅ linux/amd64 · windows/amd64; arm64 via QEMU agent | +| 11 | CI/CD Pipeline | ✅ Complete — Gitea Actions, all checks green | +| 12 | Release Packaging | ✅ linux/amd64 · linux/arm64 (native) · windows/amd64 | --- diff --git a/docs/wiki/Home.md b/docs/wiki/Home.md index 68881fdf..26fa1118 100644 --- a/docs/wiki/Home.md +++ b/docs/wiki/Home.md @@ -2,7 +2,7 @@ **TFTSR** is a secure desktop application for guided IT incident triage, root cause analysis (RCA), and post-mortem documentation. Built with Tauri 2.x (Rust + WebView) and React 18. -**CI:** ![build](http://172.0.0.29:3000/sarman/tftsr-devops_investigation/badges/master/status.svg) — rustfmt · clippy · 64 Rust tests · tsc · vitest — all green +**CI:** ![build](http://172.0.0.29:3000/sarman/tftsr-devops_investigation/actions/workflows/test.yml/badge.svg) — rustfmt · clippy · 64 Rust tests · tsc · vitest — all green ## Quick Navigation @@ -14,7 +14,7 @@ | [AI Providers](wiki/AI-Providers) | Supported providers and configuration | | [PII Detection](wiki/PII-Detection) | Patterns, redaction flow, security | | [IPC Commands](wiki/IPC-Commands) | Full list of Tauri backend commands | -| [CI/CD Pipeline](wiki/CICD-Pipeline) | Woodpecker CI + Gogs setup, multi-platform builds | +| [CI/CD Pipeline](wiki/CICD-Pipeline) | Gitea Actions setup, multi-platform builds, act_runner config | | [Security Model](wiki/Security-Model) | Encryption, audit trail, capabilities | | [Integrations](wiki/Integrations) | Confluence, ServiceNow, Azure DevOps (v0.2) | | [Troubleshooting](wiki/Troubleshooting) | Known issues and fixes | @@ -34,9 +34,9 @@ | Version | Status | Platforms | |---------|--------|-----------| -| v0.1.0-alpha | 🚀 Released | linux/amd64 (.deb, .rpm, .AppImage), windows/amd64 (.exe, .msi) | +| v0.1.0-rc1 | 🚀 Released | linux/amd64 · linux/arm64 · windows/amd64 (.deb, .rpm, .AppImage, .exe, .msi) | -Download from [Releases](https://gogs.tftsr.com/sarman/tftsr-devops_investigation/releases). +Download from [Releases](https://gogs.tftsr.com/sarman/tftsr-devops_investigation/releases). All builds are produced natively (no QEMU emulation). ## Project Status @@ -45,8 +45,8 @@ Download from [Releases](https://gogs.tftsr.com/sarman/tftsr-devops_investigatio | Phases 1–8 (Core application) | ✅ Complete | | Phase 9 (History/Search) | 🔲 Pending | | Phase 10 (Integrations) | 🕐 v0.2 stubs only | -| Phase 11 (CI/CD) | ✅ Complete — Woodpecker CI fully operational | -| Phase 12 (Release packaging) | ✅ linux/amd64 + windows/amd64; arm64 via QEMU agent | +| Phase 11 (CI/CD) | ✅ Complete — Gitea Actions fully operational | +| Phase 12 (Release packaging) | ✅ linux/amd64 · linux/arm64 (native) · windows/amd64 | ## Tech Stack @@ -60,4 +60,4 @@ Download from [Releases](https://gogs.tftsr.com/sarman/tftsr-devops_investigatio | Secret storage | tauri-plugin-stronghold | | State | Zustand | | Testing | Vitest (13 frontend) + `#[cfg(test)]` (64 Rust tests) | -| CI/CD | Woodpecker CI v0.15.4 + Gogs | +| CI/CD | Gitea Actions (act_runner v0.3.1) + Gitea | diff --git a/docs/wiki/Troubleshooting.md b/docs/wiki/Troubleshooting.md index f59b4342..0294b3cd 100644 --- a/docs/wiki/Troubleshooting.md +++ b/docs/wiki/Troubleshooting.md @@ -1,133 +1,120 @@ # Troubleshooting -## CI/CD +## CI/CD — Gitea Actions -### Builds Not Triggering After Push +### Build Not Triggering After Push -**Cause:** Woodpecker 0.15.4 `token.ParseRequest()` does not read `?token=` URL params. +**Check:** +1. Verify the workflow file exists in `.gitea/workflows/` on the pushed branch +2. Check the Actions tab at `http://172.0.0.29:3000/sarman/tftsr-devops_investigation/actions` +3. Confirm the act_runner is online: `docker logs gitea_act_runner_amd64 --since 5m` -**Fix:** Webhook URL must use `?access_token=` (not `?token=`). +--- -Regenerate the JWT if it's stale (see [CICD-Pipeline → Webhook Configuration](wiki/CICD-Pipeline)): -```python -# JWT payload: {"text":"sarman/tftsr-devops_investigation","type":"hook"} -# Signed HS256 with repo_hash = dK8zFWtAu67qfKd3Et6N8LptqTmedumJ +### Job Container Can't Reach Gitea (`172.0.0.29:3000` blocked) + +**Cause:** act_runner creates an isolated Docker network per job (when `container:` is specified). Traffic from the job container to `172.0.0.29:3000` is blocked by the host firewall. + +**Fix:** Ensure `container.network: host` is set in the act_runner config AND that `CONFIG_FILE=/data/config.yaml` is in the container's environment: + +```yaml +# /docker_mounts/gitea/runner/amd64/config.yaml +container: + network: "host" +``` + +```yaml +# docker-compose.yml for act-runner-amd64 +environment: + - CONFIG_FILE=/data/config.yaml +``` + +Also set `capacity: 1` — with capacity > 1, concurrent jobs may not get host networking: + +```yaml +runner: + capacity: 1 +``` + +Restart runner: `docker restart gitea_act_runner_amd64` + +--- + +### `Unable to locate package git` in Rust Job + +**Cause:** `rust:1.88-slim` has an empty apt package cache. + +**Fix:** Always run `apt-get update` before `apt-get install`: +```yaml +- name: Checkout + run: | + apt-get update -qq && apt-get install -y -qq git + git init + git remote add origin http://172.0.0.29:3000/sarman/tftsr-devops_investigation.git + git fetch --depth=1 origin $GITHUB_SHA + git checkout FETCH_HEAD ``` --- -### Build Stuck in "Pending" or "Running" with No Containers +### `exec: "node": executable file not found in $PATH` -**Cause:** After a Woodpecker server restart, the agent enters a loop trying to clean up orphaned containers from the previous session and stops processing new tasks. +**Cause:** `actions/checkout@v4` is a Node.js action. `rust:1.88-slim` and similar slim images don't have Node. -**Fix:** -```bash -# 1. Kill any orphan step containers and volumes -ssh sarman@172.0.0.29 -docker rm -f $(docker ps -aq --filter 'name=0_') -docker volume rm $(docker volume ls -q | grep '0_') - -# 2. Cancel stuck builds in Woodpecker DB -python3 -c " -import sqlite3; conn = sqlite3.connect('/docker_mounts/woodpecker/data/woodpecker.sqlite') -conn.execute(\"UPDATE builds SET build_status='cancelled' WHERE build_status IN ('pending','running')\") -conn.execute('DELETE FROM tasks') -conn.commit() -" - -# 3. Restart agent -docker restart woodpecker_agent -``` +**Fix:** Don't use `actions/checkout@v4` — use direct git commands instead (see above). --- -### Pipeline Step Can't Reach Gogs (`gogs_app` not found) +### Job Skipped (status 6) on Tag Push -**Cause:** Step containers run on the default Docker bridge. They cannot resolve `gogs_app` hostname or reach `172.0.0.29:3000` (blocked by host firewall). +**Cause:** Pattern matching issue with `on: push: tags:`. Use unquoted glob in the workflow: -**Fix:** Add `network_mode: gogs_default` to any step needing Gogs access. Requires `repo_trusted=1`: -```bash -python3 -c " -import sqlite3; conn = sqlite3.connect('/docker_mounts/woodpecker/data/woodpecker.sqlite') -conn.execute(\"UPDATE repos SET repo_trusted=1 WHERE repo_full_name='sarman/tftsr-devops_investigation'\") -conn.commit() -" +```yaml +# Correct +on: + push: + tags: + - v* +``` + +Also add `workflow_dispatch` for manual triggering during testing: +```yaml +on: + push: + tags: + - v* + workflow_dispatch: + inputs: + tag: + description: 'Release tag' + required: true ``` --- ### `CI=woodpecker` Rejected by `cargo tauri build` -**Cause:** Woodpecker sets `CI=woodpecker` (string). Tauri CLI's `--ci` flag expects `true`/`false`. +**Cause:** CI runners set `CI=woodpecker` (string). Tauri CLI expects `true`/`false`. **Fix:** Prefix the build command: ```yaml -commands: - - CI=true cargo tauri build --target $TARGET +- run: CI=true cargo tauri build --target $TARGET ``` --- -### Release Artifacts Not Uploaded (Upload Step Silent Failure) +### Release Artifacts Not Uploaded -**Cause 1:** Upload container on default bridge can't reach Gogs API at `http://172.0.0.29:3000`. -**Fix:** Add `network_mode: gogs_default` to the upload step and use `gogs_app:3000` in the API URL. - -**Cause 2:** Artifacts written to `/artifacts/` (absolute path) not visible to the upload step. -**Fix:** Write artifacts to the workspace using relative paths (`artifacts/linux-amd64/`). Only the workspace directory is shared between pipeline steps. - -**Cause 3:** `GOGS_TOKEN` secret not set or has wrong value. -**Check:** +**Cause 1:** `RELEASE_TOKEN` secret not set or expired. ```bash -python3 -c " -import sqlite3; conn = sqlite3.connect('/docker_mounts/woodpecker/data/woodpecker.sqlite') -s = conn.execute(\"SELECT secret_value FROM secrets WHERE secret_name='GOGS_TOKEN'\").fetchone() -print('Token length:', len(s[0]) if s else 'NOT SET') -" -# Test token (replace 172.0.0.29:3000 with gogs_app:3000 from inside gogs_default network) -curl -H "Authorization: token " http://172.0.0.29:3000/api/v1/user +# Recreate via admin CLI: +docker exec -u git gitea_app gitea admin user generate-access-token \ + --username sarman --token-name gitea-ci-token --raw \ + --scopes 'write:repository,read:user' +# Add the token as RELEASE_TOKEN in repo Settings > Actions > Secrets ``` ---- - -### `git switch refs/tags/v*` Fails in Release Build - -**Cause:** `woodpeckerci/plugin-git:latest` uses `git switch` which doesn't support detached HEAD for tag refs. - -**Fix:** Override the clone section with `alpine/git` and explicit commands: -```yaml -clone: - git: - image: alpine/git - network_mode: gogs_default - commands: - - git init -b master - - git remote add origin http://gogs_app:3000/sarman/tftsr-devops_investigation.git - - git fetch --depth=1 origin +refs/tags/${CI_COMMIT_TAG}:refs/tags/${CI_COMMIT_TAG} - - git checkout ${CI_COMMIT_TAG} -``` - ---- - -### Woodpecker Login Fails - -**Cause:** Gogs 0.14 SPA login form uses `login=` field; backend reads `username=`. - -**Fix:** Use the nginx proxy at `http://172.0.0.29:8085/login` which serves a corrected login form. - ---- - -### Empty Clone URL in Pipeline (Push Events) - -**Cause:** Woodpecker 0.15.4 `go-gogs-client` `PayloadRepo` struct is missing `CloneURL`. - -**Fix:** Override with `CI_REPO_CLONE_URL` environment variable in the clone section: -```yaml -clone: - git: - environment: - - CI_REPO_CLONE_URL=http://gogs_app:3000/sarman/tftsr-devops_investigation.git -``` +**Cause 2:** Each build job uploads its own artifacts independently. All jobs require host network on the runner (see above). --- @@ -141,7 +128,6 @@ error[E0277]: `MutexGuard<'_, Connection>` cannot be sent between threads safely **Fix:** Release the mutex lock before any `.await` point: ```rust -// ✅ Correct let result = { let db = state.db.lock().map_err(|e| e.to_string())?; db.query_row(...)? @@ -153,22 +139,17 @@ async_fn().await?; ### Clippy Lints Fail in CI -Common lint fixes required by `-D warnings` (especially on Rust 1.88+): +Common lint fixes required by `-D warnings` (Rust 1.88+): ```rust -// uninlined_format_args format!("{}", x) → format!("{x}") - -// range::contains x >= a && x < b → (a..b).contains(&x) - -// push_str single char s.push_str("a") → s.push('a') ``` -Run locally: `cargo clippy --manifest-path src-tauri/Cargo.toml -- -D warnings -W clippy::uninlined_format_args` +Run locally: `cargo clippy --manifest-path src-tauri/Cargo.toml -- -D warnings` -Auto-fix: `cargo clippy --manifest-path src-tauri/Cargo.toml --fix --allow-dirty -- -D warnings -W clippy::uninlined_format_args` +Auto-fix: `cargo clippy --manifest-path src-tauri/Cargo.toml --fix --allow-dirty -- -D warnings` --- @@ -194,17 +175,10 @@ sudo apt-get install -y libwebkit2gtk-4.1-dev libssl-dev libgtk-3-dev \ **Symptom:** App fails to start with SQLCipher error. -**Check:** 1. `TFTSR_DB_KEY` env var is set 2. Key matches what was used when DB was created 3. File isn't corrupted: `file tftsr.db` should say `SQLite 3.x database` -**Warning:** Changing the key requires re-encrypting the database: -```bash -sqlite3 tftsr.db "ATTACH 'new.db' AS newdb KEY 'new-key'; \ - SELECT sqlcipher_export('newdb'); DETACH DATABASE newdb;" -``` - --- ### Migration Fails to Run @@ -214,8 +188,6 @@ Check which migrations have been applied: SELECT name, applied_at FROM _migrations ORDER BY id; ``` -If a migration is partially applied, the DB may be inconsistent. Restore from backup or recreate. - --- ## Frontend @@ -234,37 +206,36 @@ Ensure `tauriCommands.ts` matches Rust command signatures exactly (especially `I `get_issue()` returns a **nested** struct: ```typescript -// ✅ Correct +// Correct const title = detail.issue.title; -// ❌ Wrong -const title = detail.title; // field doesn't exist at top level +// Wrong — field doesn't exist at top level +const title = detail.title; ``` --- ### Vitest Tests Fail -```bash -npm run test:run -``` - Common causes: - Mocked `invoke()` return type doesn't match updated command signature - `sessionStore` state not reset between tests (call `store.reset()` in `beforeEach`) --- -## Gogs +## Gitea -### Token Authentication +### API Token Authentication -The `sha1` field from the Gogs token create API **is** the bearer token — use it directly: ```bash -curl -H "Authorization: token " https://gogs.tftsr.com/api/v1/user +curl -H "Authorization: token " http://172.0.0.29:3000/api/v1/user ``` -Do not confuse with the `sha1` column in the `access_token` table, which stores `sha1(token)[:40]` (a hash of the token, not the token itself). +Create tokens in Gitea Settings > Applications > Access Tokens, or via admin CLI: +```bash +docker exec -u git gitea_app gitea admin user generate-access-token \ + --username sarman --token-name mytoken --raw --scopes 'read:user,write:repository' +``` ### PostgreSQL Access @@ -272,4 +243,4 @@ Do not confuse with the `sha1` column in the `access_token` table, which stores docker exec gogs_postgres_db psql -U gogs -d gogsdb -c "SELECT id, lower_name, is_private FROM repository;" ``` -Database is named `gogsdb`, not `gogs`. +Database is named `gogsdb`. The PostgreSQL instance uses SCRAM-SHA-256 auth (MD5 also configured for the `gogs` user for compatibility with older clients). diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 720496d2..8534f781 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2639,6 +2639,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" dependencies = [ "cc", + "openssl-sys", "pkg-config", "vcpkg", ] @@ -3161,6 +3162,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[package]] +name = "openssl-src" +version = "300.5.5+3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" +dependencies = [ + "cc", +] + [[package]] name = "openssl-sys" version = "0.9.112" @@ -3169,6 +3179,7 @@ checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" dependencies = [ "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ]