← repoman internals

internal/globalconfig

import "git.griefed.de/griefed/repoman/internal/globalconfig"
package globalconfig // import "git.griefed.de/griefed/repoman/internal/globalconfig"

Package globalconfig defines the in-memory shape of the
global_configurations.content_json blob and the service layer that loads and
saves it.

Every save appends a new row to global_configurations (the table is append-only
/ version-history by design). The active config is always the row with the
highest ID.

TYPES

type AuthCfg struct {
	SessionRollingWindowHours int `json:"session_rolling_window_hours,omitempty"`
	SessionAbsoluteMaxHours   int `json:"session_absolute_max_hours,omitempty"`
	AutoLogoutMinutes         int `json:"auto_logout_minutes,omitempty"`

	// MaxFailedAttempts before the first soft-lock. 0 = use default (10).
	MaxFailedAttempts int `json:"max_failed_attempts,omitempty"`

	// SoftLockDurationMinutes. 0 = use default (15).
	SoftLockDurationMinutes int `json:"soft_lock_duration_minutes,omitempty"`

	// HardLockAfterSoftLocks. 0 = use default (3).
	HardLockAfterSoftLocks int `json:"hard_lock_after_soft_locks,omitempty"`
}
    AuthCfg holds operator-tunable auth policy fields. Merged with the hardcoded
    defaults in internal/auth so the UI only needs to surface the values that
    differ.

func (c AuthCfg) AuthSettings() (auth.Settings, error)
    AuthSettings merges these operator overrides onto the auth-subsystem
    defaults (auth.DefaultSettings) and validates the result. Each field left
    at its zero value keeps the default — the "0 = use default" contract the
    settings UI documents. When the merged values are inconsistent (for example
    an absolute session max below the rolling window, or an auto-logout longer
    than the rolling window), it returns auth.DefaultSettings together with the
    error, so the caller can log the bad config and fall back to a known-safe
    policy instead of running with values that would break the session
    lifecycle.

type BackupCfg struct {
	// Enabled gates the scheduled backup job. When false no automatic
	// backups are taken; the manual "Back up now" button still works.
	Enabled bool `json:"enabled"`

	// DailyTime is the UTC time of day the daily backup runs, "HH:MM" (24h).
	// Empty defaults to 03:00. A window missed because the daemon was down
	// is caught up on the next start.
	DailyTime string `json:"daily_time,omitempty"`

	// KeepDaily/KeepWeekly/KeepMonthly are the GFS retention counts: how many
	// backups to keep at each tier. A file survives pruning if it is the most
	// recent backup of any retained day, ISO week, or month. KeepWeekly and
	// KeepMonthly may be 0 to disable that tier; KeepDaily must be >= 1 when
	// backups are enabled, so the newest backups are never pruned to nothing.
	KeepDaily   int `json:"keep_daily,omitempty"`
	KeepWeekly  int `json:"keep_weekly,omitempty"`
	KeepMonthly int `json:"keep_monthly,omitempty"`
}
    BackupCfg configures automatic database backups: when they run and how
    many are retained under a grandfather-father-son (GFS) scheme. The backup
    scheduler (internal/backup) reads these via the serve wiring.

func (b BackupCfg) DailyHourMinute() (hour, minute int)
    DailyHourMinute returns the configured daily-backup time as hour and minute,
    defaulting to 03:00 when unset or unparseable. Validate rejects a malformed
    non-empty value, so a saved config always parses cleanly here.

type ForgejoCfg struct {
	// URL is the externally-visible base URL, e.g. https://git.example.com.
	// No trailing slash.
	URL string `json:"url"`

	// AdminToken is a Forgejo personal-access token with the
	// repository:write and admin:org scopes. Stored encrypted.
	AdminToken string `json:"admin_token"`

	// SkipTLSVerify disables certificate validation. Never use in
	// production; provided for self-signed dev instances.
	SkipTLSVerify bool `json:"skip_tls_verify,omitempty"`
}
    ForgejoCfg holds the connection details for the target Forgejo instance.

