main cfg.go
Eric Bower  ·  2026-02-25
  1package patchbin
  2
  3import (
  4	"fmt"
  5	"log/slog"
  6	"path/filepath"
  7	"strings"
  8	"time"
  9
 10	"github.com/knadh/koanf/parsers/toml"
 11	"github.com/knadh/koanf/providers/env"
 12	"github.com/knadh/koanf/providers/file"
 13	"github.com/knadh/koanf/v2"
 14	"golang.org/x/crypto/ssh"
 15)
 16
 17var k = koanf.New(".")
 18
 19type GitCfg struct {
 20	DataDir           string          `koanf:"data_dir"`
 21	Url               string          `koanf:"url"`
 22	Host              string          `koanf:"host"`
 23	SshPort           string          `koanf:"ssh_port"`
 24	WebPort           string          `koanf:"web_port"`
 25	PromPort          string          `koanf:"prom_port"`
 26	AdminsStr         []string        `koanf:"admins"`
 27	Admins            []ssh.PublicKey `koanf:"admins_pk"`
 28	Theme             string          `koanf:"theme"`
 29	TimeFormat        string          `koanf:"time_format"`
 30	Desc              string          `koanf:"desc"`
 31	RateLimitCount    int             `koanf:"rate_limit_count"`
 32	RateLimitInterval string          `koanf:"rate_limit_interval"`
 33	MaxStdinBytes     int64           `koanf:"max_stdin_bytes"`
 34	Logger            *slog.Logger
 35}
 36
 37func LoadConfigFile(fpath string, logger *slog.Logger) {
 38	fpp, err := filepath.Abs(fpath)
 39	if err != nil {
 40		panic(err)
 41	}
 42	logger.Info("loading configuration file", "fpath", fpp)
 43
 44	if err := k.Load(file.Provider(fpp), toml.Parser()); err != nil {
 45		panic(fmt.Sprintf("error loading config: %v", err))
 46	}
 47}
 48
 49func NewGitCfg(logger *slog.Logger) *GitCfg {
 50	err := k.Load(env.Provider("GITPR_", ".", func(s string) string {
 51		keyword := strings.ToLower(strings.TrimPrefix(s, "GITPR_"))
 52		return keyword
 53	}), nil)
 54	if err != nil {
 55		panic(fmt.Sprintf("could not load environment variables: %v", err))
 56	}
 57
 58	var out GitCfg
 59	err = k.UnmarshalWithConf("", &out, koanf.UnmarshalConf{Tag: "koanf"})
 60	if err != nil {
 61		panic(fmt.Sprintf("could not unmarshal config: %v", err))
 62	}
 63
 64	if len(out.AdminsStr) > 0 {
 65		keys, err := GetAuthorizedKeys(out.AdminsStr)
 66		if err == nil {
 67			out.Admins = keys
 68		} else {
 69			panic(fmt.Sprintf("could not parse authorized keys file: %v", err))
 70		}
 71	} else {
 72		logger.Info("no admin specified in config")
 73	}
 74
 75	// make datadir absolute
 76	tmpdir := out.DataDir
 77	if out.DataDir == "" {
 78		tmpdir = "./data"
 79	}
 80	datadir, err := filepath.Abs(tmpdir)
 81	out.DataDir = datadir
 82	if err != nil {
 83		panic(err)
 84	}
 85
 86	if out.Host == "" {
 87		out.Host = "0.0.0.0"
 88	}
 89
 90	if out.SshPort == "" {
 91		out.SshPort = "2222"
 92	}
 93
 94	if out.WebPort == "" {
 95		out.WebPort = "3000"
 96	}
 97
 98	if out.Theme == "" {
 99		out.Theme = "dracula"
100	}
101
102	if out.TimeFormat == "" {
103		out.TimeFormat = time.RFC3339
104	}
105
106	if out.RateLimitCount == 0 {
107		out.RateLimitCount = 10
108	}
109
110	if out.RateLimitInterval == "" {
111		out.RateLimitInterval = "1m"
112	}
113
114	if out.MaxStdinBytes == 0 {
115		out.MaxStdinBytes = 5 << 20 // 5MB
116	}
117
118	logger.Info(
119		"config",
120		"url", out.Url,
121		"data_dir", out.DataDir,
122		"host", out.Host,
123		"ssh_port", out.SshPort,
124		"web_port", out.WebPort,
125		"theme", out.Theme,
126		"time_format", out.TimeFormat,
127		"desc", out.Desc,
128		"rate_limit_count", out.RateLimitCount,
129		"rate_limit_interval", out.RateLimitInterval,
130		"max_stdin_bytes", out.MaxStdinBytes,
131	)
132
133	for _, pubkey := range out.AdminsStr {
134		logger.Info("admin", "pubkey", pubkey)
135	}
136
137	out.Logger = logger
138	return &out
139}