web

package
v0.4.7 Latest Latest
Warning

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

Go to latest
Published: Jul 3, 2026 License: Apache-2.0 Imports: 70 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func CSRFToken

func CSRFToken(ctx context.Context) string

CSRFToken returns the per-request CSRF token for template injection.

func ClientIP

func ClientIP(ctx context.Context) netip.Addr

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

func ServiceIP(ctx context.Context, dc *docker.Client, project, service string) (string, bool)

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

func SessionFrom(ctx context.Context) *session.Session

SessionFrom returns the loaded session, or nil if unauthenticated.

func TokenID

func TokenID(ctx context.Context) string

TokenID returns the authenticated API token id (for audit), or "" on the browser plane.

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 New

func New(cfg *config.Config, d Deps) (*Server, error)

New builds a Server from a validated config and its dependencies.

func (*Server) DiscoverEdgePools

func (s *Server) DiscoverEdgePools(ctx context.Context, routes []edge.Route) map[string][]string

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) Handler

func (s *Server) Handler() http.Handler

Handler assembles the full middleware chain in pipeline order.

func (*Server) Reload

func (s *Server) Reload(ctx context.Context) error

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) Run

func (s *Server) Run(ctx context.Context) error

Run starts the loopback HTTP server and blocks until ctx is cancelled.

func (*Server) RunCertRenewWatcher

func (s *Server) RunCertRenewWatcher(ctx context.Context, interval time.Duration)

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

func (s *Server) RunGitPoller(ctx context.Context, interval time.Duration)

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

func (s *Server) Scale(ctx context.Context, appProject, service string, replicas int) error

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

func (s *Server) SetCircuitClearer(c func(project, service string))

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).

Jump to

Keyboard shortcuts

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