56fcf8b399
orca init transforms from a bare mkdir into a full single-node cluster bootstrap. After `orca init`, `orca doctor` passes with zero FAILs on the bootstrap checks (CA, cert, db, localhost node). Changes: - migration 0006: nodes.kind + nodes.os nullable columns (REQ-049) - model.Node: Kind + OS fields + NodeKind constants (localhost|linux|proxmox) - NodeRepo: extended Insert/Get/List/Watch/scanNode for kind/os columns (NULL -> "" mapping); added GetByName + UpdateLastSeenAndOS helpers - internal/cli/osdetect.go: detectOS() from /etc/os-release ID= field (D-032); fallback to /usr/lib/os-release then "linux" - internal/cli/init.go: full bootstrap sequence (REQ-047, REQ-048): 1. MkdirAll namespace dir 2. store.Open (runs migrations 0001..0006) 3. security.CAInit (idempotent fast-path) 4. server cert gen if absent (D-036: skip if present) 5. detectOS from /etc/os-release 6. localhost node upsert (insert if new, refresh last_seen+os if exists) Idempotent re-run: no duplicate node, no cert regen, id/joined_at preserved - --json output: full bootstrap summary (namespace, db, ca_fp, cert_fp, os, node_id, steps array) - tests: init idempotency, osdetect parsing (ubuntu/debian/alpine/pve), kind/os round-trip, NULL->"" mapping, GetByName, UpdateLastSeenAndOS E2E smoke test: orca init -> 5 PASS / 0 WARN / 1 FAIL (network=daemon not running, expected); orca node list shows localhost node (os=ubuntu). ---ci--- project: orca phase: 1 milestone: v0.6 status: execute ---/ci---
61 lines
1.7 KiB
Go
61 lines
1.7 KiB
Go
package cli
|
|
|
|
import (
|
|
"bufio"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// osReleasePaths are checked in order for the os-release file. The
|
|
// freedesktop.org spec says /etc/os-release is the canonical path,
|
|
// with /usr/lib/os-release as a fallback for minimal containers that
|
|
// may not symlink the former.
|
|
var osReleasePaths = []string{"/etc/os-release", "/usr/lib/os-release"}
|
|
|
|
// detectOS reads /etc/os-release (then /usr/lib/os-release as a
|
|
// fallback) and returns the value of the ID= field. Returns "linux"
|
|
// (the generic fallback per D-032) if the file is missing, the ID
|
|
// field is absent, or the value is empty. Unknown ID values (e.g.
|
|
// "fedora", "arch") are returned verbatim — doctor os can warn on
|
|
// unknown values, but orca init must not fail.
|
|
func detectOS() string {
|
|
for _, p := range osReleasePaths {
|
|
data, err := os.ReadFile(p)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if id := parseOSReleaseID(data); id != "" {
|
|
return id
|
|
}
|
|
}
|
|
return "linux"
|
|
}
|
|
|
|
// parseOSReleaseID extracts the ID= value from os-release content.
|
|
// The format is shell-compatible KEY=VALUE lines; values may be
|
|
// double-quoted. Returns "" if ID is absent or empty.
|
|
func parseOSReleaseID(data []byte) string {
|
|
scanner := bufio.NewScanner(strings.NewReader(string(data)))
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
key, value, ok := strings.Cut(line, "=")
|
|
if !ok {
|
|
continue
|
|
}
|
|
key = strings.TrimSpace(key)
|
|
if key != "ID" {
|
|
continue
|
|
}
|
|
value = strings.TrimSpace(value)
|
|
// Strip surrounding double quotes (freedesktop spec allows quoted values).
|
|
if len(value) >= 2 && value[0] == '"' && value[len(value)-1] == '"' {
|
|
value = value[1 : len(value)-1]
|
|
}
|
|
return value
|
|
}
|
|
return ""
|
|
}
|