Compare commits

...

41 Commits

Author SHA1 Message Date
Jeff Carr af7984a887 convert to httppb 2025-09-09 18:02:06 -05:00
Jeff Carr a29a774aa4 something, something, GUI codebase 2025-09-09 05:41:01 -05:00
Jeff Carr d00e96e354 cleanups 2025-09-08 23:14:04 -05:00
Jeff Carr 0c4a91054f ConfigSave() and upgrade all buttons 2025-09-03 04:03:23 -05:00
Jeff Carr 80fa693ba4 os.Exec() plugins to test if they will actually work 2025-08-17 22:55:35 -05:00
Jeff Carr dda5629688 update 10 at a time 2025-08-17 00:13:27 -05:00
Jeff Carr f39125655d autogenpb syntax change 2025-06-30 07:56:30 -05:00
Jeff Carr 17c9a34aa4 checkboxes to show only out of sync machines 2025-04-20 19:38:26 -05:00
Jeff Carr 7ef9f39a62 add virtigod versions 2025-04-20 18:02:09 -05:00
Jeff Carr d29e55b0d5 common update code 2025-03-25 13:17:00 -05:00
Jeff Carr 64d44e2272 attempt to send the right command to zood 2025-03-23 18:23:45 -05:00
Jeff Carr 0a70f3bdde new FindVersion() 2025-03-23 11:43:42 -05:00
Jeff Carr a24c5f67e5 add virtigod to display 2025-03-23 09:32:50 -05:00
Jeff Carr f90c4af25e show virtigod version 2025-03-23 09:32:50 -05:00
Jeff Carr fc82e095ef show virtigod for now 2025-03-22 09:45:41 -05:00
Jeff Carr b5925dcdce uptime ignores unresponsive hosts 2025-03-22 09:45:32 -05:00
Jeff Carr 687e97ab09 zood upgrade worked for the first time. added Kuma check 2025-03-12 15:34:07 -05:00
Jeff Carr 767380abd0 the forgotten patch 2025-03-12 13:17:19 -05:00
Jeff Carr bb4a6a01df try to fix zood update 2025-03-06 07:54:56 -06:00
Jeff Carr 76c5626d0a trigger apt upgrade 2025-03-06 05:34:25 -06:00
Jeff Carr e85a8ae69f talking with zood again 2025-03-06 05:34:25 -06:00
Jeff Carr 8c7f6c3c1b more gui buttons 2025-03-06 05:34:25 -06:00
Jeff Carr 8edebe18e1 cleaner window code 2025-03-06 05:34:25 -06:00
Jeff Carr f97e2a48c6 1st really awesome table with auto updates 2025-03-06 05:34:25 -06:00
Jeff Carr 40fedc09b4 autogenpb changes 2025-03-05 22:14:21 -06:00
Jeff Carr 53dbe44d38 fixes for 'go vet' 2025-03-05 13:33:04 -06:00
Jeff Carr d214d670ac continue locally now 2025-03-05 12:42:50 -06:00
Jeff Carr b621bba629 testing code 2025-03-05 12:42:50 -06:00
Jeff Carr a57b2875af protobuf update works 2025-03-05 12:42:50 -06:00
Jeff Carr 46f69bee21 rm old code 2025-02-22 14:46:17 -06:00
Jeff Carr c5f840ec2f fixes for go-args 2025-02-22 14:31:08 -06:00
Jeff Carr 7e19f3b9ae rm old code. long live generic protobuf tables 2025-02-22 13:50:27 -06:00
Jeff Carr 49d2653fd4 convert to pb grid 2025-02-22 13:50:27 -06:00
Jeff Carr e9f7b99846 time in a table works 2025-02-20 03:23:14 -06:00
Jeff Carr d667d8c26a attempt a table string func 2025-02-19 17:39:45 -06:00
Jeff Carr 3a42f0f9a5 string rows 2025-02-19 17:39:45 -06:00
Jeff Carr 58ff04ba4d more table tests 2025-02-19 17:39:45 -06:00
Jeff Carr 656f706aa1 testing close window 2025-02-19 17:39:44 -06:00
Jeff Carr b53e71ed9e app works again 2025-02-16 12:05:28 -06:00
Jeff Carr 97f29457db runs and saves config 2025-02-15 12:21:32 -06:00
Jeff Carr 8eed7faa7c try to debug Unmarshal() panic 2025-02-15 12:21:28 -06:00
16 changed files with 617 additions and 407 deletions

View File

