162 lines
4.2 KiB
Go
162 lines
4.2 KiB
Go
// Package config — baca/tulis config tia (PRD §7.1).
|
|
//
|
|
// Sesuai prinsip ponytail: struct ini memuat subset schema PRD §7.2 yang
|
|
// SUDAH dipakai; field lain ditambahkan saat fiturnya benar-benar masuk
|
|
// (supaya config tidak pernah berisi opsi mati).
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type Config struct {
|
|
Version int `yaml:"version"`
|
|
StacksDir string `yaml:"stacks_dir"`
|
|
UI UI `yaml:"ui"`
|
|
Docker DockerCfg `yaml:"docker"`
|
|
}
|
|
|
|
// DockerCfg — pengaturan interaksi runtime (Docker ATAU Podman).
|
|
type DockerCfg struct {
|
|
Host string `yaml:"host"` // kosong = auto-detect (docker → podman)
|
|
ComposeCommand string `yaml:"compose_command"` // "auto" | "docker compose" | "podman compose" | …
|
|
}
|
|
|
|
type UI struct {
|
|
Language string `yaml:"language"` // id | en
|
|
ThemeMode string `yaml:"theme_mode"` // auto | dark | light | terminal
|
|
ThemeAccent string `yaml:"theme_accent"` // 14 warna aksen Catppuccin (validasi di theme)
|
|
SoundEnabled bool `yaml:"sound_enabled"`
|
|
SoundVolume int `yaml:"sound_volume"` // 0-100
|
|
SoundMuteOnSSH bool `yaml:"sound_mute_on_ssh"`
|
|
AnimationEnabled bool `yaml:"animation_enabled"`
|
|
LogTailLines int `yaml:"log_tail_lines"` // jumlah baris log yang disimpan
|
|
ShowTimestamps bool `yaml:"show_timestamps"` // timestamp di log kontainer
|
|
}
|
|
|
|
func Default() Config {
|
|
home, _ := os.UserHomeDir()
|
|
return Config{
|
|
Version: 1,
|
|
StacksDir: filepath.Join(home, ".tia", "stacks"),
|
|
UI: UI{
|
|
Language: "id",
|
|
ThemeMode: "auto",
|
|
ThemeAccent: "mauve",
|
|
SoundEnabled: true,
|
|
SoundVolume: 70,
|
|
SoundMuteOnSSH: true,
|
|
AnimationEnabled: true,
|
|
LogTailLines: 300,
|
|
ShowTimestamps: true,
|
|
},
|
|
Docker: DockerCfg{ComposeCommand: "auto"},
|
|
}
|
|
}
|
|
|
|
// Path — ~/.config/tia/config.yaml (mengikuti XDG_CONFIG_HOME).
|
|
func Path() string {
|
|
dir, err := os.UserConfigDir()
|
|
if err != nil {
|
|
dir = filepath.Join(os.Getenv("HOME"), ".config")
|
|
}
|
|
return filepath.Join(dir, "tia", "config.yaml")
|
|
}
|
|
|
|
// Load membaca config; file belum ada BUKAN error (memakai default).
|
|
func Load() (Config, error) { return LoadFrom(Path()) }
|
|
|
|
func LoadFrom(path string) (Config, error) {
|
|
c := Default()
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return c, nil
|
|
}
|
|
return c, err
|
|
}
|
|
if err := yaml.Unmarshal(b, &c); err != nil {
|
|
return Default(), fmt.Errorf("config: parse %s: %w", path, err)
|
|
}
|
|
c.normalize()
|
|
return c, nil
|
|
}
|
|
|
|
// Save menulis config secara atomik (temp + rename) — tidak pernah setengah jadi.
|
|
func Save(c Config) error { return SaveTo(Path(), c) }
|
|
|
|
func SaveTo(path string, c Config) error {
|
|
c.normalize()
|
|
b, err := yaml.Marshal(c)
|
|
if err != nil {
|
|
return fmt.Errorf("config: marshal: %w", err)
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
return err
|
|
}
|
|
tmp := path + ".tmp"
|
|
if err := os.WriteFile(tmp, b, 0o644); err != nil {
|
|
return err
|
|
}
|
|
if err := os.Rename(tmp, path); err != nil {
|
|
_ = os.Remove(tmp)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// normalize — validasi nilai (file config adalah batas kepercayaan:
|
|
// bisa diedit tangan; nilai rusak tidak boleh menjatuhkan aplikasi).
|
|
func (c *Config) normalize() {
|
|
switch c.UI.Language {
|
|
case "id", "en":
|
|
default:
|
|
c.UI.Language = "id"
|
|
}
|
|
switch c.UI.ThemeMode {
|
|
case "auto", "dark", "light", "terminal":
|
|
default:
|
|
c.UI.ThemeMode = "auto"
|
|
}
|
|
if c.UI.ThemeAccent == "" {
|
|
c.UI.ThemeAccent = "mauve"
|
|
}
|
|
if c.UI.SoundVolume < 0 {
|
|
c.UI.SoundVolume = 0
|
|
}
|
|
if c.UI.SoundVolume > 100 {
|
|
c.UI.SoundVolume = 100
|
|
}
|
|
if c.UI.LogTailLines < 10 {
|
|
c.UI.LogTailLines = 10
|
|
}
|
|
if c.UI.LogTailLines > 10000 {
|
|
c.UI.LogTailLines = 10000
|
|
}
|
|
if c.StacksDir == "" {
|
|
c.StacksDir = Default().StacksDir
|
|
}
|
|
if c.Version == 0 {
|
|
c.Version = 1
|
|
}
|
|
if c.Docker.ComposeCommand == "" {
|
|
c.Docker.ComposeCommand = "auto"
|
|
}
|
|
}
|
|
|
|
// ExpandPath — ubah "~/..." jadi absolut (config menyimpan path gaya user).
|
|
func ExpandPath(p string) string {
|
|
if p == "~" || strings.HasPrefix(p, "~/") {
|
|
home, err := os.UserHomeDir()
|
|
if err == nil {
|
|
return filepath.Join(home, strings.TrimPrefix(strings.TrimPrefix(p, "~"), "/"))
|
|
}
|
|
}
|
|
return p
|
|
}
|