Files
deploy-felhom-compose/controller/internal/integrations/onlyoffice_filebrowser.go
T
admin 0a5840a255 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>
2026-02-25 20:06:20 +01:00

114 lines
3.5 KiB
Go

package integrations
import (
"fmt"
"os"
"path/filepath"
"strings"
)
// OnlyOfficeFileBrowserHandler enables/disables OnlyOffice document editing in FileBrowser Quantum.
type OnlyOfficeFileBrowserHandler struct{}
func (h *OnlyOfficeFileBrowserHandler) Apply(ac *ApplyContext) error {
jwtSecret := ac.ProviderEnv["JWT_SECRET"]
if jwtSecret == "" {
return fmt.Errorf("OnlyOffice JWT_SECRET nincs beállítva — telepítsd újra az alkalmazást")
}
subdomain := ac.ProviderEnv["SUBDOMAIN"]
if subdomain == "" && ac.ProviderMeta != nil {
subdomain = ac.ProviderMeta.Subdomain
}
if subdomain == "" {
return fmt.Errorf("OnlyOffice aldomain nem ismert")
}
configPath := filepath.Join(ac.StacksDir, "filebrowser", "config.yaml")
configData, err := os.ReadFile(configPath)
if err != nil {
return fmt.Errorf("FileBrowser config olvasási hiba: %w", err)
}
// Remove any existing integrations section, then append the new one
configStr := removeIntegrationsSection(string(configData))
officeURL := fmt.Sprintf("https://%s.%s", subdomain, ac.Domain)
internalURL := "http://onlyoffice:80"
integrationsBlock := fmt.Sprintf("integrations:\n office:\n url: %q\n internalUrl: %q\n secret: %q\n viewOnly: false\n",
officeURL, internalURL, jwtSecret)
configStr = strings.TrimRight(configStr, "\n") + "\n" + integrationsBlock
// Atomic write
tmpPath := configPath + ".tmp"
if err := os.WriteFile(tmpPath, []byte(configStr), 0644); err != nil {
return fmt.Errorf("config írási hiba: %w", err)
}
if err := os.Rename(tmpPath, configPath); err != nil {
_ = os.Remove(tmpPath)
return fmt.Errorf("config átnevezési hiba: %w", err)
}
ac.Logger.Printf("[INFO] FileBrowser config updated with OnlyOffice integration")
return ac.RestartStack("filebrowser")
}
func (h *OnlyOfficeFileBrowserHandler) Revoke(ac *ApplyContext) error {
configPath := filepath.Join(ac.StacksDir, "filebrowser", "config.yaml")
configData, err := os.ReadFile(configPath)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return fmt.Errorf("config olvasási hiba: %w", err)
}
cleaned := removeIntegrationsSection(string(configData))
if cleaned == string(configData) {
return nil // no integrations section, nothing to remove
}
tmpPath := configPath + ".tmp"
if err := os.WriteFile(tmpPath, []byte(cleaned), 0644); err != nil {
return fmt.Errorf("config írási hiba: %w", err)
}
if err := os.Rename(tmpPath, configPath); err != nil {
_ = os.Remove(tmpPath)
return fmt.Errorf("config átnevezési hiba: %w", err)
}
ac.Logger.Printf("[INFO] FileBrowser config cleaned — OnlyOffice integration removed")
return ac.RestartStack("filebrowser")
}
// removeIntegrationsSection strips the integrations: YAML block from a config string.
// It removes from the line starting with "integrations:" to the next unindented key or EOF.
func removeIntegrationsSection(config string) string {
lines := strings.Split(config, "\n")
var result []string
inBlock := false
for _, line := range lines {
if strings.HasPrefix(line, "integrations:") {
inBlock = true
continue
}
if inBlock {
trimmed := strings.TrimRight(line, " \t\r")
if trimmed == "" {
continue // skip blank lines within block
}
if line[0] != ' ' && line[0] != '\t' {
// New top-level key — end of integrations block
inBlock = false
result = append(result, line)
}
// else: still inside indented block, skip
continue
}
result = append(result, line)
}
return strings.Join(result, "\n")
}