14 KiB
14 KiB
Core-API Refactoring Plan
Date: 2025-11-14 Goal: Restructure Core-API into controller-based architecture and add Infrastructure Management API
Current Structure
src/
├── api/
│ └── v1/
│ ├── chat.py # AI chat completions
│ ├── models.py # Model listing
│ ├── conversations.py # Conversation memory
│ └── schemas.py # Pydantic schemas
├── web_scraper/
│ ├── router.py # Webscraper endpoints
│ ├── service.py
│ └── schemas.py
├── models/
│ ├── ollama_client.py # Ollama HTTP client
│ └── embeddings.py
├── memory/ # Memory tier system
├── config.py # Global settings
└── main.py # FastAPI app
Target Structure
src/
├── controllers/ # NEW: Controller-based routing
│ ├── __init__.py
│ ├── base.py # Base controller class
│ ├── ai_controller.py # AI Orchestrator (chat, models, conversations)
│ ├── tools_controller.py # Utility tools (webscraper, etc.)
│ ├── health_controller.py # Health & monitoring
│ └── infrastructure_controller.py # Infrastructure automation
├── clients/ # NEW: External API clients
│ ├── __init__.py
│ ├── portainer_client.py # Portainer API
│ ├── npm_client.py # Nginx Proxy Manager API
│ └── kuma_client.py # Uptime Kuma Socket.IO API
├── api/v1/ # Keep existing for backward compat
├── web_scraper/ # Keep as-is for now
├── models/ # Keep as-is
├── memory/ # Keep as-is
├── config.py # Enhanced with infrastructure settings
└── main.py # Updated routing
Implementation Phases
Phase 1: Infrastructure Setup ✅ COMPLETE
- Research API authentication methods
- Add infrastructure settings to config.py
- Create credentials.py for sensitive data (gitignored)
- Create credentials.example.py as template
- Update .gitignore to exclude credentials.py
- Update config.py to import from credentials module
- Create /controllers directory structure
- Create /clients directory structure
- Create base controller class
Phase 2: API Clients ✅ COMPLETE (Portainer & NPM)
- Implement Portainer API client (access token auth)
- Implement NPM API client (JWT with refresh)
- Add token storage/refresh mechanisms
- Implement Uptime Kuma Socket.IO client (DEFERRED - WebSocket complexity)
Phase 3: Infrastructure Controller ✅ COMPLETE
- GET /infrastructure/health - Check connectivity ✅ TESTED
- GET /infrastructure/services - List all services ✅ TESTED
- GET /infrastructure/services/{name} - Get service details ✅ TESTED
- GET /infrastructure/ports - List allocated ports ✅ IMPLEMENTED & TESTED
- GET /infrastructure/domains - List configured domains ✅ TESTED
- Integrate with main.py routing ✅ TESTED
- Fix Pydantic validation issues (status field type conversion)
- POST /infrastructure/services - Deploy new service ✅ TESTED
- PUT /infrastructure/services/{name} - Update service ✅ TESTED
- DELETE /infrastructure/services/{name} - Remove service ✅ TESTED
- POST /infrastructure/proxy - Create NPM proxy host with optional SSL ✅ IMPLEMENTED
- POST /infrastructure/monitoring/add - Auto-add Kuma monitor (DEFERRED - Socket.IO complexity)
Phase 4: Refactor Existing Controllers ✅ COMPLETE
- Move AI endpoints to ai_controller.py ✅ COMPLETE
- Move webscraper to tools_controller.py ✅ COMPLETE
- Move health check to health_controller.py ✅ COMPLETE
- Update main.py imports and routing ✅ COMPLETE
- Test all refactored endpoints ✅ ALL WORKING
Phase 5: Testing & Documentation ✅ COMPLETE
- Test all refactored endpoints ✅ ALL WORKING
- Update API documentation (OpenAPI spec auto-generated and validated)
- [~] Create CLI wrapper scripts (SKIPPED - LLMs consume OpenAPI spec directly)
- [~] Remove old shell scripts (DEFERRED - not blocking)
Phase 6: Infrastructure Improvements 📋 FUTURE
- Consolidate Docker network topology into single
docker-dataplanenetwork- Currently each stack has its own network (172.22.0.x, 172.25.0.x, 172.20.0.x, etc.)
- Error-prone and unnecessarily complex
- Single shared network simplifies inter-service communication
- Reduces subnet conflicts and improves service discovery
- Update all compose files to use:
networks: [docker-dataplane] - Create network once:
docker network create docker-dataplane
Progress Notes (2025-11-14)
Session 1: Foundation & Read Endpoints
Completed:
- Created controller and client architecture
- Implemented Portainer client with full CRUD operations for stacks
- Implemented NPM client with JWT refresh and proxy/certificate management
- Built infrastructure controller with 5 read/list endpoints
- Added infrastructure settings to config.py
Files Created:
src/controllers/__init__.pysrc/controllers/base.pysrc/controllers/infrastructure_controller.pysrc/clients/__init__.pysrc/clients/portainer_client.pysrc/clients/npm_client.pyREFACTORING_PLAN.md(this file)
Session 2: Credentials & Testing (2025-11-14 Evening)
Completed:
- Created credentials management system (credentials.py gitignored, credentials.example.py committed)
- Updated config.py to import from credentials module with fallback
- Generated Portainer API token programmatically via API
- Integrated infrastructure controller into main.py
- Fixed Pydantic validation bug (status field int→str conversion)
- Tested all read endpoints with live Portainer/NPM infrastructure
- Verified 8 stacks detected, domains with SSL status working
Test Results:
- ✅ GET /infrastructure/health - Portainer connected, NPM accessible
- ✅ GET /infrastructure/services - Returns 8 active stacks
- ✅ GET /infrastructure/services/{name} - Service lookup working
- ✅ GET /infrastructure/domains - Returns proxy hosts with SSL status
- ✅ NPM health check fixed (now accepts 2xx/3xx status codes and follows redirects)
Session 3: Write Endpoints (2025-11-14 Evening)
Completed:
- Created request/response models for write operations (DeployServiceRequest, UpdateServiceRequest, CreateProxyRequest, OperationResult)
- Implemented POST /infrastructure/services - Deploy new service from compose YAML
- Implemented PUT /infrastructure/services/{name} - Update existing service configuration
- Implemented DELETE /infrastructure/services/{name} - Remove service and stack
- Implemented POST /infrastructure/proxy - Create NPM proxy host with optional SSL certificate
- Updated main.py API description with write endpoints
- Tested all service management endpoints (POST/PUT/DELETE) with live Portainer instance
Test Results:
- ✅ POST /infrastructure/services - Created test-nginx stack (ID: 30)
- ✅ PUT /infrastructure/services/test-nginx - Updated compose with environment variable
- ✅ DELETE /infrastructure/services/test-nginx - Removed stack successfully
- ✅ POST /infrastructure/proxy - Implemented (not tested to avoid production interference)
Next Steps:
Refactor existing AI/tools/health endpoints into separate controllers (Phase 4)✅ DONE (2025-11-14)Fix NPM health check to handle redirects✅ DONE (2025-11-14)Implement port allocation detection logic✅ DONE (2025-11-14)Create CLI wrappers for common operations⊘ SKIPPED (LLMs use OpenAPI)- (OPTIONAL) Consolidate Docker networks into
docker-dataplane(Phase 6)
Session 4: NPM Health Check & Port Detection (2025-11-14 Afternoon)
Completed:
- Fixed NPM health check to handle redirects properly
- Updated
npm_client.pyto accept 2xx/3xx status codes as healthy - Enabled explicit redirect following in httpx client
- Verified fix with live NPM instance (now shows 9 proxy hosts)
- Implemented comprehensive port detection in
GET /infrastructure/portsendpoint - Added
get_containers()andget_container()methods to PortainerClient - Enhanced PortInfo model with internal/external hostname and IP fields
- Implemented domain mapping from NPM proxy hosts to services
- Added deduplication logic for port entries (Docker returns duplicates per bind address)
Port Detection Features:
- Scans all running containers across all Portainer endpoints
- Extracts internal port, host port, and protocol for each container
- Maps container names to service names via Docker Compose labels
- Retrieves internal Docker hostnames and IP addresses per network
- Cross-references NPM proxy hosts to identify external domains
- Returns 22 unique port mappings with complete metadata
Technical Details:
NPM Health Check:
- Issue: NPM's
/apiendpoint returns 302 redirect, old code only accepted 200 - Solution: Accept
200 <= status_code < 400as healthy response - Result: NPM health check now returns
trueand proxy hosts are enumerated correctly
Port Detection:
- Queries Portainer Docker API for container list and port mappings
- Extracts NetworkSettings for internal IPs and hostnames
- Builds port→domain map from NPM proxy hosts configuration
- Matches services to external domains using multiple strategies:
- By container name + port
- By internal IP + port
- By host address + host port (localhost, 127.0.0.1, server IP)
- Deduplicates based on (port, container_name, protocol) tuple
- Example output: Nextcloud port 80 → internal IP 172.25.0.3 → external domain cloud.schweitz.net
Session 5: Controller Architecture Refactoring (2025-11-14 Evening)
Completed:
- Created
ai_controller.pyconsolidating chat, models, and conversations endpoints - Created
tools_controller.pyfor web scraper functionality - Created
health_controller.pyfor service health and info endpoints - Updated
main.pyto use new controller-based architecture - Removed legacy router imports and inline endpoint definitions
- Tested all refactored endpoints - 16 endpoints working correctly
Architecture Changes:
- All endpoints now follow consistent controller pattern inheriting from
BaseController - Controllers use
create_router()method for FastAPI router configuration - Clean separation of concerns:
ai_controller.py- AI orchestration and conversation memory (7 endpoints)tools_controller.py- Utility tools like web scraper (1 endpoint)health_controller.py- Service status and info (2 endpoints)infrastructure_controller.py- Infrastructure management (6 endpoints)
- Simplified
main.pyfrom 220 lines to 152 lines - Backward compatible - all existing endpoints work identically
Test Results:
- ✅ GET / - Service information
- ✅ GET /health - Health check with Ollama status
- ✅ GET /v1/models - Model listing
- ✅ POST /v1/chat/completions - Chat completions
- ✅ GET /v1/conversations/{id} - Conversation history
- ✅ GET /infrastructure/health - Infrastructure health
- ✅ POST /web-scraper/scrape - Web scraping
- ✅ OpenAPI spec generation - 16 endpoints documented
API Authentication Strategy
Portainer
- Method: Access Token (X-API-Key header)
- Setup: Manual creation in UI, store in config/env
- Duration: Long-lived
- Storage: Environment variable
PORTAINER_API_KEY
Nginx Proxy Manager
- Method: JWT Bearer Token
- Setup: Login via
/api/tokenswith credentials - Duration: ~24 hours
- Strategy: Auto-refresh with stored credentials
- Storage:
NPM_EMAILandNPM_PASSWORDin env
Uptime Kuma
- Method: Socket.IO WebSocket
- Setup: Login via Socket.IO
loginevent - Duration: Session-based
- Strategy: Maintain persistent connection or re-auth per request
- Storage:
KUMA_USERNAMEandKUMA_PASSWORDin env
Configuration Changes
Credentials Management Strategy
Use credentials.py for sensitive data (added to .gitignore):
- Keeps secrets out of version control
- Easy terminal-based management with editor
- Python format for type safety and autocomplete
- Separate from config for security isolation
Implementation:
- Create
src/credentials.pywith credentials (gitignored) - Create
src/credentials.example.pyas template (committed) - Update
config.pyto import from credentials module - Add
credentials.pyto.gitignore
Example src/credentials.py:
"""
Infrastructure credentials (GITIGNORED)
Copy from credentials.example.py and fill in real values
"""
# Portainer
PORTAINER_URL = "http://localhost:8001"
PORTAINER_API_KEY = "ptr_your_actual_token_here"
# Nginx Proxy Manager
NPM_URL = "http://localhost:81"
NPM_EMAIL = "jpmschweitzer@gmail.com"
NPM_PASSWORD = "your_actual_password"
# Uptime Kuma
KUMA_URL = "http://localhost:3001"
KUMA_USERNAME = "admin"
KUMA_PASSWORD = "your_actual_password"
Updated config.py to use credentials:
from src.credentials import (
PORTAINER_URL, PORTAINER_API_KEY,
NPM_URL, NPM_EMAIL, NPM_PASSWORD,
KUMA_URL, KUMA_USERNAME, KUMA_PASSWORD
)
class Settings(BaseSettings):
# Infrastructure Management (from credentials.py)
portainer_url: str = PORTAINER_URL
portainer_api_key: str = PORTAINER_API_KEY
npm_url: str = NPM_URL
npm_email: str = NPM_EMAIL
npm_password: str = NPM_PASSWORD
kuma_url: str = KUMA_URL
kuma_username: str = KUMA_USERNAME
kuma_password: str = KUMA_PASSWORD
Benefits
- Cleaner Code: Separation of concerns, easier to maintain
- Automation: Programmatic service deployment and configuration
- Elimination of Shell Scripts: Replace ad-hoc scripts with proper API
- Service Discovery: Auto-detect running services and configurations
- Self-Managing Homelab: Foundation for autonomous infrastructure
Migration Notes
- Existing
/v1/endpoints remain unchanged for backward compatibility - Web scraper endpoints stay at
/web-scraper/initially - Old shell scripts in
/stacks/will be replaced with CLI wrappers