Help us shape the future of Go! Take the 2026 Go Developer Survey

external

package
v0.45.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 25, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package external is the one place the runtime asks "how is OnlyOffice / drawio / the converter configured right now?".

Why this exists

Before it, the answer came from two places that could not agree. The admin UI wrote `external_services` rows and the capability probe read them, so `GET /api/files/capabilities` — the thing the explorer consults to decide whether to offer the Office editor at all — reported the operator's edit immediately. But the code that USES the configuration read `cfg.ExternalServices`, a snapshot taken from env/YAML once at boot: the OnlyOffice service was constructed (or not) in server.New, and the converter URL was baked into the AI handlers in routes.go. An operator who configured the services in the admin UI — the documented way, and the only way when the services live in a separate compose file — therefore got a green Test button, a capability payload that said "configured", and a 503 `{"error":"onlyoffice not configured"}` the moment they opened a document (issue #17). Thumbnails kept working throughout, because the thumbnailer shells out to local binaries and never consults external services at all — that asymmetry is what named the bug.

The rule

The DB row is the single runtime source of truth. Env/YAML is declarative configuration for it: a non-empty value is ASSERTED onto the row at every boot (see server.seedExternalDefaults), so a compose-managed install keeps behaving exactly as before and an edit to FILEX_ONLYOFFICE_URL still takes effect. Anything env does not pin is owned by the admin UI and applies live, with no restart.

Index

Constants

View Source
const (
	SeverityWarning = "warning"
	SeverityNote    = "note"
)

Advisory severities. `warning` colours the card and withholds the "configuration complete" badge; `note` is information the operator may want and never blocks anything.

View Source
const (
	FieldURL       = "url"
	FieldPublicURL = "public_url"
)

Advisory fields.

View Source
const (
	// CodeBrowserBareHost — the Document Server URL's host is a single label
	// with no dot: `http://onlyoffice`, a container name. It resolves only
	// inside the container network. A browser never can.
	CodeBrowserBareHost = "browser_bare_host"
	// CodeBrowserLoopbackHost — the Document Server URL is a loopback address
	// while filex itself is published somewhere else, so the people who reach
	// filex are not on this host and `localhost` means their own machine.
	CodeBrowserLoopbackHost = "browser_loopback_host"
	// CodePublicURLLoopback — FILEX_PUBLIC_URL is loopback. Inside the
	// Document Server's own container, that address is the Document Server.
	CodePublicURLLoopback = "public_url_loopback"
	// CodePublicURLBareHost — FILEX_PUBLIC_URL is a container name. Correct
	// for the callback, wrong for every share link built from it. This is the
	// one setup that genuinely wants two different values; see docs/ONLYOFFICE.md.
	CodePublicURLBareHost = "public_url_bare_host"
	// CodePublicURLUnresolved — FILEX_PUBLIC_URL's hostname does not resolve
	// on this machine. A note, not a warning: split-horizon DNS is a real and
	// working arrangement.
	CodePublicURLUnresolved = "public_url_unresolved"
)

Advisory codes. The admin UI translates by code (EN + TR); Message is the English fallback for API and MCP callers, who get no i18n bundle.

View Source
const (
	ClassEmpty       = ""
	ClassLoopback    = "loopback"
	ClassUnspecified = "unspecified"
	ClassIP          = "ip"
	ClassDotted      = "dotted"
	ClassBare        = "bare"
)

Host classes returned by HostClass.

View Source
const (
	OnlyOffice = "onlyoffice"
	Drawio     = "drawio"
	Convert    = "convert"
)

Known service names. These are the rows server.seedExternalDefaults creates.

View Source
const OptionKeyCallbackURL = "callback_url"

OptionKeyCallbackURL is where CallbackURL lives inside options_json.

Variables

This section is empty.

Functions

func CallbackURLFromOptions

func CallbackURLFromOptions(optionsJSON string) string

CallbackURLFromOptions reads the callback URL out of a row's options blob. A malformed blob reads as empty rather than failing: this is configuration the operator can retype, not a reason to refuse to serve documents.

func HasWarning

func HasWarning(list []Advisory) bool

HasWarning reports whether any advisory is severity warning — the signal the admin UI uses to withhold a "configuration complete" badge.

func HostClass

func HostClass(rawURL string) string

HostClass buckets a URL's host into the only distinctions that matter for "can a machine that is not this one reach it?".

loopback    localhost, *.localhost, 127.0.0.0/8, ::1
unspecified 0.0.0.0, ::
ip          any other literal address (LAN or public — both routable)
dotted      a name with a dot: office.example.com, files.lan
bare        a single-label name: onlyoffice, filex — container-network only

