Documentation
¶
Overview ¶
Package web implements the HTTP control plane: the fail-closed request pipeline (plan §5), the auth/session handlers, and the admin UI shell. It binds loopback only (plan §3); the managed edge fronts the public ports.
Index ¶
- func CSRFToken(ctx context.Context) string
- func ClientIP(ctx context.Context) netip.Addr
- func DiscoverL4Pools(ctx context.Context, dc *docker.Client, log *slog.Logger, routes []l4.Route) map[string][]string
- func ReplicaIPsByService(ctx context.Context, dc *docker.Client, log *slog.Logger) map[string][]string
- func ServiceIP(ctx context.Context, dc *docker.Client, project, service string) (string, bool)
- func SessionFrom(ctx context.Context) *session.Session
- func TokenID(ctx context.Context) string
- type Deps
- type Server
- func (s *Server) DiscoverEdgePools(ctx context.Context, routes []edge.Route) map[string][]string
- func (s *Server) Handler() http.Handler
- func (s *Server) Reload(ctx context.Context) error
- func (s *Server) Remediate(ctx context.Context, app monitor.App, service string, rung selfheal.Rung) error
- func (s *Server) Run(ctx context.Context) error
- func (s *Server) RunCertRenewWatcher(ctx context.Context, interval time.Duration)
- func (s *Server) RunGitPoller(ctx context.Context, interval time.Duration)
- func (s *Server) Scale(ctx context.Context, appProject, service string, replicas int) error
- func (s *Server) SetCircuitClearer(c func(project, service string))
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ClientIP ¶
ClientIP returns the resolved client IP (the real peer, or the single overwritten XFF value when the peer is a trusted proxy). Zero value if unset.
func DiscoverL4Pools ¶
func DiscoverL4Pools(ctx context.Context, dc *docker.Client, log *slog.Logger, routes []l4.Route) map[string][]string
DiscoverL4Pools resolves each L4 route to the live container endpoints (ip:port) of its backing service's replicas, so the host nginx dials bridge IPs directly and never has to resolve a compose service name (which it can't — and one unresolvable upstream makes `nginx -t` reject the WHOLE config). Counterpart of DiscoverEdgePools, keyed by l4.PoolKey(route). Fail-safe: a nil docker client / discovery error / a service with no running replica yields no pool for that route — the renderer then SKIPS it (rather than emitting an unresolvable name that takes every listener down). An L4 upstream is a raw TCP/UDP service:port, so there is no https case to skip.
It is a free function (not a *Server method like DiscoverEdgePools) because the L4 reconcile closure is built BEFORE the *Server — web.New needs that closure — so it has only the docker client to work with, not a *Server.
func ReplicaIPsByService ¶
func ReplicaIPsByService(ctx context.Context, dc *docker.Client, log *slog.Logger) map[string][]string
ReplicaIPsByService lists running containers ONCE and groups one routable bridge IP per replica, keyed by svcKey(project, service). A nil client or a list error yields nil (callers fail safe). IPs() is per-container sorted, so a multi-homed replica's chosen IP is stable.
func ServiceIP ¶
ServiceIP returns one routable bridge IP for a running replica of (project, service), chosen deterministically (lowest IP). For single-service callers (the ops prober). ok=false when there is no running replica or the socket-proxy is down — fail-safe.
func SessionFrom ¶
SessionFrom returns the loaded session, or nil if unauthenticated.
Types ¶
type Deps ¶
type Deps struct {
DB *store.DB
ConfigPath string // for SIGHUP allowlist+auth reload
Version string // the running Mooring build version (for the Server tab's .deb cleanup)
UpdateCheck *updatecheck.Checker // self-update / security-advisory posture (nil when disabled)
ImageScans *imagescan.Store // per-app Trivy scan results (surface on the Server tab)
Log *slog.Logger
Monitor *monitor.Monitor
OpsStore *ops.ConfigStore
Prober *ops.Prober
Runner *dockerexec.Runner
Docker *docker.Client
EnvStore *envstore.Store
CfgStore *cfgstore.Store
GitStore *gitstore.Store
ProvStore *provstore.Store
SetupStore *setupstore.Store
AlertStore *alertstore.Store
EdgeRoutes *edge.RouteStore
EdgeRecon *edge.Reconciler // nil when the edge isn't owned (external/unavailable)
EdgeReason string // why the edge isn't owned (banner), "" when owned
L4Routes *l4.RouteStore // managed L4 (TCP/UDP) routes (nil when L4 LB disabled)
L4Reconcile func(context.Context) error // push the L4 route set to the nginx-stream LB (nil when disabled)
DefStore *definition.Store // canonical mooring.yaml store (source of truth; may be nil)
SelfHeal *selfheal.Store // supervisor FSM + expected_down leases (may be nil)
Scaling *scale.Store // auto-scaling policies + state (may be nil)
DockerSem *dockerexec.Semaphore // global one-docker-child semaphore (shared with Runner)
APITokens *apitoken.Store // scoped read/deploy API tokens (M19; may be nil → /api/v1 disabled)
Backups *backupstore.Store // encrypted Mooring-state backups (may be nil)
}
Deps are the (mostly optional) collaborators a Server uses. Anything nil degrades gracefully (e.g. nil mon → "collecting…"; nil runner → write plane shown disabled).
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server holds everything the request pipeline needs. Construct with New.
func (*Server) DiscoverEdgePools ¶
DiscoverEdgePools resolves each route to the live container endpoints (ip:port) of its backing service's replicas, for the managed edge to dial directly. It is the wiring the auto-scaler's edge pool was designed for (edge.Reconciler.SetPoolDiscoverer): it takes each running replica's docker-bridge IP (the host edge routes to the bridge directly, so it never resolves the compose service name) and dials that pool with least-conn + health.
The result is keyed by edge.PoolKey(route). It is fail-safe: a nil docker client or a discovery error returns nil (every route keeps its single service-name dial), and a route is simply omitted when it has no routable replica IP. It NEVER returns a poisoned endpoint — loopback/link-local IPs are filtered in discovery, https upstreams are skipped (a bare-IP dial breaks their TLS verification), and edge.Render re-validates every member as the hard SBD-4 backstop.
func (*Server) Reload ¶
Reload re-reads the config file and hot-swaps the allowlist + auth (plan §5.1). On any validation error the current state is kept (fail-closed: a bad edit never widens or breaks the running policy).
func (*Server) Remediate ¶
func (s *Server) Remediate(ctx context.Context, app monitor.App, service string, rung selfheal.Rung) error
Remediate makes *Server the supervisor's Actioner: it runs the rung through the SAME write path the operator uses (env render, §5.6 validation + config-file materialization for recreate/redeploy), but via RunHeld — the supervisor's safety gate already holds the one-docker-child semaphore, so re-acquiring would deadlock. Authority never widens what may run: a protected project is refused here too.
func (*Server) RunCertRenewWatcher ¶
RunCertRenewWatcher closes the cert-renewal gap (plan §7.5): the edge auto-renews each leaf (~30 days before expiry), but the copy mounted into a TLS-terminating service (cert_bindings — EMQX :8883, a DoT resolver :853, …) is otherwise refreshed only on a deploy, so the service serves the OLD leaf until a manual redeploy. This watcher re-syncs each app's cert_bindings from the edge and, when a leaf ACTUALLY changed, recreates the affected services via the EXACT deploy machinery — no redeploy needed.
Safe + idempotent by construction:
- acts ONLY when the synced leaf digest changed (changedServices); an unchanged leaf is a no-op (re-sync writes identical bytes → same digest → nothing recreated)
- takes the single-flight deploy lock (gitDeploy) so it never races a deploy
- holds an expected-down lease per app so self-heal ignores the brief recreate
- reuses syncCertBindings + managedDigests/changedServices + renderEnvFile + the deploy's `up --force-recreate` job, so a renewal recreate == a deploy recreate
Gated by the caller to the write plane + managed edge. Linux/runtime path — not exercised off-Linux.
func (*Server) RunGitPoller ¶
RunGitPoller is the "just connect the repo and it works" loop: it FETCHES every connected repo so change-detection needs NO webhook setup. It is READ-PLANE ONLY — it never deploys; it surfaces an "update available" the operator deploys with a click (push-to-deploy stays an explicit opt-in via the webhook + auto_deploy, never this loop). A fetch never mutates a running app, and the loop is serialized through the same single-flight gate as deploys, so it can never pile up docker/git children.
Because Mooring never auto-deploys, fetching when nobody is looking is wasted work — so the loop only fetches while a focused dashboard is open (the heartbeat above). When the operator opens/returns to the dashboard, the next tick fetches.
interval <= 0 disables polling. Blocks until ctx is cancelled.
func (*Server) Scale ¶
Scale makes *Server the auto-scaler's Scaler: it changes a service's replica count with a static-argv `docker compose up -d --no-deps --no-recreate --scale <svc>=<n>` through the gated write path (env render + §5.6 validation), via RunHeld — the scaler's safety gate already holds the one-docker-child semaphore. A protected project is refused (authority never widens what may run).
func (*Server) SetCircuitClearer ¶
SetCircuitClearer wires the supervisor's clear-circuit entry point (set by cmd_serve after both the server and the watcher exist — avoids an import cycle).
Source Files
¶
- alerts.go
- api.go
- apitokens_web.go
- appdelete.go
- apps_list.go
- assets.go
- auth.go
- backups_web.go
- certrenew.go
- configfiles.go
- configfiles_canonical.go
- context.go
- csrf.go
- discovery.go
- edge.go
- edge_pool.go
- env.go
- github_oauth.go
- gitops.go
- gitops_build.go
- gitops_discover.go
- gitops_siblings.go
- gitpoll.go
- handlers.go
- incidents.go
- l4_pool.go
- lifecycle.go
- localtime.go
- lockout.go
- metrics_history.go
- middleware.go
- nofollow_unix.go
- ntfy_managed.go
- ops_handlers.go
- pathsafe.go
- provision.go
- ratelimit.go
- routes_apply.go
- scale.go
- selfheal.go
- server.go
- server_tab.go
- service_page.go
- session_status.go
- settings.go
- setup.go
- theme.go
- update_banner.go