From 8638eb3567c1893e8ce993656dc4de98757a92b8 Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Fri, 10 Apr 2026 11:20:00 -0300 Subject: [PATCH] Fix DriveMonitor dispatch failure - avoid double Arc in tokio::spawn - Added static save_file_states_static() helper method - Changed tokio::spawn calls to use Arc::clone instead of Arc::new(self.clone()) - This prevents double Arc wrapping which causes 'dispatch failure' errors - Fixes config.csv not syncing from bucket to database for salesianos/default bots --- .goutputstream-0HCON3 | Bin 0 -> 60175 bytes AGENTS.md | 33 + PROD.md | 1336 +++++++++++++++++++++++++++++++++++++++++ botserver | 2 +- prompts/htmlview.md | 1 + prompts/prod.md | 259 -------- restart-fast.sh | 49 -- restart.sh | 74 ++- stop.sh | 4 - 9 files changed, 1405 insertions(+), 353 deletions(-) create mode 100644 .goutputstream-0HCON3 create mode 100644 PROD.md create mode 100644 prompts/htmlview.md delete mode 100644 prompts/prod.md delete mode 100644 restart-fast.sh delete mode 100755 stop.sh diff --git a/.goutputstream-0HCON3 b/.goutputstream-0HCON3 new file mode 100644 index 0000000000000000000000000000000000000000..5aaec64253d4b71ff6f454f7d50a13d45c96e528 GIT binary patch literal 60175 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%0D0=INcY>>aj(#7t2SZTSO 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⁢TsVFEf7Fr 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 | Main botserver + botui container | +| `alm-ci` | `` | CI/CD runner (Forgejo Actions) | +| `drive` | `` | Object storage | +| `monitor` | `` | Monitoring service | + +### Port Mapping (system container) + +| Service | Internal Port | External URL | +|---------|--------------|--------------| +| botserver | `5858` | `https://system.example.com` | +| botui | `5859` | `https://chat.example.com` | + +### Access + +```bash +# SSH to host +ssh admin@ + +# Execute inside system container +sudo incus exec system -- bash -c 'command' + +# SSH from host to container (used by CI) +ssh -o StrictHostKeyChecking=no system "command" +``` + +## Services + +### botserver.service + +- **Binary**: `/opt/gbo/bin/botserver` +- **Port**: `5858` +- **User**: `gbuser` +- **Logs**: `/opt/gbo/logs/out.log`, `/opt/gbo/logs/err.log` +- **Config**: `/etc/systemd/system/botserver.service` +- **Env**: `PORT=5858` + +### ui.service + +- **Binary**: `/opt/gbo/bin/botui` +- **Port**: `5859` +- **Config**: `/etc/systemd/system/ui.service` +- **Env**: `BOTSERVER_URL=http://localhost:5858` + - ⚠️ MUST be `http://localhost:5858` — NOT `https://system.example.com` + - Rust proxy runs server-side, needs direct localhost access + - JS client uses relative URLs through `chat.example.com` + +### Data Directory + +- **Path**: `/opt/gbo/data/` +- **Structure**: `.gbai/.gbdialog/*.bas` +- **Work dir**: `/opt/gbo/work/` (compiled .ast cache) + +### Stack Services (managed by botserver bootstrap) + +- **Vault**: Secrets management +- **PostgreSQL**: Database (port 5432) +- **Valkey**: Cache (port 6379, password auth) +- **MinIO**: Object storage +- **Zitadel**: Identity provider +- **LLM**: llama.cpp + +## CI/CD Pipeline + +### Repositories + +| Repo | ALM URL | GitHub URL | +|------|---------|------------| +| gb | `https://alm.example.com/organization/gb.git` | `git@github.com:organization/gb.git` | +| botserver | `https://alm.example.com/organization/BotServer.git` | `git@github.com:organization/botserver.git` | +| botui | `https://alm.example.com/organization/BotUI.git` | `git@github.com:organization/botui.git` | +| botlib | `https://alm.example.com/organization/botlib.git` | `git@github.com:organization/botlib.git` | + +### Push Order + +```bash +# 1. Push submodules first +cd botserver && git push alm main && git push origin main && cd .. +cd botui && git push alm main && git push origin main && cd .. + +# 2. Update root workspace references +git add botserver botui botlib +git commit -m "Update submodules: " +git push alm main && git push origin main +``` + +### Build Environment + +- **CI runner**: `ci-runner` container (Debian Trixie, glibc 2.41) +- **Target**: `system` container (Debian 12 Bookworm, glibc 2.36) +- **⚠️ GLIBC MISMATCH**: Building on CI runner produces binaries incompatible with system container +- **Solution**: CI workflow transfers source to system container and builds there via SSH + +### Workflow File + +- **Location**: `botserver/.forgejo/workflows/botserver.yaml` +- **Triggers**: Push to `main` branch +- **Steps**: + 1. Setup workspace on CI runner (clone repos) + 2. Transfer source to system container via `tar | ssh` + 3. Build inside system container (matches glibc 2.36) + 4. Deploy binary inside container + 5. Verify botserver is running + +## Common Operations + +### Check Service Status + +```bash +# From host +sudo incus exec system -- systemctl status botserver --no-pager +sudo incus exec system -- systemctl status ui --no-pager + +# Check if running +sudo incus exec system -- pgrep -f botserver +sudo incus exec system -- pgrep -f botui +``` + +### View Logs + +```bash +# Systemd journal +sudo incus exec system -- journalctl -u botserver --no-pager -n 50 +sudo incus exec system -- journalctl -u ui --no-pager -n 50 + +# Application logs +sudo incus exec system -- tail -50 /opt/gbo/logs/out.log +sudo incus exec system -- tail -50 /opt/gbo/logs/err.log + +# Live tail +sudo incus exec system -- tail -f /opt/gbo/logs/out.log +``` + +### Restart Services + +**CRITICAL PRODUCTION RULE:** In production, NEVER start botserver or botui directly. Always use `systemctl` to ensure proper initialization, environment loading, and logging. + +```bash +sudo incus exec system -- systemctl restart botserver +sudo incus exec system -- systemctl restart ui +``` + +**PROHIBITED in production:** +```bash +# ❌ NEVER DO THIS IN PRODUCTION: +sudo incus exec system -- /opt/gbo/bin/botserver # Wrong - no systemd integration +sudo incus exec system -- /opt/gbo/bin/botserver & # Wrong - no service management +sudo incus exec system -- cd /opt/gbo/bin && ./botserver # Wrong - missing env vars + +# ✅ CORRECT - Always use systemctl: +sudo incus exec system -- systemctl start botserver +sudo incus exec system -- systemctl restart botserver +sudo incus exec system -- systemctl stop botserver +sudo incus exec system -- systemctl status botserver +``` + +**Why:** +- `systemctl` loads `/opt/gbo/bin/.env` (via `EnvironmentFile` in service definition) +- `systemctl` manages process lifecycle, auto-restart, and dependencies +- `systemctl` sends logs to `/opt/gbo/logs/out.log` and `/opt/gbo/logs/err.log` +- Direct execution skips environment variables and systemd service configuration + +### Manual Deploy (emergency) + +```bash +# Kill old process +sudo incus exec system -- killall botserver + +# Copy binary (from host CI workspace or local) +sudo incus exec system -- cp /opt/gbo/ci/botserver/target/debug/botserver /opt/gbo/bin/botserver +sudo incus exec system -- chmod +x /opt/gbo/bin/botserver +sudo incus exec system -- chown gbuser:gbuser /opt/gbo/bin/botserver + +# Start service +sudo incus exec system -- systemctl start botserver +``` + +### Transfer Bot Files to Production + +```bash +# From local to prod host +tar czf /tmp/bots.tar.gz -C /opt/gbo/data .gbai +scp /tmp/bots.tar.gz admin@:/tmp/ + +# From host to container +sudo incus exec system -- bash -c 'tar xzf /tmp/bots.tar.gz -C /opt/gbo/data/' + +# Clear compiled cache +sudo incus exec system -- find /opt/gbo/data -name "*.ast" -delete +sudo incus exec system -- find /opt/gbo/work -name "*.ast" -delete +``` + +### Snapshots + +```bash +# List snapshots +sudo incus snapshot list system + +# Restore snapshot +sudo incus snapshot restore system +``` + +## DriveMonitor & Bot Configuration Sync + +### DriveMonitor Architecture + +DriveMonitor is a background service that synchronizes bot files from MinIO (S3-compatible storage) to the local filesystem and database. It monitors three directories per bot: + +| Directory | Purpose | Sync Behavior | +|-----------|---------|---------------| +| `{bot}.gbai/{bot}.gbdialog/` | BASIC scripts (.bas) | Downloads and compiles on change | +| `{bot}.gbai/{bot}.gbot/` | Configuration files | Syncs to `bot_configuration` table | +| `{bot}.gbkb/` | Knowledge base documents | Downloads and indexes for vector search | + +### Bot Configuration Database Tables + +#### `bot_configuration` (main config table) +```sql +-- Location: botserver database +SELECT * FROM bot_configuration WHERE bot_id = ''; + +-- Key columns: +-- - bot_id: Bot UUID (link to bots table) +-- - config_key: Configuration key (e.g., "llm-provider", "system-prompt") +-- - config_value: Configuration value +-- - config_type: Type (string, boolean, number) +-- - is_encrypted: Whether value is encrypted +-- - updated_at: Last modification timestamp +``` + +#### `gbot_config_sync` (sync tracking table) +```sql +-- Location: botserver database +-- Tracks config.csv sync status from bucket +SELECT * FROM gbot_config_sync g + JOIN bots b ON g.bot_id = b.id + WHERE b.name = 'salesianos'; + +-- Key columns: +-- - bot_id: Bot UUID +-- - config_file_path: Path to config.csv in bucket +-- - last_sync_at: Timestamp of last successful sync +-- - file_hash: ETag/MD5 of synced file +-- - sync_count: Number of times synced +``` + +### config.csv Sync Process + +**File Locations:** +- Source: `{bot}.gbai/{bot}.gbot/config.csv` in MinIO bucket +- Sync method: DriveMonitor → ConfigManager → `bot_configuration` table +- Sync frequency: Every 10 seconds (DriveMonitor periodic check) + +**Sync Trigger Conditions:** +1. File ETag changes in MinIO +2. Initial DriveMonitor startup +3. Manual botserver restart + +**CSV Format:** +```csv +llm-provider,groq +llm-api-key,sk-xxx +llm-url,http://localhost:8085 +system-prompt-file,PROMPT.md +theme-color1,#cc0000 +theme-title,MyBot +whatsapp-id,botname +``` + +### Checking Bot Configuration Status + +#### Method 1: Query bot_configuration table +```bash +# Get all config for a bot +sudo incus exec tables -- psql -h localhost -U postgres -d botserver -c " + SELECT b.name, bc.config_key, bc.config_value, bc.updated_at + FROM bot_configuration bc + JOIN bots b ON bc.bot_id = b.id + WHERE b.name = 'salesianos' + ORDER BY bc.config_key; +" + +# Get specific LLM provider config +sudo incus exec tables -- psql -h localhost -U postgres -d botserver -c " + SELECT config_key, config_value, updated_at + FROM bot_configuration + WHERE bot_id = ( + SELECT id FROM bots WHERE name = 'salesianos' + ) + AND config_key LIKE 'llm-%' + ORDER BY config_key; +" +``` + +#### Method 2: Check DriveMonitor sync status +```bash +# Check if config.csv has been synced +sudo incus exec tables -- psql -h localhost -U postgres -d botserver -c " + SELECT b.name, gcs.last_sync_at, gcs.sync_count, gcs.config_file_path + FROM gbot_config_sync gcs + JOIN bots b ON gcs.bot_id = b.id + WHERE b.name IN ('salesianos', 'default'); +" + +-- Empty result = DriveMonitor hasn't synced config.csv yet +-- If sync_count = 0, config.csv exists but hasn't been processed +``` + +#### Method 3: Direct MinIO inspection +```bash +# Check if config.csv exists in bucket +sudo incus exec drive -- /opt/gbo/bin/mc ls local/salesianos.gbai/salesianos.gbot/ + +# View config.csv contents +sudo incus exec drive -- /opt/gbo/bin/mc cat local/salesianos.gbai/salesianos.gbot/config.csv + +# Check file ETag (for sync comparison) +sudo incus exec drive -- /opt/gbo/bin/mc stat local/salesianos.gbai/salesianos.gbot/config.csv +``` + +### DriveMonitor Debugging Logs + +#### Key log patterns to monitor +```bash +# Monitor DriveMonitor activity in real-time +sudo incus exec system -- tail -f /opt/gbo/logs/out.log | grep -E "(DRIVE_MONITOR|check_gbot|config)" + +# Check for config.csv sync attempts +sudo incus exec system -- grep "check_gbot" /opt/gbo/logs/out.log | tail -20 + +# Check for config synchronization +sudo incus exec system -- grep "sync_gbot_config" /opt/gbo/logs/out.log | tail -20 + +# Check for DriveMonitor errors +sudo incus exec system -- grep -i "drive.*error" /opt/gbo/logs/err.log | tail -20 +``` + +#### Expected successful sync logs +``` +check_gbot: Checking bucket salesianos.gbai for config.csv changes +check_gbot: Found config.csv at path: salesianos.gai/salesianos.gbot/config.csv +info config:Synced config.csv for bot - updated 3 keys +``` + +#### Error patterns and meanings +``` +# Config.csv not found in bucket +check_gbot: Config file not found or inaccessible: path/to/config.csv + +# Sync to database failed +error config:Failed to sync_gbot_config: + +# DriveMonitor not running +(no check_gbot logs in output.log) + +# MinIO connection failed +error drive_monitor:S3/MinIO unavailable for bucket +``` + +### Common Issues and Fixes + +#### Issue 1: config.csv not syncing to database + +**Symptoms:** +- `gbot_config_sync` table empty (0 rows) +- LLM provider changes in bucket not reflected in bot behavior +- Database shows old configuration values + +**Diagnosis:** +```bash +# 1. Check if config.csv exists in bucket +sudo incus exec drive -- /opt/gbo/bin/mc ls local/salesianos.gbai/salesianos.gbot/ + +# 2. Check DriveMonitor logs for sync attempts +sudo incus exec system -- grep "check_gbot" /opt/gbo/logs/out.log | tail -10 + +# 3. Check if DriveMonitor is running for the bot +sudo incus exec system -- ps aux | grep botserver +``` + +**Root Causes:** +1. config.csv missing from `{bot}.gai/{bot}.gbot/` folder +2. DriveMonitor not started for the bot +3. MinIO connection issues +4. Database write permissions + +**Fixes:** +```bash +# Case 1: Create missing config.csv +sudo incus exec drive -- bash -c ' +cat > /tmp/config.csv << EOF +llm-provider,groq +llm-api-key,your-api-key +llm-url,http://localhost:8085 +system-prompt-file,PROMPT.md +theme-color1,#cc0000 +theme-title,Salesianos +EOF +/opt/gbo/bin/mc cp /tmp/config.csv local/salesianos.gbai/salesianos.gbot/config.csv +' + +# Case 2: Restart botserver to reinitialize DriveMonitor +sudo incus exec system -- systemctl restart botserver + +# Case 3: Force immediate sync by touching config.csv +sudo incus exec drive -- /opt/gbo/bin/mc cp local/salesianos.gbai/salesianos.gbot/config.csv local/salesianos.gbai/salesianos.gbot/config.csv +``` + +#### Issue 2: LLM provider changes not taking effect + +**Symptoms:** +- config.csv shows correct provider (e.g., groq) +- Bot still uses old provider +- Database shows old value + +**Diagnosis:** +```bash +# Compare bucket vs database +BUCKET_PROVIDER=$(sudo incus exec drive -- /opt/gbo/bin/mc cat local/salesianos.gbai/salesianos.gbot/config.csv | grep "^llm-provider" | cut -d',' -f2) +DB_PROVIDER=$(sudo incus exec tables -- psql -h localhost -U postgres -d botserver -t -c " + SELECT config_value FROM bot_configuration + WHERE bot_id = (SELECT id FROM bots WHERE name = 'salesianos') + AND config_key = 'llm-provider'; +") + +echo "Bucket: $BUCKET_PROVIDER" +echo "Database: $DB_PROVIDER" + +# Check last sync time +sudo incus exec tables -- psql -h localhost -U postgres -d botserver -t -c " + SELECT last_sync_at FROM gbot_config_sync + WHERE bot_id = (SELECT id FROM bots WHERE name = 'salesianos'); +" +``` + +**Fix:** +```bash +# If sync is stale (> 10 minutes), restart DriveMonitor +sudo incus exec system -- systemctl restart botserver + +# Or manually update config value in database (temporary fix) +sudo incus exec tables -- psql -h localhost -U postgres -d botserver -c " + UPDATE bot_configuration + SET config_value = 'groq', updated_at = NOW() + WHERE bot_id = (SELECT id FROM bots WHERE name = 'salesianos') + AND config_key = 'llm-provider'; +" +``` + +#### Issue 3: DriveMonitor not checking for changes + +**Symptoms:** +- No new log entries after 30 seconds +- File changes in bucket not detected +- Bot compilation not happening after .bas file updates + +**Diagnosis:** +```bash +# Check DriveMonitor loop logs +sudo incus exec system -- tail -100 /opt/gbo/logs/out.log | grep "DRIVE_MONITOR.*Inside monitoring loop" + +# Check if is_processing flag is stuck +sudo incus exec system -- tail -100 /opt/gbo/logs/out.log | grep -E "(is_processing|monitoring loop)" +``` + +**Fix:** +```bash +# Restart botserver to clear stuck state +sudo incus exec system -- systemctl restart botserver + +# Monitor startup logs to verify DriveMonitor started +sudo incus exec system -- tail -50 /opt/gbo/logs/out.log | grep "Drive Monitor" +``` + +### Database Schema Reference + +#### List all bot databases +```bash +sudo incus exec tables -- psql -h localhost -U postgres -d postgres -c "\l" | grep bot_ +``` + +#### List tables in a specific bot database +```bash +sudo incus exec tables -- psql -h localhost -U postgres -d bot_salesianos -c "\dt" +``` + +#### List botserver management tables +```bash +sudo incus exec tables -- psql -h localhost -U postgres -d botserver -c "\dt" | grep -E "(bot|config|sync)" +``` + +### Connection Methods Summary + +| Method | Use Case | Command Pattern | +|--------|-----------|-----------------| +| **SSH to host** | Initial access, file transfer | `ssh admin@63.141.255.9` | +| **incus exec** | Execute inside container | `sudo incus exec system -- command` | +| **psql direct** | Database queries from container | `sudo incus exec tables -- psql ...` | +| **mc (MinIO CLI)** | Inspect buckets, copy files | `sudo incus exec drive -- /opt/gbo/bin/mc ...` | +| **HTTP/curl** | Service health checks | `curl http://:5858/health` | +| **journalctl** | Systemd service logs | `sudo incus exec system -- journalctl -u botserver` | + +## Vault Security Architecture + +### Overview + +The production environment uses **HashiCorp Vault** as the centralized secrets management system. All sensitive credentials (database passwords, API keys, tokens) are stored in Vault, NEVER in code or environment files. + +### Vault Connection Flow + +``` +1. botserver starts + ↓ +2. Reads VAULT_ADDR, VAULT_TOKEN from .env + ↓ +3. Initializes VaultClient with TLS/mTLS + ↓ +4. Reads secrets from Vault paths (gbo/tables, gbo/drive, etc.) + ↓ +5. Falls back to defaults if Vault unavailable +``` + +### Environment Variables (Allowed) + +**File Location:** `/opt/gbo/bin/.env` (system container) + +```bash +# Vault Connection (MANDATORY for production) +VAULT_ADDR=https://:8200 +VAULT_TOKEN= +VAULT_CACERT=/opt/gbo/conf/system/certificates/ca/ca.crt + +# Optional: Skip TLS verification (NOT recommended for production) +VAULT_SKIP_VERIFY=false + +# Optional: Use mTLS certificates +VAULT_CLIENT_CERT=/opt/gbo/conf/system/certificates/botserver/client.crt +VAULT_CLIENT_KEY=/opt/gbo/conf/system/certificates/botserver/client.key + +# Optional: Cache TTL in seconds (default: 300) +VAULT_CACHE_TTL=300 + +# Server Configuration +PORT=5858 +DATA_DIR=/opt/gbo/data/ +WORK_DIR=/opt/gbo/work/ +LOAD_ONLY=default,salesianos +``` + +**Security Rule:** +- **ONLY** `VAULT_*` environment variables are allowed in `.env` +- All other secrets MUST come from Vault +- Hardcoded secrets in code are FORBIDDEN (see AGENTS.md) + +### Vault Secret Paths Structure + +#### System-Wide Paths (Global) + +| Path | Purpose | Example Keys | +|------|---------|---------------| +| `gbo/tables` | Database (PostgreSQL) | host, port, database, username, password | +| `gbo/drive` | MinIO (Object Storage) | host, accesskey, secret | +| `gbo/cache` | Valkey (Redis) | host, port, password | +| `gbo/directory` | Zitadel (Auth) | url, project_id, client_id, client_secret | +| `gbo/email` | SMTP Email | smtp_host, smtp_port, smtp_user, smtp_password | +| `gbo/llm` | LLM Configuration | url, model, openai_key, anthropic_key | +| `gbo/vectordb` | Qdrant (Vector DB) | url, api_key | +| `gbo/jwt` | JWT Signing | secret | +| `gbo/meet` | Jitsi Meet | url, app_id, app_secret | +| `gbo/alm` | ALM Repository | url, token | +| `gbo/encryption` | Encryption Keys | master_key | +| `gbo/system/observability` | Monitoring | url, org, bucket, token | +| `gbo/system/security` | Security Policies | require_auth, anonymous_paths | +| `gbo/system/cloud` | Cloud Config | region, access_key, secret_key | +| `gbo/system/app` | Application Settings | url, environment | +| `gbo/system/models` | BotModels API | url | + +#### Organization-Specific Paths + +| Path Pattern | Purpose | +|--------------|---------| +| `gbo/orgs/{org_id}/config` | Organization configuration | +| `gbo/orgs/{org_id}/bots/{bot_id}` | Bot-specific secrets | +| `gbo/orgs/{org_id}/users/{user_id}` | User-specific secrets | +| `gbo/tenants/{tenant_id}/infrastructure` | Tenant database/cache/drive | +| `gbo/tenants/{tenant_id}/config` | Tenant configuration | + +### Credential Resolution Hierarchy + +For bot email configuration (example): +``` +1. Check gbo/orgs/{org_id}/bots/{bot_id}/email +2. Fallback: gbo/bots/default/email +3. Fallback: gbo/email +4. Fallback: Environment variables (development only) +``` + +### Vault Client Initialization (Code Reference) + +**File:** `botserver/src/core/secrets/mod.rs` + +```rust +// SecretsManager::from_env() reads: +// - VAULT_ADDR (required) +// - VAULT_TOKEN (required) +// - VAULT_CACERT (optional, has default) +// - VAULT_SKIP_VERIFY (optional, default: false) +// - VAULT_CLIENT_CERT (optional, mTLS) +// - VAULT_CLIENT_KEY (optional, mTLS) +// - VAULT_CACHE_TTL (optional, default: 300s) + +impl SecretsManager { + pub fn from_env() -> Result { + let addr = env::var("VAULT_ADDR").unwrap_or_default(); + let token = env::var("VAULT_TOKEN").unwrap_or_default(); + + if token.is_empty() || addr.is_empty() { + // Vault not configured - use environment variables directly + warn!("Vault not configured. Using environment variables directly."); + return Ok(Self { client: None, enabled: false, ... }); + } + + // Initialize VaultClient with TLS + let client = VaultClient::new(settings)?; + Ok(Self { client: Some(client), enabled: true, ... }) + } +} +``` + +### Vault Operations - Production Usage + +#### Read Secrets from Vault + +```bash +# From system container (using vault CLI) +sudo incus exec system -- bash -c ' + export VAULT_ADDR=https://10.157.134.250:8200 + export VAULT_TOKEN= + export VAULT_CACERT=/opt/gbo/conf/system/certificates/ca/ca.crt + + # Read database secrets + vault kv get -field=password secret/gbo/tables + vault kv get secret/gbo/tables + + # Read drive secrets + vault kv get secret/gbo/drive + + # Read LLM configuration + vault kv get secret/gbo/llm +' +``` + +#### Read Secrets via HTTP API (from any container) + +```bash +sudo incus exec system -- curl -sf \ + --cacert /opt/gbo/conf/system/certificates/ca/ca.crt \ + -H "X-Vault-Token: " \ + https://10.157.134.250:8200/v1/secret/data/gbo/drive | jq +``` + +#### Verify Vault Health + +```bash +sudo incus exec vault -- curl -k -sf https://localhost:8200/v1/sys/health + +# Expected output: +# {"initialized":true,"sealed":false,"standby":false,"performance_standby":false,"replication_performance_mode":"disabled","replication_dr_mode":"disabled","server_time_utc":"2026-04-10T13:55:00.123Z"} +``` + +### init.json (Vault Initialization Data) + +**Location:** `/opt/gbo/bin/botserver-stack/conf/vault/vault-conf/init.json` + +**Purpose:** Stores Vault unseal keys and root token (created during Vault initialization) + +**Contents:** +```json +{ + "recovery_keys_b64": [], + "recovery_keys_hex": [], + "recovery_keys_shares": 0, + "recovery_keys_threshold": 0, + "root_token": "", + "unseal_keys_b64": ["<5 unseal keys base64-encoded>"], + "unseal_keys_hex": ["<5 unseal keys hex-encoded>"], + "unseal_shares": 5, + "unseal_threshold": 3 +} +``` + +**Security Notes:** +- `root_token`: Used to authenticate to Vault as admin +- `unseal_keys`: Required to unseal Vault after restart (5 keys, need 3 to unseal) +- **CRITICAL:** Store `init.json` in a secure, encrypted location +- Never commit `init.json` to git or store in repo + +### Troubleshooting Vault Connection + +#### Issue 1: Botserver cannot connect to Vault + +**Symptoms:** +- Logs show "Vault connection failed" +- Secrets fall back to defaults +- Bot cannot authenticate to database + +**Diagnosis:** +```bash +# Check Vault is running +sudo incus exec vault -- systemctl status vault + +# Check Vault health +sudo incus exec vault -- curl -k -sf https://localhost:8200/v1/sys/health + +# Check .env has Vault credentials +sudo incus exec system -- grep "^VAULT_" /opt/gbo/bin/.env + +# Test Vault connection from system container +sudo incus exec system -- bash -c ' + curl -k -sf --cacert /opt/gbo/conf/system/certificates/ca/ca.crt \ + -H "X-Vault-Token: $(grep VAULT_TOKEN /opt/gbo/bin/.env | cut -d= -f2)" \ + https://10.157.134.250:8200/v1/secret/data/gbo/tables +' +``` + +**Common Causes:** +1. Vault service not running (vault container stopped) +2. `VAULT_TOKEN` expired or invalid +3. TLS certificate path incorrect or CA certificate missing +4. Network connectivity between system and vault containers + +**Fix:** +```bash +# 1. Restart Vault if stopped +sudo incus exec vault -- systemctl restart vault + +# 2. Generate new token if expired +sudo incus exec vault -- bash -c ' + export VAULT_ADDR=https://localhost:8200 + export VAULT_TOKEN= + vault token create -policy="botserver" -ttl="8760h" -format=json | jq -r .auth.client_token +' + +# 3. Update .env with new token +sudo incus exec system -- sed -i "s|VAULT_TOKEN=.*|VAULT_TOKEN=|" /opt/gbo/bin/.env + +# 4. Restart botserver +sudo incus exec system -- systemctl restart botserver +``` + +#### Issue 2: Secrets not being read from Vault + +**Symptoms:** +- Logs show "Vault read failed for 'gbo/drive'" +- Services use default credentials +- DriveMonitor cannot access MinIO + +**Diagnosis:** +```bash +# Check if Vault has secrets configured +sudo incus exec system -- bash -c ' + export VAULT_ADDR=https://10.157.134.250:8200 + export VAULT_TOKEN=$(grep VAULT_TOKEN /opt/gbo/bin/.env | cut -d= -f2) + export VAULT_CACERT=/opt/gbo/conf/system/certificates/ca/ca.crt + + echo "=== Database Secrets ===" + vault kv get secret/gbo/tables || echo "NOT FOUND" + + echo "=== Drive Secrets ===" + vault kv get secret/gbo/drive || echo "NOT FOUND" + + echo "=== LLM Secrets ===" + vault kv get secret/gbo/llm || echo "NOT FOUND" +' +``` + +**Fix - Adding Secrets to Vault:** +```bash +sudo incus exec vault -- bash -c ' + export VAULT_ADDR=https://localhost:8200 + export VAULT_TOKEN= + + # Add database secrets + vault kv put secret/gbo/tables \ + host= \ + port=5432 \ + database=botserver \ + username=gbuser \ + password= + + # Add drive (MinIO) secrets + vault kv put secret/gbo/drive \ + host= \ + port=9100 \ + accesskey= \ + secret= + + # Add LLM secrets + vault kv put secret/gbo/llm \ + url=http://localhost:8085 \ + model=gpt-4 \ + openai_key= \ + anthropic_key= +' +``` + +#### Issue 3: Vault sealed after restart + +**Symptoms:** +- All Vault operations fail +- botserver cannot read secrets +- Logs show "Vault is sealed" + +**Diagnosis:** +```bash +sudo incus exec vault -- curl -k -sf https://localhost:8200/v1/sys/health | jq .sealed +``` + +**Fix - Unseal Vault:** +```bash +sudo incus exec vault -- bash -c ' + # Need 3 of 5 unseal keys from init.json + vault operator unseal + vault operator unseal + vault operator unseal + + # Verify unsealed + vault status +' +``` + +#### Issue 4: TLS certificate errors + +**Symptoms:** +- "certificate verify failed" errors +- TLS handshake failures +- curl: (60) SSL certificate problem + +**Diagnosis:** +```bash +sudo incus exec system -- bash -c ' + # Check CA certificate exists + ls -la /opt/gbo/conf/system/certificates/ca/ca.crt + + # Test certificate + openssl x509 -in /opt/gbo/conf/system/certificates/ca/ca.crt -text -noout +' +``` + +**Fix:** +```bash +# If CA cert is missing, copy from vault container +sudo incus exec vault -- cp /opt/gbo/conf/vault/ca.crt /tmp/ + +sudo incus exec system -- mkdir -p /opt/gbo/conf/system/certificates/ca/ +sudo incus exec system -- bash -c ' + # Copy certificate from vault container + incus file pull vault/opt/gbo/conf/vault/ca.crt /tmp/ca.crt + cp /tmp/ca.crt /opt/gbo/conf/system/certificates/ca/ + chmod 644 /opt/gbo/conf/system/certificates/ca/ca.crt +' +``` + +### Security Best Practices + +1. **Never commit secrets to git** + - No API keys, passwords, tokens in code + - Use Vault for ALL sensitive data + - Init secrets from `SecretsManager::from_env()` + +2. **Use Vault for all service credentials** + - Database passwords: `gbo/tables` + - MinIO keys: `gbo/drive` + - LLM API keys: `gbo/llm` + - Email passwords: `gbo/email` + +3. **Rotate credentials regularly** + - Generate new tokens/keys periodically + - Update Vault using `vault kv put` + - No need to restart services (next read gets new values) + +4. **Enable TLS/mTLS in production** + - Always use `VAULT_CACERT` + - Enable mTLS for critical services: `VAULT_CLIENT_CERT` + `VAULT_CLIENT_KEY` + - Never use `VAULT_SKIP_VERIFY=true` in production + +5. **Limit token lifetimes** + - Root token: single use or very short TTL + - Service tokens: limited to needed time (e.g., 8760h = 1 year) + - Generate new tokens when old ones expire + +6. **Audit Vault access** + ```bash + # Check recent Vault operations + sudo incus exec vault -- vault audit list + sudo incus exec vault -- vault audit file /var/log/vault_audit.log + ``` + +### Vault Backup & Recovery + +#### Backup Vault Data + +```bash +# Snapshot vault container (includes all secrets) +sudo incus snapshot create vault backup-$(date +%Y%m%d-%H%M) + +# Export Vault config (init.json with unseal keys) +sudo incus exec vault -- cat /opt/gbo/bin/botserver-stack/conf/vault/vault-conf/init.json > /tmp/vault-init.json + +# Backup all secrets (JSON format) +sudo incus exec vault -- bash -c ' + export VAULT_ADDR=https://localhost:8200 + export VAULT_TOKEN= + + # Backup each path + for path in gbo/tables gbo/drive gbo/cache gbo/llm; do + vault kv get -format=json secret/$path > /tmp/vault-$path.json + done +' +``` + +#### Restore from Snapshot + +```bash +# Stop vault +sudo incus exec vault -- systemctl stop vault + +# Restore snapshot +sudo incus snapshot restore vault + +# Start vault +sudo incus exec vault -- systemctl start vault + +# Wait for Vault to be ready +sleep 10 + +# Verify health +sudo incus exec vault -- curl -k -sf https://localhost:8200/v1/sys/health +``` + +## Troubleshooting + +### GLIBC Version Mismatch + +**Symptom**: `GLIBC_2.39 not found` or `GLIBC_2.38 not found` + +**Cause**: Binary compiled on CI runner (glibc 2.41) but runs in system container (glibc 2.36) + +**Fix**: CI workflow must build inside the system container. Check `botserver.yaml` uses SSH to build in container. + +### botserver Not Starting + +```bash +# Check binary +sudo incus exec system -- ldd /opt/gbo/bin/botserver | grep "not found" + +# Check direct execution +sudo incus exec system -- timeout 10 /opt/gbo/bin/botserver 2>&1 + +# Check data directory +sudo incus exec system -- ls -la /opt/gbo/data/ +``` + +### botui Can't Reach botserver + +```bash +# Check BOTSERVER_URL +sudo incus exec system -- grep BOTSERVER_URL /etc/systemd/system/ui.service + +# Must be http://localhost:5858, NOT https://system.example.com +# Fix: +sudo incus exec system -- sed -i 's|BOTSERVER_URL=.*|BOTSERVER_URL=http://localhost:5858|' /etc/systemd/system/ui.service +sudo incus exec system -- systemctl daemon-reload +sudo incus exec system -- systemctl restart ui +``` + +### Suggestions Not Showing + +```bash +# Check bot files exist +sudo incus exec system -- ls -la /opt/gbo/data/.gbai/.gbdialog/ + +# Check for compilation errors +sudo incus exec system -- tail -50 /opt/gbo/logs/out.log | grep -i "error\|fail\|compile" + +# Clear cache and restart +sudo incus exec system -- find /opt/gbo/work -name "*.ast" -delete +sudo incus exec system -- systemctl restart botserver +``` + +### IPv6 DNS Issues + +**Symptom**: External API calls (Groq, Cloudflare) timeout + +**Cause**: Container DNS returns AAAA records but no IPv6 connectivity + +**Fix**: Container has `IPV6=no` in network config and `gai.conf` labels. If issues persist, check `RES_OPTIONS=inet4` in botserver.service. + +### Vault Connection & Service Discovery Issues + +**Symptom**: Logs show `Failed to read data directory ` or `Config scan failed` + +**Cause**: Botserver is using hardcoded development paths instead of production paths + +**Fix**: + +1. **Check current configuration**: + ```bash + # Check .env file + sudo incus exec system -- cat /opt/gbo/bin/.env + + # Check data directory + sudo incus exec system -- ls -la /opt/gbo/data/ + sudo incus exec system -- ls -la /opt/gbo/work/ + ``` + +2. **Verify Vault connection**: + ```bash + # Test Vault from system container + sudo incus exec system -- curl -k -sf https://:8200/v1/sys/health + + # Check Vault token + sudo incus exec system -- grep VAULT_TOKEN /opt/gbo/bin/.env + ``` + +3. **Check service discovery**: + ```bash + # Check if botserver is reading Vault secrets + sudo incus exec system -- tail -100 /opt/gbo/logs/out.log | grep -i vault + + # Check for service configuration errors + sudo incus exec system -- tail -100 /opt/gbo/logs/err.log | grep -i "config\|service" + ``` + +4. **Fix data directory paths**: + - Ensure botserver uses `/opt/gbo/data/` instead of development paths + - Update configuration if hardcoded paths exist + - Restart botserver after fixing + +5. **Verify all services are accessible**: + ```bash + # Check PostgreSQL + sudo incus exec system -- pg_isready -h -p 5432 + + # Check Valkey + sudo incus exec system -- redis-cli -h -a ping + + # Check MinIO + sudo incus exec system -- curl -sf http://:9100/minio/health/live + ``` + +6. **Update botserver configuration**: + - Ensure botserver reads from `/opt/gbo/bin/.env` for Vault configuration + - Verify service discovery uses Vault to get service endpoints + - Check that data directory is set to `/opt/gbo/data/` in configuration + - Update systemd service if needed: + ```bash + sudo incus exec system -- cat /etc/systemd/system/botserver.service + # Ensure EnvironmentFile=/opt/gbo/bin/.env is present + ``` + +7. **Test after fixes**: + ```bash + # Restart botserver + sudo incus exec system -- systemctl restart botserver + + # Wait for startup + sleep 10 + + # Check logs for errors + sudo incus exec system -- tail -50 /opt/gbo/logs/err.log + + # Verify health endpoint + curl -sf http://:5858/health + ``` + +### Vault Connection Errors + +**Symptom**: `Vault connection failed` or `Vault token invalid` + +**Fix**: +```bash +# Check Vault is running +sudo incus exec vault -- systemctl status vault + +# Check Vault health +sudo incus exec vault -- curl -k -sf https://localhost:8200/v1/sys/health + +# Verify token is valid +sudo incus exec system -- bash -c ' + export VAULT_ADDR=https://:8200 + export VAULT_TOKEN= + export VAULT_CACERT=/opt/gbo/conf/system/certificates/ca/ca.crt + vault token lookup +' + +# If token is invalid, generate new one +sudo incus exec vault -- bash -c ' + export VAULT_ADDR=https://localhost:8200 + export VAULT_TOKEN= + vault token create -policy="botserver" -ttl="8760h" +' + +# Update .env with new token +sudo incus exec system -- sed -i 's|VAULT_TOKEN=.*|VAULT_TOKEN=|' /opt/gbo/bin/.env +sudo incus exec system -- systemctl restart botserver +``` + +### Service Discovery Failures + +**Symptom**: `Service not found` or `Failed to connect to service` + +**Fix**: +```bash +# Check if service is running +sudo incus exec tables -- systemctl status postgresql +sudo incus exec cache -- systemctl status valkey +sudo incus exec drive -- systemctl status minio + +# Check if service is accessible from system container +sudo incus exec system -- nc -zv 5432 # PostgreSQL +sudo incus exec system -- nc -zv 6379 # Valkey +sudo incus exec system -- nc -zv 9100 # MinIO + +# Check Vault has service configuration +sudo incus exec system -- bash -c ' + export VAULT_ADDR=https://:8200 + export VAULT_TOKEN= + export VAULT_CACERT=/opt/gbo/conf/system/certificates/ca/ca.crt + vault kv list secret/botserver +' + +# If service config is missing, add it (see Vault Configuration section) +``` + +### Monitoring & Verification + +**Check botserver is working correctly**: +```bash +# Health check +curl -sf http://:5858/health + +# Check logs for errors +sudo incus exec system -- tail -100 /opt/gbo/logs/err.log | grep -i "error\|fail" + +# Check logs for successful service connections +sudo incus exec system -- tail -100 /opt/gbo/logs/out.log | grep -i "connected\|service\|vault" + +# Verify data directory is correct +sudo incus exec system -- tail -100 /opt/gbo/logs/out.log | grep -i "data\|work" + +# Should show /opt/gbo/data/ and /opt/gbo/work/, not development paths +``` + +**Expected log output**: +``` +info vault:Connected to Vault at https://:8200 +info service_discovery:Loaded service configuration from Vault +info database:Connected to PostgreSQL at :5432 +info cache:Connected to Valkey at :6379 +info storage:Connected to MinIO at http://:9100 +info watcher:Watching data directory /opt/gbo/data +info botserver:BotServer started successfully on port 5858 +``` + +**If logs show errors**: +1. Check Vault connection (see Vault Connection Errors section) +2. Check service accessibility (see Service Discovery Failures section) +3. Fix data directory paths (see Fix Development Paths in Production section) +4. Restart botserver and verify again + +### Vault Backup & Restore + +**Create Vault snapshot**: +```bash +# Stop Vault +sudo incus exec vault -- systemctl stop vault + +# Create snapshot +sudo incus snapshot create vault manual-$(date +%Y-%m-%d-%H%M) + +# Start Vault +sudo incus exec vault -- systemctl start vault + +# Verify +sudo incus snapshot list vault +``` + +**Restore Vault from snapshot**: +```bash +# Stop Vault +sudo incus exec vault -- systemctl stop vault + +# List snapshots +sudo incus snapshot list vault + +# Restore from latest snapshot +sudo incus snapshot restore vault + +# Start Vault +sudo incus exec vault -- systemctl start vault + +# Verify Vault is running +sudo incus exec vault -- systemctl status vault +sudo incus exec vault -- curl -k -sf https://localhost:8200/v1/sys/health +``` + +**Automated snapshots**: +```bash +# Create cron job for daily snapshots +sudo incus exec vault -- bash -c 'cat > /etc/cron.daily/vault-snapshot << EOF +#!/bin/bash +systemctl stop vault +incus snapshot create vault daily-$(date +\%Y\%m\%d) +systemctl start vault +EOF +chmod +x /etc/cron.daily/vault-snapshot' +``` + +### Update Botserver for Production + +**Required changes in botserver code**: + +1. **Read configuration from Vault**: + - Add Vault client initialization + - Read service endpoints from Vault + - Read secrets from Vault + - Fallback to environment variables if Vault is unavailable + +2. **Use production paths**: + - Remove hardcoded development paths + - Use environment variables for data directory + - Default to `/opt/gbo/data/` for production + +3. **Update .env file**: + ```bash + # /opt/gbo/bin/.env + VAULT_ADDR=https://:8200 + VAULT_TOKEN= + VAULT_CACERT=/opt/gbo/conf/system/certificates/ca/ca.crt + DATA_DIR=/opt/gbo/data/ + WORK_DIR=/opt/gbo/work/ + PORT=5858 + ``` + +4. **Update systemd service**: + ```bash + sudo incus exec system -- cat > /etc/systemd/system/botserver.service << 'EOF' + [Unit] + Description=BotServer Service + After=network.target + + [Service] + User=root + Group=root + WorkingDirectory=/opt/gbo/bin + EnvironmentFile=/opt/gbo/bin/.env + ExecStart=/opt/gbo/bin/botserver --noconsole + Restart=always + RestartSec=5 + StandardOutput=append:/opt/gbo/logs/out.log + StandardError=append:/opt/gbo/logs/err.log + + [Install] + WantedBy=multi-user.target + EOF + + sudo incus exec system -- systemctl daemon-reload + sudo incus exec system -- systemctl restart botserver + ``` + +5. **Deploy updated botserver**: + ```bash + # Push changes to ALM + cd botserver && git push alm main && git push origin main + + # CI will build and deploy automatically + # Or manually deploy (see Manual Deploy section) + ``` + +## Security + +- **NEVER** push secrets to git +- **NEVER** commit files to root with credentials +- **Vault** is single source of truth for secrets +- **CI/CD** is the only deployment method — never manually scp binaries +- **ALM** is production — ask before pushing diff --git a/botserver b/botserver index dc933c2..918cb62 160000 --- a/botserver +++ b/botserver @@ -1 +1 @@ -Subproject commit dc933c22e4a14dd1ee2acfe342e6b5e7f54c802a +Subproject commit 918cb623a17db5f35aaf56ff49945f1052b1e73a diff --git a/prompts/htmlview.md b/prompts/htmlview.md new file mode 100644 index 0000000..de5dc48 --- /dev/null +++ b/prompts/htmlview.md @@ -0,0 +1 @@ +AYVRWxru3Ciwlw7E GXmWnXQYXjn1OoK4kWnY3579FJVYTGBT \ No newline at end of file diff --git a/prompts/prod.md b/prompts/prod.md deleted file mode 100644 index d7a7c37..0000000 --- a/prompts/prod.md +++ /dev/null @@ -1,259 +0,0 @@ -# Production Environment Guide - -## Infrastructure - -### Servers - -| Host | IP | Purpose | -|------|-----|---------| -| `system` | `10.157.134.196` | Main botserver + botui container | -| `alm-ci` | `10.157.134.200` | CI/CD runner (Forgejo Actions) | -| `alm` | `10.157.134.34` | Forgejo git server | -| `dns` | `10.157.134.214` | DNS container | -| `drive` | `10.157.134.206` | Drive storage | -| `email` | `10.157.134.40` | Email service | -| `proxy` | `10.157.134.241` | Reverse proxy | -| `tables` | `10.157.134.174` | PostgreSQL | -| `table-editor` | `10.157.134.184` | Table editor | -| `webmail` | `10.157.134.86` | Webmail | - -### Port Mapping (system container) - -| Service | Internal Port | External URL | -|---------|--------------|--------------| -| botserver | `5858` | `https://system.pragmatismo.com.br` | -| botui | `5859` | `https://chat.pragmatismo.com.br` | - -### Access - -```bash -# SSH to host -ssh administrator@63.141.255.9 - -# Execute inside system container -sudo incus exec system -- bash -c 'command' - -# SSH from host to container (used by CI) -ssh -o StrictHostKeyChecking=no system "command" -``` - -## Services - -### botserver.service - -- **Binary**: `/opt/gbo/bin/botserver` -- **Port**: `5858` -- **User**: `gbuser` -- **Logs**: `/opt/gbo/logs/out.log`, `/opt/gbo/logs/err.log` -- **Config**: `/etc/systemd/system/botserver.service` -- **Env**: `PORT=5858` - -### ui.service - -- **Binary**: `/opt/gbo/bin/botui` -- **Port**: `5859` -- **Config**: `/etc/systemd/system/ui.service` -- **Env**: `BOTSERVER_URL=http://localhost:5858` - - ⚠️ MUST be `http://localhost:5858` — NOT `https://system.pragmatismo.com.br` - - Rust proxy runs server-side, needs direct localhost access - - JS client uses relative URLs through `chat.pragmatismo.com.br` - -### Data Directory - -- **Path**: `/opt/gbo/data/` -- **Structure**: `.gbai/.gbdialog/*.bas` -- **Bots**: cristo, fema, jucees, oerlabs, poupatempo, pragmatismogb, salesianos, sentient, seplagse -- **Work dir**: `/opt/gbo/work/` (compiled .ast cache) - -### Stack Services (managed by botserver bootstrap) - -- **Vault**: Secrets management -- **PostgreSQL**: Database (port 5432) -- **Valkey**: Cache (port 6379, password auth) -- **MinIO**: Object storage -- **Zitadel**: Identity provider -- **LLM**: llama.cpp - -## CI/CD Pipeline - -### Repositories - -| Repo | ALM URL | GitHub URL | -|------|---------|------------| -| gb | `https://alm.pragmatismo.com.br/GeneralBots/gb.git` | `git@github.com:GeneralBots/gb.git` | -| botserver | `https://alm.pragmatismo.com.br/GeneralBots/BotServer.git` | `git@github.com:GeneralBots/botserver.git` | -| botui | `https://alm.pragmatismo.com.br/GeneralBots/BotUI.git` | `git@github.com:GeneralBots/botui.git` | -| botlib | `https://alm.pragmatismo.com.br/GeneralBots/botlib.git` | `git@github.com:GeneralBots/botlib.git` | - -### Push Order - -```bash -# 1. Push submodules first -cd botserver && git push alm main && git push origin main && cd .. -cd botui && git push alm main && git push origin main && cd .. - -# 2. Update root workspace references -git add botserver botui botlib -git commit -m "Update submodules: " -git push alm main && git push origin main -``` - -### Build Environment - -- **CI runner**: `alm-ci` container (Debian Trixie, glibc 2.41) -- **Target**: `system` container (Debian 12 Bookworm, glibc 2.36) -- **⚠️ GLIBC MISMATCH**: Building on CI runner produces binaries incompatible with system container -- **Solution**: CI workflow transfers source to system container and builds there via SSH - -### Workflow File - -- **Location**: `botserver/.forgejo/workflows/botserver.yaml` -- **Triggers**: Push to `main` branch -- **Steps**: - 1. Setup workspace on CI runner (clone repos) - 2. Transfer source to system container via `tar | ssh` - 3. Build inside system container (matches glibc 2.36) - 4. Deploy binary inside container - 5. Verify botserver is running - -## Common Operations - -### Check Service Status - -```bash -# From host -sudo incus exec system -- systemctl status botserver --no-pager -sudo incus exec system -- systemctl status ui --no-pager - -# Check if running -sudo incus exec system -- pgrep -f botserver -sudo incus exec system -- pgrep -f botui -``` - -### View Logs - -```bash -# Systemd journal -sudo incus exec system -- journalctl -u botserver --no-pager -n 50 -sudo incus exec system -- journalctl -u ui --no-pager -n 50 - -# Application logs -sudo incus exec system -- tail -50 /opt/gbo/logs/out.log -sudo incus exec system -- tail -50 /opt/gbo/logs/err.log - -# Live tail -sudo incus exec system -- tail -f /opt/gbo/logs/out.log -``` - -### Restart Services - -```bash -sudo incus exec system -- systemctl restart botserver -sudo incus exec system -- systemctl restart ui -``` - -### Manual Deploy (emergency) - -```bash -# Kill old process -sudo incus exec system -- killall botserver - -# Copy binary (from host CI workspace or local) -sudo incus exec system -- cp /opt/gbo/ci/botserver/target/debug/botserver /opt/gbo/bin/botserver -sudo incus exec system -- chmod +x /opt/gbo/bin/botserver -sudo incus exec system -- chown gbuser:gbuser /opt/gbo/bin/botserver - -# Start service -sudo incus exec system -- systemctl start botserver -``` - -### Transfer Bot Files to Production - -```bash -# From local to prod host -tar czf /tmp/bots.tar.gz -C /opt/gbo/data .gbai -scp /tmp/bots.tar.gz administrator@63.141.255.9:/tmp/ - -# From host to container -sudo incus exec system -- bash -c 'tar xzf /tmp/bots.tar.gz -C /opt/gbo/data/' - -# Clear compiled cache -sudo incus exec system -- find /opt/gbo/data -name "*.ast" -delete -sudo incus exec system -- find /opt/gbo/work -name "*.ast" -delete -``` - -### Snapshots - -```bash -# List snapshots -sudo incus snapshot list system - -# Restore snapshot -sudo incus snapshot restore system -``` - -## Troubleshooting - -### GLIBC Version Mismatch - -**Symptom**: `GLIBC_2.39 not found` or `GLIBC_2.38 not found` - -**Cause**: Binary compiled on CI runner (glibc 2.41) but runs in system container (glibc 2.36) - -**Fix**: CI workflow must build inside the system container. Check `botserver.yaml` uses SSH to build in container. - -### botserver Not Starting - -```bash -# Check binary -sudo incus exec system -- ldd /opt/gbo/bin/botserver | grep "not found" - -# Check direct execution -sudo incus exec system -- timeout 10 /opt/gbo/bin/botserver 2>&1 - -# Check data directory -sudo incus exec system -- ls -la /opt/gbo/data/ -``` - -### botui Can't Reach botserver - -```bash -# Check BOTSERVER_URL -sudo incus exec system -- grep BOTSERVER_URL /etc/systemd/system/ui.service - -# Must be http://localhost:5858, NOT https://system.pragmatismo.com.br -# Fix: -sudo incus exec system -- sed -i 's|BOTSERVER_URL=.*|BOTSERVER_URL=http://localhost:5858|' /etc/systemd/system/ui.service -sudo incus exec system -- systemctl daemon-reload -sudo incus exec system -- systemctl restart ui -``` - -### Suggestions Not Showing - -```bash -# Check bot files exist -sudo incus exec system -- ls -la /opt/gbo/data/.gbai/.gbdialog/ - -# Check for compilation errors -sudo incus exec system -- tail -50 /opt/gbo/logs/out.log | grep -i "error\|fail\|compile" - -# Clear cache and restart -sudo incus exec system -- find /opt/gbo/work -name "*.ast" -delete -sudo incus exec system -- systemctl restart botserver -``` - -### IPv6 DNS Issues - -**Symptom**: External API calls (Groq, Cloudflare) timeout - -**Cause**: Container DNS returns AAAA records but no IPv6 connectivity - -**Fix**: Container has `IPV6=no` in network config and `gai.conf` labels. If issues persist, check `RES_OPTIONS=inet4` in botserver.service. - -## Security - -- **NEVER** push secrets to git -- **NEVER** commit files to root with credentials -- **Vault** is single source of truth for secrets -- **CI/CD** is the only deployment method — never manually scp binaries -- **ALM** is production — ask before pushing diff --git a/restart-fast.sh b/restart-fast.sh deleted file mode 100644 index 2e5f0e1..0000000 --- a/restart-fast.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash - -echo "=== Fast Restart: botserver + botmodels only ===" - -# Kill only the app services, keep infra running -pkill -f "botserver --noconsole" || true -pkill -f "botmodels" || true - -# Clean logs -rm -f botserver.log botmodels.log - -# Build only botserver (botui likely already built) -cargo build -p botserver - -# Start botmodels -cd botmodels -source venv/bin/activate -uvicorn src.main:app --host 0.0.0.0 --port 8085 > ../botmodels.log 2>&1 & -echo " botmodels PID: $!" -cd .. - -# Wait for botmodels -for i in $(seq 1 20); do - if curl -s http://localhost:8085/api/health > /dev/null 2>&1; then - echo " botmodels ready" - break - fi - sleep 1 -done - -# Start botserver (keep botui running if already up) -if ! pgrep -f "botui" > /dev/null; then - echo "Starting botui..." - cargo build -p botui - cd botui - BOTSERVER_URL="http://localhost:8080" ./target/debug/botui > ../botui.log 2>&1 & - echo " botui PID: $!" - cd .. -fi - -# Start botserver -BOTMODELS_HOST="http://localhost:8085" BOTMODELS_API_KEY="starter" RUST_LOG=info ./target/debug/botserver --noconsole > botserver.log 2>&1 & -echo " botserver PID: $!" - -# Quick health check -sleep 2 -curl -s http://localhost:8080/health > /dev/null 2>&1 && echo "✅ botserver ready" || echo "❌ botserver failed" - -echo "Done. botserver $(pgrep -f 'botserver --noconsole') botui $(pgrep -f botui) botmodels $(pgrep -f botmodels)" diff --git a/restart.sh b/restart.sh index 4dd8efd..2e5f0e1 100755 --- a/restart.sh +++ b/restart.sh @@ -1,55 +1,49 @@ #!/bin/bash -set -e -echo "=== Fast Restart: botserver only (keeps infra running) ===" +echo "=== Fast Restart: botserver + botmodels only ===" -# Only kill the app services, keep infra (postgres, valkey, minio, vault, zitadel) running +# Kill only the app services, keep infra running pkill -f "botserver --noconsole" || true pkill -f "botmodels" || true -# Clean app logs only +# Clean logs rm -f botserver.log botmodels.log -# Build botserver (incremental, should be fast) +# Build only botserver (botui likely already built) cargo build -p botserver -# Start botmodels if not running -if ! pgrep -f "botmodels" > /dev/null; then - echo "Starting botmodels..." - cd botmodels - source venv/bin/activate - uvicorn src.main:app --host 0.0.0.0 --port 8085 > ../botmodels.log 2>&1 & - echo " botmodels PID: $!" - cd .. - - # Wait for botmodels - for i in $(seq 1 15); do - if curl -s http://localhost:8085/api/health > /dev/null 2>&1; then - echo " botmodels ready" - break - fi - sleep 1 - done -else - echo " botmodels already running" -fi +# Start botmodels +cd botmodels +source venv/bin/activate +uvicorn src.main:app --host 0.0.0.0 --port 8085 > ../botmodels.log 2>&1 & +echo " botmodels PID: $!" +cd .. -# Start botserver -echo "Starting botserver..." -BOTMODELS_HOST="http://localhost:8085" BOTMODELS_API_KEY="starter" RUST_LOG=info \ - ./target/debug/botserver --noconsole > botserver.log 2>&1 & -echo " botserver PID: $!" - -# Wait for botserver health with timeout -echo "Waiting for botserver..." -for i in $(seq 1 10); do - if curl -sf http://localhost:8080/health > /dev/null 2>&1; then - echo "✅ botserver ready" - exit 0 +# Wait for botmodels +for i in $(seq 1 20); do + if curl -s http://localhost:8085/api/health > /dev/null 2>&1; then + echo " botmodels ready" + break fi sleep 1 done -echo "❌ botserver failed to start - check botserver.log" -tail -20 botserver.log -exit 1 +# Start botserver (keep botui running if already up) +if ! pgrep -f "botui" > /dev/null; then + echo "Starting botui..." + cargo build -p botui + cd botui + BOTSERVER_URL="http://localhost:8080" ./target/debug/botui > ../botui.log 2>&1 & + echo " botui PID: $!" + cd .. +fi + +# Start botserver +BOTMODELS_HOST="http://localhost:8085" BOTMODELS_API_KEY="starter" RUST_LOG=info ./target/debug/botserver --noconsole > botserver.log 2>&1 & +echo " botserver PID: $!" + +# Quick health check +sleep 2 +curl -s http://localhost:8080/health > /dev/null 2>&1 && echo "✅ botserver ready" || echo "❌ botserver failed" + +echo "Done. botserver $(pgrep -f 'botserver --noconsole') botui $(pgrep -f botui) botmodels $(pgrep -f botmodels)" diff --git a/stop.sh b/stop.sh deleted file mode 100755 index cf7d0d3..0000000 --- a/stop.sh +++ /dev/null @@ -1,4 +0,0 @@ -pkill botui -pkill botserver -9 - -