@ -7,11 +7,27 @@ BUILDTIME = $(shell date +%Y.%m.%d_%H%M)
# REDOMOD = $(shell if [ -e go.mod ]; then echo go.mod; else echo no go mod; fi) # REDOMOD = $(shell if [ -e go.mod ]; then echo go.mod; else echo no go mod; fi)
REDOMOD = $(shell if [ -e go.sum ]; then echo go.sum exists; else GO111MODULE= go mod init; GO111MODULE= go mod tidy; fi) REDOMOD = $(shell if [ -e go.sum ]; then echo go.sum exists; else GO111MODULE= go mod init; GO111MODULE= go mod tidy; fi)
all: goimports build all: build
./zookeeper --version ./zookeeper list
./zookeeper
build: vet:
@GO111MODULE=off go vet
@echo this go binary package builds okay
nogui:
./zookeeper --gui nocui
gocui: build
./zookeeper --gui gocui
gocui-5000: build
./zookeeper --gui gocui --port 5000
gocui-debugging: build
./zookeeper --gui gocui --gui-file ~/go/src/go.wit.com/toolkits/gocui/gocui.so
# ./zookeeper --gui gocui --gui-file ~/go/src/go.wit.com/toolkits/gocui/gocui.so >/tmp/forge.log 2>&1
build: goimports vet
GO111MODULE=off go build -v -x \ GO111MODULE=off go build -v -x \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}" -ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
@ -39,7 +55,7 @@ redomod:
clean: clean:
rm -f go.* rm -f go.*
rm -f zookeeper rm -f zookeeper
go-mod-clean --purge go-mod-clean purge
# git clone the sources and all the golang dependancies into ~/go/src # git clone the sources and all the golang dependancies into ~/go/src
# if you don't have go-clone, you can get it from http://go.wit.com/ # if you don't have go-clone, you can get it from http://go.wit.com/
@ -52,14 +68,5 @@ http-toogle-ZOOD:
http-list-machines: http-list-machines:
curl --silent http://localhost:8080/list curl --silent http://localhost:8080/list
http-ConfigSave: http-uptime:
curl --silent http://localhost:8080/save curl --silent http://localhost:8080/uptime
http-set-zood-target:
curl --silent "http://localhost:8080/target?package=zood&version=v0.0.8"
http-upgrade-hpdev2.grid.wit.com:
curl --silent "http://localhost:8080/upgrade?hostname=hpdev2.grid.wit.com"
http-upgrade-mirrors.wit.com:
curl --silent "http://localhost:8080/upgrade?hostname=mirrors.wit.com"

3
apt.go
View File

