Files
tia-love/internal/cli/doctor.go
T

94 lines
2.8 KiB
Go

// Package cli — mode CLI pendamping (F-028).
// Seluruh logika inti di-share dengan TUI; paket ini hanya presentasi + kode keluar.
package cli
import (
"context"
"fmt"
"io"
"os"
"runtime"
"time"
"gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/audio"
"gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/config"
"gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/docker"
"gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/i18n"
"gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/stack"
)
// Doctor memeriksa kesehatan sistem dan menulis laporan ke w.
// Kode keluar: 0 = sehat (warning boleh), 1 = ada kegagalan fatal.
func Doctor(w io.Writer) int {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
fail := 0
// Docker — dependency inti.
switch cl, err := docker.New(); {
case err != nil:
fmt.Fprintf(w, "docker : %s %v\n", i18n.T("error.fail"), err)
fail++
default:
defer cl.Close()
if err := cl.Ping(ctx); err != nil {
fmt.Fprintf(w, "docker : %s %s\n", i18n.T("error.fail"),
i18n.Tf("doctor.dockerDaemonUnreachable", err))
fail++
} else if info, err := cl.Info(ctx); err != nil {
fmt.Fprintf(w, "docker : %s %v\n", i18n.T("error.warn"), err)
} else {
fmt.Fprintf(w, "docker : %s %s\n", i18n.T("error.ok"),
i18n.Tf("doctor.dockerOk", info.ServerVersion, info.APIVersion,
info.Running, info.Containers, info.Images))
}
}
// Audio — opsional; engine init async, tunggu maks 2 dtk.
eng := audio.New(audio.Options{Enabled: true, Volume: 70, MuteOnSSH: true})
eng.WaitReady(2 * time.Second)
fmt.Fprintf(w, "audio : %s\n", eng.Status())
fmt.Fprintf(w, "go : %s (%s/%s)\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
// Konfigurasi (dipakai compose, stack, dan output config di bawah).
cfg, cfgErr := config.Load()
// Compose — dipakai operasi stack (up/down/pull).
compose := docker.NewCompose(cfg.Docker.ComposeCommand)
if ver, err := compose.Version(ctx); err != nil {
fmt.Fprintf(w, "compose : %s %v\n", i18n.T("error.warn"), err)
} else {
fmt.Fprintf(w, "compose : %s %s\n", i18n.T("error.ok"), i18n.Tf("doctor.composeOk", ver, cfg.Docker.ComposeCommand))
}
cfgPath := config.Path()
switch {
case cfgErr != nil:
fmt.Fprintf(w, "config : %s %v\n", i18n.T("error.warn"), cfgErr)
default:
if _, err := os.Stat(cfgPath); err != nil {
fmt.Fprintf(w, "config : %s\n", i18n.Tf("doctor.configMissing", cfgPath))
} else {
fmt.Fprintf(w, "config : %s\n", cfgPath)
}
}
stacks := config.ExpandPath(cfg.StacksDir)
if st, err := os.Stat(stacks); err == nil && st.IsDir() {
n := 0
if list, err := stack.ScanFS(stacks); err == nil {
n = len(list)
}
fmt.Fprintf(w, "stacks : %s\n", i18n.Tf("doctor.stacksCount", stacks, n))
} else {
fmt.Fprintf(w, "stacks : %s\n", i18n.Tf("doctor.stacksMissing", stacks))
}
if fail > 0 {
return 1
}
return 0
}