feat: app-to-app integration framework + OnlyOffice handlers

Generic integration system for connecting deployed apps via toggle UI.
First handlers: OnlyOffice→FileBrowser (config.yaml patch) and
OnlyOffice→Nextcloud (occ CLI). Lifecycle hooks auto-suspend on
stop and re-apply on start.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 20:06:20 +01:00
parent d3b53d9877
commit 0a5840a255
15 changed files with 992 additions and 1 deletions
+79 -1
View File
@@ -1197,11 +1197,76 @@ All mutating endpoints trigger an async Cloudflare sync. The `/api/geo/` path ac
---
### 14. App-to-App Integrations
Generic framework for connecting deployed applications to each other. Provider apps declare available integrations in `.felhom.yml`, and users enable/disable them via toggle switches on the provider's app info page.
#### Architecture (`internal/integrations/`)
- **`integrations.go`** — Core types: `Handler` interface (`Apply`/`Revoke`), `ApplyContext` (carries domain, decrypted env vars, restart func), `StatusInfo` (UI data)
- **`manager.go`** — `Manager` coordinates toggle operations, builds apply contexts, lists integrations for UI. Uses `StackProvider` interface to break circular imports (adapted in main.go)
- **`lifecycle.go`** — `OnStackStop` (suspend active integrations), `OnStackStart` (re-apply enabled integrations), `OnStackRemove` (permanent cleanup)
- **Handler implementations** — One file per integration pair (e.g. `onlyoffice_filebrowser.go`, `onlyoffice_nextcloud.go`)
#### Integration State
Stored in `settings.json` under `integrations` map (key: `"provider:target"`):
- `enabled` — User intent (survives stop/restart)
- `status` — Current state: `"active"`, `"error"`, `"disabled"`, `"provider_stopped"`, `"target_unavailable"`
- `last_error` — Most recent error message
- `enabled_at` — RFC3339 timestamp
#### Lifecycle
1. **Enable**: User toggles on → validates both apps deployed+running → calls `Handler.Apply()` → persists state as `"active"`
2. **Provider/target stops**: `OnStackStop` → calls `Handler.Revoke()` → sets status to `"provider_stopped"` or `"target_unavailable"` (keeps `enabled=true`)
3. **Provider/target starts**: `OnStackStart` → finds enabled integrations with non-active status → re-applies if both sides running
4. **Provider/target removed**: `OnStackRemove` → revokes and deletes integration state permanently
5. **FileBrowser config regen**: `SyncFileBrowserMounts` overwrites `config.yaml` → `OnStackStart("filebrowser")` re-applies active integrations
#### Built-in Handlers
**OnlyOffice → FileBrowser** (`onlyoffice_filebrowser.go`):
- Apply: Reads JWT_SECRET + SUBDOMAIN from OnlyOffice app.yaml, appends `integrations.office` block to FileBrowser `config.yaml`, restarts FileBrowser
- Revoke: Strips `integrations:` block from config.yaml, restarts FileBrowser
**OnlyOffice → Nextcloud** (`onlyoffice_nextcloud.go`):
- Apply: Runs `docker exec -u www-data nextcloud php occ` commands (app:install, app:enable, config:app:set for DocumentServerUrl, jwt_secret)
- Revoke: Runs `occ app:disable onlyoffice`
#### Metadata (`.felhom.yml`)
```yaml
integrations:
- target: filebrowser
label: "FileBrowser integráció"
description: "Dokumentumok szerkesztése a fájlkezelőben"
- target: nextcloud
label: "Nextcloud integráció"
description: "Dokumentumok szerkesztése a Nextcloudban"
```
#### API Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/integrations/{provider}` | List integrations for a provider app |
| POST | `/api/integrations/{provider}/{target}` | Enable/disable integration (`{"enable": true/false}`) |
#### UI
Toggle switches on the provider's app info page ("Integrációk" section). Each integration shows:
- Label and description
- Status badge: "Aktív", "Nincs telepítve", "Célalkalmazás leállítva", "Hiba"
- Toggle checkbox (disabled when target not deployed/running)
---
## Repository Layout
```
controller/
├── cmd/controller/main.go # Entry point, wires all 16 modules (setup mode branch + normal startup)
├── cmd/controller/main.go # Entry point, wires all 17 modules (setup mode branch + normal startup)
├── internal/
│ ├── config/config.go # YAML loader, validation, env overrides
│ ├── crypto/crypto.go # AES-256-GCM encryption for app.yaml secrets, key management
@@ -1237,6 +1302,12 @@ controller/
│ │ ├── waf.go # WAF rule CRUD + expression builders
│ │ ├── countries.go # ISO 3166-1 country codes + Hungarian names
│ │ └── geosync.go # Geo sync orchestrator (diff & apply rules)
│ ├── integrations/
│ │ ├── integrations.go # Core types: Handler interface, ApplyContext, StatusInfo
│ │ ├── manager.go # Manager: Toggle, ListForProvider, StackProvider interface
│ │ ├── lifecycle.go # OnStackStop, OnStackStart, OnStackRemove hooks
│ │ ├── onlyoffice_filebrowser.go # OnlyOffice → FileBrowser handler (config.yaml patch)
│ │ └── onlyoffice_nextcloud.go # OnlyOffice → Nextcloud handler (occ commands)
│ ├── assets/syncer.go # Hub asset sync (download, SHA-256 compare, resolve)
│ ├── api/
│ │ ├── router.go # REST API endpoints (~36 routes)
@@ -1484,6 +1555,13 @@ Config endpoints accept session auth OR `Authorization: Bearer <hub_api_key>` (s
| POST | `/api/assets/sync` | Trigger on-demand asset sync from Hub (async) |
| GET | `/api/assets/status` | Asset sync status (last sync, file count, total bytes) |
### Integrations
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/integrations/{provider}` | List integrations for provider app (status, target availability) |
| POST | `/api/integrations/{provider}/{target}` | Enable/disable integration (`{"enable": true/false}`) |
### Debug (debug mode only)
| Method | Endpoint | Description |