models

package
v1.6.5 Latest Latest
Warning

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

Go to latest
Published: Jul 23, 2026 License: AGPL-3.0 Imports: 6 Imported by: 0

Documentation

Index

Constants

View Source
const (
	ScopeRead   = "read"   // read-only access to resources
	ScopeWrite  = "write"  // create/update/delete resources
	ScopeDeploy = "deploy" // trigger deployments and lifecycle actions
	ScopeAdmin  = "admin"  // administrative operations
	ScopeAll    = "*"      // all scopes
	// Registry-only scopes: a key carrying ONLY these may pull/push images but is
	// rejected by the rest of the API (least-privilege CI/registry credentials).
	ScopeRegistryRead  = "registry_read"  // pull from the container registry
	ScopeRegistryWrite = "registry_write" // push to the container registry
)

API key scopes gate what an API-key caller may do. An empty scope set means read-only (least privilege); "*" grants everything.

View Source
const (
	// CertSourceImported is a bring-your-own certificate the user uploaded.
	CertSourceImported = "imported"
	// CertSourceACME is a certificate Miabi issued via ACME DNS-01 using a
	// connected DNS provider (managed; auto-renewable).
	CertSourceACME = "acme"
)

Certificate sources.

View Source
const (
	CertStatusActive  = "active"  // issued and in use
	CertStatusIssuing = "issuing" // issuance in progress
	CertStatusFailed  = "failed"  // last issuance/renewal failed (see LastError)
)

Managed-certificate (ACME) statuses.

View Source
const (
	DNSProviderCloudflare   = "cloudflare"
	DNSProviderRoute53      = "route53"
	DNSProviderDigitalOcean = "digitalocean"
)

DNS provider types Miabi can connect to. Each maps to a libdns module; the credential shape differs per type (see the dns package). "manual" is the implicit default for a domain with no connected provider (copy-paste flow).

View Source
const (
	DNSProviderStatusOK    = "ok"
	DNSProviderStatusError = "error"
)

DNSProviderStatus is the last-known health of a connection.

View Source
const (
	DNSRecordPurposeVerification = "verification" // _miabi-challenge ownership TXT
	DNSRecordPurposeAppAddress   = "app-address"  // app A/AAAA/CNAME
)

DNSRecord purposes — why Miabi created a managed record.

View Source
const (
	JobSourceManual    = "manual"
	JobSourceAPI       = "api"
	JobSourceScheduled = "scheduled"
)

Job sources.

View Source
const (
	ConcurrencyAllow   = "allow"   // always spawn a new Job
	ConcurrencyForbid  = "forbid"  // skip the tick if a run is still active
	ConcurrencyReplace = "replace" // cancel the in-flight run, then spawn
)

CronJob concurrency policies (Kubernetes parity): what to do when a tick fires while the previous run is still active.

View Source
const (
	LDAPTLSNone     = "none"     // plain LDAP (discouraged; dev/LAN only)
	LDAPTLSStartTLS = "starttls" // upgrade a plain connection with StartTLS
	LDAPTLSLDAPS    = "ldaps"    // implicit TLS (usually port 636)
)

LDAP TLS modes for the directory connection.

View Source
const (
	MetaManagedBy       = "miabi.io/managed-by"       // origin: see ManagedBy* values
	MetaTemplate        = "miabi.io/template"         // marketplace template slug
	MetaTemplateVersion = "miabi.io/template-version" // marketplace template version
	MetaTemplateInstall = "miabi.io/template-install" // template install id
	MetaStack           = "miabi.io/stack"            // owning stack docker name
	MetaGitOpsSource    = "miabi.io/gitops-source"    // id of the GitOps project that created the resource
	// MetaRuntimeAutoService marks an app whose service runtime was auto-defaulted
	// by cluster mode (not chosen by the caller). It is a one-shot marker: the first
	// deploy re-evaluates the choice — downgrading to a container if the app turns
	// out to hold node-local state — then clears it, so a later explicit runtime
	// choice is always respected. Value: "true".
	MetaRuntimeAutoService = "miabi.io/runtime-auto-service"
	// MetaDeclarativeName records the declarative/manifest resource name a logical
	// database was provisioned for. A manifest Database may live as a dedicated
	// instance or as a logical database sharing another instance, so this tag is
	// how a reconcile maps the manifest name back to the exact logical database it
	// owns (instead of guessing by instance name / first-database).
	MetaDeclarativeName = "miabi.io/declarative-name"

	// Owner reference: what entity this resource belongs to / whose lifecycle it
	// follows. Distinct from managed-by (the *mechanism* of creation); owner is
	// the *parent*, stored as kind+id+name so the UI can link to it without a
	// join. See OwnerKind* values and SetOwner/Owner.
	MetaOwnerKind = "miabi.io/owner-kind" // one of OwnerKind* values
	MetaOwnerID   = "miabi.io/owner-id"   // numeric id of the owner (omitted when 0)
	MetaOwnerName = "miabi.io/owner-name" // owner display name (for rendering + linking)
)

Built-in metadata keys (all under MetadataReservedPrefix).

View Source
const (
	ManagedByUser        = "user"         // created by hand
	ManagedByMarketplace = "marketplace"  // installed from a marketplace template
	ManagedByStack       = "stack"        // created as part of a stack
	ManagedByStackImport = "stack-import" // created by importing a compose file
)

ManagedBy values record how a resource came to exist.

View Source
const (
	OwnerUser     = "user"     // a person created it for their own use (owner id = user id)
	OwnerApp      = "app"      // it backs an application
	OwnerDatabase = "database" // it backs a database instance
	OwnerStack    = "stack"    // it belongs to a stack
)

OwnerKind values classify what a resource belongs to (the MetaOwnerKind value).

View Source
const (
	NetAllocKindWorkspace = "workspace" // a workspace's default/user network
	NetAllocKindStack     = "stack"     // a per-stack network
	NetAllocKindGateway   = "gateway"   // the shared reverse-proxy network
	NetAllocKindOverlay   = "overlay"   // a per-workspace swarm overlay
	// NetAllocKindReserved marks a subnet that is not Miabi's but overlaps the pool
	// (a pre-existing/unmanaged Docker network), so the allocator never hands it out.
	NetAllocKindReserved = "reserved"
)

Network-allocation kinds, recorded for observability and to distinguish real managed networks from subnets merely reserved to avoid an overlap.

View Source
const (
	ConfigBotToken   = "bot_token"   // telegram; encrypted at rest
	ConfigChatID     = "chat_id"     // telegram
	ConfigWebhookURL = "webhook_url" // slack/discord; encrypted at rest
)

Config keys used by the channel types.

View Source
const (
	// SecurityProfileDefault runs containers as the image's default user with no
	// extra hardening — the platform's historical behavior.
	SecurityProfileDefault = "default"
	// SecurityProfileRestricted forces app/job containers to run as the platform
	// non-root UID (UID:0) with no-new-privileges and NET_RAW dropped — like
	// OpenShift's restricted SCC. May break images that require root.
	SecurityProfileRestricted = "restricted"
)

SecurityProfile values harden how a workspace's application and job containers run. The zero value ("") is treated as SecurityProfileDefault, which keeps the Plan model's GORM zero-value-omission invariant intact (see the limits block).

View Source
const (
	RegistryStorageFilesystem = "filesystem"
	RegistryStorageS3         = "s3"
)

Registry storage driver values for RegistrySettings.StorageType.

View Source
const (
	ResourceTypeApp      = "app"
	ResourceTypeDatabase = "database"
	ResourceTypeDomain   = "domain"
)

Resource types a per-resource policy may target.

View Source
const (
	SIEMSinkSyslog  = "syslog"
	SIEMSinkWebhook = "webhook"
	SIEMSinkS3      = "s3"
)

SIEM sink kinds.

View Source
const (
	SIEMFormatJSON = "json" // NDJSON
	SIEMFormatCEF  = "cef"  // syslog CEF
)

SIEM payload formats.

View Source
const (
	VolumeDriverLocal = "local"
	VolumeDriverNFS   = "nfs"
	VolumeDriverCIFS  = "cifs"
	// VolumeDriverHost is a bind to an operator-managed host path (under /mnt/*),
	// not a Docker volume. It is meant for storage the operator has mounted at the
	// SAME path on every node (e.g. a NAS), so a replicated service can share it;
	// AccessMode is rwx. Privileged workspaces only. HostPath carries the path.
	VolumeDriverHost = "host"
)

Volume driver names Miabi understands. The NFS/CIFS variants use Docker's built-in local driver with mount options (no external plugin required).

View Source
const DefaultOrganizationName = "default"

DefaultOrganizationName is the handle of the seeded default organization.

View Source
const DefaultRegistryVolume = "mb-registry-data"

DefaultRegistryVolume is the managed data volume for the filesystem driver.

View Source
const MetadataReservedPrefix = "miabi.io/"

MetadataReservedPrefix marks built-in, platform-managed metadata keys.

View Source
const Unlimited = -1

Unlimited is the limit value meaning "no cap". Distinct from 0 ("none allowed"), so a plan can forbid a resource entirely.

View Source
const UnlimitedPlanName = "Unlimited"

UnlimitedPlanName is the name of the built-in plan with no limits and all capabilities (seeded by SeedPlans). The platform system workspace is pinned to it so platform-managed infrastructure is never constrained by quotas.

Variables

NotifiableEvents is the curated set of application lifecycle events that webhooks and notification channels may subscribe to. Configuration changes and other timeline noise are intentionally excluded.

ValidScopes is the set of scopes accepted on API-key creation.

Functions

func ComposeImageRef

func ComposeImageRef(image, tag string) string

ComposeImageRef appends tag (defaulting to "latest") to a bare image, leaving refs that already specify a tag or digest untouched.

func EngineSupportsLogicalDatabases

func EngineSupportsLogicalDatabases(e DBEngine) bool

EngineSupportsLogicalDatabases reports whether an engine can host named per-app databases with their own users (the SQL engines, plus MongoDB which has databases with scoped users). Redis cannot.

func EngineUsesLogicalDatabaseRecord

func EngineUsesLogicalDatabaseRecord(e DBEngine) bool

EngineUsesLogicalDatabaseRecord reports whether an engine hangs its backups and app connection injection off a logical Database row. This is the set of engines from EngineSupportsLogicalDatabases plus libSQL: libSQL hosts a single database (users cannot create more) but Miabi auto-creates one implicit Database row at provision time so the existing per-database backup and injection pipeline is reused unchanged. Redis, which has neither logical databases nor backups, is the only engine excluded.

func IsNotifiable

func IsNotifiable(t AppEventType) bool

IsNotifiable reports whether an event type may trigger outbound notifications.

func IsReservedMetadataKey

func IsReservedMetadataKey(key string) bool

IsReservedMetadataKey reports whether key is platform-managed (built-in).

func IsValidPermission

func IsValidPermission(p Permission) bool

IsValidPermission reports whether p is a known catalogued permission.

func IsValidResourceType

func IsValidResourceType(t string) bool

IsValidResourceType reports whether t is a supported resource type.

func IsValidSIEMSink

func IsValidSIEMSink(s string) bool

IsValidSIEMSink reports whether s is a supported sink kind.

func NormalizeSecurityProfile

func NormalizeSecurityProfile(p string) string

NormalizeSecurityProfile maps the empty/zero value to the default profile.

func RoleHasPermission

func RoleHasPermission(role WorkspaceRole, p Permission) bool

RoleHasPermission reports whether a built-in role grants a permission.

func RolePermissions

func RolePermissions(role WorkspaceRole) map[Permission]bool

RolePermissions returns the permission set of a built-in role as a copy.

func SplitImageRef

func SplitImageRef(ref string) (image, tag string)

SplitImageRef is the inverse of ComposeImageRef: it splits a pull reference into its repository and tag (e.g. "ghcr.io/org/app:v2" -> "ghcr.io/org/app", "v2"). A digest-pinned ref is returned as the image with an empty tag.

func ValidAccessMode

func ValidAccessMode(m ServerAccessMode) bool

ValidAccessMode reports whether m is a known access mode.

func ValidAppBuildMethod

func ValidAppBuildMethod(m AppBuildMethod) bool

ValidAppBuildMethod reports whether m is a known build method.

func ValidDNSProviderType

func ValidDNSProviderType(t string) bool

ValidDNSProviderType reports whether t is a known provider type.

func ValidDeployStrategy

func ValidDeployStrategy(s DeployStrategy) bool

ValidDeployStrategy reports whether s is a known strategy.

func ValidHealthcheckType

func ValidHealthcheckType(t HealthcheckType) bool

ValidHealthcheckType reports whether t is a known healthcheck type.

func ValidImagePullPolicy

func ValidImagePullPolicy(p ImagePullPolicy) bool

ValidImagePullPolicy reports whether p is a known image pull policy.

func ValidRestartPolicy

func ValidRestartPolicy(p RestartPolicy) bool

ValidRestartPolicy reports whether p is a known restart policy.

func ValidRunnerScope

func ValidRunnerScope(s RunnerScope) bool

ValidRunnerScope reports whether s is a known runner scope.

func ValidRuntimeKind

func ValidRuntimeKind(k RuntimeKind) bool

ValidRuntimeKind reports whether k is a known runtime kind.

func ValidVolumeAccessMode

func ValidVolumeAccessMode(m VolumeAccessMode) bool

ValidVolumeAccessMode reports whether m is a known access mode.

Types

type ACMEAccount

