config.go
⎇
Raw
1// Package config liest die gesamte Konfiguration aus Umgebungsvariablen.
2package config
3
4import (
5 "errors"
6 "fmt"
7 "log/slog"
8 "os"
9 "path/filepath"
10 "strconv"
11 "strings"
12)
13
14type Config struct {
15 Port int
16 DataDir string
17 DBPath string
18 MediaDir string
19 BackupDir string
20 BackupKeep int
21 Username string
22 PasswordHash string
23 PracticeName string
24 BaseURL string
25 Seed bool
26 LogLevel slog.Level
27 LogJSON bool
28}
29
30func (c *Config) IsHTTPS() bool { return strings.HasPrefix(c.BaseURL, "https://") }
31
32func get(k, def string) string {
33 if v, ok := os.LookupEnv("PHYSIOBASE_" + k); ok && v != "" {
34 return v
35 }
36 return def
37}
38
39// Load liest die Konfiguration. Ohne Benutzername und Passwort-Hash startet die
40// App nicht – es gibt keinen Modus ohne Anmeldung.
41func Load() (*Config, error) {
42 c := &Config{
43 DataDir: get("DATA_DIR", "./data"),
44 Username: get("USERNAME", ""),
45 PasswordHash: get("PASSWORD_HASH", ""),
46 PracticeName: get("PRACTICE_NAME", "PhysioBase"),
47 BaseURL: strings.TrimRight(get("BASE_URL", ""), "/"),
48 }
49 c.DBPath = get("DB_PATH", filepath.Join(c.DataDir, "physiobase.db"))
50 c.MediaDir = get("MEDIA_DIR", filepath.Join(c.DataDir, "medien"))
51 c.BackupDir = get("BACKUP_DIR", filepath.Join(c.DataDir, "backup"))
52
53 var err error
54 if c.Port, err = strconv.Atoi(get("PORT", "8080")); err != nil || c.Port < 1 || c.Port > 65535 {
55 return nil, fmt.Errorf("PHYSIOBASE_PORT ungültig: %q", get("PORT", ""))
56 }
57 if c.BackupKeep, err = strconv.Atoi(get("BACKUP_KEEP", "14")); err != nil {
58 return nil, fmt.Errorf("PHYSIOBASE_BACKUP_KEEP ungültig: %w", err)
59 }
60 if c.Seed, err = strconv.ParseBool(get("SEED", "1")); err != nil {
61 return nil, fmt.Errorf("PHYSIOBASE_SEED ungültig: %w", err)
62 }
63 if err := c.LogLevel.UnmarshalText([]byte(get("LOG_LEVEL", "info"))); err != nil {
64 return nil, fmt.Errorf("PHYSIOBASE_LOG_LEVEL ungültig: %w", err)
65 }
66 switch f := get("LOG_FORMAT", "text"); f {
67 case "text":
68 case "json":
69 c.LogJSON = true
70 default:
71 return nil, fmt.Errorf("PHYSIOBASE_LOG_FORMAT muss text oder json sein, nicht %q", f)
72 }
73
74 if c.Username == "" || c.PasswordHash == "" {
75 return nil, errors.New("PHYSIOBASE_USERNAME und PHYSIOBASE_PASSWORD_HASH sind Pflicht (Hash erzeugen: physiobase hash-password)")
76 }
77 if !strings.HasPrefix(c.PasswordHash, "$2") {
78 return nil, errors.New("PHYSIOBASE_PASSWORD_HASH ist kein bcrypt-Hash")
79 }
80 return c, nil
81}
82