serviceops

package
v1.32.0 Latest Latest
Warning

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

Go to latest
Published: Aug 5, 2026 License: MIT Imports: 31 Imported by: 0

Documentation

Overview

Package serviceops contains the shared business logic for installing, starting, stopping, and removing lerd services. The CLI commands and the MCP tools both call into here so they enforce identical preset gating, dependency cascades, and dynamic_env regeneration.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrTuningServiceNotInstalled means the service has no quadlet on
	// disk. Surfaces as 404 in HTTP. Lets default-preset names that
	// resolve through LoadPreset still error cleanly when the user has
	// explicitly `lerd service remove`d them, so an edit cannot silently
	// reinstall via the regen+restart path below.
	ErrTuningServiceNotInstalled = errors.New("service is not installed")
	// ErrTuningFamilyUnsupported means the service has no tuningMounts
	// entry for its family. Surfaces as 400 in HTTP.
	ErrTuningFamilyUnsupported = errors.New("service does not support tuning")
)

Sentinel errors returned by SaveTuningOverride so callers (CLI, HTTP handler, future MCP surface) map them to a consistent error surface without each one re-deriving the install/family checks. Wrapped via %w so callers stay free to add context (e.g. the name) before returning.

View Source
var ErrPortInUse = errors.New("port already in use")

ErrPortInUse is returned by SetPublishedPort when the requested host port is not bindable. Callers can errors.Is on it to add a surface-specific hint (the CLI appends the "check which process owns it" command).

View Source
var ErrPortReserved = errors.New("port already claimed by another lerd service")

ErrPortReserved is returned when a requested host port is already claimed by another lerd service (its default, published, or extra ports). A plain bindability test misses this while that sibling is stopped, so the two units would collide at boot — this rejects the clash up front.

View Source
var ListStoreDropIns func(dep string) []string

ListStoreDropIns, when set, returns store-index preset names that declare family or env_role matching dep. Wired from cli so serviceops does not import store. Nil keeps dropInPresets on the local preset list only.

View Source
var OnPublishedPortShift func(service string, newPort int)