type ACMEAccount struct {
	ID       uint   `json:"id" gorm:"primaryKey"`
	CADirURL string `json:"ca_dir_url" gorm:"uniqueIndex;not null"`
	Email    string `json:"email" gorm:"not null"`
	// KeyEnc is the PEM-encoded account private key, crypto.Encrypt'd at rest.
	KeyEnc string `json:"-" gorm:"type:text;not null"`
	// RegistrationJSON is the lego registration.Resource (account URL + body).
	RegistrationJSON string `json:"-" gorm:"type:text"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

ACMEAccount is the platform's registered ACME account for a given CA directory, reused across all managed-certificate issuance. The account private key is encrypted at rest and never returned; the registration resource (account URL) is stored so issuance reuses the same account. Keyed by CADirURL so switching CA (e.g. staging -> production) registers a distinct account.

type APIKey

type APIKey struct {
	ID          uint     `json:"id" gorm:"primaryKey"`
	UserID      uint     `json:"user_id" gorm:"index;not null"`
	WorkspaceID *uint    `json:"workspace_id,omitempty" gorm:"index"`
	Name        string   `json:"name" gorm:"not null"`
	KeyHash     string   `json:"-" gorm:"uniqueIndex;not null"`
	KeyPrefix   string   `json:"key_prefix" gorm:"index;not null"`
	Scopes      []string `json:"scopes" gorm:"serializer:json"`
	AllowedIPs  []string `json:"allowed_ips" gorm:"serializer:json"`
	// ApplicationID narrows an ephemeral job key to a single application: it may
	// act on (deploy) only that app, nothing else in the workspace. nil = the
	// key is not app-bound (ordinary user keys).
	ApplicationID *uint `json:"application_id,omitempty" gorm:"index"`
	// Ephemeral marks a machine-minted, short-lived job/registry credential (issued
	// per runner lease, revoked when the run goes terminal). Ephemeral keys are
	// hidden from the API-keys UI, excluded from the MaxAPIKeys quota, and findable
	// by the orphan sweeper.
	Ephemeral  bool       `json:"ephemeral" gorm:"not null;default:false;index"`
	LastUsedAt *time.Time `json:"last_used_at"`
	ExpiresAt  *time.Time `json:"expires_at"`
	Revoked    bool       `json:"revoked" gorm:"default:false;not null"`
	CreatedAt  time.Time  `json:"created_at"`
}

APIKey is a long-lived bearer credential for programmatic access, scoped to a user and (optionally) a workspace.

func (*APIKey) BoundToApp

func (k *APIKey) BoundToApp(appID uint) bool

BoundToApp reports whether the key is restricted to a single application. A caller enforcing the deploy route uses this to reject a job key acting on any app other than the one it was minted for.

func (*APIKey) HasScope

func (k *APIKey) HasScope(s string) bool

HasScope reports whether the key grants the given scope. An empty scope set defaults to read-only; "*" grants everything.

func (*APIKey) IsExpired

func (k *APIKey) IsExpired() bool

IsExpired reports whether the key has passed its expiry.

func (*APIKey) IsRegistryOnly

func (k *APIKey) IsRegistryOnly() bool

IsRegistryOnly reports whether the key's scopes grant only registry access (so it must be refused by the general API). An empty scope set defaults to read, which is a general scope, so it is never registry-only.

func (*APIKey) IsValid

func (k *APIKey) IsValid() bool

IsValid reports whether the key is currently usable (not revoked, not expired).

type Alert added in v1.6.0

type Alert struct {
	ID          uint          `json:"id" gorm:"primaryKey"`
	WorkspaceID uint          `json:"workspace_id" gorm:"index;not null"`
	RuleKey     string        `json:"rule_key" gorm:"not null"` // which built-in rule fired
	Category    AlertCategory `json:"category" gorm:"not null"`
	Severity    AlertSeverity `json:"severity" gorm:"not null"`
	State       AlertState    `json:"state" gorm:"not null;default:firing"`

	// DedupKey identifies the condition+subject. The partial unique index in the
	// migration enforces at most one ACTIVE alert per (workspace, dedup_key).
	DedupKey    string `json:"dedup_key" gorm:"not null"`
	SubjectType string `json:"subject_type"`           // app | database | node | route | backup
	SubjectRef  string `json:"subject_ref"`            // stable id/slug of the subject
	SubjectLink string `json:"subject_link,omitempty"` // UI deep-link
	// MinRole is the lowest workspace role that receives this alert (fan-out gate).
	MinRole WorkspaceRole `json:"min_role" gorm:"not null;default:developer"`

	Title  string            `json:"title"`
	Body   string            `json:"body"`
	Count  int64             `json:"count" gorm:"not null;default:1"` // signals folded into this alert
	Labels map[string]string `json:"labels,omitempty" gorm:"serializer:json"`

	FirstSeen  time.Time  `json:"first_seen"`
	LastSeen   time.Time  `json:"last_seen"`
	ResolvedAt *time.Time `json:"resolved_at,omitempty"`
	CreatedAt  time.Time  `json:"created_at"`
	UpdatedAt  time.Time  `json:"updated_at"`
}

Alert is a workspace-level, stateful, deduplicated condition derived from signals (app events, metrics, jobs). It is shared across the workspace's members; per-user delivery is a Notification. The dedup key is the identity of the condition (e.g. "crashloop:app:42"): a repeat signal updates the existing active alert (bumping Count/LastSeen) instead of creating a new row, and a recovery signal resolves it — this is what turns 40 crash events into one line.

type AlertCategory added in v1.6.0

type AlertCategory string

AlertCategory groups alerts for role-based fan-out and UI filtering.

const (
	CategoryDeploy   AlertCategory = "deploy"
	CategoryRuntime  AlertCategory = "runtime"
	CategoryDatabase AlertCategory = "database"
	CategoryStorage  AlertCategory = "storage"
	CategoryTLS      AlertCategory = "tls"
	CategoryNode     AlertCategory = "node"
	CategoryCI       AlertCategory = "ci" // runners / pipelines
	CategoryQuota    AlertCategory = "quota"
	CategorySecurity AlertCategory = "security"
)

type AlertSeverity added in v1.6.0

type AlertSeverity string

AlertSeverity ranks an alert's urgency. Only warning/critical notify by default; info lives in the inbox.

const (
	AlertInfo     AlertSeverity = "info"
	AlertWarning  AlertSeverity = "warning"
	AlertCritical AlertSeverity = "critical"
)

type AlertState added in v1.6.0

type AlertState string

AlertState is the alert's lifecycle position. An alert is a small FSM, not a log row: it fires, may be acknowledged, auto-resolves when the condition clears, and is archived after a retention window.

const (
	AlertFiring       AlertState = "firing"
	AlertAcknowledged AlertState = "acknowledged"
	AlertResolved     AlertState = "resolved"
	AlertArchived     AlertState = "archived"
)

func (AlertState) Active added in v1.6.0

func (s AlertState) Active() bool

Active reports whether the alert is still an open condition (firing or acknowledged) — the states the dedup key is unique across.

type AnalyticsRollup added in v1.6.0

type AnalyticsRollup struct {
	ID            uint      `gorm:"primaryKey" json:"-"`
	WorkspaceID   uint      `gorm:"uniqueIndex:idx_arollup,priority:1;not null" json:"workspace_id"`
	ApplicationID uint      `gorm:"uniqueIndex:idx_arollup,priority:2;index" json:"application_id"`
	RouteName     string    `gorm:"uniqueIndex:idx_arollup,priority:3;size:200" json:"route_name"`
	Bucket        time.Time `gorm:"uniqueIndex:idx_arollup,priority:4;index" json:"bucket"` // minute-truncated UTC

	Requests  int64 `json:"requests"`
	BytesIn   int64 `json:"bytes_in"`
	BytesOut  int64 `json:"bytes_out"`
	Status2xx int64 `json:"status_2xx"`
	Status3xx int64 `json:"status_3xx"`
	Status4xx int64 `json:"status_4xx"`
	Status5xx int64 `json:"status_5xx"`

	// Latency histograms (per-bucket counts over analytics.LatencyBoundsMs, with a
	// trailing overflow bucket) + sums for the mean.
	DurationHist []int64 `gorm:"serializer:json" json:"-"`
	UpstreamHist []int64 `gorm:"serializer:json" json:"-"`
	DurationSum  int64   `json:"-"` // milliseconds
	UpstreamSum  int64   `json:"-"`

	// VisitorsHLL is the serialized HyperLogLog sketch of visitor ids for this
	// bucket — mergeable, so a range's uniques is the merge of its buckets.
	VisitorsHLL []byte `json:"-"`

	// Bounded top-K count maps for the categorical breakdowns.
	TopPaths      map[string]int64 `gorm:"serializer:json" json:"-"`
	TopReferrers  map[string]int64 `gorm:"serializer:json" json:"-"`
	TopCountries  map[string]int64 `gorm:"serializer:json" json:"-"`
	TopUAFamilies map[string]int64 `gorm:"serializer:json" json:"-"` // browser family
	TopOS         map[string]int64 `gorm:"serializer:json" json:"-"`
	TopDevice     map[string]int64 `gorm:"serializer:json" json:"-"` // desktop | mobile | tablet | bot
	TopMethods    map[string]int64 `gorm:"serializer:json" json:"-"`

	// BotRequests is the subset of Requests classified as automated (crawlers,
	// scripts) — the rest are treated as human, for the bot-vs-human split.
	BotRequests int64 `json:"-"`
}

AnalyticsRollup is one minute-bucketed aggregate of a route's HTTP traffic, built by the analytics consumer from Goma's per-request event stream. Rows are keyed by (workspace, app, route, minute); queries merge the rows spanning a range. It powers the Traffic, Performance and Web Analytics dashboards without any per-request storage or app instrumentation.

Latency percentiles come from the histograms (DurationHist/UpstreamHist over analytics.LatencyBoundsMs); unique visitors from the mergeable HyperLogLog sketch (VisitorsHLL); the Top* maps are bounded top-K counts. None of it holds PII (the visitor id is a daily-salted hash produced at the edge; no IP).

type AppBuildMethod

type AppBuildMethod string

AppBuildMethod selects how a git-source application's image is built. It only applies to git apps (image apps pull a prebuilt image).

const (
	// BuildAuto inspects the cloned repository at build time: a Dockerfile in the
	// tree builds with Docker, otherwise Cloud Native Buildpacks build it. Default.
	BuildAuto AppBuildMethod = "auto"
	// BuildDockerfile always builds the repo's Dockerfile.
	BuildDockerfile AppBuildMethod = "dockerfile"
	// BuildBuildpack always builds with Cloud Native Buildpacks (no Dockerfile
	// required); the resulting image runs via the CNB launcher.
	BuildBuildpack AppBuildMethod = "buildpack"
)

type AppEnvVar

type AppEnvVar struct {
	ID            uint      `json:"id" gorm:"primaryKey"`
	ApplicationID uint      `json:"application_id" gorm:"index:idx_env_app_key,unique;not null"`
	Key           string    `json:"key" gorm:"index:idx_env_app_key,unique;not null"`
	Value         string    `json:"value"` // plaintext for non-secret; ciphertext when IsSecret
	IsSecret      bool      `json:"is_secret" gorm:"not null;default:false"`
	CreatedAt     time.Time `json:"created_at"`
	UpdatedAt     time.Time `json:"updated_at"`
}

AppEnvVar is an environment variable for an application. Secret values are encrypted at rest.

type AppEvent

type AppEvent struct {
	ID            uint              `json:"id" gorm:"primaryKey"`
	WorkspaceID   uint              `json:"workspace_id" gorm:"index;not null"`
	ApplicationID uint              `json:"application_id" gorm:"index:idx_event_app_id;not null"`
	Type          AppEventType      `json:"type" gorm:"not null"`
	Severity      AppEventSeverity  `json:"severity" gorm:"not null;default:info"`
	Message       string            `json:"message"`
	Metadata      map[string]string `json:"metadata,omitempty" gorm:"serializer:json"`
	ActorID       *uint             `json:"actor_id,omitempty"`
	CreatedAt     time.Time         `json:"created_at" gorm:"index:idx_event_app_id"`

	// Display fields are populated at delivery time (notifications, webhooks) and
	// are never persisted. They let a renderer name the resource instead of
	// showing a bare numeric id. Empty when the application has been deleted.
	ApplicationName string `json:"application_name,omitempty" gorm:"-"`
	ApplicationSlug string `json:"application_slug,omitempty" gorm:"-"`
}

AppEvent is a timeline entry for an application: lifecycle transitions (deploys, releases), runtime container events (start/stop/crash/health), and configuration changes. Distinct from AuditLog (user mutations, admin-scoped) and from deployment build logs.

type AppEventSeverity

type AppEventSeverity string

AppEventSeverity colors an event in the UI.

const (
	SeverityInfo    AppEventSeverity = "info"
	SeverityWarning AppEventSeverity = "warning"
	SeverityError   AppEventSeverity = "error"
)

type AppEventType

type AppEventType string

AppEventType is a coarse classification of an application event.

const (
	EventDeployStarted    AppEventType = "deploy.started"
	EventDeploySucceeded  AppEventType = "deploy.succeeded"
	EventDeployFailed     AppEventType = "deploy.failed"
	EventRollbackStarted  AppEventType = "rollback.started"
	EventReleaseActivated AppEventType = "release.activated"
	EventContainerStarted AppEventType = "container.started"
	EventContainerStopped AppEventType = "container.stopped"
	EventContainerDied    AppEventType = "container.died"
	EventContainerOOM     AppEventType = "container.oom"
	EventContainerHealth  AppEventType = "container.health"
	EventEnvUpdated       AppEventType = "env.updated"
	EventDomainAttached   AppEventType = "domain.attached"
	EventDomainVerified   AppEventType = "domain.verified"
	EventVolumeAttached   AppEventType = "volume.attached"
	EventVolumeDetached   AppEventType = "volume.detached"
	EventSettingsUpdated  AppEventType = "settings.updated"
	EventAppCreated       AppEventType = "app.created"
	EventAppDeleted       AppEventType = "app.deleted"
)

type AppMount

type AppMount struct {
	VolumeID   uint   `json:"volume_id"`
	DockerName string `json:"docker_name"`
	Path       string `json:"path"`
	HostPreset string `json:"host_preset,omitempty"` // set => privileged host bind, not a volume
	// HostPath is the bind source for a "host" driver volume (denormalized from the
	// volume at attach time so the runtime binds it without a volume lookup). When
	// set, the mount is a bind of HostPath, not a Docker named volume.
	HostPath string `json:"host_path,omitempty"`
	ReadOnly bool   `json:"read_only,omitempty"`
}

AppMount attaches storage to an application at a container path. It is either a managed workspace volume (VolumeID/DockerName) or — for privileged workspaces only — an allow-listed host bind identified by HostPreset (see package hostmount). The two are mutually exclusive; the host source path is resolved server-side from the preset, never stored from client input.

type AppPort

type AppPort struct {
	ID            uint   `json:"id" gorm:"primaryKey"`
	ApplicationID uint   `json:"application_id" gorm:"index:idx_appport_unique,unique;not null"`
	ContainerPort int    `json:"container_port" gorm:"index:idx_appport_unique,unique;not null"`
	Protocol      string `json:"protocol" gorm:"index:idx_appport_unique,unique;not null;default:tcp"` // tcp | udp
	// Scheme is the application protocol the container speaks on this port
	// (http | https), used to build the Gateway backend URL. Transport stays in
	// Protocol; this is L7. Defaults to http.
	Scheme string `json:"scheme" gorm:"not null;default:http"`
	Name   string `json:"name"`
}

AppPort is a container port an application exposes. Developer-managed; no approval is required to declare one.

type AppSourceType

type AppSourceType string

AppSourceType is how an application's image is obtained.

const (
	AppSourceImage AppSourceType = "image" // pull a prebuilt image
	AppSourceGit   AppSourceType = "git"   // build from a Git repo
)

type AppStatus

type AppStatus string

AppStatus is the high-level state of an application.

const (
	AppStatusCreated   AppStatus = "created"
	AppStatusDeploying AppStatus = "deploying"
	AppStatusRunning   AppStatus = "running"
	AppStatusStopped   AppStatus = "stopped"
	AppStatusFailed    AppStatus = "failed"
)

type Application

type Application struct {
	UIDModel
	ID          uint `json:"id" gorm:"primaryKey"`
	WorkspaceID uint `json:"workspace_id" gorm:"index:idx_app_workspace_name,unique;not null"`
	// Name is the unique, URL/CLI/docker handle (lowercase [a-z0-9-]) scoped to
	// the workspace. Renamed from the former "slug"; the numeric ID/UID remain the
	// stable internal references.
	Name string `json:"name" gorm:"index:idx_app_workspace_name,unique;not null"`
	// DisplayName is the free-text label shown in the UI. Renamed from the former
	// "name"; not unique.
	DisplayName string        `json:"display_name"`
	SourceType  AppSourceType `json:"source_type" gorm:"not null;default:image"`
	// Alias is the stable in-network DNS name + container hostname for the app,
	// shaped "mb-app-<token>-<id>". Generated once at creation and reused as the
	// reverse-proxy upstream so it survives redeploys. Empty on legacy apps,
	// which fall back to "mb-app-<id>".
	Alias string `json:"alias,omitempty" gorm:"index"`
	// ServerID is the node this app runs on (0 = local control-plane node). In
	// cluster mode (RuntimeService) it is a placement hint rather than a fixed
	// node — Swarm schedules the tasks subject to PlacementConstraints.
	ServerID uint `json:"server_id" gorm:"index;not null;default:0"`
	// ServerName is the display name of the node (transient; populated on read so
	// the UI can show where the app runs). Empty if the node is unknown.
	ServerName string `json:"server_name,omitempty" gorm:"-"`
	// Nodes is the real per-node replica placement of a cluster (RuntimeService)
	// app: where the Swarm scheduler actually placed its running tasks (transient;
	// populated only on the app-detail read, and only in cluster mode). Empty for
	// container apps, whose single node is ServerName.
	Nodes []NodePlacement `json:"nodes,omitempty" gorm:"-"`

	// Cluster runtime. RuntimeKind defaults to "container" (single container on
	// plain Docker); "service" runs the app as a replicated Swarm service on the
	// workspace overlay (only when cluster mode is enabled). Replicas,
	// PlacementConstraints and UpdateConfig apply to the service runtime.
	RuntimeKind          RuntimeKind          `json:"runtime_kind" gorm:"not null;default:container"`
	Replicas             int                  `json:"replicas" gorm:"not null;default:1"`
	PlacementConstraints []string             `json:"placement_constraints,omitempty" gorm:"serializer:json"`
	UpdateConfig         *ServiceUpdateConfig `json:"update_config,omitempty" gorm:"serializer:json"`

	// Icon is an optional logo for the app (a URL or an "mdi-…" class) shown in
	// the UI. Set from a marketplace template on install; empty for ad-hoc apps,
	// which fall back to a generated avatar.
	Icon string `json:"icon,omitempty"`

	// Image source. Image is the repository (e.g. "nginx", "ghcr.io/org/app");
	// Tag is optional and defaults to "latest" when composing the pull ref.
	Image string `json:"image"`
	Tag   string `json:"tag,omitempty"`

	// Git source.
	GitRepo string `json:"git_repo,omitempty"`
	GitRef  string `json:"git_ref,omitempty"`

	// Build config (git source only). BuildMethod selects how the image is built:
	// auto (Dockerfile if present, else buildpacks), dockerfile, or buildpack.
	// Builder optionally overrides the buildpack builder image (empty = platform
	// default); Buildpacks pins extra buildpacks to apply; BuildEnv supplies build
	// arguments to the buildpack build. All are ignored for image-source apps.
	BuildMethod AppBuildMethod    `json:"build_method,omitempty" gorm:"not null;default:auto"`
	Builder     string            `json:"builder,omitempty"`
	Buildpacks  []string          `json:"buildpacks,omitempty" gorm:"serializer:json"`
	BuildEnv    map[string]string `json:"build_env,omitempty" gorm:"serializer:json"`

	// Credentials. RegistryID selects a stored registry credential used to pull
	// private images; GitRepositoryID selects a stored Git credential used to
	// clone private repos. Both nil = anonymous (public) source.
	RegistryID      *uint `json:"registry_id,omitempty" gorm:"index"`
	GitRepositoryID *uint `json:"git_repository_id,omitempty" gorm:"index"`

	// StackID optionally groups the app under a Stack (nil = ungrouped). When
	// set, the stack's DockerName is applied to the app's containers as the
	// Docker Compose project label.
	StackID *uint `json:"stack_id,omitempty" gorm:"index"`

	// TemplateInstallID links the app back to the marketplace template install it
	// was created from (nil = not installed from a template). Lets the UI show
	// provenance ("installed from <template>") and jump to the install.
	TemplateInstallID *uint `json:"template_install_id,omitempty" gorm:"index"`

	// OfficialTemplate marks an app created from an *official* marketplace template.
	// It is set by the platform at install time (never by user input) and is the
	// trust boundary for the AllowOfficialImageUser plan capability, which lets such
	// apps keep the image's default user under the restricted security profile.
	OfficialTemplate bool `json:"official_template" gorm:"not null;default:false"`

	// Metadata holds free-form labels (provenance, grouping, declarative/GitOps).
	// Keys under the reserved "miabi.io/" prefix are platform-managed.
	Metadata Metadata `json:"metadata,omitempty" gorm:"serializer:json"`

	// Annotations holds free-form, non-identifying descriptive metadata (the
	// manifest's metadata.annotations). Unlike Metadata/labels it is never used
	// for selection or grouping and carries no reserved keys — purely human/tool
	// notes (owner, description, links). Persisted as JSON.
	Annotations Metadata `json:"annotations,omitempty" gorm:"serializer:json"`

	// ContainerLabels are user-defined Docker labels stamped onto this app's
	// container(s)/service at deploy time, for label-driven ecosystem tools
	// (Traefik, autoheal, Watchtower, …). Distinct from Metadata (a DB-record
	// concern): these land on the real Docker object. Keys under Miabi's reserved
	// Docker prefixes (io.miabi.*, miabi.*, com.docker.*) are stripped/rejected so
	// user labels can never override or spoof platform system labels. Persisted as
	// JSON. Editing them requires a redeploy to take effect (like ports/volumes).
	ContainerLabels map[string]string `json:"container_labels,omitempty" gorm:"serializer:json"`

	// Runtime config.
	Command     []string   `json:"command,omitempty" gorm:"serializer:json"`
	Mounts      []AppMount `json:"mounts,omitempty" gorm:"serializer:json"` // attached volumes
	Port        int        `json:"port,omitempty"`                          // primary container port
	MemoryBytes int64      `json:"memory_bytes"`                            // 0 = unlimited
	NanoCPUs    int64      `json:"nano_cpus"`                               // 0 = unlimited; 1 core = 1e9
	// GPUCount is the number of whole GPU units this app requests (0 = none). It
	// is declarative on the app but only consumes quota while the app is running,
	// and is resolved to concrete devices on the app's node at each deploy.
	GPUCount int `json:"gpu_count" gorm:"not null;default:0"`
	// GPUKind narrows the request to a vendor or model when set ("nvidia",
	// "NVIDIA A100-…"); empty = any enabled GPU on the app's node.
	GPUKind string `json:"gpu_kind,omitempty"`
	// RestartPolicy is the Docker restart policy for the app's container.
	// Defaults to unless-stopped (the platform's historical behavior).
	RestartPolicy RestartPolicy `json:"restart_policy" gorm:"not null;default:unless-stopped"`
	// ImagePullPolicy decides whether a deploy pulls the image. Defaults to
	// always (the platform's historical behavior of fetching the tag each deploy).
	ImagePullPolicy ImagePullPolicy `json:"image_pull_policy" gorm:"not null;default:always"`

	// Healthcheck. Type none disables it; http builds a curl/wget probe against
	// HealthcheckHTTPPath on HealthcheckPort (0 = the app's primary port);
	// command runs HealthcheckCommand via the shell. When a healthcheck is set,
	// a deploy waits for the container to report healthy before going live.
	HealthcheckType               HealthcheckType `json:"healthcheck_type" gorm:"not null;default:none"`
	HealthcheckHTTPPath           string          `json:"healthcheck_http_path"`
	HealthcheckPort               int             `json:"healthcheck_port"`
	HealthcheckCommand            string          `json:"healthcheck_command"`
	HealthcheckIntervalSeconds    int             `json:"healthcheck_interval_seconds" gorm:"not null;default:30"`
	HealthcheckTimeoutSeconds     int             `json:"healthcheck_timeout_seconds" gorm:"not null;default:5"`
	HealthcheckRetries            int             `json:"healthcheck_retries" gorm:"not null;default:3"`
	HealthcheckStartPeriodSeconds int             `json:"healthcheck_start_period_seconds" gorm:"not null;default:0"`

	// Imported marks an app adopted from a pre-existing (hand-run / compose)
	// container rather than created through Miabi. It clears on the first
	// native deploy, which recreates the container under Miabi conventions.
	Imported bool `json:"imported" gorm:"not null;default:false"`

	// ExternalLabel is the stable DNS label used for one-click external access
	// (`<label>.<base-domain>`). Generated once on first expose (default
	// `<slug>-<token>`) so the public URL survives renames/redeploys.
	ExternalLabel string `json:"external_label,omitempty" gorm:"index"`

	Status           AppStatus `json:"status" gorm:"not null;default:created"`
	CurrentReleaseID *uint     `json:"current_release_id"`
	// RedeployRequired is set when config changes while the app is stopped (the
	// running container, if any, is stale). Start/Restart then redeploy instead
	// of reusing the old container, and any successful deploy clears it.
	RedeployRequired bool `json:"redeploy_required" gorm:"not null;default:false"`

	// DeployStrategy is the app's default rollout method, applied when a deploy
	// does not specify one. See DeployStrategy constants.
	DeployStrategy DeployStrategy `json:"deploy_strategy" gorm:"not null;default:rolling"`

	// Canary tuning (used when DeployStrategy is canary or a deploy overrides to
	// canary). The platform starts at CanaryInitialWeight and adds
	// CanaryStepWeight every CanaryStepIntervalSeconds until it reaches 100%.
	CanaryInitialWeight       int `json:"canary_initial_weight" gorm:"not null;default:10"`
	CanaryStepWeight          int `json:"canary_step_weight" gorm:"not null;default:20"`
	CanaryStepIntervalSeconds int `json:"canary_step_interval_seconds" gorm:"not null;default:60"`

	// Canary live state. When CanaryReleaseID is set, a second (canary) container
	// runs alongside the stable release and the reverse-proxy splits traffic:
	// CanaryWeight percent goes to the canary, the rest to the stable release.
	// Cleared on promote/abort and superseded by any normal deploy.
	CanaryReleaseID *uint `json:"canary_release_id,omitempty"`
	CanaryWeight    int   `json:"canary_weight" gorm:"not null;default:0"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`

	EnvVars  []AppEnvVar `json:"env_vars,omitempty" gorm:"foreignKey:ApplicationID"`
	Networks []Network   `json:"networks,omitempty" gorm:"many2many:application_networks;"`
	Ports    []AppPort   `json:"ports,omitempty" gorm:"foreignKey:ApplicationID"`
	Stack    *Stack      `json:"stack,omitempty" gorm:"foreignKey:StackID;constraint:OnDelete:SET NULL"`
	// Credential FKs declared so deleting a referenced registry/git credential
	// nulls the pointer here (ON DELETE SET NULL) instead of dangling. The app
	// then pulls/clones anonymously until re-linked. Associations not serialized.
	Registry      *Registry      `json:"-" gorm:"foreignKey:RegistryID;constraint:OnDelete:SET NULL"`
	GitRepository *GitRepository `json:"-" gorm:"foreignKey:GitRepositoryID;constraint:OnDelete:SET NULL"`
}

Application is a deployable workload owned by a workspace.

func (*Application) ImageRef

func (a *Application) ImageRef(tagOverride string) string

ImageRef composes the effective pull reference from Image and Tag. When tagOverride is non-empty it takes precedence over the app's Tag. If the image already carries a tag or digest, it is used verbatim; otherwise the tag (or "latest") is appended.

type AuditLog

type AuditLog struct {
	ID          uint           `json:"id" gorm:"primaryKey"`
	ActorID     *uint          `json:"actor_id" gorm:"index"`
	WorkspaceID *uint          `json:"workspace_id" gorm:"index"`
	Action      string         `json:"action" gorm:"index;not null"`
	TargetType  string         `json:"target_type"`
	TargetID    string         `json:"target_id"`
	IPAddress   string         `json:"ip_address"`
	Metadata    map[string]any `json:"metadata,omitempty" gorm:"serializer:json"`
	CreatedAt   time.Time      `json:"created_at"`
}

AuditLog is an append-only record of a mutating action.

type Backup

type Backup struct {
	ID          uint         `json:"id" gorm:"primaryKey"`
	WorkspaceID uint         `json:"workspace_id" gorm:"index;not null"`
	DatabaseID  uint         `json:"database_id" gorm:"index;not null"`
	Engine      DBEngine     `json:"engine"`
	ServerID    uint         `json:"server_id" gorm:"index;not null;default:0"` // node the backup ran on
	Status      BackupStatus `json:"status" gorm:"not null;default:pending"`
	Trigger     string       `json:"trigger"`               // manual | scheduled
	Destination string       `json:"destination"`           // local | s3
	VolumeName  string       `json:"volume_name,omitempty"` // local destination
	S3Bucket    string       `json:"s3_bucket,omitempty"`   // s3 destination (reference)
	S3Path      string       `json:"s3_path,omitempty"`
	Filename    string       `json:"filename,omitempty"`
	SizeBytes   int64        `json:"size_bytes"`
	// Logs is a bounded tail of the backup output for instant display; the full
	// log lives in the log store at LogRef once the run is terminal (see
	// plans/log-storage.md). LogRef is empty when the store is disabled or the
	// row predates externalization — readers fall back to this tail.
	Logs         string     `json:"logs,omitempty" gorm:"type:text"`
	LogRef       string     `json:"log_ref,omitempty"`
	LogBytes     int64      `json:"log_bytes,omitempty"`
	LogLines     int        `json:"log_lines,omitempty"`
	LogTruncated bool       `json:"log_truncated,omitempty"`
	Error        string     `json:"error,omitempty" gorm:"type:text"`
	StartedAt    *time.Time `json:"started_at"`
	FinishedAt   *time.Time `json:"finished_at"`
	CreatedAt    time.Time  `json:"created_at"`
}

Backup is a single backup run of a managed database.

type BackupSchedule

type BackupSchedule struct {
	ID          uint   `json:"id" gorm:"primaryKey"`
	WorkspaceID uint   `json:"workspace_id" gorm:"index;not null"`
	DatabaseID  uint   `json:"database_id" gorm:"index;not null"`
	Cron        string `json:"cron" gorm:"not null"`
	Destination string `json:"destination" gorm:"not null;default:local"`
	Enabled     bool   `json:"enabled" gorm:"not null;default:true"`

	// Retention, applied after each scheduled run. 0 = unlimited.
	MaxBackups    int `json:"max_backups" gorm:"not null;default:0"`    // keep at most N most-recent backups
	RetentionDays int `json:"retention_days" gorm:"not null;default:0"` // delete backups older than N days

	S3Endpoint       string `json:"s3_endpoint,omitempty"`
	S3Bucket         string `json:"s3_bucket,omitempty"`
	S3Region         string `json:"s3_region,omitempty"`
	S3AccessKey      string `json:"s3_access_key,omitempty"`
	S3SecretKeyEnc   string `json:"-" gorm:"column:s3_secret_key_enc"` // encrypted
	S3Path           string `json:"s3_path,omitempty"`
	S3UseSSL         bool   `json:"s3_use_ssl"`
	S3ForcePathStyle bool   `json:"s3_force_path_style"`

	LastRunAt *time.Time `json:"last_run_at"`
	CreatedAt time.Time  `json:"created_at"`
	UpdatedAt time.Time  `json:"updated_at"`
}

BackupSchedule runs backups of a database on a cron schedule. When Destination is "s3", the S3* fields hold the target (secret key encrypted).

type BackupStatus

type BackupStatus string

BackupStatus is the state of a backup run.

const (
	BackupPending   BackupStatus = "pending"
	BackupRunning   BackupStatus = "running"
	BackupCompleted BackupStatus = "completed"
	BackupFailed    BackupStatus = "failed"
)

type Certificate

type Certificate struct {
	ID          uint `json:"id" gorm:"primaryKey"`
	WorkspaceID uint `json:"workspace_id" gorm:"index:idx_cert_workspace_name,unique;not null"`
	// Name is the unique slug handle scoped to the workspace.
	Name string `json:"name" gorm:"index:idx_cert_workspace_name,unique;not null"`
	// DisplayName is the free-text label shown in the UI; not unique.
	DisplayName string `json:"display_name"`
	CertPEM     string `json:"-" gorm:"type:text"` // leaf + intermediates (PEM)
	KeyEnc      string `json:"-" gorm:"type:text"` // private key, encrypted at rest

	// Parsed metadata (populated on import / replace / issue).
	CommonName string    `json:"common_name"`
	DNSNames   []string  `json:"dns_names" gorm:"serializer:json"` // SANs, for host auto-match
	Issuer     string    `json:"issuer"`
	NotBefore  time.Time `json:"not_before"`
	NotAfter   time.Time `json:"not_after"`
	SerialHex  string    `json:"serial_hex"`

	// Managed (ACME) fields; apply only to "acme" certs (Source defaults to "imported").
	Source        string `json:"source" gorm:"not null;default:imported"` // imported | acme
	DNSProviderID *uint  `json:"dns_provider_id,omitempty" gorm:"index"`  // provider used to issue
	AutoRenew     bool   `json:"auto_renew" gorm:"not null;default:false"`
	Status        string `json:"status" gorm:"not null;default:active"` // active | issuing | failed
	LastError     string `json:"last_error,omitempty"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

Certificate is a workspace-scoped TLS certificate. A route with TLSMode=custom references it by CertificateID; the PEM/key are sourced server-side at render time. The leaf + chain (CertPEM) is public, but the private key (KeyEnc) is encrypted at rest and never returned. Metadata is parsed from the leaf.

type CronJob

type CronJob struct {
	ID            uint   `json:"id" gorm:"primaryKey"`
	WorkspaceID   uint   `json:"workspace_id" gorm:"index;not null"`
	ApplicationID uint   `json:"application_id" gorm:"index;not null"`
	Name          string `json:"name" gorm:"not null"`
	Schedule      string `json:"schedule" gorm:"not null"` // cron expression (UTC)
	// AppName is the owning application's name (transient; populated on read).
	AppName string `json:"app_name,omitempty" gorm:"-"`

	Command     []string `json:"command" gorm:"serializer:json"`
	Entrypoint  []string `json:"entrypoint,omitempty" gorm:"serializer:json"`
	TimeoutSecs int      `json:"timeout_secs" gorm:"not null;default:0"`
	// Image optionally overrides the app's active-release image for spawned jobs
	// (blank = run the app's current image). RegistryID authenticates its pull.
	Image      string `json:"image,omitempty"`
	RegistryID *uint  `json:"registry_id,omitempty"`

	Enabled           bool   `json:"enabled" gorm:"not null;default:true"`
	ConcurrencyPolicy string `json:"concurrency_policy" gorm:"not null;default:allow"`
	HistoryLimit      int    `json:"history_limit" gorm:"not null;default:0"` // keep last N spawned jobs (0 = default)

	LastRunAt *time.Time `json:"last_run_at"`
	CreatedAt time.Time  `json:"created_at"`
	UpdatedAt time.Time  `json:"updated_at"`
}

CronJob is a schedule that spawns Jobs on a cron expression — the command it carries is the template each spawned Job is created from. Schedules are evaluated in UTC; missed ticks (control plane down) are not backfilled.

type CustomRole

type CustomRole struct {
	ID          uint          `json:"id" gorm:"primaryKey"`
	WorkspaceID *uint         `json:"workspace_id" gorm:"index"`
	Name        string        `json:"name" gorm:"not null"`
	BaseRole    WorkspaceRole `json:"base_role" gorm:"not null"`
	Permissions []string      `json:"permissions" gorm:"serializer:json"`
	CreatedAt   time.Time     `json:"created_at"`
	UpdatedAt   time.Time     `json:"updated_at"`
}

CustomRole is an admin-defined permission set assigned to workspace members (Enterprise; gated on the custom_roles entitlement for writes). BaseRole gives a rank so the legacy rank-based RequireRole still resolves sanely for a member on a custom role; the Permissions list drives the fine-grained RequirePermission checks. The table is empty in Community.

func (*CustomRole) PermissionSet

func (r *CustomRole) PermissionSet() map[Permission]bool

PermissionSet returns the role's permissions as a lookup set.

type DBEngine

type DBEngine string

DBEngine is a supported managed-database engine.

const (
	DBEnginePostgres DBEngine = "postgres"
	DBEngineMySQL    DBEngine = "mysql"
	DBEngineMariaDB  DBEngine = "mariadb"
	DBEngineRedis    DBEngine = "redis"
	DBEngineMongoDB  DBEngine = "mongodb"
	// DBEngineLibSQL is a single-database libSQL (sqld) server. Unlike the SQL
	// engines it hosts exactly one database (no user-managed logical databases),
	// has no CLI client (clients speak HTTP/Hrana), and authenticates with a JWT.
	DBEngineLibSQL DBEngine = "libsql"
)

type DBStatus

type DBStatus string

DBStatus is the lifecycle state of a managed database.

const (
	DBStatusProvisioning DBStatus = "provisioning"
	DBStatusRunning      DBStatus = "running"
	DBStatusStopped      DBStatus = "stopped"
	DBStatusFailed       DBStatus = "failed"
	// DBStatusUpgrading marks an instance whose engine version is being upgraded.
	// Lifecycle actions (start/stop/restart/delete) are refused while in this
	// state; the transient Upgrade* fields carry live progress.
	DBStatusUpgrading DBStatus = "upgrading"
)

type DNSProvider

type DNSProvider struct {
	ID          uint `json:"id" gorm:"primaryKey"`
	WorkspaceID uint `json:"workspace_id" gorm:"index:idx_dnsprov_workspace_name,unique;not null"`
	// Name is the unique slug handle scoped to the workspace.
	Name string `json:"name" gorm:"index:idx_dnsprov_workspace_name,unique;not null"`
	// DisplayName is the free-text label shown in the UI; not unique.
	DisplayName string `json:"display_name"`
	Type        string `json:"type" gorm:"not null"` // cloudflare | route53 | digitalocean

	// CredentialsEnc is crypto.Encrypt(JSON-of-credentials); write-only — never
	// serialized to clients (json:"-").
	CredentialsEnc string `json:"-" gorm:"type:text"`

	// Status / LastError record the last connection test or reconcile result.
	Status    string `json:"status" gorm:"not null;default:ok"` // ok | error
	LastError string `json:"last_error,omitempty"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

DNSProvider is a workspace's connection to a DNS host. Credentials are an opaque, encrypted JSON blob whose shape depends on Type (a Cloudflare API token; Route 53 access key/secret/region; a DigitalOcean token). The blob is encrypted at rest (crypto.Encrypt) and never returned by the API after create.

type DNSRecord

type DNSRecord struct {
	ID       uint `json:"id" gorm:"primaryKey"`
	DomainID uint `json:"domain_id" gorm:"index:idx_dnsrec_domain_key,unique;not null"`
	// Name+Type form the managed key within a domain (one ledgered record per
	// name+type). Name is a FQDN.
	Name string `json:"name" gorm:"index:idx_dnsrec_domain_key,unique;not null"`
	Type string `json:"type" gorm:"index:idx_dnsrec_domain_key,unique;not null"` // TXT | A | AAAA | CNAME

	Value   string `json:"value"`
	Purpose string `json:"purpose" gorm:"not null"` // verification | app-address
	// AppID is set for app-address records so they are cleaned up when the app /
	// route is removed.
	AppID *uint `json:"app_id,omitempty" gorm:"index"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

DNSRecord is the ledger of DNS records Miabi created through a connected provider, so reconcile and cleanup only ever touch records Miabi owns — never a user's other records (the same prune-only-managed safety the GitOps engine uses). A record at a managed name that is NOT in this ledger is treated as a conflict, never overwritten.

type Database

type Database struct {
	UIDModel
	ID            uint     `json:"id" gorm:"primaryKey"`
	WorkspaceID   uint     `json:"workspace_id" gorm:"index;not null"`
	InstanceID    uint     `json:"instance_id" gorm:"index:idx_db_instance_name,unique;not null"`
	Name          string   `json:"name" gorm:"index:idx_db_instance_name,unique;not null"` // db name on the server
	Username      string   `json:"username" gorm:"not null"`
	PasswordEnc   string   `json:"-" gorm:"column:password_enc"` // encrypted
	Status        DBStatus `json:"status" gorm:"not null;default:provisioning"`
	ApplicationID *uint    `json:"application_id,omitempty" gorm:"index"` // optional owner
	// Application declares the FK so deleting the owning app detaches this
	// database (ON DELETE SET NULL) instead of dangling. Not serialized.
	Application *Application `json:"-" gorm:"foreignKey:ApplicationID;constraint:OnDelete:SET NULL"`
	// EnvPrefix namespaces the connection env vars injected into the owning app
	// (e.g. "ANALYTICS" -> ANALYTICS_DATABASE_URL); empty = unprefixed
	// (DATABASE_URL, DB_*). Lets one app hold several databases without clobbering.
	EnvPrefix string `json:"env_prefix,omitempty"`
	// SizeBytes is the database's on-disk size, refreshed by a sync.
	SizeBytes    int64      `json:"size_bytes"`
	SizeSyncedAt *time.Time `json:"size_synced_at"`
	// Metadata holds free-form labels; "miabi.io/" keys are platform-managed.
	Metadata  Metadata  `json:"metadata,omitempty" gorm:"serializer:json"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

Database is a logical database hosted on a DatabaseInstance, with its own dedicated user (privileges scoped to this database). The password is encrypted at rest. Optionally owned by an application.

type DatabaseInstance

type DatabaseInstance struct {
	UIDModel
	ID          uint `json:"id" gorm:"primaryKey"`
	WorkspaceID uint `json:"workspace_id" gorm:"index:idx_dbinst_workspace_name,unique;not null"`
	// Name is the unique, URL/CLI/docker handle (lowercase [a-z0-9-]) scoped to the
	// workspace. Renamed from the former "slug".
	Name string `json:"name" gorm:"index:idx_dbinst_workspace_name,unique;not null"`
	// DisplayName is the free-text label shown in the UI. Renamed from "name".
	DisplayName string   `json:"display_name"`
	Engine      DBEngine `json:"engine" gorm:"not null"`
	Version     string   `json:"version" gorm:"not null"`
	Status      DBStatus `json:"status" gorm:"not null;default:provisioning"`
	// ServerID is the node this instance runs on (0 = local control-plane node).
	ServerID uint `json:"server_id" gorm:"index;not null;default:0"`
	// ServerName is the display name of the node (transient; populated on read so
	// the UI can show where the instance lives). Empty if the node is unknown.
	ServerName  string `json:"server_name,omitempty" gorm:"-"`
	ContainerID string `json:"container_id,omitempty"`
	// Image is the resolved server image ref (repo:tag), pinned at provision time
	// from the deployment-config catalog. Empty on legacy rows (falls back to the
	// engine default).
	Image      string `json:"image,omitempty"`
	VolumeName string `json:"volume_name,omitempty"`
	// VolumeSizeBytes is the declared capacity of the instance's data volume in
	// bytes (0 = unspecified/unlimited), distinct from the measured SizeBytes
	// below. Recorded at provision time for quota accounting.
	VolumeSizeBytes int64 `json:"volume_size_bytes" gorm:"not null;default:0"`
	// MountPath is the in-container data directory the volume is mounted at
	// (transient; populated on read from the engine spec).
	MountPath string `json:"mount_path,omitempty" gorm:"-"`
	Host      string `json:"host"` // in-network DNS alias
	Port      int    `json:"port"`
	// NetworkName is the Docker network the instance's container joins — the
	// workspace's default network, shared with the workspace's applications so
	// they reach the database by its alias. The gateway network is reserved for
	// exposed (routed) applications. Empty on legacy rows (fall back to gateway).
	NetworkName string `json:"network_name,omitempty"`
	// Admin (superuser/root) credentials for the server. For Redis this is the
	// requirepass password (AdminUser empty).
	AdminUser        string `json:"admin_user"`
	AdminPasswordEnc string `json:"-" gorm:"column:admin_password_enc"` // encrypted
	// JWTPrivateKeyEnc is the encrypted Ed25519 private key used to mint client
	// auth tokens for a libSQL instance. sqld is started with the matching public
	// key (SQLD_AUTH_JWT_KEY), derived from this private key at bring-up. Empty for
	// every other engine.
	JWTPrivateKeyEnc string `json:"-" gorm:"column:jwt_private_key_enc"` // encrypted

	// SizeBytes is the instance's on-disk size (sum of logical DBs for SQL, used
	// memory for Redis), refreshed by a sync. SizeSyncedAt is when it was last
	// computed.
	SizeBytes    int64      `json:"size_bytes"`
	SizeSyncedAt *time.Time `json:"size_synced_at"`

	// Metadata holds free-form labels; "miabi.io/" keys are platform-managed.
	Metadata Metadata `json:"metadata,omitempty" gorm:"serializer:json"`
	// Annotations holds free-form, non-identifying descriptive metadata (the
	// manifest's metadata.annotations); no reserved keys. Persisted as JSON.
	Annotations Metadata  `json:"annotations,omitempty" gorm:"serializer:json"`
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`

	// Upgrade carries live progress for a version upgrade. Persisted (as JSON) so
	// it is visible across the API server and the worker that runs the job, and
	// survives a restart. Nil when no upgrade is in flight; on failure it lingers
	// with Phase="failed" until the next upgrade attempt clears it.
	Upgrade *UpgradeProgress `json:"upgrade,omitempty" gorm:"serializer:json"`

	Databases []Database `json:"databases,omitempty" gorm:"foreignKey:InstanceID"`
	// Networks are the Docker networks the instance's container joins. The
	// workspace's default network is always attached (it carries the alias apps
	// reach the instance by); the user may attach additional custom networks.
	Networks []Network `json:"networks,omitempty" gorm:"many2many:database_instance_networks;"`
}

DatabaseInstance is a workspace-owned database server running as a container (PostgreSQL/MySQL/MariaDB/Redis). A single instance hosts one or more logical Databases (SQL engines), so small deployments can share one server across apps instead of one container per database. Admin credentials are used to create/drop the logical databases and are encrypted at rest.

func (*DatabaseInstance) NetworkNames

func (i *DatabaseInstance) NetworkNames(fallback string) []string

NetworkNames returns every Docker network the instance's container joins: the pinned primary network first (the alias-bearing default), then each other attached network. Helper jobs (DDL, size probes, backup, restore) must join this full set — not just the primary — so they always share a network with the instance and can resolve it by name; attaching to only one risks landing on a network the instance is not on. fallback (the gateway network) is used when no primary is pinned (legacy rows provisioned before per-workspace networking). The result is de-duplicated and never empty.

func (*DatabaseInstance) SupportsLogicalDatabases

func (i *DatabaseInstance) SupportsLogicalDatabases() bool

SupportsLogicalDatabases reports whether the instance's engine can host named per-app databases with their own users.

type DeployStrategy

type DeployStrategy string

DeployStrategy is how a new release replaces the running one.

const (
	// DeployRecreate stops the current container before starting the new one
	// (brief downtime; use when two versions cannot run at once).
	DeployRecreate DeployStrategy = "recreate"
	// DeployRolling starts the new container, waits for health, then retires the
	// old one (zero-downtime for a single replica). Default.
	DeployRolling DeployStrategy = "rolling"
	// DeployCanary runs the new release alongside the stable one and shifts
	// weighted traffic to it progressively (platform-driven).
	DeployCanary DeployStrategy = "canary"
)

type Deployment

type Deployment struct {
	ID uint `json:"id" gorm:"primaryKey"`
	// Number is the per-application sequential deployment number (1, 2, 3…),
	// independent of the global ID. It mirrors Release.Version: stable and
	// user-friendly for managing an app's deploy history (the first deploy is #1,
	// the second #2, …). Assigned automatically in BeforeCreate; the composite
	// unique index keeps it gap-free per app and prevents duplicates.
	Number        int              `json:"number" gorm:"index:idx_deploy_app_number,unique;not null;default:0"`
	ApplicationID uint             `json:"application_id" gorm:"index:idx_deploy_app_number,unique;index;not null"`
	Status        DeploymentStatus `json:"status" gorm:"not null;default:pending"`
	Image         string           `json:"image"`
	Trigger       string           `json:"trigger"`                                  // manual | rollback | auto | pipeline
	Strategy      DeployStrategy   `json:"strategy" gorm:"not null;default:rolling"` // rollout method for this deploy
	// Commit pins the source revision this deployment was built from. Set by the
	// pipeline runner so a deploy reproduces the exact commit the run captured,
	// even if the app's branch advanced after the run started.
	Commit string `json:"commit,omitempty"`
	// ImageID links a deployment to a prebuilt Image (built by a pipeline run on
	// this node). When set and the image is present locally the deploy worker
	// runs it directly — no rebuild, no pull.
	ImageID *uint `json:"image_id,omitempty"`
	// RegistryID is the registry credential used for this deploy (snapshot of
	// the app's selection, allowing per-deploy override). nil = anonymous.
	RegistryID *uint `json:"registry_id,omitempty"`
	// RunnerID records which runner built the image this deployment rolls out
	// (provenance / "built on runner X"); nil for images built by the internal
	// runner or pulled directly.
	RunnerID    *uint  `json:"runner_id,omitempty"`
	ContainerID string `json:"container_id,omitempty"`
	// Logs is a bounded tail of the build/deploy output for instant display; the
	// full log lives in the log store at LogRef once the deployment is terminal
	// (see plans/log-storage.md). LogRef is empty when the store is disabled or
	// the row predates externalization — readers fall back to this tail.
	Logs         string     `json:"logs,omitempty" gorm:"type:text"`
	LogRef       string     `json:"log_ref,omitempty"`
	LogBytes     int64      `json:"log_bytes,omitempty"`
	LogLines     int        `json:"log_lines,omitempty"`
	LogTruncated bool       `json:"log_truncated,omitempty"`
	Error        string     `json:"error,omitempty" gorm:"type:text"`
	StartedAt    *time.Time `json:"started_at"`
	FinishedAt   *time.Time `json:"finished_at"`
	CreatedAt    time.Time  `json:"created_at"`

	// Current is a transient flag (not persisted) marking the deployment whose
	// release is the application's active one — i.e. what is live right now.
	Current bool `json:"current" gorm:"-"`
}

Deployment is a single attempt to deploy an application.

func (*Deployment) BeforeCreate

func (d *Deployment) BeforeCreate(tx *gorm.DB) error

BeforeCreate assigns the per-application sequential Number (MAX+1 scoped to the app), mirroring how Release.Version is allocated. Running inside the insert's transaction keeps every creation path (deploy, redeploy, pipeline, import) consistent without each call site computing the number itself. An explicit Number (e.g. a backfill) is left untouched.

type DeploymentStatus

type DeploymentStatus string

DeploymentStatus is the state of a single deploy attempt.

const (
	DeploymentPending   DeploymentStatus = "pending"
	DeploymentBuilding  DeploymentStatus = "building"
	DeploymentDeploying DeploymentStatus = "deploying"
	// DeploymentCanary is a non-terminal state: the canary container is live and
	// serving a weighted share of traffic while the rollout progresses. It becomes
	// terminal when the canary is promoted/superseded (succeeded) or aborted
	// (failed), so the deployment's log stream stays open for the whole rollout.
	DeploymentCanary DeploymentStatus = "canary"
	// DeploymentSucceeded is the terminal success state of a deploy attempt.
	// Whether the app is currently live is a property of the active Release, not
	// of a historical deployment.
	DeploymentSucceeded DeploymentStatus = "succeeded"
	DeploymentFailed    DeploymentStatus = "failed"
)

func (DeploymentStatus) IsTerminal

func (s DeploymentStatus) IsTerminal() bool

IsTerminal reports whether the deployment has reached a final state.

type Domain

type Domain struct {
	ID          uint   `json:"id" gorm:"primaryKey"`
	WorkspaceID uint   `json:"workspace_id" gorm:"index:idx_domain_workspace_name,unique;not null"`
	Name        string `json:"name" gorm:"index:idx_domain_workspace_name,unique;not null"` // e.g. example.com

	// TLSMode is the default certificate policy for routes under this domain.
	TLSMode DomainTLSMode `json:"tls_mode" gorm:"not null;default:acme"`
	// Wildcard marks the domain as covering *.name as well as name.
	Wildcard bool `json:"wildcard" gorm:"not null;default:false"`

	// Verified records DNS-proven ownership. VerificationToken is published by the
	// user as a TXT record; it is not a secret (it is meant to be public).
	Verified          bool       `json:"verified" gorm:"not null;default:false"`
	VerifiedAt        *time.Time `json:"verified_at,omitempty"`
	VerificationToken string     `json:"verification_token" gorm:"not null"`

	// VerificationCheckedAt records the last time ownership was checked (whether or
	// not it succeeded), and VerificationError carries the last failure reason.
	// Together they give the UI an observable verification history and let the
	// drift cron count consecutive misses before un-verifying a domain.
	VerificationCheckedAt *time.Time `json:"verification_checked_at,omitempty"`
	VerificationError     string     `json:"verification_error,omitempty"`
	// VerificationMisses counts consecutive failed re-verifications of an
	// already-verified domain; the drift cron flips Verified off once it crosses a
	// threshold, absorbing transient DNS blips.
	VerificationMisses int `json:"-" gorm:"not null;default:0"`

	// Banned blocks a domain platform-wide: a banned domain is never served by the
	// gateway (its routes are forced offline) and cannot be verified, regardless of
	// ownership or workspace privilege. Set by a platform admin (e.g. for abuse).
	Banned    bool       `json:"banned" gorm:"not null;default:false"`
	BannedAt  *time.Time `json:"banned_at,omitempty"`
	BanReason string     `json:"ban_reason,omitempty"`

	// DNSProviderID optionally links the domain to a connected DNS provider so
	// Miabi automates the ownership TXT (and, later, app A/AAAA) records. nil =
	// manual: the user adds DNS records by hand (today's copy-paste flow).
	DNSProviderID *uint `json:"dns_provider_id,omitempty" gorm:"index"`
	// DNSProvider declares the FK so deleting the provider nulls this link
	// (ON DELETE SET NULL) — the domain reverts to manual DNS — instead of
	// dangling. Association not serialized.
	DNSProvider *DNSProvider `json:"-" gorm:"foreignKey:DNSProviderID;constraint:OnDelete:SET NULL"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

Domain is a hostname (or zone) a workspace owns. It tracks DNS-verified ownership and the default TLS policy; routes bind hostnames that resolve under a verified domain. This is the first-class owned-hostname resource — distinct from the declarative Route kind, which is an HTTP routing rule.

func (*Domain) ChallengeHost

func (d *Domain) ChallengeHost() string

ChallengeHost is the DNS name where the ownership TXT record must be added.

func (*Domain) ChallengeValue

func (d *Domain) ChallengeValue() string

ChallengeValue is the expected TXT record value proving ownership.

func (*Domain) Status

func (d *Domain) Status() DomainStatus

Status derives the domain's presentational state for the UI. A ban takes precedence over verification, since it overrides serving entirely.

type DomainStatus

type DomainStatus string

DomainStatus is a presentational verification state derived from the domain's verification fields (not stored): "verified", "failed" (a check ran and did not prove ownership), or "pending" (no successful check yet).

const (
	DomainStatusPending  DomainStatus = "pending"
	DomainStatusVerified DomainStatus = "verified"
	DomainStatusFailed   DomainStatus = "failed"
	DomainStatusBanned   DomainStatus = "banned"
)

type DomainTLSMode

type DomainTLSMode string

DomainTLSMode is the default certificate policy for routes served under a domain.

const (
	// DomainTLSACME serves certificates from Goma's ACME (Let's Encrypt) issuer.
	DomainTLSACME DomainTLSMode = "acme"
	// DomainTLSCustom expects a user-supplied certificate.
	DomainTLSCustom DomainTLSMode = "custom"
)

type Environment

type Environment struct {
	ID          uint `json:"id" gorm:"primaryKey"`
	WorkspaceID uint `json:"workspace_id" gorm:"index:idx_env_ws_name,unique;not null"`
	// Name is the unique slug handle scoped to the workspace (e.g. "prod").
	Name string `json:"name" gorm:"index:idx_env_ws_name,unique;not null"`
	// DisplayName is the free-text label shown in the UI; not unique.
	DisplayName string `json:"display_name"`
	Description string `json:"description,omitempty"`
	// Rank orders environments for promotion flow (lower promotes into higher).
	Rank int `json:"rank" gorm:"not null;default:0"`
	// RequiredApprovals gates promotion into this environment (0 = no gate).
	RequiredApprovals int `json:"required_approvals" gorm:"not null;default:0"`
	// GitSourceID optionally binds the environment to a GitOps source/folder.
	GitSourceID *uint `json:"git_source_id,omitempty" gorm:"index"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

Environment is a promotion stage within a workspace (dev → staging → prod). A Release is promoted by updating the target environment's desired state — a Git commit (GitOps) or an API write — so promotion is auditable and reconciled.

type GPUDevice added in v1.2.0

type GPUDevice struct {
	ID       uint `json:"id" gorm:"primaryKey"`
	ServerID uint `json:"server_id" gorm:"index;not null"` // the node it lives on

	// UUID is the stable identity reported by the node agent (nvidia-smi -q -x),
	// e.g. "GPU-3f2c...". It is the join key across re-inventories.
	UUID     string    `json:"uuid" gorm:"uniqueIndex;not null"`
	Index    int       `json:"index"` // host-local device index at last inventory (informational)
	Vendor   GPUVendor `json:"vendor" gorm:"not null;default:nvidia"`
	Model    string    `json:"model"`     // "NVIDIA A100-SXM4-40GB"
	MemoryMB int       `json:"memory_mb"` // total framebuffer memory

	// Admin policy. A device is not offered to any workload until Enabled — a
	// newly discovered card always arrives disabled (fail closed).
	Enabled bool `json:"enabled" gorm:"not null;default:false"`
	// Shared: many apps may request this device concurrently (inference, dev).
	// Dedicated (false): reserved for one app at a time (training). Fleet-wide
	// exclusivity enforcement for dedicated cards lands in a later phase.
	Shared bool `json:"shared" gorm:"not null;default:true"`

	// LastSeenAt is stamped on every inventory scan that observes the device. A
	// device that drops out of a scan is kept (not deleted) so history and an
	// app's GPUKind reference survive a transient probe failure or a reboot
	// mid-scan; staleness is derived from an old LastSeenAt.
	LastSeenAt *time.Time `json:"last_seen_at"`
	CreatedAt  time.Time  `json:"created_at"`
	UpdatedAt  time.Time  `json:"updated_at"`
}

GPUDevice is one physical GPU discovered on a node's Docker host. A node advertises the GPUs it has (via the inventory probe); the platform admin controls whether each is offered to workloads. Rows are owned by a Server and keyed for re-inventory by the stable GPU UUID reported by nvidia-smi — never the host-local index, which can change across reboots.

Devices get their own table (not a JSON blob on Server) because each is individually addressable: the admin enables/disables and marks shared or dedicated per card, and a device is the unit an app is scheduled onto. Same reasoning that gave network subnets their own rows.

type GPUVendor added in v1.2.0

type GPUVendor string

GPUVendor is the vendor of a physical GPU. Only "nvidia" is supported in v1; "amd" is reserved for a later ROCm phase.

const (
	GPUVendorNvidia GPUVendor = "nvidia"
	GPUVendorAMD    GPUVendor = "amd" // reserved (ROCm), not yet supported
)

type GatewayUpdateProgress

type GatewayUpdateProgress struct {
	FromImage string `json:"from_image"`
	ToImage   string `json:"to_image"`
	// Phase: queued | pulling | testing | observing | promoting | verifying |
	// done | failed.
	Phase string `json:"phase"`
	Error string `json:"error,omitempty"`
}

GatewayUpdateProgress is the live state of a safe edge-gateway update: the new image is first started as a throwaway test container with the same config and volumes, observed for a grace period, then promoted to replace the live gateway. The admin watches these phases on the node detail page. Mirrors UpgradeProgress (database version upgrades).

type GitAuthType

type GitAuthType string

GitAuthType is how an application authenticates to a Git repository.

const (
	// GitAuthPublic is an anonymous (public) repository — no credentials.
	GitAuthPublic GitAuthType = "public"
	// GitAuthToken uses HTTPS basic auth (username + personal access token).
	GitAuthToken GitAuthType = "token"
	// GitAuthSSH uses an SSH private key against an ssh:// or git@ URL.
	GitAuthSSH GitAuthType = "ssh"
)

type GitRepository

type GitRepository struct {
	ID          uint `json:"id" gorm:"primaryKey"`
	WorkspaceID uint `json:"workspace_id" gorm:"index:idx_gitrepo_workspace_name,unique;not null"`
	// Name is the unique slug handle scoped to the workspace.
	Name string `json:"name" gorm:"index:idx_gitrepo_workspace_name,unique;not null"`
	// DisplayName is the free-text label shown in the UI; not unique.
	DisplayName string      `json:"display_name"`
	URL         string      `json:"url" gorm:"not null"`
	AuthType    GitAuthType `json:"auth_type" gorm:"not null;default:token"`
	// Username for HTTPS basic auth. Empty defaults to a provider-friendly value
	// ("x-access-token") at clone time.
	Username string `json:"username"`
	// Secret holds the access token (token auth) or SSH private key (ssh auth),
	// encrypted at rest.
	Secret string `json:"-" gorm:"not null"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`

	// HasSecret is a transient flag for responses (never persisted).
	HasSecret bool `json:"has_secret" gorm:"-"`
}

GitRepository is a stored Git credential used to clone private repositories at build time. The Secret (token or SSH private key) is encrypted at rest and never serialized.

type GitSource

type GitSource struct {
	UIDModel
	ID          uint `json:"id" gorm:"primaryKey"`
	WorkspaceID uint `json:"workspace_id" gorm:"index:idx_gitsource_ws_name,unique;not null"`
	// Name is the unique slug handle scoped to the workspace.
	Name string `json:"name" gorm:"index:idx_gitsource_ws_name,unique;not null"`
	// DisplayName is the free-text label shown in the UI; not unique.
	DisplayName string `json:"display_name"`

	RepoURL string `json:"repo_url" gorm:"not null"`
	Ref     string `json:"ref" gorm:"not null;default:main"` // branch, tag or commit
	Path    string `json:"path" gorm:"not null;default:."`   // manifest subdirectory

	// GitRepositoryID optionally references stored credentials (token/SSH) for a
	// private repository. nil means the repo is public.
	GitRepositoryID *uint `json:"git_repository_id,omitempty" gorm:"index"`

	SyncPolicy GitSyncPolicy `json:"sync_policy" gorm:"not null;default:manual"`
	// Prune deletes managed resources that disappear from Git (opt-in for safety).
	Prune bool `json:"prune" gorm:"not null;default:false"`
	// SelfHeal re-applies when the live state drifts from Git.
	SelfHeal bool `json:"self_heal" gorm:"not null;default:false"`
	// AllowEmpty permits an empty manifest set to prune ALL managed resources
	// (intentional teardown). Off by default: an empty/zero-resource result is
	// otherwise treated as an error, never a "delete everything" instruction, so
	// a wrong path or a wiped repo can't tear down a deployment. A missing path is
	// always an error regardless. Only meaningful together with Prune.
	AllowEmpty bool `json:"allow_empty" gorm:"not null;default:false"`

	// WebhookSecret authenticates inbound provider push webhooks. Never serialized.
	WebhookSecret string `json:"-" gorm:"not null"`

	Status           GitSourceStatus `json:"status" gorm:"not null;default:unknown"`
	Message          string          `json:"message,omitempty" gorm:"type:text"`
	LastSyncedCommit string          `json:"last_synced_commit,omitempty"`
	// LastSyncedAuthor/Subject record who authored the synced commit and its
	// subject line, for display on the detail page.
	LastSyncedAuthor  string     `json:"last_synced_author,omitempty"`
	LastSyncedSubject string     `json:"last_synced_subject,omitempty"`
	LastSyncedAt      *time.Time `json:"last_synced_at,omitempty"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

GitSource binds a Git repository path of miabi.io/v1 manifests to a workspace as a continuously-reconciled desired-state source. It is the GitOps half of the declarative model: Git → (sync) → Postgres desired → (existing reconciler) → Docker.

type GitSourceStatus

type GitSourceStatus string

GitSourceStatus is the aggregate sync state of a GitSource.

const (
	GitSourceUnknown     GitSourceStatus = "unknown"
	GitSourceSynced      GitSourceStatus = "synced"
	GitSourceOutOfSync   GitSourceStatus = "out_of_sync"
	GitSourceProgressing GitSourceStatus = "progressing"
	GitSourceError       GitSourceStatus = "error"
)

type GitSyncPolicy

type GitSyncPolicy string

GitSyncPolicy controls when a GitSource reconciles.

const (
	// GitSyncManual reconciles only on an explicit Sync (or webhook) request.
	GitSyncManual GitSyncPolicy = "manual"
	// GitSyncAuto reconciles on the polling sweep and on webhook push.
	GitSyncAuto GitSyncPolicy = "auto"
)

type HealthcheckType

type HealthcheckType string

HealthcheckType is how an application's container health is probed.

const (
	HealthcheckNone    HealthcheckType = "none"    // no healthcheck
	HealthcheckHTTP    HealthcheckType = "http"    // HTTP GET against a path/port
	HealthcheckCommand HealthcheckType = "command" // shell command (CMD-SHELL)
)

type Image

type Image struct {
	ID          uint   `json:"id" gorm:"primaryKey"`
	WorkspaceID uint   `json:"workspace_id" gorm:"index:idx_image_ws_digest,unique;not null"`
	Repository  string `json:"repository" gorm:"not null"`
	Digest      string `json:"digest" gorm:"index:idx_image_ws_digest,unique;not null"` // sha256:…
	Tag         string `json:"tag,omitempty"`
	SizeBytes   int64  `json:"size_bytes"`

	// Provenance.
	PipelineRunID *uint      `json:"pipeline_run_id,omitempty" gorm:"index"`
	ApplicationID *uint      `json:"application_id,omitempty" gorm:"index"`
	Commit        string     `json:"commit,omitempty"`
	Runner        string     `json:"runner,omitempty"`
	BuiltAt       *time.Time `json:"built_at,omitempty"`

	// SBOM / Signature attach supply-chain metadata. Never serialized.
	SBOM      string `json:"-" gorm:"type:text"`
	Signature string `json:"-" gorm:"type:text"`

	CreatedAt time.Time `json:"created_at"`
}

Image is a built container artifact with provenance. Deploy-by-digest records exactly what ran; the catalog/GC reasons about retention without ever collecting a digest a live deployment or pinned release references.

func (*Image) Ref

func (i *Image) Ref() string

Ref returns the canonical repository@digest pin.

type ImagePullPolicy

type ImagePullPolicy string

ImagePullPolicy decides whether a deploy pulls the app's image from the registry. The platform default is "always" (a redeploy fetches the current tag); "if-not-present" reuses a locally cached image; "never" requires it to be present already (air-gapped / locally built). Digest-pinned refs are immutable, so they are never re-pulled when already present regardless of policy.

const (
	PullAlways       ImagePullPolicy = "always"         // always pull (default)
	PullIfNotPresent ImagePullPolicy = "if-not-present" // pull only when the image is absent locally
	PullNever        ImagePullPolicy = "never"          // never pull; fail if the image is absent
)

type InvitationStatus

type InvitationStatus string

InvitationStatus is the lifecycle state of a workspace invitation.

const (
	InvitationStatusPending  InvitationStatus = "pending"
	InvitationStatusAccepted InvitationStatus = "accepted"
	InvitationStatusRevoked  InvitationStatus = "revoked"
)

type Job

type Job struct {
	ID            uint `json:"id" gorm:"primaryKey"`
	WorkspaceID   uint `json:"workspace_id" gorm:"index;not null"`
	ApplicationID uint `json:"application_id" gorm:"index;not null"`
	// ServerID is the node the job ran on (0 = local), copied from the app.
	ServerID uint `json:"server_id" gorm:"not null;default:0"`
	// CronJobID links runs spawned by a CronJob to their schedule.
	CronJobID *uint `json:"cronjob_id,omitempty" gorm:"index"`
	// AppName is the owning application's name (transient; populated on read for
	// the workspace-level Jobs view).
	AppName string `json:"app_name,omitempty" gorm:"-"`

	Name       string   `json:"name"`
	Command    []string `json:"command" gorm:"serializer:json"`
	Entrypoint []string `json:"entrypoint,omitempty" gorm:"serializer:json"`
	Image      string   `json:"image"` // resolved image actually run
	// RegistryID authenticates the image pull for a custom image (nil = the app's
	// registry, or anonymous). Pull marks that the image must be pulled before the
	// run (set for a custom image; the app's active-release image is already
	// present on its node, and git-built images are local-only).
	RegistryID  *uint     `json:"registry_id,omitempty"`
	Pull        bool      `json:"pull"`
	Status      JobStatus `json:"status" gorm:"not null;default:pending"`
	ExitCode    *int      `json:"exit_code,omitempty"`
	ContainerID string    `json:"container_id,omitempty"`
	// Logs is a bounded tail of the run's output for instant display; the full
	// log lives in the log store at LogRef once the run is terminal (see
	// plans/log-storage.md). LogRef is empty when the store is disabled or the
	// row predates externalization — readers then fall back to this tail.
	Logs         string `json:"logs,omitempty" gorm:"type:text"`
	LogRef       string `json:"log_ref,omitempty"`
	LogBytes     int64  `json:"log_bytes,omitempty"`
	LogLines     int    `json:"log_lines,omitempty"`
	LogTruncated bool   `json:"log_truncated,omitempty"`
	Error        string `json:"error,omitempty" gorm:"type:text"`
	TimeoutSecs  int    `json:"timeout_secs" gorm:"not null;default:0"`

	TriggeredByID *uint  `json:"triggered_by_id,omitempty"`
	Source        string `json:"source" gorm:"not null;default:manual"` // manual | api | scheduled

	StartedAt  *time.Time `json:"started_at"`
	FinishedAt *time.Time `json:"finished_at"`
	CreatedAt  time.Time  `json:"created_at"`
	UpdatedAt  time.Time  `json:"updated_at"`
}

Job is a single one-off command run in an application's runtime context (same image, env, networks, volumes, node, and resource limits as the app). The command is captured for history/audit; secrets are resolved into the container env at run time and never persisted on the row.

type JobStatus

type JobStatus string

JobStatus is the lifecycle state of a one-off Job run.

const (
	JobPending   JobStatus = "pending"
	JobRunning   JobStatus = "running"
	JobSucceeded JobStatus = "succeeded"
	JobFailed    JobStatus = "failed"
	JobCanceled  JobStatus = "canceled"
)

func (JobStatus) IsTerminal

func (s JobStatus) IsTerminal() bool

IsTerminal reports whether the job has reached a final state.

type LDAPConfig

type LDAPConfig struct {
	ID             uint  `json:"id" gorm:"primaryKey"`
	OrganizationID *uint `json:"organization_id" gorm:"index"`
	// Name is the globally unique handle (slug); DisplayName is the free-text label.
	Name        string `json:"name" gorm:"uniqueIndex;not null"`
	DisplayName string `json:"display_name"`

	// Connection.
	Host            string `json:"host" gorm:"not null"`
	Port            int    `json:"port" gorm:"not null;default:389"`
	TLSMode         string `json:"tls_mode" gorm:"not null;default:'starttls'"` // none | starttls | ldaps
	CACertPEM       string `json:"ca_cert_pem" gorm:"type:text"`                // optional private-CA trust
	InsecureSkipTLS bool   `json:"insecure_skip_tls" gorm:"not null;default:false"`
	TimeoutSeconds  int    `json:"timeout_seconds" gorm:"not null;default:10"`

	// Bind — the service account used to search for the user entry before
	// re-binding as the user to verify the password.
	BindDN string `json:"bind_dn"`
	// BindPasswordEnc is the service-account password, encrypted at rest (crypto
	// package). Never serialized; BindPasswordSet exposes only whether it is set.
	BindPasswordEnc string `json:"-" gorm:"type:text"`

	// User search.
	UserBaseDN   string `json:"user_base_dn"`
	UserFilter   string `json:"user_filter"`   // %s = escaped identifier, e.g. (sAMAccountName=%s) or (uid=%s)
	AttrEmail    string `json:"attr_email"`    // mail | userPrincipalName (blank → "mail")
	AttrName     string `json:"attr_name"`     // displayName | cn (blank → "displayName")
	AttrUsername string `json:"attr_username"` // sAMAccountName | uid → User.Username

	// Group resolution (optional; drives access via LDAPGroupMapping).
	GroupBaseDN  string `json:"group_base_dn"`
	GroupFilter  string `json:"group_filter"`  // %s = user DN, e.g. (member=%s); blank → use MemberAttr
	MemberAttr   string `json:"member_attr"`   // memberOf (read from the user entry) — AD default
	NestedGroups bool   `json:"nested_groups"` // AD: expand nested groups (LDAP_MATCHING_RULE_IN_CHAIN)

	Enabled   bool      `json:"enabled" gorm:"not null;default:true"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`

	// Mappings are loaded on demand (not embedded in list responses).
	Mappings []LDAPGroupMapping `json:"mappings,omitempty" gorm:"foreignKey:LDAPConfigID"`

	// BindPasswordSet is a read-only flag for the API (whether a service-account
	// password is stored) so the secret itself is never exposed. Not persisted;
	// populated by the repository on read.
	BindPasswordSet bool `json:"bind_password_set" gorm:"-"`
}

LDAPConfig is an LDAP / Active Directory connection for an organization. The table is migrated in every build but is only read/written by the enterprise LDAP authenticator + the admin handler (gated on the sso_ldap entitlement); it stays empty in Community. Mirrors SAMLConfig's per-org, name/display_name shape.

type LDAPGroupMapping

type LDAPGroupMapping struct {
	ID           uint `json:"id" gorm:"primaryKey"`
	LDAPConfigID uint `json:"ldap_config_id" gorm:"index;not null"`
	// GroupDN is matched (case-insensitively) against the user's resolved group
	// DNs; a bare CN is also accepted for convenience.
	GroupDN     string `json:"group_dn" gorm:"not null"`
	SystemAdmin bool   `json:"system_admin" gorm:"not null;default:false"`
	// WorkspaceID + WorkspaceRole grant workspace membership (nil = no grant).
	WorkspaceID   *uint         `json:"workspace_id"`
	WorkspaceRole WorkspaceRole `json:"workspace_role"`
	CreatedAt     time.Time     `json:"created_at"`
	UpdatedAt     time.Time     `json:"updated_at"`
}

LDAPGroupMapping maps a directory group to Miabi access. It is reconciled on every login: SystemAdmin grants the platform-admin role, and a WorkspaceID + WorkspaceRole grants membership in that workspace. A mapping with neither is a no-op (kept for documentation).

type LeaseKind

type LeaseKind string

LeaseKind namespaces a lease's RunID so pipeline runs and deploy builds — two different id spaces — never collide in the shared runner_leases table.

const (
	// LeaseKindPipeline: RunID is a PipelineRun id.
	LeaseKindPipeline LeaseKind = "pipeline"
	// LeaseKindBuild: RunID is a Deployment id (a git-app build dispatched to a
	// runner, outside any pipeline).
	LeaseKindBuild LeaseKind = "build"
)

type License

type License struct {
	ID          uint      `json:"id" gorm:"primaryKey"`
	LicenseID   string    `json:"license_id" gorm:"uniqueIndex;not null"`
	Edition     string    `json:"edition" gorm:"not null"`
	Token       string    `json:"-" gorm:"type:text;not null"` // never serialized to the API
	Customer    string    `json:"customer"`
	NotBefore   time.Time `json:"not_before"`
	NotAfter    time.Time `json:"not_after"`
	GraceDays   int       `json:"grace_days"`
	InstalledBy uint      `json:"installed_by"`
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`
}

License is the single active commercial license row. The signed Token is the source of truth — it is re-verified against the embedded public key on every load, so the parsed columns are only a cache for display/queries. The table exists in every build (it is migrated unconditionally) but is empty and unused in the Community Edition, which links no license-verification code.

type Metadata

type Metadata = map[string]string

Metadata is a free-form set of key/value labels attached to a resource (Application, Stack, Database, Volume, Route, Secret). It backs provenance, grouping, and the declarative/GitOps features.

Keys under MetadataReservedPrefix are platform-managed ("built-in") and are protected from user modification; all other keys are user-defined and freely editable. Persisted as JSON (gorm serializer).

func DefaultManagedBy

func DefaultManagedBy(m Metadata, value string) Metadata

DefaultManagedBy ensures the managed-by built-in is set, defaulting to value when absent, and returns the (possibly newly created) map. Used at creation so every resource records its origin.

func DefaultOwner

func DefaultOwner(m Metadata, kind string, id uint, name string) Metadata

DefaultOwner sets the owner reference only when one is not already present, so higher-level callers (marketplace, stacks, apply) that record a richer owner win over a creation-path default. Returns the (possibly updated) map.

func MergeUserMetadata

func MergeUserMetadata(current, overlay Metadata) Metadata

MergeUserMetadata applies a user-supplied overlay onto the current metadata while protecting built-in keys: reserved keys from current are preserved and cannot be overridden or removed by the overlay; non-reserved keys are replaced wholesale by the overlay (so users can add/remove their own labels). Use on update with the user's desired user-metadata as overlay.

func SanitizeUserMetadata

func SanitizeUserMetadata(in Metadata) Metadata

SanitizeUserMetadata returns a copy of in with all reserved (built-in) keys removed, so user-supplied metadata can never set or spoof platform-managed keys. nil in → nil out.

func SetBuiltin

func SetBuiltin(m Metadata, kv ...string) Metadata

SetBuiltin sets one or more reserved (built-in) key/value pairs on m, creating the map if needed, and returns it. Reserved keys are authoritative, so this always wins. Pairs are passed as key, value, key, value, …

func SetOwner

func SetOwner(m Metadata, kind string, id uint, name string) Metadata

SetOwner records the owner reference on m (creating the map if needed) and returns it. id is omitted when 0 and name when empty, so callers can record a partial reference (e.g. kind+id with the name resolved later).

type MetricSample

type MetricSample struct {
	ID            uint      `json:"id" gorm:"primaryKey"`
	ApplicationID uint      `json:"application_id" gorm:"index:idx_metric_app_time;not null"`
	RecordedAt    time.Time `json:"recorded_at" gorm:"index:idx_metric_app_time"`
	CPUPercent    float64   `json:"cpu_percent"`
	MemoryBytes   uint64    `json:"memory_bytes"`
	MemoryPercent float64   `json:"memory_percent"`
	NetRxBytes    uint64    `json:"net_rx_bytes"`
	NetTxBytes    uint64    `json:"net_tx_bytes"`
}

MetricSample is a point-in-time resource sample for an application, captured by the background scraper for short-term history.

type Middleware

type Middleware struct {
	ID          uint `json:"id" gorm:"primaryKey"`
	WorkspaceID uint `json:"workspace_id" gorm:"index:idx_mw_workspace_name,unique;not null"`
	// Name is the unique slug handle scoped to the workspace (Goma middleware name).
	Name string `json:"name" gorm:"index:idx_mw_workspace_name,unique;not null"`
	// DisplayName is the free-text label shown in the UI; not unique.
	DisplayName string                 `json:"display_name"`
	Type        string                 `json:"type" gorm:"not null"` // Goma middleware kind, e.g. basicAuth, rateLimit
	Paths       []string               `json:"paths,omitempty" gorm:"serializer:json"`
	Rule        map[string]interface{} `json:"rule,omitempty" gorm:"serializer:json"`
	CreatedAt   time.Time              `json:"created_at"`
	UpdatedAt   time.Time              `json:"updated_at"`
}

Middleware is a Goma Gateway middleware owned by a workspace. Routes reference it by Name. Rule holds the type-specific configuration as defined by Goma (mirrors goma-admin's JSON config approach).

type Network

type Network struct {
	ID          uint `json:"id" gorm:"primaryKey"`
	WorkspaceID uint `json:"workspace_id" gorm:"index:idx_network_workspace_name,unique;not null"`
	// Name is the unique slug handle (lowercase [a-z0-9-]) scoped to the workspace.
	Name string `json:"name" gorm:"index:idx_network_workspace_name,unique;not null"`
	// DisplayName is the free-text label shown in the UI; not unique.
	DisplayName string `json:"display_name"`
	DockerName  string `json:"docker_name" gorm:"uniqueIndex;not null"`
	Driver      string `json:"driver" gorm:"not null;default:bridge"`
	Internal    bool   `json:"internal" gorm:"not null;default:false"`
	IsDefault   bool   `json:"is_default" gorm:"not null;default:false"`
	// Imported marks a network that references a pre-existing external Docker
	// network by its own name (DockerName = the existing name; nothing recreated).
	Imported  bool      `json:"imported" gorm:"not null;default:false"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

Network is a managed Docker network owned by a workspace. The user-facing Name is unique per workspace; DockerName is the platform-managed, globally unique Docker network name (workspace + short id).

type NetworkAllocation

type NetworkAllocation struct {
	ID uint `json:"id" gorm:"primaryKey"`
	// DockerName is the globally-unique Docker network name this subnet is pinned to.
	DockerName string `json:"docker_name" gorm:"uniqueIndex;not null"`
	// Subnet is the allocated CIDR (e.g. 10.42.3.0/24); Gateway is its first host.
	Subnet  string `json:"subnet" gorm:"uniqueIndex;not null"`
	Gateway string `json:"gateway" gorm:"not null"`
	// NodeID is the node the network lives on (0 = local control-plane node). The
	// same subnet may recur across nodes for the same logical network — bridges are
	// node-local — so uniqueness is on DockerName/Subnet, not (Subnet, NodeID).
	NodeID uint `json:"node_id" gorm:"not null;default:0"`
	// Kind labels the allocation (see NetAllocKind*).
	Kind      string    `json:"kind" gorm:"not null;default:workspace"`
	CreatedAt time.Time `json:"created_at"`
}

NetworkAllocation is the ledger entry pinning one Docker network to a subnet carved from the platform network pool (MIABI_NETWORK_POOL_CIDR). It is the single source of truth the allocator consults to hand out non-overlapping subnets, so Miabi never depends on Docker's small built-in address pools.

DockerName is unique — the ledger is keyed by the Docker network name, which covers ownerless networks (gateway/overlay) uniformly and lets syncNetworks reuse the same subnet when it recreates a network on another node. Reserved rows (Kind "reserved") use a sentinel DockerName ("reserved:<subnet>").

type NodePlacement

type NodePlacement struct {
	// Name is the node's Miabi display name, or a short swarm node id when the node
	// is not (yet) correlated to a Miabi server record.
	Name string `json:"name"`
	// Tasks is the number of the app's running tasks on this node.
	Tasks int `json:"tasks"`
}

NodePlacement is where one slice of a cluster app's replicas runs: a node's display name and how many of the app's running tasks the scheduler placed on it. Transient — built on read from live Swarm task placement.

type Notification added in v1.6.0

type Notification struct {
	ID          uint          `json:"id" gorm:"primaryKey"`
	UserID      uint          `json:"user_id" gorm:"index:idx_notif_user_alert,priority:1;not null"`
	WorkspaceID uint          `json:"workspace_id" gorm:"index;not null"`
	AlertID     *uint         `json:"alert_id,omitempty" gorm:"index:idx_notif_user_alert,priority:2"`
	Kind        string        `json:"kind" gorm:"not null;default:alert"` // alert | info
	Category    AlertCategory `json:"category"`
	Severity    AlertSeverity `json:"severity" gorm:"not null;default:info"`
	Title       string        `json:"title"`
	Body        string        `json:"body"`
	SubjectLink string        `json:"subject_link,omitempty"`
	// ReadAt is nil while unread. Read state is per-user.
	ReadAt    *time.Time `json:"read_at,omitempty"`
	CreatedAt time.Time  `json:"created_at" gorm:"index"`
	UpdatedAt time.Time  `json:"updated_at"`
}

Notification is a per-user inbox item — the delivery of an Alert (or a standalone info message) to one member. Alerts are workspace-level and shared; notifications are per-user so read/unread stays clean. It renders the alert at delivery time (Title/Body/link), so the inbox is self-contained even after the alert archives. When an alert updates (crash-loop count bump, auto-resolve), the existing notification is updated in place (unique per user+alert) rather than a new row spamming the bell.

type NotificationChannel

type NotificationChannel struct {
	ID          uint                    `json:"id" gorm:"primaryKey"`
	WorkspaceID uint                    `json:"workspace_id" gorm:"index;not null"`
	Type        NotificationChannelType `json:"type" gorm:"not null"`
	Name        string                  `json:"name"`
	Config      map[string]string       `json:"config" gorm:"serializer:json"`
	Events      []string                `json:"events" gorm:"serializer:json"`
	Enabled     bool                    `json:"enabled" gorm:"not null;default:true"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

NotificationChannel is a workspace-configured outbound channel that pushes a human-readable message when a subscribed event fires. Transport-specific settings live in Config; secret values (e.g. bot_token) are encrypted at rest and masked in API responses.

type NotificationChannelType

type NotificationChannelType string

NotificationChannelType identifies a notification transport. Telegram ships first; the model is generic so Slack/Discord/email can follow.

const (
	ChannelTelegram NotificationChannelType = "telegram"
	ChannelSlack    NotificationChannelType = "slack"
	ChannelDiscord  NotificationChannelType = "discord"
)

type OAuthProvider

type OAuthProvider struct {
	ID uint `json:"id" gorm:"primaryKey"`
	// Name is the globally unique handle used in callback routes.
	Name string `json:"name" gorm:"uniqueIndex;not null"`
	// DisplayName is the free-text label shown on login buttons.
	DisplayName string            `json:"display_name"`
	Type        OAuthProviderType `json:"type" gorm:"not null"`

	ClientID        string `json:"-" gorm:"not null"`
	ClientSecretEnc string `json:"-" gorm:"column:client_secret;not null"`

	// OIDC endpoints. For "google" these are derived from the well-known config.
	Issuer      string `json:"issuer"`
	AuthURL     string `json:"auth_url"`
	TokenURL    string `json:"token_url"`
	UserInfoURL string `json:"userinfo_url"`

	Scopes string `json:"scopes" gorm:"default:'openid email profile'"`

	Enabled      bool `json:"enabled" gorm:"default:true;not null"`
	Hidden       bool `json:"hidden" gorm:"default:false;not null"`       // hide from login buttons
	AutoRegister bool `json:"auto_register" gorm:"default:true;not null"` // auto-create users on first login

	// AllowedDomains is a CSV of email domains permitted to sign in / register
	// through this provider. Empty means any domain is allowed.
	AllowedDomains string `json:"allowed_domains"`

	// OrganizationID is the realm this provider belongs to (nullable → default org).
	OrganizationID *uint `json:"organization_id" gorm:"index"`

	// EmailClaim / NameClaim map a generic OIDC userinfo claim onto the user's
	// email / display name. Empty falls back to the standard "email" / "name".
	EmailClaim string `json:"email_claim"`
	NameClaim  string `json:"name_claim"`

	// DefaultWorkspaceID + DefaultRole auto-join a newly registered SSO user to a
	// workspace with a role on first login. Nil = no auto-join (user is invited
	// separately).
	DefaultWorkspaceID *uint         `json:"default_workspace_id" gorm:"index"`
	DefaultRole        WorkspaceRole `json:"default_role"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

OAuthProvider is a platform-level SSO identity provider. Secrets are stored encrypted at rest (see internal/services/crypto) and never serialized.

type OAuthProviderType

type OAuthProviderType string

OAuthProviderType is the kind of identity provider.

const (
	OAuthProviderGoogle OAuthProviderType = "google" // Google OAuth 2.0 (well-known discovery)
	OAuthProviderOIDC   OAuthProviderType = "oidc"   // generic OpenID Connect
)

type Organization

type Organization struct {
	ID uint `json:"id" gorm:"primaryKey"`
	// Name is the globally unique handle (e.g. "default").
	Name string `json:"name" gorm:"uniqueIndex;not null"`
	// DisplayName is the free-text label shown in the UI.
	DisplayName string `json:"display_name"`
	// IsDefault marks the org new workspaces and providers attach to. Exactly one
	// row is the default.
	IsDefault bool `json:"is_default" gorm:"default:false;not null"`
	// EnforceSSO disables local password login for this org's users (Enterprise;
	// writing it is gated on the sso_saml entitlement). Off by default.
	EnforceSSO bool      `json:"enforce_sso" gorm:"default:false;not null"`
	CreatedAt  time.Time `json:"created_at"`
	UpdatedAt  time.Time `json:"updated_at"`
}

Organization is a realm that owns identity configuration — SSO providers, SAML, enforced login, and SCIM provisioning. Miabi seeds a single default organization spanning all workspaces. The table and the nullable foreign keys exist from day one so multi-org needs no destructive migration (the same discipline as Server / workspace_id).

type OwnerRef

type OwnerRef struct {
	Kind string `json:"kind"`           // one of OwnerKind*
	ID   uint   `json:"id,omitempty"`   // owning resource id (0 = none/not applicable)
	Name string `json:"name,omitempty"` // display name
}

OwnerRef is the parsed owner reference attached to a resource's metadata.

func Owner

func Owner(m Metadata) (OwnerRef, bool)

Owner parses the owner reference from m. ok is false when no owner kind is set.

type PasswordResetToken

type PasswordResetToken struct {
	ID        uint       `json:"id" gorm:"primaryKey"`
	UserID    uint       `json:"user_id" gorm:"index;not null"`
	TokenHash string     `json:"-" gorm:"uniqueIndex;not null"`
	ExpiresAt time.Time  `json:"expires_at" gorm:"not null"`
	UsedAt    *time.Time `json:"used_at"`
	CreatedAt time.Time  `json:"created_at"`
}

PasswordResetToken is a single-use, expiring password-reset credential.

type Permission

type Permission string

Permission is a fine-grained capability of the form "resource:action". Each built-in WorkspaceRole maps to a permission set that reproduces the rank-based RequireRole behavior exactly (monotonicity is enforced by permission_test.go). RequirePermission gates routes on a specific capability; custom roles are just alternative permission sets.

const (
	PermAppRead   Permission = "app:read"
	PermAppWrite  Permission = "app:write"  // create / update config / env / attach
	PermAppDeploy Permission = "app:deploy" // deploy / start / stop / restart / rollback / canary
	PermAppDelete Permission = "app:delete"
	PermAppAdmin  Permission = "app:admin" // reveal secrets, privileged host mounts

	PermDatabaseRead   Permission = "database:read"
	PermDatabaseWrite  Permission = "database:write"
	PermDatabaseDelete Permission = "database:delete"

	PermVolumeRead  Permission = "volume:read"
	PermVolumeWrite Permission = "volume:write"

	PermDomainRead  Permission = "domain:read"
	PermDomainWrite Permission = "domain:write"

	PermRouteRead  Permission = "route:read"
	PermRouteWrite Permission = "route:write"

	PermNetworkRead  Permission = "network:read"
	PermNetworkWrite Permission = "network:write"

	PermBackupRead    Permission = "backup:read"
	PermBackupRun     Permission = "backup:run"
	PermBackupRestore Permission = "backup:restore"

	PermSecretRead  Permission = "secret:read"
	PermSecretWrite Permission = "secret:write"

	PermMemberRead  Permission = "member:read"
	PermMemberWrite Permission = "member:write"

	PermWorkspaceWrite  Permission = "workspace:write"
	PermWorkspaceDelete Permission = "workspace:delete"
)

func AllPermissions

func AllPermissions() []Permission

AllPermissions returns the full catalog in a stable, grouped order (for the UI and the permission-picker in custom roles).

func (Permission) Action

func (p Permission) Action() string

Action returns the action segment ("deploy" for "app:deploy").

func (Permission) Resource

func (p Permission) Resource() string

Resource returns the resource segment of a permission ("app" for "app:deploy").

type PipelineDefinition

type PipelineDefinition struct {
	UIDModel
	ID          uint `json:"id" gorm:"primaryKey"`
	WorkspaceID uint `json:"workspace_id" gorm:"index:idx_pipeline_ws_name,unique;not null"`
	// Name is the unique slug handle scoped to the workspace.
	Name string `json:"name" gorm:"index:idx_pipeline_ws_name,unique;not null"`
	// DisplayName is the free-text label shown in the UI; not unique.
	DisplayName string `json:"display_name"`
	// ApplicationID optionally binds the pipeline's deploy step to an app.
	ApplicationID *uint `json:"application_id,omitempty" gorm:"index"`
	// Spec is the raw kind: Pipeline YAML (on/steps). Parsed at run time.
	Spec    string `json:"spec" gorm:"type:text;not null"`
	Enabled bool   `json:"enabled" gorm:"not null;default:true"`
	// WebhookSecret authenticates inbound provider push webhooks that fire this
	// pipeline. Generated on create; never serialized.
	WebhookSecret string `json:"-" gorm:"not null;default:''"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`

	// LastRun is a non-persisted summary of the pipeline's most recent run,
	// populated by the list endpoint for an at-a-glance health signal. nil when
	// the pipeline has never run.
	LastRun *PipelineRunSummary `json:"last_run,omitempty" gorm:"-"`
}

PipelineDefinition is a versioned, pipeline-as-code definition (kind: Pipeline). The raw YAML spec is stored so the runner reads the same document the repo carries at .miabi/pipeline.yaml.

type PipelineRun

type PipelineRun struct {
	ID          uint `json:"id" gorm:"primaryKey"`
	WorkspaceID uint `json:"workspace_id" gorm:"index;not null"`
	PipelineID  uint `json:"pipeline_id" gorm:"index;not null"`
	// Number is the per-pipeline monotonic run counter (#1, #2, …).
	Number int               `json:"number" gorm:"not null"`
	Status PipelineRunStatus `json:"status" gorm:"not null;default:pending"`
	// Trigger records how the run started: push | manual | schedule | upstream.
	Trigger       string `json:"trigger"`
	Commit        string `json:"commit,omitempty"`
	CommitMessage string `json:"commit_message,omitempty" gorm:"type:text"`
	// TriggeredByKeyID / TriggeredByUserID attribute the run (mirrors Deployment).
	TriggeredByKeyID  *uint `json:"triggered_by_key_id,omitempty"`
	TriggeredByUserID *uint `json:"triggered_by_user_id,omitempty"`
	// ImageID is the artifact the run produced (nil until the build step pushes).
	ImageID *uint `json:"image_id,omitempty"`
	// RunnerID records which runner executed this run (provenance / "built on
	// runner X"); nil when it ran on the internal runner.
	RunnerID *uint  `json:"runner_id,omitempty"`
	Error    string `json:"error,omitempty" gorm:"type:text"`

	StartedAt  *time.Time `json:"started_at,omitempty"`
	FinishedAt *time.Time `json:"finished_at,omitempty"`
	CreatedAt  time.Time  `json:"created_at"`

	Steps []PipelineStepRun `json:"steps,omitempty" gorm:"foreignKey:PipelineRunID"`
}

PipelineRun is one execution of a PipelineDefinition: build → test → … → deploy, turning a commit into an image and a Release.

func (PipelineRun) Summary

func (r PipelineRun) Summary() *PipelineRunSummary

Summary projects a run onto its list-view summary.

type PipelineRunStatus

type PipelineRunStatus string

PipelineRunStatus is the lifecycle state of a run or one of its steps.

const (
	PipelineRunPending   PipelineRunStatus = "pending"
	PipelineRunRunning   PipelineRunStatus = "running"
	PipelineRunSucceeded PipelineRunStatus = "succeeded"
	PipelineRunFailed    PipelineRunStatus = "failed"
	PipelineRunCanceled  PipelineRunStatus = "canceled"
)

func (PipelineRunStatus) IsTerminal

func (s PipelineRunStatus) IsTerminal() bool

IsTerminal reports whether the status is final.

type PipelineRunSummary

type PipelineRunSummary struct {
	ID         uint              `json:"id"`
	Number     int               `json:"number"`
	Status     PipelineRunStatus `json:"status"`
	Trigger    string            `json:"trigger,omitempty"`
	Commit     string            `json:"commit,omitempty"`
	StartedAt  *time.Time        `json:"started_at,omitempty"`
	FinishedAt *time.Time        `json:"finished_at,omitempty"`
	CreatedAt  time.Time         `json:"created_at"`
}

PipelineRunSummary is a lightweight view of a run for list contexts — enough to render a status badge and "ran 2h ago" without shipping steps or logs.

type PipelineStepRun

type PipelineStepRun struct {
	ID            uint              `json:"id" gorm:"primaryKey"`
	PipelineRunID uint              `json:"pipeline_run_id" gorm:"index;not null"`
	Ordinal       int               `json:"ordinal" gorm:"not null"`
	Name          string            `json:"name" gorm:"not null"`
	Status        PipelineRunStatus `json:"status" gorm:"not null;default:pending"`
	Image         string            `json:"image,omitempty"`
	Uses          string            `json:"uses,omitempty"` // built-in step kind (e.g. "deploy")
	// Run is the shell command for a container (Image) step. The runner executes
	// it in a non-login shell inside the step image — like a GitHub Actions
	// `run:` step — so shell features (pipes, &&, env expansion) work. Empty for
	// `uses:` built-in steps.
	Run string `json:"run,omitempty" gorm:"type:text"`
	// ContinueOnError lets the run keep going (and still succeed) when this step
	// fails; the step is still recorded failed.
	ContinueOnError bool `json:"continue_on_error,omitempty"`
	ExitCode        int  `json:"exit_code"`
	// Logs is a bounded tail of the step's output for instant display; the full
	// log lives in the log store at LogRef once the step is terminal (see
	// plans/log-storage.md). LogRef is empty when the store is disabled or the
	// row predates externalization — readers fall back to this tail.
	Logs         string `json:"logs,omitempty" gorm:"type:text"`
	LogRef       string `json:"log_ref,omitempty"`
	LogBytes     int64  `json:"log_bytes,omitempty"`
	LogLines     int    `json:"log_lines,omitempty"`
	LogTruncated bool   `json:"log_truncated,omitempty"`

	StartedAt  *time.Time `json:"started_at,omitempty"`
	FinishedAt *time.Time `json:"finished_at,omitempty"`
	CreatedAt  time.Time  `json:"created_at"`
}

PipelineStepRun is one step within a run, executed in an isolated container.

type Plan

type Plan struct {
	ID          uint   `json:"id" gorm:"primaryKey"`
	Name        string `json:"name" gorm:"uniqueIndex;not null"`
	Description string `json:"description"`
	IsDefault   bool   `json:"is_default" gorm:"not null;default:false"` // applied to workspaces with no plan
	IsActive    bool   `json:"is_active" gorm:"not null;default:true"`

	// Numeric limits (per workspace unless noted). -1 = unlimited, 0 = none.
	// The DB default is 0 (= the Go zero value) so GORM's zero-value omission on
	// insert is a no-op; an intentional 0 ("none") persists correctly, and -1
	// ("unlimited") is non-zero so it is always written.
	MaxApps              int `json:"max_apps" gorm:"not null;default:0"`
	MaxDatabaseInstances int `json:"max_database_instances" gorm:"not null;default:0"`
	MaxCronJobs          int `json:"max_cron_jobs" gorm:"not null;default:0"`
	MaxVolumes           int `json:"max_volumes" gorm:"not null;default:0"`
	MaxNetworks          int `json:"max_networks" gorm:"not null;default:0"`
	MaxAPIKeys           int `json:"max_api_keys" gorm:"not null;default:0"`
	// MaxMembers caps the workspace's member count (owner + invited members).
	MaxMembers int `json:"max_members" gorm:"not null;default:0"`
	// MaxDatabasesPerInstance caps logical databases within a single instance.
	MaxDatabasesPerInstance int `json:"max_databases_per_instance" gorm:"not null;default:0"`
	// MaxCPUCores / MaxMemoryMB cap the workspace's aggregate app compute.
	MaxCPUCores int `json:"max_cpu_cores" gorm:"not null;default:0"`
	MaxMemoryMB int `json:"max_memory_mb" gorm:"not null;default:0"`
	// MaxDatabaseInstanceSizeMB caps a single DB instance's declared data-volume
	// size; MaxStorageMB caps the workspace's aggregate declared storage
	// (volumes + DB instance data volumes).
	MaxDatabaseInstanceSizeMB int `json:"max_database_instance_size_mb" gorm:"not null;default:0"`
	MaxStorageMB              int `json:"max_storage_mb" gorm:"not null;default:0"`
	// MaxRunners caps how many build/pipeline runners a workspace may register
	// (its own build machines). -1 = unlimited, 0 = none.
	MaxRunners int `json:"max_runners" gorm:"not null;default:0"`
	// MaxGPUs caps the aggregate number of whole GPU units a workspace's *running*
	// apps may hold at once (summed across apps, like MaxCPUCores). 0 = none,
	// -1 = unlimited. A stopped app frees its units.
	MaxGPUs int `json:"max_gpus" gorm:"not null;default:0"`

	// Capabilities (feature gates). Default false for the same omission reason.
	AllowCustomTLS            bool `json:"allow_custom_tls" gorm:"not null;default:false"`
	AllowPrivilegedHostMounts bool `json:"allow_privileged_host_mounts" gorm:"not null;default:false"`
	// AllowShellExec gates opening an interactive shell (docker exec) into a
	// running application container from the panel.
	AllowShellExec bool `json:"allow_shell_exec" gorm:"not null;default:false"`
	// AllowSharedStorage gates creating shared-storage volumes (NFS / CIFS-SMB)
	// that replicas can mount read-write across nodes. Node-local volumes are
	// always allowed; this only governs the rwx backends.
	AllowSharedStorage bool `json:"allow_shared_storage" gorm:"not null;default:false"`
	// AllowDNSProviders gates connecting a managed DNS provider (Cloudflare/Route
	// 53/DigitalOcean) for automated ownership verification + app records. Manual
	// (copy-paste) DNS always works; this only governs the automation.
	AllowDNSProviders bool `json:"allow_dns_providers" gorm:"not null;default:false"`
	// AllowCustomLabels gates attaching user-defined Docker labels to app
	// containers (for label-driven tools like Traefik). Off by default; the
	// reserved-prefix protection (io.miabi.*, com.docker.*) applies regardless.
	AllowCustomLabels bool `json:"allow_custom_labels" gorm:"not null;default:false"`
	// AllowPlatformRunners grants this workspace's build/pipeline jobs access to
	// the platform-shared runner pool (in addition to any runners it owns). Off by
	// default; owned runners are always usable.
	AllowPlatformRunners bool `json:"allow_platform_runners" gorm:"not null;default:false"`
	// AllowCustomBuilder gates a per-app custom buildpack builder image. A custom
	// builder runs on the runner with docker-daemon access, so it is a privileged
	// input on shared/multi-tenant runners; off by default (the platform default
	// builder is used instead).
	AllowCustomBuilder bool `json:"allow_custom_builder" gorm:"not null;default:false"`
	// SecurityProfile hardens how this workspace's application and job containers
	// run. "" / "default" = the image's default user (unchanged); "restricted"
	// forces a non-root platform UID. Default "default" is the zero-equivalent, so
	// the GORM zero-value omission still round-trips an intentional value.
	SecurityProfile string `json:"security_profile" gorm:"not null;default:'default'"`
	// AllowOfficialImageUser lets applications installed from an official
	// marketplace template keep the image's own default user even when this
	// workspace's SecurityProfile is "restricted". Only official-source installs
	// qualify (a tenant cannot self-declare an app official), and it never relaxes
	// a platform-wide MIABI_FORCE_NON_ROOT_USER mandate. Off by default.
	AllowOfficialImageUser bool `json:"allow_official_image_user" gorm:"not null;default:false"`
	// AllowGPU gates whether this workspace's apps may request GPU devices at all.
	// Off by default, like AllowSharedStorage: a workspace cannot request any GPU
	// until its plan opts in. Attaching a GPU is device passthrough (privileged),
	// so it is gated hard here and is incompatible with the restricted profile.
	AllowGPU bool `json:"allow_gpu" gorm:"not null;default:false"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

Plan is an admin-defined per-workspace quota + capability template. Numeric limits use -1 for unlimited and 0 for none; capability booleans gate features.

type PlatformBackup

type PlatformBackup struct {
	ID          uint                  `json:"id" gorm:"primaryKey"`
	Subject     PlatformBackupSubject `json:"subject" gorm:"not null"` // database | volume
	VolumeName  string                `json:"volume_name,omitempty"`   // target volume (subject=volume)
	Status      BackupStatus          `json:"status" gorm:"not null;default:pending"`
	Trigger     string                `json:"trigger"`     // manual | scheduled
	Destination string                `json:"destination"` // local | s3

	S3Bucket  string `json:"s3_bucket,omitempty"`
	S3Path    string `json:"s3_path,omitempty"`  // remote folder prefix used
	Filename  string `json:"filename,omitempty"` // artifact object name
	SizeBytes int64  `json:"size_bytes"`

	// Logs is a bounded tail of the backup output for instant display; the full
	// log lives in the log store at LogRef once the run is terminal (see
	// plans/log-storage.md). LogRef is empty when the store is disabled or the
	// row predates externalization — readers fall back to this tail.
	Logs         string `json:"logs,omitempty" gorm:"type:text"`
	LogRef       string `json:"log_ref,omitempty"`
	LogBytes     int64  `json:"log_bytes,omitempty"`
	LogLines     int    `json:"log_lines,omitempty"`
	LogTruncated bool   `json:"log_truncated,omitempty"`
	Error        string `json:"error,omitempty" gorm:"type:text"`

	StartedAt  *time.Time `json:"started_at"`
	FinishedAt *time.Time `json:"finished_at"`
	CreatedAt  time.Time  `json:"created_at"`
}

PlatformBackup is a single disaster-recovery backup run of the platform itself — the control-plane database or a platform volume. It is distinct from the per-workspace Backup/VolumeBackup tables (tenant data) and reuses BackupStatus and the existing one-shot/logging lifecycle.

type PlatformBackupSettings

type PlatformBackupSettings struct {
	ID uint `json:"id" gorm:"primaryKey"`

	// Destination. S3 is strongly recommended for DR (a local artifact on the box
	// you are recovering from is no DR at all).
	S3Enabled        bool   `json:"s3_enabled"`
	S3Endpoint       string `json:"s3_endpoint,omitempty"`
	S3Bucket         string `json:"s3_bucket,omitempty"`
	S3Region         string `json:"s3_region,omitempty"`
	S3AccessKey      string `json:"s3_access_key,omitempty"`
	S3SecretKeyEnc   string `json:"-" gorm:"column:s3_secret_key_enc"` // crypto.Encrypt (platform scope)
	S3UseSSL         bool   `json:"s3_use_ssl"`
	S3ForcePathStyle bool   `json:"s3_force_path_style"`

	// Path prefixes within the bucket.
	DatabaseBackupPath string `json:"database_backup_path,omitempty"`
	VolumeBackupPath   string `json:"volume_backup_path,omitempty"`

	// Schedule + retention.
	ScheduleEnabled bool     `json:"schedule_enabled"`
	ScheduleCron    string   `json:"schedule_cron,omitempty"`
	MaxBackups      int      `json:"max_backups"`
	RetentionDays   int      `json:"retention_days"`
	Volumes         []string `json:"volumes" gorm:"serializer:json"` // platform volumes to include

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`

	// S3SecretSet reports whether a secret key is stored, without exposing it.
	// Not persisted; populated on read so the UI can render "••••• (set)".
	S3SecretSet bool `json:"s3_secret_set" gorm:"-"`
}

PlatformBackupSettings is the single-row platform backup target + policy: the S3 destination (mirroring WorkspaceBackupSettings), the schedule, retention, and the set of platform volumes to include. The S3 secret is encrypted at rest with the platform-scoped crypto.Encrypt and never returned.

type PlatformBackupSubject

type PlatformBackupSubject string

PlatformBackupSubject identifies what a platform backup run captured: Miabi's own control-plane database, or one of the platform/system Docker volumes.

const (
	PlatformBackupDatabase PlatformBackupSubject = "database"
	PlatformBackupVolume   PlatformBackupSubject = "volume"
)

type PortBinding

type PortBinding struct {
	ID            uint              `json:"id" gorm:"primaryKey"`
	WorkspaceID   uint              `json:"workspace_id" gorm:"index;not null"`
	ApplicationID uint              `json:"application_id" gorm:"index;not null"`
	ContainerPort int               `json:"container_port" gorm:"not null"`
	Protocol      string            `json:"protocol" gorm:"not null;default:tcp"`
	HostPort      int               `json:"host_port" gorm:"not null"`
	Status        PortBindingStatus `json:"status" gorm:"not null;default:pending;index"`
	// ServerID is the node the host port is published on. Host ports are a
	// per-host resource, so conflict checks + allocation are scoped to it
	// (0 = the local/manager node). Backfilled from the owning app.
	ServerID uint `json:"server_id" gorm:"index;not null;default:0"`
	// Managed marks a control-plane auto-forward binding (created for a
	// port-forward node's route ingress) rather than a user request. Managed
	// bindings are auto-approved and never enter the admin review queue.
	Managed bool `json:"managed" gorm:"not null;default:false"`
	// BindIP is the host interface the port is published on ("" = all/0.0.0.0).
	// Managed bindings use the node's private address so ingress stays private.
	BindIP      string    `json:"bind_ip,omitempty"`
	RequestedBy uint      `json:"requested_by"`
	ReviewedBy  *uint     `json:"reviewed_by,omitempty"`
	ReviewNote  string    `json:"review_note,omitempty"`
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`
}

PortBinding is a request to publish a container port on a host port. Because host ports are a node-wide shared resource, a binding is only published once a platform admin approves it.

type PortBindingStatus

type PortBindingStatus string

PortBindingStatus is the review state of a host port binding request.

const (
	PortBindingPending  PortBindingStatus = "pending"
	PortBindingApproved PortBindingStatus = "approved"
	PortBindingRejected PortBindingStatus = "rejected"
)

type Registry

type Registry struct {
	ID          uint `json:"id" gorm:"primaryKey"`
	WorkspaceID uint `json:"workspace_id" gorm:"index:idx_registry_workspace_name,unique;not null"`
	// Name is the unique slug handle scoped to the workspace.
	Name string `json:"name" gorm:"index:idx_registry_workspace_name,unique;not null"`
	// DisplayName is the free-text label shown in the UI; not unique.
	DisplayName string `json:"display_name"`
	// Server is the registry host, e.g. "registry-1.docker.io", "ghcr.io",
	// "registry.gitlab.com". Empty defaults to Docker Hub.
	Server   string `json:"server"`
	Username string `json:"username"`
	// Secret holds the password or access token, encrypted at rest.
	Secret string `json:"-" gorm:"not null"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`

	// HasSecret is a transient flag for responses (never persisted).
	HasSecret bool `json:"has_secret" gorm:"-"`
}

Registry is a stored container-registry credential used to pull private images at deploy time. The Secret (password / access token) is encrypted at rest and never serialized.

type RegistrySettings

type RegistrySettings struct {
	ID uint `json:"id" gorm:"primaryKey"`

	// Enabled runs the registry container and seeds its gateway route/middleware.
	// Default false → a no-op so single-node installs are unchanged.
	Enabled bool `json:"enabled"`
	// Host is the public registry hostname (e.g. registry.<external-base-domain>),
	// the docker login target. Derived from the external base domain when blank.
	Host string `json:"host,omitempty"`

	// StorageType is "filesystem" (a managed volume) or "s3" (S3/MinIO).
	StorageType string `json:"storage_type" gorm:"not null;default:filesystem"`

	// Filesystem driver: the managed data volume.
	VolumeName string `json:"volume_name,omitempty"`

	// S3 / MinIO driver.
	S3Endpoint       string `json:"s3_endpoint,omitempty"`
	S3Bucket         string `json:"s3_bucket,omitempty"`
	S3Region         string `json:"s3_region,omitempty"`
	S3AccessKey      string `json:"s3_access_key,omitempty"`
	S3SecretKeyEnc   string `json:"-" gorm:"column:s3_secret_key_enc"` // crypto.Encrypt (platform scope)
	S3ForcePathStyle bool   `json:"s3_force_path_style"`

	// DeleteEnabled turns on the registry's delete API (a prerequisite for GC).
	DeleteEnabled bool `json:"delete_enabled"`
	// PerWorkspaceQuotaMB caps a workspace namespace's total blob size (0 = none).
	PerWorkspaceQuotaMB int `json:"per_workspace_quota_mb"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`

	// S3SecretSet reports whether a secret key is stored, without exposing it.
	// Not persisted; populated on read so the UI can render "••••• (set)".
	S3SecretSet bool `json:"s3_secret_set" gorm:"-"`
}

RegistrySettings is the single-row, platform-scoped configuration for the built-in Docker registry (CNCF distribution / `registry:3`). One registry per platform, so this mirrors PlatformBackupSettings (global, not per-workspace). The S3 secret is encrypted at rest with the platform-scoped crypto.Encrypt and never returned by the API.

func (*RegistrySettings) UsesS3

func (r *RegistrySettings) UsesS3() bool

UsesS3 reports whether the registry is configured for the S3 storage driver.

type Release

type Release struct {
	ID uint `json:"id" gorm:"primaryKey"`
	// (ApplicationID, Version) is unique: concurrent deploys that both compute the
	// same MAX(version)+1 can no longer both persist — one hits the constraint and
	// rolls back instead of corrupting history with duplicate versions.
	ApplicationID uint   `json:"application_id" gorm:"index:idx_release_app_version,unique;index;not null"`
	DeploymentID  uint   `json:"deployment_id" gorm:"not null"`
	Version       int    `json:"version" gorm:"index:idx_release_app_version,unique;not null"`
	Image         string `json:"image" gorm:"not null"`
	ContainerID   string `json:"container_id"`
	Active        bool   `json:"active" gorm:"not null;default:false"`
	// Adopted marks a release whose ContainerID points at a pre-existing container
	// adopted by the import flow (not created by the deploy pipeline). It lets the
	// Docker browser badge the container "adopted" and is cleared when a native
	// deploy supersedes it.
	Adopted bool `json:"adopted" gorm:"not null;default:false"`
	// Pinned releases are protected from deletion during cleanup.
	Pinned bool `json:"pinned" gorm:"not null;default:false"`

	// Provenance (GitOps & CI/CD). A release generalizes into a promotable
	// artifact: image digest + config snapshot + version + provenance, flowing
	// through environments. These fields are populated when a deploy originates
	// from a pipeline run or a digest-pinned apply; they stay zero for legacy
	// hand-triggered deploys.
	Digest        string `json:"digest,omitempty"`                       // sha256:… the release ran
	Commit        string `json:"commit,omitempty"`                       // source commit, when known
	PipelineRunID *uint  `json:"pipeline_run_id,omitempty" gorm:"index"` // producing run
	ImageID       *uint  `json:"image_id,omitempty" gorm:"index"`        // catalog artifact
	EnvironmentID *uint  `json:"environment_id,omitempty" gorm:"index"`  // promotion stage

	CreatedAt time.Time `json:"created_at"`
}

Release is a successfully deployed, rollback-able version of an application.

type ReleaseApproval

type ReleaseApproval struct {
	ID            uint      `json:"id" gorm:"primaryKey"`
	WorkspaceID   uint      `json:"workspace_id" gorm:"index;not null"`
	ReleaseID     uint      `json:"release_id" gorm:"index;not null"`
	EnvironmentID *uint     `json:"environment_id,omitempty" gorm:"index"`
	ApproverID    uint      `json:"approver_id" gorm:"not null"`
	Approved      bool      `json:"approved" gorm:"not null;default:true"`
	Comment       string    `json:"comment,omitempty"`
	CreatedAt     time.Time `json:"created_at"`
}

ReleaseApproval records one approver's decision on promoting a release into an environment. A promotion needs Environment.RequiredApprovals approvals.

type ResourcePolicy

type ResourcePolicy struct {
	ID           uint      `json:"id" gorm:"primaryKey"`
	WorkspaceID  uint      `json:"workspace_id" gorm:"index:idx_resource_policy;not null"`
	UserID       uint      `json:"user_id" gorm:"index:idx_resource_policy;not null"`
	ResourceType string    `json:"resource_type" gorm:"index:idx_resource_policy;not null"`
	ResourceID   uint      `json:"resource_id" gorm:"index:idx_resource_policy;not null"`
	Permissions  []string  `json:"permissions" gorm:"serializer:json"`
	CreatedAt    time.Time `json:"created_at"`
	UpdatedAt    time.Time `json:"updated_at"`

	User User `json:"user,omitempty" gorm:"foreignKey:UserID"`
}

ResourcePolicy grants a user a set of permissions on a single resource (Enterprise; gated on resource_policies for writes). It augments the workspace-level role: a member may be a viewer everywhere yet hold app:deploy on one specific app. The table is empty in Community.

func (*ResourcePolicy) Grants

func (p *ResourcePolicy) Grants(perm Permission) bool

Grants reports whether the policy includes a permission.

type RestartPolicy

type RestartPolicy string

RestartPolicy is the Docker restart policy applied to an app's container. It mirrors the engine's policies; the platform default is "unless-stopped".

const (
	RestartNo            RestartPolicy = "no"             // never restart
	RestartAlways        RestartPolicy = "always"         // always restart
	RestartUnlessStopped RestartPolicy = "unless-stopped" // restart unless explicitly stopped (default)
	RestartOnFailure     RestartPolicy = "on-failure"     // restart only on non-zero exit
)

type Route

type Route struct {
	UIDModel
	ID          uint `json:"id" gorm:"primaryKey"`
	WorkspaceID uint `json:"workspace_id" gorm:"index:idx_route_workspace_name,unique;not null"`
	// Name is the unique slug handle scoped to the workspace (Goma route name).
	Name string `json:"name" gorm:"index:idx_route_workspace_name,unique;not null"`
	// DisplayName is the free-text label shown in the UI; not unique.
	DisplayName   string       `json:"display_name"`
	ApplicationID uint         `json:"application_id" gorm:"index;not null"`
	Path          string       `json:"path" gorm:"not null;default:/"`
	Hosts         []string     `json:"hosts,omitempty" gorm:"serializer:json"`
	Methods       []string     `json:"methods,omitempty" gorm:"serializer:json"`
	Middlewares   []string     `json:"middlewares,omitempty" gorm:"serializer:json"` // middleware names
	Rewrite       string       `json:"rewrite,omitempty"`                            // path rewrite (Goma route rewrite)
	TargetPort    int          `json:"target_port"`                                  // 0 = use the app's port
	TLSMode       RouteTLSMode `json:"tls_mode" gorm:"not null;default:acme"`
	// TLSProvider names the Goma certManager provider that issues/serves this
	// route's cert (the multi-provider certManager). Empty = the gateway default.
	TLSProvider string `json:"tls_provider,omitempty"`
	// Generated marks a platform-generated external-access route (managed from the
	// app's External access card, shown read-only in the Routes UI).
	Generated bool `json:"generated" gorm:"not null;default:false"`
	// AdvancedConfig is a raw Goma route YAML the admin authors directly; when
	// set, it supersedes the structured fields at render time (Miabi still
	// injects name + backends + tls so the route can't misroute). Empty = simple.
	AdvancedConfig string `json:"advanced_config,omitempty" gorm:"type:text"`
	// CertificateID references a stored Certificate when TLSMode=custom.
	CertificateID *uint `json:"certificate_id,omitempty" gorm:"index"`
	// Certificate declares the FK so deleting the referenced certificate nulls
	// this pointer (ON DELETE SET NULL) instead of leaving it dangling. The route
	// falls back to its default TLS mode. Association is not serialized.
	Certificate *Certificate `json:"-" gorm:"foreignKey:CertificateID;constraint:OnDelete:SET NULL"`
	Enabled     bool         `json:"enabled" gorm:"not null;default:true"`
	// Status is the route's config-sync status, set whenever the workspace proxy
	// config is reconciled.
	Status RouteStatus `json:"status" gorm:"not null;default:pending"`
	// StatusReason explains a non-live status (e.g. "domain not verified",
	// "disabled", or the gateway write error). Empty when live.
	StatusReason string `json:"status_reason,omitempty"`
	// SyncedAt is when the route was last reconciled into the gateway config.
	SyncedAt *time.Time `json:"synced_at,omitempty"`
	// Metadata holds free-form labels; "miabi.io/" keys are platform-managed.
	Metadata  Metadata  `json:"metadata,omitempty" gorm:"serializer:json"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`

	// HasCustomCert is a transient flag for responses (never persisted).
	HasCustomCert bool `json:"has_custom_cert" gorm:"-"`

	// DNSTarget / DNSHostname are the public address an A/AAAA (or CNAME) record
	// for this route's hosts should point to — the gateway that terminates the
	// route. Transient, populated on read.
	DNSTarget   string `json:"dns_target,omitempty" gorm:"-"`
	DNSHostname string `json:"dns_hostname,omitempty" gorm:"-"`

	// Backends are the actual upstream endpoints the gateway uses for this route
	// (the node-local DNS alias, or a port-forward node's address:hostPort).
	// Transient, populated on read so the UI shows the real backend.
	Backends []string `json:"backends,omitempty" gorm:"-"`
}

Route is a Goma Gateway route owned by a workspace and bound to an application. The container backend is injected by the platform at render time (the app's network alias), so users never set it.

type RouteStatus

type RouteStatus string

RouteStatus is a route's config-sync status with the gateway — whether Goma is actually serving the route's config. It is NOT upstream health: RouteLive means "Goma is configured to serve this route", not "the backend answers 200".

const (
	// RouteStatusPending is the initial state before the route has been synced.
	RouteStatusPending RouteStatus = "pending"
	// RouteStatusLive means the route was synced and Goma is serving it (enabled,
	// and every host falls under a verified domain).
	RouteStatusLive RouteStatus = "live"
	// RouteStatusOffline means the route is synced but not served — it is disabled,
	// or one of its hosts is not under a verified domain (rendered enabled:false).
	RouteStatusOffline RouteStatus = "offline"
	// RouteStatusError means the last attempt to push the route's config to the
	// gateway failed; StatusReason carries the detail.
	RouteStatusError RouteStatus = "error"
)

type RouteTLSMode

type RouteTLSMode string

RouteTLSMode selects how TLS is served for a route's hosts.

const (
	RouteTLSNone   RouteTLSMode = "none"
	RouteTLSACME   RouteTLSMode = "acme"   // Goma's global ACME issuer
	RouteTLSCustom RouteTLSMode = "custom" // user-supplied certificate
)

type Runner

type Runner struct {
	UIDModel
	ID uint `json:"id" gorm:"primaryKey"`
	// Name is a URL-safe slug handle, unique within its owner (per workspace for
	// owned runners; among shared runners for platform ones).
	Name string `json:"name" gorm:"index:idx_runner_ws_name,unique;not null"`
	// DisplayName is the free-text label shown in the UI (defaults to Name).
	DisplayName string `json:"display_name"`
	// WorkspaceID is nil for platform-shared runners, set for workspace-owned
	// ones. Part of the (workspace_id, name) uniqueness index.
	WorkspaceID *uint       `json:"workspace_id,omitempty" gorm:"index:idx_runner_ws_name,unique"`
	Scope       RunnerScope `json:"scope" gorm:"not null;default:workspace"`
	// Labels route jobs to eligible runners (e.g. "arch=amd64", "buildkit",
	// "gpu"). A job runs only where its required labels are a subset of these.
	Labels []string `json:"labels,omitempty" gorm:"serializer:json"`
	// Concurrency is how many jobs the runner may lease at once (declared by the
	// operator; the scheduler tracks live leases against it).
	Concurrency int `json:"concurrency" gorm:"not null;default:1"`

	// Self-reported platform facts (filled on connect).
	OS      string `json:"os,omitempty"`
	Arch    string `json:"arch,omitempty"`
	Version string `json:"version,omitempty"`
	// RemoteIP is the source address of the runner's most recent tunnel
	// connection (last known; persists across disconnects for the detail view).
	RemoteIP string `json:"remote_ip,omitempty"`

	Status RunnerStatus `json:"status" gorm:"not null;default:offline"`
	// Cordoned holds the runner out of scheduling without disconnecting it
	// (operator pause), mirroring Server.Cordoned.
	Cordoned bool `json:"cordoned" gorm:"not null;default:false"`
	// Enabled is the admin/owner on-off switch; a disabled runner never receives
	// jobs even if connected. Defaults true.
	Enabled bool `json:"enabled" gorm:"not null;default:true"`
	// TokenHash is the SHA-256 of the one-time registration token; the plaintext
	// is shown once at creation and never stored (mirrors Server.TokenHash).
	TokenHash string `json:"-" gorm:"index"`
	// Ephemeral marks a per-job, autoscaled runner (spun up for one job and torn
	// down after). Standing runners are non-ephemeral. Reserved for the later
	// autoscaling phase; excluded from the owned-runner quota when set.
	Ephemeral bool `json:"ephemeral" gorm:"not null;default:false"`
	// Connected reflects a live tunnel (transient; set by the connection manager).
	Connected bool `json:"connected" gorm:"-"`

	LastSeenAt *time.Time `json:"last_seen_at,omitempty"`
	// ConnectedSince is when the current unbroken connection began — reset on every
	// reconnect, cleared on disconnect. LastSeenAt cannot answer "how long has this
	// runner been up?" because the heartbeat refreshes it every 30s; the offline
	// alert needs that to avoid clearing on a runner that is flapping.
	ConnectedSince *time.Time `json:"connected_since,omitempty"`
	CreatedByID    *uint      `json:"created_by_id,omitempty"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

Runner is a machine dedicated to build and pipeline execution. Unlike a Server (which runs apps/databases), a runner never hosts workloads: it leases build jobs, executes each step in an isolated container, builds and pushes the image to a registry, and reports status — the app node only ever pulls the resulting digest. A runner dials in over the same outbound tunnel shape as a node agent, but with a distinct, tightly-scoped registration token.

A runner is either workspace-owned (WorkspaceID set, Scope=workspace) or platform-shared (WorkspaceID nil, Scope=shared), following the nullable WorkspaceID modeling used by TemplateSource.

type RunnerLease

type RunnerLease struct {
	ID       uint `json:"id" gorm:"primaryKey"`
	RunnerID uint `json:"runner_id" gorm:"index;not null"`
	// Kind namespaces RunID (pipeline run vs deploy build); see LeaseKind.
	Kind LeaseKind `json:"kind" gorm:"not null;default:pipeline"`
	// RunID is the job this lease claims (a PipelineRun or a Deployment, per Kind).
	// A partial unique index on (kind, run_id) for active rows enforces the
	// at-most-one active claim per job at the DB level.
	RunID  uint  `json:"run_id" gorm:"index;not null"`
	StepID *uint `json:"step_id,omitempty"`

	Status   RunnerLeaseStatus `json:"status" gorm:"not null;default:active;index"`
	LeasedAt time.Time         `json:"leased_at"`
	// ExpiresAt is the lease deadline; a live runner renews it, a dead one lets it
	// lapse (→ requeue). Mirrors the job deadline.
	ExpiresAt time.Time `json:"expires_at" gorm:"index"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

RunnerLease is a runner's at-most-once claim on a job — a pipeline run or a deploy build (per Kind) — and, optionally, a specific step. It is created when a runner leases a job and released when the job goes terminal; an active lease past its ExpiresAt is a dead runner, so the sweeper marks it expired and the job requeues. Concurrency is a runner's active-lease count vs its Concurrency.

type RunnerLeaseStatus

type RunnerLeaseStatus string

RunnerLeaseStatus is the state of a runner's claim on a pipeline run.

const (
	// LeaseActive: the runner holds the job and is executing it. Exactly one
	// active lease may exist per run (the at-most-once claim).
	LeaseActive RunnerLeaseStatus = "active"
	// LeaseDone: the run reached a terminal state and the lease was released.
	LeaseDone RunnerLeaseStatus = "done"
	// LeaseExpired: the lease passed its deadline without completing (a dead
	// runner); the sweeper requeues the run onto another runner.
	LeaseExpired RunnerLeaseStatus = "expired"
)

type RunnerScope

type RunnerScope string

RunnerScope is who may schedule jobs onto a runner.

const (
	// ScopeWorkspace: a runner registered and owned by a single workspace; only
	// that workspace's jobs run on it (WorkspaceID is set).
	ScopeWorkspace RunnerScope = "workspace"
	// ScopeShared: a platform-shared runner (WorkspaceID is nil) usable by any
	// workspace whose plan grants the platform-runners capability. Managed by
	// admins, mirroring a global TemplateSource/OAuthProvider.
	ScopeShared RunnerScope = "shared"
)

type RunnerStatus

type RunnerStatus string

RunnerStatus is the reachability state of a runner, mirroring ServerStatus.

const (
	// RunnerStatusOnline: the runner has a live tunnel and is leasing jobs.
	RunnerStatusOnline RunnerStatus = "online"
	// RunnerStatusOffline: no live tunnel (never connected, or heartbeat lapsed).
	RunnerStatusOffline RunnerStatus = "offline"
	// RunnerStatusDraining: connected but finishing its in-flight jobs before it
	// disconnects (no new leases). Distinct from Cordoned, which is an operator
	// hold applied while the runner may still be online.
	RunnerStatusDraining RunnerStatus = "draining"
)

type RuntimeKind

type RuntimeKind string

RuntimeKind is how an application runs: a single Docker container (default, works on plain Docker) or a replicated Swarm service (cluster mode). It is auto-gated on the manager being a swarm manager — a "service" app only deploys when cluster mode is enabled.

const (
	// RuntimeContainer runs the app as one plain Docker container on its node.
	RuntimeContainer RuntimeKind = "container"
	// RuntimeService runs the app as a replicated Swarm service on the workspace
	// overlay network.
	RuntimeService RuntimeKind = "service"
)

type SAMLConfig

type SAMLConfig struct {
	ID             uint  `json:"id" gorm:"primaryKey"`
	OrganizationID *uint `json:"organization_id" gorm:"index"`
	// Name is the globally unique handle.
	Name string `json:"name" gorm:"uniqueIndex;not null"`
	// DisplayName is the free-text label shown in the UI.
	DisplayName string `json:"display_name"`

	// IdP metadata: a URL to fetch, or inline XML. One of the two is required.
	IDPMetadataURL string `json:"idp_metadata_url"`
	IDPMetadataXML string `json:"idp_metadata_xml" gorm:"type:text"`

	// SPEntityID overrides the service-provider entity ID (defaults to the SP
	// metadata URL when blank).
	SPEntityID string `json:"sp_entity_id"`

	// Attribute names mapping the assertion onto the user. Blank falls back to
	// the standard "email" / "displayName" (and the NameID for email).
	AttrEmail string `json:"attr_email"`
	AttrName  string `json:"attr_name"`

	Enabled   bool      `json:"enabled" gorm:"default:true;not null"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

SAMLConfig is a SAML 2.0 identity-provider connection for an organization. The table is migrated in every build but is only read/written by the enterprise SAML handler (gated on the sso_saml entitlement); it stays empty in Community.

type SCIMToken

type SCIMToken struct {
	ID             uint       `json:"id" gorm:"primaryKey"`
	OrganizationID *uint      `json:"organization_id" gorm:"index"`
	Name           string     `json:"name" gorm:"not null"`
	TokenHash      string     `json:"-" gorm:"uniqueIndex;not null"`
	LastUsedAt     *time.Time `json:"last_used_at"`
	CreatedAt      time.Time  `json:"created_at"`
}

SCIMToken is a bearer credential an identity provider uses to call the SCIM 2.0 provisioning endpoint. Only the SHA-256 hash is stored; the plaintext is shown once at creation. The table is migrated in every build but only read/written by the enterprise SCIM handler (gated on the scim entitlement).

type SIEMConfig

type SIEMConfig struct {
	ID       uint   `json:"id" gorm:"primaryKey"`
	Name     string `json:"name" gorm:"not null"`
	Sink     string `json:"sink" gorm:"not null"`         // syslog | webhook | s3
	Endpoint string `json:"endpoint"`                     // syslog addr / webhook URL / s3 bucket+prefix
	Format   string `json:"format" gorm:"default:'json'"` // json | cef
	// AuthHeaderEnc is a webhook Authorization value, encrypted at rest. Never
	// serialized.
	AuthHeaderEnc string `json:"-" gorm:"column:auth_header"`
	Enabled       bool   `json:"enabled" gorm:"default:true;not null"`

	// LastShippedID is the durable cursor: the highest audit-log id confirmed
	// shipped to this sink. The streamer only advances it after a successful ship.
	LastShippedID uint       `json:"last_shipped_id"`
	LastError     string     `json:"last_error"`
	LastShippedAt *time.Time `json:"last_shipped_at"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

SIEMConfig is one external audit-streaming target (Enterprise; gated on the siem_stream entitlement). The streamer ships audit events at-least-once and records its progress in LastShippedID — a durable cursor that survives restarts and sink outages. The table is empty in Community.

type Secret

type Secret struct {
	UIDModel
	ID          uint `json:"id" gorm:"primaryKey"`
	WorkspaceID uint `json:"workspace_id" gorm:"index:idx_secret_workspace_name,unique;not null"`
	// Name is the unique slug handle scoped to the workspace; env refs use it.
	Name string `json:"name" gorm:"index:idx_secret_workspace_name,unique;not null"`
	// DisplayName is the free-text label shown in the UI; not unique.
	DisplayName string `json:"display_name"`
	ValueEnc    string `json:"-" gorm:"column:value_enc;type:text"` // encrypted
	Description string `json:"description"`
	// Version bumps on each value change (rotation), for display/rollback.
	Version     int   `json:"version" gorm:"not null;default:1"`
	UpdatedByID *uint `json:"updated_by_id,omitempty"`

	// Managed marks a secret auto-created and owned by a platform resource (e.g.
	// a managed database). Its value is derived (rotate via the owner, not by
	// hand) and its lifecycle follows the owner. OwnerKind/OwnerID identify it.
	Managed   bool   `json:"managed" gorm:"not null;default:false"`
	OwnerKind string `json:"owner_kind,omitempty" gorm:"index:idx_secret_owner"`
	OwnerID   uint   `json:"owner_id,omitempty" gorm:"index:idx_secret_owner"`

	// Metadata holds free-form labels; "miabi.io/" keys are platform-managed.
	Metadata  Metadata  `json:"metadata,omitempty" gorm:"serializer:json"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

Secret is a workspace-scoped named secret (the Vault). Env var values reference it by name (`${{ secrets.NAME }}`); the reference is resolved into a container's environment at deploy/job time and never returned by the API. The value is encrypted at rest.

type Server

type Server struct {
	UIDModel
	ID   uint   `json:"id" gorm:"primaryKey"`
	Name string `json:"name" gorm:"uniqueIndex;not null"`
	// Slug is a stable URL-safe identifier derived from the name, used in the
	// Goma HTTP-provider endpoint path (/api/v1/provider/{slug}).
	Slug         string             `json:"slug" gorm:"index"`
	Connectivity ServerConnectivity `json:"connectivity" gorm:"not null;default:port-forward"`
	// AccessMode is how the control plane reaches this node's Docker engine.
	// Existing rows backfill to "agent" (column default); the local node is set
	// to "socket" at bootstrap.
	AccessMode ServerAccessMode `json:"access_mode" gorm:"not null;default:agent"`
	// DockerEndpoint is the Docker host for non-agent modes: unix://… (socket) or
	// tcp://host:2376 (api). Unused for agent.
	DockerEndpoint string `json:"docker_endpoint"`
	IsLocal        bool   `json:"is_local" gorm:"not null;default:false"`
	// Role is the node's cluster role: "manager" for the control-plane node,
	// "node" for workers.
	Role       ServerRole   `json:"role" gorm:"not null;default:node"`
	Status     ServerStatus `json:"status" gorm:"not null;default:unknown"`
	LastSeenAt *time.Time   `json:"last_seen_at"`

	// api (TCP) TLS material — optional (plaintext when empty). PEM-encoded; the
	// private key is encrypted at rest and never serialized.
	TLSCACert string `json:"-" gorm:"type:text"`
	TLSCert   string `json:"-" gorm:"type:text"`
	TLSKeyEnc string `json:"-" gorm:"column:tls_key_enc;type:text"`
	// TLSEnabled is a transient flag for responses (whether TLS material is set).
	TLSEnabled bool `json:"tls_enabled" gorm:"-"`

	// Remote-node fields.
	// Address is the host/IP the reverse proxy uses to reach this node's
	// published app ports (e.g. "10.0.0.7").
	Address string `json:"address,omitempty"`
	// PublicIP / PublicHostname are the node's externally reachable address that
	// end users point DNS at (A/AAAA record → PublicIP, or CNAME → PublicHostname).
	// Distinct from Address (the private host the proxy dials): an edge-gateway
	// node terminates its own ingress here, while the control-plane (local) node's
	// value is the default DNS target for every port-forward app.
	PublicIP       string `json:"public_ip,omitempty"`
	PublicHostname string `json:"public_hostname,omitempty"`
	// TokenHash is the SHA-256 of the agent join token; the plaintext is shown
	// once at creation and never stored.
	TokenHash string `json:"-" gorm:"index"`
	// GatewayTokenEnc is the node's edge-gateway provider token, encrypted at
	// rest (recoverable, unlike the join token). The gateway authenticates its
	// HTTP-provider polls with it; minted lazily on first edge deploy so a
	// gateway can be (re)deployed on demand without the plaintext join token.
	GatewayTokenEnc string `json:"-"`
	// GatewayDeployedAt is when the node's gateway was last deployed (display).
	GatewayDeployedAt *time.Time `json:"gateway_deployed_at,omitempty"`
	// GatewayConfigYAML is the admin's custom goma.yml for this node's edge
	// gateway; empty = use the rendered default. The provider token is injected
	// as the INSTANCE_API_KEY env var at deploy, so this holds no secret.
	GatewayConfigYAML string `json:"-" gorm:"type:text"`
	// GatewayImage overrides the edge-gateway image/tag for this node; empty =
	// the resolved catalog/default image.
	GatewayImage string `json:"gateway_image,omitempty"`
	// GatewayContainer is the Docker container name of an *imported* (adopted)
	// gateway this node tracks instead of the managed mb-node-gateway. Empty = the
	// managed default. Set by the gateway import flow so status/logs resolve to
	// the adopted container.
	GatewayContainer string `json:"gateway_container,omitempty"`
	// GatewayImported marks that the node's gateway was adopted from a pre-existing
	// container rather than deployed by Miabi.
	GatewayImported bool `json:"gateway_imported" gorm:"not null;default:false"`
	// GatewayRedisPasswordEnc is the password (encrypted) for the per-node Redis
	// the edge gateway uses for shared cache + distributed rate limiting. Minted on
	// first gateway deploy of a remote edge node. Empty on the manager, which
	// reuses the platform Redis instead of running its own.
	GatewayRedisPasswordEnc string `json:"-"`
	// GatewayUpdate is the live state of an in-flight safe gateway update (test →
	// promote). Persisted as JSON so progress survives a reconnect and is visible
	// to every SSE subscriber; nil when no update is running.
	GatewayUpdate *GatewayUpdateProgress `json:"gateway_update,omitempty" gorm:"serializer:json"`
	// AgentConnected reflects a live agent tunnel (transient; set by the
	// connection manager).
	AgentConnected bool              `json:"agent_connected" gorm:"-"`
	AgentVersion   string            `json:"agent_version,omitempty"`
	Cordoned       bool              `json:"cordoned" gorm:"not null;default:false"`
	Labels         map[string]string `json:"labels,omitempty" gorm:"serializer:json"`

	// Cluster (Docker Swarm) fields. Clustering is opt-in and auto-detected;
	// these stay empty on plain Docker.
	//
	// SwarmNodeID is persisted: it correlates this Miabi node to its Docker Swarm
	// node, set when the node joins the swarm (and for the manager from its own
	// `docker info`). It is the stable key the Nodes page uses to look the node up
	// in `docker node ls`.
	SwarmNodeID string `json:"swarm_node_id,omitempty" gorm:"index"`
	// AutoJoined marks a node the CLUSTER brought in rather than an admin: the
	// global agent service landed on a swarm member, the agent registered itself,
	// and Miabi created this record. It distinguishes "I added this machine" from
	// "the swarm gave me this machine", which is the difference between a node an
	// operator chose to place workloads on and one that simply exists because it is
	// in the swarm.
	AutoJoined bool `json:"auto_joined" gorm:"not null;default:false"`
	// The rest are transient (not stored): populated from the manager's
	// `docker node ls` each time nodes are listed, so the Nodes page can show a
	// node's swarm role and availability.
	//
	// SwarmRole is the node's role in the swarm: leader | manager | worker, or
	// "standalone" when cluster mode is on but this node is not a swarm member.
	// Blank when cluster mode is off entirely.
	SwarmRole string `json:"swarm_role,omitempty" gorm:"-"`
	// SwarmAvailability is the scheduling availability: active | pause | drain.
	SwarmAvailability string `json:"swarm_availability,omitempty" gorm:"-"`
	// SwarmState is the swarm-reported reachability: ready | down | unknown |
	// disconnected.
	SwarmState string `json:"swarm_state,omitempty" gorm:"-"`
	// InSwarm reports whether this node is currently a member of the swarm.
	InSwarm bool `json:"in_swarm" gorm:"-"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

Server is a Docker host (node). The local node runs alongside the control plane; remote nodes run a `miabi agent` that dials in and exposes their Docker engine over a tunnel.

type ServerAccessMode

type ServerAccessMode string

ServerAccessMode is how the control plane reaches a node's Docker engine. Distinct from ServerConnectivity (how the proxy reaches apps).

const (
	// AccessSocket: a local/Unix (or custom) Docker host where the control plane
	// runs (DockerEndpoint, e.g. unix:///var/run/docker.sock). The control-plane
	// node uses this.
	AccessSocket ServerAccessMode = "socket"
	// AccessAgent: the node's agent dials in over a tunnel (NAT-friendly, no
	// inbound ports on the node). DockerEndpoint is unused.
	AccessAgent ServerAccessMode = "agent"
	// AccessAPI: the control plane dials the node's Docker API over TCP
	// (DockerEndpoint = tcp://host:2376), optionally with TLS/mTLS.
	AccessAPI ServerAccessMode = "api"
)

type ServerConnectivity

type ServerConnectivity string

ServerConnectivity is how the platform reaches apps running on a node.

const (
	// ConnectivityPortForward: the central proxy forwards to the node's published
	// host port (http://<address>:<hostPort>). For trusted/private networks.
	ConnectivityPortForward ServerConnectivity = "port-forward"
	// ConnectivityEdgeGateway: the node runs its own gateway (public ingress, its
	// own TLS) that pulls its routes from the control plane's HTTP provider.
	ConnectivityEdgeGateway ServerConnectivity = "edge-gateway"
)

type ServerRole

type ServerRole string

ServerRole is a node's role in the cluster.

const (
	// RoleManager is the control-plane node that runs Miabi itself (the
	// platform manager). There is exactly one, and it reaches Docker via its local
	// socket.
	RoleManager ServerRole = "manager"
	// RoleNode is a worker node that only runs workloads.
	RoleNode ServerRole = "node"
)

type ServerStatus

type ServerStatus string

ServerStatus is the reachability state of a node.

const (
	ServerStatusOnline  ServerStatus = "online"
	ServerStatusOffline ServerStatus = "offline"
	ServerStatusUnknown ServerStatus = "unknown"
)

type ServiceUpdateConfig

type ServiceUpdateConfig struct {
	Parallelism  int `json:"parallelism,omitempty"`
	DelaySeconds int `json:"delay_seconds,omitempty"`
}

ServiceUpdateConfig tunes how Swarm rolls out a service update for cluster apps (parallelism = tasks updated at once; delay = pause between batches).

type Session

type Session struct {
	ID        uint      `json:"id" gorm:"primaryKey"`
	UserID    uint      `json:"user_id" gorm:"index;not null"`
	JTI       string    `json:"jti" gorm:"uniqueIndex;not null;size:36"`
	IPAddress string    `json:"ip_address"`
	UserAgent string    `json:"user_agent" gorm:"size:512"`
	Revoked   bool      `json:"revoked" gorm:"default:false;not null"`
	CreatedAt time.Time `json:"created_at"`
	ExpiresAt time.Time `json:"expires_at"`
}

Session tracks an active JWT session for a user (revocable via its JTI).

func (*Session) IsActive

func (s *Session) IsActive() bool

IsActive reports whether the session is neither revoked nor expired.

type Setting

type Setting struct {
	ID        uint        `json:"id" gorm:"primaryKey"`
	Key       string      `json:"key" gorm:"uniqueIndex;not null"`
	Value     string      `json:"value" gorm:"type:text"`
	Type      SettingType `json:"type" gorm:"default:string;not null"`
	CreatedAt time.Time   `json:"created_at"`
	UpdatedAt time.Time   `json:"updated_at"`
}

Setting is a single global, platform-wide key/value configuration entry. The value is always stored as a string; Type records how to parse it.

type SettingType

type SettingType string

SettingType describes how a setting's string value should be interpreted.

const (
	SettingTypeString SettingType = "string"
	SettingTypeInt    SettingType = "int"
	SettingTypeBool   SettingType = "bool"
	SettingTypeJSON   SettingType = "json"
)

type Stack

type Stack struct {
	UIDModel
	ID          uint `json:"id" gorm:"primaryKey"`
	WorkspaceID uint `json:"workspace_id" gorm:"index:idx_stack_workspace_name,unique;not null"`
	// Name is the unique slug handle scoped to the workspace.
	Name string `json:"name" gorm:"index:idx_stack_workspace_name,unique;not null"`
	// DisplayName is the free-text label shown in the UI; not unique.
	DisplayName string `json:"display_name"`
	DockerName  string `json:"docker_name" gorm:"uniqueIndex;not null"`
	// DockerNetwork is the platform-managed Docker network shared by the stack's
	// apps, giving them service-name DNS discovery isolated from other stacks.
	DockerNetwork string `json:"docker_network,omitempty"`
	Description   string `json:"description,omitempty"`
	// Metadata holds free-form labels; "miabi.io/" keys are platform-managed.
	Metadata Metadata `json:"metadata,omitempty" gorm:"serializer:json"`
	// Annotations holds free-form, non-identifying descriptive metadata (the
	// manifest's metadata.annotations); no reserved keys. Persisted as JSON.
	Annotations Metadata  `json:"annotations,omitempty" gorm:"serializer:json"`
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`

	// Apps are the applications assigned to this stack. Populated on the stack
	// detail view; an application belongs to at most one stack.
	Apps []Application `json:"apps,omitempty" gorm:"foreignKey:StackID"`
}

Stack groups related applications in a workspace so they can be managed together. The user-facing Name is unique per workspace; DockerName is the platform-managed Docker Compose project name applied to member containers as the `com.docker.compose.project` label, so Docker tooling groups them too.

type StackEnvVar

type StackEnvVar struct {
	ID        uint      `json:"id" gorm:"primaryKey"`
	StackID   uint      `json:"stack_id" gorm:"index:idx_stack_env_key,unique;not null"`
	Key       string    `json:"key" gorm:"index:idx_stack_env_key,unique;not null"`
	Value     string    `json:"value"` // plaintext for non-secret; ciphertext when IsSecret
	IsSecret  bool      `json:"is_secret" gorm:"not null;default:false"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

StackEnvVar is a shared environment variable injected into every member application's containers at deploy time. App-level vars with the same key take precedence. Secret values are encrypted at rest.

type SystemRole

type SystemRole string

SystemRole is a platform-wide role (distinct from per-workspace roles).

const (
	SystemRoleAdmin SystemRole = "admin" // platform super-admin
	SystemRoleUser  SystemRole = "user"
)

type Template

type Template struct {
	ID       uint `json:"id" gorm:"primaryKey"`
	SourceID uint `json:"source_id" gorm:"index:idx_template_src_name_ver,unique;not null"`
	// Name is the stable template handle (e.g. "ghost").
	Name string `json:"name" gorm:"index:idx_template_src_name_ver,unique;not null"`
	// Version is the manifest semver; unique per (source, name).
	Version string `json:"version" gorm:"index:idx_template_src_name_ver,unique;not null"`
	// DisplayName is the free-text catalog label (e.g. "Ghost").
	DisplayName string         `json:"display_name"`
	Category    string         `json:"category,omitempty"`
	Icon        string         `json:"icon,omitempty"`
	Digest      string         `json:"digest,omitempty"`
	RawYAML     string         `json:"-" gorm:"type:text"`
	CreatedAt   time.Time      `json:"created_at"`
	UpdatedAt   time.Time      `json:"updated_at"`
	DeletedAt   gorm.DeletedAt `json:"-" gorm:"index"`
}

Template is one version of a catalog entry, synced from a source. RawYAML is the manifest as authored; the parsed form is re-derived on demand.

type TemplateInstall

type TemplateInstall struct {
	ID          uint   `json:"id" gorm:"primaryKey"`
	WorkspaceID uint   `json:"workspace_id" gorm:"index;not null"`
	Source      string `json:"source"` // official | community | custom
	// TemplateName is the template handle installed from (references Template.Name).
	TemplateName string `json:"template_name" gorm:"index;not null"`
	// TemplateDisplayName is the catalog label snapshot at install time.
	TemplateDisplayName string `json:"template_display_name"`
	Version             string `json:"version"`
	StackID             *uint  `json:"stack_id,omitempty" gorm:"index"`
	AppIDs              []uint `json:"app_ids,omitempty" gorm:"serializer:json"`
	DatabaseIDs         []uint `json:"database_ids,omitempty" gorm:"serializer:json"`
	VolumeIDs           []uint `json:"volume_ids,omitempty" gorm:"serializer:json"`
	// Inputs holds the non-secret install answers, for re-install/upgrade prompts.
	Inputs    map[string]string `json:"inputs,omitempty" gorm:"serializer:json"`
	CreatedAt time.Time         `json:"created_at"`
}

TemplateInstall records that a stack/app(s) were installed from a template, so the UI can show provenance ("installed from Ghost 1.2.0") and later offer upgrade/uninstall.

type TemplateSource

type TemplateSource struct {
	ID          uint               `json:"id" gorm:"primaryKey"`
	WorkspaceID *uint              `json:"workspace_id,omitempty" gorm:"index"`
	Name        string             `json:"name" gorm:"not null"`
	Type        TemplateSourceType `json:"type" gorm:"not null"`
	URL         string             `json:"url,omitempty"`
	Ref         string             `json:"ref,omitempty"`         // git branch/tag
	GitRepoID   *uint              `json:"git_repo_id,omitempty"` // optional stored credential
	Official    bool               `json:"official" gorm:"not null;default:false"`
	Verified    bool               `json:"verified" gorm:"not null;default:false"`
	SyncStatus  string             `json:"sync_status,omitempty"` // idle | syncing | error
	LastSynced  *time.Time         `json:"last_synced,omitempty"`
	LastError   string             `json:"last_error,omitempty"`
	CreatedAt   time.Time          `json:"created_at"`
	UpdatedAt   time.Time          `json:"updated_at"`
	DeletedAt   gorm.DeletedAt     `json:"-" gorm:"index"`
}

TemplateSource is where templates come from. Global sources (WorkspaceID nil) are admin-managed; a custom source is per-workspace.

type TemplateSourceType

type TemplateSourceType string

TemplateSourceType is where a set of templates comes from.

const (
	TemplateSourceBuiltin TemplateSourceType = "builtin" // embedded official catalog
	TemplateSourceGit     TemplateSourceType = "git"     // a Git repo with an index.yaml
	TemplateSourceHTTP    TemplateSourceType = "http"    // a remote index.yaml
	TemplateSourceCustom  TemplateSourceType = "custom"  // per-workspace user imports
)

type TwoFactorRecoveryCode

type TwoFactorRecoveryCode struct {
	ID        uint       `json:"id" gorm:"primaryKey"`
	UserID    uint       `json:"user_id" gorm:"index;not null"`
	CodeHash  string     `json:"-" gorm:"uniqueIndex;not null"`
	UsedAt    *time.Time `json:"used_at"`
	CreatedAt time.Time  `json:"created_at"`
}

TwoFactorRecoveryCode is a single-use backup code for two-factor authentication. Only the sha256 hash of the code is stored; the plaintext is shown to the user once at generation time.

type UIDModel

type UIDModel struct {
	UID string `json:"uid" gorm:"type:uuid;uniqueIndex;not null;default:gen_random_uuid()"`
}

UIDModel is an embeddable mixin giving a resource a stable, globally-unique, opaque identifier (UUIDv7) alongside its uint primary key. The PK stays the internal handle (foreign keys, Docker labels, performance); the uid is the portable, external one — the Terraform resource ID, the GitOps/backup cross-install reference, and the non-enumerable handle in public URLs.

Embed it anonymously so `obj.UID` is the string directly and JSON exposes "uid". New rows get a UUIDv7 from BeforeCreate (time-ordered); the gen_random_uuid() default backfills pre-existing rows when AutoMigrate adds the column and is a safety net if the hook ever doesn't run.

func (*UIDModel) BeforeCreate

func (m *UIDModel) BeforeCreate(*gorm.DB) error

BeforeCreate assigns a UUIDv7 when one was not supplied. A caller may preset UID (e.g. import/restore keeping an incoming identity); only an empty value is generated.

type UpdateStatus

type UpdateStatus struct {
	ID uint `json:"-" gorm:"primaryKey"`
	// LatestVersion is the newest release for the running build's channel, as a
	// semver tag with the leading "v" (e.g. "v1.0.0-beta.5"). Empty until the
	// first successful check.
	LatestVersion string     `json:"latest_version"`
	ReleaseURL    string     `json:"release_url"`
	PublishedAt   *time.Time `json:"published_at"`
	// ETag from the last successful GitHub response. Replayed as If-None-Match so
	// an unchanged release list answers 304, which costs no API quota.
	ETag string `json:"-"`
	// CheckedVersion is the running build that produced LatestVersion. The verdict
	// depends on BOTH the release list and the version we compare it against, but
	// the ETag only fingerprints the list — so after an upgrade the list is
	// unchanged, GitHub answers 304, and a verdict computed for the OLD build would
	// be kept forever. That is how an install could be told "v1.2.1 is available"
	// while running 1.3.0. Storing it lets Check notice the build moved and redo
	// the comparison.
	CheckedVersion string `json:"-"`
	// CheckedAt is the last *attempt*; LastError explains a failing one. Both are
	// admin-visible so a silently broken checker cannot masquerade as "up to date".
	CheckedAt *time.Time `json:"checked_at"`
	LastError string     `json:"last_error,omitempty"`
	// DismissedVersion is the version an admin chose to stop being notified about.
	// Platform-wide: only platform admins ever see the notice.
	DismissedVersion string    `json:"dismissed_version,omitempty"`
	UpdatedAt        time.Time `json:"-"`
}

UpdateStatus caches the result of the daily release check. Exactly one row (ID 1) ever exists: it is platform state, not a user-editable setting, so it deliberately lives outside the `settings` table — every row there is listed and editable on the admin Settings page.

type UpgradeProgress

type UpgradeProgress struct {
	FromVersion string `json:"from_version"`
	ToVersion   string `json:"to_version"`
	Path        string `json:"path"`  // "in-place" | "dump-restore"
	Phase       string `json:"phase"` // backing-up | stopping-apps | swapping | dumping | restoring | cutover | verifying | starting-apps | done | failed
	Error       string `json:"error,omitempty"`
}

UpgradeProgress is the live state of an in-flight version upgrade, surfaced on the instance while Status is "upgrading" (and briefly after, on failure).

type UpgradeStep

type UpgradeStep struct {
	ID        uint      `json:"id" gorm:"primaryKey"`
	Version   string    `json:"version" gorm:"index;not null"`
	Name      string    `json:"name" gorm:"uniqueIndex;not null"`
	AppliedAt time.Time `json:"applied_at"`
}

UpgradeStep records an applied, versioned data-upgrade step so it runs at most once. Schema changes use GORM AutoMigrate; data backfills use steps.

type User

type User struct {
	ID uint `json:"id" gorm:"primaryKey"`
	// Name is the free-text display name.
	Name string `json:"name" gorm:"not null;default:''"`
	// Username is the unique, directory-friendly handle (lowercase [a-z0-9-]),
	// the join key a future LDAP/OIDC uid maps onto and a potential per-user
	// registry namespace. Distinct from Email (the login/contact). Auto-derived
	// from the email local-part on create when left blank (see BeforeCreate).
	Username     string     `json:"username" gorm:"uniqueIndex;not null"`
	Email        string     `json:"email" gorm:"uniqueIndex;not null"`
	PasswordHash string     `json:"-" gorm:"not null"`
	Role         SystemRole `json:"role" gorm:"default:user;not null"`
	// TwoFactorSecret holds the TOTP secret, encrypted at rest (crypto package).
	// Never serialized. TwoFactorEnabled is only set once a code is confirmed.
	TwoFactorSecret    string `json:"-" gorm:"type:text"`
	TwoFactorEnabled   bool   `json:"two_factor_enabled" gorm:"default:false;not null"`
	Active             bool   `json:"active" gorm:"default:true;not null"`
	MustChangePassword bool   `json:"must_change_password" gorm:"not null;default:false"`
	// AuthSource records how the account authenticates: "local" (password), or an
	// external directory/IdP ("ldap", "saml", "oauth"). Directory-managed accounts
	// carry an unusable local password and have their access reconciled on login.
	AuthSource string `json:"auth_source" gorm:"not null;default:'local'"`
	// WorkspaceLimit is an Enterprise per-user override of how many workspaces
	// this user may own, superseding the platform-global max_workspaces_per_user.
	// nil = inherit the platform limit; -1 = unlimited; 0 = none; N = at most N.
	// Only a platform admin sets it, and only with the user_workspace_limit
	// entitlement (existing overrides stay enforced read-only if the license lapses).
	WorkspaceLimit *int `json:"workspace_limit,omitempty"`
	// WorkspaceMembershipLimit is the Enterprise per-user override of how many
	// workspaces this user may JOIN as a non-owner member (invites + SSO/SCIM
	// auto-join), superseding the platform-global max_workspace_memberships_per_user.
	// Same convention: nil = inherit; -1 = unlimited; 0 = none; N = at most N.
	// Gated by the user_workspace_membership_limit entitlement.
	WorkspaceMembershipLimit *int `json:"workspace_membership_limit,omitempty"`
	// ScheduledDeletionAt, when set, is the time this account is permanently
	// purged (with all its data). The admin schedules it; a daily job purges due
	// accounts; an admin can cancel before then. The account stays disabled
	// (Active=false) throughout the grace window.
	ScheduledDeletionAt *time.Time `json:"scheduled_deletion_at" gorm:"index"`
	EmailVerifiedAt     *time.Time `json:"email_verified_at"`
	LastLoginAt         *time.Time `json:"last_login_at"`
	// OnboardingDismissedAt, when set, is when the user dismissed (or completed)
	// the getting-started checklist. Nil = still show onboarding guidance.
	OnboardingDismissedAt *time.Time `json:"onboarding_dismissed_at"`
	CreatedAt             time.Time  `json:"created_at"`
	UpdatedAt             time.Time  `json:"updated_at"`
}

User is a global identity.

func (*User) BeforeCreate

func (u *User) BeforeCreate(tx *gorm.DB) error

BeforeCreate derives a unique username when one was not supplied, so every creation path (seed, admin-create, OAuth/SAML auto-provision, SCIM) gets a valid handle without repeating the logic. The base is the email local-part, slugified; collisions and reserved words are resolved by numeric suffixing. An explicitly supplied username is normalized but otherwise trusted (callers validate user-chosen handles up front).

func (*User) IsAdmin

func (u *User) IsAdmin() bool

IsAdmin reports whether the user is a platform super-admin.

type Volume

type Volume struct {
	UIDModel
	ID          uint `json:"id" gorm:"primaryKey"`
	WorkspaceID uint `json:"workspace_id" gorm:"index:idx_vol_workspace_name,unique;not null"`
	// Name is the unique, URL/CLI/docker handle (lowercase [a-z0-9-]) scoped to the
	// workspace. Renamed from the former "slug".
	Name string `json:"name" gorm:"index:idx_vol_workspace_name,unique;not null"`
	// DisplayName is the free-text label shown in the UI. Renamed from "name".
	DisplayName string `json:"display_name"`
	DockerName  string `json:"docker_name" gorm:"uniqueIndex;not null"`
	// ServerID is the node this volume lives on (0 = local control-plane node).
	ServerID uint `json:"server_id" gorm:"index;not null;default:0"`
	// ServerName is the display name of the node (transient; populated on read).
	ServerName string `json:"server_name,omitempty" gorm:"-"`
	Mountpoint string `json:"mountpoint,omitempty"`
	// SizeBytes is the declared capacity / size limit of the volume in bytes
	// (0 = unspecified/unlimited). Recorded at create time and used for quota
	// accounting; hard enforcement depends on the node's storage backend.
	SizeBytes int64 `json:"size_bytes" gorm:"not null;default:0"`
	// UsedBytes is the last MEASURED on-disk usage (docker system df), distinct
	// from the declared SizeBytes. 0 with nil UsedMeasuredAt = never measured.
	UsedBytes      int64      `json:"used_bytes" gorm:"not null;default:0"`
	UsedMeasuredAt *time.Time `json:"used_measured_at,omitempty"`
	// Imported marks a volume that references a pre-existing external Docker volume
	// by its own name (DockerName = the existing name; no data was moved/created).
	Imported bool `json:"imported" gorm:"not null;default:false"`

	// Shared storage (cluster mode). Driver is the Docker volume driver: "local"
	// (default, node-local) or "nfs"/"cifs" (a backend a replicated service can
	// share across nodes). AccessMode follows from it: local => rwo, shared => rwx.
	Driver string `json:"driver" gorm:"not null;default:local"`
	// AccessMode is rwo (node-local, single node) or rwx (shared, multi-node). The
	// guardrails use it to refuse replicating an app backed by an rwo volume.
	AccessMode VolumeAccessMode `json:"access_mode" gorm:"not null;default:rwo"`
	// DriverOptsEnc is the encrypted JSON of the driver's mount options (which may
	// include a CIFS password). Set at create time, never returned. The volume is
	// immutable, so options can't be edited after creation.
	DriverOptsEnc string `json:"-" gorm:"type:text"`
	// HostPath is the operator-managed host directory a "host" driver volume binds
	// (under /mnt/*). Empty for every other driver. Not a secret — it is the bind
	// source, mounted directly into the app's containers on each node.
	HostPath string `json:"host_path,omitempty"`
	// Metadata holds free-form labels; "miabi.io/" keys are platform-managed.
	Metadata Metadata `json:"metadata,omitempty" gorm:"serializer:json"`
	// Annotations holds free-form, non-identifying descriptive metadata (the
	// manifest's metadata.annotations); no reserved keys. Persisted as JSON.
	Annotations Metadata  `json:"annotations,omitempty" gorm:"serializer:json"`
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`
}

Volume is a workspace-owned, managed Docker volume for persistent data.

type VolumeAccessMode

type VolumeAccessMode string

VolumeAccessMode is how many nodes can mount a volume read-write at once.

const (
	// AccessRWO (ReadWriteOnce): a node-local volume usable by one node at a time
	// (the default "local" driver). A replicated service must NOT mount one, or
	// each node would silently get its own empty copy.
	AccessRWO VolumeAccessMode = "rwo"
	// AccessRWX (ReadWriteMany): a shared volume (NFS/CIFS/Ceph) every replica can
	// mount at once, so a stateful service can run with replicas > 1.
	AccessRWX VolumeAccessMode = "rwx"
)

type VolumeBackup

type VolumeBackup struct {
	ID          uint         `json:"id" gorm:"primaryKey"`
	WorkspaceID uint         `json:"workspace_id" gorm:"index;not null"`
	VolumeID    uint         `json:"volume_id" gorm:"index;not null"`
	ServerID    uint         `json:"server_id" gorm:"index;not null;default:0"` // node the volume lives on
	VolumeName  string       `json:"volume_name"`                               // docker volume name
	Status      BackupStatus `json:"status" gorm:"not null;default:pending"`
	Trigger     string       `json:"trigger"` // manual | scheduled

	S3Bucket  string `json:"s3_bucket,omitempty"`
	S3Path    string `json:"s3_path,omitempty"`  // remote folder prefix used
	Filename  string `json:"filename,omitempty"` // archive object name
	SizeBytes int64  `json:"size_bytes"`

	// Logs is a bounded tail of the backup output for instant display; the full
	// log lives in the log store at LogRef once the run is terminal (see
	// plans/log-storage.md). LogRef is empty when the store is disabled or the
	// row predates externalization — readers fall back to this tail.
	Logs         string `json:"logs,omitempty" gorm:"type:text"`
	LogRef       string `json:"log_ref,omitempty"`
	LogBytes     int64  `json:"log_bytes,omitempty"`
	LogLines     int    `json:"log_lines,omitempty"`
	LogTruncated bool   `json:"log_truncated,omitempty"`
	Error        string `json:"error,omitempty" gorm:"type:text"`

	StartedAt  *time.Time `json:"started_at"`
	FinishedAt *time.Time `json:"finished_at"`
	CreatedAt  time.Time  `json:"created_at"`
}

VolumeBackup is a single backup run of a managed volume's contents, archived (compressed) to the workspace's S3 target. Reuses BackupStatus.

type Webhook

type Webhook struct {
	ID          uint     `json:"id" gorm:"primaryKey"`
	WorkspaceID uint     `json:"workspace_id" gorm:"index;not null"`
	Name        string   `json:"name"`
	URL         string   `json:"url" gorm:"not null"`
	Secret      string   `json:"-" gorm:"not null"`
	Events      []string `json:"events" gorm:"serializer:json"`
	// Headers are extra HTTP headers sent with each delivery (e.g. an
	// Authorization header for the receiving service).
	Headers map[string]string `json:"headers,omitempty" gorm:"serializer:json"`
	Enabled bool              `json:"enabled" gorm:"not null;default:true"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`

	// HasSecret is a transient flag for responses (never persisted).
	HasSecret bool `json:"has_secret" gorm:"-"`
	// PlainSecret carries the generated secret in the create response only.
	PlainSecret string `json:"secret,omitempty" gorm:"-"`
}

Webhook is a workspace-registered HTTP endpoint that receives a signed JSON payload when a subscribed application event fires. The signing Secret is encrypted at rest and never serialized (a transient HasSecret flag is exposed instead); it is returned in cleartext exactly once, at creation time.

type WebhookDelivery

type WebhookDelivery struct {
	ID          uint   `json:"id" gorm:"primaryKey"`
	WebhookID   uint   `json:"webhook_id" gorm:"index;not null"`
	WorkspaceID uint   `json:"workspace_id" gorm:"index;not null"`
	Event       string `json:"event" gorm:"not null"`
	// EventID is the source AppEvent, enabling exact redelivery. 0 for tests.
	EventID        uint                  `json:"event_id,omitempty"`
	Status         WebhookDeliveryStatus `json:"status" gorm:"type:varchar(16);not null;index"`
	HTTPStatusCode int                   `json:"http_status_code"`
	ErrorMessage   string                `json:"error_message,omitempty"`
	Attempt        int                   `json:"attempt" gorm:"not null;default:1"`
	CreatedAt      time.Time             `json:"created_at"`
}

WebhookDelivery records the outcome of delivering one event to one webhook. One row is written per attempt; Attempt is the 1-based attempt number.

type WebhookDeliveryStatus

type WebhookDeliveryStatus string

WebhookDeliveryStatus is the terminal outcome of a delivery attempt chain.

const (
	WebhookDeliverySuccess WebhookDeliveryStatus = "success"
	WebhookDeliveryFailed  WebhookDeliveryStatus = "failed"
)

type Workspace

type Workspace struct {
	UIDModel
	ID uint `json:"id" gorm:"primaryKey"`
	// Name is the unique, URL/CLI/docker handle (lowercase [a-z0-9-]). It is the
	// human key for scoped routes and the registry namespace. Renamed from the
	// former "slug"; the numeric ID/UID remain the stable internal references.
	Name string `json:"name" gorm:"uniqueIndex;not null"`
	// DisplayName is the free-text label shown in the UI. Renamed from the former
	// "name"; not unique.
	DisplayName string `json:"display_name"`
	Description string `json:"description"`
	OwnerID     uint   `json:"owner_id" gorm:"index;not null"`
	// OrganizationID is the realm this workspace belongs to (nullable → the
	// default org). Lets SSO/enforced-login/SCIM scope to an org without a
	// destructive migration when multi-org lands.
	OrganizationID *uint `json:"organization_id" gorm:"index"`
	// Privileged grants trusted capabilities — currently, host port-binding
	// requests are auto-approved (range and conflict checks still apply). Only a
	// platform admin can set it.
	Privileged bool `json:"privileged" gorm:"not null;default:false"`
	// System marks the built-in platform workspace ("Miabi System"). It holds
	// platform-managed apps (e.g. the per-node Goma gateways), is created on
	// first boot, is always privileged, and cannot be deleted. Only platform
	// admins manage it.
	System bool `json:"system" gorm:"not null;default:false"`
	// PlanID is the assigned plan (nil → the default plan → unlimited). Drives
	// per-workspace resource quotas when plan enforcement is enabled.
	PlanID    *uint     `json:"plan_id,omitempty" gorm:"index"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`

	Members []WorkspaceMember `json:"-" gorm:"foreignKey:WorkspaceID"`
}

Workspace is the multi-tenant root that owns all resources.

type WorkspaceBackupSettings

type WorkspaceBackupSettings struct {
	ID          uint `json:"id" gorm:"primaryKey"`
	WorkspaceID uint `json:"workspace_id" gorm:"uniqueIndex;not null"`

	S3Enabled        bool   `json:"s3_enabled"`
	S3Endpoint       string `json:"s3_endpoint,omitempty"`
	S3Bucket         string `json:"s3_bucket,omitempty"`
	S3Region         string `json:"s3_region,omitempty"`
	S3AccessKey      string `json:"s3_access_key,omitempty"`
	S3SecretKeyEnc   string `json:"-" gorm:"column:s3_secret_key_enc"` // encrypted at rest
	S3UseSSL         bool   `json:"s3_use_ssl"`
	S3ForcePathStyle bool   `json:"s3_force_path_style"`

	// Path prefixes within the bucket (one shared bucket, two prefixes).
	DatabaseBackupPath string `json:"database_backup_path,omitempty"`
	VolumeBackupPath   string `json:"volume_backup_path,omitempty"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`

	// S3SecretSet reports whether a secret key is stored, without exposing it.
	// Not persisted; populated on read so the UI can render "••••• (set)".
	S3SecretSet bool `json:"s3_secret_set" gorm:"-"`
}

WorkspaceBackupSettings holds a workspace's shared backup target: one S3-compatible bucket + credentials, plus the path prefixes under which database and volume backups are stored. Both database and volume backups read this single config. The secret key is encrypted at rest and never returned.

type WorkspaceInvitation

type WorkspaceInvitation struct {
	ID          uint             `json:"id" gorm:"primaryKey"`
	WorkspaceID uint             `json:"workspace_id" gorm:"index;not null"`
	Email       string           `json:"email" gorm:"not null;index"`
	Role        WorkspaceRole    `json:"role" gorm:"not null;default:viewer"`
	TokenHash   string           `json:"-" gorm:"uniqueIndex;not null"`
	Status      InvitationStatus `json:"status" gorm:"not null;default:pending"`
	InvitedBy   uint             `json:"invited_by" gorm:"not null"`
	ExpiresAt   time.Time        `json:"expires_at" gorm:"not null"`
	CreatedAt   time.Time        `json:"created_at"`
}

WorkspaceInvitation is a pending invite for an email to join a workspace.

type WorkspaceKey

type WorkspaceKey struct {
	ID          uint `json:"id" gorm:"primaryKey"`
	WorkspaceID uint `json:"workspace_id" gorm:"index:idx_wskey_ws_ver,unique;not null"`
	Version     int  `json:"version" gorm:"index:idx_wskey_ws_ver,unique;not null"`
	// WrappedDEK is the DEK encrypted (AES-GCM) under the master KEK — i.e.
	// crypto.Encrypt(base64(dek)). Never returned by the API.
	WrappedDEK string `json:"-" gorm:"type:text;not null"`
	// Active marks the version new writes encrypt with. Older versions are kept so
	// existing ciphertext stays decryptable until a rotation sweep migrates it.
	Active bool `json:"active" gorm:"not null;default:true;index"`

	CreatedAt time.Time `json:"created_at"`
	RotatedAt time.Time `json:"rotated_at"`
}

WorkspaceKey is a workspace's data-encryption key (DEK), stored wrapped by the master KEK (MIABI_ENCRYPTION_KEY). The DEK encrypts the workspace's secrets at rest; the KEK only ever wraps the DEK. Keys are versioned: rotation creates a new active version and re-encrypts the workspace's data to it, then retires the old one. Exactly one version per workspace is Active (the one new writes use).

type WorkspaceMember

type WorkspaceMember struct {
	ID          uint          `json:"id" gorm:"primaryKey"`
	WorkspaceID uint          `json:"workspace_id" gorm:"uniqueIndex:idx_workspace_user;not null"`
	UserID      uint          `json:"user_id" gorm:"uniqueIndex:idx_workspace_user;not null"`
	Role        WorkspaceRole `json:"role" gorm:"not null;default:viewer"`
	// CustomRoleID, when set, overrides Role with an admin-defined permission set
	// (Enterprise). Role still holds the custom role's BaseRole for rank checks.
	CustomRoleID *uint     `json:"custom_role_id" gorm:"index"`
	CreatedAt    time.Time `json:"created_at"`
	UpdatedAt    time.Time `json:"updated_at"`

	User User `json:"user" gorm:"foreignKey:UserID"`
}

WorkspaceMember links a user to a workspace with a role.

type WorkspaceQuota

type WorkspaceQuota struct {
	WorkspaceID               uint    `json:"workspace_id" gorm:"primaryKey"`
	MaxApps                   *int    `json:"max_apps,omitempty"`
	MaxDatabaseInstances      *int    `json:"max_database_instances,omitempty"`
	MaxCronJobs               *int    `json:"max_cron_jobs,omitempty"`
	MaxVolumes                *int    `json:"max_volumes,omitempty"`
	MaxNetworks               *int    `json:"max_networks,omitempty"`
	MaxAPIKeys                *int    `json:"max_api_keys,omitempty"`
	MaxMembers                *int    `json:"max_members,omitempty"`
	MaxDatabasesPerInstance   *int    `json:"max_databases_per_instance,omitempty"`
	MaxCPUCores               *int    `json:"max_cpu_cores,omitempty"`
	MaxMemoryMB               *int    `json:"max_memory_mb,omitempty"`
	MaxDatabaseInstanceSizeMB *int    `json:"max_database_instance_size_mb,omitempty"`
	MaxStorageMB              *int    `json:"max_storage_mb,omitempty"`
	MaxRunners                *int    `json:"max_runners,omitempty"`
	MaxGPUs                   *int    `json:"max_gpus,omitempty"`
	AllowCustomTLS            *bool   `json:"allow_custom_tls,omitempty"`
	AllowPrivilegedHostMounts *bool   `json:"allow_privileged_host_mounts,omitempty"`
	AllowShellExec            *bool   `json:"allow_shell_exec,omitempty"`
	AllowSharedStorage        *bool   `json:"allow_shared_storage,omitempty"`
	AllowDNSProviders         *bool   `json:"allow_dns_providers,omitempty"`
	AllowCustomLabels         *bool   `json:"allow_custom_labels,omitempty"`
	AllowPlatformRunners      *bool   `json:"allow_platform_runners,omitempty"`
	AllowCustomBuilder        *bool   `json:"allow_custom_builder,omitempty"`
	AllowGPU                  *bool   `json:"allow_gpu,omitempty"`
	SecurityProfile           *string `json:"security_profile,omitempty"`          // nil = inherit plan
	AllowOfficialImageUser    *bool   `json:"allow_official_image_user,omitempty"` // nil = inherit plan

	UpdatedAt time.Time `json:"updated_at"`
}

WorkspaceQuota holds per-workspace overrides applied on top of the assigned plan. Any non-nil field overrides the plan for that workspace; nil inherits.

type WorkspaceRole

type WorkspaceRole string

WorkspaceRole is a member's role within a workspace.

const (
	WorkspaceRoleOwner     WorkspaceRole = "owner"
	WorkspaceRoleAdmin     WorkspaceRole = "admin"
	WorkspaceRoleDeveloper WorkspaceRole = "developer"
	WorkspaceRoleViewer    WorkspaceRole = "viewer"
)

func (WorkspaceRole) AtLeast

func (r WorkspaceRole) AtLeast(min WorkspaceRole) bool

AtLeast reports whether r is at least as privileged as min.

func (WorkspaceRole) Rank

func (r WorkspaceRole) Rank() int

Rank returns the privilege level of a role (0 if unknown).

func (WorkspaceRole) Valid

func (r WorkspaceRole) Valid() bool

Valid reports whether r is a known role.

type WorkspaceWithRole

type WorkspaceWithRole struct {
	Workspace
	Role WorkspaceRole `json:"role"`
}

WorkspaceWithRole is a workspace annotated with the requesting user's role in it. Returned by the list endpoint so the UI can render role badges and gate admin-only affordances without an extra round-trip per workspace.

Jump to

Keyboard shortcuts

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