diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3462d4b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +unnamed-project diff --git a/assets/.gitignore b/assets/.gitignore new file mode 100644 index 0000000..f42b841 --- /dev/null +++ b/assets/.gitignore @@ -0,0 +1 @@ +assets_gen.go diff --git a/assets/assets.go b/assets/assets.go new file mode 100644 index 0000000..4c983c8 --- /dev/null +++ b/assets/assets.go @@ -0,0 +1,9 @@ +package assets + +import ( + assetfs "github.com/elazarl/go-bindata-assetfs" +) + +func AssetFS() *assetfs.AssetFS { + return &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo, Prefix: "public"} +} diff --git a/go.mod b/go.mod index b64733f..5499ec1 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module varia.zone/unnamed-project go 1.18 + +require github.com/elazarl/go-bindata-assetfs v1.0.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..270de33 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw= +github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= diff --git a/makefile b/makefile new file mode 100644 index 0000000..1eaa537 --- /dev/null +++ b/makefile @@ -0,0 +1,10 @@ +default: run + +MAIN := unnamed-project.go +ASSETS := assets/assets_gen.go + +generate: + @rm -rf $(ASSETS) && go generate $(MAIN) + +run: generate + @go run $(MAIN) diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..abb4708 --- /dev/null +++ b/public/index.html @@ -0,0 +1,22 @@ + + + + + + + Unnamed Project + + +

Hello, World!