OnPublishedPortShift, if set, is invoked when the port-ownership guard moves a service's published port to avoid a host server (service name, new port). The CLI wires this to regenerate host-proxy sites' .env so their loopback DB target follows the moved port; it stays nil in pure serviceops tests (which don't import package cli). The MCP server shares the binary, so the CLI init sets it there too — that is fine: the env refresh is still the right thing to do, and the MCP server repoints os.Stdout at stderr so its output can't corrupt the protocol. Fired from the guard so any quadlet-write path (install, start, reinstall, `service port --reset`) refreshes followers, not just the explicit `lerd service port` command.

View Source
var UnitInstalledFn = func(unit string) bool { return podman.QuadletInstalled(unit) }

UnitInstalledFn reports whether the platform container unit is installed. Defaults to the .container check; the CLI overrides it with the platform-aware services.Mgr.ContainerUnitInstalled (launchd plist on macOS) for reconcile.

Functions

func AddExtraPort added in v1.27.0

func AddExtraPort(name, spec string) error

AddExtraPort adds a single extra published port to a built-in service. Adding a mapping already present is a harmless re-render (updateExtraPorts de-duplicates).

func CloneDatabase added in v1.31.0

func CloneDatabase(service, src, dst string) error

CloneDatabase copies the schema and data of src into dst inside the same service container, by piping the declared export straight into the declared import. dst must already exist.

func CreateDatabase added in v1.19.0

func CreateDatabase(svc, name string) (bool, error)

CreateDatabase creates dbName inside the named service container if it does not already exist, through the create action the engine's preset declares. Returns (true, nil) if created, (false, nil) if it already existed or the engine declares no create action, or (false, err) on failure.

func CreateExtension added in v1.31.0

func CreateExtension(service, database, name string) error

CreateExtension creates one extension in a database, idempotently, so a dump that carries its own CREATE EXTENSION and lerd's own call cannot collide.

func DatabaseActionDeclared added in v1.31.0

func DatabaseActionDeclared(service, action string) bool

DatabaseActionDeclared reports whether the engine declares the named action on its databases entity, so API layers can gate what they offer.

func DatabaseDumpFormat added in v1.31.0

func DatabaseDumpFormat(service string) string

DatabaseDumpFormat returns the declared dump format of the engine's databases entity ("sql" turns on the SQL sanitizer and error tally), or empty when none is declared.

func DeclaresDatabases added in v1.31.0

func DeclaresDatabases(service string) bool

DeclaresDatabases reports whether a service holds databases the UI can enumerate, read off its own declaration rather than a known family, so a store-published engine reaches the Databases tab without a Go change.

func DeleteSnapshot added in v1.22.0

func DeleteSnapshot(service, database, name string, allDatabases bool) error

DeleteSnapshot removes a stored snapshot, erroring when it does not exist so callers can report the miss clearly.

func DependencyDisplayName added in v1.31.0

func DependencyDisplayName(dep string) string

DependencyDisplayName is the short name to show for dep in CLI/TUI/UI: the preset the installed satisfier came from (mariadb for mariadb-11-8), the satisfier's own name when it has no preset, or dep when nothing is installed. Prefers a running satisfier so the dashboard does not show phpMyAdmin hanging off a grey mysql chip while MariaDB is the engine actually serving it.

func DependencyDisplayNames added in v1.31.0

func DependencyDisplayNames(deps []string) []string

DependencyDisplayNames maps each declared depends_on entry through DependencyDisplayName.

func DropDatabase added in v1.22.0

func DropDatabase(svc, name string) (bool, error)

DropDatabase removes the named database from the service container through the declared drop action. Returns (true, nil) if it was dropped, (false, nil) if it was already gone or the engine declares no drop action, or (false, err) on failure.

func DumpFlags added in v1.31.0

func DumpFlags(family string) []string

DumpFlags are the flags every dump of a family carries, so an export, a snapshot and a migration all produce the same file. Postgres needs --clean --if-exists to load back over objects that already exist, which mysqldump does on its own; mysqldump needs --routines and --events, which are off by default and would otherwise leave stored procedures out of the dump without a word.

func DumpReader added in v1.31.0

func DumpReader(r io.Reader) (io.Reader, error)

DumpReader unwraps a gzipped dump so a .sql.gz loads like a .sql. The engine clients read plain SQL: handed compressed bytes, psql reports a couple of encoding errors, loads nothing and still exits clean, which reads as a load that mostly worked. Anything else is passed through untouched, including a custom-format archive, which psql itself recognises and names.

func EmptyDatabase added in v1.31.0

func EmptyDatabase(service, database string) error

EmptyDatabase drops and recreates a database, the same replacement a snapshot restore does, so a dump lands on nothing rather than on top of what is there.

func EnsureCustomServiceQuadlet

func EnsureCustomServiceQuadlet(svc *config.CustomService) error

EnsureCustomServiceQuadlet writes the quadlet for a custom service and reloads systemd only when the file actually changed on disk. Materialises any declared file mounts and resolves dynamic_env directives so the rendered quadlet has the computed values.

func EnsureDefaultPresetQuadlet added in v1.19.0

func EnsureDefaultPresetQuadlet(name string) error

EnsureDefaultPresetQuadlet writes the quadlet for a default-preset service (mysql, postgres, redis, ...) by resolving the canonical CustomService from its YAML preset, layering the user's image / extra-port overrides from global config, applying the platform-specific image override last (matching the legacy "platform override wins" semantics), and finally writing through the shared custom-service quadlet writer.

This replaces the older embedded-template flow (cli.ensureServiceQuadlet) so default services and add-on presets share one code path.

func EnsureDefaultPresetQuadletPinned added in v1.19.0

func EnsureDefaultPresetQuadletPinned(name, pinnedImage string) error

EnsureDefaultPresetQuadletPinned is the reinstall-aware sibling of EnsureDefaultPresetQuadlet. When pinnedImage is non-empty, it is used as the source-of-truth for the Image= line, taking precedence over both the preset.Image fallback and the on-disk preserved image. Reinstall captures the on-disk image *before* RemoveService deletes the quadlet, then passes it here so the fresh install pins the same tag the user was running — otherwise the rolling preset.Image bump that the v1.19.0-beta.6 fix was designed to prevent fires on every reinstall.

Callers outside the reinstall path should use EnsureDefaultPresetQuadlet (which passes pinnedImage="").

func EnsureDependencyRunning added in v1.31.0

func EnsureDependencyRunning(dep string) (string, error)

EnsureDependencyRunning resolves dep to an installed satisfier and starts it.

func EnsureExtensions added in v1.31.0

func EnsureExtensions(service, database string) error

EnsureExtensions applies an engine's up-front extensions to a database it has just created.

func EnsureS3Bucket added in v1.19.0

func EnsureS3Bucket(name string) (bool, error)

EnsureS3Bucket creates a bucket for the given name in lerd-rustfs using an ephemeral mc container. Returns (true, nil) if created, (false, nil) if it already existed, or (false, err) on failure. Retries up to 3 times (2s apart) to bridge the window between the host TCP port becoming reachable and the container network being fully ready for mc operations.

func EnsureServiceRunning

func EnsureServiceRunning(name string) error

EnsureServiceRunning starts the service if it is not already active and waits until it is ready. Recurses through depends_on for custom services. Unlike StartService it does not mark the service manually started, start reverse dependents, or regenerate discover_family consumers — those are user-facing start side effects; this is the "bring it up for env/link/db" helper.

func EnsureTuningQuadlet added in v1.23.0

func EnsureTuningQuadlet(name string, svc *config.CustomService) error

EnsureTuningQuadlet rewrites the quadlet for `name` so the tuning override Volume= mount is present on installs predating the feature. Built-in default presets regenerate through EnsureDefaultPresetQuadlet (which itself resolves to EnsureCustomServiceQuadlet, so the mount lands either way); custom-YAML services regenerate through EnsureCustomServiceQuadlet directly.

Split out from SaveTuningOverride so the CLI's `lerd service config` (whose editor writes to the override file out-of-band, and which has a `--no-restart` flag) can share the regen step without forcing a restart. Failures are propagated, NOT logged-and-ignored — skipping the regen orphans the user's just-written override and the next restart would re-read the OLD config (no mount, no values picked up).

func EntityExecEnv added in v1.31.0

func EntityExecEnv() []string

EntityExecEnv is the fixed admin-credential env every declared command runs with, exported for callers that exec the command themselves.

func EntityExists added in v1.31.0

func EntityExists(service string, spec *config.EntitySpec, name string) (bool, error)

EntityExists reports whether the named entity is in the spec's list output.

func EntityExportFilename added in v1.31.0

func EntityExportFilename(service, kind, name string) string

EntityExportFilename is the download name for an entity export, from the declared filename, with a neutral fallback. The result becomes an output path in the CLI and MCP export paths, so a name that fails validation never shapes it.

func EntityFor added in v1.31.0

func EntityFor(service, kind string) *config.EntitySpec

EntityFor resolves the declared entity spec of the given kind for a service. Kind "databases" also honours the legacy list_databases field.

func EntityKinds added in v1.31.0

func EntityKinds(service string) []string

EntityKinds lists the non-database entity kinds a service declares, in declaration order.

func ExportDatabase added in v1.30.0

func ExportDatabase(service, database string, w io.Writer) error

ExportDatabase streams a dump of database from the service container to w, through the export action the engine's preset declares. The dump is uncompressed so the browser can save it directly.

func ExportEntity added in v1.31.0

func ExportEntity(service, kind, name string, w io.Writer) error

ExportEntity streams the declared export of one entity (a bucket's objects as a tar, a database dump) to w.

func ExportFilename added in v1.31.0

func ExportFilename(service, database string) string

ExportFilename is the download name for a live export of database, from the declared export action's filename, with a neutral fallback for engines that declare none.

func ExportShellCommand added in v1.31.0

func ExportShellCommand(service, database string) (string, error)

ExportShellCommand resolves the declared in-container command that dumps one database to stdout.

func ExportSnapshot added in v1.30.0

func ExportSnapshot(service, database, name string, w io.Writer) error

ExportSnapshot streams a snapshot's stored dump to w, decompressed, so it downloads as a plain .sql the same as a live export rather than a .gz.

func ImportEntity added in v1.31.0

func ImportEntity(service, kind, name string, r io.Reader) error

ImportEntity streams an uploaded archive from r into one entity through the declared import. A gzipped upload is unwrapped here, the same way database dumps are, so a declared import always reads the plain format it expects and both a .tar and a .tar.gz load.

func ImportShellCommand added in v1.31.0

func ImportShellCommand(service, database string) (string, error)

ImportShellCommand resolves the declared in-container command that loads a dump from stdin into one database.

func InstallPresetByName

func InstallPresetByName(name, version string) (*config.CustomService, error)

InstallPresetByName materialises a bundled preset as a custom service. version selects a tag for multi-version presets; empty falls back to the preset's DefaultVersion. It resolves and registers in one shot; callers that need to pull the image before registering (so a failed pull leaves nothing behind) should use resolvePresetForInstall + registerPreset instead.

func InstallPresetStreaming added in v1.18.0

func InstallPresetStreaming(name, version string, emit func(PhaseEvent)) (*config.CustomService, error)

InstallPresetStreaming runs the full install flow and emits a PhaseEvent at every step. The image is pulled before the service is registered (config + quadlet) so a failed pull never leaves a registered-but-broken service behind; pulling here also turns the hidden on-demand pull latency into visible progress in the UI. Registration precedes the dependency-start loop so a dependency that fails to start still leaves the service installed on disk; this matters for reinstall, which has already removed the prior copy.

func InstalledExtensions added in v1.31.0

func InstalledExtensions(service, database string) ([]string, error)

InstalledExtensions returns the extensions already present in a database.

func IntrospectCommand added in v1.30.0

func IntrospectCommand(service string) string

IntrospectCommand resolves an engine's list-databases query through the entity surface, so both the entities form and the legacy list_databases field serve it, preset first.

func IsBuiltin

func IsBuiltin(name string) bool

IsBuiltin reports whether name is a built-in (default-preset) lerd service. Kept as a passthrough so callers don't have to import config.

func MigrateService added in v1.19.0

func MigrateService(name, targetImage string, emit func(PhaseEvent)) error

MigrateService runs a per-family dump/restore migration so the service can move across data-incompatible SQL versions (e.g. mysql 8.0 → 9.0). Errors when no handler is registered for the service family.

func MissingPresetDependencies

func MissingPresetDependencies(svc *config.CustomService) []string

MissingPresetDependencies returns declared dependencies that ResolveDependency cannot satisfy, or that are only met by a drop-in the consumer cannot bind (no discover_family covering it and no pinned lerd-<dep> host to rewrite). Each entry is the dependency name, with known drop-in alternatives listed when the store or the consumer's admin_for declares any.

func PortAvailable added in v1.27.0

func PortAvailable(port int) bool

PortAvailable reports whether a TCP port is free to bind on both loopback stacks. It is the exported form of the guard's own bindability test (now the shared freeport.Bindable), used by the `lerd service port` pre-flight so the CLI and the guard agree on what "free" means — a plain dial test would miss a port reserved only on ::1.

func ReadTuningBackupContent added in v1.23.0

func ReadTuningBackupContent(name, backupName string) ([]byte, error)

ReadTuningBackupContent returns the raw bytes of a named backup so the restore modal can show a diff before the user accepts. Validates the backup name against the per-service anchored regex, then reads the file directly — the caller is the HTTP handler so the response streams from this byte slice.

func RefreshDiscoverFamilyConsumers added in v1.31.0

func RefreshDiscoverFamilyConsumers()

RefreshDiscoverFamilyConsumers waits for running family members that any installed discover_family consumer cares about, then rewrites those consumers and every pinned-dependency-host consumer. Bulk start (lerd start / install) brings engines and admin UIs up together via StartUnit and never hits RegenerateDynamicEnvConsumersForService, so without this pass phpMyAdmin can start with an empty PMA_HOSTS written during pre-start reconcile when no engine was up yet, and RedisInsight can keep a stale RI_REDIS_HOST.

func RegenerateDependencyHostConsumers added in v1.31.0

func RegenerateDependencyHostConsumers(name string)

RegenerateDependencyHostConsumers rewrites installed customs that pin a dependency host name can satisfy, so RedisInsight picks up lerd-valkey when Valkey starts (or drops it when the last redis-role engine stops).

func RegenerateDynamicEnvConsumersForService added in v1.31.0

func RegenerateDynamicEnvConsumersForService(name string)

RegenerateDynamicEnvConsumersForService regenerates discover_family and pinned-dependency-host consumers after name starts or stops.

func RegenerateFamilyConsumers

func RegenerateFamilyConsumers(family string)

RegenerateFamilyConsumers re-renders the quadlet of any installed custom service whose dynamic_env references the named family. Active consumers are bounced only when the rendered unit changed, so a bulk start that already has the right host list does not restart phpMyAdmin on every lerd start.

func RegenerateFamilyConsumersForService

func RegenerateFamilyConsumersForService(name string)

RegenerateFamilyConsumersForService wraps RegenerateFamilyConsumers. When name is up it waits until ready first so discover_family includes this member; when name is down it waits until the unit is inactive so the member is omitted. Without those waits, consumers are rewritten with a stale host list.

func ReinstallService added in v1.19.0

func ReinstallService(name string, resetData bool, emit func(PhaseEvent)) error

ReinstallService stops, removes, and reinstalls a service, optionally wiping its data and reprovisioning per-site state (databases, buckets) on the fresh service.

Reinstall requires that the service is currently installed: for default presets that means a quadlet on disk; for custom services that means a custom-service YAML. If neither is found ReinstallService returns an error.

Pre-flight validation runs before RemoveService so configuration errors (unknown preset, bad version, missing dependencies, unreachable image) fail with the on-disk state intact rather than after the service config has already been deleted.

When resetData is true the data dir is renamed-aside (recoverable as .pre-remove-<ts>) and ReprovisionLinkedSites is invoked after the install completes so dependent sites' DBs/buckets exist on the fresh service.

func RemoveExtraPort added in v1.27.0

func RemoveExtraPort(name, spec string) error

RemoveExtraPort removes a single extra published port from a built-in service.

func RemoveService added in v1.19.0

func RemoveService(name string, opts RemoveOptions, emit func(PhaseEvent)) error

func ReprovisionLinkedSites added in v1.19.0

func ReprovisionLinkedSites(serviceName string, emit func(PhaseEvent)) error

ReprovisionLinkedSites recreates per-site state on a freshly installed service after a reset-data reinstall. For db families (mysql/mariadb/postgres) it ensures each linked site's database exists. For object-storage families (rustfs) it ensures each linked site's bucket exists. Other families are a no-op (cache services hold no client-owned state).

Per-site failures are collected and joined; the loop continues so one misconfigured site doesn't block the rest.

func ResolveDependency added in v1.31.0

func ResolveDependency(dep string) string

ResolveDependency returns an installed service that meets dep: the named service itself, or any installed service whose family or env_role is dep. Prefers the literal name when installed; among drop-ins prefers same-family members over env_role substitutes, then sorted name. Empty when nothing installed satisfies dep.

func ResolveMigrateTarget added in v1.22.0

func ResolveMigrateTarget(name, currentImage, version string) (string, error)

ResolveMigrateTarget maps a migrate version argument to a target image. A bare preset version ("18") resolves to that version's full preset image; an unknown argument is substituted as a literal tag onto the current image.

func RestartIfConfigDrifted added in v1.28.1

func RestartIfConfigDrifted(name, preset string) (bool, error)

RestartIfConfigDrifted re-materialises a running service's preset config files and, when the config file is newer than the container's boot (i.e. the container is on stale config after a shipped preset change like a higher max_allowed_packet), restarts it, returning whether it did. It is the single seam both the custom reconcile and the default-stack start pass use, since those flow through separate install paths. A stopped or missing container, or a preset with no config files, is a no-op; the mtime only advances on a real content change, so a steady state never restarts anything.

func RestartService added in v1.31.0

func RestartService(name string) error

RestartService is the shared restart path for CLI, UI, TUI (via CLI), and MCP: refresh the quadlet (so dynamic_env and file mounts land), restart the unit, clear paused, and regenerate dynamic_env consumers.

func RestorePublishedPorts added in v1.27.1

func RestorePublishedPorts(name string, snap PublishedPortSnapshot) error

RestorePublishedPorts rolls a service's published-port config back to a snapshot and re-renders and restarts the unit so the running service matches, undoing a partially applied ports-modal save. It restores the whole port set at once, so a mapping the failed save had already moved returns to its prior port.

func RestoreTuningBackup added in v1.23.0

func RestoreTuningBackup(name, backupName string) (string, string, error)

RestoreTuningBackup copies the named backup over the live tuning file atomically and returns the restored content plus the backup path (the caller deletes the backup only after a successful restart, so a failed restart leaves the recovery copy on disk).

IMPORTANT: callers must hold the per-service tuning lock via lockTuningService(name). External callers should use RestoreTuningFromBackup which acquires the lock and drives the full restart + auto-rollback pipeline; the lower-level helper exists because RestoreTuningFromBackup composes it.

func RollbackService added in v1.19.0

func RollbackService(name string, emit func(PhaseEvent)) error

RollbackService swaps a service back to its previously-running image. Refuses when no previous image is recorded or when the most recent op was a migrate (binary/schema mismatch would corrupt the data dir).

func RunEntityAction added in v1.31.0

func RunEntityAction(service string, spec *config.EntitySpec, action, name string) error

RunEntityAction runs a declared non-streaming action against one entity.

func S3BucketName added in v1.19.0

func S3BucketName(name string) string

S3BucketName converts a project handle into a valid S3 bucket name: lowercase, hyphens instead of underscores, leading/trailing non-alphanumerics stripped, max length 63.

func SanitizeDump added in v1.31.0

func SanitizeDump(t DumpTarget, r io.Reader) (io.Reader, func() ImportNotes)

SanitizeDump filters a dump on its way to the engine so statements that name roles the local engine does not have never reach it, and creates a declared extension the moment a statement reaches for one of its types. It returns the filtered stream and a function reporting what it did, valid once the stream has been read to the end.

func ServiceEntities added in v1.31.0

func ServiceEntities(service string) []config.EntitySpec

ServiceEntities returns the non-database entity specs a service declares. The databases kind is excluded because the Databases tab is its surface.

func ServiceFamily

func ServiceFamily(name string) string

ServiceFamily returns the family of a service by name. Honours the explicit Family field on a custom service first, falls back to config.InferFamily for built-ins and pattern-matched alternates.

func ServiceInstalled added in v1.23.0

func ServiceInstalled(name string) bool

ServiceInstalled is the single source of truth for install state (issue #678): the quadlet for built-in default presets (no YAML by design), the services/ YAML for custom services so it agrees with the services list, not the quadlet.

func SetExtraPorts added in v1.27.0

func SetExtraPorts(name string, ports []string) error

SetExtraPorts replaces a bundled preset's extra published ports with ports (each a bare "host", "host:container", or "ip:host:container" mapping), de-duplicating and validating, then re-rendering and restarting the unit when it is running. Shared by the CLI `service expose`, MCP service:expose, and the Web UI ports endpoint. Any preset lerd ships qualifies (default-stack or optional like gotenberg); genuinely custom services declare their ports in their own YAML, so they're excluded.

func SnapshotExportFilename added in v1.31.0

func SnapshotExportFilename(service, snapshot string) string

SnapshotExportFilename is the download name for a stored snapshot. A snapshot downloads in the engine's own dump format, so the extension comes from the declared export filename (.archive for mongo, .sql for the SQL engines), defaulting to .sql. The snapshot name is sanitized so a bad name can neither escape the header nor carry a path.

func SnapshotSupported added in v1.31.0

func SnapshotSupported(service string, allDatabases bool) bool

SnapshotSupported reports whether the service declares the export and import actions snapshots are built from, in the requested scope.

func StartDependencies

func StartDependencies(svc *config.CustomService) error

StartDependencies ensures every entry in svc.DependsOn is up and ready before the parent is started. Each entry is resolved to an installed satisfier (literal name, same family, or env_role drop-in) first.

func StartService added in v1.31.0

func StartService(name string) error

StartService is the shared start path for CLI, UI, TUI (via CLI), and MCP: ensure the quadlet, bring depends_on satisfiers up for customs, start the unit (retrying briefly for quadlet generator lag), mark it manually started, start reverse dependents, and regenerate dynamic_env consumers.

func StopService added in v1.31.0

func StopService(name string) error

StopService is the shared stop path for CLI, UI, TUI (via CLI), and MCP: cascade-stop dependents when no other running satisfier remains, stop name, mark it paused, and regenerate dynamic_env consumers.

func StopWithDependents

func StopWithDependents(name string) error

StopWithDependents stops custom services that declare a depends_on entry name can satisfy, but only when nothing else still running would satisfy that entry once name is gone. Otherwise the dependent stays up and the caller's family-consumer regen refreshes it. Then stops name itself.

Returns the first stop error encountered. Unit status can lag (especially on launchd); ContainerRunning is checked the same way RegenerateFamilyConsumers does, and StopUnit is always attempted when either signal says the unit is up so a lagging "inactive" reading cannot report a silent success.

func SupportsMigration added in v1.19.0

func SupportsMigration(name string) bool

SupportsMigration reports whether a registered family migrator exists for the named service.

func UpdateServiceStreaming added in v1.19.0

func UpdateServiceStreaming(name, targetImage string, emit func(PhaseEvent)) error

UpdateServiceStreaming pulls the chosen image, persists it, rewrites the quadlet, and restarts the unit. Phases: checking_registry, pulling_image, writing_quadlet, restarting_unit, done.

func ValidateDatabaseName added in v1.30.0

func ValidateDatabaseName(name string) error

ValidateDatabaseName guards the sinks that assume a slugged name: the snapshot paths built with filepath.Join and the information_schema lookups built by string interpolation.

func ValidateExtraPort added in v1.27.0

func ValidateExtraPort(spec string) error

ValidateExtraPort checks that spec is a usable podman port mapping: a bare host port, "host:container", or "ip:host:container", each port in 0-65535, with an optional "/tcp" or "/udp" suffix.

func WithDashboardPort added in v1.27.1

func WithDashboardPort(rawURL string, defaultPorts []string, cfg config.ServiceConfig) string

WithDashboardPort re-ports a dashboard URL to follow a published-port move of whichever mapping it is served on. It matches the URL's host port against the service's default port mappings, resolves that mapping's effective host port (per-port override, then primary override, then default), and rewrites the URL to it. A dashboard on the primary (meilisearch's 7700) follows a primary move; one on a secondary (mailpit's 8025 UI, rustfs' 9001 console) follows only a move of that secondary, and stays put otherwise. Proxied same-origin paths (/_svc/<name>/) have no host and pass through unchanged.

func WithURLPort added in v1.27.0

func WithURLPort(rawURL string, port int) string

WithURLPort returns rawURL with its host port set to port, preserving scheme, userinfo, host, and path. Used to keep a service's developer-facing connection URL in sync after its published port is overridden. Returns the input unchanged when it is empty, unparseable, or has no host.

Types

type DatabaseInfo added in v1.30.0

type DatabaseInfo struct {
	Name      string `json:"name"`
	SizeBytes int64  `json:"size_bytes"`
}

DatabaseInfo is one user database inside an engine, with its on-disk size.

func ListDatabases added in v1.30.0

func ListDatabases(service, listCommand string) ([]DatabaseInfo, error)

ListDatabases runs the preset-declared introspection command inside the service container and parses its "name<TAB>size_bytes" rows. The command is engine-specific and lives in the service store, so this stays framework agnostic: it only knows how to run a command and read tab-separated output.

type DumpTarget added in v1.31.0

type DumpTarget struct {
	Service    string
	Family     string
	Database   string
	Extensions []Extension
}

DumpTarget is where a dump is going, so the filter knows which statements can never apply and which extensions the engine could create for it.

type EntityRow added in v1.31.0

type EntityRow struct {
	Name   string   `json:"name"`
	Values []string `json:"values"`
}

EntityRow is one listed entity: its name, then the declared columns' values in declaration order, as the list command printed them.

func ListEntities added in v1.31.0

func ListEntities(service string, spec *config.EntitySpec) ([]EntityRow, error)

ListEntities runs a spec's list command and parses its tab-separated rows.

type Extension added in v1.31.0

type Extension = config.Extension

Extension is an engine's declared extension, aliased so callers work in one package without the store type leaking a second definition.

func DeclaredExtensions added in v1.31.0

func DeclaredExtensions(service string) []Extension

DeclaredExtensions returns what an engine's image can create, preferring the preset the service was installed from so an engine installed before the field existed still gets it, the same resolution IntrospectCommand uses.

type ImportIssue added in v1.30.0

type ImportIssue struct {
	Message string `json:"message"`
	Count   int    `json:"count"`
}

ImportIssue is one distinct complaint the engine made, and how often it made it. ImportReport summarises a load: psql exits 0 even when every statement in a dump fails, so without counting its output a dump can half land and still look like a clean import.

type ImportNotes added in v1.31.0

type ImportNotes struct {
	Skipped []ImportIssue
	Created []ImportIssue
}

ImportNotes is what the filter did on the way in, reported beside the errors so a dump lerd changed never reads as one that arrived untouched.

type ImportOptions added in v1.31.0

type ImportOptions struct {
	Fresh bool
}

ImportOptions carries the choices a caller makes about a load. Fresh drops and recreates the database first, so a dump replaces what is there instead of colliding with it statement by statement.

type ImportReport added in v1.30.0

type ImportReport struct {
	Errors  int           `json:"errors"`
	Issues  []ImportIssue `json:"issues,omitempty"`
	Omitted int           `json:"omitted,omitempty"`
	Skipped []ImportIssue `json:"skipped,omitempty"`
	Created []ImportIssue `json:"created,omitempty"`
}

Omitted is how many further distinct complaints were dropped past the cap, so a truncated list never reads as the whole of what went wrong. Skipped is what the sanitizer held back on the way in, reported so lerd never rewrites a dump silently.

func ImportDatabase added in v1.30.0

func ImportDatabase(service, database string, r io.Reader, opt ImportOptions) (ImportReport, error)

ImportDatabase streams a dump from r into database on the service container, through the declared import action. The database must already exist unless Fresh is set. For SQL-format engines the report carries what the engine complained about, which is the only sign of a partial load when the client still exits clean, and what the sanitizer held back; other formats stream untouched and rely on the client's exit code.

func RestoreSnapshot added in v1.22.0

func RestoreSnapshot(t SnapshotTarget, name string, emit func(PhaseEvent)) (ImportReport, error)

RestoreSnapshot loads a stored snapshot back into its database. A per-database restore drops and recreates the target database first so no orphan tables survive; an all-databases restore replays the self-cleaning dump as-is.

func (ImportReport) CreatedSummary added in v1.31.0

func (r ImportReport) CreatedSummary() string

CreatedSummary renders the extensions the load needed and lerd created, or could not, for the same reason.

func (ImportReport) SkippedSummary added in v1.31.0

func (r ImportReport) SkippedSummary() string

SkippedSummary renders what the sanitizer held back, so a load that came out clean because lerd filtered it says so rather than looking untouched.

func (ImportReport) Summary added in v1.30.0

func (r ImportReport) Summary() string

Summary renders the report as one line for the CLI and the phase stream.

type ImportTally added in v1.30.0

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

ImportTally counts an engine's complaints as its output streams past, so a caller can hand the output straight to a terminal and still summarise it at the end. It counts psql and mysql error lines, plus psql's "invalid command" lines, which are what a COPY block turns into once its table failed to exist.

func (*ImportTally) Report added in v1.30.0

func (t *ImportTally) Report() ImportReport

Report closes off whatever line was still in flight and returns the summary. Errors are kept in the order the engine hit them, because the first failures are what caused the later ones, and the cascade of unparsed COPY data that follows a missing table is folded into a single loudest line so it cannot crowd the cause out of the list.

func (*ImportTally) Stream added in v1.30.0

func (t *ImportTally) Stream() io.Writer

Stream returns a writer for one of a command's output streams. Each stream keeps its own unterminated tail, because os/exec copies stdout and stderr on a goroutine each and one shared tail would splice fragments of one line onto the other.

type PhaseEvent added in v1.18.0

type PhaseEvent struct {
	Phase   string `json:"phase"`
	Image   string `json:"image,omitempty"`
	Message string `json:"message,omitempty"`
	Dep     string `json:"dep,omitempty"`
	State   string `json:"state,omitempty"`
	Unit    string `json:"unit,omitempty"`
}

PhaseEvent is one step of the streaming preset-install flow.

type PortChange added in v1.27.0

type PortChange struct {
	Requested int  // the port the caller asked for (0 = reset to default)
	Actual    int  // the resulting published port (the guard may shift a reset)
	Installed bool // false when the service isn't installed (override saved only)
	WasActive bool // whether the unit was restarted to apply the change
	NoOp      bool // the requested port already matched the current override
}

PortChange reports the outcome of SetPublishedPort so each surface (CLI, MCP, Web UI) renders its own message from one shared code path.

func SetPublishedPort added in v1.27.0

func SetPublishedPort(name string, port int) (PortChange, error)

SetPublishedPort moves a service's published host port (port > 0) or resets it to the preset default (port == 0), persisting the override and re-rendering and restarting the unit as needed. It is the single entry point shared by the CLI `service port` command, the MCP service:port action, and the Web UI ports endpoint, so all three enforce identical validation, the port-ownership guard, and the host-proxy follower refresh. The container-internal port is untouched.

func SetPublishedPortFor added in v1.27.1

func SetPublishedPortFor(name string, containerPort, hostPort int) (PortChange, error)

SetPublishedPortFor moves the published host port of the mapping whose container-internal port is containerPort, the multi-port generalisation of SetPublishedPort. hostPort 0 (or the mapping's preset default) clears the override. When containerPort is the primary mapping it delegates to SetPublishedPort so the primary keeps its guard and host-proxy-follower handling; secondary ports (mailpit's 8025 UI, rustfs' 9001 console) persist to PublishedPorts and re-render like an extra-ports change.

type PublishedPortSnapshot added in v1.27.1

type PublishedPortSnapshot struct {
	PublishedPort  int
	PublishedPorts map[int]int
	ExtraPorts     []string
}

PublishedPortSnapshot captures a service's published-port configuration so a multi-step change (the Web UI ports modal applies the primary port, the secondary ports, and the extra ports in sequence) can be rolled back to a consistent pre-save state when a later step fails.

func SnapshotPublishedPorts added in v1.27.1

func SnapshotPublishedPorts(name string) (PublishedPortSnapshot, bool)

SnapshotPublishedPorts records a service's current published-port config. The bool is false only when the global config can't be read, in which case the caller should skip the transactional rollback rather than restore junk.

type ReconcileResult added in v1.27.0

type ReconcileResult struct {
	QuadletsRegenerated   []string // YAML present, unit was missing and regenerated
	OrphansRemoved        []string // service quadlet with no YAML, removed
	RunningOrphansSkipped []string // orphan left in place because its container is up
	DefinitionsRefreshed  []string // preset-backed YAML re-materialised from the store
	ConfigsApplied        []string // preset config file drifted; rewritten and the running service restarted
}

ReconcileResult reports what ReconcileServices changed.

func ReconcileServices added in v1.27.0

func ReconcileServices(emit func(PhaseEvent)) (ReconcileResult, error)

ReconcileServices enforces the issue #678 invariant: regenerate a missing unit from its YAML, remove an orphan service quadlet (no YAML; data left). A running orphan is skipped, not destroyed. Per-item errors are collected so one bad service can't block the rest.

type RemoveOptions added in v1.19.0

type RemoveOptions struct {
	// RemoveData renames the service's data directory to a timestamped
	// `.pre-remove-<ts>` sibling. The data is recoverable by renaming back.
	// On EXDEV the helper falls back to copy-tree + delete so the
	// recoverability promise holds across filesystems too.
	RemoveData bool

	// SkipFamilyRegen suppresses the post-remove RegenerateFamilyConsumers
	// pass. Set by ReinstallService, which then drives the regen itself
	// after install: InstallPresetByName regenerates internally for custom
	// services, while default-preset and failure paths call regen
	// explicitly. The regen-during-remove was racing the post-install
	// regen on macOS (launchctl bootout/bootstrap can fall through to
	// kickstart which doesn't re-read the plist), leaving consumers on
	// the partial plist.
	SkipFamilyRegen bool
}

RemoveOptions controls optional side effects of RemoveService.

type Snapshot added in v1.22.0

type Snapshot struct {
	Name         string    `json:"name"`
	Created      time.Time `json:"created"`
	Service      string    `json:"service"`
	Family       string    `json:"family"`
	Database     string    `json:"database"`
	AllDatabases bool      `json:"all_databases"`
	DumpFile     string    `json:"dump_file"`
	Compressed   bool      `json:"compressed"`
	SizeBytes    int64     `json:"size_bytes"`
	Site         string    `json:"site,omitempty"`
	GitBranch    string    `json:"git_branch,omitempty"`
}

Snapshot is the meta.json sidecar describing one stored database snapshot.

func CreateSnapshot added in v1.22.0

func CreateSnapshot(t SnapshotTarget, name string, ctx SnapshotMeta, emit func(PhaseEvent)) (*Snapshot, error)

CreateSnapshot dumps the target database (or every database when t.AllDatabases is set) into a new named snapshot under config.SnapshotsDir(). An empty name is auto-generated from the current UTC time.

func ListSnapshots added in v1.22.0

func ListSnapshots(service, database string, includeAll bool) ([]Snapshot, error)

ListSnapshots returns the stored snapshots for a service. A non-empty database narrows the result to that database; an empty database lists every database on the service. Service-wide all-databases snapshots are included when includeAll is set. Results are sorted newest first.

type SnapshotMeta added in v1.22.0

type SnapshotMeta struct {
	Site      string
	GitBranch string
}

SnapshotMeta carries the optional best-effort context recorded into a snapshot's meta.json.

type SnapshotTarget added in v1.22.0

type SnapshotTarget struct {
	Service      string
	Family       string
	Database     string
	AllDatabases bool
}

SnapshotTarget identifies the live database a snapshot is taken from or restored into. The cli and mcp layers build it from a resolved DB env.

type TuningBackup added in v1.23.0

type TuningBackup struct {
	Name      string `json:"name"`
	MtimeUnix int64  `json:"mtime_unix"`
}

TuningBackup is one row of the GET /api/services/{name}/config/backups response. Mirrors SiteNginxBackup so the frontend reuses the same list / diff modal / restore plumbing.

func ListTuningBackups added in v1.23.0

func ListTuningBackups(name string) ([]TuningBackup, error)

ListTuningBackups enumerates timestamped backups for a service, newest first. Returns an empty slice (not nil) when the directory exists but holds no matching files, and nil when the directory does not exist; an unreachable directory returns an error so the UI can distinguish a truly-empty backup set from a read failure.

type TuningResetResult added in v1.23.0

type TuningResetResult struct {
	RolledBack     bool
	AutoBackupName string
	ContentOnDisk  string
	Exists         bool
}

ResetTuningOverride replaces the user-editable tuning file with the bundled commented template (no active directives) and restarts the service so it picks up the bundled defaults. The file is NOT removed because the generated quadlet declares a Volume= bind mount at the same path; a missing source path would make podman refuse to start the container. Overwriting with the template keeps the mount valid AND lets the user see the same "what could I tune" hints they see on first save. Backups are preserved in ServiceTuningBkpDir() so a Restore can recover from an accidental reset. TuningResetResult mirrors TuningSaveResult so the HTTP handler can surface the same recovery state to the modal. RolledBack is true when the template restart failed but the prior bytes were successfully restored and the service is back on its previous config. AutoBackupName is set when the reset staged an implicit backup of the pre-reset content (so the user can recover even if they never ticked the explicit backup checkbox on prior saves).

func ResetTuningOverride added in v1.23.0

func ResetTuningOverride(name string) (TuningResetResult, error)

type TuningRestoreResult added in v1.23.0

type TuningRestoreResult struct {
	Content       string
	RolledBack    bool
	ContentOnDisk string
	Exists        bool
}

TuningRestoreResult carries the post-restore state, including the recovery flag that lets the HTTP layer tell a successful restore from one that needed an auto-rollback to keep the service running.

func RestoreTuningFromBackup added in v1.23.0

func RestoreTuningFromBackup(name, backupName string) (TuningRestoreResult, error)

RestoreTuningFromBackup is the entry point for the HTTP restore endpoint. Locks the per-service mutex, restores the named backup over the live file, restarts the service, and only removes the backup when the restart succeeded. If the restored bytes themselves crash the service (e.g. a stale backup with directives the current image rejects), the prior bytes are restored and the service is re-restarted so the user only loses the attempted restore, not the running service.

type TuningSaveResult added in v1.23.0

type TuningSaveResult struct {
	BackupName    string
	RolledBack    bool
	ContentOnDisk string
	Exists        bool
}

TuningSaveResult carries the information the HTTP handler needs to build a response. Content/Exists mirror what's on disk after the operation finished (whether or not the restart succeeded) so the client can refresh its `original` baseline even on the reload- failure path.

func SaveTuningOverride added in v1.23.0

func SaveTuningOverride(name, content string, backup bool) (TuningSaveResult, error)

SaveTuningOverride is the single entry point for writing the user tuning override file, regenerating the quadlet so the override Volume= mount is present on installs predating the feature, and restarting the unit so it re-reads the config. Shared by the `lerd service config` CLI command and the `/api/services/{name}/config` HTTP handler; matches the pattern of xdebugops.Apply.

When backup is true and a previous version of the file exists, a timestamped copy lands in ServiceTuningBkpDir() BEFORE the new bytes land so a crash between the backup write and the live write still leaves a recoverable copy on disk. An in-memory snapshot is taken regardless of `backup` so a failed restart auto-rolls the live file back to the pre-save bytes and re-restarts the service; the user only loses their unsaved edits, not the running service.

Order:

  1. ServiceInstalled guard — block silent-reinstall-on-edit for removed default presets that ResolveServiceForTuning would otherwise still resolve via LoadPreset.
  2. ResolveServiceForTuning + ServiceTuningMount — fail fast with family-unsupported for services that don't expose a mount.
  3. MaterializeServiceTuning — seed the template on first save.
  4. Snapshot current bytes for rollback.
  5. Stage a backup file when requested.
  6. Write `content` atomically.
  7. EnsureTuningQuadlet — regen so a freshly-written file isn't orphaned on installs predating the feature.
  8. Restart the unit so the container re-reads the override.
  9. WaitReady — if the new config crashes the service, restoreTuning Snapshot + restart again and report the failure.

type UpdateAvailability added in v1.19.0

type UpdateAvailability struct {
	Service       string `json:"service"`
	CurrentImage  string `json:"current_image"`
	CurrentTag    string `json:"current_tag"`
	LatestTag     string `json:"latest_tag,omitempty"`
	LatestImage   string `json:"latest_image,omitempty"`
	Available     bool   `json:"available"`
	Strategy      string `json:"strategy"`
	UpgradeTag    string `json:"upgrade_tag,omitempty"`
	UpgradeImage  string `json:"upgrade_image,omitempty"`
	PreviousImage string `json:"previous_image,omitempty"`
	// CanRollback is false when the most recent op was a migrate (rolling the
	// image back without restoring the pre-migrate data dir would corrupt it).
	CanRollback bool `json:"can_rollback"`
}

UpdateAvailability is the metadata returned by CheckUpdateAvailable so the UI can render an "update available → v8.4.3" badge without applying it.

func CheckUpdateAvailable added in v1.19.0

func CheckUpdateAvailable(name string) (*UpdateAvailability, error)

CheckUpdateAvailable queries the registry for a newer tag matching the preset's update_strategy. Network and unsupported-registry errors are swallowed so the UI stays quiet on offline / custom-registry installs. Successful results are cached for updateAvailabilityTTL so snapshot rebuilds don't fork a `podman image inspect` per service per rebuild.

func RefreshUpdateAvailability added in v1.23.0

func RefreshUpdateAvailability(name string) (*UpdateAvailability, error)

RefreshUpdateAvailability forces a fresh CheckUpdateAvailable by dropping both the in-memory availability cache and the on-disk registry tag cache for the service's image. Used by the manual "check for updates" UI so the user can bypass the 30s/6h cache windows on demand. Resolves the service once and threads it through computeUpdateAvailableResolved to avoid re-reading preset YAML.

Jump to

Keyboard shortcuts

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