Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
Some checks failed
Test / test (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
This commit is contained in:
189
internal/fleet/tiers.go
Normal file
189
internal/fleet/tiers.go
Normal file
@@ -0,0 +1,189 @@
|
||||
package fleet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TierType identifies one of 14 Linux deploy mechanisms.
|
||||
type TierType string
|
||||
|
||||
const (
|
||||
TierSSHKey TierType = "ssh_key"
|
||||
TierCurlBash TierType = "curl_bash"
|
||||
TierAnsiblePull TierType = "ansible_pull"
|
||||
TierPodmanRootless TierType = "podman_rootless"
|
||||
TierSystemdTransient TierType = "systemd_transient"
|
||||
TierSnapFlatpak TierType = "snap_flatpak"
|
||||
TierLANCachePeer TierType = "lan_cache_peer"
|
||||
TierDNSTXT TierType = "dns_txt"
|
||||
TierMTLSWireGuard TierType = "mtls_wireguard"
|
||||
TierImmutableOCI TierType = "immutable_oci"
|
||||
TierNixFlake TierType = "nix_flake"
|
||||
TierErasureReasm TierType = "erasure_reassembly"
|
||||
TierFleetTorrent TierType = "fleet_torrent"
|
||||
TierOfflineBundle TierType = "offline_contingency"
|
||||
)
|
||||
|
||||
// DefaultTierOrder returns the canonical 14-tier Linux deploy sequence.
|
||||
func DefaultTierOrder() []TierType {
|
||||
return []TierType{
|
||||
TierSSHKey,
|
||||
TierCurlBash,
|
||||
TierAnsiblePull,
|
||||
TierPodmanRootless,
|
||||
TierSystemdTransient,
|
||||
TierSnapFlatpak,
|
||||
TierLANCachePeer,
|
||||
TierDNSTXT,
|
||||
TierMTLSWireGuard,
|
||||
TierImmutableOCI,
|
||||
TierNixFlake,
|
||||
TierErasureReasm,
|
||||
TierFleetTorrent,
|
||||
TierOfflineBundle,
|
||||
}
|
||||
}
|
||||
|
||||
// TierSlot maps 1-based tier index to type.
|
||||
func TierSlot(n int) (TierType, error) {
|
||||
order := DefaultTierOrder()
|
||||
if n < 1 || n > len(order) {
|
||||
return "", fmt.Errorf("tier %d out of range 1-%d", n, len(order))
|
||||
}
|
||||
return order[n-1], nil
|
||||
}
|
||||
|
||||
// TierExecutor runs deploy phases for a single tier.
|
||||
type TierExecutor struct {
|
||||
Store *Store
|
||||
HostID string
|
||||
Report *ReconReport
|
||||
}
|
||||
|
||||
// TierResult captures outcome of a tier attempt.
|
||||
type TierResult struct {
|
||||
Tier int `json:"tier"`
|
||||
Type TierType `json:"type"`
|
||||
Phase string `json:"phase"`
|
||||
Status string `json:"status"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Elapsed time.Duration `json:"elapsed_ms"`
|
||||
}
|
||||
|
||||
// ExecuteTier runs recon → patch_first gate → deploy for one tier slot.
|
||||
func (e *TierExecutor) ExecuteTier(ctx context.Context, tierNum int) (*TierResult, error) {
|
||||
tierType, err := TierSlot(tierNum)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
result := &TierResult{Tier: tierNum, Type: tierType, Phase: "recon", Status: "running"}
|
||||
|
||||
if e.Store != nil && e.Report != nil {
|
||||
phenotype := PhenotypeFromRecon(e.Report)
|
||||
if skip, _ := e.Store.ShouldSkipTier(ctx, phenotype, tierNum); skip {
|
||||
result.Phase = "atlas_skip"
|
||||
result.Status = "skipped"
|
||||
_ = e.Store.LogLOTL(ctx, e.HostID, tierNum, "atlas_skip", "skipped", "failure atlas immune", ReconJSON(e.Report))
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Recon phase (read-only, already done at executor init)
|
||||
_ = e.Store.LogLOTL(ctx, e.HostID, tierNum, "recon", "success", "", ReconJSON(e.Report))
|
||||
|
||||
// Patch-first gate
|
||||
result.Phase = "patch_first"
|
||||
if !e.passPatchGate(tierType) {
|
||||
result.Status = "skipped"
|
||||
result.Error = "patch_first gate failed"
|
||||
_ = e.Store.LogLOTL(ctx, e.HostID, tierNum, "patch_first", "skipped", result.Error, "")
|
||||
_ = e.Store.RecordFailure(ctx, PhenotypeFromRecon(e.Report), tierNum)
|
||||
return result, nil
|
||||
}
|
||||
_ = e.Store.LogLOTL(ctx, e.HostID, tierNum, "patch_first", "success", "", "")
|
||||
|
||||
// Deploy phase (simulated success paths per tier capability)
|
||||
result.Phase = "deploy"
|
||||
deployErr := e.deploy(ctx, tierType)
|
||||
if deployErr != nil {
|
||||
result.Status = "failed"
|
||||
result.Error = deployErr.Error()
|
||||
_ = e.Store.LogLOTL(ctx, e.HostID, tierNum, "deploy", "failed", result.Error, "")
|
||||
_ = e.Store.RecordFailure(ctx, PhenotypeFromRecon(e.Report), tierNum)
|
||||
} else {
|
||||
result.Status = "success"
|
||||
_ = e.Store.LogLOTL(ctx, e.HostID, tierNum, "deploy", "success", "", "")
|
||||
_ = e.Store.RecordWin(ctx, PhenotypeFromRecon(e.Report), tierNum)
|
||||
}
|
||||
|
||||
result.Elapsed = time.Since(start)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (e *TierExecutor) passPatchGate(t TierType) bool {
|
||||
if e.Report == nil {
|
||||
return true
|
||||
}
|
||||
switch t {
|
||||
case TierPodmanRootless, TierImmutableOCI:
|
||||
return e.Report.PodmanAvailable || e.Report.DockerAvailable
|
||||
case TierNixFlake:
|
||||
return commandExists("nix")
|
||||
case TierMTLSWireGuard:
|
||||
return commandExists("wg")
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (e *TierExecutor) deploy(ctx context.Context, t TierType) error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
switch t {
|
||||
case TierPodmanRootless:
|
||||
if e.Report != nil && !e.Report.PodmanAvailable {
|
||||
return fmt.Errorf("podman not available")
|
||||
}
|
||||
case TierImmutableOCI:
|
||||
if e.Report == nil || (!e.Report.PodmanAvailable && !e.Report.DockerAvailable) {
|
||||
return fmt.Errorf("no container runtime")
|
||||
}
|
||||
case TierNixFlake:
|
||||
if !commandExists("nix") {
|
||||
return fmt.Errorf("nix not installed")
|
||||
}
|
||||
}
|
||||
// Authorized deploy tiers succeed when gates pass; actual spawn is agent-side.
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunTierChain executes tiers in order until one succeeds or all fail.
|
||||
func RunTierChain(ctx context.Context, store *Store, hostID string, order []int) ([]*TierResult, error) {
|
||||
report, err := RunRecon()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exec := &TierExecutor{Store: store, HostID: hostID, Report: report}
|
||||
var results []*TierResult
|
||||
|
||||
for _, tierNum := range order {
|
||||
res, err := exec.ExecuteTier(ctx, tierNum)
|
||||
if err != nil {
|
||||
return results, err
|
||||
}
|
||||
results = append(results, res)
|
||||
if res.Status == "success" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
Reference in New Issue
Block a user