@ -1,3 +1,6 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main package main
import ( import (

View File

@ -1,3 +1,6 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main package main
import ( import (

54
argv.go
View File

@ -1,3 +1,6 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main package main
/* /*
@ -6,24 +9,38 @@ package main
*/ */
import ( import (
"go.wit.com/dev/alexflint/arg" "fmt"
"os"
"go.wit.com/log" "go.wit.com/log"
) )
var argv args var argv args
type args struct { type args struct {
Gui *EmptyCmd `arg:"subcommand:gui" help:"open the gui"`
List *EmptyCmd `arg:"subcommand:list" help:"list the machines in your zoo"`
Upgrade *EmptyCmd `arg:"subcommand:upgrade" help:"upgrade the machines"`
Verbose bool `arg:"--verbose" default:"false" help:"talk more"`
Daemon bool `arg:"--daemon" default:"false" help:"run in daemon mode"` Daemon bool `arg:"--daemon" default:"false" help:"run in daemon mode"`
Port int `arg:"--port" default:"8080" help:"port to run on"` Port int `arg:"--port" default:"8080" help:"port to run on"`
NoPort bool `arg:"--no-port" help:"don't open socket"`
Bash bool `arg:"--bash" help:"generate bash completion"`
BashAuto []string `arg:"--auto-complete" help:"todo: move this to go-arg"`
}
type EmptyCmd struct {
} }
func (args) Version() string { func (args) Version() string {
return "zood " + VERSION + " Built on: " + BUILDTIME return "zookeeper " + VERSION + " Built on: " + BUILDTIME
} }
/*
func init() { func init() {
arg.MustParse(&argv) arg.MustParse(&argv)
} }
*/
func (a args) Description() string { func (a args) Description() string {
return ` return `
@ -45,3 +62,36 @@ func init() {
ZOOD = log.NewFlag("ZOOD", false, full, short, "show reporting from zood") ZOOD = log.NewFlag("ZOOD", false, full, short, "show reporting from zood")
WARN = log.NewFlag("WARN", true, full, short, "bad things") WARN = log.NewFlag("WARN", true, full, short, "bad things")
} }
// prints help to STDERR // TODO: move everything below this to go-args
func (args) doBashHelp() {
if argv.BashAuto[1] != "''" {
// if this is not blank, then the user has typed something
return
}
if argv.BashAuto[0] != ARGNAME {
// if this is not the name of the command, the user already started doing something
return
}
if argv.BashAuto[0] == ARGNAME {
me.pp.WriteHelp(os.Stderr)
return
}
}
func (args) DoAutoComplete(argv []string) {
switch argv[0] {
case "list":
fmt.Println("")
case "verify":
fmt.Println("on")
case "upgrade":
fmt.Println("")
default:
if argv[0] == ARGNAME {
// list the subcommands here
fmt.Println("help list")
}
}
os.Exit(0)
}

View File

@ -8,7 +8,7 @@ Maintainer: Jeff Carr <jcarr@wit.com>
Architecture: amd64 Architecture: amd64
Depends: Depends:
URL: https://go.wit.com/apps/zookeeper URL: https://go.wit.com/apps/zookeeper
Description: zookeeper for homelab grids Description: manage your homelab cluster
keeps track of things in a grid. Maybe keeps track of things in a grid. Maybe
this is similar to the apache project by this is similar to the apache project by
the same name, but in any case, this is the same name, but in any case, this is

View File

@ -1,4 +1,7 @@
// Copyright 2016 The go-qemu Authors. // Copyright 2016 The go-qemu Authors.
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.

104
doGui.go
View File

@ -6,44 +6,43 @@ package main
// An app to submit patches for the 30 GO GUI repos // An app to submit patches for the 30 GO GUI repos
import ( import (
"fmt"
"os" "os"
"time"
"go.wit.com/gui"
"go.wit.com/lib/gadgets" "go.wit.com/lib/gadgets"
"go.wit.com/lib/protobuf/zoopb" "go.wit.com/lib/protobuf/zoopb"
"go.wit.com/log" "go.wit.com/log"
) )
func debug() { // refresh the windows & tables the user has open
for { func refresh() {
time.Sleep(90 * time.Second) if argv.Verbose {
log.Info("TODO: zookeeper scan here. repo count =") log.Info("zookeeper scan here")
}
if me.zood != nil {
// me.zood.doMachinesUpgradeTable(me.machines)
all := me.machines.All()
for all.Scan() {
m := all.Next()
if me.hostname == m.Hostname {
// this is me! This is the version of zood that should be installed everywhere
v := m.FindVersion("zood")
me.zood.version = v
me.zood.versionL.SetText(v)
}
}
me.zood.refresh()
} }
} }
func doGui() { func doGui() {
me.myGui = gui.New() win := gadgets.RawBasicWindow("Zookeeper: (inventory your cluster)")
me.myGui.InitEmbed(resources) win.Make()
me.myGui.Default() win.Show()
win.Custom = func() {
mainWindow := gadgets.RawBasicWindow("Zookeeper: (inventory your cluster)")
mainWindow.Make()
mainWindow.Show()
mainWindow.Custom = func() {
log.Warn("Main window close") log.Warn("Main window close")
os.Exit(0) os.Exit(0)
} }
drawWindow(mainWindow)
// sits here forever
debug()
}
func drawWindow(win *gadgets.BasicWindow) {
box := win.Box() box := win.Box()
vbox := box.NewVerticalBox("BOX2") vbox := box.NewVerticalBox("BOX2")
@ -51,53 +50,32 @@ func drawWindow(win *gadgets.BasicWindow) {
group1 := vbox.NewGroup("Zookeeper Settings") group1 := vbox.NewGroup("Zookeeper Settings")
grid := group1.NewGrid("buildOptions", 0, 0) grid := group1.NewGrid("buildOptions", 0, 0)
var tbwin *tableWindow grid.NewButton("zood versions", func() {
grid.NewButton("show zoo", func() { // if the window exists, just toggle it open or closed
win.Disable() if me.zood != nil {
defer win.Enable() me.zood.Toggle()
if tbwin == nil {
log.Info("show zoo here")
tbwin = makeTableWindow()
tbwin.showTable(me.machines)
}
if tbwin.Hidden() {
tbwin.Show()
} else {
tbwin.Hide()
}
return return
}
me.zood = makeZoodWin()
}) })
grid.NewButton("update table", func() { grid.NewButton("Cluster Events", func() {
newwin := makeTableWindow() log.Info("todo: start a list here!")
newwin.showTable(me.machines) })
newwin.Show()
grid.NewButton("ConfigSave()", func() {
saveMachineState()
}) })
} }
func (tw *tableWindow) showTable(allm *zoopb.Machines) { func saveMachineState() {
all := allm.All() cur := zoopb.NewMachines()
all := me.machines.SortByHostname()
for all.Scan() { for all.Scan() {
m := all.Next() m := all.Next()
tw.grid.NewLabel(m.Hostname) log.Info("have machine:", m.Hostname)
tw.grid.NewLabel(fmt.Sprintf("%d", m.Cpus)) cur.Append(m)
tw.grid.NewLabel(fmt.Sprintf("%d", m.Memory))
tw.grid.NewLabel(m.Distro)
tw.grid.NewLabel(findVersion(m, "zood"))
tw.grid.NewLabel(findVersion(m, "bash"))
dur := m.Laststamp.AsTime()
tw.grid.NewLabel(fmt.Sprintf("%v", time.Since(dur)))
tw.grid.NextRow()
} }
} cur.ConfigSave()
func findVersion(m *zoopb.Machine, pkgname string) string {
zood := m.Packages.FindByName(pkgname)
if zood == nil {
return "n/a"
}
return zood.Version
} }

22
exit.go Normal file
View File

@ -0,0 +1,22 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"os"
"go.wit.com/log"
)
func okExit(thing string) {
if thing != "" {
log.Info("zookeeper exit:", thing, "ok")
}
os.Exit(0)
}
func badExit(err error) {
log.Info("zookeeper failed: ", err)
os.Exit(-1)
}

198
http.go
View File

@ -1,160 +1,94 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main package main
import ( import (
"fmt" "fmt"
"io/ioutil" "io"
"net/http" "net/http"
"strings" "time"
"go.wit.com/lib/protobuf/forgepb" "go.wit.com/lib/protobuf/httppb"
"go.wit.com/lib/protobuf/zoopb"
"go.wit.com/log" "go.wit.com/log"
) )
// remove '?' part and trailing '/' /*
func cleanURL(url string) string { if strings.HasPrefix(route, "/repos/") {
url = "/" + strings.Trim(url, "/") pb := gitpb.NewRepos()
return url if err := pb.Unmarshal(reqPB.ClientData); err == nil {
} reqPB.Log("Repos Unmarshal() len=%d", pb.Len())
} else {
func okHandler(w http.ResponseWriter, r *http.Request) { reqPB.Logf("Repos Unmarshal() err=%v", err)
// log.Info("Got URL Path: ", r.URL.Path) }
route := cleanURL(r.URL.Path) result := gitpb.NewRepos()
switch route {
hostname := r.URL.Query().Get("hostname") case "/repos/check":
flag := r.URL.Query().Get("flag") result = addRequest(pb, reqPB)
packname := r.URL.Query().Get("package") reqPB.Logf("repos check result.Len()=%d pb.Len()=%d\n", result.Len(), pb.Len())
version := r.URL.Query().Get("version") case "/repos/pull":
result = pullRequest(pb, reqPB)
msg, err := ioutil.ReadAll(r.Body) // Read the body as []byte case "/repos/add":
if err != nil { result = addRequest(pb, reqPB)
log.Info("ReadAll() error =", err) default:
reqPB.Logf("repos check result.Len()=%d pb.Len()=%d\n", result.Len(), pb.Len())
log.Info("repos", route, "unknown")
}
if err := result.SendReply(w, reqPB); err != nil {
reqPB.Logf("Oh well, Send to client failed. err=%v", err)
}
// todo: logReq(reqPB)
logReqPB(reqPB)
return return
} }
*/
func okHandler(w http.ResponseWriter, r *http.Request) {
reqPB, err := httppb.ReqToPB(r)
reqPB.Logf("START: Got %d bytes from the client", len(reqPB.ClientData))
if err != nil {
reqPB.Logf("httppb err %v", err)
}
route := reqPB.Route
if route == "/" { if route == "/" {
return return
} }
if route == "/machine" { if route == "/machine" {
var m *zoopb.Machine handleMachine(w, reqPB)
m = new(zoopb.Machine)
if err := m.Unmarshal(msg); err != nil {
log.Info("zoo host sent unknown machine protobuf len", len(msg))
forgepb.IdentifyProtobuf(msg)
log.Info("error =", err)
return
}
log.Log(INFO, "proto.Unmarshal() worked on wire message len", len(msg), "from", m.Hostname)
b := me.upgrade[m.Hostname]
switch updateMachine(m) {
case "upgrade":
if b {
fmt.Fprintln(w, "apt update")
me.upgrade[m.Hostname] = false
} else {
fmt.Fprintln(w, "upgrade")
}
default:
fmt.Fprintln(w, "notsure")
}
return return
} }
if route == "/status" { if route == "/uptime" {
var packs *zoopb.Packages doUptime(w)
packs = new(zoopb.Packages)
if err := packs.Unmarshal(msg); err != nil {
log.Info("/status proto.Unmarshal() failed on wire message len", len(msg), "from", hostname)
return
}
log.Info("/status Unmarshal worked with msg len", len(msg), "from", hostname)
log.Info("/status hostname", hostname, "has", packs.Len(), "packages installed")
fmt.Fprintln(w, "upgrade")
return
}
// list out the machines and thier version of zood
if route == "/list" {
log.HttpMode(w)
defer log.HttpMode(nil)
loop := me.machines.SortByHostname()
for loop.Scan() {
m := loop.Next()
zood := m.Packages.FindByName("zood")
v := me.targets["zood"] // this is the target version
if zood == nil {
log.Info("machine", m.Hostname, "does not have zood installed")
} else {
log.Info(fmt.Sprintf("zood version %s vs target version %s on machine %s", zood.Version, v, m.Hostname))
}
}
return
}
// save the config file
if route == "/save" {
log.HttpMode(w)
defer log.HttpMode(nil)
if err := me.machines.ConfigSave(); err == nil {
log.Log(NOW, "ConfigSave() ok")
} else {
log.Log(NOW, "ConfigSave() failed", err)
}
return
}
// flag a package to attempt to upgrade
if route == "/upgrade" {
log.HttpMode(w)
defer log.HttpMode(nil)
me.upgrade[hostname] = true
log.Log(NOW, "setting package ", packname, " to upgrade")
return
}
// set the target version for a package
if route == "/target" {
log.HttpMode(w)
defer log.HttpMode(nil)
// me.targets[packname] = version
log.Log(NOW, "setting package/version to ", packname, " ", version)
return
}
// toggle logging flags
if route == "/flag" {
log.HttpMode(w)
defer log.HttpMode(nil)
log.Info("going to toggle flag:", flag)
switch flag {
case "ZOOD":
if ZOOD.Enabled() {
log.Log(NOW, "toogle ZOOD false")
ZOOD.SetBool(false)
} else {
log.Log(NOW, "toogle ZOOD true")
ZOOD.SetBool(true)
}
default:
log.Info("unknown looging flag:", flag)
}
return return
} }
log.Warn("BAD URL =", route) log.Warn("BAD URL =", route)
} }
// starts and sits waiting for HTTP requests func doUptime(w io.Writer) {
func startHTTP() { if me.zood == nil {
http.HandleFunc("/", okHandler) fmt.Fprintf(w, "BAD zood == nil\n")
return
p := fmt.Sprintf(":%d", argv.Port) }
log.Println("Running on port", p) var count int
var bad int
err := http.ListenAndServe(p, nil) for m := range me.machines.IterAll() {
if err != nil { count += 1
log.Println("Error starting server:", err) if m.FindVersion("zood") != me.zood.version {
if m.SinceLastUpdate() > 10*time.Minute {
// skip machines that have not been updated in the last 10 minutes
log.Info("ignoring old machine", m.Hostname)
continue
}
bad += 1
}
}
if bad == 0 {
fmt.Fprintf(w, "GOOD machine count=(%d) all machines are version %s\n", count, me.zood.version)
} else {
fmt.Fprintf(w, "BAD machine count=(%d) upgrade=(%d) to %s\n", count, bad, me.zood.version)
} }
} }

View File

@ -1,13 +1,76 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main package main
import ( import (
"fmt"
"net/http"
"time" "time"
"go.wit.com/lib/protobuf/httppb"
"go.wit.com/lib/protobuf/zoopb" "go.wit.com/lib/protobuf/zoopb"
"go.wit.com/log" "go.wit.com/log"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
) )
func rawGetHostname(data []byte) *zoopb.Machine {
newm := new(zoopb.Machine)
newm.Unmarshal(data)
return newm
}
func handleMachine(w http.ResponseWriter, reqPB *httppb.HttpRequest) {
// hostname := strings.TrimSpace(reqPB.Hostname)
newm := rawGetHostname(reqPB.ClientData)
if newm == nil {
log.Info("unmarshal failed on data len =", len(reqPB.ClientData))
}
/*
if reqPB.Hostname != newm.Hostname {
// log.Info("hostname mismatch", hostname, "vs", newm.Hostname)
hostname = newm.Hostname
}
*/
if reqPB.Hostname == "" {
ua := reqPB.DumpUserAgent()
ra := reqPB.DumpRemoteAddr()
log.Info("hostname is blank even after unmarshal. data len =", len(reqPB.ClientData), ra, ua, newm.Cpus, newm.Hostname)
return
}
// log.Info("lookoing for", hostname)
m := me.machines.FindByHostname(reqPB.Hostname)
if m == nil {
am := new(zoopb.Machine)
am.Hostname = newm.Hostname
am.Memory = newm.Memory
me.machines.Append(newm)
log.Info("new machine", am.Hostname, am.Memory)
return
}
ua := reqPB.DumpUserAgent()
ra := reqPB.DumpRemoteAddr()
if m.UserAgent != ua {
log.Info("hostname ua changed len =", len(reqPB.ClientData), ra, reqPB.Hostname, ua)
m.UserAgent = ua
}
if m.Upgrade {
log.Info(m.Hostname, "was told to upgrade zood")
if m.UpgradeCmd == "" {
fmt.Fprintln(w, "apt update")
} else {
fmt.Fprintln(w, m.UpgradeCmd)
}
m.UpgradeCmd = ""
m.Upgrade = false
} else {
fmt.Fprintln(w, "good")
}
// log.Info("update machine protobuf", reqPB.hostname)
updateMachine(newm)
}
// someone sent machine 'u' is it new? // someone sent machine 'u' is it new?
// if not, update the record of it // if not, update the record of it
func updateMachine(u *zoopb.Machine) string { func updateMachine(u *zoopb.Machine) string {
@ -18,6 +81,12 @@ func updateMachine(u *zoopb.Machine) string {
if m == nil { if m == nil {
log.Info("adding new machine", u.Hostname) log.Info("adding new machine", u.Hostname)
me.machines.Append(u) me.machines.Append(u)
if me.zood == nil {
// do nothing. window has not been opened
} else {
me.zood.doMachinesUpgradeTable(me.machines)
}
saveMachineState()
return "new" return "new"
} }
// log.Info("updating machine", m.Hostname) // log.Info("updating machine", m.Hostname)
@ -46,7 +115,9 @@ func updateMachine(u *zoopb.Machine) string {
} }
m.Laststamp = timestamppb.New(time.Now()) m.Laststamp = timestamppb.New(time.Now())
updatePackages(m, u.Packages) if updatePackages(m, u.Packages) {
// trigger save pb
}
return "upgrade" return "upgrade"
} }
@ -61,20 +132,36 @@ func updatePackages(m *zoopb.Machine, newp *zoopb.Packages) bool {
for loop.Scan() { for loop.Scan() {
p := loop.Next() p := loop.Next()
if p.Name == "zood" { if p.Name == "zood" {
if pold := m.Packages.FindByName("zood"); pold == nil { if updatePackageVersion(m, p) {
changed = true changed = true
log.Log(ZOOD, "updatePackages() new package", p.Name, "version", p.Version, "machine", m.Hostname)
m.Packages.Append(p)
} else {
if p.Version == pold.Version {
log.Log(ZOOD, "updatePackages() unchanged", p.Version, "machine", m.Hostname)
} else {
changed = true
log.Log(NOW, "updatePackages() package", p.Name, "version changed", pold.Version, "to", p.Version, "machine", m.Hostname)
pold.Version = p.Version
} }
} }
if p.Name == "virtigod" {
if updatePackageVersion(m, p) {
changed = true
}
}
if p.Name == "forge" {
if updatePackageVersion(m, p) {
changed = true
}
} }
} }
return changed return changed
} }
func updatePackageVersion(m *zoopb.Machine, pnew *zoopb.Package) bool {
pold := m.Packages.FindByName(pnew.Name)
if pold == nil {
log.Log(NOW, "updatePackages() new package", pnew.Name, "version", pnew.Version, "machine", m.Hostname)
m.Packages.Append(pnew)
return true
}
if pold.Version == pnew.Version {
log.Log(ZOOD, "updatePackages() unchanged", pold.Version, "machine", m.Hostname)
return false
}
log.Log(NOW, "updatePackages() package", pnew.Name, "version changed", pold.Version, "to", pnew.Version, "machine", m.Hostname)
pold.Version = pnew.Version
return true
}

55
main.go
View File

@ -1,25 +1,15 @@
// Copyright 2016 The go-qemu Authors. // Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// // Use of this source code is governed by the GPL 3.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main package main
import ( import (
"embed"
"os" "os"
"time" "time"
"go.wit.com/dev/alexflint/arg" "go.wit.com/dev/alexflint/arg"
"go.wit.com/lib/gui/prep"
"go.wit.com/lib/protobuf/httppb"
"go.wit.com/lib/protobuf/zoopb" "go.wit.com/lib/protobuf/zoopb"
"go.wit.com/log" "go.wit.com/log"
) )
@ -27,37 +17,42 @@ import (
var VERSION string var VERSION string
var BUILDTIME string var BUILDTIME string
//go:embed resources/* var ARGNAME string = "zookeeper"
var resources embed.FS
func main() { func main() {
var pp *arg.Parser me = new(mainType)
pp = arg.MustParse(&argv) prep.Bash(ARGNAME, argv.DoAutoComplete) // this line should be: prep.Bash(argv)
me.myGui = prep.Gui() // prepares the GUI package for go-args
if pp == nil { me.pp = arg.MustParse(&argv)
pp.WriteHelp(os.Stdout)
os.Exit(0)
}
if argv.Daemon { if argv.Daemon {
// turn off timestamps for STDOUT (systemd adds them) // turn off timestamps for STDOUT (systemd adds them)
log.DaemonMode(true) log.DaemonMode(true)
} }
me = new(stuff)
me.hostname, _ = os.Hostname() me.hostname, _ = os.Hostname()
me.pollDelay = 10 * time.Second me.pollDelay = time.Hour
me.machines = new(zoopb.Machines) me.machines = zoopb.NewMachines()
if err := me.machines.ConfigLoad(); err != nil { if err := me.machines.ConfigLoad(); err != nil {
log.Warn("load config failed", err) log.Warn("load config failed", err)
os.Exit(-1) os.Exit(-1)
} }
// me.targets = make(map[string]string) // keep track of what versions the machines should be running if argv.List != nil {
me.upgrade = make(map[string]bool) // used to trigger upgrade attempts log.Info("do list here")
okExit("")
}
go NewWatchdog() go NewWatchdog()
if !argv.NoPort {
go httppb.StartHTTP(okHandler, argv.Port)
}
go startHTTP() me.myGui.Start() // loads the GUI toolkit
doGui() // start making our forge GUI
doGui() // sit here forever refreshing the GUI
for {
refresh()
time.Sleep(90 * time.Second)
}
} }

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -1,16 +1,23 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main package main
import ( import (
"time" "time"
"go.wit.com/dev/alexflint/arg"
"go.wit.com/gui" "go.wit.com/gui"
"go.wit.com/lib/gadgets"
"go.wit.com/lib/gui/prep"
"go.wit.com/lib/protobuf/zoopb" "go.wit.com/lib/protobuf/zoopb"
) )
var me *stuff var me *mainType
// this app's variables // this app's variables
type stuff struct { type mainType struct {
pp *arg.Parser // for parsing the command line args. Yay to alexf lint!
hostname string // my fqdn dns zookeeper hostname hostname string // my fqdn dns zookeeper hostname
pollDelay time.Duration // how often to report our status pollDelay time.Duration // how often to report our status
dog *time.Ticker // the watchdog timer dog *time.Ticker // the watchdog timer
@ -20,5 +27,9 @@ type stuff struct {
machines *zoopb.Machines // every machine that has reported itself to the zookeeper machines *zoopb.Machines // every machine that has reported itself to the zookeeper
targets map[string]string // what versions the machines should be running targets map[string]string // what versions the machines should be running
upgrade map[string]bool // use this to trigger builds upgrade map[string]bool // use this to trigger builds
myGui *gui.Node // the gui toolkit handle myGui *prep.GuiPrep // the gui toolkit handle
machinesWin *gadgets.GenericWindow // the machines gui window
machinesBox *gui.Node // the machines gui parent box widget
machinesTB *zoopb.MachinesTable // the machines gui table buffer
zood *stdTableWin // the zood version window
} }

View File

@ -1,135 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"sync"
"go.wit.com/lib/gadgets"
"go.wit.com/gui"
)
type tableWindow struct {
once sync.Once // only init() the window once
win *gadgets.BasicWindow // the patches window
stack *gui.Node // the top box set as vertical
shelf *gui.Node // the first box in the stack, set as horizontal
grid *gui.Node // the list of available patches
// summary *patchSummary // summary of current patches
setgrid *gui.Node // the list of each patchset
}
// todo: autogenerate these or make them standared 'gui' package functions
// make this an go interface somehow
// is the window hidden right now?
func (w *tableWindow) Hidden() bool {
return w.win.Hidden()
}
// switches between the window being visable or hidden on the desktop
func (w *tableWindow) Toggle() {
if w.Hidden() {
w.Show()
} else {
w.Hide()
}
}
// hides the window completely
func (w *tableWindow) Show() {
w.win.Show()
}
func (w *tableWindow) Hide() {
w.win.Hide()
}
// should be the first box/widget in the window
// greys out the window to the user
func (w *tableWindow) Disable() {
w.stack.Disable()
}
func (w *tableWindow) Enable() {
w.stack.Enable()
}
// you can only have one of these
func makeTableWindow() *tableWindow {
pw := new(tableWindow)
// sync.Once()
pw.win = gadgets.RawBasicWindow("Table window for ")
pw.win.Make()
pw.stack = pw.win.Box().NewBox("bw vbox", false)
// me.reposwin.Draw()
pw.win.Custom = func() {
// sets the hidden flag to false so Toggle() works
pw.win.Hide()
}
grid := pw.stack.NewGrid("", 0, 0)
grid.NewLabel("Patchset Details:")
grid.NextRow()
g := pw.stack.NewGroup("PatchSet List")
// make a grid to put the list of git repos that have patches
// in this particular patchset
pw.grid = g.NewGrid("", 0, 0)
pw.grid.NewLabel("hostname")
pw.grid.NewLabel("cpus")
pw.grid.NewLabel("Memory")
pw.grid.NewLabel("distro")
pw.grid.NewLabel("zood")
pw.grid.NewLabel("bash")
pw.grid.NewLabel("age")
pw.grid.NextRow()
// add the patches to the grid
// pw.addPatchset(grid, pset)
return pw
}
/*
func (r *tableWindow) addPatchset(grid *gui.Node, pset *forgepb.Patchset) {
all := pset.Patches.SortByFilename()
for all.Scan() {
p := all.Next()
// for repo, patches := range repomap {
rn := p.RepoNamespace
repo := me.forge.FindByGoPath(rn)
if repo == nil {
log.Info("Could not figure out repo path", rn)
rn += " bad repo"
}
log.Info("Adding patches for", rn)
grid.NewLabel(rn)
// hash := repohash[repo]
grid.NewLabel(p.StartHash)
grid.NewLabel(p.Filename)
if repo == nil {
continue
}
grid.NewButton("apply", func() {
filename, _ := savePatch(p)
if err := applyPatch(repo, filename); err != nil {
log.Info("warn user of git am error", err)
}
})
grid.NewCheckbox("").SetChecked(true)
grid.NewCheckbox("").SetChecked(true)
grid.NewButton("save patch to /tmp", func() {
})
grid.NextRow()
}
}
*/

View File

@ -1,3 +1,6 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main package main
import ( import (
@ -52,7 +55,7 @@ func NewWatchdog() {
// log.Info("know about machine", m.Hostname, "zood version", zood.Version) // log.Info("know about machine", m.Hostname, "zood version", zood.Version)
} }
} }
log.Info("zookeeper has", counter, "machines. Current time:", t) log.Info("hour watchdog:", counter, "machines. Current time:", t)
// h.pollHypervisor() // h.pollHypervisor()
// h.Scan() // h.Scan()

250
windowZood.go Normal file
View File

@ -0,0 +1,250 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"sync"
"time"
"go.wit.com/gui"
"go.wit.com/lib/gadgets"
"go.wit.com/lib/protobuf/zoopb"
"go.wit.com/log"
)
type stdTableWin struct {
sync.Mutex
win *gadgets.GenericWindow // the machines gui window
box *gui.Node // the machines gui parent box widget
TB *zoopb.MachinesTable // the machines gui table buffer
version string // the current zood version
versionL *gui.Node // label widget to display the current zood version
outOfDate *gui.Node // checkbox to only show out of date droplets
showAll *gui.Node // show everything in the zoo
update bool // if the window should be updated
}
func (w *stdTableWin) Toggle() {
if w == nil {
return
}
if w.win == nil {
return
}
w.win.Toggle()
}
func makeZoodWin() *stdTableWin {
stdw := new(stdTableWin)
stdw.win = gadgets.NewGenericWindow("zood daemon versions", "todo: add global controls here")
stdw.win.Custom = func() {
log.Info("test delete window here")
}
grid := stdw.win.Group.RawGrid()
grid.NewButton("save machines.pb", func() {
saveMachineState()
})
grid.NewButton("show active", func() {
stdw.doMachinesUpgradeTable(me.machines)
})
grid.NewButton("refresh", func() {
stdw.refresh()
})
stdw.versionL = grid.NewLabel("scan")
stdw.outOfDate = grid.NewCheckbox("out of date")
stdw.showAll = grid.NewCheckbox("all")
grid.NewButton("upgrade 10", func() {
sendUpgrade(10)
})
grid.NewButton("upgrade all", func() {
sendUpgrade(-1)
})
// make a box at the bottom of the window for the protobuf table
stdw.box = stdw.win.Bottom.Box().SetProgName("TBOX")
stdw.doMachinesUpgradeTable(me.machines)
return stdw
}
func sendUpgrade(i int) {
var count int
all := me.machines.All()
for all.Scan() {
m := all.Next()
mtime := m.Laststamp.AsTime()
if time.Since(mtime) > 10*time.Hour {
continue
}
if m.FindVersion("zood") != me.zood.version {
count += 1
m.Upgrade = true
log.Info("upgrade", m.Hostname, count)
}
if i == -1 || count > i {
return
}
}
}
func (stdw *stdTableWin) refresh() {
if stdw.outOfDate.Checked() {
log.Info("refresh() showing out of date zoo")
found := zoopb.NewMachines()
all := me.machines.All()
for all.Scan() {
m := all.Next()
if !stdw.showAll.Checked() {
// skip non-active zoo members
mtime := m.Laststamp.AsTime()
if time.Since(mtime) > 10*time.Hour {
continue
}
}
if m.FindVersion("zood") != me.zood.version {
found.Append(m)
}
}
stdw.doMachinesUpgradeTable(found)
return
}
if stdw.showAll.Checked() {
log.Info("refresh() showing everything in zoo")
stdw.doMachinesUpgradeTable(me.machines)
return
}
log.Info("refresh() only show active zoo")
found := zoopb.NewMachines()
all := me.machines.All()
for all.Scan() {
m := all.Next()
mtime := m.Laststamp.AsTime()
// now := time.Now()
if time.Since(mtime) > 10*time.Hour {
continue
}
found.Append(m)
}
stdw.doMachinesUpgradeTable(found)
}
func (zood *stdTableWin) doMachinesUpgradeTable(pb *zoopb.Machines) {
zood.Lock()
defer zood.Unlock()
if zood.TB != nil {
zood.TB.Delete()
zood.TB = nil
}
/*
found := zoopb.NewMachines()
all := pb.SortByHostname()
for all.Scan() {
m := all.Next()
found.Append(m)
}
*/
// display the protobuf
zood.TB = AddMachinesPB(zood.box, pb)
f := func(m *zoopb.Machine) {
log.Info("Triggering machine", m.Hostname, "to upgrade zood")
m.Upgrade = true
}
zood.TB.Custom(f)
log.Info("table has uuid", zood.TB.GetUuid())
}
func AddMachinesPB(tbox *gui.Node, pb *zoopb.Machines) *zoopb.MachinesTable {
t := pb.NewTable("MachinesPB")
t.NewUuid()
t.SetParent(tbox)
upbut := t.AddButtonFunc("upgrade", func(m *zoopb.Machine) string {
if me.zood != nil {
mver := m.FindVersion("zood")
if mver == me.zood.version {
return ""
} else {
// log.Info("machine mismatch", m.Hostname, mver, me.zood.version)
}
}
// log.Info("machine =", m.Hostname)
return "now"
})
upbut.Custom = func(m *zoopb.Machine) {
log.Info("Triggering machine", m.Hostname, "to upgrade zood")
m.Upgrade = true
}
t.AddHostname()
t.AddMemory()
t.AddCpus()
t.AddStringFunc("sMB", func(m *zoopb.Machine) string {
if m.Memory/(1024*1024) > 10000 {
return fmt.Sprintf("%4d G", m.Memory/(1024*1024*1024))
}
return fmt.Sprintf("%4d M", m.Memory/(1024*1024))
})
t.AddStringFunc("zood", func(m *zoopb.Machine) string {
return m.FindVersion("zood")
})
virtbut := t.AddButtonFunc("virtigod", func(m *zoopb.Machine) string {
ver := m.FindVersion("virtigod")
if ver == "n/a" {
return ""
}
return ver
})
virtbut.Custom = func(m *zoopb.Machine) {
log.Info("Triggering machine", m.Hostname, "to upgrade virtigod")
m.Upgrade = true
m.UpgradeCmd = "apt install virtigod"
}
forgebut := t.AddButtonFunc("forge", func(m *zoopb.Machine) string {
ver := m.FindVersion("forge")
if ver == "n/a" {
return ""
}
return ver
})
forgebut.Custom = func(m *zoopb.Machine) {
log.Info("Triggering machine", m.Hostname, "to upgrade forge")
m.Upgrade = true
m.UpgradeCmd = "apt install forge"
}
delf := func(m *zoopb.Machine) string {
return "delete"
}
delbut := t.AddButtonFunc("delete", delf)
delbut.Custom = func(m *zoopb.Machine) {
log.Info("Need to delete the protobuf record here", m.Hostname)
}
/*
// show if the machine needs to be upgraded
t.AddStringFunc("triggered?", func(m *zoopb.Machine) string {
if m.Upgrade {
return "yes"
}
return ""
})
*/
t.AddTimeFunc("age", func(m *zoopb.Machine) time.Time {
return m.Laststamp.AsTime()
})
t.ShowTable()
return t
}