chore: convert guru-connect to submodule; integrate ADR-008 + 2026-05-29 session log

guru-connect is now tracked as a submodule (azcomputerguru/guru-connect @ e3e95f8);
its working state was published to the GC repo first, so no content is lost. guru-rmm
advanced to include ADR-008 (GC integration boundary) replayed on top of the team's
Integrations Center / discovery advances. Includes the native-remote-control spec
(now inside the GC submodule), the versionable-products memory, and the session log.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-29 06:36:54 -07:00
parent 1dd88edba3
commit a00247bc74
138 changed files with 129 additions and 33995 deletions

View File

@@ -70,6 +70,7 @@
- [Mac gururmm setup pending](project_mac_gururmm_setup_pending.md) — ACTION REQUIRED: run `bash scripts/install-hooks.sh` in gururmm repo on Mikes-MacBook-Air before any RMM work
## Project
- [Only RMM & GC are versionable products](project_versionable_products.md) — GuruRMM + GuruConnect are the only products with own repos/submodules; everything else stays in the claudetools monorepo. Split only for independent pipeline OR versioned external consumer.
- [Quantum GoDaddy M365 tenant](project_quantum_godaddy_m365_tenant.md) — quantumwms.com parked in a GoDaddy-provisioned M365 tenant (id ddf3d2c9-b76c-40d9-a216-9f11a1a26f97, netorg18235235.onmicrosoft.com); blocks Pax8 migration until GoDaddy removed. Managed = no DNS takeover; need GoDaddy/GA access.
- [Cascades Migration Plan](project-cascades-migration-plan.md) — Active multi-day migration. Plan file: `C:\Users\Howard\.claude\plans\wise-discovering-panda.md`. Syncro ticket: #110680053. Resume: "resume the Cascades migration plan".
- [GuruRMM Development Principles](gururmm-development-principles.md) - MANDATORY: every feature needs full stack (backend, API, UI, docs, scalability). Product must work without AI agents (AI features are enhancements). Documented in guru-rmm/docs/DESIGN.md.

View File

@@ -0,0 +1,23 @@
---
name: project_versionable_products
description: Only GuruRMM and GuruConnect are versionable products (own repos/submodules); everything else stays in the claudetools monorepo
metadata:
type: project
---
GuruRMM (`azcomputerguru/gururmm`) and GuruConnect (`azcomputerguru/guru-connect`) are the
**only** versionable products. Each has its own Gitea repo + CI/CD + release cadence and is tracked
as a git **submodule** under `projects/msp-tools/` (guru-rmm, guru-connect). Confirmed by Mike 2026-05-29.
Everything else — the ClaudeTools API itself, quote-wizard, dataforth-dos, radio-show, msp-tools
scripts, `.claude/`, `wiki/`, `session-logs/` — stays **in the claudetools monorepo**. Do not split
them into their own repos.
**Why:** Separate repos are only justified by an independent release/deploy pipeline OR an external
consumer that integrates via a versioned contract (see RMM↔GC boundary, ADR-008). Every extra repo
multiplies submodule-sync burden — and an un-pushed vendored copy drifts (GC's repo was 4 months
stale until 2026-05-29 because edits stayed in the monorepo and were never pushed).
**How to apply:** When asked "should X be its own repo?", default to NO unless X ships/deploys on its
own pipeline or is consumed elsewhere through a versioned interface. For the two products, never edit
the submodule's files without committing + pushing inside the submodule. Relates to [[feedback_gururmm_builds]].

4
.gitmodules vendored
View File

@@ -2,3 +2,7 @@
path = projects/msp-tools/guru-rmm
url = https://git.azcomputerguru.com/azcomputerguru/gururmm.git
branch = main
[submodule "projects/msp-tools/guru-connect"]
path = projects/msp-tools/guru-connect
url = http://azcomputerguru@172.16.3.20:3000/azcomputerguru/guru-connect.git
branch = main

View File

@@ -1,145 +0,0 @@
name: Build and Test
on:
push:
branches:
- main
- develop
pull_request:
branches:
- main
jobs:
build-server:
name: Build Server (Linux)
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: x86_64-unknown-linux-gnu
override: true
components: rustfmt, clippy
- name: Cache Cargo dependencies
uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-server-${{ hashFiles('server/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-server-
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y pkg-config libssl-dev protobuf-compiler
- name: Check formatting
run: cd server && cargo fmt --all -- --check
- name: Run Clippy
run: cd server && cargo clippy --all-targets --all-features -- -D warnings
- name: Build server
run: |
cd server
cargo build --release --target x86_64-unknown-linux-gnu
- name: Run tests
run: |
cd server
cargo test --release
- name: Upload server binary
uses: actions/upload-artifact@v3
with:
name: guruconnect-server-linux
path: server/target/x86_64-unknown-linux-gnu/release/guruconnect-server
retention-days: 30
build-agent:
name: Build Agent (Windows)
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: x86_64-pc-windows-msvc
override: true
- name: Install cross-compilation tools
run: |
sudo apt-get update
sudo apt-get install -y mingw-w64
- name: Cache Cargo dependencies
uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-agent-${{ hashFiles('agent/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-agent-
- name: Build agent (cross-compile for Windows)
run: |
rustup target add x86_64-pc-windows-gnu
cd agent
cargo build --release --target x86_64-pc-windows-gnu
- name: Upload agent binary
uses: actions/upload-artifact@v3
with:
name: guruconnect-agent-windows
path: agent/target/x86_64-pc-windows-gnu/release/guruconnect.exe
retention-days: 30
security-audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install cargo-audit
run: cargo install cargo-audit
- name: Run security audit on server
run: cd server && cargo audit
- name: Run security audit on agent
run: cd agent && cargo audit
build-summary:
name: Build Summary
runs-on: ubuntu-latest
needs: [build-server, build-agent, security-audit]
steps:
- name: Build succeeded
run: |
echo "All builds completed successfully"
echo "Server: Linux x86_64"
echo "Agent: Windows x86_64"
echo "Security: Passed"

View File

@@ -1,88 +0,0 @@
name: Deploy to Production
on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
default: 'production'
type: choice
options:
- production
- staging
jobs:
deploy-server:
name: Deploy Server
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment || 'production' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: x86_64-unknown-linux-gnu
- name: Build server
run: |
cd server
cargo build --release --target x86_64-unknown-linux-gnu
- name: Create deployment package
run: |
mkdir -p deploy
cp server/target/x86_64-unknown-linux-gnu/release/guruconnect-server deploy/
cp -r server/static deploy/
cp -r server/migrations deploy/
cp server/.env.example deploy/.env.example
tar -czf guruconnect-server-${{ github.ref_name }}.tar.gz -C deploy .
- name: Upload deployment package
uses: actions/upload-artifact@v3
with:
name: deployment-package
path: guruconnect-server-${{ github.ref_name }}.tar.gz
retention-days: 90
- name: Deploy to server (production)
if: github.event.inputs.environment == 'production' || startsWith(github.ref, 'refs/tags/')
run: |
echo "Deployment command would run here"
echo "SSH to 172.16.3.30 and deploy"
# Actual deployment would use SSH keys and run:
# scp guruconnect-server-*.tar.gz guru@172.16.3.30:/tmp/
# ssh guru@172.16.3.30 'bash /home/guru/guru-connect/scripts/deploy.sh'
create-release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs: deploy-server
if: startsWith(github.ref, 'refs/tags/')
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v3
- name: Create Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref_name }}
release_name: Release ${{ github.ref_name }}
draft: false
prerelease: false
- name: Upload Release Assets
run: |
echo "Upload server and agent binaries to release"
# Would attach artifacts to the release here

View File

@@ -1,124 +0,0 @@
name: Run Tests
on:
push:
branches:
- main
- develop
- 'feature/**'
pull_request:
jobs:
test-server:
name: Test Server
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: x86_64-unknown-linux-gnu
components: rustfmt, clippy
- name: Cache Cargo dependencies
uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-test-${{ hashFiles('server/Cargo.lock') }}
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y pkg-config libssl-dev protobuf-compiler
- name: Run unit tests
run: |
cd server
cargo test --lib --release
- name: Run integration tests
run: |
cd server
cargo test --test '*' --release
- name: Run doc tests
run: |
cd server
cargo test --doc --release
test-agent:
name: Test Agent
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Run agent tests
run: |
cd agent
cargo test --release
code-coverage:
name: Code Coverage
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: llvm-tools-preview
- name: Install tarpaulin
run: cargo install cargo-tarpaulin
- name: Generate coverage report
run: |
cd server
cargo tarpaulin --out Xml --output-dir ../coverage
- name: Upload coverage to artifact
uses: actions/upload-artifact@v3
with:
name: coverage-report
path: coverage/
lint:
name: Lint and Format Check
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt, clippy
- name: Check formatting (server)
run: cd server && cargo fmt --all -- --check
- name: Check formatting (agent)
run: cd agent && cargo fmt --all -- --check
- name: Run clippy (server)
run: cd server && cargo clippy --all-targets --all-features -- -D warnings
- name: Run clippy (agent)
run: cd agent && cargo clippy --all-targets --all-features -- -D warnings

View File

@@ -1,629 +0,0 @@
# GuruConnect CI/CD Activation Guide
**Date:** 2026-01-18
**Status:** Ready for Activation
**Server:** 172.16.3.30 (gururmm)
---
## Prerequisites Complete
- [x] Gitea Actions workflows committed
- [x] Deployment automation scripts created
- [x] Gitea Actions runner binary installed
- [x] Systemd service configured
- [x] All documentation complete
---
## Step 1: Register Gitea Actions Runner
### 1.1 Get Registration Token
1. Open browser and navigate to:
```
https://git.azcomputerguru.com/admin/actions/runners
```
2. Log in with Gitea admin credentials
3. Click **"Create new Runner"**
4. Copy the registration token (starts with something like `D0g...`)
### 1.2 Register Runner on Server
```bash
# SSH to server
ssh guru@172.16.3.30
# Register runner with token from above
sudo -u gitea-runner act_runner register \
--instance https://git.azcomputerguru.com \
--token YOUR_REGISTRATION_TOKEN_HERE \
--name gururmm-runner \
--labels ubuntu-latest,ubuntu-22.04
```
**Expected Output:**
```
INFO Registering runner, arch=amd64, os=linux, version=0.2.11.
INFO Successfully registered runner.
```
### 1.3 Start Runner Service
```bash
# Reload systemd configuration
sudo systemctl daemon-reload
# Enable runner to start on boot
sudo systemctl enable gitea-runner
# Start runner service
sudo systemctl start gitea-runner
# Check status
sudo systemctl status gitea-runner
```
**Expected Output:**
```
● gitea-runner.service - Gitea Actions Runner
Loaded: loaded (/etc/systemd/system/gitea-runner.service; enabled)
Active: active (running) since Sat 2026-01-18 16:00:00 UTC
```
### 1.4 Verify Registration
1. Go back to: https://git.azcomputerguru.com/admin/actions/runners
2. Verify "gururmm-runner" appears in the list
3. Status should show: **Online** (green)
---
## Step 2: Test Build Workflow
### 2.1 Trigger First Build
```bash
# On server
cd ~/guru-connect
# Make empty commit to trigger CI
git commit --allow-empty -m "test: trigger CI/CD pipeline"
git push origin main
```
### 2.2 Monitor Build Progress
1. Open browser: https://git.azcomputerguru.com/azcomputerguru/guru-connect/actions
2. You should see a new workflow run: **"Build and Test"**
3. Click on the workflow run to view progress
4. Watch the jobs complete:
- Build Server (Linux) - ~2-3 minutes
- Build Agent (Windows) - ~2-3 minutes
- Security Audit - ~1 minute
- Build Summary - ~10 seconds
### 2.3 Expected Results
**Build Server Job:**
```
✓ Checkout code
✓ Install Rust toolchain
✓ Cache Cargo dependencies
✓ Install dependencies (pkg-config, libssl-dev, protobuf-compiler)
✓ Build server
✓ Upload server binary
```
**Build Agent Job:**
```
✓ Checkout code
✓ Install Rust toolchain
✓ Install cross-compilation tools
✓ Build agent
✓ Upload agent binary
```
**Security Audit Job:**
```
✓ Checkout code
✓ Install Rust toolchain
✓ Install cargo-audit
✓ Run security audit
```
### 2.4 Download Build Artifacts
1. Scroll down to **Artifacts** section
2. Download artifacts:
- `guruconnect-server-linux` (server binary)
- `guruconnect-agent-windows` (agent .exe)
3. Verify file sizes:
- Server: ~15-20 MB
- Agent: ~10-15 MB
---
## Step 3: Test Workflow
### 3.1 Trigger Test Suite
```bash
# Tests run automatically on push, or trigger manually:
cd ~/guru-connect
# Make a code change to trigger tests
echo "// Test comment" >> server/src/main.rs
git add server/src/main.rs
git commit -m "test: trigger test workflow"
git push origin main
```
### 3.2 Monitor Test Execution
1. Go to: https://git.azcomputerguru.com/azcomputerguru/guru-connect/actions
2. Click on **"Run Tests"** workflow
3. Watch jobs complete:
- Test Server - ~3-5 minutes
- Test Agent - ~2-3 minutes
- Code Coverage - ~4-6 minutes
- Lint - ~2-3 minutes
### 3.3 Expected Results
**Test Server Job:**
```
✓ Run unit tests
✓ Run integration tests
✓ Run doc tests
```
**Test Agent Job:**
```
✓ Run agent tests
```
**Code Coverage Job:**
```
✓ Install tarpaulin
✓ Generate coverage report
✓ Upload coverage artifact
```
**Lint Job:**
```
✓ Check formatting (server) - cargo fmt
✓ Check formatting (agent) - cargo fmt
✓ Run clippy (server) - zero warnings
✓ Run clippy (agent) - zero warnings
```
---
## Step 4: Test Deployment Workflow
### 4.1 Create Version Tag
```bash
# On server
cd ~/guru-connect/scripts
# Create first release tag (v0.1.0)
./version-tag.sh patch
```
**Expected Interaction:**
```
=========================================
GuruConnect Version Tagging
=========================================
Current version: v0.0.0
New version: v0.1.0
Changes since v0.0.0:
-------------------------------------------
5b7cf5f ci: add Gitea Actions workflows and deployment automation
[previous commits...]
-------------------------------------------
Create tag v0.1.0? (y/N) y
Updating Cargo.toml versions...
Updated server/Cargo.toml
Updated agent/Cargo.toml
Committing version bump...
[main abc1234] chore: bump version to v0.1.0
Creating tag v0.1.0...
Tag created successfully
To push tag to remote:
git push origin v0.1.0
```
### 4.2 Push Tag to Trigger Deployment
```bash
# Push the version bump commit
git push origin main
# Push the tag (this triggers deployment workflow)
git push origin v0.1.0
```
### 4.3 Monitor Deployment
1. Go to: https://git.azcomputerguru.com/azcomputerguru/guru-connect/actions
2. Click on **"Deploy to Production"** workflow
3. Watch deployment progress:
- Deploy Server - ~10-15 minutes
- Create Release - ~2-3 minutes
### 4.4 Expected Deployment Flow
**Deploy Server Job:**
```
✓ Checkout code
✓ Install Rust toolchain
✓ Build release binary
✓ Create deployment package
✓ Transfer to server (via SSH)
✓ Run deployment script
├─ Backup current version
├─ Stop service
├─ Deploy new binary
├─ Start service
├─ Health check
└─ Verify deployment
✓ Upload deployment artifact
```
**Create Release Job:**
```
✓ Create GitHub/Gitea release
✓ Upload release assets
├─ guruconnect-server-v0.1.0.tar.gz
├─ guruconnect-agent-v0.1.0.exe
└─ SHA256SUMS
```
### 4.5 Verify Deployment
```bash
# Check service status
sudo systemctl status guruconnect
# Check new version
~/guru-connect/target/x86_64-unknown-linux-gnu/release/guruconnect-server --version
# Should output: v0.1.0
# Check health endpoint
curl http://172.16.3.30:3002/health
# Should return: {"status":"OK"}
# Check backup created
ls -lh /home/guru/deployments/backups/
# Should show: guruconnect-server-20260118-HHMMSS
# Check artifact saved
ls -lh /home/guru/deployments/artifacts/
# Should show: guruconnect-server-v0.1.0.tar.gz
```
---
## Step 5: Test Manual Deployment
### 5.1 Download Deployment Artifact
```bash
# From Actions page, download: guruconnect-server-v0.1.0.tar.gz
# Or use artifact from server:
cd /home/guru/deployments/artifacts
ls -lh guruconnect-server-v0.1.0.tar.gz
```
### 5.2 Run Manual Deployment
```bash
cd ~/guru-connect/scripts
./deploy.sh /home/guru/deployments/artifacts/guruconnect-server-v0.1.0.tar.gz
```
**Expected Output:**
```
=========================================
GuruConnect Deployment Script
=========================================
Package: /home/guru/deployments/artifacts/guruconnect-server-v0.1.0.tar.gz
Target: /home/guru/guru-connect
Creating backup...
[OK] Backup created: /home/guru/deployments/backups/guruconnect-server-20260118-161500
Stopping GuruConnect service...
[OK] Service stopped
Extracting deployment package...
Deploying new binary...
[OK] Binary deployed
Archiving deployment package...
[OK] Artifact saved
Starting GuruConnect service...
[OK] Service started successfully
Running health check...
[OK] Health check: PASSED
Deployment version information:
GuruConnect Server v0.1.0
=========================================
Deployment Complete!
=========================================
Deployment time: 20260118-161500
Backup location: /home/guru/deployments/backups/guruconnect-server-20260118-161500
Artifact location: /home/guru/deployments/artifacts/guruconnect-server-20260118-161500.tar.gz
```
---
## Troubleshooting
### Runner Not Starting
**Symptom:** `systemctl status gitea-runner` shows "inactive" or "failed"
**Solution:**
```bash
# Check logs
sudo journalctl -u gitea-runner -n 50
# Common issues:
# 1. Not registered - run registration command again
# 2. Wrong token - get new token from Gitea admin
# 3. Permissions - ensure gitea-runner user owns /home/gitea-runner/.runner
# Re-register if needed
sudo -u gitea-runner act_runner register \
--instance https://git.azcomputerguru.com \
--token NEW_TOKEN_HERE
```
### Workflow Not Triggering
**Symptom:** Push to main branch but no workflow appears in Actions tab
**Checklist:**
1. Is runner registered and online? (Check admin/actions/runners)
2. Are workflow files in `.gitea/workflows/` directory?
3. Did you push to the correct branch? (main or develop)
4. Are Gitea Actions enabled in repository settings?
**Solution:**
```bash
# Verify workflows committed
git ls-tree -r main --name-only | grep .gitea/workflows
# Should show:
# .gitea/workflows/build-and-test.yml
# .gitea/workflows/deploy.yml
# .gitea/workflows/test.yml
# If missing, add and commit:
git add .gitea/
git commit -m "ci: add missing workflows"
git push origin main
```
### Build Failing
**Symptom:** Build workflow shows red X
**Solution:**
```bash
# View logs in Gitea Actions tab
# Common issues:
# 1. Missing dependencies
# Add to workflow: apt-get install -y [package]
# 2. Rust compilation errors
# Fix code and push again
# 3. Test failures
# Run tests locally first: cargo test
# 4. Clippy warnings
# Fix warnings: cargo clippy --fix
```
### Deployment Failing
**Symptom:** Deploy workflow fails or service won't start after deployment
**Solution:**
```bash
# Check deployment logs
cat /home/guru/deployments/deploy-*.log
# Check service logs
sudo journalctl -u guruconnect -n 50
# Manual rollback if needed
ls /home/guru/deployments/backups/
cp /home/guru/deployments/backups/guruconnect-server-TIMESTAMP \
~/guru-connect/target/x86_64-unknown-linux-gnu/release/guruconnect-server
sudo systemctl restart guruconnect
```
### Health Check Failing
**Symptom:** Health check returns connection refused or timeout
**Solution:**
```bash
# Check if service is running
sudo systemctl status guruconnect
# Check if port is listening
netstat -tlnp | grep 3002
# Check server logs
sudo journalctl -u guruconnect -f
# Test manually
curl -v http://172.16.3.30:3002/health
# Common issues:
# 1. Service not started - sudo systemctl start guruconnect
# 2. Port blocked - check firewall
# 3. Database connection issue - check .env file
```
---
## Validation Checklist
After completing all steps, verify:
- [ ] Runner shows "Online" in Gitea admin panel
- [ ] Build workflow completes successfully (green checkmark)
- [ ] Test workflow completes successfully (all tests pass)
- [ ] Deployment workflow completes successfully
- [ ] Service restarts with new version
- [ ] Health check returns "OK"
- [ ] Backup created in `/home/guru/deployments/backups/`
- [ ] Artifact saved in `/home/guru/deployments/artifacts/`
- [ ] Build artifacts downloadable from Actions tab
- [ ] Version tag appears in repository tags
- [ ] Manual deployment script works
---
## Next Steps After Activation
### 1. Configure Deployment SSH Keys (Optional)
For fully automated deployment without manual intervention:
```bash
# Generate SSH key for runner
sudo -u gitea-runner ssh-keygen -t ed25519 -C "gitea-runner@gururmm"
# Add public key to authorized_keys
sudo -u gitea-runner cat /home/gitea-runner/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys
# Test SSH connection
sudo -u gitea-runner ssh guru@172.16.3.30 whoami
```
### 2. Set Up Notification Webhooks (Optional)
Configure Gitea to send notifications on build/deployment events:
1. Go to repository > Settings > Webhooks
2. Add webhook for Slack/Discord/Email
3. Configure triggers: Push, Pull Request, Release
### 3. Add More Runners (Optional)
For faster builds and multi-platform support:
- **Windows Runner:** For native Windows agent builds
- **macOS Runner:** For macOS agent builds
- **Staging Runner:** For staging environment deployments
### 4. Enhance CI/CD (Optional)
**Performance:**
- Add caching for dependencies
- Parallel test execution
- Incremental builds
**Quality:**
- Code coverage thresholds
- Performance benchmarks
- Security scanning (SAST/DAST)
**Deployment:**
- Staging environment
- Canary deployments
- Blue-green deployments
- Smoke tests after deployment
---
## Quick Reference Commands
```bash
# Runner management
sudo systemctl status gitea-runner
sudo systemctl restart gitea-runner
sudo journalctl -u gitea-runner -f
# Create version tag
cd ~/guru-connect/scripts
./version-tag.sh [major|minor|patch]
# Manual deployment
./deploy.sh /path/to/package.tar.gz
# View workflows
https://git.azcomputerguru.com/azcomputerguru/guru-connect/actions
# Check service
sudo systemctl status guruconnect
curl http://172.16.3.30:3002/health
# View logs
sudo journalctl -u guruconnect -f
# Rollback deployment
cp /home/guru/deployments/backups/guruconnect-server-TIMESTAMP \
~/guru-connect/target/x86_64-unknown-linux-gnu/release/guruconnect-server
sudo systemctl restart guruconnect
```
---
## Support Resources
**Gitea Actions Documentation:**
- Overview: https://docs.gitea.com/usage/actions/overview
- Workflow Syntax: https://docs.gitea.com/usage/actions/workflow-syntax
- Act Runner: https://gitea.com/gitea/act_runner
**Repository:**
- https://git.azcomputerguru.com/azcomputerguru/guru-connect
**Created Documentation:**
- `CI_CD_SETUP.md` - Complete CI/CD setup guide
- `PHASE1_WEEK3_COMPLETE.md` - Week 3 completion summary
- `ACTIVATE_CI_CD.md` - This guide
---
**Last Updated:** 2026-01-18
**Status:** Ready for Activation
**Action Required:** Register Gitea Actions runner with admin token

View File

@@ -1,182 +0,0 @@
{
"project": "GuruConnect",
"last_updated": "2026-01-18T03:30:00Z",
"current_phase": 1,
"current_week": 2,
"current_day": 1,
"deployment_status": "deployed_to_production",
"phases": {
"phase1": {
"name": "Security & Infrastructure",
"status": "in_progress",
"progress_percentage": 50,
"checklist_summary": {
"total_items": 147,
"completed": 74,
"in_progress": 0,
"pending": 73
},
"weeks": {
"week1": {
"name": "Critical Security Fixes",
"status": "complete",
"progress_percentage": 77,
"items_completed": 10,
"items_total": 13,
"completed_items": [
"SEC-1: Remove hardcoded JWT secret",
"SEC-1: Add JWT_SECRET environment variable",
"SEC-1: Validate JWT secret strength",
"SEC-3: SQL injection audit (verified safe)",
"SEC-4: IP address extraction and logging",
"SEC-4: Failed connection attempt logging",
"SEC-4: API key strength validation",
"SEC-5: Token blacklist implementation",
"SEC-5: JWT validation with revocation",
"SEC-5: Logout and revocation endpoints",
"SEC-5: Blacklist monitoring tools",
"SEC-5: Middleware integration",
"SEC-6: Remove password logging (write to .admin-credentials)",
"SEC-7: XSS prevention (CSP headers)",
"SEC-9: Verify Argon2id usage (explicitly configured)",
"SEC-11: CORS configuration review (restricted origins)",
"SEC-12: Security headers (6 headers implemented)",
"SEC-13: Session expiration enforcement (strict validation)",
"Production deployment to 172.16.3.30:3002",
"Security header verification via HTTP responses",
"IP logging operational verification"
],
"deferred_items": [
"SEC-2: Rate limiting (deferred - tower_governor type issues)",
"SEC-8: TLS certificate validation (not applicable - NPM handles)",
"SEC-10: HTTPS enforcement (delegated to NPM reverse proxy)"
]
},
"week2": {
"name": "Infrastructure & Monitoring",
"status": "starting",
"progress_percentage": 0,
"items_completed": 0,
"items_total": 8,
"pending_items": [
"Systemd service configuration",
"Auto-restart on failure",
"Prometheus metrics endpoint",
"Grafana dashboard setup",
"PostgreSQL automated backups",
"Backup retention policy",
"Log rotation configuration",
"Health check monitoring"
]
},
"week3": {
"name": "CI/CD & Automation",
"status": "not_started",
"progress_percentage": 0,
"items_total": 6,
"pending_items": [
"Gitea CI pipeline configuration",
"Automated builds on commit",
"Automated tests in CI",
"Deployment automation scripts",
"Build artifact storage",
"Version tagging automation"
]
},
"week4": {
"name": "Production Hardening",
"status": "not_started",
"progress_percentage": 0,
"items_total": 5,
"pending_items": [
"Load testing (50+ concurrent sessions)",
"Performance optimization",
"Database connection pooling",
"Security audit",
"Production deployment checklist"
]
}
}
},
"phase2": {
"name": "Core Features",
"status": "not_started",
"progress_percentage": 0,
"weeks": {
"week5": {
"name": "End-User Portal",
"status": "not_started"
},
"week6-8": {
"name": "One-Time Agent Download",
"status": "not_started"
},
"week9-12": {
"name": "Core Session Features",
"status": "not_started"
}
}
}
},
"recent_completions": [
{
"timestamp": "2026-01-17T18:00:00Z",
"item": "SEC-1: JWT Secret Security",
"notes": "Removed hardcoded secrets, added validation"
},
{
"timestamp": "2026-01-17T18:30:00Z",
"item": "SEC-3: SQL Injection Audit",
"notes": "Verified all queries safe"
},
{
"timestamp": "2026-01-17T19:00:00Z",
"item": "SEC-4: Agent Connection Validation",
"notes": "IP logging, failed connection tracking complete"
},
{
"timestamp": "2026-01-17T20:30:00Z",
"item": "SEC-5: Session Takeover Prevention",
"notes": "Token blacklist and revocation complete"
},
{
"timestamp": "2026-01-18T01:00:00Z",
"item": "SEC-6 through SEC-13 Implementation",
"notes": "Password file write, XSS prevention, Argon2id, CORS, security headers, JWT expiration"
},
{
"timestamp": "2026-01-18T02:00:00Z",
"item": "Production Deployment - Week 1 Security",
"notes": "All security fixes deployed to 172.16.3.30:3002, verified via curl and logs"
},
{
"timestamp": "2026-01-18T03:06:00Z",
"item": "Final Deployment Verification",
"notes": "All security headers operational, server stable (PID 3839055)"
}
],
"blockers": [
{
"item": "SEC-2: Rate Limiting",
"issue": "tower_governor type incompatibility with Axum 0.7",
"workaround": "Documented in SEC2_RATE_LIMITING_TODO.md - will revisit with custom middleware"
},
{
"item": "Database Connectivity",
"issue": "PostgreSQL password authentication failed",
"impact": "Cannot test token revocation end-to-end, server runs in memory-only mode",
"workaround": "Server operational without database persistence"
}
],
"next_milestone": {
"name": "Phase 1 Week 2 - Infrastructure Complete",
"target_date": "2026-01-25",
"deliverables": [
"Systemd service running with auto-restart",
"Prometheus metrics exposed",
"Grafana dashboard configured",
"Automated PostgreSQL backups",
"Log rotation configured"
]
}
}

View File

@@ -1,704 +0,0 @@
# GuruConnect Phase 1 Infrastructure Deployment - Checkpoint
**Checkpoint Date:** 2026-01-18
**Project:** GuruConnect Remote Desktop Solution
**Phase:** Phase 1 - Security, Infrastructure, CI/CD
**Status:** PRODUCTION READY (87% verified completion)
---
## Checkpoint Overview
This checkpoint captures the successful completion of GuruConnect Phase 1 infrastructure deployment. All core security systems, infrastructure monitoring, and continuous integration/deployment automation have been implemented, tested, and verified as production-ready.
**Checkpoint Creation Context:**
- Git Commit: 1bfd476
- Branch: main
- Files Changed: 39 (4185 insertions, 1671 deletions)
- Database Context ID: 6b3aa5a4-2563-4705-a053-df99d6e39df2
- Project ID: c3d9f1c8-dc2b-499f-a228-3a53fa950e7b
- Relevance Score: 9.0
---
## What Was Accomplished
### Week 1: Security Hardening
**Completed Items (9/13 - 69%)**
1. [OK] JWT Token Expiration Validation (24h lifetime)
- Explicit expiration checks implemented
- Configurable via JWT_EXPIRY_HOURS environment variable
- Validation enforced on every request
2. [OK] Argon2id Password Hashing
- Latest version (V0x13) with secure parameters
- Default configuration: 19456 KiB memory, 2 iterations
- All user passwords hashed before storage
3. [OK] Security Headers Implementation
- Content Security Policy (CSP)
- X-Frame-Options: DENY
- X-Content-Type-Options: nosniff
- X-XSS-Protection enabled
- Referrer-Policy configured
- Permissions-Policy defined
4. [OK] Token Blacklist for Logout
- In-memory HashSet with async RwLock
- Integrated into authentication flow
- Automatic cleanup of expired tokens
- Endpoints: /api/auth/logout, /api/auth/revoke-token, /api/auth/admin/revoke-user
5. [OK] API Key Validation
- 32-character minimum requirement
- Entropy checking implemented
- Weak pattern detection enabled
6. [OK] Input Sanitization
- Serde deserialization with strict types
- UUID validation in all handlers
- API key strength validation throughout
7. [OK] SQL Injection Protection
- sqlx compile-time query validation
- All database operations parameterized
- No dynamic SQL construction
8. [OK] XSS Prevention
- CSP headers prevent inline script execution
- Static HTML files from server/static/
- No user-generated content server-side rendering
9. [OK] CORS Configuration
- Restricted to specific origins (production domain + localhost)
- Limited to GET, POST, PUT, DELETE, OPTIONS
- Explicit header allowlist
- Credentials allowed
**Pending Items (3/13 - 23%)**
- [ ] TLS Certificate Auto-Renewal (Let's Encrypt with certbot)
- [ ] Session Timeout Enforcement (UI-side token expiration check)
- [ ] Comprehensive Audit Logging (beyond basic event logging)
**Incomplete Item (1/13 - 8%)**
- [WARNING] Rate Limiting on Auth Endpoints
- Code implemented but not operational
- Compilation issues with tower_governor dependency
- Documented in SEC2_RATE_LIMITING_TODO.md
- See recommendations below for mitigation
### Week 2: Infrastructure & Monitoring
**Completed Items (11/11 - 100%)**
1. [OK] Systemd Service Configuration
- Service file: /etc/systemd/system/guruconnect.service
- Runs as guru user
- Working directory configured
- Environment variables loaded
2. [OK] Auto-Restart on Failure
- Restart=on-failure policy
- 10-second restart delay
- Start limit: 3 restarts per 5-minute interval
3. [OK] Prometheus Metrics Endpoint (/metrics)
- Unauthenticated access (appropriate for internal monitoring)
- Supports all monitoring tools (Prometheus, Grafana, etc.)
4. [OK] 11 Metric Types Exposed
- requests_total (counter)
- request_duration_seconds (histogram)
- sessions_total (counter)
- active_sessions (gauge)
- session_duration_seconds (histogram)
- connections_total (counter)
- active_connections (gauge)
- errors_total (counter)
- db_operations_total (counter)
- db_query_duration_seconds (histogram)
- uptime_seconds (gauge)
5. [OK] Grafana Dashboard
- 10-panel dashboard configured
- Real-time metrics visualization
- Dashboard file: infrastructure/grafana-dashboard.json
6. [OK] Automated Daily Backups
- Systemd timer: guruconnect-backup.timer
- Scheduled daily at 02:00 UTC
- Persistent execution for missed runs
- Backup directory: /home/guru/backups/guruconnect/
7. [OK] Log Rotation Configuration
- Daily rotation frequency
- 30-day retention
- Compression enabled
- Systemd journal integration
8. [OK] Health Check Endpoint (/health)
- Unauthenticated access (appropriate for load balancers)
- Returns "OK" status string
9. [OK] Service Monitoring
- Systemd status integration
- Journal logging enabled
- SyslogIdentifier set for filtering
10. [OK] Prometheus Configuration
- Target: 172.16.3.30:3002
- Scrape interval: 15 seconds
- File: infrastructure/prometheus.yml
11. [OK] Grafana Configuration
- Grafana dashboard templates available
- Admin credentials: admin/admin (default)
- Port: 3000
### Week 3: CI/CD Automation
**Completed Items (10/11 - 91%)**
1. [OK] Gitea Actions Workflows (3 workflows)
- build-and-test.yml
- test.yml
- deploy.yml
2. [OK] Build Automation
- Rust toolchain setup
- Server and agent parallel builds
- Dependency caching enabled
- Formatting and Clippy checks
3. [OK] Test Automation
- Unit tests, integration tests, doc tests
- Code coverage with cargo-tarpaulin
- Clippy with -D warnings (zero tolerance)
4. [OK] Deployment Automation
- Triggered on version tags (v*.*.*)
- Manual dispatch option available
- Build, package, and release steps
5. [OK] Deployment Script with Rollback
- Location: scripts/deploy.sh
- Automatic backup creation
- Health check integration
- Automatic rollback on failure
6. [OK] Version Tagging Automation
- Location: scripts/version-tag.sh
- Semantic versioning support (major/minor/patch)
- Cargo.toml version updates
- Git tag creation
7. [OK] Build Artifact Management
- 30-day retention for build artifacts
- 90-day retention for deployment artifacts
- Artifact storage: /home/guru/deployments/artifacts/
8. [OK] Gitea Actions Runner Installation
- Act runner version 0.2.11
- Binary installation complete
- Directory structure configured
9. [OK] Systemd Service for Runner
- Service file created
- User: gitea-runner
- Proper startup configuration
10. [OK] Complete CI/CD Documentation
- CI_CD_SETUP.md (setup guide)
- ACTIVATE_CI_CD.md (activation instructions)
- PHASE1_WEEK3_COMPLETE.md (summary)
- Inline script documentation
**Pending Items (1/11 - 9%)**
- [ ] Gitea Actions Runner Registration
- Requires admin token from Gitea
- Instructions: https://git.azcomputerguru.com/admin/actions/runners
- Non-blocking: Manual deployments still possible
---
## Production Readiness Status
**Overall Assessment: APPROVED FOR PRODUCTION**
### Ready Immediately
- [OK] Core authentication system
- [OK] Session management
- [OK] Database operations with compiled queries
- [OK] Monitoring and metrics collection
- [OK] Health checks
- [OK] Automated backups
- [OK] Basic security hardening
### Required Before Full Activation
- [WARNING] Rate limiting via firewall (fail2ban recommended as temporary solution)
- [INFO] Gitea runner registration (non-critical for manual deployments)
### Recommended Within 30 Days
- [INFO] TLS certificate auto-renewal
- [INFO] Session timeout UI implementation
- [INFO] Comprehensive audit logging
---
## Git Commit Details
**Commit Hash:** 1bfd476
**Branch:** main
**Timestamp:** 2026-01-18
**Changes Summary:**
- Files changed: 39
- Insertions: 4185
- Deletions: 1671
**Commit Message:**
"feat: Complete Phase 1 infrastructure deployment with production monitoring"
**Key Files Modified:**
- Security implementations (auth/, middleware/)
- Infrastructure configuration (systemd/, monitoring/)
- CI/CD workflows (.gitea/workflows/)
- Documentation (*.md files)
- Deployment scripts (scripts/)
**Recovery Info:**
- Tag checkpoint: Use `git checkout 1bfd476` to restore
- Branch: Remains on main
- No breaking changes from previous commits
---
## Database Context Save Details
**Context Metadata:**
- Context ID: 6b3aa5a4-2563-4705-a053-df99d6e39df2
- Project ID: c3d9f1c8-dc2b-499f-a228-3a53fa950e7b
- Relevance Score: 9.0/10.0
- Context Type: phase_completion
- Saved: 2026-01-18
**Tags Applied:**
- guruconnect
- phase1
- infrastructure
- security
- monitoring
- ci-cd
- prometheus
- systemd
- deployment
- production
**Dense Summary:**
Phase 1 infrastructure deployment complete. Security: 9/13 items (JWT, Argon2, CSP, token blacklist, API key validation, input sanitization, SQL injection protection, XSS prevention, CORS). Infrastructure: 11/11 (systemd service, auto-restart, Prometheus metrics, Grafana dashboard, daily backups, log rotation, health checks). CI/CD: 10/11 (3 Gitea Actions workflows, deployment with rollback, version tagging). Production ready with documented pending items (rate limiting, TLS renewal, audit logging, runner registration).
**Usage for Context Recall:**
When resuming Phase 1 work or starting Phase 2, recall this context via:
```bash
curl -X GET "http://localhost:8000/api/conversation-contexts/recall?project_id=c3d9f1c8-dc2b-499f-a228-3a53fa950e7b&limit=5&min_relevance_score=8.0"
```
---
## Verification Summary
### Audit Results
- **Source:** PHASE1_COMPLETENESS_AUDIT.md (2026-01-18)
- **Auditor:** Claude Code
- **Overall Grade:** A- (87% verified completion, excellent quality)
### Completion by Category
- Security: 69% (9/13 complete, 3 pending, 1 incomplete)
- Infrastructure: 100% (11/11 complete)
- CI/CD: 91% (10/11 complete, 1 pending)
- **Phase Total:** 87% (30/35 complete, 4 pending, 1 incomplete)
### Discrepancies Found
- Rate limiting: Implemented in code but not operational (tower_governor type issues)
- All documentation accurately reflects implementation status
- Several unclaimed items actually completed (API key validation depth, token cleanup, metrics comprehensiveness)
---
## Infrastructure Overview
### Services Running
| Service | Status | Port | PID | Uptime |
|---------|--------|------|-----|--------|
| guruconnect | active | 3002 | 3947824 | running |
| prometheus | active | 9090 | active | running |
| grafana-server | active | 3000 | active | running |
### File Locations
| Component | Location |
|-----------|----------|
| Server Binary | ~/guru-connect/target/x86_64-unknown-linux-gnu/release/guruconnect-server |
| Static Files | ~/guru-connect/server/static/ |
| Database | PostgreSQL (localhost:5432/guruconnect) |
| Backups | /home/guru/backups/guruconnect/ |
| Deployment Backups | /home/guru/deployments/backups/ |
| Systemd Service | /etc/systemd/system/guruconnect.service |
| Prometheus Config | /etc/prometheus/prometheus.yml |
| Grafana Config | /etc/grafana/grafana.ini |
| Log Rotation | /etc/logrotate.d/guruconnect |
### Access Information
**GuruConnect Dashboard**
- URL: https://connect.azcomputerguru.com/dashboard
- Credentials: howard / AdminGuruConnect2026 (test account)
**Gitea Repository**
- URL: https://git.azcomputerguru.com/azcomputerguru/guru-connect
- Actions: https://git.azcomputerguru.com/azcomputerguru/guru-connect/actions
- Runner Admin: https://git.azcomputerguru.com/admin/actions/runners
**Monitoring Endpoints**
- Prometheus: http://172.16.3.30:9090
- Grafana: http://172.16.3.30:3000 (admin/admin)
- Metrics: http://172.16.3.30:3002/metrics
- Health: http://172.16.3.30:3002/health
---
## Performance Benchmarks
### Build Times (Expected)
- Server build: 2-3 minutes
- Agent build: 2-3 minutes
- Test suite: 1-2 minutes
- Total CI pipeline: 5-8 minutes
- Deployment: 10-15 minutes
### Deployment Performance
- Backup creation: ~1 second
- Service stop: ~2 seconds
- Binary deployment: ~1 second
- Service start: ~3 seconds
- Health check: ~2 seconds
- **Total deployment time:** ~10 seconds
### Monitoring
- Metrics scrape interval: 15 seconds
- Grafana refresh: 5 seconds
- Backup execution: 5-10 seconds
---
## Pending Items & Mitigation
### HIGH PRIORITY - Before Full Production
**Rate Limiting**
- Status: Code implemented, not operational
- Issue: tower_governor type resolution failures
- Current Risk: Vulnerable to brute force attacks
- Mitigation: Implement firewall-level rate limiting (fail2ban)
- Timeline: 1-3 hours to resolve
- Options:
- Option A: Fix tower_governor types (1-2 hours)
- Option B: Implement custom middleware (2-3 hours)
- Option C: Use Redis-based rate limiting (3-4 hours)
**Firewall Rate Limiting (Temporary)**
- Install fail2ban on server
- Configure rules for /api/auth/login endpoint
- Monitor for brute force attempts
- Timeline: 1 hour
### MEDIUM PRIORITY - Within 30 Days
**TLS Certificate Auto-Renewal**
- Status: Manual renewal required
- Issue: Let's Encrypt auto-renewal not configured
- Action: Install certbot with auto-renewal timer
- Timeline: 2-4 hours
- Impact: Prevents certificate expiration
**Session Timeout UI**
- Status: Server-side expiration works, UI redirect missing
- Action: Implement JavaScript token expiration check
- Impact: Improved security UX
- Timeline: 2-4 hours
**Comprehensive Audit Logging**
- Status: Basic event logging exists
- Action: Expand to full audit trail
- Timeline: 2-3 hours
- Impact: Regulatory compliance, forensics
### LOW PRIORITY - Non-Blocking
**Gitea Actions Runner Registration**
- Status: Installation complete, registration pending
- Timeline: 5 minutes
- Impact: Enables full CI/CD automation
- Alternative: Manual builds and deployments still work
- Action: Get token from admin dashboard and register
---
## Recommendations
### Immediate Actions (Before Launch)
1. Activate Rate Limiting via Firewall
```bash
sudo apt-get install fail2ban
# Configure for /api/auth/login
```
2. Register Gitea Runner
```bash
sudo -u gitea-runner act_runner register \
--instance https://git.azcomputerguru.com \
--token YOUR_REGISTRATION_TOKEN \
--name gururmm-runner
```
3. Test CI/CD Pipeline
- Trigger build: `git push origin main`
- Verify in Actions tab
- Test deployment tag creation
### Short-Term (Within 1 Month)
4. Configure TLS Auto-Renewal
```bash
sudo apt-get install certbot
sudo certbot renew --dry-run
```
5. Implement Session Timeout UI
- Add JavaScript token expiration detection
- Show countdown warning
- Redirect on expiration
6. Set Up Comprehensive Audit Logging
- Expand event logging coverage
- Implement retention policies
- Create audit dashboard
### Long-Term (Phase 2+)
7. Systemd Watchdog Implementation
- Add systemd crate to Cargo.toml
- Implement sd_notify calls
- Re-enable WatchdogSec in service file
8. Distributed Rate Limiting
- Implement Redis-based rate limiting
- Prepare for multi-instance deployment
---
## How to Restore from This Checkpoint
### Using Git
**Option 1: Checkout Specific Commit**
```bash
cd ~/guru-connect
git checkout 1bfd476
```
**Option 2: Create Tag for Easy Reference**
```bash
cd ~/guru-connect
git tag -a phase1-checkpoint-2026-01-18 -m "Phase 1 complete and verified" 1bfd476
git push origin phase1-checkpoint-2026-01-18
```
**Option 3: Revert to Checkpoint if Forward Work Fails**
```bash
cd ~/guru-connect
git reset --hard 1bfd476
git clean -fd
```
### Using Database Context
**Recall Full Context**
```bash
curl -X GET "http://localhost:8000/api/conversation-contexts/recall" \
-H "Authorization: Bearer $JWT_TOKEN" \
-d '{
"project_id": "c3d9f1c8-dc2b-499f-a228-3a53fa950e7b",
"context_id": "6b3aa5a4-2563-4705-a053-df99d6e39df2",
"tags": ["guruconnect", "phase1"]
}'
```
**Retrieve Checkpoint Metadata**
```bash
curl -X GET "http://localhost:8000/api/conversation-contexts/6b3aa5a4-2563-4705-a053-df99d6e39df2" \
-H "Authorization: Bearer $JWT_TOKEN"
```
### Using Documentation Files
**Key Files for Restoration Context:**
- PHASE1_COMPLETE.md - Status summary
- PHASE1_COMPLETENESS_AUDIT.md - Verification details
- INSTALLATION_GUIDE.md - Infrastructure setup
- CI_CD_SETUP.md - CI/CD configuration
- ACTIVATE_CI_CD.md - Runner activation
---
## Risk Assessment
### Mitigated Risks (Low)
- Service crashes: Auto-restart configured
- Disk space: Log rotation + backup cleanup
- Failed deployments: Automatic rollback
- Database issues: Daily backups (7-day retention)
### Monitored Risks (Medium)
- Database growth: Metrics configured, manual cleanup if needed
- Log volume: Rotation configured
- Metrics retention: Prometheus defaults (15 days)
### Unmitigated Risks (High) - Requires Action
- TLS certificate expiration: Requires certbot setup
- Brute force attacks: Requires rate limiting fix or firewall rules
- Security vulnerabilities: Requires periodic audits
---
## Code Quality Assessment
### Strengths
- Security markers (SEC-1 through SEC-13) throughout code
- Defense-in-depth approach
- Modern cryptographic standards (Argon2id, JWT)
- Compile-time SQL injection prevention
- Comprehensive monitoring (11 metric types)
- Automated backups with retention policies
- Health checks for all services
- Excellent documentation practices
### Areas for Improvement
- Rate limiting activation (tower_governor issues)
- TLS certificate management automation
- Comprehensive audit logging expansion
### Documentation Quality
- Honest status tracking
- Clear next steps documented
- Technical debt tracked systematically
- Multiple format guides (setup, troubleshooting, reference)
---
## Success Metrics
### Availability
- Target: 99.9% uptime
- Current: Service running with auto-restart
- Monitoring: Prometheus + Grafana + Health endpoint
### Performance
- Target: < 100ms HTTP response time
- Monitoring: HTTP request duration histogram
### Security
- Target: Zero successful unauthorized access
- Current: JWT auth + API keys + rate limiting (pending)
- Monitoring: Failed auth counter
### Deployments
- Target: < 15 minutes deployment
- Current: ~10 seconds deployment + CI pipeline
- Reliability: Automatic rollback on failure
---
## Documentation Index
**Status & Completion:**
- PHASE1_COMPLETE.md - Comprehensive Phase 1 summary
- PHASE1_COMPLETENESS_AUDIT.md - Detailed audit verification
- CHECKPOINT_2026-01-18.md - This document
**Setup & Configuration:**
- INSTALLATION_GUIDE.md - Complete infrastructure installation
- CI_CD_SETUP.md - CI/CD setup and configuration
- ACTIVATE_CI_CD.md - Runner activation and testing
- INFRASTRUCTURE_STATUS.md - Current status and next steps
**Reference:**
- DEPLOYMENT_COMPLETE.md - Week 2 summary
- PHASE1_WEEK3_COMPLETE.md - Week 3 summary
- SEC2_RATE_LIMITING_TODO.md - Rate limiting implementation details
- TECHNICAL_DEBT.md - Known issues and workarounds
- CLAUDE.md - Project guidelines and architecture
**Troubleshooting:**
- Quick reference commands for all systems
- Database issue resolution
- Monitoring and CI/CD troubleshooting
- Service management procedures
---
## Next Steps
### Immediate (Next 1-2 Days)
1. Implement firewall rate limiting (fail2ban)
2. Register Gitea Actions runner
3. Test CI/CD pipeline with test commit
4. Verify all services operational
### Short-Term (Next 1-4 Weeks)
1. Configure TLS auto-renewal
2. Implement session timeout UI
3. Complete rate limiting implementation
4. Set up comprehensive audit logging
### Phase 2 Preparation
- Multi-session support
- File transfer capability
- Chat enhancements
- Mobile dashboard
---
## Checkpoint Metadata
**Created:** 2026-01-18
**Status:** PRODUCTION READY
**Completion:** 87% verified (30/35 items)
**Overall Grade:** A- (excellent quality, documented pending items)
**Next Review:** After rate limiting implementation and runner registration
**Archived Files for Reference:**
- PHASE1_COMPLETE.md - Status documentation
- PHASE1_COMPLETENESS_AUDIT.md - Verification report
- All infrastructure configuration files
- All CI/CD workflow definitions
- All documentation guides
**To Resume Work:**
1. Checkout commit 1bfd476 or tag phase1-checkpoint-2026-01-18
2. Recall context: `c3d9f1c8-dc2b-499f-a228-3a53fa950e7b`
3. Review pending items section above
4. Follow "Immediate" next steps
---
**Checkpoint Complete**
**Ready for Production Deployment**
**Pending Items Documented and Prioritized**

View File

@@ -1,544 +0,0 @@
<!-- Document created on 2026-01-18 -->
# GuruConnect CI/CD Setup Guide
**Version:** Phase 1 Week 3
**Status:** Ready for Installation
**CI Platform:** Gitea Actions
---
## Overview
Automated CI/CD pipeline for GuruConnect using Gitea Actions:
- **Automated Builds** - Build server and agent on every commit
- **Automated Tests** - Run unit, integration, and security tests
- **Automated Deployment** - Deploy to production on version tags
- **Build Artifacts** - Store and version all build outputs
- **Version Tagging** - Automated semantic versioning
---
## Architecture
```
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Git Push │─────>│ Gitea Actions│─────>│ Deploy │
│ │ │ Workflows │ │ to Server │
└─────────────┘ └──────────────┘ └─────────────┘
├─ Build Server (Linux)
├─ Build Agent (Windows)
├─ Run Tests
├─ Security Audit
└─ Create Artifacts
```
---
## Workflows
### 1. Build and Test (`build-and-test.yml`)
**Triggers:**
- Push to `main` or `develop` branches
- Pull requests to `main`
**Jobs:**
- Build Server (Linux x86_64)
- Build Agent (Windows x86_64)
- Security Audit (cargo audit)
- Upload Artifacts (30-day retention)
**Artifacts:**
- `guruconnect-server-linux` - Server binary
- `guruconnect-agent-windows` - Agent binary (.exe)
### 2. Run Tests (`test.yml`)
**Triggers:**
- Push to any branch
- Pull requests
**Jobs:**
- Unit Tests (server & agent)
- Integration Tests
- Code Coverage
- Linting & Formatting
**Artifacts:**
- Coverage reports (XML)
### 3. Deploy to Production (`deploy.yml`)
**Triggers:**
- Push tags matching `v*.*.*` (e.g., v0.1.0)
- Manual workflow dispatch
**Jobs:**
- Build release version
- Create deployment package
- Deploy to production server (172.16.3.30)
- Create GitHub release
- Upload release assets
**Artifacts:**
- Deployment packages (90-day retention)
---
## Installation Steps
### 1. Install Gitea Actions Runner
```bash
# On the RMM server (172.16.3.30)
ssh guru@172.16.3.30
cd ~/guru-connect/scripts
sudo bash install-gitea-runner.sh
```
### 2. Register the Runner
```bash
# Get registration token from Gitea:
# https://git.azcomputerguru.com/admin/actions/runners
# Register runner
sudo -u gitea-runner act_runner register \
--instance https://git.azcomputerguru.com \
--token YOUR_REGISTRATION_TOKEN \
--name gururmm-runner \
--labels ubuntu-latest,ubuntu-22.04
```
### 3. Start the Runner Service
```bash
sudo systemctl daemon-reload
sudo systemctl enable gitea-runner
sudo systemctl start gitea-runner
sudo systemctl status gitea-runner
```
### 4. Upload Workflow Files
```bash
# From local machine
cd D:\ClaudeTools\projects\msp-tools\guru-connect
# Copy workflow files to server
scp -r .gitea guru@172.16.3.30:~/guru-connect/
# Copy scripts to server
scp scripts/deploy.sh guru@172.16.3.30:~/guru-connect/scripts/
scp scripts/version-tag.sh guru@172.16.3.30:~/guru-connect/scripts/
# Make scripts executable
ssh guru@172.16.3.30 "cd ~/guru-connect/scripts && chmod +x *.sh"
```
### 5. Commit and Push Workflows
```bash
# On server
ssh guru@172.16.3.30
cd ~/guru-connect
git add .gitea/ scripts/
git commit -m "ci: add Gitea Actions workflows and deployment automation"
git push origin main
```
---
## Usage
### Triggering Builds
**Automatic:**
- Push to `main` or `develop` → Runs build + test
- Create pull request → Runs all tests
- Push version tag → Deploys to production
**Manual:**
- Go to repository > Actions
- Select workflow
- Click "Run workflow"
### Creating a Release
```bash
# Use the version tagging script
cd ~/guru-connect/scripts
./version-tag.sh patch # Bump patch version (0.1.0 → 0.1.1)
./version-tag.sh minor # Bump minor version (0.1.1 → 0.2.0)
./version-tag.sh major # Bump major version (0.2.0 → 1.0.0)
# Push tag to trigger deployment
git push origin main
git push origin v0.1.1
```
### Manual Deployment
```bash
# Deploy from artifact
cd ~/guru-connect/scripts
./deploy.sh /path/to/guruconnect-server-v0.1.0.tar.gz
# Deploy latest
./deploy.sh /home/guru/deployments/artifacts/guruconnect-server-latest.tar.gz
```
---
## Monitoring
### View Workflow Runs
```
https://git.azcomputerguru.com/azcomputerguru/guru-connect/actions
```
### Check Runner Status
```bash
# On server
sudo systemctl status gitea-runner
# View logs
sudo journalctl -u gitea-runner -f
# In Gitea
https://git.azcomputerguru.com/admin/actions/runners
```
### View Build Artifacts
```
Repository > Actions > Workflow Run > Artifacts section
```
---
## Deployment Process
### Automated Deployment Flow
1. **Tag Creation** - Developer creates version tag
2. **Workflow Trigger** - `deploy.yml` starts automatically
3. **Build** - Compiles release binary
4. **Package** - Creates deployment tarball
5. **Transfer** - Copies to server (via SSH)
6. **Backup** - Saves current binary
7. **Stop Service** - Stops GuruConnect systemd service
8. **Deploy** - Extracts and installs new binary
9. **Start Service** - Restarts systemd service
10. **Health Check** - Verifies server is responding
11. **Rollback** - Automatic if health check fails
### Deployment Locations
```
Backups: /home/guru/deployments/backups/
Artifacts: /home/guru/deployments/artifacts/
Deploy Dir: /home/guru/guru-connect/
```
### Rollback
```bash
# List backups
ls -lh /home/guru/deployments/backups/
# Rollback to specific version
cp /home/guru/deployments/backups/guruconnect-server-TIMESTAMP \
~/guru-connect/target/x86_64-unknown-linux-gnu/release/guruconnect-server
sudo systemctl restart guruconnect
```
---
## Configuration
### Secrets (Required)
Configure in Gitea repository settings:
```
Repository > Settings > Secrets
```
**Required Secrets:**
- `SSH_PRIVATE_KEY` - SSH key for deployment to 172.16.3.30
- `SSH_HOST` - Deployment server host (172.16.3.30)
- `SSH_USER` - Deployment user (guru)
### Environment Variables
```yaml
# In workflow files
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: "-D warnings"
DEPLOY_SERVER: "172.16.3.30"
DEPLOY_USER: "guru"
```
---
## Troubleshooting
### Runner Not Starting
```bash
# Check status
sudo systemctl status gitea-runner
# View logs
sudo journalctl -u gitea-runner -n 50
# Verify registration
sudo -u gitea-runner cat /home/gitea-runner/.runner/.runner
# Re-register if needed
sudo -u gitea-runner act_runner register --instance https://git.azcomputerguru.com --token NEW_TOKEN
```
### Workflow Failing
**Check logs in Gitea:**
1. Go to Actions tab
2. Click on failed run
3. View job logs
**Common Issues:**
- Missing dependencies → Add to workflow
- Rust version mismatch → Update toolchain version
- Test failures → Fix tests before merging
### Deployment Failing
```bash
# Check deployment logs on server
cat /home/guru/deployments/deploy-TIMESTAMP.log
# Verify service status
sudo systemctl status guruconnect
# Check GuruConnect logs
sudo journalctl -u guruconnect -n 50
# Manual deployment
cd ~/guru-connect/scripts
./deploy.sh /path/to/package.tar.gz
```
### Artifacts Not Uploading
**Check retention settings:**
- Build artifacts: 30 days
- Deployment packages: 90 days
**Check storage:**
```bash
# On Gitea server
df -h
du -sh /var/lib/gitea/data/actions_artifacts/
```
---
## Security
### Runner Security
- Runner runs as dedicated `gitea-runner` user
- Limited permissions (no sudo)
- Isolated working directory
- Automatic cleanup after jobs
### Deployment Security
- SSH key-based authentication
- Automated backups before deployment
- Health checks before considering deployment successful
- Automatic rollback on failure
- Audit trail in deployment logs
### Artifact Security
- Artifacts stored with limited retention
- Accessible only to repository collaborators
- Build artifacts include checksums
---
## Performance
### Build Times (Estimated)
- Server build: ~2-3 minutes
- Agent build: ~2-3 minutes
- Tests: ~1-2 minutes
- Total pipeline: ~5-8 minutes
### Caching
Workflows use cargo cache to speed up builds:
- Cache hit: ~1 minute
- Cache miss: ~2-3 minutes
### Concurrent Builds
- Multiple workflows can run in parallel
- Limited by runner capacity (1 runner = 1 job at a time)
---
## Maintenance
### Runner Updates
```bash
# Stop runner
sudo systemctl stop gitea-runner
# Download new version
RUNNER_VERSION="0.2.12" # Update as needed
cd /tmp
wget https://dl.gitea.com/act_runner/${RUNNER_VERSION}/act_runner-${RUNNER_VERSION}-linux-amd64
sudo mv act_runner-* /usr/local/bin/act_runner
sudo chmod +x /usr/local/bin/act_runner
# Restart runner
sudo systemctl start gitea-runner
```
### Cleanup Old Artifacts
```bash
# Manual cleanup on server
rm /home/guru/deployments/backups/guruconnect-server-$(date -d '90 days ago' +%Y%m%d)*
rm /home/guru/deployments/artifacts/guruconnect-server-$(date -d '90 days ago' +%Y%m%d)*
```
### Monitor Disk Usage
```bash
# Check deployment directories
du -sh /home/guru/deployments/*
# Check runner cache
du -sh /home/gitea-runner/.cache/act/
```
---
## Best Practices
### Branching Strategy
```
main - Production-ready code
develop - Integration branch
feature/* - Feature branches
hotfix/* - Emergency fixes
```
### Version Tagging
- Use semantic versioning: `vMAJOR.MINOR.PATCH`
- MAJOR: Breaking changes
- MINOR: New features (backward compatible)
- PATCH: Bug fixes
### Commit Messages
```
feat: Add new feature
fix: Fix bug
docs: Update documentation
ci: CI/CD changes
chore: Maintenance tasks
test: Add/update tests
```
### Testing Before Merge
1. All tests must pass
2. No clippy warnings
3. Code formatted (cargo fmt)
4. Security audit passed
---
## Future Enhancements
### Phase 2 Improvements
- Add more test runners (Windows, macOS)
- Implement staging environment
- Add smoke tests post-deployment
- Configure Slack/email notifications
- Add performance benchmarking
- Implement canary deployments
- Add Docker container builds
### Monitoring Integration
- Send build metrics to Prometheus
- Grafana dashboard for CI/CD metrics
- Alert on failed deployments
- Track build duration trends
---
## Reference Commands
```bash
# Runner management
sudo systemctl status gitea-runner
sudo systemctl restart gitea-runner
sudo journalctl -u gitea-runner -f
# Deployment
cd ~/guru-connect/scripts
./deploy.sh <package.tar.gz>
# Version tagging
./version-tag.sh [major|minor|patch]
# Manual build
cd ~/guru-connect
cargo build --release --target x86_64-unknown-linux-gnu
# View artifacts
ls -lh /home/guru/deployments/artifacts/
# View backups
ls -lh /home/guru/deployments/backups/
```
---
## Support
**Documentation:**
- Gitea Actions: https://docs.gitea.com/usage/actions/overview
- Act Runner: https://gitea.com/gitea/act_runner
**Repository:**
- https://git.azcomputerguru.com/azcomputerguru/guru-connect
**Contact:**
- Open issue in Gitea repository
---
**Last Updated:** 2026-01-18
**Phase:** 1 Week 3 - CI/CD Automation
**Status:** Ready for Installation

View File

@@ -1,200 +0,0 @@
# GuruConnect - Project Guidelines
## Overview
GuruConnect is a remote desktop solution for MSPs, similar to ConnectWise ScreenConnect. It provides real-time screen sharing, remote control, and support session management.
## Architecture
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Dashboard │◄───────►│ GuruConnect │◄───────►│ GuruConnect │
│ (HTML/JS) │ WSS │ Server (Rust) │ WSS │ Agent (Rust) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
│ ▼
│ ┌─────────────────┐
└──────────────────►│ PostgreSQL │
└─────────────────┘
```
## Design Constraints
### Agent (Windows)
- **Target OS:** Windows 7 SP1 and later (including Server 2008 R2+)
- **Single binary:** Agent and viewer in one executable
- **No runtime dependencies:** Statically linked, no .NET or VC++ redistributables
- **Protocol handler:** `guruconnect://` URL scheme for launching viewer
- **Tray icon:** System tray presence with status and exit option
- **UAC aware:** Graceful handling of elevated/non-elevated contexts
- **Auto-install:** Detects if not installed and offers installation
### Server (Linux)
- **Target OS:** Ubuntu 22.04 LTS
- **Framework:** Axum for HTTP/WebSocket
- **Database:** PostgreSQL with sqlx (compile-time checked queries)
- **Static files:** Served from `server/static/`
- **No containers required:** Runs as systemd service or direct binary
### Protocol
- **Wire format:** Protocol Buffers (protobuf) for ALL client-server messages
- **Transport:** WebSocket over TLS (wss://)
- **Compression:** Zstd for video frames
- **Schema:** `proto/guruconnect.proto` is the source of truth
## Security Rules
### Authentication
- **Dashboard/API:** JWT tokens required for all endpoints except `/health` and `/api/auth/login`
- **Viewer WebSocket:** JWT token required in `token` query parameter
- **Agent WebSocket:** Must provide either:
- Valid support code (for ad-hoc support sessions)
- Valid API key (for persistent/managed agents)
- **Never** accept unauthenticated agent connections
### Credentials
- **Never** hardcode secrets in source code
- **Never** commit credentials to git
- Use environment variables for all secrets:
- `JWT_SECRET` - JWT signing key
- `DATABASE_URL` - PostgreSQL connection string
- `AGENT_API_KEY` - Optional shared key for agents
### Password Storage
- Use Argon2id for password hashing
- Never store plaintext passwords
## Coding Standards
### Rust
- Use `tracing` crate for logging (not `println!` or `log`)
- Use `anyhow` for error handling in binaries
- Use `thiserror` for library error types
- Prefer `async`/`await` over blocking code
- Run `cargo clippy` before commits
### Logging Levels
- `error!` - Failures that need attention
- `warn!` - Unexpected but handled situations
- `info!` - Normal operational messages (startup, connections, sessions)
- `debug!` - Detailed debugging info
- `trace!` - Very verbose, message-level tracing
### Naming
- Rust: `snake_case` for functions/variables, `PascalCase` for types
- Protobuf: `PascalCase` for messages, `snake_case` for fields
- Database: `snake_case` for tables and columns
## Build & Version
### Version Format
- Semantic versioning: `MAJOR.MINOR.PATCH`
- Build identification: `VERSION-GITHASH[-dirty]`
- Example: `0.1.0-48076e1` or `0.1.0-48076e1-dirty`
### Build Info (Agent)
The agent embeds at compile time:
- `VERSION` - Cargo.toml version
- `GIT_HASH` - Short commit hash (8 chars)
- `GIT_BRANCH` - Branch name
- `GIT_DIRTY` - "clean" or "dirty"
- `BUILD_TIMESTAMP` - UTC build time
- `BUILD_TARGET` - Target triple
### Commands
```bash
# Build agent (Windows)
cargo build -p guruconnect --release
# Build server (Linux, from Linux or cross-compile)
cargo build -p guruconnect-server --release --target x86_64-unknown-linux-gnu
# Check version
./guruconnect --version # Short: 0.1.0-48076e1
./guruconnect version-info # Full details
```
## Database Schema
### Key Tables
- `users` - Dashboard users (admin-created only)
- `machines` - Registered agents (persistent)
- `sessions` - Connection sessions (historical)
- `events` - Audit log
- `support_codes` - One-time support codes
### Conventions
- Primary keys: `id UUID DEFAULT gen_random_uuid()`
- Timestamps: `created_at TIMESTAMPTZ DEFAULT NOW()`
- Soft deletes: Prefer `deleted_at` over hard deletes for audit trail
- Foreign keys: Always with `ON DELETE CASCADE` or explicit handling
## File Structure
```
guru-connect/
├── agent/ # Windows agent + viewer
│ ├── src/
│ │ ├── main.rs # CLI entry point
│ │ ├── capture/ # Screen capture (DXGI, GDI)
│ │ ├── encoder/ # Video encoding
│ │ ├── input/ # Mouse/keyboard injection
│ │ ├── viewer/ # Native viewer window
│ │ ├── transport/ # WebSocket client
│ │ ├── session/ # Session management
│ │ ├── tray/ # System tray
│ │ └── install.rs # Installation & protocol handler
│ ├── build.rs # Build script (protobuf, version info)
│ └── Cargo.toml
├── server/ # Linux relay server
│ ├── src/
│ │ ├── main.rs # Server entry point
│ │ ├── relay/ # WebSocket relay handlers
│ │ ├── session/ # Session state management
│ │ ├── auth/ # JWT authentication
│ │ ├── api/ # REST API handlers
│ │ └── db/ # Database operations
│ ├── static/ # Dashboard HTML/JS/CSS
│ │ ├── login.html
│ │ ├── dashboard.html
│ │ ├── viewer.html
│ │ └── downloads/ # Agent binaries
│ ├── migrations/ # SQL migrations
│ └── Cargo.toml
├── proto/ # Protocol definitions
│ └── guruconnect.proto
└── CLAUDE.md # This file
```
## Deployment
### Server (172.16.3.30)
- **Binary:** `/home/guru/guru-connect/target/x86_64-unknown-linux-gnu/release/guruconnect-server`
- **Static:** `/home/guru/guru-connect/server/static/`
- **Startup:** `~/guru-connect/start-server.sh`
- **Port:** 3002 (proxied via NPM to connect.azcomputerguru.com)
### Agent Distribution
- **Download URL:** https://connect.azcomputerguru.com/downloads/guruconnect.exe
- **Auto-update:** Not yet implemented (future feature)
## Issue Tracking
Use Gitea issues: https://git.azcomputerguru.com/azcomputerguru/guru-connect/issues
Reference issues in commits:
- `Fixes #1` - Closes the issue
- `Related to #1` - Links without closing
## Testing Checklist
Before releasing:
- [ ] Agent connects with support code
- [ ] Agent connects with API key
- [ ] Viewer connects with JWT token
- [ ] Unauthenticated connections rejected
- [ ] Screen capture works (DXGI primary, GDI fallback)
- [ ] Mouse/keyboard input works
- [ ] Chat messages relay correctly
- [ ] Protocol handler launches viewer
- [ ] Tray icon shows correct status

View File

@@ -1,27 +0,0 @@
[workspace]
resolver = "2"
members = [
"agent",
"server",
]
[workspace.package]
version = "0.1.0"
edition = "2021"
authors = ["AZ Computer Guru"]
license = "Proprietary"
[workspace.dependencies]
# Shared dependencies across workspace
tokio = { version = "1", features = ["full"] }
tokio-tungstenite = { version = "0.24", features = ["native-tls"] }
prost = "0.13"
prost-types = "0.13"
bytes = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tracing = "0.1"
anyhow = "1"
thiserror = "1"
uuid = { version = "1", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }

View File

@@ -1,566 +0,0 @@
# GuruConnect Phase 1 Week 2 - Infrastructure Deployment COMPLETE
**Date:** 2026-01-18 15:38 UTC
**Server:** 172.16.3.30 (gururmm)
**Status:** ALL INFRASTRUCTURE OPERATIONAL ✓
---
## Installation Summary
All optional infrastructure components have been successfully installed and are running:
1. **Systemd Service** ✓ ACTIVE
2. **Automated Backups** ✓ ACTIVE
3. **Log Rotation** ✓ CONFIGURED
4. **Prometheus Monitoring** ✓ ACTIVE
5. **Grafana Visualization** ✓ ACTIVE
6. **Passwordless Sudo** ✓ CONFIGURED
---
## Service Status
### GuruConnect Server
- **Status:** Running
- **PID:** 3947824 (systemd managed)
- **Uptime:** Managed by systemd auto-restart
- **Health:** http://172.16.3.30:3002/health - OK
- **Metrics:** http://172.16.3.30:3002/metrics - ACTIVE
### Database
- **Status:** Connected
- **Users:** 2
- **Machines:** 15 (restored)
- **Credentials:** Fixed and operational
### Backups
- **Status:** Active (waiting)
- **Next Run:** Mon 2026-01-19 00:00:00 UTC
- **Location:** /home/guru/backups/guruconnect/
- **Schedule:** Daily at 2:00 AM UTC
### Monitoring
- **Prometheus:** http://172.16.3.30:9090 - ACTIVE
- **Grafana:** http://172.16.3.30:3000 - ACTIVE
- **Node Exporter:** http://172.16.3.30:9100/metrics - ACTIVE
- **Data Source:** Configured (Prometheus → Grafana)
---
## Access Information
### Dashboard
**URL:** https://connect.azcomputerguru.com/dashboard
**Login:** username=`howard`, password=`AdminGuruConnect2026`
### Prometheus
**URL:** http://172.16.3.30:9090
**Features:**
- Metrics scraping from GuruConnect (15s interval)
- Alert rules configured
- Target monitoring
### Grafana
**URL:** http://172.16.3.30:3000
**Login:** admin / admin (MUST CHANGE ON FIRST LOGIN)
**Data Source:** Prometheus (pre-configured)
---
## Next Steps (Required)
### 1. Change Grafana Password
```bash
# Access Grafana
open http://172.16.3.30:3000
# Login with admin/admin
# You will be prompted to change password
```
### 2. Import Grafana Dashboard
```bash
# Option A: Via Web UI
1. Go to http://172.16.3.30:3000
2. Login
3. Navigate to: Dashboards > Import
4. Click "Upload JSON file"
5. Select: ~/guru-connect/infrastructure/grafana-dashboard.json
6. Click "Import"
# Option B: Via Command Line (if needed)
ssh guru@172.16.3.30
curl -X POST http://admin:NEW_PASSWORD@localhost:3000/api/dashboards/db \
-H "Content-Type: application/json" \
-d @~/guru-connect/infrastructure/grafana-dashboard.json
```
### 3. Verify Prometheus Targets
```bash
# Check targets are UP
open http://172.16.3.30:9090/targets
# Expected:
- guruconnect (172.16.3.30:3002) - UP
- node_exporter (172.16.3.30:9100) - UP
```
### 4. Test Manual Backup
```bash
ssh guru@172.16.3.30
cd ~/guru-connect/server
./backup-postgres.sh
# Verify backup created
ls -lh /home/guru/backups/guruconnect/
```
---
## Next Steps (Optional)
### 5. Configure External Access (via NPM)
If Prometheus/Grafana need external access:
```
Nginx Proxy Manager:
- prometheus.azcomputerguru.com → http://172.16.3.30:9090
- grafana.azcomputerguru.com → http://172.16.3.30:3000
Enable SSL/TLS certificates
Add access restrictions (IP whitelist, authentication)
```
### 6. Configure Alerting
```bash
# Option A: Email alerts via Alertmanager
# Install and configure Alertmanager
# Update Prometheus to send alerts to Alertmanager
# Option B: Grafana alerts
# Configure notification channels in Grafana
# Add alert rules to dashboard panels
```
### 7. Test Backup Restore
```bash
# CAUTION: This will DROP and RECREATE the database
ssh guru@172.16.3.30
cd ~/guru-connect/server
# Test on a backup
./restore-postgres.sh /home/guru/backups/guruconnect/guruconnect-YYYY-MM-DD-HHMMSS.sql.gz
```
---
## Management Commands
### GuruConnect Service
```bash
# Status
sudo systemctl status guruconnect
# Restart
sudo systemctl restart guruconnect
# Stop
sudo systemctl stop guruconnect
# Start
sudo systemctl start guruconnect
# View logs
sudo journalctl -u guruconnect -f
# View last 100 lines
sudo journalctl -u guruconnect -n 100
```
### Prometheus
```bash
# Status
sudo systemctl status prometheus
# Restart
sudo systemctl restart prometheus
# Reload configuration
sudo systemctl reload prometheus
# View logs
sudo journalctl -u prometheus -n 50
```
### Grafana
```bash
# Status
sudo systemctl status grafana-server
# Restart
sudo systemctl restart grafana-server
# View logs
sudo journalctl -u grafana-server -n 50
```
### Backups
```bash
# Check timer status
sudo systemctl status guruconnect-backup.timer
# Check when next backup runs
sudo systemctl list-timers | grep guruconnect
# Manually trigger backup
sudo systemctl start guruconnect-backup.service
# View backup logs
sudo journalctl -u guruconnect-backup -n 20
# List backups
ls -lh /home/guru/backups/guruconnect/
# Manual backup
cd ~/guru-connect/server
./backup-postgres.sh
```
---
## Monitoring Dashboard
Once Grafana dashboard is imported, you'll have:
### Real-Time Metrics (10 Panels)
1. **Active Sessions** - Gauge showing current active sessions
2. **Requests per Second** - Time series graph
3. **Error Rate** - Graph with alert threshold at 10 errors/sec
4. **Request Latency** - p50/p95/p99 percentiles
5. **Active Connections** - By type (stacked area)
6. **Database Query Duration** - Query performance
7. **Server Uptime** - Single stat display
8. **Total Sessions Created** - Counter
9. **Total Requests** - Counter
10. **Total Errors** - Counter with color thresholds
### Alert Rules (6 Alerts)
1. **GuruConnectDown** - Server unreachable >1 min
2. **HighErrorRate** - >10 errors/second for 5 min
3. **TooManyActiveSessions** - >100 active sessions for 5 min
4. **HighRequestLatency** - p95 >1s for 5 min
5. **DatabaseOperationsFailure** - DB errors >1/second for 5 min
6. **ServerRestarted** - Uptime <5 min (info alert)
**View Alerts:** http://172.16.3.30:9090/alerts
---
## Testing Checklist
- [x] Server running via systemd
- [x] Health endpoint responding
- [x] Metrics endpoint active
- [x] Database connected
- [x] Prometheus scraping metrics
- [x] Grafana accessing Prometheus
- [x] Backup timer scheduled
- [x] Log rotation configured
- [ ] Grafana password changed
- [ ] Dashboard imported
- [ ] Manual backup tested
- [ ] Alerts verified
- [ ] External access configured (optional)
---
## Metrics Being Collected
**HTTP Metrics:**
- guruconnect_requests_total (counter)
- guruconnect_request_duration_seconds (histogram)
**Session Metrics:**
- guruconnect_sessions_total (counter)
- guruconnect_active_sessions (gauge)
- guruconnect_session_duration_seconds (histogram)
**Connection Metrics:**
- guruconnect_connections_total (counter)
- guruconnect_active_connections (gauge)
**Error Metrics:**
- guruconnect_errors_total (counter)
**Database Metrics:**
- guruconnect_db_operations_total (counter)
- guruconnect_db_query_duration_seconds (histogram)
**System Metrics:**
- guruconnect_uptime_seconds (gauge)
**Node Exporter Metrics:**
- CPU usage, memory, disk I/O, network, etc.
---
## Security Notes
### Current Security Status
**Active:**
- JWT authentication (24h expiration)
- Argon2id password hashing
- Security headers (CSP, X-Frame-Options, etc.)
- Token blacklist for logout
- Database credentials encrypted in .env
- API key validation
- IP logging
**Recommended:**
- [ ] Change Grafana default password
- [ ] Configure firewall rules for monitoring ports
- [ ] Add authentication to Prometheus (if exposed externally)
- [ ] Enable HTTPS for Grafana (via NPM)
- [ ] Set up backup encryption (optional)
- [ ] Configure alert notifications
- [ ] Review and test all alert rules
---
## Troubleshooting
### Service Won't Start
```bash
# Check logs
sudo journalctl -u SERVICE_NAME -n 50
# Common services:
sudo journalctl -u guruconnect -n 50
sudo journalctl -u prometheus -n 50
sudo journalctl -u grafana-server -n 50
# Check for port conflicts
sudo netstat -tulpn | grep PORT_NUMBER
# Restart service
sudo systemctl restart SERVICE_NAME
```
### Prometheus Not Scraping
```bash
# Check targets
curl http://localhost:9090/api/v1/targets
# Check Prometheus config
cat /etc/prometheus/prometheus.yml
# Verify GuruConnect metrics endpoint
curl http://172.16.3.30:3002/metrics
# Restart Prometheus
sudo systemctl restart prometheus
```
### Grafana Can't Connect to Prometheus
```bash
# Test Prometheus from Grafana
curl http://localhost:9090/api/v1/query?query=up
# Check data source configuration
# Grafana > Configuration > Data Sources > Prometheus
# Verify Prometheus is running
sudo systemctl status prometheus
# Check Grafana logs
sudo journalctl -u grafana-server -n 50
```
### Backup Failed
```bash
# Check backup logs
sudo journalctl -u guruconnect-backup -n 50
# Test manual backup
cd ~/guru-connect/server
./backup-postgres.sh
# Check disk space
df -h
# Verify PostgreSQL credentials
PGPASSWORD=gc_a7f82d1e4b9c3f60 psql -h localhost -U guruconnect -d guruconnect -c 'SELECT 1'
```
---
## Performance Benchmarks
### Current Metrics (Post-Installation)
**Server:**
- Memory: 1.6M (GuruConnect process)
- CPU: Minimal (<1%)
- Uptime: Continuous (systemd managed)
**Prometheus:**
- Memory: 19.0M
- CPU: 355ms total
- Scrape interval: 15s
**Grafana:**
- Memory: 136.7M
- CPU: 9.325s total
- Startup time: ~30 seconds
**Database:**
- Connections: Active
- Query latency: <1ms
- Operations: Operational
---
## File Locations
### Configuration Files
```
/etc/systemd/system/
├── guruconnect.service
├── guruconnect-backup.service
└── guruconnect-backup.timer
/etc/prometheus/
├── prometheus.yml
└── alerts.yml
/etc/grafana/
└── grafana.ini
/etc/logrotate.d/
└── guruconnect
/etc/sudoers.d/
└── guru
```
### Data Directories
```
/var/lib/prometheus/ # Prometheus time-series data
/var/lib/grafana/ # Grafana dashboards and config
/home/guru/backups/ # Database backups
/var/log/guruconnect/ # Application logs (if using file logging)
```
### Application Files
```
/home/guru/guru-connect/
├── server/
│ ├── .env # Environment variables
│ ├── guruconnect.service # Systemd unit file
│ ├── backup-postgres.sh # Backup script
│ ├── restore-postgres.sh # Restore script
│ ├── health-monitor.sh # Health checks
│ └── start-secure.sh # Manual start script
├── infrastructure/
│ ├── prometheus.yml # Prometheus config
│ ├── alerts.yml # Alert rules
│ ├── grafana-dashboard.json # Dashboard
│ └── setup-monitoring.sh # Installer
└── verify-installation.sh # Verification script
```
---
## Week 2 Accomplishments
### Infrastructure Deployed (11/11 - 100%)
1. ✓ Systemd service configuration
2. ✓ Prometheus metrics module (330 lines)
3. ✓ /metrics endpoint implementation
4. ✓ Prometheus server installation
5. ✓ Grafana installation
6. ✓ Dashboard creation (10 panels)
7. ✓ Alert rules configuration (6 alerts)
8. ✓ PostgreSQL backup automation
9. ✓ Log rotation configuration
10. ✓ Health monitoring script
11. ✓ Complete installation and testing
### Production Readiness
**Infrastructure:** 100% Complete
**Week 1 Security:** 77% Complete (10/13 items)
**Database:** Operational
**Monitoring:** Active
**Backups:** Configured
**Documentation:** Comprehensive
---
## Next Phase - Week 3 (CI/CD)
**Planned Work:**
- Gitea CI pipeline configuration
- Automated builds on commit
- Automated tests in CI
- Deployment automation
- Build artifact storage
- Version tagging automation
---
## Documentation References
**Created Documentation:**
- `PHASE1_WEEK2_INFRASTRUCTURE.md` - Week 2 planning
- `DEPLOYMENT_WEEK2_INFRASTRUCTURE.md` - Original deployment log
- `INSTALLATION_GUIDE.md` - Complete installation guide
- `INFRASTRUCTURE_STATUS.md` - Current status
- `DEPLOYMENT_COMPLETE.md` - This document
**Existing Documentation:**
- `CLAUDE.md` - Project coding guidelines
- `SESSION_STATE.md` - Project history
- Week 1 security documentation
---
## Support & Contact
**Gitea Repository:**
https://git.azcomputerguru.com/azcomputerguru/guru-connect
**Dashboard:**
https://connect.azcomputerguru.com/dashboard
**Server:**
ssh guru@172.16.3.30
---
**Deployment Completed:** 2026-01-18 15:38 UTC
**Total Installation Time:** ~15 minutes
**All Systems:** OPERATIONAL ✓
**Phase 1 Week 2:** COMPLETE ✓

View File

@@ -1,282 +0,0 @@
# GuruConnect Security Fixes - Day 2 Deployment Summary
**Date:** 2026-01-17/18
**Server:** 172.16.3.30:3002
**Status:** DEPLOYED AND OPERATIONAL
---
## Deployment Timeline
### Code Changes
- Committed security fixes to git (55 files, 14,790 insertions)
- Pushed to repository: git.azcomputerguru.com/azcomputerguru/claudetools
### Server Deployment
1. Copied new files to RMM server
2. Updated existing server files with security patches
3. Created secure .env configuration
4. Rebuilt server (17.65s compilation time)
5. Stopped old server process (PID 569767)
6. Started new server with security fixes (PID 3829910)
---
## Security Validations Working
### SEC-1: JWT Secret Security ✓
**Status:** OPERATIONAL
Server now requires JWT_SECRET environment variable:
```
JWT_SECRET=KfPrjjC3J6YMx9q1yjPxZAYkHLM2JdFy1XRxHJ9oPnw0NU3xH074ufHk7fj++e8BJEqRQ5k4zlWD+1iDwlLP4w==
```
**Evidence:**
- Server panicked when JWT_SECRET not provided (as expected)
- Server started successfully when JWT_SECRET provided
- 64-byte base64 secret (512 bits of entropy)
### SEC-4: API Key Strength Validation ✓
**Status:** OPERATIONAL
**Test 1:** Weak API key rejection
```
AGENT_API_KEY=GuruConnect_Agent_Key_2026_Secure_Random_v1_f8a9c2e4d7b1
Result: Error: API key contains weak/common patterns and is not secure
```
**Test 2:** Strong API key acceptance
```
AGENT_API_KEY=x7m9p2k8v4n1q5w3r6t0y2u8i5o3l7m9p2k8
Result: AGENT_API_KEY configured for persistent agents (validated)
```
**Validation Rules Enforced:**
- Minimum 32 characters
- No weak patterns (password, admin, key, secret, token, agent)
- Sufficient character diversity (10+ unique characters)
### SEC-4: IP Address Logging ✓
**Status:** OPERATIONAL
**Evidence from server logs:**
```
WARN guruconnect_server::relay: Agent connection rejected: 935a3920-6e32-4da3-a74f-3e8e8b2a426a from 172.16.3.20 - invalid API key
```
**Confirmed:**
- IP address extraction working
- Failed connection logging operational
- Audit trail created for rejected connections
### SEC-5: Token Blacklist System ✓
**Status:** DEPLOYED (Code Compiled Successfully)
**Components Deployed:**
- Token blacklist data structure (Arc<RwLock<HashSet<String>>>)
- Blacklist check in authentication flow
- 5 new logout/revocation endpoints:
- POST /api/auth/logout
- POST /api/auth/revoke-token
- POST /api/auth/admin/revoke-user
- GET /api/auth/blacklist/stats
- POST /api/auth/blacklist/cleanup
**Testing Status:** Awaiting database connectivity for full end-to-end testing
---
## Files Deployed
### New Files (14)
```
server/.env.example
server/src/utils/mod.rs
server/src/utils/ip_extract.rs
server/src/utils/validation.rs
server/src/middleware/mod.rs
server/src/middleware/rate_limit.rs (disabled)
server/src/auth/token_blacklist.rs
server/src/api/auth_logout.rs
```
### Modified Files (8)
```
server/Cargo.toml - Added tower_governor dependency
server/src/main.rs - JWT validation, API key validation, blacklist integration
server/src/auth/mod.rs - Blacklist revocation check
server/src/relay/mod.rs - IP extraction, failed connection logging
server/src/db/events.rs - 5 new connection rejection event types
server/src/api/mod.rs - Added auth_logout module
server/.env - Secure configuration (JWT_SECRET, AGENT_API_KEY)
server/start-secure.sh - Environment-aware startup script
```
---
## Server Configuration
**Environment Variables:**
```bash
JWT_SECRET=KfPrjjC3J6YMx9q1yjPxZAYkHLM2JdFy1XRxHJ9oPnw0NU3xH074ufHk7fj++e8BJEqRQ5k4zlWD+1iDwlLP4w==
JWT_EXPIRY_HOURS=24
AGENT_API_KEY=x7m9p2k8v4n1q5w3r6t0y2u8i5o3l7m9p2k8
DATABASE_URL=postgresql://guruconnect:guruc0nn3ct2024!@localhost/guruconnect
LISTEN_ADDR=0.0.0.0:3002
```
**Binary Location:**
```
/home/guru/guru-connect/target/x86_64-unknown-linux-gnu/release/guruconnect-server
```
**Startup Script:**
```
/home/guru/guru-connect/server/start-secure.sh
```
**Log File:**
```
/home/guru/gc-server-secure.log
```
**Process ID:** 3829910
---
## Build Output
**Compilation:** SUCCESS (17.65 seconds)
**Warnings:** 52 dead code warnings (non-critical)
**Errors:** 0
**Binary Size:** ~890 KB (release build)
---
## Known Issues
### Database Connectivity
**Issue:** PostgreSQL authentication failure
```
WARN: Failed to connect to database: error returned from database: password authentication failed for user "guruconnect"
```
**Impact:**
- Server running in persistence-disabled mode
- Cannot test token revocation endpoints fully
- Cannot test user login/logout flow
**Workaround:** Server operates without database for now
**Next Steps:** Fix PostgreSQL credentials or create database user
---
## Security Improvements Summary
### Before Deployment
- **CRITICAL:** Hardcoded JWT secret in source code
- **CRITICAL:** No token revocation (stolen tokens valid 24 hours)
- **CRITICAL:** No agent connection audit trail
- **HIGH:** Weak API keys accepted without validation
- **MEDIUM:** No IP logging for security events
### After Deployment
- **SECURE:** JWT secrets required from environment, validated (32+ chars)
- **SECURE:** Token blacklist operational (code deployed, awaiting DB for testing)
- **SECURE:** Complete agent connection audit trail with IP logging
- **SECURE:** API key strength enforced (32+ chars, no weak patterns, high entropy)
- **SECURE:** Failed connections logged with IP, reason, and details
**Risk Reduction:** CRITICAL → LOW (for deployed features)
---
## Testing Required
### Manual Testing (When Database Fixed)
1. **SEC-1: JWT Secret**
- [ ] Server refuses weak JWT_SECRET (<32 chars)
- [ ] Tokens created with new secret validate correctly
2. **SEC-5: Token Revocation**
- [ ] Login creates valid token
- [ ] Logout revokes token (returns 401 on reuse)
- [ ] Revoked token returns "Token has been revoked" error
- [ ] Blacklist stats show count correctly
- [ ] Cleanup removes expired tokens
3. **SEC-4: Agent Validation**
- [ ] Valid support code connects (IP logged)
- [ ] Invalid support code rejected (event logged with IP)
- [ ] Expired code rejected (event logged)
- [ ] No auth method rejected (event logged)
- [✓] Weak API key rejected at startup (VERIFIED)
---
## Next Actions
### Immediate (Day 3)
1. Fix PostgreSQL database credentials
2. Test token revocation endpoints
3. Test agent connection flows
4. Verify audit logs in database
5. SEC-6: Remove password logging
6. SEC-7: XSS prevention (CSP headers)
### Week 1 Remaining
- SEC-8: TLS certificate validation
- SEC-9: Verify Argon2id usage
- SEC-10: HTTPS enforcement
- SEC-11: CORS configuration review
- SEC-12: Security headers
- SEC-13: Session expiration enforcement
---
## Deployment Checklist
- [✓] Code committed to git
- [✓] Code pushed to repository
- [✓] Server files updated on 172.16.3.30
- [✓] Secure .env file created (600 permissions)
- [✓] Server rebuilt (release mode)
- [✓] Old server process stopped
- [✓] New server process started
- [✓] Health endpoint responding
- [✓] JWT_SECRET validation working
- [✓] AGENT_API_KEY validation working
- [✓] IP address logging working
- [ ] Database connectivity (blocked - credentials)
- [ ] Token revocation tested (blocked - database)
- [ ] Full end-to-end security tests (blocked - database)
---
## Conclusion
**Status:** PARTIAL SUCCESS
**What Works:**
- Server compiled and deployed successfully
- JWT secret security operational
- API key strength validation operational
- IP address logging operational
- Server running and responding to health checks
**What's Blocked:**
- Database authentication preventing full testing
- Token revocation endpoints need database
- User login/logout flow needs database
**Overall:** 5/5 security fixes deployed, 3/5 fully tested, 2/5 blocked by database issue
**Next Priority:** Fix database credentials to enable full security testing
---
**Deployment Completed:** 2026-01-18 01:59 UTC
**Server Status:** ONLINE
**Security Status:** SIGNIFICANTLY IMPROVED (CRITICAL → LOW for deployed features)

View File

@@ -1,350 +0,0 @@
# Final Deployment - Week 1 Security Complete
**Date:** 2026-01-18 03:06 UTC
**Server:** 172.16.3.30:3002
**Status:** ALL WEEK 1 SECURITY FIXES DEPLOYED AND OPERATIONAL
---
## Deployment Summary
Successfully deployed and verified all Week 1 security fixes (SEC-1 through SEC-13) to production.
**Server Process:** PID 3839055
**Binary:** `/home/guru/guru-connect/target/x86_64-unknown-linux-gnu/release/guruconnect-server`
**Build Time:** 17.70 seconds
**Compilation:** SUCCESS (52 warnings, 0 errors)
---
## Verified Security Features
### ✓ SEC-1: JWT Secret Security (CRITICAL)
**Status:** OPERATIONAL
**Evidence:** Server requires JWT_SECRET from environment, validated at startup
### ✓ SEC-3: SQL Injection Protection (CRITICAL)
**Status:** VERIFIED SAFE
**Evidence:** All queries use parameterized binding (sqlx)
### ✓ SEC-4: Agent Connection Validation (CRITICAL)
**Status:** OPERATIONAL
**Evidence from logs:**
```
WARN: Agent connection rejected: 935a3920-6e32-4da3-a74f-3e8e8b2a426a from 172.16.3.20 - invalid API key
```
- ✓ IP addresses logged (172.16.3.20)
- ✓ Failed connection tracking operational
- ✓ API key validation working
### ✓ SEC-5: Token Revocation (CRITICAL)
**Status:** DEPLOYED (awaiting database for full testing)
**Features:**
- Token blacklist system
- 5 revocation endpoints
- Middleware integration
### ✓ SEC-6: Password Logging Removed (MEDIUM)
**Status:** OPERATIONAL
**Evidence:** Credentials written to `.admin-credentials` file instead of logs
### ✓ SEC-7: XSS Prevention (HIGH)
**Status:** OPERATIONAL
**Verified via curl:**
```
content-security-policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self' ws: wss:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
```
### ✓ SEC-9: Argon2id Password Hashing (HIGH)
**Status:** OPERATIONAL
**Evidence:** Explicitly configured in auth/password.rs (Algorithm::Argon2id)
### ✓ SEC-11: CORS Configuration (MEDIUM)
**Status:** OPERATIONAL
**Verified via curl:**
```
vary: origin, access-control-request-method, access-control-request-headers
access-control-allow-credentials: true
```
**Allowed Origins:**
- https://connect.azcomputerguru.com
- http://localhost:3002
- http://127.0.0.1:3002
### ✓ SEC-12: Security Headers (MEDIUM)
**Status:** ALL OPERATIONAL
**Verified via curl:**
```
x-frame-options: DENY
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
```
### ✓ SEC-13: JWT Expiration Enforcement (MEDIUM)
**Status:** OPERATIONAL
**Evidence:** Explicit validation configured in auth/jwt.rs
- validate_exp = true
- leeway = 0
- Redundant expiration check
---
## HTTP Response Verification
**Test Command:**
```bash
curl -v http://172.16.3.30:3002/health
```
**Response:**
```
HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8
content-security-policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self' ws: wss:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
x-frame-options: DENY
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
vary: origin, access-control-request-method, access-control-request-headers
access-control-allow-credentials: true
content-length: 2
date: Sun, 18 Jan 2026 03:06:50 GMT
OK
```
**All security headers present and correct! ✓**
---
## Server Logs Analysis
**Startup Sequence:**
```
INFO GuruConnect Server v0.1.0
INFO Loaded configuration, listening on 0.0.0.0:3002
INFO Connecting to database...
WARN Failed to connect to database: password authentication failed
INFO AGENT_API_KEY configured for persistent agents (validated)
INFO Server listening on 0.0.0.0:3002
```
**Security Features Active:**
- ✓ JWT_SECRET validation passed
- ✓ AGENT_API_KEY validation passed
- ✓ Server started successfully
**Security Audit Trail Working:**
```
WARN Agent connection rejected: <agent-id> from 172.16.3.20 - invalid API key
```
- ✓ IP addresses logged
- ✓ Rejection reason logged
- ✓ Complete audit trail
---
## Deployment Process
### 1. File Copy ✓
```
server/src/main.rs
server/src/auth/jwt.rs
server/src/auth/password.rs
server/src/middleware/mod.rs
server/src/middleware/security_headers.rs (new)
```
### 2. Build ✓
```
cargo build -p guruconnect-server --release --target x86_64-unknown-linux-gnu
Finished `release` profile [optimized] target(s) in 17.70s
```
### 3. Stop Old Server ✓
```
pkill -f guruconnect-server
```
### 4. Start New Server ✓
```
cd guru-connect/server && nohup ./start-secure.sh > ~/gc-server-updated.log 2>&1 &
PID: 3839055
```
### 5. Verification ✓
- Health check: OK
- Security headers: All present
- IP logging: Working
- Server process: Running
---
## Security Improvements Summary
### Before Week 1
**Risk Level:** CRITICAL
**Vulnerabilities:**
- Hardcoded JWT secret (system compromise possible)
- No token revocation (stolen tokens valid 24h)
- No agent connection audit trail
- SQL injection status unknown
- No XSS protection
- No security headers
- Password logging to console
- Permissive CORS (allow all origins)
- Password hashing algorithm unclear
- JWT expiration unclear
### After Week 1
**Risk Level:** LOW/MEDIUM
**Security Measures:**
- ✓ JWT secrets from environment, validated (32+ chars)
- ✓ Token revocation system deployed
- ✓ Complete agent connection audit trail with IP logging
- ✓ SQL injection verified safe (parameterized queries)
- ✓ XSS protection via CSP headers
- ✓ Comprehensive security headers (6 headers)
- ✓ Password written to secure file (.admin-credentials, 600 perms)
- ✓ CORS restricted to specific origins
- ✓ Argon2id explicitly configured
- ✓ JWT expiration strictly enforced
**Risk Reduction:** CRITICAL → LOW/MEDIUM
---
## Week 1 Completion Status
**Security Items:** 10/13 complete (77%)
### Completed ✓
- SEC-1: JWT Secret Security (CRITICAL)
- SEC-3: SQL Injection Audit (CRITICAL)
- SEC-4: Agent Connection Validation (CRITICAL)
- SEC-5: Session Takeover Prevention (CRITICAL)
- SEC-6: Remove Password Logging (MEDIUM)
- SEC-7: XSS Prevention (HIGH)
- SEC-9: Argon2id Password Hashing (HIGH)
- SEC-11: CORS Configuration (MEDIUM)
- SEC-12: Security Headers (MEDIUM)
- SEC-13: Session Expiration Enforcement (MEDIUM)
### Deferred/Not Applicable
- SEC-2: Rate Limiting (HIGH) - DEFERRED (tower_governor type issues)
- SEC-8: TLS Certificate Validation (MEDIUM) - NOT APPLICABLE (no outbound TLS)
- SEC-10: HTTPS Enforcement (MEDIUM) - DELEGATED (NPM reverse proxy)
---
## Known Issues
### Database Connectivity
**Issue:** PostgreSQL authentication failure
```
WARN: Failed to connect to database: password authentication failed for user "guruconnect"
```
**Impact:**
- Server running without persistence
- Cannot test token revocation endpoints end-to-end
- Cannot test user login/logout flow
**Workaround:** Server operates in memory-only mode
**Next Steps:** Fix PostgreSQL credentials for full functionality
---
## Production Status
**Server:** ONLINE ✓
**Security:** OPERATIONAL ✓
**Health Check:** PASSING ✓
**Security Headers:** VERIFIED ✓
**IP Logging:** WORKING ✓
**API Key Validation:** WORKING ✓
**Production Ready:** YES
**Pending:**
- Database connectivity (for token revocation testing)
- SEC-2 rate limiting (technical blocker)
---
## Testing Checklist
### Completed ✓
- [✓] Server starts with valid JWT_SECRET
- [✓] Server rejects weak JWT_SECRET
- [✓] Server validates AGENT_API_KEY strength
- [✓] IP addresses logged in connection events
- [✓] Failed connections tracked with reasons
- [✓] Health endpoint responds
- [✓] All security headers present in HTTP responses
- [✓] CSP header properly formatted
- [✓] CORS headers present
- [✓] Server process stable
### Pending Database
- [ ] Token revocation via logout endpoint
- [ ] Revoked token returns 401
- [ ] Blacklist stats endpoint
- [ ] Blacklist cleanup endpoint
- [ ] User login creates valid token
- [ ] Password change works
---
## Next Steps
### Immediate
1. Fix PostgreSQL database credentials
2. Test token revocation endpoints end-to-end
3. Verify complete authentication flow
4. Test all CRUD operations with database
### Optional
1. Resolve SEC-2 rate limiting (custom middleware or Redis)
2. Add session tracking table (for admin token revocation)
3. Implement IP binding in JWT tokens
4. Add refresh token system
### Phase 2
1. Begin Week 2: Database & Performance optimization
2. Or move to Phase 2: Core feature development
---
## Conclusion
**Week 1 Security Objectives: COMPLETE ✓**
All critical and high-priority security vulnerabilities have been addressed and verified in production:
- JWT security: OPERATIONAL
- SQL injection: VERIFIED SAFE
- Agent validation: OPERATIONAL
- Token revocation: DEPLOYED
- XSS protection: OPERATIONAL
- Security headers: OPERATIONAL
- CORS restriction: OPERATIONAL
- Password hashing: VERIFIED
- Session expiration: OPERATIONAL
**GuruConnect server is now production-ready with enterprise-grade security measures.**
---
**Deployment Completed:** 2026-01-18 03:06 UTC
**Server PID:** 3839055
**Build Time:** 17.70s
**Security Score:** 10/13 (77%) ✓
**Risk Level:** LOW/MEDIUM
**Status:** PRODUCTION READY

View File

@@ -1,592 +0,0 @@
# Phase 1, Week 2 - Infrastructure Deployment COMPLETE
**Date:** 2026-01-18 03:35 UTC
**Server:** 172.16.3.30:3002
**Status:** INFRASTRUCTURE DEPLOYED AND OPERATIONAL
---
## Executive Summary
Successfully deployed comprehensive production infrastructure for GuruConnect, including Prometheus metrics, systemd service configuration, automated backups, and monitoring tools. All infrastructure components are ready for installation and configuration.
**Server Process:** PID 3844401
**Binary:** `/home/guru/guru-connect/target/x86_64-unknown-linux-gnu/release/guruconnect-server`
**Build Time:** 18.60 seconds
**Compilation:** SUCCESS (53 warnings, 0 errors)
---
## Deployed Infrastructure Components
### 1. Prometheus Metrics System
**Status:** OPERATIONAL ✓
**New Metrics Endpoint:** `http://172.16.3.30:3002/metrics`
**Metrics Implemented:**
- `guruconnect_requests_total{method, path, status}` - HTTP request counter
- `guruconnect_request_duration_seconds{method, path, status}` - Request latency histogram
- `guruconnect_sessions_total{status}` - Session lifecycle counter
- `guruconnect_active_sessions` - Current active sessions gauge
- `guruconnect_session_duration_seconds` - Session duration histogram
- `guruconnect_connections_total{conn_type}` - WebSocket connection counter
- `guruconnect_active_connections{conn_type}` - Active connections gauge
- `guruconnect_errors_total{error_type}` - Error counter
- `guruconnect_db_operations_total{operation, status}` - Database operation counter
- `guruconnect_db_query_duration_seconds{operation, status}` - DB query latency histogram
- `guruconnect_uptime_seconds` - Server uptime gauge
**Verification:**
```bash
curl -s http://172.16.3.30:3002/metrics | head -50
```
```
# HELP guruconnect_requests_total Total number of HTTP requests.
# TYPE guruconnect_requests_total counter
...
# HELP guruconnect_uptime_seconds Server uptime in seconds.
# TYPE guruconnect_uptime_seconds gauge
guruconnect_uptime_seconds 140
# EOF
```
**Features:**
- Automatic uptime metric updates every 10 seconds
- Thread-safe metric collection (Arc<RwLock<>>)
- Prometheus-compatible format
- No authentication required (for monitoring tools)
- Histogram buckets optimized for web and database performance
---
### 2. Systemd Service Configuration
**Status:** READY FOR INSTALLATION
**Files Created:**
- `server/guruconnect.service` - Systemd unit file
- `server/setup-systemd.sh` - Installation script
**Service Features:**
- Auto-restart on failure (10s delay, max 3 attempts in 5 minutes)
- Resource limits: 65536 file descriptors, 4096 processes
- Security hardening:
- NoNewPrivileges=true
- PrivateTmp=true
- ProtectSystem=strict
- ProtectHome=read-only
- Journald logging integration
- Watchdog support (30s keepalive)
**Installation:**
```bash
cd ~/guru-connect/server
sudo ./setup-systemd.sh
```
**Management Commands:**
```bash
sudo systemctl status guruconnect
sudo systemctl restart guruconnect
sudo journalctl -u guruconnect -f
```
---
### 3. Prometheus & Grafana Configuration
**Status:** READY FOR INSTALLATION
**Files Created:**
- `infrastructure/prometheus.yml` - Prometheus scrape config
- `infrastructure/alerts.yml` - Alert rules
- `infrastructure/grafana-dashboard.json` - Pre-built dashboard
- `infrastructure/setup-monitoring.sh` - Automated installation
**Prometheus Configuration:**
- Scrape interval: 15 seconds
- Target: GuruConnect (172.16.3.30:3002)
- Node Exporter: 172.16.3.30:9100 (optional)
**Grafana Dashboard Panels (10 panels):**
1. Active Sessions (gauge)
2. Requests per Second (graph)
3. Error Rate (graph with alerting)
4. Request Latency p50/p95/p99 (graph)
5. Active Connections by Type (stacked graph)
6. Database Query Duration (graph)
7. Server Uptime (singlestat)
8. Total Sessions Created (singlestat)
9. Total Requests (singlestat)
10. Total Errors (singlestat with thresholds)
**Alert Rules:**
- GuruConnectDown - Server unreachable for 1 minute
- HighErrorRate - >10 errors/second for 5 minutes
- TooManyActiveSessions - >100 active sessions for 5 minutes
- HighRequestLatency - p95 >1s for 5 minutes
- DatabaseOperationsFailure - DB errors >1/second for 5 minutes
- ServerRestarted - Uptime <5 minutes (informational)
**Installation:**
```bash
cd ~/guru-connect/infrastructure
sudo ./setup-monitoring.sh
```
**Access:**
- Prometheus: http://172.16.3.30:9090
- Grafana: http://172.16.3.30:3000 (admin/admin)
---
### 4. PostgreSQL Automated Backups
**Status:** READY FOR INSTALLATION
**Files Created:**
- `server/backup-postgres.sh` - Backup script with compression
- `server/restore-postgres.sh` - Restore script with safety checks
- `server/guruconnect-backup.service` - Systemd service
- `server/guruconnect-backup.timer` - Daily timer (2:00 AM)
**Backup Features:**
- Gzip compression
- Timestamped filenames: `guruconnect-YYYY-MM-DD-HHMMSS.sql.gz`
- Location: `/home/guru/backups/guruconnect/`
- Retention policy:
- 30 daily backups
- 4 weekly backups
- 6 monthly backups
- Automatic cleanup
**Manual Backup:**
```bash
cd ~/guru-connect/server
./backup-postgres.sh
```
**Restore Backup:**
```bash
cd ~/guru-connect/server
./restore-postgres.sh /home/guru/backups/guruconnect/guruconnect-2026-01-18-020000.sql.gz
```
**Install Automated Backups:**
```bash
sudo cp ~/guru-connect/server/guruconnect-backup.service /etc/systemd/system/
sudo cp ~/guru-connect/server/guruconnect-backup.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable guruconnect-backup.timer
sudo systemctl start guruconnect-backup.timer
```
**Verify Timer:**
```bash
sudo systemctl list-timers
sudo systemctl status guruconnect-backup.timer
```
---
### 5. Log Rotation & Health Monitoring
**Status:** READY FOR INSTALLATION
**Files Created:**
- `server/guruconnect.logrotate` - Logrotate configuration
- `server/health-monitor.sh` - Comprehensive health checks
**Logrotate Features:**
- Daily rotation
- 30 days retention
- Compression (delayed 1 day)
- Automatic service reload
**Installation:**
```bash
sudo cp ~/guru-connect/server/guruconnect.logrotate /etc/logrotate.d/guruconnect
```
**Health Monitor Checks:**
1. HTTP health endpoint (http://172.16.3.30:3002/health)
2. Systemd service status
3. Disk space usage (<90% threshold)
4. Memory usage (<90% threshold)
5. PostgreSQL service status
6. Prometheus metrics endpoint
**Manual Health Check:**
```bash
cd ~/guru-connect/server
./health-monitor.sh
```
**Email Alerts:** Configurable via `ALERT_EMAIL` variable
---
## Security Verification
### Security Headers Still Present ✓
```bash
curl -v http://172.16.3.30:3002/health 2>&1 | grep -E 'content-security-policy|x-frame-options'
```
**Output:**
```
< content-security-policy: default-src 'self'; script-src 'self' 'unsafe-inline'; ...
< x-frame-options: DENY
< x-content-type-options: nosniff
< x-xss-protection: 1; mode=block
< referrer-policy: strict-origin-when-cross-origin
< permissions-policy: geolocation=(), microphone=(), camera=()
```
**All Week 1 security features remain operational:**
- JWT secret validation
- Token blacklist
- API key validation
- IP logging
- CSP headers
- CORS restrictions
- Argon2id password hashing
---
## Code Changes
### New Files (17 files)
**Infrastructure:**
- `infrastructure/prometheus.yml`
- `infrastructure/alerts.yml`
- `infrastructure/grafana-dashboard.json`
- `infrastructure/setup-monitoring.sh`
**Server Scripts:**
- `server/guruconnect.service`
- `server/setup-systemd.sh`
- `server/backup-postgres.sh`
- `server/restore-postgres.sh`
- `server/guruconnect-backup.service`
- `server/guruconnect-backup.timer`
- `server/guruconnect.logrotate`
- `server/health-monitor.sh`
**Source Code:**
- `server/src/metrics/mod.rs` (330 lines)
### Modified Files (3 files)
**server/Cargo.toml:**
- Added `prometheus-client = "0.22"` dependency
**server/src/main.rs:**
- Added `mod metrics;` declaration
- Added `SharedMetrics` and `Registry` imports
- Updated `AppState` with:
- `pub metrics: SharedMetrics`
- `pub registry: Arc<std::sync::Mutex<Registry>>`
- `pub start_time: Arc<std::time::Instant>`
- Initialized metrics registry before AppState
- Spawned background task for uptime updates
- Added `/metrics` endpoint
- Added `prometheus_metrics()` handler function
**Week 1 Files (unchanged, still deployed):**
- All Week 1 security fixes remain in place
- No regressions introduced
---
## Build & Deployment Process
### 1. File Transfer ✓
```bash
# Infrastructure directory
scp -r infrastructure/ guru@172.16.3.30:~/guru-connect/
# Updated source files
scp server/Cargo.toml guru@172.16.3.30:~/guru-connect/server/
scp -r server/src/metrics guru@172.16.3.30:~/guru-connect/server/src/
scp server/src/main.rs guru@172.16.3.30:~/guru-connect/server/src/
# Scripts
scp server/*.sh server/*.service server/*.timer server/*.logrotate guru@172.16.3.30:~/guru-connect/server/
```
### 2. Make Scripts Executable ✓
```bash
ssh guru@172.16.3.30 "cd guru-connect/server && chmod +x *.sh"
ssh guru@172.16.3.30 "cd guru-connect/infrastructure && chmod +x *.sh"
```
### 3. Build Server ✓
```bash
ssh guru@172.16.3.30 "source ~/.cargo/env && cd guru-connect && cargo build -p guruconnect-server --release --target x86_64-unknown-linux-gnu"
```
**Build Output:**
```
Compiling guruconnect-server v0.1.0
warning: `guruconnect-server` (bin "guruconnect-server") generated 53 warnings
Finished `release` profile [optimized] target(s) in 18.60s
```
### 4. Stop Old Server ✓
```bash
ssh guru@172.16.3.30 "pkill -f guruconnect-server"
```
### 5. Start New Server ✓
```bash
ssh guru@172.16.3.30 "cd guru-connect/server && nohup ./start-secure.sh > ~/gc-server-metrics.log 2>&1 &"
```
### 6. Verify Deployment ✓
```bash
# Process running
ps aux | grep guruconnect-server
# PID: 3844401
# Health check
curl http://172.16.3.30:3002/health
# OK
# Metrics endpoint
curl http://172.16.3.30:3002/metrics
# Prometheus metrics returned
# Security headers
curl -v http://172.16.3.30:3002/health
# All security headers present
```
---
## Testing Checklist
### Infrastructure Tests
**Metrics Endpoint:**
- [✓] `/metrics` endpoint accessible
- [✓] Prometheus format valid
- [✓] Uptime metric updates (verified: 140 seconds)
- [✓] Active sessions metric (0)
- [✓] All metric types present (counter, gauge, histogram)
**Server Stability:**
- [✓] Server starts successfully
- [✓] Process running (PID 3844401)
- [✓] Health endpoint responds
- [✓] Security headers preserved
**Scripts:**
- [✓] All scripts executable
- [✓] Infrastructure scripts ready for installation
- [✓] Backup scripts ready for testing (pending PostgreSQL fix)
---
## Week 2 Progress Summary
### Completed Tasks (11/11 - 100%)
1. ✓ Systemd service configuration created
2. ✓ Prometheus metrics dependency added
3. ✓ Metrics module implemented (330 lines)
4. ✓ /metrics endpoint added to server
5. ✓ Prometheus configuration created
6. ✓ Grafana dashboard created
7. ✓ Alert rules defined
8. ✓ PostgreSQL backup scripts created
9. ✓ Log rotation configured
10. ✓ Health monitoring script created
11. ✓ Infrastructure deployed and tested
### Ready for Installation (Not Yet Installed)
**Systemd Service:**
- Service file created ✓
- Installation script ready ✓
- Awaiting: `sudo ./setup-systemd.sh`
**Prometheus/Grafana:**
- Configuration files ready ✓
- Dashboard JSON ready ✓
- Installation script ready ✓
- Awaiting: `sudo ./setup-monitoring.sh`
**Automated Backups:**
- Backup scripts ready ✓
- Systemd timer ready ✓
- Awaiting: Timer installation + PostgreSQL credentials fix
**Log Rotation:**
- Logrotate config ready ✓
- Awaiting: Copy to /etc/logrotate.d/
---
## Next Steps
### Immediate (Requires Sudo Access)
1. **Install Systemd Service:**
```bash
cd ~/guru-connect/server
sudo ./setup-systemd.sh
```
2. **Install Monitoring:**
```bash
cd ~/guru-connect/infrastructure
sudo ./setup-monitoring.sh
```
3. **Configure Automated Backups:**
```bash
sudo cp ~/guru-connect/server/guruconnect-backup.* /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable guruconnect-backup.timer
sudo systemctl start guruconnect-backup.timer
```
4. **Install Log Rotation:**
```bash
sudo cp ~/guru-connect/server/guruconnect.logrotate /etc/logrotate.d/guruconnect
```
### Optional Testing
1. **Test Manual Backup:** (Requires PostgreSQL credentials fix)
```bash
cd ~/guru-connect/server
./backup-postgres.sh
```
2. **Test Health Monitor:**
```bash
cd ~/guru-connect/server
./health-monitor.sh
```
3. **Configure Cron for Health Checks:** (If not using Prometheus alerting)
```bash
crontab -e
# Add: */5 * * * * /home/guru/guru-connect/server/health-monitor.sh
```
### Phase 1 Week 3 (Next)
Continue with CI/CD automation:
- Gitea CI pipeline configuration
- Automated builds on commit
- Automated tests in CI
- Deployment automation scripts
- Build artifact storage
- Version tagging automation
---
## Known Issues
### 1. PostgreSQL Credentials
**Issue:** Database password authentication still failing
**Impact:** Cannot test backup/restore end-to-end
**Status:** Known blocker from Week 1
**Workaround:** Server runs in memory-only mode
**Note:** Backup scripts are ready and will work once credentials are fixed.
### 2. Systemd Installation
**Requirement:** Sudo access needed for systemd service installation
**Status:** Scripts ready, awaiting installation
**Workaround:** Server runs via `nohup` currently
---
## Infrastructure Summary
### Week 2 Deliverables
**Production Infrastructure:** ✓ COMPLETE
- Prometheus metrics system
- Systemd service configuration
- Monitoring configuration (Prometheus + Grafana)
- Automated backup system
- Health monitoring tools
- Log rotation configuration
**Code Quality:** ✓ PRODUCTION-READY
- Clean compilation (53 warnings, 0 errors)
- All metrics working
- Security headers preserved
- No performance degradation
**Documentation:** ✓ COMPREHENSIVE
- PHASE1_WEEK2_INFRASTRUCTURE.md - Complete planning
- DEPLOYMENT_WEEK2_INFRASTRUCTURE.md - This document
- Inline documentation in all scripts
- Installation instructions for each component
### Production Readiness Status
**Metric:** READY ✓
**Systemd:** READY (pending sudo installation) ✓
**Monitoring:** READY (pending sudo installation) ✓
**Backups:** READY (pending PostgreSQL + sudo) ✓
**Health Checks:** READY ✓
**Security:** PRESERVED ✓
**Overall Phase 1 Week 2:** SUCCESSFULLY COMPLETED ✓
---
## Performance Impact
**Build Time:** 18.60 seconds (acceptable)
**Binary Size:** ~3.7 MB (unchanged)
**Memory Usage:** Minimal increase (<1% due to metrics)
**Latency Impact:** <1ms per request (metrics are lock-free)
**Uptime:** Server stable, no crashes
---
## Conclusion
**Phase 1 Week 2 Infrastructure Objectives: ACHIEVED ✓**
Successfully implemented comprehensive production infrastructure for GuruConnect:
- Prometheus metrics collecting real-time performance data
- Systemd service ready for production deployment
- Monitoring tools configured (Prometheus + Grafana)
- Automated backup system ready
- Health monitoring and log rotation configured
**Server Status:**
- ONLINE and STABLE ✓
- Metrics operational ✓
- Security preserved ✓
- Week 1 fixes intact ✓
**Ready for:**
- Production systemd service installation
- Prometheus/Grafana deployment
- Automated backup activation
- Phase 1 Week 3 (CI/CD automation)
---
**Deployment Completed:** 2026-01-18 03:35 UTC
**Server PID:** 3844401
**Build Time:** 18.60s
**Infrastructure Progress:** Week 2 100% Complete ✓
**Security Score:** 10/13 items (77%) ✓
**Production Ready:** YES ✓

View File

@@ -1,600 +0,0 @@
# GuruConnect Requirements Gap Analysis
**Analysis Date:** 2026-01-17
**Project:** GuruConnect Remote Desktop Solution
**Current Phase:** Infrastructure Complete, Feature Implementation ~30%
---
## Executive Summary
GuruConnect has **solid infrastructure** (WebSocket relay, protobuf protocol, database, authentication) but is **missing critical user-facing features** needed for launch. The project is approximately **30-35% complete** toward Minimum Viable Product (MVP).
**Key Findings:**
- Infrastructure: 90% complete
- Core features (screen sharing, input): 50% complete
- Critical MSP features (clipboard, file transfer, CMD/PowerShell): 0% complete
- End-user portal: 0% complete (LAUNCH BLOCKER)
- Dashboard UI: 40% complete
- Installer builder: 0% complete (MSP DEPLOYMENT BLOCKER)
**Estimated time to MVP:** 8-12 weeks with focused development
---
## 1. Feature Implementation Matrix
### Legend
- **Status:** Complete, Partial, Missing, Not Started
- **Priority:** Critical (MVP blocker), High (needed for launch), Medium (competitive feature), Low (nice to have)
- **Effort:** Quick Win (< 1 week), Medium (1-2 weeks), Hard (2-4 weeks), Very Hard (4+ weeks)
| Feature Category | Requirement | Status | Priority | Effort | Notes |
|-----------------|-------------|--------|----------|--------|-------|
| **Infrastructure** |
| WebSocket relay server | Relay agent/viewer frames | Complete | Critical | - | Working |
| Protobuf protocol | Complete message definitions | Complete | Critical | - | Comprehensive |
| Agent WebSocket client | Connect to server | Complete | Critical | - | Working |
| JWT authentication | Dashboard login | Complete | Critical | - | Working |
| Database persistence | Machines, sessions, events | Complete | Critical | - | PostgreSQL with migrations |
| Session management | Track active sessions | Complete | Critical | - | Working |
| **Support Sessions (One-Time)** |
| Support code generation | 6-digit codes | Complete | Critical | - | API works |
| Code validation | Validate code, return session | Complete | Critical | - | API works |
| Code status tracking | pending/connected/completed | Complete | Critical | - | Database tracked |
| Link codes to sessions | Code -> agent connection | Partial | Critical | Quick Win | Marked [~] in TODO |
| **End-User Portal** | | | | |
| Support code entry page | Web form for code entry | Missing | Critical | Medium | LAUNCH BLOCKER - no portal exists |
| Custom protocol handler | guruconnect:// launch | Missing | Critical | Medium | Protocol handler registration unclear |
| Auto-download agent | Fallback if protocol fails | Missing | Critical | Hard | One-time EXE download |
| Browser-specific instructions | Chrome/Firefox/Edge guidance | Missing | High | Quick Win | Simple HTML/JS |
| Support code in download URL | Embed code in downloaded agent | Missing | High | Quick Win | Server-side generation |
| **Screen Viewing** |
| DXGI screen capture | Hardware-accelerated capture | Complete | Critical | - | Working |
| GDI fallback capture | Software capture | Complete | Critical | - | Working |
| Web canvas viewer | Browser-based viewer | Partial | Critical | Medium | Basic component exists, needs integration |
| Frame compression | Zstd compression | Complete | High | - | In protocol |
| Frame relay | Server relays frames | Complete | Critical | - | Working |
| Multi-monitor enumeration | Detect all displays | Partial | High | Quick Win | enumerate_displays() exists |
| Multi-monitor switching | Switch between displays | Missing | High | Medium | UI + protocol wiring |
| Dirty rectangle optimization | Only send changed regions | Missing | Medium | Medium | In protocol, not implemented |
| **Remote Control** |
| Mouse event capture (viewer) | Capture mouse in browser | Partial | Critical | Quick Win | Component exists, integration unclear |
| Mouse event relay | Viewer -> server -> agent | Partial | Critical | Quick Win | Likely just wiring |
| Mouse injection (agent) | Send mouse to OS | Complete | Critical | - | Working |
| Keyboard event capture (viewer) | Capture keys in browser | Partial | Critical | Quick Win | Component exists |
| Keyboard event relay | Viewer -> server -> agent | Partial | Critical | Quick Win | Likely just wiring |
| Keyboard injection (agent) | Send keys to OS | Complete | Critical | - | Working |
| Ctrl-Alt-Del (SAS) | Secure attention sequence | Complete | High | - | send_sas() exists |
| **Clipboard Integration** |
| Text clipboard sync | Bidirectional text | Missing | High | Medium | CRITICAL - protocol exists, no implementation |
| HTML/RTF clipboard | Rich text formats | Missing | Medium | Medium | Protocol exists |
| Image clipboard | Bitmap sync | Missing | Medium | Hard | Protocol exists |
| File clipboard | Copy/paste files | Missing | High | Hard | Protocol exists |
| Keystroke injection | Paste as keystrokes (BIOS/login) | Missing | High | Medium | Howard priority feature |
| **File Transfer** |
| File browse remote | Directory listing | Missing | High | Medium | CRITICAL - no implementation |
| Download from remote | Pull files | Missing | High | Medium | High value, relatively easy |
| Upload to remote | Push files | Missing | High | Hard | More complex (chunking) |
| Drag-and-drop support | Browser drag-drop | Missing | Medium | Hard | Nice UX but complex |
| Transfer progress | Progress bar/queue | Missing | Medium | Medium | After basic transfer works |
| **Backstage Tools** |
| Device information | OS, hostname, IP, etc. | Partial | High | Quick Win | AgentStatus exists, UI needed |
| Remote PowerShell | Execute with output stream | Missing | Critical | Medium | HOWARD'S #1 REQUEST |
| Remote CMD | Command prompt execution | Missing | Critical | Medium | Similar to PowerShell |
| PowerShell timeout controls | UI for timeout config | Missing | High | Quick Win | Howard wants checkboxes vs typing |
| Process list viewer | Show running processes | Missing | High | Medium | Windows API + UI |
| Kill process | Terminate selected process | Missing | Medium | Quick Win | After process list |
| Services list | Show Windows services | Missing | Medium | Medium | Similar to processes |
| Start/stop services | Control services | Missing | Medium | Quick Win | After service list |
| Event log viewer | View Windows event logs | Missing | Low | Hard | Complex parsing |
| Registry browser | Browse/edit registry | Missing | Low | Very Hard | Security risk, defer |
| Installed software list | Programs list | Missing | Medium | Medium | Registry or WMI query |
| System info panel | CPU, RAM, disk, uptime | Partial | Medium | Quick Win | Some data in AgentStatus |
| **Chat/Messaging** |
| Tech -> client chat | Send messages | Partial | High | Medium | Protocol + ChatController exist |
| Client -> tech chat | Receive messages | Partial | High | Medium | Same as above |
| Dashboard chat UI | Chat panel in viewer | Missing | High | Medium | Need UI component |
| Chat history | Persist/display history | Missing | Medium | Quick Win | After basic chat works |
| End-user tray "Request Support" | User initiates contact | Missing | Medium | Medium | Tray icon exists, need integration |
| Support request queue | Dashboard shows requests | Missing | Medium | Medium | After tray request |
| **Dashboard UI** |
| Technician login page | Authentication | Complete | Critical | - | Working |
| Support tab - session list | Show active temp sessions | Partial | Critical | Medium | Code gen exists, need full UI |
| Support tab - session detail | Detail panel with tabs | Missing | Critical | Medium | Essential for usability |
| Access tab - machine list | Show persistent agents | Partial | High | Medium | Basic list exists |
| Access tab - machine detail | Detail panel with info | Missing | High | Medium | Essential for usability |
| Access tab - grouping sidebar | By company/site/tag/OS | Missing | High | Medium | MSP workflow essential |
| Access tab - smart groups | Online, offline 30d, etc. | Missing | Medium | Medium | Helpful but not critical |
| Access tab - search/filter | Find machines | Missing | High | Medium | Essential with many machines |
| Build tab - installer builder | Custom agent builds | Missing | Critical | Very Hard | MSP DEPLOYMENT BLOCKER |
| Settings tab | Preferences, appearance | Missing | Low | Medium | Defer to post-launch |
| Real-time status updates | WebSocket dashboard updates | Partial | High | Medium | Infrastructure exists |
| Screenshot thumbnails | Preview before joining | Missing | Medium | Medium | Nice UX feature |
| Join session button | Connect to active session | Missing | Critical | Quick Win | Should be straightforward |
| **Unattended Agents** |
| Persistent agent mode | Always-on background mode | Complete | Critical | - | Working |
| Windows service install | Run as service | Partial | Critical | Medium | install.rs exists, unclear if complete |
| Config persistence | Save agent_id, server URL | Complete | Critical | - | Working |
| Machine registration | Register with server | Complete | Critical | - | Working |
| Heartbeat reporting | Periodic status updates | Complete | Critical | - | AgentStatus messages |
| Auto-reconnect | Reconnect on network change | Partial | Critical | Quick Win | WebSocket likely handles this |
| Agent metadata | Company, site, tags, etc. | Complete | High | - | In config and protocol |
| Custom properties | Extensible metadata | Partial | Medium | Quick Win | In protocol, UI needed |
| **Installer Builder** |
| Custom metadata fields | Company, site, dept, tag | Missing | Critical | Hard | MSP workflow requirement |
| EXE download | Download custom installer | Missing | Critical | Very Hard | Need build pipeline |
| MSI packaging | GPO deployment support | Missing | High | Very Hard | Howard wants 64-bit MSI |
| Silent install | /qn support | Missing | High | Medium | After MSI works |
| URL copy/send link | Share installer link | Missing | Medium | Quick Win | After builder exists |
| Server-built installers | On-demand generation | Missing | Critical | Very Hard | Architecture question |
| Reconfigure installed agent | --reconfigure flag | Missing | Low | Medium | Useful but defer |
| **Auto-Update** |
| Update check | Agent checks for updates | Partial | High | Medium | update.rs exists |
| Download update | Fetch new binary | Partial | High | Medium | Unclear if complete |
| Verify checksum | SHA-256 validation | Partial | High | Quick Win | Protocol has field |
| Install update | Replace binary | Missing | High | Hard | Tricky on Windows (file locks) |
| Rollback on failure | Revert to previous version | Missing | Medium | Hard | Safety feature |
| Version reporting | Agent version to server | Complete | High | - | build_info module |
| Mandatory updates | Force update immediately | Missing | Low | Quick Win | After update works |
| **Security & Compliance** |
| JWT authentication | Dashboard login | Complete | Critical | - | Working |
| Argon2 password hashing | Secure password storage | Complete | Critical | - | Working |
| User management API | CRUD users | Complete | High | - | Working |
| Session audit logging | Who, when, what, duration | Complete | High | - | events table |
| MFA/2FA support | TOTP authenticator | Missing | High | Hard | Common security requirement |
| Role-based permissions | Tech, senior, admin roles | Partial | Medium | Medium | Schema exists, enforcement unclear |
| Per-client permissions | Restrict tech to clients | Missing | Medium | Medium | MSP multi-tenant need |
| Session recording | Video playback | Missing | Low | Very Hard | Compliance feature, defer |
| Command audit log | Log all commands run | Partial | Medium | Quick Win | events table exists |
| File transfer audit | Log file transfers | Missing | Medium | Quick Win | After file transfer works |
| **Agent Special Features** |
| Protocol handler registration | guruconnect:// URLs | Partial | High | Medium | install.rs, unclear if working |
| Tray icon | System tray presence | Partial | Medium | Medium | tray.rs exists |
| Tray menu | Status, exit, request support | Missing | Medium | Medium | After tray works |
| Safe mode reboot | Reboot to safe mode + networking | Missing | Medium | Hard | Malware removal feature |
| Emergency reboot | Force immediate reboot | Missing | Low | Medium | Useful but not critical |
| Wake-on-LAN | Wake offline machines | Missing | Low | Hard | Needs local relay agent |
| Self-delete (support mode) | Cleanup after one-time session | Missing | High | Medium | One-time agent requirement |
| Run without admin | User-space support sessions | Partial | Critical | Quick Win | Should work, needs testing |
| Optional elevation | Admin access when needed | Missing | High | Medium | UAC prompt + elevated mode |
| **Session Management** |
| Transfer session | Hand off to another tech | Missing | Medium | Hard | Useful collaboration feature |
| Pause/resume session | Temporary pause | Missing | Low | Medium | Nice to have |
| Session notes | Per-session documentation | Missing | Medium | Medium | Good MSP practice |
| Timeline view | Connection history | Partial | Medium | Medium | Database exists, UI needed |
| Session tags | Categorize sessions | Missing | Low | Quick Win | After basic session mgmt |
| **Integration** |
| GuruRMM integration | Shared auth, launch from RMM | Missing | Low | Hard | Future phase |
| PSA integration | HaloPSA, Autotask, CW | Missing | Low | Very Hard | Future phase |
| Standalone mode | Works without RMM | Complete | Critical | - | Current state |
---
## 2. MVP Feature Set Recommendation
To ship a **Minimum Viable Product** that MSPs can actually use, the following features are ESSENTIAL:
### ABSOLUTE MVP (cannot function without these)
1. End-user portal with support code entry
2. Auto-download one-time agent executable
3. Browser-based screen viewing (working)
4. Mouse and keyboard control (working)
5. Dashboard with session list and join capability
**Current Status:** Items 3-4 mostly done, items 1-2-5 are blockers
### CRITICAL MVP (needed for real MSP work)
6. Text clipboard sync (bidirectional)
7. File download from remote machine
8. Remote PowerShell/CMD execution with output streaming
9. Persistent agent installer (Windows service)
10. Multi-session handling (tech manages multiple sessions)
**Current Status:** Item 9 partially done, items 6-8-10 missing
### HIGH PRIORITY MVP (competitive parity)
11. Chat between tech and end user
12. Process viewer with kill capability
13. System information display
14. Installer builder with custom metadata
15. Dashboard machine grouping (by company/site)
**Current Status:** All missing except partial system info
### RECOMMENDED MVP SCOPE
Include: Items 1-14 (defer item 15 to post-launch)
Defer: MSI packaging, advanced backstage tools, session recording, mobile support
**Estimated Time:** 8-10 weeks with focused development
---
## 3. Critical Gaps That Block Launch
### LAUNCH BLOCKERS (ship-stoppers)
| Gap | Impact | Why Critical | Effort |
|-----|--------|-------------|--------|
| **No end-user portal** | Cannot ship | End users have no way to initiate support sessions. Support codes are useless without a portal to enter them. | Medium (2 weeks) |
| **No one-time agent download** | Cannot ship | The entire attended support model depends on downloading a temporary agent. Without this, only persistent agents work. | Hard (3-4 weeks) |
| **Input relay incomplete** | Barely functional | If mouse/keyboard doesn't work reliably, it's not remote control - it's just screen viewing. | Quick Win (1 week) |
| **No dashboard session list UI** | Cannot ship | Technicians can't see or join sessions. The API exists but there's no UI to use it. | Medium (2 weeks) |
**Total to unblock launch:** 8-9 weeks
### USABILITY BLOCKERS (can ship but product is barely functional)
| Gap | Impact | Why Critical | Effort |
|-----|--------|-------------|--------|
| **No clipboard sync** | Poor UX | Industry standard feature. MSPs expect to copy/paste credentials, commands, URLs between local and remote. Howard emphasized this. | Medium (2 weeks) |
| **No file transfer** | Limited utility | Essential for support work - uploading fixes, downloading logs, transferring files. Every competitor has this. | Medium (2-3 weeks) |
| **No remote CMD/PowerShell** | Deal breaker for MSPs | Howard's #1 feature request. Windows admin work requires running commands remotely. ScreenConnect has this, we must have it. | Medium (2 weeks) |
| **No installer builder** | Deployment blocker | Can't easily deploy to client machines. Manual agent setup doesn't scale. MSPs need custom installers with company/site metadata baked in. | Very Hard (4+ weeks) |
**Total to be competitive:** Additional 10-13 weeks
---
## 4. Quick Wins (High Value, Low Effort)
These features provide significant value with minimal implementation effort:
| Feature | Value | Effort | Rationale |
|---------|-------|--------|-----------|
| **Complete input relay** | Critical | 1 week | Server already relays messages. Just connect viewer input capture to WebSocket properly. |
| **Text clipboard sync** | High | 2 weeks | Protocol defined. Implement Windows clipboard API on agent, JS clipboard API in viewer. Start with text only. |
| **System info display** | Medium | 1 week | AgentStatus already collects hostname, OS, uptime. Just display it in dashboard detail panel. |
| **Basic file download** | High | 1-2 weeks | Simpler than bidirectional. Agent reads file, streams chunks, viewer saves. High MSP value. |
| **Session detail panel** | High | 1 week | Data exists (session info, machine info). Create UI component with tabs (Info, Screen, Chat, etc.). |
| **Support code in download URL** | Medium | 1 week | Server embeds code in downloaded agent filename or metadata. Agent reads it on startup. |
| **Join session button** | Critical | 3 days | Straightforward: button clicks -> JWT auth -> WebSocket connect -> viewer loads. |
| **PowerShell timeout controls** | High | 3 days | Howard specifically requested checkboxes/textboxes instead of typing timeout flags every time. |
| **Process list viewer** | Medium | 1 week | Windows API call to enumerate processes. Display in dashboard. Foundation for kill process. |
| **Chat UI integration** | Medium | 1-2 weeks | ChatController exists on agent. Protocol defined. Just create dashboard UI component and wire it up. |
**Total quick wins time:** 8-10 weeks (if done in parallel: 4-5 weeks)
---
## 5. Feature Prioritization Roadmap
### PHASE A: Make It Work (6-8 weeks)
**Goal:** Basic functional product for attended support
| Priority | Feature | Status | Effort |
|----------|---------|--------|--------|
| 1 | End-user portal (support code entry) | Missing | 2 weeks |
| 2 | One-time agent download | Missing | 3-4 weeks |
| 3 | Complete input relay (mouse/keyboard) | Partial | 1 week |
| 4 | Dashboard session list UI | Partial | 2 weeks |
| 5 | Session detail panel with tabs | Missing | 1 week |
| 6 | Join session functionality | Missing | 3 days |
**Deliverable:** MSP can generate support code, end user can connect, tech can view screen and control remotely.
### PHASE B: Make It Useful (6-8 weeks)
**Goal:** Competitive for real support work
| Priority | Feature | Status | Effort |
|----------|---------|--------|--------|
| 7 | Text clipboard sync (bidirectional) | Missing | 2 weeks |
| 8 | Remote PowerShell execution | Missing | 2 weeks |
| 9 | PowerShell timeout controls | Missing | 3 days |
| 10 | Basic file download | Missing | 1-2 weeks |
| 11 | Process list viewer | Missing | 1 week |
| 12 | System information display | Partial | 1 week |
| 13 | Chat UI in dashboard | Missing | 1-2 weeks |
| 14 | Multi-monitor support | Missing | 2 weeks |
**Deliverable:** Full-featured support tool competitive with ScreenConnect for attended sessions.
### PHASE C: Make It Production (8-10 weeks)
**Goal:** Complete MSP solution with deployment tools
| Priority | Feature | Status | Effort |
|----------|---------|--------|--------|
| 15 | Persistent agent Windows service | Partial | 2 weeks |
| 16 | Installer builder (custom EXE) | Missing | 4 weeks |
| 17 | Dashboard machine grouping | Missing | 2 weeks |
| 18 | Search and filtering | Missing | 2 weeks |
| 19 | File upload capability | Missing | 2 weeks |
| 20 | Rich clipboard (HTML, RTF, images) | Missing | 2 weeks |
| 21 | Services list viewer | Missing | 1 week |
| 22 | Command audit logging | Partial | 1 week |
**Deliverable:** Full MSP remote access solution with deployment automation.
### PHASE D: Polish & Advanced Features (ongoing)
**Goal:** Feature parity with ScreenConnect, competitive advantages
| Priority | Feature | Status | Effort |
|----------|---------|--------|--------|
| 23 | MSI packaging (64-bit) | Missing | 3-4 weeks |
| 24 | MFA/2FA support | Missing | 2 weeks |
| 25 | Role-based permissions enforcement | Partial | 2 weeks |
| 26 | Session recording | Missing | 4+ weeks |
| 27 | Safe mode reboot | Missing | 2 weeks |
| 28 | Event log viewer | Missing | 3 weeks |
| 29 | Auto-update complete | Partial | 3 weeks |
| 30 | Mobile viewer | Missing | 8+ weeks |
**Deliverable:** Enterprise-grade solution with advanced features.
---
## 6. Requirement Quality Assessment
### CLEAR AND TESTABLE
- Most requirements are well-defined with specific capabilities
- Mock-ups provided for dashboard design (helpful)
- Howard's feedback is concrete (PowerShell timeouts, 64-bit client)
- Protocol definitions are precise
### CONFLICTS OR AMBIGUITIES
- **None identified** - requirements are internally consistent
- Design mockups match written requirements
### UNREALISTIC REQUIREMENTS
- **None found** - all features exist in ScreenConnect and are technically feasible
- MSI packaging is complex but standard industry practice
- Safe mode reboot is possible via Windows APIs
- WoL requires network relay but requirement acknowledges this
### MISSING REQUIREMENTS
| Area | What's Missing | Impact | Recommendation |
|------|---------------|--------|----------------|
| **Performance** | Vague targets ("30+ FPS on LAN") | Can't validate if met | Define minimum acceptable: "15+ FPS WAN, 30+ FPS LAN, <200ms input latency" |
| **Bandwidth** | No network requirements | Can't test WAN scenarios | Specify: "Must work on 1 Mbps WAN, graceful degradation on slower" |
| **Scalability** | "50+ concurrent agents" is vague | Don't know when to scale | Define: "Single server: 100 agents, 25 concurrent sessions. Cluster: 1000+ agents" |
| **Disaster Recovery** | No backup/restore mentioned | Production risk | Add: "Database backup, config export/import, agent re-registration" |
| **Migration** | No ScreenConnect import | Friction for new customers | Add: "Import ScreenConnect sessions, export contact lists" |
| **Mobile** | Mentioned but not detailed | Scope unclear | Either detail requirements or defer to Phase 2 entirely |
| **API** | Limited to PSA integration | Third-party extensibility | Add: "REST API for session control, webhook events" |
| **Monitoring** | No health checks, metrics | Operational blindness | Add: "Prometheus metrics, health endpoints, alerting" |
| **Internationalization** | English only assumed | Global MSPs excluded | Consider: "i18n support for dashboard" or explicitly English-only |
| **Accessibility** | No WCAG compliance | ADA compliance risk | Add: "WCAG 2.1 AA compliance" or acknowledge limitation |
### RECOMMENDATIONS FOR REQUIREMENTS
1. **Add Performance Acceptance Criteria**
- Minimum FPS: 15 FPS WAN, 30 FPS LAN
- Maximum latency: 200ms input delay on WAN
- Bandwidth: Functional on 1 Mbps, optimal on 5+ Mbps
- Scalability: 100 agents / 25 concurrent sessions per server
2. **Create ScreenConnect Feature Parity Checklist**
- List all ScreenConnect features
- Mark must-have vs nice-to-have
- Use as validation for "done"
3. **Detail or Defer Mobile Requirements**
- Either: Full mobile spec (iOS/Android apps)
- Or: Explicitly defer to Phase 2, focus on web
4. **Add Operational Requirements**
- Monitoring and alerting
- Backup and restore procedures
- Multi-server deployment architecture
- Load balancing strategy
5. **Specify Migration/Import Tools**
- ScreenConnect session import (if possible)
- Bulk agent deployment strategies
- Configuration migration scripts
---
## 7. Implementation Status Summary
### By Category (% Complete)
| Category | Complete | Partial | Missing | Overall % |
|----------|----------|---------|---------|-----------|
| Infrastructure | 10 | 0 | 0 | 100% |
| Support Sessions | 4 | 1 | 2 | 70% |
| End-User Portal | 0 | 0 | 5 | 0% |
| Screen Viewing | 5 | 2 | 2 | 65% |
| Remote Control | 3 | 3 | 1 | 60% |
| Clipboard | 0 | 0 | 5 | 0% |
| File Transfer | 0 | 0 | 5 | 0% |
| Backstage Tools | 0 | 2 | 10 | 10% |
| Chat/Messaging | 0 | 2 | 4 | 20% |
| Dashboard UI | 2 | 3 | 10 | 25% |
| Unattended Agents | 5 | 3 | 1 | 70% |
| Installer Builder | 0 | 0 | 7 | 0% |
| Auto-Update | 2 | 3 | 3 | 40% |
| Security | 4 | 2 | 4 | 50% |
| Agent Features | 0 | 3 | 6 | 20% |
| Session Management | 0 | 1 | 4 | 10% |
**Overall Project Completion: 32%**
### What Works Today
- Persistent agent connects to server
- JWT authentication for dashboard
- Support code generation and validation
- Screen capture (DXGI + GDI fallback)
- Basic WebSocket relay
- Database persistence
- User management
- Machine registration
### What Doesn't Work Today
- End users can't initiate sessions (no portal)
- Input control not fully wired
- No clipboard sync
- No file transfer
- No backstage tools
- No installer builder
- Dashboard is very basic
- Chat not integrated
### What Needs Completion
- Wire up existing components (input, chat, system info)
- Build missing UI (portal, dashboard panels)
- Implement protocol features (clipboard, file transfer)
- Create new features (backstage tools, installer builder)
---
## 8. Risk Assessment
### HIGH RISK (likely to cause delays)
| Risk | Probability | Impact | Mitigation |
|------|------------|--------|------------|
| One-time agent download complexity | High | Critical | Start early, may need to simplify (just run without install) |
| Installer builder scope creep | High | High | Define MVP: EXE only, defer MSI to Phase 2 |
| Input relay timing issues | Medium | Critical | Thorough testing on various networks |
| Clipboard compatibility issues | Medium | High | Start with text-only, add formats incrementally |
### MEDIUM RISK (manageable)
| Risk | Probability | Impact | Mitigation |
|------|------------|--------|------------|
| Multi-monitor switching complexity | Medium | Medium | Good protocol support, mainly UI work |
| File transfer chunking/resume | Medium | Medium | Simple implementation first, optimize later |
| PowerShell output streaming | Medium | High | Use existing .NET libraries, test thoroughly |
| Dashboard real-time updates | Low | High | WebSocket infrastructure exists |
### LOW RISK (minor concerns)
| Risk | Probability | Impact | Mitigation |
|------|------------|--------|------------|
| MSI packaging learning curve | Low | Medium | Defer to Phase D, use WiX |
| Safe mode reboot compatibility | Low | Low | Windows API well-documented |
| Cross-browser compatibility | Low | Medium | Modern browsers similar, test all |
---
## 9. Recommendations
### IMMEDIATE ACTIONS (Week 1-2)
1. **Create End-User Portal** (static HTML/JS)
- Support code entry form
- Validation via API
- Download link generation
- Browser detection for instructions
2. **Complete Input Relay Chain**
- Verify viewer captures mouse/keyboard
- Ensure server relays to agent
- Test end-to-end on LAN and WAN
3. **Build Dashboard Session List UI**
- Display active sessions from API
- Real-time updates via WebSocket
- Join button that launches viewer
### SHORT TERM (Week 3-8)
4. **One-Time Agent Download**
- Simplify: agent runs without install
- Embed support code in download URL
- Test on Windows 10/11 without admin
5. **Text Clipboard Sync**
- Windows clipboard API on agent
- JavaScript clipboard API in viewer
- Bidirectional sync on change
6. **Remote PowerShell**
- Execute process, capture stdout/stderr
- Stream output to dashboard
- UI with timeout controls (checkboxes)
7. **File Download**
- Agent reads file, chunks it
- Stream via WebSocket
- Viewer saves to local disk
### MEDIUM TERM (Week 9-16)
8. **Persistent Agent Service Mode**
- Complete Windows service installation
- Auto-start on boot
- Test on Server 2016/2019/2022
9. **Dashboard Enhancements**
- Machine grouping by company/site
- Search and filtering
- Session detail panels with tabs
10. **Installer Builder MVP**
- Generate custom EXE with metadata
- Server-side build pipeline
- Download from dashboard
### LONG TERM (Week 17+)
11. **MSI Packaging**
- WiX toolset integration
- 64-bit support (Howard requirement)
- Silent install for GPO
12. **Advanced Features**
- Session recording
- MFA/2FA
- Mobile viewer
- PSA integrations
### PROCESS IMPROVEMENTS
13. **Add Performance Testing**
- Define FPS benchmarks
- Latency measurement
- Bandwidth profiling
14. **Create Test Plan**
- End-to-end scenarios
- Cross-browser testing
- Network simulation (WAN throttling)
15. **Update Requirements Document**
- Add missing operational requirements
- Define performance targets
- Create ScreenConnect parity checklist
---
## 10. Conclusion
GuruConnect has **excellent technical foundations** but needs **significant feature development** to reach MVP. The infrastructure (server, protocol, database, auth) is production-ready, but user-facing features are 30-35% complete.
### Path to Launch
**Conservative Estimate:** 20-24 weeks to production-ready
**Aggressive Estimate:** 12-16 weeks with focused development
**Recommended Approach:** 3-phase delivery
1. **Phase A (6-8 weeks):** Basic functional product - attended support only
2. **Phase B (6-8 weeks):** Competitive features - clipboard, file transfer, PowerShell
3. **Phase C (8-10 weeks):** Full MSP solution - installer builder, grouping, polish
### Key Success Factors
1. **Prioritize ruthlessly** - Defer nice-to-haves (MSI, session recording, mobile)
2. **Leverage existing code** - Chat, system info, auth already partially done
3. **Start with simple implementations** - Text-only clipboard, download-only files
4. **Focus on Howard's priorities** - PowerShell/CMD, 64-bit client, clipboard
5. **Test early and often** - Input latency, cross-browser, WAN performance
### Critical Path Items
The following items are on the critical path and cannot be parallelized:
1. End-user portal (blocks testing)
2. One-time agent download (blocks end-user usage)
3. Input relay completion (blocks remote control validation)
4. Dashboard session UI (blocks technician workflow)
Everything else can be developed in parallel by separate developers.
**Bottom Line:** The project is viable and well-architected, but needs 3-6 months of focused feature development to compete with ScreenConnect. Howard's team should plan accordingly.
---
**Generated:** 2026-01-17
**Next Review:** After Phase A completion

View File

@@ -1,336 +0,0 @@
# GuruConnect Production Infrastructure Status
**Date:** 2026-01-18 15:36 UTC
**Server:** 172.16.3.30 (gururmm)
**Installation Status:** IN PROGRESS
---
## Completed Components
### 1. Systemd Service - ACTIVE ✓
**Status:** Running
**PID:** 3944724
**Service:** guruconnect.service
**Auto-start:** Enabled
```bash
sudo systemctl status guruconnect
sudo journalctl -u guruconnect -f
```
**Features:**
- Auto-restart on failure (10s delay, max 3 in 5 min)
- Resource limits: 65536 FDs, 4096 processes
- Security hardening enabled
- Journald logging integration
- Watchdog support (30s keepalive)
---
### 2. Automated Backups - CONFIGURED ✓
**Status:** Active (waiting)
**Timer:** guruconnect-backup.timer
**Next Run:** Mon 2026-01-19 00:00:00 UTC (8h remaining)
```bash
sudo systemctl status guruconnect-backup.timer
```
**Configuration:**
- Schedule: Daily at 2:00 AM UTC
- Location: `/home/guru/backups/guruconnect/`
- Format: `guruconnect-YYYY-MM-DD-HHMMSS.sql.gz`
- Retention: 30 daily, 4 weekly, 6 monthly
- Compression: Gzip
**Manual Backup:**
```bash
cd ~/guru-connect/server
./backup-postgres.sh
```
---
### 3. Log Rotation - CONFIGURED ✓
**Status:** Configured
**File:** `/etc/logrotate.d/guruconnect`
**Configuration:**
- Rotation: Daily
- Retention: 30 days
- Compression: Yes (delayed 1 day)
- Post-rotate: Reload guruconnect service
---
### 4. Passwordless Sudo - CONFIGURED ✓
**Status:** Active
**File:** `/etc/sudoers.d/guru`
The `guru` user can now run all commands with `sudo` without password prompts.
---
## In Progress
### 5. Prometheus & Grafana - INSTALLING ⏳
**Status:** Installing (in progress)
**Progress:**
- ✓ Prometheus packages downloaded and installed
- ✓ Prometheus Node Exporter installed
- ⏳ Grafana being installed (194 MB download complete, unpacking)
**Expected Installation Time:** ~5-10 minutes remaining
**Will be available at:**
- Prometheus: http://172.16.3.30:9090
- Grafana: http://172.16.3.30:3000 (admin/admin)
- Node Exporter: http://172.16.3.30:9100/metrics
---
## Server Status
### GuruConnect Server
**Health:** OK
**Metrics:** Operational
**Uptime:** 20 seconds (via systemd)
```bash
# Health check
curl http://172.16.3.30:3002/health
# Metrics
curl http://172.16.3.30:3002/metrics
```
### Database
**Status:** Connected
**Users:** 2
**Machines:** 15 (restored from database)
**Credentials:** Fixed (gc_a7f82d1e4b9c3f60)
### Authentication
**Admin User:** howard
**Password:** AdminGuruConnect2026
**Dashboard:** https://connect.azcomputerguru.com/dashboard
**JWT Token Example:**
```
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIwOThhNmEyNC05YmNiLTRmOWItODUyMS04ZmJiOTU5YzlmM2YiLCJ1c2VybmFtZSI6Imhvd2FyZCIsInJvbGUiOiJhZG1pbiIsInBlcm1pc3Npb25zIjpbInZpZXciLCJjb250cm9sIiwidHJhbnNmZXIiLCJtYW5hZ2VfY2xpZW50cyJdLCJleHAiOjE3Njg3OTUxNDYsImlhdCI6MTc2ODcwODc0Nn0.q2SFMDOWDH09kLj3y1MiVXFhIqunbHHp_-kjJP6othA
```
---
## Verification Commands
```bash
# Run comprehensive verification
bash ~/guru-connect/verify-installation.sh
# Check individual components
sudo systemctl status guruconnect
sudo systemctl status guruconnect-backup.timer
sudo systemctl status prometheus
sudo systemctl status grafana-server
# Test endpoints
curl http://172.16.3.30:3002/health
curl http://172.16.3.30:3002/metrics
curl http://172.16.3.30:9090 # Prometheus (after install)
curl http://172.16.3.30:3000 # Grafana (after install)
```
---
## Next Steps
### After Prometheus/Grafana Installation Completes
1. **Access Grafana:**
- URL: http://172.16.3.30:3000
- Login: admin/admin
- Change default password
2. **Import Dashboard:**
```
Grafana > Dashboards > Import
Upload: ~/guru-connect/infrastructure/grafana-dashboard.json
```
3. **Verify Prometheus Scraping:**
- URL: http://172.16.3.30:9090/targets
- Check GuruConnect target is UP
- Verify metrics being collected
4. **Test Alerts:**
- URL: http://172.16.3.30:9090/alerts
- Review configured alert rules
- Consider configuring Alertmanager for notifications
---
## Production Readiness Checklist
- [x] Server running via systemd
- [x] Database connected and operational
- [x] Admin credentials configured
- [x] Automated backups configured
- [x] Log rotation configured
- [x] Passwordless sudo enabled
- [ ] Prometheus/Grafana installed (in progress)
- [ ] Grafana dashboard imported
- [ ] Grafana default password changed
- [ ] Firewall rules reviewed
- [ ] SSL/TLS certificates valid
- [ ] Monitoring alerts tested
- [ ] Backup restore tested
- [ ] Health monitoring cron configured (optional)
---
## Infrastructure Files
**On Server:**
```
/home/guru/guru-connect/
├── server/
│ ├── guruconnect.service # Systemd service unit
│ ├── setup-systemd.sh # Service installer
│ ├── backup-postgres.sh # Backup script
│ ├── restore-postgres.sh # Restore script
│ ├── health-monitor.sh # Health checks
│ ├── guruconnect-backup.service # Backup service unit
│ ├── guruconnect-backup.timer # Backup timer
│ ├── guruconnect.logrotate # Log rotation config
│ └── start-secure.sh # Manual start script
├── infrastructure/
│ ├── prometheus.yml # Prometheus config
│ ├── alerts.yml # Alert rules
│ ├── grafana-dashboard.json # Pre-built dashboard
│ └── setup-monitoring.sh # Monitoring installer
├── install-production-infrastructure.sh # Master installer
└── verify-installation.sh # Verification script
```
**Systemd Files:**
```
/etc/systemd/system/
├── guruconnect.service
├── guruconnect-backup.service
└── guruconnect-backup.timer
```
**Configuration Files:**
```
/etc/prometheus/
├── prometheus.yml
└── alerts.yml
/etc/logrotate.d/
└── guruconnect
/etc/sudoers.d/
└── guru
```
---
## Troubleshooting
### Server Not Starting
```bash
# Check logs
sudo journalctl -u guruconnect -n 50
# Check for port conflicts
sudo netstat -tulpn | grep 3002
# Verify binary
ls -la ~/guru-connect/target/x86_64-unknown-linux-gnu/release/guruconnect-server
# Check environment
cat ~/guru-connect/server/.env
```
### Database Connection Issues
```bash
# Test connection
PGPASSWORD=gc_a7f82d1e4b9c3f60 psql -h localhost -U guruconnect -d guruconnect -c 'SELECT 1'
# Check PostgreSQL
sudo systemctl status postgresql
# Verify credentials
cat ~/guru-connect/server/.env | grep DATABASE_URL
```
### Backup Issues
```bash
# Test backup manually
cd ~/guru-connect/server
./backup-postgres.sh
# Check backup directory
ls -lh /home/guru/backups/guruconnect/
# View timer logs
sudo journalctl -u guruconnect-backup -n 50
```
---
## Performance Metrics
**Current Metrics (Prometheus):**
- Active Sessions: 0
- Server Uptime: 20 seconds
- Database Connected: Yes
- Request Latency: <1ms
- Memory Usage: 1.6M
- CPU Usage: Minimal
**10 Prometheus Metrics Collected:**
1. guruconnect_requests_total
2. guruconnect_request_duration_seconds
3. guruconnect_sessions_total
4. guruconnect_active_sessions
5. guruconnect_session_duration_seconds
6. guruconnect_connections_total
7. guruconnect_active_connections
8. guruconnect_errors_total
9. guruconnect_db_operations_total
10. guruconnect_db_query_duration_seconds
---
## Security Status
**Week 1 Security Fixes:** 10/13 (77%)
**Week 2 Infrastructure:** 100% Complete
**Active Security Features:**
- JWT authentication with 24h expiration
- Argon2id password hashing
- Security headers (CSP, X-Frame-Options, etc.)
- Token blacklist for logout
- Database credentials encrypted in .env
- API key validation for agents
- IP logging for connections
---
**Last Updated:** 2026-01-18 15:36 UTC
**Next Update:** After Prometheus/Grafana installation completes

View File

@@ -1,518 +0,0 @@
# GuruConnect Production Infrastructure Installation Guide
**Date:** 2026-01-18
**Server:** 172.16.3.30
**Status:** Core system operational, infrastructure ready for installation
---
## Current Status
- Server Process: Running (PID 3847752)
- Health Check: OK
- Metrics Endpoint: Operational
- Database: Connected (2 users)
- Dashboard: https://connect.azcomputerguru.com/dashboard
**Login:** username=`howard`, password=`AdminGuruConnect2026`
---
## Installation Options
### Option 1: One-Command Installation (Recommended)
Run the master installation script that installs everything:
```bash
ssh guru@172.16.3.30
cd ~/guru-connect
sudo bash install-production-infrastructure.sh
```
This will install:
1. Systemd service for auto-start and management
2. Prometheus & Grafana monitoring stack
3. Automated PostgreSQL backups (daily at 2:00 AM)
4. Log rotation configuration
**Time:** ~10-15 minutes (Grafana installation takes longest)
---
### Option 2: Step-by-Step Manual Installation
If you prefer to install components individually:
#### Step 1: Install Systemd Service
```bash
ssh guru@172.16.3.30
cd ~/guru-connect/server
sudo ./setup-systemd.sh
```
**What this does:**
- Installs GuruConnect as a systemd service
- Enables auto-start on boot
- Configures auto-restart on failure
- Sets resource limits and security hardening
**Verify:**
```bash
sudo systemctl status guruconnect
sudo journalctl -u guruconnect -n 20
```
---
#### Step 2: Install Prometheus & Grafana
```bash
ssh guru@172.16.3.30
cd ~/guru-connect/infrastructure
sudo ./setup-monitoring.sh
```
**What this does:**
- Installs Prometheus for metrics collection
- Installs Grafana for visualization
- Configures Prometheus to scrape GuruConnect metrics
- Sets up Prometheus data source in Grafana
**Access:**
- Prometheus: http://172.16.3.30:9090
- Grafana: http://172.16.3.30:3000 (admin/admin)
**Post-installation:**
1. Access Grafana at http://172.16.3.30:3000
2. Login with admin/admin
3. Change the default password
4. Import dashboard:
- Go to Dashboards > Import
- Upload `~/guru-connect/infrastructure/grafana-dashboard.json`
---
#### Step 3: Install Automated Backups
```bash
ssh guru@172.16.3.30
# Create backup directory
sudo mkdir -p /home/guru/backups/guruconnect
sudo chown guru:guru /home/guru/backups/guruconnect
# Install systemd timer
sudo cp ~/guru-connect/server/guruconnect-backup.service /etc/systemd/system/
sudo cp ~/guru-connect/server/guruconnect-backup.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable guruconnect-backup.timer
sudo systemctl start guruconnect-backup.timer
```
**Verify:**
```bash
sudo systemctl status guruconnect-backup.timer
sudo systemctl list-timers
```
**Test manual backup:**
```bash
cd ~/guru-connect/server
./backup-postgres.sh
ls -lh /home/guru/backups/guruconnect/
```
**Backup Schedule:** Daily at 2:00 AM
**Retention:** 30 daily, 4 weekly, 6 monthly backups
---
#### Step 4: Install Log Rotation
```bash
ssh guru@172.16.3.30
sudo cp ~/guru-connect/server/guruconnect.logrotate /etc/logrotate.d/guruconnect
sudo chmod 644 /etc/logrotate.d/guruconnect
```
**Verify:**
```bash
sudo cat /etc/logrotate.d/guruconnect
sudo logrotate -d /etc/logrotate.d/guruconnect
```
**Log Rotation:** Daily, 30 days retention, compressed
---
## Verification
After installation, verify everything is working:
```bash
ssh guru@172.16.3.30
bash ~/guru-connect/verify-installation.sh
```
Expected output (all green):
- Server process: Running
- Health endpoint: OK
- Metrics endpoint: OK
- Systemd service: Active
- Prometheus: Active
- Grafana: Active
- Backup timer: Active
- Log rotation: Configured
- Database: Connected
---
## Post-Installation Tasks
### 1. Configure Grafana
1. Access http://172.16.3.30:3000
2. Login with admin/admin
3. Change password when prompted
4. Import dashboard:
```
Dashboards > Import > Upload JSON file
Select: ~/guru-connect/infrastructure/grafana-dashboard.json
```
### 2. Test Backup & Restore
**Test backup:**
```bash
ssh guru@172.16.3.30
cd ~/guru-connect/server
./backup-postgres.sh
```
**Verify backup created:**
```bash
ls -lh /home/guru/backups/guruconnect/
```
**Test restore (CAUTION - use test database):**
```bash
cd ~/guru-connect/server
./restore-postgres.sh /home/guru/backups/guruconnect/guruconnect-YYYY-MM-DD-HHMMSS.sql.gz
```
### 3. Configure NPM (Nginx Proxy Manager)
If Prometheus/Grafana need external access:
1. Add proxy hosts in NPM:
- prometheus.azcomputerguru.com -> http://172.16.3.30:9090
- grafana.azcomputerguru.com -> http://172.16.3.30:3000
2. Enable SSL/TLS via Let's Encrypt
3. Restrict access (firewall or NPM access lists)
### 4. Test Health Monitoring
```bash
ssh guru@172.16.3.30
cd ~/guru-connect/server
./health-monitor.sh
```
Expected output: All checks passed
---
## Service Management
### GuruConnect Server
```bash
# Start server
sudo systemctl start guruconnect
# Stop server
sudo systemctl stop guruconnect
# Restart server
sudo systemctl restart guruconnect
# Check status
sudo systemctl status guruconnect
# View logs
sudo journalctl -u guruconnect -f
# View recent logs
sudo journalctl -u guruconnect -n 100
```
### Prometheus
```bash
# Status
sudo systemctl status prometheus
# Restart
sudo systemctl restart prometheus
# Logs
sudo journalctl -u prometheus -n 50
```
### Grafana
```bash
# Status
sudo systemctl status grafana-server
# Restart
sudo systemctl restart grafana-server
# Logs
sudo journalctl -u grafana-server -n 50
```
### Backups
```bash
# Check timer status
sudo systemctl status guruconnect-backup.timer
# Check when next backup runs
sudo systemctl list-timers
# Manually trigger backup
sudo systemctl start guruconnect-backup.service
# View backup logs
sudo journalctl -u guruconnect-backup -n 20
```
---
## Troubleshooting
### Server Won't Start
```bash
# Check logs
sudo journalctl -u guruconnect -n 50
# Check if port 3002 is in use
sudo netstat -tulpn | grep 3002
# Verify .env file
cat ~/guru-connect/server/.env
# Test manual start
cd ~/guru-connect/server
./start-secure.sh
```
### Database Connection Issues
```bash
# Test PostgreSQL
PGPASSWORD=gc_a7f82d1e4b9c3f60 psql -h localhost -U guruconnect -d guruconnect -c 'SELECT 1'
# Check PostgreSQL service
sudo systemctl status postgresql
# Verify DATABASE_URL in .env
cat ~/guru-connect/server/.env | grep DATABASE_URL
```
### Prometheus Not Scraping Metrics
```bash
# Check Prometheus targets
# Access: http://172.16.3.30:9090/targets
# Verify GuruConnect metrics endpoint
curl http://172.16.3.30:3002/metrics
# Check Prometheus config
sudo cat /etc/prometheus/prometheus.yml
# Restart Prometheus
sudo systemctl restart prometheus
```
### Grafana Dashboard Not Loading
```bash
# Check Grafana logs
sudo journalctl -u grafana-server -n 50
# Verify data source
# Access: http://172.16.3.30:3000/datasources
# Test Prometheus connection
curl http://localhost:9090/api/v1/query?query=up
```
---
## Monitoring & Alerts
### Prometheus Alerts
Configured alerts (from `infrastructure/alerts.yml`):
1. **GuruConnectDown** - Server unreachable for 1 minute
2. **HighErrorRate** - >10 errors/second for 5 minutes
3. **TooManyActiveSessions** - >100 active sessions
4. **HighRequestLatency** - p95 >1s for 5 minutes
5. **DatabaseOperationsFailure** - DB errors >1/second
6. **ServerRestarted** - Uptime <5 minutes (informational)
**View alerts:** http://172.16.3.30:9090/alerts
### Grafana Dashboard
Pre-configured panels:
1. Active Sessions (gauge)
2. Requests per Second (graph)
3. Error Rate (graph with alerting)
4. Request Latency p50/p95/p99 (graph)
5. Active Connections by Type (stacked graph)
6. Database Query Duration (graph)
7. Server Uptime (singlestat)
8. Total Sessions Created (singlestat)
9. Total Requests (singlestat)
10. Total Errors (singlestat with thresholds)
---
## Backup & Recovery
### Manual Backup
```bash
cd ~/guru-connect/server
./backup-postgres.sh
```
Backup location: `/home/guru/backups/guruconnect/guruconnect-YYYY-MM-DD-HHMMSS.sql.gz`
### Restore from Backup
**WARNING:** This will drop and recreate the database!
```bash
cd ~/guru-connect/server
./restore-postgres.sh /path/to/backup.sql.gz
```
The script will:
1. Stop GuruConnect service
2. Drop existing database
3. Recreate database
4. Restore from backup
5. Restart service
### Backup Verification
```bash
# List backups
ls -lh /home/guru/backups/guruconnect/
# Check backup size
du -sh /home/guru/backups/guruconnect/*
# Verify backup contents (without restoring)
zcat /path/to/backup.sql.gz | head -50
```
---
## Security Checklist
- [x] JWT secret configured (96-char base64)
- [x] Database password changed from default
- [x] Admin password changed from default
- [x] Security headers enabled (CSP, X-Frame-Options, etc.)
- [x] Database credentials in .env (not committed to git)
- [ ] Grafana default password changed (admin/admin)
- [ ] Firewall rules configured (limit access to monitoring ports)
- [ ] SSL/TLS enabled for public endpoints
- [ ] Backup encryption (optional - consider encrypting backups)
- [ ] Regular security updates (OS, PostgreSQL, Prometheus, Grafana)
---
## Files Reference
### Configuration Files
- `server/.env` - Environment variables and secrets
- `server/guruconnect.service` - Systemd service unit
- `infrastructure/prometheus.yml` - Prometheus scrape config
- `infrastructure/alerts.yml` - Alert rules
- `infrastructure/grafana-dashboard.json` - Pre-built dashboard
### Scripts
- `server/start-secure.sh` - Manual server start
- `server/backup-postgres.sh` - Manual backup
- `server/restore-postgres.sh` - Restore from backup
- `server/health-monitor.sh` - Health checks
- `server/setup-systemd.sh` - Install systemd service
- `infrastructure/setup-monitoring.sh` - Install Prometheus/Grafana
- `install-production-infrastructure.sh` - Master installer
- `verify-installation.sh` - Verify installation status
---
## Support & Documentation
**Main Documentation:**
- `PHASE1_WEEK2_INFRASTRUCTURE.md` - Week 2 planning
- `DEPLOYMENT_WEEK2_INFRASTRUCTURE.md` - Week 2 deployment log
- `CLAUDE.md` - Project coding guidelines
**Gitea Repository:**
- https://git.azcomputerguru.com/azcomputerguru/guru-connect
**Dashboard:**
- https://connect.azcomputerguru.com/dashboard
**API Docs:**
- http://172.16.3.30:3002/api/docs (if OpenAPI enabled)
---
## Next Steps (Phase 1 Week 3)
After infrastructure is fully installed:
1. **CI/CD Automation**
- Gitea CI pipeline configuration
- Automated builds on commit
- Automated tests in CI
- Deployment automation
- Build artifact storage
- Version tagging
2. **Advanced Monitoring**
- Alertmanager configuration for email/Slack alerts
- Custom Grafana dashboards
- Log aggregation (optional - Loki)
- Distributed tracing (optional - Jaeger)
3. **Production Hardening**
- Firewall configuration
- Fail2ban for brute-force protection
- Rate limiting
- DDoS protection
- Regular security audits
---
**Last Updated:** 2026-01-18 04:00 UTC
**Version:** Phase 1 Week 2 Complete

View File

@@ -1,789 +0,0 @@
# GuruConnect - Master Action Plan
**Comprehensive Review Synthesis**
**Date:** 2026-01-17
**Project Status:** Infrastructure Complete, 30-35% Feature Complete
**Reviews Conducted:** 6 specialized analyses
---
## EXECUTIVE SUMMARY
GuruConnect has **excellent technical foundations** but requires **significant development** across security, features, UI/UX, and infrastructure before production readiness. All reviews converge on a **3-6 month timeline** to MVP with focused effort.
### Overall Grades
| Review Area | Grade | Completion | Key Finding |
|-------------|-------|------------|-------------|
| **Security** | D+ | 40% secure | 5 CRITICAL vulnerabilities must be fixed before launch |
| **Architecture** | B- | 30% complete | Solid design, needs feature implementation |
| **Code Quality** | B+ | 85% ready | High quality Rust code, good practices |
| **Infrastructure** | D+ | 15-20% ready | No systemd, no monitoring, manual deployment |
| **Frontend/UI** | C+ | 35-40% complete | Good visual design, massive UX gaps |
| **Requirements Gap** | C | 30-35% complete | 4 launch blockers, 10+ critical missing features |
### Critical Path Insights
**LAUNCH BLOCKERS** (Cannot ship without):
1. JWT secret hardcoded (SECURITY)
2. No end-user portal (FUNCTIONALITY)
3. No one-time agent download (FUNCTIONALITY)
4. Input relay incomplete (FUNCTIONALITY)
5. No systemd service (INFRASTRUCTURE)
**Time to Unblock:** 10-12 weeks minimum
### Recommended Approach
**PHASE 1: Security & Foundation** (3-4 weeks)
Fix all critical security issues, establish proper deployment infrastructure
**PHASE 2: Core Features** (6-8 weeks)
Build missing launch blockers: portal, agent download, input completion, dashboard UI
**PHASE 3: Competitive Features** (6-8 weeks)
Add clipboard, file transfer, PowerShell, chat - features needed to compete with ScreenConnect
**PHASE 4: Polish & Production** (4-6 weeks)
Installer builder, machine grouping, monitoring, optimization
**Total Time to Production:** 19-26 weeks (Conservative: 26 weeks, Aggressive: 16 weeks)
---
## 1. CRITICAL SECURITY ISSUES (Must Fix Before Launch)
### SEVERITY: CRITICAL (5 issues)
| ID | Issue | Impact | Fix Effort | Priority |
|----|-------|--------|-----------|----------|
| **SEC-1** | JWT secret hardcoded in source | Anyone can forge admin tokens, full system compromise | 2 hours | P0 - IMMEDIATE |
| **SEC-2** | No rate limiting on auth endpoints | Brute force attacks succeed | 1 day | P0 - IMMEDIATE |
| **SEC-3** | SQL injection in machine filters | Database compromise | 3 days | P0 - IMMEDIATE |
| **SEC-4** | Agent connections without validation | Rogue agents can connect | 2 days | P0 - IMMEDIATE |
| **SEC-5** | Session takeover possible | Attackers can hijack sessions | 2 days | P0 - IMMEDIATE |
**Total Critical Fix Time:** 1.5 weeks
### SEVERITY: HIGH (8 issues)
| ID | Issue | Impact | Fix Effort | Priority |
|----|-------|--------|-----------|----------|
| **SEC-6** | Plaintext passwords in logs | Credential exposure | 1 day | P1 |
| **SEC-7** | No input sanitization (XSS) | Dashboard compromise | 2 days | P1 |
| **SEC-8** | Missing TLS cert validation | MITM attacks | 1 day | P1 |
| **SEC-9** | Weak PBKDF2 password hashing | Password cracking easier | 1 day | P1 |
| **SEC-10** | No HTTPS enforcement | Credential interception | 4 hours | P1 |
| **SEC-11** | Overly permissive CORS | Cross-site attacks | 2 hours | P1 |
| **SEC-12** | No CSP headers | XSS attacks easier | 4 hours | P1 |
| **SEC-13** | Session tokens never expire | Stolen tokens valid forever | 1 day | P1 |
**Total High-Priority Fix Time:** 1.5 weeks
### Security Roadmap
**Week 1:**
- Day 1-2: Fix JWT secret (SEC-1), add env variable, rotate keys
- Day 3: Implement rate limiting (SEC-2)
- Day 4-5: Fix SQL injection (SEC-3), use parameterized queries
**Week 2:**
- Day 1-2: Fix agent validation (SEC-4)
- Day 3-4: Fix session takeover (SEC-5)
- Day 5: Add HTTPS enforcement (SEC-10)
**Week 3:**
- Day 1: Fix password logging (SEC-6)
- Day 2-3: Add input sanitization (SEC-7)
- Day 4: Upgrade to Argon2id (SEC-9)
- Day 5: Add session expiration (SEC-13)
**Security Testing:** After Week 3, conduct penetration testing
---
## 2. LAUNCH BLOCKERS (Cannot Ship Without These)
### Functional Blockers
| Blocker | Current State | Required State | Effort | Dependencies |
|---------|--------------|---------------|--------|--------------|
| **Portal Missing** | 0% | End-user portal with code entry, agent download | 2 weeks | None |
| **Agent Download** | 0% | One-time agent EXE with embedded code | 3-4 weeks | Portal |
| **Input Relay** | 50% | Complete mouse/keyboard viewer → agent | 1 week | None |
| **Dashboard UI** | 40% | Session list, join button, real-time updates | 2 weeks | None |
### Infrastructure Blockers
| Blocker | Current State | Required State | Effort | Dependencies |
|---------|--------------|---------------|--------|--------------|
| **Systemd Service** | None | Server runs as systemd service, auto-restart | 1 week | None |
| **Monitoring** | None | Prometheus metrics, health checks, alerting | 1 week | None |
| **Automated Backup** | None | Daily PostgreSQL backups, retention policy | 3 days | None |
| **CI/CD Pipeline** | None | Automated builds, tests, deployment | 1 week | None |
### Combined Launch Blocker Timeline
**Can be parallelized:**
- Security fixes (3 weeks) || Portal + Agent Download (5 weeks) || Infrastructure (2.5 weeks)
- Input relay (1 week) || Dashboard UI (2 weeks)
**Critical Path:** Portal → Agent Download → Testing = 6 weeks
**Parallel Work:** Security (3 weeks) + Infrastructure (2.5 weeks)
**Minimum Time to Launchable MVP:** 8-10 weeks (with 2+ developers)
---
## 3. FEATURE PRIORITIZATION MATRIX
### TIER 0: Launch Blockers (Must Have)
| Feature | Status | Effort | Critical Path | Owner |
|---------|--------|--------|---------------|-------|
| End-user portal | 0% | 2 weeks | YES | Frontend Dev |
| One-time agent download | 0% | 3-4 weeks | YES | Agent Dev |
| Complete input relay | 50% | 1 week | YES | Agent Dev |
| Dashboard session list UI | 40% | 2 weeks | YES | Frontend Dev |
| JWT secret externalized | 0% | 2 hours | NO | Backend Dev |
| SQL injection fixes | 0% | 3 days | NO | Backend Dev |
| Rate limiting | 0% | 1 day | NO | Backend Dev |
| Systemd service | 0% | 1 week | NO | DevOps |
### TIER 1: Critical for Usability (Howard's Priorities)
| Feature | Status | Effort | Business Value | Owner |
|---------|--------|--------|----------------|-------|
| Text clipboard sync | 0% | 2 weeks | HIGH - industry standard | Agent Dev |
| Remote PowerShell/CMD | 0% | 2 weeks | CRITICAL - Howard's #1 request | Agent Dev |
| PowerShell timeout controls | 0% | 3 days | HIGH - Howard specific ask | Frontend Dev |
| File download | 0% | 1-2 weeks | HIGH - essential for support | Agent Dev |
| System info display | 20% | 1 week | MEDIUM - quick win | Frontend Dev |
| Chat UI integration | 20% | 1-2 weeks | HIGH - user expectation | Frontend Dev |
| Process viewer | 0% | 1 week | MEDIUM - troubleshooting aid | Agent Dev |
| Multi-monitor support | 0% | 2 weeks | MEDIUM - common scenario | Agent Dev |
### TIER 2: Competitive Parity (Nice to Have)
| Feature | Status | Effort | Competitor Has | Owner |
|---------|--------|--------|----------------|-------|
| Persistent agent service | 70% | 2 weeks | ScreenConnect, TeamViewer | Agent Dev |
| Installer builder (EXE) | 0% | 4 weeks | ScreenConnect | DevOps |
| Machine grouping (company/site) | 0% | 2 weeks | ScreenConnect | Frontend Dev |
| Search and filtering | 0% | 2 weeks | All competitors | Frontend Dev |
| File upload | 0% | 2 weeks | All competitors | Agent Dev |
| Rich clipboard (HTML, images) | 0% | 2 weeks | TeamViewer, AnyDesk | Agent Dev |
| Session recording | 0% | 4+ weeks | ScreenConnect (paid) | Agent Dev |
### TIER 3: Advanced Features (Defer to Post-Launch)
| Feature | Status | Effort | Justification for Deferral |
|---------|--------|--------|---------------------------|
| MSI packaging (64-bit) | 0% | 3-4 weeks | EXE works for initial launch |
| MFA/2FA support | 0% | 2 weeks | Single-tenant MSP initially |
| Mobile viewer | 0% | 8+ weeks | Desktop-first strategy |
| GuruRMM integration | 0% | 4+ weeks | Standalone value first |
| PSA integrations | 0% | 6+ weeks | After market validation |
| Safe mode reboot | 0% | 2 weeks | Advanced troubleshooting |
| Wake-on-LAN | 0% | 3 weeks | Requires network infrastructure |
---
## 4. INTEGRATED DEVELOPMENT ROADMAP
### PHASE 1: Security & Infrastructure (Weeks 1-4)
**Goal:** Fix critical vulnerabilities, establish production-ready infrastructure
**Team:** 1 Backend Dev + 1 DevOps Engineer
| Week | Backend Tasks | DevOps Tasks | Deliverable |
|------|--------------|--------------|-------------|
| 1 | JWT secret fix, rate limiting, SQL injection fixes | Systemd service setup, auto-restart config | Secure auth system |
| 2 | Agent validation, session security, password logging fix | Prometheus metrics, Grafana dashboards | Production monitoring |
| 3 | Input sanitization, session expiration, Argon2id upgrade | PostgreSQL automated backups, retention policy | Secure data persistence |
| 4 | TLS enforcement, CORS fix, CSP headers | CI/CD pipeline (GitHub Actions or Gitea CI) | Automated deployments |
**Milestone:** Production-ready infrastructure, all critical security issues resolved
**Exit Criteria:**
- [ ] No critical or high-severity security issues remain
- [ ] Server runs as systemd service with auto-restart
- [ ] Prometheus metrics exposed, Grafana dashboard configured
- [ ] Daily automated PostgreSQL backups
- [ ] CI/CD pipeline builds and tests on every commit
### PHASE 2: Core Functionality (Weeks 5-12)
**Goal:** Build missing features needed for basic attended support sessions
**Team:** 1 Frontend Dev + 1 Agent Dev + 1 Backend Dev (part-time)
| Week | Frontend | Agent | Backend | Deliverable |
|------|----------|-------|---------|-------------|
| 5 | End-user portal HTML/CSS/JS | Complete input relay wiring | Support code API enhancements | Portal + input working |
| 6 | Portal browser detection, instructions | One-time agent download (phase 1) | Support code → agent linking | Code entry functional |
| 7 | Dashboard session list real-time updates | One-time agent download (phase 2) | Session state management | Live session tracking |
| 8 | Session detail panel with tabs | One-time agent download (phase 3) | File download API | Agent download working |
| 9 | Join session button, viewer launch | Text clipboard sync (agent side) | Clipboard relay protocol | Join sessions working |
| 10 | Clipboard sync UI indicators | Text clipboard sync (complete) | PowerShell execution backend | Clipboard working |
| 11 | Remote PowerShell UI with output | PowerShell timeout controls | Command streaming | PowerShell working |
| 12 | System info panel, process viewer | File download implementation | File transfer protocol | File download working |
**Milestone:** Functional attended support sessions end-to-end
**Exit Criteria:**
- [ ] End user can enter support code and download agent
- [ ] Technician can see session in dashboard and join
- [ ] Screen viewing works reliably
- [ ] Mouse and keyboard control works
- [ ] Text clipboard syncs bidirectionally
- [ ] Remote PowerShell executes with live output
- [ ] Files can be downloaded from remote machine
- [ ] System information displays in dashboard
### PHASE 3: Competitive Features (Weeks 13-20)
**Goal:** Feature parity with ScreenConnect for attended support
**Team:** Same team as Phase 2
| Week | Frontend | Agent | Backend | Deliverable |
|------|----------|-------|---------|-------------|
| 13 | Chat UI in session panel | Chat integration | Chat persistence | Working chat |
| 14 | Multi-monitor switcher UI | Multi-monitor enumeration | Monitor state tracking | Multi-monitor support |
| 15 | Machine grouping sidebar (company/site) | Persistent agent service completion | Machine grouping API | Persistent agents |
| 16 | Search and filter interface | Process viewer, kill process | Process list API | Advanced troubleshooting |
| 17 | File upload UI with drag-drop | File upload implementation | File upload chunking | Bidirectional file transfer |
| 18 | Rich clipboard UI indicators | Rich clipboard (HTML, RTF) | Enhanced clipboard protocol | Advanced clipboard |
| 19 | Screenshot thumbnails, session timeline | Services viewer | Service control API | Enhanced session management |
| 20 | Performance optimization, polish | Agent optimization | Server optimization | Performance tuning |
**Milestone:** Competitive product ready for MSP beta testing
**Exit Criteria:**
- [ ] Chat works between tech and end user
- [ ] Multi-monitor switching works
- [ ] Persistent agents install as Windows service
- [ ] Machines can be grouped by company/site
- [ ] Search and filtering works
- [ ] File upload and download both work
- [ ] Rich clipboard formats supported
- [ ] Process and service viewers functional
### PHASE 4: Production Readiness (Weeks 21-26)
**Goal:** Installer builder, scalability, polish for general availability
**Team:** 2 Frontend Devs + 1 Agent Dev + 1 DevOps
| Week | Frontend | Agent | DevOps | Deliverable |
|------|----------|-------|--------|-------------|
| 21 | Installer builder UI | Installer metadata embedding | Build pipeline for custom agents | Builder MVP |
| 22 | Mobile-responsive dashboard | 64-bit agent compilation (Howard req) | Horizontal scaling architecture | Multi-device support |
| 23 | Advanced grouping (smart groups) | Auto-update implementation | Load balancer configuration | Smart filtering |
| 24 | Accessibility improvements (WCAG 2.1) | Update verification | Database connection pooling | Accessible UI |
| 25 | UI polish, animations, final design pass | Agent stability testing | Performance testing, benchmarking | Polished product |
| 26 | User testing feedback integration | Bug fixes | Production deployment checklist | Production-ready |
**Milestone:** Production-ready MSP remote support solution
**Exit Criteria:**
- [ ] Installer builder generates custom EXE with metadata
- [ ] 64-bit agent available (Howard requirement)
- [ ] Dashboard works on tablets and phones
- [ ] Smart groups (Online, Offline 30d, Attention) work
- [ ] WCAG 2.1 AA accessibility compliance
- [ ] Auto-update mechanism works
- [ ] Server can handle 50+ concurrent sessions
- [ ] Full end-to-end testing passed
---
## 5. RESOURCE REQUIREMENTS
### Team Composition
**Minimum Team (Slower Path - 26 weeks):**
- 1 Full-Stack Developer (Rust + Frontend)
- 1 DevOps Engineer (part-time, first 4 weeks full-time)
**Recommended Team (Faster Path - 16-20 weeks):**
- 1 Frontend Developer (HTML/CSS/JS)
- 1 Agent Developer (Rust, Windows APIs)
- 1 Backend Developer (Rust, Axum, PostgreSQL)
- 1 DevOps Engineer (Weeks 1-4 full-time, then part-time)
**Optimal Team (Aggressive Path - 12-16 weeks):**
- 2 Frontend Developers (one for dashboard, one for portal/viewer)
- 2 Agent Developers (one for capture/input, one for features)
- 1 Backend Developer
- 1 DevOps Engineer (Weeks 1-4 full-time)
- 1 QA Engineer (Weeks 8+)
### Skill Requirements
**Frontend Developer:**
- HTML5, CSS3, Modern JavaScript (ES6+)
- WebSocket client programming
- Canvas API (for viewer rendering)
- Protobuf.js or similar
- Responsive design, accessibility (WCAG)
**Agent Developer:**
- Rust (intermediate to advanced)
- Windows API (screen capture, input injection, clipboard)
- Tokio async runtime
- Protobuf
- Windows internals (services, registry, UAC)
**Backend Developer:**
- Rust (advanced)
- Axum or similar async web framework
- PostgreSQL, sqlx
- JWT authentication
- WebSocket relay patterns
- Security best practices
**DevOps Engineer:**
- Linux system administration (Ubuntu)
- Systemd services
- Prometheus, Grafana
- PostgreSQL administration
- CI/CD pipelines (GitHub Actions or Gitea)
- NPM (Nginx Proxy Manager) or similar
---
## 6. RISK ASSESSMENT & MITIGATION
### HIGH RISK (Likely to Cause Delays)
| Risk | Probability | Impact | Mitigation Strategy |
|------|------------|--------|---------------------|
| **One-time agent download complexity** | 80% | CRITICAL | Start early (Week 6), consider simplified approach (agent runs without install initially) |
| **Installer builder scope creep** | 70% | HIGH | Define strict MVP: EXE only with embedded metadata. Defer MSI to Phase 4 or post-launch. |
| **Input relay timing/latency issues** | 60% | CRITICAL | Extensive testing on WAN (throttled networks), optimize early, consider adaptive quality. |
| **Team availability/turnover** | 50% | HIGH | Document everything, code reviews, pair programming for knowledge transfer. |
| **Security vulnerabilities in rush** | 60% | CRITICAL | Security review after each phase, automated security scanning in CI/CD. |
### MEDIUM RISK (Manageable)
| Risk | Probability | Impact | Mitigation Strategy |
|------|------------|--------|---------------------|
| **Multi-monitor switching complexity** | 50% | MEDIUM | Protocol already supports it. Focus on UI simplicity. Test with 2-4 monitors. |
| **Clipboard compatibility issues** | 50% | MEDIUM | Start text-only, add formats incrementally. Test on Windows 7-11. |
| **PowerShell output streaming** | 40% | HIGH | Use existing .NET/Windows libraries, test with long-running commands, handle timeouts gracefully. |
| **File transfer chunking/resume** | 40% | MEDIUM | Start with simple implementation (no resume), optimize later based on real-world usage. |
| **Dashboard real-time update performance** | 30% | MEDIUM | WebSocket infrastructure exists. Test with 50+ sessions, optimize selectively. |
### LOW RISK (Minor Concerns)
| Risk | Probability | Impact | Mitigation Strategy |
|------|------------|--------|---------------------|
| **Cross-browser compatibility** | 30% | MEDIUM | Modern browsers are similar. Test Chrome, Firefox, Edge. Defer Safari/old browsers. |
| **MSI packaging learning curve** | 30% | LOW | Defer to Phase 4 or post-launch. Use WiX toolset, plenty of documentation. |
| **Safe mode reboot compatibility** | 20% | LOW | Windows API well-documented. Test on Windows 10/11 and Server 2019/2022. |
---
## 7. QUICK WINS (High Value, Low Effort)
These features can be completed quickly and provide immediate value:
| Week | Quick Win | Value | Effort | Owner |
|------|-----------|-------|--------|-------|
| 2 | Join session button | CRITICAL | 3 days | Frontend |
| 5 | Complete input relay | CRITICAL | 1 week | Agent |
| 9 | System info display | MEDIUM | 1 week | Frontend |
| 11 | PowerShell timeout controls | HIGH | 3 days | Frontend |
| 12 | Process list viewer | MEDIUM | 1 week | Agent + Frontend |
| 15 | Session detail panel | HIGH | 1 week | Frontend |
| 19 | Chat UI integration | HIGH | 1-2 weeks | Frontend |
| 22 | Command audit logging | MEDIUM | 3 days | Backend |
**Combined Quick Win Time:** 6-7 weeks of work (can be distributed across phases)
---
## 8. FRONTEND/UI SPECIFIC IMPROVEMENTS
### Tier 1: Critical UX Issues (Blocks Adoption)
| Issue | Current State | Target State | Effort | Week |
|-------|--------------|--------------|--------|------|
| **Machine organization missing** | Flat list | Company/Site/Tag hierarchy with collapsible tree | 2 weeks | 15-16 |
| **No session detail panel** | Click machine → nothing | Detail panel with tabs (Info, Screen, Chat, Commands, Files) | 1 week | 8 |
| **No search/filter** | No search box | Full-text search + multi-filter (online, OS, company, tag) | 2 weeks | 16-17 |
| **Connect flow confusing** | Modal with web/native choice | Default to web viewer, clear guidance | 3 days | 9 |
| **Support code entry not optimized** | Single input field | 6 segmented inputs with auto-advance (Apple-style) | 1 week | 5 |
### Tier 2: Important UX Improvements
| Issue | Current State | Target State | Effort | Week |
|-------|--------------|--------------|--------|------|
| **No toast notifications** | Silent updates | Toast for new sessions, errors, status changes | 1 week | 11 |
| **No keyboard navigation** | Mouse-only | Full Tab order, focus indicators, shortcuts | 1 week | 24 |
| **Minimal viewer toolbar** | 3 buttons | 10+ buttons (Quality, Monitors, Clipboard, Files, Chat, Screenshot) | 1 week | 18 |
| **No connection quality feedback** | FPS counter only | Latency, bandwidth, quality indicator (Good/Fair/Poor) | 1 week | 20 |
| **Poor mobile experience** | Desktop-only | Responsive dashboard, mobile-optimized viewer | 2 weeks | 22-23 |
### Tier 3: Polish & Accessibility
| Improvement | Effort | Week |
|-------------|--------|------|
| WCAG 2.1 AA compliance (focus, ARIA, contrast) | 1 week | 24 |
| Dark/light theme toggle | 3 days | 25 |
| Loading skeletons for async content | 2 days | 25 |
| Empty states with helpful instructions | 2 days | 25 |
| Micro-animations and transitions | 3 days | 25 |
**Total Frontend Improvement Time:** Integrated into main roadmap (Weeks 5-25)
---
## 9. TESTING STRATEGY
### Unit Testing (Ongoing)
**Target Coverage:** 70%+ for agent, server
**Framework:** Rust `cargo test`
**CI Integration:** Run on every commit
**Focus Areas:**
- Agent: Screen capture, input injection, clipboard
- Server: Session management, authentication, WebSocket relay
- Protocol: Message serialization/deserialization
### Integration Testing (Weekly)
**Target:** End-to-end workflows
**Tools:** Manual testing + automated scripts (Playwright for dashboard)
**Test Scenarios:**
- Week 8: Support code entry → agent download → join session
- Week 12: Screen viewing + input control + clipboard sync
- Week 16: PowerShell execution + file download
- Week 20: Multi-monitor + chat + file upload
- Week 25: Full MSP workflow (code gen → session → transfer → close)
### Performance Testing (Weeks 20, 25)
**Metrics:**
- Screen FPS: Target 30+ FPS on LAN, 15+ FPS on WAN
- Input latency: Target <100ms on LAN, <200ms on WAN
- Concurrent sessions: Target 50+ sessions on single server
- Bandwidth: Measure at various quality levels
**Tools:**
- Network throttling (Chrome DevTools, tc on Linux)
- Load generation (custom script or k6)
- Prometheus metrics analysis
### Security Testing (Weeks 4, 12, 20, 26)
**Penetration Testing:**
- Week 4: After security fixes, basic pen test
- Week 12: Full authentication and session security review
- Week 20: WebSocket relay attack scenarios
- Week 26: Pre-production comprehensive security audit
**Automated Scanning:**
- OWASP ZAP or similar in CI/CD
- Rust `cargo audit` for dependency vulnerabilities
- Static analysis (Clippy in strict mode)
### User Acceptance Testing (Weeks 24-26)
**Beta Testers:** 3-5 MSP technicians (Howard + team)
**Scenarios:**
- Remote troubleshooting sessions
- Software installation
- Network configuration
- Credential retrieval
- Multi-monitor workflows
**Feedback Collection:** Survey + direct interviews
---
## 10. DECISION POINTS & GO/NO-GO CRITERIA
### DECISION POINT 1: After Week 4 (Security & Infrastructure Complete)
**Go Criteria:**
- [ ] All critical security issues resolved (SEC-1 through SEC-5)
- [ ] All high-priority security issues resolved (SEC-6 through SEC-13)
- [ ] Systemd service operational with auto-restart
- [ ] Prometheus metrics exposed, Grafana dashboard configured
- [ ] Automated PostgreSQL backups running
- [ ] CI/CD pipeline functional
**No-Go Scenarios:**
- Security issues remain → Continue Phase 1, delay Phase 2
- Infrastructure unreliable → Bring in senior DevOps consultant
- Team capacity issues → Reduce scope or extend timeline
**Decision:** Proceed to Phase 2 or re-evaluate timeline
### DECISION POINT 2: After Week 12 (Core Features Complete)
**Go Criteria:**
- [ ] End-user portal functional
- [ ] One-time agent download working
- [ ] Input relay complete and responsive
- [ ] Dashboard session list with join functionality
- [ ] Text clipboard syncs bidirectionally
- [ ] Remote PowerShell executes with live output
- [ ] File download works
**No-Go Scenarios:**
- Input latency >500ms on WAN → Optimize before proceeding
- Agent download fails >20% of the time → Fix reliability
- Core features unstable → Extend Phase 2
**Decision:** Proceed to Phase 3 or extend core feature development
### DECISION POINT 3: After Week 20 (Competitive Features Complete)
**Go Criteria:**
- [ ] Chat functional
- [ ] Multi-monitor support working
- [ ] Persistent agents install as service
- [ ] Machine grouping (company/site) implemented
- [ ] Search and filtering functional
- [ ] File upload and download both work
- [ ] Rich clipboard formats supported
- [ ] 30+ FPS on LAN, 15+ FPS on WAN (performance targets met)
**No-Go Scenarios:**
- Performance significantly below targets → Optimization sprint
- Critical bugs in competitive features → Fix before launch
- User testing reveals major UX issues → Address before GA
**Decision:** Proceed to Phase 4 or conduct extended beta period
### DECISION POINT 4: After Week 26 (Production Readiness)
**Go Criteria:**
- [ ] Installer builder generates custom agents
- [ ] 64-bit agent available
- [ ] Dashboard mobile-responsive
- [ ] WCAG 2.1 AA compliant
- [ ] Auto-update working
- [ ] 50+ concurrent sessions supported
- [ ] Security audit passed
- [ ] Beta testing feedback addressed
**Launch Decision:** General Availability or Extended Beta
---
## 11. POST-LAUNCH ROADMAP (Optional Phase 5)
### Months 7-9: Advanced Features
- MSI packaging (64-bit) for GPO deployment
- MFA/2FA support
- Session recording and playback
- Advanced role-based permissions (per-client access)
- Event log viewer
- Registry browser (with safety warnings)
### Months 10-12: Integrations & Scale
- GuruRMM integration (shared auth, launch from RMM)
- PSA integrations (HaloPSA, Autotask, ConnectWise)
- Multi-server clustering
- Geographic load balancing
- Mobile apps (iOS, Android)
### Year 2: Enterprise Features
- SSO integration (SAML, OAuth)
- LDAP/AD synchronization
- Custom branding/white-labeling
- Advanced reporting and analytics
- Wake-on-LAN with local relay
- Disaster recovery automation
---
## 12. COST ESTIMATION
### Labor Costs (Recommended Team - 20 weeks)
| Role | Weeks | Hours/Week | Total Hours | Rate Estimate | Total Cost |
|------|-------|------------|-------------|---------------|------------|
| Frontend Developer | 20 | 40 | 800 | $75/hr | $60,000 |
| Agent Developer | 20 | 40 | 800 | $85/hr | $68,000 |
| Backend Developer | 20 | 40 | 800 | $85/hr | $68,000 |
| DevOps Engineer | 8 (full) + 12 (part) | 40 + 20 | 560 | $80/hr | $44,800 |
| QA Engineer | 12 | 30 | 360 | $60/hr | $21,600 |
**Total Labor:** $262,400
### Infrastructure Costs (6 months)
| Resource | Monthly Cost | Total (6 months) |
|----------|-------------|------------------|
| Server (existing 172.16.3.30) | $0 (owned) | $0 |
| PostgreSQL (on same server) | $0 | $0 |
| Prometheus + Grafana (on same server) | $0 | $0 |
| Backup storage (100GB) | $5 | $30 |
| SSL certificates (Let's Encrypt) | $0 | $0 |
| Domain (azcomputerguru.com) | $15 | $90 |
| CI/CD (Gitea + runners) | $0 (self-hosted) | $0 |
**Total Infrastructure:** $120 (minimal)
### Tools & Licenses
| Tool | Cost |
|------|------|
| Development tools (VS Code, etc.) | $0 (free) |
| Testing tools (Playwright, k6) | $0 (free) |
| Security scanning (OWASP ZAP) | $0 (free) |
| Protobuf compiler | $0 (free) |
**Total Tools:** $0
### **TOTAL PROJECT COST (20-week timeline):** ~$262,500
---
## 13. SUCCESS METRICS
### Technical Metrics
| Metric | Target | Measurement |
|--------|--------|-------------|
| Screen FPS (LAN) | 30+ FPS | Prometheus metrics |
| Screen FPS (WAN) | 15+ FPS | Prometheus metrics |
| Input latency (LAN) | <100ms | Manual testing |
| Input latency (WAN) | <200ms | Manual testing |
| Concurrent sessions | 50+ | Load testing |
| Uptime | 99.5%+ | Prometheus uptime |
| Security issues | 0 critical/high | Quarterly audits |
### Business Metrics
| Metric | Target | Measurement |
|--------|--------|-------------|
| MSP adoption rate | 5+ MSPs in first 3 months | Tracking |
| Sessions per week | 100+ | Database query |
| Agent installations | 200+ | Database query |
| Support tickets | <10/week | Gitea issues |
| Customer satisfaction | 4.5+/5 | Survey |
### User Experience Metrics
| Metric | Target | Measurement |
|--------|--------|-------------|
| Time to first session | <5 minutes | User testing |
| Session join time | <10 seconds | Prometheus metrics |
| Dashboard load time | <2 seconds | Browser DevTools |
| Agent download success | >95% | Server logs |
| Accessibility compliance | WCAG 2.1 AA | Automated testing |
---
## 14. FINAL RECOMMENDATIONS
### IMMEDIATE ACTIONS (This Week)
1. **Prioritize security fixes** - Cannot launch with hardcoded JWT secret
2. **Hire/assign frontend developer** - Critical path bottleneck
3. **Set up systemd service** - Infrastructure requirement for production
4. **Create GitHub/Gitea issues** - Track all findings from this review
5. **Schedule weekly team syncs** - Every Monday, review progress vs roadmap
### STRATEGIC DECISIONS
**Decision 1: Timeline**
- **Conservative (26 weeks):** Lower risk, thorough testing, minimal team stress
- **Aggressive (16 weeks):** Higher risk, requires optimal team, potential burnout
- **RECOMMENDED (20 weeks):** Balanced approach with contingency buffer
**Decision 2: Team Size**
- **Minimum (1-2 people):** 26+ weeks, high risk of delays
- **RECOMMENDED (4-5 people):** 16-20 weeks, manageable risk
- **Optimal (6-7 people):** 12-16 weeks, lowest risk
**Decision 3: Feature Scope**
- **MVP Only (Tier 0):** Fast to market but not competitive
- **RECOMMENDED (Tier 0 + Tier 1):** Competitive product, reasonable timeline
- **Full Feature (Tier 0-3):** 26+ weeks, defer some to post-launch
### KEY SUCCESS FACTORS
1. **Fix security issues FIRST** - Non-negotiable
2. **Build end-user portal early** - Unblocks all testing
3. **Focus on Howard's priorities** - PowerShell/CMD, clipboard, 64-bit
4. **Test on real networks** - WAN latency is critical
5. **Get beta users early** - MSP feedback invaluable
6. **Maintain code quality** - Rust makes this easier, don't compromise
7. **Document as you go** - Reduces onboarding time for new team members
---
## 15. APPENDICES
### A. Review Sources
This master action plan synthesizes findings from:
1. **Security Review** - 23 vulnerabilities (5 critical, 8 high, 6 medium, 4 low)
2. **Architecture Review** - Design assessment, 30% MVP completeness
3. **Code Quality Review** - Grade B+, 85/100 production readiness
4. **Infrastructure Review** - 15-20% production ready, systemd/monitoring gaps
5. **Frontend/UI/UX Review** - Grade C+, 35-40% complete, 14-section analysis
6. **Requirements Gap Analysis** - 100+ feature matrix, 30-35% implementation
### B. File References
- **GAP_ANALYSIS.md** - Detailed feature implementation matrix
- **REQUIREMENTS.md** - Original requirements specification
- **TODO.md** - Current task tracking
- **CLAUDE.md** - Project guidelines and architecture
- Security review (conversation archive)
- Architecture review (conversation archive)
- Code quality review (conversation archive)
- Infrastructure review (conversation archive)
- Frontend/UI review (conversation archive)
### C. Contact & Escalation
**Project Owner:** Howard
**Technical Escalation:** TBD (assign technical lead)
**Security Escalation:** TBD (assign security lead)
---
**Document Version:** 1.0
**Last Updated:** 2026-01-17
**Next Review:** After Phase 1 completion (Week 4)
**Status:** DRAFT - Awaiting Howard's approval
---
## SUMMARY: THE PATH FORWARD
GuruConnect is a **well-architected project** with **solid technical foundations** that needs **focused feature development and security hardening** to reach production readiness.
**Timeline:** 16-26 weeks (recommended: 20 weeks)
**Team:** 4-5 developers + 1 DevOps
**Cost:** ~$262,500 labor + minimal infrastructure
**Risk Level:** MEDIUM (manageable with proper planning)
**Critical Path:**
1. Fix 5 critical security vulnerabilities (3 weeks)
2. Build end-user portal + agent download (5 weeks)
3. Complete core features (clipboard, PowerShell, files) (7 weeks)
4. Add competitive features (chat, multi-monitor, grouping) (8 weeks)
5. Polish and production readiness (6 weeks)
**Outcome:** Competitive MSP remote support solution ready for general availability
**Next Step:** Howard reviews this plan, approves timeline/budget, assigns team

View File

@@ -1,610 +0,0 @@
# Phase 1 Complete - Production Infrastructure
**Date:** 2026-01-18
**Project:** GuruConnect Remote Desktop Solution
**Server:** 172.16.3.30 (gururmm)
**Status:** PRODUCTION READY
---
## Executive Summary
Phase 1 of GuruConnect infrastructure deployment is complete and ready for production use. All core infrastructure, monitoring, and CI/CD automation has been successfully implemented and tested.
**Overall Completion: 89% (31/35 items)**
---
## Phase 1 Breakdown
### Week 1: Security Hardening (77% - 10/13)
**Completed:**
- [x] JWT token expiration validation (24h lifetime)
- [x] Argon2id password hashing for user accounts
- [x] Security headers (CSP, X-Frame-Options, HSTS, X-Content-Type-Options)
- [x] Token blacklist for logout invalidation
- [x] API key validation for agent connections
- [x] Input sanitization on API endpoints
- [x] SQL injection protection (sqlx compile-time checks)
- [x] XSS prevention in templates
- [x] CORS configuration for dashboard
- [x] Rate limiting on auth endpoints
**Pending:**
- [ ] TLS certificate auto-renewal (Let's Encrypt with certbot)
- [ ] Session timeout enforcement (UI-side)
- [ ] Security audit logging (comprehensive audit trail)
**Impact:** Core security is operational. Missing items are enhancements for production hardening.
---
### Week 2: Infrastructure & Monitoring (100% - 11/11)
**Completed:**
- [x] Systemd service configuration
- [x] Auto-restart on failure
- [x] Prometheus metrics endpoint (/metrics)
- [x] 11 metric types exposed:
- Active sessions (gauge)
- Total connections (counter)
- Active WebSocket connections (gauge)
- Failed authentication attempts (counter)
- HTTP request duration (histogram)
- HTTP requests total (counter)
- Database connection pool (gauge)
- Agent connections (gauge)
- Viewer connections (gauge)
- Protocol errors (counter)
- Bytes transmitted (counter)
- [x] Grafana dashboard with 10 panels
- [x] Automated daily backups (systemd timer)
- [x] Log rotation configuration
- [x] Health check endpoint (/health)
- [x] Service monitoring (systemctl status)
**Details:**
- **Service:** guruconnect.service running as PID 3947824
- **Prometheus:** Running on port 9090
- **Grafana:** Running on port 3000 (admin/admin)
- **Backups:** Daily at 00:00 UTC → /home/guru/backups/guruconnect/
- **Retention:** 7 days automatic cleanup
- **Log Rotation:** Daily rotation, 14-day retention, compressed
**Documentation:**
- `INSTALLATION_GUIDE.md` - Complete setup instructions
- `INFRASTRUCTURE_STATUS.md` - Current status and next steps
- `DEPLOYMENT_COMPLETE.md` - Week 2 summary
---
### Week 3: CI/CD Automation (91% - 10/11)
**Completed:**
- [x] Gitea Actions workflows (3 workflows)
- [x] Build automation (build-and-test.yml)
- [x] Test automation (test.yml)
- [x] Deployment automation (deploy.yml)
- [x] Deployment script with rollback (deploy.sh)
- [x] Version tagging automation (version-tag.sh)
- [x] Build artifact management
- [x] Gitea Actions runner installed (act_runner 0.2.11)
- [x] Systemd service for runner
- [x] Complete CI/CD documentation
**Pending:**
- [ ] Gitea Actions runner registration (requires admin token)
**Workflows:**
1. **Build and Test** (.gitea/workflows/build-and-test.yml)
- Triggers: Push to main/develop, PRs to main
- Jobs: Build server, Build agent, Security audit, Summary
- Artifacts: Server binary (Linux), Agent binary (Windows)
- Retention: 30 days
- Duration: ~5-8 minutes
2. **Run Tests** (.gitea/workflows/test.yml)
- Triggers: Push to any branch, PRs
- Jobs: Test server, Test agent, Code coverage, Lint
- Artifacts: Coverage report
- Quality gates: Zero clippy warnings, all tests pass
- Duration: ~3-5 minutes
3. **Deploy to Production** (.gitea/workflows/deploy.yml)
- Triggers: Version tags (v*.*.*), Manual dispatch
- Jobs: Deploy server, Create release
- Process: Build → Package → Transfer → Backup → Deploy → Health Check
- Rollback: Automatic on health check failure
- Retention: 90 days
- Duration: ~10-15 minutes
**Automation Scripts:**
- `scripts/deploy.sh` - Deployment with automatic rollback
- `scripts/version-tag.sh` - Semantic version tagging
- `scripts/install-gitea-runner.sh` - Runner installation
**Documentation:**
- `CI_CD_SETUP.md` - Complete CI/CD setup guide
- `PHASE1_WEEK3_COMPLETE.md` - Week 3 detailed summary
- `ACTIVATE_CI_CD.md` - Runner activation and testing guide
---
## Infrastructure Overview
### Services Running
```
Service Status Port PID Uptime
------------------------------------------------------------
guruconnect active 3002 3947824 running
prometheus active 9090 active running
grafana-server active 3000 active running
```
### Automated Tasks
```
Task Frequency Next Run Status
------------------------------------------------------------
Daily Backups Daily Mon 00:00 UTC active
Log Rotation Daily Daily active
```
### File Locations
```
Component Location
------------------------------------------------------------
Server Binary ~/guru-connect/target/x86_64-unknown-linux-gnu/release/guruconnect-server
Static Files ~/guru-connect/server/static/
Database PostgreSQL (localhost:5432/guruconnect)
Backups /home/guru/backups/guruconnect/
Deployment Backups /home/guru/deployments/backups/
Deployment Artifacts /home/guru/deployments/artifacts/
Systemd Service /etc/systemd/system/guruconnect.service
Prometheus Config /etc/prometheus/prometheus.yml
Grafana Config /etc/grafana/grafana.ini
Log Rotation /etc/logrotate.d/guruconnect
```
---
## Access Information
### GuruConnect Dashboard
- **URL:** https://connect.azcomputerguru.com/dashboard
- **Username:** howard
- **Password:** AdminGuruConnect2026
### Gitea Repository
- **URL:** https://git.azcomputerguru.com/azcomputerguru/guru-connect
- **Actions:** https://git.azcomputerguru.com/azcomputerguru/guru-connect/actions
- **Runner Admin:** https://git.azcomputerguru.com/admin/actions/runners
### Monitoring
- **Prometheus:** http://172.16.3.30:9090
- **Grafana:** http://172.16.3.30:3000 (admin/admin)
- **Metrics Endpoint:** http://172.16.3.30:3002/metrics
- **Health Endpoint:** http://172.16.3.30:3002/health
---
## Key Achievements
### Infrastructure
- Production-grade systemd service with auto-restart
- Comprehensive metrics collection (11 metric types)
- Visual monitoring dashboards (10 panels)
- Automated backup and recovery system
- Log management and rotation
- Health monitoring
### Security
- JWT authentication with token expiration
- Argon2id password hashing
- Security headers (CSP, HSTS, etc.)
- API key validation for agents
- Token blacklist for logout
- Rate limiting on auth endpoints
### CI/CD
- Automated build pipeline for server and agent
- Comprehensive test suite automation
- Automated deployment with rollback
- Version tagging automation
- Build artifact management
- Release automation
### Documentation
- Complete installation guides
- Infrastructure status documentation
- CI/CD setup and usage guides
- Activation and testing procedures
- Troubleshooting guides
---
## Performance Benchmarks
### Build Times (Expected)
- Server build: ~2-3 minutes
- Agent build: ~2-3 minutes
- Test suite: ~1-2 minutes
- Total CI pipeline: ~5-8 minutes
- Deployment: ~10-15 minutes
### Deployment
- Backup creation: ~1 second
- Service stop: ~2 seconds
- Binary deployment: ~1 second
- Service start: ~3 seconds
- Health check: ~2 seconds
- **Total deployment time:** ~10 seconds
### Monitoring
- Metrics scrape interval: 15 seconds
- Grafana dashboard refresh: 5 seconds
- Backup execution time: ~5-10 seconds (depending on DB size)
---
## Testing Checklist
### Infrastructure Testing (Complete)
- [x] Systemd service starts successfully
- [x] Service auto-restarts on failure
- [x] Prometheus scrapes metrics endpoint
- [x] Grafana displays metrics
- [x] Daily backup timer scheduled
- [x] Backup creates valid dump files
- [x] Log rotation configured
- [x] Health endpoint returns OK
- [x] Admin login works
### CI/CD Testing (Pending Runner Registration)
- [ ] Runner shows online in Gitea admin
- [ ] Build workflow triggers on push
- [ ] Test workflow runs successfully
- [ ] Deployment workflow triggers on tag
- [ ] Deployment creates backup
- [ ] Deployment performs health check
- [ ] Rollback works on failure
- [ ] Build artifacts are downloadable
- [ ] Version tagging script works
---
## Next Steps
### Immediate (Required for Full CI/CD)
**1. Register Gitea Actions Runner**
```bash
# Get token from: https://git.azcomputerguru.com/admin/actions/runners
ssh guru@172.16.3.30
sudo -u gitea-runner act_runner register \
--instance https://git.azcomputerguru.com \
--token YOUR_REGISTRATION_TOKEN_HERE \
--name gururmm-runner \
--labels ubuntu-latest,ubuntu-22.04
sudo systemctl enable gitea-runner
sudo systemctl start gitea-runner
```
**2. Test CI/CD Pipeline**
```bash
# Trigger first build
cd ~/guru-connect
git commit --allow-empty -m "test: trigger CI/CD"
git push origin main
# Verify in Actions tab
https://git.azcomputerguru.com/azcomputerguru/guru-connect/actions
```
**3. Create First Release**
```bash
# Create version tag
cd ~/guru-connect/scripts
./version-tag.sh patch
# Push to trigger deployment
git push origin main
git push origin v0.1.0
```
### Optional Enhancements
**Security Hardening:**
- Configure Let's Encrypt auto-renewal
- Implement session timeout UI
- Add comprehensive audit logging
- Set up intrusion detection (fail2ban)
**Monitoring:**
- Import Grafana dashboard from `infrastructure/grafana-dashboard.json`
- Configure Alertmanager for Prometheus
- Set up notification webhooks
- Add uptime monitoring (UptimeRobot, etc.)
**CI/CD:**
- Configure deployment SSH keys for full automation
- Add Windows runner for native agent builds
- Implement staging environment
- Add smoke tests post-deployment
- Configure notification webhooks
**Infrastructure:**
- Set up database replication
- Configure offsite backup sync
- Implement centralized logging (ELK stack)
- Add performance profiling
---
## Troubleshooting
### Service Issues
```bash
# Check service status
sudo systemctl status guruconnect
# View logs
sudo journalctl -u guruconnect -f
# Restart service
sudo systemctl restart guruconnect
# Check if port is listening
netstat -tlnp | grep 3002
```
### Database Issues
```bash
# Check database connection
psql -U guruconnect -d guruconnect -c "SELECT 1;"
# View active connections
psql -U postgres -c "SELECT * FROM pg_stat_activity WHERE datname='guruconnect';"
# Check database size
psql -U postgres -c "SELECT pg_size_pretty(pg_database_size('guruconnect'));"
```
### Backup Issues
```bash
# Check backup timer status
sudo systemctl status guruconnect-backup.timer
# List backups
ls -lh /home/guru/backups/guruconnect/
# Manual backup
sudo systemctl start guruconnect-backup.service
# View backup logs
sudo journalctl -u guruconnect-backup.service -n 50
```
### Monitoring Issues
```bash
# Check Prometheus
systemctl status prometheus
curl http://localhost:9090/-/healthy
# Check Grafana
systemctl status grafana-server
curl http://localhost:3000/api/health
# Check metrics endpoint
curl http://localhost:3002/metrics
```
### CI/CD Issues
```bash
# Check runner status
sudo systemctl status gitea-runner
sudo journalctl -u gitea-runner -f
# View runner logs
sudo -u gitea-runner cat /home/gitea-runner/.runner/.runner
# Re-register runner
sudo -u gitea-runner act_runner register \
--instance https://git.azcomputerguru.com \
--token NEW_TOKEN
```
---
## Quick Reference Commands
### Service Management
```bash
sudo systemctl start guruconnect
sudo systemctl stop guruconnect
sudo systemctl restart guruconnect
sudo systemctl status guruconnect
sudo journalctl -u guruconnect -f
```
### Deployment
```bash
cd ~/guru-connect/scripts
./deploy.sh /path/to/package.tar.gz
./version-tag.sh [major|minor|patch]
```
### Backups
```bash
# Manual backup
sudo systemctl start guruconnect-backup.service
# List backups
ls -lh /home/guru/backups/guruconnect/
# Restore from backup
psql -U guruconnect -d guruconnect < /home/guru/backups/guruconnect/guruconnect-20260118-000000.sql
```
### Monitoring
```bash
# Check metrics
curl http://localhost:3002/metrics
# Check health
curl http://localhost:3002/health
# Prometheus UI
http://172.16.3.30:9090
# Grafana UI
http://172.16.3.30:3000
```
### CI/CD
```bash
# View workflows
https://git.azcomputerguru.com/azcomputerguru/guru-connect/actions
# Runner status
sudo systemctl status gitea-runner
# Trigger build
git push origin main
# Create release
./version-tag.sh patch
git push origin main && git push origin v0.1.0
```
---
## Documentation Index
**Installation & Setup:**
- `INSTALLATION_GUIDE.md` - Complete infrastructure installation
- `CI_CD_SETUP.md` - CI/CD setup and configuration
- `ACTIVATE_CI_CD.md` - Runner activation and testing
**Status & Completion:**
- `INFRASTRUCTURE_STATUS.md` - Infrastructure status and next steps
- `DEPLOYMENT_COMPLETE.md` - Week 2 deployment summary
- `PHASE1_WEEK3_COMPLETE.md` - Week 3 CI/CD summary
- `PHASE1_COMPLETE.md` - This document
**Project Documentation:**
- `README.md` - Project overview and getting started
- `CLAUDE.md` - Development guidelines and architecture
- `SESSION_STATE.md` - Current session state (if exists)
---
## Success Metrics
### Availability
- **Target:** 99.9% uptime
- **Current:** Service running with auto-restart
- **Monitoring:** Prometheus + Grafana + Health endpoint
### Performance
- **Target:** < 100ms HTTP response time
- **Monitoring:** HTTP request duration histogram
### Security
- **Target:** Zero successful unauthorized access attempts
- **Current:** JWT auth + API keys + rate limiting
- **Monitoring:** Failed auth counter
### Deployments
- **Target:** < 15 minutes deployment time
- **Current:** ~10 second deployment + CI pipeline time
- **Reliability:** Automatic rollback on failure
---
## Risk Assessment
### Low Risk Items (Mitigated)
- **Service crashes:** Auto-restart configured
- **Disk space:** Log rotation + backup cleanup
- **Failed deployments:** Automatic rollback
- **Database issues:** Daily backups with 7-day retention
### Medium Risk Items (Monitored)
- **Database growth:** Monitoring configured, manual cleanup if needed
- **Log volume:** Rotation configured, monitor disk usage
- **Metrics retention:** Prometheus defaults (15 days)
### High Risk Items (Manual Intervention)
- **TLS certificate expiration:** Requires certbot auto-renewal setup
- **Security vulnerabilities:** Requires periodic security audits
- **Database connection pool exhaustion:** Monitor pool metrics
---
## Cost Analysis
**Server Resources (172.16.3.30):**
- CPU: Minimal (< 5% average)
- RAM: ~200MB for GuruConnect + 300MB for monitoring
- Disk: ~50MB for binaries + backups (growing)
- Network: Minimal (internal metrics scraping)
**External Services:**
- Domain: connect.azcomputerguru.com (existing)
- TLS Certificate: Let's Encrypt (free)
- Git hosting: Self-hosted Gitea
**Total Additional Cost:** $0/month
---
## Phase 1 Summary
**Start Date:** 2026-01-15
**Completion Date:** 2026-01-18
**Duration:** 3 days
**Items Completed:** 31/35 (89%)
**Production Ready:** Yes
**Blocking Issues:** None
**Key Deliverables:**
- Production-grade infrastructure
- Comprehensive monitoring
- Automated CI/CD pipeline (pending runner registration)
- Complete documentation
**Next Phase:** Phase 2 - Feature Development
- Multi-session support
- File transfer capability
- Chat enhancements
- Mobile dashboard
---
**Deployment Status:** PRODUCTION READY
**Activation Status:** Pending Gitea Actions runner registration
**Documentation Status:** Complete
**Next Action:** Register runner → Test pipeline → Begin Phase 2
---
**Last Updated:** 2026-01-18
**Document Version:** 1.0
**Phase:** 1 Complete (89%)

View File

@@ -1,592 +0,0 @@
# GuruConnect Phase 1 - Completeness Audit Report
**Audit Date:** 2026-01-18
**Auditor:** Claude Code
**Project:** GuruConnect Remote Desktop Solution
**Phase:** Phase 1 (Security, Infrastructure, CI/CD)
**Claimed Completion:** 89% (31/35 items)
---
## Executive Summary
After comprehensive code review and verification, the Phase 1 completion claim of **89% (31/35 items)** is **ACCURATE** with minor discrepancies. The actual verified completion is **87% (30/35 items)** - one claimed item (rate limiting) is not fully operational.
**Overall Assessment: PRODUCTION READY** with documented pending items.
**Key Findings:**
- Security implementations verified and robust
- Infrastructure fully operational
- CI/CD pipelines complete but not activated (pending runner registration)
- Documentation comprehensive and accurate
- One security item (rate limiting) implemented in code but not active due to compilation issues
---
## Detailed Verification Results
### Week 1: Security Hardening (Claimed: 77% - 10/13)
#### VERIFIED COMPLETE (10/10 claimed)
1. **JWT Token Expiration Validation (24h lifetime)**
- **Status:** VERIFIED
- **Evidence:**
- `server/src/auth/jwt.rs` lines 92-118
- Explicit expiration check with `validate_exp = true`
- 24-hour default lifetime configurable via `JWT_EXPIRY_HOURS`
- Additional redundant expiration check at line 111-115
- **Code Marker:** SEC-13
2. **Argon2id Password Hashing**
- **Status:** VERIFIED
- **Evidence:**
- `server/src/auth/password.rs` lines 20-34
- Explicitly uses `Algorithm::Argon2id` (line 25)
- Latest version (V0x13)
- Default secure params: 19456 KiB memory, 2 iterations
- **Code Marker:** SEC-9
3. **Security Headers (CSP, X-Frame-Options, HSTS, X-Content-Type-Options)**
- **Status:** VERIFIED
- **Evidence:**
- `server/src/middleware/security_headers.rs` lines 13-75
- CSP implemented (lines 20-35)
- X-Frame-Options: DENY (lines 38-41)
- X-Content-Type-Options: nosniff (lines 44-47)
- X-XSS-Protection (lines 49-53)
- Referrer-Policy (lines 55-59)
- Permissions-Policy (lines 61-65)
- HSTS ready but commented out (lines 68-72) - appropriate for HTTP testing
- **Code Markers:** SEC-7, SEC-12
4. **Token Blacklist for Logout Invalidation**
- **Status:** VERIFIED
- **Evidence:**
- `server/src/auth/token_blacklist.rs` - Complete implementation
- In-memory HashSet with async RwLock
- Integrated into authentication flow (line 109-112 in auth/mod.rs)
- Cleanup mechanism for expired tokens
- **Endpoints:**
- `/api/auth/logout` - Implemented
- `/api/auth/revoke-token` - Implemented
- `/api/auth/admin/revoke-user` - Implemented
5. **API Key Validation for Agent Connections**
- **Status:** VERIFIED
- **Evidence:**
- `server/src/main.rs` lines 209-216
- API key strength validation: `server/src/utils/validation.rs`
- Minimum 32 characters
- Entropy checking
- Weak pattern detection
- **Code Marker:** SEC-4 (validation strength)
6. **Input Sanitization on API Endpoints**
- **Status:** VERIFIED
- **Evidence:**
- Serde deserialization with strict types
- UUID validation in handlers
- API key strength validation
- All API handlers use typed extractors (Json, Path, Query)
7. **SQL Injection Protection (sqlx compile-time checks)**
- **Status:** VERIFIED
- **Evidence:**
- `server/src/db/` modules use `sqlx::query!` and `sqlx::query_as!` macros
- Compile-time query validation
- All database operations parameterized
- **Sample:** `db/events.rs` lines 1-10 show sqlx usage
8. **XSS Prevention in Templates**
- **Status:** VERIFIED
- **Evidence:**
- CSP headers prevent inline script execution from untrusted sources
- Static HTML files served from `server/static/`
- No user-generated content rendered server-side
9. **CORS Configuration for Dashboard**
- **Status:** VERIFIED
- **Evidence:**
- `server/src/main.rs` lines 328-347
- Restricted to specific origins (production domain + localhost)
- Limited methods (GET, POST, PUT, DELETE, OPTIONS)
- Explicit header allowlist
- Credentials allowed
- **Code Marker:** SEC-11
10. **Rate Limiting on Auth Endpoints**
- **Status:** PARTIAL - CODE EXISTS BUT NOT ACTIVE
- **Evidence:**
- Rate limiting middleware implemented: `server/src/middleware/rate_limit.rs`
- Three limiters defined (auth: 5/min, support: 10/min, api: 60/min)
- NOT applied in main.rs due to compilation issues
- TODOs present in main.rs lines 258, 277
- **Issue:** Type resolution problems with tower_governor
- **Documentation:** `SEC2_RATE_LIMITING_TODO.md`
- **Recommendation:** Counts as INCOMPLETE until actually deployed
**CORRECTION:** Rate limiting claim should be marked as incomplete. Adjusted count: **9/10 completed**
#### VERIFIED PENDING (3/3 claimed)
11. **TLS Certificate Auto-Renewal**
- **Status:** VERIFIED PENDING
- **Evidence:** Documented in TECHNICAL_DEBT.md
- **Impact:** Manual renewal required
12. **Session Timeout Enforcement (UI-side)**
- **Status:** VERIFIED PENDING
- **Evidence:** JWT expiration works server-side, UI redirect not implemented
13. **Security Audit Logging (comprehensive audit trail)**
- **Status:** VERIFIED PENDING
- **Evidence:** Basic event logging exists in `db/events.rs`, comprehensive audit trail not yet implemented
**Week 1 Verified Result: 69% (9/13)** vs Claimed: 77% (10/13)
---
### Week 2: Infrastructure & Monitoring (Claimed: 100% - 11/11)
#### VERIFIED COMPLETE (11/11 claimed)
1. **Systemd Service Configuration**
- **Status:** VERIFIED
- **Evidence:**
- `server/guruconnect.service` - Complete systemd unit file
- Service type: simple
- User/Group: guru
- Working directory configured
- Environment file loaded
- **Note:** WatchdogSec removed due to crash issues (documented in TECHNICAL_DEBT.md)
2. **Auto-Restart on Failure**
- **Status:** VERIFIED
- **Evidence:**
- `server/guruconnect.service` lines 20-23
- Restart=on-failure
- RestartSec=10s
- StartLimitInterval=5min, StartLimitBurst=3
3. **Prometheus Metrics Endpoint (/metrics)**
- **Status:** VERIFIED
- **Evidence:**
- `server/src/metrics/mod.rs` - Complete metrics implementation
- `server/src/main.rs` line 256 - `/metrics` endpoint
- No authentication required (appropriate for internal monitoring)
4. **11 Metric Types Exposed**
- **Status:** VERIFIED
- **Evidence:** `server/src/metrics/mod.rs` lines 49-72
- requests_total (Counter family)
- request_duration_seconds (Histogram family)
- sessions_total (Counter family)
- active_sessions (Gauge)
- session_duration_seconds (Histogram)
- connections_total (Counter family)
- active_connections (Gauge family)
- errors_total (Counter family)
- db_operations_total (Counter family)
- db_query_duration_seconds (Histogram family)
- uptime_seconds (Gauge)
- **Count:** 11 metrics confirmed
5. **Grafana Dashboard with 10 Panels**
- **Status:** VERIFIED
- **Evidence:**
- `infrastructure/grafana-dashboard.json` exists
- Dashboard JSON structure present
- **Note:** Unable to verify exact panel count without opening Grafana, but file exists
6. **Automated Daily Backups (systemd timer)**
- **Status:** VERIFIED
- **Evidence:**
- `server/guruconnect-backup.timer` - Timer unit (daily at 02:00)
- `server/guruconnect-backup.service` - Backup service unit
- `server/backup-postgres.sh` - Backup script
- Persistent=true for missed executions
7. **Log Rotation Configuration**
- **Status:** VERIFIED
- **Evidence:**
- `server/guruconnect.logrotate` - Complete logrotate config
- Daily rotation
- 30-day retention
- Compression enabled
- Systemd journal integration documented
8. **Health Check Endpoint (/health)**
- **Status:** VERIFIED
- **Evidence:**
- `server/src/main.rs` line 254, 364-366
- Returns "OK" string
- No authentication required (appropriate for load balancers)
9. **Service Monitoring (systemctl status)**
- **Status:** VERIFIED
- **Evidence:**
- Systemd service configured
- Journal logging enabled (lines 37-39 in guruconnect.service)
- SyslogIdentifier set
10. **Prometheus Configuration**
- **Status:** VERIFIED
- **Evidence:**
- `infrastructure/prometheus.yml` - Complete config
- Scrapes GuruConnect on 172.16.3.30:3002
- 15-second scrape interval
11. **Grafana Configuration**
- **Status:** VERIFIED
- **Evidence:**
- Dashboard JSON template exists
- Installation instructions in prometheus.yml comments
**Week 2 Verified Result: 100% (11/11)** - Matches claimed completion
---
### Week 3: CI/CD Automation (Claimed: 91% - 10/11)
#### VERIFIED COMPLETE (10/10 claimed)
1. **Gitea Actions Workflows (3 workflows)**
- **Status:** VERIFIED
- **Evidence:**
- `.gitea/workflows/build-and-test.yml` - Build workflow
- `.gitea/workflows/test.yml` - Test workflow
- `.gitea/workflows/deploy.yml` - Deploy workflow
2. **Build Automation (build-and-test.yml)**
- **Status:** VERIFIED
- **Evidence:**
- Complete workflow with server + agent builds
- Triggers: push to main/develop, PRs to main
- Rust toolchain setup
- Dependency caching
- Formatting and Clippy checks
- Test execution
3. **Test Automation (test.yml)**
- **Status:** VERIFIED
- **Evidence:**
- Unit tests, integration tests, doc tests
- Code coverage with cargo-tarpaulin
- Lint and format checks
- Clippy with -D warnings
4. **Deployment Automation (deploy.yml)**
- **Status:** VERIFIED
- **Evidence:**
- Triggers on version tags (v*.*.*)
- Manual dispatch option
- Build and package steps
- Deployment notes (SSH commented out - appropriate for security)
- Release creation
5. **Deployment Script with Rollback (deploy.sh)**
- **Status:** VERIFIED
- **Evidence:**
- `scripts/deploy.sh` - Complete deployment script
- Backup creation (lines 49-56)
- Service stop/start
- Health check (lines 139-147)
- Automatic rollback on failure (lines 123-136)
6. **Version Tagging Automation (version-tag.sh)**
- **Status:** VERIFIED
- **Evidence:**
- `scripts/version-tag.sh` - Complete version script
- Semantic versioning support (major/minor/patch)
- Cargo.toml version updates
- Git tag creation
- Changelog display
7. **Build Artifact Management**
- **Status:** VERIFIED
- **Evidence:**
- Workflows upload artifacts with retention policies
- build-and-test.yml: 30-day retention
- deploy.yml: 90-day retention
- deploy.sh saves artifacts to `/home/guru/deployments/artifacts/`
8. **Gitea Actions Runner Installed (act_runner 0.2.11)**
- **Status:** VERIFIED
- **Evidence:**
- `scripts/install-gitea-runner.sh` - Installation script
- Version 0.2.11 specified (line 24)
- User creation, binary installation
- Directory structure setup
9. **Systemd Service for Runner**
- **Status:** VERIFIED
- **Evidence:**
- `scripts/install-gitea-runner.sh` lines 79-95
- Service unit created at /etc/systemd/system/gitea-runner.service
- Proper service configuration (User, WorkingDirectory, ExecStart)
10. **Complete CI/CD Documentation**
- **Status:** VERIFIED
- **Evidence:**
- `CI_CD_SETUP.md` - Complete setup guide
- `ACTIVATE_CI_CD.md` - Activation instructions
- `PHASE1_WEEK3_COMPLETE.md` - Summary
- Scripts include inline documentation
#### VERIFIED PENDING (1/1 claimed)
11. **Gitea Actions Runner Registration**
- **Status:** VERIFIED PENDING
- **Evidence:** Documented in ACTIVATE_CI_CD.md
- **Blocker:** Requires admin token from Gitea
- **Impact:** CI/CD pipeline ready but not active
**Week 3 Verified Result: 91% (10/11)** - Matches claimed completion
---
## Discrepancies Found
### 1. Rate Limiting Implementation
**Claimed:** Completed
**Actual Status:** Code exists but not operational
**Details:**
- Rate limiting middleware written and well-designed
- Type resolution issues with tower_governor prevent compilation
- Not applied to routes in main.rs (commented out with TODO)
- Documented in SEC2_RATE_LIMITING_TODO.md
**Impact:** Minor - server is still secure, but vulnerable to brute force attacks without additional mitigations (firewall, fail2ban)
**Recommendation:** Mark as incomplete. Use alternative:
- Option A: Fix tower_governor types (1-2 hours)
- Option B: Implement custom middleware (2-3 hours)
- Option C: Use Redis-based rate limiting (3-4 hours)
### 2. Documentation Accuracy
**Finding:** All documentation accurately reflects implementation status
**Notable Documentation:**
- `PHASE1_COMPLETE.md` - Accurate summary
- `TECHNICAL_DEBT.md` - Honest tracking of issues
- `SEC2_RATE_LIMITING_TODO.md` - Clear status of incomplete work
- Installation and setup guides comprehensive
### 3. Unclaimed Completed Work
**Items NOT claimed but actually completed:**
- API key strength validation (goes beyond basic validation)
- Token blacklist cleanup mechanism
- Comprehensive metrics (11 types, not just basic)
- Deployment rollback automation
- Grafana alert configuration template (`infrastructure/alerts.yml`)
---
## Verification Summary by Category
### Security (Week 1)
| Category | Claimed | Verified | Status |
|----------|---------|----------|--------|
| Completed | 10/13 | 9/13 | 1 item incomplete |
| Pending | 3/13 | 3/13 | Accurate |
| **Total** | **77%** | **69%** | **-8% discrepancy** |
### Infrastructure (Week 2)
| Category | Claimed | Verified | Status |
|----------|---------|----------|--------|
| Completed | 11/11 | 11/11 | Accurate |
| Pending | 0/11 | 0/11 | Accurate |
| **Total** | **100%** | **100%** | **No discrepancy** |
### CI/CD (Week 3)
| Category | Claimed | Verified | Status |
|----------|---------|----------|--------|
| Completed | 10/11 | 10/11 | Accurate |
| Pending | 1/11 | 1/11 | Accurate |
| **Total** | **91%** | **91%** | **No discrepancy** |
### Overall Phase 1
| Category | Claimed | Verified | Status |
|----------|---------|----------|--------|
| Completed | 31/35 | 30/35 | Rate limiting incomplete |
| Pending | 4/35 | 4/35 | Accurate |
| **Total** | **89%** | **87%** | **-2% discrepancy** |
---
## Code Quality Assessment
### Strengths
1. **Security Implementation Quality**
- Explicit security markers (SEC-1 through SEC-13) in code
- Defense in depth approach
- Modern cryptographic standards (Argon2id, JWT)
- Compile-time SQL injection prevention
2. **Infrastructure Robustness**
- Comprehensive monitoring (11 metric types)
- Automated backups with retention
- Health checks for all services
- Proper systemd integration
3. **CI/CD Pipeline Design**
- Multiple quality gates (formatting, clippy, tests)
- Security audit integration
- Artifact management with retention
- Automatic rollback on deployment failure
4. **Documentation Excellence**
- Honest status tracking
- Clear next steps documented
- Technical debt tracked systematically
- Multiple formats (guides, summaries, technical specs)
### Weaknesses
1. **Rate Limiting**
- Not operational despite code existence
- Dependency issues not resolved
2. **Watchdog Implementation**
- Removed due to crash issues
- Proper sd_notify implementation pending
3. **TLS Certificate Management**
- Manual renewal required
- Auto-renewal not configured
---
## Production Readiness Assessment
### Ready for Production ✓
**Core Functionality:**
- ✓ Authentication and authorization
- ✓ Session management
- ✓ Database operations
- ✓ Monitoring and metrics
- ✓ Health checks
- ✓ Automated backups
- ✓ Deployment automation
**Security (Operational):**
- ✓ JWT token validation with expiration
- ✓ Argon2id password hashing
- ✓ Security headers (CSP, X-Frame-Options, etc.)
- ✓ Token blacklist for logout
- ✓ API key validation
- ✓ SQL injection protection
- ✓ CORS configuration
- ✗ Rate limiting (pending - use firewall alternative)
**Infrastructure:**
- ✓ Systemd service with auto-restart
- ✓ Log rotation
- ✓ Prometheus metrics
- ✓ Grafana dashboards
- ✓ Daily backups
### Pending Items (Non-Blocking)
1. **Gitea Actions Runner Registration** (5 minutes)
- Required for: Automated CI/CD
- Alternative: Manual builds and deployments
- Impact: Operational efficiency
2. **Rate Limiting Activation** (1-3 hours)
- Required for: Brute force protection
- Alternative: Firewall rate limiting (fail2ban, NPM)
- Impact: Security hardening
3. **TLS Auto-Renewal** (2-4 hours)
- Required for: Certificate management
- Alternative: Manual renewal reminders
- Impact: Operational maintenance
4. **Session Timeout UI** (2-4 hours)
- Required for: Enhanced security UX
- Alternative: Server-side expiration works
- Impact: User experience
---
## Recommendations
### Immediate (Before Production Launch)
1. **Activate Rate Limiting** (Priority: HIGH)
- Implement one of three options from SEC2_RATE_LIMITING_TODO.md
- Test with curl/Postman
- Verify rate limit headers
2. **Register Gitea Runner** (Priority: MEDIUM)
- Get registration token from admin
- Register and activate runner
- Test with dummy commit
3. **Configure Firewall Rate Limiting** (Priority: HIGH - temporary)
- Install fail2ban
- Configure rules for /api/auth/login
- Monitor for brute force attempts
### Short Term (Within 1 Month)
4. **TLS Certificate Auto-Renewal** (Priority: HIGH)
- Install certbot
- Configure auto-renewal timer
- Test dry-run renewal
5. **Session Timeout UI** (Priority: MEDIUM)
- Implement JavaScript token expiration check
- Redirect to login on expiration
- Show countdown warning
6. **Comprehensive Audit Logging** (Priority: MEDIUM)
- Expand event logging
- Add audit trail for sensitive operations
- Implement log retention policies
### Long Term (Phase 2+)
7. **Systemd Watchdog Implementation**
- Add systemd crate
- Implement sd_notify calls
- Re-enable WatchdogSec in service file
8. **Distributed Rate Limiting**
- Implement Redis-based rate limiting
- Prepare for multi-instance deployment
---
## Conclusion
The Phase 1 completion claim of **89%** is **SUBSTANTIALLY ACCURATE** with a verified completion of **87%**. The 2-point discrepancy is due to rate limiting being implemented in code but not operational in production.
**Overall Assessment: APPROVED FOR PRODUCTION** with the following caveats:
1. Implement temporary rate limiting via firewall (fail2ban)
2. Monitor authentication endpoints for abuse
3. Schedule TLS auto-renewal setup within 30 days
4. Register Gitea runner when convenient (non-critical)
**Code Quality:** Excellent
**Documentation:** Comprehensive and honest
**Security Posture:** Strong (9/10 security items operational)
**Infrastructure:** Production-ready
**CI/CD:** Complete but not activated
The project demonstrates high-quality engineering practices, honest documentation, and production-ready infrastructure. The pending items are clearly documented and have reasonable alternatives or mitigations in place.
---
**Audit Completed:** 2026-01-18
**Next Review:** After Gitea runner registration and rate limiting implementation
**Overall Grade:** A- (87% verified completion, excellent quality)

View File

@@ -1,316 +0,0 @@
# Phase 1: Security & Infrastructure
**Duration:** 4 weeks
**Team:** 1 Backend Developer + 1 DevOps Engineer
**Goal:** Fix critical vulnerabilities, establish production-ready infrastructure
---
## Week 1: Critical Security Fixes
### Day 1-2: JWT Secret & Rate Limiting
**SEC-1: JWT Secret Hardcoded (CRITICAL)**
- [ ] Remove hardcoded JWT secret from source code
- [ ] Add JWT_SECRET environment variable to .env
- [ ] Update server/src/auth/ to read from env
- [ ] Generate strong random secret (64+ chars)
- [ ] Document secret rotation procedure
- [ ] Test authentication with new secret
- [ ] Verify old tokens rejected after rotation
**SEC-2: Rate Limiting (CRITICAL)**
- [ ] Install tower-governor or similar rate limiting middleware
- [ ] Add rate limiting to /api/auth/login (5 attempts/minute)
- [ ] Add rate limiting to /api/auth/register (2 attempts/minute)
- [ ] Add rate limiting to support code validation (10 attempts/minute)
- [ ] Add IP-based tracking
- [ ] Test rate limiting with automated requests
- [ ] Add rate limit headers (X-RateLimit-Remaining, etc.)
### Day 3: SQL Injection Prevention
**SEC-3: SQL Injection in Machine Filters (CRITICAL)**
- [ ] Audit all raw SQL queries in server/src/db/
- [ ] Replace string concatenation with sqlx parameterized queries
- [ ] Focus on machine_filters.rs (high risk)
- [ ] Review user_queries.rs for injection points
- [ ] Add input validation for filter parameters
- [ ] Test with SQL injection payloads ('; DROP TABLE--, etc.)
- [ ] Document safe query patterns for team
### Day 4-5: Agent & Session Security
**SEC-4: Agent Connection Validation (CRITICAL)**
- [ ] Implement support code validation in relay handler
- [ ] Implement API key validation for persistent agents
- [ ] Reject connections without valid credentials
- [ ] Add connection attempt logging
- [ ] Test with invalid codes/keys
- [ ] Add IP whitelisting option for agents
- [ ] Document agent authentication flow
**SEC-5: Session Takeover Prevention (CRITICAL)**
- [ ] Add session ownership validation
- [ ] Verify JWT user_id matches session creator
- [ ] Prevent cross-user session access
- [ ] Add session token binding (tie to initial connection)
- [ ] Test with stolen session IDs
- [ ] Add session hijacking detection (IP change alerts)
- [ ] Implement session timeout (4-hour max)
---
## Week 2: High-Priority Security
### Day 1: Logging & HTTPS
**SEC-6: Password Logging (HIGH)**
- [ ] Audit all logging statements for sensitive data
- [ ] Remove password/token logging from auth.rs
- [ ] Add [REDACTED] filter for sensitive fields
- [ ] Update tracing configuration
- [ ] Test logs don't contain credentials
- [ ] Document logging security policy
**SEC-10: HTTPS Enforcement (HIGH)**
- [ ] Add HTTPS redirect middleware
- [ ] Configure HSTS headers (max-age=31536000)
- [ ] Update NPM to enforce HTTPS
- [ ] Test HTTP requests redirect to HTTPS
- [ ] Add secure cookie flags (Secure, HttpOnly)
- [ ] Update documentation with HTTPS URLs
### Day 2-3: Input Sanitization
**SEC-7: XSS Prevention (HIGH)**
- [ ] Install validator crate for input sanitization
- [ ] Sanitize all user inputs in API endpoints
- [ ] Escape HTML in machine names, notes, tags
- [ ] Add Content-Security-Policy headers
- [ ] Test with XSS payloads (<script>, onerror=, etc.)
- [ ] Review dashboard.html for unsafe innerHTML usage
- [ ] Add CSP reporting endpoint
### Day 4: Password Hashing Upgrade
**SEC-9: Argon2id Migration (HIGH)**
- [ ] Install argon2 crate
- [ ] Replace PBKDF2 with Argon2id in auth service
- [ ] Set parameters (memory=65536, iterations=3, parallelism=4)
- [ ] Add password hash migration for existing users
- [ ] Test login with old and new hashes
- [ ] Force password reset for all users (optional)
- [ ] Document hashing algorithm choice
### Day 5: Session & CORS Security
**SEC-13: Session Expiration (HIGH)**
- [ ] Add exp claim to JWT tokens (4-hour expiry)
- [ ] Implement refresh token mechanism
- [ ] Add token renewal endpoint /api/auth/refresh
- [ ] Update dashboard to refresh tokens automatically
- [ ] Test token expiration and renewal
- [ ] Add session cleanup job (delete expired sessions)
**SEC-11: CORS Configuration (HIGH)**
- [ ] Review CORS middleware settings
- [ ] Restrict allowed origins to known domains
- [ ] Remove wildcard (*) CORS if present
- [ ] Set Access-Control-Allow-Credentials properly
- [ ] Test cross-origin requests blocked
- [ ] Document CORS policy
**SEC-12: CSP Headers (HIGH)**
- [ ] Add Content-Security-Policy header
- [ ] Set policy: default-src 'self'; script-src 'self'
- [ ] Allow wss: for WebSocket connections
- [ ] Test dashboard loads without CSP violations
- [ ] Add CSP reporting to monitor violations
**SEC-8: TLS Certificate Validation (HIGH)**
- [ ] Add TLS certificate verification in agent WebSocket client
- [ ] Use rustls or native-tls with validation enabled
- [ ] Test agent rejects invalid certificates
- [ ] Add certificate pinning option (optional)
- [ ] Document TLS requirements
---
## Week 3: Infrastructure Setup
### Day 1-2: Systemd Service
**INF-1: Systemd Service Configuration**
- [ ] Create /etc/systemd/system/guruconnect-server.service
- [ ] Set User=guru, WorkingDirectory=/home/guru/guru-connect
- [ ] Configure ExecStart with full binary path
- [ ] Add Restart=on-failure, RestartSec=5s
- [ ] Set environment file EnvironmentFile=/home/guru/.env
- [ ] Enable service: systemctl enable guruconnect-server
- [ ] Test start/stop/restart
- [ ] Test auto-restart on crash (kill -9 process)
- [ ] Configure log rotation with journald
- [ ] Document service management commands
### Day 3-4: Prometheus Monitoring
**INF-2: Prometheus Metrics**
- [ ] Install prometheus crate and metrics_exporter_prometheus
- [ ] Add /metrics endpoint to server
- [ ] Expose metrics: active_sessions, connected_agents, http_requests
- [ ] Add custom metrics: frame_latency, input_latency
- [ ] Install Prometheus on server (apt install prometheus)
- [ ] Configure Prometheus scrape config
- [ ] Test metrics endpoint returns data
- [ ] Create Prometheus systemd service
- [ ] Configure retention (30 days)
**INF-3: Grafana Dashboards**
- [ ] Install Grafana (apt install grafana)
- [ ] Configure Prometheus data source
- [ ] Create dashboard: GuruConnect Overview
- [ ] Add panels: Active Sessions, Connected Agents, CPU/Memory
- [ ] Add panels: WebSocket Connections, HTTP Request Rate
- [ ] Add panel: Session Duration Histogram
- [ ] Set up alerts: High error rate, No agents connected
- [ ] Export dashboard JSON for version control
- [ ] Create Grafana systemd service
- [ ] Configure Grafana HTTPS via NPM
### Day 5: Alerting
**INF-4: Alertmanager Setup**
- [ ] Install alertmanager
- [ ] Configure alert rules in Prometheus
- [ ] Set up email notifications (SMTP config)
- [ ] Add alerts: Server Down, High Memory, Database Errors
- [ ] Test alert firing and notifications
- [ ] Document alert response procedures
---
## Week 4: Backups & CI/CD
### Day 1: PostgreSQL Backups
**INF-5: Automated Backups**
- [ ] Create backup script /home/guru/scripts/backup-postgres.sh
- [ ] Use pg_dump with compression (gzip)
- [ ] Store backups in /home/guru/backups/guruconnect/
- [ ] Add timestamp to backup filenames
- [ ] Configure cron job (daily at 2 AM)
- [ ] Implement retention policy (keep 30 days)
- [ ] Test backup creation
- [ ] Test backup restoration to test database
- [ ] Add backup monitoring (alert if backup fails)
- [ ] Document restore procedure
### Day 2-3: CI/CD Pipeline
**INF-6: Gitea CI/CD**
- [ ] Create .gitea/workflows/ci.yml
- [ ] Add job: cargo test (run tests on every commit)
- [ ] Add job: cargo clippy (lint checks)
- [ ] Add job: cargo audit (security vulnerabilities)
- [ ] Configure Gitea runner
- [ ] Test pipeline on commit
- [ ] Add job: cargo build --release (build artifacts)
- [ ] Store build artifacts (for deployment)
**INF-7: Deployment Automation**
- [ ] Create deployment script deploy.sh
- [ ] Add steps: Pull latest, build, stop service, replace binary, start service
- [ ] Add pre-deployment backup
- [ ] Add smoke tests after deployment
- [ ] Test deployment script on staging
- [ ] Configure deploy job in CI/CD (manual trigger)
- [ ] Document deployment process
### Day 4: Health Checks
**INF-8: Health Monitoring**
- [ ] Add /health endpoint to server
- [ ] Check database connection in health check
- [ ] Check Redis connection (if applicable)
- [ ] Return 200 OK if healthy, 503 if unhealthy
- [ ] Configure NPM health check monitoring
- [ ] Add health check to Prometheus (blackbox exporter)
- [ ] Test health endpoint
- [ ] Add liveness and readiness probes (Kubernetes-style)
### Day 5: Documentation & Testing
**DOC-1: Infrastructure Documentation**
- [ ] Document systemd service configuration
- [ ] Document monitoring setup (Prometheus, Grafana)
- [ ] Document backup and restore procedures
- [ ] Document deployment process
- [ ] Create runbook for common issues
- [ ] Document alerting and on-call procedures
**TEST-1: End-to-End Security Testing**
- [ ] Run OWASP ZAP scan against server
- [ ] Test all fixed vulnerabilities
- [ ] Verify rate limiting works
- [ ] Verify HTTPS enforcement
- [ ] Test authentication with expired tokens
- [ ] Penetration test: SQL injection, XSS, CSRF
- [ ] Document remaining security issues (medium/low)
---
## Phase 1 Completion Criteria
### Security Checklist
- [ ] All 5 critical vulnerabilities fixed (SEC-1 to SEC-5)
- [ ] All 8 high-priority vulnerabilities fixed (SEC-6 to SEC-13)
- [ ] OWASP ZAP scan shows no critical/high issues
- [ ] Penetration testing passed
### Infrastructure Checklist
- [ ] Systemd service operational with auto-restart
- [ ] Prometheus metrics exposed and scraped
- [ ] Grafana dashboard configured with alerts
- [ ] Automated PostgreSQL backups running daily
- [ ] Backup restoration tested successfully
- [ ] CI/CD pipeline running tests on every commit
- [ ] Deployment automation tested
### Documentation Checklist
- [ ] All security fixes documented
- [ ] Infrastructure setup documented
- [ ] Deployment procedures documented
- [ ] Runbook created for common issues
- [ ] Team trained on new procedures
### Performance Checklist
- [ ] Health endpoint responds in <100ms
- [ ] Prometheus scrape completes in <5s
- [ ] Backup completes in <10 minutes
- [ ] Service restart completes in <30s
---
## Dependencies & Blockers
**External Dependencies:**
- NPM access for HTTPS configuration
- SMTP server for alerting (if not configured)
- Gitea runner setup (if not available)
**Potential Blockers:**
- Database schema changes may be needed for session security
- Agent code changes needed for TLS validation
- Dashboard changes needed for token refresh
**Risk Mitigation:**
- Test all changes on staging environment first
- Keep rollback procedure ready
- Communicate downtime windows to users (if any)
---
**Phase Owner:** Backend Developer + DevOps Engineer
**Start Date:** TBD
**Target Completion:** 4 weeks from start
**Next Phase:** Phase 2 - Core Functionality

View File

@@ -1,457 +0,0 @@
# Phase 1, Week 2 - Infrastructure & Monitoring
**Date Started:** 2026-01-18
**Target Completion:** 2026-01-25
**Status:** Starting
**Priority:** HIGH (Production Readiness)
---
## Executive Summary
With Week 1 security fixes complete and deployed, Week 2 focuses on production infrastructure hardening. The server currently runs manually (`nohup start-secure.sh &`), lacks monitoring, and has no automated recovery. This week establishes production-grade infrastructure.
**Goals:**
1. Systemd service with auto-restart on failure
2. Prometheus metrics for monitoring
3. Grafana dashboards for visualization
4. Automated PostgreSQL backups
5. Log rotation and management
**Dependencies:**
- SSH access to 172.16.3.30 as `guru` user
- Sudo access for systemd service installation
- PostgreSQL credentials (currently broken, but can set up backup automation)
---
## Week 2 Task Breakdown
### Day 1: Systemd Service Configuration
**Goal:** Convert manual server startup to systemd-managed service
**Tasks:**
1. Create systemd service file (`/etc/systemd/system/guruconnect.service`)
2. Configure service dependencies (network, postgresql)
3. Set restart policy (on-failure, with backoff)
4. Configure environment variables securely
5. Enable service to start on boot
6. Test service start/stop/restart
7. Verify auto-restart on crash
**Files to Create:**
- `server/guruconnect.service` - Systemd unit file
- `server/setup-systemd.sh` - Installation script
**Verification:**
- Service starts automatically on boot
- Service restarts on failure (kill -9 test)
- Logs go to journalctl
---
### Day 2: Prometheus Metrics
**Goal:** Expose metrics for monitoring server health and performance
**Tasks:**
1. Add `prometheus-client` dependency to Cargo.toml
2. Create metrics module (`server/src/metrics/mod.rs`)
3. Implement metric types:
- Counter: requests_total, sessions_total, errors_total
- Gauge: active_sessions, active_connections
- Histogram: request_duration_seconds, session_duration_seconds
4. Add `/metrics` endpoint
5. Integrate metrics into existing code:
- Session creation/close
- Request handling
- WebSocket connections
- Database operations
6. Test metrics endpoint (`curl http://172.16.3.30:3002/metrics`)
**Files to Create/Modify:**
- `server/Cargo.toml` - Add dependencies
- `server/src/metrics/mod.rs` - Metrics module
- `server/src/main.rs` - Add /metrics endpoint
- `server/src/relay/mod.rs` - Add session metrics
- `server/src/api/mod.rs` - Add request metrics
**Metrics to Track:**
- `guruconnect_requests_total{method, path, status}` - HTTP requests
- `guruconnect_sessions_total{status}` - Sessions (created, closed, failed)
- `guruconnect_active_sessions` - Current active sessions
- `guruconnect_active_connections{type}` - WebSocket connections (agents, viewers)
- `guruconnect_request_duration_seconds{method, path}` - Request latency
- `guruconnect_session_duration_seconds` - Session lifetime
- `guruconnect_errors_total{type}` - Error counts
- `guruconnect_db_operations_total{operation, status}` - Database operations
**Verification:**
- Metrics endpoint returns Prometheus format
- Metrics update in real-time
- No performance degradation
---
### Day 3: Grafana Dashboard
**Goal:** Create visual dashboards for monitoring GuruConnect
**Tasks:**
1. Install Prometheus on 172.16.3.30
2. Configure Prometheus to scrape GuruConnect metrics
3. Install Grafana on 172.16.3.30
4. Configure Grafana data source (Prometheus)
5. Create dashboards:
- Overview: Active sessions, requests/sec, errors
- Sessions: Session lifecycle, duration distribution
- Performance: Request latency, database query time
- Errors: Error rates by type
6. Set up alerting rules (if time permits)
**Files to Create:**
- `infrastructure/prometheus.yml` - Prometheus configuration
- `infrastructure/grafana-dashboard.json` - Pre-built dashboard
- `infrastructure/setup-monitoring.sh` - Installation script
**Grafana Dashboard Panels:**
1. Active Sessions (Gauge)
2. Requests per Second (Graph)
3. Error Rate (Graph)
4. Session Creation Rate (Graph)
5. Request Latency p50/p95/p99 (Graph)
6. Active Connections by Type (Graph)
7. Database Operations (Graph)
8. Top Errors (Table)
**Verification:**
- Prometheus scrapes metrics successfully
- Grafana dashboard displays real-time data
- Alerts fire on test conditions
---
### Day 4: Automated PostgreSQL Backups
**Goal:** Implement automated daily backups with retention policy
**Tasks:**
1. Create backup script (`server/backup-postgres.sh`)
2. Configure backup location (`/home/guru/backups/guruconnect/`)
3. Implement retention policy (keep 30 daily, 4 weekly, 6 monthly)
4. Create systemd timer for daily backups
5. Add backup monitoring (success/failure metrics)
6. Test backup and restore process
7. Document restore procedure
**Files to Create:**
- `server/backup-postgres.sh` - Backup script
- `server/restore-postgres.sh` - Restore script
- `server/guruconnect-backup.service` - Systemd service
- `server/guruconnect-backup.timer` - Systemd timer
**Backup Strategy:**
- Daily full backups at 2:00 AM
- Compressed with gzip
- Named with timestamp: `guruconnect-YYYY-MM-DD-HHMMSS.sql.gz`
- Stored in `/home/guru/backups/guruconnect/`
- Retention: 30 days daily, 4 weeks weekly, 6 months monthly
**Verification:**
- Manual backup works
- Automated backup runs daily
- Restore process verified
- Old backups cleaned up correctly
---
### Day 5: Log Rotation & Health Checks
**Goal:** Implement log rotation and continuous health monitoring
**Tasks:**
1. Configure logrotate for GuruConnect logs
2. Implement health check improvements:
- Database connectivity check
- Disk space check
- Memory usage check
- Active session count check
3. Create monitoring script (`server/health-monitor.sh`)
4. Add health metrics to Prometheus
5. Create systemd watchdog configuration
6. Document operational procedures
**Files to Create:**
- `server/guruconnect.logrotate` - Logrotate configuration
- `server/health-monitor.sh` - Health monitoring script
- `server/OPERATIONS.md` - Operational runbook
**Health Checks:**
- `/health` endpoint (basic - already exists)
- `/health/deep` endpoint (detailed checks):
- Database connection: OK/FAIL
- Disk space: >10% free
- Memory: <90% used
- Active sessions: <100 (threshold)
- Uptime: seconds since start
**Verification:**
- Logs rotate correctly
- Health checks report accurate status
- Alerts triggered on health failures
---
## Infrastructure Files Structure
```
guru-connect/
├── server/
│ ├── guruconnect.service # Systemd service file
│ ├── setup-systemd.sh # Service installation script
│ ├── backup-postgres.sh # PostgreSQL backup script
│ ├── restore-postgres.sh # PostgreSQL restore script
│ ├── guruconnect-backup.service # Backup systemd service
│ ├── guruconnect-backup.timer # Backup systemd timer
│ ├── guruconnect.logrotate # Logrotate configuration
│ ├── health-monitor.sh # Health monitoring script
│ └── OPERATIONS.md # Operational runbook
├── infrastructure/
│ ├── prometheus.yml # Prometheus configuration
│ ├── grafana-dashboard.json # Grafana dashboard export
│ └── setup-monitoring.sh # Monitoring setup script
└── docs/
└── MONITORING.md # Monitoring documentation
```
---
## Systemd Service Configuration
**Service File: `/etc/systemd/system/guruconnect.service`**
```ini
[Unit]
Description=GuruConnect Remote Desktop Server
Documentation=https://git.azcomputerguru.com/azcomputerguru/guru-connect
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
Type=simple
User=guru
Group=guru
WorkingDirectory=/home/guru/guru-connect/server
# Environment variables
EnvironmentFile=/home/guru/guru-connect/server/.env
# Start command
ExecStart=/home/guru/guru-connect/target/x86_64-unknown-linux-gnu/release/guruconnect-server
# Restart policy
Restart=on-failure
RestartSec=10s
StartLimitInterval=5min
StartLimitBurst=3
# Resource limits
LimitNOFILE=65536
LimitNPROC=4096
# Security
NoNewPrivileges=true
PrivateTmp=true
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=guruconnect
# Watchdog
WatchdogSec=30s
[Install]
WantedBy=multi-user.target
```
**Environment File: `/home/guru/guru-connect/server/.env`**
```bash
# Database
DATABASE_URL=postgresql://guruconnect:PASSWORD@localhost:5432/guruconnect
# Security
JWT_SECRET=your-very-secure-jwt-secret-at-least-32-characters
AGENT_API_KEY=your-very-secure-api-key-at-least-32-characters
# Server Configuration
RUST_LOG=info
HOST=0.0.0.0
PORT=3002
# Monitoring
PROMETHEUS_PORT=3002 # Expose on same port as main service
```
---
## Prometheus Configuration
**File: `infrastructure/prometheus.yml`**
```yaml
global:
scrape_interval: 15s
evaluation_interval: 15s
external_labels:
cluster: 'guruconnect-production'
scrape_configs:
- job_name: 'guruconnect'
static_configs:
- targets: ['172.16.3.30:3002']
labels:
env: 'production'
service: 'guruconnect-server'
- job_name: 'node_exporter'
static_configs:
- targets: ['172.16.3.30:9100']
labels:
env: 'production'
instance: 'rmm-server'
# Alerting rules (optional for Week 2)
rule_files:
- 'alerts.yml'
alerting:
alertmanagers:
- static_configs:
- targets: ['localhost:9093']
```
---
## Testing Checklist
### Systemd Service Tests
- [ ] Service starts correctly: `sudo systemctl start guruconnect`
- [ ] Service stops correctly: `sudo systemctl stop guruconnect`
- [ ] Service restarts correctly: `sudo systemctl restart guruconnect`
- [ ] Service auto-starts on boot: `sudo systemctl enable guruconnect`
- [ ] Service restarts on crash: `sudo kill -9 <pid>` (wait 10s)
- [ ] Logs visible in journalctl: `sudo journalctl -u guruconnect -f`
### Prometheus Metrics Tests
- [ ] Metrics endpoint accessible: `curl http://172.16.3.30:3002/metrics`
- [ ] Metrics format valid (Prometheus client can scrape)
- [ ] Session metrics update on session creation/close
- [ ] Request metrics update on HTTP requests
- [ ] Error metrics update on failures
### Grafana Dashboard Tests
- [ ] Prometheus data source connected
- [ ] All panels display data
- [ ] Data updates in real-time (<30s delay)
- [ ] Historical data visible (after 1 hour)
- [ ] Dashboard exports to JSON successfully
### Backup Tests
- [ ] Manual backup creates file: `bash backup-postgres.sh`
- [ ] Backup file is compressed and named correctly
- [ ] Restore works: `bash restore-postgres.sh <backup-file>`
- [ ] Timer triggers daily at 2:00 AM
- [ ] Retention policy removes old backups
### Health Check Tests
- [ ] Basic health endpoint: `curl http://172.16.3.30:3002/health`
- [ ] Deep health endpoint: `curl http://172.16.3.30:3002/health/deep`
- [ ] Health checks report database status
- [ ] Health checks report disk/memory usage
---
## Risk Assessment
### HIGH RISK
**Issue:** Database credentials still broken
**Impact:** Cannot test database-dependent features
**Mitigation:** Create backup scripts that work even if database is down (conditional logic)
**Issue:** Sudo access required for systemd
**Impact:** Cannot install service without password
**Mitigation:** Prepare scripts and documentation, request sudo access from system admin
### MEDIUM RISK
**Issue:** Prometheus/Grafana installation may require dependencies
**Impact:** Additional setup time
**Mitigation:** Use Docker containers if system install is complex
**Issue:** Metrics may add performance overhead
**Impact:** Latency increase
**Mitigation:** Use efficient metrics library, test performance before/after
### LOW RISK
**Issue:** Log rotation misconfiguration
**Impact:** Disk space issues
**Mitigation:** Test logrotate configuration thoroughly, set conservative limits
---
## Success Criteria
Week 2 is complete when:
1. **Systemd Service**
- Service starts/stops correctly
- Auto-restarts on failure
- Starts on boot
- Logs to journalctl
2. **Prometheus Metrics**
- /metrics endpoint working
- Key metrics implemented:
- Request counts and latency
- Session counts and duration
- Active connections
- Error rates
- Prometheus can scrape successfully
3. **Grafana Dashboard**
- Prometheus data source configured
- Dashboard with 8+ panels
- Real-time data display
- Dashboard exported to JSON
4. **Automated Backups**
- Backup script functional
- Daily backups via systemd timer
- Retention policy enforced
- Restore procedure documented
5. **Health Monitoring**
- Log rotation configured
- Health checks implemented
- Health metrics exposed
- Operational runbook created
**Exit Criteria:** All 5 areas have passing tests, production infrastructure is stable and monitored.
---
## Next Steps (Week 3)
After Week 2 infrastructure completion:
- Week 3: CI/CD pipeline (Gitea CI, automated builds, deployment automation)
- Week 4: Production hardening (load testing, performance optimization, security audit)
- Phase 2: Core features development
---
**Document Status:** READY
**Owner:** Development Team
**Started:** 2026-01-18
**Target:** 2026-01-25

View File

@@ -1,653 +0,0 @@
# Phase 1 Week 3 - CI/CD Automation COMPLETE
**Date:** 2026-01-18
**Server:** 172.16.3.30 (gururmm)
**Status:** CI/CD PIPELINE READY ✓
---
## Executive Summary
Successfully implemented comprehensive CI/CD automation for GuruConnect using Gitea Actions. All automation infrastructure is deployed and ready for activation after runner registration.
**Key Achievements:**
- 3 automated workflow pipelines created
- Deployment automation with rollback capability
- Version tagging automation
- Build artifact management
- Gitea Actions runner installed
- Complete documentation
---
## Implemented Components
### 1. Automated Build Pipeline (`build-and-test.yml`)
**Status:** READY ✓
**Location:** `.gitea/workflows/build-and-test.yml`
**Features:**
- Automatic builds on push to main/develop
- Parallel builds (server + agent)
- Security audit (cargo audit)
- Code quality checks (clippy, rustfmt)
- 30-day artifact retention
**Triggers:**
- Push to `main` or `develop` branches
- Pull requests to `main`
**Build Targets:**
- Server: Linux x86_64
- Agent: Windows x86_64 (cross-compiled)
**Artifacts Generated:**
- `guruconnect-server-linux` - Server binary
- `guruconnect-agent-windows` - Agent executable
---
### 2. Test Automation Pipeline (`test.yml`)
**Status:** READY ✓
**Location:** `.gitea/workflows/test.yml`
**Test Coverage:**
- Unit tests (server & agent)
- Integration tests
- Documentation tests
- Code coverage reports
- Linting & formatting checks
**Quality Gates:**
- Zero clippy warnings
- All tests must pass
- Code must be formatted
- No security vulnerabilities
---
### 3. Deployment Pipeline (`deploy.yml`)
**Status:** READY ✓
**Location:** `.gitea/workflows/deploy.yml`
**Deployment Features:**
- Automated deployment on version tags
- Manual deployment via workflow dispatch
- Deployment package creation
- Release artifact publishing
- 90-day artifact retention
**Triggers:**
- Push tags matching `v*.*.*` (v0.1.0, v1.2.3, etc.)
- Manual workflow dispatch
**Deployment Process:**
1. Build release binary
2. Create deployment tarball
3. Transfer to server
4. Backup current version
5. Stop service
6. Deploy new version
7. Start service
8. Health check
9. Auto-rollback on failure
---
### 4. Deployment Automation Script
**Status:** OPERATIONAL ✓
**Location:** `scripts/deploy.sh`
**Features:**
- Automated backup before deployment
- Service management (stop/start)
- Health check verification
- Automatic rollback on failure
- Deployment logging
- Artifact archival
**Usage:**
```bash
cd ~/guru-connect/scripts
./deploy.sh /path/to/package.tar.gz
```
**Deployment Locations:**
- Backups: `/home/guru/deployments/backups/`
- Artifacts: `/home/guru/deployments/artifacts/`
- Logs: Console output + systemd journal
---
### 5. Version Tagging Automation
**Status:** OPERATIONAL ✓
**Location:** `scripts/version-tag.sh`
**Features:**
- Semantic versioning (MAJOR.MINOR.PATCH)
- Automatic Cargo.toml version updates
- Git tag creation
- Changelog integration
- Push instructions
**Usage:**
```bash
cd ~/guru-connect/scripts
./version-tag.sh patch # 0.1.0 → 0.1.1
./version-tag.sh minor # 0.1.0 → 0.2.0
./version-tag.sh major # 0.1.0 → 1.0.0
```
---
### 6. Gitea Actions Runner
**Status:** INSTALLED ✓ (Pending Registration)
**Binary:** `/usr/local/bin/act_runner`
**Version:** 0.2.11
**Runner Configuration:**
- User: `gitea-runner` (dedicated)
- Working Directory: `/home/gitea-runner/.runner`
- Systemd Service: `gitea-runner.service`
- Labels: `ubuntu-latest`, `ubuntu-22.04`
**Installation Complete - Requires Registration**
---
## Setup Status
### Completed Tasks (10/11 - 91%)
1. ✓ Gitea Actions runner installed
2. ✓ Build workflow created
3. ✓ Test workflow created
4. ✓ Deployment workflow created
5. ✓ Deployment script created
6. ✓ Version tagging script created
7. ✓ Systemd service configured
8. ✓ All files uploaded to server
9. ✓ Workflows committed to Git
10. ✓ Complete documentation created
### Pending Tasks (1/11 - 9%)
1.**Register Gitea Actions Runner** - Requires Gitea admin access
---
## Next Steps - Runner Registration
### Step 1: Get Registration Token
1. Go to https://git.azcomputerguru.com/admin/actions/runners
2. Click "Create new Runner"
3. Copy the registration token
### Step 2: Register Runner
```bash
ssh guru@172.16.3.30
sudo -u gitea-runner act_runner register \
--instance https://git.azcomputerguru.com \
--token YOUR_REGISTRATION_TOKEN_HERE \
--name gururmm-runner \
--labels ubuntu-latest,ubuntu-22.04
```
### Step 3: Start Runner Service
```bash
sudo systemctl daemon-reload
sudo systemctl enable gitea-runner
sudo systemctl start gitea-runner
sudo systemctl status gitea-runner
```
### Step 4: Verify Registration
1. Go to https://git.azcomputerguru.com/admin/actions/runners
2. Confirm "gururmm-runner" is listed and online
---
## Testing the CI/CD Pipeline
### Test 1: Automated Build
```bash
# Make a small change
ssh guru@172.16.3.30
cd ~/guru-connect
# Trigger build
git commit --allow-empty -m "test: trigger CI/CD build"
git push origin main
# View results
# Go to: https://git.azcomputerguru.com/azcomputerguru/guru-connect/actions
```
**Expected Result:**
- Build workflow runs automatically
- Server and agent build successfully
- Tests pass
- Artifacts uploaded
### Test 2: Create a Release
```bash
# Create version tag
cd ~/guru-connect/scripts
./version-tag.sh patch
# Push tag (triggers deployment)
git push origin main
git push origin v0.1.1
# View deployment
# Go to: https://git.azcomputerguru.com/azcomputerguru/guru-connect/actions
```
**Expected Result:**
- Deploy workflow runs automatically
- Deployment package created
- Service deployed and restarted
- Health check passes
### Test 3: Manual Deployment
```bash
# Download artifact from Gitea
# Or use existing package
cd ~/guru-connect/scripts
./deploy.sh /path/to/guruconnect-server-v0.1.0.tar.gz
```
**Expected Result:**
- Backup created
- Service stopped
- New version deployed
- Service started
- Health check passes
---
## Workflow Reference
### Build and Test Workflow
**File:** `.gitea/workflows/build-and-test.yml`
**Jobs:** 4 (build-server, build-agent, security-audit, build-summary)
**Duration:** ~5-8 minutes
**Artifacts:** 2 (server binary, agent binary)
### Test Workflow
**File:** `.gitea/workflows/test.yml`
**Jobs:** 4 (test-server, test-agent, code-coverage, lint)
**Duration:** ~3-5 minutes
**Artifacts:** 1 (coverage report)
### Deploy Workflow
**File:** `.gitea/workflows/deploy.yml`
**Jobs:** 2 (deploy-server, create-release)
**Duration:** ~10-15 minutes
**Artifacts:** 1 (deployment package)
---
## Artifact Management
### Build Artifacts
- **Location:** Gitea Actions artifacts
- **Retention:** 30 days
- **Contents:** Compiled binaries
### Deployment Artifacts
- **Location:** `/home/guru/deployments/artifacts/`
- **Retention:** Manual (recommend 90 days)
- **Contents:** Deployment packages (tar.gz)
### Backups
- **Location:** `/home/guru/deployments/backups/`
- **Retention:** Manual (recommend 30 days)
- **Contents:** Previous binary versions
---
## Security Configuration
### Runner Security
- Dedicated non-root user (`gitea-runner`)
- Limited filesystem access
- No sudo permissions
- Isolated working directory
### Deployment Security
- SSH key-based authentication (to be configured)
- Automated backups before deployment
- Health checks before completion
- Automatic rollback on failure
- Audit trail in logs
### Secrets Required
Configure in Gitea repository settings:
```
Repository > Settings > Secrets (when available in Gitea 1.25.2)
```
**Future Secrets:**
- `SSH_PRIVATE_KEY` - For deployment automation
- `DEPLOY_HOST` - Target server (172.16.3.30)
- `DEPLOY_USER` - Deployment user (guru)
---
## Monitoring & Observability
### CI/CD Metrics
**View in Gitea:**
- Workflow runs: Repository > Actions
- Build duration: Individual workflow runs
- Success rate: Actions dashboard
- Artifact downloads: Workflow artifacts section
**Integration with Prometheus:**
- Future enhancement
- Track build duration
- Monitor deployment frequency
- Alert on failed builds
---
## Troubleshooting
### Runner Not Registered
```bash
# Check runner status
sudo systemctl status gitea-runner
# View logs
sudo journalctl -u gitea-runner -f
# Re-register
sudo -u gitea-runner act_runner register \
--instance https://git.azcomputerguru.com \
--token NEW_TOKEN
```
### Workflow Not Triggering
**Checklist:**
1. Runner registered and online?
2. Workflow files committed to `.gitea/workflows/`?
3. Branch matches trigger condition?
4. Gitea Actions enabled in repository settings?
### Build Failing
**Check Logs:**
1. Go to Repository > Actions
2. Click failed workflow run
3. Review job logs
**Common Issues:**
- Missing Rust dependencies
- Test failures
- Clippy warnings
- Formatting not applied
### Deployment Failing
```bash
# Check deployment logs
cat /home/guru/deployments/deploy-*.log
# Check service status
sudo systemctl status guruconnect
# View service logs
sudo journalctl -u guruconnect -n 50
# Manual rollback
ls /home/guru/deployments/backups/
cp /home/guru/deployments/backups/guruconnect-server-TIMESTAMP \
~/guru-connect/target/x86_64-unknown-linux-gnu/release/guruconnect-server
sudo systemctl restart guruconnect
```
---
## Documentation
### Created Documentation
**Primary:**
- `CI_CD_SETUP.md` - Complete CI/CD setup and usage guide
- `PHASE1_WEEK3_COMPLETE.md` - This document
**Workflow Files:**
- `.gitea/workflows/build-and-test.yml` - Build automation
- `.gitea/workflows/test.yml` - Test automation
- `.gitea/workflows/deploy.yml` - Deployment automation
**Scripts:**
- `scripts/deploy.sh` - Deployment automation
- `scripts/version-tag.sh` - Version tagging
- `scripts/install-gitea-runner.sh` - Runner installation
---
## Performance Benchmarks
### Expected Build Times
**Server Build:**
- Cache hit: ~1 minute
- Cache miss: ~2-3 minutes
**Agent Build:**
- Cache hit: ~1 minute
- Cache miss: ~2-3 minutes
**Tests:**
- Unit tests: ~1 minute
- Integration tests: ~1 minute
- Total: ~2 minutes
**Total Pipeline:**
- Build + Test: ~5-8 minutes
- Deploy: ~10-15 minutes (includes health checks)
---
## Future Enhancements
### Phase 2 CI/CD Improvements
1. **Multi-Runner Setup**
- Add Windows runner for native agent builds
- Add macOS runner for multi-platform support
2. **Enhanced Testing**
- End-to-end tests
- Performance benchmarks
- Load testing in CI
3. **Deployment Improvements**
- Staging environment
- Canary deployments
- Blue-green deployments
- Automatic rollback triggers
4. **Monitoring Integration**
- CI/CD metrics to Prometheus
- Grafana dashboards for build trends
- Slack/email notifications
- Build quality reports
5. **Security Enhancements**
- Dependency scanning
- Container scanning
- License compliance checking
- SBOM generation
---
## Phase 1 Summary
### Week 1: Security (77% Complete)
- JWT expiration validation
- Argon2id password hashing
- Security headers (CSP, X-Frame-Options, etc.)
- Token blacklist for logout
- API key validation
### Week 2: Infrastructure (100% Complete)
- Systemd service configuration
- Prometheus metrics (11 metric types)
- Automated backups (daily)
- Log rotation
- Grafana dashboards
- Health monitoring
### Week 3: CI/CD (91% Complete)
- Gitea Actions workflows (3 workflows)
- Deployment automation
- Version tagging automation
- Build artifact management
- Runner installation
- **Pending:** Runner registration (requires admin access)
---
## Repository Status
**Commit:** 5b7cf5f
**Branch:** main
**Files Added:**
- 3 workflow files
- 3 automation scripts
- Complete CI/CD documentation
**Recent Commit:**
```
ci: add Gitea Actions workflows and deployment automation
- Add build-and-test workflow for automated builds
- Add deploy workflow for production deployments
- Add test workflow for comprehensive testing
- Add deployment automation script with rollback
- Add version tagging automation
- Add Gitea Actions runner installation script
```
---
## Success Criteria
### Phase 1 Week 3 Goals - ALL MET ✓
1.**Gitea CI Pipeline** - 3 workflows created
2.**Automated Builds** - Build on commit implemented
3.**Automated Tests** - Test suite in CI
4.**Deployment Automation** - Deploy script with rollback
5.**Build Artifacts** - Storage and versioning configured
6.**Version Tagging** - Automated tagging script
7.**Documentation** - Complete setup guide created
---
## Quick Reference
### Key Commands
```bash
# Runner management
sudo systemctl status gitea-runner
sudo journalctl -u gitea-runner -f
# Deployment
cd ~/guru-connect/scripts
./deploy.sh <package.tar.gz>
# Version tagging
./version-tag.sh [major|minor|patch]
# View workflows
https://git.azcomputerguru.com/azcomputerguru/guru-connect/actions
# Manual build
cd ~/guru-connect
cargo build --release --target x86_64-unknown-linux-gnu
```
### Key URLs
**Gitea Actions:** https://git.azcomputerguru.com/azcomputerguru/guru-connect/actions
**Runner Admin:** https://git.azcomputerguru.com/admin/actions/runners
**Repository:** https://git.azcomputerguru.com/azcomputerguru/guru-connect
---
## Conclusion
**Phase 1 Week 3 Objectives: ACHIEVED ✓**
Successfully implemented comprehensive CI/CD automation for GuruConnect:
- 3 automated workflow pipelines operational
- Deployment automation with safety features
- Version management automated
- Build artifacts managed and versioned
- Runner installed and ready for activation
**Overall Phase 1 Status:**
- Week 1 Security: 77% (10/13 items)
- Week 2 Infrastructure: 100% (11/11 items)
- Week 3 CI/CD: 91% (10/11 items)
**Ready for:**
- Runner registration (final step)
- First automated build
- Production deployments via CI/CD
- Phase 2 planning
---
**Deployment Completed:** 2026-01-18 15:50 UTC
**Total Implementation Time:** ~45 minutes
**Status:** READY FOR ACTIVATION ✓
**Next Action:** Register Gitea Actions runner
---
## Activation Checklist
To activate the CI/CD pipeline:
- [ ] Register Gitea Actions runner (requires admin)
- [ ] Start runner systemd service
- [ ] Verify runner shows up in Gitea admin
- [ ] Make test commit to trigger build
- [ ] Verify build completes successfully
- [ ] Create test version tag
- [ ] Verify deployment workflow runs
- [ ] Configure deployment SSH keys (optional for auto-deploy)
- [ ] Set up notification webhooks (optional)
---
**Phase 1 Complete:** ALL WEEKS FINISHED ✓

View File

@@ -1,294 +0,0 @@
# Phase 2: Core Features
**Duration:** 8 weeks
**Team:** 1 Frontend Developer + 1 Agent Developer + 1 Backend Developer (part-time)
**Goal:** Build missing launch blockers and essential features
---
## Overview
Phase 2 focuses on implementing the core features needed for basic attended support sessions:
- End-user portal for support code entry
- One-time agent download mechanism
- Complete input relay (mouse/keyboard)
- Dashboard session management UI
- Text clipboard synchronization
- Remote PowerShell execution
- Basic file download
**Completion Criteria:** MSP can generate support code, end user can connect, tech can view screen, control remotely, sync clipboard, run commands, and download files.
---
## Week 5: Portal & Input Foundation
### End-User Portal (Frontend Developer)
- [ ] Create server/static/portal.html (support code entry page)
- [ ] Design 6-segment code input (Apple-style auto-advance)
- [ ] Add support code validation via API
- [ ] Implement browser detection (Chrome, Firefox, Edge, Safari)
- [ ] Add download button (triggers agent download)
- [ ] Style with GuruConnect branding (match dashboard theme)
- [ ] Test on all major browsers
- [ ] Add error handling (invalid code, expired code, server error)
- [ ] Add loading indicators during validation
- [ ] Deploy to server/static/
### Input Relay Completion (Agent Developer)
- [ ] Review viewer input capture in viewer.html
- [ ] Verify mouse events captured correctly
- [ ] Verify keyboard events captured correctly
- [ ] Test special keys (Ctrl, Alt, Shift, Windows key)
- [ ] Wire input events to WebSocket send
- [ ] Test viewer → server → agent relay
- [ ] Add input latency logging
- [ ] Test on LAN (target <50ms)
- [ ] Test on WAN with throttling (target <200ms)
- [ ] Fix any input lag issues
---
## Week 6: Agent Download (Phase 1)
### Support Code Embedding (Backend Developer)
- [ ] Modify support code API to return download URL
- [ ] Create /api/support-codes/:code/download endpoint
- [ ] Generate one-time download token (expires in 5 minutes)
- [ ] Link download token to support code
- [ ] Test download URL generation
- [ ] Add download tracking (log when agent downloaded)
### One-Time Agent Build (Agent Developer)
- [ ] Create agent/src/onetime_mode.rs
- [ ] Add --support-code flag to agent CLI
- [ ] Implement support code embedding in agent config
- [ ] Make agent auto-connect with embedded code
- [ ] Disable persistence (no registry, no service)
- [ ] Add self-delete after session ends
- [ ] Test one-time agent connects automatically
- [ ] Test agent deletes itself on exit
---
## Week 7: Agent Download (Phase 2)
### Download Endpoint (Backend Developer)
- [ ] Create server download handler
- [ ] Stream agent binary from server/static/downloads/
- [ ] Embed support code in download filename
- [ ] Add Content-Disposition header
- [ ] Test browser downloads file correctly
- [ ] Add virus scanning (optional, ClamAV)
- [ ] Log download events
### Portal Integration (Frontend Developer)
- [ ] Wire portal download button to API
- [ ] Show download progress (if possible)
- [ ] Add instructions: "Run the downloaded file"
- [ ] Add timeout warning (code expires in 10 minutes)
- [ ] Test end-to-end: code entry → download → run
- [ ] Add troubleshooting section (firewall, antivirus)
- [ ] Test on Windows 10/11 (no admin required)
---
## Week 8: Agent Download (Phase 3) & Dashboard UI
### Agent Polish (Agent Developer)
- [ ] Add tray icon to one-time agent (optional)
- [ ] Show "Connecting..." message
- [ ] Show "Connected" message
- [ ] Test agent launches without UAC prompt
- [ ] Test on Windows 7 (if required)
- [ ] Add error messages for connection failures
- [ ] Test firewall scenarios
### Dashboard Session List (Frontend Developer)
- [ ] Create session list component in dashboard.html
- [ ] Fetch active sessions from /api/sessions
- [ ] Display: support code, machine name, status, duration
- [ ] Add real-time updates via WebSocket
- [ ] Add "Join" button for each session
- [ ] Add "End" button (disconnect session)
- [ ] Add auto-refresh (every 3 seconds as fallback)
- [ ] Style session cards
- [ ] Test with multiple concurrent sessions
- [ ] Add empty state ("No active sessions")
### Session Detail Panel (Frontend Developer)
- [ ] Create session detail panel (right side of dashboard)
- [ ] Add tabs: Info, Screen, Chat, Commands, Files
- [ ] Info tab: machine details, OS, uptime, connection time
- [ ] Test tab switching
- [ ] Add close button to collapse panel
- [ ] Style with consistent theme
---
## Week 9: Clipboard Sync (Phase 1)
### Agent-Side Clipboard (Agent Developer)
- [ ] Add Windows clipboard API integration
- [ ] Implement clipboard change detection
- [ ] Read text from clipboard on change
- [ ] Send ClipboardUpdate message to server
- [ ] Receive ClipboardUpdate from server
- [ ] Write text to clipboard
- [ ] Test bidirectional sync
- [ ] Add clipboard permission handling
- [ ] Test with Unicode text
- [ ] Add error handling (clipboard locked, etc.)
### Viewer-Side Clipboard (Frontend Developer)
- [ ] Add JavaScript Clipboard API integration
- [ ] Detect clipboard changes in viewer
- [ ] Send clipboard updates via WebSocket
- [ ] Receive clipboard updates from agent
- [ ] Write to local clipboard
- [ ] Request clipboard permissions from user
- [ ] Test bidirectional sync
- [ ] Add UI indicator ("Clipboard synced")
- [ ] Test on Chrome, Firefox, Edge
---
## Week 10: Clipboard Sync (Phase 2) & PowerShell Foundation
### Clipboard Protocol (Backend Developer)
- [ ] Review ClipboardUpdate protobuf message
- [ ] Implement relay handler for clipboard
- [ ] Relay clipboard updates viewer ↔ agent
- [ ] Add clipboard event logging
- [ ] Test end-to-end clipboard sync
- [ ] Add rate limiting (prevent clipboard spam)
### Clipboard Testing (All)
- [ ] Test: Copy text on local → appears on remote
- [ ] Test: Copy text on remote → appears on local
- [ ] Test: Long text (10KB+)
- [ ] Test: Unicode characters (emoji, Chinese, etc.)
- [ ] Test: Rapid clipboard changes
- [ ] Document clipboard limitations (text-only for now)
### PowerShell Backend (Backend Developer)
- [ ] Create /api/sessions/:id/execute endpoint
- [ ] Accept command, timeout parameters
- [ ] Store command execution request in database
- [ ] Send CommandExecute message to agent via WebSocket
- [ ] Relay command output from agent to viewer
- [ ] Add command history logging
- [ ] Test with simple commands (hostname, ipconfig)
---
## Week 11: PowerShell Execution
### Agent PowerShell (Agent Developer)
- [ ] Implement CommandExecute handler in agent
- [ ] Spawn PowerShell.exe process
- [ ] Capture stdout and stderr streams
- [ ] Stream output back to server (chunked)
- [ ] Handle command timeouts (kill process)
- [ ] Send CommandComplete when done
- [ ] Test with long-running commands
- [ ] Test with commands requiring input (handle failure)
- [ ] Add error handling (command not found, etc.)
### Dashboard PowerShell UI (Frontend Developer)
- [ ] Add "Commands" tab to session detail panel
- [ ] Create command input textbox
- [ ] Add timeout controls (checkboxes: 30s, 60s, 5min, custom)
- [ ] Add "Execute" button
- [ ] Display command output (terminal-style, monospace)
- [ ] Add output scrolling
- [ ] Show command status (Running, Completed, Failed, Timeout)
- [ ] Add command history (previous commands)
- [ ] Test with PowerShell commands (Get-Process, Get-Service)
- [ ] Test with CMD commands (ipconfig, netstat)
---
## Week 12: File Download
### File Browse API (Backend Developer)
- [ ] Create /api/sessions/:id/files/browse endpoint
- [ ] Accept path parameter (default: C:\)
- [ ] Send FileBrowse message to agent
- [ ] Relay file list from agent
- [ ] Return JSON: files, directories, sizes, dates
- [ ] Add path validation (prevent directory traversal)
- [ ] Test with various paths
### Agent File Browser (Agent Developer)
- [ ] Implement FileBrowse handler
- [ ] List files and directories at given path
- [ ] Read file metadata (size, modified date, attributes)
- [ ] Send FileList response
- [ ] Handle permission errors (access denied)
- [ ] Test on C:\, D:\, network shares
- [ ] Add file type detection (extension-based)
### File Download Implementation (Agent Developer)
- [ ] Implement FileDownload handler in agent
- [ ] Read file in chunks (64KB chunks)
- [ ] Send FileChunk messages to server
- [ ] Handle large files (stream, don't load into memory)
- [ ] Send FileComplete when done
- [ ] Add progress tracking (bytes sent / total bytes)
- [ ] Handle file read errors
- [ ] Test with small files (KB)
- [ ] Test with large files (100MB+)
### Dashboard File Browser (Frontend Developer)
- [ ] Add "Files" tab to session detail panel
- [ ] Create file browser UI (left pane: remote files)
- [ ] Fetch file list from API
- [ ] Display: name, size, type, modified date
- [ ] Add breadcrumb navigation (C:\ > Users > Downloads)
- [ ] Add "Download" button for selected file
- [ ] Show download progress bar
- [ ] Save file to local disk (browser download)
- [ ] Test file browsing and download
- [ ] Add file type icons
---
## Phase 2 Completion Criteria
### Functional Checklist
- [ ] End-user portal functional (code entry, validation, download)
- [ ] One-time agent downloads and connects automatically
- [ ] Dashboard shows active sessions in real-time
- [ ] "Join" button launches viewer
- [ ] Input relay works (mouse + keyboard) with <200ms latency on WAN
- [ ] Text clipboard syncs bidirectionally
- [ ] Remote PowerShell executes with live output streaming
- [ ] Files can be browsed and downloaded from remote machine
### Quality Checklist
- [ ] All features tested on Windows 10/11
- [ ] Cross-browser testing (Chrome, Firefox, Edge)
- [ ] Network testing (LAN + WAN with throttling)
- [ ] Error handling for all failure scenarios
- [ ] Loading indicators for async operations
- [ ] User-friendly error messages
### Performance Checklist
- [ ] Portal loads in <2 seconds
- [ ] Dashboard session list updates in <1 second
- [ ] Clipboard sync latency <500ms
- [ ] PowerShell output streams in real-time (<100ms chunks)
- [ ] File download speed: 1MB/s+ on LAN
### Documentation Checklist
- [ ] End-user guide (how to use support portal)
- [ ] Technician guide (how to manage sessions)
- [ ] API documentation updated
- [ ] Known limitations documented (text-only clipboard, etc.)
---
**Phase Owner:** Frontend Developer + Agent Developer + Backend Developer
**Prerequisites:** Phase 1 complete (security + infrastructure)
**Target Completion:** 8 weeks from start
**Next Phase:** Phase 3 - Competitive Features

View File

@@ -1,147 +0,0 @@
# GuruConnect - Project Overview
**Status:** Phase 1 Starting
**Last Updated:** 2026-01-17
---
## Quick Reference
**Current Phase:** Phase 1 - Security & Infrastructure (Week 1 of 4)
**Team:** Backend Developer + DevOps Engineer
**Next Milestone:** All critical security vulnerabilities fixed (Week 2)
---
## Project Structure
```
guru-connect/
├── PROJECT_OVERVIEW.md ← YOU ARE HERE (quick reference)
├── MASTER_ACTION_PLAN.md ← Full roadmap (all 4 phases)
├── GAP_ANALYSIS.md ← Feature implementation matrix
├── PHASE1_SECURITY_INFRASTRUCTURE.md ← Current phase details
├── PHASE2_CORE_FEATURES.md ← Next phase details
├── CHECKLIST_STATE.json ← Current progress tracking
└── [Review archives]
├── Security review (conversation archive)
├── Architecture review (conversation archive)
├── Code quality review (conversation archive)
├── Infrastructure review (conversation archive)
└── Frontend/UI review (conversation archive)
```
---
## Phase Summary
| Phase | Name | Duration | Status | Start Date | Completion |
|-------|------|----------|--------|------------|------------|
| **1** | **Security & Infrastructure** | 4 weeks | **STARTING** | 2026-01-17 | TBD |
| 2 | Core Features | 8 weeks | Not Started | TBD | TBD |
| 3 | Competitive Features | 8 weeks | Not Started | TBD | TBD |
| 4 | Production Readiness | 6 weeks | Not Started | TBD | TBD |
**Total Timeline:** 26 weeks (conservative) / 20 weeks (recommended) / 16 weeks (aggressive)
---
## Phase 1: This Week's Focus
### Week 1 Goals
- Fix JWT secret hardcoded (SEC-1) - **CRITICAL**
- Implement rate limiting (SEC-2) - **CRITICAL**
- Fix SQL injection (SEC-3) - **CRITICAL**
- Fix agent validation (SEC-4) - **CRITICAL**
- Fix session takeover (SEC-5) - **CRITICAL**
### Active Tasks (see TodoWrite in session)
Check current session todos for real-time progress.
### Checklist Progress
- Total Phase 1 items: 147
- Completed: 0
- In Progress: (see session todos)
---
## Critical Path
**Current Blocker:** None (starting fresh)
**Next Blocker Risk:** JWT secret fix may require database migration
**Mitigation:** Test on staging first, prepare rollback procedure
---
## Team Assignments
**Backend Developer:**
- Security fixes (SEC-1 through SEC-13)
- API enhancements
- Database migrations
**DevOps Engineer:**
- Systemd service setup
- Prometheus monitoring
- Automated backups
- CI/CD pipeline
---
## Key Decisions Made
1. **Timeline:** 20-week recommended path (balanced risk)
2. **Team Size:** 4-5 developers (optimal)
3. **Scope:** Tier 0 + Tier 1 features (competitive MVP)
4. **Architecture:** Keep current Rust + Axum + PostgreSQL stack
5. **Deployment:** Systemd service (not Docker for Phase 1)
---
## Success Metrics
**Phase 1 Exit Criteria:**
- [ ] All 5 critical security issues fixed
- [ ] All 8 high-priority security issues fixed
- [ ] OWASP ZAP scan clean (no critical/high)
- [ ] Systemd service operational
- [ ] Prometheus + Grafana configured
- [ ] Automated backups running
- [ ] CI/CD pipeline functional
---
## Quick Commands
**View detailed phase plan:**
```bash
cat PHASE1_SECURITY_INFRASTRUCTURE.md
```
**Check current progress:**
```bash
cat CHECKLIST_STATE.json
```
**View full roadmap:**
```bash
cat MASTER_ACTION_PLAN.md
```
**View feature gaps:**
```bash
cat GAP_ANALYSIS.md
```
---
## Communication
**Status Updates:** Weekly (every Monday)
**Blocker Escalation:** Immediate (notify project owner)
**Phase Review:** End of each phase (4-week intervals)
---
**Project Owner:** Howard
**Technical Lead:** TBD
**Phase 1 Lead:** Backend Developer + DevOps Engineer

View File

@@ -1,25 +0,0 @@
# GuruConnect — Project State
> Last updated: 2026-04-20
**Status:** STALLED
**Last Activity:** 2026-01-17 (planning), last session log 2025-12-29
GuruConnect is an MSP remote desktop solution (ScreenConnect replacement) built in Rust/Axum with a PostgreSQL backend. 24 conversation log files exist. Phase 1 (Security & Infrastructure) was scoped in January 2026 but never started — the project has been stalled since initial planning.
## What Was Done
- Full architecture designed: Rust agent (Windows), Rust relay server (Linux), WebSocket protocol, protobuf wire format
- Security gap analysis completed: 5 critical issues identified (JWT hardcoded, rate limiting, SQL injection, agent validation, session takeover)
- Phase 1-4 roadmap created (26-week timeline)
- CLAUDE.md, MASTER_ACTION_PLAN.md, GAP_ANALYSIS.md, CHECKLIST_STATE.json all written
- Server deploys to 172.16.3.30:3002, proxied via NPM to connect.azcomputerguru.com
- Codebase: Rust workspace with agent/ and server/ crates, proto/ for protobuf definitions
## If Resuming
1. Read `PHASE1_SECURITY_INFRASTRUCTURE.md` — 5 critical security fixes still outstanding
2. Read `CHECKLIST_STATE.json` for current progress (0/147 Phase 1 items completed as of last update)
3. Start with SEC-1 (JWT secret hardcoded) — highest priority blocker
4. Server is at 172.16.3.30:3002; static dashboard files in `server/static/`
5. Build: `cargo build -p guruconnect --release` (agent, Windows); cross-compile for Linux server

View File

@@ -1,599 +0,0 @@
# GuruConnect - Remote Desktop Solution
**Project Type:** Internal Tool / MSP Platform Component
**Status:** Phase 1 MVP Development
**Technology Stack:** Rust, React, WebSockets, Protocol Buffers
**Integration:** GuruRMM platform
## Project Overview
GuruConnect is a remote desktop solution similar to ScreenConnect/ConnectWise Control, designed for fast, secure remote screen control and backstage tools for Windows systems. Built as an integrated component of the GuruRMM platform.
**Goal:** Provide MSP technicians with enterprise-grade remote desktop capabilities fully integrated with GuruRMM's monitoring and management features.
---
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ GuruConnect System │
└─────────────────────────────────────────────────────────────┘
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Dashboard │ │ GuruConnect │ │ GuruConnect │
│ (React) │◄──WSS──►│ Server (Rust) │◄──WSS──►│ Agent (Rust) │
│ │ │ │ │ │
│ - Session list │ │ - Relay frames │ │ - Capture │
│ - Live viewer │ │ - Auth/JWT │ │ - Input inject │
│ - Controls │ │ - Session mgmt │ │ - Encoding │
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐
│ PostgreSQL │
│ (Sessions, │
│ Audit Log) │
└─────────────────┘
```
### Components
#### 1. Agent (Rust - Windows)
**Location:** `~/claude-projects/guru-connect/agent/`
Runs on Windows client machines to capture screen and inject input.
**Responsibilities:**
- Screen capture via DXGI (with GDI fallback)
- Frame encoding (Raw+Zstd, VP9, H264)
- Dirty rectangle detection
- Mouse/keyboard input injection
- WebSocket client connection to server
#### 2. Server (Rust + Axum)
**Location:** `~/claude-projects/guru-connect/server/`
Relay server that brokers connections between dashboard and agents.
**Responsibilities:**
- WebSocket relay for screen frames and input
- JWT authentication for dashboard users
- API key authentication for agents
- Session management and tracking
- Audit logging
- Database persistence
#### 3. Dashboard (React)
**Location:** `~/claude-projects/guru-connect/dashboard/`
Web-based viewer interface, to be integrated into GuruRMM dashboard.
**Responsibilities:**
- Live video stream display
- Mouse/keyboard event capture
- Session controls (pause, record, etc.)
- Quality/encoding settings
- Connection status
#### 4. Protocol Definitions (Protobuf)
**Location:** `~/claude-projects/guru-connect/proto/`
Shared message definitions for efficient serialization.
**Key Message Types:**
- `VideoFrame` - Screen frames (raw+zstd, VP9, H264)
- `MouseEvent` - Mouse input (click, move, scroll)
- `KeyEvent` - Keyboard input
- `SessionRequest/Response` - Session management
---
## Encoding Strategy
GuruConnect dynamically selects encoding based on network conditions and GPU availability:
| Scenario | Encoding | Target | Notes |
|----------|----------|--------|-------|
| LAN (<20ms RTT) | Raw BGRA + Zstd | <50ms latency | Dirty rectangles only |
| WAN + GPU | H264 hardware | 100-500 Kbps | NVENC/QuickSync |
| WAN - GPU | VP9 software | 200-800 Kbps | CPU encoding |
### Implementation Details
**DXGI Screen Capture:**
- Desktop Duplication API for Windows 8+
- Dirty region tracking (only changed areas)
- Fallback to GDI BitBlt for Windows 7
**Compression:**
- Zstd for lossless (LAN scenarios)
- VP9 for high-quality software encoding
- H264 for GPU-accelerated encoding
**Frame Rate Adaptation:**
- Target 30 FPS for active sessions
- Drop to 5 FPS when idle
- Skip frames if network buffer full
---
## Security Model
### Authentication
**Dashboard Users:** JWT tokens
- Login via GuruRMM credentials
- Tokens expire after 24 hours
- Refresh tokens for long sessions
**Agents:** API keys
- Pre-registered API key per agent
- Tied to machine ID in GuruRMM database
- Rotatable via admin panel
### Transport Security
**TLS Required:** All WebSocket connections use WSS (TLS)
- Certificate validation enforced
- Self-signed certs rejected in production
- SNI support for multi-tenant hosting
### Session Audit
**Logged Events:**
- Session start/end with user and machine IDs
- Connection duration and data transfer
- User actions (mouse clicks, keystrokes - aggregate only)
- Quality/encoding changes
- Recording start/stop (Phase 4)
**Retention:** 90 days in PostgreSQL
---
## Phase 1 MVP Goals
### Completed Features
- [x] Project structure and build system
- [x] Protocol Buffers definitions
- [x] Basic WebSocket relay server
- [x] DXGI screen capture implementation
### In Progress
- [ ] GDI fallback for screen capture
- [ ] Raw + Zstd encoding with dirty rectangles
- [ ] Mouse and keyboard input injection
- [ ] React viewer component
- [ ] Session management API
### Future Phases
- **Phase 2:** VP9 and H264 encoding
- **Phase 3:** GuruRMM dashboard integration
- **Phase 4:** Session recording and playback
- **Phase 5:** File transfer and clipboard sync
- **Phase 6:** Multi-monitor support
---
## Development
### Prerequisites
**Rust:** 1.75+ (install via rustup)
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
**Windows SDK:** For agent development
- Visual Studio 2019+ with C++ tools
- Windows 10 SDK
**Protocol Buffers Compiler:**
```bash
# macOS
brew install protobuf
# Windows (via Chocolatey)
choco install protoc
# Linux
apt-get install protobuf-compiler
```
### Build Commands
```bash
# Build all components (from workspace root)
cd ~/claude-projects/guru-connect
cargo build --release
# Build agent only
cargo build -p guruconnect-agent --release
# Build server only
cargo build -p guruconnect-server --release
# Run tests
cargo test
# Check for warnings
cargo clippy
```
### Cross-Compilation
Building Windows agent from Linux:
```bash
# Install Windows target
rustup target add x86_64-pc-windows-msvc
# Build (requires cross or appropriate linker)
cross build -p guruconnect-agent --target x86_64-pc-windows-msvc --release
# Alternative: Use GitHub Actions for Windows builds
```
---
## Running in Development
### Server
```bash
# Development mode
cargo run -p guruconnect-server
# With environment variables
export DATABASE_URL=postgres://user:pass@localhost/guruconnect
export JWT_SECRET=your-secret-key-here
export RUST_LOG=debug
cargo run -p guruconnect-server
# Production build
./target/release/guruconnect-server --bind 0.0.0.0:8443
```
### Agent
Agent must run on Windows:
```powershell
# Run from Windows
.\target\release\guruconnect-agent.exe
# With custom server URL
.\target\release\guruconnect-agent.exe --server wss://guruconnect.azcomputerguru.com
```
### Dashboard
```bash
cd dashboard
npm install
npm run dev
# Production build
npm run build
```
---
## Configuration
### Server Config
**Environment Variables:**
```bash
DATABASE_URL=postgres://guruconnect:password@localhost:5432/guruconnect
JWT_SECRET=<generate-random-256-bit-secret>
BIND_ADDRESS=0.0.0.0:8443
TLS_CERT=/path/to/cert.pem
TLS_KEY=/path/to/key.pem
LOG_LEVEL=info
```
### Agent Config
**Command-Line Flags:**
```
--server <url> Server WebSocket URL (wss://...)
--api-key <key> Agent API key for authentication
--quality <low|med|high> Default quality preset
--log-level <level> Logging verbosity
```
**Registry Settings (Windows):**
```
HKLM\SOFTWARE\GuruConnect\Server = wss://guruconnect.azcomputerguru.com
HKLM\SOFTWARE\GuruConnect\ApiKey = <api-key>
```
---
## Deployment
### Server Deployment
**Recommended:** Docker container on GuruRMM server (172.16.3.30)
```yaml
# docker-compose.yml
version: '3.8'
services:
guruconnect:
image: guruconnect-server:latest
ports:
- "8443:8443"
environment:
DATABASE_URL: postgres://guruconnect:${DB_PASS}@db:5432/guruconnect
JWT_SECRET: ${JWT_SECRET}
volumes:
- ./certs:/certs:ro
depends_on:
- db
```
### Agent Deployment
**Method 1:** GuruRMM Agent Integration
- Bundle with GuruRMM agent installer
- Auto-start via Windows service
- Managed API key provisioning
**Method 2:** Standalone MSI Installer
- Separate install package
- Manual API key configuration
- Service registration
---
## Monitoring and Logs
### Server Logs
```bash
# View real-time logs
docker logs -f guruconnect-server
# Check error rate
grep ERROR /var/log/guruconnect/server.log | wc -l
```
### Agent Logs
**Location:** `C:\ProgramData\GuruConnect\Logs\agent.log`
**Key Metrics:**
- Frame capture rate
- Encoding latency
- Network send buffer usage
- Connection errors
### Session Metrics
**Database Query:**
```sql
SELECT
machine_id,
user_id,
AVG(duration_seconds) as avg_duration,
SUM(bytes_transferred) as total_data
FROM sessions
WHERE created_at > NOW() - INTERVAL '7 days'
GROUP BY machine_id, user_id;
```
---
## Testing
### Unit Tests
```bash
# Run all unit tests
cargo test
# Test specific module
cargo test --package guruconnect-agent --lib capture
```
### Integration Tests
```bash
# Start test server
cargo run -p guruconnect-server -- --bind 127.0.0.1:8444
# Run agent against test server
cargo run -p guruconnect-agent -- --server ws://127.0.0.1:8444
# Dashboard tests
cd dashboard && npm test
```
### Performance Testing
```bash
# Measure frame capture latency
cargo bench --package guruconnect-agent
# Network throughput test
iperf3 -c <server> -p 8443
```
---
## Troubleshooting
### Agent Cannot Connect
**Check:**
1. Server URL correct? `wss://guruconnect.azcomputerguru.com`
2. API key valid? Check GuruRMM admin panel
3. Firewall blocking? Test: `telnet <server> 8443`
4. TLS certificate valid? Check browser: `https://<server>:8443/health`
**Logs:**
```powershell
Get-Content C:\ProgramData\GuruConnect\Logs\agent.log -Tail 50
```
### Black Screen in Viewer
**Common Causes:**
1. DXGI capture failed, no GDI fallback
2. Encoding errors (check agent logs)
3. Network packet loss (check quality)
4. Agent service stopped
**Debug:**
```powershell
# Check agent service
Get-Service GuruConnectAgent
# Test screen capture manually
.\guruconnect-agent.exe --test-capture
```
### High CPU Usage
**Possible Issues:**
1. Software encoding (VP9) on weak CPU
2. Full-screen capture when dirty rects should be used
3. Too high frame rate for network conditions
**Solutions:**
- Enable H264 hardware encoding (if GPU available)
- Lower quality preset
- Reduce frame rate to 15 FPS
---
## Key References
**RustDesk Source:**
`~/claude-projects/reference/rustdesk/`
**GuruRMM:**
`~/claude-projects/gururmm/` and `D:\ClaudeTools\projects\msp-tools\guru-rmm\`
**Development Plan:**
`~/.claude/plans/shimmering-wandering-crane.md`
**Session Logs:**
`~/claude-projects/session-logs/2025-12-21-guruconnect-session.md`
---
## Integration with GuruRMM
### Dashboard Integration
GuruConnect viewer will be embedded in GuruRMM dashboard:
```jsx
// Example React component integration
import { GuruConnectViewer } from '@guruconnect/react';
function MachineDetails({ machineId }) {
return (
<div>
<h2>Machine: {machineId}</h2>
<GuruConnectViewer
machineId={machineId}
apiToken={userToken}
/>
</div>
);
}
```
### API Integration
**Start Session:**
```http
POST /api/sessions/start
Authorization: Bearer <jwt-token>
Content-Type: application/json
{
"machine_id": "abc-123-def",
"quality": "medium"
}
```
**Response:**
```json
{
"session_id": "sess_xyz789",
"websocket_url": "wss://guruconnect.azcomputerguru.com/ws/sess_xyz789"
}
```
---
## Roadmap
### Phase 1: MVP (In Progress)
- Basic screen capture and viewing
- Mouse/keyboard input
- Simple quality control
### Phase 2: Production Ready
- VP9 and H264 encoding
- Adaptive quality
- Connection recovery
- Performance optimization
### Phase 3: GuruRMM Integration
- Embedded dashboard viewer
- Single sign-on
- Unified session management
- Audit integration
### Phase 4: Advanced Features
- Session recording and playback
- Multi-monitor support
- Audio streaming
- Clipboard sync
### Phase 5: Enterprise Features
- Permission management
- Session sharing (invite technician)
- Chat overlay
- File transfer
---
## Project History
**2025-12-21:** Initial project planning and architecture design
**2025-12-21:** Build system setup, basic agent structure
**2026-01-XX:** Phase 1 MVP development ongoing
---
## License & Credits
**License:** Proprietary (Arizona Computer Guru internal use)
**Credits:**
- Architecture inspired by RustDesk
- Built with Rust, Tokio, Axum
- WebRTC considered but rejected (complexity)
---
## Support
**Technical Contact:** Mike Swanson
**Email:** mike@azcomputerguru.com
**Phone:** 520.304.8300
---
**Status:** Active Development - Phase 1 MVP
**Priority:** Medium (supporting GuruRMM platform)
**Next Milestone:** Complete dirty rectangle detection and input injection

View File

@@ -1,801 +0,0 @@
# GuruConnect Requirements
## Design Principles
1. **End-user simplicity** - One-click or code-based session joining
2. **Standalone capable** - Works independently, integrates with GuruRMM optionally
3. **Technician-centric** - Built for MSP workflows
---
## End-User Portal (connect.azcomputerguru.com)
### Unauthenticated View
When a user visits the portal without being logged in:
```
┌─────────────────────────────────────────────────────┐
│ │
│ [Company Logo] │
│ │
│ Enter your support code: │
│ ┌─────────────────────────┐ │
│ │ 8 4 7 2 9 1 │ │
│ └─────────────────────────┘ │
│ │
│ [ Connect ] │
│ │
│ ───────────────────────────────────────────────── │
│ │
│ Instructions will appear here after clicking │
│ Connect, based on your browser. │
│ │
└─────────────────────────────────────────────────────┘
```
### Connection Flow
1. **User enters code** → Click "Connect"
2. **Server validates code** → Returns session info or error
3. **Attempt app launch** via custom protocol:
- `guruconnect://session/{code}`
- If app is installed, it launches and connects
4. **If app doesn't launch** (timeout ~3 seconds):
- Auto-download small EXE (`GuruConnect-{code}.exe`)
- Show browser-specific instructions
### Browser-Specific Instructions
Detect browser via User-Agent and show appropriate guidance:
**Chrome:**
> "Click the download in the bottom-left corner of your screen, then click 'Open'"
> [Screenshot of Chrome download bar]
**Firefox:**
> "Click 'Save File', then open your Downloads folder and double-click the file"
> [Screenshot of Firefox download dialog]
**Edge:**
> "Click 'Open file' in the download notification at the top of your screen"
> [Screenshot of Edge download prompt]
**Safari:**
> "Click the download icon in the toolbar, then double-click the file"
> [Screenshot of Safari downloads]
**Generic/Unknown:**
> "Your download should start automatically. Look for the file in your Downloads folder and double-click to run it."
### Custom Protocol Handler
**Protocol:** `guruconnect://`
**Format:** `guruconnect://session/{code}`
**Registration:**
- Permanent agent registers protocol handler on install
- One-time agent does NOT register (to avoid clutter)
**Behavior:**
- If registered: OS launches installed agent with session code
- If not registered: Browser shows "nothing happened" → triggers download fallback
### One-Time Session Agent (Temp/Support)
**Key Requirements:**
- Runs in **user space** - NO admin elevation required
- Downloads as `GuruConnect-{code}.exe` (code baked in)
- ~3-5MB executable
- Self-contained (no installer, no dependencies)
- Connects directly to session on launch
- Self-deletes after session ends (or on next reboot)
**Elevation Note:**
- Basic screen sharing works without admin
- Some features (input to elevated windows, UAC dialogs) need admin
- Show optional "Run as Administrator" button for full access
---
## Technician Dashboard (Logged-In View)
### Visual Style
Follow GuruRMM dashboard design:
- HSL CSS variables for theming (dark/light mode support)
- Sidebar navigation with lucide-react icons
- Card-based content areas
- Responsive layout (mobile hamburger menu)
- Consistent component library (Button, Card, Input)
### Navigation Structure
```
┌──────────────────────────────────────────────────────────────┐
│ ┌──────────┐ │
│ │GuruConnect│ │
│ └──────────┘ │
│ │
│ [LIST] Support ← Active temp sessions │
│ [COMPUTER] Access ← Unattended/permanent sessions │
│ [CONFIG] Build ← Installer builder │
│ [GEAR] Settings ← Preferences, groupings, appearance │
│ │
│ ───────────── │
│ 👤 Mike S. │
│ Admin │
│ [Sign out] │
└──────────────────────────────────────────────────────────────┘
```
### Support Tab (Active Temporary Sessions)
**Layout:**
```
┌─────────────────────────────────────────────────────────────────────┐
│ Support Sessions [ + Generate Code ] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ▼ My Sessions (3) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 847291 │ John's PC │ Connected │ 00:15:32 │ [Join] [End] │ │
│ │ 293847 │ Waiting │ Pending │ - │ [Cancel] │ │
│ │ 182736 │ Sarah-PC │ Connected │ 00:45:10 │ [Join] [End] │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ▼ Team Sessions (2) [Howard's sessions] │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 928374 │ DESKTOP-A │ Connected │ 00:05:22 │ [View] [Join] │ │
│ │ 746382 │ Laptop-01 │ Connected │ 01:20:15 │ [View] │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ▶ Support Requests (1) [End-user initiated] │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
**Features:**
- Sessions grouped by technician (own first, then team)
- Real-time status updates (WebSocket)
- Duration timer for active sessions
- Quick actions: Join, View (spectate), End, Cancel
- Support request queue from end-user tray icon requests
### Access Tab (Unattended/Permanent Sessions)
**Layout:**
```
┌─────────────────────────────────────────────────────────────────────┐
│ Access [SEARCH] [Search...] [ + Build ] │
├──────────────┬──────────────────────────────────────────────────────┤
│ │ │
│ ▼ By Company │ All Machines by Company 1083 machines │
│ (empty) 120│ ─────────────────────────────────────────────────── │
│ 4 Paws 1│ ┌──────────────────────────────────────────────┐ │
│ ACG 10│ │ ● PC-FRONT01 │ Glaztech │ Win 11 │ Online │ │
│ Glaztech 224 │ ● SERVER-DC01 │ Glaztech │ Svr 22 │ Online │ │
│ AirPros 2│ │ ○ LAPTOP-SALES │ Glaztech │ Win 10 │ 2h ago │ │
│ ... │ │ ● WORKSTATION-3 │ ACG │ Win 11 │ Online │ │
│ │ │ ... │ │
│ ▶ By Site │ └──────────────────────────────────────────────┘ │
│ ▶ By OS │ │
│ ▶ By Tag │ ──────────────── Machine Detail ───────────────── │
│ │ Name: PC-FRONT01 │
│ ──────────── │ Company: Glaztech Industries │
│ Smart Groups │ Site: Phoenix Office │
│ ──────────── │ OS: Windows 11 Pro (23H2) │
│ Attention 1│ User: jsmith │
│ Online 847 IP: 192.168.1.45 / 72.194.62.4 │
│ Offline 30d 241 Serial: 8XKJF93 │
│ Offline 1yr 238 Last Seen: Now │
│ Outdated 516│ │
│ Recent 5│ [ Connect ] [ Wake ] [ Tools ▼ ] │
│ │ │
│ ▶ My Filters │ │
│ + New Filter│ │
└──────────────┴──────────────────────────────────────────────────────┘
```
**Left Sidebar - Groupings:**
- By Company (with counts, expandable)
- By Site
- By OS
- By Tag
- By Device Type
- Smart Groups (auto-generated)
- Custom Filters (user-created)
**Main Panel:**
- Machine list with status indicators (● online, ○ offline)
- Quick info columns (configurable)
- Click to select → shows detail panel
**Right Panel - Machine Detail:**
- Full machine info (Session, Device, Network sections)
- Action buttons: Connect, Wake (if offline), Tools dropdown
### Build Tab (Installer Builder)
**Layout:**
```
┌─────────────────────────────────────────────────────────────────────┐
│ Build Installer │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Name: [ Use Machine Name ▼ ] │
│ Company: [ __________________________ ] (autocomplete) │
│ Site: [ __________________________ ] (autocomplete) │
│ Department: [ __________________________ ] │
│ Device Type: [ Workstation ▼ ] │
│ Tag: [ __________________________ ] │
│ │
│ Platform: ○ Windows 64-bit (recommended) │
│ ○ Windows 32-bit │
│ ○ Linux (coming soon) │
│ │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ [ Download EXE ] [ Download MSI ] [ Copy URL ] [ Send Link ] │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
### Settings Tab
**Sections:**
**Appearance:**
- Theme: Light / Dark / System
- Sidebar: Expanded / Collapsed by default
- Default landing tab: Support / Access
**Groupings:**
- Default grouping for Access tab
- Show/hide specific smart groups
- Configure custom filter defaults
**Notifications:**
- Browser notifications: On/Off
- Sound alerts: On/Off
- Email alerts for support requests: On/Off
**Session Defaults:**
- Default session visibility: Private / Team / Company
- Auto-accept from specific companies
**Account:**
- Change password
- Two-factor authentication
- API keys (for integrations)
---
## Session Types
### 1. Support Sessions (Attended/One-Time)
**End-User Experience:**
- User visits portal (e.g., `support.azcomputerguru.com`)
- Portal generates a 5-6 digit numeric code (e.g., `847291`)
- User enters code OR clicks generated link
- Small executable downloads and runs (no install required)
- Session connects to assigned technician
**Technician Experience:**
- Generate session codes from dashboard
- Codes can be pre-assigned to specific tech or first-come
- Session appears on assigned tech's dashboard
**Code Management:**
- Codes remain active until used (no automatic expiration)
- Anti-collision: Active codes tracked in database, never reissued while active
- Once session completes, code is released back to pool
- Manual code cancellation available
- Optional: Tech can set manual expiration if desired
- 6 digits = 1M codes, plenty of headroom for concurrent active codes
### 2. Unattended Sessions (Permanent/MSP)
**Installer Builder:**
Build custom installers with pre-defined metadata fields:
| Field | Description | Example |
|-------|-------------|---------|
| Name | Machine identifier | "Use Machine Name" (auto) or custom |
| Company | Client/organization | "Glaztech Industries" |
| Site | Physical location | "Phoenix Office" |
| Department | Business unit | "Accounting" |
| Device Type | Machine category | "Workstation", "Server", "Laptop" |
| Tag | Custom label | "VIP", "Critical", "Testing" |
**Installer Output Options:**
- Download EXE directly
- Download MSI (for GPO deployment)
- Copy installer URL (for deployment scripts)
- Send link via email
**Server-Built Installers:**
- Server generates installers on-demand
- All metadata (Company, Site, etc.) baked into binary
- Unique installer per configuration
- No manual config file editing required
- Server URL and auth token embedded
**MSI Support:**
- MSI wrapper for Group Policy deployment
- Silent install support: `msiexec /i guruconnect.msi /qn`
- Uninstall via Add/Remove Programs or GPO
- Transform files (.mst) for custom configurations (optional)
**End-User Reconfiguration:**
- Re-run installer with flags to modify settings
- `--reconfigure` flag enters config mode instead of reinstall
- User can change: Name, Site, Tag, Department
- Changes sync to server on next check-in
- Useful for when machine moves to different site/department
Example:
```
guruconnect-agent.exe --reconfigure --site "New York Office" --tag "Laptop"
```
**Deployment:**
- Installed as Windows service
- Persists across reboots
- Auto-reconnects on network changes
- Can be bundled with GuruRMM agent OR standalone
- Metadata fields baked into agent at build time
**Management:**
- Assigned to client/site hierarchy
- Always available for remote access (when machine is on)
- Background service, no user interaction required
---
## Technician Dashboard
### Session Visibility & Permissions
| Role | Own Sessions | Team Sessions | All Sessions |
|------|--------------|---------------|--------------|
| Technician | Full access | View if permitted | No |
| Senior Tech | Full access | View + join | View |
| Admin | Full access | Full access | Full access |
**Permission Model:**
- Sessions created by a tech default to their dashboard
- Configurable visibility: Private, Team, Company-wide
- "Snoop" capability for supervisors (view session list, optionally join)
- Session handoff between technicians
### Auto-Generated Groups (Sidebar)
The dashboard automatically generates navigable groups based on metadata and status:
**By Metadata Field:**
- All Machines by Company (with counts per company)
- All Machines by Site
- All Machines by OS
- All Machines by Tag
- All Machines by Device Type
**Smart Status Groups:**
| Group | Definition |
|-------|------------|
| Attention | Machines flagged for follow-up |
| Host Connected | Tech currently connected |
| Guest Connected | End-user currently at machine |
| Recently Accessed | Connected within last 24 hours |
| Offline 30 Days | No check-in for 30+ days |
| Offline 1 Year | Stale agents, cleanup candidates |
| Outdated Clients | Agent version behind current |
| Powered on last 10 min | Just came online |
**Custom Session Groups:**
- Create saved filter combinations
- Name and organize custom groups
- Share groups with team (optional)
### Machine Detail Panel
When a machine is selected, show comprehensive info in side panel:
**Session Info:**
- Name, Company, Site, Department
- Device Type, Tag
- Hosts Connected (tech count)
- Guests Connected (user present)
- Guest Last Connected
- Logged On User
- Idle Time
- Pending Activity
- Custom Attributes
**Device Info:**
- Machine name
- Operating System + Version
- OS Install Date
- Processor
- Available Memory
- Manufacturer & Model
- Serial Number / Service Tag
- Machine Description
**Network Info:**
- Public IP Address
- Private IP Address(es)
- MAC Address(es)
**Other:**
- Agent Version
- Last Check-in
- First Seen
- Screenshot thumbnail (optional)
### Unattended Session Search
**Searchable Fields:**
- Hostname / Computer name
- Internal IP address
- External/Public IP address
- Currently logged-in user
- OS type (Windows 10, 11, Server 2019, etc.)
- OS version/build number
- Serial number
- Service tag (Dell, HP, Lenovo tags)
- Client/Site assignment
- Custom tags/labels
- Last check-in time
- Agent version
**Filter Capabilities:**
- Last check-in: < 1 hour, < 24 hours, < 7 days, > 30 days (stale)
- OS type grouping
- Client/Site hierarchy
- Online/Offline status
- Custom saved filters (user-defined queries)
**Saved Searches:**
- Create and name custom filter combinations
- Share saved searches with team
- Pin frequently used searches
---
## Remote Control Features
### Screen Control
- Real-time screen viewing
- Mouse control (click, drag, scroll)
- Keyboard input
- Multi-monitor support (switch displays, view all)
### Clipboard Integration
**Priority Feature - Full Bidirectional Clipboard:**
| Direction | Content Types |
|-----------|---------------|
| Local → Remote | Text, Files, Images, Rich text |
| Remote → Local | Text, Files, Images, Rich text |
**Special Capabilities:**
- **Keystroke injection from clipboard** - Paste local clipboard as keystrokes (for login screens, BIOS, pre-OS environments)
- Drag-and-drop file transfer
- Large file support (chunked transfer with progress)
### File Transfer
- Browse remote filesystem
- Upload files to remote
- Download files from remote
- Drag-and-drop support
- Transfer queue with progress
### Backstage Tools (No Screen Required)
- Remote command prompt / PowerShell
- Task manager view
- Services manager
- Registry editor (future)
- Event log viewer (future)
- System info panel
### Chat / Messaging
**Bidirectional Chat:**
- Tech can message end user during session
- End user can message tech
- Chat persists across session reconnects
- Chat history viewable in session log
**End-User Initiated Contact:**
- System tray icon for permanent agents
- "Request Support" option in tray menu
- User can type message/description of issue
- Creates support request visible to assigned technicians
**Technician Notifications:**
- Dashboard shows pending support requests
- Optional: Desktop/browser notifications for new requests
- Optional: Email/webhook alerts for after-hours requests
- Request queue with timestamps and user messages
### Credential Management (Future)
**Credential Injection:**
- Integration with ITGlue for credential lookup
- Integration with GuruRMM credential vault
- Tech selects credential from dropdown, never sees actual password
- Credential injected directly as keystrokes to remote session
- Audit log of which credential was used, by whom, when
**Local Credential Capture (Future):**
- Optional feature to capture credentials entered during session
- Stored encrypted, accessible only to admins
- For scenarios where client provides password verbally
---
## Security Requirements
### Authentication
- Technician login with username/password
- MFA/2FA support (TOTP)
- SSO integration (future - Azure AD, Google)
- API key auth for programmatic access
### Session Security
- All traffic over TLS/WSS
- End-to-end encryption for screen data
- Session consent prompt (attended sessions)
- Configurable session timeout
### Audit & Compliance
- Full audit log: who, when, what machine, duration
- Optional session recording
- Action logging (file transfers, commands run)
- Exportable audit reports
---
## Integration
### GuruRMM Integration
- Launch remote session from RMM agent list
- Share agent data (hostname, IP, user, etc.)
- Single authentication
- Unified dashboard option
### Standalone Mode
- Fully functional without GuruRMM
- Own user management
- Own agent deployment
- Can be licensed/sold separately
---
## Agent Requirements
### Support Session Agent (One-Time)
- Single executable, no installation
- Downloads and runs from portal
- Self-deletes after session ends
- Minimal footprint (<5MB)
- No admin rights required for basic screen share
- Admin rights optional for elevated access
### Unattended Agent (Permanent)
- Windows service installation
- Auto-start on boot
- Runs as SYSTEM for full access
- Configurable check-in interval
- Resilient reconnection
**Auto-Update:**
- Agent checks for updates on configurable interval
- Silent background update (no user interaction)
- Rollback capability if update fails
- Version reported to server for "Outdated Clients" filtering
**Lightweight Performance:**
- Minimal CPU/RAM footprint when idle
- No performance impact during normal operation
- Screen capture only active during remote session
- Target: <10MB RAM idle, <1% CPU idle
**Survival & Recovery:**
- Survives reboots (Windows service auto-start)
- Works in Safe Mode with Networking
- Registers as safe-mode-capable service
- Remote-initiated Safe Mode reboot (with networking)
- Auto-reconnects after safe mode boot
**Safe Mode Reboot Feature:**
- Tech can trigger safe mode reboot from dashboard
- Options: Safe Mode, Safe Mode with Networking, Safe Mode with Command Prompt
- Agent persists through safe mode boot
- Useful for malware removal, driver issues, repairs
**Emergency Reboot:**
- Force immediate reboot without waiting for processes
- Bypasses "program not responding" dialogs
- Equivalent to holding power button, but cleaner
- Use case: Frozen system, hung updates, unresponsive machine
- Confirmation required to prevent accidental use
**Wake-on-LAN:**
- Store MAC address for each agent
- Send WoL magic packet to wake offline machines
- Works within same broadcast domain (LAN)
- For remote WoL: requires WoL relay/proxy on local network
- Dashboard shows "Wake" button for offline machines with known MAC
- Optional: Integration with GuruRMM agent as WoL relay
### Reported Metrics (Unattended)
- Hostname
- Internal IP(s)
- External IP
- Current user
- OS type and version
- Serial number
- Service tag
- CPU, RAM, Disk (basic)
- Last boot time
- Agent version
- Custom properties (extensible)
---
## Platform Support
### Build Targets
| Target | Architecture | Priority | Notes |
|--------|--------------|----------|-------|
| `x86_64-pc-windows-msvc` | 64-bit | Primary | Default build, Win7+ |
| `i686-pc-windows-msvc` | 32-bit | Secondary | Legacy outliers |
### Phase 1 (MVP)
- Windows 10/11 agents (64-bit)
- Windows Server 2016+ agents (64-bit)
- Web dashboard (any browser)
### Phase 2
- 32-bit agent builds for legacy systems
- Windows 7/8.1 support
### Future Phases
- macOS agent
- Linux agent
- Mobile viewer (iOS/Android)
---
## Non-Functional Requirements
### Performance
- Screen updates: 30+ FPS on LAN, 15+ FPS on WAN
- Input latency: <100ms on LAN, <200ms on WAN
- Support 50+ concurrent unattended agents per server (scalable)
### Reliability
- Agent auto-reconnect on network change
- Server clustering for HA (future)
- Graceful degradation on poor networks
### Deployment
- Single binary server (Docker or native)
- Single binary agent (MSI installer + standalone EXE)
- Cloud-hostable or on-premises
---
## Team Feedback (2025-12-28)
### Howard's Requirements
#### Core Remote Support & Access Capabilities
1. **Screen Sharing & Remote Control**
- View and interact with the end-user's desktop in real time
- Technicians can control mouse and keyboard, just like sitting at the remote machine
2. **Attended & Unattended Access**
- Attended support: on-demand support sessions where the user connects via a session code or link
- Unattended access: persistent remote connections that allow access anytime without user presence
3. **Session Management**
- Initiate, pause, transfer, and end remote sessions
- Session transfer: pass control of a session to another technician
- Session pause and idle timeout controls
4. **File & Clipboard Sharing**
- Drag-and-drop file transfer between local and remote systems
- Clipboard sharing for copy/paste between devices
5. **Multi-Session Handling**
- Technicians can manage multiple concurrent remote sessions
6. **Multi-Monitor Support**
- Seamlessly switch between multiple monitors on the remote system
#### Advanced Support & Administrative Functions
7. **Backstage / Silent Support Mode**
- Execute tasks, run scripts, and troubleshoot without disrupting the user's screen (background session)
8. **Shared & Personal Toolboxes**
- Save commonly used tools, scripts, or executables
- Share them with team members for reuse in sessions
9. **Custom Scripts & Automation**
- Automate repetitive tasks during remote sessions
10. **Diagnostic & Command Tools**
- Run PowerShell, Command Prompt, view system event logs, uninstall apps, start/stop services, kill processes, etc.
- Better PowerShell/CMD running abilities with configurable timeouts (checkboxes/text boxes instead of typing every time)
#### Security & Access Control Features
11. **Encryption**
- All traffic is secured with AES-256 encryption
12. **Role-Based Permissions**
- Create granular technician roles and permissions to control who can do what
13. **Two-Factor & Login Security**
- Support for multi-factor authentication (MFA) and other secure login methodologies
14. **Session Consent & Alerts**
- Require end-user consent before connecting (configurable)
- Alerts notify users of maintenance or work in progress
15. **Audit Logs & Session Recording**
- Automatically record sessions
- Maintain detailed logs of connections and actions for compliance
#### Communication & Collaboration Tools
16. **Real-Time Chat**
- Text chat between technician and end user during sessions
17. **Screen Annotations**
- Draw and highlight areas on the user's screen for clearer instructions
#### Cross-Platform & Mobile Support
18. **Cross-Platform Support**
- Remote control across Windows, macOS, Linux, iOS, and Android
19. **Mobile Technician Support**
- Technicians can support clients from mobile devices (view screens, send Ctrl-Alt-Delete, reboot)
20. **Guest Mobile Support**
- Remote assistance for user Android and iOS devices
#### Integration & Customization
21. **PSA & Ticketing Integrations**
- Launch support sessions from RMM/PSA and other ticketing systems
22. **Custom Branding & Interface**
- White-labeling, logos, colors, and custom client titles
23. **Machine Organization & Search**
- Dynamic grouping of devices and custom property filtering to locate machines quickly
#### Reporting & Monitoring
24. **Session & System Reports**
- Audit logs, session histories, technician performance data, etc.
25. **Diagnostic Reporting**
- Collect performance and diagnostic information during or after sessions
### Additional Notes from Howard
- **64-bit client requirement** - ScreenConnect doesn't have a 64-bit client, which limits deployment options
- **PowerShell timeout controls** - Should have UI controls (checkboxes/text boxes) for timeouts rather than typing commands every time

View File

@@ -1,74 +0,0 @@
# SEC-2: Rate Limiting - Implementation Notes
**Status:** Partially Implemented - Needs Type Resolution
**Priority:** HIGH
**Blocker:** Compilation errors with tower_governor type signatures
## What Was Done
1. Added tower_governor dependency to Cargo.toml
2. Created middleware/rate_limit.rs module
3. Defined three rate limiters:
- `auth_rate_limiter()` - 5 requests/minute for login
- `support_code_rate_limiter()` - 10 requests/minute for code validation
- `api_rate_limiter()` - 60 requests/minute for general API
4. Applied rate limiting to routes in main.rs:
- `/api/auth/login`
- `/api/auth/change-password`
- `/api/codes/:code/validate`
## Current Blocker
Tower_governor GovernorLayer requires 2 generic type parameters, but the exact types are complex:
- Key extractor: SmartIpKeyExtractor
- Rate limiter method: (type unclear from docs)
## Attempted Solutions
1. Used default types - Failed (DefaultDirectRateLimiter doesn't exist)
2. Used impl Trait - Too complex, nested trait bounds
3. Added "axum" feature to tower_governor - Still type errors
## Next Steps to Complete
1. Research tower_governor v0.4 examples for Axum 0.7
2. OR: Use simpler alternative like tower-http RequestBodyLimitLayer
3. OR: Implement custom rate limiting with Redis/in-memory cache
4. Test with actual HTTP requests (curl, Postman)
5. Add rate limit headers (X-RateLimit-Remaining, X-RateLimit-Reset)
## Recommended Approach
**Option A: Fix tower_governor types** (1-2 hours)
- Find working example for tower_governor + Axum 0.7
- Copy exact type signatures
- Test compilation
**Option B: Switch to custom middleware** (2-3 hours)
- Use in-memory HashMap<IP, (count, last_reset)>
- Implement middleware manually
- More control, simpler types
**Option C: Use Redis for rate limiting** (3-4 hours)
- Add redis dependency
- Implement with atomic INCR + EXPIRE
- Production-grade, distributed-ready
## Temporary Mitigation
Until rate limiting is fully operational:
- Monitor auth endpoint logs for brute force attempts
- Consider firewall-level rate limiting (fail2ban, NPM)
- Enable account lockout after N failed attempts (add to user table)
## Files Modified
- `server/Cargo.toml` - Added tower_governor dependency
- `server/src/middleware/rate_limit.rs` - Rate limiter definitions (NOT compiling)
- `server/src/middleware/mod.rs` - Module exports
- `server/src/main.rs` - Applied rate limiting to routes (commented out for now)
---
**Created:** 2026-01-17
**Next Action:** Move to SEC-3 (SQL Injection) - Higher priority

View File

@@ -1,143 +0,0 @@
# SEC-3: SQL Injection - Security Audit
**Status:** SAFE - No vulnerabilities found
**Priority:** CRITICAL (Resolved)
**Date:** 2026-01-17
## Audit Findings
### GOOD NEWS: No SQL Injection Vulnerabilities
The GuruConnect server uses **sqlx** with **parameterized queries** throughout the entire codebase. This is the gold standard for SQL injection prevention.
### Files Audited
1. **server/src/db/users.rs** - All queries use `$1, $2` placeholders with `.bind()`
2. **server/src/db/machines.rs** - All queries use parameterized binding
3. **server/src/db/sessions.rs** - All queries safe
4. **server/src/db/events.rs** - Not checked but follows same pattern
5. **server/src/db/support_codes.rs** - Not checked but follows same pattern
6. **server/src/db/releases.rs** - Not checked but follows same pattern
### Example of Safe Code
```rust
// From users.rs:51-58 - SAFE
pub async fn get_user_by_username(pool: &PgPool, username: &str) -> Result<Option<User>> {
let user = sqlx::query_as::<_, User>(
"SELECT * FROM users WHERE username = $1" // $1 is placeholder
)
.bind(username) // username is bound as parameter, not concatenated
.fetch_optional(pool)
.await?;
Ok(user)
}
```
```rust
// From machines.rs:32-47 - SAFE
sqlx::query_as::<_, Machine>(
r#"
INSERT INTO connect_machines (agent_id, hostname, is_persistent, status, last_seen)
VALUES ($1, $2, $3, 'online', NOW()) // All user inputs are placeholders
ON CONFLICT (agent_id) DO UPDATE SET
hostname = EXCLUDED.hostname,
status = 'online',
last_seen = NOW()
RETURNING *
"#,
)
.bind(agent_id)
.bind(hostname)
.bind(is_persistent)
.fetch_one(pool)
.await
```
### Why This is Safe
**Sqlx Parameterized Queries:**
- User input is **never** concatenated into SQL strings
- Parameters are sent separately to the database
- Database treats parameters as data, not executable code
- Prevents all forms of SQL injection
**No Unsafe Patterns Found:**
- No `format!()` macros with SQL
- No string concatenation with user input
- No raw SQL string building
- No dynamic query construction
### What Was Searched For
Searched entire `server/src/db/` directory for:
- `format!.*SELECT`
- `format!.*WHERE`
- `format!.*INSERT`
- String concatenation patterns
- Raw query builders
**Result:** No unsafe patterns found
## Additional Recommendations
While SQL injection is not a concern, consider these improvements:
### 1. Input Validation (Defense in Depth)
Even though sqlx protects against SQL injection, validate input for data integrity:
```rust
// Example: Validate username format
pub fn validate_username(username: &str) -> Result<()> {
if username.len() < 3 || username.len() > 50 {
return Err(anyhow!("Username must be 3-50 characters"));
}
if !username.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
return Err(anyhow!("Username can only contain letters, numbers, _ and -"));
}
Ok(())
}
```
### 2. Add Input Sanitization Module
Create `server/src/validation.rs`:
- Username validation (alphanumeric + _ -)
- Email validation (basic format check)
- Agent ID validation (UUID or alphanumeric)
- Hostname validation (DNS-safe characters)
- Tag validation (no special characters except - _)
### 3. Prepared Statement Caching
Sqlx already caches prepared statements, but ensure:
- Connection pool is properly sized
- Prepared statements are reused efficiently
### 4. Query Monitoring
Add logging for:
- Slow queries (>1 second)
- Failed queries (authentication errors, constraint violations)
- Unusual query patterns
## Conclusion
**SEC-3: SQL Injection is RESOLVED**
The codebase uses best practices for SQL injection prevention. No changes required for this security issue.
However, adding input validation is still recommended for:
- Data integrity
- Better error messages
- Defense in depth
**Status:** [SAFE] No SQL injection vulnerabilities
**Action Required:** None (optional: add input validation for data integrity)
---
**Audit Completed:** 2026-01-17
**Audited By:** Phase 1 Security Review
**Next Review:** After any database query changes

View File

@@ -1,302 +0,0 @@
# SEC-4: Agent Connection Validation - Security Audit
**Status:** NEEDS ENHANCEMENT - Validation exists but has security gaps
**Priority:** CRITICAL
**Date:** 2026-01-17
## Audit Findings
### GOOD: Existing Validation
The agent connection handler (relay/mod.rs:54-123) has solid validation logic:
**Support Code Validation (Lines 74-87)**
```rust
if let Some(ref code) = support_code {
let code_info = state.support_codes.get_status(code).await;
if code_info.is_none() {
warn!("Agent connection rejected: {} - invalid support code {}", agent_id, code);
return Err(StatusCode::UNAUTHORIZED); // ✓ Rejects invalid codes
}
let status = code_info.unwrap();
if status != "pending" && status != "connected" {
warn!("Agent connection rejected: {} - support code {} has status {}", agent_id, code, status);
return Err(StatusCode::UNAUTHORIZED); // ✓ Rejects expired/cancelled codes
}
}
```
**API Key Validation (Lines 90-98)**
```rust
if let Some(ref key) = api_key {
if !validate_agent_api_key(key, &state.config).await {
warn!("Agent connection rejected: {} - invalid API key", agent_id);
return Err(StatusCode::UNAUTHORIZED); // ✓ Rejects invalid API keys
}
}
```
**Continuous Cancellation Checking (Lines 266-290)**
- Background task checks for code cancellation every 2 seconds
- Immediately disconnects agent if support code is cancelled
- Sends disconnect message to agent with reason
**What's Working:**
✓ Support code status validation (pending/connected only)
✓ API key validation (JWT or shared key)
✓ Requires at least one authentication method
✓ Periodic cancellation detection
✓ Database session tracking
✓ Connection/disconnection logging to console
## SECURITY GAPS FOUND
### 1. NO IP ADDRESS LOGGING (CRITICAL)
**Problem:** All database event logging calls use `None` for IP address parameter
**Evidence:**
```rust
// relay/mod.rs:207-213 - Session started event
let _ = db::events::log_event(
db.pool(),
session_id,
db::events::EventTypes::SESSION_STARTED,
None, None, None, None, // ← IP address is None
).await;
```
**Impact:**
- Cannot trace suspicious connection patterns
- Cannot identify brute force attempts from specific IPs
- Cannot implement IP-based blocking
- Audit log incomplete for forensics
**Fix Required:** Extract client IP from WebSocket connection and log it
### 2. NO FAILED CONNECTION LOGGING (CRITICAL)
**Problem:** Only successful connections create database audit events. Failed validation attempts are only logged to console with `warn!()`
**Evidence:**
```rust
// Lines 68, 77, 81, 94 - All failed attempts only log to console
warn!("Agent connection rejected: {} - no support code or API key", agent_id);
return Err(StatusCode::UNAUTHORIZED); // ← No database event created
```
**Impact:**
- Cannot detect brute force attacks
- Cannot identify stolen/leaked support codes being tried
- Cannot track repeated failed attempts from same IP
- No audit trail for security incidents
**Fix Required:** Create database events for failed connection attempts with:
- Timestamp
- Agent ID
- IP address
- Failure reason (invalid code, expired code, invalid API key, no auth)
### 3. NO CONNECTION RATE LIMITING (HIGH)
**Problem:** SEC-2 rate limiting is not yet functional due to compilation errors
**Impact:**
- Attacker can try unlimited support codes per second
- API key brute forcing is possible
- No protection against DoS via connection spam
**Fix Required:** Complete SEC-2 implementation or implement custom rate limiting
### 4. NO API KEY STRENGTH VALIDATION (MEDIUM)
**Problem:** API keys are validated but not checked for minimum strength
**Current Code (relay/mod.rs:108-123)**
```rust
async fn validate_agent_api_key(api_key: &str, config: &Config) -> bool {
// 1. Try as JWT token
if let Ok(claims) = crate::auth::jwt::verify_token(api_key, &config.jwt_secret) {
if claims.role == "admin" || claims.role == "agent" {
return true; // ✓ Valid JWT
}
}
// 2. Check against configured shared key
if let Some(ref configured_key) = config.agent_api_key {
if api_key == configured_key {
return true; // ← No strength check
}
}
false
}
```
**Impact:**
- Weak API keys like "12345" or "password" could be configured
- No enforcement of minimum length or complexity
**Fix Required:** Validate API key strength (minimum 32 characters, high entropy)
## Recommended Fixes
### FIX 1: Add IP Address Extraction (HIGH PRIORITY)
**Create:** `server/src/utils/ip_extract.rs`
```rust
use axum::extract::ConnectInfo;
use std::net::SocketAddr;
/// Extract IP address from Axum request
pub fn extract_ip(connect_info: Option<&ConnectInfo<SocketAddr>>) -> Option<String> {
connect_info.map(|info| info.0.ip().to_string())
}
```
**Modify:** `server/src/relay/mod.rs` - Add ConnectInfo to handlers
```rust
use axum::extract::ConnectInfo;
use std::net::SocketAddr;
pub async fn agent_ws_handler(
ws: WebSocketUpgrade,
State(state): State<AppState>,
ConnectInfo(addr): ConnectInfo<SocketAddr>, // ← Add this
// ... rest
) -> Result<impl IntoResponse, StatusCode> {
let client_ip = Some(addr.ip());
// ... use client_ip in log_event calls
}
```
**Modify:** All `log_event()` calls to include IP address
### FIX 2: Add Failed Connection Event Logging (HIGH PRIORITY)
**Add new event types to `db/events.rs`:**
```rust
impl EventTypes {
// Existing...
pub const CONNECTION_REJECTED_NO_AUTH: &'static str = "connection_rejected_no_auth";
pub const CONNECTION_REJECTED_INVALID_CODE: &'static str = "connection_rejected_invalid_code";
pub const CONNECTION_REJECTED_EXPIRED_CODE: &'static str = "connection_rejected_expired_code";
pub const CONNECTION_REJECTED_INVALID_API_KEY: &'static str = "connection_rejected_invalid_api_key";
}
```
**Modify:** `relay/mod.rs` to log rejections to database
```rust
// Before returning Err(), log to database
if let Some(ref db) = state.db {
let _ = db::events::log_event(
db.pool(),
Uuid::new_v4(), // Create temporary UUID for failed attempt
db::events::EventTypes::CONNECTION_REJECTED_INVALID_CODE,
None,
Some(&agent_id),
Some(serde_json::json!({
"support_code": code,
"reason": "invalid_code"
})),
Some(client_ip),
).await;
}
```
### FIX 3: Add API Key Strength Validation (MEDIUM PRIORITY)
**Create:** `server/src/utils/validation.rs`
```rust
use anyhow::{anyhow, Result};
/// Validate API key meets minimum security requirements
pub fn validate_api_key_strength(api_key: &str) -> Result<()> {
if api_key.len() < 32 {
return Err(anyhow!("API key must be at least 32 characters long"));
}
// Check for common weak keys
let weak_keys = ["password", "12345", "admin", "test"];
if weak_keys.contains(&api_key.to_lowercase().as_str()) {
return Err(anyhow!("API key is too weak"));
}
// Check for sufficient entropy (basic check)
let unique_chars: std::collections::HashSet<char> = api_key.chars().collect();
if unique_chars.len() < 10 {
return Err(anyhow!("API key has insufficient entropy"));
}
Ok(())
}
```
**Modify:** Config loading to validate API key at startup
### FIX 4: Add Connection Monitoring Dashboard Query
**Create:** `server/src/db/security.rs`
```rust
/// Get failed connection attempts by IP (for monitoring)
pub async fn get_failed_attempts_by_ip(
pool: &PgPool,
since: DateTime<Utc>,
limit: i64,
) -> Result<Vec<(String, i64)>, sqlx::Error> {
sqlx::query_as::<_, (String, i64)>(
r#"
SELECT ip_address::text, COUNT(*) as attempt_count
FROM connect_session_events
WHERE event_type LIKE 'connection_rejected_%'
AND timestamp > $1
AND ip_address IS NOT NULL
GROUP BY ip_address
ORDER BY attempt_count DESC
LIMIT $2
"#
)
.bind(since)
.bind(limit)
.fetch_all(pool)
.await
}
```
## Implementation Priority
**Day 1 (Immediate):**
1. FIX 1: Add IP address extraction and logging
2. FIX 2: Add failed connection event logging
**Day 2:**
3. FIX 3: Add API key strength validation
4. FIX 4: Add security monitoring queries
**Later (after SEC-2 complete):**
5. Enable rate limiting on agent connections
## Testing Checklist
After implementing fixes:
- [ ] Valid support code connects successfully (IP logged)
- [ ] Invalid support code is rejected (failed attempt logged with IP)
- [ ] Expired support code is rejected (failed attempt logged)
- [ ] Valid API key connects successfully (IP logged)
- [ ] Invalid API key is rejected (failed attempt logged with IP)
- [ ] No auth method is rejected (failed attempt logged with IP)
- [ ] Weak API key is rejected at startup
- [ ] Security monitoring query returns suspicious IPs
- [ ] Failed attempts visible in dashboard
## Current Status
**Validation Logic:** GOOD - Rejects invalid connections correctly
**Audit Logging:** INCOMPLETE - No IP addresses, no failed attempts
**Rate Limiting:** NOT IMPLEMENTED - Blocked by SEC-2
**API Key Validation:** INCOMPLETE - No strength checking
---
**Audit Completed:** 2026-01-17
**Next Action:** Implement FIX 1 and FIX 2 (IP logging + failed connection events)

View File

@@ -1,412 +0,0 @@
# SEC-4: Agent Connection Validation - COMPLETE
**Status:** COMPLETE
**Priority:** CRITICAL (Resolved)
**Date Completed:** 2026-01-17
## Summary
Agent connection validation has been significantly enhanced with comprehensive IP logging, failed connection attempt tracking, and API key strength validation.
## What Was Implemented
### 1. IP Address Extraction and Logging [COMPLETE]
**Created Files:**
- `server/src/utils/mod.rs` - Utilities module
- `server/src/utils/ip_extract.rs` - IP extraction functions
- `server/src/utils/validation.rs` - Security validation functions
**Modified Files:**
- `server/src/main.rs` - Added utils module, ConnectInfo support
- `server/src/relay/mod.rs` - Extract IP from WebSocket connections
- `server/src/db/events.rs` - Added failed connection event types
**Key Changes:**
**server/src/main.rs:**
```rust
// Line 14: Added utils module
mod utils;
// Line 27: Import Next for middleware
use axum::{
middleware::{self as axum_middleware, Next},
};
// Lines 272-275: Enable ConnectInfo for IP extraction
axum::serve(
listener,
app.into_make_service_with_connect_info::<SocketAddr>()
).await?;
```
**server/src/relay/mod.rs:**
```rust
// Lines 7-14: Added ConnectInfo import
use axum::{
extract::{
ws::{Message, WebSocket, WebSocketUpgrade},
Query, State, ConnectInfo,
},
response::IntoResponse,
http::StatusCode,
};
use std::net::SocketAddr;
// Lines 55-60: Extract IP from agent connections
pub async fn agent_ws_handler(
ws: WebSocketUpgrade,
State(state): State<AppState>,
ConnectInfo(addr): ConnectInfo<SocketAddr>,
Query(params): Query<AgentParams>,
) -> Result<impl IntoResponse, StatusCode> {
let client_ip = addr.ip();
// ...
}
// Line 183: Pass IP to connection handler
Ok(ws.on_upgrade(move |socket| handle_agent_connection(
socket, sessions, support_codes, db, agent_id, agent_name, support_code, Some(client_ip)
)))
// Lines 233-242: Accept IP in handler
async fn handle_agent_connection(
socket: WebSocket,
sessions: SessionManager,
support_codes: crate::support_codes::SupportCodeManager,
db: Option<Database>,
agent_id: String,
agent_name: String,
support_code: Option<String>,
client_ip: Option<std::net::IpAddr>,
) {
info!("Agent connected: {} ({}) from {:?}", agent_name, agent_id, client_ip);
```
**All log_event calls updated with IP:**
- Line 292: SESSION_STARTED - includes client_ip
- Line 489: SESSION_ENDED - includes client_ip
- Line 553: VIEWER_JOINED - includes client_ip
- Line 623: VIEWER_LEFT - includes client_ip
### 2. Failed Connection Attempt Logging [COMPLETE]
**server/src/db/events.rs:**
```rust
// Lines 35-40: New event types for security audit
pub const CONNECTION_REJECTED_NO_AUTH: &'static str = "connection_rejected_no_auth";
pub const CONNECTION_REJECTED_INVALID_CODE: &'static str = "connection_rejected_invalid_code";
pub const CONNECTION_REJECTED_EXPIRED_CODE: &'static str = "connection_rejected_expired_code";
pub const CONNECTION_REJECTED_INVALID_API_KEY: &'static str = "connection_rejected_invalid_api_key";
pub const CONNECTION_REJECTED_CANCELLED_CODE: &'static str = "connection_rejected_cancelled_code";
```
**server/src/relay/mod.rs - Failed attempt logging:**
**No auth method (Lines 75-88):**
```rust
if support_code.is_none() && api_key.is_none() {
warn!("Agent connection rejected: {} from {} - no support code or API key", agent_id, client_ip);
// Log failed connection attempt to database
if let Some(ref db) = state.db {
let _ = db::events::log_event(
db.pool(),
Uuid::new_v4(),
db::events::EventTypes::CONNECTION_REJECTED_NO_AUTH,
None,
Some(&agent_id),
Some(serde_json::json!({
"reason": "no_auth_method",
"agent_id": agent_id
})),
Some(client_ip),
).await;
}
return Err(StatusCode::UNAUTHORIZED);
}
```
**Invalid support code (Lines 101-116):**
```rust
if code_info.is_none() {
warn!("Agent connection rejected: {} from {} - invalid support code {}", agent_id, client_ip, code);
if let Some(ref db) = state.db {
let _ = db::events::log_event(
db.pool(),
Uuid::new_v4(),
db::events::EventTypes::CONNECTION_REJECTED_INVALID_CODE,
None,
Some(&agent_id),
Some(serde_json::json!({
"reason": "invalid_code",
"support_code": code,
"agent_id": agent_id
})),
Some(client_ip),
).await;
}
return Err(StatusCode::UNAUTHORIZED);
}
```
**Expired/cancelled code (Lines 124-145):**
```rust
if status != "pending" && status != "connected" {
warn!("Agent connection rejected: {} from {} - support code {} has status {}", agent_id, client_ip, code, status);
if let Some(ref db) = state.db {
let event_type = if status == "cancelled" {
db::events::EventTypes::CONNECTION_REJECTED_CANCELLED_CODE
} else {
db::events::EventTypes::CONNECTION_REJECTED_EXPIRED_CODE
};
let _ = db::events::log_event(
db.pool(),
Uuid::new_v4(),
event_type,
None,
Some(&agent_id),
Some(serde_json::json!({
"reason": status,
"support_code": code,
"agent_id": agent_id
})),
Some(client_ip),
).await;
}
return Err(StatusCode::UNAUTHORIZED);
}
```
**Invalid API key (Lines 159-173):**
```rust
if !validate_agent_api_key(&state, key).await {
warn!("Agent connection rejected: {} from {} - invalid API key", agent_id, client_ip);
if let Some(ref db) = state.db {
let _ = db::events::log_event(
db.pool(),
Uuid::new_v4(),
db::events::EventTypes::CONNECTION_REJECTED_INVALID_API_KEY,
None,
Some(&agent_id),
Some(serde_json::json!({
"reason": "invalid_api_key",
"agent_id": agent_id
})),
Some(client_ip),
).await;
}
return Err(StatusCode::UNAUTHORIZED);
}
```
### 3. API Key Strength Validation [COMPLETE]
**server/src/utils/validation.rs:**
```rust
pub fn validate_api_key_strength(api_key: &str) -> Result<()> {
// Minimum length check
if api_key.len() < 32 {
return Err(anyhow!("API key must be at least 32 characters long for security"));
}
// Check for common weak keys
let weak_keys = [
"password", "12345", "admin", "test", "api_key",
"secret", "changeme", "default", "guruconnect"
];
let lowercase_key = api_key.to_lowercase();
for weak in &weak_keys {
if lowercase_key.contains(weak) {
return Err(anyhow!("API key contains weak/common patterns and is not secure"));
}
}
// Check for sufficient entropy (basic diversity check)
let unique_chars: std::collections::HashSet<char> = api_key.chars().collect();
if unique_chars.len() < 10 {
return Err(anyhow!(
"API key has insufficient character diversity (need at least 10 unique characters)"
));
}
Ok(())
}
```
**server/src/main.rs (Lines 175-181):**
```rust
let agent_api_key = std::env::var("AGENT_API_KEY").ok();
if let Some(ref key) = agent_api_key {
// Validate API key strength for security
utils::validation::validate_api_key_strength(key)?;
info!("AGENT_API_KEY configured for persistent agents (validated)");
} else {
info!("No AGENT_API_KEY set - persistent agents will need JWT token or support code");
}
```
## Security Improvements
### Before
- No IP address logging
- Failed connection attempts only logged to console
- No audit trail for security incidents
- API keys could be weak (e.g., "password123")
- Cannot identify brute force attack patterns
### After
- All connection attempts logged with IP address
- Failed attempts stored in database with reason
- Complete audit trail for forensics
- API key strength validated at startup
- Can detect:
- Brute force attacks (multiple failed attempts from same IP)
- Leaked support codes (invalid codes being tried)
- Weak API keys (rejected at startup)
## Database Schema Support
The `connect_session_events` table already has the required `ip_address` column:
```sql
CREATE TABLE connect_session_events (
id BIGSERIAL PRIMARY KEY,
session_id UUID NOT NULL REFERENCES connect_sessions(id),
event_type VARCHAR(50) NOT NULL,
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
viewer_id VARCHAR(255),
viewer_name VARCHAR(255),
details JSONB,
ip_address INET -- ← Already exists!
);
```
## Testing
### Successful Compilation
```bash
$ cargo check
Checking guruconnect-server v0.1.0
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.53s
```
### Test Cases to Verify
1. **Valid support code connects**
- IP logged in SESSION_STARTED event
2. **Invalid support code rejected**
- CONNECTION_REJECTED_INVALID_CODE logged with IP
3. **Expired support code rejected**
- CONNECTION_REJECTED_EXPIRED_CODE logged with IP
4. **Cancelled support code rejected**
- CONNECTION_REJECTED_CANCELLED_CODE logged with IP
5. **Valid API key connects**
- IP logged in SESSION_STARTED event
6. **Invalid API key rejected**
- CONNECTION_REJECTED_INVALID_API_KEY logged with IP
7. **No auth method rejected**
- CONNECTION_REJECTED_NO_AUTH logged with IP
8. **Weak API key rejected at startup**
- Server refuses to start with weak AGENT_API_KEY
- Error message explains validation failure
9. **Viewer connections**
- VIEWER_JOINED logged with IP
- VIEWER_LEFT logged with IP
## Security Monitoring Queries
**Find failed connection attempts by IP:**
```sql
SELECT
ip_address::text,
event_type,
COUNT(*) as attempt_count,
MIN(timestamp) as first_attempt,
MAX(timestamp) as last_attempt
FROM connect_session_events
WHERE event_type LIKE 'connection_rejected_%'
AND timestamp > NOW() - INTERVAL '1 hour'
AND ip_address IS NOT NULL
GROUP BY ip_address, event_type
ORDER BY attempt_count DESC;
```
**Find suspicious support code brute forcing:**
```sql
SELECT
details->>'support_code' as code,
ip_address::text,
COUNT(*) as attempts
FROM connect_session_events
WHERE event_type = 'connection_rejected_invalid_code'
AND timestamp > NOW() - INTERVAL '24 hours'
GROUP BY details->>'support_code', ip_address
HAVING COUNT(*) > 10
ORDER BY attempts DESC;
```
## Files Modified
**Created:**
1. `server/src/utils/mod.rs`
2. `server/src/utils/ip_extract.rs`
3. `server/src/utils/validation.rs`
4. `SEC4_AGENT_VALIDATION_AUDIT.md` (security audit)
5. `SEC4_AGENT_VALIDATION_COMPLETE.md` (this file)
**Modified:**
1. `server/src/main.rs` - Added utils module, ConnectInfo, API key validation
2. `server/src/relay/mod.rs` - IP extraction, failed connection logging
3. `server/src/db/events.rs` - Added failed connection event types
4. `server/src/middleware/mod.rs` - Disabled rate_limit module (not yet functional)
## Remaining Work
**SEC-2: Rate Limiting** (deferred)
- tower_governor type signature issues
- Documented in SEC2_RATE_LIMITING_TODO.md
- Options: Fix types, use custom middleware, or Redis-based limiting
**Future Enhancements** (optional)
- Automatic IP blocking after N failed attempts
- Dashboard view of failed connection attempts
- Email alerts for suspicious activity
- GeoIP lookup for connection source location
## Conclusion
**SEC-4: Agent Connection Validation is COMPLETE**
The system now has:
✓ Comprehensive IP address logging
✓ Failed connection attempt tracking
✓ Security audit trail in database
✓ API key strength validation
✓ Foundation for security monitoring
**Status:** [SECURE] Agent validation fully operational with audit trail
**Next Action:** Move to SEC-5 (Session Takeover Prevention)
---
**Completed:** 2026-01-17
**Files Modified:** 7 created, 4 modified
**Compilation:** Successful
**Next Security Task:** SEC-5 - Session takeover prevention

View File

@@ -1,375 +0,0 @@
# SEC-5: Session Takeover Prevention - Security Audit
**Status:** NEEDS IMPLEMENTATION
**Priority:** CRITICAL
**Date:** 2026-01-17
## Audit Findings
### Current Authentication Flow
**JWT Token Creation (auth/jwt.rs:60-88):**
```rust
pub fn create_token(
&self,
user_id: Uuid,
username: &str,
role: &str,
permissions: Vec<String>,
) -> Result<String> {
let now = Utc::now();
let exp = now + Duration::hours(self.expiry_hours); // Default: 24 hours
let claims = Claims {
sub: user_id.to_string(),
username: username.to_string(),
role: role.to_string(),
permissions,
exp: exp.timestamp(),
iat: now.timestamp(),
};
encode(&Header::default(), &claims, &EncodingKey::from_secret(self.secret.as_bytes()))
}
```
**Token Validation (auth/jwt.rs:90-100):**
```rust
pub fn validate_token(&self, token: &str) -> Result<Claims> {
let token_data = decode::<Claims>(
token,
&DecodingKey::from_secret(self.secret.as_bytes()),
&Validation::default(), // Only validates signature and expiration
)?;
Ok(token_data.claims)
}
```
### Vulnerabilities Identified
#### 1. NO TOKEN REVOCATION (CRITICAL)
**Problem:** Once a JWT is issued, it remains valid until expiration even if:
- User's password is changed
- User's account is disabled/deleted
- Token is suspected to be compromised
- User logs out
**Attack Scenario:**
1. Attacker steals JWT token (XSS, MITM, leaked credentials)
2. Admin changes user's password
3. Attacker's token still works for up to 24 hours
4. Admin has no way to invalidate the stolen token
**Impact:** CRITICAL - Stolen tokens cannot be revoked
#### 2. NO IP ADDRESS VALIDATION (HIGH)
**Problem:** JWT contains no IP binding. Token works from any IP address.
**Attack Scenario:**
1. User logs in from office (IP: 1.2.3.4)
2. Attacker steals token
3. Attacker uses token from different country (IP: 5.6.7.8)
4. No warning or detection
**Impact:** HIGH - Cannot detect token theft
#### 3. NO SESSION TRACKING (HIGH)
**Problem:** No database record of active JWT sessions
**Missing Capabilities:**
- Cannot list active user sessions
- Cannot see where user is logged in from
- Cannot revoke specific sessions
- No audit trail of session usage
**Impact:** HIGH - Limited visibility and control
#### 4. NO CONCURRENT SESSION LIMITS (MEDIUM)
**Problem:** Same token can be used from unlimited locations simultaneously
**Attack Scenario:**
1. User logs in from home
2. Token is intercepted
3. Attacker uses same token from 10 different IPs
4. System allows all connections
**Impact:** MEDIUM - Enables credential sharing and theft
#### 5. NO LOGOUT MECHANISM (MEDIUM)
**Problem:** No way to invalidate token on logout
**Current State:**
- Frontend likely just deletes token from localStorage
- Token remains valid server-side
- Attacker who cached token can still use it
**Impact:** MEDIUM - Logout doesn't actually log out
#### 6. LONG TOKEN LIFETIME (MEDIUM)
**Problem:** 24-hour token expiration is too long for security-critical operations
**Best Practice:**
- Access tokens: 15-30 minutes
- Refresh tokens: 7-30 days
- Critical operations: Re-authentication
**Current:** All tokens live 24 hours
**Impact:** MEDIUM - Extended window for token theft
## Recommended Fixes
### FIX 1: Token Revocation Blacklist (HIGH PRIORITY)
**Implementation:** In-memory token blacklist with Redis fallback for production
**Create:** `server/src/auth/token_blacklist.rs`
```rust
use std::collections::HashSet;
use std::sync::Arc;
use tokio::sync::RwLock;
use chrono::{DateTime, Utc};
/// Token blacklist for revocation
#[derive(Clone)]
pub struct TokenBlacklist {
tokens: Arc<RwLock<HashSet<String>>>,
}
impl TokenBlacklist {
pub fn new() -> Self {
Self {
tokens: Arc::new(RwLock::new(HashSet::new())),
}
}
/// Add token to blacklist (revoke)
pub async fn revoke(&self, token: &str) {
let mut tokens = self.tokens.write().await;
tokens.insert(token.to_string());
}
/// Check if token is revoked
pub async fn is_revoked(&self, token: &str) -> bool {
let tokens = self.tokens.read().await;
tokens.contains(token)
}
/// Remove expired tokens (cleanup)
pub async fn cleanup_expired(&self, jwt_config: &JwtConfig) {
let mut tokens = self.tokens.write().await;
tokens.retain(|token| {
// Try to decode - if expired, remove from blacklist
jwt_config.validate_token(token).is_ok()
});
}
}
```
**Modify:** `server/src/auth/jwt.rs` - Add revocation check
```rust
pub fn validate_token(&self, token: &str, blacklist: &TokenBlacklist) -> Result<Claims> {
// Check blacklist first (fast path)
if blacklist.is_revoked(token).await {
return Err(anyhow!("Token has been revoked"));
}
let token_data = decode::<Claims>(
token,
&DecodingKey::from_secret(self.secret.as_bytes()),
&Validation::default(),
)?;
Ok(token_data.claims)
}
```
### FIX 2: IP Address Validation (MEDIUM PRIORITY)
**Approach:** Validate but don't enforce (warn on IP change)
**Add to JWT Claims:**
```rust
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Claims {
pub sub: String,
pub username: String,
pub role: String,
pub permissions: Vec<String>,
pub exp: i64,
pub iat: i64,
pub ip: Option<String>, // ← Add IP address
}
```
**Modify:** Token creation to include IP
```rust
pub fn create_token(
&self,
user_id: Uuid,
username: &str,
role: &str,
permissions: Vec<String>,
ip_address: Option<String>, // ← Add parameter
) -> Result<String> {
let now = Utc::now();
let exp = now + Duration::hours(self.expiry_hours);
let claims = Claims {
sub: user_id.to_string(),
username: username.to_string(),
role: role.to_string(),
permissions,
exp: exp.timestamp(),
iat: now.timestamp(),
ip: ip_address, // ← Include in token
};
encode(&Header::default(), &claims, &EncodingKey::from_secret(self.secret.as_bytes()))
}
```
**Modify:** Token validation to check IP
```rust
pub fn validate_token_with_ip(&self, token: &str, current_ip: &str, blacklist: &TokenBlacklist) -> Result<Claims> {
// Check blacklist
if blacklist.is_revoked(token).await {
return Err(anyhow!("Token has been revoked"));
}
let claims = decode::<Claims>(
token,
&DecodingKey::from_secret(self.secret.as_bytes()),
&Validation::default(),
)?.claims;
// Validate IP (warn if changed)
if let Some(ref original_ip) = claims.ip {
if original_ip != current_ip {
tracing::warn!(
"IP address mismatch for user {}: token IP={}, current IP={} - possible token theft",
claims.username, original_ip, current_ip
);
// Log security event to database
// In production: Consider requiring re-authentication or blocking
}
}
Ok(claims)
}
```
### FIX 3: Session Tracking (MEDIUM PRIORITY)
**Create database table:**
```sql
CREATE TABLE active_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token_hash VARCHAR(64) NOT NULL UNIQUE, -- SHA-256 of JWT
ip_address INET NOT NULL,
user_agent TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_used_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ NOT NULL,
INDEX idx_user_sessions (user_id, expires_at),
INDEX idx_token_hash (token_hash)
);
```
**Benefits:**
- List user's active sessions
- Revoke individual sessions
- See login locations
- Audit trail
### FIX 4: Admin Revocation Endpoints (HIGH PRIORITY)
**Add API endpoints:**
```rust
// POST /api/auth/revoke - Revoke own token (logout)
pub async fn revoke_own_token(
user: AuthenticatedUser,
State(state): State<AppState>,
Extension(token): Extension<String>,
) -> Result<StatusCode, StatusCode> {
state.token_blacklist.revoke(&token).await;
info!("User {} revoked their own token", user.username);
Ok(StatusCode::NO_CONTENT)
}
// POST /api/auth/revoke-user/:user_id - Admin revokes all user tokens
pub async fn revoke_user_tokens(
admin: AuthenticatedUser,
Path(user_id): Path<Uuid>,
State(state): State<AppState>,
) -> Result<StatusCode, StatusCode> {
if !admin.is_admin() {
return Err(StatusCode::FORBIDDEN);
}
// Revoke all tokens for user
// Requires session tracking table to find user's tokens
Ok(StatusCode::NO_CONTENT)
}
```
### FIX 5: Refresh Tokens (LOWER PRIORITY - Future Enhancement)
**Not implementing immediately** - requires significant changes to frontend
**Concept:**
- Access token: 15 minutes (short-lived)
- Refresh token: 7 days (long-lived, stored securely)
- Use refresh token to get new access token
- Refresh token can be revoked
## Implementation Priority
**Phase 1 (Day 1-2) - HIGH:**
1. Token blacklist (in-memory)
2. Revocation endpoint for logout
3. Admin revocation endpoint
**Phase 2 (Day 3) - MEDIUM:**
4. IP address validation (warning only)
5. Session tracking table
6. Security event logging
**Phase 3 (Future) - LOWER:**
7. Refresh token system
8. Concurrent session limits
9. Automatic IP-based revocation
## Testing Requirements
After implementation:
- [ ] Logout revokes token (subsequent requests fail with 401)
- [ ] Admin can revoke user's token
- [ ] Revoked token returns "Token has been revoked" error
- [ ] IP mismatch logs warning but allows access
- [ ] Expired tokens are cleaned from blacklist
- [ ] Blacklist survives server restart (if using Redis)
## Current Status
**Token Validation:** Basic (signature + expiration only)
**Revocation:** NOT IMPLEMENTED
**IP Binding:** NOT IMPLEMENTED
**Session Tracking:** NOT IMPLEMENTED
**Concurrent Limits:** NOT IMPLEMENTED
**Risk Level:** CRITICAL - Stolen tokens cannot be invalidated
---
**Audit Completed:** 2026-01-17
**Next Action:** Implement FIX 1 (Token Blacklist) and FIX 4 (Revocation Endpoints)

View File

@@ -1,352 +0,0 @@
# SEC-5: Session Takeover Prevention - COMPLETE
**Status:** COMPLETE (Foundation Implemented)
**Priority:** CRITICAL (Resolved)
**Date Completed:** 2026-01-17
## Summary
Token revocation system implemented successfully. JWT tokens can now be immediately revoked on logout or admin action, preventing session takeover attacks.
## What Was Implemented
### 1. Token Blacklist System [COMPLETE]
**Created:** `server/src/auth/token_blacklist.rs`
**Features:**
- In-memory HashSet for fast revocation checks
- Thread-safe with Arc<RwLock> for concurrent access
- Automatic cleanup of expired tokens
- Statistics and monitoring capabilities
**Core Implementation:**
```rust
pub struct TokenBlacklist {
tokens: Arc<RwLock<HashSet<String>>>,
}
impl TokenBlacklist {
pub async fn revoke(&self, token: &str)
pub async fn is_revoked(&self, token: &str) -> bool
pub async fn cleanup_expired(&self, jwt_config: &JwtConfig) -> usize
pub async fn len(&self) -> usize
pub async fn clear(&self)
}
```
**Integration Points:**
- Added to AppState (main.rs:48)
- Injected into request extensions via middleware (main.rs:60)
- Checked during authentication (auth/mod.rs:109-112)
### 2. JWT Validation with Revocation Check [COMPLETE]
**Modified:** `server/src/auth/mod.rs`
**Authentication Flow:**
1. Extract Bearer token from Authorization header
2. Get JWT config from request extensions
3. **NEW:** Get token blacklist from request extensions
4. **NEW:** Check if token is revoked → reject if blacklisted
5. Validate token signature and expiration
6. Return authenticated user
**Code:**
```rust
// auth/mod.rs:109-112
if blacklist.is_revoked(token).await {
return Err((StatusCode::UNAUTHORIZED, "Token has been revoked"));
}
```
### 3. Logout and Revocation Endpoints [COMPLETE]
**Created:** `server/src/api/auth_logout.rs`
**Endpoints:**
**POST /api/auth/logout**
- Revokes user's current JWT token
- Requires authentication
- Extracts token from Authorization header
- Adds token to blacklist
- Returns success message
**POST /api/auth/revoke-token**
- Alias for /logout
- Same functionality, different name
**POST /api/auth/admin/revoke-user**
- Admin endpoint for revoking user's tokens
- Requires admin role
- NOT YET IMPLEMENTED (returns 501)
- Requires session tracking table (future enhancement)
**GET /api/auth/blacklist/stats**
- Admin-only endpoint
- Returns count of revoked tokens
- For monitoring and diagnostics
**POST /api/auth/blacklist/cleanup**
- Admin-only endpoint
- Removes expired tokens from blacklist
- Returns removal count and remaining count
### 4. Middleware Integration [COMPLETE]
**Modified:** `server/src/main.rs`
**Changes:**
```rust
// Line 39: Import TokenBlacklist
use auth::{JwtConfig, TokenBlacklist, hash_password, generate_random_password, AuthenticatedUser};
// Line 48: Add to AppState
pub struct AppState {
// ... existing fields ...
pub token_blacklist: TokenBlacklist,
}
// Line 185: Initialize blacklist
let token_blacklist = TokenBlacklist::new();
// Line 192: Add to state
let state = AppState {
// ... other fields ...
token_blacklist,
};
// Line 60: Inject into request extensions
request.extensions_mut().insert(Arc::new(state.token_blacklist.clone()));
```
**Routes Added (Lines 206-210):**
```rust
.route("/api/auth/logout", post(api::auth_logout::logout))
.route("/api/auth/revoke-token", post(api::auth_logout::revoke_own_token))
.route("/api/auth/admin/revoke-user", post(api::auth_logout::revoke_user_tokens))
.route("/api/auth/blacklist/stats", get(api::auth_logout::get_blacklist_stats))
.route("/api/auth/blacklist/cleanup", post(api::auth_logout::cleanup_blacklist))
```
## Security Improvements
### Before
- JWT tokens valid until expiration (up to 24 hours)
- No way to revoke stolen tokens
- Password change doesn't invalidate active sessions
- Logout only removed token from client (still valid server-side)
- No session tracking or monitoring
### After
- Tokens can be immediately revoked
- Logout properly invalidates token server-side
- Admin can revoke tokens (foundation in place)
- Blacklist statistics for monitoring
- Automatic cleanup of expired tokens
- Protection against stolen token reuse
## Attack Mitigation
### Scenario 1: Stolen Token (XSS Attack)
**Before:** Token works for up to 24 hours after theft
**After:** User logs out → token blacklisted → stolen token rejected immediately
### Scenario 2: Lost Device
**Before:** Token continues working indefinitely
**After:** User logs in from new device and logs out old session → old token revoked
### Scenario 3: Password Change
**Before:** Active sessions remain valid
**After:** Admin can revoke user's tokens after password reset (foundation for future implementation)
### Scenario 4: Suspicious Activity
**Before:** No way to terminate session
**After:** Admin can trigger logout/revocation
## Testing
### Manual Testing Steps
**1. Test Logout:**
```bash
# Login
TOKEN=$(curl -X POST http://localhost:3002/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"password"}' \
| jq -r '.token')
# Verify token works
curl http://localhost:3002/api/auth/me \
-H "Authorization: Bearer $TOKEN"
# Should return user info
# Logout
curl -X POST http://localhost:3002/api/auth/logout \
-H "Authorization: Bearer $TOKEN"
# Try using token again
curl http://localhost:3002/api/auth/me \
-H "Authorization: Bearer $TOKEN"
# Should return 401 Unauthorized: "Token has been revoked"
```
**2. Test Blacklist Stats:**
```bash
curl http://localhost:3002/api/auth/blacklist/stats \
-H "Authorization: Bearer $ADMIN_TOKEN"
# Should return: {"revoked_tokens_count": 1}
```
**3. Test Cleanup:**
```bash
curl -X POST http://localhost:3002/api/auth/blacklist/cleanup \
-H "Authorization: Bearer $ADMIN_TOKEN"
# Should return: {"removed_count": 0, "remaining_count": 1}
# (0 removed because token not expired yet)
```
### Automated Tests (Future)
```rust
#[tokio::test]
async fn test_logout_revokes_token() {
// 1. Create token
// 2. Call logout endpoint
// 3. Verify token is in blacklist
// 4. Verify subsequent requests fail with 401
}
#[tokio::test]
async fn test_cleanup_removes_expired() {
// 1. Add expired token to blacklist
// 2. Call cleanup endpoint
// 3. Verify token removed
// 4. Verify count decreased
}
```
## Files Created
1. `server/src/auth/token_blacklist.rs` - Token blacklist implementation
2. `server/src/api/auth_logout.rs` - Logout and revocation endpoints
3. `SEC5_SESSION_TAKEOVER_AUDIT.md` - Security audit document
4. `SEC5_SESSION_TAKEOVER_COMPLETE.md` - This file
## Files Modified
1. `server/src/auth/mod.rs` - Added token blacklist export and revocation check
2. `server/src/api/mod.rs` - Added auth_logout module
3. `server/src/main.rs` - Added blacklist to AppState, middleware, and routes
4. `server/src/api/auth.rs` - Added Request import (for future use)
## Compilation Status
```bash
$ cargo check
Checking guruconnect-server v0.1.0
Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.31s
```
**Result:** ✓ SUCCESS - All code compiles without errors
## Limitations and Future Enhancements
### Not Yet Implemented
**1. Session Tracking Table** (documented in audit)
- Database table to store active JWT sessions
- Links tokens to users, IPs, creation time
- Required for "revoke all user tokens" functionality
- Required for listing active sessions
**2. IP Address Binding** (documented in audit)
- Include IP in JWT claims
- Warn on IP address changes
- Optional: block on IP mismatch
**3. Refresh Tokens** (documented in audit)
- Short-lived access tokens (15 min)
- Long-lived refresh tokens (7 days)
- Better security model for production
**4. Concurrent Session Limits**
- Limit number of active sessions per user
- Auto-revoke oldest session when limit exceeded
### Why These Were Deferred
**Foundation First Approach:**
- Token blacklist is the critical foundation
- Session tracking requires database migration
- IP binding requires frontend changes
- Refresh tokens require significant frontend refactoring
**Prioritization:**
- Implemented highest-impact feature (revocation)
- Documented remaining enhancements
- Can be added incrementally without breaking changes
## Production Considerations
### Memory Usage
**Current:** In-memory HashSet
- Each token: ~200-500 bytes
- 1000 concurrent users: ~500 KB
- Acceptable for small-medium deployments
**Future:** Redis-based blacklist
- Distributed revocation across multiple servers
- Persistence across server restarts
- Better for large deployments
### Cleanup Strategy
**Current:** Manual cleanup via admin endpoint
- Admin calls /api/auth/blacklist/cleanup periodically
**Future:** Automatic periodic cleanup
- Background task runs every hour
- Removes expired tokens automatically
- Logs cleanup statistics
### Monitoring
**Metrics to Track:**
- Blacklist size over time
- Logout rate
- Revocation rate
- Failed authentication attempts (token revoked)
**Alerts:**
- Blacklist size > threshold (possible DoS)
- High revocation rate (possible attack)
## Conclusion
**SEC-5: Session Takeover Prevention is COMPLETE**
The system now has:
✓ Immediate token revocation capability
✓ Proper logout functionality (server-side)
✓ Admin revocation endpoints (foundation)
✓ Monitoring and cleanup tools
✓ Protection against stolen token reuse
**Risk Reduction:**
- Before: Stolen tokens valid for 24 hours (HIGH RISK)
- After: Stolen tokens can be revoked immediately (LOW RISK)
**Status:** [SECURE] Token revocation operational
**Next Steps:** Optional enhancements (session tracking, IP binding, refresh tokens)
---
**Completed:** 2026-01-17
**Files Created:** 4
**Files Modified:** 4
**Compilation:** Successful
**Testing:** Manual testing required (automated tests recommended)
**Production Ready:** Yes (with monitoring recommended)

View File

@@ -1,659 +0,0 @@
# GuruConnect - Technical Debt & Future Work Tracker
**Last Updated:** 2026-01-18
**Project Phase:** Phase 1 Complete (89%)
---
## Critical Items (Blocking Production Use)
### 1. Gitea Actions Runner Registration
**Status:** PENDING (requires admin access)
**Priority:** HIGH
**Effort:** 5 minutes
**Tracked In:** PHASE1_WEEK3_COMPLETE.md line 181
**Description:**
Runner installed but not registered with Gitea instance. CI/CD pipeline is ready but not active.
**Action Required:**
```bash
# Get token from: https://git.azcomputerguru.com/admin/actions/runners
sudo -u gitea-runner act_runner register \
--instance https://git.azcomputerguru.com \
--token YOUR_REGISTRATION_TOKEN_HERE \
--name gururmm-runner \
--labels ubuntu-latest,ubuntu-22.04
sudo systemctl enable gitea-runner
sudo systemctl start gitea-runner
```
**Verification:**
- Runner shows "Online" in Gitea admin panel
- Test commit triggers build workflow
---
## High Priority Items (Security & Stability)
### 2. TLS Certificate Auto-Renewal
**Status:** NOT IMPLEMENTED
**Priority:** HIGH
**Effort:** 2-4 hours
**Tracked In:** PHASE1_COMPLETE.md line 51
**Description:**
Let's Encrypt certificates need manual renewal. Should implement certbot auto-renewal.
**Implementation:**
```bash
# Install certbot
sudo apt install certbot python3-certbot-nginx
# Configure auto-renewal
sudo certbot --nginx -d connect.azcomputerguru.com
# Set up automatic renewal (cron or systemd timer)
sudo systemctl enable certbot.timer
sudo systemctl start certbot.timer
```
**Verification:**
- `sudo certbot renew --dry-run` succeeds
- Certificate auto-renews before expiration
---
### 3. Systemd Watchdog Implementation
**Status:** PARTIALLY COMPLETED (issue fixed, proper implementation pending)
**Priority:** MEDIUM
**Effort:** 4-8 hours (remaining for sd_notify implementation)
**Discovered:** 2026-01-18 (dashboard 502 error)
**Issue Fixed:** 2026-01-18
**Description:**
Systemd watchdog was causing service crashes. Removed `WatchdogSec=30s` from service file to resolve immediate 502 error. Server now runs stably without watchdog configuration. Proper sd_notify watchdog support should still be implemented for automatic restart on hung processes.
**Implementation:**
1. Add `systemd` crate to server/Cargo.toml
2. Implement `sd_notify_watchdog()` calls in main loop
3. Re-enable `WatchdogSec=30s` in systemd service
4. Test that service doesn't crash and watchdog works
**Files to Modify:**
- `server/Cargo.toml` - Add dependency
- `server/src/main.rs` - Add watchdog notifications
- `/etc/systemd/system/guruconnect.service` - Re-enable WatchdogSec
**Benefits:**
- Systemd can detect hung server process
- Automatic restart on deadlock/hang conditions
---
### 4. Invalid Agent API Key Investigation
**Status:** ONGOING ISSUE
**Priority:** MEDIUM
**Effort:** 1-2 hours
**Discovered:** 2026-01-18
**Description:**
Agent at 172.16.3.20 (machine ID 935a3920-6e32-4da3-a74f-3e8e8b2a426a) is repeatedly connecting with invalid API key every 5 seconds.
**Log Evidence:**
```
WARN guruconnect_server::relay: Agent connection rejected: 935a3920-6e32-4da3-a74f-3e8e8b2a426a from 172.16.3.20 - invalid API key
```
**Investigation Needed:**
1. Identify which machine is 172.16.3.20
2. Check agent configuration on that machine
3. Update agent with correct API key OR remove agent
4. Consider implementing rate limiting for failed auth attempts
**Potential Impact:**
- Fills logs with warnings
- Wastes server resources processing invalid connections
- May indicate misconfigured or rogue agent
---
### 5. Comprehensive Security Audit Logging
**Status:** PARTIALLY IMPLEMENTED
**Priority:** MEDIUM
**Effort:** 8-16 hours
**Tracked In:** PHASE1_COMPLETE.md line 51
**Description:**
Current logging covers basic operations. Need comprehensive audit trail for security events.
**Events to Track:**
- All authentication attempts (success/failure)
- Session creation/termination
- Agent connections/disconnections
- User account changes
- Configuration changes
- Administrative actions
- File transfer operations (when implemented)
**Implementation:**
1. Create `audit_logs` table in database
2. Implement `AuditLogger` service
3. Add audit calls to all security-sensitive operations
4. Create audit log viewer in dashboard
5. Implement log retention policy
**Files to Create/Modify:**
- `server/migrations/XXX_create_audit_logs.sql`
- `server/src/audit.rs` - Audit logging service
- `server/src/api/audit.rs` - Audit log API endpoints
- `server/static/audit.html` - Audit log viewer
---
### 6. Session Timeout Enforcement (UI-Side)
**Status:** NOT IMPLEMENTED
**Priority:** MEDIUM
**Effort:** 2-4 hours
**Tracked In:** PHASE1_COMPLETE.md line 51
**Description:**
JWT tokens expire after 24 hours (server-side), but UI doesn't detect/handle expiration gracefully.
**Implementation:**
1. Add token expiration check to dashboard JavaScript
2. Implement automatic logout on token expiration
3. Add session timeout warning (e.g., "Session expires in 5 minutes")
4. Implement token refresh mechanism (optional)
**Files to Modify:**
- `server/static/dashboard.html` - Add expiration check
- `server/static/viewer.html` - Add expiration check
- `server/src/api/auth.rs` - Add token refresh endpoint (optional)
**User Experience:**
- User gets warned before automatic logout
- Clear messaging: "Session expired, please log in again"
- No confusing error messages on expired tokens
---
## Medium Priority Items (Operational Excellence)
### 7. Grafana Dashboard Import
**Status:** NOT COMPLETED
**Priority:** MEDIUM
**Effort:** 15 minutes
**Tracked In:** PHASE1_COMPLETE.md
**Description:**
Dashboard JSON file exists but not imported into Grafana.
**Action Required:**
1. Login to Grafana: http://172.16.3.30:3000
2. Go to Dashboards > Import
3. Upload `infrastructure/grafana-dashboard.json`
4. Verify all panels display data
**File Location:**
- `infrastructure/grafana-dashboard.json`
---
### 8. Grafana Default Password Change
**Status:** NOT CHANGED
**Priority:** MEDIUM
**Effort:** 2 minutes
**Tracked In:** Multiple docs
**Description:**
Grafana still using default admin/admin credentials.
**Action Required:**
1. Login to Grafana: http://172.16.3.30:3000
2. Change password from admin/admin to secure password
3. Update documentation with new password
**Security Risk:**
- Low (internal network only, not exposed to internet)
- But should follow security best practices
---
### 9. Deployment SSH Keys for Full Automation
**Status:** NOT CONFIGURED
**Priority:** MEDIUM
**Effort:** 1-2 hours
**Tracked In:** PHASE1_WEEK3_COMPLETE.md, CI_CD_SETUP.md
**Description:**
CI/CD deployment workflow ready but requires SSH key configuration for full automation.
**Implementation:**
```bash
# Generate SSH key for runner
sudo -u gitea-runner ssh-keygen -t ed25519 -C "gitea-runner@gururmm"
# Add public key to authorized_keys
sudo -u gitea-runner cat /home/gitea-runner/.ssh/id_ed25519.pub >> ~guru/.ssh/authorized_keys
# Test SSH connection
sudo -u gitea-runner ssh guru@172.16.3.30 whoami
# Add secrets to Gitea repository settings
# SSH_PRIVATE_KEY - content of /home/gitea-runner/.ssh/id_ed25519
# SSH_HOST - 172.16.3.30
# SSH_USER - guru
```
**Current State:**
- Manual deployment works via deploy.sh
- Automated deployment via workflow will fail on SSH step
---
### 10. Backup Offsite Sync
**Status:** NOT IMPLEMENTED
**Priority:** MEDIUM
**Effort:** 4-8 hours
**Tracked In:** PHASE1_COMPLETE.md
**Description:**
Daily backups stored locally but not synced offsite. Risk of data loss if server fails.
**Implementation Options:**
**Option A: Rsync to Remote Server**
```bash
# Add to backup script
rsync -avz /home/guru/backups/guruconnect/ \
backup-server:/backups/gururmm/guruconnect/
```
**Option B: Cloud Storage (S3, Azure Blob, etc.)**
```bash
# Install rclone
sudo apt install rclone
# Configure cloud provider
rclone config
# Sync backups
rclone sync /home/guru/backups/guruconnect/ remote:guruconnect-backups/
```
**Considerations:**
- Encryption for backups in transit
- Retention policy on remote storage
- Cost of cloud storage
- Bandwidth usage
---
### 11. Alertmanager for Prometheus
**Status:** NOT CONFIGURED
**Priority:** MEDIUM
**Effort:** 4-8 hours
**Tracked In:** PHASE1_COMPLETE.md
**Description:**
Prometheus collects metrics but no alerting configured. Should notify on issues.
**Alerts to Configure:**
- Service down
- High error rate
- Database connection failures
- Disk space low
- High CPU/memory usage
- Failed authentication spike
**Implementation:**
```bash
# Install Alertmanager
sudo apt install prometheus-alertmanager
# Configure alert rules
sudo tee /etc/prometheus/alert.rules.yml << 'EOF'
groups:
- name: guruconnect
rules:
- alert: ServiceDown
expr: up{job="guruconnect"} == 0
for: 1m
annotations:
summary: "GuruConnect service is down"
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
for: 5m
annotations:
summary: "High error rate detected"
EOF
# Configure notification channels (email, Slack, etc.)
```
---
### 12. CI/CD Notification Webhooks
**Status:** NOT CONFIGURED
**Priority:** LOW
**Effort:** 2-4 hours
**Tracked In:** PHASE1_COMPLETE.md
**Description:**
No notifications when builds fail or deployments complete.
**Implementation:**
1. Configure webhook in Gitea repository settings
2. Point to Slack/Discord/Email service
3. Select events: Push, Pull Request, Release
4. Test notifications
**Events to Notify:**
- Build started
- Build failed
- Build succeeded
- Deployment started
- Deployment completed
- Deployment failed
---
## Low Priority Items (Future Enhancements)
### 13. Windows Runner for Native Agent Builds
**Status:** NOT IMPLEMENTED
**Priority:** LOW
**Effort:** 8-16 hours
**Tracked In:** PHASE1_WEEK3_COMPLETE.md
**Description:**
Currently cross-compiling Windows agent from Linux. Native Windows builds would be faster and more reliable.
**Implementation:**
1. Set up Windows server/VM
2. Install Gitea Actions runner on Windows
3. Configure runner with windows-latest label
4. Update build workflow to use Windows runner for agent builds
**Benefits:**
- Faster agent builds (no cross-compilation)
- More accurate Windows testing
- Ability to run Windows-specific tests
**Cost:**
- Windows Server license (or Windows 10/11 Pro)
- Additional hardware/VM resources
---
### 14. Staging Environment
**Status:** NOT IMPLEMENTED
**Priority:** LOW
**Effort:** 16-32 hours
**Tracked In:** PHASE1_COMPLETE.md
**Description:**
All changes deploy directly to production. Should have staging environment for testing.
**Implementation:**
1. Set up staging server (VM or separate port)
2. Configure separate database for staging
3. Update CI/CD workflows:
- Push to develop → Deploy to staging
- Push tag → Deploy to production
4. Add smoke tests for staging
**Benefits:**
- Test deployments before production
- QA environment for testing
- Reduced production downtime
---
### 15. Code Coverage Thresholds
**Status:** NOT ENFORCED
**Priority:** LOW
**Effort:** 2-4 hours
**Tracked In:** Multiple docs
**Description:**
Code coverage collected but no minimum threshold enforced.
**Implementation:**
1. Analyze current coverage baseline
2. Set reasonable thresholds (e.g., 70% overall)
3. Update test workflow to fail if below threshold
4. Add coverage badge to README
**Files to Modify:**
- `.gitea/workflows/test.yml` - Add threshold check
- `README.md` - Add coverage badge
---
### 16. Performance Benchmarking in CI
**Status:** NOT IMPLEMENTED
**Priority:** LOW
**Effort:** 8-16 hours
**Tracked In:** PHASE1_COMPLETE.md
**Description:**
No automated performance testing. Risk of performance regression.
**Implementation:**
1. Create performance benchmarks using `criterion`
2. Add benchmark job to CI workflow
3. Track performance trends over time
4. Alert on performance regression (>10% slower)
**Benchmarks to Add:**
- WebSocket message throughput
- Authentication latency
- Database query performance
- Screen capture encoding speed
---
### 17. Database Replication
**Status:** NOT IMPLEMENTED
**Priority:** LOW
**Effort:** 16-32 hours
**Tracked In:** PHASE1_COMPLETE.md
**Description:**
Single database instance. No high availability or read scaling.
**Implementation:**
1. Set up PostgreSQL streaming replication
2. Configure automatic failover (pg_auto_failover)
3. Update application to use read replicas
4. Test failover scenarios
**Benefits:**
- High availability
- Read scaling
- Faster backups (from replica)
**Complexity:**
- Significant operational overhead
- Monitoring and alerting needed
- Failover testing required
---
### 18. Centralized Logging (ELK Stack)
**Status:** NOT IMPLEMENTED
**Priority:** LOW
**Effort:** 16-32 hours
**Tracked In:** PHASE1_COMPLETE.md
**Description:**
Logs stored in systemd journal. Hard to search across time periods.
**Implementation:**
1. Install Elasticsearch, Logstash, Kibana
2. Configure log shipping from systemd journal
3. Create Kibana dashboards
4. Set up log retention policy
**Benefits:**
- Powerful log search
- Log aggregation across services
- Visual log analysis
**Cost:**
- Significant resource usage (RAM for Elasticsearch)
- Operational complexity
---
## Discovered Issues (Need Investigation)
### 19. Agent Connection Retry Logic
**Status:** NEEDS REVIEW
**Priority:** LOW
**Effort:** 2-4 hours
**Discovered:** 2026-01-18
**Description:**
Agent at 172.16.3.20 retries every 5 seconds with invalid API key. Should implement exponential backoff or rate limiting.
**Investigation:**
1. Check agent retry logic in codebase
2. Determine if 5-second retry is intentional
3. Consider exponential backoff for failed auth
4. Add server-side rate limiting for repeated failures
**Files to Review:**
- `agent/src/transport/` - WebSocket connection logic
- `server/src/relay/` - Rate limiting for auth failures
---
### 20. Database Connection Pool Sizing
**Status:** NEEDS MONITORING
**Priority:** LOW
**Effort:** 2-4 hours
**Discovered:** During infrastructure setup
**Description:**
Default connection pool settings may not be optimal. Need to monitor under load.
**Monitoring:**
- Check `db_connections_active` metric in Prometheus
- Monitor for pool exhaustion warnings
- Track query latency
**Tuning:**
- Adjust `max_connections` in PostgreSQL config
- Adjust pool size in server .env file
- Monitor and iterate
---
## Completed Items (For Reference)
### ✓ Systemd Service Configuration
**Completed:** 2026-01-17
**Phase:** Phase 1 Week 2
### ✓ Prometheus Metrics Integration
**Completed:** 2026-01-17
**Phase:** Phase 1 Week 2
### ✓ Grafana Dashboard Setup
**Completed:** 2026-01-17
**Phase:** Phase 1 Week 2
### ✓ Automated Backup System
**Completed:** 2026-01-17
**Phase:** Phase 1 Week 2
### ✓ Log Rotation Configuration
**Completed:** 2026-01-17
**Phase:** Phase 1 Week 2
### ✓ CI/CD Workflows Created
**Completed:** 2026-01-18
**Phase:** Phase 1 Week 3
### ✓ Deployment Automation Script
**Completed:** 2026-01-18
**Phase:** Phase 1 Week 3
### ✓ Version Tagging Automation
**Completed:** 2026-01-18
**Phase:** Phase 1 Week 3
### ✓ Gitea Actions Runner Installation
**Completed:** 2026-01-18
**Phase:** Phase 1 Week 3
### ✓ Systemd Watchdog Issue Fixed (Partial Completion)
**Completed:** 2026-01-18
**What Was Done:** Removed `WatchdogSec=30s` from systemd service file
**Result:** Resolved immediate 502 error; server now runs stably
**Status:** Issue fixed but full implementation (sd_notify) still pending
**Item Reference:** Item #3 (full sd_notify implementation remains as future work)
**Impact:** Production server is now stable and responding correctly
---
## Summary by Priority
**Critical (1 item):**
1. Gitea Actions runner registration
**High (4 items):**
2. TLS certificate auto-renewal
4. Invalid agent API key investigation
5. Comprehensive security audit logging
6. Session timeout enforcement
**High - Partial/Pending (1 item):**
3. Systemd watchdog implementation (issue fixed; sd_notify implementation pending)
**Medium (6 items):**
7. Grafana dashboard import
8. Grafana password change
9. Deployment SSH keys
10. Backup offsite sync
11. Alertmanager for Prometheus
12. CI/CD notification webhooks
**Low (8 items):**
13. Windows runner for agent builds
14. Staging environment
15. Code coverage thresholds
16. Performance benchmarking
17. Database replication
18. Centralized logging (ELK)
19. Agent retry logic review
20. Database pool sizing monitoring
---
## Tracking Notes
**How to Use This Document:**
1. Before starting new work, review this list
2. When discovering new issues, add them here
3. When completing items, move to "Completed Items" section
4. Prioritize based on: Security > Stability > Operations > Features
5. Update status and dates as work progresses
**Related Documents:**
- `PHASE1_COMPLETE.md` - Overall Phase 1 status
- `PHASE1_WEEK3_COMPLETE.md` - CI/CD specific items
- `CI_CD_SETUP.md` - CI/CD documentation
- `INFRASTRUCTURE_STATUS.md` - Infrastructure status
---
**Document Version:** 1.1
**Items Tracked:** 20 (1 critical, 4 high, 1 high-partial, 6 medium, 8 low)
**Last Updated:** 2026-01-18 (Item #3 marked as partial completion)
**Next Review:** Before Phase 2 planning

View File

@@ -1,230 +0,0 @@
# GuruConnect Feature Tracking
## Status Legend
- [ ] Not started
- [~] In progress
- [x] Complete
---
## Phase 1: Core MVP
### Infrastructure
- [x] WebSocket relay server (Axum)
- [x] Agent WebSocket client
- [x] Protobuf message protocol
- [x] Agent authentication (agent_id, api_key)
- [x] Session management (create, join, leave)
- [x] Systemd service deployment
- [x] NPM proxy (connect.azcomputerguru.com)
### Support Codes
- [x] Generate 6-digit codes
- [x] Code validation API
- [x] Code status tracking (pending, connected, completed, cancelled)
- [~] Link support codes to agent sessions
- [ ] Code expiration (auto-expire after X minutes)
- [ ] Support code in agent download URL
### Dashboard
- [x] Technician login page
- [x] Support tab with code generation
- [x] Access tab with connected agents
- [ ] Session detail panel with tabs
- [ ] Screenshot thumbnails
- [ ] Join/Connect button
### Agent (Windows)
- [x] DXGI screen capture
- [x] GDI fallback capture
- [x] WebSocket connection
- [x] Config persistence (agent_id)
- [ ] Support code parameter
- [ ] Hostname/machine info reporting
- [ ] Screenshot-only mode (for thumbnails)
---
## Phase 2: Remote Control
### Screen Viewing
- [ ] Web-based viewer (canvas)
- [ ] Raw frame decoding
- [ ] Dirty rectangle optimization
- [ ] Frame rate adaptation
### Input Control
- [x] Mouse event handling (agent)
- [x] Keyboard event handling (agent)
- [ ] Input relay through server
- [ ] Multi-monitor support
### Encoding
- [ ] VP9 software encoding
- [ ] H.264 hardware encoding (NVENC/QSV)
- [ ] Adaptive quality based on bandwidth
---
## Phase 3: Backstage Tools (like ScreenConnect)
### Device Information
- [ ] OS version, hostname, domain
- [ ] Logged-in user
- [ ] Public/private IP addresses
- [ ] MAC address
- [ ] CPU, RAM, disk info
- [ ] Uptime
### Toolbox APIs
- [ ] Process list (name, PID, memory)
- [ ] Installed software list
- [ ] Windows services list
- [ ] Event log viewer
- [ ] Registry browser
### Remote Commands
- [ ] Run shell commands
- [ ] PowerShell execution
- [ ] Command output streaming
- [ ] Command history per session
### Chat/Messaging
- [ ] Technician → Client messages
- [ ] Client → Technician messages
- [ ] Message history
### File Transfer
- [ ] Upload files to remote
- [ ] Download files from remote
- [ ] Progress tracking
- [ ] Folder browsing
---
## Phase 4: Session Management
### Timeline/History
- [ ] Connection events
- [ ] Session duration tracking
- [ ] Guest connection history
- [ ] Activity log
### Session Recording
- [ ] Record session video
- [ ] Playback interface
- [ ] Storage management
### Notes
- [ ] Per-session notes
- [ ] Session tagging
---
## Phase 5: Access Mode (Unattended)
### Persistent Agent
- [ ] Windows service installation
- [ ] Auto-start on boot
- [ ] Silent/background mode
- [ ] Automatic reconnection
### Machine Groups
- [ ] Company/client organization
- [ ] Site/location grouping
- [ ] Custom tags
- [ ] Filtering/search
### Installer Builder
- [ ] Customized agent builds
- [ ] Pre-configured company/site
- [ ] Silent install options
- [ ] MSI packaging
---
## Phase 6: Security & Authentication
### Technician Auth
- [ ] User accounts
- [ ] Password hashing
- [ ] JWT tokens
- [ ] Session management
### MFA
- [ ] TOTP (Google Authenticator)
- [ ] Email verification
### Audit Logging
- [ ] Login attempts
- [ ] Session access
- [ ] Command execution
- [ ] File transfers
### Permissions
- [ ] Role-based access
- [ ] Per-client permissions
- [ ] Feature restrictions
---
## Phase 7: Integrations
### PSA Integration
- [ ] HaloPSA
- [ ] Autotask
- [ ] ConnectWise
### GuruRMM Integration
- [ ] Dashboard embedding
- [ ] Single sign-on
- [ ] Asset linking
---
## Phase 8: Polish
### Branding
- [ ] White-label support
- [ ] Custom logos
- [ ] Custom colors
### Mobile Support
- [ ] Responsive viewer
- [ ] Touch input handling
### Annotations
- [ ] Draw on screen
- [ ] Pointer highlighting
- [ ] Screenshot annotations
---
## Current Sprint
### In Progress
1. Link support codes to agent sessions
2. Show connected status in dashboard
### Next Up
1. Support code in agent download/config
2. Device info reporting from agent
3. Screenshot thumbnails
---
## Notes
### ScreenConnect Feature Reference (from screenshots)
- Support session list with idle times and connection bars
- Detail panel with tabbed interface:
- Join/Screen (thumbnail, Join button)
- Info (device details)
- Timeline (connection history)
- Chat (messaging)
- Commands (shell execution)
- Notes
- Toolbox (processes, software, events, services)
- File transfer
- Logs
- Settings

View File

@@ -1,277 +0,0 @@
# Week 1, Day 1-2 - Security Fixes Summary
**Date:** 2026-01-17
**Phase:** Phase 1 - Security & Infrastructure
**Status:** CRITICAL SECURITY FIXES COMPLETE
---
## Executive Summary
Successfully completed 5 critical security vulnerabilities in the GuruConnect server. All code compiles and is ready for testing. The system is now significantly more secure against common attack vectors.
## Security Fixes Completed
### ✓ SEC-1: Hardcoded JWT Secret (CRITICAL)
**Problem:** JWT secret was hardcoded in source code, allowing anyone with access to forge admin tokens.
**Fix:**
- Removed hardcoded secret from server/src/main.rs and server/src/auth/jwt.rs
- Made JWT_SECRET environment variable mandatory (server panics if not set)
- Added minimum length validation (32+ characters)
- Generated strong random secret in server/.env.example
**Files Modified:** 3
**Impact:** System compromise prevented
**Status:** COMPLETE
---
### ✓ SEC-2: Rate Limiting (HIGH)
**Problem:** No rate limiting on authentication endpoints, allowing brute force attacks.
**Attempted Fix:**
- Added tower_governor dependency
- Created rate limiting middleware in server/src/middleware/rate_limit.rs
- Defined 3 rate limiters (auth: 5/min, support_code: 10/min, api: 60/min)
**Blocker:** tower_governor type signature incompatible with Axum 0.7
**Current Status:** Documented in SEC2_RATE_LIMITING_TODO.md, middleware disabled
**Next Steps:** Research compatible types, use custom middleware, or implement Redis-based limiting
**Status:** DEFERRED (not blocking other work)
---
### ✓ SEC-3: SQL Injection (CRITICAL)
**Problem:** Potential SQL injection vulnerabilities in database queries.
**Investigation:**
- Audited all database files: users.rs, machines.rs, sessions.rs
- Searched for vulnerable patterns (format!, string concatenation)
**Finding:** NO VULNERABILITIES FOUND
- All queries use sqlx parameterized queries ($1, $2 placeholders)
- No format! or string concatenation with user input
- Database treats parameters as data, not executable code
**Files Audited:** 6 database modules
**Impact:** Confirmed secure from SQL injection
**Status:** COMPLETE (verified safe)
---
### ✓ SEC-4: Agent Connection Validation (CRITICAL)
**Problem:** No IP logging, no failed connection logging, weak API keys allowed.
**Fix 1: IP Address Extraction and Logging**
- Created server/src/utils/ip_extract.rs
- Modified relay/mod.rs to extract IP from ConnectInfo
- Updated all log_event calls to include IP address
- Added ConnectInfo support to server startup
**Fix 2: Failed Connection Attempt Logging**
- Added 5 new event types to db/events.rs:
- CONNECTION_REJECTED_NO_AUTH
- CONNECTION_REJECTED_INVALID_CODE
- CONNECTION_REJECTED_EXPIRED_CODE
- CONNECTION_REJECTED_INVALID_API_KEY
- CONNECTION_REJECTED_CANCELLED_CODE
- All failed attempts logged to database with IP, reason, and details
**Fix 3: API Key Strength Validation**
- Created server/src/utils/validation.rs
- Validates API keys at startup:
- Minimum 32 characters
- No weak patterns (password, admin, etc.)
- Sufficient character diversity (10+ unique chars)
- Server refuses to start with weak AGENT_API_KEY
**Files Created:** 4
**Files Modified:** 4
**Impact:** Complete security audit trail, weak credentials prevented
**Status:** COMPLETE
---
### ✓ SEC-5: Session Takeover Prevention (CRITICAL)
**Problem:** JWT tokens cannot be revoked. Stolen tokens valid until expiration (24 hours).
**Fix 1: Token Blacklist**
- Created server/src/auth/token_blacklist.rs
- In-memory HashSet for revoked tokens
- Thread-safe with Arc<RwLock>
- Automatic cleanup of expired tokens
**Fix 2: JWT Validation with Revocation Check**
- Modified auth/mod.rs to check blacklist before validating token
- Tokens on blacklist rejected with "Token has been revoked" error
**Fix 3: Logout and Revocation Endpoints**
- Created server/src/api/auth_logout.rs with 5 endpoints:
- POST /api/auth/logout - Revoke own token
- POST /api/auth/revoke-token - Alias for logout
- POST /api/auth/admin/revoke-user - Admin revocation (foundation)
- GET /api/auth/blacklist/stats - Monitor blacklist
- POST /api/auth/blacklist/cleanup - Clean expired tokens
**Fix 4: Middleware Integration**
- Added TokenBlacklist to AppState
- Injected into request extensions via middleware
- All authenticated requests check blacklist
**Files Created:** 3
**Files Modified:** 4
**Impact:** Stolen tokens can be immediately revoked
**Status:** COMPLETE (foundation implemented)
---
## Summary Statistics
**Security Vulnerabilities Fixed:** 5/5 critical issues
**Vulnerabilities Verified Safe:** 1 (SQL injection)
**Vulnerabilities Deferred:** 1 (rate limiting - type issues)
**Code Changes:**
- Files Created: 14
- Files Modified: 15
- Lines of Code: ~2,500
- Compilation: SUCCESS (no errors)
**Security Improvements:**
- JWT secrets: Secure (environment variable, validated)
- SQL injection: Protected (parameterized queries)
- Agent connections: Audited (IP logging, failed attempt tracking)
- API keys: Validated (minimum strength enforced)
- Session takeover: Protected (token revocation implemented)
---
## Testing Requirements
### SEC-1: JWT Secret
- [ ] Server refuses to start without JWT_SECRET
- [ ] Server refuses to start with weak JWT_SECRET (<32 chars)
- [ ] Tokens created with new secret validate correctly
### SEC-2: Rate Limiting
- Deferred - not testable until type issues resolved
### SEC-3: SQL Injection
- ✓ Code audit complete (all queries use parameterized binding)
- [ ] Penetration testing (optional)
### SEC-4: Agent Validation
- [ ] Valid support code connects (IP logged in SESSION_STARTED)
- [ ] Invalid support code rejected (CONNECTION_REJECTED_INVALID_CODE logged with IP)
- [ ] Expired code rejected (CONNECTION_REJECTED_EXPIRED_CODE logged)
- [ ] No auth method rejected (CONNECTION_REJECTED_NO_AUTH logged)
- [ ] Weak API key rejected at startup
### SEC-5: Session Takeover
- [ ] Logout revokes token (subsequent requests return 401)
- [ ] Revoked token returns "Token has been revoked" error
- [ ] Blacklist stats show count correctly
- [ ] Cleanup removes expired tokens
---
## Next Steps
### Immediate (Day 3)
1. **Test all security fixes** - Manual testing with curl/Postman
2. **SEC-6: Password logging** - Remove sensitive data from logs
3. **SEC-7: XSS prevention** - Add CSP headers, input sanitization
### Week 1 Remaining
- SEC-8: TLS certificate validation
- SEC-9: Argon2id password hashing (verify in use)
- SEC-10: HTTPS enforcement
- SEC-11: CORS configuration
- SEC-12: CSP headers
- SEC-13: Session expiration
### Future Enhancements (SEC-5)
- Session tracking table for listing active sessions
- IP address binding in JWT (warn on IP change)
- Refresh token system (short-lived access tokens)
- Concurrent session limits
---
## Files Reference
**Created:**
1. server/.env.example
2. server/src/utils/mod.rs
3. server/src/utils/ip_extract.rs
4. server/src/utils/validation.rs
5. server/src/middleware/rate_limit.rs (disabled)
6. server/src/middleware/mod.rs
7. server/src/auth/token_blacklist.rs
8. server/src/api/auth_logout.rs
9. SEC2_RATE_LIMITING_TODO.md
10. SEC3_SQL_INJECTION_AUDIT.md
11. SEC4_AGENT_VALIDATION_AUDIT.md
12. SEC4_AGENT_VALIDATION_COMPLETE.md
13. SEC5_SESSION_TAKEOVER_AUDIT.md
14. SEC5_SESSION_TAKEOVER_COMPLETE.md
**Modified:**
1. server/src/main.rs - JWT validation, utils module, blacklist integration
2. server/src/auth/jwt.rs - Removed insecure default secret
3. server/src/auth/mod.rs - Added blacklist check, exports
4. server/src/relay/mod.rs - IP extraction, failed connection logging
5. server/src/db/events.rs - Added failed connection event types
6. server/Cargo.toml - Added tower_governor (disabled)
7. server/src/middleware/mod.rs - Disabled rate_limit module
8. server/src/api/mod.rs - Added auth_logout module
9. server/src/api/auth.rs - Added Request import
---
## Risk Assessment
### Before Day 1
- **CRITICAL:** Hardcoded JWT secret (system compromise)
- **CRITICAL:** No token revocation (stolen tokens valid 24h)
- **CRITICAL:** No agent connection validation (no audit trail)
- **HIGH:** No rate limiting (brute force attacks)
- **MEDIUM:** SQL injection unknown
### After Day 1
- **LOW:** JWT secrets secure (environment variable, validated)
- **LOW:** Token revocation operational (immediate invalidation)
- **LOW:** Agent connections audited (IP logging, failed attempts tracked)
- **MEDIUM:** Rate limiting not operational (deferred)
- **LOW:** SQL injection verified safe (parameterized queries)
**Overall Risk Reduction:** CRITICAL → LOW/MEDIUM
---
## Conclusion
Successfully completed the most critical security fixes for GuruConnect. The system is now significantly more secure:
✓ JWT secrets properly secured
✓ SQL injection verified safe
✓ Agent connections fully audited
✓ API key strength enforced
✓ Token revocation operational
**Compilation:** SUCCESS
**Production Ready:** Yes (with testing recommended)
**Next Focus:** Complete remaining Week 1 security fixes
---
**Day 1-2 Complete:** 2026-01-17
**Security Progress:** 5/13 items complete (38%)
**Next Session:** Testing + SEC-6, SEC-7

View File

@@ -1,462 +0,0 @@
# Week 1, Day 2-3 - Security Fixes COMPLETE
**Date:** 2026-01-17/18
**Phase:** Phase 1 - Security & Infrastructure
**Status:** Week 1 Security Objectives ACHIEVED
---
## Executive Summary
Successfully completed 10 of 13 security items for Week 1. All critical and high-priority security vulnerabilities have been addressed. The GuruConnect server now has production-grade security measures in place.
**Overall Progress:** 77% Complete (10/13 items)
**Critical Items:** 100% Complete (5/5 items)
**High Priority:** 100% Complete (3/3 items)
**Medium Priority:** 40% Complete (2/5 items)
---
## Completed Security Items
### ✓ SEC-1: Hardcoded JWT Secret (CRITICAL) - COMPLETE
**Problem:** JWT secret hardcoded in source code, allowing token forgery
**Solution:**
- Removed hardcoded secret from jwt.rs
- Made JWT_SECRET environment variable mandatory
- Added 32-character minimum validation
- Server panics at startup if JWT_SECRET missing or weak
**Files Modified:**
- `server/src/main.rs` (lines 82-87)
- `server/src/auth/jwt.rs` (removed default_jwt_secret function)
- `server/.env.example` (added secure secret template)
**Testing:** ✓ Verified - server refuses to start without JWT_SECRET
---
### ✓ SEC-2: Rate Limiting (HIGH) - DEFERRED
**Problem:** No rate limiting on authentication endpoints
**Status:** DEFERRED due to tower_governor type incompatibility with Axum 0.7
**Attempted:**
- Added tower_governor dependency
- Created middleware/rate_limit.rs
- Encountered type signature issues
**Documentation:** SEC2_RATE_LIMITING_TODO.md
**Next Steps:** Research compatible types or implement custom middleware
---
### ✓ SEC-3: SQL Injection Audit (CRITICAL) - COMPLETE
**Problem:** Potential SQL injection vulnerabilities
**Investigation:**
- Audited all database files (users.rs, machines.rs, sessions.rs, etc.)
- Searched for vulnerable patterns (format!, string concatenation)
**Finding:** NO VULNERABILITIES FOUND
- All queries use sqlx parameterized queries ($1, $2 placeholders)
- No format! or string concatenation with user input
- Database treats parameters as data, not executable code
**Documentation:** SEC3_SQL_INJECTION_AUDIT.md
---
### ✓ SEC-4: Agent Connection Validation (CRITICAL) - COMPLETE
**Problem:** No IP logging, no failed connection logging, weak API keys accepted
**Solutions Implemented:**
**1. IP Address Extraction and Logging**
- Created `server/src/utils/ip_extract.rs`
- Modified relay/mod.rs to extract IP from ConnectInfo
- Updated all log_event calls to include IP address
- Added ConnectInfo support to server startup
**2. Failed Connection Attempt Logging**
- Added 5 new event types to db/events.rs:
- CONNECTION_REJECTED_NO_AUTH
- CONNECTION_REJECTED_INVALID_CODE
- CONNECTION_REJECTED_EXPIRED_CODE
- CONNECTION_REJECTED_INVALID_API_KEY
- CONNECTION_REJECTED_CANCELLED_CODE
- All failed attempts logged to database with IP, reason, and details
**3. API Key Strength Validation**
- Created `server/src/utils/validation.rs`
- Validates API keys at startup:
- Minimum 32 characters
- No weak patterns (password, admin, key, secret, token, agent)
- Sufficient character diversity (10+ unique chars)
- Server refuses to start with weak AGENT_API_KEY
**Testing:** ✓ Verified - weak key rejected, IP addresses logged in events
---
### ✓ SEC-5: Session Takeover Prevention (CRITICAL) - COMPLETE
**Problem:** JWT tokens cannot be revoked, stolen tokens valid for 24 hours
**Solutions Implemented:**
**1. Token Blacklist System**
- Created `server/src/auth/token_blacklist.rs`
- In-memory HashSet for revoked tokens (Arc<RwLock<HashSet<String>>>)
- Thread-safe concurrent access
- Automatic cleanup of expired tokens
**2. JWT Validation with Revocation Check**
- Modified auth/mod.rs to check blacklist before validating token
- Tokens on blacklist rejected with "Token has been revoked" error
**3. Logout and Revocation Endpoints**
- Created `server/src/api/auth_logout.rs` with 5 endpoints:
- POST /api/auth/logout - Revoke own token
- POST /api/auth/revoke-token - Alias for logout
- POST /api/auth/admin/revoke-user - Admin revocation (foundation)
- GET /api/auth/blacklist/stats - Monitor blacklist
- POST /api/auth/blacklist/cleanup - Clean expired tokens
**4. Middleware Integration**
- Added TokenBlacklist to AppState
- Injected into request extensions via middleware
- All authenticated requests check blacklist
**Testing:** Code deployed (awaiting database for end-to-end testing)
---
### ✓ SEC-6: Remove Password Logging (MEDIUM) - COMPLETE
**Problem:** Initial admin password logged in server output
**Solution:**
- Modified main.rs to write credentials to `.admin-credentials` file
- Set file permissions to 600 (Unix only)
- Removed password from log output
- Clear warning message directing admin to read file
- Fallback to logging if file write fails (with security warning)
**Files Modified:**
- `server/src/main.rs` (lines 136-164)
**Security Improvement:**
- Before: Password visible in logs (security risk if logs are compromised)
- After: Password in secure file with restricted permissions
---
### ✓ SEC-7: XSS Prevention (CSP Headers) (HIGH) - COMPLETE
**Problem:** No Content Security Policy, vulnerable to XSS attacks
**Solution:**
- Created `server/src/middleware/security_headers.rs`
- Implemented comprehensive Content Security Policy:
```
default-src 'self'
script-src 'self' 'unsafe-inline'
style-src 'self' 'unsafe-inline'
img-src 'self' data:
font-src 'self'
connect-src 'self' ws: wss:
frame-ancestors 'none'
base-uri 'self'
form-action 'self'
```
- Applied CSP to all responses via middleware
**Files Created:**
- `server/src/middleware/security_headers.rs`
**Files Modified:**
- `server/src/middleware/mod.rs` (added security_headers module)
- `server/src/main.rs` (applied middleware to router)
---
### ⊗ SEC-8: TLS Certificate Validation (MEDIUM) - NOT APPLICABLE
**Status:** NOT APPLICABLE for server
**Rationale:**
- Server accepts connections, doesn't make outbound TLS connections
- TLS/HTTPS handled by NPM reverse proxy (connect.azcomputerguru.com)
- No server-side TLS validation needed
**Action:** Verified NPM has valid Let's Encrypt certificate
---
### ✓ SEC-9: Verify Argon2id Usage (HIGH) - COMPLETE
**Problem:** Unclear if Argon2id variant is being used
**Solution:**
- Modified `server/src/auth/password.rs` to explicitly specify Argon2id
- Added detailed documentation of Argon2id parameters:
- Algorithm: Argon2id (hybrid variant)
- Version: 0x13 (latest)
- Memory: 19456 KiB (default)
- Iterations: 2 (default)
- Parallelism: 1 (default)
- Explicitly configured Algorithm::Argon2id instead of relying on default
**Files Modified:**
- `server/src/auth/password.rs` (lines 1-44)
**Verification:** ✓ Argon2id explicitly configured and documented
---
### ⊗ SEC-10: HTTPS Enforcement (MEDIUM) - DELEGATED TO REVERSE PROXY
**Status:** HANDLED BY NPM
**Rationale:**
- HTTPS enforcement at reverse proxy level (NPM)
- Server runs on HTTP:3002 (internal only)
- Public access via https://connect.azcomputerguru.com (NPM handles TLS)
**Action Taken:**
- Added commented-out HSTS header in security_headers.rs
- Documented that HSTS should only be enabled if server serves HTTPS directly
- Current setup: NPM enforces HTTPS, server doesn't need HSTS
---
### ✓ SEC-11: CORS Configuration Review (MEDIUM) - COMPLETE
**Problem:** CORS allows all origins (`allow_origin(Any)`), overly permissive
**Solution:**
- Restricted allowed origins to:
- https://connect.azcomputerguru.com (production)
- http://localhost:3002 (development)
- http://127.0.0.1:3002 (development)
- Restricted allowed methods to: GET, POST, PUT, DELETE, OPTIONS
- Restricted allowed headers to: Authorization, Content-Type, Accept
- Enabled credentials (cookies, auth headers)
**Files Modified:**
- `server/src/main.rs` (lines 31-32, 295-315)
**Security Improvement:**
- Before: Any origin can access API (CSRF risk)
- After: Only specified origins allowed (CSRF protection)
---
### ✓ SEC-12: Security Headers Implementation (MEDIUM) - COMPLETE
**Problem:** Missing security headers (X-Frame-Options, X-Content-Type-Options, etc.)
**Solution:**
- Created comprehensive security headers middleware
- Implemented headers:
- **Content-Security-Policy** - XSS prevention (SEC-7)
- **X-Frame-Options: DENY** - Clickjacking protection
- **X-Content-Type-Options: nosniff** - MIME sniffing protection
- **X-XSS-Protection: 1; mode=block** - Legacy XSS filter
- **Referrer-Policy: strict-origin-when-cross-origin** - Referrer control
- **Permissions-Policy** - Feature policy (geolocation, microphone, camera disabled)
- Applied to all responses via middleware
**Files Created:**
- `server/src/middleware/security_headers.rs`
**Verification:** Headers will be applied to all HTTP responses
---
### ✓ SEC-13: Session Expiration Enforcement (MEDIUM) - COMPLETE
**Problem:** Unclear if JWT expiration is strictly enforced
**Solution:**
- Made JWT expiration validation explicit in jwt.rs
- Configured validation settings:
- `validate_exp = true` - Enforce expiration check
- `validate_nbf = false` - Not using "not before" claim
- `leeway = 0` - No clock skew tolerance
- Added redundant expiration check (defense in depth)
- Documented expiration enforcement
**Files Modified:**
- `server/src/auth/jwt.rs` (lines 90-118)
**Verification:** JWT expiration strictly enforced, expired tokens rejected
---
## Summary Statistics
### Security Items Completed
- **Total:** 10/13 (77%)
- **Critical:** 5/5 (100%)
- **High:** 3/3 (100%)
- **Medium:** 2/5 (40%)
### Deferred/Not Applicable
- **SEC-2:** Rate Limiting - DEFERRED (technical blocker)
- **SEC-8:** TLS Validation - NOT APPLICABLE (server doesn't make outbound TLS connections)
- **SEC-10:** HTTPS Enforcement - DELEGATED (handled by NPM reverse proxy)
### Code Changes
- **Files Created:** 18
- **Files Modified:** 20
- **Lines Added:** ~3,000
- **Compilation:** SUCCESS (53 warnings, 0 errors)
---
## Risk Assessment
### Before Week 1
- **CRITICAL:** Hardcoded JWT secret (system compromise possible)
- **CRITICAL:** No token revocation (stolen tokens valid 24h)
- **CRITICAL:** No agent connection audit trail
- **CRITICAL:** SQL injection unknown
- **HIGH:** No rate limiting (brute force possible)
- **HIGH:** No XSS protection
- **HIGH:** Password hashing unclear
- **MEDIUM:** Weak CORS configuration
- **MEDIUM:** Missing security headers
- **MEDIUM:** Password logging
- **MEDIUM:** Session expiration unclear
### After Week 1
- **SECURE:** JWT secrets from environment, validated (32+ chars)
- **SECURE:** Token revocation operational (immediate invalidation)
- **SECURE:** Complete agent connection audit trail (IP logging, failed attempts)
- **SECURE:** SQL injection verified safe (parameterized queries)
- **DEFERRED:** Rate limiting (technical blocker - to be resolved)
- **SECURE:** XSS protection (CSP headers)
- **SECURE:** Argon2id explicitly configured
- **SECURE:** CORS restricted to specific origins
- **SECURE:** Comprehensive security headers
- **SECURE:** Password written to secure file
- **SECURE:** JWT expiration strictly enforced
**Overall Risk Reduction:** CRITICAL → LOW/MEDIUM
---
## Files Reference
### Created Files (18)
1. `server/.env.example` - Secure environment configuration template
2. `server/src/utils/mod.rs` - Utilities module
3. `server/src/utils/ip_extract.rs` - IP address extraction
4. `server/src/utils/validation.rs` - API key strength validation
5. `server/src/middleware/rate_limit.rs` - Rate limiting (disabled)
6. `server/src/middleware/security_headers.rs` - Security headers middleware
7. `server/src/auth/token_blacklist.rs` - Token revocation system
8. `server/src/api/auth_logout.rs` - Logout/revocation endpoints
9. `SEC2_RATE_LIMITING_TODO.md` - Rate limiting blocker documentation
10. `SEC3_SQL_INJECTION_AUDIT.md` - SQL injection audit report
11. `SEC4_AGENT_VALIDATION_AUDIT.md` - Agent validation audit
12. `SEC4_AGENT_VALIDATION_COMPLETE.md` - Agent validation completion
13. `SEC5_SESSION_TAKEOVER_AUDIT.md` - Session takeover audit
14. `SEC5_SESSION_TAKEOVER_COMPLETE.md` - Session takeover completion
15. `WEEK1_DAY1_SUMMARY.md` - Day 1 summary
16. `DEPLOYMENT_DAY2_SUMMARY.md` - Day 2 deployment summary
17. `CHECKLIST_STATE.json` - Project state tracking
18. `WEEK1_DAY2-3_SECURITY_COMPLETE.md` - This document
### Modified Files (20)
1. `server/Cargo.toml` - Added tower_governor dependency
2. `server/src/main.rs` - JWT validation, API key validation, blacklist, security headers, CORS
3. `server/src/auth/mod.rs` - Blacklist revocation check, TokenBlacklist export
4. `server/src/auth/jwt.rs` - Explicit expiration validation, removed default secret
5. `server/src/auth/password.rs` - Explicit Argon2id configuration
6. `server/src/relay/mod.rs` - IP extraction, failed connection logging
7. `server/src/db/events.rs` - 5 new connection rejection event types
8. `server/src/api/mod.rs` - Added auth_logout module
9. `server/src/middleware/mod.rs` - Added security_headers module
---
## Testing Requirements
### Manual Testing (Completed)
- [✓] Server refuses to start without JWT_SECRET
- [✓] Server refuses to start with weak JWT_SECRET (<32 chars)
- [✓] Server refuses to start with weak AGENT_API_KEY
- [✓] IP addresses logged in connection rejection events
### Manual Testing (Pending Database)
- [ ] Login creates valid token
- [ ] Logout revokes token (returns 401 on reuse)
- [ ] Revoked token returns "Token has been revoked" error
- [ ] Blacklist stats show count correctly
- [ ] Cleanup removes expired tokens
### Automated Testing (Future)
- [ ] Unit tests for token blacklist
- [ ] Unit tests for API key validation
- [ ] Integration tests for security headers
- [ ] Integration tests for CORS configuration
- [ ] Penetration testing for XSS/CSRF
---
## Next Steps
### Immediate (Day 4)
1. Fix PostgreSQL database credentials
2. Test token revocation endpoints end-to-end
3. Deploy updated server to production
4. Verify security headers in HTTP responses
5. Test CORS configuration with production domain
### Future Enhancements
1. Resolve SEC-2 rate limiting (custom middleware or alternative library)
2. Implement session tracking table (for SEC-5 admin revocation)
3. Add IP address binding to JWT (detect session hijacking)
4. Implement refresh token system (short-lived access tokens)
5. Add concurrent session limits
6. Automated security scanning (OWASP ZAP, etc.)
---
## Conclusion
**Week 1 Security Objectives: ACHIEVED**
Successfully addressed all critical and high-priority security vulnerabilities:
- ✓ JWT secret security operational
- ✓ SQL injection verified safe
- ✓ Agent connections fully audited
- ✓ Token revocation system deployed
- ✓ XSS protection via CSP
- ✓ Argon2id explicitly configured
- ✓ CORS properly restricted
- ✓ Comprehensive security headers
- ✓ Password logging removed
- ✓ JWT expiration enforced
**Risk Level:** Reduced from CRITICAL to LOW/MEDIUM
**Production Readiness:** READY (with database connectivity pending)
**Compilation Status:** SUCCESS
**Code Quality:** Production-grade with comprehensive documentation
---
**Week 1 Completed:** 2026-01-18
**Security Progress:** 10/13 items complete (77%)
**Next Phase:** Deploy to production and begin Week 2 tasks

View File

@@ -1,114 +0,0 @@
[package]
name = "guruconnect"
version = "0.1.0"
edition = "2021"
authors = ["AZ Computer Guru"]
description = "GuruConnect Remote Desktop - Agent and Viewer"
[dependencies]
# CLI
clap = { version = "4", features = ["derive"] }
# Async runtime
tokio = { version = "1", features = ["full", "sync", "time", "rt-multi-thread", "macros"] }
# WebSocket
tokio-tungstenite = { version = "0.24", features = ["native-tls"] }
futures-util = "0.3"
# Windowing (for viewer)
winit = { version = "0.30", features = ["rwh_06"] }
softbuffer = "0.4"
raw-window-handle = "0.6"
# Compression
zstd = "0.13"
# Protocol (protobuf)
prost = "0.13"
prost-types = "0.13"
bytes = "1"
# Serialization
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# Logging
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Error handling
anyhow = "1"
thiserror = "1"
# Configuration
toml = "0.8"
# Crypto
ring = "0.17"
sha2 = "0.10"
# HTTP client for updates
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "stream", "json"] }
# UUID
uuid = { version = "1", features = ["v4", "serde"] }
# Time
chrono = { version = "0.4", features = ["serde"] }
# Hostname
hostname = "0.4"
# URL encoding
urlencoding = "2"
# System tray (Windows)
tray-icon = "0.19"
muda = "0.15" # Menu for tray icon
# Image handling for tray icon
image = { version = "0.25", default-features = false, features = ["png"] }
# URL parsing
url = "2"
[target.'cfg(windows)'.dependencies]
# Windows APIs for screen capture, input, and shell operations
windows = { version = "0.58", features = [
"Win32_Foundation",
"Win32_Graphics_Gdi",
"Win32_Graphics_Dxgi",
"Win32_Graphics_Dxgi_Common",
"Win32_Graphics_Direct3D",
"Win32_Graphics_Direct3D11",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_WindowsAndMessaging",
"Win32_UI_Shell",
"Win32_System_LibraryLoader",
"Win32_System_Threading",
"Win32_System_Registry",
"Win32_System_Console",
"Win32_System_Environment",
"Win32_Security",
"Win32_Storage_FileSystem",
"Win32_System_Pipes",
"Win32_System_SystemServices",
"Win32_System_IO",
]}
# Windows service support
windows-service = "0.7"
[build-dependencies]
prost-build = "0.13"
winres = "0.1"
chrono = "0.4"
[[bin]]
name = "guruconnect"
path = "src/main.rs"
[[bin]]
name = "guruconnect-sas-service"
path = "src/bin/sas_service.rs"

View File

@@ -1,98 +0,0 @@
use std::io::Result;
use std::process::Command;
fn main() -> Result<()> {
// Compile protobuf definitions
prost_build::compile_protos(&["../proto/guruconnect.proto"], &["../proto/"])?;
// Rerun if proto changes
println!("cargo:rerun-if-changed=../proto/guruconnect.proto");
// Rerun if git HEAD changes (new commits)
println!("cargo:rerun-if-changed=../.git/HEAD");
println!("cargo:rerun-if-changed=../.git/index");
// Build timestamp (UTC)
let build_timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string();
println!("cargo:rustc-env=BUILD_TIMESTAMP={}", build_timestamp);
// Git commit hash (short)
let git_hash = Command::new("git")
.args(["rev-parse", "--short=8", "HEAD"])
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
.unwrap_or_else(|| "unknown".to_string());
println!("cargo:rustc-env=GIT_HASH={}", git_hash);
// Git commit hash (full)
let git_hash_full = Command::new("git")
.args(["rev-parse", "HEAD"])
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
.unwrap_or_else(|| "unknown".to_string());
println!("cargo:rustc-env=GIT_HASH_FULL={}", git_hash_full);
// Git branch name
let git_branch = Command::new("git")
.args(["rev-parse", "--abbrev-ref", "HEAD"])
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
.unwrap_or_else(|| "unknown".to_string());
println!("cargo:rustc-env=GIT_BRANCH={}", git_branch);
// Git dirty state (uncommitted changes)
let git_dirty = Command::new("git")
.args(["status", "--porcelain"])
.output()
.ok()
.map(|o| !o.stdout.is_empty())
.unwrap_or(false);
println!("cargo:rustc-env=GIT_DIRTY={}", if git_dirty { "dirty" } else { "clean" });
// Git commit date
let git_commit_date = Command::new("git")
.args(["log", "-1", "--format=%ci"])
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
.unwrap_or_else(|| "unknown".to_string());
println!("cargo:rustc-env=GIT_COMMIT_DATE={}", git_commit_date);
// Build profile (debug/release)
let profile = std::env::var("PROFILE").unwrap_or_else(|_| "unknown".to_string());
println!("cargo:rustc-env=BUILD_PROFILE={}", profile);
// Target triple
let target = std::env::var("TARGET").unwrap_or_else(|_| "unknown".to_string());
println!("cargo:rustc-env=BUILD_TARGET={}", target);
// On Windows, embed the manifest for UAC elevation
#[cfg(target_os = "windows")]
{
println!("cargo:rerun-if-changed=guruconnect.manifest");
let mut res = winres::WindowsResource::new();
res.set_manifest_file("guruconnect.manifest");
res.set("ProductName", "GuruConnect Agent");
res.set("FileDescription", "GuruConnect Remote Desktop Agent");
res.set("LegalCopyright", "Copyright (c) AZ Computer Guru");
res.set_icon("guruconnect.ico"); // Optional: add icon if available
// Only compile if the manifest exists
if std::path::Path::new("guruconnect.manifest").exists() {
if let Err(e) = res.compile() {
// Don't fail the build if resource compilation fails
eprintln!("Warning: Failed to compile Windows resources: {}", e);
}
}
}
Ok(())
}

View File

@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="*"
name="GuruConnect.Agent"
type="win32"
/>
<description>GuruConnect Remote Desktop Agent</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<!-- Request highest available privileges (admin if possible, user otherwise) -->
<requestedExecutionLevel level="highestAvailable" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 and Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
</windowsSettings>
</application>
</assembly>

View File

@@ -1,638 +0,0 @@
//! GuruConnect SAS Service
//!
//! Windows Service running as SYSTEM to handle Ctrl+Alt+Del (Secure Attention Sequence).
//! The agent communicates with this service via named pipe IPC.
use std::ffi::OsString;
use std::io::{Read, Write as IoWrite};
use std::sync::mpsc;
use std::time::Duration;
use anyhow::{Context, Result};
use windows::core::{s, w};
use windows::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryW};
use windows_service::{
define_windows_service,
service::{
ServiceAccess, ServiceControl, ServiceControlAccept, ServiceErrorControl, ServiceExitCode,
ServiceInfo, ServiceStartType, ServiceState, ServiceStatus, ServiceType,
},
service_control_handler::{self, ServiceControlHandlerResult},
service_dispatcher,
service_manager::{ServiceManager, ServiceManagerAccess},
};
// Service configuration
const SERVICE_NAME: &str = "GuruConnectSAS";
const SERVICE_DISPLAY_NAME: &str = "GuruConnect SAS Service";
const SERVICE_DESCRIPTION: &str = "Handles Secure Attention Sequence (Ctrl+Alt+Del) for GuruConnect remote sessions";
const PIPE_NAME: &str = r"\\.\pipe\guruconnect-sas";
const INSTALL_DIR: &str = r"C:\Program Files\GuruConnect";
// Windows named pipe constants
const PIPE_ACCESS_DUPLEX: u32 = 0x00000003;
const PIPE_TYPE_MESSAGE: u32 = 0x00000004;
const PIPE_READMODE_MESSAGE: u32 = 0x00000002;
const PIPE_WAIT: u32 = 0x00000000;
const PIPE_UNLIMITED_INSTANCES: u32 = 255;
const INVALID_HANDLE_VALUE: isize = -1;
const SECURITY_DESCRIPTOR_REVISION: u32 = 1;
// FFI declarations for named pipe operations
#[link(name = "kernel32")]
extern "system" {
fn CreateNamedPipeW(
lpName: *const u16,
dwOpenMode: u32,
dwPipeMode: u32,
nMaxInstances: u32,
nOutBufferSize: u32,
nInBufferSize: u32,
nDefaultTimeOut: u32,
lpSecurityAttributes: *mut SECURITY_ATTRIBUTES,
) -> isize;
fn ConnectNamedPipe(hNamedPipe: isize, lpOverlapped: *mut std::ffi::c_void) -> i32;
fn DisconnectNamedPipe(hNamedPipe: isize) -> i32;
fn CloseHandle(hObject: isize) -> i32;
fn ReadFile(
hFile: isize,
lpBuffer: *mut u8,
nNumberOfBytesToRead: u32,
lpNumberOfBytesRead: *mut u32,
lpOverlapped: *mut std::ffi::c_void,
) -> i32;
fn WriteFile(
hFile: isize,
lpBuffer: *const u8,
nNumberOfBytesToWrite: u32,
lpNumberOfBytesWritten: *mut u32,
lpOverlapped: *mut std::ffi::c_void,
) -> i32;
fn FlushFileBuffers(hFile: isize) -> i32;
}
#[link(name = "advapi32")]
extern "system" {
fn InitializeSecurityDescriptor(pSecurityDescriptor: *mut u8, dwRevision: u32) -> i32;
fn SetSecurityDescriptorDacl(
pSecurityDescriptor: *mut u8,
bDaclPresent: i32,
pDacl: *mut std::ffi::c_void,
bDaclDefaulted: i32,
) -> i32;
}
#[repr(C)]
struct SECURITY_ATTRIBUTES {
nLength: u32,
lpSecurityDescriptor: *mut u8,
bInheritHandle: i32,
}
fn main() {
// Set up logging
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.with_target(false)
.init();
match std::env::args().nth(1).as_deref() {
Some("install") => {
if let Err(e) = install_service() {
eprintln!("Failed to install service: {}", e);
std::process::exit(1);
}
}
Some("uninstall") => {
if let Err(e) = uninstall_service() {
eprintln!("Failed to uninstall service: {}", e);
std::process::exit(1);
}
}
Some("start") => {
if let Err(e) = start_service() {
eprintln!("Failed to start service: {}", e);
std::process::exit(1);
}
}
Some("stop") => {
if let Err(e) = stop_service() {
eprintln!("Failed to stop service: {}", e);
std::process::exit(1);
}
}
Some("status") => {
if let Err(e) = query_status() {
eprintln!("Failed to query status: {}", e);
std::process::exit(1);
}
}
Some("service") => {
// Called by SCM when service starts
if let Err(e) = run_as_service() {
eprintln!("Service error: {}", e);
std::process::exit(1);
}
}
Some("test") => {
// Test mode: run pipe server directly (for debugging)
println!("Running in test mode (not as service)...");
if let Err(e) = run_pipe_server() {
eprintln!("Pipe server error: {}", e);
std::process::exit(1);
}
}
_ => {
print_usage();
}
}
}
fn print_usage() {
println!("GuruConnect SAS Service");
println!();
println!("Usage: guruconnect-sas-service <command>");
println!();
println!("Commands:");
println!(" install Install the service");
println!(" uninstall Remove the service");
println!(" start Start the service");
println!(" stop Stop the service");
println!(" status Query service status");
println!(" test Run in test mode (not as service)");
}
// Generate the Windows service boilerplate
define_windows_service!(ffi_service_main, service_main);
/// Entry point called by the Windows Service Control Manager
fn run_as_service() -> Result<()> {
service_dispatcher::start(SERVICE_NAME, ffi_service_main)
.context("Failed to start service dispatcher")?;
Ok(())
}
/// Main service function called by the SCM
fn service_main(_arguments: Vec<OsString>) {
if let Err(e) = run_service() {
tracing::error!("Service error: {}", e);
}
}
/// The actual service implementation
fn run_service() -> Result<()> {
// Create a channel to receive stop events
let (shutdown_tx, shutdown_rx) = mpsc::channel();
// Create the service control handler
let event_handler = move |control_event| -> ServiceControlHandlerResult {
match control_event {
ServiceControl::Stop | ServiceControl::Shutdown => {
tracing::info!("Received stop/shutdown command");
let _ = shutdown_tx.send(());
ServiceControlHandlerResult::NoError
}
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
_ => ServiceControlHandlerResult::NotImplemented,
}
};
// Register the service control handler
let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)
.context("Failed to register service control handler")?;
// Report that we're starting
status_handle
.set_service_status(ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::StartPending,
controls_accepted: ServiceControlAccept::empty(),
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::from_secs(5),
process_id: None,
})
.ok();
// Report that we're running
status_handle
.set_service_status(ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::Running,
controls_accepted: ServiceControlAccept::STOP | ServiceControlAccept::SHUTDOWN,
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::default(),
process_id: None,
})
.ok();
tracing::info!("GuruConnect SAS Service started");
// Run the pipe server in a separate thread
let pipe_handle = std::thread::spawn(|| {
if let Err(e) = run_pipe_server() {
tracing::error!("Pipe server error: {}", e);
}
});
// Wait for shutdown signal
let _ = shutdown_rx.recv();
tracing::info!("Shutting down...");
// Report that we're stopping
status_handle
.set_service_status(ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::StopPending,
controls_accepted: ServiceControlAccept::empty(),
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::from_secs(3),
process_id: None,
})
.ok();
// The pipe thread will exit when the service stops
drop(pipe_handle);
// Report stopped
status_handle
.set_service_status(ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::Stopped,
controls_accepted: ServiceControlAccept::empty(),
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::default(),
process_id: None,
})
.ok();
Ok(())
}
/// Run the named pipe server
fn run_pipe_server() -> Result<()> {
tracing::info!("Starting pipe server on {}", PIPE_NAME);
loop {
// Create security descriptor that allows everyone
let mut sd = [0u8; 256];
unsafe {
if InitializeSecurityDescriptor(sd.as_mut_ptr(), SECURITY_DESCRIPTOR_REVISION) == 0 {
tracing::error!("Failed to initialize security descriptor");
std::thread::sleep(Duration::from_secs(1));
continue;
}
// Set NULL DACL = allow everyone
if SetSecurityDescriptorDacl(sd.as_mut_ptr(), 1, std::ptr::null_mut(), 0) == 0 {
tracing::error!("Failed to set security descriptor DACL");
std::thread::sleep(Duration::from_secs(1));
continue;
}
}
let mut sa = SECURITY_ATTRIBUTES {
nLength: std::mem::size_of::<SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: sd.as_mut_ptr(),
bInheritHandle: 0,
};
// Create the pipe name as wide string
let pipe_name: Vec<u16> = PIPE_NAME.encode_utf16().chain(std::iter::once(0)).collect();
// Create the named pipe
let pipe = unsafe {
CreateNamedPipeW(
pipe_name.as_ptr(),
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
512,
512,
0,
&mut sa,
)
};
if pipe == INVALID_HANDLE_VALUE {
tracing::error!("Failed to create named pipe");
std::thread::sleep(Duration::from_secs(1));
continue;
}
tracing::info!("Waiting for client connection...");
// Wait for a client to connect
let connected = unsafe { ConnectNamedPipe(pipe, std::ptr::null_mut()) };
if connected == 0 {
let err = std::io::Error::last_os_error();
// ERROR_PIPE_CONNECTED (535) means client connected between Create and Connect
if err.raw_os_error() != Some(535) {
tracing::warn!("ConnectNamedPipe error: {}", err);
}
}
tracing::info!("Client connected");
// Read command from pipe
let mut buffer = [0u8; 512];
let mut bytes_read = 0u32;
let read_result = unsafe {
ReadFile(
pipe,
buffer.as_mut_ptr(),
buffer.len() as u32,
&mut bytes_read,
std::ptr::null_mut(),
)
};
if read_result != 0 && bytes_read > 0 {
let command = String::from_utf8_lossy(&buffer[..bytes_read as usize]);
let command = command.trim();
tracing::info!("Received command: {}", command);
let response = match command {
"sas" => {
match send_sas() {
Ok(()) => {
tracing::info!("SendSAS executed successfully");
"ok\n"
}
Err(e) => {
tracing::error!("SendSAS failed: {}", e);
"error\n"
}
}
}
"ping" => {
tracing::info!("Ping received");
"pong\n"
}
_ => {
tracing::warn!("Unknown command: {}", command);
"unknown\n"
}
};
// Write response
let mut bytes_written = 0u32;
unsafe {
WriteFile(
pipe,
response.as_ptr(),
response.len() as u32,
&mut bytes_written,
std::ptr::null_mut(),
);
FlushFileBuffers(pipe);
}
}
// Disconnect and close the pipe
unsafe {
DisconnectNamedPipe(pipe);
CloseHandle(pipe);
}
}
}
/// Call SendSAS via sas.dll
fn send_sas() -> Result<()> {
unsafe {
let lib = LoadLibraryW(w!("sas.dll")).context("Failed to load sas.dll")?;
let proc = GetProcAddress(lib, s!("SendSAS"));
if proc.is_none() {
anyhow::bail!("SendSAS function not found in sas.dll");
}
// SendSAS takes a BOOL parameter: FALSE (0) = Ctrl+Alt+Del
type SendSASFn = unsafe extern "system" fn(i32);
let send_sas_fn: SendSASFn = std::mem::transmute(proc.unwrap());
tracing::info!("Calling SendSAS(0)...");
send_sas_fn(0);
Ok(())
}
}
/// Install the service
fn install_service() -> Result<()> {
println!("Installing GuruConnect SAS Service...");
// Get current executable path
let current_exe = std::env::current_exe().context("Failed to get current executable")?;
let binary_dest = std::path::PathBuf::from(format!(r"{}\\guruconnect-sas-service.exe", INSTALL_DIR));
// Create install directory
std::fs::create_dir_all(INSTALL_DIR).context("Failed to create install directory")?;
// Copy binary
println!("Copying binary to: {:?}", binary_dest);
std::fs::copy(&current_exe, &binary_dest).context("Failed to copy binary")?;
// Open service manager
let manager = ServiceManager::local_computer(
None::<&str>,
ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE,
)
.context("Failed to connect to Service Control Manager. Run as Administrator.")?;
// Check if service exists and remove it
if let Ok(service) = manager.open_service(
SERVICE_NAME,
ServiceAccess::QUERY_STATUS | ServiceAccess::DELETE | ServiceAccess::STOP,
) {
println!("Removing existing service...");
if let Ok(status) = service.query_status() {
if status.current_state != ServiceState::Stopped {
let _ = service.stop();
std::thread::sleep(Duration::from_secs(2));
}
}
service.delete().context("Failed to delete existing service")?;
drop(service);
std::thread::sleep(Duration::from_secs(2));
}
// Create the service
let service_info = ServiceInfo {
name: OsString::from(SERVICE_NAME),
display_name: OsString::from(SERVICE_DISPLAY_NAME),
service_type: ServiceType::OWN_PROCESS,
start_type: ServiceStartType::AutoStart,
error_control: ServiceErrorControl::Normal,
executable_path: binary_dest.clone(),
launch_arguments: vec![OsString::from("service")],
dependencies: vec![],
account_name: None, // LocalSystem
account_password: None,
};
let service = manager
.create_service(&service_info, ServiceAccess::CHANGE_CONFIG | ServiceAccess::START)
.context("Failed to create service")?;
// Set description
service
.set_description(SERVICE_DESCRIPTION)
.context("Failed to set service description")?;
// Configure recovery
let _ = std::process::Command::new("sc")
.args([
"failure",
SERVICE_NAME,
"reset=86400",
"actions=restart/5000/restart/5000/restart/5000",
])
.output();
println!("\n** GuruConnect SAS Service installed successfully!");
println!("\nBinary: {:?}", binary_dest);
println!("\nStarting service...");
// Start the service
start_service()?;
Ok(())
}
/// Uninstall the service
fn uninstall_service() -> Result<()> {
println!("Uninstalling GuruConnect SAS Service...");
let binary_path = std::path::PathBuf::from(format!(r"{}\\guruconnect-sas-service.exe", INSTALL_DIR));
let manager = ServiceManager::local_computer(
None::<&str>,
ServiceManagerAccess::CONNECT,
)
.context("Failed to connect to Service Control Manager. Run as Administrator.")?;
match manager.open_service(
SERVICE_NAME,
ServiceAccess::QUERY_STATUS | ServiceAccess::STOP | ServiceAccess::DELETE,
) {
Ok(service) => {
if let Ok(status) = service.query_status() {
if status.current_state != ServiceState::Stopped {
println!("Stopping service...");
let _ = service.stop();
std::thread::sleep(Duration::from_secs(3));
}
}
println!("Deleting service...");
service.delete().context("Failed to delete service")?;
}
Err(_) => {
println!("Service was not installed");
}
}
// Remove binary
if binary_path.exists() {
std::thread::sleep(Duration::from_secs(1));
if let Err(e) = std::fs::remove_file(&binary_path) {
println!("Warning: Failed to remove binary: {}", e);
}
}
println!("\n** GuruConnect SAS Service uninstalled successfully!");
Ok(())
}
/// Start the service
fn start_service() -> Result<()> {
let manager = ServiceManager::local_computer(
None::<&str>,
ServiceManagerAccess::CONNECT,
)
.context("Failed to connect to Service Control Manager")?;
let service = manager
.open_service(SERVICE_NAME, ServiceAccess::START | ServiceAccess::QUERY_STATUS)
.context("Failed to open service. Is it installed?")?;
service.start::<String>(&[]).context("Failed to start service")?;
std::thread::sleep(Duration::from_secs(1));
let status = service.query_status()?;
match status.current_state {
ServiceState::Running => println!("** Service started successfully"),
ServiceState::StartPending => println!("** Service is starting..."),
other => println!("Service state: {:?}", other),
}
Ok(())
}
/// Stop the service
fn stop_service() -> Result<()> {
let manager = ServiceManager::local_computer(
None::<&str>,
ServiceManagerAccess::CONNECT,
)
.context("Failed to connect to Service Control Manager")?;
let service = manager
.open_service(SERVICE_NAME, ServiceAccess::STOP | ServiceAccess::QUERY_STATUS)
.context("Failed to open service")?;
service.stop().context("Failed to stop service")?;
std::thread::sleep(Duration::from_secs(1));
let status = service.query_status()?;
match status.current_state {
ServiceState::Stopped => println!("** Service stopped"),
ServiceState::StopPending => println!("** Service is stopping..."),
other => println!("Service state: {:?}", other),
}
Ok(())
}
/// Query service status
fn query_status() -> Result<()> {
let manager = ServiceManager::local_computer(
None::<&str>,
ServiceManagerAccess::CONNECT,
)
.context("Failed to connect to Service Control Manager")?;
match manager.open_service(SERVICE_NAME, ServiceAccess::QUERY_STATUS) {
Ok(service) => {
let status = service.query_status()?;
println!("GuruConnect SAS Service");
println!("=======================");
println!("Name: {}", SERVICE_NAME);
println!("State: {:?}", status.current_state);
println!("Binary: {}\\guruconnect-sas-service.exe", INSTALL_DIR);
println!("Pipe: {}", PIPE_NAME);
}
Err(_) => {
println!("GuruConnect SAS Service");
println!("=======================");
println!("Status: NOT INSTALLED");
println!("\nTo install: guruconnect-sas-service install");
}
}
Ok(())
}

View File

@@ -1,159 +0,0 @@
//! Display enumeration and information
use anyhow::Result;
/// Information about a display/monitor
#[derive(Debug, Clone)]
pub struct Display {
/// Unique display ID
pub id: u32,
/// Display name (e.g., "\\\\.\\DISPLAY1")
pub name: String,
/// X position in virtual screen coordinates
pub x: i32,
/// Y position in virtual screen coordinates
pub y: i32,
/// Width in pixels
pub width: u32,
/// Height in pixels
pub height: u32,
/// Whether this is the primary display
pub is_primary: bool,
/// Platform-specific handle (HMONITOR on Windows)
#[cfg(windows)]
pub handle: isize,
}
/// Display info for protocol messages
#[derive(Debug, Clone)]
pub struct DisplayInfo {
pub displays: Vec<Display>,
pub primary_id: u32,
}
impl Display {
/// Total pixels in the display
pub fn pixel_count(&self) -> u32 {
self.width * self.height
}
/// Bytes needed for BGRA frame buffer
pub fn buffer_size(&self) -> usize {
(self.width * self.height * 4) as usize
}
}
/// Enumerate all connected displays
#[cfg(windows)]
pub fn enumerate_displays() -> Result<Vec<Display>> {
use windows::Win32::Graphics::Gdi::{
EnumDisplayMonitors, GetMonitorInfoW, HMONITOR, MONITORINFOEXW,
};
use windows::Win32::Foundation::{BOOL, LPARAM, RECT};
use std::mem;
let mut displays = Vec::new();
let mut display_id = 0u32;
// Callback for EnumDisplayMonitors
unsafe extern "system" fn enum_callback(
hmonitor: HMONITOR,
_hdc: windows::Win32::Graphics::Gdi::HDC,
_rect: *mut RECT,
lparam: LPARAM,
) -> BOOL {
let displays = &mut *(lparam.0 as *mut Vec<(HMONITOR, u32)>);
let id = displays.len() as u32;
displays.push((hmonitor, id));
BOOL(1) // Continue enumeration
}
// Collect all monitor handles
let mut monitors: Vec<(windows::Win32::Graphics::Gdi::HMONITOR, u32)> = Vec::new();
unsafe {
let result = EnumDisplayMonitors(
None,
None,
Some(enum_callback),
LPARAM(&mut monitors as *mut _ as isize),
);
if !result.as_bool() {
anyhow::bail!("EnumDisplayMonitors failed");
}
}
// Get detailed info for each monitor
for (hmonitor, id) in monitors {
let mut info: MONITORINFOEXW = unsafe { mem::zeroed() };
info.monitorInfo.cbSize = mem::size_of::<MONITORINFOEXW>() as u32;
unsafe {
if GetMonitorInfoW(hmonitor, &mut info.monitorInfo as *mut _ as *mut _).as_bool() {
let rect = info.monitorInfo.rcMonitor;
let name = String::from_utf16_lossy(
&info.szDevice[..info.szDevice.iter().position(|&c| c == 0).unwrap_or(info.szDevice.len())]
);
let is_primary = (info.monitorInfo.dwFlags & 1) != 0; // MONITORINFOF_PRIMARY
displays.push(Display {
id,
name,
x: rect.left,
y: rect.top,
width: (rect.right - rect.left) as u32,
height: (rect.bottom - rect.top) as u32,
is_primary,
handle: hmonitor.0 as isize,
});
}
}
}
// Sort by position (left to right, top to bottom)
displays.sort_by(|a, b| {
if a.y != b.y {
a.y.cmp(&b.y)
} else {
a.x.cmp(&b.x)
}
});
// Reassign IDs after sorting
for (i, display) in displays.iter_mut().enumerate() {
display.id = i as u32;
}
if displays.is_empty() {
anyhow::bail!("No displays found");
}
Ok(displays)
}
#[cfg(not(windows))]
pub fn enumerate_displays() -> Result<Vec<Display>> {
anyhow::bail!("Display enumeration only supported on Windows")
}
/// Get display info for protocol
pub fn get_display_info() -> Result<DisplayInfo> {
let displays = enumerate_displays()?;
let primary_id = displays
.iter()
.find(|d| d.is_primary)
.map(|d| d.id)
.unwrap_or(0);
Ok(DisplayInfo {
displays,
primary_id,
})
}

View File

@@ -1,326 +0,0 @@
//! DXGI Desktop Duplication screen capture
//!
//! Uses the Windows Desktop Duplication API (available on Windows 8+) for
//! high-performance, low-latency screen capture with hardware acceleration.
//!
//! Reference: RustDesk's scrap library implementation
use super::{CapturedFrame, Capturer, DirtyRect, Display};
use anyhow::{Context, Result};
use std::ptr;
use std::time::Instant;
use windows::Win32::Graphics::Direct3D::D3D_DRIVER_TYPE_UNKNOWN;
use windows::Win32::Graphics::Direct3D11::{
D3D11CreateDevice, ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D,
D3D11_SDK_VERSION, D3D11_TEXTURE2D_DESC,
D3D11_USAGE_STAGING, D3D11_MAPPED_SUBRESOURCE, D3D11_MAP_READ,
};
use windows::Win32::Graphics::Dxgi::{
CreateDXGIFactory1, IDXGIAdapter1, IDXGIFactory1, IDXGIOutput, IDXGIOutput1,
IDXGIOutputDuplication, IDXGIResource, DXGI_ERROR_ACCESS_LOST,
DXGI_ERROR_WAIT_TIMEOUT, DXGI_OUTDUPL_DESC, DXGI_OUTDUPL_FRAME_INFO,
DXGI_RESOURCE_PRIORITY_MAXIMUM,
};
use windows::core::Interface;
/// DXGI Desktop Duplication capturer
pub struct DxgiCapturer {
display: Display,
device: ID3D11Device,
context: ID3D11DeviceContext,
duplication: IDXGIOutputDuplication,
staging_texture: Option<ID3D11Texture2D>,
width: u32,
height: u32,
last_frame: Option<Vec<u8>>,
}
impl DxgiCapturer {
/// Create a new DXGI capturer for the specified display
pub fn new(display: Display) -> Result<Self> {
let (device, context, duplication, desc) = Self::create_duplication(&display)?;
Ok(Self {
display,
device,
context,
duplication,
staging_texture: None,
width: desc.ModeDesc.Width,
height: desc.ModeDesc.Height,
last_frame: None,
})
}
/// Create D3D device and output duplication
fn create_duplication(
target_display: &Display,
) -> Result<(ID3D11Device, ID3D11DeviceContext, IDXGIOutputDuplication, DXGI_OUTDUPL_DESC)> {
unsafe {
// Create DXGI factory
let factory: IDXGIFactory1 = CreateDXGIFactory1()
.context("Failed to create DXGI factory")?;
// Find the adapter and output for this display
let (adapter, output) = Self::find_adapter_output(&factory, target_display)?;
// Create D3D11 device
let mut device: Option<ID3D11Device> = None;
let mut context: Option<ID3D11DeviceContext> = None;
D3D11CreateDevice(
&adapter,
D3D_DRIVER_TYPE_UNKNOWN,
None,
Default::default(),
None,
D3D11_SDK_VERSION,
Some(&mut device),
None,
Some(&mut context),
)
.context("Failed to create D3D11 device")?;
let device = device.context("D3D11 device is None")?;
let context = context.context("D3D11 context is None")?;
// Get IDXGIOutput1 interface
let output1: IDXGIOutput1 = output.cast()
.context("Failed to get IDXGIOutput1 interface")?;
// Create output duplication
let duplication = output1.DuplicateOutput(&device)
.context("Failed to create output duplication")?;
// Get duplication description
let desc = duplication.GetDesc();
tracing::info!(
"Created DXGI duplication: {}x{}, display: {}",
desc.ModeDesc.Width,
desc.ModeDesc.Height,
target_display.name
);
Ok((device, context, duplication, desc))
}
}
/// Find the adapter and output for the specified display
fn find_adapter_output(
factory: &IDXGIFactory1,
display: &Display,
) -> Result<(IDXGIAdapter1, IDXGIOutput)> {
unsafe {
let mut adapter_idx = 0u32;
loop {
// Enumerate adapters
let adapter: IDXGIAdapter1 = match factory.EnumAdapters1(adapter_idx) {
Ok(a) => a,
Err(_) => break,
};
let mut output_idx = 0u32;
loop {
// Enumerate outputs for this adapter
let output: IDXGIOutput = match adapter.EnumOutputs(output_idx) {
Ok(o) => o,
Err(_) => break,
};
// Check if this is the display we want
let desc = output.GetDesc()?;
let name = String::from_utf16_lossy(
&desc.DeviceName[..desc.DeviceName.iter().position(|&c| c == 0).unwrap_or(desc.DeviceName.len())]
);
if name == display.name || desc.Monitor.0 as isize == display.handle {
return Ok((adapter, output));
}
output_idx += 1;
}
adapter_idx += 1;
}
// If we didn't find the specific display, use the first one
let adapter: IDXGIAdapter1 = factory.EnumAdapters1(0)
.context("No adapters found")?;
let output: IDXGIOutput = adapter.EnumOutputs(0)
.context("No outputs found")?;
Ok((adapter, output))
}
}
/// Create or get the staging texture for CPU access
fn get_staging_texture(&mut self, src_texture: &ID3D11Texture2D) -> Result<&ID3D11Texture2D> {
if self.staging_texture.is_none() {
unsafe {
let mut desc = D3D11_TEXTURE2D_DESC::default();
src_texture.GetDesc(&mut desc);
desc.Usage = D3D11_USAGE_STAGING;
desc.BindFlags = Default::default();
desc.CPUAccessFlags = 0x20000; // D3D11_CPU_ACCESS_READ
desc.MiscFlags = Default::default();
let mut staging: Option<ID3D11Texture2D> = None;
self.device.CreateTexture2D(&desc, None, Some(&mut staging))
.context("Failed to create staging texture")?;
let staging = staging.context("Staging texture is None")?;
// Set high priority
let resource: IDXGIResource = staging.cast()?;
resource.SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM)?;
self.staging_texture = Some(staging);
}
}
Ok(self.staging_texture.as_ref().unwrap())
}
/// Acquire the next frame from the desktop
fn acquire_frame(&mut self, timeout_ms: u32) -> Result<Option<(ID3D11Texture2D, DXGI_OUTDUPL_FRAME_INFO)>> {
unsafe {
let mut frame_info = DXGI_OUTDUPL_FRAME_INFO::default();
let mut desktop_resource: Option<IDXGIResource> = None;
let result = self.duplication.AcquireNextFrame(
timeout_ms,
&mut frame_info,
&mut desktop_resource,
);
match result {
Ok(_) => {
let resource = desktop_resource.context("Desktop resource is None")?;
// Check if there's actually a new frame
if frame_info.LastPresentTime == 0 {
self.duplication.ReleaseFrame().ok();
return Ok(None);
}
let texture: ID3D11Texture2D = resource.cast()
.context("Failed to cast to ID3D11Texture2D")?;
Ok(Some((texture, frame_info)))
}
Err(e) if e.code() == DXGI_ERROR_WAIT_TIMEOUT => {
// No new frame available
Ok(None)
}
Err(e) if e.code() == DXGI_ERROR_ACCESS_LOST => {
// Desktop duplication was invalidated, need to recreate
tracing::warn!("Desktop duplication access lost, will need to recreate");
Err(anyhow::anyhow!("Access lost"))
}
Err(e) => {
Err(e).context("Failed to acquire frame")
}
}
}
}
/// Copy frame data to CPU-accessible memory
fn copy_frame_data(&mut self, texture: &ID3D11Texture2D) -> Result<Vec<u8>> {
unsafe {
// Get or create staging texture
let staging = self.get_staging_texture(texture)?.clone();
// Copy from GPU texture to staging texture
self.context.CopyResource(&staging, texture);
// Map the staging texture for CPU read
let mut mapped = D3D11_MAPPED_SUBRESOURCE::default();
self.context
.Map(&staging, 0, D3D11_MAP_READ, 0, Some(&mut mapped))
.context("Failed to map staging texture")?;
// Copy pixel data
let src_pitch = mapped.RowPitch as usize;
let dst_pitch = (self.width * 4) as usize;
let height = self.height as usize;
let mut data = vec![0u8; dst_pitch * height];
let src_ptr = mapped.pData as *const u8;
for y in 0..height {
let src_row = src_ptr.add(y * src_pitch);
let dst_row = data.as_mut_ptr().add(y * dst_pitch);
ptr::copy_nonoverlapping(src_row, dst_row, dst_pitch);
}
// Unmap
self.context.Unmap(&staging, 0);
Ok(data)
}
}
/// Extract dirty rectangles from frame info
fn extract_dirty_rects(&self, _frame_info: &DXGI_OUTDUPL_FRAME_INFO) -> Option<Vec<DirtyRect>> {
// TODO: Implement dirty rectangle extraction using
// IDXGIOutputDuplication::GetFrameDirtyRects and GetFrameMoveRects
// For now, return None to indicate full frame update
None
}
}
impl Capturer for DxgiCapturer {
fn capture(&mut self) -> Result<Option<CapturedFrame>> {
// Try to acquire a frame with 100ms timeout
let frame_result = self.acquire_frame(100)?;
let (texture, frame_info) = match frame_result {
Some((t, f)) => (t, f),
None => return Ok(None), // No new frame
};
// Copy frame data to CPU memory
let data = self.copy_frame_data(&texture)?;
// Release the frame
unsafe {
self.duplication.ReleaseFrame().ok();
}
// Extract dirty rectangles if available
let dirty_rects = self.extract_dirty_rects(&frame_info);
Ok(Some(CapturedFrame {
width: self.width,
height: self.height,
data,
timestamp: Instant::now(),
display_id: self.display.id,
dirty_rects,
}))
}
fn display(&self) -> &Display {
&self.display
}
fn is_valid(&self) -> bool {
// Could check if duplication is still valid
true
}
}
impl Drop for DxgiCapturer {
fn drop(&mut self) {
// Release any held frame
unsafe {
self.duplication.ReleaseFrame().ok();
}
}
}

View File

@@ -1,148 +0,0 @@
//! GDI screen capture fallback
//!
//! Uses Windows GDI (Graphics Device Interface) for screen capture.
//! Slower than DXGI but works on older systems and edge cases.
use super::{CapturedFrame, Capturer, Display};
use anyhow::Result;
use std::time::Instant;
use windows::Win32::Graphics::Gdi::{
BitBlt, CreateCompatibleBitmap, CreateCompatibleDC, DeleteDC, DeleteObject,
GetDIBits, SelectObject, BITMAPINFO, BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS,
SRCCOPY, GetDC, ReleaseDC,
};
use windows::Win32::Foundation::HWND;
/// GDI-based screen capturer
pub struct GdiCapturer {
display: Display,
width: u32,
height: u32,
}
impl GdiCapturer {
/// Create a new GDI capturer for the specified display
pub fn new(display: Display) -> Result<Self> {
Ok(Self {
width: display.width,
height: display.height,
display,
})
}
/// Capture the screen using GDI
fn capture_gdi(&self) -> Result<Vec<u8>> {
unsafe {
// Get device context for the entire screen
let screen_dc = GetDC(HWND::default());
if screen_dc.is_invalid() {
anyhow::bail!("Failed to get screen DC");
}
// Create compatible DC and bitmap
let mem_dc = CreateCompatibleDC(screen_dc);
if mem_dc.is_invalid() {
ReleaseDC(HWND::default(), screen_dc);
anyhow::bail!("Failed to create compatible DC");
}
let bitmap = CreateCompatibleBitmap(screen_dc, self.width as i32, self.height as i32);
if bitmap.is_invalid() {
DeleteDC(mem_dc);
ReleaseDC(HWND::default(), screen_dc);
anyhow::bail!("Failed to create compatible bitmap");
}
// Select bitmap into memory DC
let old_bitmap = SelectObject(mem_dc, bitmap);
// Copy screen to memory DC
if let Err(e) = BitBlt(
mem_dc,
0,
0,
self.width as i32,
self.height as i32,
screen_dc,
self.display.x,
self.display.y,
SRCCOPY,
) {
SelectObject(mem_dc, old_bitmap);
DeleteObject(bitmap);
DeleteDC(mem_dc);
ReleaseDC(HWND::default(), screen_dc);
anyhow::bail!("BitBlt failed: {}", e);
}
// Prepare bitmap info for GetDIBits
let mut bmi = BITMAPINFO {
bmiHeader: BITMAPINFOHEADER {
biSize: std::mem::size_of::<BITMAPINFOHEADER>() as u32,
biWidth: self.width as i32,
biHeight: -(self.height as i32), // Negative for top-down
biPlanes: 1,
biBitCount: 32,
biCompression: BI_RGB.0,
biSizeImage: 0,
biXPelsPerMeter: 0,
biYPelsPerMeter: 0,
biClrUsed: 0,
biClrImportant: 0,
},
bmiColors: [Default::default()],
};
// Allocate buffer for pixel data
let buffer_size = (self.width * self.height * 4) as usize;
let mut data = vec![0u8; buffer_size];
// Get the bits
let lines = GetDIBits(
mem_dc,
bitmap,
0,
self.height,
Some(data.as_mut_ptr() as *mut _),
&mut bmi,
DIB_RGB_COLORS,
);
// Cleanup
SelectObject(mem_dc, old_bitmap);
DeleteObject(bitmap);
DeleteDC(mem_dc);
ReleaseDC(HWND::default(), screen_dc);
if lines == 0 {
anyhow::bail!("GetDIBits failed");
}
Ok(data)
}
}
}
impl Capturer for GdiCapturer {
fn capture(&mut self) -> Result<Option<CapturedFrame>> {
let data = self.capture_gdi()?;
Ok(Some(CapturedFrame {
width: self.width,
height: self.height,
data,
timestamp: Instant::now(),
display_id: self.display.id,
dirty_rects: None, // GDI doesn't provide dirty rects
}))
}
fn display(&self) -> &Display {
&self.display
}
fn is_valid(&self) -> bool {
true
}
}

View File

@@ -1,102 +0,0 @@
//! Screen capture module
//!
//! Provides DXGI Desktop Duplication for high-performance screen capture on Windows 8+,
//! with GDI fallback for legacy systems or edge cases.
#[cfg(windows)]
mod dxgi;
#[cfg(windows)]
mod gdi;
mod display;
pub use display::{Display, DisplayInfo};
use anyhow::Result;
use std::time::Instant;
/// Captured frame data
#[derive(Debug)]
pub struct CapturedFrame {
/// Frame width in pixels
pub width: u32,
/// Frame height in pixels
pub height: u32,
/// Raw BGRA pixel data (4 bytes per pixel)
pub data: Vec<u8>,
/// Timestamp when frame was captured
pub timestamp: Instant,
/// Display ID this frame is from
pub display_id: u32,
/// Regions that changed since last frame (if available)
pub dirty_rects: Option<Vec<DirtyRect>>,
}
/// Rectangular region that changed
#[derive(Debug, Clone, Copy)]
pub struct DirtyRect {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
/// Screen capturer trait
pub trait Capturer: Send {
/// Capture the next frame
///
/// Returns None if no new frame is available (screen unchanged)
fn capture(&mut self) -> Result<Option<CapturedFrame>>;
/// Get the current display info
fn display(&self) -> &Display;
/// Check if capturer is still valid (display may have changed)
fn is_valid(&self) -> bool;
}
/// Create a capturer for the specified display
#[cfg(windows)]
pub fn create_capturer(display: Display, use_dxgi: bool, gdi_fallback: bool) -> Result<Box<dyn Capturer>> {
if use_dxgi {
match dxgi::DxgiCapturer::new(display.clone()) {
Ok(capturer) => {
tracing::info!("Using DXGI Desktop Duplication for capture");
return Ok(Box::new(capturer));
}
Err(e) => {
tracing::warn!("DXGI capture failed: {}, trying fallback", e);
if !gdi_fallback {
return Err(e);
}
}
}
}
// GDI fallback
tracing::info!("Using GDI for capture");
Ok(Box::new(gdi::GdiCapturer::new(display)?))
}
#[cfg(not(windows))]
pub fn create_capturer(_display: Display, _use_dxgi: bool, _gdi_fallback: bool) -> Result<Box<dyn Capturer>> {
anyhow::bail!("Screen capture only supported on Windows")
}
/// Get all available displays
pub fn enumerate_displays() -> Result<Vec<Display>> {
display::enumerate_displays()
}
/// Get the primary display
pub fn primary_display() -> Result<Display> {
let displays = enumerate_displays()?;
displays
.into_iter()
.find(|d| d.is_primary)
.ok_or_else(|| anyhow::anyhow!("No primary display found"))
}

View File

@@ -1,172 +0,0 @@
//! Chat window for the agent
//!
//! Provides a simple chat interface for communication between
//! the technician and the end user.
use std::sync::mpsc::{self, Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::thread;
use tracing::{info, warn, error};
#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::*;
#[cfg(windows)]
use windows::Win32::Foundation::*;
#[cfg(windows)]
use windows::Win32::Graphics::Gdi::*;
#[cfg(windows)]
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
#[cfg(windows)]
use windows::core::PCWSTR;
/// A chat message
#[derive(Debug, Clone)]
pub struct ChatMessage {
pub id: String,
pub sender: String,
pub content: String,
pub timestamp: i64,
}
/// Commands that can be sent to the chat window
#[derive(Debug)]
pub enum ChatCommand {
Show,
Hide,
AddMessage(ChatMessage),
Close,
}
/// Controller for the chat window
pub struct ChatController {
command_tx: Sender<ChatCommand>,
message_rx: Arc<Mutex<Receiver<ChatMessage>>>,
_handle: thread::JoinHandle<()>,
}
impl ChatController {
/// Create a new chat controller (spawns chat window thread)
#[cfg(windows)]
pub fn new() -> Option<Self> {
let (command_tx, command_rx) = mpsc::channel::<ChatCommand>();
let (message_tx, message_rx) = mpsc::channel::<ChatMessage>();
let handle = thread::spawn(move || {
run_chat_window(command_rx, message_tx);
});
Some(Self {
command_tx,
message_rx: Arc::new(Mutex::new(message_rx)),
_handle: handle,
})
}
#[cfg(not(windows))]
pub fn new() -> Option<Self> {
warn!("Chat window not supported on this platform");
None
}
/// Show the chat window
pub fn show(&self) {
let _ = self.command_tx.send(ChatCommand::Show);
}
/// Hide the chat window
pub fn hide(&self) {
let _ = self.command_tx.send(ChatCommand::Hide);
}
/// Add a message to the chat window
pub fn add_message(&self, msg: ChatMessage) {
let _ = self.command_tx.send(ChatCommand::AddMessage(msg));
}
/// Check for outgoing messages from the user
pub fn poll_outgoing(&self) -> Option<ChatMessage> {
if let Ok(rx) = self.message_rx.lock() {
rx.try_recv().ok()
} else {
None
}
}
/// Close the chat window
pub fn close(&self) {
let _ = self.command_tx.send(ChatCommand::Close);
}
}
#[cfg(windows)]
fn run_chat_window(command_rx: Receiver<ChatCommand>, message_tx: Sender<ChatMessage>) {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
info!("Starting chat window thread");
// For now, we'll use a simple message box approach
// A full implementation would create a proper window with a text input
// Process commands
loop {
match command_rx.recv() {
Ok(ChatCommand::Show) => {
info!("Chat window: Show requested");
// Show a simple notification that chat is available
}
Ok(ChatCommand::Hide) => {
info!("Chat window: Hide requested");
}
Ok(ChatCommand::AddMessage(msg)) => {
info!("Chat message received: {} - {}", msg.sender, msg.content);
// Show the message to the user via a message box (simple implementation)
let title = format!("Message from {}", msg.sender);
let content = msg.content.clone();
// Spawn a thread to show the message box (non-blocking)
thread::spawn(move || {
show_message_box_internal(&title, &content);
});
}
Ok(ChatCommand::Close) => {
info!("Chat window: Close requested");
break;
}
Err(_) => {
// Channel closed
break;
}
}
}
}
#[cfg(windows)]
fn show_message_box_internal(title: &str, message: &str) {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
let title_wide: Vec<u16> = OsStr::new(title)
.encode_wide()
.chain(std::iter::once(0))
.collect();
let message_wide: Vec<u16> = OsStr::new(message)
.encode_wide()
.chain(std::iter::once(0))
.collect();
unsafe {
MessageBoxW(
None,
PCWSTR(message_wide.as_ptr()),
PCWSTR(title_wide.as_ptr()),
MB_OK | MB_ICONINFORMATION | MB_TOPMOST | MB_SETFOREGROUND,
);
}
}
#[cfg(not(windows))]
fn run_chat_window(_command_rx: Receiver<ChatCommand>, _message_tx: Sender<ChatMessage>) {
// No-op on non-Windows
}

View File

@@ -1,459 +0,0 @@
//! Agent configuration management
//!
//! Supports three configuration sources (in priority order):
//! 1. Embedded config (magic bytes appended to executable)
//! 2. Config file (guruconnect.toml or %ProgramData%\GuruConnect\agent.toml)
//! 3. Environment variables (fallback)
use anyhow::{anyhow, Context, Result};
use serde::{Deserialize, Serialize};
use std::io::{Read, Seek, SeekFrom};
use std::path::PathBuf;
use tracing::{info, warn};
use uuid::Uuid;
/// Magic marker for embedded configuration (10 bytes)
const MAGIC_MARKER: &[u8] = b"GURUCONFIG";
/// Embedded configuration data (appended to executable)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbeddedConfig {
/// Server WebSocket URL
pub server_url: String,
/// API key for authentication
pub api_key: String,
/// Company/organization name
#[serde(default)]
pub company: Option<String>,
/// Site/location name
#[serde(default)]
pub site: Option<String>,
/// Tags for categorization
#[serde(default)]
pub tags: Vec<String>,
}
/// Detected run mode based on filename
#[derive(Debug, Clone, PartialEq)]
pub enum RunMode {
/// Viewer-only installation (filename contains "Viewer")
Viewer,
/// Temporary support session (filename contains 6-digit code)
TempSupport(String),
/// Permanent agent with embedded config
PermanentAgent,
/// Unknown/default mode
Default,
}
/// Agent configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
/// Server WebSocket URL (e.g., wss://connect.example.com/ws)
pub server_url: String,
/// Agent API key for authentication
pub api_key: String,
/// Unique agent identifier (generated on first run)
#[serde(default = "generate_agent_id")]
pub agent_id: String,
/// Optional hostname override
pub hostname_override: Option<String>,
/// Company/organization name (from embedded config)
#[serde(default)]
pub company: Option<String>,
/// Site/location name (from embedded config)
#[serde(default)]
pub site: Option<String>,
/// Tags for categorization (from embedded config)
#[serde(default)]
pub tags: Vec<String>,
/// Support code for one-time support sessions (set via command line or filename)
#[serde(skip)]
pub support_code: Option<String>,
/// Capture settings
#[serde(default)]
pub capture: CaptureConfig,
/// Encoding settings
#[serde(default)]
pub encoding: EncodingConfig,
}
fn generate_agent_id() -> String {
Uuid::new_v4().to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CaptureConfig {
/// Target frames per second (1-60)
#[serde(default = "default_fps")]
pub fps: u32,
/// Use DXGI Desktop Duplication (recommended)
#[serde(default = "default_true")]
pub use_dxgi: bool,
/// Fall back to GDI if DXGI fails
#[serde(default = "default_true")]
pub gdi_fallback: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncodingConfig {
/// Preferred codec (auto, raw, vp9, h264)
#[serde(default = "default_codec")]
pub codec: String,
/// Quality (1-100, higher = better quality, more bandwidth)
#[serde(default = "default_quality")]
pub quality: u32,
/// Use hardware encoding if available
#[serde(default = "default_true")]
pub hardware_encoding: bool,
}
fn default_fps() -> u32 {
30
}
fn default_true() -> bool {
true
}
fn default_codec() -> String {
"auto".to_string()
}
fn default_quality() -> u32 {
75
}
impl Default for CaptureConfig {
fn default() -> Self {
Self {
fps: default_fps(),
use_dxgi: true,
gdi_fallback: true,
}
}
}
impl Default for EncodingConfig {
fn default() -> Self {
Self {
codec: default_codec(),
quality: default_quality(),
hardware_encoding: true,
}
}
}
impl Config {
/// Detect run mode from executable filename
pub fn detect_run_mode() -> RunMode {
let exe_path = match std::env::current_exe() {
Ok(p) => p,
Err(_) => return RunMode::Default,
};
let filename = match exe_path.file_stem() {
Some(s) => s.to_string_lossy().to_string(),
None => return RunMode::Default,
};
let filename_lower = filename.to_lowercase();
// Check for viewer mode
if filename_lower.contains("viewer") {
info!("Detected viewer mode from filename: {}", filename);
return RunMode::Viewer;
}
// Check for support code in filename (6-digit number)
if let Some(code) = Self::extract_support_code(&filename) {
info!("Detected support code from filename: {}", code);
return RunMode::TempSupport(code);
}
// Check for embedded config
if Self::has_embedded_config() {
info!("Detected embedded config in executable");
return RunMode::PermanentAgent;
}
RunMode::Default
}
/// Extract 6-digit support code from filename
fn extract_support_code(filename: &str) -> Option<String> {
// Look for patterns like "GuruConnect-123456" or "GuruConnect_123456"
for part in filename.split(|c| c == '-' || c == '_' || c == '.') {
let trimmed = part.trim();
if trimmed.len() == 6 && trimmed.chars().all(|c| c.is_ascii_digit()) {
return Some(trimmed.to_string());
}
}
// Check if last 6 characters are all digits
if filename.len() >= 6 {
let last_six = &filename[filename.len() - 6..];
if last_six.chars().all(|c| c.is_ascii_digit()) {
return Some(last_six.to_string());
}
}
None
}
/// Check if embedded configuration exists in the executable
pub fn has_embedded_config() -> bool {
Self::read_embedded_config().is_ok()
}
/// Read embedded configuration from the executable
pub fn read_embedded_config() -> Result<EmbeddedConfig> {
let exe_path = std::env::current_exe()
.context("Failed to get current executable path")?;
let mut file = std::fs::File::open(&exe_path)
.context("Failed to open executable for reading")?;
let file_size = file.metadata()?.len();
if file_size < (MAGIC_MARKER.len() + 4) as u64 {
return Err(anyhow!("File too small to contain embedded config"));
}
// Read the last part of the file to find magic marker
// Structure: [PE binary][GURUCONFIG][length:u32][json config]
// We need to search backwards from the end
// Read last 64KB (should be more than enough for config)
let search_size = std::cmp::min(65536, file_size as usize);
let search_start = file_size - search_size as u64;
file.seek(SeekFrom::Start(search_start))?;
let mut buffer = vec![0u8; search_size];
file.read_exact(&mut buffer)?;
// Find magic marker
let marker_pos = buffer.windows(MAGIC_MARKER.len())
.rposition(|window| window == MAGIC_MARKER)
.ok_or_else(|| anyhow!("Magic marker not found"))?;
// Read config length (4 bytes after marker)
let length_start = marker_pos + MAGIC_MARKER.len();
if length_start + 4 > buffer.len() {
return Err(anyhow!("Invalid embedded config: length field truncated"));
}
let config_length = u32::from_le_bytes([
buffer[length_start],
buffer[length_start + 1],
buffer[length_start + 2],
buffer[length_start + 3],
]) as usize;
// Read config data
let config_start = length_start + 4;
if config_start + config_length > buffer.len() {
return Err(anyhow!("Invalid embedded config: data truncated"));
}
let config_bytes = &buffer[config_start..config_start + config_length];
let config: EmbeddedConfig = serde_json::from_slice(config_bytes)
.context("Failed to parse embedded config JSON")?;
info!("Loaded embedded config: server={}, company={:?}",
config.server_url, config.company);
Ok(config)
}
/// Check if an explicit agent configuration file exists
/// This returns true only if there's a real config file, not generated defaults
pub fn has_agent_config() -> bool {
// Check for embedded config first
if Self::has_embedded_config() {
return true;
}
// Check for config in current directory
let local_config = PathBuf::from("guruconnect.toml");
if local_config.exists() {
return true;
}
// Check in program data directory (Windows)
#[cfg(windows)]
{
if let Ok(program_data) = std::env::var("ProgramData") {
let path = PathBuf::from(program_data)
.join("GuruConnect")
.join("agent.toml");
if path.exists() {
return true;
}
}
}
false
}
/// Load configuration from embedded config, file, or environment
pub fn load() -> Result<Self> {
// Priority 1: Try loading from embedded config
if let Ok(embedded) = Self::read_embedded_config() {
info!("Using embedded configuration");
let config = Config {
server_url: embedded.server_url,
api_key: embedded.api_key,
agent_id: generate_agent_id(),
hostname_override: None,
company: embedded.company,
site: embedded.site,
tags: embedded.tags,
support_code: None,
capture: CaptureConfig::default(),
encoding: EncodingConfig::default(),
};
// Save to file for persistence (so agent_id is preserved)
let _ = config.save();
return Ok(config);
}
// Priority 2: Try loading from config file
let config_path = Self::config_path();
if config_path.exists() {
let contents = std::fs::read_to_string(&config_path)
.with_context(|| format!("Failed to read config from {:?}", config_path))?;
let mut config: Config = toml::from_str(&contents)
.with_context(|| "Failed to parse config file")?;
// Ensure agent_id is set and saved
if config.agent_id.is_empty() {
config.agent_id = generate_agent_id();
let _ = config.save();
}
// support_code is always None when loading from file (set via CLI)
config.support_code = None;
return Ok(config);
}
// Priority 3: Fall back to environment variables
let server_url = std::env::var("GURUCONNECT_SERVER_URL")
.unwrap_or_else(|_| "wss://connect.azcomputerguru.com/ws/agent".to_string());
let api_key = std::env::var("GURUCONNECT_API_KEY")
.unwrap_or_else(|_| "dev-key".to_string());
let agent_id = std::env::var("GURUCONNECT_AGENT_ID")
.unwrap_or_else(|_| generate_agent_id());
let config = Config {
server_url,
api_key,
agent_id,
hostname_override: std::env::var("GURUCONNECT_HOSTNAME").ok(),
company: None,
site: None,
tags: Vec::new(),
support_code: None,
capture: CaptureConfig::default(),
encoding: EncodingConfig::default(),
};
// Save config with generated agent_id for persistence
let _ = config.save();
Ok(config)
}
/// Get the configuration file path
fn config_path() -> PathBuf {
// Check for config in current directory first
let local_config = PathBuf::from("guruconnect.toml");
if local_config.exists() {
return local_config;
}
// Check in program data directory (Windows)
#[cfg(windows)]
{
if let Ok(program_data) = std::env::var("ProgramData") {
let path = PathBuf::from(program_data)
.join("GuruConnect")
.join("agent.toml");
if path.exists() {
return path;
}
}
}
// Default to local config
local_config
}
/// Get the hostname to use
pub fn hostname(&self) -> String {
self.hostname_override
.clone()
.unwrap_or_else(|| {
hostname::get()
.map(|h| h.to_string_lossy().to_string())
.unwrap_or_else(|_| "unknown".to_string())
})
}
/// Save current configuration to file
pub fn save(&self) -> Result<()> {
let config_path = Self::config_path();
// Ensure parent directory exists
if let Some(parent) = config_path.parent() {
std::fs::create_dir_all(parent)?;
}
let contents = toml::to_string_pretty(self)?;
std::fs::write(&config_path, contents)?;
Ok(())
}
}
/// Example configuration file content
pub fn example_config() -> &'static str {
r#"# GuruConnect Agent Configuration
# Server connection
server_url = "wss://connect.example.com/ws"
api_key = "your-agent-api-key"
agent_id = "auto-generated-uuid"
# Optional: override hostname
# hostname_override = "custom-hostname"
[capture]
fps = 30
use_dxgi = true
gdi_fallback = true
[encoding]
codec = "auto" # auto, raw, vp9, h264
quality = 75 # 1-100
hardware_encoding = true
"#
}

View File

@@ -1,52 +0,0 @@
//! Frame encoding module
//!
//! Encodes captured frames for transmission. Supports:
//! - Raw BGRA + Zstd compression (lowest latency, LAN mode)
//! - VP9 software encoding (universal fallback)
//! - H264 hardware encoding (when GPU available)
mod raw;
pub use raw::RawEncoder;
use crate::capture::CapturedFrame;
use crate::proto::{VideoFrame, RawFrame, DirtyRect as ProtoDirtyRect};
use anyhow::Result;
/// Encoded frame ready for transmission
#[derive(Debug)]
pub struct EncodedFrame {
/// Protobuf video frame message
pub frame: VideoFrame,
/// Size in bytes after encoding
pub size: usize,
/// Whether this is a keyframe (full frame)
pub is_keyframe: bool,
}
/// Frame encoder trait
pub trait Encoder: Send {
/// Encode a captured frame
fn encode(&mut self, frame: &CapturedFrame) -> Result<EncodedFrame>;
/// Request a keyframe on next encode
fn request_keyframe(&mut self);
/// Get encoder name/type
fn name(&self) -> &str;
}
/// Create an encoder based on configuration
pub fn create_encoder(codec: &str, quality: u32) -> Result<Box<dyn Encoder>> {
match codec.to_lowercase().as_str() {
"raw" | "zstd" => Ok(Box::new(RawEncoder::new(quality)?)),
// "vp9" => Ok(Box::new(Vp9Encoder::new(quality)?)),
// "h264" => Ok(Box::new(H264Encoder::new(quality)?)),
"auto" | _ => {
// Default to raw for now (best for LAN)
Ok(Box::new(RawEncoder::new(quality)?))
}
}
}

View File

@@ -1,232 +0,0 @@
//! Raw frame encoder with Zstd compression
//!
//! Best for LAN connections where bandwidth is plentiful and latency is critical.
//! Compresses BGRA pixel data using Zstd for fast compression/decompression.
use super::{EncodedFrame, Encoder};
use crate::capture::{CapturedFrame, DirtyRect};
use crate::proto::{video_frame, DirtyRect as ProtoDirtyRect, RawFrame, VideoFrame};
use anyhow::Result;
/// Raw frame encoder with Zstd compression
pub struct RawEncoder {
/// Compression level (1-22, default 3 for speed)
compression_level: i32,
/// Previous frame for delta detection
previous_frame: Option<Vec<u8>>,
/// Force keyframe on next encode
force_keyframe: bool,
/// Frame counter
sequence: u32,
}
impl RawEncoder {
/// Create a new raw encoder
///
/// Quality 1-100 maps to Zstd compression level:
/// - Low quality (1-33): Level 1-3 (fastest)
/// - Medium quality (34-66): Level 4-9
/// - High quality (67-100): Level 10-15 (best compression)
pub fn new(quality: u32) -> Result<Self> {
let compression_level = Self::quality_to_level(quality);
Ok(Self {
compression_level,
previous_frame: None,
force_keyframe: true, // Start with keyframe
sequence: 0,
})
}
/// Convert quality (1-100) to Zstd compression level
fn quality_to_level(quality: u32) -> i32 {
// Lower quality = faster compression (level 1-3)
// Higher quality = better compression (level 10-15)
// We optimize for speed, so cap at 6
match quality {
0..=33 => 1,
34..=50 => 2,
51..=66 => 3,
67..=80 => 4,
81..=90 => 5,
_ => 6,
}
}
/// Compress data using Zstd
fn compress(&self, data: &[u8]) -> Result<Vec<u8>> {
let compressed = zstd::encode_all(data, self.compression_level)?;
Ok(compressed)
}
/// Detect dirty rectangles by comparing with previous frame
fn detect_dirty_rects(
&self,
current: &[u8],
previous: &[u8],
width: u32,
height: u32,
) -> Vec<DirtyRect> {
// Simple block-based dirty detection
// Divide screen into 64x64 blocks and check which changed
const BLOCK_SIZE: u32 = 64;
let mut dirty_rects = Vec::new();
let stride = (width * 4) as usize;
let blocks_x = (width + BLOCK_SIZE - 1) / BLOCK_SIZE;
let blocks_y = (height + BLOCK_SIZE - 1) / BLOCK_SIZE;
for by in 0..blocks_y {
for bx in 0..blocks_x {
let x = bx * BLOCK_SIZE;
let y = by * BLOCK_SIZE;
let block_w = (BLOCK_SIZE).min(width - x);
let block_h = (BLOCK_SIZE).min(height - y);
// Check if this block changed
let mut changed = false;
'block_check: for row in 0..block_h {
let row_start = ((y + row) as usize * stride) + (x as usize * 4);
let row_end = row_start + (block_w as usize * 4);
if row_end <= current.len() && row_end <= previous.len() {
if current[row_start..row_end] != previous[row_start..row_end] {
changed = true;
break 'block_check;
}
} else {
changed = true;
break 'block_check;
}
}
if changed {
dirty_rects.push(DirtyRect {
x,
y,
width: block_w,
height: block_h,
});
}
}
}
// Merge adjacent dirty rects (simple optimization)
// TODO: Implement proper rectangle merging
dirty_rects
}
/// Extract pixels for dirty rectangles only
fn extract_dirty_pixels(
&self,
data: &[u8],
width: u32,
dirty_rects: &[DirtyRect],
) -> Vec<u8> {
let stride = (width * 4) as usize;
let mut pixels = Vec::new();
for rect in dirty_rects {
for row in 0..rect.height {
let row_start = ((rect.y + row) as usize * stride) + (rect.x as usize * 4);
let row_end = row_start + (rect.width as usize * 4);
if row_end <= data.len() {
pixels.extend_from_slice(&data[row_start..row_end]);
}
}
}
pixels
}
}
impl Encoder for RawEncoder {
fn encode(&mut self, frame: &CapturedFrame) -> Result<EncodedFrame> {
self.sequence = self.sequence.wrapping_add(1);
let is_keyframe = self.force_keyframe || self.previous_frame.is_none();
self.force_keyframe = false;
let (data_to_compress, dirty_rects, full_frame) = if is_keyframe {
// Keyframe: send full frame
(frame.data.clone(), Vec::new(), true)
} else if let Some(ref previous) = self.previous_frame {
// Delta frame: detect and send only changed regions
let dirty_rects =
self.detect_dirty_rects(&frame.data, previous, frame.width, frame.height);
if dirty_rects.is_empty() {
// No changes, skip frame
return Ok(EncodedFrame {
frame: VideoFrame::default(),
size: 0,
is_keyframe: false,
});
}
// If too many dirty rects, just send full frame
if dirty_rects.len() > 50 {
(frame.data.clone(), Vec::new(), true)
} else {
let dirty_pixels = self.extract_dirty_pixels(&frame.data, frame.width, &dirty_rects);
(dirty_pixels, dirty_rects, false)
}
} else {
(frame.data.clone(), Vec::new(), true)
};
// Compress the data
let compressed = self.compress(&data_to_compress)?;
let size = compressed.len();
// Build protobuf message
let proto_dirty_rects: Vec<ProtoDirtyRect> = dirty_rects
.iter()
.map(|r| ProtoDirtyRect {
x: r.x as i32,
y: r.y as i32,
width: r.width as i32,
height: r.height as i32,
})
.collect();
let raw_frame = RawFrame {
width: frame.width as i32,
height: frame.height as i32,
data: compressed,
compressed: true,
dirty_rects: proto_dirty_rects,
is_keyframe: full_frame,
};
let video_frame = VideoFrame {
timestamp: frame.timestamp.elapsed().as_millis() as i64,
display_id: frame.display_id as i32,
sequence: self.sequence as i32,
encoding: Some(video_frame::Encoding::Raw(raw_frame)),
};
// Save current frame for next comparison
self.previous_frame = Some(frame.data.clone());
Ok(EncodedFrame {
frame: video_frame,
size,
is_keyframe: full_frame,
})
}
fn request_keyframe(&mut self) {
self.force_keyframe = true;
}
fn name(&self) -> &str {
"raw+zstd"
}
}

View File

@@ -1,296 +0,0 @@
//! Keyboard input simulation using Windows SendInput API
use anyhow::Result;
#[cfg(windows)]
use windows::Win32::UI::Input::KeyboardAndMouse::{
SendInput, INPUT, INPUT_0, INPUT_KEYBOARD, KEYBD_EVENT_FLAGS, KEYEVENTF_EXTENDEDKEY,
KEYEVENTF_KEYUP, KEYEVENTF_SCANCODE, KEYEVENTF_UNICODE, KEYBDINPUT,
MapVirtualKeyW, MAPVK_VK_TO_VSC_EX,
};
/// Keyboard input controller
pub struct KeyboardController {
// Track modifier states for proper handling
#[allow(dead_code)]
modifiers: ModifierState,
}
#[derive(Default)]
struct ModifierState {
ctrl: bool,
alt: bool,
shift: bool,
meta: bool,
}
impl KeyboardController {
/// Create a new keyboard controller
pub fn new() -> Result<Self> {
Ok(Self {
modifiers: ModifierState::default(),
})
}
/// Press a key down by virtual key code
#[cfg(windows)]
pub fn key_down(&mut self, vk_code: u16) -> Result<()> {
self.send_key(vk_code, true)
}
/// Release a key by virtual key code
#[cfg(windows)]
pub fn key_up(&mut self, vk_code: u16) -> Result<()> {
self.send_key(vk_code, false)
}
/// Send a key event
#[cfg(windows)]
fn send_key(&mut self, vk_code: u16, down: bool) -> Result<()> {
// Get scan code from virtual key
let scan_code = unsafe { MapVirtualKeyW(vk_code as u32, MAPVK_VK_TO_VSC_EX) as u16 };
let mut flags = KEYBD_EVENT_FLAGS::default();
// Add extended key flag for certain keys
if Self::is_extended_key(vk_code) || (scan_code >> 8) == 0xE0 {
flags |= KEYEVENTF_EXTENDEDKEY;
}
if !down {
flags |= KEYEVENTF_KEYUP;
}
let input = INPUT {
r#type: INPUT_KEYBOARD,
Anonymous: INPUT_0 {
ki: KEYBDINPUT {
wVk: windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY(vk_code),
wScan: scan_code,
dwFlags: flags,
time: 0,
dwExtraInfo: 0,
},
},
};
self.send_input(&[input])
}
/// Type a unicode character
#[cfg(windows)]
pub fn type_char(&mut self, ch: char) -> Result<()> {
let mut inputs = Vec::new();
let mut buf = [0u16; 2];
let encoded = ch.encode_utf16(&mut buf);
// For characters that fit in a single u16
for &code_unit in encoded.iter() {
// Key down
inputs.push(INPUT {
r#type: INPUT_KEYBOARD,
Anonymous: INPUT_0 {
ki: KEYBDINPUT {
wVk: windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY(0),
wScan: code_unit,
dwFlags: KEYEVENTF_UNICODE,
time: 0,
dwExtraInfo: 0,
},
},
});
// Key up
inputs.push(INPUT {
r#type: INPUT_KEYBOARD,
Anonymous: INPUT_0 {
ki: KEYBDINPUT {
wVk: windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY(0),
wScan: code_unit,
dwFlags: KEYEVENTF_UNICODE | KEYEVENTF_KEYUP,
time: 0,
dwExtraInfo: 0,
},
},
});
}
self.send_input(&inputs)
}
/// Type a string of text
#[cfg(windows)]
pub fn type_string(&mut self, text: &str) -> Result<()> {
for ch in text.chars() {
self.type_char(ch)?;
}
Ok(())
}
/// Send Secure Attention Sequence (Ctrl+Alt+Delete)
///
/// This uses a multi-tier approach:
/// 1. Try the GuruConnect SAS Service (runs as SYSTEM, handles via named pipe)
/// 2. Try the sas.dll directly (requires SYSTEM privileges)
/// 3. Fallback to key simulation (won't work on secure desktop)
#[cfg(windows)]
pub fn send_sas(&mut self) -> Result<()> {
// Tier 1: Try the SAS service (named pipe IPC to SYSTEM service)
if let Ok(()) = crate::sas_client::request_sas() {
tracing::info!("SAS sent via GuruConnect SAS Service");
return Ok(());
}
tracing::info!("SAS service not available, trying direct sas.dll...");
// Tier 2: Try using the sas.dll directly (requires SYSTEM privileges)
use windows::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryW};
use windows::core::PCWSTR;
unsafe {
let dll_name: Vec<u16> = "sas.dll\0".encode_utf16().collect();
let lib = LoadLibraryW(PCWSTR(dll_name.as_ptr()));
if let Ok(lib) = lib {
let proc_name = b"SendSAS\0";
if let Some(proc) = GetProcAddress(lib, windows::core::PCSTR(proc_name.as_ptr())) {
// SendSAS takes a BOOL parameter: FALSE for Ctrl+Alt+Del
let send_sas: extern "system" fn(i32) = std::mem::transmute(proc);
send_sas(0); // FALSE = Ctrl+Alt+Del
tracing::info!("SAS sent via direct sas.dll call");
return Ok(());
}
}
}
// Tier 3: Fallback - try sending the keys (won't work on secure desktop)
tracing::warn!("SAS service and sas.dll not available, Ctrl+Alt+Del may not work");
// VK codes
const VK_CONTROL: u16 = 0x11;
const VK_MENU: u16 = 0x12; // Alt
const VK_DELETE: u16 = 0x2E;
// Press keys
self.key_down(VK_CONTROL)?;
self.key_down(VK_MENU)?;
self.key_down(VK_DELETE)?;
// Release keys
self.key_up(VK_DELETE)?;
self.key_up(VK_MENU)?;
self.key_up(VK_CONTROL)?;
Ok(())
}
/// Check if a virtual key code is an extended key
#[cfg(windows)]
fn is_extended_key(vk: u16) -> bool {
matches!(
vk,
0x21..=0x28 | // Page Up, Page Down, End, Home, Arrow keys
0x2D | 0x2E | // Insert, Delete
0x5B | 0x5C | // Left/Right Windows keys
0x5D | // Applications key
0x6F | // Numpad Divide
0x90 | // Num Lock
0x91 // Scroll Lock
)
}
/// Send input events
#[cfg(windows)]
fn send_input(&self, inputs: &[INPUT]) -> Result<()> {
let sent = unsafe { SendInput(inputs, std::mem::size_of::<INPUT>() as i32) };
if sent as usize != inputs.len() {
anyhow::bail!(
"SendInput failed: sent {} of {} inputs",
sent,
inputs.len()
);
}
Ok(())
}
#[cfg(not(windows))]
pub fn key_down(&mut self, _vk_code: u16) -> Result<()> {
anyhow::bail!("Keyboard input only supported on Windows")
}
#[cfg(not(windows))]
pub fn key_up(&mut self, _vk_code: u16) -> Result<()> {
anyhow::bail!("Keyboard input only supported on Windows")
}
#[cfg(not(windows))]
pub fn type_char(&mut self, _ch: char) -> Result<()> {
anyhow::bail!("Keyboard input only supported on Windows")
}
#[cfg(not(windows))]
pub fn send_sas(&mut self) -> Result<()> {
anyhow::bail!("SAS only supported on Windows")
}
}
/// Common Windows virtual key codes
#[allow(dead_code)]
pub mod vk {
pub const BACK: u16 = 0x08;
pub const TAB: u16 = 0x09;
pub const RETURN: u16 = 0x0D;
pub const SHIFT: u16 = 0x10;
pub const CONTROL: u16 = 0x11;
pub const MENU: u16 = 0x12; // Alt
pub const PAUSE: u16 = 0x13;
pub const CAPITAL: u16 = 0x14; // Caps Lock
pub const ESCAPE: u16 = 0x1B;
pub const SPACE: u16 = 0x20;
pub const PRIOR: u16 = 0x21; // Page Up
pub const NEXT: u16 = 0x22; // Page Down
pub const END: u16 = 0x23;
pub const HOME: u16 = 0x24;
pub const LEFT: u16 = 0x25;
pub const UP: u16 = 0x26;
pub const RIGHT: u16 = 0x27;
pub const DOWN: u16 = 0x28;
pub const INSERT: u16 = 0x2D;
pub const DELETE: u16 = 0x2E;
// 0-9 keys
pub const KEY_0: u16 = 0x30;
pub const KEY_9: u16 = 0x39;
// A-Z keys
pub const KEY_A: u16 = 0x41;
pub const KEY_Z: u16 = 0x5A;
// Windows keys
pub const LWIN: u16 = 0x5B;
pub const RWIN: u16 = 0x5C;
// Function keys
pub const F1: u16 = 0x70;
pub const F2: u16 = 0x71;
pub const F3: u16 = 0x72;
pub const F4: u16 = 0x73;
pub const F5: u16 = 0x74;
pub const F6: u16 = 0x75;
pub const F7: u16 = 0x76;
pub const F8: u16 = 0x77;
pub const F9: u16 = 0x78;
pub const F10: u16 = 0x79;
pub const F11: u16 = 0x7A;
pub const F12: u16 = 0x7B;
// Modifier keys
pub const LSHIFT: u16 = 0xA0;
pub const RSHIFT: u16 = 0xA1;
pub const LCONTROL: u16 = 0xA2;
pub const RCONTROL: u16 = 0xA3;
pub const LMENU: u16 = 0xA4; // Left Alt
pub const RMENU: u16 = 0xA5; // Right Alt
}

View File

@@ -1,91 +0,0 @@
//! Input injection module
//!
//! Handles mouse and keyboard input simulation using Windows SendInput API.
mod mouse;
mod keyboard;
pub use mouse::MouseController;
pub use keyboard::KeyboardController;
use anyhow::Result;
/// Combined input controller for mouse and keyboard
pub struct InputController {
mouse: MouseController,
keyboard: KeyboardController,
}
impl InputController {
/// Create a new input controller
pub fn new() -> Result<Self> {
Ok(Self {
mouse: MouseController::new()?,
keyboard: KeyboardController::new()?,
})
}
/// Get mouse controller
pub fn mouse(&mut self) -> &mut MouseController {
&mut self.mouse
}
/// Get keyboard controller
pub fn keyboard(&mut self) -> &mut KeyboardController {
&mut self.keyboard
}
/// Move mouse to absolute position
pub fn mouse_move(&mut self, x: i32, y: i32) -> Result<()> {
self.mouse.move_to(x, y)
}
/// Click mouse button
pub fn mouse_click(&mut self, button: MouseButton, down: bool) -> Result<()> {
if down {
self.mouse.button_down(button)
} else {
self.mouse.button_up(button)
}
}
/// Scroll mouse wheel
pub fn mouse_scroll(&mut self, delta_x: i32, delta_y: i32) -> Result<()> {
self.mouse.scroll(delta_x, delta_y)
}
/// Press or release a key
pub fn key_event(&mut self, vk_code: u16, down: bool) -> Result<()> {
if down {
self.keyboard.key_down(vk_code)
} else {
self.keyboard.key_up(vk_code)
}
}
/// Type a unicode character
pub fn type_unicode(&mut self, ch: char) -> Result<()> {
self.keyboard.type_char(ch)
}
/// Send Ctrl+Alt+Delete (requires special handling on Windows)
pub fn send_ctrl_alt_del(&mut self) -> Result<()> {
self.keyboard.send_sas()
}
}
/// Mouse button types
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MouseButton {
Left,
Right,
Middle,
X1,
X2,
}
impl Default for InputController {
fn default() -> Self {
Self::new().expect("Failed to create input controller")
}
}

View File

@@ -1,223 +0,0 @@
//! Mouse input simulation using Windows SendInput API
use super::MouseButton;
use anyhow::Result;
#[cfg(windows)]
use windows::Win32::UI::Input::KeyboardAndMouse::{
SendInput, INPUT, INPUT_0, INPUT_MOUSE, MOUSEEVENTF_ABSOLUTE, MOUSEEVENTF_HWHEEL,
MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP,
MOUSEEVENTF_MOVE, MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, MOUSEEVENTF_VIRTUALDESK,
MOUSEEVENTF_WHEEL, MOUSEEVENTF_XDOWN, MOUSEEVENTF_XUP, MOUSEINPUT,
};
// X button constants (not exported in windows crate 0.58+)
#[cfg(windows)]
const XBUTTON1: u32 = 0x0001;
#[cfg(windows)]
const XBUTTON2: u32 = 0x0002;
#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{
GetSystemMetrics, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN,
SM_YVIRTUALSCREEN,
};
/// Mouse input controller
pub struct MouseController {
/// Virtual screen dimensions for coordinate translation
#[cfg(windows)]
virtual_screen: VirtualScreen,
}
#[cfg(windows)]
struct VirtualScreen {
x: i32,
y: i32,
width: i32,
height: i32,
}
impl MouseController {
/// Create a new mouse controller
pub fn new() -> Result<Self> {
#[cfg(windows)]
{
let virtual_screen = unsafe {
VirtualScreen {
x: GetSystemMetrics(SM_XVIRTUALSCREEN),
y: GetSystemMetrics(SM_YVIRTUALSCREEN),
width: GetSystemMetrics(SM_CXVIRTUALSCREEN),
height: GetSystemMetrics(SM_CYVIRTUALSCREEN),
}
};
Ok(Self { virtual_screen })
}
#[cfg(not(windows))]
{
anyhow::bail!("Mouse input only supported on Windows")
}
}
/// Move mouse to absolute screen coordinates
#[cfg(windows)]
pub fn move_to(&mut self, x: i32, y: i32) -> Result<()> {
// Convert screen coordinates to normalized absolute coordinates (0-65535)
let norm_x = ((x - self.virtual_screen.x) * 65535) / self.virtual_screen.width;
let norm_y = ((y - self.virtual_screen.y) * 65535) / self.virtual_screen.height;
let input = INPUT {
r#type: INPUT_MOUSE,
Anonymous: INPUT_0 {
mi: MOUSEINPUT {
dx: norm_x,
dy: norm_y,
mouseData: 0,
dwFlags: MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK,
time: 0,
dwExtraInfo: 0,
},
},
};
self.send_input(&[input])
}
/// Press mouse button down
#[cfg(windows)]
pub fn button_down(&mut self, button: MouseButton) -> Result<()> {
let (flags, data) = match button {
MouseButton::Left => (MOUSEEVENTF_LEFTDOWN, 0),
MouseButton::Right => (MOUSEEVENTF_RIGHTDOWN, 0),
MouseButton::Middle => (MOUSEEVENTF_MIDDLEDOWN, 0),
MouseButton::X1 => (MOUSEEVENTF_XDOWN, XBUTTON1),
MouseButton::X2 => (MOUSEEVENTF_XDOWN, XBUTTON2),
};
let input = INPUT {
r#type: INPUT_MOUSE,
Anonymous: INPUT_0 {
mi: MOUSEINPUT {
dx: 0,
dy: 0,
mouseData: data,
dwFlags: flags,
time: 0,
dwExtraInfo: 0,
},
},
};
self.send_input(&[input])
}
/// Release mouse button
#[cfg(windows)]
pub fn button_up(&mut self, button: MouseButton) -> Result<()> {
let (flags, data) = match button {
MouseButton::Left => (MOUSEEVENTF_LEFTUP, 0),
MouseButton::Right => (MOUSEEVENTF_RIGHTUP, 0),
MouseButton::Middle => (MOUSEEVENTF_MIDDLEUP, 0),
MouseButton::X1 => (MOUSEEVENTF_XUP, XBUTTON1),
MouseButton::X2 => (MOUSEEVENTF_XUP, XBUTTON2),
};
let input = INPUT {
r#type: INPUT_MOUSE,
Anonymous: INPUT_0 {
mi: MOUSEINPUT {
dx: 0,
dy: 0,
mouseData: data,
dwFlags: flags,
time: 0,
dwExtraInfo: 0,
},
},
};
self.send_input(&[input])
}
/// Scroll mouse wheel
#[cfg(windows)]
pub fn scroll(&mut self, delta_x: i32, delta_y: i32) -> Result<()> {
let mut inputs = Vec::new();
// Vertical scroll
if delta_y != 0 {
inputs.push(INPUT {
r#type: INPUT_MOUSE,
Anonymous: INPUT_0 {
mi: MOUSEINPUT {
dx: 0,
dy: 0,
mouseData: delta_y as u32,
dwFlags: MOUSEEVENTF_WHEEL,
time: 0,
dwExtraInfo: 0,
},
},
});
}
// Horizontal scroll
if delta_x != 0 {
inputs.push(INPUT {
r#type: INPUT_MOUSE,
Anonymous: INPUT_0 {
mi: MOUSEINPUT {
dx: 0,
dy: 0,
mouseData: delta_x as u32,
dwFlags: MOUSEEVENTF_HWHEEL,
time: 0,
dwExtraInfo: 0,
},
},
});
}
if !inputs.is_empty() {
self.send_input(&inputs)?;
}
Ok(())
}
/// Send input events
#[cfg(windows)]
fn send_input(&self, inputs: &[INPUT]) -> Result<()> {
let sent = unsafe {
SendInput(inputs, std::mem::size_of::<INPUT>() as i32)
};
if sent as usize != inputs.len() {
anyhow::bail!("SendInput failed: sent {} of {} inputs", sent, inputs.len());
}
Ok(())
}
#[cfg(not(windows))]
pub fn move_to(&mut self, _x: i32, _y: i32) -> Result<()> {
anyhow::bail!("Mouse input only supported on Windows")
}
#[cfg(not(windows))]
pub fn button_down(&mut self, _button: MouseButton) -> Result<()> {
anyhow::bail!("Mouse input only supported on Windows")
}
#[cfg(not(windows))]
pub fn button_up(&mut self, _button: MouseButton) -> Result<()> {
anyhow::bail!("Mouse input only supported on Windows")
}
#[cfg(not(windows))]
pub fn scroll(&mut self, _delta_x: i32, _delta_y: i32) -> Result<()> {
anyhow::bail!("Mouse input only supported on Windows")
}
}

View File

@@ -1,417 +0,0 @@
//! Installation and protocol handler registration
//!
//! Handles:
//! - Self-installation to Program Files (with UAC) or LocalAppData (fallback)
//! - Protocol handler registration (guruconnect://)
//! - UAC elevation with graceful fallback
use anyhow::{anyhow, Result};
use tracing::{info, warn, error};
#[cfg(windows)]
use windows::{
core::PCWSTR,
Win32::Foundation::HANDLE,
Win32::Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY},
Win32::System::Threading::{GetCurrentProcess, OpenProcessToken},
Win32::System::Registry::{
RegCreateKeyExW, RegSetValueExW, RegCloseKey, HKEY, HKEY_CLASSES_ROOT,
HKEY_CURRENT_USER, KEY_WRITE, REG_SZ, REG_OPTION_NON_VOLATILE,
},
Win32::UI::Shell::ShellExecuteW,
Win32::UI::WindowsAndMessaging::SW_SHOWNORMAL,
};
#[cfg(windows)]
use std::ffi::OsStr;
#[cfg(windows)]
use std::os::windows::ffi::OsStrExt;
/// Install locations
pub const SYSTEM_INSTALL_PATH: &str = r"C:\Program Files\GuruConnect";
pub const USER_INSTALL_PATH: &str = r"GuruConnect"; // Relative to %LOCALAPPDATA%
/// Check if running with elevated privileges
#[cfg(windows)]
pub fn is_elevated() -> bool {
unsafe {
let mut token_handle = HANDLE::default();
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token_handle).is_err() {
return false;
}
let mut elevation = TOKEN_ELEVATION::default();
let mut size = std::mem::size_of::<TOKEN_ELEVATION>() as u32;
let result = GetTokenInformation(
token_handle,
TokenElevation,
Some(&mut elevation as *mut _ as *mut _),
size,
&mut size,
);
let _ = windows::Win32::Foundation::CloseHandle(token_handle);
result.is_ok() && elevation.TokenIsElevated != 0
}
}
#[cfg(not(windows))]
pub fn is_elevated() -> bool {
unsafe { libc::geteuid() == 0 }
}
/// Get the install path based on elevation status
pub fn get_install_path(elevated: bool) -> std::path::PathBuf {
if elevated {
std::path::PathBuf::from(SYSTEM_INSTALL_PATH)
} else {
let local_app_data = std::env::var("LOCALAPPDATA")
.unwrap_or_else(|_| {
let home = std::env::var("USERPROFILE").unwrap_or_else(|_| ".".to_string());
format!(r"{}\AppData\Local", home)
});
std::path::PathBuf::from(local_app_data).join(USER_INSTALL_PATH)
}
}
/// Get the executable path
pub fn get_exe_path(install_path: &std::path::Path) -> std::path::PathBuf {
install_path.join("guruconnect.exe")
}
/// Attempt to elevate and re-run with install command
#[cfg(windows)]
pub fn try_elevate_and_install() -> Result<bool> {
let exe_path = std::env::current_exe()?;
let exe_path_wide: Vec<u16> = OsStr::new(exe_path.as_os_str())
.encode_wide()
.chain(std::iter::once(0))
.collect();
let verb: Vec<u16> = OsStr::new("runas")
.encode_wide()
.chain(std::iter::once(0))
.collect();
let params: Vec<u16> = OsStr::new("install --elevated")
.encode_wide()
.chain(std::iter::once(0))
.collect();
unsafe {
let result = ShellExecuteW(
None,
PCWSTR(verb.as_ptr()),
PCWSTR(exe_path_wide.as_ptr()),
PCWSTR(params.as_ptr()),
PCWSTR::null(),
SW_SHOWNORMAL,
);
// ShellExecuteW returns > 32 on success
if result.0 as usize > 32 {
info!("UAC elevation requested");
Ok(true)
} else {
warn!("UAC elevation denied or failed");
Ok(false)
}
}
}
#[cfg(not(windows))]
pub fn try_elevate_and_install() -> Result<bool> {
Ok(false)
}
/// Register the guruconnect:// protocol handler
#[cfg(windows)]
pub fn register_protocol_handler(elevated: bool) -> Result<()> {
let install_path = get_install_path(elevated);
let exe_path = get_exe_path(&install_path);
let exe_path_str = exe_path.to_string_lossy();
// Command to execute: "C:\...\guruconnect.exe" "launch" "%1"
let command = format!("\"{}\" launch \"%1\"", exe_path_str);
// Choose registry root based on elevation
let root_key = if elevated {
HKEY_CLASSES_ROOT
} else {
// User-level registration under Software\Classes
HKEY_CURRENT_USER
};
let base_path = if elevated {
"guruconnect"
} else {
r"Software\Classes\guruconnect"
};
unsafe {
// Create guruconnect key
let mut protocol_key = HKEY::default();
let key_path = to_wide(base_path);
let result = RegCreateKeyExW(
root_key,
PCWSTR(key_path.as_ptr()),
0,
PCWSTR::null(),
REG_OPTION_NON_VOLATILE,
KEY_WRITE,
None,
&mut protocol_key,
None,
);
if result.is_err() {
return Err(anyhow!("Failed to create protocol key: {:?}", result));
}
// Set default value (protocol description)
let description = to_wide("GuruConnect Protocol");
let result = RegSetValueExW(
protocol_key,
PCWSTR::null(),
0,
REG_SZ,
Some(&description_to_bytes(&description)),
);
if result.is_err() {
let _ = RegCloseKey(protocol_key);
return Err(anyhow!("Failed to set protocol description: {:?}", result));
}
// Set URL Protocol (empty string indicates this is a protocol handler)
let url_protocol = to_wide("URL Protocol");
let empty = to_wide("");
let result = RegSetValueExW(
protocol_key,
PCWSTR(url_protocol.as_ptr()),
0,
REG_SZ,
Some(&description_to_bytes(&empty)),
);
if result.is_err() {
let _ = RegCloseKey(protocol_key);
return Err(anyhow!("Failed to set URL Protocol: {:?}", result));
}
let _ = RegCloseKey(protocol_key);
// Create shell\open\command key
let command_path = if elevated {
r"guruconnect\shell\open\command"
} else {
r"Software\Classes\guruconnect\shell\open\command"
};
let command_key_path = to_wide(command_path);
let mut command_key = HKEY::default();
let result = RegCreateKeyExW(
root_key,
PCWSTR(command_key_path.as_ptr()),
0,
PCWSTR::null(),
REG_OPTION_NON_VOLATILE,
KEY_WRITE,
None,
&mut command_key,
None,
);
if result.is_err() {
return Err(anyhow!("Failed to create command key: {:?}", result));
}
// Set the command
let command_wide = to_wide(&command);
let result = RegSetValueExW(
command_key,
PCWSTR::null(),
0,
REG_SZ,
Some(&description_to_bytes(&command_wide)),
);
if result.is_err() {
let _ = RegCloseKey(command_key);
return Err(anyhow!("Failed to set command: {:?}", result));
}
let _ = RegCloseKey(command_key);
}
info!("Protocol handler registered: guruconnect://");
Ok(())
}
#[cfg(not(windows))]
pub fn register_protocol_handler(_elevated: bool) -> Result<()> {
warn!("Protocol handler registration not supported on this platform");
Ok(())
}
/// Install the application
pub fn install(force_user_install: bool) -> Result<()> {
let elevated = is_elevated();
// If not elevated and not forcing user install, try to elevate
if !elevated && !force_user_install {
info!("Attempting UAC elevation for system-wide install...");
match try_elevate_and_install() {
Ok(true) => {
// Elevation was requested, exit this instance
// The elevated instance will continue the install
info!("Elevated process started, exiting current instance");
std::process::exit(0);
}
Ok(false) => {
info!("UAC denied, falling back to user install");
}
Err(e) => {
warn!("Elevation failed: {}, falling back to user install", e);
}
}
}
let install_path = get_install_path(elevated);
let exe_path = get_exe_path(&install_path);
info!("Installing to: {}", install_path.display());
// Create install directory
std::fs::create_dir_all(&install_path)?;
// Copy ourselves to install location
let current_exe = std::env::current_exe()?;
if current_exe != exe_path {
std::fs::copy(&current_exe, &exe_path)?;
info!("Copied executable to: {}", exe_path.display());
}
// Register protocol handler
register_protocol_handler(elevated)?;
info!("Installation complete!");
if elevated {
info!("Installed system-wide to: {}", install_path.display());
} else {
info!("Installed for current user to: {}", install_path.display());
}
Ok(())
}
/// Check if the guruconnect:// protocol handler is registered
#[cfg(windows)]
pub fn is_protocol_handler_registered() -> bool {
use windows::Win32::System::Registry::{
RegOpenKeyExW, RegCloseKey, HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, KEY_READ,
};
unsafe {
// Check system-wide registration (HKCR\guruconnect)
let mut key = HKEY::default();
let key_path = to_wide("guruconnect");
if RegOpenKeyExW(
HKEY_CLASSES_ROOT,
PCWSTR(key_path.as_ptr()),
0,
KEY_READ,
&mut key,
).is_ok() {
let _ = RegCloseKey(key);
return true;
}
// Check user-level registration (HKCU\Software\Classes\guruconnect)
let key_path = to_wide(r"Software\Classes\guruconnect");
if RegOpenKeyExW(
HKEY_CURRENT_USER,
PCWSTR(key_path.as_ptr()),
0,
KEY_READ,
&mut key,
).is_ok() {
let _ = RegCloseKey(key);
return true;
}
}
false
}
#[cfg(not(windows))]
pub fn is_protocol_handler_registered() -> bool {
// On non-Windows, assume not registered (or check ~/.local/share/applications)
false
}
/// Parse a guruconnect:// URL and extract session parameters
pub fn parse_protocol_url(url_str: &str) -> Result<(String, String, Option<String>)> {
// Expected formats:
// guruconnect://view/SESSION_ID
// guruconnect://view/SESSION_ID?token=API_KEY
// guruconnect://connect/SESSION_ID?server=wss://...&token=API_KEY
//
// Note: In URL parsing, "view" becomes the host, SESSION_ID is the path
let url = url::Url::parse(url_str)
.map_err(|e| anyhow!("Invalid URL: {}", e))?;
if url.scheme() != "guruconnect" {
return Err(anyhow!("Invalid scheme: expected guruconnect://"));
}
// The "action" (view/connect) is parsed as the host
let action = url.host_str()
.ok_or_else(|| anyhow!("Missing action in URL"))?;
// The session ID is the first path segment
let path = url.path().trim_start_matches('/');
info!("URL path: '{}', host: '{:?}'", path, url.host_str());
let session_id = if path.is_empty() {
return Err(anyhow!("Invalid URL: Missing session ID (path was empty, full URL: {})", url_str));
} else {
path.split('/').next().unwrap_or("").to_string()
};
if session_id.is_empty() {
return Err(anyhow!("Missing session ID"));
}
// Extract query parameters
let mut server = None;
let mut token = None;
for (key, value) in url.query_pairs() {
match key.as_ref() {
"server" => server = Some(value.to_string()),
"token" | "api_key" => token = Some(value.to_string()),
_ => {}
}
}
// Default server if not specified
let server = server.unwrap_or_else(|| "wss://connect.azcomputerguru.com/ws/viewer".to_string());
match action {
"view" | "connect" => Ok((server, session_id, token)),
_ => Err(anyhow!("Unknown action: {}", action)),
}
}
// Helper functions for Windows registry operations
#[cfg(windows)]
fn to_wide(s: &str) -> Vec<u16> {
OsStr::new(s)
.encode_wide()
.chain(std::iter::once(0))
.collect()
}
#[cfg(windows)]
fn description_to_bytes(wide: &[u16]) -> Vec<u8> {
wide.iter()
.flat_map(|w| w.to_le_bytes())
.collect()
}

View File

@@ -1,571 +0,0 @@
//! GuruConnect - Remote Desktop Agent and Viewer
//!
//! Single binary for both agent (receiving connections) and viewer (initiating connections).
//!
//! Usage:
//! guruconnect agent - Run as background agent
//! guruconnect view <session_id> - View a remote session
//! guruconnect install - Install and register protocol handler
//! guruconnect launch <url> - Handle guruconnect:// URL
//! guruconnect [support_code] - Legacy: run agent with support code
// Hide console window by default on Windows (release builds)
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
mod capture;
mod chat;
mod config;
mod encoder;
mod input;
mod install;
mod sas_client;
mod session;
mod startup;
mod transport;
mod tray;
mod update;
mod viewer;
pub mod proto {
include!(concat!(env!("OUT_DIR"), "/guruconnect.rs"));
}
/// Build information embedded at compile time
pub mod build_info {
/// Cargo package version (from Cargo.toml)
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
/// Git commit hash (short, 8 chars)
pub const GIT_HASH: &str = env!("GIT_HASH");
/// Git commit hash (full)
pub const GIT_HASH_FULL: &str = env!("GIT_HASH_FULL");
/// Git branch name
pub const GIT_BRANCH: &str = env!("GIT_BRANCH");
/// Git dirty state ("clean" or "dirty")
pub const GIT_DIRTY: &str = env!("GIT_DIRTY");
/// Git commit date
pub const GIT_COMMIT_DATE: &str = env!("GIT_COMMIT_DATE");
/// Build timestamp (UTC)
pub const BUILD_TIMESTAMP: &str = env!("BUILD_TIMESTAMP");
/// Build profile (debug/release)
pub const BUILD_PROFILE: &str = env!("BUILD_PROFILE");
/// Target triple (e.g., x86_64-pc-windows-msvc)
pub const BUILD_TARGET: &str = env!("BUILD_TARGET");
/// Short version string for display (version + git hash)
pub fn short_version() -> String {
if GIT_DIRTY == "dirty" {
format!("{}-{}-dirty", VERSION, GIT_HASH)
} else {
format!("{}-{}", VERSION, GIT_HASH)
}
}
/// Full version string with all details
pub fn full_version() -> String {
format!(
"GuruConnect v{}\n\
Git: {} ({})\n\
Branch: {}\n\
Commit: {}\n\
Built: {}\n\
Profile: {}\n\
Target: {}",
VERSION,
GIT_HASH,
GIT_DIRTY,
GIT_BRANCH,
GIT_COMMIT_DATE,
BUILD_TIMESTAMP,
BUILD_PROFILE,
BUILD_TARGET
)
}
}
use anyhow::Result;
use clap::{Parser, Subcommand};
use tracing::{info, error, warn, Level};
use tracing_subscriber::FmtSubscriber;
#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_OK, MB_ICONINFORMATION, MB_ICONERROR};
#[cfg(windows)]
use windows::core::PCWSTR;
#[cfg(windows)]
use windows::Win32::System::Console::{AllocConsole, GetConsoleWindow};
#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_SHOW};
/// GuruConnect Remote Desktop
#[derive(Parser)]
#[command(name = "guruconnect")]
#[command(version = concat!(env!("CARGO_PKG_VERSION"), "-", env!("GIT_HASH")), about = "Remote desktop agent and viewer")]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
/// Support code for legacy mode (runs agent with code)
#[arg(value_name = "SUPPORT_CODE")]
support_code: Option<String>,
/// Enable verbose logging
#[arg(short, long, global = true)]
verbose: bool,
/// Internal flag: set after auto-update to trigger cleanup
#[arg(long, hide = true)]
post_update: bool,
}
#[derive(Subcommand)]
enum Commands {
/// Run as background agent (receive remote connections)
Agent {
/// Support code for one-time session
#[arg(short, long)]
code: Option<String>,
},
/// View a remote session (connect to an agent)
View {
/// Session ID to connect to
session_id: String,
/// Server URL
#[arg(short, long, default_value = "wss://connect.azcomputerguru.com/ws/viewer")]
server: String,
/// API key for authentication
#[arg(short, long, default_value = "")]
api_key: String,
},
/// Install GuruConnect and register protocol handler
Install {
/// Skip UAC elevation, install for current user only
#[arg(long)]
user_only: bool,
/// Called internally when running elevated
#[arg(long, hide = true)]
elevated: bool,
},
/// Uninstall GuruConnect
Uninstall,
/// Handle a guruconnect:// protocol URL
Launch {
/// The guruconnect:// URL to handle
url: String,
},
/// Show detailed version and build information
#[command(name = "version-info")]
VersionInfo,
}
fn main() -> Result<()> {
let cli = Cli::parse();
// Initialize logging
let level = if cli.verbose { Level::DEBUG } else { Level::INFO };
FmtSubscriber::builder()
.with_max_level(level)
.with_target(true)
.with_thread_ids(true)
.init();
info!("GuruConnect {} ({})", build_info::short_version(), build_info::BUILD_TARGET);
info!("Built: {} | Commit: {}", build_info::BUILD_TIMESTAMP, build_info::GIT_COMMIT_DATE);
// Handle post-update cleanup
if cli.post_update {
info!("Post-update mode: cleaning up old executable");
update::cleanup_post_update();
}
match cli.command {
Some(Commands::Agent { code }) => {
run_agent_mode(code)
}
Some(Commands::View { session_id, server, api_key }) => {
run_viewer_mode(&server, &session_id, &api_key)
}
Some(Commands::Install { user_only, elevated }) => {
run_install(user_only || elevated)
}
Some(Commands::Uninstall) => {
run_uninstall()
}
Some(Commands::Launch { url }) => {
run_launch(&url)
}
Some(Commands::VersionInfo) => {
// Show detailed version info (allocate console on Windows for visibility)
#[cfg(windows)]
show_debug_console();
println!("{}", build_info::full_version());
Ok(())
}
None => {
// No subcommand - detect mode from filename or embedded config
// Legacy: if support_code arg provided, use that
if let Some(code) = cli.support_code {
return run_agent_mode(Some(code));
}
// Detect run mode from filename
use config::RunMode;
match config::Config::detect_run_mode() {
RunMode::Viewer => {
// Filename indicates viewer-only (e.g., "GuruConnect-Viewer.exe")
info!("Viewer mode detected from filename");
if !install::is_protocol_handler_registered() {
info!("Installing protocol handler for viewer");
run_install(false)
} else {
info!("Viewer already installed, nothing to do");
show_message_box("GuruConnect Viewer", "GuruConnect viewer is installed.\n\nUse guruconnect:// links to connect to remote sessions.");
Ok(())
}
}
RunMode::TempSupport(code) => {
// Filename contains support code (e.g., "GuruConnect-123456.exe")
info!("Temp support session detected from filename: {}", code);
run_agent_mode(Some(code))
}
RunMode::PermanentAgent => {
// Embedded config found - run as permanent agent
info!("Permanent agent mode detected (embedded config)");
if !install::is_protocol_handler_registered() {
// First run - install then run as agent
info!("First run - installing agent");
if let Err(e) = install::install(false) {
warn!("Installation failed: {}", e);
}
}
run_agent_mode(None)
}
RunMode::Default => {
// No special mode detected - use legacy logic
if !install::is_protocol_handler_registered() {
// Protocol handler not registered - user likely downloaded from web
info!("Protocol handler not registered, running installer");
run_install(false)
} else if config::Config::has_agent_config() {
// Has agent config - run as agent
info!("Agent config found, running as agent");
run_agent_mode(None)
} else {
// Viewer-only installation - just exit silently
info!("Viewer-only installation, exiting");
Ok(())
}
}
}
}
}
}
/// Run in agent mode (receive remote connections)
fn run_agent_mode(support_code: Option<String>) -> Result<()> {
info!("Running in agent mode");
// Check elevation status
if install::is_elevated() {
info!("Running with elevated (administrator) privileges");
} else {
info!("Running with standard user privileges");
}
// Load configuration
let mut config = config::Config::load()?;
// Set support code if provided
if let Some(code) = support_code {
info!("Support code: {}", code);
config.support_code = Some(code);
}
info!("Server: {}", config.server_url);
if let Some(ref company) = config.company {
info!("Company: {}", company);
}
if let Some(ref site) = config.site {
info!("Site: {}", site);
}
// Run the agent
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(run_agent(config))
}
/// Run in viewer mode (connect to remote session)
fn run_viewer_mode(server: &str, session_id: &str, api_key: &str) -> Result<()> {
info!("Running in viewer mode");
info!("Connecting to session: {}", session_id);
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(viewer::run(server, session_id, api_key))
}
/// Handle guruconnect:// URL launch
fn run_launch(url: &str) -> Result<()> {
info!("Handling protocol URL: {}", url);
match install::parse_protocol_url(url) {
Ok((server, session_id, token)) => {
let api_key = token.unwrap_or_default();
run_viewer_mode(&server, &session_id, &api_key)
}
Err(e) => {
error!("Failed to parse URL: {}", e);
show_error_box("GuruConnect", &format!("Invalid URL: {}", e));
Err(e)
}
}
}
/// Install GuruConnect
fn run_install(force_user_install: bool) -> Result<()> {
info!("Installing GuruConnect...");
match install::install(force_user_install) {
Ok(()) => {
show_message_box("GuruConnect", "Installation complete!\n\nYou can now use guruconnect:// links.");
Ok(())
}
Err(e) => {
error!("Installation failed: {}", e);
show_error_box("GuruConnect", &format!("Installation failed: {}", e));
Err(e)
}
}
}
/// Uninstall GuruConnect
fn run_uninstall() -> Result<()> {
info!("Uninstalling GuruConnect...");
// Remove from startup
if let Err(e) = startup::remove_from_startup() {
warn!("Failed to remove from startup: {}", e);
}
// TODO: Remove registry keys for protocol handler
// TODO: Remove install directory
show_message_box("GuruConnect", "Uninstall complete.");
Ok(())
}
/// Show a message box (Windows only)
#[cfg(windows)]
fn show_message_box(title: &str, message: &str) {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
let title_wide: Vec<u16> = OsStr::new(title)
.encode_wide()
.chain(std::iter::once(0))
.collect();
let message_wide: Vec<u16> = OsStr::new(message)
.encode_wide()
.chain(std::iter::once(0))
.collect();
unsafe {
MessageBoxW(
None,
PCWSTR(message_wide.as_ptr()),
PCWSTR(title_wide.as_ptr()),
MB_OK | MB_ICONINFORMATION,
);
}
}
#[cfg(not(windows))]
fn show_message_box(_title: &str, message: &str) {
println!("{}", message);
}
/// Show an error message box (Windows only)
#[cfg(windows)]
fn show_error_box(title: &str, message: &str) {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
let title_wide: Vec<u16> = OsStr::new(title)
.encode_wide()
.chain(std::iter::once(0))
.collect();
let message_wide: Vec<u16> = OsStr::new(message)
.encode_wide()
.chain(std::iter::once(0))
.collect();
unsafe {
MessageBoxW(
None,
PCWSTR(message_wide.as_ptr()),
PCWSTR(title_wide.as_ptr()),
MB_OK | MB_ICONERROR,
);
}
}
#[cfg(not(windows))]
fn show_error_box(_title: &str, message: &str) {
eprintln!("ERROR: {}", message);
}
/// Show debug console window (Windows only)
#[cfg(windows)]
#[allow(dead_code)]
fn show_debug_console() {
unsafe {
let hwnd = GetConsoleWindow();
if hwnd.0 == std::ptr::null_mut() {
let _ = AllocConsole();
} else {
let _ = ShowWindow(hwnd, SW_SHOW);
}
}
}
#[cfg(not(windows))]
#[allow(dead_code)]
fn show_debug_console() {}
/// Clean up before exiting
fn cleanup_on_exit() {
info!("Cleaning up before exit");
if let Err(e) = startup::remove_from_startup() {
warn!("Failed to remove from startup: {}", e);
}
}
/// Run the agent main loop
async fn run_agent(config: config::Config) -> Result<()> {
let elevated = install::is_elevated();
let mut session = session::SessionManager::new(config.clone(), elevated);
let is_support_session = config.support_code.is_some();
let hostname = config.hostname();
// Add to startup
if let Err(e) = startup::add_to_startup() {
warn!("Failed to add to startup: {}", e);
}
// Create tray icon
let tray = match tray::TrayController::new(&hostname, config.support_code.as_deref(), is_support_session) {
Ok(t) => {
info!("Tray icon created");
Some(t)
}
Err(e) => {
warn!("Failed to create tray icon: {}", e);
None
}
};
// Create chat controller
let chat_ctrl = chat::ChatController::new();
// Connect to server and run main loop
loop {
info!("Connecting to server...");
if is_support_session {
if let Some(ref t) = tray {
if t.exit_requested() {
info!("Exit requested by user");
cleanup_on_exit();
return Ok(());
}
}
}
match session.connect().await {
Ok(_) => {
info!("Connected to server");
if let Some(ref t) = tray {
t.update_status("Status: Connected");
}
if let Err(e) = session.run_with_tray(tray.as_ref(), chat_ctrl.as_ref()).await {
let error_msg = e.to_string();
if error_msg.contains("USER_EXIT") {
info!("Session ended by user");
cleanup_on_exit();
return Ok(());
}
if error_msg.contains("SESSION_CANCELLED") {
info!("Session was cancelled by technician");
cleanup_on_exit();
show_message_box("Support Session Ended", "The support session was cancelled.");
return Ok(());
}
if error_msg.contains("ADMIN_DISCONNECT") {
info!("Session disconnected by administrator - uninstalling");
if let Err(e) = startup::uninstall() {
warn!("Uninstall failed: {}", e);
}
show_message_box("Remote Session Ended", "The session was ended by the administrator.");
return Ok(());
}
if error_msg.contains("ADMIN_UNINSTALL") {
info!("Uninstall command received from server - uninstalling");
if let Err(e) = startup::uninstall() {
warn!("Uninstall failed: {}", e);
}
show_message_box("GuruConnect Removed", "This computer has been removed from remote management.");
return Ok(());
}
if error_msg.contains("ADMIN_RESTART") {
info!("Restart command received - will reconnect");
// Don't exit, just let the loop continue to reconnect
} else {
error!("Session error: {}", e);
}
}
}
Err(e) => {
let error_msg = e.to_string();
if error_msg.contains("cancelled") {
info!("Support code was cancelled");
cleanup_on_exit();
show_message_box("Support Session Cancelled", "This support session has been cancelled.");
return Ok(());
}
error!("Connection failed: {}", e);
}
}
if is_support_session {
info!("Support session ended, not reconnecting");
cleanup_on_exit();
return Ok(());
}
info!("Reconnecting in 5 seconds...");
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
}
}

View File

@@ -1,106 +0,0 @@
//! SAS Client - Named pipe client for communicating with GuruConnect SAS Service
//!
//! The SAS Service runs as SYSTEM and handles Ctrl+Alt+Del requests.
//! This client sends commands to the service via named pipe.
use std::fs::OpenOptions;
use std::io::{Read, Write};
use std::time::Duration;
use anyhow::{Context, Result};
use tracing::{debug, error, info, warn};
const PIPE_NAME: &str = r"\\.\pipe\guruconnect-sas";
const TIMEOUT_MS: u64 = 5000;
/// Request Ctrl+Alt+Del (Secure Attention Sequence) via the SAS service
pub fn request_sas() -> Result<()> {
info!("Requesting SAS via service pipe...");
// Try to connect to the pipe
let mut pipe = match OpenOptions::new()
.read(true)
.write(true)
.open(PIPE_NAME)
{
Ok(p) => p,
Err(e) => {
warn!("Failed to connect to SAS service pipe: {}", e);
return Err(anyhow::anyhow!(
"SAS service not available. Install with: guruconnect-sas-service install"
));
}
};
debug!("Connected to SAS service pipe");
// Send the command
pipe.write_all(b"sas\n")
.context("Failed to send command to SAS service")?;
// Read the response
let mut response = [0u8; 64];
let n = pipe.read(&mut response)
.context("Failed to read response from SAS service")?;
let response_str = String::from_utf8_lossy(&response[..n]);
let response_str = response_str.trim();
debug!("SAS service response: {}", response_str);
match response_str {
"ok" => {
info!("SAS request successful");
Ok(())
}
"error" => {
error!("SAS service reported an error");
Err(anyhow::anyhow!("SAS service failed to send Ctrl+Alt+Del"))
}
_ => {
error!("Unexpected response from SAS service: {}", response_str);
Err(anyhow::anyhow!("Unexpected SAS service response: {}", response_str))
}
}
}
/// Check if the SAS service is available
pub fn is_service_available() -> bool {
// Try to open the pipe
if let Ok(mut pipe) = OpenOptions::new()
.read(true)
.write(true)
.open(PIPE_NAME)
{
// Send a ping command
if pipe.write_all(b"ping\n").is_ok() {
let mut response = [0u8; 64];
if let Ok(n) = pipe.read(&mut response) {
let response_str = String::from_utf8_lossy(&response[..n]);
return response_str.trim() == "pong";
}
}
}
false
}
/// Get information about SAS service status
pub fn get_service_status() -> String {
if is_service_available() {
"SAS service is running and responding".to_string()
} else {
"SAS service is not available".to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_service_check() {
// This test just checks the function runs without panicking
let available = is_service_available();
println!("SAS service available: {}", available);
}
}

View File

@@ -1,582 +0,0 @@
//! Session management for the agent
//!
//! Handles the lifecycle of a remote session including:
//! - Connection to server
//! - Idle mode (heartbeat only, minimal resources)
//! - Active/streaming mode (capture and send frames)
//! - Input event handling
#[cfg(windows)]
use windows::Win32::System::Console::{AllocConsole, GetConsoleWindow};
#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_SHOW};
use crate::capture::{self, Capturer, Display};
use crate::chat::{ChatController, ChatMessage as ChatMsg};
use crate::config::Config;
use crate::encoder::{self, Encoder};
use crate::input::InputController;
/// Show the debug console window (Windows only)
#[cfg(windows)]
fn show_debug_console() {
unsafe {
let hwnd = GetConsoleWindow();
if hwnd.0 == std::ptr::null_mut() {
let _ = AllocConsole();
tracing::info!("Debug console window opened");
} else {
let _ = ShowWindow(hwnd, SW_SHOW);
tracing::info!("Debug console window shown");
}
}
}
#[cfg(not(windows))]
fn show_debug_console() {
// No-op on non-Windows platforms
}
use crate::proto::{Message, message, ChatMessage, AgentStatus, Heartbeat, HeartbeatAck};
use crate::transport::WebSocketTransport;
use crate::tray::{TrayController, TrayAction};
use anyhow::Result;
use std::time::{Duration, Instant};
// Heartbeat interval (30 seconds)
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(30);
// Status report interval (60 seconds)
const STATUS_INTERVAL: Duration = Duration::from_secs(60);
// Update check interval (1 hour)
const UPDATE_CHECK_INTERVAL: Duration = Duration::from_secs(3600);
/// Session manager handles the remote control session
pub struct SessionManager {
config: Config,
transport: Option<WebSocketTransport>,
state: SessionState,
// Lazy-initialized streaming resources
capturer: Option<Box<dyn Capturer>>,
encoder: Option<Box<dyn Encoder>>,
input: Option<InputController>,
// Streaming state
current_viewer_id: Option<String>,
// System info for status reports
hostname: String,
is_elevated: bool,
start_time: Instant,
}
#[derive(Debug, Clone, PartialEq)]
enum SessionState {
Disconnected,
Connecting,
Idle, // Connected but not streaming - minimal resource usage
Streaming, // Actively capturing and sending frames
}
impl SessionManager {
/// Create a new session manager
pub fn new(config: Config, is_elevated: bool) -> Self {
let hostname = config.hostname();
Self {
config,
transport: None,
state: SessionState::Disconnected,
capturer: None,
encoder: None,
input: None,
current_viewer_id: None,
hostname,
is_elevated,
start_time: Instant::now(),
}
}
/// Connect to the server
pub async fn connect(&mut self) -> Result<()> {
self.state = SessionState::Connecting;
let transport = WebSocketTransport::connect(
&self.config.server_url,
&self.config.agent_id,
&self.config.api_key,
Some(&self.hostname),
self.config.support_code.as_deref(),
).await?;
self.transport = Some(transport);
self.state = SessionState::Idle; // Start in idle mode
tracing::info!("Connected to server, entering idle mode");
Ok(())
}
/// Initialize streaming resources (capturer, encoder, input)
fn init_streaming(&mut self) -> Result<()> {
if self.capturer.is_some() {
return Ok(()); // Already initialized
}
tracing::info!("Initializing streaming resources...");
tracing::info!("Capture config: use_dxgi={}, gdi_fallback={}, fps={}",
self.config.capture.use_dxgi, self.config.capture.gdi_fallback, self.config.capture.fps);
// Get primary display with panic protection
tracing::debug!("Enumerating displays...");
let primary_display = match std::panic::catch_unwind(|| capture::primary_display()) {
Ok(result) => result?,
Err(e) => {
tracing::error!("Panic during display enumeration: {:?}", e);
return Err(anyhow::anyhow!("Display enumeration panicked"));
}
};
tracing::info!("Using display: {} ({}x{})",
primary_display.name, primary_display.width, primary_display.height);
// Create capturer with panic protection
// Force GDI mode if DXGI fails or panics
tracing::debug!("Creating capturer (DXGI={})...", self.config.capture.use_dxgi);
let capturer = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
capture::create_capturer(
primary_display.clone(),
self.config.capture.use_dxgi,
self.config.capture.gdi_fallback,
)
})) {
Ok(result) => result?,
Err(e) => {
tracing::error!("Panic during capturer creation: {:?}", e);
// Try GDI-only as last resort
tracing::warn!("Attempting GDI-only capture after DXGI panic...");
capture::create_capturer(primary_display.clone(), false, false)?
}
};
self.capturer = Some(capturer);
tracing::info!("Capturer created successfully");
// Create encoder with panic protection
tracing::debug!("Creating encoder (codec={}, quality={})...",
self.config.encoding.codec, self.config.encoding.quality);
let encoder = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
encoder::create_encoder(
&self.config.encoding.codec,
self.config.encoding.quality,
)
})) {
Ok(result) => result?,
Err(e) => {
tracing::error!("Panic during encoder creation: {:?}", e);
return Err(anyhow::anyhow!("Encoder creation panicked"));
}
};
self.encoder = Some(encoder);
tracing::info!("Encoder created successfully");
// Create input controller with panic protection
tracing::debug!("Creating input controller...");
let input = match std::panic::catch_unwind(InputController::new) {
Ok(result) => result?,
Err(e) => {
tracing::error!("Panic during input controller creation: {:?}", e);
return Err(anyhow::anyhow!("Input controller creation panicked"));
}
};
self.input = Some(input);
tracing::info!("Streaming resources initialized successfully");
Ok(())
}
/// Release streaming resources to save CPU/memory when idle
fn release_streaming(&mut self) {
if self.capturer.is_some() {
tracing::info!("Releasing streaming resources");
self.capturer = None;
self.encoder = None;
self.input = None;
self.current_viewer_id = None;
}
}
/// Get display count for status reports
fn get_display_count(&self) -> i32 {
capture::enumerate_displays().map(|d| d.len() as i32).unwrap_or(1)
}
/// Send agent status to server
async fn send_status(&mut self) -> Result<()> {
let status = AgentStatus {
hostname: self.hostname.clone(),
os_version: std::env::consts::OS.to_string(),
is_elevated: self.is_elevated,
uptime_secs: self.start_time.elapsed().as_secs() as i64,
display_count: self.get_display_count(),
is_streaming: self.state == SessionState::Streaming,
agent_version: crate::build_info::short_version(),
organization: self.config.company.clone().unwrap_or_default(),
site: self.config.site.clone().unwrap_or_default(),
tags: self.config.tags.clone(),
};
let msg = Message {
payload: Some(message::Payload::AgentStatus(status)),
};
if let Some(transport) = self.transport.as_mut() {
transport.send(msg).await?;
}
Ok(())
}
/// Send heartbeat to server
async fn send_heartbeat(&mut self) -> Result<()> {
let heartbeat = Heartbeat {
timestamp: chrono::Utc::now().timestamp_millis(),
};
let msg = Message {
payload: Some(message::Payload::Heartbeat(heartbeat)),
};
if let Some(transport) = self.transport.as_mut() {
transport.send(msg).await?;
}
Ok(())
}
/// Run the session main loop with tray and chat event processing
pub async fn run_with_tray(&mut self, tray: Option<&TrayController>, chat: Option<&ChatController>) -> Result<()> {
if self.transport.is_none() {
anyhow::bail!("Not connected");
}
// Send initial status
self.send_status().await?;
// Timing for heartbeat and status
let mut last_heartbeat = Instant::now();
let mut last_status = Instant::now();
let mut last_frame_time = Instant::now();
let mut last_update_check = Instant::now();
let frame_interval = Duration::from_millis(1000 / self.config.capture.fps as u64);
// Main loop
loop {
// Process tray events
if let Some(t) = tray {
if let Some(action) = t.process_events() {
match action {
TrayAction::EndSession => {
tracing::info!("User requested session end via tray");
return Err(anyhow::anyhow!("USER_EXIT: Session ended by user"));
}
TrayAction::ShowDetails => {
tracing::info!("User requested details (not yet implemented)");
}
TrayAction::ShowDebugWindow => {
show_debug_console();
}
}
}
if t.exit_requested() {
tracing::info!("Exit requested via tray");
return Err(anyhow::anyhow!("USER_EXIT: Exit requested by user"));
}
}
// Process incoming messages
let messages: Vec<Message> = {
let transport = self.transport.as_mut().unwrap();
let mut msgs = Vec::new();
while let Some(msg) = transport.try_recv()? {
msgs.push(msg);
}
msgs
};
for msg in messages {
// Handle chat messages specially
if let Some(message::Payload::ChatMessage(chat_msg)) = &msg.payload {
if let Some(c) = chat {
c.add_message(ChatMsg {
id: chat_msg.id.clone(),
sender: chat_msg.sender.clone(),
content: chat_msg.content.clone(),
timestamp: chat_msg.timestamp,
});
}
continue;
}
// Handle control messages that affect state
if let Some(ref payload) = msg.payload {
match payload {
message::Payload::StartStream(start) => {
tracing::info!("StartStream received from viewer: {}", start.viewer_id);
if let Err(e) = self.init_streaming() {
tracing::error!("Failed to init streaming: {}", e);
} else {
self.state = SessionState::Streaming;
self.current_viewer_id = Some(start.viewer_id.clone());
tracing::info!("Now streaming to viewer {}", start.viewer_id);
}
continue;
}
message::Payload::StopStream(stop) => {
tracing::info!("StopStream received for viewer: {}", stop.viewer_id);
// Only stop if it matches current viewer
if self.current_viewer_id.as_ref() == Some(&stop.viewer_id) {
self.release_streaming();
self.state = SessionState::Idle;
tracing::info!("Stopped streaming, returning to idle mode");
}
continue;
}
message::Payload::Heartbeat(hb) => {
// Respond to server heartbeat with ack
let ack = HeartbeatAck {
client_timestamp: hb.timestamp,
server_timestamp: chrono::Utc::now().timestamp_millis(),
};
let ack_msg = Message {
payload: Some(message::Payload::HeartbeatAck(ack)),
};
if let Some(transport) = self.transport.as_mut() {
let _ = transport.send(ack_msg).await;
}
continue;
}
_ => {}
}
}
// Handle other messages (input events, disconnect, etc.)
self.handle_message(msg).await?;
}
// Check for outgoing chat messages
if let Some(c) = chat {
if let Some(outgoing) = c.poll_outgoing() {
let chat_proto = ChatMessage {
id: outgoing.id,
sender: "client".to_string(),
content: outgoing.content,
timestamp: outgoing.timestamp,
};
let msg = Message {
payload: Some(message::Payload::ChatMessage(chat_proto)),
};
let transport = self.transport.as_mut().unwrap();
transport.send(msg).await?;
}
}
// State-specific behavior
match self.state {
SessionState::Idle => {
// In idle mode, just send heartbeats and status periodically
if last_heartbeat.elapsed() >= HEARTBEAT_INTERVAL {
last_heartbeat = Instant::now();
if let Err(e) = self.send_heartbeat().await {
tracing::warn!("Failed to send heartbeat: {}", e);
}
}
if last_status.elapsed() >= STATUS_INTERVAL {
last_status = Instant::now();
if let Err(e) = self.send_status().await {
tracing::warn!("Failed to send status: {}", e);
}
}
// Periodic update check (only for persistent agents, not support sessions)
if self.config.support_code.is_none() && last_update_check.elapsed() >= UPDATE_CHECK_INTERVAL {
last_update_check = Instant::now();
let server_url = self.config.server_url.replace("/ws/agent", "").replace("wss://", "https://").replace("ws://", "http://");
match crate::update::check_for_update(&server_url).await {
Ok(Some(version_info)) => {
tracing::info!("Update available: {} -> {}", crate::build_info::VERSION, version_info.latest_version);
if let Err(e) = crate::update::perform_update(&version_info).await {
tracing::error!("Auto-update failed: {}", e);
}
}
Ok(None) => {
tracing::debug!("No update available");
}
Err(e) => {
tracing::debug!("Update check failed: {}", e);
}
}
}
// Longer sleep in idle mode to reduce CPU usage
tokio::time::sleep(Duration::from_millis(100)).await;
}
SessionState::Streaming => {
// In streaming mode, capture and send frames
if last_frame_time.elapsed() >= frame_interval {
last_frame_time = Instant::now();
if let (Some(capturer), Some(encoder)) =
(self.capturer.as_mut(), self.encoder.as_mut())
{
if let Ok(Some(frame)) = capturer.capture() {
if let Ok(encoded) = encoder.encode(&frame) {
if encoded.size > 0 {
let msg = Message {
payload: Some(message::Payload::VideoFrame(encoded.frame)),
};
let transport = self.transport.as_mut().unwrap();
if let Err(e) = transport.send(msg).await {
tracing::warn!("Failed to send frame: {}", e);
}
}
}
}
}
}
// Short sleep in streaming mode
tokio::time::sleep(Duration::from_millis(1)).await;
}
_ => {
// Disconnected or connecting - shouldn't be in main loop
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
// Check if still connected
if let Some(transport) = self.transport.as_ref() {
if !transport.is_connected() {
tracing::warn!("Connection lost");
break;
}
} else {
tracing::warn!("Transport is None");
break;
}
}
self.release_streaming();
self.state = SessionState::Disconnected;
Ok(())
}
/// Handle incoming message from server
async fn handle_message(&mut self, msg: Message) -> Result<()> {
match msg.payload {
Some(message::Payload::MouseEvent(mouse)) => {
if let Some(input) = self.input.as_mut() {
use crate::proto::MouseEventType;
use crate::input::MouseButton;
match MouseEventType::try_from(mouse.event_type).unwrap_or(MouseEventType::MouseMove) {
MouseEventType::MouseMove => {
input.mouse_move(mouse.x, mouse.y)?;
}
MouseEventType::MouseDown => {
input.mouse_move(mouse.x, mouse.y)?;
if let Some(ref buttons) = mouse.buttons {
if buttons.left { input.mouse_click(MouseButton::Left, true)?; }
if buttons.right { input.mouse_click(MouseButton::Right, true)?; }
if buttons.middle { input.mouse_click(MouseButton::Middle, true)?; }
}
}
MouseEventType::MouseUp => {
if let Some(ref buttons) = mouse.buttons {
if buttons.left { input.mouse_click(MouseButton::Left, false)?; }
if buttons.right { input.mouse_click(MouseButton::Right, false)?; }
if buttons.middle { input.mouse_click(MouseButton::Middle, false)?; }
}
}
MouseEventType::MouseWheel => {
input.mouse_scroll(mouse.wheel_delta_x, mouse.wheel_delta_y)?;
}
}
}
}
Some(message::Payload::KeyEvent(key)) => {
if let Some(input) = self.input.as_mut() {
input.key_event(key.vk_code as u16, key.down)?;
}
}
Some(message::Payload::SpecialKey(special)) => {
if let Some(input) = self.input.as_mut() {
use crate::proto::SpecialKey;
match SpecialKey::try_from(special.key).ok() {
Some(SpecialKey::CtrlAltDel) => {
input.send_ctrl_alt_del()?;
}
_ => {}
}
}
}
Some(message::Payload::AdminCommand(cmd)) => {
use crate::proto::AdminCommandType;
tracing::info!("Admin command received: {:?} - {}", cmd.command, cmd.reason);
match AdminCommandType::try_from(cmd.command).ok() {
Some(AdminCommandType::AdminUninstall) => {
tracing::warn!("Uninstall command received from server");
// Return special error to trigger uninstall in main loop
return Err(anyhow::anyhow!("ADMIN_UNINSTALL: {}", cmd.reason));
}
Some(AdminCommandType::AdminRestart) => {
tracing::info!("Restart command received from server");
// For now, just disconnect - the auto-restart logic will handle it
return Err(anyhow::anyhow!("ADMIN_RESTART: {}", cmd.reason));
}
Some(AdminCommandType::AdminUpdate) => {
tracing::info!("Update command received from server: {}", cmd.reason);
// Trigger update check and perform update if available
// The server URL is derived from the config
let server_url = self.config.server_url.replace("/ws/agent", "").replace("wss://", "https://").replace("ws://", "http://");
match crate::update::check_for_update(&server_url).await {
Ok(Some(version_info)) => {
tracing::info!("Update available: {} -> {}", crate::build_info::VERSION, version_info.latest_version);
if let Err(e) = crate::update::perform_update(&version_info).await {
tracing::error!("Update failed: {}", e);
}
// If we get here, the update failed (perform_update exits on success)
}
Ok(None) => {
tracing::info!("Already running latest version");
}
Err(e) => {
tracing::error!("Failed to check for updates: {}", e);
}
}
}
None => {
tracing::warn!("Unknown admin command: {}", cmd.command);
}
}
}
Some(message::Payload::Disconnect(disc)) => {
tracing::info!("Disconnect requested: {}", disc.reason);
if disc.reason.contains("cancelled") {
return Err(anyhow::anyhow!("SESSION_CANCELLED: {}", disc.reason));
}
if disc.reason.contains("administrator") || disc.reason.contains("Disconnected") {
return Err(anyhow::anyhow!("ADMIN_DISCONNECT: {}", disc.reason));
}
return Err(anyhow::anyhow!("Disconnect: {}", disc.reason));
}
_ => {
// Ignore unknown messages
}
}
Ok(())
}
}

View File

@@ -1,298 +0,0 @@
//! Startup persistence for the agent
//!
//! Handles adding/removing the agent from Windows startup.
use anyhow::Result;
use tracing::{info, warn, error};
#[cfg(windows)]
use windows::Win32::System::Registry::{
RegOpenKeyExW, RegSetValueExW, RegDeleteValueW, RegCloseKey,
HKEY_CURRENT_USER, KEY_WRITE, REG_SZ,
};
#[cfg(windows)]
use windows::core::PCWSTR;
const STARTUP_KEY: &str = r"Software\Microsoft\Windows\CurrentVersion\Run";
const STARTUP_VALUE_NAME: &str = "GuruConnect";
/// Add the current executable to Windows startup
#[cfg(windows)]
pub fn add_to_startup() -> Result<()> {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
// Get the path to the current executable
let exe_path = std::env::current_exe()?;
let exe_path_str = exe_path.to_string_lossy();
info!("Adding to startup: {}", exe_path_str);
// Convert strings to wide strings
let key_path: Vec<u16> = OsStr::new(STARTUP_KEY)
.encode_wide()
.chain(std::iter::once(0))
.collect();
let value_name: Vec<u16> = OsStr::new(STARTUP_VALUE_NAME)
.encode_wide()
.chain(std::iter::once(0))
.collect();
let value_data: Vec<u16> = OsStr::new(&*exe_path_str)
.encode_wide()
.chain(std::iter::once(0))
.collect();
unsafe {
let mut hkey = windows::Win32::Foundation::HANDLE::default();
// Open the Run key
let result = RegOpenKeyExW(
HKEY_CURRENT_USER,
PCWSTR(key_path.as_ptr()),
0,
KEY_WRITE,
&mut hkey as *mut _ as *mut _,
);
if result.is_err() {
anyhow::bail!("Failed to open registry key: {:?}", result);
}
let hkey_raw = std::mem::transmute::<_, windows::Win32::System::Registry::HKEY>(hkey);
// Set the value
let data_bytes = std::slice::from_raw_parts(
value_data.as_ptr() as *const u8,
value_data.len() * 2,
);
let set_result = RegSetValueExW(
hkey_raw,
PCWSTR(value_name.as_ptr()),
0,
REG_SZ,
Some(data_bytes),
);
let _ = RegCloseKey(hkey_raw);
if set_result.is_err() {
anyhow::bail!("Failed to set registry value: {:?}", set_result);
}
}
info!("Successfully added to startup");
Ok(())
}
/// Remove the agent from Windows startup
#[cfg(windows)]
pub fn remove_from_startup() -> Result<()> {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
info!("Removing from startup");
let key_path: Vec<u16> = OsStr::new(STARTUP_KEY)
.encode_wide()
.chain(std::iter::once(0))
.collect();
let value_name: Vec<u16> = OsStr::new(STARTUP_VALUE_NAME)
.encode_wide()
.chain(std::iter::once(0))
.collect();
unsafe {
let mut hkey = windows::Win32::Foundation::HANDLE::default();
let result = RegOpenKeyExW(
HKEY_CURRENT_USER,
PCWSTR(key_path.as_ptr()),
0,
KEY_WRITE,
&mut hkey as *mut _ as *mut _,
);
if result.is_err() {
warn!("Failed to open registry key for removal: {:?}", result);
return Ok(()); // Not an error if key doesn't exist
}
let hkey_raw = std::mem::transmute::<_, windows::Win32::System::Registry::HKEY>(hkey);
let delete_result = RegDeleteValueW(hkey_raw, PCWSTR(value_name.as_ptr()));
let _ = RegCloseKey(hkey_raw);
if delete_result.is_err() {
warn!("Registry value may not exist: {:?}", delete_result);
} else {
info!("Successfully removed from startup");
}
}
Ok(())
}
/// Full uninstall: remove from startup and delete the executable
#[cfg(windows)]
pub fn uninstall() -> Result<()> {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use windows::Win32::Storage::FileSystem::{MoveFileExW, MOVEFILE_DELAY_UNTIL_REBOOT};
info!("Uninstalling agent");
// First remove from startup
let _ = remove_from_startup();
// Get the path to the current executable
let exe_path = std::env::current_exe()?;
let exe_path_str = exe_path.to_string_lossy();
info!("Scheduling deletion of: {}", exe_path_str);
// Convert path to wide string
let exe_wide: Vec<u16> = OsStr::new(&*exe_path_str)
.encode_wide()
.chain(std::iter::once(0))
.collect();
// Schedule the file for deletion on next reboot
// This is necessary because the executable is currently running
unsafe {
let result = MoveFileExW(
PCWSTR(exe_wide.as_ptr()),
PCWSTR::null(),
MOVEFILE_DELAY_UNTIL_REBOOT,
);
if result.is_err() {
warn!("Failed to schedule file deletion: {:?}. File may need manual removal.", result);
} else {
info!("Executable scheduled for deletion on reboot");
}
}
Ok(())
}
/// Install the SAS service if the binary is available
/// This allows the agent to send Ctrl+Alt+Del even without SYSTEM privileges
#[cfg(windows)]
pub fn install_sas_service() -> Result<()> {
info!("Attempting to install SAS service...");
// Check if the SAS service binary exists alongside the agent
let exe_path = std::env::current_exe()?;
let exe_dir = exe_path.parent().ok_or_else(|| anyhow::anyhow!("No parent directory"))?;
let sas_binary = exe_dir.join("guruconnect-sas-service.exe");
if !sas_binary.exists() {
// Also check in Program Files
let program_files = std::path::PathBuf::from(r"C:\Program Files\GuruConnect\guruconnect-sas-service.exe");
if !program_files.exists() {
warn!("SAS service binary not found");
return Ok(());
}
}
// Run the install command
let sas_path = if sas_binary.exists() {
sas_binary
} else {
std::path::PathBuf::from(r"C:\Program Files\GuruConnect\guruconnect-sas-service.exe")
};
let output = std::process::Command::new(&sas_path)
.arg("install")
.output();
match output {
Ok(result) => {
if result.status.success() {
info!("SAS service installed successfully");
} else {
let stderr = String::from_utf8_lossy(&result.stderr);
warn!("SAS service install failed: {}", stderr);
}
}
Err(e) => {
warn!("Failed to run SAS service installer: {}", e);
}
}
Ok(())
}
/// Uninstall the SAS service
#[cfg(windows)]
pub fn uninstall_sas_service() -> Result<()> {
info!("Attempting to uninstall SAS service...");
// Try to find and run the uninstall command
let paths = [
std::env::current_exe().ok().and_then(|p| p.parent().map(|d| d.join("guruconnect-sas-service.exe"))),
Some(std::path::PathBuf::from(r"C:\Program Files\GuruConnect\guruconnect-sas-service.exe")),
];
for path_opt in paths.iter() {
if let Some(ref path) = path_opt {
if path.exists() {
let output = std::process::Command::new(path)
.arg("uninstall")
.output();
if let Ok(result) = output {
if result.status.success() {
info!("SAS service uninstalled successfully");
return Ok(());
}
}
}
}
}
warn!("SAS service binary not found for uninstall");
Ok(())
}
/// Check if the SAS service is installed and running
#[cfg(windows)]
pub fn check_sas_service() -> bool {
use crate::sas_client;
sas_client::is_service_available()
}
#[cfg(not(windows))]
pub fn add_to_startup() -> Result<()> {
warn!("Startup persistence not implemented for this platform");
Ok(())
}
#[cfg(not(windows))]
pub fn remove_from_startup() -> Result<()> {
Ok(())
}
#[cfg(not(windows))]
pub fn uninstall() -> Result<()> {
warn!("Uninstall not implemented for this platform");
Ok(())
}
#[cfg(not(windows))]
pub fn install_sas_service() -> Result<()> {
warn!("SAS service only available on Windows");
Ok(())
}
#[cfg(not(windows))]
pub fn uninstall_sas_service() -> Result<()> {
Ok(())
}
#[cfg(not(windows))]
pub fn check_sas_service() -> bool {
false
}

View File

@@ -1,5 +0,0 @@
//! WebSocket transport for agent-server communication
mod websocket;
pub use websocket::WebSocketTransport;

View File

@@ -1,206 +0,0 @@
//! WebSocket client transport
//!
//! Handles WebSocket connection to the GuruConnect server with:
//! - TLS encryption
//! - Automatic reconnection
//! - Protobuf message serialization
use crate::proto::Message;
use anyhow::{Context, Result};
use bytes::Bytes;
use futures_util::{SinkExt, StreamExt};
use prost::Message as ProstMessage;
use std::collections::VecDeque;
use std::sync::Arc;
use tokio::net::TcpStream;
use tokio::sync::Mutex;
use tokio_tungstenite::{
connect_async, tungstenite::protocol::Message as WsMessage, MaybeTlsStream, WebSocketStream,
};
type WsStream = WebSocketStream<MaybeTlsStream<TcpStream>>;
/// WebSocket transport for server communication
pub struct WebSocketTransport {
stream: Arc<Mutex<WsStream>>,
incoming: VecDeque<Message>,
connected: bool,
}
impl WebSocketTransport {
/// Connect to the server
pub async fn connect(
url: &str,
agent_id: &str,
api_key: &str,
hostname: Option<&str>,
support_code: Option<&str>,
) -> Result<Self> {
// Build query parameters
let mut params = format!("agent_id={}&api_key={}", agent_id, api_key);
if let Some(hostname) = hostname {
params.push_str(&format!("&hostname={}", urlencoding::encode(hostname)));
}
if let Some(code) = support_code {
params.push_str(&format!("&support_code={}", code));
}
// Append parameters to URL
let url_with_params = if url.contains('?') {
format!("{}&{}", url, params)
} else {
format!("{}?{}", url, params)
};
tracing::info!("Connecting to {} as agent {}", url, agent_id);
if let Some(code) = support_code {
tracing::info!("Using support code: {}", code);
}
let (ws_stream, response) = connect_async(&url_with_params)
.await
.context("Failed to connect to WebSocket server")?;
tracing::info!("Connected, status: {}", response.status());
Ok(Self {
stream: Arc::new(Mutex::new(ws_stream)),
incoming: VecDeque::new(),
connected: true,
})
}
/// Send a protobuf message
pub async fn send(&mut self, msg: Message) -> Result<()> {
let mut stream = self.stream.lock().await;
// Serialize to protobuf binary
let mut buf = Vec::with_capacity(msg.encoded_len());
msg.encode(&mut buf)?;
// Send as binary WebSocket message
stream
.send(WsMessage::Binary(buf.into()))
.await
.context("Failed to send message")?;
Ok(())
}
/// Try to receive a message (non-blocking)
pub fn try_recv(&mut self) -> Result<Option<Message>> {
// Return buffered message if available
if let Some(msg) = self.incoming.pop_front() {
return Ok(Some(msg));
}
// Try to receive more messages
let stream = self.stream.clone();
let result = tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
let mut stream = stream.lock().await;
// Use try_next for non-blocking receive
match tokio::time::timeout(
std::time::Duration::from_millis(1),
stream.next(),
)
.await
{
Ok(Some(Ok(ws_msg))) => Ok(Some(ws_msg)),
Ok(Some(Err(e))) => Err(anyhow::anyhow!("WebSocket error: {}", e)),
Ok(None) => {
// Connection closed
Ok(None)
}
Err(_) => {
// Timeout - no message available
Ok(None)
}
}
})
});
match result? {
Some(ws_msg) => {
if let Some(msg) = self.parse_message(ws_msg)? {
Ok(Some(msg))
} else {
Ok(None)
}
}
None => Ok(None),
}
}
/// Receive a message (blocking)
pub async fn recv(&mut self) -> Result<Option<Message>> {
// Return buffered message if available
if let Some(msg) = self.incoming.pop_front() {
return Ok(Some(msg));
}
let result = {
let mut stream = self.stream.lock().await;
stream.next().await
};
match result {
Some(Ok(ws_msg)) => self.parse_message(ws_msg),
Some(Err(e)) => {
self.connected = false;
Err(anyhow::anyhow!("WebSocket error: {}", e))
}
None => {
self.connected = false;
Ok(None)
}
}
}
/// Parse a WebSocket message into a protobuf message
fn parse_message(&mut self, ws_msg: WsMessage) -> Result<Option<Message>> {
match ws_msg {
WsMessage::Binary(data) => {
let msg = Message::decode(Bytes::from(data))
.context("Failed to decode protobuf message")?;
Ok(Some(msg))
}
WsMessage::Ping(data) => {
// Pong is sent automatically by tungstenite
tracing::trace!("Received ping");
Ok(None)
}
WsMessage::Pong(_) => {
tracing::trace!("Received pong");
Ok(None)
}
WsMessage::Close(frame) => {
tracing::info!("Connection closed: {:?}", frame);
self.connected = false;
Ok(None)
}
WsMessage::Text(text) => {
// We expect binary protobuf, but log text messages
tracing::warn!("Received unexpected text message: {}", text);
Ok(None)
}
_ => Ok(None),
}
}
/// Check if connected
pub fn is_connected(&self) -> bool {
self.connected
}
/// Close the connection
pub async fn close(&mut self) -> Result<()> {
let mut stream = self.stream.lock().await;
stream.close(None).await?;
self.connected = false;
Ok(())
}
}

View File

@@ -1,197 +0,0 @@
//! System tray icon and menu for the agent
//!
//! Provides a tray icon with menu options:
//! - Connection status
//! - Machine name
//! - End session
use anyhow::Result;
use muda::{Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tray_icon::{Icon, TrayIcon, TrayIconBuilder, TrayIconEvent};
use tracing::{info, warn};
#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{
PeekMessageW, TranslateMessage, DispatchMessageW, MSG, PM_REMOVE,
};
/// Events that can be triggered from the tray menu
#[derive(Debug, Clone)]
pub enum TrayAction {
EndSession,
ShowDetails,
ShowDebugWindow,
}
/// Tray icon controller
pub struct TrayController {
_tray_icon: TrayIcon,
menu: Menu,
end_session_item: MenuItem,
debug_item: MenuItem,
status_item: MenuItem,
exit_requested: Arc<AtomicBool>,
}
impl TrayController {
/// Create a new tray controller
/// `allow_end_session` - If true, show "End Session" menu item (only for support sessions)
pub fn new(machine_name: &str, support_code: Option<&str>, allow_end_session: bool) -> Result<Self> {
// Create menu items
let status_text = if let Some(code) = support_code {
format!("Support Session: {}", code)
} else {
"Persistent Agent".to_string()
};
let status_item = MenuItem::new(&status_text, false, None);
let machine_item = MenuItem::new(format!("Machine: {}", machine_name), false, None);
let separator = PredefinedMenuItem::separator();
// Only show "End Session" for support sessions
// Persistent agents can only be removed by admin
let end_session_item = if allow_end_session {
MenuItem::new("End Session", true, None)
} else {
MenuItem::new("Managed by Administrator", false, None)
};
// Debug window option (always available)
let debug_item = MenuItem::new("Show Debug Window", true, None);
// Build menu
let menu = Menu::new();
menu.append(&status_item)?;
menu.append(&machine_item)?;
menu.append(&separator)?;
menu.append(&debug_item)?;
menu.append(&end_session_item)?;
// Create tray icon
let icon = create_default_icon()?;
let tray_icon = TrayIconBuilder::new()
.with_menu(Box::new(menu.clone()))
.with_tooltip(format!("GuruConnect - {}", machine_name))
.with_icon(icon)
.build()?;
let exit_requested = Arc::new(AtomicBool::new(false));
Ok(Self {
_tray_icon: tray_icon,
menu,
end_session_item,
debug_item,
status_item,
exit_requested,
})
}
/// Check if exit has been requested
pub fn exit_requested(&self) -> bool {
self.exit_requested.load(Ordering::SeqCst)
}
/// Update the connection status display
pub fn update_status(&self, status: &str) {
self.status_item.set_text(status);
}
/// Process pending menu events (call this from the main loop)
pub fn process_events(&self) -> Option<TrayAction> {
// Pump Windows message queue to process tray icon events
#[cfg(windows)]
pump_windows_messages();
// Check for menu events
if let Ok(event) = MenuEvent::receiver().try_recv() {
if event.id == self.end_session_item.id() {
info!("End session requested from tray menu");
self.exit_requested.store(true, Ordering::SeqCst);
return Some(TrayAction::EndSession);
}
if event.id == self.debug_item.id() {
info!("Debug window requested from tray menu");
return Some(TrayAction::ShowDebugWindow);
}
}
// Check for tray icon events (like double-click)
if let Ok(event) = TrayIconEvent::receiver().try_recv() {
match event {
TrayIconEvent::DoubleClick { .. } => {
info!("Tray icon double-clicked");
return Some(TrayAction::ShowDetails);
}
_ => {}
}
}
None
}
}
/// Pump the Windows message queue to process tray icon events
#[cfg(windows)]
fn pump_windows_messages() {
unsafe {
let mut msg = MSG::default();
// Process all pending messages
while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
let _ = TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
}
/// Create a simple default icon (green circle for connected)
fn create_default_icon() -> Result<Icon> {
// Create a simple 32x32 green icon
let size = 32u32;
let mut rgba = vec![0u8; (size * size * 4) as usize];
let center = size as f32 / 2.0;
let radius = size as f32 / 2.0 - 2.0;
for y in 0..size {
for x in 0..size {
let dx = x as f32 - center;
let dy = y as f32 - center;
let dist = (dx * dx + dy * dy).sqrt();
let idx = ((y * size + x) * 4) as usize;
if dist <= radius {
// Green circle
rgba[idx] = 76; // R
rgba[idx + 1] = 175; // G
rgba[idx + 2] = 80; // B
rgba[idx + 3] = 255; // A
} else if dist <= radius + 1.0 {
// Anti-aliased edge
let alpha = ((radius + 1.0 - dist) * 255.0) as u8;
rgba[idx] = 76;
rgba[idx + 1] = 175;
rgba[idx + 2] = 80;
rgba[idx + 3] = alpha;
}
}
}
let icon = Icon::from_rgba(rgba, size, size)?;
Ok(icon)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_icon() {
let icon = create_default_icon();
assert!(icon.is_ok());
}
}

View File

@@ -1,318 +0,0 @@
//! Auto-update module for GuruConnect agent
//!
//! Handles checking for updates, downloading new versions, and performing
//! in-place binary replacement with restart.
use anyhow::{anyhow, Result};
use sha2::{Sha256, Digest};
use std::path::PathBuf;
use tracing::{info, warn, error};
use crate::build_info;
/// Version information from the server
#[derive(Debug, Clone, serde::Deserialize)]
pub struct VersionInfo {
pub latest_version: String,
pub download_url: String,
pub checksum_sha256: String,
pub is_mandatory: bool,
pub release_notes: Option<String>,
}
/// Update state tracking
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum UpdateState {
Idle,
Checking,
Downloading,
Verifying,
Installing,
Restarting,
Failed,
}
/// Check if an update is available
pub async fn check_for_update(server_base_url: &str) -> Result<Option<VersionInfo>> {
let url = format!("{}/api/version", server_base_url.trim_end_matches('/'));
info!("Checking for updates at {}", url);
let client = reqwest::Client::builder()
.danger_accept_invalid_certs(true) // For self-signed certs in dev
.build()?;
let response = client
.get(&url)
.timeout(std::time::Duration::from_secs(30))
.send()
.await?;
if response.status() == reqwest::StatusCode::NOT_FOUND {
info!("No stable release available on server");
return Ok(None);
}
if !response.status().is_success() {
return Err(anyhow!("Version check failed: HTTP {}", response.status()));
}
let version_info: VersionInfo = response.json().await?;
// Compare versions
let current = build_info::VERSION;
if is_newer_version(&version_info.latest_version, current) {
info!(
"Update available: {} -> {} (mandatory: {})",
current, version_info.latest_version, version_info.is_mandatory
);
Ok(Some(version_info))
} else {
info!("Already running latest version: {}", current);
Ok(None)
}
}
/// Simple semantic version comparison
/// Returns true if `available` is newer than `current`
fn is_newer_version(available: &str, current: &str) -> bool {
// Strip any git hash suffix (e.g., "0.1.0-abc123" -> "0.1.0")
let available_clean = available.split('-').next().unwrap_or(available);
let current_clean = current.split('-').next().unwrap_or(current);
let parse_version = |s: &str| -> Vec<u32> {
s.split('.')
.filter_map(|p| p.parse().ok())
.collect()
};
let av = parse_version(available_clean);
let cv = parse_version(current_clean);
// Compare component by component
for i in 0..av.len().max(cv.len()) {
let a = av.get(i).copied().unwrap_or(0);
let c = cv.get(i).copied().unwrap_or(0);
if a > c {
return true;
}
if a < c {
return false;
}
}
false
}
/// Download update to temporary file
pub async fn download_update(version_info: &VersionInfo) -> Result<PathBuf> {
info!("Downloading update from {}", version_info.download_url);
let client = reqwest::Client::builder()
.danger_accept_invalid_certs(true)
.build()?;
let response = client
.get(&version_info.download_url)
.timeout(std::time::Duration::from_secs(300)) // 5 minutes for large files
.send()
.await?;
if !response.status().is_success() {
return Err(anyhow!("Download failed: HTTP {}", response.status()));
}
// Get temp directory
let temp_dir = std::env::temp_dir();
let temp_path = temp_dir.join("guruconnect-update.exe");
// Download to file
let bytes = response.bytes().await?;
std::fs::write(&temp_path, &bytes)?;
info!("Downloaded {} bytes to {:?}", bytes.len(), temp_path);
Ok(temp_path)
}
/// Verify downloaded file checksum
pub fn verify_checksum(file_path: &PathBuf, expected_sha256: &str) -> Result<bool> {
info!("Verifying checksum...");
let contents = std::fs::read(file_path)?;
let mut hasher = Sha256::new();
hasher.update(&contents);
let result = hasher.finalize();
let computed = format!("{:x}", result);
let matches = computed.eq_ignore_ascii_case(expected_sha256);
if matches {
info!("Checksum verified: {}", computed);
} else {
error!("Checksum mismatch! Expected: {}, Got: {}", expected_sha256, computed);
}
Ok(matches)
}
/// Perform the actual update installation
/// This renames the current executable and copies the new one in place
pub fn install_update(temp_path: &PathBuf) -> Result<PathBuf> {
info!("Installing update...");
// Get current executable path
let current_exe = std::env::current_exe()?;
let exe_dir = current_exe.parent()
.ok_or_else(|| anyhow!("Cannot get executable directory"))?;
// Create paths for backup and new executable
let backup_path = exe_dir.join("guruconnect.exe.old");
// Delete any existing backup
if backup_path.exists() {
if let Err(e) = std::fs::remove_file(&backup_path) {
warn!("Could not remove old backup: {}", e);
}
}
// Rename current executable to .old (this works even while running)
info!("Renaming current exe to backup: {:?}", backup_path);
std::fs::rename(&current_exe, &backup_path)?;
// Copy new executable to original location
info!("Copying new exe to: {:?}", current_exe);
std::fs::copy(temp_path, &current_exe)?;
// Clean up temp file
let _ = std::fs::remove_file(temp_path);
info!("Update installed successfully");
Ok(current_exe)
}
/// Spawn new process and exit current one
pub fn restart_with_new_version(exe_path: &PathBuf, args: &[String]) -> Result<()> {
info!("Restarting with new version...");
// Build command with --post-update flag
let mut cmd_args = vec!["--post-update".to_string()];
cmd_args.extend(args.iter().cloned());
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
const DETACHED_PROCESS: u32 = 0x00000008;
std::process::Command::new(exe_path)
.args(&cmd_args)
.creation_flags(CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS)
.spawn()?;
}
#[cfg(not(windows))]
{
std::process::Command::new(exe_path)
.args(&cmd_args)
.spawn()?;
}
info!("New process spawned, exiting current process");
Ok(())
}
/// Clean up old executable after successful update
pub fn cleanup_post_update() {
let current_exe = match std::env::current_exe() {
Ok(p) => p,
Err(e) => {
warn!("Could not get current exe path for cleanup: {}", e);
return;
}
};
let exe_dir = match current_exe.parent() {
Some(d) => d,
None => {
warn!("Could not get executable directory for cleanup");
return;
}
};
let backup_path = exe_dir.join("guruconnect.exe.old");
if backup_path.exists() {
info!("Cleaning up old executable: {:?}", backup_path);
match std::fs::remove_file(&backup_path) {
Ok(_) => info!("Old executable removed successfully"),
Err(e) => {
warn!("Could not remove old executable (may be in use): {}", e);
// On Windows, we might need to schedule deletion on reboot
#[cfg(windows)]
schedule_delete_on_reboot(&backup_path);
}
}
}
}
/// Schedule file deletion on reboot (Windows)
#[cfg(windows)]
fn schedule_delete_on_reboot(path: &PathBuf) {
use std::os::windows::ffi::OsStrExt;
use windows::Win32::Storage::FileSystem::{MoveFileExW, MOVEFILE_DELAY_UNTIL_REBOOT};
use windows::core::PCWSTR;
let path_wide: Vec<u16> = path.as_os_str()
.encode_wide()
.chain(std::iter::once(0))
.collect();
unsafe {
let result = MoveFileExW(
PCWSTR(path_wide.as_ptr()),
PCWSTR::null(),
MOVEFILE_DELAY_UNTIL_REBOOT,
);
if result.is_ok() {
info!("Scheduled {:?} for deletion on reboot", path);
} else {
warn!("Failed to schedule {:?} for deletion on reboot", path);
}
}
}
/// Perform complete update process
pub async fn perform_update(version_info: &VersionInfo) -> Result<()> {
// Download
let temp_path = download_update(version_info).await?;
// Verify
if !verify_checksum(&temp_path, &version_info.checksum_sha256)? {
let _ = std::fs::remove_file(&temp_path);
return Err(anyhow!("Update verification failed: checksum mismatch"));
}
// Install
let exe_path = install_update(&temp_path)?;
// Restart
// Get current args (without the current executable name)
let args: Vec<String> = std::env::args().skip(1).collect();
restart_with_new_version(&exe_path, &args)?;
// Exit current process
std::process::exit(0);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_comparison() {
assert!(is_newer_version("0.2.0", "0.1.0"));
assert!(is_newer_version("1.0.0", "0.9.9"));
assert!(is_newer_version("0.1.1", "0.1.0"));
assert!(!is_newer_version("0.1.0", "0.1.0"));
assert!(!is_newer_version("0.1.0", "0.2.0"));
assert!(is_newer_version("0.2.0-abc123", "0.1.0-def456"));
}
}

View File

@@ -1,173 +0,0 @@
//! Low-level keyboard hook for capturing all keys including Win key
use super::InputEvent;
#[cfg(windows)]
use crate::proto;
use anyhow::Result;
use tokio::sync::mpsc;
#[cfg(windows)]
use tracing::trace;
#[cfg(windows)]
use windows::{
Win32::Foundation::{LPARAM, LRESULT, WPARAM},
Win32::UI::WindowsAndMessaging::{
CallNextHookEx, DispatchMessageW, GetMessageW, PeekMessageW, SetWindowsHookExW,
TranslateMessage, UnhookWindowsHookEx, HHOOK, KBDLLHOOKSTRUCT, MSG, PM_REMOVE,
WH_KEYBOARD_LL, WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP,
},
};
#[cfg(windows)]
use std::sync::OnceLock;
#[cfg(windows)]
static INPUT_TX: OnceLock<mpsc::Sender<InputEvent>> = OnceLock::new();
#[cfg(windows)]
static mut HOOK_HANDLE: HHOOK = HHOOK(std::ptr::null_mut());
/// Virtual key codes for special keys
#[cfg(windows)]
mod vk {
pub const VK_LWIN: u32 = 0x5B;
pub const VK_RWIN: u32 = 0x5C;
pub const VK_APPS: u32 = 0x5D;
pub const VK_LSHIFT: u32 = 0xA0;
pub const VK_RSHIFT: u32 = 0xA1;
pub const VK_LCONTROL: u32 = 0xA2;
pub const VK_RCONTROL: u32 = 0xA3;
pub const VK_LMENU: u32 = 0xA4; // Left Alt
pub const VK_RMENU: u32 = 0xA5; // Right Alt
pub const VK_TAB: u32 = 0x09;
pub const VK_ESCAPE: u32 = 0x1B;
pub const VK_SNAPSHOT: u32 = 0x2C; // Print Screen
}
#[cfg(windows)]
pub struct KeyboardHook {
_hook: HHOOK,
}
#[cfg(windows)]
impl KeyboardHook {
pub fn new(input_tx: mpsc::Sender<InputEvent>) -> Result<Self> {
// Store the sender globally for the hook callback
INPUT_TX.set(input_tx).map_err(|_| anyhow::anyhow!("Input TX already set"))?;
unsafe {
let hook = SetWindowsHookExW(
WH_KEYBOARD_LL,
Some(keyboard_hook_proc),
None,
0,
)?;
HOOK_HANDLE = hook;
Ok(Self { _hook: hook })
}
}
}
#[cfg(windows)]
impl Drop for KeyboardHook {
fn drop(&mut self) {
unsafe {
if !HOOK_HANDLE.0.is_null() {
let _ = UnhookWindowsHookEx(HOOK_HANDLE);
HOOK_HANDLE = HHOOK(std::ptr::null_mut());
}
}
}
}
#[cfg(windows)]
unsafe extern "system" fn keyboard_hook_proc(
code: i32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
if code >= 0 {
let kb_struct = &*(lparam.0 as *const KBDLLHOOKSTRUCT);
let vk_code = kb_struct.vkCode;
let scan_code = kb_struct.scanCode;
let is_down = wparam.0 as u32 == WM_KEYDOWN || wparam.0 as u32 == WM_SYSKEYDOWN;
let is_up = wparam.0 as u32 == WM_KEYUP || wparam.0 as u32 == WM_SYSKEYUP;
if is_down || is_up {
// Check if this is a key we want to intercept (Win key, Alt+Tab, etc.)
let should_intercept = matches!(
vk_code,
vk::VK_LWIN | vk::VK_RWIN | vk::VK_APPS
);
// Send the key event to the remote
if let Some(tx) = INPUT_TX.get() {
let event = proto::KeyEvent {
down: is_down,
key_type: proto::KeyEventType::KeyVk as i32,
vk_code,
scan_code,
unicode: String::new(),
modifiers: Some(get_current_modifiers()),
};
let _ = tx.try_send(InputEvent::Key(event));
trace!("Key hook: vk={:#x} scan={} down={}", vk_code, scan_code, is_down);
}
// For Win key, consume the event so it doesn't open Start menu locally
if should_intercept {
return LRESULT(1);
}
}
}
CallNextHookEx(HOOK_HANDLE, code, wparam, lparam)
}
#[cfg(windows)]
fn get_current_modifiers() -> proto::Modifiers {
use windows::Win32::UI::Input::KeyboardAndMouse::GetAsyncKeyState;
unsafe {
proto::Modifiers {
ctrl: GetAsyncKeyState(0x11) < 0, // VK_CONTROL
alt: GetAsyncKeyState(0x12) < 0, // VK_MENU
shift: GetAsyncKeyState(0x10) < 0, // VK_SHIFT
meta: GetAsyncKeyState(0x5B) < 0 || GetAsyncKeyState(0x5C) < 0, // VK_LWIN/RWIN
caps_lock: GetAsyncKeyState(0x14) & 1 != 0, // VK_CAPITAL
num_lock: GetAsyncKeyState(0x90) & 1 != 0, // VK_NUMLOCK
}
}
}
/// Pump Windows message queue (required for hooks to work)
#[cfg(windows)]
pub fn pump_messages() {
unsafe {
let mut msg = MSG::default();
while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
let _ = TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
}
// Non-Windows stubs
#[cfg(not(windows))]
#[allow(dead_code)]
pub struct KeyboardHook;
#[cfg(not(windows))]
#[allow(dead_code)]
impl KeyboardHook {
pub fn new(_input_tx: mpsc::Sender<InputEvent>) -> Result<Self> {
Ok(Self)
}
}
#[cfg(not(windows))]
#[allow(dead_code)]
pub fn pump_messages() {}

View File

@@ -1,121 +0,0 @@
//! Viewer module - Native remote desktop viewer with full keyboard capture
//!
//! This module provides the viewer functionality for connecting to remote
//! GuruConnect sessions with low-level keyboard hooks for Win key capture.
mod input;
mod render;
mod transport;
use crate::proto;
use anyhow::Result;
use std::sync::Arc;
use tokio::sync::{mpsc, Mutex};
use tracing::{info, error, warn};
#[derive(Debug, Clone)]
pub enum ViewerEvent {
Connected,
Disconnected(String),
Frame(render::FrameData),
CursorPosition(i32, i32, bool),
CursorShape(proto::CursorShape),
}
#[derive(Debug, Clone)]
pub enum InputEvent {
Mouse(proto::MouseEvent),
Key(proto::KeyEvent),
SpecialKey(proto::SpecialKeyEvent),
}
/// Run the viewer to connect to a remote session
pub async fn run(server_url: &str, session_id: &str, api_key: &str) -> Result<()> {
info!("GuruConnect Viewer starting");
info!("Server: {}", server_url);
info!("Session: {}", session_id);
// Create channels for communication between components
let (viewer_tx, viewer_rx) = mpsc::channel::<ViewerEvent>(100);
let (input_tx, input_rx) = mpsc::channel::<InputEvent>(100);
// Connect to server
let ws_url = format!("{}?session_id={}", server_url, session_id);
info!("Connecting to {}", ws_url);
let (ws_sender, mut ws_receiver) = transport::connect(&ws_url, api_key).await?;
let ws_sender = Arc::new(Mutex::new(ws_sender));
info!("Connected to server");
let _ = viewer_tx.send(ViewerEvent::Connected).await;
// Clone sender for input forwarding
let ws_sender_input = ws_sender.clone();
// Spawn task to forward input events to server
let mut input_rx = input_rx;
let input_task = tokio::spawn(async move {
while let Some(event) = input_rx.recv().await {
let msg = match event {
InputEvent::Mouse(m) => proto::Message {
payload: Some(proto::message::Payload::MouseEvent(m)),
},
InputEvent::Key(k) => proto::Message {
payload: Some(proto::message::Payload::KeyEvent(k)),
},
InputEvent::SpecialKey(s) => proto::Message {
payload: Some(proto::message::Payload::SpecialKey(s)),
},
};
if let Err(e) = transport::send_message(&ws_sender_input, &msg).await {
error!("Failed to send input: {}", e);
break;
}
}
});
// Spawn task to receive messages from server
let viewer_tx_recv = viewer_tx.clone();
let receive_task = tokio::spawn(async move {
while let Some(msg) = ws_receiver.recv().await {
match msg.payload {
Some(proto::message::Payload::VideoFrame(frame)) => {
if let Some(proto::video_frame::Encoding::Raw(raw)) = frame.encoding {
let frame_data = render::FrameData {
width: raw.width as u32,
height: raw.height as u32,
data: raw.data,
compressed: raw.compressed,
is_keyframe: raw.is_keyframe,
};
let _ = viewer_tx_recv.send(ViewerEvent::Frame(frame_data)).await;
}
}
Some(proto::message::Payload::CursorPosition(pos)) => {
let _ = viewer_tx_recv.send(ViewerEvent::CursorPosition(
pos.x, pos.y, pos.visible
)).await;
}
Some(proto::message::Payload::CursorShape(shape)) => {
let _ = viewer_tx_recv.send(ViewerEvent::CursorShape(shape)).await;
}
Some(proto::message::Payload::Disconnect(d)) => {
warn!("Server disconnected: {}", d.reason);
let _ = viewer_tx_recv.send(ViewerEvent::Disconnected(d.reason)).await;
break;
}
_ => {}
}
}
});
// Run the window (this blocks until window closes)
render::run_window(viewer_rx, input_tx).await?;
// Cleanup
input_task.abort();
receive_task.abort();
Ok(())
}

View File

@@ -1,508 +0,0 @@
//! Window rendering and frame display
use super::{ViewerEvent, InputEvent};
use crate::proto;
#[cfg(windows)]
use super::input;
use anyhow::Result;
use std::num::NonZeroU32;
use std::sync::Arc;
use tokio::sync::mpsc;
use tracing::{debug, error, info, warn};
use winit::{
application::ApplicationHandler,
dpi::LogicalSize,
event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent},
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowId},
};
/// Frame data received from server
#[derive(Debug, Clone)]
pub struct FrameData {
pub width: u32,
pub height: u32,
pub data: Vec<u8>,
pub compressed: bool,
pub is_keyframe: bool,
}
struct ViewerApp {
window: Option<Arc<Window>>,
surface: Option<softbuffer::Surface<Arc<Window>, Arc<Window>>>,
frame_buffer: Vec<u32>,
frame_width: u32,
frame_height: u32,
viewer_rx: mpsc::Receiver<ViewerEvent>,
input_tx: mpsc::Sender<InputEvent>,
mouse_x: i32,
mouse_y: i32,
#[cfg(windows)]
keyboard_hook: Option<input::KeyboardHook>,
}
impl ViewerApp {
fn new(
viewer_rx: mpsc::Receiver<ViewerEvent>,
input_tx: mpsc::Sender<InputEvent>,
) -> Self {
Self {
window: None,
surface: None,
frame_buffer: Vec::new(),
frame_width: 0,
frame_height: 0,
viewer_rx,
input_tx,
mouse_x: 0,
mouse_y: 0,
#[cfg(windows)]
keyboard_hook: None,
}
}
fn process_frame(&mut self, frame: FrameData) {
let data = if frame.compressed {
// Decompress zstd
match zstd::decode_all(frame.data.as_slice()) {
Ok(decompressed) => decompressed,
Err(e) => {
error!("Failed to decompress frame: {}", e);
return;
}
}
} else {
frame.data
};
// Convert BGRA to ARGB (softbuffer expects 0RGB format on little-endian)
let pixel_count = (frame.width * frame.height) as usize;
if data.len() < pixel_count * 4 {
error!("Frame data too small: {} < {}", data.len(), pixel_count * 4);
return;
}
// Resize frame buffer if needed
if self.frame_width != frame.width || self.frame_height != frame.height {
self.frame_width = frame.width;
self.frame_height = frame.height;
self.frame_buffer.resize(pixel_count, 0);
// Resize window to match frame
if let Some(window) = &self.window {
let _ = window.request_inner_size(LogicalSize::new(frame.width, frame.height));
}
}
// Convert BGRA to 0RGB (ignore alpha, swap B and R)
for i in 0..pixel_count {
let offset = i * 4;
let b = data[offset] as u32;
let g = data[offset + 1] as u32;
let r = data[offset + 2] as u32;
// 0RGB format: 0x00RRGGBB
self.frame_buffer[i] = (r << 16) | (g << 8) | b;
}
// Request redraw
if let Some(window) = &self.window {
window.request_redraw();
}
}
fn render(&mut self) {
let Some(surface) = &mut self.surface else { return };
let Some(window) = &self.window else { return };
if self.frame_buffer.is_empty() || self.frame_width == 0 || self.frame_height == 0 {
return;
}
let size = window.inner_size();
if size.width == 0 || size.height == 0 {
return;
}
// Resize surface if needed
let width = NonZeroU32::new(size.width).unwrap();
let height = NonZeroU32::new(size.height).unwrap();
if let Err(e) = surface.resize(width, height) {
error!("Failed to resize surface: {}", e);
return;
}
let mut buffer = match surface.buffer_mut() {
Ok(b) => b,
Err(e) => {
error!("Failed to get surface buffer: {}", e);
return;
}
};
// Simple nearest-neighbor scaling
let scale_x = self.frame_width as f32 / size.width as f32;
let scale_y = self.frame_height as f32 / size.height as f32;
for y in 0..size.height {
for x in 0..size.width {
let src_x = ((x as f32 * scale_x) as u32).min(self.frame_width - 1);
let src_y = ((y as f32 * scale_y) as u32).min(self.frame_height - 1);
let src_idx = (src_y * self.frame_width + src_x) as usize;
let dst_idx = (y * size.width + x) as usize;
if src_idx < self.frame_buffer.len() && dst_idx < buffer.len() {
buffer[dst_idx] = self.frame_buffer[src_idx];
}
}
}
if let Err(e) = buffer.present() {
error!("Failed to present buffer: {}", e);
}
}
fn send_mouse_event(&self, event_type: proto::MouseEventType, x: i32, y: i32) {
let event = proto::MouseEvent {
x,
y,
buttons: Some(proto::MouseButtons::default()),
wheel_delta_x: 0,
wheel_delta_y: 0,
event_type: event_type as i32,
};
let _ = self.input_tx.try_send(InputEvent::Mouse(event));
}
fn send_mouse_button(&self, button: MouseButton, state: ElementState) {
let event_type = match state {
ElementState::Pressed => proto::MouseEventType::MouseDown,
ElementState::Released => proto::MouseEventType::MouseUp,
};
let mut buttons = proto::MouseButtons::default();
match button {
MouseButton::Left => buttons.left = true,
MouseButton::Right => buttons.right = true,
MouseButton::Middle => buttons.middle = true,
_ => {}
}
let event = proto::MouseEvent {
x: self.mouse_x,
y: self.mouse_y,
buttons: Some(buttons),
wheel_delta_x: 0,
wheel_delta_y: 0,
event_type: event_type as i32,
};
let _ = self.input_tx.try_send(InputEvent::Mouse(event));
}
fn send_mouse_wheel(&self, delta_x: i32, delta_y: i32) {
let event = proto::MouseEvent {
x: self.mouse_x,
y: self.mouse_y,
buttons: Some(proto::MouseButtons::default()),
wheel_delta_x: delta_x,
wheel_delta_y: delta_y,
event_type: proto::MouseEventType::MouseWheel as i32,
};
let _ = self.input_tx.try_send(InputEvent::Mouse(event));
}
fn send_key_event(&self, key: PhysicalKey, state: ElementState) {
let vk_code = match key {
PhysicalKey::Code(code) => keycode_to_vk(code),
_ => return,
};
let event = proto::KeyEvent {
down: state == ElementState::Pressed,
key_type: proto::KeyEventType::KeyVk as i32,
vk_code,
scan_code: 0,
unicode: String::new(),
modifiers: Some(proto::Modifiers::default()),
};
let _ = self.input_tx.try_send(InputEvent::Key(event));
}
fn screen_to_frame_coords(&self, x: f64, y: f64) -> (i32, i32) {
let Some(window) = &self.window else {
return (x as i32, y as i32);
};
let size = window.inner_size();
if size.width == 0 || size.height == 0 || self.frame_width == 0 || self.frame_height == 0 {
return (x as i32, y as i32);
}
// Scale from window coordinates to frame coordinates
let scale_x = self.frame_width as f64 / size.width as f64;
let scale_y = self.frame_height as f64 / size.height as f64;
let frame_x = (x * scale_x) as i32;
let frame_y = (y * scale_y) as i32;
(frame_x, frame_y)
}
}
impl ApplicationHandler for ViewerApp {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.window.is_some() {
return;
}
let window_attrs = Window::default_attributes()
.with_title("GuruConnect Viewer")
.with_inner_size(LogicalSize::new(1280, 720));
let window = Arc::new(event_loop.create_window(window_attrs).unwrap());
// Create software rendering surface
let context = softbuffer::Context::new(window.clone()).unwrap();
let surface = softbuffer::Surface::new(&context, window.clone()).unwrap();
self.window = Some(window.clone());
self.surface = Some(surface);
// Install keyboard hook
#[cfg(windows)]
{
let input_tx = self.input_tx.clone();
match input::KeyboardHook::new(input_tx) {
Ok(hook) => {
info!("Keyboard hook installed");
self.keyboard_hook = Some(hook);
}
Err(e) => {
error!("Failed to install keyboard hook: {}", e);
}
}
}
info!("Window created");
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WindowEvent) {
// Check for incoming viewer events (non-blocking)
while let Ok(viewer_event) = self.viewer_rx.try_recv() {
match viewer_event {
ViewerEvent::Frame(frame) => {
self.process_frame(frame);
}
ViewerEvent::Connected => {
info!("Connected to remote session");
}
ViewerEvent::Disconnected(reason) => {
warn!("Disconnected: {}", reason);
event_loop.exit();
}
ViewerEvent::CursorPosition(_x, _y, _visible) => {
// Could update cursor display here
}
ViewerEvent::CursorShape(_shape) => {
// Could update cursor shape here
}
}
}
match event {
WindowEvent::CloseRequested => {
info!("Window close requested");
event_loop.exit();
}
WindowEvent::RedrawRequested => {
self.render();
}
WindowEvent::Resized(size) => {
debug!("Window resized to {}x{}", size.width, size.height);
if let Some(window) = &self.window {
window.request_redraw();
}
}
WindowEvent::CursorMoved { position, .. } => {
let (x, y) = self.screen_to_frame_coords(position.x, position.y);
self.mouse_x = x;
self.mouse_y = y;
self.send_mouse_event(proto::MouseEventType::MouseMove, x, y);
}
WindowEvent::MouseInput { state, button, .. } => {
self.send_mouse_button(button, state);
}
WindowEvent::MouseWheel { delta, .. } => {
let (dx, dy) = match delta {
MouseScrollDelta::LineDelta(x, y) => (x as i32 * 120, y as i32 * 120),
MouseScrollDelta::PixelDelta(pos) => (pos.x as i32, pos.y as i32),
};
self.send_mouse_wheel(dx, dy);
}
WindowEvent::KeyboardInput { event, .. } => {
// Note: This handles keys that aren't captured by the low-level hook
// The hook handles Win key and other special keys
if !event.repeat {
self.send_key_event(event.physical_key, event.state);
}
}
_ => {}
}
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
// Keep checking for events
event_loop.set_control_flow(ControlFlow::Poll);
// Process Windows messages for keyboard hook
#[cfg(windows)]
input::pump_messages();
// Request redraw periodically to check for new frames
if let Some(window) = &self.window {
window.request_redraw();
}
}
}
/// Run the viewer window
pub async fn run_window(
viewer_rx: mpsc::Receiver<ViewerEvent>,
input_tx: mpsc::Sender<InputEvent>,
) -> Result<()> {
let event_loop = EventLoop::new()?;
let mut app = ViewerApp::new(viewer_rx, input_tx);
event_loop.run_app(&mut app)?;
Ok(())
}
/// Convert winit KeyCode to Windows virtual key code
fn keycode_to_vk(code: KeyCode) -> u32 {
match code {
// Letters
KeyCode::KeyA => 0x41,
KeyCode::KeyB => 0x42,
KeyCode::KeyC => 0x43,
KeyCode::KeyD => 0x44,
KeyCode::KeyE => 0x45,
KeyCode::KeyF => 0x46,
KeyCode::KeyG => 0x47,
KeyCode::KeyH => 0x48,
KeyCode::KeyI => 0x49,
KeyCode::KeyJ => 0x4A,
KeyCode::KeyK => 0x4B,
KeyCode::KeyL => 0x4C,
KeyCode::KeyM => 0x4D,
KeyCode::KeyN => 0x4E,
KeyCode::KeyO => 0x4F,
KeyCode::KeyP => 0x50,
KeyCode::KeyQ => 0x51,
KeyCode::KeyR => 0x52,
KeyCode::KeyS => 0x53,
KeyCode::KeyT => 0x54,
KeyCode::KeyU => 0x55,
KeyCode::KeyV => 0x56,
KeyCode::KeyW => 0x57,
KeyCode::KeyX => 0x58,
KeyCode::KeyY => 0x59,
KeyCode::KeyZ => 0x5A,
// Numbers
KeyCode::Digit0 => 0x30,
KeyCode::Digit1 => 0x31,
KeyCode::Digit2 => 0x32,
KeyCode::Digit3 => 0x33,
KeyCode::Digit4 => 0x34,
KeyCode::Digit5 => 0x35,
KeyCode::Digit6 => 0x36,
KeyCode::Digit7 => 0x37,
KeyCode::Digit8 => 0x38,
KeyCode::Digit9 => 0x39,
// Function keys
KeyCode::F1 => 0x70,
KeyCode::F2 => 0x71,
KeyCode::F3 => 0x72,
KeyCode::F4 => 0x73,
KeyCode::F5 => 0x74,
KeyCode::F6 => 0x75,
KeyCode::F7 => 0x76,
KeyCode::F8 => 0x77,
KeyCode::F9 => 0x78,
KeyCode::F10 => 0x79,
KeyCode::F11 => 0x7A,
KeyCode::F12 => 0x7B,
// Special keys
KeyCode::Escape => 0x1B,
KeyCode::Tab => 0x09,
KeyCode::CapsLock => 0x14,
KeyCode::ShiftLeft => 0x10,
KeyCode::ShiftRight => 0x10,
KeyCode::ControlLeft => 0x11,
KeyCode::ControlRight => 0x11,
KeyCode::AltLeft => 0x12,
KeyCode::AltRight => 0x12,
KeyCode::Space => 0x20,
KeyCode::Enter => 0x0D,
KeyCode::Backspace => 0x08,
KeyCode::Delete => 0x2E,
KeyCode::Insert => 0x2D,
KeyCode::Home => 0x24,
KeyCode::End => 0x23,
KeyCode::PageUp => 0x21,
KeyCode::PageDown => 0x22,
// Arrow keys
KeyCode::ArrowUp => 0x26,
KeyCode::ArrowDown => 0x28,
KeyCode::ArrowLeft => 0x25,
KeyCode::ArrowRight => 0x27,
// Numpad
KeyCode::NumLock => 0x90,
KeyCode::Numpad0 => 0x60,
KeyCode::Numpad1 => 0x61,
KeyCode::Numpad2 => 0x62,
KeyCode::Numpad3 => 0x63,
KeyCode::Numpad4 => 0x64,
KeyCode::Numpad5 => 0x65,
KeyCode::Numpad6 => 0x66,
KeyCode::Numpad7 => 0x67,
KeyCode::Numpad8 => 0x68,
KeyCode::Numpad9 => 0x69,
KeyCode::NumpadAdd => 0x6B,
KeyCode::NumpadSubtract => 0x6D,
KeyCode::NumpadMultiply => 0x6A,
KeyCode::NumpadDivide => 0x6F,
KeyCode::NumpadDecimal => 0x6E,
KeyCode::NumpadEnter => 0x0D,
// Punctuation
KeyCode::Semicolon => 0xBA,
KeyCode::Equal => 0xBB,
KeyCode::Comma => 0xBC,
KeyCode::Minus => 0xBD,
KeyCode::Period => 0xBE,
KeyCode::Slash => 0xBF,
KeyCode::Backquote => 0xC0,
KeyCode::BracketLeft => 0xDB,
KeyCode::Backslash => 0xDC,
KeyCode::BracketRight => 0xDD,
KeyCode::Quote => 0xDE,
// Other
KeyCode::PrintScreen => 0x2C,
KeyCode::ScrollLock => 0x91,
KeyCode::Pause => 0x13,
_ => 0,
}
}

View File

@@ -1,102 +0,0 @@
//! WebSocket transport for viewer-server communication
use crate::proto;
use anyhow::{anyhow, Result};
use bytes::Bytes;
use futures_util::{SinkExt, StreamExt};
use prost::Message as ProstMessage;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio_tungstenite::{
connect_async,
tungstenite::protocol::Message as WsMessage,
MaybeTlsStream, WebSocketStream,
};
use tokio::net::TcpStream;
use tracing::{debug, error, trace};
pub type WsSender = futures_util::stream::SplitSink<
WebSocketStream<MaybeTlsStream<TcpStream>>,
WsMessage,
>;
pub type WsReceiver = futures_util::stream::SplitStream<
WebSocketStream<MaybeTlsStream<TcpStream>>,
>;
/// Receiver wrapper that parses protobuf messages
pub struct MessageReceiver {
inner: WsReceiver,
}
impl MessageReceiver {
pub async fn recv(&mut self) -> Option<proto::Message> {
loop {
match self.inner.next().await {
Some(Ok(WsMessage::Binary(data))) => {
match proto::Message::decode(Bytes::from(data)) {
Ok(msg) => return Some(msg),
Err(e) => {
error!("Failed to decode message: {}", e);
continue;
}
}
}
Some(Ok(WsMessage::Close(_))) => {
debug!("WebSocket closed");
return None;
}
Some(Ok(WsMessage::Ping(_))) => {
trace!("Received ping");
continue;
}
Some(Ok(WsMessage::Pong(_))) => {
trace!("Received pong");
continue;
}
Some(Ok(_)) => continue,
Some(Err(e)) => {
error!("WebSocket error: {}", e);
return None;
}
None => return None,
}
}
}
}
/// Connect to the GuruConnect server
pub async fn connect(url: &str, token: &str) -> Result<(WsSender, MessageReceiver)> {
// Add auth token to URL
let full_url = if token.is_empty() {
url.to_string()
} else if url.contains('?') {
format!("{}&token={}", url, urlencoding::encode(token))
} else {
format!("{}?token={}", url, urlencoding::encode(token))
};
debug!("Connecting to {}", full_url);
let (ws_stream, _) = connect_async(&full_url)
.await
.map_err(|e| anyhow!("Failed to connect: {}", e))?;
let (sender, receiver) = ws_stream.split();
Ok((sender, MessageReceiver { inner: receiver }))
}
/// Send a protobuf message over the WebSocket
pub async fn send_message(
sender: &Arc<Mutex<WsSender>>,
msg: &proto::Message,
) -> Result<()> {
let mut buf = Vec::with_capacity(msg.encoded_len());
msg.encode(&mut buf)?;
let mut sender = sender.lock().await;
sender.send(WsMessage::Binary(buf)).await?;
Ok(())
}

View File

@@ -1,25 +0,0 @@
{
"name": "@guruconnect/dashboard",
"version": "0.1.0",
"description": "GuruConnect Remote Desktop Viewer Components",
"author": "AZ Computer Guru",
"license": "Proprietary",
"main": "src/components/index.ts",
"types": "src/components/index.ts",
"scripts": {
"typecheck": "tsc --noEmit",
"lint": "eslint src"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"typescript": "^5.0.0"
},
"dependencies": {
"fzstd": "^0.1.1"
}
}

View File

@@ -1,215 +0,0 @@
/**
* RemoteViewer Component
*
* Canvas-based remote desktop viewer that connects to a GuruConnect
* agent via the relay server. Handles frame rendering and input capture.
*/
import React, { useRef, useEffect, useCallback, useState } from 'react';
import { useRemoteSession, createMouseEvent, createKeyEvent } from '../hooks/useRemoteSession';
import type { VideoFrame, ConnectionStatus, MouseEventType } from '../types/protocol';
interface RemoteViewerProps {
serverUrl: string;
sessionId: string;
className?: string;
onStatusChange?: (status: ConnectionStatus) => void;
autoConnect?: boolean;
showStatusBar?: boolean;
}
export const RemoteViewer: React.FC<RemoteViewerProps> = ({
serverUrl,
sessionId,
className = '',
onStatusChange,
autoConnect = true,
showStatusBar = true,
}) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const ctxRef = useRef<CanvasRenderingContext2D | null>(null);
// Display dimensions from received frames
const [displaySize, setDisplaySize] = useState({ width: 1920, height: 1080 });
// Frame buffer for rendering
const frameBufferRef = useRef<ImageData | null>(null);
// Handle incoming video frames
const handleFrame = useCallback((frame: VideoFrame) => {
if (!frame.raw || !canvasRef.current) return;
const { width, height, data, compressed, isKeyframe } = frame.raw;
// Update display size if changed
if (width !== displaySize.width || height !== displaySize.height) {
setDisplaySize({ width, height });
}
// Get or create context
if (!ctxRef.current) {
ctxRef.current = canvasRef.current.getContext('2d', {
alpha: false,
desynchronized: true,
});
}
const ctx = ctxRef.current;
if (!ctx) return;
// For MVP, we assume raw BGRA frames
// In production, handle compressed frames with fzstd
let frameData = data;
// Create or reuse ImageData
if (!frameBufferRef.current ||
frameBufferRef.current.width !== width ||
frameBufferRef.current.height !== height) {
frameBufferRef.current = ctx.createImageData(width, height);
}
const imageData = frameBufferRef.current;
// Convert BGRA to RGBA for canvas
const pixels = imageData.data;
const len = Math.min(frameData.length, pixels.length);
for (let i = 0; i < len; i += 4) {
pixels[i] = frameData[i + 2]; // R <- B
pixels[i + 1] = frameData[i + 1]; // G <- G
pixels[i + 2] = frameData[i]; // B <- R
pixels[i + 3] = 255; // A (opaque)
}
// Draw to canvas
ctx.putImageData(imageData, 0, 0);
}, [displaySize]);
// Set up session
const { status, connect, disconnect, sendMouseEvent, sendKeyEvent } = useRemoteSession({
serverUrl,
sessionId,
onFrame: handleFrame,
onStatusChange,
});
// Auto-connect on mount
useEffect(() => {
if (autoConnect) {
connect();
}
return () => {
disconnect();
};
}, [autoConnect, connect, disconnect]);
// Update canvas size when display size changes
useEffect(() => {
if (canvasRef.current) {
canvasRef.current.width = displaySize.width;
canvasRef.current.height = displaySize.height;
// Reset context reference
ctxRef.current = null;
frameBufferRef.current = null;
}
}, [displaySize]);
// Get canvas rect for coordinate translation
const getCanvasRect = useCallback(() => {
return canvasRef.current?.getBoundingClientRect() ?? new DOMRect();
}, []);
// Mouse event handlers
const handleMouseMove = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
const event = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 0);
sendMouseEvent(event);
}, [getCanvasRect, displaySize, sendMouseEvent]);
const handleMouseDown = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
e.preventDefault();
const event = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 1);
sendMouseEvent(event);
}, [getCanvasRect, displaySize, sendMouseEvent]);
const handleMouseUp = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
const event = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 2);
sendMouseEvent(event);
}, [getCanvasRect, displaySize, sendMouseEvent]);
const handleWheel = useCallback((e: React.WheelEvent<HTMLCanvasElement>) => {
e.preventDefault();
const baseEvent = createMouseEvent(e, getCanvasRect(), displaySize.width, displaySize.height, 3);
sendMouseEvent({
...baseEvent,
wheelDeltaX: Math.round(e.deltaX),
wheelDeltaY: Math.round(e.deltaY),
});
}, [getCanvasRect, displaySize, sendMouseEvent]);
const handleContextMenu = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
e.preventDefault(); // Prevent browser context menu
}, []);
// Keyboard event handlers
const handleKeyDown = useCallback((e: React.KeyboardEvent<HTMLCanvasElement>) => {
e.preventDefault();
const event = createKeyEvent(e, true);
sendKeyEvent(event);
}, [sendKeyEvent]);
const handleKeyUp = useCallback((e: React.KeyboardEvent<HTMLCanvasElement>) => {
e.preventDefault();
const event = createKeyEvent(e, false);
sendKeyEvent(event);
}, [sendKeyEvent]);
return (
<div ref={containerRef} className={`remote-viewer ${className}`}>
<canvas
ref={canvasRef}
tabIndex={0}
onMouseMove={handleMouseMove}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onWheel={handleWheel}
onContextMenu={handleContextMenu}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
style={{
width: '100%',
height: 'auto',
aspectRatio: `${displaySize.width} / ${displaySize.height}`,
cursor: 'none', // Hide cursor, remote cursor is shown in frame
outline: 'none',
backgroundColor: '#1a1a1a',
}}
/>
{showStatusBar && (
<div className="remote-viewer-status" style={{
display: 'flex',
justifyContent: 'space-between',
padding: '4px 8px',
backgroundColor: '#333',
color: '#fff',
fontSize: '12px',
fontFamily: 'monospace',
}}>
<span>
{status.connected ? (
<span style={{ color: '#4ade80' }}>Connected</span>
) : (
<span style={{ color: '#f87171' }}>Disconnected</span>
)}
</span>
<span>{displaySize.width}x{displaySize.height}</span>
{status.fps !== undefined && <span>{status.fps} FPS</span>}
{status.latencyMs !== undefined && <span>{status.latencyMs}ms</span>}
</div>
)}
</div>
);
};
export default RemoteViewer;

View File

@@ -1,187 +0,0 @@
/**
* Session Controls Component
*
* Toolbar for controlling the remote session (quality, displays, special keys)
*/
import React, { useState } from 'react';
import type { QualitySettings, Display } from '../types/protocol';
interface SessionControlsProps {
displays?: Display[];
currentDisplay?: number;
onDisplayChange?: (displayId: number) => void;
quality?: QualitySettings;
onQualityChange?: (settings: QualitySettings) => void;
onSpecialKey?: (key: 'ctrl-alt-del' | 'lock-screen' | 'print-screen') => void;
onDisconnect?: () => void;
}
export const SessionControls: React.FC<SessionControlsProps> = ({
displays = [],
currentDisplay = 0,
onDisplayChange,
quality,
onQualityChange,
onSpecialKey,
onDisconnect,
}) => {
const [showQuality, setShowQuality] = useState(false);
const handleQualityPreset = (preset: 'auto' | 'low' | 'balanced' | 'high') => {
onQualityChange?.({
preset,
codec: 'auto',
});
};
return (
<div className="session-controls" style={{
display: 'flex',
gap: '8px',
padding: '8px',
backgroundColor: '#222',
borderBottom: '1px solid #444',
}}>
{/* Display selector */}
{displays.length > 1 && (
<select
value={currentDisplay}
onChange={(e) => onDisplayChange?.(Number(e.target.value))}
style={{
padding: '4px 8px',
backgroundColor: '#333',
color: '#fff',
border: '1px solid #555',
borderRadius: '4px',
}}
>
{displays.map((d) => (
<option key={d.id} value={d.id}>
{d.name || `Display ${d.id + 1}`}
{d.isPrimary ? ' (Primary)' : ''}
</option>
))}
</select>
)}
{/* Quality dropdown */}
<div style={{ position: 'relative' }}>
<button
onClick={() => setShowQuality(!showQuality)}
style={{
padding: '4px 12px',
backgroundColor: '#333',
color: '#fff',
border: '1px solid #555',
borderRadius: '4px',
cursor: 'pointer',
}}
>
Quality: {quality?.preset || 'auto'}
</button>
{showQuality && (
<div style={{
position: 'absolute',
top: '100%',
left: 0,
marginTop: '4px',
backgroundColor: '#333',
border: '1px solid #555',
borderRadius: '4px',
zIndex: 100,
}}>
{(['auto', 'low', 'balanced', 'high'] as const).map((preset) => (
<button
key={preset}
onClick={() => {
handleQualityPreset(preset);
setShowQuality(false);
}}
style={{
display: 'block',
width: '100%',
padding: '8px 16px',
backgroundColor: quality?.preset === preset ? '#444' : 'transparent',
color: '#fff',
border: 'none',
textAlign: 'left',
cursor: 'pointer',
}}
>
{preset.charAt(0).toUpperCase() + preset.slice(1)}
</button>
))}
</div>
)}
</div>
{/* Special keys */}
<button
onClick={() => onSpecialKey?.('ctrl-alt-del')}
title="Send Ctrl+Alt+Delete"
style={{
padding: '4px 12px',
backgroundColor: '#333',
color: '#fff',
border: '1px solid #555',
borderRadius: '4px',
cursor: 'pointer',
}}
>
Ctrl+Alt+Del
</button>
<button
onClick={() => onSpecialKey?.('lock-screen')}
title="Lock Screen (Win+L)"
style={{
padding: '4px 12px',
backgroundColor: '#333',
color: '#fff',
border: '1px solid #555',
borderRadius: '4px',
cursor: 'pointer',
}}
>
Lock
</button>
<button
onClick={() => onSpecialKey?.('print-screen')}
title="Print Screen"
style={{
padding: '4px 12px',
backgroundColor: '#333',
color: '#fff',
border: '1px solid #555',
borderRadius: '4px',
cursor: 'pointer',
}}
>
PrtSc
</button>
{/* Spacer */}
<div style={{ flex: 1 }} />
{/* Disconnect */}
<button
onClick={onDisconnect}
style={{
padding: '4px 12px',
backgroundColor: '#dc2626',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
}}
>
Disconnect
</button>
</div>
);
};
export default SessionControls;

View File

@@ -1,22 +0,0 @@
/**
* GuruConnect Dashboard Components
*
* Export all components for use in GuruRMM dashboard
*/
export { RemoteViewer } from './RemoteViewer';
export { SessionControls } from './SessionControls';
// Re-export types
export type {
ConnectionStatus,
Display,
DisplayInfo,
QualitySettings,
VideoFrame,
MouseEvent as ProtoMouseEvent,
KeyEvent as ProtoKeyEvent,
} from '../types/protocol';
// Re-export hooks
export { useRemoteSession, createMouseEvent, createKeyEvent } from '../hooks/useRemoteSession';

View File

@@ -1,239 +0,0 @@
/**
* React hook for managing remote desktop session connection
*/
import { useState, useEffect, useCallback, useRef } from 'react';
import type { ConnectionStatus, VideoFrame, MouseEvent as ProtoMouseEvent, KeyEvent as ProtoKeyEvent, MouseEventType, KeyEventType, Modifiers } from '../types/protocol';
import { encodeMouseEvent, encodeKeyEvent, decodeVideoFrame } from '../lib/protobuf';
interface UseRemoteSessionOptions {
serverUrl: string;
sessionId: string;
onFrame?: (frame: VideoFrame) => void;
onStatusChange?: (status: ConnectionStatus) => void;
}
interface UseRemoteSessionReturn {
status: ConnectionStatus;
connect: () => void;
disconnect: () => void;
sendMouseEvent: (event: ProtoMouseEvent) => void;
sendKeyEvent: (event: ProtoKeyEvent) => void;
}
export function useRemoteSession(options: UseRemoteSessionOptions): UseRemoteSessionReturn {
const { serverUrl, sessionId, onFrame, onStatusChange } = options;
const [status, setStatus] = useState<ConnectionStatus>({
connected: false,
});
const wsRef = useRef<WebSocket | null>(null);
const reconnectTimeoutRef = useRef<number | null>(null);
const frameCountRef = useRef(0);
const lastFpsUpdateRef = useRef(Date.now());
// Update status and notify
const updateStatus = useCallback((newStatus: Partial<ConnectionStatus>) => {
setStatus(prev => {
const updated = { ...prev, ...newStatus };
onStatusChange?.(updated);
return updated;
});
}, [onStatusChange]);
// Calculate FPS
const updateFps = useCallback(() => {
const now = Date.now();
const elapsed = now - lastFpsUpdateRef.current;
if (elapsed >= 1000) {
const fps = Math.round((frameCountRef.current * 1000) / elapsed);
updateStatus({ fps });
frameCountRef.current = 0;
lastFpsUpdateRef.current = now;
}
}, [updateStatus]);
// Handle incoming WebSocket messages
const handleMessage = useCallback((event: MessageEvent) => {
if (event.data instanceof Blob) {
event.data.arrayBuffer().then(buffer => {
const data = new Uint8Array(buffer);
const frame = decodeVideoFrame(data);
if (frame) {
frameCountRef.current++;
updateFps();
onFrame?.(frame);
}
});
} else if (event.data instanceof ArrayBuffer) {
const data = new Uint8Array(event.data);
const frame = decodeVideoFrame(data);
if (frame) {
frameCountRef.current++;
updateFps();
onFrame?.(frame);
}
}
}, [onFrame, updateFps]);
// Connect to server
const connect = useCallback(() => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
return;
}
// Clear any pending reconnect
if (reconnectTimeoutRef.current) {
window.clearTimeout(reconnectTimeoutRef.current);
reconnectTimeoutRef.current = null;
}
const wsUrl = `${serverUrl}/ws/viewer?session_id=${encodeURIComponent(sessionId)}`;
const ws = new WebSocket(wsUrl);
ws.binaryType = 'arraybuffer';
ws.onopen = () => {
updateStatus({
connected: true,
sessionId,
});
};
ws.onmessage = handleMessage;
ws.onclose = (event) => {
updateStatus({
connected: false,
latencyMs: undefined,
fps: undefined,
});
// Auto-reconnect after 2 seconds
if (!event.wasClean) {
reconnectTimeoutRef.current = window.setTimeout(() => {
connect();
}, 2000);
}
};
ws.onerror = () => {
updateStatus({ connected: false });
};
wsRef.current = ws;
}, [serverUrl, sessionId, handleMessage, updateStatus]);
// Disconnect from server
const disconnect = useCallback(() => {
if (reconnectTimeoutRef.current) {
window.clearTimeout(reconnectTimeoutRef.current);
reconnectTimeoutRef.current = null;
}
if (wsRef.current) {
wsRef.current.close(1000, 'User disconnected');
wsRef.current = null;
}
updateStatus({
connected: false,
sessionId: undefined,
latencyMs: undefined,
fps: undefined,
});
}, [updateStatus]);
// Send mouse event
const sendMouseEvent = useCallback((event: ProtoMouseEvent) => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
const data = encodeMouseEvent(event);
wsRef.current.send(data);
}
}, []);
// Send key event
const sendKeyEvent = useCallback((event: ProtoKeyEvent) => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
const data = encodeKeyEvent(event);
wsRef.current.send(data);
}
}, []);
// Cleanup on unmount
useEffect(() => {
return () => {
disconnect();
};
}, [disconnect]);
return {
status,
connect,
disconnect,
sendMouseEvent,
sendKeyEvent,
};
}
/**
* Helper to create mouse event from DOM mouse event
*/
export function createMouseEvent(
domEvent: React.MouseEvent<HTMLElement>,
canvasRect: DOMRect,
displayWidth: number,
displayHeight: number,
eventType: MouseEventType
): ProtoMouseEvent {
// Calculate position relative to canvas and scale to display coordinates
const scaleX = displayWidth / canvasRect.width;
const scaleY = displayHeight / canvasRect.height;
const x = Math.round((domEvent.clientX - canvasRect.left) * scaleX);
const y = Math.round((domEvent.clientY - canvasRect.top) * scaleY);
return {
x,
y,
buttons: {
left: (domEvent.buttons & 1) !== 0,
right: (domEvent.buttons & 2) !== 0,
middle: (domEvent.buttons & 4) !== 0,
x1: (domEvent.buttons & 8) !== 0,
x2: (domEvent.buttons & 16) !== 0,
},
wheelDeltaX: 0,
wheelDeltaY: 0,
eventType,
};
}
/**
* Helper to create key event from DOM keyboard event
*/
export function createKeyEvent(
domEvent: React.KeyboardEvent<HTMLElement>,
down: boolean
): ProtoKeyEvent {
const modifiers: Modifiers = {
ctrl: domEvent.ctrlKey,
alt: domEvent.altKey,
shift: domEvent.shiftKey,
meta: domEvent.metaKey,
capsLock: domEvent.getModifierState('CapsLock'),
numLock: domEvent.getModifierState('NumLock'),
};
// Use key code for special keys, unicode for regular characters
const isCharacter = domEvent.key.length === 1;
return {
down,
keyType: isCharacter ? 2 : 0, // KEY_UNICODE or KEY_VK
vkCode: domEvent.keyCode,
scanCode: 0, // Not available in browser
unicode: isCharacter ? domEvent.key : undefined,
modifiers,
};
}

View File

@@ -1,162 +0,0 @@
/**
* Minimal protobuf encoder/decoder for GuruConnect messages
*
* For MVP, we use a simplified binary format. In production,
* this would use a proper protobuf library like protobufjs.
*/
import type { MouseEvent, KeyEvent, MouseEventType, KeyEventType, VideoFrame, RawFrame } from '../types/protocol';
// Message type identifiers (matching proto field numbers)
const MSG_VIDEO_FRAME = 10;
const MSG_MOUSE_EVENT = 20;
const MSG_KEY_EVENT = 21;
/**
* Encode a mouse event to binary format
*/
export function encodeMouseEvent(event: MouseEvent): Uint8Array {
const buffer = new ArrayBuffer(32);
const view = new DataView(buffer);
// Message type
view.setUint8(0, MSG_MOUSE_EVENT);
// Event type
view.setUint8(1, event.eventType);
// Coordinates (scaled to 16-bit for efficiency)
view.setInt16(2, event.x, true);
view.setInt16(4, event.y, true);
// Buttons bitmask
let buttons = 0;
if (event.buttons.left) buttons |= 1;
if (event.buttons.right) buttons |= 2;
if (event.buttons.middle) buttons |= 4;
if (event.buttons.x1) buttons |= 8;
if (event.buttons.x2) buttons |= 16;
view.setUint8(6, buttons);
// Wheel deltas
view.setInt16(7, event.wheelDeltaX, true);
view.setInt16(9, event.wheelDeltaY, true);
return new Uint8Array(buffer, 0, 11);
}
/**
* Encode a key event to binary format
*/
export function encodeKeyEvent(event: KeyEvent): Uint8Array {
const buffer = new ArrayBuffer(32);
const view = new DataView(buffer);
// Message type
view.setUint8(0, MSG_KEY_EVENT);
// Key down/up
view.setUint8(1, event.down ? 1 : 0);
// Key type
view.setUint8(2, event.keyType);
// Virtual key code
view.setUint16(3, event.vkCode, true);
// Scan code
view.setUint16(5, event.scanCode, true);
// Modifiers bitmask
let mods = 0;
if (event.modifiers.ctrl) mods |= 1;
if (event.modifiers.alt) mods |= 2;
if (event.modifiers.shift) mods |= 4;
if (event.modifiers.meta) mods |= 8;
if (event.modifiers.capsLock) mods |= 16;
if (event.modifiers.numLock) mods |= 32;
view.setUint8(7, mods);
// Unicode character (if present)
if (event.unicode && event.unicode.length > 0) {
const charCode = event.unicode.charCodeAt(0);
view.setUint16(8, charCode, true);
return new Uint8Array(buffer, 0, 10);
}
return new Uint8Array(buffer, 0, 8);
}
/**
* Decode a video frame from binary format
*/
export function decodeVideoFrame(data: Uint8Array): VideoFrame | null {
if (data.length < 2) return null;
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
const msgType = view.getUint8(0);
if (msgType !== MSG_VIDEO_FRAME) return null;
const encoding = view.getUint8(1);
const displayId = view.getUint8(2);
const sequence = view.getUint32(3, true);
const timestamp = Number(view.getBigInt64(7, true));
// Frame dimensions
const width = view.getUint16(15, true);
const height = view.getUint16(17, true);
// Compressed flag
const compressed = view.getUint8(19) === 1;
// Is keyframe
const isKeyframe = view.getUint8(20) === 1;
// Frame data starts at offset 21
const frameData = data.slice(21);
const encodingStr = ['raw', 'vp9', 'h264', 'h265'][encoding] as 'raw' | 'vp9' | 'h264' | 'h265';
if (encodingStr === 'raw') {
return {
timestamp,
displayId,
sequence,
encoding: 'raw',
raw: {
width,
height,
data: frameData,
compressed,
dirtyRects: [], // TODO: Parse dirty rects
isKeyframe,
},
};
}
return {
timestamp,
displayId,
sequence,
encoding: encodingStr,
encoded: {
data: frameData,
keyframe: isKeyframe,
pts: timestamp,
dts: timestamp,
},
};
}
/**
* Simple zstd decompression placeholder
* In production, use a proper zstd library like fzstd
*/
export async function decompressZstd(data: Uint8Array): Promise<Uint8Array> {
// For MVP, assume uncompressed frames or use fzstd library
// This is a placeholder - actual implementation would use:
// import { decompress } from 'fzstd';
// return decompress(data);
return data;
}

View File

@@ -1,135 +0,0 @@
/**
* TypeScript types matching guruconnect.proto definitions
* These are used for WebSocket message handling in the viewer
*/
export enum SessionType {
SCREEN_CONTROL = 0,
VIEW_ONLY = 1,
BACKSTAGE = 2,
FILE_TRANSFER = 3,
}
export interface SessionRequest {
agentId: string;
sessionToken: string;
sessionType: SessionType;
clientVersion: string;
}
export interface SessionResponse {
success: boolean;
sessionId: string;
error?: string;
displayInfo?: DisplayInfo;
}
export interface DisplayInfo {
displays: Display[];
primaryDisplay: number;
}
export interface Display {
id: number;
name: string;
x: number;
y: number;
width: number;
height: number;
isPrimary: boolean;
}
export interface DirtyRect {
x: number;
y: number;
width: number;
height: number;
}
export interface RawFrame {
width: number;
height: number;
data: Uint8Array;
compressed: boolean;
dirtyRects: DirtyRect[];
isKeyframe: boolean;
}
export interface EncodedFrame {
data: Uint8Array;
keyframe: boolean;
pts: number;
dts: number;
}
export interface VideoFrame {
timestamp: number;
displayId: number;
sequence: number;
encoding: 'raw' | 'vp9' | 'h264' | 'h265';
raw?: RawFrame;
encoded?: EncodedFrame;
}
export enum MouseEventType {
MOUSE_MOVE = 0,
MOUSE_DOWN = 1,
MOUSE_UP = 2,
MOUSE_WHEEL = 3,
}
export interface MouseButtons {
left: boolean;
right: boolean;
middle: boolean;
x1: boolean;
x2: boolean;
}
export interface MouseEvent {
x: number;
y: number;
buttons: MouseButtons;
wheelDeltaX: number;
wheelDeltaY: number;
eventType: MouseEventType;
}
export enum KeyEventType {
KEY_VK = 0,
KEY_SCAN = 1,
KEY_UNICODE = 2,
}
export interface Modifiers {
ctrl: boolean;
alt: boolean;
shift: boolean;
meta: boolean;
capsLock: boolean;
numLock: boolean;
}
export interface KeyEvent {
down: boolean;
keyType: KeyEventType;
vkCode: number;
scanCode: number;
unicode?: string;
modifiers: Modifiers;
}
export interface QualitySettings {
preset: 'auto' | 'low' | 'balanced' | 'high';
customFps?: number;
customBitrate?: number;
codec: 'auto' | 'raw' | 'vp9' | 'h264' | 'h265';
}
export interface ConnectionStatus {
connected: boolean;
sessionId?: string;
latencyMs?: number;
fps?: number;
bitrateKbps?: number;
}

View File

@@ -1,21 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"exclude": ["node_modules"]
}

View File

@@ -1,68 +0,0 @@
# Prometheus Alert Rules for GuruConnect
#
# This file defines alerting rules for monitoring GuruConnect health and performance.
# Copy to /etc/prometheus/alerts.yml and reference in prometheus.yml
groups:
- name: guruconnect_alerts
interval: 30s
rules:
# GuruConnect is down
- alert: GuruConnectDown
expr: up{job="guruconnect"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "GuruConnect server is down"
description: "GuruConnect server on {{ $labels.instance }} has been down for more than 1 minute"
# High error rate
- alert: HighErrorRate
expr: rate(guruconnect_errors_total[5m]) > 10
for: 5m
labels:
severity: warning
annotations:
summary: "High error rate detected"
description: "Error rate is {{ $value | humanize }} errors/second over the last 5 minutes"
# Too many active sessions
- alert: TooManyActiveSessions
expr: guruconnect_active_sessions > 100
for: 5m
labels:
severity: warning
annotations:
summary: "Too many active sessions"
description: "There are {{ $value }} active sessions, exceeding threshold of 100"
# High request latency
- alert: HighRequestLatency
expr: histogram_quantile(0.95, rate(guruconnect_request_duration_seconds_bucket[5m])) > 1
for: 5m
labels:
severity: warning
annotations:
summary: "High request latency"
description: "95th percentile request latency is {{ $value | humanize }}s"
# Database operations failing
- alert: DatabaseOperationsFailure
expr: rate(guruconnect_db_operations_total{status="error"}[5m]) > 1
for: 5m
labels:
severity: critical
annotations:
summary: "Database operations failing"
description: "Database error rate is {{ $value | humanize }} errors/second"
# Server uptime low (recent restart)
- alert: ServerRestarted
expr: guruconnect_uptime_seconds < 300
for: 1m
labels:
severity: info
annotations:
summary: "Server recently restarted"
description: "Server uptime is only {{ $value | humanize }}s, indicating a recent restart"

View File

@@ -1,228 +0,0 @@
{
"dashboard": {
"title": "GuruConnect Monitoring",
"tags": ["guruconnect", "monitoring"],
"timezone": "browser",
"schemaVersion": 16,
"version": 1,
"refresh": "10s",
"panels": [
{
"id": 1,
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
"type": "graph",
"title": "Active Sessions",
"targets": [
{
"expr": "guruconnect_active_sessions",
"legendFormat": "Active Sessions",
"refId": "A"
}
],
"yaxes": [
{"label": "Sessions", "show": true},
{"show": false}
],
"lines": true,
"fill": 1,
"linewidth": 2,
"tooltip": {"shared": true}
},
{
"id": 2,
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0},
"type": "graph",
"title": "Requests per Second",
"targets": [
{
"expr": "rate(guruconnect_requests_total[1m])",
"legendFormat": "{{method}} {{path}}",
"refId": "A"
}
],
"yaxes": [
{"label": "Requests/sec", "show": true},
{"show": false}
],
"lines": true,
"fill": 1,
"linewidth": 2,
"tooltip": {"shared": true}
},
{
"id": 3,
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 8},
"type": "graph",
"title": "Error Rate",
"targets": [
{
"expr": "rate(guruconnect_errors_total[1m])",
"legendFormat": "{{error_type}}",
"refId": "A"
}
],
"yaxes": [
{"label": "Errors/sec", "show": true},
{"show": false}
],
"lines": true,
"fill": 1,
"linewidth": 2,
"tooltip": {"shared": true},
"alert": {
"conditions": [
{
"evaluator": {"params": [10], "type": "gt"},
"operator": {"type": "and"},
"query": {"params": ["A", "1m", "now"]},
"reducer": {"params": [], "type": "avg"},
"type": "query"
}
],
"executionErrorState": "alerting",
"frequency": "60s",
"handler": 1,
"name": "High Error Rate",
"noDataState": "no_data",
"notifications": []
}
},
{
"id": 4,
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 8},
"type": "graph",
"title": "Request Latency (p50, p95, p99)",
"targets": [
{
"expr": "histogram_quantile(0.50, rate(guruconnect_request_duration_seconds_bucket[5m]))",
"legendFormat": "p50",
"refId": "A"
},
{
"expr": "histogram_quantile(0.95, rate(guruconnect_request_duration_seconds_bucket[5m]))",
"legendFormat": "p95",
"refId": "B"
},
{
"expr": "histogram_quantile(0.99, rate(guruconnect_request_duration_seconds_bucket[5m]))",
"legendFormat": "p99",
"refId": "C"
}
],
"yaxes": [
{"label": "Latency (seconds)", "show": true, "format": "s"},
{"show": false}
],
"lines": true,
"fill": 0,
"linewidth": 2,
"tooltip": {"shared": true}
},
{
"id": 5,
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 16},
"type": "graph",
"title": "Active Connections by Type",
"targets": [
{
"expr": "guruconnect_active_connections",
"legendFormat": "{{conn_type}}",
"refId": "A"
}
],
"yaxes": [
{"label": "Connections", "show": true},
{"show": false}
],
"lines": true,
"fill": 1,
"linewidth": 2,
"stack": true,
"tooltip": {"shared": true}
},
{
"id": 6,
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 16},
"type": "graph",
"title": "Database Query Duration",
"targets": [
{
"expr": "histogram_quantile(0.95, rate(guruconnect_db_query_duration_seconds_bucket[5m]))",
"legendFormat": "{{operation}} p95",
"refId": "A"
}
],
"yaxes": [
{"label": "Duration (seconds)", "show": true, "format": "s"},
{"show": false}
],
"lines": true,
"fill": 0,
"linewidth": 2,
"tooltip": {"shared": true}
},
{
"id": 7,
"gridPos": {"h": 4, "w": 6, "x": 0, "y": 24},
"type": "singlestat",
"title": "Server Uptime",
"targets": [
{
"expr": "guruconnect_uptime_seconds",
"refId": "A"
}
],
"format": "s",
"valueName": "current",
"sparkline": {"show": true}
},
{
"id": 8,
"gridPos": {"h": 4, "w": 6, "x": 6, "y": 24},
"type": "singlestat",
"title": "Total Sessions Created",
"targets": [
{
"expr": "guruconnect_sessions_total{status=\"created\"}",
"refId": "A"
}
],
"format": "short",
"valueName": "current",
"sparkline": {"show": true}
},
{
"id": 9,
"gridPos": {"h": 4, "w": 6, "x": 12, "y": 24},
"type": "singlestat",
"title": "Total Requests",
"targets": [
{
"expr": "sum(guruconnect_requests_total)",
"refId": "A"
}
],
"format": "short",
"valueName": "current",
"sparkline": {"show": true}
},
{
"id": 10,
"gridPos": {"h": 4, "w": 6, "x": 18, "y": 24},
"type": "singlestat",
"title": "Total Errors",
"targets": [
{
"expr": "sum(guruconnect_errors_total)",
"refId": "A"
}
],
"format": "short",
"valueName": "current",
"sparkline": {"show": true},
"thresholds": "10,100",
"colors": ["#299c46", "#e0b400", "#d44a3a"]
}
]
}
}

View File

@@ -1,45 +0,0 @@
# Prometheus configuration for GuruConnect
#
# Install Prometheus:
# sudo apt-get install prometheus
#
# Copy this file to:
# sudo cp prometheus.yml /etc/prometheus/prometheus.yml
#
# Restart Prometheus:
# sudo systemctl restart prometheus
global:
scrape_interval: 15s # Scrape metrics every 15 seconds
evaluation_interval: 15s # Evaluate rules every 15 seconds
external_labels:
cluster: 'guruconnect-production'
environment: 'production'
# Scrape configurations
scrape_configs:
# GuruConnect server metrics
- job_name: 'guruconnect'
static_configs:
- targets: ['172.16.3.30:3002']
labels:
service: 'guruconnect-server'
instance: 'rmm-server'
# Node Exporter (system metrics)
# Install: sudo apt-get install prometheus-node-exporter
- job_name: 'node_exporter'
static_configs:
- targets: ['172.16.3.30:9100']
labels:
instance: 'rmm-server'
# Alert rules (optional)
# rule_files:
# - '/etc/prometheus/alerts.yml'
# Alertmanager configuration (optional)
# alerting:
# alertmanagers:
# - static_configs:
# - targets: ['localhost:9093']

View File

@@ -1,102 +0,0 @@
#!/bin/bash
# GuruConnect Monitoring Setup Script
# Installs and configures Prometheus and Grafana
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo "========================================="
echo "GuruConnect Monitoring Setup"
echo "========================================="
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}ERROR: This script must be run as root (sudo)${NC}"
exit 1
fi
# Update package list
echo "Updating package list..."
apt-get update
# Install Prometheus
echo ""
echo "Installing Prometheus..."
apt-get install -y prometheus prometheus-node-exporter
# Copy Prometheus configuration
echo "Copying Prometheus configuration..."
cp prometheus.yml /etc/prometheus/prometheus.yml
if [ -f "alerts.yml" ]; then
cp alerts.yml /etc/prometheus/alerts.yml
fi
# Set permissions
chown prometheus:prometheus /etc/prometheus/prometheus.yml
if [ -f "/etc/prometheus/alerts.yml" ]; then
chown prometheus:prometheus /etc/prometheus/alerts.yml
fi
# Restart Prometheus
echo "Restarting Prometheus..."
systemctl restart prometheus
systemctl enable prometheus
systemctl restart prometheus-node-exporter
systemctl enable prometheus-node-exporter
# Install Grafana
echo ""
echo "Installing Grafana..."
apt-get install -y software-properties-common
add-apt-repository -y "deb https://packages.grafana.com/oss/deb stable main"
wget -q -O - https://packages.grafana.com/gpg.key | apt-key add -
apt-get update
apt-get install -y grafana
# Start Grafana
echo "Starting Grafana..."
systemctl start grafana-server
systemctl enable grafana-server
# Wait for Grafana to start
sleep 5
# Configure Grafana data source (Prometheus)
echo ""
echo "Configuring Grafana data source..."
curl -X POST -H "Content-Type: application/json" \
-d '{
"name":"Prometheus",
"type":"prometheus",
"url":"http://localhost:9090",
"access":"proxy",
"isDefault":true
}' \
http://admin:admin@localhost:3000/api/datasources || true
echo ""
echo "========================================="
echo "Monitoring Setup Complete!"
echo "========================================="
echo ""
echo "Services:"
echo " Prometheus: http://172.16.3.30:9090"
echo " Grafana: http://172.16.3.30:3000 (default login: admin/admin)"
echo " Node Exporter: http://172.16.3.30:9100/metrics"
echo ""
echo "Next steps:"
echo "1. Access Grafana at http://172.16.3.30:3000"
echo "2. Login with default credentials (admin/admin)"
echo "3. Change the default password"
echo "4. Import the dashboard from grafana-dashboard.json"
echo "5. Configure alerting (optional)"
echo ""
echo "To import the dashboard:"
echo " Grafana > Dashboards > Import > Upload JSON file"
echo " Select: infrastructure/grafana-dashboard.json"
echo ""

View File

@@ -1,378 +0,0 @@
syntax = "proto3";
package guruconnect;
// ============================================================================
// Session Management
// ============================================================================
message SessionRequest {
string agent_id = 1;
string session_token = 2;
SessionType session_type = 3;
string client_version = 4;
}
message SessionResponse {
bool success = 1;
string session_id = 2;
string error = 3;
DisplayInfo display_info = 4;
}
enum SessionType {
SCREEN_CONTROL = 0;
VIEW_ONLY = 1;
BACKSTAGE = 2;
FILE_TRANSFER = 3;
}
// ============================================================================
// Display Information
// ============================================================================
message DisplayInfo {
repeated Display displays = 1;
int32 primary_display = 2;
}
message Display {
int32 id = 1;
string name = 2;
int32 x = 3;
int32 y = 4;
int32 width = 5;
int32 height = 6;
bool is_primary = 7;
}
message SwitchDisplay {
int32 display_id = 1;
}
// ============================================================================
// Video Frames
// ============================================================================
message VideoFrame {
int64 timestamp = 1;
int32 display_id = 2;
int32 sequence = 3;
oneof encoding {
RawFrame raw = 10;
EncodedFrame vp9 = 11;
EncodedFrame h264 = 12;
EncodedFrame h265 = 13;
}
}
message RawFrame {
int32 width = 1;
int32 height = 2;
bytes data = 3; // Zstd compressed BGRA
bool compressed = 4;
repeated DirtyRect dirty_rects = 5;
bool is_keyframe = 6; // Full frame vs incremental
}
message DirtyRect {
int32 x = 1;
int32 y = 2;
int32 width = 3;
int32 height = 4;
}
message EncodedFrame {
bytes data = 1;
bool keyframe = 2;
int64 pts = 3;
int64 dts = 4;
}
message VideoAck {
int32 sequence = 1;
int64 timestamp = 2;
}
// ============================================================================
// Cursor
// ============================================================================
message CursorShape {
uint64 id = 1;
int32 hotspot_x = 2;
int32 hotspot_y = 3;
int32 width = 4;
int32 height = 5;
bytes data = 6; // BGRA bitmap
}
message CursorPosition {
int32 x = 1;
int32 y = 2;
bool visible = 3;
}
// ============================================================================
// Input Events
// ============================================================================
message MouseEvent {
int32 x = 1;
int32 y = 2;
MouseButtons buttons = 3;
int32 wheel_delta_x = 4;
int32 wheel_delta_y = 5;
MouseEventType event_type = 6;
}
enum MouseEventType {
MOUSE_MOVE = 0;
MOUSE_DOWN = 1;
MOUSE_UP = 2;
MOUSE_WHEEL = 3;
}
message MouseButtons {
bool left = 1;
bool right = 2;
bool middle = 3;
bool x1 = 4;
bool x2 = 5;
}
message KeyEvent {
bool down = 1; // true = key down, false = key up
KeyEventType key_type = 2;
uint32 vk_code = 3; // Virtual key code (Windows VK_*)
uint32 scan_code = 4; // Hardware scan code
string unicode = 5; // Unicode character (for text input)
Modifiers modifiers = 6;
}
enum KeyEventType {
KEY_VK = 0; // Virtual key code
KEY_SCAN = 1; // Scan code
KEY_UNICODE = 2; // Unicode character
}
message Modifiers {
bool ctrl = 1;
bool alt = 2;
bool shift = 3;
bool meta = 4; // Windows key
bool caps_lock = 5;
bool num_lock = 6;
}
message SpecialKeyEvent {
SpecialKey key = 1;
}
enum SpecialKey {
CTRL_ALT_DEL = 0;
LOCK_SCREEN = 1;
PRINT_SCREEN = 2;
}
// ============================================================================
// Clipboard
// ============================================================================
message ClipboardData {
ClipboardFormat format = 1;
bytes data = 2;
string mime_type = 3;
}
enum ClipboardFormat {
CLIPBOARD_TEXT = 0;
CLIPBOARD_HTML = 1;
CLIPBOARD_RTF = 2;
CLIPBOARD_IMAGE = 3;
CLIPBOARD_FILES = 4;
}
message ClipboardRequest {
// Request current clipboard content
}
// ============================================================================
// Quality Control
// ============================================================================
message QualitySettings {
QualityPreset preset = 1;
int32 custom_fps = 2; // 1-60
int32 custom_bitrate = 3; // kbps
CodecPreference codec = 4;
}
enum QualityPreset {
QUALITY_AUTO = 0;
QUALITY_LOW = 1; // Low bandwidth
QUALITY_BALANCED = 2;
QUALITY_HIGH = 3; // Best quality
}
enum CodecPreference {
CODEC_AUTO = 0;
CODEC_RAW = 1; // Raw + Zstd (LAN)
CODEC_VP9 = 2;
CODEC_H264 = 3;
CODEC_H265 = 4;
}
message LatencyReport {
int64 rtt_ms = 1;
int32 fps = 2;
int32 bitrate_kbps = 3;
}
// ============================================================================
// Chat Messages
// ============================================================================
message ChatMessage {
string id = 1; // Unique message ID
string sender = 2; // "technician" or "client"
string content = 3; // Message text
int64 timestamp = 4; // Unix timestamp
}
// ============================================================================
// Control Messages
// ============================================================================
message Heartbeat {
int64 timestamp = 1;
}
message HeartbeatAck {
int64 client_timestamp = 1;
int64 server_timestamp = 2;
}
message Disconnect {
string reason = 1;
}
// Server commands agent to start streaming video
message StartStream {
string viewer_id = 1; // ID of viewer requesting stream
int32 display_id = 2; // Which display to stream (0 = primary)
}
// Server commands agent to stop streaming
message StopStream {
string viewer_id = 1; // Which viewer disconnected
}
// Agent reports its status periodically when idle
message AgentStatus {
string hostname = 1;
string os_version = 2;
bool is_elevated = 3;
int64 uptime_secs = 4;
int32 display_count = 5;
bool is_streaming = 6;
string agent_version = 7; // Agent version (e.g., "0.1.0-abc123")
string organization = 8; // Company/organization name
string site = 9; // Site/location name
repeated string tags = 10; // Tags for categorization
}
// Server commands agent to uninstall itself
message AdminCommand {
AdminCommandType command = 1;
string reason = 2; // Why the command was issued
}
enum AdminCommandType {
ADMIN_UNINSTALL = 0; // Uninstall agent and remove from startup
ADMIN_RESTART = 1; // Restart the agent process
ADMIN_UPDATE = 2; // Download and install update
}
// ============================================================================
// Auto-Update Messages
// ============================================================================
// Update command details (sent with AdminCommand or standalone)
message UpdateInfo {
string version = 1; // Target version (e.g., "0.2.0")
string download_url = 2; // HTTPS URL to download new binary
string checksum_sha256 = 3; // SHA-256 hash for verification
bool mandatory = 4; // If true, agent must update immediately
}
// Update status report (agent -> server)
message UpdateStatus {
string current_version = 1; // Current running version
UpdateState state = 2; // Current update state
string error_message = 3; // Error details if state is FAILED
int32 progress_percent = 4; // Download progress (0-100)
}
enum UpdateState {
UPDATE_IDLE = 0; // No update in progress
UPDATE_CHECKING = 1; // Checking for updates
UPDATE_DOWNLOADING = 2; // Downloading new binary
UPDATE_VERIFYING = 3; // Verifying checksum
UPDATE_INSTALLING = 4; // Installing (rename/copy)
UPDATE_RESTARTING = 5; // About to restart
UPDATE_COMPLETE = 6; // Update successful (after restart)
UPDATE_FAILED = 7; // Update failed
}
// ============================================================================
// Top-Level Message Wrapper
// ============================================================================
message Message {
oneof payload {
// Session
SessionRequest session_request = 1;
SessionResponse session_response = 2;
// Video
VideoFrame video_frame = 10;
VideoAck video_ack = 11;
SwitchDisplay switch_display = 12;
// Cursor
CursorShape cursor_shape = 15;
CursorPosition cursor_position = 16;
// Input
MouseEvent mouse_event = 20;
KeyEvent key_event = 21;
SpecialKeyEvent special_key = 22;
// Clipboard
ClipboardData clipboard_data = 30;
ClipboardRequest clipboard_request = 31;
// Quality
QualitySettings quality_settings = 40;
LatencyReport latency_report = 41;
// Control
Heartbeat heartbeat = 50;
HeartbeatAck heartbeat_ack = 51;
Disconnect disconnect = 52;
StartStream start_stream = 53;
StopStream stop_stream = 54;
AgentStatus agent_status = 55;
// Chat
ChatMessage chat_message = 60;
// Admin commands (server -> agent)
AdminCommand admin_command = 70;
// Auto-update messages
UpdateInfo update_info = 75; // Server -> Agent: update available
UpdateStatus update_status = 76; // Agent -> Server: update progress
}
}

View File

@@ -1,14 +0,0 @@
[package]
name = "guru-connect-scripts"
version = "0.1.0"
edition = "2021"
[workspace]
[[bin]]
name = "reset-admin-password"
path = "reset-admin-password.rs"
[dependencies]
argon2 = { version = "0.5", features = ["std"] }
rand_core = { version = "0.6", features = ["std"] }

View File

@@ -1,169 +0,0 @@
#!/bin/bash
# Automated deployment script for GuruConnect
# Called by CI/CD pipeline or manually
# Usage: ./deploy.sh [package_file.tar.gz]
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo "========================================="
echo "GuruConnect Deployment Script"
echo "========================================="
echo ""
# Configuration
DEPLOY_DIR="/home/guru/guru-connect"
BACKUP_DIR="/home/guru/deployments/backups"
ARTIFACT_DIR="/home/guru/deployments/artifacts"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
# Detect package file
if [ -n "$1" ]; then
PACKAGE_FILE="$1"
elif [ -f "/tmp/guruconnect-server-latest.tar.gz" ]; then
PACKAGE_FILE="/tmp/guruconnect-server-latest.tar.gz"
else
echo -e "${RED}ERROR: No deployment package specified${NC}"
echo "Usage: $0 <package_file.tar.gz>"
exit 1
fi
if [ ! -f "$PACKAGE_FILE" ]; then
echo -e "${RED}ERROR: Package file not found: $PACKAGE_FILE${NC}"
exit 1
fi
echo "Package: $PACKAGE_FILE"
echo "Target: $DEPLOY_DIR"
echo ""
# Create backup and artifact directories
mkdir -p "$BACKUP_DIR"
mkdir -p "$ARTIFACT_DIR"
# Backup current binary
echo "Creating backup..."
if [ -f "$DEPLOY_DIR/target/x86_64-unknown-linux-gnu/release/guruconnect-server" ]; then
cp "$DEPLOY_DIR/target/x86_64-unknown-linux-gnu/release/guruconnect-server" \
"$BACKUP_DIR/guruconnect-server-${TIMESTAMP}"
echo -e "${GREEN}Backup created: ${BACKUP_DIR}/guruconnect-server-${TIMESTAMP}${NC}"
else
echo -e "${YELLOW}No existing binary to backup${NC}"
fi
# Stop service
echo ""
echo "Stopping GuruConnect service..."
if sudo systemctl is-active --quiet guruconnect; then
sudo systemctl stop guruconnect
echo -e "${GREEN}Service stopped${NC}"
else
echo -e "${YELLOW}Service not running${NC}"
fi
# Extract new binary
echo ""
echo "Extracting deployment package..."
TEMP_EXTRACT="/tmp/guruconnect-deploy-${TIMESTAMP}"
mkdir -p "$TEMP_EXTRACT"
tar -xzf "$PACKAGE_FILE" -C "$TEMP_EXTRACT"
# Deploy binary
echo "Deploying new binary..."
if [ -f "$TEMP_EXTRACT/guruconnect-server" ]; then
mkdir -p "$DEPLOY_DIR/target/x86_64-unknown-linux-gnu/release"
cp "$TEMP_EXTRACT/guruconnect-server" \
"$DEPLOY_DIR/target/x86_64-unknown-linux-gnu/release/guruconnect-server"
chmod +x "$DEPLOY_DIR/target/x86_64-unknown-linux-gnu/release/guruconnect-server"
echo -e "${GREEN}Binary deployed${NC}"
else
echo -e "${RED}ERROR: Binary not found in package${NC}"
exit 1
fi
# Deploy static files if present
if [ -d "$TEMP_EXTRACT/static" ]; then
echo "Deploying static files..."
cp -r "$TEMP_EXTRACT/static" "$DEPLOY_DIR/server/"
echo -e "${GREEN}Static files deployed${NC}"
fi
# Deploy migrations if present
if [ -d "$TEMP_EXTRACT/migrations" ]; then
echo "Deploying database migrations..."
cp -r "$TEMP_EXTRACT/migrations" "$DEPLOY_DIR/server/"
echo -e "${GREEN}Migrations deployed${NC}"
fi
# Save artifact
echo ""
echo "Archiving deployment package..."
cp "$PACKAGE_FILE" "$ARTIFACT_DIR/guruconnect-server-${TIMESTAMP}.tar.gz"
ln -sf "$ARTIFACT_DIR/guruconnect-server-${TIMESTAMP}.tar.gz" \
"$ARTIFACT_DIR/guruconnect-server-latest.tar.gz"
echo -e "${GREEN}Artifact saved${NC}"
# Cleanup temp directory
rm -rf "$TEMP_EXTRACT"
# Start service
echo ""
echo "Starting GuruConnect service..."
sudo systemctl start guruconnect
sleep 2
# Verify service started
if sudo systemctl is-active --quiet guruconnect; then
echo -e "${GREEN}Service started successfully${NC}"
else
echo -e "${RED}ERROR: Service failed to start${NC}"
echo "Rolling back to previous version..."
# Rollback
if [ -f "$BACKUP_DIR/guruconnect-server-${TIMESTAMP}" ]; then
cp "$BACKUP_DIR/guruconnect-server-${TIMESTAMP}" \
"$DEPLOY_DIR/target/x86_64-unknown-linux-gnu/release/guruconnect-server"
sudo systemctl start guruconnect
echo -e "${YELLOW}Rolled back to previous version${NC}"
fi
echo "Check logs: sudo journalctl -u guruconnect -n 50"
exit 1
fi
# Health check
echo ""
echo "Running health check..."
sleep 2
if curl -s http://172.16.3.30:3002/health | grep -q "OK"; then
echo -e "${GREEN}Health check: PASSED${NC}"
else
echo -e "${YELLOW}WARNING: Health check failed${NC}"
echo "Service may still be starting up..."
fi
# Get version info
echo ""
echo "Deployment version information:"
VERSION=$($DEPLOY_DIR/target/x86_64-unknown-linux-gnu/release/guruconnect-server --version 2>/dev/null || echo "Version info not available")
echo "$VERSION"
echo ""
echo "========================================="
echo "Deployment Complete!"
echo "========================================="
echo ""
echo "Deployment time: $TIMESTAMP"
echo "Backup location: $BACKUP_DIR/guruconnect-server-${TIMESTAMP}"
echo "Artifact location: $ARTIFACT_DIR/guruconnect-server-${TIMESTAMP}.tar.gz"
echo ""
echo "Service status:"
sudo systemctl status guruconnect --no-pager | head -15
echo ""
echo "To view logs: sudo journalctl -u guruconnect -f"
echo "To rollback: cp $BACKUP_DIR/guruconnect-server-${TIMESTAMP} target/x86_64-unknown-linux-gnu/release/guruconnect-server && sudo systemctl restart guruconnect"
echo ""

View File

@@ -1,113 +0,0 @@
#!/bin/bash
# Install and configure Gitea Actions Runner
# Run as: sudo bash install-gitea-runner.sh
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo "========================================="
echo "Gitea Actions Runner Installation"
echo "========================================="
echo ""
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}ERROR: This script must be run as root (sudo)${NC}"
exit 1
fi
# Variables
RUNNER_VERSION="0.2.11"
RUNNER_USER="gitea-runner"
RUNNER_HOME="/home/${RUNNER_USER}"
GITEA_URL="https://git.azcomputerguru.com"
RUNNER_NAME="gururmm-runner"
echo "Installing Gitea Actions Runner v${RUNNER_VERSION}"
echo "Target: ${GITEA_URL}"
echo ""
# Create runner user
if ! id "${RUNNER_USER}" &>/dev/null; then
echo "Creating ${RUNNER_USER} user..."
useradd -m -s /bin/bash "${RUNNER_USER}"
echo -e "${GREEN}User created${NC}"
else
echo -e "${YELLOW}User ${RUNNER_USER} already exists${NC}"
fi
# Download runner binary
echo "Downloading Gitea Actions Runner..."
cd /tmp
wget -q "https://dl.gitea.com/act_runner/${RUNNER_VERSION}/act_runner-${RUNNER_VERSION}-linux-amd64" -O act_runner
# Install binary
echo "Installing binary..."
chmod +x act_runner
mv act_runner /usr/local/bin/
chown root:root /usr/local/bin/act_runner
# Create runner directory
echo "Creating runner directory..."
mkdir -p "${RUNNER_HOME}/.runner"
chown -R "${RUNNER_USER}:${RUNNER_USER}" "${RUNNER_HOME}/.runner"
echo ""
echo "========================================="
echo "Runner Registration"
echo "========================================="
echo ""
echo "To complete setup, you need to register the runner with Gitea:"
echo ""
echo "1. Go to: ${GITEA_URL}/admin/actions/runners"
echo "2. Click 'Create new Runner'"
echo "3. Copy the registration token"
echo "4. Run as ${RUNNER_USER}:"
echo ""
echo " sudo -u ${RUNNER_USER} act_runner register \\"
echo " --instance ${GITEA_URL} \\"
echo " --token YOUR_REGISTRATION_TOKEN \\"
echo " --name ${RUNNER_NAME} \\"
echo " --labels ubuntu-latest,ubuntu-22.04"
echo ""
echo "5. Then create systemd service:"
echo ""
cat > /etc/systemd/system/gitea-runner.service << 'EOF'
[Unit]
Description=Gitea Actions Runner
After=network.target
[Service]
Type=simple
User=gitea-runner
WorkingDirectory=/home/gitea-runner/.runner
ExecStart=/usr/local/bin/act_runner daemon
Restart=always
RestartSec=10
Environment="HOME=/home/gitea-runner"
[Install]
WantedBy=multi-user.target
EOF
echo "Systemd service created at /etc/systemd/system/gitea-runner.service"
echo ""
echo "After registration, enable and start the service:"
echo " sudo systemctl daemon-reload"
echo " sudo systemctl enable gitea-runner"
echo " sudo systemctl start gitea-runner"
echo " sudo systemctl status gitea-runner"
echo ""
echo "========================================="
echo "Installation Complete!"
echo "========================================="
echo ""
echo -e "${YELLOW}Next Steps:${NC}"
echo "1. Register the runner (see instructions above)"
echo "2. Start the systemd service"
echo "3. Verify runner shows up in Gitea Admin > Actions > Runners"
echo ""

View File

@@ -1,27 +0,0 @@
// Temporary password reset utility
// Usage: cargo run --manifest-path scripts/Cargo.toml --bin reset-admin-password
use argon2::{
password_hash::{PasswordHasher, SaltString},
Argon2, Algorithm, Version, Params,
};
use rand_core::OsRng;
fn main() {
let password = "AdminGuruConnect2026"; // Temporary password (no special chars)
let argon2 = Argon2::new(
Algorithm::Argon2id,
Version::V0x13,
Params::default(),
);
let salt = SaltString::generate(&mut OsRng);
let password_hash = argon2
.hash_password(password.as_bytes(), &salt)
.expect("Failed to hash password")
.to_string();
println!("Password: {}", password);
println!("Hash: {}", password_hash);
}

View File

@@ -1,120 +0,0 @@
#!/bin/bash
# Automated version tagging script
# Creates git tags based on semantic versioning
# Usage: ./version-tag.sh [major|minor|patch]
set -e
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
BUMP_TYPE="${1:-patch}"
echo "========================================="
echo "GuruConnect Version Tagging"
echo "========================================="
echo ""
# Validate bump type
if [[ ! "$BUMP_TYPE" =~ ^(major|minor|patch)$ ]]; then
echo -e "${RED}ERROR: Invalid bump type: $BUMP_TYPE${NC}"
echo "Usage: $0 [major|minor|patch]"
exit 1
fi
# Get current version from latest tag
CURRENT_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
echo "Current version: $CURRENT_TAG"
# Parse version
if [[ $CURRENT_TAG =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
MAJOR="${BASH_REMATCH[1]}"
MINOR="${BASH_REMATCH[2]}"
PATCH="${BASH_REMATCH[3]}"
else
echo -e "${YELLOW}No valid version tag found, starting from v0.1.0${NC}"
MAJOR=0
MINOR=1
PATCH=0
fi
# Bump version
case $BUMP_TYPE in
major)
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
;;
minor)
MINOR=$((MINOR + 1))
PATCH=0
;;
patch)
PATCH=$((PATCH + 1))
;;
esac
NEW_TAG="v${MAJOR}.${MINOR}.${PATCH}"
echo "New version: $NEW_TAG"
echo ""
# Check if tag already exists
if git rev-parse "$NEW_TAG" >/dev/null 2>&1; then
echo -e "${RED}ERROR: Tag $NEW_TAG already exists${NC}"
exit 1
fi
# Show changes since last tag
echo "Changes since $CURRENT_TAG:"
echo "-------------------------------------------"
git log --oneline "${CURRENT_TAG}..HEAD" | head -20
echo "-------------------------------------------"
echo ""
# Confirm
read -p "Create tag $NEW_TAG? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Cancelled."
exit 0
fi
# Update Cargo.toml versions
echo ""
echo "Updating Cargo.toml versions..."
if [ -f "server/Cargo.toml" ]; then
sed -i.bak "s/^version = .*/version = \"${MAJOR}.${MINOR}.${PATCH}\"/" server/Cargo.toml
rm server/Cargo.toml.bak 2>/dev/null || true
echo -e "${GREEN}Updated server/Cargo.toml${NC}"
fi
if [ -f "agent/Cargo.toml" ]; then
sed -i.bak "s/^version = .*/version = \"${MAJOR}.${MINOR}.${PATCH}\"/" agent/Cargo.toml
rm agent/Cargo.toml.bak 2>/dev/null || true
echo -e "${GREEN}Updated agent/Cargo.toml${NC}"
fi
# Commit version bump
echo ""
echo "Committing version bump..."
git add server/Cargo.toml agent/Cargo.toml 2>/dev/null || true
git commit -m "chore: bump version to ${NEW_TAG}" || echo "No changes to commit"
# Create tag
echo ""
echo "Creating tag $NEW_TAG..."
git tag -a "$NEW_TAG" -m "Release $NEW_TAG"
echo -e "${GREEN}Tag created successfully${NC}"
echo ""
echo "To push tag to remote:"
echo " git push origin $NEW_TAG"
echo ""
echo "To push all changes and tag:"
echo " git push origin main && git push origin $NEW_TAG"
echo ""
echo "This will trigger the deployment workflow in CI/CD"
echo ""

View File

@@ -1,33 +0,0 @@
# GuruConnect Server Configuration
# REQUIRED: JWT Secret for authentication token signing
# Generate a new secret with: openssl rand -base64 64
# CRITICAL: Change this before deploying to production!
JWT_SECRET=KfPrjjC3J6YMx9q1yjPxZAYkHLM2JdFy1XRxHJ9oPnw0NU3xH074ufHk7fj++e8BJEqRQ5k4zlWD+1iDwlLP4w==
# JWT token expiration in hours (default: 24)
JWT_EXPIRY_HOURS=24
# Database connection URL (PostgreSQL)
# Format: postgresql://username:password@host:port/database
DATABASE_URL=postgresql://guruconnect:password@172.16.3.30:5432/guruconnect
# Maximum database connections in pool
DATABASE_MAX_CONNECTIONS=10
# Server listen address and port
LISTEN_ADDR=0.0.0.0:3002
# Optional: API key for persistent agents
# If set, persistent agents must provide this key to connect
AGENT_API_KEY=
# Debug mode (enables verbose logging)
DEBUG=false
# SECURITY NOTES:
# 1. NEVER commit the actual .env file to git
# 2. Rotate JWT_SECRET regularly (every 90 days recommended)
# 3. Use a unique AGENT_API_KEY per deployment
# 4. Keep DATABASE_URL credentials secure
# 5. Set restrictive file permissions: chmod 600 .env

View File

@@ -1,67 +0,0 @@
[package]
name = "guruconnect-server"
version = "0.1.0"
edition = "2021"
authors = ["AZ Computer Guru"]
description = "GuruConnect Remote Desktop Relay Server"
[dependencies]
# Async runtime
tokio = { version = "1", features = ["full", "sync", "time", "rt-multi-thread", "macros"] }
# Web framework
axum = { version = "0.7", features = ["ws", "macros"] }
tower = "0.5"
tower-http = { version = "0.6", features = ["cors", "trace", "compression-gzip", "fs"] }
tower_governor = { version = "0.4", features = ["axum"] }
# WebSocket
futures-util = "0.3"
# Database
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "uuid", "chrono", "json"] }
# Protocol (protobuf)
prost = "0.13"
prost-types = "0.13"
bytes = "1"
# Serialization
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# Logging
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Error handling
anyhow = "1"
thiserror = "1"
# Configuration
toml = "0.8"
# Auth
jsonwebtoken = "9"
argon2 = "0.5"
# Crypto
ring = "0.17"
# UUID
uuid = { version = "1", features = ["v4", "serde"] }
# Time
chrono = { version = "0.4", features = ["serde"] }
rand = "0.8"
# Monitoring
prometheus-client = "0.22"
[build-dependencies]
prost-build = "0.13"
[profile.release]
lto = true
codegen-units = 1
strip = true

View File

@@ -1,80 +0,0 @@
#!/bin/bash
# GuruConnect PostgreSQL Backup Script
# Creates a compressed backup of the GuruConnect database
set -e
# Configuration
DB_NAME="guruconnect"
DB_USER="guruconnect"
DB_HOST="localhost"
BACKUP_DIR="/home/guru/backups/guruconnect"
DATE=$(date +%Y-%m-%d-%H%M%S)
BACKUP_FILE="$BACKUP_DIR/guruconnect-$DATE.sql.gz"
# Retention policy (days)
DAILY_RETENTION=30
WEEKLY_RETENTION=28 # 4 weeks
MONTHLY_RETENTION=180 # 6 months
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo "========================================="
echo "GuruConnect Database Backup"
echo "========================================="
echo "Date: $(date)"
echo "Database: $DB_NAME"
echo "Backup file: $BACKUP_FILE"
echo ""
# Create backup directory if it doesn't exist
mkdir -p "$BACKUP_DIR"
# Perform backup
echo "Starting backup..."
if PGPASSWORD="${DB_PASSWORD:-}" pg_dump -h "$DB_HOST" -U "$DB_USER" "$DB_NAME" | gzip > "$BACKUP_FILE"; then
BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
echo -e "${GREEN}SUCCESS: Backup completed${NC}"
echo "Backup size: $BACKUP_SIZE"
else
echo -e "${RED}ERROR: Backup failed${NC}"
exit 1
fi
# Retention policy enforcement
echo ""
echo "Applying retention policy..."
# Keep daily backups for 30 days
find "$BACKUP_DIR" -name "guruconnect-*.sql.gz" -type f -mtime +$DAILY_RETENTION -delete
DAILY_DELETED=$?
# Keep weekly backups (Sunday) for 4 weeks
# For weekly backups, we keep only files created on Sunday that are older than 30 days but younger than 58 days
# Note: This is a simplified approach - production might use more sophisticated logic
# Keep monthly backups (1st of month) for 6 months
# Similar simplified approach
echo -e "${GREEN}Retention policy applied${NC}"
echo ""
# Summary
echo "========================================="
echo "Backup Summary"
echo "========================================="
echo "Backup file: $BACKUP_FILE"
echo "Backup size: $BACKUP_SIZE"
echo "Backups in directory: $(ls -1 $BACKUP_DIR/*.sql.gz 2>/dev/null | wc -l)"
echo ""
# Display disk usage
echo "Backup directory disk usage:"
du -sh "$BACKUP_DIR"
echo ""
echo -e "${GREEN}Backup completed successfully!${NC}"

View File

@@ -1,11 +0,0 @@
use std::io::Result;
fn main() -> Result<()> {
// Compile protobuf definitions
prost_build::compile_protos(&["../proto/guruconnect.proto"], &["../proto/"])?;
// Rerun if proto changes
println!("cargo:rerun-if-changed=../proto/guruconnect.proto");
Ok(())
}

View File

@@ -1,20 +0,0 @@
[Unit]
Description=GuruConnect PostgreSQL Backup
Documentation=https://git.azcomputerguru.com/azcomputerguru/guru-connect
[Service]
Type=oneshot
User=guru
Group=guru
WorkingDirectory=/home/guru/guru-connect/server
# Environment variables (database password)
EnvironmentFile=/home/guru/guru-connect/server/.env
# Run backup script
ExecStart=/bin/bash /home/guru/guru-connect/server/backup-postgres.sh
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=guruconnect-backup

View File

@@ -1,14 +0,0 @@
[Unit]
Description=GuruConnect PostgreSQL Backup Timer
Documentation=https://git.azcomputerguru.com/azcomputerguru/guru-connect
[Timer]
# Run daily at 2:00 AM
OnCalendar=daily
OnCalendar=*-*-* 02:00:00
# If system was off, run 10 minutes after boot
Persistent=true
[Install]
WantedBy=timers.target

View File

@@ -1,22 +0,0 @@
# GuruConnect log rotation configuration
# Copy to: /etc/logrotate.d/guruconnect
/var/log/guruconnect/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 0640 guru guru
sharedscripts
postrotate
systemctl reload guruconnect >/dev/null 2>&1 || true
endscript
}
# If using journald (systemd), logs are managed automatically
# View logs with: journalctl -u guruconnect
# Configure journald retention in: /etc/systemd/journald.conf
# SystemMaxUse=500M
# MaxRetentionSec=1month

View File

@@ -1,45 +0,0 @@
[Unit]
Description=GuruConnect Remote Desktop Server
Documentation=https://git.azcomputerguru.com/azcomputerguru/guru-connect
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
Type=simple
User=guru
Group=guru
WorkingDirectory=/home/guru/guru-connect/server
# Environment variables (loaded from .env file)
EnvironmentFile=/home/guru/guru-connect/server/.env
# Start command
ExecStart=/home/guru/guru-connect/target/x86_64-unknown-linux-gnu/release/guruconnect-server
# Restart policy
Restart=on-failure
RestartSec=10s
StartLimitInterval=5min
StartLimitBurst=3
# Resource limits
LimitNOFILE=65536
LimitNPROC=4096
# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/home/guru/guru-connect/server
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=guruconnect
# Watchdog (server must send keepalive every 30s or systemd restarts)
WatchdogSec=30s
[Install]
WantedBy=multi-user.target

View File

@@ -1,148 +0,0 @@
#!/bin/bash
# GuruConnect Health Monitoring Script
# Checks server health and sends alerts if issues detected
set -e
# Configuration
HEALTH_URL="http://172.16.3.30:3002/health"
ALERT_EMAIL="admin@azcomputerguru.com"
LOG_FILE="/var/log/guruconnect/health-monitor.log"
# Thresholds
MAX_DISK_USAGE=90
MAX_MEMORY_USAGE=90
MAX_SESSIONS=100
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Logging function
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# Health check result
HEALTH_STATUS="OK"
HEALTH_ISSUES=()
log "========================================="
log "GuruConnect Health Check"
log "========================================="
# Check 1: HTTP health endpoint
log "Checking HTTP health endpoint..."
if HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_URL" --max-time 5); then
if [ "$HTTP_STATUS" = "200" ]; then
log "[OK] HTTP health endpoint responding (HTTP $HTTP_STATUS)"
else
log "[ERROR] HTTP health endpoint returned HTTP $HTTP_STATUS"
HEALTH_STATUS="ERROR"
HEALTH_ISSUES+=("HTTP health endpoint returned HTTP $HTTP_STATUS")
fi
else
log "[ERROR] HTTP health endpoint not reachable"
HEALTH_STATUS="ERROR"
HEALTH_ISSUES+=("HTTP health endpoint not reachable")
fi
# Check 2: Systemd service status
log "Checking systemd service status..."
if systemctl is-active --quiet guruconnect 2>/dev/null; then
log "[OK] guruconnect service is running"
else
log "[ERROR] guruconnect service is not running"
HEALTH_STATUS="ERROR"
HEALTH_ISSUES+=("guruconnect service is not running")
fi
# Check 3: Disk space
log "Checking disk space..."
DISK_USAGE=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -lt "$MAX_DISK_USAGE" ]; then
log "[OK] Disk usage: ${DISK_USAGE}% (threshold: ${MAX_DISK_USAGE}%)"
else
log "[WARNING] Disk usage: ${DISK_USAGE}% (threshold: ${MAX_DISK_USAGE}%)"
HEALTH_STATUS="WARNING"
HEALTH_ISSUES+=("Disk usage ${DISK_USAGE}% exceeds threshold")
fi
# Check 4: Memory usage
log "Checking memory usage..."
MEMORY_USAGE=$(free | awk 'NR==2 {printf "%.0f", $3/$2 * 100.0}')
if [ "$MEMORY_USAGE" -lt "$MAX_MEMORY_USAGE" ]; then
log "[OK] Memory usage: ${MEMORY_USAGE}% (threshold: ${MAX_MEMORY_USAGE}%)"
else
log "[WARNING] Memory usage: ${MEMORY_USAGE}% (threshold: ${MAX_MEMORY_USAGE}%)"
HEALTH_STATUS="WARNING"
HEALTH_ISSUES+=("Memory usage ${MEMORY_USAGE}% exceeds threshold")
fi
# Check 5: Database connectivity
log "Checking database connectivity..."
if systemctl is-active --quiet postgresql 2>/dev/null; then
log "[OK] PostgreSQL service is running"
else
log "[WARNING] PostgreSQL service is not running"
HEALTH_STATUS="WARNING"
HEALTH_ISSUES+=("PostgreSQL service is not running")
fi
# Check 6: Metrics endpoint
log "Checking Prometheus metrics endpoint..."
if METRICS=$(curl -s "http://172.16.3.30:3002/metrics" --max-time 5); then
if echo "$METRICS" | grep -q "guruconnect_uptime_seconds"; then
log "[OK] Prometheus metrics endpoint working"
else
log "[WARNING] Prometheus metrics endpoint not returning expected data"
HEALTH_STATUS="WARNING"
HEALTH_ISSUES+=("Prometheus metrics endpoint not returning expected data")
fi
else
log "[ERROR] Prometheus metrics endpoint not reachable"
HEALTH_STATUS="ERROR"
HEALTH_ISSUES+=("Prometheus metrics endpoint not reachable")
fi
# Summary
log "========================================="
log "Health Check Summary"
log "========================================="
log "Status: $HEALTH_STATUS"
if [ "${#HEALTH_ISSUES[@]}" -gt 0 ]; then
log "Issues found:"
for issue in "${HEALTH_ISSUES[@]}"; do
log " - $issue"
done
# Send alert email (if configured)
if command -v mail &> /dev/null; then
{
echo "GuruConnect Health Check FAILED"
echo ""
echo "Status: $HEALTH_STATUS"
echo "Date: $(date)"
echo ""
echo "Issues:"
for issue in "${HEALTH_ISSUES[@]}"; do
echo " - $issue"
done
} | mail -s "GuruConnect Health Check Alert" "$ALERT_EMAIL"
log "Alert email sent to $ALERT_EMAIL"
fi
else
log "All checks passed!"
fi
# Exit with appropriate code
if [ "$HEALTH_STATUS" = "ERROR" ]; then
exit 2
elif [ "$HEALTH_STATUS" = "WARNING" ]; then
exit 1
else
exit 0
fi

View File

@@ -1,88 +0,0 @@
-- GuruConnect Initial Schema
-- Machine persistence, session audit logging, and support codes
-- Enable UUID generation
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- Machines table - persistent agent records that survive server restarts
CREATE TABLE connect_machines (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
agent_id VARCHAR(255) UNIQUE NOT NULL,
hostname VARCHAR(255) NOT NULL,
os_version VARCHAR(255),
is_elevated BOOLEAN DEFAULT FALSE,
is_persistent BOOLEAN DEFAULT TRUE,
first_seen TIMESTAMPTZ DEFAULT NOW(),
last_seen TIMESTAMPTZ DEFAULT NOW(),
last_session_id UUID,
status VARCHAR(20) DEFAULT 'offline',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_connect_machines_agent_id ON connect_machines(agent_id);
CREATE INDEX idx_connect_machines_status ON connect_machines(status);
-- Sessions table - connection history
CREATE TABLE connect_sessions (
id UUID PRIMARY KEY,
machine_id UUID REFERENCES connect_machines(id) ON DELETE CASCADE,
started_at TIMESTAMPTZ DEFAULT NOW(),
ended_at TIMESTAMPTZ,
duration_secs INTEGER,
is_support_session BOOLEAN DEFAULT FALSE,
support_code VARCHAR(10),
status VARCHAR(20) DEFAULT 'active'
);
CREATE INDEX idx_connect_sessions_machine ON connect_sessions(machine_id);
CREATE INDEX idx_connect_sessions_started ON connect_sessions(started_at DESC);
CREATE INDEX idx_connect_sessions_support_code ON connect_sessions(support_code);
-- Session events - comprehensive audit log
CREATE TABLE connect_session_events (
id BIGSERIAL PRIMARY KEY,
session_id UUID REFERENCES connect_sessions(id) ON DELETE CASCADE,
event_type VARCHAR(50) NOT NULL,
timestamp TIMESTAMPTZ DEFAULT NOW(),
viewer_id VARCHAR(255),
viewer_name VARCHAR(255),
details JSONB,
ip_address INET
);
CREATE INDEX idx_connect_events_session ON connect_session_events(session_id);
CREATE INDEX idx_connect_events_time ON connect_session_events(timestamp DESC);
CREATE INDEX idx_connect_events_type ON connect_session_events(event_type);
-- Support codes - persistent across restarts
CREATE TABLE connect_support_codes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(10) UNIQUE NOT NULL,
session_id UUID,
created_by VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
expires_at TIMESTAMPTZ,
status VARCHAR(20) DEFAULT 'pending',
client_name VARCHAR(255),
client_machine VARCHAR(255),
connected_at TIMESTAMPTZ
);
CREATE INDEX idx_support_codes_code ON connect_support_codes(code);
CREATE INDEX idx_support_codes_status ON connect_support_codes(status);
CREATE INDEX idx_support_codes_session ON connect_support_codes(session_id);
-- Trigger to auto-update updated_at on machines
CREATE OR REPLACE FUNCTION update_connect_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER update_connect_machines_updated_at
BEFORE UPDATE ON connect_machines
FOR EACH ROW
EXECUTE FUNCTION update_connect_updated_at();

View File

@@ -1,44 +0,0 @@
-- GuruConnect User Management Schema
-- User authentication, roles, and per-client access control
-- Users table
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
username VARCHAR(64) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
email VARCHAR(255),
role VARCHAR(32) NOT NULL DEFAULT 'viewer',
enabled BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_login TIMESTAMPTZ
);
-- Granular permissions (what actions a user can perform)
CREATE TABLE user_permissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
permission VARCHAR(64) NOT NULL,
UNIQUE(user_id, permission)
);
-- Per-client access (which machines a user can access)
-- No entries = access to all clients (for admins)
CREATE TABLE user_client_access (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
client_id UUID NOT NULL REFERENCES connect_machines(id) ON DELETE CASCADE,
UNIQUE(user_id, client_id)
);
-- Indexes
CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_users_enabled ON users(enabled);
CREATE INDEX idx_user_permissions_user ON user_permissions(user_id);
CREATE INDEX idx_user_client_access_user ON user_client_access(user_id);
-- Trigger for updated_at
CREATE TRIGGER update_users_updated_at
BEFORE UPDATE ON users
FOR EACH ROW
EXECUTE FUNCTION update_connect_updated_at();

View File

@@ -1,35 +0,0 @@
-- Migration: 003_auto_update.sql
-- Purpose: Add auto-update infrastructure (releases table and machine version tracking)
-- ============================================================================
-- Releases Table
-- ============================================================================
-- Track available agent releases
CREATE TABLE IF NOT EXISTS releases (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
version VARCHAR(32) NOT NULL UNIQUE,
download_url TEXT NOT NULL,
checksum_sha256 VARCHAR(64) NOT NULL,
release_notes TEXT,
is_stable BOOLEAN NOT NULL DEFAULT false,
is_mandatory BOOLEAN NOT NULL DEFAULT false,
min_version VARCHAR(32), -- Minimum version that can update to this
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Index for finding latest stable release
CREATE INDEX IF NOT EXISTS idx_releases_stable ON releases(is_stable, created_at DESC);
-- ============================================================================
-- Machine Version Tracking
-- ============================================================================
-- Add version tracking columns to existing machines table
ALTER TABLE connect_machines ADD COLUMN IF NOT EXISTS agent_version VARCHAR(32);
ALTER TABLE connect_machines ADD COLUMN IF NOT EXISTS update_status VARCHAR(32);
ALTER TABLE connect_machines ADD COLUMN IF NOT EXISTS last_update_check TIMESTAMPTZ;
-- Index for finding machines needing updates
CREATE INDEX IF NOT EXISTS idx_machines_version ON connect_machines(agent_version);
CREATE INDEX IF NOT EXISTS idx_machines_update_status ON connect_machines(update_status);

View File

@@ -1,104 +0,0 @@
#!/bin/bash
# GuruConnect PostgreSQL Restore Script
# Restores a GuruConnect database backup
set -e
# Configuration
DB_NAME="guruconnect"
DB_USER="guruconnect"
DB_HOST="localhost"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Check arguments
if [ $# -eq 0 ]; then
echo -e "${RED}ERROR: No backup file specified${NC}"
echo ""
echo "Usage: $0 <backup-file.sql.gz>"
echo ""
echo "Example:"
echo " $0 /home/guru/backups/guruconnect/guruconnect-2026-01-18-020000.sql.gz"
echo ""
echo "Available backups:"
ls -lh /home/guru/backups/guruconnect/*.sql.gz 2>/dev/null || echo " No backups found"
exit 1
fi
BACKUP_FILE="$1"
# Check if backup file exists
if [ ! -f "$BACKUP_FILE" ]; then
echo -e "${RED}ERROR: Backup file not found: $BACKUP_FILE${NC}"
exit 1
fi
echo "========================================="
echo "GuruConnect Database Restore"
echo "========================================="
echo "Date: $(date)"
echo "Database: $DB_NAME"
echo "Backup file: $BACKUP_FILE"
echo ""
# Warning
echo -e "${YELLOW}WARNING: This will OVERWRITE the current database!${NC}"
echo ""
read -p "Are you sure you want to restore? (yes/no): " -r
echo
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
echo "Restore cancelled."
exit 0
fi
# Stop GuruConnect server (if running as systemd service)
echo "Stopping GuruConnect server..."
if systemctl is-active --quiet guruconnect 2>/dev/null; then
sudo systemctl stop guruconnect
echo -e "${GREEN}Server stopped${NC}"
else
echo "Server not running or not managed by systemd"
fi
# Drop and recreate database
echo ""
echo "Dropping existing database..."
PGPASSWORD="${DB_PASSWORD:-}" psql -h "$DB_HOST" -U "$DB_USER" -c "DROP DATABASE IF EXISTS $DB_NAME;" postgres
echo "Creating new database..."
PGPASSWORD="${DB_PASSWORD:-}" psql -h "$DB_HOST" -U "$DB_USER" -c "CREATE DATABASE $DB_NAME;" postgres
# Restore backup
echo ""
echo "Restoring from backup..."
if gunzip -c "$BACKUP_FILE" | PGPASSWORD="${DB_PASSWORD:-}" psql -h "$DB_HOST" -U "$DB_USER" "$DB_NAME"; then
echo -e "${GREEN}SUCCESS: Database restored${NC}"
else
echo -e "${RED}ERROR: Restore failed${NC}"
exit 1
fi
# Restart GuruConnect server
echo ""
echo "Starting GuruConnect server..."
if systemctl is-enabled --quiet guruconnect 2>/dev/null; then
sudo systemctl start guruconnect
sleep 2
if systemctl is-active --quiet guruconnect; then
echo -e "${GREEN}Server started successfully${NC}"
else
echo -e "${RED}ERROR: Server failed to start${NC}"
echo "Check logs with: sudo journalctl -u guruconnect -n 50"
fi
else
echo "Server not configured as systemd service - start manually"
fi
echo ""
echo "========================================="
echo "Restore completed!"
echo "========================================="

Some files were not shown because too many files have changed in this diff Show More