phase 3-4: get_execution_status tool, approval creation, delete bin/ + homelab.go, update AGENTS.md
This commit is contained in:
@@ -1,137 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type cliHost struct {
|
||||
Name string
|
||||
LanIP string `yaml:"lan_ip"`
|
||||
Role string
|
||||
Kind string
|
||||
State string
|
||||
Mesh map[string]any
|
||||
Runs []string
|
||||
Services []map[string]any `yaml:"services_hosted"`
|
||||
}
|
||||
|
||||
func runHomelabSubcommand() {
|
||||
if len(os.Args) < 3 {
|
||||
fmt.Fprintln(os.Stderr, "usage: oikos homelab <list|whoami|ssh|secret>")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
ctxDir := os.Getenv("HOMELAB_CONTEXT_DIR")
|
||||
if ctxDir == "" {
|
||||
ctxDir = "."
|
||||
}
|
||||
|
||||
switch os.Args[2] {
|
||||
case "list":
|
||||
inv := loadInventory(ctxDir)
|
||||
fmt.Printf("%-25s %-8s %-18s %-6s %s\n", "HOST", "TYPE", "IP", "STATE", "ROLE")
|
||||
fmt.Println(strings.Repeat("-", 80))
|
||||
for name, host := range inv.Hosts {
|
||||
state := host.State
|
||||
if state == "" {
|
||||
state = "active"
|
||||
}
|
||||
fmt.Printf("%-25s %-8s %-18s %-6s %s\n", name, host.Kind, host.LanIP, state, host.Role)
|
||||
}
|
||||
|
||||
case "whoami":
|
||||
hostname, _ := os.Hostname()
|
||||
// Try short hostname and full hostname
|
||||
hostnameShort := strings.Split(hostname, ".")[0]
|
||||
inv := loadInventory(ctxDir)
|
||||
h, ok := inv.Hosts[hostnameShort]
|
||||
if !ok {
|
||||
// Try with full hostname
|
||||
h, ok = inv.Hosts[hostname]
|
||||
}
|
||||
if !ok {
|
||||
fmt.Printf("host: %s (not enrolled — no entry in inventory.yaml)\n", hostname)
|
||||
return
|
||||
}
|
||||
state := h.State
|
||||
if state == "" {
|
||||
state = "active"
|
||||
}
|
||||
fmt.Printf("host: %s kind: %s role: %s lan_ip: %s state: %s\n",
|
||||
hostnameShort, h.Kind, h.Role, h.LanIP, state)
|
||||
for _, svc := range h.Services {
|
||||
fmt.Printf(" service: %v\n", svc["name"])
|
||||
}
|
||||
|
||||
case "ssh":
|
||||
if len(os.Args) < 4 {
|
||||
fmt.Fprintln(os.Stderr, "usage: oikos homelab ssh <host>")
|
||||
os.Exit(1)
|
||||
}
|
||||
target := os.Args[3]
|
||||
inv := loadInventory(ctxDir)
|
||||
h, ok := inv.Hosts[target]
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, "unknown host: %s\n", target)
|
||||
os.Exit(1)
|
||||
}
|
||||
addr := h.LanIP
|
||||
if addr == "" {
|
||||
for _, m := range []string{"netbird", "tailscale"} {
|
||||
if mh, ok := h.Mesh[m].(map[string]any); ok {
|
||||
if ip, ok := mh["ip"].(string); ok && ip != "" {
|
||||
addr = ip
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if addr == "" {
|
||||
fmt.Fprintf(os.Stderr, "no address for %s\n", target)
|
||||
os.Exit(1)
|
||||
}
|
||||
user := "root"
|
||||
cmd := exec.Command("ssh", user+"@"+addr)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Run()
|
||||
|
||||
case "secret":
|
||||
if len(os.Args) < 4 {
|
||||
fmt.Fprintln(os.Stderr, "usage: oikos homelab secret <name>")
|
||||
os.Exit(1)
|
||||
}
|
||||
secretName := os.Args[3]
|
||||
secretFile := filepath.Join(ctxDir, "secrets", secretName+".yaml")
|
||||
// Shell out to sops for decryption
|
||||
cmd := exec.Command("sops", "-d", secretFile)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "decrypt %s: %v\n", secretName, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown homelab command: %s\n", os.Args[2])
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func loadInventory(ctxDir string) inventoryFile {
|
||||
data, _ := os.ReadFile(filepath.Join(ctxDir, "inventory.yaml"))
|
||||
var inv inventoryFile
|
||||
yaml.Unmarshal(data, &inv)
|
||||
return inv
|
||||
}
|
||||
|
||||
type inventoryFile struct {
|
||||
Hosts map[string]cliHost `yaml:"hosts"`
|
||||
}
|
||||
@@ -60,8 +60,6 @@ func main() {
|
||||
slog.Error("export failed", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
case "homelab":
|
||||
runHomelabSubcommand()
|
||||
case "api":
|
||||
if err := runAPI(ctx, cfg); err != nil {
|
||||
slog.Error("api failed", "error", err)
|
||||
@@ -114,15 +112,15 @@ Roles:
|
||||
migrate Run database migrations (forward-only, idempotent)
|
||||
seed Ingest seed YAML files into the database
|
||||
export Export DB state back to seed YAMLs (DR / version control)
|
||||
homelab Operator CLI: list, whoami, ssh, secret
|
||||
api Run the REST + MCP API server (Phase 2)
|
||||
scheduler Run the observe + act loop (Phase 3)
|
||||
notifier Run the notification service (Phase 3)
|
||||
api Run the REST + MCP API server
|
||||
scheduler Run the observe loop
|
||||
notifier Run the notification service (Matrix alerts)
|
||||
all Run all roles in one process (dev mode)
|
||||
secret Secret management (Phase 5)
|
||||
secret Secret management (Infisical)
|
||||
knowledge Convert wiki to knowledge seed (one-shot)
|
||||
version Print version info
|
||||
|
||||
The operator interface is Hermes (MCP agent) — no CLI needed.
|
||||
Environment:
|
||||
OIKOS_DATABASE_URL Postgres connection string
|
||||
OIKOS_API_LISTEN API listen address (default :8090)
|
||||
|
||||
Reference in New Issue
Block a user