feat(node): configure system environment through the desktop workflow
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:embed environment-profile.json
|
||||
var environmentProfile []byte
|
||||
|
||||
type EnvironmentRun struct {
|
||||
Schema string `json:"schema"`
|
||||
ProfileRevision string `json:"profile_revision"`
|
||||
RunID string `json:"run_id"`
|
||||
State string `json:"state"`
|
||||
StartedAt float64 `json:"started_at"`
|
||||
UpdatedAt float64 `json:"updated_at"`
|
||||
Steps []EnvironmentStep `json:"steps"`
|
||||
}
|
||||
type EnvironmentStep struct {
|
||||
ID string `json:"id"`
|
||||
State string `json:"state"`
|
||||
Detail string `json:"detail"`
|
||||
}
|
||||
type EnvironmentStatus struct {
|
||||
Profile json.RawMessage `json:"profile"`
|
||||
Run *EnvironmentRun `json:"run"`
|
||||
Available bool `json:"available"`
|
||||
}
|
||||
|
||||
func ReadEnvironment() EnvironmentStatus {
|
||||
result := readEnvironmentFile("/var/lib/mission-core-node-environment/last-run.json")
|
||||
if result.Run != nil && result.Run.State == "running" {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
state, err := exec.CommandContext(ctx, "/usr/bin/systemctl", "show", "--property=ActiveState", "--value", "mission-core-node-environment.service").Output()
|
||||
if err != nil || (string(state) != "activating\n" && string(state) != "active\n") {
|
||||
// A stopped job/reboot cannot leave yesterday's spinner running.
|
||||
result.Run.State = "interrupted"
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func readEnvironmentFile(path string) EnvironmentStatus {
|
||||
result := EnvironmentStatus{Profile: json.RawMessage(environmentProfile), Available: true}
|
||||
f, err := os.Open(path)
|
||||
if os.IsNotExist(err) {
|
||||
return result
|
||||
}
|
||||
if err != nil {
|
||||
result.Available = false
|
||||
return result
|
||||
}
|
||||
defer f.Close()
|
||||
info, err := f.Stat()
|
||||
if err != nil || !info.Mode().IsRegular() || info.Size() > 32768 {
|
||||
result.Available = false
|
||||
return result
|
||||
}
|
||||
var run EnvironmentRun
|
||||
decoder := json.NewDecoder(io.LimitReader(f, 32769))
|
||||
if decoder.Decode(&run) != nil || decoder.Decode(new(any)) != io.EOF || !validEnvironmentRun(run) {
|
||||
result.Available = false
|
||||
return result
|
||||
}
|
||||
result.Run = &run
|
||||
return result
|
||||
}
|
||||
|
||||
// Invalid or inconsistent progress cannot turn a failed setup into green UI.
|
||||
func validEnvironmentRun(run EnvironmentRun) bool {
|
||||
if run.Schema != "missioncore.node.environment/v1" || run.RunID == "" || len(run.RunID) > 64 || len(run.Steps) == 0 || len(run.Steps) > 32 || run.ProfileRevision == "" {
|
||||
return false
|
||||
}
|
||||
if run.State != "running" && run.State != "complete" && run.State != "error" {
|
||||
return false
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, step := range run.Steps {
|
||||
if step.ID == "" || len(step.ID) > 64 || seen[step.ID] || len(step.Detail) > 4096 {
|
||||
return false
|
||||
}
|
||||
seen[step.ID] = true
|
||||
switch step.State {
|
||||
case "pending", "running", "complete", "error", "blocked":
|
||||
default:
|
||||
return false
|
||||
}
|
||||
if run.State == "complete" && step.State != "complete" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user