- ed25519-deploy-nyckel genereras av servern (config-volymen, 0600), GIT_SSH_COMMAND med egen known_hosts (accept-new) - inställningar: remote-URL, live-gren, read-only, auto-synk-intervall - bakgrundssynk: endast fast-forward, flaggar attention vid merge-behov - pull/push, skapa/byt gren, merge-popup (ours/theirs per fil eller allt), abort, reset från remote med automatisk backup-gren - headless-konfig via config.json eller updateGitSync-mutationen - openssh-client tillagd i runtime-imagen; mutex kring alla git-operationer Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
150 lines
4.5 KiB
Go
150 lines
4.5 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// Config holds all backend runtime configuration.
|
|
type Config struct {
|
|
StoragePath string `json:"storage_path"`
|
|
DBPath string `json:"db_path"`
|
|
LDAP LDAPConfig `json:"ldap"`
|
|
OIDC OIDCConfig `json:"oidc"`
|
|
JWTSecret string `json:"jwt_secret"`
|
|
ListenAddr string `json:"listen_addr"`
|
|
// PublicURL is the externally reachable base URL of Archivum
|
|
// (e.g. https://archivum.brasse-pc.eu). Used to build the OIDC
|
|
// redirect URL when one is not set explicitly.
|
|
PublicURL string `json:"public_url"`
|
|
GitSync GitSyncConfig `json:"git_sync"`
|
|
}
|
|
|
|
// GitSyncConfig configures synchronisation of the wiki git repository
|
|
// with a remote (SSH or HTTPS). The SSH keypair is generated by the
|
|
// server itself and lives next to config.json so the whole feature can
|
|
// be driven headlessly by editing config.json and restarting.
|
|
type GitSyncConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
RemoteURL string `json:"remote_url"` // e.g. ssh://git@git.brasse-pc.eu:2222/brasse/Archivum-documets.git
|
|
// LiveBranch is the branch that counts as the live content of this
|
|
// instance: auto-sync pulls/pushes it and resets target it.
|
|
LiveBranch string `json:"live_branch"`
|
|
// ReadOnly forbids every push to the remote; the instance only
|
|
// pulls. Local edits still commit locally.
|
|
ReadOnly bool `json:"read_only"`
|
|
// AutoSyncMinutes > 0 enables background sync on that interval.
|
|
// Background pulls are fast-forward only; anything needing a real
|
|
// merge is left for an admin to resolve in the UI.
|
|
AutoSyncMinutes int `json:"auto_sync_minutes"`
|
|
}
|
|
|
|
// Normalize fills in defaults for optional git-sync fields.
|
|
func (g *GitSyncConfig) Normalize() {
|
|
if g.LiveBranch == "" {
|
|
g.LiveBranch = "main"
|
|
}
|
|
if g.AutoSyncMinutes < 0 {
|
|
g.AutoSyncMinutes = 0
|
|
}
|
|
}
|
|
|
|
type LDAPConfig struct {
|
|
Url string `json:"url"`
|
|
BaseDN string `json:"base_dn"`
|
|
AdminUser string `json:"admin_user"`
|
|
AdminPass string `json:"admin_pass"`
|
|
}
|
|
|
|
// OIDCConfig configures Single-Sign-On via an OpenID Connect provider
|
|
// (Authentik in this deployment). The provider authenticates the user and
|
|
// returns their group membership in the groups claim.
|
|
type OIDCConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
Issuer string `json:"issuer"` // e.g. https://authentik.brasse-pc.eu/application/o/archivum/
|
|
ClientID string `json:"client_id"`
|
|
ClientSecret string `json:"client_secret"`
|
|
RedirectURL string `json:"redirect_url"` // optional; derived from PublicURL when empty
|
|
// Claim/group mapping. Sensible defaults are applied in Normalize().
|
|
GroupsClaim string `json:"groups_claim"` // default "groups"
|
|
UsernameClaim string `json:"username_claim"` // default "preferred_username"
|
|
AdminGroup string `json:"admin_group"` // default "Archivum-admin"
|
|
ReaderGroup string `json:"reader_group"` // default "Archivum-reader"
|
|
}
|
|
|
|
// Normalize fills in defaults for optional OIDC fields.
|
|
func (o *OIDCConfig) Normalize() {
|
|
if o.GroupsClaim == "" {
|
|
o.GroupsClaim = "groups"
|
|
}
|
|
if o.UsernameClaim == "" {
|
|
o.UsernameClaim = "preferred_username"
|
|
}
|
|
if o.AdminGroup == "" {
|
|
o.AdminGroup = "Archivum-admin"
|
|
}
|
|
if o.ReaderGroup == "" {
|
|
o.ReaderGroup = "Archivum-reader"
|
|
}
|
|
}
|
|
|
|
// ResolvedRedirectURL returns the OIDC callback URL, deriving it from
|
|
// PublicURL when RedirectURL is not set.
|
|
func (c *Config) ResolvedRedirectURL() string {
|
|
if c.OIDC.RedirectURL != "" {
|
|
return c.OIDC.RedirectURL
|
|
}
|
|
if c.PublicURL != "" {
|
|
return strings.TrimRight(c.PublicURL, "/") + "/auth/oidc/callback"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// ErrRequireSetup is returned when config is missing or empty,
|
|
// signalling that the frontend should start the Setup Wizard.
|
|
var ErrRequireSetup = errors.New("REQUIRE_SETUP")
|
|
|
|
func Load(path string) (*Config, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return nil, ErrRequireSetup
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
var cfg Config
|
|
if err := json.Unmarshal(data, &cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if cfg.StoragePath == "" || cfg.JWTSecret == "" {
|
|
return nil, ErrRequireSetup
|
|
}
|
|
|
|
if cfg.ListenAddr == "" {
|
|
cfg.ListenAddr = ":4000"
|
|
}
|
|
|
|
// Default DB path to the dedicated db volume so it can be backed up
|
|
// independently of the wiki content.
|
|
if cfg.DBPath == "" {
|
|
cfg.DBPath = "/data/db/archivum.db"
|
|
}
|
|
|
|
cfg.OIDC.Normalize()
|
|
cfg.GitSync.Normalize()
|
|
|
|
return &cfg, nil
|
|
}
|
|
|
|
func Save(path string, cfg *Config) error {
|
|
data, err := json.MarshalIndent(cfg, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(path, data, 0600)
|
|
}
|