80e6ad8c47
gates / gates (push) Successful in 26s
R-650: internal/dockerexec — every docker exec routed through it; under go test a real docker is refused (opt-in FELHOM_TEST_REAL_DOCKER=1; a stub under the temp dir is allowed). api/stacks/web tests run under a silent stub (TestMain). TestR650_NoBareDockerExec pins it repo-wide. R-640: a dump without its engine's completion marker is refused before the first mutation (unit + off-site restore) and again before any load. R-499: the Tier-2 page's system-disk sentence has four true branches. R-518: the backup button states the measured ~8 min stop. R-626: measured on 9202, not reproduced. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
110 lines
4.6 KiB
Go
110 lines
4.6 KiB
Go
package integrations
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/dockerexec"
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/util"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// OnlyOfficeNextcloudHandler enables/disables OnlyOffice document editing in Nextcloud via occ.
|
|
type OnlyOfficeNextcloudHandler struct{}
|
|
|
|
func (h *OnlyOfficeNextcloudHandler) Apply(ac *ApplyContext) error {
|
|
jwtSecret := ac.ProviderEnv["JWT_SECRET"]
|
|
if jwtSecret == "" {
|
|
ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-Nextcloud apply: JWT_SECRET not set")
|
|
return util.MsgError("err.integrations.onlyoffice_jwt_secret_nincs_beallitva")
|
|
}
|
|
|
|
subdomain := ac.ProviderEnv["SUBDOMAIN"]
|
|
if subdomain == "" && ac.ProviderMeta != nil {
|
|
subdomain = ac.ProviderMeta.Subdomain
|
|
}
|
|
if subdomain == "" {
|
|
ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-Nextcloud apply: subdomain unknown")
|
|
return util.MsgError("err.integrations.onlyoffice_no_subdomain")
|
|
}
|
|
|
|
publicURL := fmt.Sprintf("https://%s.%s", subdomain, ac.Domain)
|
|
internalURL := "http://onlyoffice:80"
|
|
|
|
// Install and configure OnlyOffice app in Nextcloud
|
|
commands := []struct {
|
|
args []string
|
|
tolerate string // substring in output to tolerate as success
|
|
}{
|
|
{
|
|
args: []string{"docker", "exec", "-u", "www-data", "nextcloud", "php", "occ", "app:install", "onlyoffice"},
|
|
tolerate: "already installed",
|
|
},
|
|
{
|
|
args: []string{"docker", "exec", "-u", "www-data", "nextcloud", "php", "occ", "app:enable", "onlyoffice"},
|
|
},
|
|
{
|
|
args: []string{"docker", "exec", "-u", "www-data", "nextcloud", "php", "occ", "config:app:set", "onlyoffice", "DocumentServerUrl", "--value=" + publicURL},
|
|
},
|
|
{
|
|
args: []string{"docker", "exec", "-u", "www-data", "nextcloud", "php", "occ", "config:app:set", "onlyoffice", "DocumentServerInternalUrl", "--value=" + internalURL},
|
|
},
|
|
{
|
|
args: []string{"docker", "exec", "-u", "www-data", "nextcloud", "php", "occ", "config:app:set", "onlyoffice", "jwt_secret", "--value=" + jwtSecret},
|
|
},
|
|
{
|
|
// StorageUrl: tells OO Document Server where to reach Nextcloud internally for file callbacks.
|
|
// Trailing slash is critical — NC's OO connector does string replacement of the server URL
|
|
// (which ends with /) with StorageUrl. Without trailing slash, "/apps/..." merges into hostname.
|
|
args: []string{"docker", "exec", "-u", "www-data", "nextcloud", "php", "occ", "config:app:set", "onlyoffice", "StorageUrl", "--value=http://nextcloud/"},
|
|
},
|
|
{
|
|
// Add internal hostname to trusted_domains so OO Document Server callbacks
|
|
// (arriving with Host: nextcloud) are not rejected by Nextcloud.
|
|
args: []string{"docker", "exec", "-u", "www-data", "nextcloud", "php", "occ", "config:system:set", "trusted_domains", "10", "--value=nextcloud"},
|
|
},
|
|
}
|
|
|
|
for _, cmd := range commands {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
c := dockerexec.CommandContext(ctx, cmd.args[0], cmd.args[1:]...)
|
|
out, err := c.CombinedOutput()
|
|
cancel()
|
|
if err != nil {
|
|
if cmd.tolerate != "" && strings.Contains(string(out), cmd.tolerate) {
|
|
ac.Logger.Printf("[DEBUG] [integrations] Nextcloud occ: tolerated — %s", strings.TrimSpace(string(out)))
|
|
continue
|
|
}
|
|
ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-Nextcloud apply: occ %s failed: %v", cmd.args[len(cmd.args)-1], err)
|
|
return util.MsgError("err.integrations.occ_failed", cmd.args[len(cmd.args)-1], err, strings.TrimSpace(string(out)))
|
|
}
|
|
ac.Logger.Printf("[DEBUG] [integrations] Nextcloud occ %s: ok", strings.Join(cmd.args[7:], " "))
|
|
}
|
|
|
|
ac.Logger.Printf("[INFO] [integrations] OnlyOffice integration applied to Nextcloud")
|
|
return nil
|
|
}
|
|
|
|
func (h *OnlyOfficeNextcloudHandler) Revoke(ac *ApplyContext) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
|
|
cmd := dockerexec.CommandContext(ctx, "docker", "exec", "-u", "www-data", "nextcloud", "php", "occ", "app:disable", "onlyoffice")
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
outStr := string(out)
|
|
// Tolerate container not running or app not enabled
|
|
if strings.Contains(err.Error(), "No such container") ||
|
|
strings.Contains(outStr, "not enabled") ||
|
|
strings.Contains(outStr, "not installed") {
|
|
ac.Logger.Printf("[DEBUG] [integrations] Nextcloud occ app:disable skipped — %s", strings.TrimSpace(outStr))
|
|
return nil
|
|
}
|
|
ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-Nextcloud revoke: occ app:disable failed: %v", err)
|
|
return util.MsgError("err.integrations.occ_disable_failed", err, strings.TrimSpace(outStr))
|
|
}
|
|
|
|
ac.Logger.Printf("[INFO] [integrations] OnlyOffice integration revoked from Nextcloud")
|
|
return nil
|
|
}
|