func WithCallbackURL

func WithCallbackURL(optionsJSON, rawURL string) (string, error)

WithCallbackURL returns optionsJSON with the callback URL set (or removed, when url is empty), preserving every other key the blob carries.

Types

type Advisory

type Advisory struct {
	Code     string `json:"code"`
	Field    string `json:"field"`
	Severity string `json:"severity"`
	// Detail is the offending value, so a log line or an MCP caller can see
	// what triggered the advisory without re-reading the row.
	Detail string `json:"detail,omitempty"`
	// Message is English. The admin UI prefers its own translation of Code.
	Message string `json:"message"`
}

Advisory is one thing worth saying about a configuration that no probe from this process can settle.

func Advise

func Advise(in AdvisoryInput) []Advisory

Advise is Advisories with the callback address included.

func Advisories

func Advisories(service, serviceURL, publicURL string, publicURLSet bool, lookup LookupFunc) []Advisory

Advisories returns everything worth saying about one service's addresses.

serviceURL is the row's URL; publicURL and publicURLSet come from the process config (publicURLSet is false when nobody chose one and it defaulted to http://localhost:5212 — worth saying out loud, because the operator does not know a default is in play). lookup may be nil, which skips the DNS note.

type AdvisoryInput

type AdvisoryInput struct {
	Service      string
	ServiceURL   string
	PublicURL    string
	PublicURLSet bool
	// CallbackURL is the separate address the service uses to reach filex,
	// when the operator set one. It, not PublicURL, is what the callback
	// advisories must judge: the whole point of the field is that the two can
	// differ.
	CallbackURL string
	Lookup      LookupFunc
}

AdvisoryInput is one service's addresses, as they are right now.

type LookupFunc

type LookupFunc func(host string) (definitelyNotFound bool)

LookupFunc reports whether host is DEFINITELY absent from DNS as this machine sees it.

⚠ It must return false for every inconclusive answer — timeout, SERVFAIL, no resolver configured. An advisory raised on a slow resolver would fire on a working setup, which is the failure mode this whole file exists to avoid.

func SystemLookup

func SystemLookup(ctx context.Context, timeout time.Duration) LookupFunc

SystemLookup returns a LookupFunc backed by this machine's resolver.

⚠ It answers true ONLY for an authoritative "no such host". A timeout, a SERVFAIL or a machine with no resolver at all answers false, because an advisory raised on those would fire on setups that work.

type Resolver

type Resolver struct {
	// contains filtered or unexported fields
}

Resolver answers from the `external_services` table, with a short cache so a per-request lookup does not become a per-request query.

The cache is deliberately short (not "until invalidated"): the admin PATCH handler does call Invalidate, but a second filex process behind the same database would never see that call, and a stale answer that heals itself in a second is a far smaller problem than one that needs a restart — which is the bug this package exists to remove.

func New

func New(store db.Store) *Resolver

New constructs a Resolver. A nil store yields a Resolver that reports everything unconfigured, which is what tests and the no-DB paths want.

func (*Resolver) Get

func (r *Resolver) Get(ctx context.Context, name string) Settings

Get returns the live settings for one service. A missing row, a disabled row or a row with no URL all read as "not configured" — Settings.Enabled is only true when the service can actually be used.

func (*Resolver) Invalidate

func (r *Resolver) Invalidate()

Invalidate drops the cache so the next Get re-reads the table. Called by the admin PATCH handler so an operator's change is visible on the very next request rather than up to `ttl` later.

func (*Resolver) URL

func (r *Resolver) URL(ctx context.Context, name string) string

URL is the convenience form for the services that need nothing but the base URL (drawio, the converter). Empty when the service is not usable.

type Settings

type Settings struct {
	Enabled bool
	URL     string
	Secret  string
	// CallbackURL is the address the SERVICE uses to reach filex — the third
	// address in the three-address problem (see advisory.go). Empty means "use
	// filex's public URL", which is right whenever one address serves both
	// purposes.
	//
	// ⚠ It exists because that is not always true. filex has ONE public URL
	// and it builds both the share links people click and the document URL it
	// hands the document server; a container that cannot resolve the public
	// hostname needs a different address for the second, and until this field
	// there was no way to give it one — the document opened and the save never
	// came back (issue #17, third round). Stored in options_json so no
	// migration is needed and the row keeps one shape.
	CallbackURL string
}

Settings is one service's live configuration.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL