Cut virtualization from the app: out of scope
Ham Lab is meant to be lightweight; VMs, passthrough and their consoles are too much. Removes the Virtualization tab, the incus package, the /api/virt routes, the instance.* sensors and their alert default, the virt-export command, the VGA console and the vendored spice-html5, and VIRTUALIZATION.md. A saved instance-down rule is dropped on load. Everything removed is kept, as it was, on the virtualization branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a2674bea6a
commit
d70ddb6d67
+6
-19
@@ -71,9 +71,6 @@ type alertSaved struct {
|
||||
Channels []AlertChannel `json:"channels"`
|
||||
Events []AlertEvent `json:"events"`
|
||||
Seeded bool `json:"seeded"`
|
||||
// SawInstanceRule marks that the instance-down default has been offered once; set so that
|
||||
// removing it stays removed.
|
||||
SawInstanceRule bool `json:"saw_instance_rule,omitempty"`
|
||||
// Firing is what was firing when the hub last looked, so a restart (every deploy is one)
|
||||
// carries on where it was and doesn't announce the same alerts all over again.
|
||||
Firing map[string]alertKept `json:"firing,omitempty"`
|
||||
@@ -105,7 +102,6 @@ var defaultRules = []AlertRule{
|
||||
{ID: "machine-offline", Name: "Machine not answering", Sensor: "hamlab.reachable", Op: "==", Value: false, ForSec: 120, Severity: "crit", Enabled: true},
|
||||
{ID: "container-down", Name: "Container down", Sensor: "container.*.down", Op: "==", Value: true, ForSec: 120, Severity: "crit", Enabled: true},
|
||||
{ID: "container-unhealthy", Name: "Container unhealthy", Sensor: "container.*.health", Op: "==", Value: "unhealthy", ForSec: 180, Severity: "warn", Enabled: true},
|
||||
{ID: "instance-down", Name: "VM or container down", Sensor: "instance.*.down", Op: "==", Value: true, ForSec: 120, Severity: "crit", Enabled: true},
|
||||
{ID: "service-failed", Name: "A service has failed", Sensor: "services.failed", Op: ">", Value: 0.0, ForSec: 300, Severity: "warn", Enabled: true},
|
||||
{ID: "disk-full", Name: "Filesystem nearly full", Sensor: "fs.*.used_pct", Op: ">=", Value: 90.0, ForSec: 300, Severity: "warn", Enabled: true},
|
||||
{ID: "pool-unhealthy", Name: "ZFS pool not healthy", Sensor: "zfs.*.healthy", Op: "==", Value: false, ForSec: 60, Severity: "crit", Enabled: true},
|
||||
@@ -126,23 +122,14 @@ func newAlertEngine(s *Server) *alertEngine {
|
||||
for key, k := range e.saved.Firing {
|
||||
e.state[key] = &alertState{pending: time.UnixMilli(k.Since), firing: true, since: time.UnixMilli(k.Since), value: k.Value}
|
||||
}
|
||||
// a default added after an install was seeded joins its rules once; removing it afterwards sticks
|
||||
if e.saved.Seeded && !e.saved.SawInstanceRule {
|
||||
has := false
|
||||
for _, r := range e.saved.Rules {
|
||||
if r.ID == "instance-down" {
|
||||
has = true
|
||||
}
|
||||
// the Virtualization tab's default went with the tab (2026-10-15): nothing publishes instance.* now
|
||||
kept := e.saved.Rules[:0]
|
||||
for _, r := range e.saved.Rules {
|
||||
if r.ID != "instance-down" {
|
||||
kept = append(kept, r)
|
||||
}
|
||||
if !has {
|
||||
for _, d := range defaultRules {
|
||||
if d.ID == "instance-down" {
|
||||
e.saved.Rules = append(e.saved.Rules, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
e.saved.SawInstanceRule = true
|
||||
}
|
||||
e.saved.Rules = kept
|
||||
for i, r := range e.saved.Rules { // the first day's default watched "not running", which a container stopped on purpose also is
|
||||
if r.ID == "container-down" && r.Sensor == "container.*.running" {
|
||||
e.saved.Rules[i].Name, e.saved.Rules[i].Sensor, e.saved.Rules[i].Value = "Container down", "container.*.down", true
|
||||
|
||||
@@ -34,7 +34,6 @@ import (
|
||||
"hamlab/internal/cron"
|
||||
"hamlab/internal/files"
|
||||
"hamlab/internal/hardware"
|
||||
"hamlab/internal/incus"
|
||||
"hamlab/internal/media"
|
||||
"hamlab/internal/metrics"
|
||||
"hamlab/internal/network"
|
||||
@@ -77,7 +76,6 @@ type Server struct {
|
||||
update updateState
|
||||
events eventLog
|
||||
kvms kvmProxies
|
||||
virt *incus.Manager
|
||||
fsLocal files.FS
|
||||
fsSudo files.FS // nil without passwordless sudo
|
||||
thumbs *thumbSvc
|
||||
@@ -113,7 +111,6 @@ func New(cfg *config.Config, sessions *auth.Sessions, mgr *term.Manager, col *me
|
||||
if s.user == "" {
|
||||
s.user = "?"
|
||||
}
|
||||
s.virt = incus.New(cfg.DataDir(), s.user, version)
|
||||
s.migrateUsers()
|
||||
// The speed test and the outside view. Its history outlives a restart, and its last result
|
||||
// is published as sensors, so the line is graphable and alertable like anything else.
|
||||
@@ -237,37 +234,6 @@ func (s *Server) routes() *http.ServeMux {
|
||||
m.HandleFunc("GET /api/docker/containers/{id}/events", s.auth(s.dockerEvents))
|
||||
m.HandleFunc("POST /api/docker/containers/{id}/exec", s.auth(s.dockerExec))
|
||||
m.HandleFunc("POST /api/docker/containers/{id}/limits", s.auth(s.dockerUpdate))
|
||||
m.HandleFunc("GET /api/virt", s.auth(s.virtOverview))
|
||||
m.HandleFunc("POST /api/virt/install", s.auth(s.virtInstall))
|
||||
m.HandleFunc("GET /api/virt/images/remote", s.auth(s.virtRemoteImages))
|
||||
m.HandleFunc("POST /api/virt/images/download", s.auth(s.virtImageDownload))
|
||||
m.HandleFunc("POST /api/virt/images/import", s.auth(s.virtImageImport))
|
||||
m.HandleFunc("POST /api/virt/images/publish", s.auth(s.virtImagePublish))
|
||||
m.HandleFunc("POST /api/virt/images/delete", s.auth(s.virtImageDelete))
|
||||
m.HandleFunc("POST /api/virt/instances", s.auth(s.virtCreate))
|
||||
m.HandleFunc("GET /api/virt/instances/{name}", s.auth(s.virtInstance))
|
||||
m.HandleFunc("POST /api/virt/instances/{name}/rename", s.auth(s.virtRename))
|
||||
m.HandleFunc("PUT /api/virt/instances/{name}/config", s.auth(s.virtConfig))
|
||||
m.HandleFunc("POST /api/virt/instances/{name}/snapshots", s.auth(s.virtSnapshotCreate))
|
||||
m.HandleFunc("POST /api/virt/instances/{name}/snapshots/{snap}/{action}", s.auth(s.virtSnapshotAction))
|
||||
m.HandleFunc("POST /api/virt/instances/from-iso", s.auth(s.virtFromISO))
|
||||
m.HandleFunc("GET /api/virt/instances/{name}/vga", s.auth(s.virtVGA))
|
||||
m.HandleFunc("POST /api/virt/instances/{name}/iso", s.auth(s.virtISOAttach))
|
||||
m.HandleFunc("POST /api/virt/instances/{name}/iso/detach", s.auth(s.virtISODetach))
|
||||
m.HandleFunc("POST /api/virt/instances/{name}/mounts", s.auth(s.virtMountAdd))
|
||||
m.HandleFunc("POST /api/virt/instances/{name}/mounts/remove", s.auth(s.virtMountRemove))
|
||||
m.HandleFunc("POST /api/virt/instances/{name}/{action}", s.auth(s.virtAction))
|
||||
m.HandleFunc("POST /api/virt/isos/fetch", s.auth(s.virtISOFetch))
|
||||
m.HandleFunc("POST /api/virt/isos/import", s.auth(s.virtISOImport))
|
||||
m.HandleFunc("POST /api/virt/isos/delete", s.auth(s.virtISODelete))
|
||||
m.HandleFunc("POST /api/virt/isos/rename", s.auth(s.virtISORename))
|
||||
m.HandleFunc("GET /api/virt/exports", s.auth(s.virtExports))
|
||||
m.HandleFunc("PUT /api/virt/exports", s.auth(s.virtExportsPut))
|
||||
m.HandleFunc("POST /api/virt/exports/run", s.auth(s.virtExportsRun))
|
||||
m.HandleFunc("POST /api/virt/exports/delete", s.auth(s.virtExportDelete))
|
||||
m.HandleFunc("GET /api/virt/sshkeys", s.auth(s.virtSSHKeys))
|
||||
m.HandleFunc("POST /api/virt/pools", s.auth(s.virtPoolCreate))
|
||||
m.HandleFunc("POST /api/virt/pools/delete", s.auth(s.virtPoolDelete))
|
||||
m.HandleFunc("GET /api/alerts", s.auth(s.alertsGet))
|
||||
m.HandleFunc("PUT /api/alerts", s.auth(s.alertsPut))
|
||||
m.HandleFunc("POST /api/alerts/test", s.auth(s.alertsTest))
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"hamlab/internal/incus"
|
||||
"hamlab/internal/priv"
|
||||
"hamlab/internal/sensors"
|
||||
"hamlab/internal/storage"
|
||||
@@ -55,7 +54,6 @@ func newStatusSensors(s *Server) *statusSensors {
|
||||
p := &statusSensors{s: s, by: map[string][]sensors.Sensor{}, hist: map[string]*sensors.Series{}, acc: map[string]*bucket{}}
|
||||
go p.loop("services", servicesEvery, p.services)
|
||||
go p.loop("containers", containersEvery, p.containers)
|
||||
go p.loop("instances", containersEvery, p.instances)
|
||||
go p.loop("updates", updatesEvery, p.updates)
|
||||
go p.loop("zfs", zfsEvery, p.zfs)
|
||||
go p.loop("smart", smartEvery, p.smart)
|
||||
@@ -322,43 +320,6 @@ type cpuMark struct {
|
||||
t int64
|
||||
}
|
||||
|
||||
// instances are Incus's containers and VMs. "Down" means it should be up and is not: an
|
||||
// instance marked to start with the machine that is stopped, or one in Error — a stopped
|
||||
// instance with no such mark was stopped on purpose, as with Docker containers.
|
||||
func (p *statusSensors) instances(ctx context.Context, t int64) []sensors.Sensor {
|
||||
if !incus.Installed() {
|
||||
return nil
|
||||
}
|
||||
list, err := p.s.virt.InstancesFor(ctx, "sensors")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var res []sensors.Sensor
|
||||
var running, total float64
|
||||
for _, in := range list {
|
||||
total++
|
||||
id := "instance." + sensors.Seg(in.Name)
|
||||
l := map[string]string{"instance": in.Name, "type": in.Type, "image": in.Image}
|
||||
up := in.Status == "Running"
|
||||
if up {
|
||||
running++
|
||||
}
|
||||
down := in.Status == "Error" || (!up && in.Config["boot.autostart"] == "true")
|
||||
res = append(res,
|
||||
sensors.Sensor{ID: id + ".running", Name: in.Name, Kind: "state", Value: up, T: t, Labels: l},
|
||||
sensors.Sensor{ID: id + ".down", Name: in.Name + " down", Kind: "state", Value: down, T: t, Labels: l},
|
||||
sensors.Sensor{ID: id + ".state", Name: in.Name + " state", Kind: "state", Value: strings.ToLower(in.Status), T: t, Labels: l})
|
||||
if up {
|
||||
res = append(res,
|
||||
sensors.Sensor{ID: id + ".cpu", Name: in.Name + " CPU", Kind: "usage", Unit: "%", Value: float64(int(in.CPUPct*10+0.5)) / 10, T: t, Min: sensors.F(0), Labels: l},
|
||||
sensors.Sensor{ID: id + ".mem", Name: in.Name + " memory", Kind: "bytes", Unit: "B", Value: float64(in.MemUsage), T: t, Labels: l})
|
||||
}
|
||||
}
|
||||
return append(res,
|
||||
sensors.Sensor{ID: "instances.total", Name: "Instances", Kind: "count", Value: total, T: t},
|
||||
sensors.Sensor{ID: "instances.running", Name: "Running instances", Kind: "count", Value: running, T: t})
|
||||
}
|
||||
|
||||
func (p *statusSensors) updates(ctx context.Context, t int64) []sensors.Sensor {
|
||||
st := p.s.updates.Status(ctx)
|
||||
if st.Manager == "" {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user