123 lines
4.3 KiB
Go
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
|
|
}
|