feat(config): Daemon-Port server.port mit Env-Override TALK_PORT (Default 7800)

This commit is contained in:
m3ta-chiron committed 2026-10-18 17:10:26 +02:00
1 parent 3b44605234
commit 716f6b2fc9
2 files changed
+94 -3

No files matched your search

+30 -3
View File
@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
) )
@@ -16,13 +17,21 @@ const (
SinkNull = "null" SinkNull = "null"
) )
// DefaultServerPort ist der Default-Port des Daemon-HTTP-Servers auf
// 127.0.0.1 (Task-04-Brief: „Port Default 7800").
const DefaultServerPort = 7800
// Config ist der effektiv geladene Konfigurations-Stand von talk. Task 02 // Config ist der effektiv geladene Konfigurations-Stand von talk. Task 02
// führt audio.sink; Task 03 ergänzt den Provider-Pin, Provider-Endpunkte // führt audio.sink; Task 03 ergänzt den Provider-Pin, Provider-Endpunkte
// und das Voice-Mapping; spätere Tasks ergänzen weitere Felder (server, // und das Voice-Mapping; Task 04 den Daemon-Port (server.port, TALK_PORT);
// queue …), ohne die Auflösungsreihenfolge zu ändern. // spätere Tasks ergänzen weitere Felder (queue …), ohne die Auflösungs-
// reihenfolge zu ändern.
type Config struct { type Config struct {
Audio Audio `toml:"audio"` Audio Audio `toml:"audio"`
// Server steuert den Daemon-HTTP-Server (talkd) auf 127.0.0.1.
Server Server `toml:"server"`
// Provider pinnt die Kette auf genau einen Provider (z. B. // Provider pinnt die Kette auf genau einen Provider (z. B.
// `provider = "elevenlabs"`); leer = komplette Default-Kette // `provider = "elevenlabs"`); leer = komplette Default-Kette
// (elevenlabs → openai → edge → osbuiltin). Das CLI-Flag --provider // (elevenlabs → openai → edge → osbuiltin). Das CLI-Flag --provider
@@ -48,6 +57,14 @@ type Audio struct {
Sink string `toml:"sink"` Sink string `toml:"sink"`
} }
// Server sind die Netz-Einstellungen des Daemons (nur Loopback; Binding
// auf 127.0.0.1 macht cmd/talkd, die Config liefert nur den Port).
type Server struct {
// Port des Daemon-HTTP-Servers auf 127.0.0.1 — config
// `[server] port = 7800`, Env-Override TALK_PORT, Default 7800.
Port int `toml:"port"`
}
// ProviderSettings sind die per Provider konfigurierbaren Endpunkte und // ProviderSettings sind die per Provider konfigurierbaren Endpunkte und
// Modelle (TOML `[providers.<name>]`). Leer = eingebaute Defaults; die // Modelle (TOML `[providers.<name>]`). Leer = eingebaute Defaults; die
// Env-Overrides TALK_ELEVENLABS_ENDPOINT / TALK_OPENAI_ENDPOINT / // Env-Overrides TALK_ELEVENLABS_ENDPOINT / TALK_OPENAI_ENDPOINT /
@@ -62,7 +79,10 @@ type ProviderSettings struct {
// Defaults liefert die eingebaute Default-Konfiguration (unterste Schicht // Defaults liefert die eingebaute Default-Konfiguration (unterste Schicht
// der Auflösung). // der Auflösung).
func Defaults() Config { func Defaults() Config {
return Config{Audio: Audio{Sink: SinkOto}} return Config{
Audio: Audio{Sink: SinkOto},
Server: Server{Port: DefaultServerPort},
}
} }
// Options steuert Load. Alle Felder sind injizierbar, damit die Unit-Tests // Options steuert Load. Alle Felder sind injizierbar, damit die Unit-Tests
@@ -116,6 +136,13 @@ func applyEnv(cfg Config, getenv func(string) string) (Config, error) {
if v := getenv("TALK_PROVIDER"); v != "" { if v := getenv("TALK_PROVIDER"); v != "" {
cfg.Provider = v cfg.Provider = v
} }
if v := getenv("TALK_PORT"); v != "" {
port, err := strconv.Atoi(v)
if err != nil || port < 1 || port > 65535 {
return cfg, fmt.Errorf("TALK_PORT: ungültiger Wert %q (1–65535)", v)
}
cfg.Server.Port = port
}
switch cfg.Audio.Sink { switch cfg.Audio.Sink {
case SinkOto, SinkNull: case SinkOto, SinkNull:
return cfg, nil return cfg, nil
+64
View File
@@ -288,3 +288,67 @@ de = "de-DE-KatjaNeural"
} }
}) })
} }
// TestLoadServerPort deckt die Task-04-Felder ab: Daemon-Port per Defaults
// (7800), TOML [server] port, Env-Override TALK_PORT samt Validierung.
func TestLoadServerPort(t *testing.T) {
writeTOML := func(t *testing.T, content string) string {
t.Helper()
path := filepath.Join(t.TempDir(), "config.toml")
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatal(err)
}
return path
}
t.Run("Default 7800", func(t *testing.T) {
cfg, err := Load(Options{
File: filepath.Join(t.TempDir(), "gibt-es-nicht.toml"),
Getenv: env(nil),
})
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.Server.Port != DefaultServerPort {
t.Errorf("Server.Port = %d, want Default %d", cfg.Server.Port, DefaultServerPort)
}
})
t.Run("TOML überschreibt Default", func(t *testing.T) {
cfg, err := Load(Options{
File: writeTOML(t, "[server]\nport = 7999\n"),
Getenv: env(nil),
})
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.Server.Port != 7999 {
t.Errorf("Server.Port = %d, want 7999 aus TOML", cfg.Server.Port)
}
})
t.Run("TALK_PORT schlägt Datei", func(t *testing.T) {
cfg, err := Load(Options{
File: writeTOML(t, "[server]\nport = 7999\n"),
Getenv: env(map[string]string{"TALK_PORT": "7912"}),
})
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.Server.Port != 7912 {
t.Errorf("Server.Port = %d, want Env-Override 7912", cfg.Server.Port)
}
})
t.Run("ungültiger TALK_PORT-Wert ist klarer Fehler", func(t *testing.T) {
for _, bad := range []string{"null", "0", "-1", "70000"} {
_, err := Load(Options{
File: filepath.Join(t.TempDir(), "gibt-es-nicht.toml"),
Getenv: env(map[string]string{"TALK_PORT": bad}),
})
if err == nil || !strings.Contains(err.Error(), "TALK_PORT") {
t.Fatalf("TALK_PORT=%q: Fehler = %v, will klaren TALK_PORT-Fehler", bad, err)
}
}
})
}