docs: sync wiki with latest changes including LiteLLM + Bedrock guide

Shaun Arman 2026-03-31 12:25:56 -05:00
parent 10e1491638
commit 10b14a2ffe
6 changed files with 722 additions and 156 deletions

@ -19,7 +19,7 @@ pub trait Provider {
### 1. OpenAI-Compatible
Covers: OpenAI, Azure OpenAI, LM Studio, vLLM, and any OpenAI-API-compatible endpoint.
Covers: OpenAI, Azure OpenAI, LM Studio, vLLM, **LiteLLM (AWS Bedrock)**, and any OpenAI-API-compatible endpoint.
| Field | Value |
|-------|-------|
@ -30,7 +30,9 @@ Covers: OpenAI, Azure OpenAI, LM Studio, vLLM, and any OpenAI-API-compatible end
**Models:** `gpt-4o`, `gpt-4o-mini`, `gpt-4-turbo`
**Custom endpoint:** Set `config.base_url` to any OpenAI-compatible API (e.g., LM Studio at `http://localhost:1234/v1`).
**Custom endpoint:** Set `config.base_url` to any OpenAI-compatible API:
- LM Studio: `http://localhost:1234/v1`
- **LiteLLM (AWS Bedrock):** `http://localhost:8000/v1` — See [LiteLLM + Bedrock Setup](LiteLLM-Bedrock-Setup) for full configuration guide
---

@ -4,10 +4,26 @@
| Component | URL | Notes |
|-----------|-----|-------|
| Gogs | `http://172.0.0.29:3000` / `https://gogs.tftsr.com` | Git server, version 0.14 |
| Woodpecker CI (direct) | `http://172.0.0.29:8084` | v0.15.4 |
| Woodpecker CI (proxy) | `http://172.0.0.29:8085` | nginx with custom login page |
| PostgreSQL (Gogs DB) | Container: `gogs_postgres_db` | DB: `gogsdb`, User: `gogs` |
| Gitea | `https://gogs.tftsr.com` / `http://172.0.0.29:3000` | Git server (migrated from Gogs 0.14) |
| Woodpecker CI (direct) | `http://172.0.0.29:8084` | v2.x |
| Woodpecker CI (proxy) | `http://172.0.0.29:8085` | nginx reverse proxy |
| PostgreSQL (Gitea DB) | Container: `gogs_postgres_db` | DB: `gogsdb`, User: `gogs` |
### CI Agents
| Agent | Platform | Host | Purpose |
|-------|----------|------|---------|
| `gitea_act_runner_amd64` (Docker) | `linux-amd64` | 172.0.0.29 | Native x86_64 — test builds + amd64/windows release |
| `act_runner` (systemd) | `linux-arm64` | 172.0.0.29 | Native aarch64 — arm64 release builds |
| `act_runner` (launchd) | `macos-arm64` | sarman's local Mac | Native Apple Silicon — macOS `.dmg` release builds |
Agent labels configured in `~/.config/act_runner/config.yaml`:
```yaml
runner:
labels:
- "macos-arm64:host"
```
macOS runner runs jobs **directly on the host** (no Docker container) — macOS SDK cannot run in Docker.
---
@ -19,140 +35,227 @@
Pipeline steps:
1. rust-fmt-check → cargo fmt --check
2. rust-clippy → cargo clippy -- -D warnings
3. rust-tests → cargo test
3. rust-tests → cargo test (64 tests)
4. frontend-typecheck → npx tsc --noEmit
5. frontend-tests → npm run test:run (Vitest)
5. frontend-tests → npm run test:run (13 Vitest tests)
```
**Docker images used:**
- `rust:1.88-slim` — Rust steps (minimum for cookie_store + time + darling)
- `node:22-alpine` — Frontend steps
**System dependencies installed in CI (Rust steps):**
```
libwebkit2gtk-4.1-dev, libssl-dev, libgtk-3-dev, libsoup-3.0-dev,
librsvg2-dev, libglib2.0-dev
```
**Pipeline YAML format (Woodpecker 0.15.4 — legacy MAP format):**
**Pipeline YAML format (Woodpecker 2.x — steps list format):**
```yaml
clone:
git:
image: woodpeckerci/plugin-git
network_mode: gogs_default # requires repo_trusted=1
environment:
- CI_REPO_CLONE_URL=http://gogs_app:3000/sarman/tftsr-devops_investigation.git
- CI_REPO_CLONE_URL=http://gitea_app:3000/sarman/tftsr-devops_investigation.git
pipeline:
step-name: # KEY = step name (MAP, not list!)
steps:
- name: step-name # LIST format (- name:)
image: rust:1.88-slim
commands:
- cargo test
```
> ⚠️ **Do NOT** use the newer `steps:` list format — Woodpecker 0.15.4 uses the Drone-legacy map format.
> ⚠️ Woodpecker 2.x uses the `steps:` list format. The legacy `pipeline:` map format from
> Woodpecker 0.15.4 is no longer supported.
---
## Release Pipeline (`.woodpecker/release.yml`)
## Release Pipeline (`.gitea/workflows/release.yml`)
**Triggers:** Git tags matching `v*`
```
Pipeline steps:
1. build-linux-amd64 → cargo tauri build (x86_64-unknown-linux-gnu)
2. build-linux-arm64 → cargo tauri build (aarch64-unknown-linux-gnu, cross-compile)
3. upload-release → Create Gogs release + upload artifacts via API
Jobs (run in parallel):
build-linux-amd64 → cargo tauri build (x86_64-unknown-linux-gnu)
→ {.deb, .rpm, .AppImage} uploaded to Gitea release
build-windows-amd64 → cargo tauri build (x86_64-pc-windows-gnu) via mingw-w64
→ {.exe, .msi} uploaded to Gitea release
build-linux-arm64 → cargo tauri build (aarch64-unknown-linux-gnu)
→ {.deb, .rpm, .AppImage} uploaded to Gitea release
build-macos-arm64 → cargo tauri build (aarch64-apple-darwin) — runs on local Mac
→ {.dmg} uploaded to Gitea release
→ unsigned; after install run: xattr -cr /Applications/TFTSR.app
```
**Per-step agent routing (Woodpecker 2.x labels):**
```yaml
steps:
- name: build-linux-amd64
labels:
platform: linux/amd64 # → woodpecker_agent on 172.0.0.29
- name: build-linux-arm64
labels:
platform: linux/arm64 # → woodpecker-agent.service on local arm64 machine
```
**Multi-agent workspace isolation:**
Steps routed to different agents do **not** share a workspace. The arm64 step clones
the repo directly within its commands (using `http://172.0.0.29:3000`, accessible from
the local machine) and uploads its artifacts inline. The `upload-release` step (amd64)
handles amd64 + windows artifacts only.
**Clone override (release.yml — amd64 workspace):**
```yaml
clone:
git:
image: alpine/git
network_mode: gogs_default
commands:
- git init -b master
- git remote add origin http://gitea_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}
```
**Windows cross-compile environment:**
```yaml
environment:
TARGET: x86_64-pc-windows-gnu
CC_x86_64_pc_windows_gnu: x86_64-w64-mingw32-gcc
CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER: x86_64-w64-mingw32-gcc
```
**Artifacts per platform:**
- Linux amd64: `.deb`, `.rpm`, `.AppImage`
- Linux arm64: `.deb`, `.AppImage`
- Windows amd64: `.exe` (NSIS installer), `.msi`
- Linux arm64: `.deb`, `.rpm`, `.AppImage`
**Gogs Release API:**
**Upload step (requires gogs_default network for amd64, host IP for arm64):**
```yaml
# amd64 upload step
upload-release:
image: curlimages/curl:latest
labels:
platform: linux/amd64
network_mode: gogs_default
secrets: [GOGS_TOKEN]
```
The `GOGS_TOKEN` Woodpecker secret must be created via the Woodpecker UI or API after
migration. The secret name stays `GOGS_TOKEN` for pipeline compatibility.
**Gitea Release API (replaces Gogs API — same endpoints, different container name):**
```bash
# Create release
POST $API/repos/sarman/tftsr-devops_investigation/releases
POST http://gitea_app:3000/api/v1/repos/sarman/tftsr-devops_investigation/releases
Authorization: token $GOGS_TOKEN
# Upload artifact
POST $API/repos/sarman/tftsr-devops_investigation/releases/{id}/assets
POST http://gitea_app:3000/api/v1/repos/sarman/tftsr-devops_investigation/releases/{id}/assets
```
The `GOGS_TOKEN` is stored as a Woodpecker secret.
From the arm64 agent (local machine), use `http://172.0.0.29:3000/api/v1` instead.
---
## Multi-File Pipeline Support (Woodpecker 2.x)
Woodpecker 2.x supports multiple pipeline files in the `.woodpecker/` directory.
All `.yml` files are evaluated on every trigger; `when:` conditions control which
pipelines actually run.
Current files:
- `.woodpecker/test.yml` — runs on every push/PR
- `.woodpecker/release.yml` — runs on `v*` tags only
No DB config path switching needed (unlike Woodpecker 0.15.4).
---
## Webhook Configuration
**Hook ID:** 6 (in Gogs)
**Events:** `create`, `push`, `pull_request`
**URL:** `http://172.0.0.29:8084/hook?access_token=<JWT>`
**Woodpecker 2.x with Gitea OAuth2:**
**JWT signing:**
- Algorithm: HS256
- Secret: `repo_hash` value from Woodpecker DB (`dK8zFWtAu67qfKd3Et6N8LptqTmedumJ`)
- Payload: `{"text":"sarman/tftsr-devops_investigation","type":"hook"}`
After migration, Woodpecker 2.x registers webhooks automatically when a repo is
activated via the UI. No manual JWT-signed webhook setup required.
> ⚠️ JWT has an `iat` claim. If it's stale, regenerate it.
1. Log in at `http://172.0.0.29:8085` via Gitea OAuth2
2. Add repo `sarman/tftsr-devops_investigation`
3. Woodpecker creates webhook in Gitea automatically
---
## Woodpecker DB State
## Branch Protection
SQLite at `/docker_mounts/woodpecker/data/woodpecker.sqlite` (on host `172.0.0.29`).
Master branch is protected: all changes require a PR.
Key values:
```sql
-- User
SELECT user_token FROM users WHERE user_login='sarman';
-- Should be: a6b3ee679fe9601e983f1a0c01493758d6033ef3
-- Gitea branch protection (via psql on gogs_postgres_db container)
-- Check protection
SELECT name, protected, require_pull_request FROM protect_branch WHERE repo_id=42;
-- Repo
SELECT repo_active, repo_trusted, repo_config_path, repo_hash
FROM repos WHERE repo_full_name='sarman/tftsr-devops_investigation';
-- repo_active=1, repo_trusted=1
-- repo_config_path='.woodpecker/test.yml'
-- repo_hash='dK8zFWtAu67qfKd3Et6N8LptqTmedumJ'
-- Temporarily disable for urgent fixes (restore immediately after!)
UPDATE protect_branch SET protected=false WHERE repo_id=42 AND name='master';
-- ... push ...
UPDATE protect_branch SET protected=true, require_pull_request=true WHERE repo_id=42 AND name='master';
```
---
## Known Issues & Fixes
### Webhook JWT Must Use `?access_token=`
`token.ParseRequest()` in Woodpecker 0.15.4 does **not** read `?token=` URL params. Use `?access_token=<JWT>` instead.
### Step Containers Cannot Reach `gitea_app`
Default Docker bridge containers cannot resolve `gitea_app` or reach `172.0.0.29:3000`
(host firewall). Fix: use `network_mode: gogs_default` in any step that needs Gitea
access. Requires `repo_trusted=1`.
### JWT Signed with `repo_hash` (Not User Hash)
Hook JWT must be signed with the `repo_hash` value, not the user's hash.
### `CI=woodpecker` Rejected by Tauri CLI
Woodpecker sets `CI=woodpecker`; `cargo tauri build` expects a boolean. Fix: prefix with
`CI=true cargo tauri build`.
### Directory-Based Config Not Supported
Woodpecker 0.15.4 only supports a **single config file**. Set `repo_config_path = .woodpecker/test.yml` in the Woodpecker DB. The `.woodpecker/` directory approach requires v2.x+.
### Step Containers Network Isolation
Pipeline step containers run on the default Docker bridge and cannot resolve `gogs_app` hostname. Fix: set `network_mode: gogs_default` in the clone section (requires `repo_trusted=1`).
### Empty Clone URL Bug
Woodpecker 0.15.4's `go-gogs-client` `PayloadRepo` struct lacks `CloneURL`/`SSHURL` fields, so `build_remote` is always empty from Gogs push payloads. Fix: override the clone URL via `CI_REPO_CLONE_URL` environment variable.
### Gogs Token Authentication
The `sha1` field in Gogs token create API response **is** the actual bearer token (not a hash). Use it directly:
```
Authorization: token <sha1_from_create_response>
### Agent Stalls After Server Restart
After restarting the Woodpecker server, the agent may enter a loop cleaning up orphaned
containers and stop picking up new builds. Fix:
```bash
docker rm -f $(docker ps -aq --filter 'name=0_')
docker volume rm $(docker volume ls -q | grep '0_')
docker restart woodpecker_agent
```
### Gogs SPA Login Field Mismatch
Gogs 0.14 SPA login form uses `login=` field; the Gogs backend reads `username=`. A custom login page is served by nginx at `/login`.
### Windows DLL Export Ordinal Too Large
`/usr/bin/x86_64-w64-mingw32-ld: error: export ordinal too large: 106290`
### Gogs OAuth2 Limitation
Gogs 0.14 has no OAuth2 provider support, blocking upgrade to Woodpecker 2.x.
Fix: `src-tauri/.cargo/config.toml`:
```toml
[target.x86_64-pc-windows-gnu]
rustflags = ["-C", "link-arg=-Wl,--exclude-all-symbols"]
```
### GOGS_TOKEN Secret Must Be Recreated After Migration
After migrating from Woodpecker 0.15.4 to 2.x, recreate the `GOGS_TOKEN` secret:
1. Log in to Gitea, create a new API token under Settings → Applications
2. In Woodpecker UI → Repository → Secrets, add secret `GOGS_TOKEN` with the token value
---
## Gogs PostgreSQL Access
## Gitea PostgreSQL Access
```bash
docker exec gogs_postgres_db psql -U gogs -d gogsdb -c "SELECT id, lower_name FROM repository;"
```
> Database name is `gogsdb`, not `gogs`.
> Database name is `gogsdb` (unchanged from Gogs migration).
---
## Migration Notes (Gogs 0.14 → Gitea)
Gitea auto-migrates the Gogs PostgreSQL schema on first start. Users, repos, teams, and
issues are preserved. API tokens stored in the DB are also migrated but should be
regenerated for security.
Key changes after migration:
- Container name: `gogs_app``gitea_app`
- Config dir: `/data/gitea` (was `/data/gogs` inside container, same host volume)
- Repo dir: `gogs-repositories``gitea-repositories` (renamed on host during migration)
- OAuth2 provider: Gitea now supports OAuth2 (Woodpecker 2.x uses this for login)
- Woodpecker 2.x multi-file pipeline support enabled (no more single config file limitation)

@ -90,7 +90,7 @@ npm run test:coverage
npx tsc --noEmit
```
Current test status: **13/13 frontend tests passing**, Rust tests passing.
Current test status: **13/13 frontend tests passing**, **64/64 Rust tests passing**.
---

