// Package preflight checks a prepared installation without creating application state. package preflight import ( "context" "encoding/json" "io" "os" "runtime" "strconv" "strings" "time" "github.com/aritmolab/thothii/tools/tht/internal/compose" ) const Protocol = 1 type Check struct { ID string `json:"id"` Outcome string `json:"outcome"` Field string `json:"field"` Action string `json:"action"` } type Report struct { SchemaVersion int `json:"schema_version"` OK bool `json:"ok"` Checks []Check `json:"checks"` } func NewReport() Report { return Report{SchemaVersion: 1, OK: true, Checks: []Check{}} } func (r *Report) Add(id, outcome, field, action string) { r.Checks = append(r.Checks, Check{id, outcome, field, action}) if outcome == "error" { r.OK = false } } func (r *Report) Merge(other Report) { r.Checks = append(r.Checks, other.Checks...) r.OK = r.OK && other.OK } func (r Report) JSON() string { data, _ := json.Marshal(r); return string(data) } type Runner interface { Run(context.Context, []string, io.Reader) (compose.Result, error) } func docker(ctx context.Context, runner Runner, args ...string) (compose.Result, error) { bound, cancel := context.WithTimeout(ctx, 15*time.Second) defer cancel() return runner.Run(bound, args, nil) } type Requirements struct { CPUs int `json:"cpus"` MemoryBytes uint64 `json:"memory_bytes"` DiskBytes uint64 `json:"disk_bytes"` } func DefaultRequirements() Requirements { return Requirements{2, 4 << 30, 10 << 30} } type Host struct { OS string Arch string Kernel string Distribution string FreeBytes uint64 } func InspectHost(directory string) (Host, error) { h := Host{OS: runtime.GOOS, Arch: runtime.GOARCH} kernel, _ := os.ReadFile("/proc/sys/kernel/osrelease") h.Kernel = strings.ToLower(string(kernel)) distribution, _ := os.ReadFile("/etc/os-release") h.Distribution = strings.ToLower(string(distribution)) var err error h.FreeBytes, err = freeBytes(directory) return h, err } func CheckHost(ctx context.Context, runner Runner, host Host, minimum Requirements) Report { r := NewReport() check := func(id string, ok bool, action string) { outcome := "passed" if !ok { outcome = "error" } r.Add(id, outcome, "host", action) } check("host-platform", (host.OS == "linux" || host.OS == "darwin") && (host.Arch == "amd64" || host.Arch == "arm64"), "Use the Linux executable in Ubuntu WSL2/Omarchy, or the matching macOS executable; native Windows is not the installation path.") if strings.Contains(strings.ToLower(host.Kernel), "microsoft") { check("wsl2", strings.Contains(strings.ToLower(host.Kernel), "wsl2") && strings.Contains(host.Distribution, "ubuntu"), "Use Ubuntu WSL2 and enable Docker Desktop integration for that distribution.") } info, err := docker(ctx, runner, "info", "--format", "{{json .}}") var parsed struct { OSType string Architecture string NCPU int MemTotal uint64 } good := err == nil && json.Unmarshal([]byte(info.Stdout), &parsed) == nil check("docker-daemon", good && parsed.OSType == "linux", "Start a reachable Linux Docker daemon for this user.") arch := parsed.Architecture if arch == "x86_64" { arch = "amd64" } if arch == "aarch64" { arch = "arm64" } check("docker-architecture", good && arch == host.Arch, "Use a Linux Docker daemon matching the host architecture; emulation is not certified.") check("docker-resources", good && parsed.NCPU >= minimum.CPUs && parsed.MemTotal >= minimum.MemoryBytes, "Allocate at least the release CPU and memory minimum to Docker.") check("installation-disk", host.FreeBytes >= minimum.DiskBytes && host.FreeBytes > 0, "Provide the release minimum free space on the installation filesystem.") result, err := docker(ctx, runner, "compose", "version", "--short") parts := strings.Split(strings.TrimPrefix(strings.TrimSpace(result.Stdout), "v"), ".") major, _ := strconv.Atoi(parts[0]) minor := 0 if len(parts) > 1 { minor, _ = strconv.Atoi(parts[1]) } check("docker-compose", err == nil && (major > 2 || (major == 2 && minor >= 24)), "Install Docker Compose v2.24 or newer.") r.Add("daemon-storage", "warning", "host", "Host disk capacity does not measure a Docker Desktop VM disk; reserve equivalent Docker storage and verify volume allocation during setup.") return r }