Documentation
¶
Overview ¶
Package store provides a pure-Go SQLite-backed persistence layer.
It uses modernc.org/sqlite which is a CGO-free SQLite implementation, so the whole application can be cross-compiled to a single static binary for Windows/Linux/macOS without a C toolchain.
Index ¶
- Constants
- Variables
- func NormalizeRegistryHost(host string) string
- func ValidSection(key string) bool
- type APIToken
- type APITokenWithUser
- type AlertDelivery
- type AlertEvent
- type AlertQuery
- type AlertRule
- type AlertState
- type AuditEntry
- type AuthFactor
- type ComposeFragment
- type Grant
- type Host
- type LDAPConfig
- type LDAPGroupMapping
- type MCPTokenPolicy
- type OAuthClient
- type OAuthCode
- type OAuthRefreshToken
- type ParseRule
- type Project
- type ProjectTemplate
- type Registry
- type RegistryAuth
- type Role
- type RoleSection
- type SMTPConfig
- type ServiceBlock
- type Session
- type Store
- func (s *Store) APITokenByHash(ctx context.Context, hash string) (*APIToken, error)
- func (s *Store) AckAlertEvent(ctx context.Context, id int64, by string) error
- func (s *Store) AckMatchingAlertEvents(ctx context.Context, q AlertQuery, by string) (int64, error)
- func (s *Store) AdminRevokeAPIToken(ctx context.Context, id int64) (bool, error)
- func (s *Store) AlertDeliveriesFor(ctx context.Context, eventIDs []int64) (map[int64][]AlertDelivery, error)
- func (s *Store) AlertEventHost(ctx context.Context, id int64) (int64, error)
- func (s *Store) Audit(ctx context.Context, e AuditEntry) error
- func (s *Store) AuthByID(ctx context.Context, id int64) (*RegistryAuth, error)
- func (s *Store) AuthForHost(ctx context.Context, host string) (*RegistryAuth, error)
- func (s *Store) BackupTo(ctx context.Context, path string) error
- func (s *Store) BumpSessionEpoch(ctx context.Context, userID int64) error
- func (s *Store) BurnFactorCounter(ctx context.Context, id, counter int64) error
- func (s *Store) CanReachHost(ctx context.Context, u *User, hostID int64) (bool, error)
- func (s *Store) Close() error
- func (s *Store) ComposeFragmentByID(ctx context.Context, id int64) (*ComposeFragment, error)
- func (s *Store) ComposeFragmentBySlug(ctx context.Context, slug string) (*ComposeFragment, error)
- func (s *Store) ConsumeOAuthCode(ctx context.Context, codeHash string) (*OAuthCode, error)
- func (s *Store) ConsumeRefreshToken(ctx context.Context, tokenHash string) (*OAuthRefreshToken, error)
- func (s *Store) CountAdmins(ctx context.Context) (int, error)
- func (s *Store) CountFactors(ctx context.Context, userID int64) (int, error)
- func (s *Store) CountUnacknowledged(ctx context.Context) (int, error)
- func (s *Store) CountUsers(ctx context.Context) (int, error)
- func (s *Store) CreateAPIToken(ctx context.Context, t *APIToken) (int64, error)
- func (s *Store) CreateAlertRule(ctx context.Context, r *AlertRule) (int64, error)
- func (s *Store) CreateComposeFragment(ctx context.Context, f *ComposeFragment) (int64, error)
- func (s *Store) CreateFactor(ctx context.Context, f *AuthFactor, authorised bool) (int64, error)
- func (s *Store) CreateFirstUser(ctx context.Context, u *User) (int64, error)
- func (s *Store) CreateHost(ctx context.Context, h *Host) (int64, error)
- func (s *Store) CreateOAuthClient(ctx context.Context, c *OAuthClient) error
- func (s *Store) CreateOAuthCode(ctx context.Context, codeHash string, c *OAuthCode) error
- func (s *Store) CreateParseRule(ctx context.Context, name, pattern string) (int64, error)
- func (s *Store) CreateProject(ctx context.Context, p *Project) (int64, error)
- func (s *Store) CreateProjectTemplate(ctx context.Context, t *ProjectTemplate) (int64, error)
- func (s *Store) CreateRefreshToken(ctx context.Context, tokenHash string, t *OAuthRefreshToken) error
- func (s *Store) CreateRegistry(ctx context.Context, name, address, username, secret string) (int64, error)
- func (s *Store) CreateRole(ctx context.Context, r *Role) (int64, error)
- func (s *Store) CreateServiceBlock(ctx context.Context, b *ServiceBlock) (int64, error)
- func (s *Store) CreateSession(ctx context.Context, sess *Session) error
- func (s *Store) CreateUser(ctx context.Context, u *User) (int64, error)
- func (s *Store) CreateWebhook(ctx context.Context, w *Webhook) (int64, error)
- func (s *Store) DeleteAlertRule(ctx context.Context, id int64) error
- func (s *Store) DeleteAlertState(ctx context.Context, hostID int64, containerID, metric string) error
- func (s *Store) DeleteComposeFragment(ctx context.Context, id int64) error
- func (s *Store) DeleteExpiredOAuth(ctx context.Context) error
- func (s *Store) DeleteFactor(ctx context.Context, id, userID int64) error
- func (s *Store) DeleteHost(ctx context.Context, id int64) error
- func (s *Store) DeleteOAuthClient(ctx context.Context, id string) (bool, error)
- func (s *Store) DeleteParseRule(ctx context.Context, id int64) error
- func (s *Store) DeleteProject(ctx context.Context, id int64) error
- func (s *Store) DeleteProjectTemplate(ctx context.Context, id int64) error
- func (s *Store) DeleteRegistry(ctx context.Context, id int64) error
- func (s *Store) DeleteRole(ctx context.Context, id int64) error
- func (s *Store) DeleteServiceBlock(ctx context.Context, id int64) error
- func (s *Store) DeleteSession(ctx context.Context, id string, userID int64) error
- func (s *Store) DeleteUser(ctx context.Context, id int64) error
- func (s *Store) DeleteUserFactors(ctx context.Context, userID int64) error
- func (s *Store) DeleteUserSessions(ctx context.Context, userID int64) error
- func (s *Store) DeleteWebhook(ctx context.Context, id int64) error
- func (s *Store) DisabledSections(ctx context.Context) ([]string, error)
- func (s *Store) EffectiveGrants(ctx context.Context, u *User) (map[string]Grant, error)
- func (s *Store) EffectiveSections(ctx context.Context, u *User) ([]string, error)
- func (s *Store) EncryptPlaintextHostKeys(ctx context.Context) error
- func (s *Store) EnsureLocalHost(ctx context.Context) error
- func (s *Store) ExistingRoleIDs(ctx context.Context, ids []int64) ([]int64, error)
- func (s *Store) FactorByCredentialID(ctx context.Context, credentialID string) (*AuthFactor, error)
- func (s *Store) FactorByID(ctx context.Context, id, userID int64) (*AuthFactor, error)
- func (s *Store) GetLDAP(ctx context.Context) (LDAPConfig, error)
- func (s *Store) GetSMTP(ctx context.Context) (SMTPConfig, error)
- func (s *Store) HostByID(ctx context.Context, id int64) (*Host, error)
- func (s *Store) InsertAlertEvent(ctx context.Context, e *AlertEvent) (int64, error)
- func (s *Store) ListAPITokens(ctx context.Context, userID int64) ([]APIToken, error)
- func (s *Store) ListAlertEvents(ctx context.Context, q AlertQuery) ([]AlertEvent, int, error)
- func (s *Store) ListAlertRules(ctx context.Context) ([]AlertRule, error)
- func (s *Store) ListAlertStates(ctx context.Context) ([]AlertState, error)
- func (s *Store) ListAllAPITokens(ctx context.Context) ([]APITokenWithUser, error)
- func (s *Store) ListComposeFragments(ctx context.Context) ([]ComposeFragment, error)
- func (s *Store) ListFactors(ctx context.Context, userID int64) ([]AuthFactor, error)
- func (s *Store) ListHosts(ctx context.Context) ([]Host, error)
- func (s *Store) ListOAuthClients(ctx context.Context) ([]OAuthClient, error)
- func (s *Store) ListParseRules(ctx context.Context) ([]ParseRule, error)
- func (s *Store) ListProjectTemplates(ctx context.Context) ([]ProjectTemplate, error)
- func (s *Store) ListProjects(ctx context.Context) ([]Project, error)
- func (s *Store) ListRegistries(ctx context.Context) ([]Registry, error)
- func (s *Store) ListRoles(ctx context.Context) ([]Role, error)
- func (s *Store) ListServiceBlocks(ctx context.Context) ([]ServiceBlock, error)
- func (s *Store) ListSessions(ctx context.Context, userID int64) ([]Session, error)
- func (s *Store) ListUsers(ctx context.Context) ([]User, error)
- func (s *Store) ListWebhooks(ctx context.Context) ([]Webhook, error)
- func (s *Store) LocalhostNo2FA(ctx context.Context) (bool, error)
- func (s *Store) MCPTokenPolicy(ctx context.Context) (MCPTokenPolicy, error)
- func (s *Store) NormalizeHostID(ctx context.Context, id int64) int64
- func (s *Store) OAuthClientByID(ctx context.Context, id string) (*OAuthClient, error)
- func (s *Store) PairPendingFactor(ctx context.Context, userID int64, pending, name string) (int64, error)
- func (s *Store) Ping(ctx context.Context) error
- func (s *Store) ProjectByID(ctx context.Context, id int64) (*Project, error)
- func (s *Store) ProjectTemplateByID(ctx context.Context, id int64) (*ProjectTemplate, error)
- func (s *Store) ProjectTemplateBySlug(ctx context.Context, slug string) (*ProjectTemplate, error)
- func (s *Store) PromoteTOTPPending(ctx context.Context, userID int64) error
- func (s *Store) PurgeExpiredSessions(ctx context.Context) error
- func (s *Store) ReachableHosts(ctx context.Context, u *User) (hosts map[int64]bool, all bool, err error)
- func (s *Store) RecentAudit(ctx context.Context, limit int, before int64) ([]AuditEntry, error)
- func (s *Store) RecordAlertDelivery(ctx context.Context, d *AlertDelivery) error
- func (s *Store) RevokeAPIToken(ctx context.Context, id, userID int64) (bool, error)
- func (s *Store) RoleByID(ctx context.Context, id int64) (*Role, error)
- func (s *Store) RoleIDsForUser(ctx context.Context, userID int64) ([]int64, error)
- func (s *Store) RolesForUser(ctx context.Context, userID int64) ([]Role, error)
- func (s *Store) ServiceBlockByID(ctx context.Context, id int64) (*ServiceBlock, error)
- func (s *Store) ServiceBlockBySlug(ctx context.Context, slug string) (*ServiceBlock, error)
- func (s *Store) SessionEpoch(ctx context.Context, userID int64) (int64, error)
- func (s *Store) SessionExists(ctx context.Context, id string, userID int64) (bool, error)
- func (s *Store) SetAlertRuleEnabled(ctx context.Context, id int64, enabled bool) error
- func (s *Store) SetAuthSource(ctx context.Context, userID int64, source string) error
- func (s *Store) SetCipher(c *crypto.Cipher)
- func (s *Store) SetDisabledSections(ctx context.Context, keys []string) error
- func (s *Store) SetHostAlertEmail(ctx context.Context, id int64, email string) error
- func (s *Store) SetHostDisabled(ctx context.Context, id int64, disabled bool) error
- func (s *Store) SetHostKey(ctx context.Context, id int64, key string) error
- func (s *Store) SetLDAP(ctx context.Context, c LDAPConfig) error
- func (s *Store) SetLastDeployedProfiles(ctx context.Context, id int64, profiles []string) error
- func (s *Store) SetLocalhostNo2FA(ctx context.Context, on bool) error
- func (s *Store) SetMCPTokenPolicy(ctx context.Context, p MCPTokenPolicy) error
- func (s *Store) SetPasswordless(ctx context.Context, userID int64, on bool) error
- func (s *Store) SetSMTP(ctx context.Context, c SMTPConfig) error
- func (s *Store) SetSetting(ctx context.Context, key, value string) error
- func (s *Store) SetTOTPLastCounter(ctx context.Context, userID, counter int64) error
- func (s *Store) SetTOTPPending(ctx context.Context, userID int64, secret string, stepUp bool) error
- func (s *Store) SetUserEmail(ctx context.Context, id int64, email string) error
- func (s *Store) SetUserPrefs(ctx context.Context, userID int64, prefs string) error
- func (s *Store) SetUserRoles(ctx context.Context, userID int64, roleIDs []int64) error
- func (s *Store) Setting(ctx context.Context, key string) (string, error)
- func (s *Store) TouchAPIToken(ctx context.Context, id int64) error
- func (s *Store) TouchLogin(ctx context.Context, userID int64) error
- func (s *Store) TouchProject(ctx context.Context, id int64) error
- func (s *Store) TouchSession(ctx context.Context, id string) error
- func (s *Store) UpdateAlertRule(ctx context.Context, id int64, r *AlertRule) error
- func (s *Store) UpdateComposeFragment(ctx context.Context, f *ComposeFragment) error
- func (s *Store) UpdateCredential(ctx context.Context, id int64, credential string) error
- func (s *Store) UpdatePassword(ctx context.Context, userID int64, hash string) error
- func (s *Store) UpdateProjectSettings(ctx context.Context, id int64, name string, hostID int64, ...) error
- func (s *Store) UpdateProjectTemplate(ctx context.Context, id int64, name, description string) error
- func (s *Store) UpdateRole(ctx context.Context, id int64, name, description string, ...) error
- func (s *Store) UpdateServiceBlock(ctx context.Context, b *ServiceBlock) error
- func (s *Store) UpdateUserAccess(ctx context.Context, id int64, role string, readOnly bool, sections []string) error
- func (s *Store) UpsertAlertState(ctx context.Context, a *AlertState) error
- func (s *Store) UserByID(ctx context.Context, id int64) (*User, error)
- func (s *Store) UserByUsername(ctx context.Context, username string) (*User, error)
- func (s *Store) UserByWebAuthnHandle(ctx context.Context, handle []byte) (*User, error)
- func (s *Store) UserPrefs(ctx context.Context, userID int64) (string, error)
- func (s *Store) WebAuthnHandle(ctx context.Context, userID int64) ([]byte, error)
- func (s *Store) WebhookByID(ctx context.Context, id int64) (*Webhook, error)
- type TokenLifetimeError
- type User
- type Webhook
Constants ¶
const ( KindFiring = "firing" KindEscalated = "escalated" KindEased = "eased" KindRepeat = "repeat" KindResolved = "resolved" )
AlertKind values. Anything level-triggered moves between these; anything edge-triggered stays at KindFiring.
const ( // FactorKindTOTP is an authenticator app. FactorKindTOTP = "totp" // FactorKindPasskey is a WebAuthn credential. FactorKindPasskey = "webauthn" )
const ( // BuiltinViewer grants every section, read-only. BuiltinViewer = "Viewer" // BuiltinOperator grants day-to-day operation but not the sections that // confer authority over the installation itself. BuiltinOperator = "Operator" )
Roles are assignable bundles of section grants, so an admin doesn't tick 13 checkboxes per user. A role grants a set of sections, each either read-only or writable; a user's effective access is the union of their roles and their own per-user section list, capped by their read-only flag and by the app-wide disabled sections.
The two built-in roles are read-only in the UI (Duplicate to customise), the same model Templates uses for built-in presets. The `admin` role remains a string on the user rather than a row here: it is the lockout safety valve, and making it data would invite a migration that locks the operator out.
Variables ¶
var ErrBuiltinRole = errors.New("store: built-in roles cannot be modified")
ErrBuiltinRole is returned when a built-in role is edited or deleted. They are the known-good baseline; the UI offers Duplicate to customise instead.
var ErrCounterNotAdvanced = errors.New("store: that code's time step was already used")
ErrCounterNotAdvanced means the time step was already spent.
var ErrDuplicate = errors.New("store: duplicate")
ErrDuplicate is returned when an insert violates a UNIQUE constraint (e.g. a project slug that already exists).
var ErrLastFactor = errors.New("store: that is the only second factor on this account")
ErrLastFactor is returned when a delete would leave the account with no second factor at all.
var ErrNotFirstFactor = errors.New("store: this account already has a second factor")
ErrNotFirstFactor means an unauthorised pairing arrived at an account that already has a factor. Adding the first needs no password; adding another does.
var ErrNotFound = errors.New("store: not found")
ErrNotFound is returned when a lookup yields no row.
var ErrRoleInUseAsFallback = errors.New("store: role is the configured LDAP fallback")
ErrRoleInUseAsFallback is returned when deleting the role configured as the LDAP fallback. Allowing it would leave the fallback itself dangling — the one thing the fallback exists to prevent.
var ErrSetupTaken = errors.New("store: setup already completed")
ErrSetupTaken means the first account already existed by the time this insert ran — two setup requests raced and this one lost.
var ErrTokenNeverForbidden = errors.New("never-expiring MCP tokens are not allowed by the administrator")
ErrTokenNeverForbidden is returned when a never-expiring token is asked for and the policy does not allow one.
var Sections = []string{
"dashboard", "containers", "projects", "images", "volumes", "networks", "topology",
"logs", "events", "alerts", "hosts", "registries", "audit",
}
Sections are the access-control units, matching the app's menu. A user's permissions and the global feature flags are both expressed as sets of these.
Functions ¶
func NormalizeRegistryHost ¶
NormalizeRegistryHost maps the various Docker Hub aliases to a single key so a stored "docker.io" credential matches refs like "nginx" or "user/app".
func ValidSection ¶
ValidSection reports whether key is a known section.
Types ¶
type APIToken ¶ added in v1.4.0
type APIToken struct {
ID int64
UserID int64
TokenHash string
Name string
Sections []string // empty = inherit all of the user's sections
HostIDs []int64 // empty = inherit all of the user's hosts
ReadOnly bool
CreatedAt time.Time
LastUsedAt time.Time
ExpiresAt time.Time // zero = never expires
Revoked bool
}
APIToken is a long-lived bearer credential for programmatic (MCP) access. The plaintext secret is never stored — only TokenHash (a SHA-256 hex digest). A token can only narrow its owner's rights, never widen them:
- Sections, when non-empty, restricts the token to a subset of the user's granted sections (the dispatcher still intersects with the live user grants, so revoking a section in the admin UI also shrinks the token).
- HostIDs, when non-empty, restricts the token to a subset of Docker hosts. The local daemon (0) is always reachable, matching the RBAC rule.
- ReadOnly, when true, forces read-only even if the user is read-write.
type APITokenWithUser ¶ added in v1.4.0
APITokenWithUser is an APIToken plus its owner's username, for the admin overview where tokens from every account are listed together.
type AlertDelivery ¶ added in v1.6.0
type AlertDelivery struct {
ID int64 `json:"id"`
EventID int64 `json:"eventId"`
Channel string `json:"channel"` // webhook | email
Target string `json:"target"` // webhook name + host, or recipients
OK bool `json:"ok"`
Status int `json:"status,omitempty"` // HTTP status, webhooks only
Detail string `json:"detail,omitempty"` // response excerpt or the error
Attempted time.Time `json:"attemptedAt"`
}
AlertDelivery is one attempt to get an alert out of the building.
type AlertEvent ¶
type AlertEvent struct {
ID int64 `json:"id"`
RuleID int64 `json:"ruleId"`
RuleName string `json:"ruleName"`
Type string `json:"type"`
Severity string `json:"severity"`
HostID int64 `json:"hostId"`
HostName string `json:"hostName"`
ContainerID string `json:"containerId"`
ContainerName string `json:"containerName"`
Message string `json:"message"`
Value *float64 `json:"value"`
Acknowledged bool `json:"acknowledged"`
CreatedAt time.Time `json:"createdAt"`
// Kind is the point in a condition's life this event marks:
//
// firing the condition started
// escalated it is still on, at a higher severity than before
// eased it is still on, at a lower severity
// repeat it is still on and the re-notify interval elapsed
// resolved it stopped; DurationSec says how long it lasted
//
// Edge-triggered rules (state, log, restart) only ever emit "firing" —
// a container that died or a log line that matched has no later moment at
// which it stops being true.
Kind string `json:"kind"`
// DurationSec is how long the condition held, set on resolved events.
DurationSec int `json:"durationSec"`
// AcknowledgedBy names the user who acknowledged it — "someone dealt with
// this" is only useful if you can ask them about it.
AcknowledgedBy string `json:"acknowledgedBy,omitempty"`
AcknowledgedAt *time.Time `json:"acknowledgedAt,omitempty"`
// Deliveries is filled in on request, not on every list.
Deliveries []AlertDelivery `json:"deliveries,omitempty"`
}
AlertEvent is a fired alert recorded for the in-app feed.
type AlertQuery ¶ added in v1.6.0
type AlertQuery struct {
Severity string
// Severities matches any of several, for callers that mean "the ones that
// indicate something is wrong" rather than one specific level.
Severities []string
Kind string
HostID *int64
Container string // substring
Rule string // substring
Text string // substring of the message
Unacked bool
// HostIDs restricts the query to these hosts; nil means no restriction.
// Empty-but-non-nil means nothing is visible, which must return no rows
// rather than all of them — the difference is the whole point of the type.
HostIDs []int64
Limit int
Offset int
// Sort names a column to order by; Desc reverses it. The value is mapped
// through a fixed whitelist before it reaches SQL — an ORDER BY cannot be a
// bound parameter, so anything else would be string-building a query out of
// a query-string value.
Sort string
Desc bool
}
AlertQuery filters and pages the event feed. Zero values mean "no filter".
type AlertRule ¶
type AlertRule struct {
ID int64 `json:"id"`
Name string `json:"name"`
Enabled bool `json:"enabled"`
Type string `json:"type"` // state | resource | log | restart
Target string `json:"target"` // container name substring; ” or '*' = all
Config string `json:"config"` // raw JSON, interpreted by the engine
Severity string `json:"severity"` // info | warning | critical
WebhookID *int64 `json:"webhookId"`
Email bool `json:"email"` // also send this rule by e-mail
// Emails are this rule's own recipients. Empty falls back to the instance-wide
// SMTP "To" (and the per-host override), so rules written before per-rule
// recipients existed keep delivering exactly as they did.
Emails []string `json:"emails"`
CooldownSec int `json:"cooldownSec"`
CreatedAt time.Time `json:"createdAt"`
}
AlertRule defines when an alert fires and where it goes.
type AlertState ¶ added in v1.6.0
type AlertState struct {
HostID int64
HostName string
ContainerID string
ContainerName string
Metric string
RuleID int64
RuleName string
Severity string
LastValue *float64
StartedAt time.Time
NotifiedAt time.Time
}
AlertState is a condition currently held to be true — one row per (host, container, metric), regardless of how many rules noticed it.
type AuditEntry ¶
type AuditEntry struct {
ID int64 `json:"id"`
UserID int64 `json:"userId"`
Username string `json:"username"`
Action string `json:"action"`
Target string `json:"target"`
Detail string `json:"detail"`
IP string `json:"ip"`
// HostID is the Docker host the action targeted, 0 for the local daemon or
// for actions with no host dimension. Recorded because a scoped action is only
// meaningful with the "where" alongside the "what".
HostID int64 `json:"hostId"`
CreatedAt time.Time `json:"createdAt"`
}
AuditEntry is a single recorded security-relevant action.
type AuthFactor ¶ added in v1.6.0
type AuthFactor struct {
ID int64
UserID int64
Kind string
Name string
Secret string
LastCounter int64
CreatedAt time.Time
LastUsedAt time.Time
// CredentialID identifies a passkey; empty for an authenticator app.
CredentialID string
// Credential is the WebAuthn library's own JSON for the key: the public key,
// the signature counter, and the flags that say what the authenticator proved.
// Opaque here on purpose — this package should not have opinions about a format
// the library owns.
Credential string
}
AuthFactor is one paired second factor: an authenticator app today, a passkey once that lands. The kind is stored rather than implied so both can live in one list — an account's factors are a single set, and splitting them into parallel tables is how "how many factors does this account have?" ends up with two answers.
type ComposeFragment ¶ added in v1.4.0
type ComposeFragment struct {
ID int64 `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
Description string `json:"description"`
Content string `json:"content"`
CreatedBy string `json:"createdBy"`
CreatedAt time.Time `json:"createdAt"`
}
ComposeFragment is a user-saved "shared definition": a top-level compose fragment (a YAML anchor) merged into builds above services:.
type Grant ¶ added in v1.6.0
type Grant struct {
Granted bool
Write bool
// AllHosts is set when at least one grant of this section came from an
// unscoped source (an unscoped role, or the account's own section list).
// Then Hosts is irrelevant.
AllHosts bool
// Hosts are the additional in-scope host ids when AllHosts is false. The
// local daemon (0) is always in scope and is not listed.
Hosts map[int64]bool
}
Grant is the effective access to one section: whether it's granted at all, whether writes are permitted, and on which hosts.
type Host ¶
type Host struct {
ID int64
Name string
Kind string
Address string
TLSCA string
TLSCert string
TLSKey string
HostKey string // pinned SSH host public key (authorized_keys line); ssh hosts only
AlertEmail string // per-host alert recipient override (falls back to global SMTP To)
Disabled bool // when true the monitor ignores this host (no events/stats)
CreatedAt time.Time
}
Host describes a Docker engine endpoint the app can connect to.
Kind is one of:
- "local": the local daemon (unix socket / windows named pipe)
- "tcp": a remote daemon over TCP, optionally TLS-secured
- "ssh": a remote daemon reached through an SSH tunnel
type LDAPConfig ¶
type LDAPConfig struct {
Enabled bool `json:"enabled"`
URL string `json:"url"` // ldap://host:389 or ldaps://host:636
StartTLS bool `json:"startTls"` // upgrade a plain connection to TLS
BindDN string `json:"bindDn"` // service account used to search for users
BindPassword string `json:"bindPassword"`
UserBaseDN string `json:"userBaseDn"`
UserFilter string `json:"userFilter"` // e.g. (uid=%s) or (sAMAccountName=%s)
AdminGroupDN string `json:"adminGroupDn"` // optional: members are provisioned as admins
// GroupMappings grant RBAC sections by LDAP group membership. When any are
// set, LDAP is authoritative for a user's sections (re-synced on each login).
GroupMappings []LDAPGroupMapping `json:"groupMappings"`
// FallbackRoleID is granted in place of a mapped role that no longer exists,
// so deleting a role degrades its members to a known baseline instead of
// silently leaving them with nothing. 0 means no fallback. It applies only to
// a mapping that matched and then failed to resolve — never to a user whose
// groups map to nothing, which would hand access to every account in the
// directory.
FallbackRoleID int64 `json:"fallbackRoleId"`
}
LDAPConfig configures optional LDAP / Active Directory authentication. The bind password is encrypted at rest (like the SMTP one) and never returned.
func (LDAPConfig) Configured ¶
func (c LDAPConfig) Configured() bool
Configured reports whether enough is set to attempt LDAP authentication.
type LDAPGroupMapping ¶ added in v1.5.0
type LDAPGroupMapping struct {
GroupDN string `json:"groupDn"`
Sections []string `json:"sections"`
// omitempty so a config written before roles existed stays truly absent
// rather than serialising as "roleIds": null.
RoleIDs []int64 `json:"roleIds,omitempty"`
}
LDAPGroupMapping grants access to members of an LDAP group, matched on the group's full DN. A mapping can hand out named roles, a raw list of sections, or both; a user's effective access is the union over every mapping whose group they belong to. Roles are the intended way to use this; the Sections field predates roles and stays for configs written before they existed.
type MCPTokenPolicy ¶ added in v1.6.0
type MCPTokenPolicy struct {
// DefaultDays is the lifetime applied when the request doesn't name one.
DefaultDays int `json:"defaultDays"`
// MaxDays is the longest lifetime a user may choose. 0 means no ceiling.
//
// This exists so that "no unlimited tokens" means something. Without a
// ceiling the rule is a formality: anyone told they cannot have a token that
// never expires can simply ask for one lasting a hundred years, and the
// policy has achieved nothing but a longer number.
MaxDays int `json:"maxDays"`
// AllowUnlimited permits tokens with no expiry at all.
AllowUnlimited bool `json:"allowUnlimited"`
}
How long an MCP bearer token is allowed to live.
A token that never expires is a credential nobody ever has to think about again — which is exactly the problem. It outlives the laptop it was pasted into, the contractor it was minted for, and the incident it was involved in. Revocation exists, but revocation requires somebody to remember; an expiry date is the only control here that works when nobody is paying attention.
So the default is 30 days, and never-expiring tokens are off unless an admin turns them back on. This governs what may be MINTED — it deliberately does not touch tokens that already exist, because silently expiring credentials that are in use would break running setups to enforce a policy chosen after they were created. Existing forever-tokens are visible on the MCP admin page and can be revoked there.
func DefaultMCPTokenPolicy ¶ added in v1.6.0
func DefaultMCPTokenPolicy() MCPTokenPolicy
DefaultMCPTokenPolicy is what a fresh install gets, and what a stored policy falls back to field by field when it is missing or nonsensical.
func (MCPTokenPolicy) ResolveExpiry ¶ added in v1.6.0
func (p MCPTokenPolicy) ResolveExpiry(requestedDays int, never bool, now time.Time) (time.Time, error)
ResolveExpiry turns a request into a concrete expiry time.
"Never" is a separate flag rather than days==0 on purpose. Overloading zero would make the two very different intents — "I did not choose" and "I want this to live forever" — indistinguishable on the wire, and the safe reading of silence has to be the policy default, not immortality.
The whole decision lives here so the handler cannot enforce it slightly differently from anything added later. now is passed in so the tests are not timing-dependent.
type OAuthClient ¶ added in v1.4.0
type OAuthClient struct {
ID string // client_id
Name string
RedirectURIs []string
CreatedAt time.Time
}
OAuthClient is a dynamically-registered (RFC 7591) MCP OAuth client. Clients are public (no secret); security rests on PKCE + exact redirect-URI matching.
type OAuthCode ¶ added in v1.4.0
type OAuthCode struct {
ClientID string
UserID int64
RedirectURI string
CodeChallenge string
Resource string
Scope string
ExpiresAt time.Time
}
OAuthCode is the state bound to a single-use authorization code.
type OAuthRefreshToken ¶ added in v1.4.0
type OAuthRefreshToken struct {
ClientID string
UserID int64
Scope string
Resource string
ExpiresAt time.Time
}
OAuthRefreshToken is the state bound to a refresh token.
type ParseRule ¶
type ParseRule struct {
ID int64 `json:"id"`
Name string `json:"name"`
Pattern string `json:"pattern"`
CreatedAt time.Time `json:"createdAt"`
}
ParseRule is a saved log-parsing rule: a regex with named capture groups that the Logs view applies to extract structured fields (columns) from log lines.
type Project ¶ added in v1.2.0
type Project struct {
ID int64
Name string
Slug string
ComposeFile string
HostID int64 // target Docker host for deploy; 0 = local daemon
// AllowRemoteHostPaths lets a remote deploy mount bind sources from OUTSIDE
// the project folder — paths on the remote host itself, which are otherwise
// refused because we can't see what they hold. Off by default; enabling it
// requires the "hosts" permission and is audited.
AllowRemoteHostPaths bool
// LastDeployedProfiles is the profile list passed to the last successful
// `compose up` for this project — what's actually running, as opposed to
// whatever profiles a user has since selected for the NEXT deploy (that
// selection is a client-only preference; see web/src/pages/Projects.tsx).
// nil/empty means never successfully deployed, or deployed with no profiles.
LastDeployedProfiles []string
CreatedBy string
CreatedAt time.Time
UpdatedAt time.Time
}
Project is a managed compose project: a folder under the data dir holding a compose file plus sidecar config/script files, deployed via the docker compose CLI. The folder is keyed by the numeric ID (derived at runtime, not stored) so renames never move files. Slug is the compose project name.
type ProjectTemplate ¶ added in v1.4.0
type ProjectTemplate struct {
ID int64 `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
Description string `json:"description"`
CreatedBy string `json:"createdBy"`
CreatedAt time.Time `json:"createdAt"`
}
ProjectTemplate is a user-saved project preset. Only metadata lives in the DB; the scaffold files live on disk under DataDir/project-templates/{id}/.
type Registry ¶
Registry holds credentials for a container image registry. The secret (password/token) is encrypted at rest and never returned in listings.
type RegistryAuth ¶
RegistryAuth is the decrypted credential pair used to authenticate to a registry for pull/push. It is only assembled server-side, never serialised.
type Role ¶ added in v1.6.0
type Role struct {
ID int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Builtin bool `json:"builtin"`
Sections []RoleSection `json:"sections"`
// HostIDs limits the role to those hosts. EMPTY MEANS EVERY HOST, so an
// existing role keeps its reach and a newly created one isn't accidentally
// scoped to nothing. The local daemon (0) is always reachable and is never
// stored here.
HostIDs []int64 `json:"hostIds"`
}
Role is a named bundle of section grants, optionally limited to a set of Docker hosts.
type RoleSection ¶ added in v1.6.0
RoleSection is one section grant inside a role.
type SMTPConfig ¶
type SMTPConfig struct {
Host string `json:"host"`
Port int `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
From string `json:"from"`
To string `json:"to"` // comma-separated recipients
TLS bool `json:"tls"` // implicit TLS (e.g. port 465); otherwise STARTTLS if offered
}
SMTPConfig is the mail server used for the email alert channel. The password is stored encrypted at rest (the persisted JSON holds ciphertext); it is decrypted on read and never returned to API clients.
func (SMTPConfig) Configured ¶
func (c SMTPConfig) Configured() bool
Configured reports whether enough is set to attempt sending.
type ServiceBlock ¶ added in v1.4.0
type ServiceBlock struct {
ID int64 `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
Description string `json:"description"`
Service string `json:"service"`
ServiceYAML string `json:"serviceYaml"`
Volumes []string `json:"volumes"`
CreatedBy string `json:"createdBy"`
CreatedAt time.Time `json:"createdAt"`
}
ServiceBlock is a user-defined builder block — a single compose service fragment stored inline.
type Session ¶ added in v1.6.0
type Session struct {
ID string // the token's jti
UserID int64
IP string
UserAgent string
CreatedAt time.Time
LastSeenAt time.Time
ExpiresAt time.Time
}
Session is one signed-in browser or client.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store wraps the database handle and exposes typed queries.
func Open ¶
Open opens (creating if necessary) the SQLite database at path and runs all pending migrations. A path of ":memory:" yields an ephemeral DB.
func (*Store) APITokenByHash ¶ added in v1.4.0
APITokenByHash looks up an active (non-revoked) token by its SHA-256 hash. Expiry is NOT enforced here — callers check Expired() so they can treat an expired token identically to a missing one. Returns ErrNotFound if absent or revoked.
func (*Store) AckAlertEvent ¶
AckAlertEvent marks an alert event acknowledged, recording who did it.
func (*Store) AckMatchingAlertEvents ¶ added in v1.6.0
AckMatchingAlertEvents acknowledges every unacknowledged event the filter matches, returning how many changed. Used by "acknowledge all", which is deliberately scoped to what the caller is currently looking at rather than to the whole table.
func (*Store) AdminRevokeAPIToken ¶ added in v1.4.0
AdminRevokeAPIToken marks any token revoked regardless of owner — for admins managing the fleet. Unlike RevokeAPIToken it is not scoped to a user. The bool reports whether a matching, still-active token was revoked (false → unknown id or already revoked), so the handler can return 404 instead of a false success.
func (*Store) AlertDeliveriesFor ¶ added in v1.6.0
func (s *Store) AlertDeliveriesFor(ctx context.Context, eventIDs []int64) (map[int64][]AlertDelivery, error)
AlertDeliveriesFor returns the delivery attempts for the given events.
func (*Store) AlertEventHost ¶ added in v1.6.0
AlertEventHost returns the Docker host an alert event belongs to.
Exists so a caller holding only an alert id can authorise against that alert's host before reading anything about it. Alert ids are sequential integers, so "you need the id first" is not an access control.
func (*Store) Audit ¶
func (s *Store) Audit(ctx context.Context, e AuditEntry) error
Audit appends an entry to the audit log. Failures are returned but callers generally log-and-continue: an audit write must never block a user action.
func (*Store) AuthForHost ¶
AuthForHost returns the decrypted credentials whose address matches the registry host of an image reference, or ErrNotFound if none is configured.
func (*Store) BackupTo ¶ added in v1.6.0
BackupTo writes a consistent snapshot of the database to path using `VACUUM INTO`. The database runs in WAL mode, so copying the file directly is unsafe: committed data can still live in the -wal file, and a copy taken during a write yields a torn database. VACUUM INTO takes the snapshot through the live connection instead, so it is safe while the server is running.
func (*Store) BumpSessionEpoch ¶ added in v1.6.0
BumpSessionEpoch invalidates every session token already issued for a user.
A JWT is self-contained: nothing about changing a password reaches the copy a browser (or a script) already holds, so without this an attacker whose access prompted the reset keeps it until the token expires — up to twelve hours of full Docker control, granted by the very act meant to revoke it.
func (*Store) BurnFactorCounter ¶ added in v1.6.0
BurnFactorCounter records the time step a code came from, and that the factor was used. The counter must move forward: replaying the same code inside its 30-second window has to fail, which is the whole point of storing it.
func (*Store) CanReachHost ¶ added in v1.6.0
CanReachHost reports whether a user may see anything at all on hostID. The local daemon (0) is always reachable, matching Grant.HasHost.
func (*Store) ComposeFragmentByID ¶ added in v1.4.0
func (*Store) ComposeFragmentBySlug ¶ added in v1.4.0
func (*Store) ConsumeOAuthCode ¶ added in v1.4.0
ConsumeOAuthCode atomically fetches and deletes an authorization code, so a code can never be redeemed twice. Returns ErrNotFound if absent. Callers must still check ExpiresAt.
func (*Store) ConsumeRefreshToken ¶ added in v1.4.0
func (s *Store) ConsumeRefreshToken(ctx context.Context, tokenHash string) (*OAuthRefreshToken, error)
ConsumeRefreshToken atomically fetches and deletes a refresh token (rotation: every use invalidates the old token and a fresh one is issued). Returns ErrNotFound if absent. Callers must still check ExpiresAt.
func (*Store) CountAdmins ¶
CountAdmins returns how many admin accounts exist (to guard the last admin).
func (*Store) CountFactors ¶ added in v1.6.0
CountFactors reports how many factors an account has paired.
func (*Store) CountUnacknowledged ¶
CountUnacknowledged returns the number of unacknowledged alert events.
func (*Store) CountUsers ¶
CountUsers returns the number of accounts; used to detect first-run setup.
func (*Store) CreateAPIToken ¶ added in v1.4.0
CreateAPIToken inserts a new token row and returns its assigned ID. The caller is responsible for generating the secret and passing its SHA-256 hash.
func (*Store) CreateAlertRule ¶
CreateAlertRule inserts an alert rule and returns its ID.
func (*Store) CreateComposeFragment ¶ added in v1.4.0
func (*Store) CreateFactor ¶ added in v1.6.0
CreateFactor pairs a new factor and returns its id.
authorised says the password was proved. When it is false the caller is relying on "this account has nothing to protect yet", and THAT is checked here, as part of the INSERT — not by the caller beforehand.
The distinction matters because the gap between a caller's check and this write is attacker-controlled. The WebAuthn library reads the request body, so a client that sends its headers and then stalls holds the handler open between the two for as long as it likes: the check runs against an unprotected account, the owner pairs their real factor, and the insert lands afterwards. Making the condition part of the write closes that, and the plain race with it — the same reason PairPendingFactor claims its enrolment with a compare-and-swap instead of a read.
Creating a factor also drops any half-finished TOTP enrolment. That enrolment is a live capability authorised against an account with nothing to protect, and this is the moment the account stops being unprotected — leaving it behind would let whoever started it add their own authenticator afterwards, with no password. All of it goes together or not at all.
func (*Store) CreateFirstUser ¶ added in v1.6.0
CreateFirstUser inserts the first account, and only the first.
Setup is otherwise a check-then-act: NeedsSetup counts, the handler validates, and the insert happens later — so two requests arriving together can both pass the count and both create an admin. The window is small but the payoff is permanent admin on a fresh instance, and a fresh instance is exactly what is reachable before anyone is watching. The condition therefore lives in the INSERT itself, where SQLite settles it: zero rows affected means somebody else was first.
func (*Store) CreateHost ¶
CreateHost inserts a new host and returns its ID. The TLS private key is encrypted at rest (CA and client cert are public, so they're stored as-is).
func (*Store) CreateOAuthClient ¶ added in v1.4.0
func (s *Store) CreateOAuthClient(ctx context.Context, c *OAuthClient) error
CreateOAuthClient stores a newly registered client.
func (*Store) CreateOAuthCode ¶ added in v1.4.0
CreateOAuthCode stores an authorization code (by hash).
func (*Store) CreateParseRule ¶
CreateParseRule inserts a parse rule and returns its ID.
func (*Store) CreateProject ¶ added in v1.2.0
CreateProject inserts a project and returns its ID. A slug collision yields ErrDuplicate.
func (*Store) CreateProjectTemplate ¶ added in v1.4.0
func (*Store) CreateRefreshToken ¶ added in v1.4.0
func (s *Store) CreateRefreshToken(ctx context.Context, tokenHash string, t *OAuthRefreshToken) error
CreateRefreshToken stores a refresh token (by hash).
func (*Store) CreateRegistry ¶
func (s *Store) CreateRegistry(ctx context.Context, name, address, username, secret string) (int64, error)
CreateRegistry stores a registry, encrypting the secret. The address is normalised so it matches image references later (see NormalizeRegistryHost).
func (*Store) CreateRole ¶ added in v1.6.0
CreateRole inserts a user-defined role. A duplicate name yields ErrDuplicate.
func (*Store) CreateServiceBlock ¶ added in v1.4.0
func (*Store) CreateSession ¶ added in v1.6.0
CreateSession records a new session at login.
func (*Store) CreateUser ¶
CreateUser inserts a new account and returns its assigned ID.
func (*Store) CreateWebhook ¶
CreateWebhook inserts a webhook and returns its ID.
func (*Store) DeleteAlertRule ¶
DeleteAlertRule removes an alert rule by ID.
func (*Store) DeleteAlertState ¶ added in v1.6.0
func (s *Store) DeleteAlertState(ctx context.Context, hostID int64, containerID, metric string) error
DeleteAlertState clears a condition that no longer holds.
func (*Store) DeleteComposeFragment ¶ added in v1.4.0
func (*Store) DeleteExpiredOAuth ¶ added in v1.4.0
DeleteExpiredOAuth purges authorization codes and refresh tokens whose expiry has passed (issued-but-never-redeemed codes, lapsed refresh tokens). Run periodically so the tables don't grow unbounded.
func (*Store) DeleteFactor ¶ added in v1.6.0
DeleteFactor removes one factor, unless it is the last one.
Scoped by user id as well as factor id, so knowing another account's factor id achieves nothing — and the "is this the last one?" test is part of the DELETE rather than a read before it. Counting first and deleting second is a race two concurrent requests win together: both see two factors, both delete, and the account is left with none. That is not the self-lockout it looks like — 2FA is derived from whether any factor exists, so zero factors means the password alone signs in. The guard has to be atomic or it is decoration.
func (*Store) DeleteHost ¶
DeleteHost removes a host by ID.
func (*Store) DeleteOAuthClient ¶ added in v1.4.0
DeleteOAuthClient removes a registered client and, in the same transaction, any authorization codes and refresh tokens issued to it — so de-registering a client immediately severs every credential derived from it. The bool reports whether a client row actually existed (false → unknown id → 404).
func (*Store) DeleteParseRule ¶
DeleteParseRule removes a parse rule by ID.
func (*Store) DeleteProject ¶ added in v1.2.0
DeleteProject removes the project row (the caller removes the folder).
func (*Store) DeleteProjectTemplate ¶ added in v1.4.0
func (*Store) DeleteRegistry ¶
DeleteRegistry removes a registry by ID.
func (*Store) DeleteRole ¶ added in v1.6.0
DeleteRole removes a user-defined role and any assignments of it. Built-ins are refused.
func (*Store) DeleteServiceBlock ¶ added in v1.4.0
func (*Store) DeleteSession ¶ added in v1.6.0
DeleteSession revokes one session. Scoped by user id as well as session id, so knowing (or guessing) another account's session id achieves nothing.
func (*Store) DeleteUser ¶
DeleteUser removes an account.
func (*Store) DeleteUserFactors ¶ added in v1.6.0
DeleteUserFactors removes every factor for a user, for account deletion.
func (*Store) DeleteUserSessions ¶ added in v1.6.0
DeleteUserSessions revokes every session for a user — sign out everywhere, and what a password change does.
func (*Store) DeleteWebhook ¶
DeleteWebhook removes a webhook by ID.
func (*Store) DisabledSections ¶
DisabledSections returns the sections an admin has turned off app-wide.
func (*Store) EffectiveGrants ¶ added in v1.6.0
EffectiveGrants computes a user's access per section: the union of their roles' grants and their own per-user section list, then capped.
- A per-user section (the pre-roles model) grants write unless the account is read-only, which is exactly how it behaved before roles existed.
- A role section grants write only if the role says so.
- The user-level read-only flag caps everything to reads, so it keeps meaning "this account cannot change anything" regardless of role.
- App-wide disabled sections are removed last: a feature turned off is off for everyone (admins bypass this elsewhere, in checkAccess).
Admins are not special-cased here; checkAccess short-circuits for them, and keeping this function purely about grants makes it testable on its own.
func (*Store) EffectiveSections ¶ added in v1.6.0
EffectiveSections lists the sections a user can reach, sorted — used for the users list and for LDAP section syncing.
func (*Store) EncryptPlaintextHostKeys ¶ added in v1.5.0
EncryptPlaintextHostKeys re-encrypts any host TLS private key still stored in plaintext (rows created before encryption-at-rest). Called once at startup, after the cipher is set; a no-op when there's nothing to migrate.
func (*Store) EnsureLocalHost ¶
EnsureLocalHost guarantees a "local" host row exists so the app is usable immediately on first run without manual host configuration.
func (*Store) ExistingRoleIDs ¶ added in v1.6.0
ExistingRoleIDs filters ids down to the ones that still name a role, keeping the caller's order. Used when applying LDAP mappings, where an id can outlive the role it referred to.
func (*Store) FactorByCredentialID ¶ added in v1.6.0
FactorByCredentialID finds a passkey by the credential id an assertion names.
NOT scoped to a user, deliberately: an assertion arrives naming a credential and the account it belongs to is the answer, not the question. That is safe because the id is unique across the table (see the index) and because the caller still has to verify the signature against this credential's public key — knowing an id proves nothing on its own.
func (*Store) FactorByID ¶ added in v1.6.0
FactorByID returns one factor, scoped to its owner.
func (*Store) GetLDAP ¶
func (s *Store) GetLDAP(ctx context.Context) (LDAPConfig, error)
GetLDAP loads the LDAP config, decrypting the bind password.
func (*Store) GetSMTP ¶
func (s *Store) GetSMTP(ctx context.Context) (SMTPConfig, error)
GetSMTP loads the SMTP config, decrypting the password.
func (*Store) InsertAlertEvent ¶
InsertAlertEvent records an alert and returns its ID.
A resolution is stored ALREADY ACKNOWLEDGED. It is not a task — there is nothing for anyone to do about a condition that ended — so it must never sit in an outstanding-work list or push up the sidebar count. Settling it here, once, is why nothing downstream needs a special case for it; and if the UI ever offers an Acknowledge action on resolutions again, the data still means what it says. AcknowledgedBy stays empty, because no person did it.
func (*Store) ListAPITokens ¶ added in v1.4.0
ListAPITokens returns a user's tokens (newest first) for the management UI. The hash is included but is not the secret — the secret is unrecoverable.
func (*Store) ListAlertEvents ¶
func (s *Store) ListAlertEvents(ctx context.Context, q AlertQuery) ([]AlertEvent, int, error)
ListAlertEvents returns a page of the event feed, newest first, plus the total number of events matching the filter so a caller can page through it.
The filter is built as parameterised fragments rather than string-concatenated values: every one of these comes from a query string.
func (*Store) ListAlertRules ¶
ListAlertRules returns all alert rules.
func (*Store) ListAlertStates ¶ added in v1.6.0
func (s *Store) ListAlertStates(ctx context.Context) ([]AlertState, error)
ListAlertStates returns every condition currently held to be firing.
func (*Store) ListAllAPITokens ¶ added in v1.4.0
func (s *Store) ListAllAPITokens(ctx context.Context) ([]APITokenWithUser, error)
ListAllAPITokens returns every user's tokens (newest first), each annotated with the owner's username, for the admin overview. Revoked tokens are included so an admin can see recently-revoked credentials; the handler/UI distinguishes them via the Revoked flag. The token hash is deliberately NOT selected — the overview is metadata-only, so the digest never even reaches process memory here (no chance of leaking via a log line or panic).
func (*Store) ListComposeFragments ¶ added in v1.4.0
func (s *Store) ListComposeFragments(ctx context.Context) ([]ComposeFragment, error)
func (*Store) ListFactors ¶ added in v1.6.0
ListFactors returns a user's paired factors, oldest first — the order they were added is the order that makes sense of "which one is my old phone".
func (*Store) ListOAuthClients ¶ added in v1.4.0
func (s *Store) ListOAuthClients(ctx context.Context) ([]OAuthClient, error)
ListOAuthClients returns every registered MCP OAuth client (newest first) for the admin overview. Clients are public (no secret stored), so the full row is safe to surface.
func (*Store) ListParseRules ¶
ListParseRules returns all saved log-parsing rules.
func (*Store) ListProjectTemplates ¶ added in v1.4.0
func (s *Store) ListProjectTemplates(ctx context.Context) ([]ProjectTemplate, error)
func (*Store) ListProjects ¶ added in v1.2.0
ListProjects returns all projects ordered by name.
func (*Store) ListRegistries ¶
ListRegistries returns the configured registries without their secrets.
func (*Store) ListRoles ¶ added in v1.6.0
ListRoles returns every role with its grants, ordered built-ins first then by name (mirroring how Templates lists built-in presets ahead of user ones).
func (*Store) ListServiceBlocks ¶ added in v1.4.0
func (s *Store) ListServiceBlocks(ctx context.Context) ([]ServiceBlock, error)
func (*Store) ListSessions ¶ added in v1.6.0
ListSessions returns a user's own sessions, newest first, dropping any that have expired (a token past its expiry is refused anyway, so listing it would only invite people to revoke something already gone).
func (*Store) ListUsers ¶
ListUsers returns all accounts (without secrets) for the admin user manager.
func (*Store) ListWebhooks ¶
ListWebhooks returns all configured webhooks.
func (*Store) LocalhostNo2FA ¶
LocalhostNo2FA reports whether password-only login is allowed from loopback.
func (*Store) MCPTokenPolicy ¶ added in v1.6.0
func (s *Store) MCPTokenPolicy(ctx context.Context) (MCPTokenPolicy, error)
MCPTokenPolicy reads the policy, falling back to the default when unset.
func (*Store) NormalizeHostID ¶ added in v1.6.0
NormalizeHostID collapses the two ways the local daemon can be named into one. A request may say host 0 ("the default local host") or the id of the seeded `kind = 'local'` row — they mean the same daemon, and host scoping has to treat them the same or the local daemon ends up reachable under one name and not the other. Returns 0 for either, and id unchanged for a remote host.
func (*Store) OAuthClientByID ¶ added in v1.4.0
OAuthClientByID looks up a registered client.
func (*Store) PairPendingFactor ¶ added in v1.6.0
func (s *Store) PairPendingFactor(ctx context.Context, userID int64, pending, name string) (int64, error)
PairPendingFactor turns the account's pending enrolment into a factor, atomically.
The caller has already checked a code against `pending`. Between that check and this call, anything could have happened — including the same request arriving sixteen times in parallel, which is a POST away. So the claim on the pending secret is a compare-and-swap: exactly one caller clears it, and only that caller inserts. Without it, one enrolment becomes N factors holding ONE secret, and since the replay watermark is per factor, every future code from that authenticator becomes spendable N times.
func (*Store) Ping ¶ added in v1.1.0
Ping checks that the database is reachable (used by the health endpoint).
func (*Store) ProjectByID ¶ added in v1.2.0
ProjectByID looks up a project by primary key.
func (*Store) ProjectTemplateByID ¶ added in v1.4.0
func (*Store) ProjectTemplateBySlug ¶ added in v1.4.0
func (*Store) PromoteTOTPPending ¶ added in v1.6.0
PromoteTOTPPending makes the pending secret the active one and clears it. Used once the user has proved they can generate codes from the new authenticator.
func (*Store) PurgeExpiredSessions ¶ added in v1.6.0
PurgeExpiredSessions drops rows whose tokens can no longer be presented.
func (*Store) ReachableHosts ¶ added in v1.6.0
func (s *Store) ReachableHosts(ctx context.Context, u *User) (hosts map[int64]bool, all bool, err error)
ReachableHosts is the union of every host a user's grants reach, across all sections. all=true means "no restriction anywhere" — either an unscoped grant exists or the user is unconstrained, and the host set is then irrelevant.
It answers a coarser question than EffectiveGrants: not "may they do X on host N" but "may they see host N at all". That is what the aggregate views need — the host list, the project list, the alert feed — and what the handful of routes that take ?host= without belonging to a section need, since there is no single section to check them against.
func (*Store) RecentAudit ¶
RecentAudit returns the most recent audit entries, newest first. When before is > 0, only entries older than that id are returned (cursor pagination).
func (*Store) RecordAlertDelivery ¶ added in v1.6.0
func (s *Store) RecordAlertDelivery(ctx context.Context, d *AlertDelivery) error
RecordAlertDelivery stores the outcome of one delivery attempt.
func (*Store) RevokeAPIToken ¶ added in v1.4.0
RevokeAPIToken marks a token revoked. It is scoped to userID so a caller can only revoke their own tokens. The bool reports whether a matching, owned token was actually revoked (false → unknown id or not the caller's), so the handler can return 404 instead of a misleading success.
func (*Store) RoleIDsForUser ¶ added in v1.6.0
RoleIDsForUser returns the ids of the roles assigned to a user.
func (*Store) RolesForUser ¶ added in v1.6.0
RolesForUser returns the roles assigned to a user, with their grants.
func (*Store) ServiceBlockByID ¶ added in v1.4.0
func (*Store) ServiceBlockBySlug ¶ added in v1.4.0
func (*Store) SessionEpoch ¶ added in v1.6.0
SessionEpoch returns the account's current session generation. A token minted before the last bump is stale and must be refused.
func (*Store) SessionExists ¶ added in v1.6.0
SessionExists reports whether a session id is still valid for that user.
func (*Store) SetAlertRuleEnabled ¶
SetAlertRuleEnabled toggles an alert rule on or off.
func (*Store) SetAuthSource ¶ added in v1.6.0
SetAuthSource records which authority owns this account's password. Used by the LDAP provisioning path, and by tests that need an account this app does not own.
func (*Store) SetCipher ¶
SetCipher installs the cipher used to encrypt secrets at rest (registry credentials). It is wired up once at startup, after the key is loaded.
func (*Store) SetDisabledSections ¶
SetDisabledSections persists the app-wide disabled sections.
func (*Store) SetHostAlertEmail ¶
SetHostAlertEmail sets a host's per-host alert recipient override.
func (*Store) SetHostDisabled ¶ added in v1.2.0
SetHostDisabled toggles whether the monitor ignores a host.
func (*Store) SetHostKey ¶
SetHostKey pins (or clears, when key is "") the trusted SSH host public key for a host. Subsequent connections verify the daemon's key against it.
func (*Store) SetLDAP ¶
func (s *Store) SetLDAP(ctx context.Context, c LDAPConfig) error
SetLDAP persists the config, encrypting the bind password. An empty bind password preserves the previously stored one.
func (*Store) SetLastDeployedProfiles ¶ added in v1.6.1
SetLastDeployedProfiles records the profiles used on a project's last successful `compose up`, so the UI can tell "currently deployed" apart from whatever's merely selected for the next deploy. Called only after a deploy actually succeeds — a failed deploy must not overwrite what's still running.
func (*Store) SetLocalhostNo2FA ¶
SetLocalhostNo2FA toggles the localhost 2FA exemption.
func (*Store) SetMCPTokenPolicy ¶ added in v1.6.0
func (s *Store) SetMCPTokenPolicy(ctx context.Context, p MCPTokenPolicy) error
SetMCPTokenPolicy persists the policy after normalising it.
func (*Store) SetPasswordless ¶ added in v1.6.0
SetPasswordless turns signing in with a passkey alone on or off for one account.
func (*Store) SetSMTP ¶
func (s *Store) SetSMTP(ctx context.Context, c SMTPConfig) error
SetSMTP persists the SMTP config, encrypting the password. An empty password preserves the previously stored one (so the UI need not resend the secret).
func (*Store) SetSetting ¶
SetSetting upserts a key/value pair.
func (*Store) SetTOTPLastCounter ¶ added in v1.6.0
SetTOTPLastCounter records the time step whose code was just accepted, so the same code cannot be presented again while it is still inside its window.
Written with a `>` guard rather than a plain assignment: two requests carrying the same code can race here, and the loser must not be able to move the watermark backwards.
func (*Store) SetTOTPPending ¶ added in v1.6.0
SetTOTPPending stores a secret for an authenticator being paired while another one is still active, without touching the working secret.
stepUp records whether the password was proved to start this enrolment, so that redeeming it can be judged against the account's protection at that time rather than at the time the button was pressed.
func (*Store) SetUserEmail ¶ added in v1.6.0
SetUserEmail records where a user's own alert e-mails go. It is self-service — an account edits its own address — and is also written by the LDAP sync when the directory publishes one.
func (*Store) SetUserPrefs ¶ added in v1.1.0
SetUserPrefs replaces a user's UI preferences JSON blob.
func (*Store) SetUserRoles ¶ added in v1.6.0
SetUserRoles replaces a user's role assignments. Unknown role ids are dropped rather than erroring, so a stale UI can't wedge the form.
func (*Store) Setting ¶
Setting reads a single key from the settings table. Returns ("", nil) when the key is absent so callers can treat "missing" as "use default".
func (*Store) TouchAPIToken ¶ added in v1.4.0
TouchAPIToken records the last time a token was used. Best-effort: callers ignore the error so a logging write never blocks an authenticated request.
func (*Store) TouchLogin ¶
TouchLogin records the timestamp of a successful login.
func (*Store) TouchProject ¶ added in v1.2.0
TouchProject bumps updated_at (called when a file changes).
func (*Store) TouchSession ¶ added in v1.6.0
TouchSession records that a session was used, at minute granularity.
Written only when the stored value is already a minute old: this runs on every authenticated request, and a write per request would turn a read-mostly workload into a write-mostly one against a single-writer database.
func (*Store) UpdateAlertRule ¶
UpdateAlertRule replaces a rule's mutable fields (enabled is managed separately via SetAlertRuleEnabled).
func (*Store) UpdateComposeFragment ¶ added in v1.4.0
func (s *Store) UpdateComposeFragment(ctx context.Context, f *ComposeFragment) error
UpdateComposeFragment edits a fragment's editable fields (the slug is immutable).
func (*Store) UpdateCredential ¶ added in v1.6.0
UpdateCredential writes back the credential after a successful assertion — the signature counter moves, and that movement is what detects a cloned key.
func (*Store) UpdatePassword ¶
UpdatePassword replaces the stored Argon2id hash for a user.
func (*Store) UpdateProjectSettings ¶ added in v1.6.0
func (s *Store) UpdateProjectSettings(ctx context.Context, id int64, name string, hostID int64, allowRemoteHostPaths bool) error
UpdateProjectSettings changes the display name, target host and the remote-host-path opt-in (the slug stays immutable).
func (*Store) UpdateProjectTemplate ¶ added in v1.4.0
func (s *Store) UpdateProjectTemplate(ctx context.Context, id int64, name, description string) error
UpdateProjectTemplate changes a template's display name and description. The slug (its stable identifier on disk and in create references) is immutable, so renames never move files — mirrors how project renames work.
func (*Store) UpdateRole ¶ added in v1.6.0
func (s *Store) UpdateRole(ctx context.Context, id int64, name, description string, sections []RoleSection, hostIDs []int64) error
UpdateRole renames a role and replaces its grants. Built-in roles are refused: they are the known-good baseline, and the UI offers Duplicate instead.
func (*Store) UpdateServiceBlock ¶ added in v1.4.0
func (s *Store) UpdateServiceBlock(ctx context.Context, b *ServiceBlock) error
UpdateServiceBlock changes a block's editable fields. The slug stays immutable (it backs the builder reference), like project/template renames.
func (*Store) UpdateUserAccess ¶
func (s *Store) UpdateUserAccess(ctx context.Context, id int64, role string, readOnly bool, sections []string) error
UpdateUserAccess changes a user's role, read-only flag and allowed sections.
func (*Store) UpsertAlertState ¶ added in v1.6.0
func (s *Store) UpsertAlertState(ctx context.Context, a *AlertState) error
UpsertAlertState records or updates a firing condition. StartedAt is written only on insert, so the age of an incident survives escalation and re-notify.
func (*Store) UserByUsername ¶
UserByUsername looks up a user by their unique username.
func (*Store) UserByWebAuthnHandle ¶ added in v1.6.0
UserByWebAuthnHandle resolves the account a user handle belongs to.
func (*Store) UserPrefs ¶ added in v1.1.0
UserPrefs returns a user's UI preferences as a JSON object string ("{}" if none). These are opaque to the server — the frontend owns the shape.
func (*Store) WebAuthnHandle ¶ added in v1.6.0
WebAuthnHandle returns the account's opaque user handle, creating one on first use. It is what an authenticator stores next to the key, so it has to be stable for the life of the account and must not be derived from anything guessable or meaningful — the spec asks for 64 random bytes, and a username would leak into the authenticator's own storage.
type TokenLifetimeError ¶ added in v1.6.0
type TokenLifetimeError struct{ MaxDays int }
TokenLifetimeError reports a requested lifetime beyond the policy ceiling. A typed error so the message can name the actual limit — a bare "too long" leaves the user guessing what to type instead.
func (*TokenLifetimeError) Error ¶ added in v1.6.0
func (e *TokenLifetimeError) Error() string
type User ¶
type User struct {
ID int64
Username string
PasswordHash string
Role string
// Email receives alerts from rules this user creates. Optional; when an LDAP
// directory publishes a mail attribute it is synced here on login.
Email string
AuthSource string // "local" (password stored here) or "ldap" (verified externally)
ReadOnly bool
Sections []string
TOTPSecret string
// TOTPEnabled means "has an authenticator app". MFAEnabled means "has a second
// factor of any kind" — the two stopped being the same thing when passkeys
// arrived, and the login path needs the second: an account with only a passkey
// must still be challenged, and must not be asked for a code it cannot produce.
TOTPEnabled bool
MFAEnabled bool
// TOTPPending holds a secret being paired. It becomes a factor on confirmation,
// and otherwise never takes effect: a wrong code, a cancel or a closed tab leave
// it sitting here until something clears it. Do not treat its presence as "a
// pairing is in progress".
//
// A stale value is NOT harmless, which is why it is cleared whenever a factor is
// created and why TOTPPendingStepUp exists. It is a capability: an enrolment
// begun on an unprotected account needs no password, so one left lying around
// could otherwise be redeemed later, against an account that has since been
// protected, by whoever started it.
TOTPPending string
// TOTPPendingStepUp records whether the password was proved when the pending
// enrolment was started. Redeeming it on an account that has a second factor
// requires this to be true — the check belongs at the moment the factor is
// created, not only at the moment the enrolment begins.
TOTPPendingStepUp bool
// Passwordless says a passkey alone may sign this account in. Off unless the
// owner turned it on with their password: it changes what the account rests on,
// and for a synced passkey it moves that to the platform account.
Passwordless bool
// TOTPLastCounter is the last 30-second time step whose code was accepted.
// A code is only valid once: within its window it would otherwise work
// repeatedly, so one shoulder-surfed or phished code could be spent several
// times — and the challenge token it satisfies lives for five minutes.
TOTPLastCounter int64
// SessionEpoch is bumped when previously issued sessions must stop working.
SessionEpoch int64
CreatedAt time.Time
LastLoginAt time.Time
}
User is an application account. PasswordHash is an Argon2id encoded hash. TOTPSecret is the base32 shared secret; it is only meaningful once TOTPEnabled is true (i.e. the user confirmed enrollment with a valid code).
Role is "admin" (full access incl. user/feature management) or "user". For "user" accounts, Sections lists the menu sections they may access and ReadOnly blocks mutating actions. Admins ignore both.
type Webhook ¶
type Webhook struct {
ID int64 `json:"id"`
Name string `json:"name"`
URL string `json:"url"`
Method string `json:"method"`
Headers map[string]string `json:"headers"`
BodyTemplate string `json:"bodyTemplate"`
CreatedAt time.Time `json:"createdAt"`
}
Webhook is a generic HTTP destination an alert rule can fire to. body_template is a Go text/template rendered against the alert event.