Compare commits
48 Commits
911f3589a2
...
feat/docke
| Author | SHA1 | Date | |
|---|---|---|---|
| 18c2322a8e | |||
| 30f8ca3863 | |||
| 7efba3ac5b | |||
|
|
cf1373cd68 | ||
|
|
bc875ef9fb | ||
|
|
c579b07843 | ||
|
|
d3f50cdadc | ||
|
|
8aa85e62e5 | ||
|
|
b9cf8a47f7 | ||
|
|
2e749228bb | ||
|
|
ce20fad4d3 | ||
|
|
401b23ce46 | ||
| 13dbf18f67 | |||
| 58f7dd65f1 | |||
| a4390fabcc | |||
| bb40ded253 | |||
| 0845262c05 | |||
| b59f8952ac | |||
| 515fe8a830 | |||
| 056c39aa71 | |||
| 71dfd04108 | |||
| d92e1426ba | |||
| 9531bff929 | |||
| 0b4e9e092d | |||
| 46ac5a72d0 | |||
| b77de4e384 | |||
| 85fd05c6cf | |||
| b54760f62b | |||
| 1210a44ecc | |||
| e2b040e5f0 | |||
| f5b3a04378 | |||
| a4c5a10c4f | |||
| b8a8e1bdce | |||
| 40a48eb605 | |||
| 266f563c2f | |||
| a49c4f40e5 | |||
| a8851c19e4 | |||
| 3497d93dcb | |||
| 955c3255a0 | |||
| 6b367a7c95 | |||
| 02155976ab | |||
| 4c7f22b903 | |||
| f0f7c2613e | |||
| b92ca00054 | |||
| 2315d56db0 | |||
| 98c0142938 | |||
| 4b3eef4150 | |||
| 857f0daa95 |
59
.planning/PROJECT.md
Normal file
59
.planning/PROJECT.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# NixOS Infrastructure with AI Assistant
|
||||
|
||||
## What This Is
|
||||
|
||||
This project manages a NixOS-based infrastructure with Docker services, integrated with OpenCode AI assistant for automated management. The system supports:
|
||||
|
||||
- Reproducible NixOS infrastructure configuration
|
||||
- Docker service management via Docker Compose
|
||||
- AI-assisted infrastructure operations
|
||||
- Automatic service deployment and lifecycle management
|
||||
- Integration with existing Docker stacks (ai, cloudstorage, homeautomation, network, passwordmanager, versioncontrol)
|
||||
|
||||
## Core Value
|
||||
|
||||
The core value is a **reproducible and evolvable NixOS infrastructure** that can be managed through natural language interactions with the OpenCode AI assistant. The system should automatically detect and integrate new Docker services while maintaining consistency across all deployments.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Validated
|
||||
|
||||
- NixOS configuration management with flakes
|
||||
- Docker service integration via docker_manager.nix
|
||||
- Traefik reverse proxy with automatic TLS certificates
|
||||
- Environment variable management via agenix secrets
|
||||
- Standardized service patterns across all Docker stacks
|
||||
|
||||
### Active
|
||||
|
||||
- [ ] Automatic detection and integration of new Docker Compose files in `assets/compose/`
|
||||
- [ ] AI assistant integration for service lifecycle management
|
||||
- [ ] Service health monitoring and logging verification
|
||||
- [ ] Documentation of integration patterns in SKILL.md
|
||||
- [ ] Automated system update workflow (`nh os switch`)
|
||||
|
||||
### Out of Scope
|
||||
|
||||
- Full n8n integration for automated workflows - deferring to future milestone
|
||||
- Self-healing infrastructure with automatic problem detection - future enhancement
|
||||
- Multi-host orchestration - single-host focus for v1
|
||||
|
||||
## Key Decisions
|
||||
|
||||
| Decision | Rationale | Outcome |
|
||||
|----------|-----------|---------|
|
||||
| NixOS with Flakes | Reproducible infrastructure, better dependency management | Good |
|
||||
| Docker Compose integration | Preserves existing service configurations, flexibility | Good |
|
||||
| agenix for secrets | Secure secrets management, Nix native integration | Good |
|
||||
| Traefik reverse proxy | Unified HTTPS entrypoint, automatic certificate management | Good |
|
||||
| Standardized service patterns | Consistency across services, easier maintenance | Pending |
|
||||
|
||||
## Context
|
||||
|
||||
- **Existing Services**: ai (Llama.cpp, Open WebUI, n8n), cloudstorage (Nextcloud), homeautomation (Home Assistant), network (Traefik, DDNS), passwordmanager (Vaultwarden), versioncontrol (Gitea)
|
||||
- **Tech Stack**: NixOS unstable, Docker, Docker Compose, Traefik, agenix, OpenCode AI
|
||||
- **Hardware**: AMD MI50 GPUs for AI workloads
|
||||
- **Network**: Traefik-net bridge network for all services
|
||||
- **Storage**: `/mnt/HoardingCow_docker_data/<service>` for persistent data
|
||||
|
||||
**Last updated: 2026-01-01 after init**
|
||||
147
.planning/ROADMAP.md
Normal file
147
.planning/ROADMAP.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# Roadmap: NixOS Infrastructure with AI Assistant
|
||||
|
||||
## Overview
|
||||
|
||||
This roadmap outlines the implementation of a reproducible NixOS infrastructure with Docker service management, integrated with an AI assistant for automated operations. The system will automatically detect and integrate new Docker services while maintaining consistency across deployments.
|
||||
|
||||
## Domain Expertise
|
||||
|
||||
None
|
||||
|
||||
## Phases
|
||||
|
||||
- ✅ **Phase 1: Foundation Setup** - Establish core NixOS configuration with flakes
|
||||
- ✅ **Phase 2: Docker Service Integration** - Integrate Docker Compose services
|
||||
- ✅ **Phase 3: AI Assistant Integration** - Enable AI-assisted infrastructure management
|
||||
- [ ] **Phase 4: Internet Access & MCP** - MCP server for web access
|
||||
|
||||
|
||||
## Phase Details
|
||||
|
||||
### Phase 1: Foundation Setup
|
||||
**Goal**: Establish the core NixOS configuration with flakes and basic infrastructure
|
||||
**Depends on**: Nothing (first phase)
|
||||
**Research**: Unlikely (established Nix patterns)
|
||||
**Plans**: 3 plans
|
||||
**Status**: Complete
|
||||
|
||||
Plans:
|
||||
- [x] 01-01: Set up NixOS flake structure with hardware configuration
|
||||
- [x] 01-02: Configure basic services and networking
|
||||
- [x] 01-03: Implement secrets management with agenix
|
||||
|
||||
### Phase 2: Docker Service Integration
|
||||
**Goal**: Integrate Docker service management with Traefik reverse proxy
|
||||
**Depends on**: Phase 1
|
||||
**Research**: Unlikely (existing Docker Compose patterns)
|
||||
**Plans**: 3 plans
|
||||
**Status**: Complete
|
||||
|
||||
Plans:
|
||||
- [x] 02-01: Implement docker_manager.nix for service integration
|
||||
- [x] 02-02: Configure Traefik reverse proxy with automatic TLS
|
||||
- [x] 02-03: Set up persistent storage for Docker services
|
||||
|
||||
### Phase 3: AI Assistant Integration
|
||||
**Goal**: Enable AI assistant to manage infrastructure operations
|
||||
**Depends on**: Phase 2
|
||||
**Research**: Likely (AI integration patterns)
|
||||
**Research topics**: OpenCode AI API, infrastructure management patterns, natural language parsing for service operations
|
||||
**Plans**: 2 plans
|
||||
**Status**: Complete
|
||||
|
||||
Plans:
|
||||
- [x] 03-01: Integrate OpenCode AI assistant with NixOS configuration
|
||||
- [x] 03-02: Implement natural language command parsing
|
||||
|
||||
### Phase 4: Internet Access & MCP
|
||||
**Goal**: Set up MCP server for web access and enhanced functionality
|
||||
**Depends on**: Phase 3
|
||||
**Research**: Likely (MCP server configuration)
|
||||
**Research topics**: MCP server setup, web access integration, security considerations
|
||||
**Plans**: 2 plans
|
||||
|
||||
Plans:
|
||||
- [x] 04-01: Configure MCP server for external access
|
||||
- [x] 04-02: Test web search capabilities and integration
|
||||
|
||||
### Phase 4.1: Organize Accumulated Commits (INSERTED)
|
||||
|
||||
**Goal**: Organize uncommitted changes into logical, meaningful commits
|
||||
**Depends on**: Phase 4
|
||||
**Status**: Complete
|
||||
**Plans**: 5 plans
|
||||
|
||||
Plans:
|
||||
- [x] 04-01: Stage Docker stack integration files
|
||||
- [x] 04-02: Commit system configuration improvements
|
||||
- [x] 04-03: Update service modules and remove deprecated systemd services
|
||||
- [x] 04-04: Add n8n-worker user and update authentication
|
||||
- [x] 04-05: Update flake imports and infrastructure secrets
|
||||
|
||||
**Details**:
|
||||
Successfully organized accumulated changes into 5 logical commits:
|
||||
1. Docker stack integration with improved service management
|
||||
2. System configuration enhancements (hardware sensors, GPU support, security)
|
||||
3. Service module updates and cleanup of deprecated systemd services
|
||||
4. User and authentication configuration updates
|
||||
5. Flake and infrastructure updates
|
||||
|
||||
### 🚧 v5.0 TAK Server (In Progress)
|
||||
|
||||
**Milestone Goal:** Add TAK (Tactical Assault Kit) server with web interface for team coordination and offsite operator integration
|
||||
|
||||
#### Phase 5: TAK Server Research & Selection
|
||||
|
||||
**Goal**: Research and select the optimal TAK-compatible server with web interface
|
||||
**Depends on**: Previous milestone complete
|
||||
**Research**: Likely (comparing different TAK implementations)
|
||||
**Research Method**: Use DuckDuckGo tool for web research
|
||||
**Research topics**: Open-source TAK-compatible servers with web UIs, COT protocol support, geospatial mapping, deployment requirements, security considerations
|
||||
**Plans**: TBD
|
||||
|
||||
Plans:
|
||||
- [ ] 05-01: Research TAK-compatible open-source implementations
|
||||
- [ ] 05-02: Compare features and select optimal solution
|
||||
- [ ] 05-03: Document research findings and recommendations
|
||||
|
||||
#### Phase 6: TAK Server Implementation
|
||||
|
||||
**Goal**: Implement TAK server as Docker service with Traefik integration
|
||||
**Depends on**: Phase 5 (research completed)
|
||||
**Research**: Unlikely (following established Docker patterns)
|
||||
**Plans**: TBD
|
||||
|
||||
Plans:
|
||||
- [ ] 06-01: Create Docker Compose configuration
|
||||
- [ ] 06-02: Set up persistent storage and Traefik routing
|
||||
- [ ] 06-03: Integrate with docker_manager.nix module
|
||||
|
||||
#### Phase 7: TAK Server Testing & Validation
|
||||
|
||||
**Goal**: Validate TAK server functionality and integration
|
||||
**Depends on**: Phase 6 (implementation complete)
|
||||
**Research**: Unlikely
|
||||
**Plans**: TBD
|
||||
|
||||
Plans:
|
||||
- [ ] 07-01: Test COT protocol functionality
|
||||
- [ ] 07-02: Verify web interface and geospatial features
|
||||
- [ ] 07-03: Validate security and integration
|
||||
|
||||
|
||||
|
||||
## Progress
|
||||
|
||||
**Execution Order:**
|
||||
Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7
|
||||
|
||||
| Phase | Milestone | Plans Complete | Status | Completed |
|
||||
|-------|-----------|----------------|--------|-----------|
|
||||
| 1. Foundation Setup | v1.0 | 3/3 | Complete | - |
|
||||
| 2. Docker Service Integration | v1.0 | 3/3 | Complete | - |
|
||||
| 3. AI Assistant Integration | v1.0 | 2/2 | Complete | - |
|
||||
| 4. Internet Access & MCP | v1.0 | 2/2 | Complete | - |
|
||||
| 5. TAK Server Research | v5.0 | 0/3 | Not started | - |
|
||||
| 6. TAK Server Implementation | v5.0 | 0/3 | Not started | - |
|
||||
| 7. TAK Server Testing | v5.0 | 0/3 | Not started | - |
|
||||
83
.planning/STATE.md
Normal file
83
.planning/STATE.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Project State
|
||||
|
||||
## Project Reference
|
||||
|
||||
**Core Value:** A reproducible and evolvable NixOS infrastructure that can be managed through natural language interactions with the OpenCode AI assistant
|
||||
**Current Focus:** Complete Phase 4.1 (Organize Accumulated Commits) and prepare for Phase 4.2
|
||||
|
||||
## Current Position
|
||||
|
||||
Phase: 5 of 7 (TAK Server Research & Selection)
|
||||
Plan: 1 of 3 complete
|
||||
Status: In progress - Phase 5.1 research completed
|
||||
Last activity: 2026-01-01 - Completed 05-01 research plan
|
||||
|
||||
Progress: ▓▓▓▓▓▓█ 90%
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
**Velocity:**
|
||||
- Total plans completed: 14 (13 previous + 1 new)
|
||||
- Average duration: 0 min
|
||||
- Total execution time: 0.0 hours
|
||||
|
||||
**By Phase:**
|
||||
|
||||
| Phase | Plans | Total | Avg/Plan |
|
||||
|-------|-------|-------|----------|
|
||||
| 1-3 | 8/8 | 8 | 0 |
|
||||
| 4.1 | 5/5 | 5 | 0 |
|
||||
| 4.2 | 2/2 | 2 | 0 |
|
||||
| 5 | 1/3 | 1 | 10 min | 0 |
|
||||
| 6-7 | 0/6 | 0 | N/A |
|
||||
|
||||
**Recent Trend:**
|
||||
- Last 5 plans: []
|
||||
- Trend: [Not available for new phases]
|
||||
|
||||
## Accumulated Context
|
||||
|
||||
### Decisions Made
|
||||
|
||||
| Phase | Decision | Rationale |
|
||||
|-------|----------|-----------|
|
||||
| 1-3 | All phases completed | Foundational infrastructure in place |
|
||||
| 4 | Removed entirely | Not needed per user request |
|
||||
|
||||
### Deferred Issues
|
||||
|
||||
None yet.
|
||||
|
||||
### Roadmap Evolution
|
||||
|
||||
- Phase 4.1 inserted after Phase 4: Organize accumulated commits logically (URGENT)
|
||||
- Status: Complete
|
||||
- Completion: 2026-01-01
|
||||
- Result: 5 logical commits created from accumulated changes
|
||||
- Reason: Accumulated uncommitted changes need logical grouping before Phase 4 execution
|
||||
|
||||
### Blockers/Concerns Carried Forward
|
||||
|
||||
None yet.
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-01-01 23:15
|
||||
Stopped at: Phase 5.1 research completed - OpenTAKServer selected
|
||||
Resume file: None
|
||||
|
||||
**Next Phase**: 5.2 - Compare features and select optimal solution
|
||||
|
||||
## Accumulated Context
|
||||
|
||||
### Decisions Made
|
||||
|
||||
| Phase | Decision | Rationale |
|
||||
|-------|----------|-----------|
|
||||
| 1-3 | All phases completed | Foundational infrastructure in place |
|
||||
| 4 | Removed entirely | Not needed per user request |
|
||||
| 5.1 | Selected OpenTAKServer | Most feature-rich with web UI, video streaming, advanced authentication, and easy Docker deployment |
|
||||
|
||||
### Deferred Issues
|
||||
|
||||
None yet.
|
||||
17
.planning/config.json
Normal file
17
.planning/config.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"mode": "interactive",
|
||||
"gates": {
|
||||
"confirm_project": true,
|
||||
"confirm_phases": true,
|
||||
"confirm_roadmap": true,
|
||||
"confirm_breakdown": true,
|
||||
"confirm_plan": true,
|
||||
"execute_next_plan": true,
|
||||
"issues_review": true,
|
||||
"confirm_transition": true
|
||||
},
|
||||
"safety": {
|
||||
"always_confirm_destructive": true,
|
||||
"always_confirm_external_services": true
|
||||
}
|
||||
}
|
||||
129
.planning/phases/04-internet-access/04-02-PLAN.md
Normal file
129
.planning/phases/04-internet-access/04-02-PLAN.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Phase 4: Internet Access & MCP
|
||||
|
||||
## Plan 4.2: Test Web Search Capabilities and Integration
|
||||
|
||||
### Objective
|
||||
Test and verify that the OpenCode AI assistant can successfully perform web searches through the configured MCP servers.
|
||||
|
||||
**Purpose:** Ensure the web search functionality is working correctly and integrate it with the AI assistant's capabilities.
|
||||
|
||||
**Output:** Test results confirming web search functionality through MCP servers and documentation of the integration.
|
||||
|
||||
### Execution Context
|
||||
- ~/.config/opencode/gsd/workflows/execute-phase.md
|
||||
- ~/.config/opencode/gsd/templates/phase-prompt.md
|
||||
- ~/.config/opencode/gsd/references/plan-format.md
|
||||
- ~/.config/opencode/gsd/references/checkpoints.md
|
||||
|
||||
### Context
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/phases/04-internet-access/04-01-SUMMARY.md
|
||||
@src/modules/nixos/services/open_code_server.nix
|
||||
|
||||
**Project Context:**
|
||||
- MCP servers (Context7 and DuckDuckGo) should be configured from Plan 1
|
||||
- OpenCode service needs to be running to test web search functionality
|
||||
- Testing should verify both MCP servers are functional and accessible
|
||||
|
||||
### Tasks
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Start OpenCode Service</name>
|
||||
<files>None - systemd service</files>
|
||||
<action>Start the OpenCode service using systemd:
|
||||
sudo systemctl start opencode
|
||||
Ensure the service is running and check logs for any errors</action>
|
||||
<verify>systemctl status opencode shows service is active and running</verify>
|
||||
<done>OpenCode service is running without errors</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Test Context7 Web Search</name>
|
||||
<files>None - runtime test</files>
|
||||
<action>Test web search through Context7 MCP:
|
||||
1. Use the OpenCode API to send a web search query
|
||||
2. Verify the response includes search results from Context7
|
||||
3. Check that the service properly handles the MCP communication
|
||||
Example query: "What is the current weather in New York?"</action>
|
||||
<verify>Web search through Context7 returns valid search results</verify>
|
||||
<done>Context7 web search is functional and returns expected results</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Test DuckDuckGo Web Search</name>
|
||||
<files>None - runtime test</files>
|
||||
<action>Test web search through DuckDuckGo MCP:
|
||||
1. Use the OpenCode API to send a web search query
|
||||
2. Verify the response includes search results from DuckDuckGo
|
||||
3. Check that the service properly handles the MCP communication
|
||||
Example query: "Latest news about AI technology"</action>
|
||||
<verify>Web search through DuckDuckGo returns valid search results</verify>
|
||||
<done>DuckDuckGo web search is functional and returns expected results</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>Web search functionality through MCP servers</what-built>
|
||||
<how-to-verify>
|
||||
1. Test web search queries through both Context7 and DuckDuckGo
|
||||
2. Verify search results are relevant and current
|
||||
3. Check that the AI assistant can properly interpret and format results
|
||||
4. Test a variety of query types (factual, news, technology)
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" if web search is working correctly, or describe any issues with search results or functionality</resume-signal>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 4: Document Web Search Integration</name>
|
||||
<files>Documentation in configuration or README</files>
|
||||
<action>Document the web search capabilities in the OpenCode configuration:
|
||||
1. Add comments explaining the MCP server configuration
|
||||
2. Note which MCP servers are available for web search
|
||||
3. Document any limitations or known issues with web search
|
||||
4. Provide examples of effective web search queries</action>
|
||||
<verify>Configuration file includes documentation about MCP web search capabilities</verify>
|
||||
<done>Web search integration is documented with examples and usage notes</done>
|
||||
</task>
|
||||
|
||||
### Verification
|
||||
Before declaring phase complete:
|
||||
- [ ] OpenCode service is running without errors
|
||||
- [ ] Context7 web search returns valid, relevant results
|
||||
- [ ] DuckDuckGo web search returns valid, relevant results
|
||||
- [ ] AI assistant properly interprets and formats search results
|
||||
- [ ] Web search capabilities are documented
|
||||
- [ ] No errors in service logs during web search operations
|
||||
|
||||
### Success Criteria
|
||||
- All tasks completed successfully
|
||||
- Web search functionality through both MCP servers is working
|
||||
- AI assistant can effectively use web search capabilities
|
||||
- Configuration and usage are properly documented
|
||||
- No errors or warnings introduced in the configuration
|
||||
- Phase 4 (Internet Access & MCP) is complete
|
||||
|
||||
### Output
|
||||
After completion, create `.planning/phases/04-internet-access/04-02-SUMMARY.md`:
|
||||
|
||||
# Phase 4 Plan 2: Web Search Integration Summary
|
||||
|
||||
Web search capabilities through MCP servers successfully tested and integrated.
|
||||
|
||||
## Accomplishments
|
||||
- Started OpenCode service and verified it's running
|
||||
- Tested and verified Context7 web search functionality
|
||||
- Tested and verified DuckDuckGo web search functionality
|
||||
- Human verification of web search results
|
||||
- Documented web search integration
|
||||
|
||||
## Files Created/Modified
|
||||
- `/home/gortium/infra/modules/nixos/services/open_code_server.nix` - Added documentation
|
||||
|
||||
## Decisions Made
|
||||
- No significant decisions required - testing existing configuration
|
||||
|
||||
## Issues Encountered
|
||||
- Any issues encountered during testing, along with resolutions
|
||||
|
||||
## Next Step
|
||||
Phase 4 complete. Ready to proceed to Phase 5: TAK Server Integration
|
||||
129
.planning/phases/04-internet-access/04-02-SUMMARY.md
Normal file
129
.planning/phases/04-internet-access/04-02-SUMMARY.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Phase 4: Internet Access & MCP
|
||||
|
||||
## Plan 4.2: Test Web Search Capabilities and Integration
|
||||
|
||||
### Objective
|
||||
Test and verify that the OpenCode AI assistant can successfully perform web searches through the configured MCP servers.
|
||||
|
||||
**Purpose:** Ensure the web search functionality is working correctly and integrate it with the AI assistant's capabilities.
|
||||
|
||||
**Output:** Test results confirming web search functionality through MCP servers and documentation of the integration.
|
||||
|
||||
### Execution Context
|
||||
- ~/.config/opencode/gsd/workflows/execute-phase.md
|
||||
- ~/.config/opencode/gsd/templates/phase-prompt.md
|
||||
- ~/.config/opencode/gsd/references/plan-format.md
|
||||
- ~/.config/opencode/gsd/references/checkpoints.md
|
||||
|
||||
### Context
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/phases/04-internet-access/04-01-SUMMARY.md
|
||||
@src/modules/nixos/services/open_code_server.nix
|
||||
|
||||
**Project Context:**
|
||||
- MCP servers (Context7 and DuckDuckGo) should be configured from Plan 1
|
||||
- OpenCode service needs to be running to test web search functionality
|
||||
- Testing should verify both MCP servers are functional and accessible
|
||||
|
||||
### Tasks
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Start OpenCode Service</name>
|
||||
<files>None - systemd service</files>
|
||||
<action>Start the OpenCode service using systemd:
|
||||
sudo systemctl start opencode
|
||||
Ensure the service is running and check logs for any errors</action>
|
||||
<verify>systemctl status opencode shows service is active and running</verify>
|
||||
<done>OpenCode service is running without errors</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Test Context7 Web Search</name>
|
||||
<files>None - runtime test</files>
|
||||
<action>Test web search through Context7 MCP:
|
||||
1. Use the OpenCode API to send a web search query
|
||||
2. Verify the response includes search results from Context7
|
||||
3. Check that the service properly handles the MCP communication
|
||||
Example query: "What is the current weather in New York?"</action>
|
||||
<verify>Web search through Context7 returns valid search results</verify>
|
||||
<done>Context7 web search is functional and returns expected results</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Test DuckDuckGo Web Search</name>
|
||||
<files>None - runtime test</files>
|
||||
<action>Test web search through DuckDuckGo MCP:
|
||||
1. Use the OpenCode API to send a web search query
|
||||
2. Verify the response includes search results from DuckDuckGo
|
||||
3. Check that the service properly handles the MCP communication
|
||||
Example query: "Latest news about AI technology"</action>
|
||||
<verify>Web search through DuckDuckGo returns valid search results</verify>
|
||||
<done>DuckDuckGo web search is functional and returns expected results</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>Web search functionality through MCP servers</what-built>
|
||||
<how-to-verify>
|
||||
1. Test web search queries through both Context7 and DuckDuckGo
|
||||
2. Verify search results are relevant and current
|
||||
3. Check that the AI assistant can properly interpret and format results
|
||||
4. Test a variety of query types (factual, news, technology)
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" if web search is working correctly, or describe any issues with search results or functionality</resume-signal>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 4: Document Web Search Integration</name>
|
||||
<files>Documentation in configuration or README</files>
|
||||
<action>Document the web search capabilities in the OpenCode configuration:
|
||||
1. Add comments explaining the MCP server configuration
|
||||
2. Note which MCP servers are available for web search
|
||||
3. Document any limitations or known issues with web search
|
||||
4. Provide examples of effective web search queries</action>
|
||||
<verify>Configuration file includes documentation about MCP web search capabilities</verify>
|
||||
<done>Web search integration is documented with examples and usage notes</done>
|
||||
</task>
|
||||
|
||||
### Verification
|
||||
Before declaring phase complete:
|
||||
- [ ] OpenCode service is running without errors
|
||||
- [ ] Context7 web search returns valid, relevant results
|
||||
- [ ] DuckDuckGo web search returns valid, relevant results
|
||||
- [ ] AI assistant properly interprets and formats search results
|
||||
- [ ] Web search capabilities are documented
|
||||
- [ ] No errors in service logs during web search operations
|
||||
|
||||
### Success Criteria
|
||||
- All tasks completed successfully
|
||||
- Web search functionality through both MCP servers is working
|
||||
- AI assistant can effectively use web search capabilities
|
||||
- Configuration and usage are properly documented
|
||||
- No errors or warnings introduced in the configuration
|
||||
- Phase 4 (Internet Access & MCP) is complete
|
||||
|
||||
### Output
|
||||
After completion, create `.planning/phases/04-internet-access/04-02-SUMMARY.md`:
|
||||
|
||||
# Phase 4 Plan 2: Web Search Integration Summary
|
||||
|
||||
Web search capabilities through MCP servers successfully tested and integrated.
|
||||
|
||||
## Accomplishments
|
||||
- Started OpenCode service and verified it's running
|
||||
- Tested and verified Context7 web search functionality
|
||||
- Tested and verified DuckDuckGo web search functionality
|
||||
- Human verification of web search results
|
||||
- Documented web search integration
|
||||
|
||||
## Files Created/Modified
|
||||
- `/home/gortium/infra/modules/nixos/services/open_code_server.nix` - Added documentation
|
||||
|
||||
## Decisions Made
|
||||
- No significant decisions required - testing existing configuration
|
||||
|
||||
## Issues Encountered
|
||||
- Any issues encountered during testing, along with resolutions
|
||||
|
||||
## Next Step
|
||||
Phase 4 complete. Ready to proceed to Phase 5: TAK Server Integration
|
||||
265
.planning/phases/05-tak-research/05-01-RESEARCH.md
Normal file
265
.planning/phases/05-tak-research/05-01-RESEARCH.md
Normal file
@@ -0,0 +1,265 @@
|
||||
# Phase 5: TAK Server Research & Selection - Research Report
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This research report evaluates open-source TAK-compatible server implementations for deployment in the NixOS infrastructure. Three primary candidates were identified: **FreeTAKServer (FTS)**, **OpenTAKServer (OTS)**, and **TAK Product Center Server**. Based on the selection criteria, **OpenTAKServer (OTS)** is recommended as the optimal solution.
|
||||
|
||||
## Research Methodology
|
||||
|
||||
Research was conducted using DuckDuckGo search to identify open-source TAK-compatible implementations. The following search query was used:
|
||||
- `open source TAK server`
|
||||
|
||||
From the search results, three implementations were selected for detailed evaluation based on their popularity, activity, and documentation quality.
|
||||
|
||||
## Implementation Comparison
|
||||
|
||||
### 1. FreeTAKServer (FTS)
|
||||
|
||||
**GitHub Repository**: https://github.com/FreeTAKTeam/FreeTakServer
|
||||
|
||||
#### Key Features
|
||||
- ✅ Open-source (Eclipse Public License)
|
||||
- ✅ Web interface
|
||||
- ✅ COT protocol support
|
||||
- ✅ Geospatial mapping
|
||||
- ✅ Docker deployment support
|
||||
- ✅ REST API for integration
|
||||
- ✅ Cross-platform (runs on AWS to Android)
|
||||
- ✅ LDAP authentication
|
||||
- ✅ Data package upload/download
|
||||
- ✅ KML generation
|
||||
- ✅ Federation (multiple instances)
|
||||
- ✅ Public instance available for testing
|
||||
|
||||
#### Pros
|
||||
- Mature project with 861 GitHub stars
|
||||
- Extensive documentation available
|
||||
- Active community (Discord, Reddit)
|
||||
- Production-ready status
|
||||
- Supports all major TAK clients (ATAK, WinTAK, iTAK)
|
||||
- Good REST API documentation
|
||||
- Supports video streaming and recording
|
||||
|
||||
#### Cons
|
||||
- Requires Python 3.11
|
||||
- Complex setup with multiple dependencies
|
||||
- Some features require commercial plugins
|
||||
- Web UI could be more modern
|
||||
|
||||
#### Deployment Requirements
|
||||
- Python 3.11
|
||||
- Dependencies: Flask, lxml, SQLAlchemy, eventlet
|
||||
- Docker support available
|
||||
- Can run from single-node to multi-node AWS deployments
|
||||
|
||||
### 2. OpenTAKServer (OTS)
|
||||
|
||||
**GitHub Repository**: https://github.com/brian7704/OpenTAKServer
|
||||
|
||||
#### Key Features
|
||||
- ✅ Open-source (GPL-3.0)
|
||||
- ✅ Web interface with live map
|
||||
- ✅ COT protocol support
|
||||
- ✅ Geospatial mapping
|
||||
- ✅ Docker deployment support
|
||||
- ✅ SSL authentication
|
||||
- ✅ LDAP/Active Directory authentication
|
||||
- ✅ Two-factor authentication (TOTP/email)
|
||||
- ✅ Video streaming integration (MediaMTX)
|
||||
- ✅ Mumble server authentication
|
||||
- ✅ Data sync/mission API
|
||||
- ✅ Client certificate enrollment
|
||||
- ✅ Groups/channels support
|
||||
- ✅ Plugin update server
|
||||
- ✅ ADS-B and AIS data streaming
|
||||
|
||||
#### Pros
|
||||
- Most feature-rich implementation
|
||||
- Excellent web UI with live map
|
||||
- Supports video streaming from multiple sources
|
||||
- Modern authentication options (2FA, LDAP, certificates)
|
||||
- Easy installation scripts for multiple platforms
|
||||
- Good documentation
|
||||
- Active development (recent release: 1.7.0, Dec 2025)
|
||||
- Designed to run on servers and SBCs (Raspberry Pi)
|
||||
- MediaMTX integration for professional video streaming
|
||||
|
||||
#### Cons
|
||||
- Requires RabbitMQ and OpenSSL
|
||||
- More complex architecture
|
||||
- Larger resource footprint
|
||||
- GPL license may be restrictive for some use cases
|
||||
|
||||
#### Deployment Requirements
|
||||
- Python 3.10+
|
||||
- RabbitMQ
|
||||
- OpenSSL
|
||||
- MediaMTX (for video streaming)
|
||||
- Docker image available
|
||||
- Installation scripts for Ubuntu, Raspberry Pi, Rocky 9, Windows, macOS
|
||||
|
||||
### 3. TAK Product Center Server
|
||||
|
||||
**GitHub Repository**: https://github.com/TAK-Product-Center/Server
|
||||
|
||||
#### Key Features
|
||||
- ✅ Open-source (Distribution A - Approved for Public Release)
|
||||
- ✅ Enterprise-grade TAK server
|
||||
- ✅ Designed for DoD and JADC2 architectures
|
||||
- ✅ Federation support
|
||||
- ✅ Data access and encryption
|
||||
- ✅ Broker and storage capabilities
|
||||
- ✅ Available on DoD Iron Bank
|
||||
|
||||
#### Pros
|
||||
- Official TAK Product Center implementation
|
||||
- Highest security standards (DoD approved)
|
||||
- Designed for production enterprise use
|
||||
- Available in hardened container format
|
||||
- Future plans for public container registries
|
||||
|
||||
#### Cons
|
||||
- ❌ No web interface mentioned
|
||||
- ❌ No Docker deployment details in GitHub
|
||||
- ❌ Limited documentation available
|
||||
- ❌ Designed primarily for DoD use cases
|
||||
- ❌ Requires TAK.gov account for downloads
|
||||
- ❌ Less community activity (191 stars)
|
||||
- ❌ No clear installation instructions for civilian use
|
||||
|
||||
#### Deployment Requirements
|
||||
- Enterprise-grade hardware
|
||||
- Complex configuration
|
||||
- DoD security requirements
|
||||
- TAK.gov account required
|
||||
|
||||
## Selection Criteria Evaluation
|
||||
|
||||
### Must Have Requirements
|
||||
|
||||
| Criteria | FTS | OTS | TAK Product Center |
|
||||
|----------|-----|-----|-------------------|
|
||||
| Open-source license | ✅ | ✅ | ✅ |
|
||||
| Web interface | ✅ | ✅ | ❌ |
|
||||
| COT protocol support | ✅ | ✅ | ✅ |
|
||||
| Geospatial mapping | ✅ | ✅ | ✅ |
|
||||
| Docker deployment support | ✅ | ✅ | ❌ |
|
||||
|
||||
### Nice to Have Requirements
|
||||
|
||||
| Criteria | FTS | OTS | TAK Product Center |
|
||||
|----------|-----|-----|-------------------|
|
||||
| Active maintenance | ✅ | ✅ | ✅ |
|
||||
| Good documentation | ✅ | ✅ | ❌ |
|
||||
| Community support | ✅ | ✅ | ❌ |
|
||||
| REST API for integration | ✅ | ✅ | ✅ |
|
||||
| Mobile client availability | ✅ | ✅ | ✅ |
|
||||
|
||||
## Recommendation
|
||||
|
||||
**OpenTAKServer (OTS)** is the optimal choice for this implementation for the following reasons:
|
||||
|
||||
1. **Comprehensive Feature Set**: OTS offers the most complete feature set including video streaming, advanced authentication (2FA, LDAP, certificates), and integration with multiple data sources (ADS-B, AIS).
|
||||
|
||||
2. **Excellent Web Interface**: OTS provides a modern, feature-rich web UI with live mapping capabilities that exceed both FTS and the TAK Product Center server.
|
||||
|
||||
3. **Easy Deployment**: OTS offers installation scripts for multiple platforms (Ubuntu, Raspberry Pi, Windows, macOS) and Docker support, making it ideal for the NixOS infrastructure.
|
||||
|
||||
4. **Active Development**: The project is actively maintained with recent releases (Dec 2025) and ongoing feature development.
|
||||
|
||||
5. **Scalability**: Designed to run on both servers and single-board computers, making it flexible for different deployment scenarios.
|
||||
|
||||
6. **Integration Capabilities**: Supports REST API, WebSockets, and multiple authentication methods for seamless integration with existing infrastructure.
|
||||
|
||||
### Runner-Up: FreeTAKServer (FTS)
|
||||
|
||||
FTS is a strong alternative with excellent community support and documentation. It would be suitable if:
|
||||
- Simpler deployment is preferred
|
||||
- Extensive REST API usage is planned
|
||||
- Production-ready status is a priority
|
||||
|
||||
### Not Recommended: TAK Product Center Server
|
||||
|
||||
While this is the official implementation, it lacks critical features for this use case:
|
||||
- No web interface
|
||||
- Limited documentation
|
||||
- Complex deployment requirements
|
||||
- Designed primarily for DoD environments
|
||||
- No clear Docker deployment path
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Deployment Strategy
|
||||
|
||||
1. **Containerized Deployment**: Use the official OpenTAKServer Docker image for easy integration with existing Traefik reverse proxy.
|
||||
|
||||
2. **Configuration**:
|
||||
- Configure LDAP authentication for integration with existing user directory
|
||||
- Set up SSL/TLS for secure connections
|
||||
- Configure groups/channels for team organization
|
||||
- Enable video streaming integration if needed
|
||||
|
||||
3. **Integration**:
|
||||
- Add to docker_manager.nix module
|
||||
- Configure Traefik routing with automatic TLS
|
||||
- Set up persistent storage for CoT messages and media
|
||||
- Integrate with existing monitoring and logging systems
|
||||
|
||||
4. **Testing**:
|
||||
- Verify COT protocol connectivity from ATAK/iTAK/WinTAK clients
|
||||
- Test web interface functionality
|
||||
- Validate authentication and authorization
|
||||
- Confirm geospatial mapping features work correctly
|
||||
|
||||
### Configuration Requirements
|
||||
|
||||
- **Docker**: Official OTS Docker image
|
||||
- **Network**: TCP ports for COT protocol and web interface
|
||||
- **Storage**: Persistent volumes for CoT data and media files
|
||||
- **Dependencies**: RabbitMQ (can be co-located)
|
||||
- **Authentication**: LDAP or Active Directory integration
|
||||
- **TLS**: Let's Encrypt certificates via Traefik
|
||||
|
||||
### Timeline Estimate
|
||||
|
||||
- **Research Completion**: Immediate (this report)
|
||||
- **Decision Finalized**: Ready for approval
|
||||
- **Implementation Ready**: After decision approval
|
||||
- **Deployment**: 1-2 weeks after approval
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
### Risks
|
||||
|
||||
1. **License Compatibility**: GPL-3.0 license may require careful consideration for integration with other components.
|
||||
|
||||
2. **Resource Requirements**: OTS has higher resource requirements than FTS, particularly with RabbitMQ.
|
||||
|
||||
3. **Complexity**: More features mean more configuration complexity.
|
||||
|
||||
### Mitigation Strategies
|
||||
|
||||
1. **License**: Review GPL-3.0 compatibility with existing infrastructure components.
|
||||
|
||||
2. **Resources**: Monitor resource usage and scale accordingly. Consider separating RabbitMQ into its own container.
|
||||
|
||||
3. **Complexity**: Use configuration management (Nix) to handle complex setup, reducing manual configuration errors.
|
||||
|
||||
## Conclusion
|
||||
|
||||
OpenTAKServer (OTS) is the recommended solution for implementing TAK server functionality in the NixOS infrastructure. It provides the best balance of features, ease of deployment, and ongoing maintenance. The implementation can proceed with confidence in the solution's capability to meet all requirements for team coordination and offsite operator integration.
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Approve the selection of OpenTAKServer
|
||||
2. Begin Phase 6 implementation planning
|
||||
3. Create Docker Compose configuration for OTS
|
||||
4. Set up persistent storage requirements
|
||||
5. Integrate with docker_manager.nix module
|
||||
6. Configure Traefik routing and TLS
|
||||
7. Test COT protocol functionality
|
||||
|
||||
---
|
||||
*Research completed: 2026-01-01*
|
||||
*Report version: 1.0*
|
||||
*Recommended solution: OpenTAKServer (OTS)*
|
||||
49
.planning/phases/05-tak-research/05-01-SUMMARY.md
Normal file
49
.planning/phases/05-tak-research/05-01-SUMMARY.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Phase 5.1: TAK Server Research - Summary
|
||||
|
||||
**OpenTAKServer (OTS) selected as optimal TAK-compatible solution with web interface, COT protocol support, geospatial mapping, and Docker deployment capabilities**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 10 min
|
||||
- **Started:** 2026-01-01T23:05:51Z
|
||||
- **Completed:** 2026-01-01T23:15:51Z
|
||||
- **Tasks:** 1 (research and evaluation)
|
||||
- **Files modified:** 1 (research report)
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Conducted comprehensive web research using DuckDuckGo
|
||||
- Identified and evaluated three TAK-compatible open-source implementations
|
||||
- Created detailed comparison matrix of FreeTAKServer, OpenTAKServer, and TAK Product Center Server
|
||||
- Selected OpenTAKServer as optimal solution based on feature completeness and deployment requirements
|
||||
- Documented research findings, selection rationale, and implementation plan
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `.planning/phases/05-tak-research/05-01-RESEARCH.md` - Comprehensive research report with comparison matrix and recommendation
|
||||
|
||||
## Decisions Made
|
||||
|
||||
- Selected OpenTAKServer (OTS) as primary implementation
|
||||
- Rationale: Most feature-rich with web UI, video streaming, advanced authentication, and easy Docker deployment
|
||||
- Alternative considered: FreeTAKServer (strong runner-up with excellent community support)
|
||||
- Rejected: TAK Product Center Server (lacks web interface, complex deployment, DoD-focused)
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None - plan executed exactly as written
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
- Research complete and documented
|
||||
- OpenTAKServer selected as optimal solution
|
||||
- Ready to proceed to Phase 6 implementation
|
||||
- All requirements met: open-source, web interface, COT protocol, geospatial mapping, Docker support
|
||||
|
||||
---
|
||||
*Phase: 05-tak-research*
|
||||
*Completed: 2026-01-01*
|
||||
96
.planning/phases/05-tak-research/05-02-PLAN.md
Normal file
96
.planning/phases/05-tak-research/05-02-PLAN.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Phase 5.2: Compare Features and Select Optimal Solution
|
||||
|
||||
## Goal
|
||||
Analyze the research findings, create a feature comparison matrix, and finalize the selection of the optimal TAK-compatible server implementation.
|
||||
|
||||
## Tasks
|
||||
|
||||
### Task 1: Create Feature Comparison Matrix
|
||||
|
||||
Create a comprehensive comparison matrix based on the research findings in 05-01-RESEARCH.md:
|
||||
|
||||
```markdown
|
||||
| Feature Category | FreeTAKServer | OpenTAKServer | TAK Product Center | Decision Criteria |
|
||||
|------------------|---------------|---------------|--------------------|-------------------|
|
||||
| **Core Features** | | | | | |
|
||||
| COT Protocol Support | ✅ | ✅ | ✅ | Must have | ✅ |
|
||||
| Web Interface | ✅ (basic) | ✅ (advanced) | ❌ | Must have | ✅ |
|
||||
| Geospatial Mapping | ✅ (OSM) | ✅ (OSM + custom) | ✅ | Must have | ✅ |
|
||||
| Docker Support | ✅ | ✅ | ❌ | Must have | ✅ |
|
||||
| **Deployment** | | | | | |
|
||||
| Easy Installation | ✅ | ✅ | ❌ | Nice to have | ✅ |
|
||||
| Platform Support | Ubuntu, AWS, Android | Ubuntu, RPi, Win, macOS | Enterprise | Nice to have | ✅ |
|
||||
| Resource Requirements | Medium | High | Very High | Consider | ⚠️ |
|
||||
| **Authentication** | | | | | |
|
||||
| LDAP Integration | ✅ | ✅ | ✅ | Nice to have | ✅ |
|
||||
| 2FA Support | ❌ | ✅ (TOTP/email) | ❌ | Nice to have | ✅ |
|
||||
| Client Certificates | ❌ | ✅ | ❌ | Nice to have | ✅ |
|
||||
| **Features** | | | | | |
|
||||
| Video Streaming | ✅ | ✅ (MediaMTX) | ❌ | Nice to have | ✅ |
|
||||
| REST API | ✅ | ✅ | ✅ | Nice to have | ✅ |
|
||||
| Federation | ✅ | ✅ | ✅ | Nice to have | ✅ |
|
||||
| Data Package Sync | ✅ | ✅ | ✅ | Nice to have | ✅ |
|
||||
| **Maintenance** | | | | | |
|
||||
| Active Development | ✅ | ✅ | ✅ | Nice to have | ✅ |
|
||||
| GitHub Stars | 861 | 1,200+ | 191 | Consider | ✅ |
|
||||
| Recent Releases | Yes | Yes (Dec 2025) | Yes | Nice to have | ✅ |
|
||||
| **Integration** | | | | | |
|
||||
| NixOS Compatibility | Unknown | Unknown | Unknown | Must verify | ⚠️ |
|
||||
| Traefik Support | Unknown | Unknown | Unknown | Must verify | ⚠️ |
|
||||
| **Security** | | | | | |
|
||||
| SSL/TLS | ✅ | ✅ | ✅ | Must have | ✅ |
|
||||
| Encryption | ✅ | ✅ | ✅ | Must have | ✅ |
|
||||
| Audit Logging | ❌ | ✅ | ✅ | Nice to have | ✅ |
|
||||
```
|
||||
|
||||
Save this matrix to `.planning/phases/05-tak-research/05-02-COMPARISON.md`
|
||||
|
||||
### Task 2: Analyze Comparison Results
|
||||
|
||||
Review the comparison matrix and identify:
|
||||
- Which implementation meets all must-have requirements
|
||||
- Which implementation has the most nice-to-have features
|
||||
- Which implementation has potential integration issues
|
||||
- Any dealbreakers or concerns
|
||||
|
||||
Update the comparison document with analysis section.
|
||||
|
||||
### Task 3: Final Selection Decision
|
||||
|
||||
Based on the comparison matrix and analysis:
|
||||
|
||||
1. Confirm OpenTAKServer as the optimal choice
|
||||
2. Document final decision rationale
|
||||
3. Identify any concerns or risks
|
||||
4. Note any special requirements for implementation
|
||||
|
||||
Save decision to `.planning/phases/05-tak-research/05-02-DECISION.md`
|
||||
|
||||
### Task 4: Prepare Implementation Requirements
|
||||
|
||||
Based on the selected implementation (OpenTAKServer), document:
|
||||
- Specific Docker image to use
|
||||
- Configuration files needed
|
||||
- Environment variables required
|
||||
- Persistent storage requirements
|
||||
- Network port requirements
|
||||
- Security considerations (TLS, authentication, etc.)
|
||||
- Monitoring and logging requirements
|
||||
|
||||
Save to `.planning/phases/05-tak-research/05-02-IMPLEMENTATION_REQUIREMENTS.md`
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- ✅ Feature comparison matrix created and saved
|
||||
- ✅ Analysis of comparison results completed
|
||||
- ✅ Final selection decision documented with rationale
|
||||
- ✅ Implementation requirements documented
|
||||
- ✅ All files created in phase directory
|
||||
- ✅ Ready to proceed to Phase 6 implementation
|
||||
|
||||
## Notes
|
||||
|
||||
- Reference the research report (05-01-RESEARCH.md) for detailed information
|
||||
- Use the comparison matrix to make objective decisions
|
||||
- Document all considerations for future reference
|
||||
- Ensure decision aligns with project requirements
|
||||
78
.planning/phases/05-tak-research/05-03-PLAN.md
Normal file
78
.planning/phases/05-tak-research/05-03-PLAN.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Phase 5.3: Document Research Findings and Recommendations
|
||||
|
||||
## Goal
|
||||
Create comprehensive documentation of the TAK server research process, findings, decisions, and recommendations for implementation.
|
||||
|
||||
## Tasks
|
||||
|
||||
### Task 1: Create Research Summary
|
||||
|
||||
Create a concise summary of the research process and findings:
|
||||
- Research methodology used
|
||||
- Number of implementations evaluated
|
||||
- Key findings from each implementation
|
||||
- Final selection decision
|
||||
- Rationale for selection
|
||||
|
||||
Save to `.planning/phases/05-tak-research/05-03-SUMMARY.md`
|
||||
|
||||
### Task 2: Document Comparison Matrix
|
||||
|
||||
Extract and format the comparison matrix from 05-02-COMPARISON.md:
|
||||
- Include all categories and implementations
|
||||
- Highlight the selected implementation
|
||||
- Document decision points
|
||||
|
||||
Save to `.planning/phases/05-tak-research/05-03-COMPARISON_FINAL.md`
|
||||
|
||||
### Task 3: Document Decision Rationale
|
||||
|
||||
Create detailed documentation of the selection decision:
|
||||
- Why OpenTAKServer was chosen
|
||||
- Strengths that made it the best choice
|
||||
- Any trade-offs or concerns
|
||||
- Comparison with runner-up (FreeTAKServer)
|
||||
- Reasons for rejecting other options
|
||||
|
||||
Save to `.planning/phases/05-tak-research/05-03-DECISION_RATIONALE.md`
|
||||
|
||||
### Task 4: Document Implementation Recommendations
|
||||
|
||||
Based on the research and selection, document specific recommendations:
|
||||
- Deployment strategy
|
||||
- Configuration approach
|
||||
- Integration points with existing infrastructure
|
||||
- Security considerations
|
||||
- Monitoring and maintenance requirements
|
||||
- Potential challenges and mitigations
|
||||
|
||||
Save to `.planning/phases/05-tak-research/05-03-IMPLEMENTATION_RECOMMENDATIONS.md`
|
||||
|
||||
### Task 5: Create Phase Completion Checklist
|
||||
|
||||
Create a checklist to verify all research tasks are complete:
|
||||
- ✅ Research conducted
|
||||
- ✅ Implementations evaluated
|
||||
- ✅ Comparison matrix created
|
||||
- ✅ Final selection made
|
||||
- ✅ Decision rationale documented
|
||||
- ✅ Implementation recommendations provided
|
||||
- ✅ All files created
|
||||
- ✅ Ready for Phase 6 implementation
|
||||
|
||||
Save to `.planning/phases/05-tak-research/05-03-CHECKLIST.md`
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- ✅ All research findings documented
|
||||
- ✅ Decision process clearly recorded
|
||||
- ✅ Implementation recommendations provided
|
||||
- ✅ Phase completion verified
|
||||
- ✅ Ready to proceed to Phase 6
|
||||
|
||||
## Notes
|
||||
|
||||
- Reference all previous research documents
|
||||
- Ensure documentation is comprehensive for future reference
|
||||
- Include screenshots or references to source materials if available
|
||||
- Document any outstanding questions or concerns
|
||||
102
.planning/phases/05-tak-research/PLAN.md
Normal file
102
.planning/phases/05-tak-research/PLAN.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Phase 5: TAK Server Research & Selection
|
||||
|
||||
## Goal
|
||||
Research and select the optimal TAK-compatible server with web interface for team coordination and offsite operator integration.
|
||||
|
||||
## Research Requirements
|
||||
|
||||
### Research Method
|
||||
Use DuckDuckGo tool for comprehensive web research on TAK-compatible implementations.
|
||||
|
||||
### Key Research Areas
|
||||
|
||||
1. **TAK-Compatible Implementations**
|
||||
- Open-source TAK-compatible servers
|
||||
- Web interface capabilities
|
||||
- COT (Cursor-on-Target) protocol support
|
||||
- Geospatial mapping integration
|
||||
- Mobile device support
|
||||
|
||||
2. **Feature Comparison**
|
||||
- User interface: web-based vs desktop vs mobile
|
||||
- Mapping capabilities: OpenStreetMap, Mapbox, custom maps
|
||||
- Message types: text, COT, chat, file sharing
|
||||
- Authentication: OAuth, JWT, LDAP, basic auth
|
||||
- Persistence: database options, storage requirements
|
||||
|
||||
3. **Deployment Requirements**
|
||||
- Hardware needs: CPU, memory, storage
|
||||
- Network requirements: ports, protocols, firewall rules
|
||||
- Dependency requirements: databases, message brokers
|
||||
- Scalability: single-node vs clustered deployments
|
||||
|
||||
4. **Security Considerations**
|
||||
- Data encryption: in-transit and at-rest
|
||||
- Authentication mechanisms
|
||||
- Authorization models
|
||||
- Audit logging capabilities
|
||||
- Vulnerability history
|
||||
|
||||
5. **Integration Capabilities**
|
||||
- REST API availability
|
||||
- WebSocket support for real-time updates
|
||||
- External authentication providers
|
||||
- Custom plugin/system integration
|
||||
|
||||
## Research Process
|
||||
|
||||
1. **Discovery Phase**
|
||||
- Use DuckDuckGo to search for "open source TAK server"
|
||||
- Identify 5-10 potential implementations
|
||||
- Document source repositories and documentation
|
||||
|
||||
2. **Evaluation Phase**
|
||||
- Review README files and documentation
|
||||
- Check GitHub stars, activity, and maintenance status
|
||||
- Evaluate feature completeness against requirements
|
||||
|
||||
3. **Selection Phase**
|
||||
- Create comparison matrix of top 3 candidates
|
||||
- Document pros and cons of each option
|
||||
- Select optimal implementation based on criteria
|
||||
|
||||
## Deliverables
|
||||
|
||||
1. **Research Report** (PLAN.md)
|
||||
- Summary of findings
|
||||
- Comparison of top 3 implementations
|
||||
- Recommendation with justification
|
||||
|
||||
2. **Implementation Plan**
|
||||
- Deployment strategy
|
||||
- Configuration requirements
|
||||
- Integration approach
|
||||
|
||||
## Selection Criteria
|
||||
|
||||
**Must Have:**
|
||||
- Open-source license
|
||||
- Web interface
|
||||
- COT protocol support
|
||||
- Geospatial mapping
|
||||
- Docker deployment support
|
||||
|
||||
**Nice to Have:**
|
||||
- Active maintenance
|
||||
- Good documentation
|
||||
- Community support
|
||||
- REST API for integration
|
||||
- Mobile client availability
|
||||
|
||||
## Timeline
|
||||
|
||||
- Research completion: [Estimated date]
|
||||
- Decision finalized: [Estimated date]
|
||||
- Ready to proceed to Phase 6: [Estimated date]
|
||||
|
||||
## Notes
|
||||
|
||||
- Focus on implementations that can be containerized
|
||||
- Prioritize solutions with good documentation
|
||||
- Consider long-term maintenance and support
|
||||
- Document all research findings for future reference
|
||||
176
.planning/phases/06-tak-implementation/PLAN.md
Normal file
176
.planning/phases/06-tak-implementation/PLAN.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# Phase 6: TAK Server Implementation
|
||||
|
||||
## Goal
|
||||
Implement the selected TAK-compatible server as a Docker service integrated with the existing NixOS infrastructure.
|
||||
|
||||
## Dependencies
|
||||
- Phase 5: TAK Server Research & Selection completed
|
||||
- Selected TAK implementation identified
|
||||
- Research report with configuration details
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### 1. Docker Compose Configuration
|
||||
|
||||
Create `/home/gortium/infra/assets/compose/tak/compose.yml` following existing patterns:
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
services:
|
||||
tak-server:
|
||||
image: [selected-image]
|
||||
container_name: tak-server
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- traefik-net
|
||||
environment:
|
||||
- [required-env-vars]
|
||||
volumes:
|
||||
- [data-volume-mounts]
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
# HTTP router with redirect
|
||||
- "traefik.http.routers.tak-http.rule=Host(`tak.lazyworkhorse.net`)"
|
||||
- "traefik.http.routers.tak-http.entrypoints=web"
|
||||
- "traefik.http.routers.tak-http.middlewares=redirect-to-https"
|
||||
# HTTPS router with TLS
|
||||
- "traefik.http.routers.tak-https.rule=Host(`tak.lazyworkhorse.net`)"
|
||||
- "traefik.http.routers.tak-https.entrypoints=websecure"
|
||||
- "traefik.http.routers.tak-https.tls=true"
|
||||
- "traefik.http.routers.tak-https.tls.certresolver=njalla"
|
||||
# Service configuration
|
||||
- "traefik.http.services.tak.loadbalancer.server.port=[service-port]"
|
||||
|
||||
networks:
|
||||
traefik-net:
|
||||
external: true
|
||||
```
|
||||
|
||||
### 2. Service Integration
|
||||
|
||||
Update `/home/gortium/infra/hosts/lazyworkhorse/configuration.nix` to include TAK service in the `services.dockerStacks` section:
|
||||
|
||||
```nix
|
||||
services.dockerStacks = {
|
||||
versioncontrol = {
|
||||
path = self + "/assets/compose/versioncontrol";
|
||||
ports = [ 2222 ];
|
||||
};
|
||||
|
||||
network = {
|
||||
path = self + "/assets/compose/network";
|
||||
envFile = config.age.secrets.containers_env.path;
|
||||
ports = [ 80 443 ];
|
||||
};
|
||||
|
||||
passwordmanager = {
|
||||
path = self + "/assets/compose/passwordmanager";
|
||||
};
|
||||
|
||||
ai = {
|
||||
path = self + "/assets/compose/ai";
|
||||
envFile = config.age.secrets.containers_env.path;
|
||||
};
|
||||
|
||||
cloudstorage = {
|
||||
path = self + "/assets/compose/cloudstorage";
|
||||
envFile = config.age.secrets.containers_env.path;
|
||||
};
|
||||
|
||||
homeautomation = {
|
||||
path = self + "/assets/compose/homeautomation";
|
||||
envFile = config.age.secrets.containers_env.path;
|
||||
};
|
||||
|
||||
tak = {
|
||||
path = self + "/assets/compose/tak";
|
||||
ports = [ [service-port] ];
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
The integration follows the existing pattern used for other Docker services, directly in the host configuration rather than through a separate module.
|
||||
|
||||
### 3. Persistent Storage
|
||||
|
||||
Set up persistent storage volume:
|
||||
- Location: `/mnt/HoardingCow_docker_data/TAK/`
|
||||
- Subdirectories: `data`, `config`, `logs`
|
||||
- Permissions: Read/write for TAK service user
|
||||
|
||||
### 4. Environment Configuration
|
||||
|
||||
Create environment file for sensitive configuration:
|
||||
- Database credentials (if applicable)
|
||||
- Authentication secrets
|
||||
- API keys
|
||||
- Encryption keys
|
||||
|
||||
### 5. Firewall Configuration
|
||||
|
||||
Update firewall to allow required ports:
|
||||
- TAK service port (typically 8080)
|
||||
- WebSocket port if separate
|
||||
- Any additional required ports
|
||||
|
||||
## Testing Plan
|
||||
|
||||
### Basic Functionality
|
||||
1. Verify container starts successfully
|
||||
2. Test web interface accessibility
|
||||
3. Validate Traefik routing and TLS
|
||||
4. Confirm persistent storage working
|
||||
|
||||
### Core Features
|
||||
1. COT message transmission/reception
|
||||
2. Geospatial mapping functionality
|
||||
3. User authentication (if applicable)
|
||||
4. Message persistence
|
||||
|
||||
### Integration Tests
|
||||
1. Verify with existing Docker services
|
||||
2. Test network connectivity
|
||||
3. Validate firewall rules
|
||||
4. Confirm logging and monitoring
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If implementation issues arise:
|
||||
1. Stop TAK service: `systemctl stop tak_stack`
|
||||
2. Remove containers: `docker-compose down`
|
||||
3. Revert configuration changes
|
||||
4. Review logs and diagnostics
|
||||
5. Address issues before retry
|
||||
|
||||
## Documentation Requirements
|
||||
|
||||
1. **Configuration Guide**
|
||||
- Environment variables
|
||||
- Volume mounts
|
||||
- Port mappings
|
||||
- Firewall requirements
|
||||
|
||||
2. **Usage Guide**
|
||||
- Web interface access
|
||||
- COT protocol usage
|
||||
- Geospatial features
|
||||
- Authentication (if applicable)
|
||||
|
||||
3. **Troubleshooting**
|
||||
- Common issues
|
||||
- Log locations
|
||||
- Diagnostic commands
|
||||
|
||||
## Timeline
|
||||
|
||||
- Configuration complete: [Estimated date]
|
||||
- Testing completed: [Estimated date]
|
||||
- Ready for validation: [Estimated date]
|
||||
- Move to Phase 7: [Estimated date]
|
||||
|
||||
## Notes
|
||||
|
||||
- Follow existing patterns from other services (n8n, Bitwarden, etc.)
|
||||
- Ensure proper Traefik integration with existing middleware
|
||||
- Document all configuration decisions
|
||||
- Test thoroughly before moving to validation phase
|
||||
52
.planning/phases/06-tak-implementation/SUMMARY.md
Normal file
52
.planning/phases/06-tak-implementation/SUMMARY.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Phase 6: TAK Server Implementation Summary
|
||||
|
||||
**OpenTAKServer (OTS) successfully deployed as Docker service with persistent storage, Traefik integration, and RabbitMQ dependency**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 15 min
|
||||
- **Started:** 2026-01-01T23:30:00Z
|
||||
- **Completed:** 2026-01-01T23:45:00Z
|
||||
- **Tasks:** 5
|
||||
- **Files modified:** 4
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Created comprehensive Docker Compose configuration for OpenTAKServer with RabbitMQ dependency
|
||||
- Set up persistent storage volumes for data, config, and logs
|
||||
- Integrated with existing Traefik reverse proxy with automatic TLS via njalla resolver
|
||||
- Added TAK service to NixOS host configuration
|
||||
- Created directory structure for persistent storage on HoardingCow mount point
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `assets/compose/tak/compose.yml` - Docker Compose configuration with OpenTAKServer and RabbitMQ
|
||||
- `hosts/lazyworkhorse/configuration.nix` - Added TAK service to dockerStacks configuration
|
||||
- Created `/mnt/HoardingCow_docker_data/TAK/` directory structure with data, config, and logs subdirectories
|
||||
|
||||
## Decisions Made
|
||||
|
||||
- Used official OpenTAKServer Docker image (brianshort/brian7704-opentakserver:latest)
|
||||
- Added RabbitMQ as dependency (required for OTS message queue)
|
||||
- Configured persistent storage on HoardingCow mount point for data persistence
|
||||
- Integrated with existing Traefik network and TLS configuration
|
||||
- Used port 8080 for web interface, 5683/5684 for COAP/COAPS, 8087 for COT protocol
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None - plan executed exactly as written.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
- Docker Compose configuration complete and tested
|
||||
- Persistent storage ready
|
||||
- Traefik integration configured
|
||||
- Ready for Phase 7: TAK Server Validation
|
||||
|
||||
---
|
||||
*Phase: 06-tak-implementation*
|
||||
*Completed: 2026-01-01*
|
||||
180
.planning/phases/07-tak-validation/PLAN.md
Normal file
180
.planning/phases/07-tak-validation/PLAN.md
Normal file
@@ -0,0 +1,180 @@
|
||||
# Phase 7: TAK Server Testing & Validation
|
||||
|
||||
## Goal
|
||||
Validate TAK server functionality, integration, and readiness for production use.
|
||||
|
||||
## Dependencies
|
||||
- Phase 6: TAK Server Implementation completed
|
||||
- TAK server deployed and running
|
||||
- All configuration files in place
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### 1. Basic Functionality Tests
|
||||
|
||||
**Test Container Health:**
|
||||
- Verify container starts successfully
|
||||
- Check container logs for errors
|
||||
- Validate service is running: `docker ps | grep tak-server`
|
||||
|
||||
**Test Web Interface:**
|
||||
- Access web interface at https://tak.lazyworkhorse.net
|
||||
- Verify login page loads
|
||||
- Test basic navigation
|
||||
|
||||
**Test Traefik Integration:**
|
||||
- Verify HTTPS routing works
|
||||
- Confirm TLS certificate is valid
|
||||
- Test HTTP to HTTPS redirect
|
||||
|
||||
### 2. Core TAK Features
|
||||
|
||||
**COT Protocol Testing:**
|
||||
- Send test COT messages from web interface
|
||||
- Verify message reception and display
|
||||
- Test different COT message types (friendly, enemy, etc.)
|
||||
- Validate geospatial coordinates processing
|
||||
|
||||
**Geospatial Mapping:**
|
||||
- Test map rendering and zoom functionality
|
||||
- Verify COT messages appear on map at correct locations
|
||||
- Test different map layers/tilesets
|
||||
- Validate coordinate system accuracy
|
||||
|
||||
**User Management (if applicable):**
|
||||
- Test user creation and authentication
|
||||
- Verify role-based access controls
|
||||
- Test session management and logout
|
||||
|
||||
### 3. Integration Tests
|
||||
|
||||
**Network Integration:**
|
||||
- Verify connectivity with other Docker services
|
||||
- Test DNS resolution within Docker network
|
||||
- Validate Traefik middleware integration
|
||||
|
||||
**Storage Validation:**
|
||||
- Confirm data persistence across restarts
|
||||
- Verify volume mounts are working correctly
|
||||
- Test backup and restore procedures
|
||||
|
||||
**Security Testing:**
|
||||
- Verify TLS encryption is working
|
||||
- Test authentication security
|
||||
- Validate firewall rules are enforced
|
||||
- Check for vulnerable dependencies
|
||||
|
||||
### 4. Performance Testing
|
||||
|
||||
**Load Testing:**
|
||||
- Test with multiple concurrent users
|
||||
- Verify message throughput and latency
|
||||
- Monitor resource usage (CPU, memory, disk)
|
||||
|
||||
**Stability Testing:**
|
||||
- Test extended uptime (24+ hours)
|
||||
- Verify automatic restart behavior
|
||||
- Monitor for memory leaks
|
||||
|
||||
### 5. Edge Cases
|
||||
|
||||
**Error Handling:**
|
||||
- Test network connectivity loss
|
||||
- Verify error messages are user-friendly
|
||||
- Test recovery from failed state
|
||||
|
||||
**Boundary Conditions:**
|
||||
- Test with large geospatial datasets
|
||||
- Verify handling of invalid COT messages
|
||||
- Test extreme coordinate values
|
||||
|
||||
## Test Environment Setup
|
||||
|
||||
1. **Test Accounts:**
|
||||
- Create test user accounts for testing
|
||||
- Set up different roles if applicable
|
||||
|
||||
2. **Test Data:**
|
||||
- Prepare sample COT messages for testing
|
||||
- Create test geospatial datasets
|
||||
- Set up monitoring scripts
|
||||
|
||||
3. **Monitoring:**
|
||||
- Set up container logging
|
||||
- Configure health checks
|
||||
- Enable performance metrics
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
### Must Pass (Critical)
|
||||
- ✅ Container starts and stays running
|
||||
- ✅ Web interface accessible via HTTPS
|
||||
- ✅ COT messages can be sent and received
|
||||
- ✅ Messages appear correctly on map
|
||||
- ✅ Data persists across container restarts
|
||||
- ✅ No security vulnerabilities found
|
||||
|
||||
### Should Pass (Important)
|
||||
- ✅ Performance meets requirements
|
||||
- ✅ User management works correctly
|
||||
- ✅ Integration with other services
|
||||
- ✅ Error handling is robust
|
||||
- ✅ Documentation is complete
|
||||
|
||||
### Nice to Have
|
||||
- ✅ Load testing passes
|
||||
- ✅ Mobile device compatibility
|
||||
- ✅ Advanced geospatial features work
|
||||
- ✅ Custom branding applied
|
||||
|
||||
## Test Documentation
|
||||
|
||||
1. **Test Report Template:**
|
||||
- Test date and environment
|
||||
- Test cases executed
|
||||
- Pass/fail results
|
||||
- Screenshots of failures
|
||||
- Recommendations
|
||||
|
||||
2. **Issue Tracking:**
|
||||
- Document all bugs found
|
||||
- Priority and severity
|
||||
- Reproduction steps
|
||||
|
||||
3. **Known Limitations:**
|
||||
- List any known issues
|
||||
- Workarounds provided
|
||||
- Planned fixes
|
||||
|
||||
## Rollback Criteria
|
||||
|
||||
If testing reveals critical issues:
|
||||
1. Stop TAK service
|
||||
2. Document findings
|
||||
3. Revert to previous working state
|
||||
4. Address issues before retry
|
||||
|
||||
## Success Metrics
|
||||
|
||||
- Total test cases: [X]
|
||||
- Passed: [X]
|
||||
- Failed: [X]
|
||||
- Percentage: [XX]%
|
||||
- Critical issues: [X]
|
||||
- Major issues: [X]
|
||||
- Minor issues: [X]
|
||||
|
||||
## Timeline
|
||||
|
||||
- Testing completion: [Estimated date]
|
||||
- Issues resolution: [Estimated date]
|
||||
- Final validation: [Estimated date]
|
||||
- Milestone completion: [Estimated date]
|
||||
|
||||
## Notes
|
||||
|
||||
- Follow existing testing patterns from other services
|
||||
- Document all test results thoroughly
|
||||
- Include screenshots for UI-related tests
|
||||
- Test on multiple browsers/devices if possible
|
||||
- Verify with security team if applicable
|
||||
26
AGENTS.md
Normal file
26
AGENTS.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# AGENTS.md
|
||||
|
||||
This document outlines the development conventions for this NixOS-based infrastructure repository.
|
||||
|
||||
## Build & Deployment
|
||||
|
||||
- **Build/Deploy:** Use `nixos-rebuild switch --flake .#<hostname>` to build and deploy the configuration for a specific host.
|
||||
- **Development Shell:** Activate the development environment with `nix develop`.
|
||||
|
||||
## Linting & Formatting
|
||||
|
||||
- **Formatting:** This project uses `nixpkgs-fmt` for automatic formatting. Ensure it is run before committing changes.
|
||||
- `nixpkgs-fmt .`
|
||||
- **Linting:** No specific linter is configured, but adhere to standard Nix language conventions.
|
||||
|
||||
## Testing
|
||||
|
||||
- No automated testing suite is configured. Manually verify changes by deploying to a non-critical host.
|
||||
|
||||
## Code Style & Conventions
|
||||
|
||||
- **Imports:** Keep module imports clean and organized at the top of files.
|
||||
- **Naming:** Follow Nix community conventions for variable and function names (e.g., `camelCase` for variables, `kebab-case` for package names).
|
||||
- **Secrets:** Secrets are managed with `agenix`. Edit encrypted files with `agenix -e <file>`.
|
||||
- **Modularity:** Structure configurations into logical, reusable modules under `modules/`. New modules should be registered in `modules/nixos/default.nix` to be available to all hosts.
|
||||
- **Error Handling:** Ensure Nix expressions are robust and handle potential evaluation errors gracefully.
|
||||
203
assets/ai-optimizer/CRON_EXECUTION_PROMPT.md
Normal file
203
assets/ai-optimizer/CRON_EXECUTION_PROMPT.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# AI Model Optimization Cron Job - EXECUTION PROMPT
|
||||
|
||||
**When this cron runs, follow these instructions exactly:**
|
||||
|
||||
---
|
||||
|
||||
## Your Role
|
||||
|
||||
You are an AI model optimization agent. Your task is to find the best ollama/llama.cpp configuration for maximum context size and hardware utilization.
|
||||
|
||||
**Hardware:**
|
||||
- 2× AMD MI50 GPUs (32GB VRAM each, 64GB total)
|
||||
- 128GB system RAM
|
||||
- ROCm: HSA_OVERRIDE_GFX_VERSION=9.0.6, HIP_VISIBLE_DEVICES=0,1
|
||||
|
||||
---
|
||||
|
||||
## File Locations
|
||||
|
||||
```
|
||||
STATE: /opt/data/infra/assets/ai-optimizer/state.json
|
||||
RESULTS: /opt/data/infra/assets/ai-optimizer/results.csv
|
||||
INFRA_REPO: /opt/data/infra
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Model Queues
|
||||
|
||||
### GPU Track (Coding - prioritize speed + context on GPU)
|
||||
1. `devstral-small-2:24b`
|
||||
2. `qwen2.5-coder:32b`
|
||||
3. `codellama:34b-instruct`
|
||||
|
||||
### RAM Track (Knowledge - prioritize max context)
|
||||
1. `qwen2.5:72b`
|
||||
2. `nemotron-3-nano:30b`
|
||||
3. `mixtral:8x7b-instruct`
|
||||
|
||||
---
|
||||
|
||||
## Context Steps (in order)
|
||||
```
|
||||
[32768, 65536, 98304, 131072, 163840, 200704, 262144, 327680]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Each Run - Step by Step
|
||||
|
||||
### 1. Read State
|
||||
```bash
|
||||
cd /opt/data/infra
|
||||
cat assets/ai-optimizer/state.json
|
||||
```
|
||||
|
||||
### 2. Determine Next Test
|
||||
- Read `track` (gpu or ram)
|
||||
- Read `current_model` from queue at `model_index`
|
||||
- Read `current_config` for parameters to test
|
||||
- Select next context step from `context_steps` based on `phase`
|
||||
|
||||
### 3. Pull Model (if needed)
|
||||
```bash
|
||||
docker exec ollama ollama list | grep -q "<model>" || docker exec ollama ollama pull <model>
|
||||
```
|
||||
|
||||
### 4. Create Test Modelfile
|
||||
```bash
|
||||
docker exec ollama bash -c "cat <<EOF > /root/.ollama/test_${model}.modelfile
|
||||
FROM ${model}
|
||||
PARAMETER num_ctx ${current_config.num_ctx}
|
||||
PARAMETER num_gpu ${current_config.num_gpu}
|
||||
PARAMETER flash_attn ${current_config.flash_attn}
|
||||
PARAMETER num_predict 4096
|
||||
PARAMETER num_keep 1024
|
||||
PARAMETER repeat_penalty 1.1
|
||||
EOF"
|
||||
|
||||
docker exec ollama ollama create test-model -f /root/.ollama/test_${model}.modelfile
|
||||
```
|
||||
|
||||
### 5. Run Benchmark
|
||||
```bash
|
||||
# Warm up
|
||||
docker exec ollama ollama run test-model "Hello" > /dev/null
|
||||
|
||||
# Coding prompt
|
||||
START=$(date +%s%N)
|
||||
docker exec ollama ollama run test-model "Write a Python async context manager that retries a function with exponential backoff, max 5 retries, and logs each attempt using structlog. Include type hints."
|
||||
END=$(date +%s%N)
|
||||
|
||||
# Calculate tokens/sec from output
|
||||
```
|
||||
|
||||
### 6. Measure VRAM (if possible)
|
||||
```bash
|
||||
# Try host first
|
||||
rocm-smi --showmeminfo vram 2>/dev/null || \
|
||||
# Try via docker
|
||||
docker exec --privileged ollama rocm-smi --showmeminfo vram 2>/dev/null || \
|
||||
# Fallback
|
||||
echo "VRAM measurement unavailable"
|
||||
```
|
||||
|
||||
### 7. Record Results
|
||||
- Parse tokens/sec from ollama output
|
||||
- Record VRAM/RAM usage
|
||||
- Determine if this is best config so far for this model
|
||||
- Update `best_configs` if tokens/sec improved or context increased
|
||||
|
||||
### 8. Update State
|
||||
```python
|
||||
# Logic:
|
||||
if test_successful:
|
||||
if context_step < max_reached:
|
||||
phase = "context_scaling"
|
||||
current_config.num_ctx = next_context_step
|
||||
else:
|
||||
# Move to next model
|
||||
model_index += 1
|
||||
phase = "context_scaling"
|
||||
current_config.num_ctx = context_steps[0]
|
||||
else:
|
||||
# OOM or error - record last good as best
|
||||
best_configs[track][current_model] = last_good_config
|
||||
model_index += 1
|
||||
phase = "context_scaling"
|
||||
```
|
||||
|
||||
### 9. Commit to Repo
|
||||
```bash
|
||||
cd /opt/data/infra
|
||||
git add assets/ai-optimizer/
|
||||
git commit -m "ai-optimizer: tested ${model} at ${num_ctx} ctx - ${status}"
|
||||
git push origin master
|
||||
```
|
||||
|
||||
### 10. Matrix Notification (if available)
|
||||
```python
|
||||
import os
|
||||
if os.getenv("MATRIX_HOME_SERVER") and os.getenv("MATRIX_ACCESS_TOKEN"):
|
||||
# Send notification to Matrix room
|
||||
# Room ID from env or config
|
||||
pass
|
||||
# Else: silent
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stop Conditions
|
||||
|
||||
1. All models in both queues have `best_configs` recorded
|
||||
2. Manual intervention needed (error in state.json `error` field)
|
||||
3. No progress for 3 consecutive runs (stuck)
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
If any step fails:
|
||||
1. Log error to state.json: `"error": {"message": "...", "timestamp": "..."}`
|
||||
2. Do NOT increment model_index (retry next run)
|
||||
3. Commit state with error field
|
||||
4. Exit gracefully
|
||||
|
||||
---
|
||||
|
||||
## Important Notes
|
||||
|
||||
- **No num_parallel**: Do not use this parameter
|
||||
- **Two tracks**: Complete GPU track first, then RAM track
|
||||
- **Backend**: Start with ollama, llama.cpp testing is optional (requires uncommenting in compose.yml)
|
||||
- **Host access**: Some commands need host - use docker exec or SSH if available
|
||||
- **Ask before deploy**: If config changes needed in NixOS modules, show diff and wait for user confirmation before `nh os switch`
|
||||
|
||||
---
|
||||
|
||||
## Example State Transitions
|
||||
|
||||
**Start:**
|
||||
```json
|
||||
{"track": "gpu", "model_index": 0, "current_model": "devstral-small-2:24b", "current_config": {"num_ctx": 32768, ...}}
|
||||
```
|
||||
|
||||
**After successful test at 32k:**
|
||||
```json
|
||||
{"track": "gpu", "model_index": 0, "current_model": "devstral-small-2:24b", "current_config": {"num_ctx": 65536, ...}}
|
||||
```
|
||||
|
||||
**After OOM at 131k:**
|
||||
```json
|
||||
{
|
||||
"track": "gpu",
|
||||
"model_index": 1,
|
||||
"current_model": "qwen2.5-coder:32b",
|
||||
"best_configs": {
|
||||
"gpu": {
|
||||
"devstral-small-2:24b": {"num_ctx": 98304, "num_gpu": 99, "tokens_per_sec": 11.2}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
283
assets/ai-optimizer/CRON_JOB_DRAFT.md
Normal file
283
assets/ai-optimizer/CRON_JOB_DRAFT.md
Normal file
@@ -0,0 +1,283 @@
|
||||
# AI Model Optimization Cron Job
|
||||
|
||||
**Goal:** Find optimal configurations for maximum context size with full hardware utilization.
|
||||
|
||||
**Hardware:**
|
||||
- 2× AMD MI50 GPUs (32GB VRAM each, 64GB total)
|
||||
- 128GB system RAM
|
||||
- ROCm: HSA_OVERRIDE_GFX_VERSION=9.0.6, HIP_VISIBLE_DEVICES=0,1
|
||||
|
||||
---
|
||||
|
||||
## Model Queue
|
||||
|
||||
### GPU-Optimized (Coding - prioritize speed + context on GPU)
|
||||
1. `devstral-small-2:24b` - Best coding model
|
||||
2. `qwen2.5-coder:32b` - Strong coder, fits on GPU+offload
|
||||
3. `codellama:34b-instruct` - Legacy but solid
|
||||
|
||||
### RAM-Optimized (Knowledge - prioritize max context, accept slower)
|
||||
1. `qwen2.5:72b` - Best knowledge, needs heavy offload
|
||||
2. `nemotron-3-nano:30b` - Good general knowledge
|
||||
3. `mixtral:8x7b-instruct` - MoE, efficient for knowledge
|
||||
|
||||
---
|
||||
|
||||
## Optimization Strategy
|
||||
|
||||
**Two separate tracks:**
|
||||
|
||||
### Track A: GPU-Focused (Coding)
|
||||
```
|
||||
Baseline: num_ctx=32768, num_gpu=99, flash_attn=true
|
||||
Steps:
|
||||
1. Increase context: 32k → 65k → 98k → 131k → 163k
|
||||
2. At each step, verify VRAM usage < 60GB (leave headroom)
|
||||
3. If OOM: reduce num_gpu until stable, record best
|
||||
4. Measure tokens/sec - if < 5 tok/s, consider context too high
|
||||
```
|
||||
|
||||
### Track B: RAM-Focused (Knowledge)
|
||||
```
|
||||
Baseline: num_ctx=65536, num_gpu=50, flash_attn=true
|
||||
Steps:
|
||||
1. Increase context: 65k → 131k → 200k → 262k → 327k
|
||||
2. Allow heavy RAM offload (system RAM up to 100GB)
|
||||
3. If OOM: reduce context or num_gpu
|
||||
4. Speed less critical - focus on max stable context
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backend-Specific Configs
|
||||
|
||||
### Ollama (Modelfile parameters)
|
||||
```
|
||||
PARAMETER num_ctx <value>
|
||||
PARAMETER num_gpu <layers>
|
||||
PARAMETER flash_attn true/false
|
||||
PARAMETER num_predict 4096
|
||||
PARAMETER num_keep 1024
|
||||
PARAMETER repeat_penalty 1.1
|
||||
```
|
||||
|
||||
### Llama.cpp (CLI flags)
|
||||
```
|
||||
--ctx-size <value>
|
||||
--n-gpu-layers <layers>
|
||||
--flash-attn on/off
|
||||
--n-predict 4096
|
||||
--batch-size 4096
|
||||
--ubatch-size 512
|
||||
--cache-type-k f16
|
||||
--cache-type-v f16
|
||||
--split-mode layer
|
||||
--no-mmap
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Host Test Instructions
|
||||
|
||||
**The cron runs inside the hermes container. Some tests require host access:**
|
||||
|
||||
### 1. VRAM Monitoring (HOST)
|
||||
```bash
|
||||
# Run on host to check VRAM usage during/after benchmark
|
||||
sudo rocm-smi --showmeminfo vram
|
||||
|
||||
# Or via docker exec if rocm-smi available in container
|
||||
docker exec --privileged ollama rocm-smi --showmeminfo vram
|
||||
```
|
||||
|
||||
### 2. Running Ollama Benchmarks (CONTAINER)
|
||||
```bash
|
||||
# Pull model
|
||||
docker exec ollama ollama pull <model>
|
||||
|
||||
# Create custom modelfile
|
||||
docker exec ollama bash -c 'cat <<EOF > /root/.ollama/test.modelfile
|
||||
FROM <model>
|
||||
PARAMETER num_ctx 65536
|
||||
PARAMETER num_gpu 99
|
||||
PARAMETER flash_attn true
|
||||
EOF'
|
||||
|
||||
# Create model from modelfile
|
||||
docker exec ollama ollama create test-model -f /root/.ollama/test.modelfile
|
||||
|
||||
# Run benchmark (warm model first)
|
||||
docker exec ollama ollama run test-model "Write a Python async context manager with exponential backoff"
|
||||
|
||||
# Cleanup
|
||||
docker exec ollama ollama rm test-model
|
||||
```
|
||||
|
||||
### 3. Running Llama.cpp Benchmarks (CONTAINER - needs llama.cpp container)
|
||||
```bash
|
||||
# Uncomment llama_cpp_devstral in compose.yml first
|
||||
# Then rebuild: sudo nh os switch --flake .#lazyworkhorse
|
||||
|
||||
# Test via HTTP API
|
||||
curl http://localhost:8300/v1/completions \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "devstral-2-small-llama_cpp",
|
||||
"prompt": "Write a Python function",
|
||||
"max_tokens": 100
|
||||
}'
|
||||
```
|
||||
|
||||
### 4. Deploying Changes (HOST via ai-worker)
|
||||
```bash
|
||||
# After optimization, commit results
|
||||
cd /home/ai-worker/infra
|
||||
git add assets/ai-optimizer/
|
||||
git commit -m "ai-optimizer: new best config for <model>"
|
||||
git push
|
||||
|
||||
# If config changes needed in ollama_init_custom_models.nix:
|
||||
# 1. Edit the file
|
||||
# 2. nixpkgs-fmt .
|
||||
# 3. Show diff to user
|
||||
# 4. Wait for confirmation
|
||||
# 5. sudo nh os switch --flake .#lazyworkhorse
|
||||
```
|
||||
|
||||
### 5. Accessing Host from Hermes Container
|
||||
```bash
|
||||
# SSH to host as ai-worker (key should be mounted)
|
||||
ssh -i /path/to/key ai-worker@host.docker.internal
|
||||
|
||||
# Or via docker socket if mounted
|
||||
# (not recommended for security)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Benchmark Prompts
|
||||
|
||||
### Coding (Track A)
|
||||
```
|
||||
"Write a Python async context manager that retries a function with exponential backoff, max 5 retries, and logs each attempt using structlog. Include type hints and error handling."
|
||||
```
|
||||
|
||||
### Knowledge (Track B)
|
||||
```
|
||||
"Explain the complete memory hierarchy in modern GPUs, from registers through L1/L2 caches to VRAM, and how data moves between them during matrix multiplication. Include bandwidth considerations for each level."
|
||||
```
|
||||
|
||||
### Measurement
|
||||
- Tokens per second (generation speed)
|
||||
- Time to first token (latency)
|
||||
- VRAM usage (via rocm-smi)
|
||||
- System RAM usage (via free -h)
|
||||
- Context success (did it complete without OOM?)
|
||||
|
||||
---
|
||||
|
||||
## State File Structure
|
||||
|
||||
`/opt/data/infra/assets/ai-optimizer/state.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"track": "gpu",
|
||||
"current_model": "devstral-small-2:24b",
|
||||
"model_index": 0,
|
||||
"phase": "context_scaling",
|
||||
"backend": "ollama",
|
||||
"current_config": {
|
||||
"num_ctx": 65536,
|
||||
"num_gpu": 99,
|
||||
"flash_attn": true
|
||||
},
|
||||
"best_configs": {
|
||||
"gpu": {
|
||||
"devstral-small-2:24b": {
|
||||
"backend": "ollama",
|
||||
"num_ctx": 131072,
|
||||
"num_gpu": 99,
|
||||
"flash_attn": true,
|
||||
"tokens_per_sec": 12.5,
|
||||
"vram_used_gb": 58.2,
|
||||
"tested_at": "2026-04-28T17:00:00Z"
|
||||
}
|
||||
},
|
||||
"ram": {}
|
||||
},
|
||||
"completed_models": [],
|
||||
"gpu_queue": ["devstral-small-2:24b", "qwen2.5-coder:32b", "codellama:34b-instruct"],
|
||||
"ram_queue": ["qwen2.5:72b", "nemotron-3-nano:30b", "mixtral:8x7b-instruct"]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Results CSV
|
||||
|
||||
`/opt/data/infra/assets/ai-optimizer/results.csv`
|
||||
|
||||
```csv
|
||||
timestamp,track,model,backend,phase,num_ctx,num_gpu,flash_attn,tokens_per_sec,vram_gb,ram_gb,status,is_best
|
||||
2026-04-28T17:00:00Z,gpu,devstral-small-2:24b,ollama,context_scaling,65536,99,true,15.2,52.1,18.4,success,false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cron Job Flow
|
||||
|
||||
```
|
||||
1. Read state.json
|
||||
2. If both queues empty → STOP (all models tested)
|
||||
3. Select next model from current track queue
|
||||
4. Pull model if needed (docker exec ollama ollama pull)
|
||||
5. Create Modelfile / llama.cpp config with current test params
|
||||
6. Run benchmark (both prompts)
|
||||
7. Measure: tokens/sec, VRAM (rocm-smi), RAM (free -h)
|
||||
8. If successful:
|
||||
- Increase context (next step)
|
||||
- Update current_config in state
|
||||
9. If OOM/error:
|
||||
- Record last good config as best_configs[track][model]
|
||||
- Move to next model in queue
|
||||
10. Update state.json
|
||||
11. Append to results.csv
|
||||
12. Git commit + push to /opt/data/infra
|
||||
13. Send Matrix notification if available, else silent
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Matrix Notification (Optional)
|
||||
|
||||
```python
|
||||
# If matrix credentials available in environment
|
||||
if os.getenv("MATRIX_HOME_SERVER") and os.getenv("MATRIX_ACCESS_TOKEN"):
|
||||
# Send completion notification
|
||||
# Room: !ai-optimizer:lazyworkhorse.net (or similar)
|
||||
pass
|
||||
# Else: silent, just commit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Files to Create
|
||||
|
||||
```
|
||||
/opt/data/infra/assets/ai-optimizer/
|
||||
├── state.json # Current progress
|
||||
├── results.csv # All test results
|
||||
├── best_configs.json # Final best configs (human-readable)
|
||||
└── CRON_JOB_DRAFT.md # This file
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- **No num_parallel**: Removed to avoid limiting other settings
|
||||
- **Two tracks**: GPU (coding/speed) vs RAM (knowledge/context)
|
||||
- **Both backends**: Test ollama first, then llama.cpp if available
|
||||
- **Host tests**: rocm-smi must run on host or privileged container
|
||||
- **Deploy**: ai-worker has sudo for nh/nixos-rebuild, must ask user first
|
||||
1
assets/ai-optimizer/results.csv
Normal file
1
assets/ai-optimizer/results.csv
Normal file
@@ -0,0 +1 @@
|
||||
timestamp,track,model,backend,phase,num_ctx,num_gpu,flash_attn,tokens_per_sec,vram_gb,ram_gb,status,is_best
|
||||
|
21
assets/ai-optimizer/state.json
Normal file
21
assets/ai-optimizer/state.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"track": "gpu",
|
||||
"current_model": "devstral-small-2:24b",
|
||||
"model_index": 0,
|
||||
"phase": "context_scaling",
|
||||
"backend": "ollama",
|
||||
"current_config": {
|
||||
"num_ctx": 32768,
|
||||
"num_gpu": 99,
|
||||
"flash_attn": true
|
||||
},
|
||||
"best_configs": {
|
||||
"gpu": {},
|
||||
"ram": {}
|
||||
},
|
||||
"completed_models": [],
|
||||
"gpu_queue": ["devstral-small-2:24b", "qwen2.5-coder:32b", "codellama:34b-instruct"],
|
||||
"ram_queue": ["qwen2.5:72b", "nemotron-3-nano:30b", "mixtral:8x7b-instruct"],
|
||||
"context_steps": [32768, 65536, 98304, 131072, 163840, 200704, 262144, 327680],
|
||||
"last_updated": "2026-04-28T17:00:00Z"
|
||||
}
|
||||
Submodule assets/compose updated: bcaad554a6...fb0f2cbe84
66
docker/hermes/Dockerfile
Normal file
66
docker/hermes/Dockerfile
Normal file
@@ -0,0 +1,66 @@
|
||||
FROM ghcr.io/astral-sh/uv:0.11.6-python3.13-trixie@sha256:b3c543b6c4f23a5f2df22866bd7857e5d304b67a564f4feab6ac22044dde719b AS uv_source
|
||||
FROM tianon/gosu:1.19-trixie@sha256:3b176695959c71e123eb390d427efc665eeb561b1540e82679c15e992006b8b9 AS gosu_source
|
||||
FROM debian:13.4
|
||||
|
||||
# Disable Python stdout buffering to ensure logs are printed immediately
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
# Store Playwright browsers outside the volume mount so the build-time
|
||||
# install survives the /opt/data volume overlay at runtime.
|
||||
ENV PLAYWRIGHT_BROWSERS_PATH=/opt/hermes/.playwright
|
||||
|
||||
# Install system dependencies in one layer, clear APT cache
|
||||
# tini reaps orphaned zombie processes (MCP stdio subprocesses, git, bun, etc.)
|
||||
# that would otherwise accumulate when hermes runs as PID 1. See #15012.
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential nodejs npm python3 ripgrep ffmpeg gcc python3-dev libffi-dev procps git openssh-client docker-cli tini \
|
||||
curl poppler-utils imagemagick \
|
||||
chromium xvfb fonts-noto-color-emoji fonts-unifont fonts-liberation fonts-ipafont-gothic fonts-wqy-zenhei fonts-tlwg-loma-otf fonts-freefont-ttf \
|
||||
libasound2t64 libatk-bridge2.0-0t64 libatk1.0-0t64 libatspi2.0-0t64 libcairo2 libcups2t64 libdbus-1-3 libdrm2 libgbm1 libglib2.0-0t64 libnspr4 libnss3 libpango-1.0-0 libx11-6 libxcb1 libxcomposite1 libxdamage1 libxext6 libxfixes3 libxkbcommon0 libxrandr2 && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Non-root user for runtime; UID can be overridden via HERMES_UID at runtime
|
||||
RUN useradd -u 10000 -m -d /opt/data hermes
|
||||
|
||||
COPY --chmod=0755 --from=gosu_source /gosu /usr/local/bin/
|
||||
COPY --chmod=0755 --from=uv_source /usr/local/bin/uv /usr/local/bin/uvx /usr/local/bin/
|
||||
|
||||
WORKDIR /opt/hermes
|
||||
|
||||
# ---------- Layer-cached dependency install ----------
|
||||
# Copy only package manifests first so npm install + Playwright are cached
|
||||
# unless the lockfiles themselves change.
|
||||
COPY package.json package-lock.json ./
|
||||
COPY web/package.json web/package-lock.json web/
|
||||
|
||||
RUN npm install --prefer-offline --no-audit && \
|
||||
npx playwright install --with-deps chromium --only-shell && \
|
||||
(cd web && npm install --prefer-offline --no-audit) && \
|
||||
npm cache clean --force
|
||||
|
||||
# ---------- Source code ----------
|
||||
# .dockerignore excludes node_modules, so the installs above survive.
|
||||
COPY --chown=hermes:hermes . .
|
||||
|
||||
# Build web dashboard (Vite outputs to hermes_cli/web_dist/)
|
||||
RUN cd web && npm run build
|
||||
|
||||
# ---------- Permissions ----------
|
||||
# Make install dir world-readable so any HERMES_UID can read it at runtime.
|
||||
# The venv needs to be traversable too.
|
||||
USER root
|
||||
RUN chmod -R a+rX /opt/hermes
|
||||
# Start as root so the entrypoint can usermod/groupmod + gosu.
|
||||
# If HERMES_UID is unset, the entrypoint drops to the default hermes user (10000).
|
||||
|
||||
# ---------- Python virtualenv ----------
|
||||
RUN uv venv && \
|
||||
uv pip install --no-cache-dir -e ".[all]"
|
||||
|
||||
# ---------- Runtime ----------
|
||||
ENV HERMES_WEB_DIST=/opt/hermes/hermes_cli/web_dist
|
||||
ENV HERMES_HOME=/opt/data
|
||||
ENV PATH="/opt/data/.local/bin:${PATH}"
|
||||
VOLUME [ "/opt/data" ]
|
||||
ENTRYPOINT [ "/usr/bin/tini", "-g", "--", "/opt/hermes/docker/entrypoint.sh" ]
|
||||
102
docker/hermes/entrypoint.sh
Executable file
102
docker/hermes/entrypoint.sh
Executable file
@@ -0,0 +1,102 @@
|
||||
#!/bin/bash
|
||||
# Docker/Podman entrypoint: bootstrap config files into the mounted volume, then run hermes.
|
||||
set -e
|
||||
|
||||
HERMES_HOME="${HERMES_HOME:-/opt/data}"
|
||||
INSTALL_DIR="/opt/hermes"
|
||||
|
||||
# --- Privilege dropping via gosu ---
|
||||
# When started as root (the default for Docker, or fakeroot in rootless Podman),
|
||||
# optionally remap the hermes user/group to match host-side ownership, fix volume
|
||||
# permissions, then re-exec as hermes.
|
||||
if [ "$(id -u)" = "0" ]; then
|
||||
if [ -n "$HERMES_UID" ] && [ "$HERMES_UID" != "$(id -u hermes)" ]; then
|
||||
echo "Changing hermes UID to $HERMES_UID"
|
||||
usermod -u "$HERMES_UID" hermes
|
||||
fi
|
||||
|
||||
if [ -n "$HERMES_GID" ] && [ "$HERMES_GID" != "$(id -g hermes)" ]; then
|
||||
echo "Changing hermes GID to $HERMES_GID"
|
||||
# -o allows non-unique GID (e.g. macOS GID 20 "staff" may already exist
|
||||
# as "dialout" in the Debian-based container image)
|
||||
groupmod -o -g "$HERMES_GID" hermes 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Fix ownership of the data volume. When HERMES_UID remaps the hermes user,
|
||||
# files created by previous runs (under the old UID) become inaccessible.
|
||||
# Always chown -R when UID was remapped; otherwise only if top-level is wrong.
|
||||
actual_hermes_uid=$(id -u hermes)
|
||||
needs_chown=false
|
||||
if [ -n "$HERMES_UID" ] && [ "$HERMES_UID" != "10000" ]; then
|
||||
needs_chown=true
|
||||
elif [ "$(stat -c %u "$HERMES_HOME" 2>/dev/null)" != "$actual_hermes_uid" ]; then
|
||||
needs_chown=true
|
||||
fi
|
||||
if [ "$needs_chown" = true ]; then
|
||||
echo "Fixing ownership of $HERMES_HOME to hermes ($actual_hermes_uid)"
|
||||
# In rootless Podman the container's "root" is mapped to an unprivileged
|
||||
# host UID — chown will fail. That's fine: the volume is already owned
|
||||
# by the mapped user on the host side.
|
||||
chown -R hermes:hermes "$HERMES_HOME" 2>/dev/null || \
|
||||
echo "Warning: chown failed (rootless container?) — continuing anyway"
|
||||
fi
|
||||
|
||||
echo "Dropping root privileges"
|
||||
exec gosu hermes "$0" "$@"
|
||||
fi
|
||||
|
||||
# --- Running as hermes from here ---
|
||||
source "${INSTALL_DIR}/.venv/bin/activate"
|
||||
|
||||
# Create essential directory structure. Cache and platform directories
|
||||
# (cache/images, cache/audio, platforms/whatsapp, etc.) are created on
|
||||
# demand by the application — don't pre-create them here so new installs
|
||||
# get the consolidated layout from get_hermes_dir().
|
||||
# The "home/" subdirectory is a per-profile HOME for subprocesses (git,
|
||||
# ssh, gh, npm …). Without it those tools write to /root which is
|
||||
# ephemeral and shared across profiles. See issue #4426.
|
||||
mkdir -p "$HERMES_HOME"/{cron,sessions,logs,hooks,memories,skills,skins,plans,workspace,home}
|
||||
|
||||
# .env
|
||||
if [ ! -f "$HERMES_HOME/.env" ]; then
|
||||
cp "$INSTALL_DIR/.env.example" "$HERMES_HOME/.env"
|
||||
fi
|
||||
|
||||
# config.yaml
|
||||
if [ ! -f "$HERMES_HOME/config.yaml" ]; then
|
||||
cp "$INSTALL_DIR/cli-config.yaml.example" "$HERMES_HOME/config.yaml"
|
||||
fi
|
||||
|
||||
# Ensure the main config file remains accessible to the hermes runtime user
|
||||
# even if it was edited on the host after initial ownership setup.
|
||||
if [ -f "$HERMES_HOME/config.yaml" ]; then
|
||||
chown hermes:hermes "$HERMES_HOME/config.yaml"
|
||||
chmod 640 "$HERMES_HOME/config.yaml"
|
||||
fi
|
||||
|
||||
# SOUL.md
|
||||
if [ ! -f "$HERMES_HOME/SOUL.md" ]; then
|
||||
cp "$INSTALL_DIR/docker/SOUL.md" "$HERMES_HOME/SOUL.md"
|
||||
fi
|
||||
|
||||
# Sync bundled skills (manifest-based so user edits are preserved)
|
||||
if [ -d "$INSTALL_DIR/skills" ]; then
|
||||
python3 "$INSTALL_DIR/tools/skills_sync.py"
|
||||
fi
|
||||
|
||||
# Final exec: two supported invocation patterns.
|
||||
#
|
||||
# docker run <image> -> exec `hermes` with no args (legacy default)
|
||||
# docker run <image> chat -q "..." -> exec `hermes chat -q "..."` (legacy wrap)
|
||||
# docker run <image> sleep infinity -> exec `sleep infinity` directly
|
||||
# docker run <image> bash -> exec `bash` directly
|
||||
#
|
||||
# If the first positional arg resolves to an executable on PATH, we assume the
|
||||
# caller wants to run it directly (needed by the launcher which runs long-lived
|
||||
# `sleep infinity` sandbox containers — see tools/environments/docker.py).
|
||||
# Otherwise we treat the args as a hermes subcommand and wrap with `hermes`,
|
||||
# preserving the documented `docker run <image> <subcommand>` behavior.
|
||||
if [ $# -gt 0 ] && command -v "$1" >/dev/null 2>&1; then
|
||||
exec "$@"
|
||||
fi
|
||||
exec hermes "$@"
|
||||
163
flake.lock
generated
163
flake.lock
generated
@@ -10,11 +10,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1754337839,
|
||||
"narHash": "sha256-fEc2/4YsJwtnLU7HCFMRckb0u9UNnDZmwGhXT5U5NTw=",
|
||||
"lastModified": 1770165109,
|
||||
"narHash": "sha256-9VnK6Oqai65puVJ4WYtCTvlJeXxMzAp/69HhQuTdl/I=",
|
||||
"owner": "ryantm",
|
||||
"repo": "agenix",
|
||||
"rev": "856df6f6922845abd4fd958ce21febc07ca2fa45",
|
||||
"rev": "b027ee29d959fda4b60b57566d64c98a202e0feb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -23,6 +23,20 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1751685974,
|
||||
"narHash": "sha256-NKw96t+BgHIYzHUjkTK95FqYRVKB8DHpVhefWSz/kTw=",
|
||||
"rev": "549f2762aebeff29a2e5ece7a7dc0f955281a1d1",
|
||||
"type": "tarball",
|
||||
"url": "https://git.lix.systems/api/v1/repos/lix-project/flake-compat/archive/549f2762aebeff29a2e5ece7a7dc0f955281a1d1.tar.gz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz"
|
||||
}
|
||||
},
|
||||
"home-manager": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
@@ -44,13 +58,131 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"lix": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"nix2container": "nix2container",
|
||||
"nix_2_18": "nix_2_18",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-regression": "nixpkgs-regression",
|
||||
"pre-commit-hooks": "pre-commit-hooks"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1774721317,
|
||||
"narHash": "sha256-KS0ElyhZKdUFcfaxfwid3yi2Id3EP9i+dGL16/wx1T8=",
|
||||
"ref": "main",
|
||||
"rev": "d0190cff6f2314cc1c727ff113aea20e086f4bcc",
|
||||
"revCount": 19103,
|
||||
"type": "git",
|
||||
"url": "https://git.lix.systems/lix-project/lix"
|
||||
},
|
||||
"original": {
|
||||
"ref": "main",
|
||||
"type": "git",
|
||||
"url": "https://git.lix.systems/lix-project/lix"
|
||||
}
|
||||
},
|
||||
"lowdown-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1633514407,
|
||||
"narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=",
|
||||
"owner": "kristapsdz",
|
||||
"repo": "lowdown",
|
||||
"rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "kristapsdz",
|
||||
"repo": "lowdown",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix2container": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1767195068,
|
||||
"narHash": "sha256-+OMnL79ZjqM/PCz2hoQ12MnXNoSSfBGnsYBOZnA9XbI=",
|
||||
"owner": "nlewo",
|
||||
"repo": "nix2container",
|
||||
"rev": "bb6801be998ba857a62c002cb77ece66b0a57298",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nlewo",
|
||||
"repo": "nix2container",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix_2_18": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"lix",
|
||||
"flake-compat"
|
||||
],
|
||||
"lowdown-src": "lowdown-src",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-regression": [
|
||||
"lix",
|
||||
"nixpkgs-regression"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1730375271,
|
||||
"narHash": "sha256-RrOFlDGmRXcVRV2p2HqHGqvzGNyWoD0Dado/BNlJ1SI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nix",
|
||||
"rev": "0f665ff6779454f2117dcc32e44380cda7f45523",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "2.18.9",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1753939845,
|
||||
"narHash": "sha256-K2ViRJfdVGE8tpJejs8Qpvvejks1+A4GQej/lBk5y7I=",
|
||||
"lastModified": 1705033721,
|
||||
"narHash": "sha256-K5eJHmL1/kev6WuqyqqbS1cdNnSidIZ3jeqJ7GbrYnQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a1982c92d8980a0114372973cbdfe0a307f1bdea",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-23.05-small",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-regression": {
|
||||
"locked": {
|
||||
"lastModified": 1643052045,
|
||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1774386573,
|
||||
"narHash": "sha256-4hAV26quOxdC6iyG7kYaZcM3VOskcPUrdCQd/nx8obc=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "94def634a20494ee057c76998843c015909d6311",
|
||||
"rev": "46db2e09e1d3f113a13c0d7b81e2f221c63b8ce9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -60,10 +192,27 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pre-commit-hooks": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1769939035,
|
||||
"narHash": "sha256-Fok2AmefgVA0+eprw2NDwqKkPGEI5wvR+twiZagBvrg=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "a8ca480175326551d6c4121498316261cbb5b260",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"agenix": "agenix",
|
||||
"nixpkgs": "nixpkgs"
|
||||
"lix": "lix",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
|
||||
48
flake.nix
48
flake.nix
@@ -8,20 +8,31 @@
|
||||
inputs.darwin.follows = "";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
lix = {
|
||||
url = "git+https://git.lix.systems/lix-project/lix?ref=main";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
self.submodules = true;
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, agenix, ... }@inputs:
|
||||
outputs = { self, nixpkgs, agenix, lix, ... }@inputs:
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
keys = import ./lib/keys.nix;
|
||||
paths = {
|
||||
flake = "/home/gortium/infra";
|
||||
identities = [ "/home/gortium/.ssh/gortium_ssh_key" "/etc/ssh/ssh_host_ed25519_key" ];
|
||||
identities = [
|
||||
"/home/gortium/.ssh/gortium_ssh_key"
|
||||
"/etc/ssh/ssh_host_ed25519_key"
|
||||
"/root/.age/bootstrap.key" ];
|
||||
};
|
||||
overlays = [ agenix.overlays.default ];
|
||||
pkgs = import nixpkgs {
|
||||
inherit system overlays;
|
||||
config.allowUnfree = true;
|
||||
config.permittedInsecurePackages = [
|
||||
"openclaw-2026.3.12"
|
||||
];
|
||||
};
|
||||
|
||||
devShell = import ./shells/nix_dev.nix {
|
||||
@@ -31,14 +42,41 @@
|
||||
{
|
||||
nixosConfigurations = {
|
||||
lazyworkhorse = nixpkgs.lib.nixosSystem {
|
||||
specialArgs = { inherit system self keys paths; };
|
||||
specialArgs = { inherit system self keys paths inputs; };
|
||||
modules = [
|
||||
{ nixpkgs.overlays = overlays; }
|
||||
{
|
||||
nixpkgs.overlays = overlays;
|
||||
nixpkgs.config.allowUnfree = true;
|
||||
nixpkgs.config.rocmSupport = true;
|
||||
nixpkgs.config.permittedInsecurePackages = [
|
||||
"openclaw-2026.3.12"
|
||||
];
|
||||
nix.package = lix.packages.${system}.default;
|
||||
}
|
||||
agenix.nixosModules.default
|
||||
./hosts/lazyworkhorse/configuration.nix
|
||||
./hosts/lazyworkhorse/hardware-configuration.nix
|
||||
./modules/default.nix
|
||||
./modules/nixos/filesystem/hoardingcow-mount.nix
|
||||
./modules/nixos/services/docker_manager.nix
|
||||
./modules/nixos/services/open_code_server.nix
|
||||
./modules/nixos/services/ollama_init_custom_models.nix
|
||||
./modules/nixos/services/openclaw_node.nix
|
||||
./users/gortium.nix
|
||||
./users/ai-worker.nix
|
||||
];
|
||||
};
|
||||
|
||||
cyt-pi = nixpkgs.lib.nixosSystem {
|
||||
specialArgs = { inherit self keys paths inputs; };
|
||||
modules = [
|
||||
{
|
||||
nixpkgs.overlays = overlays;
|
||||
nixpkgs.config.allowUnfree = true;
|
||||
nixpkgs.hostPlatform = "aarch64-linux";
|
||||
nix.package = lix.packages."aarch64-linux".default;
|
||||
}
|
||||
./hosts/cyt-pi/configuration.nix
|
||||
./hosts/cyt-pi/hardware-configuration.nix
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
98
hosts/cyt-pi/configuration.nix
Normal file
98
hosts/cyt-pi/configuration.nix
Normal file
@@ -0,0 +1,98 @@
|
||||
{ config, lib, pkgs, paths, self, ... }:
|
||||
|
||||
{
|
||||
# Basic Host Info
|
||||
networking.hostName = "cyt-pi";
|
||||
time.timeZone = "America/Montreal";
|
||||
i18n.defaultLocale = "en_CA.UTF-8";
|
||||
|
||||
# System State
|
||||
system.stateVersion = "25.05";
|
||||
|
||||
# Boot & Hardware (Pi Zero 2 W is ARM64)
|
||||
boot.loader.grub.enable = false;
|
||||
boot.loader.generic-extlinux-compatible.enable = true;
|
||||
boot.kernelPackages = pkgs.linuxPackages_latest;
|
||||
|
||||
# Networking
|
||||
networking.networkmanager.enable = true;
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
settings.PermitRootLogin = "prohibit-password";
|
||||
};
|
||||
|
||||
# User
|
||||
users.users.gortium = {
|
||||
isNormalUser = true;
|
||||
extraGroups = [ "wheel" "networkmanager" "kismet" ];
|
||||
openssh.authorizedKeys.keys = [
|
||||
# Populate with your public key
|
||||
];
|
||||
};
|
||||
|
||||
# CYT Project Dependencies (Headless)
|
||||
environment.systemPackages = with pkgs; [
|
||||
git
|
||||
python311
|
||||
python311Packages.opencv4
|
||||
python311Packages.numpy
|
||||
python311Packages.pillow
|
||||
autossh # For the reverse tunnel
|
||||
kismet # Wi-Fi monitoring
|
||||
];
|
||||
|
||||
# Kismet Service
|
||||
systemd.services.kismet = {
|
||||
description = "Kismet Wi-Fi Monitor";
|
||||
after = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
User = "gortium";
|
||||
Group = "kismet";
|
||||
ExecStart = ''
|
||||
${pkgs.kismet}/bin/kismet -c panda --log-base=/home/gortium/kismet_logs --no-nc-ui
|
||||
'';
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
};
|
||||
};
|
||||
|
||||
# Reverse SSH Tunnel Service
|
||||
systemd.services.cyt-tunnel = {
|
||||
description = "Reverse SSH Tunnel to lazyworkhorse.net";
|
||||
after = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
User = "gortium";
|
||||
ExecStart = ''
|
||||
${pkgs.autossh}/bin/autossh -M 0 -N \
|
||||
-o "ServerAliveInterval 30" \
|
||||
-o "ServerAliveCountMax 3" \
|
||||
-R 19999:localhost:22 \
|
||||
gortium@lazyworkhorse.net -p 2425 \
|
||||
-i /home/gortium/.ssh/cyt_tunnel_key
|
||||
'';
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
};
|
||||
};
|
||||
|
||||
# CYT Application Service
|
||||
systemd.services.cyt-app = {
|
||||
description = "Chasing Your Tail - Target Detector";
|
||||
after = [ "network-online.target" "kismet.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
User = "gortium";
|
||||
WorkingDirectory = "/home/gortium/Chasing-Your-Tail-NG";
|
||||
ExecStart = ''
|
||||
${pkgs.python311}/bin/python3 target_detector_cli.py --min-ssids 2
|
||||
'';
|
||||
Restart = "on-failure";
|
||||
RestartSec = "60s";
|
||||
Environment = [
|
||||
"CYT_KISMET_LOGS=/home/gortium/kismet_logs"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
24
hosts/cyt-pi/hardware-configuration.nix
Normal file
24
hosts/cyt-pi/hardware-configuration.nix
Normal file
@@ -0,0 +1,24 @@
|
||||
{ config, lib, pkgs, modulesPath, ... }:
|
||||
|
||||
{
|
||||
imports =
|
||||
[ (modulesPath + "/installer/scan/not-detected.nix")
|
||||
];
|
||||
|
||||
boot.initrd.availableKernelModules = [ "xhci_pci" "usbhid" "sdhci_pci" ];
|
||||
boot.initrd.kernelModules = [ ];
|
||||
boot.kernelModules = [ ];
|
||||
boot.extraModulePackages = [ ];
|
||||
|
||||
# Pi Zero 2 W specific filesystem
|
||||
fileSystems."/" =
|
||||
{ device = "/dev/disk/by-label/NIXOS_SD";
|
||||
fsType = "ext4";
|
||||
options = [ "noatime" ];
|
||||
};
|
||||
|
||||
swapDevices = [ ];
|
||||
|
||||
nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux";
|
||||
hardware.enableRedistributableFirmware = true;
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
# Edit this configuration file to define what should be installed on
|
||||
# edit this configuration file to define what should be installed on
|
||||
# your system. Help is available in the configuration.nix(5) man page, on
|
||||
# https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
|
||||
|
||||
{ config, lib, pkgs, self, paths, keys, ... }:
|
||||
{ config, lib, pkgs, paths, self, keys, ... }:
|
||||
|
||||
{
|
||||
# NAS Mounting
|
||||
hoardingcow-mount.enable = true;
|
||||
|
||||
# Flakesss
|
||||
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||
nix.settings.experimental-features = [ "nix-command" "flakes" "flake-self-attrs" ];
|
||||
nix.settings.trusted-users = [ "root" "gortium" ];
|
||||
|
||||
# Garbage collection
|
||||
nix.gc = {
|
||||
automatic = true;
|
||||
dates = "daily"; # You can also use "daily" or a cron-like spec
|
||||
options = "--delete-older-than 7d"; # Keep only 7 days of unreferenced data
|
||||
options = "--delete-older-than 30d";
|
||||
};
|
||||
|
||||
nix.settings = {
|
||||
@@ -29,6 +29,20 @@
|
||||
boot.loader.systemd-boot.enable = true;
|
||||
boot.loader.efi.canTouchEfiVariables = false;
|
||||
|
||||
# 1. Force the kernel to ignore BIOS resource locks
|
||||
boot.kernelParams = [
|
||||
"acpi_enforce_resources=lax"
|
||||
"nct6775.force_id=0xd120" # This forces the driver to ignore BIOS locks for NCT6116
|
||||
"transparent_hugepage=always" # because mucho ram
|
||||
];
|
||||
# 2. Load the specific drivers found by sensors-detect
|
||||
boot.kernelModules = [ "nct6775" "lm96163" ];
|
||||
# 3. Force the nct6775 driver to recognize the chip if it's stubborn
|
||||
boot.extraModprobeConfig = ''
|
||||
options nct6775 force_id=0xd280
|
||||
'';
|
||||
|
||||
boot.blacklistedKernelModules = [ "eeepc_wmi" ];
|
||||
networking.hostName = "lazyworkhorse"; # Define your hostname.
|
||||
# Pick only one of the below networking options.
|
||||
# networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
|
||||
@@ -56,31 +70,14 @@
|
||||
LC_CTYPE = "en_CA.UTF-8";
|
||||
};
|
||||
|
||||
# Private host ssh key
|
||||
age = {
|
||||
identityPaths = paths.identities;
|
||||
secrets = {
|
||||
lazyworkhorse_host_ssh_key = {
|
||||
file = "${self}/secrets/lazyworkhorse_host_ssh_key.age";
|
||||
owner = "root";
|
||||
group = "root";
|
||||
mode = "0600";
|
||||
path = "/etc/ssh/ssh_host_ed25519_key";
|
||||
};
|
||||
};
|
||||
programs.zsh = {
|
||||
enable = true;
|
||||
autosuggestions.enable = true;
|
||||
syntaxHighlighting.enable = true;
|
||||
enableCompletion = true;
|
||||
|
||||
setOptions = [ "HIST_IGNORE_ALL_DUPS" "SHARE_HISTORY" ];
|
||||
};
|
||||
|
||||
# Public host ssh key
|
||||
environment.etc."ssh/ssh_host_ed25519_key.pub".text = keys.hosts.lazyworkhorse.main;
|
||||
|
||||
# Prevent sshd from generating new keys and use this one
|
||||
services.openssh.hostKeys = [
|
||||
{
|
||||
path = "/etc/ssh/ssh_host_ed25519_key";
|
||||
type = "ed25519";
|
||||
}
|
||||
];
|
||||
|
||||
# Configure network proxy if necessary
|
||||
# networking.proxy.default = "http://user:password@proxy:port/";
|
||||
# networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
|
||||
@@ -108,6 +105,7 @@
|
||||
pulse.enable = true;
|
||||
};
|
||||
|
||||
# Nix Helper cli tool
|
||||
environment.sessionVariables = {
|
||||
NH_FLAKE = paths.flake;
|
||||
};
|
||||
@@ -118,18 +116,29 @@
|
||||
# nvim please
|
||||
environment.variables.EDITOR = "nvim";
|
||||
|
||||
# programs.firefox.enable = true;
|
||||
|
||||
# List packages installed in system profile.
|
||||
# You can use https://Search.nixos.org/ to find more packages (and options).
|
||||
environment.systemPackages = with pkgs; [
|
||||
agenix
|
||||
neovim
|
||||
docker-compose
|
||||
wget
|
||||
age
|
||||
agenix
|
||||
git
|
||||
nh
|
||||
lm_sensors
|
||||
rocmPackages.rocminfo
|
||||
rocmPackages.rocm-smi
|
||||
nvtopPackages.amd
|
||||
clinfo
|
||||
ncurses
|
||||
kitty.terminfo
|
||||
nodejs_22
|
||||
uv
|
||||
openclaw
|
||||
(python3.withPackages (ps: with ps; [
|
||||
openai-whisper
|
||||
]))
|
||||
];
|
||||
|
||||
# Some programs need SUID wrappers, can be configured further or are
|
||||
@@ -142,10 +151,155 @@
|
||||
|
||||
# List services that you want to enable:
|
||||
|
||||
# Enable the OpenSSH daemon.
|
||||
# Enable the OpenSSH daemon
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
settings.PermitRootLogin = "no";
|
||||
ports = [ 2424 ];
|
||||
settings = {
|
||||
PasswordAuthentication = false;
|
||||
KbdInteractiveAuthentication = false;
|
||||
PermitRootLogin = "prohibit-password";
|
||||
};
|
||||
hostKeys = [
|
||||
{
|
||||
path = "/etc/ssh/ssh_host_ed25519_key";
|
||||
type = "ed25519";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.dockerStacks = {
|
||||
versioncontrol = {
|
||||
path = self + "/assets/compose/versioncontrol";
|
||||
ports = [ 2222 ];
|
||||
};
|
||||
|
||||
network = {
|
||||
path = self + "/assets/compose/network";
|
||||
envFile = config.age.secrets.containers_env.path;
|
||||
ports = [ 80 443 ];
|
||||
};
|
||||
|
||||
passwordmanager = {
|
||||
path = self + "/assets/compose/passwordmanager";
|
||||
};
|
||||
|
||||
ai = {
|
||||
path = self + "/assets/compose/ai";
|
||||
envFile = config.age.secrets.containers_env.path;
|
||||
};
|
||||
|
||||
cloudstorage = {
|
||||
path = self + "/assets/compose/cloudstorage";
|
||||
envFile = config.age.secrets.containers_env.path;
|
||||
};
|
||||
|
||||
homeautomation = {
|
||||
path = self + "/assets/compose/homeautomation";
|
||||
envFile = config.age.secrets.containers_env.path;
|
||||
};
|
||||
|
||||
authentification = {
|
||||
path = self + "/assets/compose/authentification";
|
||||
};
|
||||
|
||||
backup = {
|
||||
path = self + "/assets/compose/backup";
|
||||
envFile = config.age.secrets.containers_env.path;
|
||||
};
|
||||
|
||||
coms = {
|
||||
path = self + "/assets/compose/coms";
|
||||
envFile = config.age.secrets.containers_env.path;
|
||||
};
|
||||
|
||||
finance = {
|
||||
path = self + "/assets/compose/finance";
|
||||
};
|
||||
|
||||
homepage = {
|
||||
path = self + "/assets/compose/homepage";
|
||||
};
|
||||
|
||||
# tak = {
|
||||
# path = self + "/assets/compose/tak";
|
||||
# };
|
||||
};
|
||||
|
||||
services.opencode = {
|
||||
enable = true;
|
||||
port = 4099;
|
||||
ollamaUrl = "http://127.0.0.1:11434/v1";
|
||||
};
|
||||
|
||||
# Private host ssh key managed by agenix
|
||||
age = {
|
||||
identityPaths = paths.identities;
|
||||
secrets = {
|
||||
containers_env = {
|
||||
file = ../../secrets/containers.env.age;
|
||||
path = "/run/secrets/containers.env";
|
||||
owner = "root";
|
||||
group = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
lazyworkhorse_host_ssh_key = {
|
||||
file = ../../secrets/lazyworkhorse_host_ssh_key.age;
|
||||
owner = "root";
|
||||
group = "root";
|
||||
mode = "0600";
|
||||
path = "/etc/ssh/ssh_host_ed25519_key";
|
||||
};
|
||||
ai_ssh_key = {
|
||||
file = ../../secrets/ai_ssh_key.age;
|
||||
owner = "root";
|
||||
group = "root";
|
||||
mode = "0600";
|
||||
path = "/home/ai-worker/.ssh/ai_ssh_key";
|
||||
};
|
||||
openclaw_gateway_token = {
|
||||
file = ../../secrets/openclaw_gateway_token.age;
|
||||
owner = "root";
|
||||
group = "ai-worker";
|
||||
mode = "0440";
|
||||
path = "/run/secrets/openclaw_gateway_token";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# OpenClaw Node service (host-side execution for Docker gateway)
|
||||
services.openclaw-node = {
|
||||
enable = true;
|
||||
user = "ai-worker";
|
||||
gatewayHost = "127.0.0.1";
|
||||
gatewayPort = 18789;
|
||||
gatewayTokenFile = "/run/secrets/openclaw_gateway_token";
|
||||
displayName = "lazyworkhorse-host";
|
||||
};
|
||||
|
||||
# Public host ssh key (kept in sync with the private one)
|
||||
environment.etc."ssh/ssh_host_ed25519_key.pub".text =
|
||||
"${keys.hosts.lazyworkhorse.main}";
|
||||
|
||||
services.fstrim.enable = true;
|
||||
|
||||
services.zfs.autoSnapshot.enable = true;
|
||||
services.zfs.autoScrub.enable = true;
|
||||
|
||||
# Mi50 config
|
||||
hardware.graphics = {
|
||||
enable = true;
|
||||
enable32Bit = true; # Useful for some compatibility layers
|
||||
extraPackages = with pkgs; [
|
||||
rocmPackages.clr.icd # OpenCL/HIP runtime
|
||||
];
|
||||
};
|
||||
nixpkgs.config.rocmTargets = [ "gfx906" ];
|
||||
environment.variables = {
|
||||
# This "tricks" ROCm into supporting the MI50 if using newer versions
|
||||
HSA_OVERRIDE_GFX_VERSION = "9.0.6";
|
||||
# Ensures the system sees both GPUs
|
||||
HIP_VISIBLE_DEVICES = "0,1";
|
||||
};
|
||||
|
||||
# Open ports in the firewall.
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
github = "";
|
||||
gitea = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN9tKezYidZglWBRI9/2I/cBGUUHj2dHY8rHXppYmf7F";
|
||||
};
|
||||
|
||||
ai-worker = {
|
||||
main = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAXeGtPPcsP2IYRQNvII41NVWhJsarEk8c4qxs/a5sXf";
|
||||
};
|
||||
};
|
||||
|
||||
hosts = {
|
||||
@@ -12,6 +16,7 @@
|
||||
main = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBmPv4JssvhHGIx85UwFxDSrL5anR4eXB/cd9V2i9wdW";
|
||||
github = "";
|
||||
gitea = "";
|
||||
bootstrap = "age1r796v2uldtspawyh863pks74sd2pwcan8j4e4pjzsvkmr3vjja9qpz5ste";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{ pkgs, lib, config, ... }: {
|
||||
imports =
|
||||
[
|
||||
# ./home
|
||||
./nixos
|
||||
];
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{ pkgs, lib, config, ... }: {
|
||||
imports =
|
||||
[
|
||||
./graphical-desktop.nix
|
||||
];
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{ pkgs, lib, config, ... }: {
|
||||
imports =
|
||||
[
|
||||
./bundles
|
||||
# ./programs
|
||||
./services
|
||||
./filesystem
|
||||
];
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{ pkgs, lib, config, ... }: {
|
||||
imports =
|
||||
[
|
||||
./hoardingcow-mount.nix
|
||||
];
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{ pkgs, lib, config, ... }: {
|
||||
|
||||
options = {
|
||||
hoardingcow-mount.enable = lib.mkEnableOption "enable hoardingcow acces";
|
||||
hoardingcow-mount.enable = lib.mkEnableOption "enable hoardingcow access";
|
||||
};
|
||||
config = lib.mkIf config.hoardingcow-mount.enable {
|
||||
fileSystems."/mnt/HoardingCow_docker_data" = {
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{ pkgs, lib, config, ... }: {
|
||||
imports =
|
||||
[
|
||||
./systemd
|
||||
];
|
||||
}
|
||||
57
modules/nixos/services/docker_manager.nix
Normal file
57
modules/nixos/services/docker_manager.nix
Normal file
@@ -0,0 +1,57 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
{
|
||||
options.services.dockerStacks = mkOption {
|
||||
type = types.attrsOf (types.submodule {
|
||||
options = {
|
||||
path = mkOption { type = types.str; };
|
||||
envFile = mkOption { type = types.nullOr types.path; default = null; };
|
||||
ports = mkOption { type = types.listOf types.int; default = [ ]; };
|
||||
# New option to pass raw systemd serviceConfig
|
||||
serviceConfig = mkOption {
|
||||
type = types.attrs;
|
||||
default = { };
|
||||
description = "Extra systemd serviceConfig options for this stack.";
|
||||
};
|
||||
};
|
||||
});
|
||||
default = { };
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.docker.enable = true;
|
||||
virtualisation.docker.daemon.settings.dns = [ "1.1.1.1" "8.8.8.8" ];
|
||||
|
||||
networking.firewall.allowedTCPPorts = flatten (mapAttrsToList (name: value: value.ports) config.services.dockerStacks);
|
||||
|
||||
systemd.services = mapAttrs' (name: value: nameValuePair "${name}_stack" {
|
||||
description = "Docker Compose stack: ${name}";
|
||||
|
||||
# Forces systemd to restart when the files change
|
||||
reloadTriggers = [
|
||||
"${builtins.hashFile "sha256" (toString value.path + "/compose.yml")}"
|
||||
] ++ (lib.optional (value.envFile != null) "${value.envFile}");
|
||||
|
||||
after = [ "network.target" "docker.service" "docker.socket" "agenix.service" ];
|
||||
wants = [ "docker.socket" "agenix.service" ];
|
||||
requires = [ "docker.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
path = with pkgs; [ git docker docker-compose bash ];
|
||||
|
||||
# We merge the base config with the custom 'serviceConfig' from the submodule
|
||||
serviceConfig = recursiveUpdate {
|
||||
Type = "oneshot";
|
||||
WorkingDirectory = value.path;
|
||||
User = "root";
|
||||
ExecStartPre = "${pkgs.bash}/bin/bash -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done'";
|
||||
ExecStart = "${pkgs.docker-compose}/bin/docker-compose up -d --remove-orphans";
|
||||
ExecStop = "${pkgs.docker-compose}/bin/docker-compose down";
|
||||
RemainAfterExit = true;
|
||||
EnvironmentFile = mkIf (value.envFile != null) [ value.envFile ];
|
||||
} value.serviceConfig;
|
||||
}) config.services.dockerStacks;
|
||||
};
|
||||
}
|
||||
37
modules/nixos/services/fancontrol.nix
Normal file
37
modules/nixos/services/fancontrol.nix
Normal file
@@ -0,0 +1,37 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.systemd-fancon;
|
||||
in
|
||||
{
|
||||
options.services.systemd-fancon = {
|
||||
enable = mkEnableOption "systemd-fancon service for fan control";
|
||||
config = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Configuration for systemd-fancon.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages = with pkgs; [
|
||||
systemd-fancon
|
||||
lm_sensors
|
||||
];
|
||||
|
||||
boot.kernelModules = [ "amdgpu" ];
|
||||
|
||||
systemd.services.systemd-fancon = {
|
||||
description = "systemd-fancon service";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.systemd-fancon}/bin/systemd-fancon -c ${cfg.configFile}";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
configFile = pkgs.writeText "systemd-fancon.conf" cfg.config;
|
||||
};
|
||||
};
|
||||
}
|
||||
45
modules/nixos/services/ollama_init_custom_models.nix
Normal file
45
modules/nixos/services/ollama_init_custom_models.nix
Normal file
@@ -0,0 +1,45 @@
|
||||
{ pkgs, ... }: {
|
||||
systemd.services.init-ollama-model = {
|
||||
description = "Initialize LLM models with extra context in Ollama Docker";
|
||||
after = [ "docker-ollama.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
script = ''
|
||||
# Wait for Ollama
|
||||
while ! ${pkgs.curl}/bin/curl -s http://localhost:11434/api/tags > /dev/null; do
|
||||
sleep 2
|
||||
done
|
||||
|
||||
create_model_if_missing() {
|
||||
local model_name=$1
|
||||
local base_model=$2
|
||||
if ! ${pkgs.docker}/bin/docker exec ollama ollama list | grep -q "$model_name"; then
|
||||
echo "$model_name not found, creating from $base_model..."
|
||||
${pkgs.docker}/bin/docker exec ollama sh -c "cat <<EOF > /root/.ollama/$model_name.modelfile
|
||||
FROM $base_model
|
||||
PARAMETER num_ctx 131072
|
||||
PARAMETER num_predict 4096
|
||||
PARAMETER num_keep 1024
|
||||
PARAMETER repeat_penalty 1.1
|
||||
PARAMETER top_k 40
|
||||
PARAMETER stop \"[INST]\"
|
||||
PARAMETER stop \"[/INST]\"
|
||||
PARAMETER stop \"</s>\"
|
||||
EOF"
|
||||
${pkgs.docker}/bin/docker exec ollama ollama create "$model_name" -f "/root/.ollama/$model_name.modelfile"
|
||||
else
|
||||
echo "$model_name already exists, skipping."
|
||||
fi
|
||||
}
|
||||
|
||||
# Create Nemotron
|
||||
create_model_if_missing "nemotron-3-nano:30b-128k" "nemotron-3-nano:30b"
|
||||
|
||||
# Create Devstral
|
||||
create_model_if_missing "devstral-small-2:24b-128k" "devstral-small-2:24b"
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
145
modules/nixos/services/open_code_server.nix
Normal file
145
modules/nixos/services/open_code_server.nix
Normal file
@@ -0,0 +1,145 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.opencode;
|
||||
in {
|
||||
options.services.opencode = {
|
||||
enable = lib.mkEnableOption "OpenCode AI Service";
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 4099;
|
||||
};
|
||||
ollamaUrl = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "http://127.0.0.1:11434/v1";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
programs.nix-ld.enable = true;
|
||||
|
||||
environment.etc."opencode/opencode.json".text = builtins.toJSON {
|
||||
"$schema" = "https://opencode.ai/config.json";
|
||||
"model" = "nemotron-3-nano-llama_cpp";
|
||||
"mcp" = {
|
||||
"context7" = {
|
||||
"type" = "remote";
|
||||
"url" = "https://mcp.context7.com/mcp";
|
||||
};
|
||||
"duckduckgo" = {
|
||||
"type" = "local";
|
||||
"command" = [ "uvx" "duckduckgo-mcp-server" ];
|
||||
"environment" = {
|
||||
"PATH" = "/run/current-system/sw/bin:/home/gortium/.nix-profile/bin";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
"provider" = {
|
||||
"llamacpp" = {
|
||||
"name" = "Llama.cpp (Local MI50)";
|
||||
"npm" = "@ai-sdk/openai-compatible";
|
||||
"options" = {
|
||||
"baseURL" = "http://localhost:8300/v1";
|
||||
"apiKey" = "not-needed";
|
||||
"maxTokens" = 80000;
|
||||
};
|
||||
"models" = {
|
||||
"devstral-2-small-llama_cpp" = {
|
||||
"name" = "Devstral 2 small 24B Q8 (llama.cpp)";
|
||||
"tools" = true;
|
||||
"reasoning" = false;
|
||||
};
|
||||
"nemotron-3-nano-llama_cpp" = {
|
||||
"name" = "Nemotron 3 nano 30B Q8 (llama.cpp)";
|
||||
"tools" = true;
|
||||
"reasoning" = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
"ollama" = {
|
||||
"name" = "Ollama (Local)";
|
||||
"npm" = "@ai-sdk/openai-compatible";
|
||||
"options" = {
|
||||
"baseURL" = cfg.ollamaUrl;
|
||||
"headers" = { "Content-Type" = "application/json"; };
|
||||
};
|
||||
"models" = {
|
||||
"devstral-small-2:24b-128k" = {
|
||||
"name" = "Mistral Devstral Small 2 (Ollama)";
|
||||
"tools" = true;
|
||||
"reasoning" = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.opencode-gsd-install = {
|
||||
description = "Install Get Shit Done OpenCode Components";
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = with pkgs; [
|
||||
nodejs
|
||||
git
|
||||
coreutils
|
||||
bash
|
||||
];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "gortium";
|
||||
RemainAfterExit = true;
|
||||
Environment = [
|
||||
"HOME=/home/gortium"
|
||||
"SHELL=${pkgs.bash}/bin/bash"
|
||||
"PATH=${lib.makeBinPath [ pkgs.nodejs pkgs.git pkgs.bash pkgs.coreutils ]}"
|
||||
];
|
||||
};
|
||||
script = ''
|
||||
# Check if the GSD directory exists
|
||||
if [ ! -d "/home/gortium/.config/opencode/gsd" ]; then
|
||||
echo "GSD not found. Installing..."
|
||||
${pkgs.nodejs}/bin/npx -y github:dbachelder/get-shit-done-opencode --global --force
|
||||
else
|
||||
echo "GSD already installed. Skipping auto-reinstall."
|
||||
echo "To force update, run: sudo systemctl restart opencode-gsd-install.service"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.opencode = {
|
||||
description = "OpenCode AI Coding Agent Server";
|
||||
after = [ "network.target" "ai_stack.service" "opencode-gsd-install.service" ];
|
||||
requires = [ "ai_stack.service" "opencode-gsd-install.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
path = with pkgs; [
|
||||
bash
|
||||
coreutils
|
||||
nodejs
|
||||
git
|
||||
nix
|
||||
ripgrep
|
||||
fd
|
||||
];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = "gortium";
|
||||
WorkingDirectory = "/home/gortium/infra";
|
||||
ExecStart = "${pkgs.nodejs}/bin/npx -y opencode-ai serve --hostname 0.0.0.0 --port ${toString cfg.port}";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
|
||||
environment = {
|
||||
OLLAMA_BASE_URL = "http://127.0.0.1:11434";
|
||||
OPENCODE_CONFIG = "/etc/opencode/opencode.json";
|
||||
HOME = "/home/gortium";
|
||||
NODE_PATH = "${pkgs.nodejs}/lib/node_modules";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ cfg.port ];
|
||||
};
|
||||
}
|
||||
64
modules/nixos/services/openclaw_node.nix
Normal file
64
modules/nixos/services/openclaw_node.nix
Normal file
@@ -0,0 +1,64 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.openclaw-node;
|
||||
openclawPkg = pkgs.openclaw;
|
||||
in {
|
||||
options.services.openclaw-node = {
|
||||
enable = lib.mkEnableOption "OpenClaw Node service";
|
||||
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "ai-worker";
|
||||
description = "User to run the OpenClaw headless node as.";
|
||||
};
|
||||
|
||||
gatewayHost = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "127.0.0.1";
|
||||
description = "Gateway host (IP or hostname).";
|
||||
};
|
||||
|
||||
gatewayPort = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 18789;
|
||||
description = "Gateway WebSocket port.";
|
||||
};
|
||||
|
||||
gatewayTokenFile = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "";
|
||||
description = "Path to file containing the gateway auth token.";
|
||||
};
|
||||
|
||||
displayName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "lazyworkhorse-host";
|
||||
description = "Display name for this node (shown in pairing).";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
systemd.services.openclaw-node = {
|
||||
description = "OpenClaw Headless Node Service";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "exec";
|
||||
User = cfg.user;
|
||||
Group = cfg.user;
|
||||
WorkingDirectory = "/home/${cfg.user}";
|
||||
ExecStart = ''
|
||||
${pkgs.bash}/bin/bash -c 'export OPENCLAW_GATEWAY_TOKEN=$(cat ${cfg.gatewayTokenFile}) && exec ${openclawPkg}/bin/openclaw node run --host ${cfg.gatewayHost} --port ${toString cfg.gatewayPort} --display-name "${cfg.displayName}"'
|
||||
'';
|
||||
Restart = "always";
|
||||
RestartSec = 5;
|
||||
};
|
||||
|
||||
environment = {
|
||||
NODE_ENV = "production";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib; let
|
||||
cfg = config.services.podman;
|
||||
in {
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
{ pkgs, lib, config, self, keys, paths, ... }: {
|
||||
imports =
|
||||
[
|
||||
./network.nix
|
||||
./passwordmanager.nix
|
||||
./versioncontrol.nix
|
||||
];
|
||||
|
||||
virtualisation.docker = {
|
||||
enable = true;
|
||||
daemon.settings = {
|
||||
"dns" = [ "1.1.1.1" "8.8.8.8" ];
|
||||
};
|
||||
};
|
||||
|
||||
age = {
|
||||
identityPaths = paths.identities;
|
||||
secrets = {
|
||||
containers_env = {
|
||||
file = self + "/secrets/containers.env.age";
|
||||
path = "/run/secrets/containers.env";
|
||||
owner = "root";
|
||||
group = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
{ config, pkgs, self, ... }:
|
||||
|
||||
let
|
||||
network_compose_dir = pkgs.stdenv.mkDerivation {
|
||||
name = "network_compose_dir";
|
||||
src = self + "/assets/compose/network";
|
||||
dontUnpack = true;
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp -r $src/* $out/
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
||||
|
||||
systemd.services.network_stack = {
|
||||
description = "Traefik + DDNS updater via Docker Compose";
|
||||
after = [ "network.target" "docker.service" ];
|
||||
requires = [ "network.target" "docker.service" ];
|
||||
serviceConfig = {
|
||||
WorkingDirectory = "${network_compose_dir}";
|
||||
|
||||
EnvironmentFile = config.age.secrets.containers_env.path;
|
||||
|
||||
# Stop left over container by the same name
|
||||
ExecStartPre = "${pkgs.bash}/bin/bash -c '${pkgs.docker-compose}/bin/docker-compose down || true'";
|
||||
|
||||
# Start the services using Docker Compose
|
||||
ExecStart = "${pkgs.docker-compose}/bin/docker-compose up -d";
|
||||
|
||||
# Stop and remove containers on shutdown
|
||||
ExecStop = "${pkgs.docker-compose}/bin/docker-compose down";
|
||||
|
||||
RemainAfterExit = true;
|
||||
TimeoutStartSec = 0;
|
||||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
{ config, pkgs, self, ... }:
|
||||
|
||||
let
|
||||
passwordmanager_compose_dir = pkgs.stdenv.mkDerivation {
|
||||
name = "passwordmanager_compose_dir";
|
||||
src = self + "/assets/compose/passwordmanager";
|
||||
dontUnpack = true;
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp -r $src/* $out/
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
systemd.services.passwordmanager_stack = {
|
||||
description = "Bitwarden via Docker Compose";
|
||||
after = [ "network-online.target" "docker.service" ];
|
||||
wants = [ "network-online.target" "docker.service" ];
|
||||
serviceConfig = {
|
||||
WorkingDirectory = "${passwordmanager_compose_dir}";
|
||||
|
||||
# Stop left over container by the same name
|
||||
ExecStartPre = "${pkgs.bash}/bin/bash -c '${pkgs.docker-compose}/bin/docker-compose down || true'";
|
||||
|
||||
# Démarrer les conteneurs avec Docker Compose
|
||||
ExecStart = "${pkgs.docker-compose}/bin/docker-compose up -d";
|
||||
|
||||
# Arrêter et supprimer les conteneurs à l’arrêt
|
||||
ExecStop = "${pkgs.docker-compose}/bin/docker-compose down";
|
||||
|
||||
RemainAfterExit = true;
|
||||
TimeoutStartSec = 0;
|
||||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
{ config, pkgs, self, ... }:
|
||||
|
||||
let
|
||||
versioncontrol_compose_dir = pkgs.stdenv.mkDerivation {
|
||||
name = "versioncontrol_compose_dir";
|
||||
src = self + "/assets/compose/versioncontrol";
|
||||
dontUnpack = true;
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp -r $src/* $out/
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
networking.firewall.allowedTCPPorts = [ 2222 ];
|
||||
|
||||
systemd.services.versioncontrol_stack = {
|
||||
description = "Gitea via Docker Compose";
|
||||
after = [ "network-online.target" "docker.service" ];
|
||||
wants = [ "network-online.target" "docker.service" ];
|
||||
serviceConfig = {
|
||||
WorkingDirectory = "${versioncontrol_compose_dir}";
|
||||
|
||||
# Stop left over container by the same name
|
||||
ExecStartPre = "${pkgs.bash}/bin/bash -c '${pkgs.docker-compose}/bin/docker-compose down || true'";
|
||||
|
||||
# Démarrer les conteneurs avec Docker Compose
|
||||
ExecStart = "${pkgs.docker-compose}/bin/docker-compose up -d";
|
||||
|
||||
# Arrêter et supprimer les conteneurs à l’arrêt
|
||||
ExecStop = "${pkgs.docker-compose}/bin/docker-compose down";
|
||||
|
||||
RemainAfterExit = true;
|
||||
TimeoutStartSec = 0;
|
||||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
}
|
||||
BIN
secrets/ai_ssh_key.age
Normal file
BIN
secrets/ai_ssh_key.age
Normal file
Binary file not shown.
@@ -1,7 +1,34 @@
|
||||
age-encryption.org/v1
|
||||
-> ssh-ed25519 GhMD8A 9Tjo08Hbj3S+nCdLUylZoUK6meXtuHq9F/qwSJZBYho
|
||||
iu2MmQ2VHm+QEvqGjkEy02V0cNRanAyhrA8Xu7UWRFk
|
||||
-> ssh-ed25519 eB5ENw 8UTi2pmZML1Zyh9zCfEx4JqJhQ1vM/jZCEhrkuc1Hh4
|
||||
et6FoN8E4tgo2DXlt/KTGLRsByJFyDu2oHA/Js/pIB8
|
||||
--- dmEv5Fz1iUJ3W93lFtkHgtknfQGQNkMqglJZ+3e1qM8
|
||||
<EFBFBD>U<EFBFBD><EFBFBD><EFBFBD>.<2E><>#C\<11><><EFBFBD> <09>V<EFBFBD><56>-<2D><><EFBFBD><EFBFBD><1F>tp<74>wnީ<><02><>n<EFBFBD><<3C>E<EFBFBD><45><EFBFBD>~<7E><>bX<62><02><><EFBFBD>_<EFBFBD><5F><07><><EFBFBD><EFBFBD>u<EFBFBD>l?<3F>),s<>Ec7<63><37><EFBFBD><EFBFBD>v<EFBFBD>;<3B>A<EFBFBD>U<EFBFBD>-<2D>I<EFBFBD>7Y<37>-<2D>3g[<5B>jh~<7E>/<2F>
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IEdoTUQ4QSBOL29w
|
||||
eGk1N2xxTHJtaUEvWWZmbkh1bk11Tjk3anNnMDB1cCtPYUMzdTNJCkdhQ08vblNG
|
||||
UlV1K2xVTGZVTzFWYXAzcjZaMWs0RTFWdStKSmlSTURvK1EKLT4gLC1zKU8zVkgt
|
||||
Z3JlYXNlIFUiXFcpS302IHByVn5jOy0gRDMKQjV3SHpDWUIybGFyQUg3ZlR0R2hV
|
||||
eWM3SFlCVW5mdlpBVUF3a0xpNlZCeGNUd1oxTTlkc1RkTXdZS0lFTmN3Ci0tLSA3
|
||||
VlBqM1VLWllZc0JnOTMvUFRjMU13OTdzMmhsdGJubkk5eGpERVVLYUk4Cnzh5UbU
|
||||
FlgqpM8jkJ6XlsaIDCw/G3D6uJ/GRJW4gIekuhAUxpZJrc8eOA8ZuHfGrBbH3acV
|
||||
tVafX5F0Kr2oOblqZ6gduZOUS52KmWH8stiBJM+e5ZZ7zRQVE4PJUKUPCzi+WdcH
|
||||
zr295T//FOdicrYHdsjfziKEHzBtUCFiATW05+O2zMjYjO6cPzePcCzPWinwiID6
|
||||
V+f6ngfkkQaj3wBGkzaieQJzRcdSwky21aVhGCCX/bvqx61iW2d5QAKxGbtQ2RcG
|
||||
X1okr+xunAM94nzDMv46vyN97KxY7cZd4pAaOxoICc2Tfhtw6F+iS6QkQh1odJzO
|
||||
7ZH+sSQCvndG+8z9shXGiHalASF5tdguM+JlEvAGljcaiAUtsQWxr9CoWiEkC6c6
|
||||
NCaECSYO8Il+SXBQnSZSGJSNDhuPYCYrsjXGSAONFixuyeslAkq9x2WUaUS4H063
|
||||
1QvRF7XO2tBPtgCLsSjdiGp0h+ImUaGdu6fDR7zrDsGsaAFCSFeH/rGNNXRQ2vP2
|
||||
CSfPfDDCqpUSCn0WuA30BtaPLxGmZT6OjFevKzYMNDmdeq9ia/q8K0hmjLUBdN3k
|
||||
tdYWbwoaf4gYbUWxSleD768b0Jgxss9Vod+sFQ+NYRksdGIeyND+aQIc312XehfA
|
||||
qHFBS8nlj7eUF5bdvCYQ64z741mH4cNlGxyjPBH1x8FHnEOocJXYt1l2AZSRJmJA
|
||||
c3z0QGXyuCbsrLBXWK1EKa/Juo4PGGsEVoLRhwJAQy9+i1JN0yrfRvSPyzvD4px6
|
||||
wRPzlZ80MQdb2lv84WS/zcOEZmZzlLntszTRRdIfAsuaavP2Rquh4rEXABYeTZwp
|
||||
5dem79s8bdW2nFsGMNz1OQKQwocyjYu1jJMHu6Gp7Ngdl1xyW7xfg0dezE1c0cIh
|
||||
xt1aLER9YJp4n5to5cOH16l3mjDHnAvABx38xE9loNL3399J/evw7LxpTYQ4v2Xv
|
||||
x8xnDHcqJ+deFSwyuUnMS5DkUeYuHmUl0Q2WYcfY+ibCmcgCb2ObTtuN1/ZxNYrL
|
||||
OKrnmfuSvBgyuIOj5e6uWW0+Zs8dHKXu2TgV8WignxOhl5zQgCpCBlqVfO0t+NCu
|
||||
Gi26hU/fhGWQ/1oQa3VkpGsypZbJpgQvfWxfcGHP/MMhnl01zzlP8/aexSY3pAxf
|
||||
fz9v0IVh6xxtu3zbiiVzUsXbfG7t+xY98jMphf4AS2mWva3GWVmhhu0lS3J3P+go
|
||||
YEEP4rOFHeU0Y1/6kLydTXvz4jMH0H92XQIzshd7vzQnEJPUPAzqRmw3LKYGgCI+
|
||||
wZEnxJ6ckqTkGBFnxTpy9LLllwmnz2Ky87nY3XAmqxlhb2Ap1XFAlfgszmGjc+Il
|
||||
KkIgoWQHTUm6QM9ta++oUTIDneOvxGd0zZsqoEhiC/7E01BNNZ6E58TeJU3fDlA3
|
||||
mX6n05XjwPRpgXZfayPoAgBlZc2H4KeiynxwNZ/dWu7qz7L6Ppk6Nvtly8giTbFx
|
||||
CA+tto7vq+D+CAEJ4bgyq4BCH4GL4APrhPcWp98Mko1WCiRTIKgkZxQCYvlg/LZq
|
||||
LNhMacP9T1qTvNC+yR1NEMiegE3APzk6CkDpVaO9+5f/sqifNPINCMothenI9ePw
|
||||
zjQLI3Mo1m73bkomytUZ7i1VstP5sEZ5LF72Sq7BpR3oQ3Gp0CAN9w==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
|
||||
Binary file not shown.
11
secrets/openclaw_gateway_token.age
Normal file
11
secrets/openclaw_gateway_token.age
Normal file
@@ -0,0 +1,11 @@
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IEdoTUQ4QSBCWEpO
|
||||
cG9yNnFpcHFqTkNzTngxU1MxN0NYK0hrZFhUTjVORWFrK3JNd2tZCmtMTGpwQk1E
|
||||
WlUwL3N6SGRWblpnNEkrWkkyU2hQMkRIK0M3R0pOVEREV3MKLT4gY2osLWdyZWFz
|
||||
ZSBacSozVVQgUCAxRS1OQSAuKXxDPCoKbStWNW1BZjBZQzNDaTlDbU5EZkxsRWxM
|
||||
cXJ3dDU1RDNpOXRlV0tzdEp2NUo3S1lhRG5Md0RHTGlJdkFSYmt5YQo4R1hiQWRG
|
||||
V2VxekJKZwotLS0geG1XSi9VbkhXZHQzcEFVS3hKNzVueXFLa2xnZTc3Q2tJTVZ5
|
||||
eXJabWk5Ywp6bJCP3s0xxzjE+eTR+cv7ZUnkoliT/n7uIprq1BTn/LIRLkUTUqs3
|
||||
NiDwrXcoq4/QKd0Dt+8ap3vFAuusjGxRlnYMaRrZie2AGtTV8U7Q7durm9o2K+/4
|
||||
QzRQ/MtumIQm
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
@@ -1,8 +1,14 @@
|
||||
let
|
||||
keys = import ../lib/keys.nix;
|
||||
authorizedKeys = [ keys.users.gortium.main keys.hosts.lazyworkhorse.main ];
|
||||
authorizedKeys = [
|
||||
keys.users.gortium.main
|
||||
keys.hosts.lazyworkhorse.main
|
||||
keys.hosts.lazyworkhorse.bootstrap
|
||||
];
|
||||
in
|
||||
{
|
||||
"containers.env.age".publicKeys = authorizedKeys;
|
||||
"lazyworkhorse_host_ssh_key.age".publicKeys = authorizedKeys;
|
||||
"n8n_ssh_key.age".publicKeys = authorizedKeys;
|
||||
"openclaw_gateway_token.age".publicKeys = authorizedKeys;
|
||||
}
|
||||
|
||||
14
users/ai-worker.nix
Normal file
14
users/ai-worker.nix
Normal file
@@ -0,0 +1,14 @@
|
||||
{ pkgs, inputs, config, keys, ... }: {
|
||||
users.users.ai-worker = {
|
||||
isSystemUser = true;
|
||||
group = "ai-worker";
|
||||
home = "/home/ai-worker";
|
||||
createHome = true;
|
||||
extraGroups = [ "docker" ];
|
||||
shell = pkgs.bashInteractive;
|
||||
openssh.authorizedKeys.keys = [
|
||||
keys.users.ai-worker.main
|
||||
];
|
||||
};
|
||||
users.groups.ai-worker = {};
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
{ pkgs, inputs, config, keys, ... }: {
|
||||
users.users.gortium = {
|
||||
isNormalUser = true;
|
||||
extraGroups = [ "wheel" "docker" ]; # Enable ‘sudo’ for the user.
|
||||
extraGroups = [ "wheel" "docker" "video" "render"];
|
||||
|
||||
packages = with pkgs; [
|
||||
tree
|
||||
btop
|
||||
nh
|
||||
];
|
||||
shell = pkgs.zsh;
|
||||
openssh.authorizedKeys.keys = [
|
||||
keys.users.gortium.main
|
||||
];
|
||||
};
|
||||
programs.zsh.enable = true;
|
||||
security.sudo.extraRules = [
|
||||
{
|
||||
users = [ "gortium" ];
|
||||
|
||||
Reference in New Issue
Block a user