+ + + diff --git a/unnamed-project.go b/unnamed-project.go index 252c006..f95fb6a 100644 --- a/unnamed-project.go +++ b/unnamed-project.go @@ -1,5 +1,7 @@ package main +//go:generate go-bindata -o assets/assets_gen.go -pkg assets public/... + import ( "container/list" "encoding/json" @@ -7,12 +9,15 @@ import ( "fmt" "log" "net" + "net/http" "os" "os/user" "path/filepath" "strings" "sync" "time" + + "varia.zone/unnamed-project/assets" ) const help = `unnamed-project: WIP @@ -52,11 +57,11 @@ func main() { } conf := &config{ - nodeID: nodeIDFlag, - webPort: webPortFlag, - filePort: filePortFlag, - sharePath: pathFlag, - debug: debugFlag, + NodeID: nodeIDFlag, + WebPort: webPortFlag, + FilePort: filePortFlag, + SharePath: pathFlag, + Debug: debugFlag, } serve(conf) @@ -74,11 +79,11 @@ func handleCliFlags(username, sharePath string) { } type config struct { - nodeID string - webPort int - filePort int - sharePath string - debug bool + NodeID string `json:"nodeID"` + WebPort int `json:"webPort"` + FilePort int `json:"filePort"` + SharePath string `json:"sharePath"` + Debug bool `json:"debug"` } type announcer struct { @@ -86,10 +91,10 @@ type announcer struct { } type nodeInfo struct { - nodeID string `json:"nodeID"` - addr string `json:"addr"` - webPort int `json:"webPort"` - lastMulticast int64 `json:"lastMulticast"` + NodeID string `json:"nodeID"` + Addr string `json:"addr"` + WebPort int `json:"webPort"` + LastMulticast int64 `json:"lastMulticast"` } var ( @@ -152,7 +157,7 @@ func announcedNodeHandler(ninfo *nodeInfo, nodeList *list.List) { fmt.Print("[") for el := nodeList.Front(); el != nil; el = el.Next() { - fmt.Print(el.Value.(*nodeInfo).nodeID, " ") + fmt.Print(el.Value.(*nodeInfo).NodeID, " ") } fmt.Print("]\n\n") } @@ -163,9 +168,9 @@ func updateNodeList(ninfo *nodeInfo, nodeList *list.List) { tmp := el.Value.(*nodeInfo) // Already in list - if tmp.nodeID == ninfo.nodeID { - tmp.lastMulticast = time.Now().Unix() - fmt.Printf("Updating node %s multicast\n", ninfo.nodeID) + if tmp.NodeID == ninfo.NodeID { + tmp.LastMulticast = time.Now().Unix() + fmt.Printf("Updating node %s multicast\n", ninfo.NodeID) nodeExists = true break } @@ -175,20 +180,20 @@ func updateNodeList(ninfo *nodeInfo, nodeList *list.List) { for el := nodeList.Front(); el != nil; el = el.Next() { tmp := el.Value.(*nodeInfo) if isNodeExpired(tmp, expireTimeoutSec) { - fmt.Println("Node expired, removing: ", tmp.nodeID) + fmt.Println("Node expired, removing: ", tmp.NodeID) nodeList.Remove(el) } } if !nodeExists { - fmt.Printf("Adding new node! %p %s\n", ninfo, ninfo.nodeID) - ninfo.lastMulticast = time.Now().Unix() + fmt.Printf("Adding new node! %p %s\n", ninfo, ninfo.NodeID) + ninfo.LastMulticast = time.Now().Unix() nodeList.PushBack(ninfo) } } func isNodeExpired(nodeInfo *nodeInfo, timeout int) bool { - diff := time.Now().Unix() - nodeInfo.lastMulticast + diff := time.Now().Unix() - nodeInfo.LastMulticast return diff > int64(timeout) } @@ -202,7 +207,7 @@ func parseAnnouncePacket(size int, addr *net.UDPAddr, packet []byte) (*nodeInfo, } if string(packet[len(header):len(header)+1]) != nodeAnnounceCommand[0:] { - return nil, fmt.Errorf("Command different than NODE_ANNOUNCE_COMMAND") + return nil, fmt.Errorf("Command different than nodeAnnounceCommand") } fmt.Println("Packet command is nodeAnnounceCommand") @@ -213,8 +218,8 @@ func parseAnnouncePacket(size int, addr *net.UDPAddr, packet []byte) (*nodeInfo, nodeInfo := &nodeInfo{} err := json.Unmarshal([]byte(payload), nodeInfo) - nodeInfo.addr = addr.IP.String() - nodeInfo.nodeID = fmt.Sprintf("%s-%s", nodeInfo.nodeID, nodeInfo.addr) + nodeInfo.Addr = addr.IP.String() + nodeInfo.NodeID = fmt.Sprintf("%s-%s", nodeInfo.NodeID, nodeInfo.Addr) if err != nil { return nil, err } @@ -249,7 +254,7 @@ func listenForNodes(nodeList *list.List) { fmt.Println(err) continue } - fmt.Printf("Received multicast packet from %s Id: %s\n", udpAddr.String(), nodeInfo.nodeID) + fmt.Printf("Received multicast packet from %s Id: %s\n", udpAddr.String(), nodeInfo.NodeID) go announcedNodeHandler(nodeInfo, nodeList) } @@ -257,10 +262,10 @@ func listenForNodes(nodeList *list.List) { func (a *announcer) Start(nodeList *list.List) { nodeInfo := &nodeInfo{ - nodeID: a.conf.nodeID, - addr: "", - webPort: a.conf.webPort, - lastMulticast: 0, + NodeID: a.conf.NodeID, + Addr: "", + WebPort: a.conf.WebPort, + LastMulticast: 0, } go announceNode(nodeInfo) @@ -277,12 +282,68 @@ func serve(conf *config) { go startAnnouncer(conf, nodeList) - // TODO: next steps... - // go fileServe(conf) - // go dashboardServe(conf, nodeList) + go fileServe(conf) + go dashboardServe(conf, nodeList) for { // TODO: do this context cancel trick to escape cleanly? time.Sleep(time.Minute * 15) } } + +func fileServe(conf *config) { + fileMux := http.NewServeMux() + fileMux.Handle("/", http.FileServer(http.Dir(conf.SharePath))) + http.ListenAndServe(fmt.Sprintf("0.0.0.0:%v", conf.WebPort), fileMux) +} + +func configHandler(conf *config) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + data, err := json.Marshal(conf) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Header().Add("Content-Type", "application/json") + w.Write(data) + } +} + +func nodesHandler(nodeList *list.List) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + nodes := make([]*nodeInfo, 0) + for el := nodeList.Front(); el != nil; el = el.Next() { + tmp := el.Value.(*nodeInfo) + nodes = append(nodes, tmp) + } + + data, err := json.Marshal(nodes) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Header().Add("Content-Type", "application/json") + w.Write(data) + } +} + +func dashboardServe(conf *config, nodeList *list.List) { + dashboardMux := http.NewServeMux() + dashboardMux.Handle("/", http.FileServer(assets.AssetFS())) + dashboardMux.HandleFunc("/api/config", configHandler(conf)) + dashboardMux.HandleFunc("/api/nodes", nodesHandler(nodeList)) + + // We don't want the dashboard to be public + address := "localhost" + if conf.Debug { + address = "0.0.0.0" + } + + fmt.Printf("Starting dashboard at %s:%v\n", address, conf.FilePort) + err := http.ListenAndServe(fmt.Sprintf("%s:%v", address, conf.FilePort), dashboardMux) + if err != nil { + log.Fatal(err) + } +}