Files
ThothII/tools/tht/internal/preflight/checks.go
T

123 lines
4.3 KiB
Go

// 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
}