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.