27
Home.md

@ -2,6 +2,8 @@
**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/actions/workflows/test.yml/badge.svg) — rustfmt · clippy · 64 Rust tests · tsc · vitest — all green
## Quick Navigation
| Topic | Description |
@ -10,9 +12,10 @@
| [Development Setup](wiki/Development-Setup) | Prerequisites, commands, environment |
| [Database](wiki/Database) | Schema, migrations, encryption |
| [AI Providers](wiki/AI-Providers) | Supported providers and configuration |
| [LiteLLM + Bedrock Setup](wiki/LiteLLM-Bedrock-Setup) | AWS Bedrock integration via LiteLLM proxy |
| [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 |
| [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 |
@ -21,22 +24,30 @@
- **5-Whys AI Triage** — Interactive guided root cause analysis via multi-turn AI chat
- **PII Auto-Redaction** — Detects and redacts sensitive data before any AI send
- **Multi-Provider AI** — OpenAI, Anthropic Claude, Google Gemini, Mistral, local Ollama (fully offline)
- **Multi-Provider AI** — OpenAI, Anthropic Claude, Google Gemini, Mistral, AWS Bedrock (via LiteLLM), local Ollama (fully offline)
- **SQLCipher AES-256** — All issue history encrypted at rest
- **RCA + Post-Mortem Generation** — Auto-populated Markdown templates, exportable as MD/PDF
- **Ollama Management** — Hardware detection, model recommendations, in-app model management
- **Audit Trail** — Every external data send logged with SHA-256 hash
- **Domain-Specific Prompts** — 8 IT domains: Linux, Windows, Network, Kubernetes, Databases, Virtualization, Hardware, Observability
## Releases
| Version | Status | Platforms |
|---------|--------|-----------|
| v0.1.1 | 🚀 Released | linux/amd64 · linux/arm64 · windows/amd64 (.deb, .rpm, .AppImage, .exe, .msi) |
Download from [Releases](https://gogs.tftsr.com/sarman/tftsr-devops_investigation/releases). All builds are produced natively (no QEMU emulation).
## Project Status
| Phase | Status |
|-------|--------|
| Phases 18 (Core) | ✅ Complete |
| Phase 9 (History/Search FTS) | 🔄 Partially integrated |
| Phases 18 (Core application) | ✅ Complete |
| Phase 9 (History/Search) | 🔲 Pending |
| Phase 10 (Integrations) | 🕐 v0.2 stubs only |
| Phase 11 (CLI) | 🕐 Planned |
| Phase 12 (Release packaging) | 🔄 Linux done; macOS/Windows pending |
| Phase 11 (CI/CD) | ✅ Complete — Gitea Actions fully operational |
| Phase 12 (Release packaging) | ✅ linux/amd64 · linux/arm64 (native) · windows/amd64 |
## Tech Stack
@ -49,5 +60,5 @@
| Database | rusqlite + SQLCipher (AES-256) |
| Secret storage | tauri-plugin-stronghold |
| State | Zustand |
| Testing | Vitest (frontend) + `#[cfg(test)]` (Rust) |
| CI/CD | Woodpecker CI v0.15.4 + Gogs |
| Testing | Vitest (13 frontend) + `#[cfg(test)]` (64 Rust tests) |
| CI/CD | Gitea Actions (act_runner v0.3.1) + Gitea |

396
LiteLLM-Bedrock-Setup.md Normal file

@ -0,0 +1,396 @@
# LiteLLM + AWS Bedrock Setup
This guide covers how to use **Claude via AWS Bedrock** with TFTSR through the LiteLLM proxy, providing an OpenAI-compatible API gateway.
## Why LiteLLM + Bedrock?
- **Enterprise AWS contracts** — Use existing AWS Bedrock credits instead of direct Anthropic API
- **Multiple AWS accounts** — Run personal and business Bedrock accounts simultaneously
- **OpenAI-compatible API** — Works with any tool expecting OpenAI's API format
- **Claude Code integration** — Reuse the same AWS credentials used by Claude Code CLI
---
## Prerequisites
- **AWS account** with Bedrock access and Claude models enabled
- **AWS credentials** configured (either default profile or named profile)
- **Python 3.8+** for LiteLLM installation
---
## Installation
### 1. Install LiteLLM
```bash
pip install 'litellm[proxy]'
```
Verify installation:
```bash
litellm --version
```
---
## Basic Setup — Single AWS Account
### 1. Create Configuration File
Create `~/.litellm/config.yaml`:
```yaml
model_list:
- model_name: bedrock-claude
litellm_params:
model: bedrock/us.anthropic.claude-sonnet-4-6
aws_region_name: us-east-1
# Uses default AWS credentials from ~/.aws/credentials
general_settings:
master_key: sk-1234 # Any value — used for API authentication
```
### 2. Start LiteLLM Proxy
```bash
# Run in background
nohup litellm --config ~/.litellm/config.yaml --port 8000 > ~/.litellm/litellm.log 2>&1 &
# Verify it's running
ps aux | grep litellm
```
### 3. Test the Connection
```bash
curl http://localhost:8000/v1/chat/completions \
-H "Authorization: Bearer sk-1234" \
-H "Content-Type: application/json" \
-d '{
"model": "bedrock-claude",
"messages": [{"role": "user", "content": "Hello"}],
"max_tokens": 50
}'
```
Expected response:
```json
{
"id": "chatcmpl-...",
"model": "bedrock-claude",
"choices": [{
"message": {
"role": "assistant",
"content": "Hello! How can I help you today?"
}
}]
}
```
### 4. Configure TFTSR
In **Settings → AI Providers → Add Provider**:
| Field | Value |
|-------|-------|
| Provider Name | OpenAI |
| Base URL | `http://localhost:8000/v1` |
| API Key | `sk-1234` (or your master_key from config) |
| Model | `bedrock-claude` |
| Display Name | `Bedrock Claude` |
---
## Advanced Setup — Multiple AWS Accounts
If you have **personal** and **business** Bedrock accounts, you can run both through the same LiteLLM instance.
### 1. Configure AWS Profiles
Ensure you have AWS profiles set up in `~/.aws/credentials`:
```ini
[default]
aws_access_key_id = AKIA...
aws_secret_access_key = ...
[ClaudeCodeLP]
aws_access_key_id = AKIA...
aws_secret_access_key = ...
```
Or if using credential process (like Claude Code does):
```ini
# ~/.aws/config
[profile ClaudeCodeLP]
credential_process = /Users/${USER}/claude-code-with-bedrock/credential-process --profile ClaudeCodeLP
region = us-east-1
```
### 2. Update Configuration File
Edit `~/.litellm/config.yaml`:
```yaml
model_list:
- model_name: bedrock-personal
litellm_params:
model: bedrock/us.anthropic.claude-sonnet-4-6
aws_region_name: us-east-1
# Uses default AWS credentials
- model_name: bedrock-business
litellm_params:
model: bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0
aws_region_name: us-east-1
aws_profile_name: ClaudeCodeLP # Named profile for business account
general_settings:
master_key: sk-1234
```
### 3. Restart LiteLLM
```bash
# Find and stop existing process
pkill -f "litellm --config"
# Start with new config
nohup litellm --config ~/.litellm/config.yaml --port 8000 > ~/.litellm/litellm.log 2>&1 &
```
### 4. Verify Both Models
```bash
# List available models
curl -s http://localhost:8000/v1/models \
-H "Authorization: Bearer sk-1234" | python3 -m json.tool
# Test personal account
curl -s http://localhost:8000/v1/chat/completions \
-H "Authorization: Bearer sk-1234" \
-H "Content-Type: application/json" \
-d '{"model": "bedrock-personal", "messages": [{"role": "user", "content": "test"}]}'
# Test business account
curl -s http://localhost:8000/v1/chat/completions \
-H "Authorization: Bearer sk-1234" \
-H "Content-Type: application/json" \
-d '{"model": "bedrock-business", "messages": [{"role": "user", "content": "test"}]}'
```
### 5. Configure in TFTSR
Add both models as separate providers:
**Provider 1 — Personal:**
- Provider: OpenAI
- Base URL: `http://localhost:8000/v1`
- API Key: `sk-1234`
- Model: `bedrock-personal`
- Display Name: `Bedrock (Personal)`
**Provider 2 — Business:**
- Provider: OpenAI
- Base URL: `http://localhost:8000/v1`
- API Key: `sk-1234`
- Model: `bedrock-business`
- Display Name: `Bedrock (Business)`
---
## Claude Code Integration
If you're using [Claude Code](https://claude.ai/claude-code) with AWS Bedrock, you can reuse the same AWS credentials.
### 1. Check Claude Code Settings
Read your Claude Code configuration:
```bash
cat ~/.claude/settings.json
```
Look for:
- `AWS_PROFILE` environment variable (e.g., `ClaudeCodeLP`)
- `awsAuthRefresh` credential process path
- `AWS_REGION` setting
### 2. Use Same Profile in LiteLLM
In `~/.litellm/config.yaml`, add a model using the same profile:
```yaml
model_list:
- model_name: claude-code-bedrock
litellm_params:
model: bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0
aws_region_name: us-east-1
aws_profile_name: ClaudeCodeLP # Same as Claude Code
```
Now both Claude Code and TFTSR use the same Bedrock account without duplicate credential management.
---
## Available Claude Models on Bedrock
| Model ID | Name | Context | Best For |
|----------|------|---------|----------|
| `us.anthropic.claude-sonnet-4-6` | Claude Sonnet 4.6 | 200k tokens | Most tasks, best quality |
| `us.anthropic.claude-sonnet-4-5-20250929-v1:0` | Claude Sonnet 4.5 | 200k tokens | High performance |
| `us.anthropic.claude-opus-4-6` | Claude Opus 4.6 | 200k tokens | Complex reasoning |
| `us.anthropic.claude-haiku-4-5-20251001` | Claude Haiku 4.5 | 200k tokens | Speed + cost optimization |
Check your AWS Bedrock console for available models in your region.
---
## Troubleshooting
### Port Already in Use
If port 8000 is occupied:
```bash
# Find what's using the port
lsof -i :8000
# Use a different port
litellm --config ~/.litellm/config.yaml --port 8080
```
Update the Base URL in TFTSR to match: `http://localhost:8080/v1`
### AWS Credentials Not Found
```bash
# Verify AWS CLI works
aws bedrock list-foundation-models --region us-east-1
# Test specific profile
aws bedrock list-foundation-models --region us-east-1 --profile ClaudeCodeLP
```
If AWS CLI fails, fix credentials first before debugging LiteLLM.
### Model Not Available
Error: `Model not found` or `Access denied`
1. Check [AWS Bedrock Console](https://console.aws.amazon.com/bedrock/) → Model Access
2. Request access to Claude models if not enabled
3. Verify model ID matches exactly (case-sensitive)
### LiteLLM Won't Start
Check logs:
```bash
cat ~/.litellm/litellm.log
```
Common issues:
- Missing Python dependencies: `pip install 'litellm[proxy]' --upgrade`
- YAML syntax error: Validate with `python3 -c "import yaml; yaml.safe_load(open('${HOME}/.litellm/config.yaml'))"`
---
## Auto-Start LiteLLM on Boot
### macOS — LaunchAgent
Create `~/Library/LaunchAgents/com.litellm.proxy.plist`:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.litellm.proxy</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/litellm</string>
<string>--config</string>
<string>/Users/${USER}/.litellm/config.yaml</string>
<string>--port</string>
<string>8000</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/${USER}/.litellm/litellm.log</string>
<key>StandardErrorPath</key>
<string>/Users/${USER}/.litellm/litellm-error.log</string>
</dict>
</plist>
```
Load it:
```bash
launchctl load ~/Library/LaunchAgents/com.litellm.proxy.plist
```
### Linux — systemd
Create `/etc/systemd/system/litellm.service`:
```ini
[Unit]
Description=LiteLLM Proxy
After=network.target
[Service]
Type=simple
User=${USER}
ExecStart=/usr/local/bin/litellm --config /home/${USER}/.litellm/config.yaml --port 8000
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
Enable and start:
```bash
sudo systemctl enable litellm
sudo systemctl start litellm
sudo systemctl status litellm
```
---
## Cost Comparison
| Provider | Model | Input (per 1M tokens) | Output (per 1M tokens) |
|----------|-------|----------------------|----------------------|
| **Anthropic Direct** | Claude Sonnet 4 | $3.00 | $15.00 |
| **AWS Bedrock** | Claude Sonnet 4 | $3.00 | $15.00 |
Pricing is identical, but Bedrock provides:
- AWS consolidated billing
- AWS Credits applicability
- Integration with AWS services (S3, Lambda, etc.)
- Enterprise support contracts
---
## Security Notes
1. **Master Key** — The `master_key` in config is required but doesn't need to be complex since LiteLLM runs locally
2. **AWS Credentials** — Never commit `.aws/credentials` or credential process scripts to git
3. **Local Only** — LiteLLM proxy should only bind to `127.0.0.1` (localhost) — never expose to network
4. **Audit Logs** — TFTSR logs all AI requests with SHA-256 hashes in the audit table
---
## References
- [LiteLLM Documentation](https://docs.litellm.ai/)
- [AWS Bedrock Claude Models](https://docs.aws.amazon.com/bedrock/latest/userguide/models-claude.html)
- [Claude Code on Bedrock](https://docs.anthropic.com/claude/docs/claude-code)

@ -1,70 +1,133 @@
# 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.
**Fix:** Webhook URL must use `?access_token=<JWT>` (not `?token=`).
```
http://172.0.0.29:8084/hook?access_token=<JWT>
```
Regenerate the JWT if it's stale (JWT has an `iat` claim):
```bash
# JWT payload: {"text":"sarman/tftsr-devops_investigation","type":"hook"}
# Signed with: repo_hash (dK8zFWtAu67qfKd3Et6N8LptqTmedumJ)
```
**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`
---
### Pipeline Step Can't Reach Gogs
### Job Container Can't Reach Gitea (`172.0.0.29:3000` blocked)
**Cause:** Step containers run on the default Docker bridge, not on `gogs_default` network.
**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:** Use `network_mode: gogs_default` in the clone section and ensure `repo_trusted=1`:
```bash
docker exec woodpecker_db sqlite3 /data/woodpecker.sqlite \
"UPDATE repos SET repo_trusted=1 WHERE repo_full_name='sarman/tftsr-devops_investigation';"
```
**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:
---
### 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
**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
# /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
```
---
### `exec: "node": executable file not found in $PATH`
**Cause:** `actions/checkout@v4` is a Node.js action. `rust:1.88-slim` and similar slim images don't have Node.
**Fix:** Don't use `actions/checkout@v4` — use direct git commands instead (see above).
---
### Job Skipped (status 6) on Tag Push
**Cause:** Pattern matching issue with `on: push: tags:`. Use unquoted glob in the workflow:
```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:** CI runners set `CI=woodpecker` (string). Tauri CLI expects `true`/`false`.
**Fix:** Prefix the build command:
```yaml
- run: CI=true cargo tauri build --target $TARGET
```
---
### Release Artifacts Not Uploaded
**Cause 1:** `RELEASE_TOKEN` secret not set or expired.
```bash
# 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
```
**Cause 2:** Each build job uploads its own artifacts independently. All jobs require host network on the runner (see above).
---
## Rust Compilation
### `MutexGuard` Not `Send` Across Await
**Error:**
```
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(...)?
@ -76,21 +139,18 @@ async_fn().await?;
### Clippy Lints Fail in CI
Common lint fixes:
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`
Auto-fix: `cargo clippy --manifest-path src-tauri/Cargo.toml --fix --allow-dirty -- -D warnings`
---
### `cargo tauri dev` Fails — Missing System Libraries
@ -101,6 +161,12 @@ sudo dnf install -y glib2-devel gtk3-devel webkit2gtk4.1-devel \
libsoup3-devel openssl-devel librsvg2-devel
```
**Fix (Debian/Ubuntu):**
```bash
sudo apt-get install -y libwebkit2gtk-4.1-dev libssl-dev libgtk-3-dev \
libayatana-appindicator3-dev librsvg2-dev patchelf pkg-config
```
---
## Database
@ -109,52 +175,41 @@ sudo dnf install -y glib2-devel gtk3-devel webkit2gtk4.1-devel \
**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 (try `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;"
```
3. File isn't corrupted: `file tftsr.db` should say `SQLite 3.x database`
---
### Migration Fails to Run
Check which migrations have already been applied:
Check which migrations have been applied:
```sql
SELECT name, applied_at FROM _migrations ORDER BY id;
```
If a migration is partially applied, the DB may be in an inconsistent state. Restore from backup or recreate.
---
## Frontend
### TypeScript Errors After Pulling
Run a fresh type check:
```bash
npx tsc --noEmit
```
Ensure `tauriCommands.ts` matches the Rust command signatures exactly (especially `IssueDetail` nesting).
Ensure `tauriCommands.ts` matches Rust command signatures exactly (especially `IssueDetail` nesting).
---
### `IssueDetail` Field Access Errors
The `get_issue` command returns a **nested** struct:
`get_issue()` returns a **nested** struct:
```typescript
// Correct
// Correct
const title = detail.issue.title;
const severity = detail.issue.severity;
// Wrong — these fields don't exist at the top level
// Wrong — field doesn't exist at top level
const title = detail.title;
```
@ -162,26 +217,25 @@ 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 <sha1_value>" https://gogs.tftsr.com/api/v1/user
curl -H "Authorization: token <token_value>" 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]`.
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
@ -189,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).