type Repository interface {
	// Active returns the current settings snapshot, or nil when none
	// has been saved yet.
	Active(ctx context.Context) (*Snapshot, error)

	// Save encrypts and appends a new settings snapshot.
	Save(ctx context.Context, contentJSON, comment string, userID *int64, username string, at time.Time) (int64, error)

	// List returns recent snapshots, newest first.
	List(ctx context.Context, limit int) ([]*Snapshot, error)
}
    Repository is the persistence interface for global settings. Lives in
    internal/db; defined here to break the import cycle.

type SMTPCfg struct {
	// Enabled gates all outbound email. When false the rest of the
	// fields are ignored.
	Enabled bool `json:"enabled"`

	Host     string `json:"host"`
	Port     int    `json:"port"`
	Username string `json:"username"`

	// Password is stored encrypted via GlobalConfigRepo.
	Password string `json:"password"`

	// FromAddress is the envelope sender, e.g. repoman@example.com.
	FromAddress string `json:"from_address"`

	// FromName is the display name in the From header.
	FromName string `json:"from_name"`

	// UseTLS selects SMTPS (port 465 style). When false, STARTTLS is
	// used if offered by the server.
	UseTLS bool `json:"use_tls,omitempty"`
}
    SMTPCfg holds the outbound mail configuration.

type Service struct {
	Repo Repository
	Now  func() time.Time
}
    Service loads and saves global settings.

func NewService(repo Repository) *Service
    NewService returns a Service backed by repo.

func (s *Service) History(ctx context.Context, limit int) ([]*Snapshot, error)
    History returns up to limit recent snapshots (newest first).

func (s *Service) Load(ctx context.Context) (*Settings, error)
    Load returns the active Settings, falling back to zero-value defaults when
    nothing has been saved yet. Never returns nil.

func (s *Service) Save(
	ctx context.Context,
	settings *Settings,
	comment string,
	userID *int64,
	username string,
) error
    Save validates and persists a new settings snapshot.

type Settings struct {
	Forgejo     ForgejoCfg     `json:"forgejo"`
	SMTP        SMTPCfg        `json:"smtp"`
	Auth        AuthCfg        `json:"auth"`
	Backup      BackupCfg      `json:"backup"`
	TokenExpiry TokenExpiryCfg `json:"token_expiry"`
}
    Settings is the complete set of operator-configurable global values.

func ParseSettings(contentJSON string) (*Settings, error)
    ParseSettings unmarshals a stored snapshot's content_json into Settings.
    An empty string yields zero-value defaults. Unknown fields are ignored,
    so an older snapshot (e.g. one carrying the removed platform_tokens key)
    still parses — used by the history view to diff successive snapshots.

func (s *Settings) Validate() error
    Validate checks the Settings for obvious operator mistakes.

type Snapshot struct {
	ID                int64
	ContentJSON       string
	Comment           string
	CreatedAt         time.Time
	CreatedByUserID   *int64
	CreatedByUsername string
}
    Snapshot is the row-level representation — content_json is already decrypted
    by the GlobalConfigRepo.

type TokenExpiryCfg struct {
	// Disabled turns the feature off. The zero value is ENABLED, so a fresh
	// install warns by default — the probe is a no-op when no GitHub/GitLab
	// tokens are configured, so default-on costs nothing until tokens exist.
	Disabled bool `json:"disabled,omitempty"`

	// WarnDays is how many days before expiry warnings begin. <= 0 means the
	// default (14 days); see WarnThreshold.
	WarnDays int `json:"warn_days,omitempty"`
}
    TokenExpiryCfg configures warnings for source access tokens nearing expiry:
    a daily probe surfaces a dashboard banner and (when SMTP is on) emails the
    people who can edit the token. Only platforms that expose token expiry are
    checked — GitHub and GitLab; Gitea/Forgejo/Codeberg PATs do not expire,
    so they are never flagged.

func (c TokenExpiryCfg) Enabled() bool
    Enabled reports whether expiry warnings are active (the zero value is on).

func (c TokenExpiryCfg) WarnThreshold() time.Duration
    WarnThreshold returns how far before expiry warnings begin, defaulting to 14
    days when WarnDays is unset or non-positive.