diff --git a/furt-lua/.env.production b/.env.production similarity index 100% rename from furt-lua/.env.production rename to .env.production diff --git a/.gitignore b/.gitignore index cebfe5d..0edc799 100644 --- a/.gitignore +++ b/.gitignore @@ -1,37 +1,16 @@ # Environment variables (NEVER commit!) .env -# Go build artifacts -*.exe -*.exe~ -*.dll -*.so -*.dylib -furt-gateway -formular2mail-service -sagjan-service -/build/ -/dist/ - -# Go test files -*.test -*.out -coverage.txt -coverage.html - -# Go modules -/vendor/ - # Lua specific *.luac .luarocks/ luarocks.lock -# Furt-lua runtime/build artifacts -furt-lua/bin/ -furt-lua/logs/ -furt-lua/tmp/ -furt-lua/pid/ +# Furt runtime/build artifacts +bin/ +logs/ +tmp/ +pid/ # Issue creation scripts (these create issues, don't version them) scripts/gitea-issues/ @@ -74,8 +53,6 @@ debug.log *.sqlite3 # Configuration files with secrets -config.local.yaml -config.production.yaml -furt-lua/config/local.lua -furt-lua/config/production.lua +config.local.lua +config.production.lua diff --git a/README.md b/README.md index 940c6b9..d27042b 100644 --- a/README.md +++ b/README.md @@ -1,145 +1,160 @@ # Furt API Gateway -**Low-Tech API-Gateway für digitale Souveränität** -*Von Go zu C+Lua - Corporate-freie Technologie-Migration* +**HTTP-Server in Lua für Service-Integration** ## Überblick -Furt ist ein minimalistischer API-Gateway, der verschiedene Services unter einer einheitlichen API vereint. Der Name "Furt" (germanisch für "Durchgang durch Wasser") symbolisiert die Gateway-Funktion: Alle Requests durchqueren die API-Furt um zu den dahinterliegenden Services zu gelangen. +Furt ist ein HTTP-Server der verschiedene Services unter einer API vereint. Aktuell unterstützt es Mail-Versendung über SMTP und bietet eine einfache JSON-API für Web-Integration. -## Technologie-Migration +## Features -🔄 **Strategische Neuausrichtung (Juni 2025):** -- **Von:** Go-basierte Implementation (Corporate-controlled) -- **Zu:** C + Lua Implementation (maximale Souveränität) -- **Grund:** Elimination von Google-Dependencies für echte digitale Unabhängigkeit +- HTTP-Server mit JSON-APIs +- Mail-Versendung über SMTP +- Request-Routing und Authentication +- Health-Check-Endpoints +- Konfigurierbare Rate-Limiting +- Hugo/Website-Integration -## Aktuelle Implementierungen +## Dependencies -### 🆕 furt-lua (Aktiv entwickelt) -**Pure Lua HTTP-Server - Week 1 ✅** -- ✅ HTTP-Server mit lua-socket -- ✅ JSON API-Endpoints -- ✅ Basic Routing und Error-Handling -- ✅ Mail-Service-Grundgerüst -- 🔄 SMTP-Integration (Week 2) +**Erforderlich:** +- `lua` 5.4+ +- `lua-socket` (HTTP-Server) +- `lua-cjson` (JSON-Verarbeitung) +**Installation:** ```bash -cd furt-lua/ -./scripts/start.sh -# Server: http://127.0.0.1:8080 -``` - -### 📦 Go-Implementation (Parallel/Legacy) -- Ursprüngliche Planung in `cmd/`, `internal/` -- Wird durch Lua-Version ersetzt -- Referenz für API-Kompatibilität - -## Philosophie - -- **Technologie-Souveränität**: Nur akademische/unabhängige Technologien -- **Low-Tech-Ansatz**: C + Lua statt Corporate-Runtimes -- **Minimale Dependencies**: < 5 externe Libraries -- **Modulare Architektur**: < 200 Zeilen pro Modul -- **Vollständige Transparenz**: Jede Zeile Code verstehbar -- **Langfristige Stabilität**: 50+ Jahre bewährte Technologien - -## Tech-Stack (Final) - -**Souveräne Technologien:** -- **C** (GCC + musl) - Kern-Performance -- **Lua** (PUC-Rio University) - Business-Logic -- **LMDB** (Howard Chu/Symas) - Datenbank -- **OpenBSD httpd** - Reverse-Proxy (langfristig) - -**Corporate-frei:** Keine Google-, Microsoft-, oder VC-kontrollierten Dependencies - -## Services - -- **formular2mail**: Kontaktformulare zu E-Mail (Week 1 ✅) -- **sagjan**: Selbst-gehostetes Kommentarsystem -- **lengan**: Projektverwaltung -- **budlam**: Kontaktverwaltung -- **Weitere**: Shop, Newsletter, Kalendar, etc. - -## Installation & Entwicklung - -### Quick Start (furt-lua) -```bash -# Dependencies (Arch Linux) +# Arch Linux pacman -S lua lua-socket lua-cjson -# Start Development-Server -cd furt-lua/ -chmod +x scripts/start.sh -./scripts/start.sh - -# Test -curl -X POST http://127.0.0.1:8080/test \ - -H "Content-Type: application/json" \ - -d '{"test":"data"}' +# Ubuntu/Debian +apt install lua5.4 lua-socket lua-cjson ``` -### Testing +## Installation + ```bash -# Automated Tests -cd furt-lua/ -lua tests/test_http.lua +# Repository klonen +git clone +cd furt -# Manual curl Tests -./scripts/test_curl.sh +# Scripts ausführbar machen +chmod +x scripts/*.sh + +# Server starten +./scripts/start.sh ``` -## Roadmap +**Server läuft auf:** http://127.0.0.1:8080 -### Phase 1: Lua-Foundation (4 Wochen) ✅ -- [x] Week 1: HTTP-Server + Mail-Service-Grundgerüst -- [ ] Week 2: SMTP-Integration + API-Key-Auth -- [ ] Week 3: Service-Expansion (Comments) -- [ ] Week 4: Production-Ready (HTTPS, Systemd) +## API-Endpoints -### Phase 2: C-Integration (4-6 Wochen) -- [ ] C-HTTP-Server für Performance -- [ ] C ↔ Lua Bridge -- [ ] Memory-Management + Security-Hardening +### Health Check +```bash +GET /health +→ {"status":"healthy","service":"furt","version":"1.0.0"} +``` -### Phase 3: Infrastructure-Migration (6-12 Monate) -- [ ] OpenBSD-Migration -- [ ] ISPConfig → eigene Scripts -- [ ] Apache → OpenBSD httpd +### Mail senden +```bash +POST /v1/mail/send +Content-Type: application/json -## Dokumentation +{ + "name": "Name", + "email": "sender@example.com", + "message": "Nachricht" +} -**Development:** -- [`devdocs/furt_konzept.md`](devdocs/furt_konzept.md) - Technische Architektur -- [`devdocs/furt_master_strategy.md`](devdocs/furt_master_strategy.md) - 18-24 Monate Roadmap -- [`devdocs/furt_development_process.md`](devdocs/furt_development_process.md) - Development-Guidelines +→ {"success":true,"message":"Mail sent"} +``` -**API:** -- [`furt-lua/README.md`](furt-lua/README.md) - Lua-Implementation Details -- `docs/api/` - API-Dokumentation (in Entwicklung) +## Konfiguration -## Technologie-Rationale +**Environment Variables (.env):** +```bash +FURT_MAIL_HOST=mail.example.com +FURT_MAIL_PORT=587 +FURT_MAIL_USERNAME=user@example.com +FURT_MAIL_PASSWORD=password +FURT_MAIL_TO=empfaenger@example.com +``` -**Warum Lua statt Go?** -- Go = Google-controlled (Module-Proxy, Telemetrie) -- Lua = PUC-Rio University (echte Unabhängigkeit) -- C + Lua = 50+ Jahre bewährt vs. Corporate-Runtime -- Performance: 10x weniger Memory, 5x weniger CPU +**Server-Config (config/server.lua):** +- Port und Host-Einstellungen +- API-Key-Konfiguration +- Rate-Limiting-Parameter -**Teil der Dragons@Work Digital-Sovereignty-Strategie** +## Testing -## Status +**Automatische Tests:** +```bash +lua tests/test_http.lua +``` -🚀 **Week 1 Complete:** Lua HTTP-Server funktional -🔄 **Week 2 Active:** SMTP-Integration + Hugo-Integration -📋 **Week 3+ Planned:** Service-Expansion + C-Migration +**Manuelle Tests:** +```bash +./scripts/test_curl.sh -## Lizenz +# Oder direkt: +curl -X POST http://127.0.0.1:8080/v1/mail/send \ + -H "Content-Type: application/json" \ + -d '{"name":"Test","email":"test@example.com","message":"Test"}' +``` -Apache License 2.0 - Siehe [LICENSE](LICENSE) für Details. +## Deployment ---- +**OpenBSD:** +- rc.d-Script in `deployment/openbsd/` +- Systemd-Integration über Scripts -*Furt steht im Einklang mit den Prinzipien digitaler Souveränität und dem Low-Tech-Ansatz des Dragons@Work-Projekts.* +**Production-Setup:** +```bash +# Environment-Config kopieren +cp .env.example .env.production +# → SMTP-Credentials anpassen + +# Production-Mode starten +export FURT_ENV=production +./scripts/start.sh +``` + +## Projektstruktur + +``` +furt/ +├── src/ # Lua-Source-Code +│ ├── main.lua # HTTP-Server +│ ├── routes/ # API-Endpoints +│ └── smtp.lua # Mail-Integration +├── config/ # Konfiguration +├── scripts/ # Start/Test-Scripts +├── tests/ # Test-Suite +└── deployment/ # System-Integration +``` + +## Hugo-Integration + +**Shortcode-Beispiel:** +```html +
+ + + + +
+``` + +## Development + +**Code-Struktur:** +- Module unter 200 Zeilen +- Funktionen unter 50 Zeilen +- Klare Fehlerbehandlung +- Testbare Komponenten + +**Dependencies minimal halten:** +- Nur lua-socket und lua-cjson +- Keine externen HTTP-Libraries +- Standard-Lua-Funktionen bevorzugen diff --git a/furt-lua/config/server.lua b/config/server.lua similarity index 88% rename from furt-lua/config/server.lua rename to config/server.lua index 0a46a0e..5e0067e 100644 --- a/furt-lua/config/server.lua +++ b/config/server.lua @@ -1,14 +1,14 @@ --- furt-lua/config/server.lua +-- config/server.lua -- Server configuration for Furt Lua HTTP-Server return { -- HTTP Server settings host = "127.0.0.1", port = 8080, - + -- Timeouts (seconds) client_timeout = 10, - + -- CORS Configuration cors = { -- Default allowed origins for development @@ -33,11 +33,11 @@ return { end end)() }, - + -- Logging log_level = "info", log_requests = true, - + -- API-Key-Authentifizierung (PRODUCTION READY) api_keys = { -- Hugo Frontend API-Key (für Website-Formulare) @@ -47,11 +47,11 @@ return { allowed_ips = { "127.0.0.1", -- Localhost "10.0.0.0/8", -- Private network - "192.168.0.0/16", -- Private network + "192.168.0.0/16", -- Private network "172.16.0.0/12" -- Private network } }, - + -- Admin API-Key (für Testing und Management) [os.getenv("ADMIN_API_KEY") or "admin-dev-key-change-in-production"] = { name = "Admin Access", @@ -61,7 +61,7 @@ return { "10.0.0.0/8" -- Internal network } }, - + -- Optional: Monitoring API-Key (nur Health-Checks) [os.getenv("MONITORING_API_KEY") or "monitoring-dev-key"] = { name = "Monitoring Service", @@ -73,16 +73,16 @@ return { } } }, - + -- Mail configuration (for SMTP integration) mail = { - smtp_server = os.getenv("SMTP_HOST") or "mail.dragons-at-work.de", + smtp_server = os.getenv("SMTP_HOST") or "mail.example.org", smtp_port = tonumber(os.getenv("SMTP_PORT")) or 465, use_ssl = true, username = os.getenv("SMTP_USERNAME"), password = os.getenv("SMTP_PASSWORD"), - from_address = os.getenv("SMTP_FROM") or "noreply@dragons-at-work.de", - to_address = os.getenv("SMTP_TO") or "michael@dragons-at-work.de" + from_address = os.getenv("SMTP_FROM") or "noreply@example.org", + to_address = os.getenv("SMTP_TO") or "admin@example.org" } } diff --git a/configs/labels.registry b/configs/labels.registry deleted file mode 100644 index 83d9cd6..0000000 --- a/configs/labels.registry +++ /dev/null @@ -1,58 +0,0 @@ -# Central Label Registry for Furt API Gateway Project -# Format: name:color:context:usage_contexts -# This file is the single source of truth for all labels - -# === CORE WORKFLOW LABELS === -service-request:7057ff:new_service:service_templates,status_updates -enhancement:84b6eb:improvement:all_templates -bug:d73a4a:error:bug_template,status_updates -question:d876e3:discussion:question_template - -# === COMPONENT CATEGORIES === -gateway:0052cc:gateway_core:architecture_template,performance_template,service_templates -performance:fbca04:optimization:performance_template,architecture_template -architecture:d4c5f9:design:architecture_template,gateway -security:28a745:security_review:security_template,architecture_template -configuration:f9d71c:config_management:deployment_template,architecture_template - -# === SERVICE-SPECIFIC LABELS === -service-debug-check-final2:1d76db:service_integration:service_specific -service-clean-test4:1d76db:service_integration:service_specific -service-debug-test:1d76db:service_integration:service_specific -service-formular2mail:1d76db:formular2mail:formular2mail_integration -service-sagjan:1d76db:sagjan:sagjan_integration -service-newsletter:ff6b6b:newsletter:newsletter_integration -service-analytics:1d76db:service_integration:service_specific -service-whatever-you-want:1d76db:service_integration:service_specific -service-completely-absolut-new7:1d76db:service_integration:service_specific -service-completely-absolut-new8:1d76db:service_integration:service_specific -service-completely-absolut-new9:1d76db:service_integration:service_specific -service-completely-absolut-new10:1d76db:service_integration:service_specific -service-completely-absolut-new11:1d76db:service_integration:service_specific - -# === WORKFLOW STATE LABELS === -work-in-progress:fbca04:active:status_updates -needs-review:0e8a16:review:status_updates -blocked:d73a4a:blocked:status_updates -ready-for-deployment:28a745:deploy_ready:status_updates - -# === INTEGRATION LABELS === -hugo-integration:ff7518:frontend:hugo_templates,integration -api-contract:5319e7:api_design:api_templates,service_templates -breaking-change:d73a4a:breaking:api_templates,architecture_template - -# === PRIORITY LABELS === -high-priority:d73a4a:urgent:all_templates -low-priority:0e8a16:nice_to_have:all_templates - -# === META LABELS === -low-tech:6f42c1:low_tech_principle:architecture_template,performance_template,security_template -digital-sovereignty:6f42c1:digital_sovereignty:architecture_template,performance_template,security_template -good-first-issue:7057ff:beginner_friendly:manual_assignment -help-wanted:159818:community_help:manual_assignment - -# === DEPLOYMENT LABELS === -deployment:ff7518:deployment:deployment_template -testing:f9d71c:testing:testing_template,integration - -test-all-templates:ff0000:test:all_templates diff --git a/furt-lua/deployment/openbsd/rc.d-furt b/deployment/openbsd/rc.d-furt similarity index 100% rename from furt-lua/deployment/openbsd/rc.d-furt rename to deployment/openbsd/rc.d-furt diff --git a/devdocs/furt_development_process.md b/devdocs/furt_development_process.md deleted file mode 100644 index 894a640..0000000 --- a/devdocs/furt_development_process.md +++ /dev/null @@ -1,590 +0,0 @@ -# Entwicklungsprozess für Furt API-Gateway - -**Erstellt:** 03.06.2025 -**Letzte Aktualisierung:** 03.06.2025 -**Version:** 1.0 -**Verantwortlich:** Claude / DAW-Team -**Dateipfad:** devdocs/development-process.md - -## Zweck dieses Dokuments - -Dieses Dokument definiert den verbindlichen Prozess für die Entwicklung und Änderung von Code im Rahmen des Furt API-Gateway-Projekts. Es ergänzt die allgemeinen Entwicklungsrichtlinien um API-Gateway-spezifische Patterns und Multi-Service-Koordination. - -Es richtet sich an alle Projektbeteiligten, die am Gateway oder Services entwickeln. - -## Verwandte Dokumente - -Dieses Dokument steht im Zusammenhang mit folgenden anderen Dokumenten: - -- **KONZEPT.md:** Zentrale Referenz und Konzeptdokumentation, devdocs/KONZEPT.md -- **TESTING_GUIDELINES.md:** API-Gateway-spezifische Test-Standards, devdocs/TESTING_GUIDELINES.md -- **ARCHITECTURE.md:** Detaillierte Systemarchitektur, devdocs/ARCHITECTURE.md - -## Änderungshistorie - -| Version | Datum | Änderungen | Autor | -|---------|-------|------------|-------| -| 1.0 | 03.06.2025 | Initiale Version für Furt API-Gateway | Claude / DAW-Team | - -## 1. Grundprinzipien für API-Gateway-Entwicklung - -### 1.1 Service-First-Entwicklung - -Jede Entwicklungsaufgabe muss im Kontext des **Service-Ökosystems** betrachtet werden: - -- **Gateway-Änderungen** betreffen potenziell alle Services -- **Service-Änderungen** können Gateway-Anpassungen erfordern -- **API-Contracts** zwischen Gateway und Services sind kritisch -- **Breaking Changes** erfordern koordinierte Rollouts - -### 1.2 API-Contract-Driven Development - -Bevor Code geschrieben wird, müssen **API-Contracts** definiert werden: - -- **OpenAPI-Spezifikation** für neue Endpunkte -- **Service-Interface-Definition** für neue Services -- **Authentication/Authorization-Requirements** für alle APIs -- **Error-Response-Standards** konsistent halten - -### 1.3 Security-First-Pattern - -Sicherheit wird bei **jeder** Änderung mitgedacht: - -- **API-Key-Berechtigungen** bei neuen Endpunkten definieren -- **Input-Validation** für alle eingehenden Requests -- **Rate-Limiting** für neue Services konfigurieren -- **IP-Restrictions** wo angemessen anwenden - -## 2. Verbindlicher Entwicklungsprozess für Furt - -### 2.1 Vorbereitung - -1. **Requirements-Analyse mit Service-Impact** - - Welche Services sind betroffen? - - Welche Gateway-Komponenten benötigen Änderungen? - - Sind Breaking Changes erforderlich? - - Welche API-Contracts müssen definiert/aktualisiert werden? - -2. **Explizite Anfrage nach relevanten Dateien** - - Gateway-Dateien: `internal/gateway/`, `configs/gateway.yaml` - - Service-Dateien: `internal/services/[service]/`, `configs/services/` - - API-Dokumentation: `docs/api/`, OpenAPI-Specs - - Integration-Tests: `tests/integration/` - -3. **Analyse der Service-Integration-Pattern** - - Bestehende Service-Registry-Einträge - - Routing-Patterns und Middleware-Chain - - Authentifizierungs-Flows - - Health-Check-Mechanismen - -### 2.2 Design und Planung - -1. **API-First-Design dokumentieren** - - OpenAPI-Spezifikation **vor** der Implementierung schreiben - - Request/Response-Schemas definieren - - HTTP-Status-Codes und Error-Handling spezifizieren - - Authentication-Requirements dokumentieren - -2. **Service-Integration-Strategy festlegen** - - Wie wird der Service im Gateway registriert? - - Welche Health-Check-URL wird verwendet? - - Welche Timeout-Werte sind angemessen? - - Braucht der Service Admin-UI-Integration? - -3. **Breaking-Change-Impact analysieren** - - Betrifft die Änderung bestehende API-Contracts? - - Sind koordinierte Service-Updates erforderlich? - - Müssen Client-Integrationen (Hugo-Shortcodes) angepasst werden? - - Ist eine API-Versionierung (v1 → v2) notwendig? - -4. **Configuration-Strategy bestimmen** - - Welche neuen Config-Parameter werden benötigt? - - Sind Environment-Variable für Secrets erforderlich? - - Wie wird die Config zwischen Gateway und Service koordiniert? - -### 2.3 Implementierung - -1. **Multi-Component-Development-Order** - - **Für neue Services:** - ``` - 1. Service-Struktur scaffolden (service-generator.sh) - 2. Service-Logik implementieren (Standalone-Mode) - 3. Gateway-Integration hinzufügen - 4. Integration-Tests schreiben - 5. Deployment-Scripts anpassen - ``` - - **Für Gateway-Änderungen:** - ``` - 1. Gateway-Kern-Logik implementieren - 2. Middleware/Auth-Anpassungen - 3. Service-Integration testen - 4. Health-Check-Aggregation - 5. Admin-Interface-Updates - ``` - - **Für API-Änderungen:** - ``` - 1. OpenAPI-Spec aktualisieren - 2. Gateway-Routing anpassen - 3. Service-Endpunkt implementieren - 4. Input-Validation hinzufügen - 5. Integration-Tests erweitern - ``` - -2. **Koordinierte Entwicklung bei Service-Updates** - - **Gateway-kompatible Änderungen zuerst** (additive APIs) - - **Service-Tests** mit Gateway-Integration - - **Backward-Compatibility** während Übergangsphase - - **Coordinated Deployment** bei Breaking Changes - -3. **Configuration-Management während Entwicklung** - - **Development-Configs** in `configs/[component].dev.yaml` - - **Environment-Variable-Mapping** dokumentieren - - **Config-Validation** bei Service-Start implementieren - - **Hot-Reload** für Development (wo möglich) - -### 2.4 Testing-Integration - -1. **Multi-Layer-Testing-Strategy** - - **Unit-Tests:** Für Gateway- und Service-Komponenten isoliert - - **Integration-Tests:** Gateway ↔ Service-Kommunikation - - **API-Tests:** End-to-End API-Contract-Validation - - **Load-Tests:** Gateway-Performance mit mehreren Services - -2. **Test-Coordination-Pattern** - ```go - // Beispiel: Service-Integration-Test - func TestGatewayServiceIntegration(t *testing.T) { - // 1. Start Test-Service - service := startTestService(t, serviceConfig) - defer service.Close() - - // 2. Configure Gateway with Test-Service - gateway := startTestGateway(t, gatewayConfigWithService(service.URL)) - defer gateway.Close() - - // 3. Test Gateway → Service communication - testServiceAPIThroughGateway(t, gateway, service) - } - ``` - -3. **Breaking-Change-Test-Strategy** - - **Backward-Compatibility-Tests** bei API-Änderungen - - **Version-Migration-Tests** bei Breaking Changes - - **Client-Integration-Tests** (Hugo-Shortcode-Kompatibilität) - -## 3. Service-spezifische Entwicklungs-Pattern - -### 3.1 Neue Service-Entwicklung - -1. **Service-Scaffolding** - ```bash - ./scripts/service-generator.sh newsletter - # Erstellt komplette Service-Struktur - ``` - -2. **Service-Interface-Implementation** - ```go - // Jeder Service muss dieses Interface implementieren - type Service interface { - // Gateway-Integration - HandleRequest(w http.ResponseWriter, r *http.Request) - HealthCheck() HealthStatus - - // Standalone-Mode - HandleWithAuth(w http.ResponseWriter, r *http.Request) - - // Lifecycle - Start(ctx context.Context, config ServiceConfig) error - Stop(ctx context.Context) error - - // Service-Metadata - GetServiceInfo() ServiceInfo - } - ``` - -3. **Service-Registration im Gateway** - ```yaml - # configs/gateway.yaml - services: - newsletter: - enabled: true - path_prefix: "/v1/newsletter" - upstream: "http://127.0.0.1:8083" - health_check: "/health" - timeout: 15s - auth_required: true - rate_limit: - requests_per_minute: 60 - ``` - -### 3.2 Service-API-Design-Standards - -1. **Einheitliche Request-Patterns** - ```go - // Standard Request-Wrapper - type APIRequest struct { - RequestID string `json:"request_id,omitempty"` - Data interface{} `json:"data"` - Meta map[string]interface{} `json:"meta,omitempty"` - } - - // Standard Response-Wrapper - type APIResponse struct { - Success bool `json:"success"` - Data interface{} `json:"data,omitempty"` - Error *APIError `json:"error,omitempty"` - Meta map[string]interface{} `json:"meta,omitempty"` - RequestID string `json:"request_id,omitempty"` - } - ``` - -2. **Konsistente Error-Handling** - ```go - // Standard Error-Format - type APIError struct { - Code string `json:"code"` - Message string `json:"message"` - Details string `json:"details,omitempty"` - } - - // Standard Error-Codes - const ( - ErrInvalidInput = "INVALID_INPUT" - ErrUnauthorized = "UNAUTHORIZED" - ErrServiceUnavailable = "SERVICE_UNAVAILABLE" - ErrRateLimitExceeded = "RATE_LIMIT_EXCEEDED" - ) - ``` - -3. **Health-Check-Standards** - ```go - // Standard Health-Response - type HealthStatus struct { - Status string `json:"status"` - Version string `json:"version"` - Uptime time.Duration `json:"uptime"` - Checks map[string]string `json:"checks"` - Timestamp time.Time `json:"timestamp"` - } - - // Status-Werte - const ( - HealthStatusHealthy = "healthy" - HealthStatusDegraded = "degraded" - HealthStatusUnhealthy = "unhealthy" - ) - ``` - -### 3.3 Gateway-Integration-Pattern - -1. **Service-Discovery-Integration** - ```go - // Gateway registriert Services automatisch - func (g *Gateway) RegisterService(name string, config ServiceConfig) error { - service := &ServiceProxy{ - Name: name, - PathPrefix: config.PathPrefix, - Upstream: config.Upstream, - HealthCheck: config.HealthCheck, - // ... - } - - g.services[name] = service - g.updateRouting() - return nil - } - ``` - -2. **Request-Middleware-Chain** - ```go - // Standard Middleware-Order für alle Services - func (g *Gateway) buildMiddlewareChain(serviceName string) []Middleware { - return []Middleware{ - LoggingMiddleware, - AuthenticationMiddleware, - RateLimitingMiddleware(serviceName), - ValidationMiddleware, - ProxyMiddleware(serviceName), - } - } - ``` - -## 4. Configuration-Management-Pattern - -### 4.1 Hierarchische Konfiguration - -```go -// Config-Loading-Reihenfolge -func LoadConfig(serviceName string) (*Config, error) { - config := &Config{} - - // 1. Default-Values - config.ApplyDefaults() - - // 2. Base-Config-File - config.LoadFromFile("configs/" + serviceName + ".yaml") - - // 3. Environment-specific - if env := os.Getenv("ENVIRONMENT"); env != "" { - config.LoadFromFile("configs/" + serviceName + "." + env + ".yaml") - } - - // 4. Environment-Variables - config.LoadFromEnv() - - // 5. Command-Line-Flags - config.LoadFromFlags() - - return config.Validate() -} -``` - -### 4.2 Service-Gateway-Config-Coordination - -```yaml -# Gateway-Config für Service -services: - formular2mail: - config_sync: true - config_endpoint: "/config" - config_push_on_change: true - -# Service erhält Config vom Gateway -``` - -### 4.3 Secrets-Management - -```go -// Secrets werden nie in Config-Files gespeichert -type ServiceConfig struct { - // Public config - Port string `yaml:"port"` - LogLevel string `yaml:"log_level"` - - // Secrets via Environment - APIKey string `env:"SERVICE_API_KEY"` - DBPassword string `env:"DB_PASSWORD"` -} -``` - -## 5. Security-First-Development-Pattern - -### 5.1 Authentication-Integration - -```go -// Jeder Service-Endpunkt bekommt Auth-Context -type AuthContext struct { - APIKey string - Permissions []string - ClientIP string - UserAgent string - RequestID string -} - -func (s *Service) HandleRequest(w http.ResponseWriter, r *http.Request) { - // Auth-Context wird vom Gateway gesetzt - authCtx := r.Context().Value("auth").(*AuthContext) - - // Service-spezifische Permission-Checks - if !authCtx.HasPermission("service:" + s.name + ":write") { - http.Error(w, "Forbidden", http.StatusForbidden) - return - } - - // ... Service-Logik -} -``` - -### 5.2 Input-Validation-Standards - -```go -// Standard Validation-Middleware -func ValidationMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // 1. Content-Type validation - if !isValidContentType(r.Header.Get("Content-Type")) { - http.Error(w, "Invalid Content-Type", http.StatusBadRequest) - return - } - - // 2. Content-Length limits - if r.ContentLength > MaxRequestSize { - http.Error(w, "Request too large", http.StatusRequestEntityTooLarge) - return - } - - // 3. Request-Body validation (service-specific) - if err := validateRequestBody(r); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - next.ServeHTTP(w, r) - }) -} -``` - -### 5.3 Rate-Limiting-Strategy - -```go -// Service-spezifische Rate-Limits -type RateLimitConfig struct { - RequestsPerMinute int `yaml:"requests_per_minute"` - BurstSize int `yaml:"burst_size"` - PerAPIKey bool `yaml:"per_api_key"` - PerIP bool `yaml:"per_ip"` - Whitelist []string `yaml:"whitelist"` -} - -// Gateway-Level Rate-Limiting -func (g *Gateway) GetRateLimit(serviceName, apiKey, clientIP string) *RateLimit { - config := g.getRateLimitConfig(serviceName) - key := buildRateLimitKey(config, apiKey, clientIP) - return g.rateLimiter.GetLimit(key) -} -``` - -## 6. Breaking-Change-Management - -### 6.1 API-Versionierung-Strategy - -```go -// URL-basierte Versionierung -// /v1/mail/send → formular2mail v1 -// /v2/mail/send → formular2mail v2 - -// Gateway-Routing für mehrere Versionen -services: - formular2mail-v1: - path_prefix: "/v1/mail" - upstream: "http://127.0.0.1:8081" - formular2mail-v2: - path_prefix: "/v2/mail" - upstream: "http://127.0.0.1:8084" -``` - -### 6.2 Backward-Compatibility-Pattern - -```go -// Service unterstützt mehrere API-Versionen -func (s *FormularService) HandleRequest(w http.ResponseWriter, r *http.Request) { - version := extractAPIVersion(r.URL.Path) // v1, v2 - - switch version { - case "v1": - s.handleV1Request(w, r) - case "v2": - s.handleV2Request(w, r) - default: - s.handleLatestRequest(w, r) - } -} -``` - -### 6.3 Migration-Pattern - -```go -// Coordinated Service-Migration -type MigrationPlan struct { - FromVersion string - ToVersion string - Steps []MigrationStep -} - -type MigrationStep struct { - Name string - Component string // "gateway" | "service" - Action string // "deploy" | "config" | "test" - Rollback func() error -} -``` - -## 7. Checkliste für API-Gateway-Entwicklung - -### 7.1 Vor Implementierungsbeginn - -- [ ] **Service-Impact analysiert:** Welche Services sind betroffen? -- [ ] **API-Contract definiert:** OpenAPI-Spec erstellt/aktualisiert? -- [ ] **Gateway-Integration geplant:** Routing, Auth, Rate-Limiting? -- [ ] **Config-Strategy festgelegt:** Neue Parameter dokumentiert? -- [ ] **Breaking-Change-Assessment:** Versionierung erforderlich? -- [ ] **Security-Requirements:** Auth, Validation, Rate-Limiting? -- [ ] **Test-Strategy:** Unit, Integration, API-Tests geplant? - -### 7.2 Während der Implementierung - -- [ ] **Service-Interface-Compliance:** Standard-Interface implementiert? -- [ ] **Error-Handling-Consistency:** Standard-Error-Format verwendet? -- [ ] **Health-Check-Integration:** Standardisierte Health-Endpoint? -- [ ] **Logging-Standards:** Strukturierte Logs mit Request-IDs? -- [ ] **Config-Validation:** Startup-Config-Checks implementiert? -- [ ] **Auth-Integration:** Gateway-Auth-Context respektiert? -- [ ] **Documentation-Update:** API-Docs und Service-Docs aktualisiert? - -### 7.3 Nach Implementierungsabschluss - -- [ ] **Integration-Tests:** Gateway ↔ Service-Tests bestehen? -- [ ] **API-Contract-Tests:** OpenAPI-Compliance validiert? -- [ ] **Performance-Tests:** Load-Tests mit Gateway durchgeführt? -- [ ] **Security-Tests:** Auth, Input-Validation, Rate-Limiting getestet? -- [ ] **Deployment-Scripts:** Service-Deployment automatisiert? -- [ ] **Monitoring-Integration:** Health-Checks und Metriken? -- [ ] **Documentation-Complete:** Service-Integration dokumentiert? - -## 8. Troubleshooting-Pattern - -### 8.1 Service-Integration-Debugging - -```go -// Standard Debug-Endpoints für Services -func (s *Service) RegisterDebugEndpoints() { - http.HandleFunc("/debug/config", s.debugConfig) - http.HandleFunc("/debug/health-detail", s.debugHealthDetail) - http.HandleFunc("/debug/metrics", s.debugMetrics) - http.HandleFunc("/debug/auth", s.debugAuth) -} - -// Gateway Debug-Endpoints -func (g *Gateway) RegisterDebugEndpoints() { - http.HandleFunc("/debug/services", g.debugServices) - http.HandleFunc("/debug/routing", g.debugRouting) - http.HandleFunc("/debug/auth-keys", g.debugAuthKeys) -} -``` - -### 8.2 Request-Tracing - -```go -// Request-ID-Propagation durch alle Services -func (g *Gateway) addRequestID(r *http.Request) *http.Request { - requestID := r.Header.Get("X-Request-ID") - if requestID == "" { - requestID = generateRequestID() - } - - // Für Service-Weiterleitung - r.Header.Set("X-Request-ID", requestID) - - // Für Logging - ctx := context.WithValue(r.Context(), "request_id", requestID) - return r.WithContext(ctx) -} -``` - -## 9. Zusammenfassung: API-Gateway-Entwicklungs-Goldene-Regeln - -1. **API-Contract-First:** OpenAPI-Spec vor Code-Implementation -2. **Service-Integration-Aware:** Jede Änderung auf Service-Impact prüfen -3. **Security-by-Default:** Auth, Validation, Rate-Limiting bei jedem Endpunkt -4. **Configuration-Hierarchie:** Defaults → Environment → Service-specific -5. **Multi-Layer-Testing:** Unit → Integration → API → E2E -6. **Breaking-Change-Coordination:** Versionierung und Migration planen -7. **Health-Check-Integration:** Jeder Service braucht standardisierten Health-Endpoint -8. **Request-Tracing:** Request-IDs durch gesamte Pipeline propagieren - ---- - -**Wichtiger Hinweis:** Diese Entwicklungs-Pattern sind spezifisch für das Furt API-Gateway-System optimiert und sollten bei jeder Entwicklungsaufgabe konsultiert werden, um konsistente und gut integrierte Services zu gewährleisten. \ No newline at end of file diff --git a/devdocs/furt_konzept.md b/devdocs/furt_konzept.md deleted file mode 100644 index 7e41e1e..0000000 --- a/devdocs/furt_konzept.md +++ /dev/null @@ -1,673 +0,0 @@ -# Furt: API-Gateway im Einklang mit digitaler Souveränität - -**Erstellt:** 03. Juni 2025 -**Letzte Aktualisierung:** 17. Juni 2025 -**Version:** 2.0 -**Verantwortlich:** DAW-Team -**Dateipfad:** devdocs/KONZEPT.md - -## Zweck dieses Dokuments - -Dieses Dokument definiert das überarbeitete Konzept für Furt basierend auf der Dragons@Work Tech-Reference und dem Prinzip der maximalen technologischen Souveränität. Das System wird von Go auf C + Lua umgestellt, um Corporate-Capture zu vermeiden. - -## Grund für die Überarbeitung - -Die ursprüngliche Go-basierte Architektur widerspricht den Prinzipien digitaler Souveränität: -- **Go ist Google-controlled** (Proxy, Module-Summen, Telemetrie) -- **Corporate-Abhängigkeit** durch Go-Ökosystem -- **Nicht im Einklang** mit der Tech-Reference - -## 1. Neue Projektvision und Philosophie - -Furt (germanisch für "Durchgang durch Wasser") ist ein **selbst-gehostetes API-Gateway-System** basierend auf **C + Lua**, das vollständig im Einklang mit den Prinzipien digitaler Souveränität steht: - -### Kernprinzipien -- **C als Basis** - Bewährt seit 50+ Jahren, strukturell uncapturable -- **Lua für Logik** - PUC-Rio University, echte akademische Unabhängigkeit -- **Minimale Dependencies** - Nur GCC + musl, keine Corporate-Libraries -- **Maximale Transparenz** - Jede Zeile Code verstehbar und kontrollierbar -- **Ultra-Low-Tech** - Ressourcenschonend, lange Lebensdauer - -### Abgrenzung zu Corporate-Lösungen -- **Keine Cloud-Dependencies** -- **Keine Corporate-Runtimes** (Go, Node.js, etc.) -- **Keine Black-Box-Components** -- **Keine Vendor-Lock-ins** - -## 2. Technische Architektur (C + Lua) - -### 2.1 Technology-Stack - -**Core:** -- **C (GCC + musl)** - Gateway-Kern, HTTP-Server, System-Interface -- **Lua 5.4** - Business-Logic, Konfiguration, Service-Scripts -- **LMDB** - Datenbank (Howard Chu/Symas, keine VC-Dependencies) -- **OpenBSD httpd** - Als Reverse-Proxy (langfristig, Apache-Migration) - -**Konfiguration:** -- **Lua-Scripts** statt YAML (native, programmierbar, validierbar) -- **LMDB-Storage** für Konfiguration und State -- **Environment-Variables** nur für Secrets - -**Services:** -- **C-Module** mit Lua-Scripten für Business-Logic -- **Minimale HTTP-Implementation** in C -- **Lua-basierte** Request-Handler - -### 2.2 Projektstruktur - -``` -furt/ -├── src/ -│ ├── core/ # C Core-Implementation -│ │ ├── http_server.c # Minimal HTTP-Server -│ │ ├── router.c # Request-Routing -│ │ ├── proxy.c # Service-Proxy -│ │ ├── auth.c # Authentifizierung -│ │ └── main.c # Entry Point -│ ├── lua/ # Lua-Scripts -│ │ ├── gateway/ # Gateway-Logic -│ │ │ ├── config.lua # Konfigurationsmanagement -│ │ │ ├── middleware.lua # Middleware-Chain -│ │ │ └── services.lua # Service-Registry -│ │ └── services/ # Service-Logic -│ │ ├── formular2mail.lua # Mail-Service -│ │ └── sagjan.lua # Comment-Service -│ └── include/ # C Header-Files -│ ├── furt.h # Main Header -│ ├── http.h # HTTP Definitions -│ └── lua_bridge.h # C ↔ Lua Interface -├── config/ # Konfiguration -│ ├── gateway.lua # Gateway-Konfiguration -│ └── services/ # Service-Configs -│ ├── formular2mail.lua -│ └── sagjan.lua -├── build/ # Build-System -│ ├── Makefile # Haupt-Makefile -│ ├── config.mk # Build-Konfiguration -│ └── deps/ # Dependencies (Lua, LMDB) -├── docs/ # Dokumentation -│ ├── installation.md # Installation -│ ├── configuration.md # Konfiguration -│ └── development.md # Entwicklung -├── scripts/ # Build & Deployment -│ ├── build.sh # Build-Script -│ ├── install.sh # Installation -│ └── service-generator.sh # Service-Generator -├── tests/ # Tests -│ ├── unit/ # Unit-Tests (C) -│ ├── integration/ # Integration-Tests -│ └── lua/ # Lua-Tests -└── examples/ # Beispiele - ├── hugo/ # Hugo-Integration - └── configs/ # Beispiel-Konfigurationen -``` - -### 2.3 C + Lua Integration-Pattern - -**C-Kern mit Lua-Logic:** -```c -// src/core/main.c -#include "furt.h" -#include "lua_bridge.h" - -int main(int argc, char *argv[]) { - furt_context_t *ctx = furt_init(); - - // Initialize Lua state - lua_State *L = lua_bridge_init(ctx); - - // Load gateway configuration - lua_bridge_load_config(L, "config/gateway.lua"); - - // Start HTTP server - http_server_start(ctx, L); - - return 0; -} -``` - -**Lua-Service-Logic:** -```lua --- config/services/formular2mail.lua -local formular2mail = { - name = "formular2mail", - path_prefix = "/v1/mail", - port = 8081, - - -- Request-Handler - handle_request = function(request) - if request.method ~= "POST" then - return { status = 405, body = "Method not allowed" } - end - - local data = json.decode(request.body) - if not validate_mail_data(data) then - return { status = 400, body = "Invalid data" } - end - - local result = send_mail(data) - return { status = 200, body = json.encode(result) } - end, - - -- Health-Check - health_check = function() - return { status = "healthy", timestamp = os.time() } - end -} - -return formular2mail -``` - -## 3. Build-System und Dependencies - -### 3.1 Minimale Dependencies - -**Required:** -- **GCC** (oder clang, aber nicht MSVC) -- **musl libc** (vermeidet glibc-Komplexität) -- **Lua 5.4** (als Source eingebunden, nicht als Package) -- **LMDB** (als Source eingebunden) - -**Optional:** -- **Valgrind** für Memory-Debugging -- **strace** für System-Call-Debugging - -### 3.2 Build-Process - -```makefile -# build/Makefile -CC = gcc -CFLAGS = -std=c99 -Wall -Wextra -O2 -DLUA_USE_POSIX -LDFLAGS = -lm -ldl - -# Dependencies -LUA_DIR = deps/lua-5.4.6 -LMDB_DIR = deps/lmdb - -SOURCES = src/core/*.c $(LUA_DIR)/src/*.c $(LMDB_DIR)/*.c -TARGET = bin/furt-gateway - -all: $(TARGET) - -$(TARGET): $(SOURCES) - $(CC) $(CFLAGS) -I$(LUA_DIR)/src -I$(LMDB_DIR) \ - $(SOURCES) -o $(TARGET) $(LDFLAGS) - -clean: - rm -f $(TARGET) - -install: $(TARGET) - cp $(TARGET) /usr/local/bin/ - cp -r config/ /etc/furt/ - cp -r src/lua/ /usr/local/share/furt/ - -.PHONY: all clean install -``` - -### 3.3 Dependency-Management - -**Statically Linked:** -- Lua-Source direkt eingebunden -- LMDB-Source direkt eingebunden -- Keine Package-Manager-Dependencies - -**Why Static Linking:** -- Vermeidet Library-Version-Konflikte -- Reduziert Runtime-Dependencies -- Maximale Kontrolle über Code-Path - -## 4. Service-Entwicklung-Pattern - -### 4.1 Service als C-Module + Lua-Script - -**Service-Interface in C:** -```c -// src/core/service.h -typedef struct { - char *name; - char *path_prefix; - int port; - lua_State *lua_state; -} furt_service_t; - -// Service-Lifecycle -int service_init(furt_service_t *service, const char *config_path); -int service_handle_request(furt_service_t *service, http_request_t *req, http_response_t *res); -int service_health_check(furt_service_t *service); -void service_cleanup(furt_service_t *service); -``` - -**Service-Logic in Lua:** -```lua --- src/lua/services/base.lua -local base_service = { - -- Standard Request-Verarbeitung - process_request = function(self, request) - -- Input-Validation - if not self:validate_input(request) then - return self:error_response(400, "Invalid input") - end - - -- Business-Logic - local result = self:handle_business_logic(request) - - -- Response-Formatting - return self:format_response(result) - end, - - -- Standard Error-Handling - error_response = function(self, status, message) - return { - status = status, - headers = { ["Content-Type"] = "application/json" }, - body = json.encode({ error = message }) - } - end -} - -return base_service -``` - -### 4.2 Service-Generator (Shell + Lua) - -```bash -#!/bin/bash -# scripts/service-generator.sh - -SERVICE_NAME=$1 -if [ -z "$SERVICE_NAME" ]; then - echo "Usage: $0 " - exit 1 -fi - -# Create service Lua file -cat > "src/lua/services/${SERVICE_NAME}.lua" << EOF -local base = require("services.base") -local ${SERVICE_NAME} = {} - -setmetatable(${SERVICE_NAME}, { __index = base }) - -function ${SERVICE_NAME}:handle_business_logic(request) - -- TODO: Implement ${SERVICE_NAME} logic - return { message = "Hello from ${SERVICE_NAME}" } -end - -function ${SERVICE_NAME}:validate_input(request) - -- TODO: Implement validation - return true -end - -return ${SERVICE_NAME} -EOF - -# Create config file -cat > "config/services/${SERVICE_NAME}.lua" << EOF -return { - name = "${SERVICE_NAME}", - path_prefix = "/v1/${SERVICE_NAME}", - port = 808X, -- TODO: Set port - enabled = true, - - -- Service-specific config - config = { - -- TODO: Add service configuration - } -} -EOF - -echo "Created service: ${SERVICE_NAME}" -``` - -## 5. Konfigurationsmanagement (Lua-basiert) - -### 5.1 Lua-Konfiguration statt YAML - -**Gateway-Konfiguration:** -```lua --- config/gateway.lua -return { - server = { - host = "127.0.0.1", - port = 8080, - max_connections = 1000, - request_timeout = 30 - }, - - security = { - api_keys = { - { - key = os.getenv("HUGO_API_KEY"), - name = "hugo-frontend", - permissions = { "mail:send", "comments:read" }, - allowed_ips = { "127.0.0.1", "10.0.0.0/8" } - } - } - }, - - services = { - require("services.formular2mail"), - require("services.sagjan") - }, - - logging = { - level = "info", - file = "/var/log/furt/gateway.log" - } -} -``` - -**Vorteile Lua-Config:** -- **Native Programmierbarkeit** (Loops, Conditions, Functions) -- **Environment-Variable-Access** direkt in Config -- **Validation** durch Lua-Logic möglich -- **Kommentare und Dokumentation** integriert -- **Modularität** durch require() - -### 5.2 Config-Validation - -```lua --- src/lua/gateway/config_validator.lua -local validator = {} - -function validator.validate_gateway_config(config) - assert(config.server, "server config required") - assert(config.server.port, "server.port required") - assert(type(config.server.port) == "number", "server.port must be number") - - for _, service in ipairs(config.services) do - validator.validate_service_config(service) - end - - return true -end - -function validator.validate_service_config(service) - assert(service.name, "service.name required") - assert(service.path_prefix, "service.path_prefix required") - assert(service.port, "service.port required") - - return true -end - -return validator -``` - -## 6. Authentifizierung und Sicherheit - -### 6.1 C-basierte Auth-Implementation - -```c -// src/core/auth.c -#include "auth.h" -#include - -typedef struct { - char key[64]; - char name[32]; - char **permissions; - char **allowed_ips; -} api_key_t; - -int auth_validate_api_key(const char *key, const char *client_ip) { - // Load API keys from Lua config - // Validate key and IP - // Return permissions mask -} - -int auth_check_permission(int permissions_mask, const char *permission) { - // Check if permission is allowed -} -``` - -### 6.2 IP-Allowlisting in C - -```c -// src/core/ip_filter.c -#include - -int ip_is_allowed(const char *client_ip, char **allowed_ips) { - for (int i = 0; allowed_ips[i] != NULL; i++) { - if (ip_matches_cidr(client_ip, allowed_ips[i])) { - return 1; - } - } - return 0; -} - -int ip_matches_cidr(const char *ip, const char *cidr) { - // CIDR matching implementation -} -``` - -## 7. Performance und Ressourcen - -### 7.1 Memory-Management - -**C-Memory-Management:** -- **Stack-Allocation** wo möglich -- **Explicit malloc/free** für dynamische Allocations -- **Memory-Pools** für häufige Allocations -- **Valgrind-Testing** mandatory - -**Lua-Integration:** -- **Lua-GC-Tuning** für Server-Workloads -- **C ↔ Lua** Memory-Boundary klar definiert -- **Lua-State-Isolation** zwischen Services - -### 7.2 Performance-Charakteristika - -**Erwartete Performance:** -- **Memory:** < 10 MB für Gateway + 3 Services -- **CPU:** < 1% bei 100 req/s -- **Startup:** < 100ms Cold-Start -- **Latency:** < 1ms Gateway-Overhead - -**vs. Go-Implementation:** -- **10x weniger Memory** (keine GC, kein Runtime) -- **5x weniger CPU** (native Code, keine Abstractions) -- **100x schneller Startup** (kein Runtime-Init) - -## 8. Testing-Strategy - -### 8.1 C-Code-Testing - -```c -// tests/unit/test_auth.c -#include "auth.h" -#include "test_framework.h" - -void test_api_key_validation() { - // Setup - auth_init_test_keys(); - - // Test valid key - assert_true(auth_validate_api_key("valid-key", "127.0.0.1")); - - // Test invalid key - assert_false(auth_validate_api_key("invalid-key", "127.0.0.1")); - - // Test IP restriction - assert_false(auth_validate_api_key("valid-key", "192.168.1.1")); -} -``` - -### 8.2 Lua-Logic-Testing - -```lua --- tests/lua/test_services.lua -local service = require("services.formular2mail") - -function test_mail_service_validation() - local request = { - method = "POST", - body = '{"name":"Test","email":"test@example.com","message":"Test"}' - } - - local response = service:process_request(request) - assert(response.status == 200) -end -``` - -### 8.3 Integration-Testing - -```bash -#!/bin/bash -# tests/integration/test_gateway.sh - -# Start test gateway -./bin/furt-gateway & -GATEWAY_PID=$! - -# Test API endpoint -curl -X POST http://localhost:8080/v1/mail/send \ - -H "X-API-Key: test-key" \ - -d '{"name":"Test","email":"test@example.com","message":"Test"}' - -# Cleanup -kill $GATEWAY_PID -``` - -## 9. Deployment und Installation - -### 9.1 Native Installation (Single Binary) - -```bash -# Build from source -git clone https://gitea.dragons-at-work.de/DAW/furt.git -cd furt -make clean all - -# Install -sudo make install - -# Configure -sudo cp examples/configs/* /etc/furt/ - -# Start -sudo systemctl enable furt-gateway -sudo systemctl start furt-gateway -``` - -### 9.2 Systemd-Integration - -```ini -# /etc/systemd/system/furt-gateway.service -[Unit] -Description=Furt API Gateway -After=network.target - -[Service] -Type=simple -User=furt -Group=furt -ExecStart=/usr/local/bin/furt-gateway -Restart=always -RestartSec=5 -StandardOutput=journal -StandardError=journal - -[Install] -WantedBy=multi-user.target -``` - -### 9.3 OpenBSD-Integration (langfristig) - -```bash -# /etc/rc.d/furt_gateway -#!/bin/ksh -daemon="/usr/local/bin/furt-gateway" -daemon_user="_furt" - -. /etc/rc.d/rc.subr - -rc_cmd $1 -``` - -## 10. Migration von Go zu C + Lua - -### 10.1 Migration-Strategy - -**Phase 1: C-Grundgerüst** -- HTTP-Server in C implementieren -- Basis-Routing implementieren -- Lua-Integration testen - -**Phase 2: Service-Migration** -- Formular2Mail-Service als Lua-Script -- Auth-System in C + Lua -- Tests migrieren - -**Phase 3: Feature-Parität** -- Alle Go-Features in C + Lua -- Performance-Optimierung -- Dokumentation-Update - -**Phase 4: Deployment** -- Production-Deployment -- Go-Version deprecaten -- Community-Feedback integrieren - -### 10.2 Kompatibilität - -**API-Compatibility:** -- Gleiche HTTP-APIs wie Go-Version -- Gleiche Konfiguration-Semantik (aber Lua statt YAML) -- Gleiche Integration mit Hugo-Shortcodes - -**Migration-Pfad:** -- Side-by-Side-Deployment möglich -- Graduelle Service-Migration -- Zero-Downtime-Umstellung - -## 11. Langfristige Vision - -### 11.1 Souveräne Technologie-Stack - -**Complete Independence:** -- **Compiler:** GCC (GNU, nicht Corporate) -- **Libc:** musl (minimal, secure) -- **Database:** LMDB (academic, proven) -- **Scripting:** Lua (university-backed) -- **HTTP-Server:** Eigene Implementation (< 1000 Zeilen C) - -### 11.2 Community und Open Source - -**Authentic Open Source:** -- Apache 2.0 License -- Keine Corporate-Contributors -- Community-driven Development -- Educational Documentation - -**Biocodie-Integration:** -- Furt als Referenz-Implementation für "Organische Software" -- Minimale Komplexität, maximale Transparenz -- Natürliche Wachstums-Pattern - -## 12. Nächste Schritte - -### 12.1 Unmittelbare Implementierung - -1. **C-HTTP-Server** - Minimal-Implementation (< 500 Zeilen) -2. **Lua-Integration** - C ↔ Lua Bridge etablieren -3. **Build-System** - Makefile-basiertes Build -4. **Basic-Routing** - Request-Routing in C + Lua - -### 12.2 Service-Implementation - -1. **Formular2Mail** als erstes Lua-Service -2. **Authentication** in C mit Lua-Config -3. **Hugo-Integration** - Shortcodes adaptieren -4. **Testing-Framework** - C + Lua Tests - -### 12.3 Production-Readiness - -1. **Performance-Tuning** - Memory, CPU optimieren -2. **Security-Hardening** - Input-Validation, Memory-Safety -3. **Documentation** - Installation, Configuration, Development -4. **Deployment-Automation** - Scripts für Production - ---- - -**Diese überarbeitete Architektur entspricht vollständig der Dragons@Work Tech-Reference und ermöglicht echte digitale Souveränität durch Corporate-freie Technologien.** - diff --git a/devdocs/furt_master_strategy.md b/devdocs/furt_master_strategy.md deleted file mode 100644 index 09e073d..0000000 --- a/devdocs/furt_master_strategy.md +++ /dev/null @@ -1,296 +0,0 @@ -# Furt: Master-Strategie & Technologie-Migration - -**Erstellt:** 17. Juni 2025 -**Letzte Aktualisierung:** 17. Juni 2025 -**Version:** 1.0 -**Verantwortlich:** DAW-Team -**Dateipfad:** devdocs/MASTER_STRATEGY.md - -## Zweck dieses Dokuments - -Dieses Dokument definiert die langfristige Technologie-Migrationsstrategie für das gesamte Dragons@Work Ökosystem, mit Furt als erstem Schritt zur vollständigen digitalen Souveränität. - -## 🎯 Gesamtvision: Komplette Tech-Souveränität - -### Ausgangssituation (Juni 2025) -- **Server:** Ubuntu + Apache + ISPConfig -- **Website:** Hugo + digitalindependent Theme -- **Planned API:** Go-basiertes Furt (noch nicht implementiert) -- **Dependencies:** Viele Corporate-controlled Tools - -### Zielsituation (18-24 Monate) -- **Server:** OpenBSD + httpd + eigene Scripts -- **Website:** vefari (eigener Generator) + eigenes Theme -- **API:** C + Lua Furt (vollständig souverän) -- **Dependencies:** Minimal, alle selbst-kontrolliert - -## 📋 Migration-Roadmap - -### Phase 1: Furt API-Grundlagen (4 Wochen) -**Woche 1: Mail-Service (Pure Lua)** -- [x] Entscheidung: Pure Lua statt Go -- [ ] HTTP-Server (lua-socket) -- [ ] Mail-Handler (SMTP zu Postfix) -- [ ] API-Key-Auth -- [ ] Hugo-Integration (POST-based) - -**Woche 2-3: Service-Expansion** -- [ ] Comment-Service (sagjan-Integration) -- [ ] Health-Checks -- [ ] Error-Handling -- [ ] Basic Logging - -**Woche 4: Production-Ready** -- [ ] HTTPS (Lua-SSL) -- [ ] Systemd-Integration -- [ ] Monitoring -- [ ] Documentation - -### Phase 2: C-Integration (4-6 Wochen) -**Performance-Layer:** -- [ ] C-HTTP-Server (< 500 Zeilen) -- [ ] C ↔ Lua Bridge -- [ ] Memory-Management -- [ ] Security-Hardening - -### Phase 3: Infrastructure-Migration (6-12 Monate) -**Server-Migration:** -- [ ] OpenBSD-Evaluation -- [ ] ISPConfig → eigene Scripts -- [ ] Apache → OpenBSD httpd -- [ ] SSL-Management ohne Corporate-Tools - -### Phase 4: Website-Migration (3-6 Monate parallel) -**vefari-Entwicklung:** -- [ ] Hugo-Kompatibilität (Templates/Content) -- [ ] Markdown-Processing -- [ ] Multi-Language-Support -- [ ] Build-System - -### Phase 5: Complete Independence (langfristig) -**Advanced Features:** -- [ ] Eigener Browser (Exploration) -- [ ] Föderation zwischen Furt-Instanzen -- [ ] Advanced Services (Shop, Calendar, etc.) - -## 🏗️ Architektur-Prinzipien - -### Modularität (Anti-Monolith) -``` -Jedes Script/Modul: < 200 Zeilen -Jede Funktion: < 50 Zeilen -Jede Datei: Ein klarer Zweck -Keine 800-Zeilen-Monster! -``` - -### Technologie-Auswahl nach Tech-Reference -✅ **Erlaubt (Souverän):** -- C + GCC/musl -- Lua (PUC-Rio University) -- LMDB (Howard Chu/Symas) -- OpenBSD httpd - -❌ **Vermeiden (Corporate-Controlled):** -- Go (Google) -- Node.js (Corporate-Oligopol) -- Apache (Corporate-finanziert) -- MariaDB (VC-finanziert) - -⚠️ **Temporary OK (Migration-Pfad):** -- Ubuntu (→ OpenBSD) -- Apache (→ OpenBSD httpd) - -### Development-Prinzipien -1. **Verstehbarkeit** vor Features -2. **Kleine Module** vor Monolithen -3. **Eigene Kontrolle** vor Convenience -4. **Langfristig stabil** vor "Modern" -5. **Testing** für alles - -## 🔧 Technical Implementation Strategy - -### Furt-Architecture (Final) -``` -┌─────────────────┐ -│ OpenBSD httpd │ (SSL-Terminierung) -│ (Port 443) │ -└─────────┬───────┘ - │ -┌─────────▼───────┐ -│ C-HTTP-Gateway │ (Routing, Auth) -│ (Port 8080) │ -└─────────┬───────┘ - │ - ┌─────▼─────┐ ┌─────────┐ ┌─────────┐ - │ Lua-Mail │ │Lua-Comm│ │Lua-Shop │ - │(Port 8081)│ │(8082) │ │(8083) │ - └───────────┘ └─────────┘ └─────────┘ -``` - -### Service-Pattern (Standardisiert) -```lua --- services/template.lua -local service = { - name = "service_name", - port = 808X, - - -- Standard Interface - handle_request = function(self, request) - -- Input-Validation (< 20 Zeilen) - -- Business-Logic (< 50 Zeilen) - -- Response-Formatting (< 10 Zeilen) - end, - - health_check = function(self) - -- Health-Logic (< 10 Zeilen) - end -} -``` - -### Testing-Strategy -``` -tests/ -├── unit/ # Lua-Modul-Tests -│ ├── test_mail.lua -│ └── test_auth.lua -├── integration/ # Service-Tests -│ └── test_api.lua -├── system/ # End-to-End-Tests -│ └── test_hugo.lua -└── performance/ # Load-Tests - └── test_load.lua -``` - -## 📊 SMTP-Configuration (Week 1) - -### Postfix-Integration -```lua --- config/mail.lua -return { - smtp = { - server = "mail.dragons-at-work.de", - port = 465, - username = os.getenv("MAIL_USERNAME"), -- xxxx@dragons-at-work.de - password = os.getenv("MAIL_PASSWORD"), - security = "ssl/tls", - from = "noreply@dragons-at-work.de", - to = "michael@dragons-at-work.de" - } -} -``` - -### Security-Pattern -```lua --- Nie Passwörter in Code! --- Environment-Variables für Secrets --- API-Keys in LMDB --- IP-Allowlisting für Hugo -``` - -## 🚀 Hugo-Integration (Week 1) - -### Shortcode-Pattern -```hugo -{{< furt-mail - api-endpoint="https://api.dragons-at-work.de/v1/mail/send" - api-key="hugo-frontend-key" - success-url="/contact/thanks/" ->}} -``` - -### Progressive Enhancement -```html - -
- - - - -
- - - -``` - -## 📈 Success-Metrics - -### Week 1 Success-Criteria -- [ ] HTTP-Request funktioniert -- [ ] Mail wird via SMTP gesendet -- [ ] API-Key-Auth schützt Endpoint -- [ ] Hugo-Form sendet erfolgreich -- [ ] < 100ms Response-Time -- [ ] Jedes Modul < 200 Zeilen - -### Phase 1 Success-Criteria -- [ ] Production-ready Mail-Service -- [ ] Comment-Service implementiert -- [ ] HTTPS mit Lua-SSL -- [ ] Systemd-Service läuft stabil -- [ ] Documentation komplett - -### Long-term Success-Criteria -- [ ] Komplette Ubuntu → OpenBSD Migration -- [ ] Hugo → vefari Migration -- [ ] < 10 MB Total Memory für alle Services -- [ ] Zero Corporate-Dependencies - -## 🔄 Migration-Safety - -### Parallel-Betrieb-Strategie -``` -Week 1-4: Lua-Furt || Apache (parallel) -Month 2-6: C+Lua-Furt || Apache (parallel) -Month 6+: C+Lua-Furt || OpenBSD-httpd (parallel) -``` - -### Rollback-Plan -- Jede Migration-Phase kann rückgängig gemacht werden -- Hugo bleibt funktionsfähig während vefari-Entwicklung -- Apache bleibt als Fallback während OpenBSD-Migration - -### Testing vor Production -- Alle Changes erst auf Testumgebung -- Graduelle Umstellung Service für Service -- Monitoring für Performance-Regression - -## 📝 Documentation-Strategy - -### Development-Docs -- [ ] **Installation-Guide** (Linux → OpenBSD) -- [ ] **API-Documentation** (OpenAPI-Style) -- [ ] **Service-Development-Guide** (Lua-Pattern) -- [ ] **Testing-Guide** (Unit + Integration) - -### User-Docs -- [ ] **Hugo-Integration-Guide** -- [ ] **vefari-Migration-Guide** -- [ ] **Self-Hosting-Guide** - -### Philosophy-Docs -- [ ] **Tech-Souveränität-Rationale** -- [ ] **Corporate-Capture-Analysis** -- [ ] **Long-term-Vision** - -## 🎯 Next Session Preparation - -### Session-Focus: Lua-HTTP-Server Start -1. **Lua-Dependencies** installieren -2. **Basic HTTP-Server** (50-100 Zeilen) -3. **Request-Parsing** (POST-Body, Headers) -4. **Response-Formatting** (JSON) -5. **Error-Handling** (Basic) - -### Session-Deliverable -- `src/main.lua` - Funktionierender HTTP-Server -- `test/test_http.lua` - Basis-Tests -- `scripts/start.sh` - Start-Script - -### Session-Success-Metric -- `curl -X POST http://localhost:8080/test` → HTTP 200 Response - ---- - -**Diese Master-Strategie dient als Kompass für alle technischen Entscheidungen und stellt sicher, dass jeder kleine Schritt zum großen Ziel der technologischen Souveränität beiträgt.** \ No newline at end of file diff --git a/devdocs/furt_testing_guidelines.md b/devdocs/furt_testing_guidelines.md deleted file mode 100644 index 628ac9a..0000000 --- a/devdocs/furt_testing_guidelines.md +++ /dev/null @@ -1,783 +0,0 @@ -# Furt Testing-Richtlinien - -**Erstellt:** 03.06.2025 -**Letzte Aktualisierung:** 03.06.2025 -**Version:** 1.0 -**Verantwortlich:** DAW-Team -**Dateipfad:** devdocs/TESTING_GUIDELINES.md - -## Zweck dieses Dokuments - -Dieses Dokument definiert verbindliche Standards und Richtlinien für das Testen von Komponenten des Furt API-Gateway-Projekts. Es soll sicherstellen, dass alle implementierten Funktionalitäten ausreichend durch Tests abgedeckt sind und die Tests konsistent und wartbar bleiben. - -Es richtet sich an alle Entwickler, die Code zum Projekt beisteuern. - -## 1. Grundprinzipien - -### 1.1 Test-First-Entwicklung - -- Tests sollten parallel zur Implementierung oder idealerweise vor der eigentlichen Implementierung geschrieben werden. -- Keine Implementierung gilt als abgeschlossen, bis entsprechende Tests vorhanden sind. -- Pull Requests ohne Tests werden in der Regel nicht akzeptiert. - -### 1.2 Testabdeckung - -- Angestrebte Testabdeckung für Gateway-Kern: mindestens 85% -- Angestrebte Testabdeckung für Services: mindestens 80% -- Angestrebte Testabdeckung für Shared-Libraries: mindestens 90% -- Besonders kritische Komponenten (Authentifizierung, Routing, Service-Proxy) sollten eine Abdeckung nahe 100% haben. - -### 1.3 Test-Typen - -Folgende Test-Typen werden im Projekt verwendet: - -1. **Unit Tests**: Testen einzelner Funktionen/Methoden in Isolation -2. **Integration Tests**: Testen des Zusammenspiels von Komponenten -3. **API Tests**: Testen der API-Endpunkte des Gateways und Services -4. **Service Integration Tests**: Testen der Gateway ↔ Service-Kommunikation -5. **End-to-End Tests**: Testen der gesamten Request-Pipeline (Client → Gateway → Service) -6. **Performance Tests**: Load-Testing für Gateway und Services - -## 2. Test-Struktur und Dateiorganisation - -### 2.1 Dateistruktur - -- Test-Dateien werden neben den zu testenden Dateien platziert und erhalten den Suffix `_test.go` -- Beispiel: `gateway.go` → `gateway_test.go` -- Integration-Tests werden in `tests/integration/` platziert -- End-to-End-Tests werden in `tests/e2e/` platziert - -### 2.2 Namenkonventionen - -- Testfunktionen folgen dem Format `Test` -- Beispiel: `TestGatewayRoutingWithValidAPIKey`, `TestServiceProxyWhenServiceUnavailable` -- Benchmark-Tests: `Benchmark` -- Example-Tests: `Example` - -### 2.3 Testpakete - -- Tests sollten im selben Paket wie der zu testende Code sein (kein separates `_test`-Paket) -- Dies ermöglicht das Testen von Funktionen, die nicht exportiert werden -- Ausnahme: Integration-Tests können separate Pakete verwenden - -## 3. Unit Tests - -### 3.1 Grundstruktur - -Jeder Unit Test sollte folgende Struktur haben: - -```go -func TestFunctionName(t *testing.T) { - // Arrange: Vorbereitung der Testdaten und Abhängigkeiten - input := setupTestInput() - mockService := &MockService{} - expected := expectedResult{} - - // Act: Ausführen der zu testenden Funktion - actual, err := FunctionName(input, mockService) - - // Assert: Überprüfung des Ergebnisses - if err != nil { - t.Fatalf("Expected no error, but got: %v", err) - } - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Expected %+v, but got %+v", expected, actual) - } -} -``` - -### 3.2 Table-Driven Tests - -Für komplexere Funktionen sollten Table-Driven Tests verwendet werden: - -```go -func TestGatewayRouting(t *testing.T) { - testCases := []struct { - name string - requestPath string - expectedService string - expectedPath string - wantErr bool - }{ - { - name: "formular2mail service routing", - requestPath: "/v1/mail/send", - expectedService: "formular2mail", - expectedPath: "/send", - wantErr: false, - }, - { - name: "sagjan service routing", - requestPath: "/v1/comments/list", - expectedService: "sagjan", - expectedPath: "/list", - wantErr: false, - }, - { - name: "unknown service", - requestPath: "/v1/unknown/test", - expectedService: "", - expectedPath: "", - wantErr: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - gateway := setupTestGateway() - service, path, err := gateway.ResolveRoute(tc.requestPath) - - if tc.wantErr && err == nil { - t.Error("Expected error, but got nil") - } - if !tc.wantErr && err != nil { - t.Fatalf("Expected no error, but got: %v", err) - } - - if service != tc.expectedService { - t.Errorf("Expected service %s, got %s", tc.expectedService, service) - } - if path != tc.expectedPath { - t.Errorf("Expected path %s, got %s", tc.expectedPath, path) - } - }) - } -} -``` - -### 3.3 Mocking und Test-Doubles - -- Für HTTP-Clients verwende `httptest.NewServer` -- Für Services erstelle Interface-basierte Mocks -- Verwende Dependency Injection für bessere Testbarkeit - -```go -// Service Interface für Testbarkeit -type MailService interface { - SendMail(request MailRequest) error -} - -// Mock Implementation -type MockMailService struct { - SendMailFunc func(MailRequest) error - CallCount int -} - -func (m *MockMailService) SendMail(request MailRequest) error { - m.CallCount++ - if m.SendMailFunc != nil { - return m.SendMailFunc(request) - } - return nil -} - -// Test mit Mock -func TestFormular2MailHandler(t *testing.T) { - mockService := &MockMailService{ - SendMailFunc: func(req MailRequest) error { - if req.Email == "invalid" { - return errors.New("invalid email") - } - return nil - }, - } - - handler := NewFormular2MailHandler(mockService) - - // Test ausführen... -} -``` - -## 4. Integration Tests - -### 4.1 Gateway-Service Integration Tests - -Diese Tests prüfen die Kommunikation zwischen Gateway und Services: - -```go -// tests/integration/gateway_service_test.go -func TestGatewayServiceIntegration(t *testing.T) { - // Setup Test-Services - mailService := startTestMailService(t) - defer mailService.Close() - - commentsService := startTestCommentsService(t) - defer commentsService.Close() - - // Setup Gateway mit Test-Konfiguration - gateway := setupTestGateway(t, GatewayConfig{ - Services: map[string]ServiceConfig{ - "formular2mail": { - Enabled: true, - PathPrefix: "/v1/mail", - Upstream: mailService.URL, - }, - "sagjan": { - Enabled: true, - PathPrefix: "/v1/comments", - Upstream: commentsService.URL, - }, - }, - }) - defer gateway.Close() - - // Test Gateway → Service Routing - t.Run("mail service integration", func(t *testing.T) { - resp := makeTestRequest(t, gateway.URL+"/v1/mail/send", "POST", mailRequestBody) - assertStatusCode(t, resp, http.StatusOK) - }) - - t.Run("comments service integration", func(t *testing.T) { - resp := makeTestRequest(t, gateway.URL+"/v1/comments", "GET", nil) - assertStatusCode(t, resp, http.StatusOK) - }) -} -``` - -### 4.2 Database Integration Tests (für Services) - -Für Services mit Datenbank-Zugriff: - -```go -func TestSagjanServiceDatabaseIntegration(t *testing.T) { - // Setup Test-Database (SQLite in-memory) - db := setupTestDB(t) - defer db.Close() - - service := NewSagjanService(db) - - // Test Comment Creation - comment := &Comment{ - PageURL: "https://example.com/test", - Author: "Test User", - Content: "Test Comment", - } - - err := service.CreateComment(context.Background(), comment) - if err != nil { - t.Fatalf("Failed to create comment: %v", err) - } - - // Test Comment Retrieval - comments, err := service.GetComments(context.Background(), "https://example.com/test") - if err != nil { - t.Fatalf("Failed to get comments: %v", err) - } - - if len(comments) != 1 { - t.Errorf("Expected 1 comment, got %d", len(comments)) - } -} - -func setupTestDB(t *testing.T) *sql.DB { - db, err := sql.Open("sqlite3", ":memory:") - if err != nil { - t.Fatalf("Failed to open test database: %v", err) - } - - // Run migrations - if err := runMigrations(db); err != nil { - t.Fatalf("Failed to run migrations: %v", err) - } - - return db -} -``` - -## 5. API Tests - -### 5.1 Gateway API Tests - -Tests für die Gateway-API-Endpunkte: - -```go -func TestGatewayAPIEndpoints(t *testing.T) { - gateway := setupTestGateway(t) - defer gateway.Close() - - testCases := []struct { - name string - method string - path string - headers map[string]string - body string - expectedStatus int - expectedBody string - }{ - { - name: "health check", - method: "GET", - path: "/health", - expectedStatus: http.StatusOK, - expectedBody: `{"status":"healthy"}`, - }, - { - name: "unauthorized request", - method: "POST", - path: "/v1/mail/send", - expectedStatus: http.StatusUnauthorized, - }, - { - name: "authorized mail request", - method: "POST", - path: "/v1/mail/send", - headers: map[string]string{ - "X-API-Key": "test-api-key", - "Content-Type": "application/json", - }, - body: `{"name":"Test","email":"test@example.com","message":"Test"}`, - expectedStatus: http.StatusOK, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - req := createTestRequest(t, tc.method, gateway.URL+tc.path, tc.body) - - for key, value := range tc.headers { - req.Header.Set(key, value) - } - - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("Request failed: %v", err) - } - defer resp.Body.Close() - - if resp.StatusCode != tc.expectedStatus { - t.Errorf("Expected status %d, got %d", tc.expectedStatus, resp.StatusCode) - } - - if tc.expectedBody != "" { - body, _ := io.ReadAll(resp.Body) - if string(body) != tc.expectedBody { - t.Errorf("Expected body %s, got %s", tc.expectedBody, string(body)) - } - } - }) - } -} -``` - -### 5.2 Service API Tests - -Tests für individuelle Service-APIs: - -```go -func TestFormular2MailAPI(t *testing.T) { - service := startTestFormular2MailService(t) - defer service.Close() - - t.Run("valid mail request", func(t *testing.T) { - reqBody := `{ - "name": "John Doe", - "email": "john@example.com", - "message": "Test message" - }` - - resp := makeTestRequest(t, service.URL+"/send", "POST", reqBody) - assertStatusCode(t, resp, http.StatusOK) - - var response MailResponse - if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { - t.Fatalf("Failed to decode response: %v", err) - } - - if !response.Success { - t.Error("Expected success=true in response") - } - }) - - t.Run("invalid mail request", func(t *testing.T) { - reqBody := `{"name": "", "email": "invalid", "message": ""}` - - resp := makeTestRequest(t, service.URL+"/send", "POST", reqBody) - assertStatusCode(t, resp, http.StatusBadRequest) - }) -} -``` - -## 6. Performance Tests - -### 6.1 Gateway Performance Tests - -```go -func TestGatewayPerformance(t *testing.T) { - if testing.Short() { - t.Skip("Skipping performance test in short mode") - } - - gateway := setupTestGateway(t) - defer gateway.Close() - - // Load test - concurrency := 10 - requests := 1000 - - var wg sync.WaitGroup - errors := make(chan error, requests) - - start := time.Now() - - for i := 0; i < concurrency; i++ { - wg.Add(1) - go func() { - defer wg.Done() - for j := 0; j < requests/concurrency; j++ { - resp, err := http.Get(gateway.URL + "/health") - if err != nil { - errors <- err - return - } - resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - errors <- fmt.Errorf("unexpected status: %d", resp.StatusCode) - return - } - } - }() - } - - wg.Wait() - close(errors) - - duration := time.Since(start) - - // Check for errors - for err := range errors { - t.Errorf("Request error: %v", err) - } - - // Performance assertions - requestsPerSecond := float64(requests) / duration.Seconds() - if requestsPerSecond < 500 { // Minimum 500 RPS - t.Errorf("Performance too low: %.2f RPS", requestsPerSecond) - } - - t.Logf("Performance: %.2f RPS over %v", requestsPerSecond, duration) -} - -func BenchmarkGatewayRouting(b *testing.B) { - gateway := setupBenchmarkGateway(b) - - req := httptest.NewRequest("GET", "/v1/mail/send", nil) - req.Header.Set("X-API-Key", "test-key") - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - w := httptest.NewRecorder() - gateway.ServeHTTP(w, req) - } -} -``` - -## 7. Test-Daten und Test-Utilities - -### 7.1 Test-Daten-Management - -```go -// internal/testutil/fixtures.go -package testutil - -func CreateTestMailRequest() MailRequest { - return MailRequest{ - Name: "Test User", - Email: "test@example.com", - Subject: "Test Subject", - Message: "Test Message", - } -} - -func CreateTestComment() *Comment { - return &Comment{ - ID: uuid.New().String(), - PageURL: "https://example.com/test", - Author: "Test Author", - Email: "test@example.com", - Content: "Test Comment Content", - Status: StatusPending, - } -} - -func CreateTestGatewayConfig() GatewayConfig { - return GatewayConfig{ - Gateway: GatewaySettings{ - Port: "8080", - LogLevel: "info", - }, - Security: SecurityConfig{ - APIKeys: []APIKey{ - { - Key: "test-api-key", - Name: "Test Key", - Permissions: []string{"mail:send"}, - AllowedIPs: []string{"127.0.0.1"}, - }, - }, - }, - Services: map[string]ServiceConfig{ - "formular2mail": { - Enabled: true, - PathPrefix: "/v1/mail", - Upstream: "http://127.0.0.1:8081", - HealthCheck: "/health", - Timeout: 30 * time.Second, - }, - }, - } -} -``` - -### 7.2 Test-Helper-Funktionen - -```go -// internal/testutil/helpers.go -package testutil - -func AssertStatusCode(t *testing.T, resp *http.Response, expected int) { - t.Helper() - if resp.StatusCode != expected { - t.Errorf("Expected status code %d, got %d", expected, resp.StatusCode) - } -} - -func AssertResponseBody(t *testing.T, resp *http.Response, expected string) { - t.Helper() - body, err := io.ReadAll(resp.Body) - if err != nil { - t.Fatalf("Failed to read response body: %v", err) - } - - if string(body) != expected { - t.Errorf("Expected body %q, got %q", expected, string(body)) - } -} - -func MakeTestRequest(t *testing.T, url, method, body string) *http.Response { - t.Helper() - - var reqBody io.Reader - if body != "" { - reqBody = strings.NewReader(body) - } - - req, err := http.NewRequest(method, url, reqBody) - if err != nil { - t.Fatalf("Failed to create request: %v", err) - } - - if body != "" { - req.Header.Set("Content-Type", "application/json") - } - - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("Request failed: %v", err) - } - - return resp -} -``` - -## 8. Test-Umgebung und CI - -### 8.1 Lokale Tests - -- Alle Tests sollten mit `go test ./...` ausführbar sein -- Keine Tests sollten externe Ressourcen benötigen (wie echte E-Mail-Server) -- Performance-Tests mit `-short` Flag überspringen - -### 8.2 Test-Tags - -```go -// +build integration - -package tests - -// Integration tests that require external resources -``` - -**Ausführung:** -```bash -# Nur Unit Tests -go test ./... - -# Mit Integration Tests -go test -tags=integration ./... - -# Mit Performance Tests -go test -timeout=30m ./... - -# Kurze Tests für CI -go test -short ./... -``` - -### 8.3 Coverage-Berichte - -```bash -# Coverage generieren -go test -coverprofile=coverage.out ./... - -# HTML-Report -go tool cover -html=coverage.out -o coverage.html - -# Coverage-Threshold prüfen -go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//' -``` - -## 9. Spezifische Testfälle für Furt - -### 9.1 Gateway-Routing Tests - -```go -func TestGatewayServiceRouting(t *testing.T) { - testCases := []struct { - name string - requestPath string - method string - expectedService string - expectedUpstream string - wantErr bool - }{ - { - name: "formular2mail routing", - requestPath: "/v1/mail/send", - method: "POST", - expectedService: "formular2mail", - expectedUpstream: "http://127.0.0.1:8081", - }, - { - name: "sagjan comments routing", - requestPath: "/v1/comments", - method: "GET", - expectedService: "sagjan", - expectedUpstream: "http://127.0.0.1:8082", - }, - { - name: "unknown service", - requestPath: "/v1/unknown", - method: "GET", - wantErr: true, - }, - } - - // Implementation... -} -``` - -### 9.2 Authentication Tests - -```go -func TestGatewayAuthentication(t *testing.T) { - testCases := []struct { - name string - apiKey string - clientIP string - requestPath string - expectedStatus int - }{ - { - name: "valid API key and IP", - apiKey: "hugo-frontend-key", - clientIP: "127.0.0.1", - requestPath: "/v1/mail/send", - expectedStatus: http.StatusOK, - }, - { - name: "invalid API key", - apiKey: "invalid-key", - clientIP: "127.0.0.1", - requestPath: "/v1/mail/send", - expectedStatus: http.StatusUnauthorized, - }, - { - name: "blocked IP", - apiKey: "hugo-frontend-key", - clientIP: "192.168.1.100", - requestPath: "/v1/mail/send", - expectedStatus: http.StatusForbidden, - }, - } - - // Implementation... -} -``` - -### 9.3 Service Health Check Tests - -```go -func TestServiceHealthChecks(t *testing.T) { - // Test Gateway health aggregation - t.Run("all services healthy", func(t *testing.T) { - // Setup healthy services - // Test /health returns 200 with all services status - }) - - t.Run("one service unhealthy", func(t *testing.T) { - // Setup one failing service - // Test /health returns appropriate status - }) -} -``` - -## 10. Test-Automation und CI-Integration - -### 10.1 GitHub Actions / Gitea Actions - -```yaml -# .gitea/workflows/test.yml -name: Test Suite - -on: [push, pull_request] - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: 1.21 - - - name: Run Unit Tests - run: go test -short -race -coverprofile=coverage.out ./... - - - name: Run Integration Tests - run: go test -tags=integration ./tests/integration/ - - - name: Check Coverage - run: | - coverage=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//') - if (( $(echo "$coverage < 80" | bc -l) )); then - echo "Coverage $coverage% is below 80%" - exit 1 - fi -``` - -## 11. Best Practices Zusammenfassung - -### 11.1 Do's - -- ✅ **Testbare Architektur:** Dependency Injection verwenden -- ✅ **Isolierte Tests:** Keine Abhängigkeiten zwischen Tests -- ✅ **Realistische Test-Daten:** Aber anonymisiert und minimal -- ✅ **Performance-bewusst:** Benchmarks für kritische Pfade -- ✅ **Dokumentierte Test-Fälle:** Klare Beschreibungen der Test-Szenarien - -### 11.2 Don'ts - -- ❌ **Externe Ressourcen:** Keine echten E-Mail-Server, externe APIs -- ❌ **Feste Zeitstempel:** `time.Now()` mocken in Tests -- ❌ **Globaler State:** Tests sollten unabhängig sein -- ❌ **Überflüssige Tests:** Triviale Getter/Setter nicht testen -- ❌ **Fragile Tests:** Tests sollen bei kleinen Änderungen nicht brechen - ---- - -Diese Richtlinien sollen als Leitfaden dienen und können im Laufe des Projekts angepasst und erweitert werden. Bei Unklarheiten oder Fragen zu diesen Richtlinien kann das Entwicklungsteam kontaktiert werden. \ No newline at end of file diff --git a/furt-lua/README.md b/docs/lua-implementation-reference.md similarity index 100% rename from furt-lua/README.md rename to docs/lua-implementation-reference.md diff --git a/furt-lua/production_checklist.md b/docs/production_checklist.md similarity index 100% rename from furt-lua/production_checklist.md rename to docs/production_checklist.md diff --git a/go.mod b/go.mod deleted file mode 100644 index 4f8e1c1..0000000 --- a/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module furt - -go 1.24.3 diff --git a/projct-tree.txt b/projct-tree.txt new file mode 100644 index 0000000..7cc561e --- /dev/null +++ b/projct-tree.txt @@ -0,0 +1,38 @@ +. +├── furt-lua +│   ├── config +│   │   └── server.lua +│   ├── deployment +│   │   └── openbsd +│   │   └── rc.d-furt +│   ├── production_checklist.md +│   ├── README.md +│   ├── scripts +│   │   ├── cleanup_debug.sh +│   │   ├── manual_mail_test.sh +│   │   ├── production_test_sequence.sh +│   │   ├── setup_env.sh +│   │   ├── start.sh +│   │   ├── stress_test.sh +│   │   ├── test_auth.sh +│   │   ├── test_curl.sh +│   │   ├── test_modular.sh +│   │   └── test_smtp.sh +│   ├── src +│   │   ├── auth.lua +│   │   ├── ip_utils.lua +│   │   ├── main.lua +│   │   ├── rate_limiter.lua +│   │   ├── routes +│   │   │   ├── auth.lua +│   │   │   └── mail.lua +│   │   └── smtp.lua +│   └── tests +│   └── test_http.lua +├── LICENSE +├── projct-tree.txt +├── README.md +└── tools + └── gitea -> /home/michael/tools/tool-gitea-workflow/scripts + +11 directories, 25 files diff --git a/scripts/archive/create_issue_monster.sh b/scripts/archive/create_issue_monster.sh deleted file mode 100755 index 6038c0c..0000000 --- a/scripts/archive/create_issue_monster.sh +++ /dev/null @@ -1,779 +0,0 @@ -#!/bin/bash -# scripts/create_issue.sh - Furt API Gateway Issue Creator -# DEBUG VERSION with path fixes and diagnostic output - -set -euo pipefail - -# Standard environment setup -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" - -if [ -f "$PROJECT_ROOT/.env" ]; then - export $(cat "$PROJECT_ROOT/.env" | grep -v '^#' | xargs) -fi - -# Colors -GREEN='\033[0;32m' -BLUE='\033[0;34m' -RED='\033[0;31m' -YELLOW='\033[1;33m' -CYAN='\033[0;36m' -NC='\033[0m' - -log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } -log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } -log_error() { echo -e "${RED}[ERROR]${NC} $1"; } -log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; } -log_debug() { - if [[ "${DEBUG:-}" == "1" ]]; then - echo -e "${CYAN}[DEBUG]${NC} $1" >&2; - fi -} - -# Track new labels for auto-update (FIXED: Safe initialization) -declare -A NEW_LABELS_CREATED=() - -# === LABEL DEFINITIONS START === -# This section is auto-maintained by update_script_labels.sh -# DO NOT EDIT MANUALLY - Changes will be overwritten -declare -A LABEL_DEFINITIONS=( - ["hugo-integration"]="color:ff7518;context:frontend;usage:hugo_templates,integration" - ["service-newsletter"]="color:ff6b6b;context:newsletter;usage:newsletter_integration" - ["service-analytics"]="color:1d76db;context:service_integration;usage:service_specific" - ["ready-for-deployment"]="color:28a745;context:deploy_ready;usage:status_updates" - ["service-clean-test4"]="color:1d76db;context:service_integration;usage:service_specific" - ["service-completely-absolut-new7"]="color:1d76db;context:service_integration;usage:service_specific" - ["service-completely-absolut-new9"]="color:1d76db;context:service_integration;usage:service_specific" - ["service-completely-absolut-new8"]="color:1d76db;context:service_integration;usage:service_specific" - ["performance"]="color:fbca04;context:optimization;usage:performance_template,architecture_template" - ["bug"]="color:d73a4a;context:error;usage:bug_template,status_updates" - ["question"]="color:d876e3;context:discussion;usage:question_template" - ["service-formular2mail"]="color:1d76db;context:formular2mail;usage:formular2mail_integration" - ["good-first-issue"]="color:7057ff;context:beginner_friendly;usage:manual_assignment" - ["service-completely-absolut-new10"]="color:1d76db;context:service_integration;usage:service_specific" - ["service-completely-absolut-new11"]="color:1d76db;context:service_integration;usage:service_specific" - ["breaking-change"]="color:d73a4a;context:breaking;usage:api_templates,architecture_template" - ["service-request"]="color:7057ff;context:new_service;usage:service_templates,status_updates" - ["service-debug-test"]="color:1d76db;context:service_integration;usage:service_specific" - ["low-priority"]="color:0e8a16;context:nice_to_have;usage:all_templates" - ["blocked"]="color:d73a4a;context:blocked;usage:status_updates" - ["low-tech"]="color:6f42c1;context:low_tech_principle;usage:architecture_template,performance_template,security_template" - ["deployment"]="color:ff7518;context:deployment;usage:deployment_template" - ["gateway"]="color:0052cc;context:gateway_core;usage:architecture_template,performance_template,service_templates" - ["service-sagjan"]="color:1d76db;context:sagjan;usage:sagjan_integration" - ["work-in-progress"]="color:fbca04;context:active;usage:status_updates" - ["service-debug-check-final2"]="color:1d76db;context:service_integration;usage:service_specific" - ["digital-sovereignty"]="color:6f42c1;context:digital_sovereignty;usage:architecture_template,performance_template,security_template" - ["security"]="color:28a745;context:security_review;usage:security_template,architecture_template" - ["architecture"]="color:d4c5f9;context:design;usage:architecture_template,gateway" - ["configuration"]="color:f9d71c;context:config_management;usage:deployment_template,architecture_template" - ["needs-review"]="color:0e8a16;context:review;usage:status_updates" - ["help-wanted"]="color:159818;context:community_help;usage:manual_assignment" - ["service-whatever-you-want"]="color:1d76db;context:service_integration;usage:service_specific" - ["api-contract"]="color:5319e7;context:api_design;usage:api_templates,service_templates" - ["enhancement"]="color:84b6eb;context:improvement;usage:all_templates" - ["high-priority"]="color:d73a4a;context:urgent;usage:all_templates" - ["testing"]="color:f9d71c;context:testing;usage:testing_template,integration" - ["test-all-templates"]="color:ff0000;context:test;usage:all_templates" -) - -# Extract label info -get_label_color() { echo "${LABEL_DEFINITIONS[$1]}" | cut -d';' -f1 | cut -d':' -f2; } -get_label_context() { echo "${LABEL_DEFINITIONS[$1]}" | cut -d';' -f2 | cut -d':' -f2; } -get_label_usage() { echo "${LABEL_DEFINITIONS[$1]}" | cut -d';' -f3 | cut -d':' -f2; } - -# Check if label is valid for context -is_label_valid_for_context() { - local label="$1" - local context="$2" - local usage=$(get_label_usage "$label") - [[ "$usage" == *"$context"* ]] || [[ "$usage" == "all_templates" ]] -} -# === LABEL DEFINITIONS END === - -# === TEMPLATE LABEL MAPPINGS START === -# Auto-generated template to label mappings -declare -A TEMPLATE_LABELS=( - ["performance"]="performance,low-priority,low-tech,gateway,digital-sovereignty,enhancement,high-priority,test-all-templates" - ["bug"]="bug,low-priority,enhancement,high-priority,test-all-templates" - ["api"]="breaking-change,low-priority,api-contract,enhancement,high-priority,test-all-templates" - ["service"]="low-priority,gateway,api-contract,enhancement,high-priority,test-all-templates" - ["deployment"]="low-priority,deployment,configuration,enhancement,high-priority,test-all-templates" - ["security"]="low-priority,low-tech,digital-sovereignty,security,enhancement,high-priority,test-all-templates" - ["architecture"]="performance,breaking-change,low-priority,low-tech,gateway,digital-sovereignty,security,architecture,configuration,enhancement,high-priority,test-all-templates" - ["hugo"]="hugo-integration,low-priority,enhancement,high-priority,test-all-templates" -) -# === TEMPLATE LABEL MAPPINGS END === - -# Load existing labels from repository -declare -A LABEL_IDS - -load_existing_labels() { - if [[ -z "${GITEA_URL:-}" ]] || [[ -z "${GITEA_TOKEN:-}" ]]; then - log_error "GITEA_URL and GITEA_TOKEN must be set" - exit 1 - fi - - log_info "Loading existing labels from repository..." - - local response=$(curl -s "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/labels" \ - -H "Authorization: token $GITEA_TOKEN") - - if [[ $? -ne 0 ]]; then - log_error "Failed to fetch labels from repository" - exit 1 - fi - - while IFS= read -r line; do - local name=$(echo "$line" | jq -r '.name') - local id=$(echo "$line" | jq -r '.id') - LABEL_IDS["$name"]="$id" - done < <(echo "$response" | jq -c '.[]') - - log_info "Loaded ${#LABEL_IDS[@]} existing labels" -} - -# FIXED: Silent version of ensure_label_exists (no stdout pollution!) -ensure_label_exists_silent() { - local name="$1" - local color="${2:-ff6b6b}" - local description="${3:-Auto-generated label}" - local usage="${4:-manual_assignment}" # ADDED: usage parameter - - log_debug "Checking label: $name" - - if [[ -n "${LABEL_IDS[$name]:-}" ]]; then - log_debug "Label $name already exists (ID: ${LABEL_IDS[$name]})" - return 0 - fi - - log_debug "Creating new label: $name with color $color" - - # Create label (redirect output to prevent stdout mixing) - local response=$(curl -s -w "\n%{http_code}" -X POST \ - "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/labels" \ - -H "Authorization: token $GITEA_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{ - \"name\": \"$name\", - \"color\": \"$color\", - \"description\": \"$description\" - }" 2>/dev/null) - - local http_code=$(echo "$response" | tail -n1) - local response_body=$(echo "$response" | head -n -1) - - if [[ "$http_code" == "201" ]]; then - local new_id=$(echo "$response_body" | jq -r '.id') - LABEL_IDS["$name"]="$new_id" - - # FIXED: Track for auto-update with correct usage - NEW_LABELS_CREATED["$name"]="$color:auto_generated:$usage" - log_debug "Successfully created label $name (ID: $new_id)" - log_debug "Added to NEW_LABELS_CREATED: $name -> ${NEW_LABELS_CREATED[$name]}" - return 0 - else - log_debug "Failed to create label $name (HTTP: $http_code)" - log_debug "Response: $response_body" - return 1 - fi -} - -# Process labels for template (updates global arrays, no output) -process_labels_for_template() { - local template="$1" - shift - local additional_labels=("$@") - - log_debug "Processing labels for template: $template" - log_debug "Additional labels: ${additional_labels[*]}" - - # Get template labels - local template_labels_string="${TEMPLATE_LABELS[$template]:-}" - local all_labels=() - - # Add template labels - if [[ -n "$template_labels_string" ]]; then - IFS=',' read -ra template_labels <<< "$template_labels_string" - all_labels+=("${template_labels[@]}") - log_debug "Template labels: ${template_labels[*]}" - fi - - # Add additional labels - all_labels+=("${additional_labels[@]}") - log_debug "All labels to process: ${all_labels[*]}" - - # Process all labels and ensure they exist - for label in "${all_labels[@]}"; do - log_debug "Processing label: $label" - - # Process both known and unknown labels - if [[ -n "${LABEL_DEFINITIONS[$label]:-}" ]]; then - log_debug "Known label: $label" - # Known label - use defined color and context - local color=$(get_label_color "$label") - local context=$(get_label_context "$label") - - ensure_label_exists_silent "$label" "$color" "Furt: $context" - else - log_debug "Unknown label: $label - creating with smart defaults" - # Unknown label - auto-create with smart defaults - local default_color="ff6b6b" - local default_context="auto_generated" - - # Smart defaults based on label pattern - if [[ "$label" == service-* ]]; then - default_color="1d76db" - default_context="service_integration" - default_usage="service_specific" # FIXED: Not all_templates! - log_debug "Service label detected - using blue color and service_specific usage" - elif [[ "$label" == *-priority ]]; then - default_color="d73a4a" - default_context="priority_level" - default_usage="priority_management" - log_debug "Priority label detected - using red color" - elif [[ "$label" == hugo-* ]]; then - default_color="ff7518" - default_context="frontend_integration" - default_usage="hugo_integration" - log_debug "Hugo label detected - using orange color" - else - default_usage="manual_assignment" - fi - - ensure_label_exists_silent "$label" "$default_color" "Furt: $default_context" - - # FIXED: Track with correct usage - if [[ -n "${LABEL_IDS[$label]:-}" ]] && [[ -n "${NEW_LABELS_CREATED[$label]:-}" ]]; then - NEW_LABELS_CREATED["$label"]="$default_color:$default_context:$default_usage" - log_debug "Updated NEW_LABELS_CREATED with correct usage: $label -> $default_color:$default_context:$default_usage" - fi - fi - - # Debug: Check if this label was newly created - if [[ -n "${LABEL_IDS[$label]:-}" ]]; then - if [[ -n "${NEW_LABELS_CREATED[$label]:-}" ]]; then - log_debug " → Label $label was newly created and tracked" - else - log_debug " → Label $label already existed" - fi - else - log_warning "Failed to process label: $label" - fi - done - - # Debug: Check NEW_LABELS_CREATED at end of processing - log_debug "NEW_LABELS_CREATED after processing: ${#NEW_LABELS_CREATED[@]} entries" - if [[ "${#NEW_LABELS_CREATED[@]}" -gt 0 ]] 2>/dev/null; then - for label_name in "${!NEW_LABELS_CREATED[@]}"; do - log_debug " - $label_name: ${NEW_LABELS_CREATED[$label_name]}" - done - else - log_debug " (no entries in NEW_LABELS_CREATED array)" - fi -} - -# Build JSON from already processed labels (pure function, no side effects) -build_labels_json_from_processed() { - local template="$1" - shift - local additional_labels=("$@") - - log_debug "Building JSON from processed labels" - - # Get template labels - local template_labels_string="${TEMPLATE_LABELS[$template]:-}" - local all_labels=() - - # Add template labels - if [[ -n "$template_labels_string" ]]; then - IFS=',' read -ra template_labels <<< "$template_labels_string" - all_labels+=("${template_labels[@]}") - fi - - # Add additional labels - all_labels+=("${additional_labels[@]}") - - # Collect IDs from already processed labels - local label_ids=() - for label in "${all_labels[@]}"; do - if [[ -n "${LABEL_IDS[$label]:-}" ]]; then - label_ids+=("${LABEL_IDS[$label]}") - log_debug "Added ID ${LABEL_IDS[$label]} for $label to JSON" - else - log_warning "No ID found for label: $label" - fi - done - - log_debug "Final label IDs for JSON: ${label_ids[*]}" - - # Build JSON array (clean output only!) - if [[ ${#label_ids[@]} -gt 0 ]]; then - printf '[%s]' "$(IFS=','; echo "${label_ids[*]}")" - else - echo "[]" - fi -} - -# DEPRECATED: Old build_labels_json function (kept for compatibility) -build_labels_json() { - local template="$1" - shift - local additional_labels=("$@") - - log_debug "Building labels for template: $template" - log_debug "Additional labels: ${additional_labels[*]}" - - # Get template labels - local template_labels_string="${TEMPLATE_LABELS[$template]:-}" - local all_labels=() - - # Add template labels - if [[ -n "$template_labels_string" ]]; then - IFS=',' read -ra template_labels <<< "$template_labels_string" - all_labels+=("${template_labels[@]}") - log_debug "Template labels: ${template_labels[*]}" - fi - - # Add additional labels - all_labels+=("${additional_labels[@]}") - log_debug "All labels to process: ${all_labels[*]}" - - # FIXED: Ensure all labels exist and collect IDs (handles unknown labels!) - local label_ids=() - for label in "${all_labels[@]}"; do - log_debug "Processing label: $label" - - # Process both known and unknown labels - if [[ -n "${LABEL_DEFINITIONS[$label]:-}" ]]; then - log_debug "Known label: $label" - # Known label - use defined color and context - local color=$(get_label_color "$label") - local context=$(get_label_context "$label") - - ensure_label_exists_silent "$label" "$color" "Furt: $context" - else - log_debug "Unknown label: $label - creating with smart defaults" - # FIXED: Unknown label - auto-create with smart defaults - local default_color="ff6b6b" - local default_context="auto_generated" - - # Smart defaults based on label pattern - if [[ "$label" == service-* ]]; then - default_color="1d76db" - default_context="service_integration" - log_debug "Service label detected - using blue color" - elif [[ "$label" == *-priority ]]; then - default_color="d73a4a" - default_context="priority_level" - log_debug "Priority label detected - using red color" - elif [[ "$label" == hugo-* ]]; then - default_color="ff7518" - default_context="frontend_integration" - log_debug "Hugo label detected - using orange color" - fi - - ensure_label_exists_silent "$label" "$default_color" "Furt: $default_context" - fi - - # Collect ID if label was created/exists - if [[ -n "${LABEL_IDS[$label]:-}" ]]; then - label_ids+=("${LABEL_IDS[$label]}") - log_debug "Added label ID: ${LABEL_IDS[$label]} for $label" - - # Debug: Check if this label was newly created - if [[ -n "${NEW_LABELS_CREATED[$label]:-}" ]]; then - log_debug " → This label was newly created and tracked" - else - log_debug " → This label already existed" - fi - else - log_warning "Failed to get ID for label: $label" - fi - done - - log_debug "Final label IDs: ${label_ids[*]}" - - # Debug: Check NEW_LABELS_CREATED at end of function - log_debug "NEW_LABELS_CREATED at end of build_labels_json: ${#NEW_LABELS_CREATED[@]} entries" - if [[ "${#NEW_LABELS_CREATED[@]}" -gt 0 ]] 2>/dev/null; then - for label_name in "${!NEW_LABELS_CREATED[@]}"; do - log_debug " - $label_name: ${NEW_LABELS_CREATED[$label_name]}" - done - else - log_debug " (no entries in NEW_LABELS_CREATED array)" - fi - - # Build JSON array (clean output only!) - if [[ ${#label_ids[@]} -gt 0 ]]; then - printf '[%s]' "$(IFS=','; echo "${label_ids[*]}")" - else - echo "[]" - fi -} - -# Show which labels are being used (AFTER JSON building to avoid stdout pollution) -show_labels_used() { - local template="$1" - shift - local additional_labels=("$@") - - log_info "Labels used for this issue:" - - # Show template labels - local template_labels_string="${TEMPLATE_LABELS[$template]:-}" - if [[ -n "$template_labels_string" ]]; then - IFS=',' read -ra template_labels <<< "$template_labels_string" - for label in "${template_labels[@]}"; do - if [[ -n "${LABEL_IDS[$label]:-}" ]]; then - log_info " ✅ $label (ID: ${LABEL_IDS[$label]})" - fi - done - fi - - # Show additional labels (these may have been newly created) - for label in "${additional_labels[@]}"; do - if [[ -n "${LABEL_IDS[$label]:-}" ]]; then - if [[ -n "${NEW_LABELS_CREATED[$label]:-}" ]]; then - log_info " ✅ $label (ID: ${LABEL_IDS[$label]}) [NEW!]" - else - log_info " ✅ $label (ID: ${LABEL_IDS[$label]})" - fi - else - log_warning " ❌ $label (failed to create)" - fi - done -} - -# FIXED: AUTO-UPDATE with safe array handling and correct path -auto_update_scripts_if_needed() { - # FIXED: Safe check for empty associative array - local new_labels_count=0 - if [[ "${#NEW_LABELS_CREATED[@]}" -gt 0 ]] 2>/dev/null; then - new_labels_count=${#NEW_LABELS_CREATED[@]} - fi - - log_debug "Auto-update check: $new_labels_count new labels created" - - # Debug: Show what's in NEW_LABELS_CREATED - if [[ $new_labels_count -gt 0 ]]; then - log_debug "NEW_LABELS_CREATED contents:" - for label_name in "${!NEW_LABELS_CREATED[@]}"; do - log_debug " - $label_name: ${NEW_LABELS_CREATED[$label_name]}" - done - else - log_debug "NEW_LABELS_CREATED is empty or unset" - # Debug: Try to list what's in the array anyway - if [[ "${#NEW_LABELS_CREATED[@]}" -gt 0 ]] 2>/dev/null; then - for key in "${!NEW_LABELS_CREATED[@]}"; do - log_debug " Found key: $key" - done - else - log_debug " Array iteration failed - truly empty" - fi - fi - - if [[ $new_labels_count -eq 0 ]]; then - log_debug "No new labels created - skipping auto-update" - return 0 # No new labels, no update needed - fi - - log_info "🔄 Auto-updating scripts with $new_labels_count new labels..." - - # Check if update script exists - local update_script="$SCRIPT_DIR/update_script_labels.sh" - if [[ ! -f "$update_script" ]]; then - log_warning "Update script not found: $update_script" - log_warning "Skipping auto-update" - return 0 - fi - - if [[ ! -x "$update_script" ]]; then - log_warning "Update script not executable: $update_script" - log_warning "Making executable..." - chmod +x "$update_script" - fi - - # Add new labels to registry - for label_name in "${!NEW_LABELS_CREATED[@]}"; do - local label_info="${NEW_LABELS_CREATED[$label_name]}" - local color=$(echo "$label_info" | cut -d':' -f1) - local context=$(echo "$label_info" | cut -d':' -f2) - local usage=$(echo "$label_info" | cut -d':' -f3) - - log_info "Adding '$label_name' to registry..." - - # Add to registry (suppressing output to avoid noise) - FURT_AUTO_UPDATE=true "$update_script" add "$label_name" "$color" "$context" "$usage" >/dev/null 2>&1 || { - log_warning "Failed to add $label_name to registry" - } - done - - # Update all scripts with new labels - log_info "Synchronizing all scripts..." - "$update_script" update >/dev/null 2>&1 || { - log_warning "Failed to update scripts" - return 0 - } - - log_success "✅ All scripts automatically synchronized with new labels!" - - # Show what was added - echo "" - echo "🆕 New labels created and synchronized:" - for label_name in "${!NEW_LABELS_CREATED[@]}"; do - echo " - $label_name (ID: ${LABEL_IDS[$label_name]})" - done - echo "" -} - -# Create issue templates -create_service_issue() { - local service_name="${1:-newsletter}" - - log_debug "Creating service issue for: $service_name" - - local title="[SERVICE] $service_name für Furt Gateway" - local body="# Service-Request: $service_name - -## 🏷️ Service-Details -**Name:** $service_name -**Port:** TBD -**Zweck:** [Service-Beschreibung] - -## 📝 Funktionsanforderungen -- [ ] [Anforderung 1] -- [ ] [Anforderung 2] -- [ ] [Anforderung 3] - -## 🔗 Gateway-Integration -- [ ] **Routing:** \`/v1/$service_name/*\` -- [ ] **Auth:** API-Key required -- [ ] **Rate-Limiting:** TBD req/min -- [ ] **Health-Check:** \`/health\` - -## 🎯 Hugo-Integration -- [ ] **Shortcode:** \`{{< furt-$service_name >}}\` -- [ ] **JavaScript-Client** -- [ ] **CSS-Styling** - -## ⚡ Priorität -🔥 **Hoch** - benötigt für Website-Launch" - - # Process labels first, then build JSON - local service_label="service-$service_name" - log_debug "Service label to add: $service_label" - - # First: Process all labels (this updates global arrays) - process_labels_for_template "service" "$service_label" - - # Then: Build JSON from already-processed labels (pure function, no side effects) - local labels_json=$(build_labels_json_from_processed "service" "$service_label") - - # Show which labels are being used (AFTER processing when labels are actually created) - show_labels_used "service" "$service_label" - - create_issue "$title" "$body" "$labels_json" -} - -create_architecture_issue() { - local topic="${1:-middleware-optimization}" - - local title="[ARCH] Gateway $topic" - local body="# Architektur-Diskussion: $topic - -## 🎯 Architektur-Thema -[Beschreibung des Architektur-Themas] - -## 📊 Aktuelle Situation -- [Status Quo 1] -- [Status Quo 2] - -## 💡 Vorgeschlagene Änderung -- [Vorschlag 1] -- [Vorschlag 2] - -## 🔄 Alternativen -1. **Option A:** [Beschreibung] -2. **Option B:** [Beschreibung] - -## 📈 Betroffene Bereiche -- [ ] Gateway-Performance -- [ ] Service-Integration -- [ ] Security -- [ ] Configuration-Management" - - # Process labels first, then build JSON - process_labels_for_template "architecture" - local labels_json=$(build_labels_json_from_processed "architecture") - - create_issue "$title" "$body" "$labels_json" -} - -# Generic template creator -create_generic_issue() { - local template="$1" - local component="${2:-gateway}" - local description="[Beschreibung hinzufügen]" - - # Safe parameter handling for $3 - if [[ $# -ge 3 ]] && [[ -n "${3:-}" ]]; then - description="$3" - fi - - log_debug "Creating $template issue for: $component" - - local title_prefix - case "$template" in - api) title_prefix="[API]" ;; - security) title_prefix="[SEC]" ;; - hugo) title_prefix="[HUGO]" ;; - deployment) title_prefix="[DEPLOY]" ;; - bug) title_prefix="[BUG]" ;; - *) title_prefix="[${template^^}]" ;; - esac - - local title="$title_prefix $component $(echo ${template^} | sed 's/api/API Contract/')" - local body="# ${template^}: $component - -## 📝 ${template^}-Details -**Komponente:** $component -**Beschreibung:** $description - -## 🎯 Anforderungen -- [ ] [Anforderung 1] -- [ ] [Anforderung 2] -- [ ] [Anforderung 3] - -## ✅ Definition of Done -- [ ] [DoD Kriterium 1] -- [ ] [DoD Kriterium 2] -- [ ] [DoD Kriterium 3]" - - # Process labels first, then build JSON - process_labels_for_template "$template" - local labels_json=$(build_labels_json_from_processed "$template") - - show_labels_used "$template" - create_issue "$title" "$body" "$labels_json" -} - -# Show usage information -show_usage() { - echo "🎯 Furt API-Gateway Issue Creator (Debug Version)" - echo "" - echo "Usage: $0 [TEMPLATE] [OPTIONS]" - echo "" - echo "📋 Available Templates:" - echo " service [name] New service for gateway (default: newsletter)" - echo " architecture [topic] Gateway architecture discussion (default: middleware-optimization)" - echo " performance [comp] Performance optimization (default: gateway)" - echo " api [service] API contract update (default: formular2mail)" - echo " security [comp] Security review/issue (default: gateway)" - echo " bug [comp] [desc] Bug report (default: gateway)" - echo " hugo [feature] Hugo integration (default: shortcode)" - echo " deployment [comp] Deployment issue (default: gateway)" - echo " custom Custom issue (interactive)" - echo "" - echo "🚀 Examples:" - echo " $0 service newsletter # Create newsletter service request" - echo " $0 architecture rate-limiting # Discuss rate limiting architecture" - echo " $0 performance gateway # Gateway performance optimization" - echo " $0 custom # Interactive custom issue" - echo "" - echo "🔧 Debug Mode:" - echo " Set DEBUG=1 for verbose debug output" -} - -# Generic issue creation -create_issue() { - local title="$1" - local body="$2" - local labels_json="$3" - - log_info "Creating issue: $title" - log_debug "Labels JSON: $labels_json" - - local response=$(curl -s -w "\n%{http_code}" -X POST \ - "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/issues" \ - -H "Authorization: token $GITEA_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{ - \"title\": $(echo "$title" | jq -R .), - \"body\": $(echo "$body" | jq -R -s .), - \"labels\": $labels_json - }") - - local http_code=$(echo "$response" | tail -n1) - local response_body=$(echo "$response" | head -n -1) - - if [[ "$http_code" == "201" ]]; then - local issue_number=$(echo "$response_body" | jq -r '.number') - local issue_url=$(echo "$response_body" | jq -r '.html_url') - - log_success "Issue #$issue_number created!" - echo "🔗 $issue_url" - else - log_error "Failed to create issue (HTTP: $http_code)" - log_error "Response: $response_body" - exit 1 - fi -} - -# Main function -main() { - local template="${1:-help}" - - # Enable debug if requested - if [[ "${DEBUG:-}" == "1" ]]; then - log_info "Debug mode enabled" - fi - - if [[ "$template" == "help" ]] || [[ "$template" == "--help" ]] || [[ "$template" == "-h" ]]; then - show_usage - exit 0 - fi - - # Load existing labels - load_existing_labels - - case "$template" in - service) - create_service_issue "${2:-newsletter}" - ;; - architecture) - create_architecture_issue "${2:-middleware-optimization}" - ;; - performance) - local component="${2:-gateway}" - local title="[PERF] $component Performance-Optimierung" - local body="# Performance-Optimierung: $component" - - process_labels_for_template "performance" - local labels_json=$(build_labels_json_from_processed "performance") - - create_issue "$title" "$body" "$labels_json" - ;; - api|security|hugo|deployment|bug) - if [[ $# -ge 3 ]]; then - create_generic_issue "$template" "${2:-gateway}" "$3" - else - create_generic_issue "$template" "${2:-gateway}" - fi - ;; - *) - log_error "Unknown template: $template" - show_usage - exit 1 - ;; - esac - - # FIXED: AUTO-UPDATE: Automatically sync scripts if new labels were created - auto_update_scripts_if_needed -} - -# Run if executed directly -if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - main "$@" -fi - diff --git a/scripts/archive/get_issues_v1.sh b/scripts/archive/get_issues_v1.sh deleted file mode 100755 index ae8f0c9..0000000 --- a/scripts/archive/get_issues_v1.sh +++ /dev/null @@ -1,250 +0,0 @@ -#!/bin/bash - -# Load environment -if [ -f .env ]; then - export $(cat .env | grep -v '^#' | xargs) -else - echo "❌ .env file not found!" - echo "📋 Copy .env.example to .env and configure it first" - exit 1 -fi - -# Validate required variables -if [ -z "$GITEA_URL" ] || [ -z "$REPO_OWNER" ] || [ -z "$REPO_NAME" ] || [ -z "$GITEA_TOKEN" ]; then - echo "❌ Missing required environment variables in .env" - exit 1 -fi - -# Colors -GREEN='\033[0;32m' -BLUE='\033[0;34m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' - -log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } -log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } -log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; } - -# Get all issues with nice formatting -get_all_issues() { - log_info "Fetching all issues..." - echo "" - - response=$(curl -s "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/issues" \ - -H "Authorization: token $GITEA_TOKEN") - - if [ $? -ne 0 ]; then - echo "❌ Error fetching issues" - return 1 - fi - - echo "$response" | jq -r '.[] | - "🎯 #\(.number) \(.title)", - " 📊 State: \(.state) | 🏷️ Labels: \(.labels | map(.name) | join(", ") // "none")", - " 🔗 \(.html_url)", - ""' -} - -# Get issues by label -get_issues_by_label() { - local label="$1" - log_info "Fetching issues with label: $label" - echo "" - - response=$(curl -s "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/issues?labels=$label" \ - -H "Authorization: token $GITEA_TOKEN") - - echo "$response" | jq -r '.[] | - "🎯 #\(.number) \(.title)", - " 📊 \(.state) | 🔗 \(.html_url)", - ""' -} - -# Get issue details -get_issue_details() { - local issue_number="$1" - log_info "Fetching details for issue #$issue_number" - echo "" - - response=$(curl -s "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/issues/$issue_number" \ - -H "Authorization: token $GITEA_TOKEN") - - echo "$response" | jq -r ' - "🎯 Issue #\(.number): \(.title)", - "📊 State: \(.state)", - "👤 Assignees: \(.assignees | map(.login) | join(", ") // "none")", - "🏷️ Labels: \(.labels | map(.name) | join(", ") // "none")", - "📅 Created: \(.created_at)", - "🔗 URL: \(.html_url)", - "", - "📝 Body:", - "\(.body // "No description")", - ""' -} - -# Close issue -close_issue() { - local issue_number="$1" - log_info "Closing issue #$issue_number" - - response=$(curl -s -w "\n%{http_code}" -X PATCH \ - "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/issues/$issue_number" \ - -H "Authorization: token $GITEA_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"state": "closed"}') - - http_code=$(echo "$response" | tail -n1) - if [ "$http_code" = "201" ]; then - log_success "Issue #$issue_number closed" - else - echo "❌ Failed to close issue (HTTP: $http_code)" - fi -} - -# Get pipeline status (issues grouped by Kanban columns for Furt) -get_pipeline_status() { - log_info "Furt API Gateway - Pipeline Status Overview" - echo "" - - echo "🔧 SERVICE REQUESTS:" - get_issues_by_label "service-request" | head -10 - - echo "🏗️ ARCHITECTURE DISCUSSIONS:" - get_issues_by_label "architecture" - - echo "🚀 PERFORMANCE OPTIMIZATIONS:" - get_issues_by_label "performance" - - echo "🔒 SECURITY REVIEWS:" - get_issues_by_label "security" - - echo "🐛 BUGS:" - get_issues_by_label "bug" - - echo "🌐 HUGO INTEGRATIONS:" - get_issues_by_label "hugo-integration" - - echo "📋 WORK IN PROGRESS:" - get_issues_by_label "enhancement" | head -5 -} - -# Issue statistics -get_stats() { - log_info "Furt API Gateway - Issue Statistics" - echo "" - - all_issues=$(curl -s "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/issues" \ - -H "Authorization: token $GITEA_TOKEN") - - total=$(echo "$all_issues" | jq length) - open=$(echo "$all_issues" | jq '[.[] | select(.state == "open")] | length') - closed=$(echo "$all_issues" | jq '[.[] | select(.state == "closed")] | length') - - echo "📊 Total Issues: $total" - echo "✅ Open: $open" - echo "🔒 Closed: $closed" - echo "" - - echo "🏷️ Furt Labels:" - echo "$all_issues" | jq -r '[.[] | .labels[].name] | group_by(.) | map({label: .[0], count: length}) | sort_by(.count) | reverse | limit(10; .[]) | " \(.label): \(.count)"' -} - -case "${1:-help}" in - "all"|"") - get_all_issues - ;; - "gateway") - get_issues_by_label "gateway" - ;; - "service-request") - get_issues_by_label "service-request" - ;; - "service-formular2mail") - get_issues_by_label "service-formular2mail" - ;; - "service-sagjan") - get_issues_by_label "service-sagjan" - ;; - "architecture") - get_issues_by_label "architecture" - ;; - "performance") - get_issues_by_label "performance" - ;; - "security") - get_issues_by_label "security" - ;; - "bug") - get_issues_by_label "bug" - ;; - "enhancement") - get_issues_by_label "enhancement" - ;; - "hugo") - get_issues_by_label "hugo-integration" - ;; - "deployment") - get_issues_by_label "deployment" - ;; - "testing") - get_issues_by_label "testing" - ;; - "documentation") - get_issues_by_label "documentation" - ;; - "pipeline") - get_pipeline_status - ;; - "stats") - get_stats - ;; - "close") - if [ -z "$2" ]; then - echo "Usage: $0 close ISSUE_NUMBER" - exit 1 - fi - close_issue "$2" - ;; - [0-9]*) - get_issue_details "$1" - ;; - *) - echo "🎯 Furt API Gateway - Issues Manager" - echo "" - echo "Usage: $0 [COMMAND] [OPTIONS]" - echo "" - echo "📋 List Commands:" - echo " all List all issues (default)" - echo " gateway Gateway core issues" - echo " service-request New service requests" - echo " service-formular2mail Formular2mail service issues" - echo " service-sagjan Sagjan service issues" - echo " architecture Architecture discussions" - echo " performance Performance optimizations" - echo " security Security reviews" - echo " bug Bug reports" - echo " enhancement New features" - echo " hugo Hugo integration issues" - echo " deployment Deployment issues" - echo " testing Testing issues" - echo " documentation Documentation updates" - echo "" - echo "📊 Analysis Commands:" - echo " pipeline Kanban pipeline status" - echo " stats Issue statistics" - echo "" - echo "⚙️ Management Commands:" - echo " close NUM Close issue #NUM" - echo " NUM Show details for issue #NUM" - echo "" - echo "🚀 Examples:" - echo " $0 # List all issues" - echo " $0 pipeline # Show pipeline status" - echo " $0 service-request # Show service requests" - echo " $0 gateway # Show gateway issues" - echo " $0 5 # Show issue #5 details" - echo " $0 close 3 # Close issue #3" - ;; -esac - diff --git a/scripts/archive/update_issue_v1.sh b/scripts/archive/update_issue_v1.sh deleted file mode 100755 index b7c813c..0000000 --- a/scripts/archive/update_issue_v1.sh +++ /dev/null @@ -1,156 +0,0 @@ -#!/bin/bash - -# Load environment -if [ -f .env ]; then - export $(cat .env | grep -v '^#' | xargs) -else - echo "❌ .env file not found!" - exit 1 -fi - -# Colors -GREEN='\033[0;32m' -BLUE='\033[0;34m' -RED='\033[0;31m' -NC='\033[0m' - -log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } -log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } -log_error() { echo -e "${RED}[ERROR]${NC} $1"; } - -# Get all labels with IDs -declare -A LABEL_IDS -get_labels() { - response=$(curl -s "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/labels" \ - -H "Authorization: token $GITEA_TOKEN") - while IFS= read -r line; do - name=$(echo "$line" | jq -r '.name') - id=$(echo "$line" | jq -r '.id') - LABEL_IDS["$name"]="$id" - done < <(echo "$response" | jq -c '.[]') -} - -# Add comment to issue -add_comment() { - local issue_number="$1" - local comment="$2" - - # Use jq for proper JSON escaping - local json_payload=$(jq -n --arg body "$comment" '{body: $body}') - - response=$(curl -s -w "\n%{http_code}" -X POST \ - "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/issues/$issue_number/comments" \ - -H "Authorization: token $GITEA_TOKEN" \ - -H "Content-Type: application/json" \ - -d "$json_payload") - - http_code=$(echo "$response" | tail -n1) - if [ "$http_code" = "201" ]; then - log_success "Comment added to issue #$issue_number" - else - log_error "Failed to add comment (HTTP: $http_code)" - fi -} - -# Update issue labels - FIXED VERSION -update_labels() { - local issue_number="$1" - local labels_string="$2" - - get_labels - - # Convert to ID array - local valid_label_ids=() - IFS=',' read -ra LABEL_ARRAY <<< "$labels_string" - - for label in "${LABEL_ARRAY[@]}"; do - label=$(echo "$label" | xargs) - if [ -n "${LABEL_IDS[$label]}" ]; then - valid_label_ids+=("${LABEL_IDS[$label]}") - else - log_error "Label '$label' not found!" - return 1 - fi - done - - # Build ID array JSON - local labels_json="[" - for i in "${!valid_label_ids[@]}"; do - if [ $i -gt 0 ]; then - labels_json="${labels_json}," - fi - labels_json="${labels_json}${valid_label_ids[$i]}" - done - labels_json="${labels_json}]" - - response=$(curl -s -w "\n%{http_code}" -X PUT \ - "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/issues/$issue_number/labels" \ - -H "Authorization: token $GITEA_TOKEN" \ - -H "Content-Type: application/json" \ - -d "$labels_json") - - http_code=$(echo "$response" | tail -n1) - if [ "$http_code" = "200" ]; then - log_success "Labels updated for issue #$issue_number" - else - log_error "Failed to update labels (HTTP: $http_code)" - fi -} - -case "${1:-help}" in - "comment") - if [ -z "$2" ] || [ -z "$3" ]; then - echo "Usage: $0 comment ISSUE_NUMBER \"COMMENT_TEXT\"" - exit 1 - fi - add_comment "$2" "$3" - ;; - "labels") - if [ -z "$2" ] || [ -z "$3" ]; then - echo "Usage: $0 labels ISSUE_NUMBER \"label1,label2,label3\"" - exit 1 - fi - update_labels "$2" "$3" - ;; - "progress") - if [ -z "$2" ]; then - echo "Usage: $0 progress ISSUE_NUMBER" - exit 1 - fi - add_comment "$2" "📊 **Progress Update:** Arbeit an dieser Analyse läuft. Erste Quellen werden gesammelt und Framework-Relevanz geprüft." - update_labels "$2" "work-in-progress" - ;; - "review") - if [ -z "$2" ]; then - echo "Usage: $0 review ISSUE_NUMBER" - exit 1 - fi - add_comment "$2" "👀 **Ready for Review:** Erste Analyse abgeschlossen. Bitte um Peer-Review der Quellen und Framework-Integration." - update_labels "$2" "needs-review" - ;; - "fact-check") - if [ -z "$2" ]; then - echo "Usage: $0 fact-check ISSUE_NUMBER" - exit 1 - fi - add_comment "$2" "🔍 **Fact-Check Required:** Kritische Behauptungen gefunden die zusätzliche Quellen-Verifikation benötigen." - update_labels "$2" "fact-check-needed" - ;; - *) - echo "🔧 Issue Update Tool (FIXED VERSION)" - echo "" - echo "Usage: $0 COMMAND ISSUE_NUMBER [OPTIONS]" - echo "" - echo "Commands:" - echo " comment NUM \"TEXT\" Add comment to issue" - echo " labels NUM \"l1,l2\" Update issue labels (using IDs)" - echo " progress NUM Mark as work-in-progress" - echo " review NUM Mark as ready for review" - echo " fact-check NUM Mark as needing fact-check" - echo "" - echo "Examples:" - echo " $0 comment 5 \"Erste Quellen gefunden\"" - echo " $0 labels 3 \"regional-case,work-in-progress\"" - echo " $0 progress 7" - ;; -esac diff --git a/scripts/archive/update_script_labels_monster.sh b/scripts/archive/update_script_labels_monster.sh deleted file mode 100755 index 757c291..0000000 --- a/scripts/archive/update_script_labels_monster.sh +++ /dev/null @@ -1,491 +0,0 @@ -#!/bin/bash -# scripts/update_script_labels.sh -# Auto-updates all scripts with current label definitions from registry -# FINAL FIXED VERSION with corrected all_templates logic - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" -REGISTRY_FILE="$PROJECT_ROOT/configs/labels.registry" - -# Colors for logging -GREEN='\033[0;32m' -BLUE='\033[0;34m' -RED='\033[0;31m' -YELLOW='\033[1;33m' -NC='\033[0m' - -log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } -log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } -log_error() { echo -e "${RED}[ERROR]${NC} $1"; } -log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; } - -# Parse label registry into associative arrays -declare -A LABEL_COLORS -declare -A LABEL_CONTEXTS -declare -A LABEL_USAGES - -# Create registry file if it doesn't exist -create_registry_if_missing() { - if [[ ! -f "$REGISTRY_FILE" ]]; then - log_info "Creating label registry file..." - - mkdir -p "$(dirname "$REGISTRY_FILE")" - - cat > "$REGISTRY_FILE" << 'EOF' -# Central Label Registry for Furt API Gateway Project -# Format: name:color:context:usage_contexts -# This file is the single source of truth for all labels - -# === CORE WORKFLOW LABELS === -service-request:7057ff:new_service:service_templates,status_updates -enhancement:84b6eb:improvement:all_templates -bug:d73a4a:error:bug_template,status_updates -question:d876e3:discussion:question_template - -# === COMPONENT CATEGORIES === -gateway:0052cc:gateway_core:architecture_template,performance_template,service_templates -performance:fbca04:optimization:performance_template,architecture_template -architecture:d4c5f9:design:architecture_template,gateway -security:28a745:security_review:security_template,architecture_template -configuration:f9d71c:config_management:deployment_template,architecture_template - -# === SERVICE-SPECIFIC LABELS === -service-formular2mail:1d76db:formular2mail:formular2mail_integration -service-sagjan:1d76db:sagjan:sagjan_integration -service-newsletter:ff6b6b:newsletter:newsletter_integration - -# === WORKFLOW STATE LABELS === -work-in-progress:fbca04:active:status_updates -needs-review:0e8a16:review:status_updates -blocked:d73a4a:blocked:status_updates -ready-for-deployment:28a745:deploy_ready:status_updates - -# === INTEGRATION LABELS === -hugo-integration:ff7518:frontend:hugo_templates,integration -api-contract:5319e7:api_design:api_templates,service_templates -breaking-change:d73a4a:breaking:api_templates,architecture_template - -# === PRIORITY LABELS === -high-priority:d73a4a:urgent:all_templates -low-priority:0e8a16:nice_to_have:all_templates - -# === META LABELS === -low-tech:6f42c1:low_tech_principle:architecture_template,performance_template,security_template -digital-sovereignty:6f42c1:digital_sovereignty:architecture_template,performance_template,security_template -good-first-issue:7057ff:beginner_friendly:manual_assignment -help-wanted:159818:community_help:manual_assignment - -# === DEPLOYMENT LABELS === -deployment:ff7518:deployment:deployment_template -testing:f9d71c:testing:testing_template,integration -EOF - - log_success "Created label registry: $REGISTRY_FILE" - fi -} - -parse_registry() { - create_registry_if_missing - - log_info "Parsing label registry..." - - while IFS= read -r line; do - # Skip comments and empty lines - [[ "$line" =~ ^#.*$ ]] && continue - [[ -z "$line" ]] && continue - - # Parse format: name:color:context:usage_contexts - if [[ "$line" =~ ^([^:]+):([^:]+):([^:]+):(.+)$ ]]; then - local name="${BASH_REMATCH[1]}" - local color="${BASH_REMATCH[2]}" - local context="${BASH_REMATCH[3]}" - local usage="${BASH_REMATCH[4]}" - - LABEL_COLORS["$name"]="$color" - LABEL_CONTEXTS["$name"]="$context" - LABEL_USAGES["$name"]="$usage" - - log_info "Loaded label: $name ($context)" - fi - done < "$REGISTRY_FILE" - - log_success "Loaded ${#LABEL_COLORS[@]} labels from registry" -} - -# Generate label definitions section for scripts -generate_label_definitions() { - cat << 'EOF' -# === LABEL DEFINITIONS START === -# This section is auto-maintained by update_script_labels.sh -# DO NOT EDIT MANUALLY - Changes will be overwritten -declare -A LABEL_DEFINITIONS=( -EOF - - for label in "${!LABEL_COLORS[@]}"; do - local color="${LABEL_COLORS[$label]}" - local context="${LABEL_CONTEXTS[$label]}" - local usage="${LABEL_USAGES[$label]}" - - echo " [\"$label\"]=\"color:$color;context:$context;usage:$usage\"" - done - - cat << 'EOF' -) - -# Extract label info -get_label_color() { echo "${LABEL_DEFINITIONS[$1]}" | cut -d';' -f1 | cut -d':' -f2; } -get_label_context() { echo "${LABEL_DEFINITIONS[$1]}" | cut -d';' -f2 | cut -d':' -f2; } -get_label_usage() { echo "${LABEL_DEFINITIONS[$1]}" | cut -d';' -f3 | cut -d':' -f2; } - -# Check if label is valid for context -is_label_valid_for_context() { - local label="$1" - local context="$2" - local usage=$(get_label_usage "$label") - [[ "$usage" == *"$context"* ]] || [[ "$usage" == "all_templates" ]] -} -# === LABEL DEFINITIONS END === -EOF -} - -# Generate template-to-labels mapping - FIXED VERSION -generate_template_mappings() { - cat << 'EOF' - -# === TEMPLATE LABEL MAPPINGS START === -# Auto-generated template to label mappings -declare -A TEMPLATE_LABELS=( -EOF - - # FIXED: Consistent template names with corrected all_templates logic - declare -A template_mappings=( - ["service"]="service_templates" - ["architecture"]="architecture_template" - ["performance"]="performance_template" - ["bug"]="bug_template" - ["security"]="security_template" - ["hugo"]="hugo_templates" - ["api"]="api_templates" - ["deployment"]="deployment_template" - ) - - for template_name in "${!template_mappings[@]}"; do - local template_usage="${template_mappings[$template_name]}" - local labels=() - - # FIXED: First add all_templates labels to every template - for label in "${!LABEL_USAGES[@]}"; do - local usage="${LABEL_USAGES[$label]}" - - # EXCLUDE service-specific labels from all templates - if [[ "$label" == service-* ]]; then - continue - fi - - # Skip manual assignment labels - if [[ "$usage" == "manual_assignment" ]]; then - continue - fi - - # FIXED: Add all_templates labels to every template first - if [[ "$usage" == "all_templates" ]]; then - labels+=("$label") - continue - fi - - # Add template-specific labels - if [[ "$usage" == *"$template_usage"* ]]; then - labels+=("$label") - fi - done - - if [[ ${#labels[@]} -gt 0 ]]; then - local label_list=$(IFS=','; echo "${labels[*]}") - echo " [\"$template_name\"]=\"$label_list\"" - fi - done - - cat << 'EOF' -) -# === TEMPLATE LABEL MAPPINGS END === -EOF -} - -# Generate filter options for get_issues.sh -generate_filter_options() { - cat << 'EOF' - -# === FILTER OPTIONS START === -# Auto-generated filter options for get_issues.sh -show_filter_help() { - echo "📋 Available filters:" -EOF - - # Group labels by context for better help display - declare -A context_labels - for label in "${!LABEL_CONTEXTS[@]}"; do - local context="${LABEL_CONTEXTS[$label]}" - if [[ -z "${context_labels[$context]}" ]]; then - context_labels[$context]="$label" - else - context_labels[$context]="${context_labels[$context]},$label" - fi - done - - for context in "${!context_labels[@]}"; do - echo " echo \" $context: ${context_labels[$context]}\"" - done - - cat << 'EOF' -} - -# Filter case statement -handle_filter() { - local filter="$1" - case "$filter" in -EOF - - for label in "${!LABEL_COLORS[@]}"; do - echo " $label) filter_by_label \"$label\" ;;" - done - - cat << 'EOF' - pipeline) show_pipeline_overview ;; - stats) show_statistics ;; - all) show_all_issues ;; - *) - log_error "Unknown filter: $filter" - show_filter_help - exit 1 - ;; - esac -} -# === FILTER OPTIONS END === -EOF -} - -# Update a single script file -update_script_file() { - local script_file="$1" - - if [[ ! -f "$script_file" ]]; then - log_warning "Script not found: $script_file" - return 1 - fi - - log_info "Updating $script_file..." - - # Create backup - cp "$script_file" "${script_file}.backup" - - # Check if script has label definition sections - if ! grep -q "# === LABEL DEFINITIONS START ===" "$script_file"; then - log_warning "$script_file has no label definitions section - skipping" - return 0 - fi - - # Find section boundaries - local start_line=$(grep -n "# === LABEL DEFINITIONS START ===" "$script_file" | cut -d: -f1) - local end_line=$(grep -n "# === LABEL DEFINITIONS END ===" "$script_file" | cut -d: -f1) - - if [[ -z "$start_line" ]] || [[ -z "$end_line" ]]; then - log_error "Malformed label definition section in $script_file" - return 1 - fi - - # Generate new content - local new_definitions=$(generate_label_definitions) - - # Handle template mappings if present - if grep -q "# === TEMPLATE LABEL MAPPINGS START ===" "$script_file"; then - new_definitions+="\n$(generate_template_mappings)" - fi - - # Handle filter options if present (for get_issues.sh) - if grep -q "# === FILTER OPTIONS START ===" "$script_file"; then - new_definitions+="\n$(generate_filter_options)" - fi - - # Create temporary file with updated content - local temp_file=$(mktemp) - - # Copy everything before label definitions - sed -n "1,$((start_line-1))p" "$script_file" > "$temp_file" - - # Add new definitions - echo -e "$new_definitions" >> "$temp_file" - - # Copy everything after label definitions (find new end line) - if grep -q "# === TEMPLATE LABEL MAPPINGS END ===" "$script_file"; then - local actual_end_line=$(grep -n "# === TEMPLATE LABEL MAPPINGS END ===" "$script_file" | cut -d: -f1) - elif grep -q "# === FILTER OPTIONS END ===" "$script_file"; then - local actual_end_line=$(grep -n "# === FILTER OPTIONS END ===" "$script_file" | cut -d: -f1) - else - local actual_end_line="$end_line" - fi - - sed -n "$((actual_end_line+1)),\$p" "$script_file" >> "$temp_file" - - # Replace original file - mv "$temp_file" "$script_file" - chmod +x "$script_file" - - log_success "Updated $script_file" -} - -# Add new label to registry -add_label_to_registry() { - local name="$1" - local color="${2:-ff6b6b}" - local context="${3:-auto_generated}" - local usage="${4:-manual_assignment}" - - # Skip if called during auto-update to prevent loops - if [[ "${FURT_AUTO_UPDATE:-}" == "true" ]]; then - log_info "Auto-update mode: Adding $name to registry (skipping rebuild)" - else - log_info "Adding new label to registry: $name" - fi - - # Ensure registry exists - create_registry_if_missing - - # Check if label already exists - if grep -q "^$name:" "$REGISTRY_FILE"; then - log_warning "Label $name already exists in registry" - return 0 - fi - - # Add to appropriate section (determine by context) - local section_marker="# === CORE WORKFLOW LABELS ===" - if [[ "$context" == *"service"* ]] || [[ "$name" == service-* ]]; then - section_marker="# === SERVICE-SPECIFIC LABELS ===" - elif [[ "$context" == *"workflow"* ]]; then - section_marker="# === WORKFLOW STATE LABELS ===" - elif [[ "$context" == *"component"* ]]; then - section_marker="# === COMPONENT CATEGORIES ===" - fi - - # Create backup - cp "$REGISTRY_FILE" "${REGISTRY_FILE}.backup" - - # Find section and add label - local temp_file=$(mktemp) - local added=false - - while IFS= read -r line; do - echo "$line" >> "$temp_file" - - if [[ "$line" == "$section_marker" ]] && [[ "$added" == false ]]; then - echo "$name:$color:$context:$usage" >> "$temp_file" - added=true - log_success "Added label $name to registry" - fi - done < "$REGISTRY_FILE" - - mv "$temp_file" "$REGISTRY_FILE" -} - -# Show current registry status -show_registry_status() { - echo "📊 Label Registry Status" - echo "========================" - echo "Registry file: $REGISTRY_FILE" - - if [[ -f "$REGISTRY_FILE" ]]; then - echo "Total labels: $(grep -c "^[^#]" "$REGISTRY_FILE" 2>/dev/null || echo 0)" - echo "" - echo "Labels by category:" - - local current_section="" - while IFS= read -r line; do - if [[ "$line" =~ ^#\ ===.*===\ $ ]]; then - current_section=$(echo "$line" | sed 's/# === \(.*\) ===/\1/') - echo " $current_section:" - elif [[ "$line" =~ ^[^#]+: ]] && [[ -n "$current_section" ]]; then - local label_name=$(echo "$line" | cut -d: -f1) - echo " - $label_name" - fi - done < "$REGISTRY_FILE" - else - echo "Registry file not found!" - fi -} - -# Main function -main() { - local command="${1:-update}" - - case "$command" in - update) - log_info "Starting label synchronization..." - parse_registry - - # Update all script files - local scripts=( - "$PROJECT_ROOT/scripts/create_issue.sh" - "$PROJECT_ROOT/scripts/get_issues.sh" - "$PROJECT_ROOT/scripts/update_issue.sh" - ) - - for script in "${scripts[@]}"; do - update_script_file "$script" - done - - log_success "All scripts synchronized with label registry!" - ;; - - add) - local name="$2" - local color="${3:-ff6b6b}" - local context="${4:-auto_generated}" - local usage="${5:-manual_assignment}" - - if [[ -z "$name" ]]; then - log_error "Usage: $0 add [color] [context] [usage]" - exit 1 - fi - - add_label_to_registry "$name" "$color" "$context" "$usage" - - # Only update scripts if not in auto-update mode - if [[ "${FURT_AUTO_UPDATE:-}" != "true" ]]; then - parse_registry - main update - fi - ;; - - status) - show_registry_status - ;; - - help) - echo "Usage: $0 [command] [options]" - echo "" - echo "Commands:" - echo " update Update all scripts with current registry" - echo " add Add new label to registry and update scripts" - echo " status Show registry status" - echo " help Show this help" - echo "" - echo "Examples:" - echo " $0 update" - echo " $0 add newsletter ff6b6b newsletter_service service_templates" - echo " $0 status" - ;; - - *) - log_error "Unknown command: $command" - main help - exit 1 - ;; - esac -} - -# Run if executed directly -if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - main "$@" -fi - diff --git a/furt-lua/scripts/cleanup_debug.sh b/scripts/cleanup_debug.sh similarity index 100% rename from furt-lua/scripts/cleanup_debug.sh rename to scripts/cleanup_debug.sh diff --git a/scripts/deploy/deploy_aitvaras.sh b/scripts/deploy/deploy_aitvaras.sh deleted file mode 100755 index 3220f6f..0000000 --- a/scripts/deploy/deploy_aitvaras.sh +++ /dev/null @@ -1,966 +0,0 @@ -#!/bin/bash -# scripts/deploy/deploy_aitvaras.sh -# Deployment script: karl (development) → aitvaras (Ubuntu 24.04 production) -# -# Usage: -# ./scripts/deploy/deploy_aitvaras.sh [--dry-run] [--rollback] [--force] -# -# Dragons@Work - Furt API-Gateway Production Deployment -# Version: 1.0 - -set -euo pipefail # Exit on error, undefined vars, pipe failures - -# ============================================================================= -# CONFIGURATION -# ============================================================================= - -# Source (karl development) -SOURCE_DIR="/home/michael/Develop/DAW/furt/furt-lua" -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")" # scripts/deploy/ -> scripts/ -> furt/ - -# Target (aitvaras Ubuntu production) -AITVARAS_HOST="aitvaras" # Assumes SSH config entry (as michael user) -TARGET_DIR="/opt/furt-api" -SERVICE_USER="_furt" -SERVICE_GROUP="_furt" -SERVICE_NAME="furt-api" - -# Ubuntu-specific paths -CONFIG_DIR="/etc/furt" # ← Angepasst: wie start.sh erwartet -LOG_DIR="/var/log/furt-api" -RUN_DIR="/var/run/furt-api" -BACKUP_DIR="/var/backup/furt-api" -SYSTEMD_SERVICE="/etc/systemd/system/furt-api.service" - -# Backup configuration -BACKUP_RETENTION=5 # Keep last 5 backups (production = more backups) - -# Health check configuration -HEALTH_URL="http://localhost:8080/health" -HEALTH_TIMEOUT=15 # Longer timeout for production -HEALTH_RETRIES=5 # More retries for production - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -PURPLE='\033[0;35m' -NC='\033[0m' # No Color - -# ============================================================================= -# LOGGING FUNCTIONS -# ============================================================================= - -log_info() { - echo -e "${BLUE}[INFO]${NC} $1" -} - -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $1" -} - -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $1" -} - -log_step() { - echo -e "\n${PURPLE}==>${NC} $1" -} - -log_production() { - echo -e "${PURPLE}[PRODUCTION]${NC} $1" -} - -# ============================================================================= -# UTILITY FUNCTIONS -# ============================================================================= - -usage() { - cat << EOF -Usage: $0 [OPTIONS] - -🚨 PRODUCTION deployment script for furt-api: karl → aitvaras (Ubuntu 24.04) - -⚠️ WARNING: This deploys to PRODUCTION server aitvaras! -⚠️ All changes are immediately live and affect production services. - -🔐 SECURITY NOTE: - This script temporarily enables passwordless sudo for deployment, - then automatically disables it for security. - -OPTIONS: - --dry-run Show what would be deployed without making changes - --rollback Rollback to previous deployment - --force Skip confirmation prompts (USE WITH CAUTION!) - --help Show this help message - -EXAMPLES: - $0 # Normal production deployment with confirmation - $0 --dry-run # Preview deployment without changes (RECOMMENDED) - $0 --force # Deploy without confirmation (DANGEROUS!) - $0 --rollback # Rollback to previous version - -PRODUCTION SAFETY: - - Always run --dry-run first - - Creates automatic backups before deployment - - Health checks ensure successful deployment - - Rollback available if deployment fails - - Passwordless sudo automatically disabled after deployment - -EOF -} - -check_dependencies() { - log_step "Checking dependencies for production deployment" - - # Check if source directory exists - if [[ ! -d "$SOURCE_DIR" ]]; then - log_error "Source directory not found: $SOURCE_DIR" - exit 1 - fi - - # Check SSH connectivity to aitvaras - if ! ssh -o ConnectTimeout=10 -o BatchMode=yes "$AITVARAS_HOST" exit 2>/dev/null; then - log_error "Cannot connect to aitvaras via SSH" - log_info "Please ensure SSH key is set up for $AITVARAS_HOST" - exit 1 - fi - - # Enable passwordless sudo for deployment - if ! enable_passwordless_sudo; then - log_error "Cannot enable passwordless sudo on aitvaras" - exit 1 - fi - - # Check rsync availability - if ! command -v rsync &> /dev/null; then - log_error "rsync is required but not installed" - exit 1 - fi - - log_success "All dependencies OK" -} - -get_backup_timestamp() { - date +"%Y%m%d_%H%M%S" -} - -# ============================================================================= -# SSH REMOTE EXECUTION FUNCTIONS -# ============================================================================= - -aitvaras_exec() { - ssh "$AITVARAS_HOST" "$@" -} - -aitvaras_exec_sudo() { - ssh "$AITVARAS_HOST" "sudo $@" -} - -aitvaras_exec_as_furt() { - ssh "$AITVARAS_HOST" "sudo -u $SERVICE_USER $@" -} - -# ============================================================================= -# SUDO AUTHENTICATION FUNCTIONS -# ============================================================================= - -enable_passwordless_sudo() { - log_step "Enabling temporary passwordless sudo" - - if [[ "$DRY_RUN" == "true" ]]; then - log_info "DRY RUN: Would enable passwordless sudo temporarily" - return 0 - fi - - log_info "Testing current sudo access..." - - # Test if sudo works without password (already configured) - if ssh "$AITVARAS_HOST" "sudo -n true" 2>/dev/null; then - log_success "Passwordless sudo already enabled" - return 0 - fi - - # Need to enable passwordless sudo temporarily - log_info "Setting up temporary passwordless sudo for deployment..." - log_warning "⚠️ This will require your password ONCE to enable passwordless mode" - - # Use ssh -t for interactive terminal to set up passwordless mode - if ssh -t "$AITVARAS_HOST" "echo 'michael ALL=(ALL) NOPASSWD: ALL' | sudo tee /etc/sudoers.d/99-deployment-passwordless"; then - - # Verify it works - if ssh "$AITVARAS_HOST" "sudo -n true" 2>/dev/null; then - log_success "Temporary passwordless sudo enabled" - return 0 - else - log_error "Failed to verify passwordless sudo" - return 1 - fi - else - log_error "Failed to enable passwordless sudo" - return 1 - fi -} - -disable_passwordless_sudo() { - if [[ "$DRY_RUN" != "true" ]]; then - log_info "Removing temporary passwordless sudo configuration..." - ssh "$AITVARAS_HOST" "sudo rm -f /etc/sudoers.d/99-deployment-passwordless" 2>/dev/null || true - log_success "Passwordless sudo disabled - security restored" - fi -} - -# ============================================================================= -# USER MANAGEMENT FUNCTIONS -# ============================================================================= - -create_service_user() { - log_step "Creating service user" - - if [[ "$DRY_RUN" == "true" ]]; then - log_info "DRY RUN: Would create user $SERVICE_USER with group $SERVICE_GROUP" - return 0 - fi - - # Check if user already exists - if aitvaras_exec "id $SERVICE_USER &>/dev/null"; then - log_info "User $SERVICE_USER already exists" - return 0 - fi - - log_info "Creating system user: $SERVICE_USER" - - # Create system user (Ubuntu style) - aitvaras_exec_sudo "useradd --system --shell /usr/sbin/nologin --home-dir $TARGET_DIR --create-home --user-group $SERVICE_USER" - - # Verify user creation - if aitvaras_exec "id $SERVICE_USER &>/dev/null"; then - log_success "User $SERVICE_USER created successfully" - else - log_error "Failed to create user $SERVICE_USER" - return 1 - fi -} - -# ============================================================================= -# DIRECTORY MANAGEMENT FUNCTIONS -# ============================================================================= - -create_directories() { - log_step "Creating directory structure" - - if [[ "$DRY_RUN" == "true" ]]; then - log_info "DRY RUN: Would create directories:" - local directories=("$TARGET_DIR" "$CONFIG_DIR" "$LOG_DIR" "$RUN_DIR" "$BACKUP_DIR") - for dir in "${directories[@]}"; do - echo " - $dir" - done - return 0 - fi - - log_info "Creating directory structure..." - - # Create all directories in one sudo call - aitvaras_exec_sudo "mkdir -p $TARGET_DIR $CONFIG_DIR $LOG_DIR $RUN_DIR $BACKUP_DIR" - - # Set ownership and permissions in batch - aitvaras_exec_sudo "chown $SERVICE_USER:$SERVICE_GROUP $TARGET_DIR $CONFIG_DIR $LOG_DIR $RUN_DIR $BACKUP_DIR" - aitvaras_exec_sudo "chmod 755 $TARGET_DIR $CONFIG_DIR $RUN_DIR $BACKUP_DIR" - aitvaras_exec_sudo "chmod 750 $LOG_DIR" - - log_success "Directory structure created" -} - -# ============================================================================= -# BACKUP FUNCTIONS -# ============================================================================= - -create_backup() { - local timestamp=$(get_backup_timestamp) - local backup_path="$BACKUP_DIR/furt-api_$timestamp" - - log_step "Creating production backup" - - if [[ "$DRY_RUN" == "true" ]]; then - log_info "DRY RUN: Would create backup at $backup_path" - echo "" - return - fi - - # Create backup directory if it doesn't exist - aitvaras_exec_sudo "mkdir -p $BACKUP_DIR" - aitvaras_exec_sudo "chown $SERVICE_USER:$SERVICE_GROUP $BACKUP_DIR" - - # Check if target directory exists and has content - if aitvaras_exec "test -d $TARGET_DIR && test -n \"\$(ls -A $TARGET_DIR 2>/dev/null)\""; then - log_production "Backing up current deployment to: $backup_path" - aitvaras_exec_sudo "cp -r $TARGET_DIR $backup_path" - aitvaras_exec_sudo "chown -R $SERVICE_USER:$SERVICE_GROUP $backup_path" - - # Set backup metadata - aitvaras_exec_sudo "sh -c \"echo 'Backup created: \$(date)' > $backup_path/.backup_info\"" - aitvaras_exec_sudo "sh -c \"echo 'Original path: $TARGET_DIR' >> $backup_path/.backup_info\"" - aitvaras_exec_sudo "sh -c \"echo 'Service: $SERVICE_NAME' >> $backup_path/.backup_info\"" - aitvaras_exec_sudo "sh -c \"echo 'Host: \$(hostname)' >> $backup_path/.backup_info\"" - aitvaras_exec_sudo "chown $SERVICE_USER:$SERVICE_GROUP $backup_path/.backup_info" - - log_success "Production backup created: $backup_path" - echo "$backup_path" # Return backup path - else - log_warning "No existing deployment found or directory empty, skipping backup" - echo "" # Return empty string - fi -} - -cleanup_old_backups() { - log_step "Cleaning up old backups" - - if [[ "$DRY_RUN" == "true" ]]; then - log_info "DRY RUN: Would cleanup old backups (keep last $BACKUP_RETENTION)" - return 0 - fi - - local backup_count=$(aitvaras_exec "ls -1 $BACKUP_DIR/furt-api_* 2>/dev/null | wc -l" || echo "0") - - if [[ $backup_count -gt $BACKUP_RETENTION ]]; then - log_info "Found $backup_count backups, keeping last $BACKUP_RETENTION" - aitvaras_exec_sudo "ls -1t $BACKUP_DIR/furt-api_* | tail -n +$((BACKUP_RETENTION + 1)) | xargs rm -rf" - log_success "Old backups cleaned up" - else - log_info "Found $backup_count backups, no cleanup needed" - fi -} - -list_backups() { - log_step "Available production backups" - - if aitvaras_exec "ls -1 $BACKUP_DIR/furt-api_* 2>/dev/null"; then - aitvaras_exec "ls -1t $BACKUP_DIR/furt-api_* | head -n 5 | while read backup; do - echo \" \$backup\" - if [ -f \"\$backup/.backup_info\" ]; then - cat \"\$backup/.backup_info\" | sed 's/^/ /' - fi - echo - done" - else - log_warning "No backups found" - fi -} - -rollback_deployment() { - log_step "Rolling back production deployment" - - # List available backups - local latest_backup=$(aitvaras_exec "ls -1t $BACKUP_DIR/furt-api_* 2>/dev/null | head -n 1" || echo "") - - if [[ -z "$latest_backup" ]]; then - log_error "No backups available for rollback" - exit 1 - fi - - log_production "Latest backup: $latest_backup" - - if [[ "$FORCE" != "true" ]]; then - echo -n "⚠️ PRODUCTION ROLLBACK - Continue? [y/N]: " - read -r response - if [[ ! "$response" =~ ^[Yy]$ ]]; then - log_info "Rollback cancelled" - exit 0 - fi - fi - - # Stop service - stop_service - - # Backup current version (before rollback) - local rollback_backup=$(create_backup) - if [[ -n "$rollback_backup" ]]; then - aitvaras_exec_sudo "mv $rollback_backup ${rollback_backup}_pre_rollback" - fi - - # Restore from backup - aitvaras_exec_sudo "rm -rf $TARGET_DIR" - aitvaras_exec_sudo "cp -r $latest_backup $TARGET_DIR" - - # Fix permissions - fix_permissions - - # Start service - start_service - - # Health check - if health_check; then - log_success "Production rollback completed successfully" - else - log_error "Rollback completed but health check failed" - exit 1 - fi -} - -# ============================================================================= -# SERVICE MANAGEMENT FUNCTIONS -# ============================================================================= - -create_systemd_service() { - log_step "Creating systemd service" - - if [[ "$DRY_RUN" == "true" ]]; then - log_info "DRY RUN: Would create $SYSTEMD_SERVICE" - return 0 - fi - - log_info "Creating systemd service: $SERVICE_NAME" - - # Create systemd service file using sudo tee - aitvaras_exec_sudo "tee $SYSTEMD_SERVICE > /dev/null << 'EOF' -[Unit] -Description=Furt API Gateway -Documentation=https://gitea.dragons-at-work.de/DAW/furt -After=network.target - -[Service] -Type=forking -User=$SERVICE_USER -Group=$SERVICE_GROUP -WorkingDirectory=$TARGET_DIR -ExecStart=$TARGET_DIR/scripts/start.sh -Restart=always -RestartSec=5 -StandardOutput=journal -StandardError=journal - -# Security settings -NoNewPrivileges=true -PrivateTmp=true -ProtectHome=true -ProtectSystem=strict -ReadWritePaths=$TARGET_DIR $LOG_DIR $RUN_DIR - -# Environment -Environment=LUA_COMMAND=/usr/bin/lua5.1 -Environment=LUA_VERSION=5.1 - -[Install] -WantedBy=multi-user.target -EOF" - - # Reload systemd - aitvaras_exec_sudo "systemctl daemon-reload" - - # Enable service - aitvaras_exec_sudo "systemctl enable $SERVICE_NAME" - - log_success "Systemd service created and enabled" -} - -get_service_status() { - if aitvaras_exec "systemctl is-active $SERVICE_NAME >/dev/null 2>&1"; then - echo "running" - else - echo "stopped" - fi -} - -stop_service() { - log_step "Stopping $SERVICE_NAME service" - - if [[ "$DRY_RUN" == "true" ]]; then - log_info "DRY RUN: Would stop $SERVICE_NAME service" - return 0 - fi - - local status=$(get_service_status) - - if [[ "$status" == "running" ]]; then - log_production "Stopping production service: $SERVICE_NAME" - aitvaras_exec_sudo "systemctl stop $SERVICE_NAME" - - # Wait for service to stop - local attempts=0 - while [[ $attempts -lt 10 ]]; do - if [[ $(get_service_status) == "stopped" ]]; then - log_success "Service stopped successfully" - return 0 - fi - sleep 1 - ((attempts++)) - done - - log_error "Service did not stop within 10 seconds" - return 1 - else - log_info "Service already stopped" - fi -} - -start_service() { - log_step "Starting $SERVICE_NAME service" - - if [[ "$DRY_RUN" == "true" ]]; then - log_info "DRY RUN: Would start $SERVICE_NAME service" - return 0 - fi - - local status=$(get_service_status) - if [[ "$status" == "stopped" ]]; then - # Check port availability before starting - if ! check_port_availability; then - log_error "Cannot start service - port 8080 is occupied" - return 1 - fi - - log_production "Starting production service: $SERVICE_NAME" - aitvaras_exec_sudo "systemctl start $SERVICE_NAME" - - # Wait and check if service actually started - sleep 3 - - local new_status=$(get_service_status) - - if [[ "$new_status" == "running" ]]; then - log_success "Service started successfully" - else - log_error "Failed to start service" - # Show service logs for debugging - aitvaras_exec_sudo "journalctl -u $SERVICE_NAME --no-pager -n 20" || true - return 1 - fi - else - log_info "Service already running" - fi -} - -# ============================================================================= -# DEPLOYMENT FUNCTIONS -# ============================================================================= - -prepare_source() { - log_step "Preparing source files" - - # Check source directory structure - local required_dirs=("src" "config" "scripts") - for dir in "${required_dirs[@]}"; do - if [[ ! -d "$SOURCE_DIR/$dir" ]]; then - log_error "Required directory missing: $SOURCE_DIR/$dir" - exit 1 - fi - done - - # Check required files - local required_files=("src/main.lua" "scripts/start.sh") - for file in "${required_files[@]}"; do - if [[ ! -f "$SOURCE_DIR/$file" ]]; then - log_error "Required file missing: $SOURCE_DIR/$file" - exit 1 - fi - done - - log_success "Source files validated" -} - -sync_files() { - log_step "Syncing files to production" - - # Prepare rsync excludes - local excludes=( - "--exclude=.env" - "--exclude=.env.*" - "--exclude=*.backup" - "--exclude=*.orig" - "--exclude=*.tmp" - "--exclude=.git/" - "--exclude=.DS_Store" - "--exclude=logs/" - "--exclude=*.log" - "--exclude=tests/" - ) - - if [[ "$DRY_RUN" == "true" ]]; then - log_info "DRY RUN: Would sync the following files:" - rsync -avz --dry-run "${excludes[@]}" "$SOURCE_DIR/" "$AITVARAS_HOST:$TARGET_DIR/" - return 0 - fi - - # Clean up target directory and recreate with correct permissions - log_info "Preparing target directory for sync..." - aitvaras_exec_sudo "rm -rf $TARGET_DIR" - aitvaras_exec_sudo "mkdir -p $TARGET_DIR" - aitvaras_exec_sudo "chown michael:michael $TARGET_DIR" - - # Sync files as michael user (now has permissions) - log_production "Syncing files to production..." - rsync -avz "${excludes[@]}" \ - -e "ssh" \ - "$SOURCE_DIR/" "$AITVARAS_HOST:$TARGET_DIR/" - - # Fix ownership to _furt after successful sync - aitvaras_exec_sudo "chown -R $SERVICE_USER:$SERVICE_GROUP $TARGET_DIR" - - log_success "Files synced to production" -} - -create_environment_file() { - log_step "Creating environment configuration" - - if [[ "$DRY_RUN" == "true" ]]; then - if aitvaras_exec "test -f $CONFIG_DIR/environment"; then - log_info "DRY RUN: Environment file exists - would NOT overwrite" - else - log_info "DRY RUN: Would create $CONFIG_DIR/environment" - fi - return 0 - fi - - # Check if environment file already exists - if aitvaras_exec "test -f $CONFIG_DIR/environment"; then - log_success "Environment file already exists - preserving existing configuration" - log_info "Existing config preserved at: $CONFIG_DIR/environment" - - # Still fix permissions in case they got messed up - aitvaras_exec_sudo "chown $SERVICE_USER:$SERVICE_GROUP $CONFIG_DIR/environment" - aitvaras_exec_sudo "chmod 640 $CONFIG_DIR/environment" - - return 0 - fi - - log_info "Creating new environment file..." - - # Create environment file for production using sudo tee - aitvaras_exec_sudo "tee $CONFIG_DIR/environment > /dev/null << 'EOF' -# Furt API Production Environment Configuration -# Created by deploy_aitvaras.sh on $(date) - -# Lua Configuration -LUA_COMMAND=/usr/bin/lua5.1 -LUA_VERSION=5.1 - -# Server Configuration -SERVER_HOST=127.0.0.1 -SERVER_PORT=8080 - -# Logging -LOG_LEVEL=info -LOG_FILE=$LOG_DIR/furt-api.log - -# SMTP Configuration (to be filled manually) -# SMTP_USERNAME=your_smtp_username -# SMTP_PASSWORD=your_smtp_password -# SMTP_HOST=mail.dragons-at-work.de -# SMTP_PORT=465 -# SMTP_FROM=noreply@dragons-at-work.de -# SMTP_TO=michael@dragons-at-work.de - -# Security -# Add your production SMTP credentials here manually after deployment -EOF" - - # Set proper permissions immediately - aitvaras_exec_sudo "chown $SERVICE_USER:$SERVICE_GROUP $CONFIG_DIR/environment" - aitvaras_exec_sudo "chmod 640 $CONFIG_DIR/environment" - - log_success "Environment file created with correct permissions" - log_warning "⚠️ MANUAL STEP REQUIRED: Add SMTP credentials to $CONFIG_DIR/environment" -} - -fix_permissions() { - log_step "Fixing permissions" - - if [[ "$DRY_RUN" == "true" ]]; then - log_info "DRY RUN: Would set ownership to $SERVICE_USER:$SERVICE_GROUP" - return 0 - fi - - # Set ownership in batch - aitvaras_exec_sudo "chown -R $SERVICE_USER:$SERVICE_GROUP $TARGET_DIR $CONFIG_DIR $LOG_DIR $RUN_DIR" - - # Set permissions for scripts - aitvaras_exec_sudo "chmod +x $TARGET_DIR/scripts/*.sh" - - # Set permissions for config directory - aitvaras_exec_sudo "chmod 755 $CONFIG_DIR" - - # Set permissions for environment file only if it exists - if aitvaras_exec "test -f $CONFIG_DIR/environment"; then - aitvaras_exec_sudo "chmod 640 $CONFIG_DIR/environment" - fi - - log_success "Permissions fixed" -} - -# ============================================================================= -# HEALTH CHECK FUNCTIONS -# ============================================================================= - -check_port_availability() { - log_step "Checking port availability" - - local port="8080" # furt-api port - - if aitvaras_exec "netstat -an | grep -q ':$port'"; then - log_warning "Port $port is already in use" - aitvaras_exec "netstat -an | grep ':$port'" || true - - # Try to identify what's using the port - log_info "Checking what's using port $port..." - aitvaras_exec "sudo ss -tlnp | grep ':$port'" || true - - return 1 - else - log_success "Port $port is available" - return 0 - fi -} - -health_check() { - log_step "Running production health check" - - local retries=$HEALTH_RETRIES - while [[ $retries -gt 0 ]]; do - log_info "Health check attempt $((HEALTH_RETRIES - retries + 1))/$HEALTH_RETRIES" - - if aitvaras_exec "curl -s --max-time $HEALTH_TIMEOUT $HEALTH_URL >/dev/null 2>&1"; then - log_success "Production health check passed" - - # Get health check response - local health_response=$(aitvaras_exec "curl -s --max-time $HEALTH_TIMEOUT $HEALTH_URL" || echo "") - if [[ -n "$health_response" ]]; then - log_info "Health response: $health_response" - fi - - # Additional status info - local service_status=$(get_service_status) - log_info "Service status: $service_status" - - return 0 - fi - - ((retries--)) - if [[ $retries -gt 0 ]]; then - log_info "Health check failed, retrying in 5 seconds..." - sleep 5 - fi - done - - log_error "Production health check failed after $HEALTH_RETRIES attempts" - log_info "Debugging service status..." - local service_status=$(get_service_status) - log_info "Service status: $service_status" - - # Show service logs - log_info "Recent service logs:" - aitvaras_exec_sudo "journalctl -u $SERVICE_NAME --no-pager -n 10" || true - - # Check if port is at least listening - if aitvaras_exec "netstat -an | grep -q ':8080.*LISTEN'"; then - log_warning "Port 8080 is listening but health check failed - possible service issue" - else - log_error "Port 8080 is not listening - service not running" - fi - - return 1 -} - -# ============================================================================= -# MAIN DEPLOYMENT FUNCTION -# ============================================================================= - -deploy() { - log_step "Starting PRODUCTION deployment: karl → aitvaras" - log_production "⚠️ THIS IS A PRODUCTION DEPLOYMENT ⚠️" - - # Pre-deployment checks - check_dependencies - prepare_source - - # Show deployment summary - log_info "Production Deployment Summary:" - log_info " Source: $SOURCE_DIR" - log_info " Target: $AITVARAS_HOST:$TARGET_DIR" - log_info " Service: $SERVICE_NAME (systemd)" - log_info " User: $SERVICE_USER" - log_info " Dry Run: $DRY_RUN" - - if [[ "$DRY_RUN" == "true" ]]; then - log_warning "DRY RUN MODE - No changes will be made" - else - log_production "🚨 PRODUCTION MODE - Changes will be applied immediately!" - fi - - # Production confirmation prompt - if [[ "$FORCE" != "true" && "$DRY_RUN" != "true" ]]; then - echo "" - echo -e "${RED}⚠️ PRODUCTION DEPLOYMENT WARNING ⚠️${NC}" - echo -e "This will deploy to the live production server aitvaras." - echo -e "All changes will immediately affect live services." - echo "" - echo -n "Continue with PRODUCTION deployment? [y/N]: " - read -r response - if [[ ! "$response" =~ ^[Yy]$ ]]; then - log_info "Production deployment cancelled" - exit 0 - fi - fi - - # Create service user and directories - create_service_user - create_directories - - # Create backup before deployment - local backup_path="" - if [[ "$DRY_RUN" != "true" ]]; then - backup_path=$(create_backup) - fi - - # Stop service if running - if [[ "$DRY_RUN" != "true" ]]; then - stop_service - fi - - # Deploy files - sync_files - - # Create configuration - create_environment_file - - # Fix permissions (after environment file exists) - fix_permissions - - # Create systemd service - create_systemd_service - - # Start service - if [[ "$DRY_RUN" != "true" ]]; then - # Check if port is available before starting - if ! check_port_availability; then - log_error "Cannot start service - port conflict detected" - log_info "Check what's using port 8080: sudo ss -tlnp | grep :8080" - exit 1 - fi - - if start_service; then - # Health check - if health_check; then - log_success "🎉 PRODUCTION DEPLOYMENT COMPLETED SUCCESSFULLY!" - log_production "Service is live at: https://api.dragons-at-work.de" - - # Cleanup old backups - cleanup_old_backups - - # Disable passwordless sudo (security) - disable_passwordless_sudo - - # Show next steps - echo "" - log_info "📝 MANUAL STEPS REQUIRED:" - log_info "1. Add SMTP credentials to: $CONFIG_DIR/environment" - log_info "2. Test the API: curl https://api.dragons-at-work.de/health" - log_info "3. Monitor logs: sudo journalctl -u $SERVICE_NAME -f" - - else - log_error "Production deployment failed health check" - - # Disable passwordless sudo even on failure - disable_passwordless_sudo - - # Offer rollback - if [[ -n "$backup_path" ]]; then - echo -n "🔄 Rollback to previous version? [y/N]: " - read -r response - if [[ "$response" =~ ^[Yy]$ ]]; then - log_production "Rolling back production deployment..." - # Need to re-enable passwordless sudo for rollback - enable_passwordless_sudo - aitvaras_exec_sudo "rm -rf $TARGET_DIR" - aitvaras_exec_sudo "cp -r $backup_path $TARGET_DIR" - fix_permissions - start_service - health_check - disable_passwordless_sudo - fi - fi - exit 1 - fi - else - log_error "Failed to start service after production deployment" - disable_passwordless_sudo - exit 1 - fi - else - log_success "Dry run completed - no changes made to production" - echo "" - log_info "To execute this deployment for real:" - log_info " $0" - fi -} - -# ============================================================================= -# MAIN SCRIPT LOGIC -# ============================================================================= - -# Parse command line arguments -DRY_RUN=false -ROLLBACK=false -FORCE=false - -while [[ $# -gt 0 ]]; do - case $1 in - --dry-run) - DRY_RUN=true - shift - ;; - --rollback) - ROLLBACK=true - shift - ;; - --force) - FORCE=true - shift - ;; - --help) - usage - exit 0 - ;; - *) - log_error "Unknown option: $1" - usage - exit 1 - ;; - esac -done - -# Main execution -main() { - log_production "Furt API Production Deployment Script - karl → aitvaras" - log_info "$(date)" - - if [[ "$ROLLBACK" == "true" ]]; then - rollback_deployment - else - deploy - fi -} - -# Trap for cleanup on exit -cleanup() { - local exit_code=$? - - # Always disable passwordless sudo (security!) - disable_passwordless_sudo - - if [[ $exit_code -ne 0 && "$DRY_RUN" != "true" ]]; then - log_error "Production deployment failed (exit code: $exit_code)" - log_info "Check service status: ssh $AITVARAS_HOST \"sudo systemctl status $SERVICE_NAME\"" - log_info "Check logs: ssh $AITVARAS_HOST \"sudo journalctl -u $SERVICE_NAME -n 20\"" - fi -} -trap cleanup EXIT - -# Run main function -main "$@" - diff --git a/scripts/deploy/deploy_walter.sh b/scripts/deploy/deploy_walter.sh deleted file mode 100755 index a00ea51..0000000 --- a/scripts/deploy/deploy_walter.sh +++ /dev/null @@ -1,695 +0,0 @@ -#!/bin/bash -# scripts/deploy/deploy_walter.sh -# Deployment script: karl (development) → walter (OpenBSD staging) -# -# Usage: -# ./scripts/deploy/deploy_walter.sh [--dry-run] [--rollback] [--force] -# -# Dragons@Work - Furt API-Gateway Deployment -# Version: 1.0 - -set -euo pipefail # Exit on error, undefined vars, pipe failures - -# ============================================================================= -# CONFIGURATION -# ============================================================================= - -# Source (karl development) -SOURCE_DIR="/home/michael/Develop/DAW/furt/furt-lua" -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")" # scripts/deploy/ -> scripts/ -> furt/ - -# Target (walter OpenBSD staging) -WALTER_HOST="walter" # Assumes SSH config entry (as michael user) -TARGET_DIR="/usr/local/furt/furt-lua" -SERVICE_USER="_furt" -SERVICE_GROUP="_furt" - -# Backup configuration -BACKUP_DIR="/usr/local/furt/backups" -BACKUP_RETENTION=3 # Keep last 3 backups - -# Health check configuration -HEALTH_URL="http://localhost:8080/health" -HEALTH_TIMEOUT=10 -HEALTH_RETRIES=3 - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# ============================================================================= -# LOGGING FUNCTIONS -# ============================================================================= - -log_info() { - echo -e "${BLUE}[INFO]${NC} $1" -} - -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $1" -} - -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $1" -} - -log_step() { - echo -e "\n${BLUE}==>${NC} $1" -} - -# ============================================================================= -# UTILITY FUNCTIONS -# ============================================================================= - -usage() { - cat << EOF -Usage: $0 [OPTIONS] - -Deployment script for furt-lua: karl (development) → walter (OpenBSD staging) - -OPTIONS: - --dry-run Show what would be deployed without making changes - --rollback Rollback to previous deployment - --force Skip confirmation prompts - --help Show this help message - -EXAMPLES: - $0 # Normal deployment with confirmation - $0 --dry-run # Preview deployment without changes - $0 --force # Deploy without confirmation - $0 --rollback # Rollback to previous version - -EOF -} - -check_dependencies() { - log_step "Checking dependencies" - - # Check if source directory exists - if [[ ! -d "$SOURCE_DIR" ]]; then - log_error "Source directory not found: $SOURCE_DIR" - exit 1 - fi - - # Check SSH connectivity to walter - if ! ssh -o ConnectTimeout=5 -o BatchMode=yes "$WALTER_HOST" exit 2>/dev/null; then - log_error "Cannot connect to walter via SSH" - log_info "Please ensure SSH key is set up for $WALTER_HOST" - exit 1 - fi - - # Check rsync availability - if ! command -v rsync &> /dev/null; then - log_error "rsync is required but not installed" - exit 1 - fi - - log_success "All dependencies OK" -} - -get_backup_timestamp() { - date +"%Y%m%d_%H%M%S" -} - -# ============================================================================= -# SSH REMOTE EXECUTION FUNCTIONS -# ============================================================================= - -walter_exec() { - ssh "$WALTER_HOST" "$@" -} - -walter_exec_as_root() { - ssh "$WALTER_HOST" "doas $@" -} - -walter_exec_as_furt() { - ssh "$WALTER_HOST" "doas -u $SERVICE_USER $@" -} - -# ============================================================================= -# BACKUP FUNCTIONS -# ============================================================================= - -create_backup() { - local timestamp=$(get_backup_timestamp) - local backup_path="$BACKUP_DIR/furt-lua_$timestamp" - - log_step "Creating backup" - - # Create backup directory if it doesn't exist - walter_exec_as_root "mkdir -p $BACKUP_DIR" - walter_exec_as_root "chown $SERVICE_USER:$SERVICE_GROUP $BACKUP_DIR" - - # Check if target directory exists - if walter_exec "test -d $TARGET_DIR"; then - log_info "Backing up current deployment to: $backup_path" - walter_exec_as_root "cp -r $TARGET_DIR $backup_path" - walter_exec_as_root "chown -R $SERVICE_USER:$SERVICE_GROUP $backup_path" - - # Set backup metadata (fix shell redirect issue) - walter_exec_as_root "sh -c \"echo 'Backup created: \$(date)' > $backup_path/.backup_info\"" - walter_exec_as_root "sh -c \"echo 'Original path: $TARGET_DIR' >> $backup_path/.backup_info\"" - walter_exec_as_root "chown $SERVICE_USER:$SERVICE_GROUP $backup_path/.backup_info" - - log_success "Backup created: $backup_path" - echo "$backup_path" # Return backup path - else - log_warning "No existing deployment found, skipping backup" - echo "" # Return empty string - fi -} - -cleanup_old_backups() { - log_step "Cleaning up old backups" - - local backup_count=$(walter_exec "ls -1 $BACKUP_DIR/furt-lua_* 2>/dev/null | wc -l" || echo "0") - - if [[ $backup_count -gt $BACKUP_RETENTION ]]; then - log_info "Found $backup_count backups, keeping last $BACKUP_RETENTION" - walter_exec_as_root "ls -1t $BACKUP_DIR/furt-lua_* | tail -n +$((BACKUP_RETENTION + 1)) | xargs rm -rf" - log_success "Old backups cleaned up" - else - log_info "Found $backup_count backups, no cleanup needed" - fi -} - -list_backups() { - log_step "Available backups" - - if walter_exec "ls -1 $BACKUP_DIR/furt-lua_* 2>/dev/null"; then - walter_exec "ls -1t $BACKUP_DIR/furt-lua_* | head -n 5 | while read backup; do - echo \" \$backup\" - if [ -f \"\$backup/.backup_info\" ]; then - cat \"\$backup/.backup_info\" | sed 's/^/ /' - fi - echo - done" - else - log_warning "No backups found" - fi -} - -rollback_deployment() { - log_step "Rolling back deployment" - - # List available backups - local latest_backup=$(walter_exec "ls -1t $BACKUP_DIR/furt-lua_* 2>/dev/null | head -n 1" || echo "") - - if [[ -z "$latest_backup" ]]; then - log_error "No backups available for rollback" - exit 1 - fi - - log_info "Latest backup: $latest_backup" - - if [[ "$FORCE" != "true" ]]; then - echo -n "Rollback to this version? [y/N]: " - read -r response - if [[ ! "$response" =~ ^[Yy]$ ]]; then - log_info "Rollback cancelled" - exit 0 - fi - fi - - # Stop service - stop_service - - # Backup current version (before rollback) - local rollback_backup=$(create_backup) - if [[ -n "$rollback_backup" ]]; then - walter_exec_as_root "mv $rollback_backup ${rollback_backup}_pre_rollback" - fi - - # Restore from backup - walter_exec_as_root "rm -rf $TARGET_DIR" - walter_exec_as_root "cp -r $latest_backup $TARGET_DIR" - - # Fix permissions - fix_permissions - - # Start service - start_service - - # Health check - if health_check; then - log_success "Rollback completed successfully" - else - log_error "Rollback completed but health check failed" - exit 1 - fi -} - -# ============================================================================= -# SERVICE MANAGEMENT FUNCTIONS -# ============================================================================= - -get_service_status() { - # Check if furt process is actually running (regardless of rcctl status) - if walter_exec "pgrep -u _furt -f 'src/main.lua' >/dev/null 2>&1"; then - echo "running" - else - echo "stopped" - fi -} - -get_rcctl_status() { - # Check OpenBSD service status specifically - if walter_exec "rcctl check furt >/dev/null 2>&1"; then - echo "running" - else - echo "stopped" - fi -} - -stop_service() { - log_step "Stopping furt service" - - local process_status=$(get_service_status) - local rcctl_status=$(get_rcctl_status) - - if [[ "$process_status" == "running" ]]; then - log_info "Furt process is running (rcctl status: $rcctl_status)" - - # Try rcctl stop first if service is managed by rcctl - if [[ "$rcctl_status" == "running" ]]; then - log_info "Stopping via rcctl..." - walter_exec_as_root "rcctl stop furt" || true - sleep 2 - fi - - # Check if still running (manual process or rcctl didn't work) - if [[ $(get_service_status) == "running" ]]; then - log_info "Process still running, stopping manually..." - walter_exec_as_root "pkill -f -U $SERVICE_USER 'lua.*main.lua'" || true - sleep 2 - fi - - # Final check - local final_status=$(get_service_status) - if [[ "$final_status" == "stopped" ]]; then - log_success "Service stopped" - else - log_error "Could not stop service" - return 1 - fi - else - log_info "Service already stopped" - fi -} - -start_service() { - log_step "Starting furt service" - - local status=$(get_service_status) - if [[ "$status" == "stopped" ]]; then - # Check port availability before starting - if ! check_port_availability; then - log_error "Cannot start service - port 8080 is occupied" - return 1 - fi - - log_info "Starting furt service via rcctl..." - walter_exec_as_root "rcctl start furt" - - # Wait and check if service actually started - sleep 5 - - local process_status=$(get_service_status) - local rcctl_status=$(get_rcctl_status) - - if [[ "$process_status" == "running" ]]; then - log_success "Service started successfully (rcctl: $rcctl_status, process: $process_status)" - elif [[ "$rcctl_status" == "running" ]]; then - log_warning "rcctl reports running but process not detected - checking port..." - if walter_exec "netstat -an | grep -q ':8080.*LISTEN'"; then - log_success "Service appears to be running (port 8080 active)" - else - log_error "Service failed to start properly" - # Show service logs for debugging - walter_exec "tail -10 /var/log/daemon" || true - return 1 - fi - else - log_error "Failed to start service" - # Show service logs for debugging - walter_exec "tail -10 /var/log/daemon" || true - return 1 - fi - else - log_info "Service already running" - fi -} - -# ============================================================================= -# DEPLOYMENT FUNCTIONS -# ============================================================================= - -prepare_source() { - log_step "Preparing source files" - - # Check source directory structure - local required_dirs=("src" "config" "scripts") - for dir in "${required_dirs[@]}"; do - if [[ ! -d "$SOURCE_DIR/$dir" ]]; then - log_error "Required directory missing: $SOURCE_DIR/$dir" - exit 1 - fi - done - - # Check required files - local required_files=("src/main.lua" "scripts/start.sh") - for file in "${required_files[@]}"; do - if [[ ! -f "$SOURCE_DIR/$file" ]]; then - log_error "Required file missing: $SOURCE_DIR/$file" - exit 1 - fi - done - - log_success "Source files validated" -} - -sync_files() { - log_step "Syncing files to walter" - - # Prepare rsync excludes - local excludes=( - "--exclude=.env" - "--exclude=.env.*" - "--exclude=*.backup" - "--exclude=*.orig" - "--exclude=*.tmp" - "--exclude=.git/" - "--exclude=.DS_Store" - "--exclude=logs/" - "--exclude=*.log" - ) - - if [[ "$DRY_RUN" == "true" ]]; then - log_info "DRY RUN: Would sync the following files:" - rsync -avz --dry-run "${excludes[@]}" "$SOURCE_DIR/" "$WALTER_HOST:$TARGET_DIR/" - return 0 - fi - - # Create target directory and set initial ownership - walter_exec_as_root "mkdir -p $TARGET_DIR" - walter_exec_as_root "chown -R $SERVICE_USER:$SERVICE_GROUP $TARGET_DIR" - - # Sync files using rsync with _furt user - log_info "Syncing files..." - rsync -avz "${excludes[@]}" \ - -e "ssh" \ - --rsync-path="doas -u $SERVICE_USER rsync" \ - "$SOURCE_DIR/" "$WALTER_HOST:$TARGET_DIR/" - - log_success "Files synced" -} - -fix_service_file() { - log_step "Updating OpenBSD service file" - - if [[ "$DRY_RUN" == "true" ]]; then - log_info "DRY RUN: Would update /etc/rc.d/furt" - return 0 - fi - - log_info "Creating correct service file..." - walter_exec_as_root "sh -c 'cat > /etc/rc.d/furt << \"EOF\" -#!/bin/ksh - -daemon=\"$TARGET_DIR/scripts/start.sh\" -daemon_user=\"$SERVICE_USER\" -daemon_cwd=\"$TARGET_DIR\" -daemon_flags=\"start\" - -. /etc/rc.d/rc.subr - -# pexp NACH rc.subr überschreiben (Fix für OpenBSD Issue #77) -pexp=\"/usr/local/bin/lua src/main.lua.*\" - -rc_cmd \$1 -EOF'" - - # Make service file executable - walter_exec_as_root "chmod +x /etc/rc.d/furt" - - # Enable service if not already enabled - if ! walter_exec "rcctl ls on | grep -q furt"; then - log_info "Enabling furt service..." - walter_exec_as_root "rcctl enable furt" - log_success "Service enabled" - else - log_info "Service already enabled" - fi - - log_success "Service file updated" -} - -fix_permissions() { - log_step "Fixing permissions" - - if [[ "$DRY_RUN" == "true" ]]; then - log_info "DRY RUN: Would set ownership to $SERVICE_USER:$SERVICE_GROUP" - return 0 - fi - - # Set ownership (already done in sync_files, but ensure it's correct) - walter_exec_as_root "chown -R $SERVICE_USER:$SERVICE_GROUP $TARGET_DIR" - - # Set executable permissions for scripts - walter_exec_as_root "chmod +x $TARGET_DIR/scripts/*.sh" - - log_success "Permissions fixed" -} - -# ============================================================================= -# HEALTH CHECK FUNCTIONS -# ============================================================================= - -check_port_availability() { - log_step "Checking port availability" - - local port="8080" # Default furt port - - if walter_exec "netstat -an | grep -q ':$port'"; then - log_warning "Port $port is already in use" - walter_exec "netstat -an | grep ':$port'" || true - - # Try to identify what's using the port - log_info "Checking what's using port $port..." - walter_exec "fstat 2>/dev/null | grep ':$port'" || walter_exec "netstat -anp 2>/dev/null | grep ':$port'" || true - - return 1 - else - log_success "Port $port is available" - return 0 - fi -} - -health_check() { - log_step "Running health check" - - local retries=$HEALTH_RETRIES - while [[ $retries -gt 0 ]]; do - log_info "Health check attempt $((HEALTH_RETRIES - retries + 1))/$HEALTH_RETRIES" - - if walter_exec "curl -s --max-time $HEALTH_TIMEOUT $HEALTH_URL >/dev/null 2>&1"; then - log_success "Health check passed" - - # Get health check response - local health_response=$(walter_exec "curl -s --max-time $HEALTH_TIMEOUT $HEALTH_URL" || echo "") - if [[ -n "$health_response" ]]; then - log_info "Health response: $health_response" - fi - - # Additional status info - local process_status=$(get_service_status) - local rcctl_status=$(get_rcctl_status) - log_info "Service status - Process: $process_status, rcctl: $rcctl_status" - - return 0 - fi - - ((retries--)) - if [[ $retries -gt 0 ]]; then - log_info "Health check failed, retrying in 5 seconds..." - sleep 5 - fi - done - - log_error "Health check failed after $HEALTH_RETRIES attempts" - log_info "Debugging service status..." - local process_status=$(get_service_status) - local rcctl_status=$(get_rcctl_status) - log_info "Process status: $process_status" - log_info "rcctl status: $rcctl_status" - - # Check if port is at least listening - if walter_exec "netstat -an | grep -q ':8080.*LISTEN'"; then - log_warning "Port 8080 is listening but health check failed - possible service issue" - else - log_error "Port 8080 is not listening - service not running" - fi - - return 1 -} - -# ============================================================================= -# MAIN DEPLOYMENT FUNCTION -# ============================================================================= - -deploy() { - log_step "Starting deployment: karl → walter" - - # Pre-deployment checks - check_dependencies - prepare_source - - # Show deployment summary - log_info "Deployment Summary:" - log_info " Source: $SOURCE_DIR" - log_info " Target: $WALTER_HOST:$TARGET_DIR" - log_info " Service User: $SERVICE_USER" - log_info " Dry Run: $DRY_RUN" - - if [[ "$DRY_RUN" == "true" ]]; then - log_warning "DRY RUN MODE - No changes will be made" - fi - - # Confirmation prompt - if [[ "$FORCE" != "true" && "$DRY_RUN" != "true" ]]; then - echo -n "Continue with deployment? [y/N]: " - read -r response - if [[ ! "$response" =~ ^[Yy]$ ]]; then - log_info "Deployment cancelled" - exit 0 - fi - fi - - # Create backup before deployment - local backup_path="" - if [[ "$DRY_RUN" != "true" ]]; then - backup_path=$(create_backup) - fi - - # Stop service - if [[ "$DRY_RUN" != "true" ]]; then - stop_service - fi - - # Deploy files - sync_files - fix_permissions - - # Update service file for correct paths - fix_service_file - - # Start service - if [[ "$DRY_RUN" != "true" ]]; then - # Check if port is available before starting - if ! check_port_availability; then - log_error "Cannot start service - port conflict detected" - log_info "Try: ssh walter \"doas pkill -f 'lua.*main.lua'\" to kill conflicting processes" - exit 1 - fi - - if start_service; then - # Health check - if health_check; then - log_success "Deployment completed successfully!" - - # Cleanup old backups - cleanup_old_backups - else - log_error "Deployment failed health check" - - # Offer rollback - if [[ -n "$backup_path" ]]; then - echo -n "Rollback to previous version? [y/N]: " - read -r response - if [[ "$response" =~ ^[Yy]$ ]]; then - log_info "Rolling back..." - walter_exec_as_root "rm -rf $TARGET_DIR" - walter_exec_as_root "cp -r $backup_path $TARGET_DIR" - fix_permissions - start_service - health_check - fi - fi - exit 1 - fi - else - log_error "Failed to start service after deployment" - exit 1 - fi - else - log_success "Dry run completed - no changes made" - fi -} - -# ============================================================================= -# MAIN SCRIPT LOGIC -# ============================================================================= - -# Parse command line arguments -DRY_RUN=false -ROLLBACK=false -FORCE=false - -while [[ $# -gt 0 ]]; do - case $1 in - --dry-run) - DRY_RUN=true - shift - ;; - --rollback) - ROLLBACK=true - shift - ;; - --force) - FORCE=true - shift - ;; - --help) - usage - exit 0 - ;; - *) - log_error "Unknown option: $1" - usage - exit 1 - ;; - esac -done - -# Main execution -main() { - log_info "Furt Deployment Script - karl → walter" - log_info "$(date)" - - if [[ "$ROLLBACK" == "true" ]]; then - rollback_deployment - else - deploy - fi -} - -# Trap for cleanup on exit -cleanup() { - if [[ $? -ne 0 ]]; then - log_error "Deployment failed" - fi -} -trap cleanup EXIT - -# Run main function -main "$@" - diff --git a/scripts/manual_mail_test.sh b/scripts/manual_mail_test.sh new file mode 100644 index 0000000..3f8002f --- /dev/null +++ b/scripts/manual_mail_test.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# Manual SMTP test with corrected JSON + +echo "Testing SMTP with corrected JSON..." + +# Simple test without timestamp embedding +curl -X POST http://127.0.0.1:8080/v1/mail/send \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Furt Test User", + "email": "michael@dragons-at-work.de", + "subject": "Furt SMTP Test Success!", + "message": "This is a test email from Furt Lua HTTP-Server. SMTP Integration working!" + }' + +echo "" +echo "Check response above for success:true" + diff --git a/scripts/production_test_sequence.sh b/scripts/production_test_sequence.sh new file mode 100644 index 0000000..36a6455 --- /dev/null +++ b/scripts/production_test_sequence.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# Production Test für api.dragons-at-work.de + +echo "Testing Production API via Apache Proxy" +echo "=======================================" + +# Test 1: HTTPS Health Check +echo "" +echo "[1] Testing HTTPS Health Check..." +https_health=$(curl -s https://api.dragons-at-work.de/health) +echo "HTTPS Response: $https_health" + +if echo "$https_health" | grep -q "healthy"; then + echo "[OK] HTTPS Proxy working" +else + echo "[ERROR] HTTPS Proxy failed" + exit 1 +fi + +# Test 2: SMTP Status via HTTPS +echo "" +echo "[2] Testing SMTP Configuration via HTTPS..." +if echo "$https_health" | grep -q '"smtp_configured":true'; then + echo "[OK] SMTP configured and accessible via HTTPS" +else + echo "[ERROR] SMTP not configured or not accessible" +fi + +# Test 3: CORS Headers +echo "" +echo "[3] Testing CORS Headers..." +cors_test=$(curl -s -I https://api.dragons-at-work.de/health | grep -i "access-control") +if [ -n "$cors_test" ]; then + echo "[OK] CORS headers present: $cors_test" +else + echo "[WARN] CORS headers missing - add to Apache config" +fi + +# Test 4: Production Mail Test +echo "" +echo "[4] Testing Production Mail via HTTPS..." +echo "WARNING: This sends real email via production API" +read -p "Continue with production mail test? (y/N): " -n 1 -r +echo + +if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Sending production test email..." + + prod_mail_response=$(curl -s -X POST https://api.dragons-at-work.de/v1/mail/send \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Production Test", + "email": "test@dragons-at-work.de", + "subject": "Production API Test - Apache Proxy Success!", + "message": "This email was sent via the production API at api.dragons-at-work.de through Apache proxy to Furt-Lua backend. HTTPS integration working!" + }') + + echo "Production Response: $prod_mail_response" + + if echo "$prod_mail_response" | grep -q '"success":true'; then + echo "[OK] PRODUCTION MAIL SENT VIA HTTPS!" + echo "Check admin@dragons-at-work.de for delivery confirmation" + else + echo "[ERROR] Production mail failed" + fi +else + echo "Skipping production mail test" +fi + +# Test 5: Security Headers +echo "" +echo "[5] Testing Security Headers..." +security_headers=$(curl -s -I https://api.dragons-at-work.de/health) +echo "Security Headers:" +echo "$security_headers" | grep -i "x-content-type-options\|x-frame-options\|strict-transport" + +echo "" +echo "Production Test Complete!" +echo "========================" +echo "Next: Hugo integration with https://api.dragons-at-work.de/v1/mail/send" \ No newline at end of file diff --git a/furt-lua/scripts/setup_env.sh b/scripts/setup_env.sh similarity index 100% rename from furt-lua/scripts/setup_env.sh rename to scripts/setup_env.sh diff --git a/furt-lua/scripts/start.sh b/scripts/start.sh similarity index 100% rename from furt-lua/scripts/start.sh rename to scripts/start.sh diff --git a/furt-lua/scripts/stress_test.sh b/scripts/stress_test.sh similarity index 100% rename from furt-lua/scripts/stress_test.sh rename to scripts/stress_test.sh diff --git a/furt-lua/scripts/test_auth.sh b/scripts/test_auth.sh similarity index 100% rename from furt-lua/scripts/test_auth.sh rename to scripts/test_auth.sh diff --git a/furt-lua/scripts/test_curl.sh b/scripts/test_curl.sh similarity index 100% rename from furt-lua/scripts/test_curl.sh rename to scripts/test_curl.sh diff --git a/furt-lua/scripts/test_modular.sh b/scripts/test_modular.sh similarity index 100% rename from furt-lua/scripts/test_modular.sh rename to scripts/test_modular.sh diff --git a/furt-lua/scripts/test_smtp.sh b/scripts/test_smtp.sh similarity index 100% rename from furt-lua/scripts/test_smtp.sh rename to scripts/test_smtp.sh diff --git a/furt-lua/src/auth.lua b/src/auth.lua similarity index 100% rename from furt-lua/src/auth.lua rename to src/auth.lua diff --git a/furt-lua/src/ip_utils.lua b/src/ip_utils.lua similarity index 100% rename from furt-lua/src/ip_utils.lua rename to src/ip_utils.lua diff --git a/furt-lua/src/main.lua b/src/main.lua similarity index 100% rename from furt-lua/src/main.lua rename to src/main.lua diff --git a/furt-lua/src/rate_limiter.lua b/src/rate_limiter.lua similarity index 100% rename from furt-lua/src/rate_limiter.lua rename to src/rate_limiter.lua diff --git a/furt-lua/src/routes/auth.lua b/src/routes/auth.lua similarity index 100% rename from furt-lua/src/routes/auth.lua rename to src/routes/auth.lua diff --git a/furt-lua/src/routes/mail.lua b/src/routes/mail.lua similarity index 100% rename from furt-lua/src/routes/mail.lua rename to src/routes/mail.lua diff --git a/furt-lua/src/smtp.lua b/src/smtp.lua similarity index 100% rename from furt-lua/src/smtp.lua rename to src/smtp.lua diff --git a/furt-lua/tests/test_http.lua b/tests/test_http.lua similarity index 100% rename from furt-lua/tests/test_http.lua rename to tests/test_http.lua