cleanup
This commit is contained in:
parent
37cf186d32
commit
a86ac56b75
|
@ -124,6 +124,11 @@ __pycache__
|
||||||
# IDE's go
|
# IDE's go
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# webroot
|
||||||
|
webroot/node_modules
|
||||||
|
webroot/data
|
||||||
|
webroot/app.js
|
||||||
|
webroot/app.js.map
|
||||||
|
|
||||||
# go project
|
# go project
|
||||||
profile.cov
|
profile.cov
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestReadConfig(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
config := ReadConfigFile("../config_example.conf")
|
|
||||||
assert.NotNil(config)
|
|
||||||
|
|
||||||
assert.Equal(":8080", config.WebserverBind)
|
|
||||||
|
|
||||||
assert.Panics(func() {
|
|
||||||
ReadConfigFile("../config_example.co")
|
|
||||||
}, "wrong file")
|
|
||||||
|
|
||||||
assert.Panics(func() {
|
|
||||||
ReadConfigFile("testdata/config_panic.conf")
|
|
||||||
}, "wrong toml")
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
not unmarshalable
|
|
|
@ -1,12 +1,14 @@
|
||||||
|
state_path = "/tmp/freifunkmanager.json"
|
||||||
|
|
||||||
webserver_bind = ":8080"
|
webserver_bind = ":8080"
|
||||||
webroot = "./webroot"
|
webroot = "./webroot"
|
||||||
|
|
||||||
state_path = "/tmp/freifunkmanager.json"
|
|
||||||
|
|
||||||
ssh_key = "/etc/id_rsa"
|
ssh_key = "~/.ssh/id_rsa"
|
||||||
ssh_interface = "wlp4s0"
|
ssh_interface = "wlp4s0"
|
||||||
|
|
||||||
[yanic]
|
[yanic]
|
||||||
enable = true
|
enable = true
|
||||||
type = "tcp"
|
ifname = "lo"
|
||||||
address = "localhost:8081"
|
address = "::1"
|
||||||
|
port = 10001
|
||||||
|
|
|
@ -1,139 +0,0 @@
|
||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/genofire/golang-lib/log"
|
|
||||||
|
|
||||||
"github.com/FreifunkBremen/freifunkmanager/ssh"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
maxDB = -75
|
|
||||||
)
|
|
||||||
|
|
||||||
type Controller struct {
|
|
||||||
sshMgmt *ssh.Manager
|
|
||||||
no24Ghz map[string]bool
|
|
||||||
node map[string]map[string]time.Time
|
|
||||||
quit chan struct{}
|
|
||||||
sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewController(ssh *ssh.Manager) *Controller {
|
|
||||||
return &Controller{
|
|
||||||
sshMgmt: ssh,
|
|
||||||
quit: make(chan struct{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (c *Controller) Start() {
|
|
||||||
ticker := time.NewTicker(time.Second)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ticker.C:
|
|
||||||
c.tick()
|
|
||||||
case <-c.quit:
|
|
||||||
ticker.Stop()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) Close() {
|
|
||||||
close(c.quit)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) tick() {
|
|
||||||
c.sshMgmt.RunEverywhere("if [ \"$(uci get wireless.radio0.hwmode | grep -c a)\" -ne 0 ]; then echo \"client0\"; elif [ \"$(uci get wireless.radio1.hwmode | grep -c a)\" -ne 0 ]; then echo \"client1\"; fi", ssh.SSHResultToStringHandler(func(ip string, iface string, err error) {
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
addr := net.TCPAddr{IP: net.ParseIP(ip), Port: 22}
|
|
||||||
result, err := c.sshMgmt.RunOn(addr, fmt.Sprintf("iwinfo %s assoclist | grep SNR", iface))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, line := range strings.Split(result, "\n") {
|
|
||||||
parts := strings.Fields(line)
|
|
||||||
mac := parts[0]
|
|
||||||
db, err := strconv.Atoi(parts[1])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.no24Ghz[mac] = true
|
|
||||||
|
|
||||||
if db < maxDB {
|
|
||||||
|
|
||||||
node, ok := c.node[ip]
|
|
||||||
if !ok {
|
|
||||||
node = make(map[string]time.Time)
|
|
||||||
c.node[ip] = node
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
if last, ok := node[mac]; ok && last.After(now.Add(-3*time.Second)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Log.Info("force roamed %s from %s", mac, addr)
|
|
||||||
cmd := fmt.Sprintf("ubus call hostapd.%s del_client '{\"addr\":\"%s\", \"reason\":1, \"deauth\":true, \"ban_time\":1000}'", iface, mac)
|
|
||||||
c.sshMgmt.ExecuteOn(addr, cmd)
|
|
||||||
node[mac] = now
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}))
|
|
||||||
|
|
||||||
c.sshMgmt.RunEverywhere("if [ \"$(uci get wireless.radio0.hwmode | grep -c g)\" -ne 0 ]; then echo \"client0\"; elif [ \"$(uci get wireless.radio1.hwmode | grep -c g)\" -ne 0 ]; then echo \"client1\"; fi", ssh.SSHResultToStringHandler(func(ip string, iface string, err error) {
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
addr := net.TCPAddr{IP: net.ParseIP(ip), Port: 22}
|
|
||||||
result, err := c.sshMgmt.RunOn(addr, fmt.Sprintf("iwinfo %s assoclist | grep SNR", iface))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, line := range strings.Split(result, "\n") {
|
|
||||||
parts := strings.Fields(line)
|
|
||||||
mac := parts[0]
|
|
||||||
db, err := strconv.Atoi(parts[1])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := fmt.Sprintf("ubus call hostapd.%s del_client '{\"addr\":\"%s\", \"reason\":1, \"deauth\":true, \"ban_time\":1000}'", iface, mac)
|
|
||||||
|
|
||||||
if c.no24Ghz[mac] {
|
|
||||||
log.Log.Info("kicked becouse it use 2.4 Ghz %s from %s", mac, addr)
|
|
||||||
c.sshMgmt.ExecuteOn(addr, cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
if db < maxDB {
|
|
||||||
|
|
||||||
node, ok := c.node[ip]
|
|
||||||
if !ok {
|
|
||||||
node = make(map[string]time.Time)
|
|
||||||
c.node[ip] = node
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
if last, ok := node[mac]; ok && last.After(now.Add(-3*time.Second)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Log.Info("force roamed %s from %s", mac, addr)
|
|
||||||
c.sshMgmt.ExecuteOn(addr, cmd)
|
|
||||||
node[mac] = now
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}))
|
|
||||||
}
|
|
|
@ -8,68 +8,70 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
yanic "github.com/FreifunkBremen/yanic/database/socket/client"
|
|
||||||
runtimeYanic "github.com/FreifunkBremen/yanic/runtime"
|
|
||||||
"github.com/NYTimes/gziphandler"
|
"github.com/NYTimes/gziphandler"
|
||||||
|
"github.com/genofire/golang-lib/file"
|
||||||
httpLib "github.com/genofire/golang-lib/http"
|
httpLib "github.com/genofire/golang-lib/http"
|
||||||
"github.com/genofire/golang-lib/log"
|
|
||||||
"github.com/genofire/golang-lib/worker"
|
"github.com/genofire/golang-lib/worker"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
respondYanic "github.com/FreifunkBremen/yanic/respond"
|
||||||
|
runtimeYanic "github.com/FreifunkBremen/yanic/runtime"
|
||||||
|
|
||||||
configPackage "github.com/FreifunkBremen/freifunkmanager/config"
|
|
||||||
wifiController "github.com/FreifunkBremen/freifunkmanager/controller"
|
|
||||||
"github.com/FreifunkBremen/freifunkmanager/runtime"
|
"github.com/FreifunkBremen/freifunkmanager/runtime"
|
||||||
"github.com/FreifunkBremen/freifunkmanager/ssh"
|
"github.com/FreifunkBremen/freifunkmanager/ssh"
|
||||||
"github.com/FreifunkBremen/freifunkmanager/websocket"
|
"github.com/FreifunkBremen/freifunkmanager/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
configFile string
|
configFile string
|
||||||
config *configPackage.Config
|
config = &runtime.Config{}
|
||||||
nodes *runtime.Nodes
|
nodes *runtime.Nodes
|
||||||
commands *runtime.Commands
|
collector *respondYanic.Collector
|
||||||
yanicDialer *yanic.Dialer
|
verbose bool
|
||||||
stats *runtimeYanic.GlobalStats
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.StringVar(&configFile, "config", "config.conf", "path of configuration file (default:config.conf)")
|
flag.StringVar(&configFile, "config", "config.conf", "path of configuration file (default:config.conf)")
|
||||||
|
flag.BoolVar(&verbose, "v", false, "verbose logging")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
if verbose {
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
}
|
||||||
|
|
||||||
config = configPackage.ReadConfigFile(configFile)
|
if err := file.ReadTOML(configFile, config); err != nil {
|
||||||
|
log.Panicf("Error during read config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
log.Log.Info("starting...")
|
log.Info("starting...")
|
||||||
|
|
||||||
sshmanager := ssh.NewManager(config.SSHPrivateKey)
|
sshmanager := ssh.NewManager(config.SSHPrivateKey)
|
||||||
nodes = runtime.NewNodes(config.StatePath, config.SSHInterface, sshmanager)
|
nodes = runtime.NewNodes(config.StatePath, config.SSHInterface, sshmanager)
|
||||||
commands = runtime.NewCommands(sshmanager)
|
|
||||||
// nodesUpdateWorker := worker.NewWorker(time.Duration(3)*time.Minute, nodes.Updater)
|
|
||||||
nodesSaveWorker := worker.NewWorker(time.Duration(3)*time.Second, nodes.Saver)
|
nodesSaveWorker := worker.NewWorker(time.Duration(3)*time.Second, nodes.Saver)
|
||||||
controller := wifiController.NewController(sshmanager)
|
nodesUpdateWorker := worker.NewWorker(time.Duration(3)*time.Minute, nodes.Updater)
|
||||||
|
nodesYanic := runtimeYanic.NewNodes(&runtimeYanic.NodesConfig{})
|
||||||
|
|
||||||
// go nodesUpdateWorker.Start()
|
db := runtime.NewYanicDB(nodes)
|
||||||
go nodesSaveWorker.Start()
|
go nodesSaveWorker.Start()
|
||||||
go controller.Start()
|
go nodesUpdateWorker.Start()
|
||||||
|
|
||||||
websocket.Start(nodes, commands)
|
websocket.Start(nodes)
|
||||||
|
db.NotifyStats = websocket.NotifyStats
|
||||||
|
|
||||||
if config.Yanic.Enable {
|
if config.Yanic.Enable {
|
||||||
yanicDialer = yanic.Dial(config.Yanic.Type, config.Yanic.Address)
|
collector = respondYanic.NewCollector(db, nodesYanic, make(map[string][]string), []respondYanic.InterfaceConfig{respondYanic.InterfaceConfig{
|
||||||
yanicDialer.NodeHandler = nodes.LearnNode
|
InterfaceName: config.Yanic.InterfaceName,
|
||||||
yanicDialer.GlobalsHandler = func(data *runtimeYanic.GlobalStats) {
|
IPAddress: config.Yanic.Address,
|
||||||
stats = data
|
Port: config.Yanic.Port,
|
||||||
websocket.NotifyStats(data)
|
}})
|
||||||
}
|
defer collector.Close()
|
||||||
go yanicDialer.Start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Startwebserver
|
// Startwebserver
|
||||||
http.HandleFunc("/nodes", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/nodes", func(w http.ResponseWriter, r *http.Request) {
|
||||||
httpLib.Write(w, nodes)
|
httpLib.Write(w, nodes)
|
||||||
log.HTTP(r).Info("done")
|
|
||||||
})
|
})
|
||||||
http.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) {
|
||||||
httpLib.Write(w, stats)
|
httpLib.Write(w, db.Statistics)
|
||||||
log.HTTP(r).Info("done")
|
|
||||||
})
|
})
|
||||||
http.Handle("/", gziphandler.GzipHandler(http.FileServer(http.Dir(config.Webroot))))
|
http.Handle("/", gziphandler.GzipHandler(http.FileServer(http.Dir(config.Webroot))))
|
||||||
|
|
||||||
|
@ -79,27 +81,26 @@ func main() {
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := srv.ListenAndServe(); err != nil {
|
if err := srv.ListenAndServe(); err != nil {
|
||||||
log.Log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Log.Info("started")
|
log.Info("started")
|
||||||
|
|
||||||
// Wait for system signal
|
// Wait for system signal
|
||||||
sigs := make(chan os.Signal, 1)
|
sigs := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||||
sig := <-sigs
|
sig := <-sigs
|
||||||
|
|
||||||
// Stop services
|
|
||||||
websocket.Close()
|
websocket.Close()
|
||||||
|
|
||||||
|
// Stop services
|
||||||
srv.Close()
|
srv.Close()
|
||||||
controller.Close()
|
|
||||||
if config.Yanic.Enable {
|
if config.Yanic.Enable {
|
||||||
yanicDialer.Close()
|
collector.Close()
|
||||||
}
|
}
|
||||||
nodesSaveWorker.Close()
|
nodesSaveWorker.Close()
|
||||||
// nodesUpdateWorker.Close()
|
nodesUpdateWorker.Close()
|
||||||
sshmanager.Close()
|
|
||||||
|
|
||||||
log.Log.Info("stop recieve:", sig)
|
log.Info("stop recieve:", sig)
|
||||||
}
|
}
|
|
@ -1,44 +0,0 @@
|
||||||
package runtime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/FreifunkBremen/freifunkmanager/ssh"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Commands struct {
|
|
||||||
List map[string]*Command
|
|
||||||
mgmt *ssh.Manager
|
|
||||||
}
|
|
||||||
|
|
||||||
type Command struct {
|
|
||||||
ssh.List
|
|
||||||
Timestemp time.Time `json:"timestemp"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCommands(mgmt *ssh.Manager) *Commands {
|
|
||||||
return &Commands{
|
|
||||||
mgmt: mgmt,
|
|
||||||
List: make(map[string]*Command),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmds *Commands) AddCommand(c *Command) *Command {
|
|
||||||
cmd := mapCommand(cmds.mgmt, c)
|
|
||||||
cmds.List[cmd.ID] = cmd
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapCommand(mgmt *ssh.Manager, c *Command) *Command {
|
|
||||||
list := mgmt.CreateList(c.Command)
|
|
||||||
command := &Command{List: *list}
|
|
||||||
command.ID = c.ID
|
|
||||||
command.Timestemp = c.Timestemp
|
|
||||||
return command
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Command) Run(f func()) {
|
|
||||||
cmd.List.Run()
|
|
||||||
f()
|
|
||||||
}
|
|
|
@ -1,19 +1,12 @@
|
||||||
package config
|
package runtime
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
|
||||||
"github.com/genofire/golang-lib/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
//config file of this daemon (for more the config_example.conf in git repository)
|
//config file of this daemon (for more the config_example.conf in git repository)
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// prevent crashes
|
// prevent crashes
|
||||||
StatePath string `toml:"state_path"`
|
StatePath string `toml:"state_path"`
|
||||||
|
|
||||||
// address on which the api and static content webserver runs
|
// address on which the api and static content webserver runs
|
||||||
WebserverBind string `toml:"webserver_bind"`
|
WebserverBind string `toml:"webserver_bind"`
|
||||||
|
|
||||||
// path to deliver static content
|
// path to deliver static content
|
||||||
Webroot string `toml:"webroot"`
|
Webroot string `toml:"webroot"`
|
||||||
|
|
||||||
|
@ -23,23 +16,9 @@ type Config struct {
|
||||||
|
|
||||||
// yanic socket
|
// yanic socket
|
||||||
Yanic struct {
|
Yanic struct {
|
||||||
Enable bool `toml:"enable"`
|
Enable bool `toml:"enable"`
|
||||||
Type string `toml:"type"`
|
InterfaceName string `toml:"ifname"`
|
||||||
Address string `toml:"address"`
|
Address string `toml:"address"`
|
||||||
|
Port int `toml:"port"`
|
||||||
} `toml:"yanic"`
|
} `toml:"yanic"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//reads a config model from path of a yml file
|
|
||||||
func ReadConfigFile(path string) *Config {
|
|
||||||
config := &Config{}
|
|
||||||
file, err := ioutil.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Log.Panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := toml.Unmarshal(file, config); err != nil {
|
|
||||||
log.Log.Panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return config
|
|
||||||
}
|
|
|
@ -5,10 +5,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/genofire/golang-lib/log"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/FreifunkBremen/yanic/data"
|
yanicData "github.com/FreifunkBremen/yanic/data"
|
||||||
"github.com/FreifunkBremen/yanic/jsontime"
|
"github.com/FreifunkBremen/yanic/lib/jsontime"
|
||||||
yanicRuntime "github.com/FreifunkBremen/yanic/runtime"
|
yanicRuntime "github.com/FreifunkBremen/yanic/runtime"
|
||||||
|
|
||||||
"github.com/FreifunkBremen/freifunkmanager/ssh"
|
"github.com/FreifunkBremen/freifunkmanager/ssh"
|
||||||
|
@ -23,16 +23,16 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
Lastseen jsontime.Time `json:"lastseen"`
|
Lastseen jsontime.Time `json:"lastseen"`
|
||||||
NodeID string `json:"node_id"`
|
NodeID string `json:"node_id"`
|
||||||
Hostname string `json:"hostname"`
|
Hostname string `json:"hostname"`
|
||||||
Location data.Location `json:"location"`
|
Location yanicData.Location `json:"location"`
|
||||||
Wireless data.Wireless `json:"wireless"`
|
Wireless yanicData.Wireless `json:"wireless"`
|
||||||
Owner string `json:"owner"`
|
Owner string `json:"owner"`
|
||||||
Address net.IP `json:"-"`
|
Address net.IP `json:"-"`
|
||||||
Stats struct {
|
Stats struct {
|
||||||
Wireless data.WirelessStatistics `json:"wireless"`
|
Wireless yanicData.WirelessStatistics `json:"wireless"`
|
||||||
Clients data.Clients `json:"clients"`
|
Clients yanicData.Clients `json:"clients"`
|
||||||
} `json:"statistics"`
|
} `json:"statistics"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,12 @@ func NewNode(nodeOrigin *yanicRuntime.Node) *Node {
|
||||||
node := &Node{
|
node := &Node{
|
||||||
Hostname: nodeinfo.Hostname,
|
Hostname: nodeinfo.Hostname,
|
||||||
NodeID: nodeinfo.NodeID,
|
NodeID: nodeinfo.NodeID,
|
||||||
Address: nodeOrigin.Address,
|
}
|
||||||
|
for _, ip := range nodeinfo.Network.Addresses {
|
||||||
|
ipAddr := net.ParseIP(ip)
|
||||||
|
if node.Address == nil || ipAddr.IsGlobalUnicast() {
|
||||||
|
node.Address = ipAddr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if owner := nodeinfo.Owner; owner != nil {
|
if owner := nodeinfo.Owner; owner != nil {
|
||||||
node.Owner = owner.Contact
|
node.Owner = owner.Contact
|
||||||
|
@ -71,14 +76,14 @@ func (n *Node) SSHUpdate(ssh *ssh.Manager, iface string, oldnode *Node) {
|
||||||
ssh.ExecuteOn(addr, fmt.Sprintf(SSHUpdateOwner, n.Owner))
|
ssh.ExecuteOn(addr, fmt.Sprintf(SSHUpdateOwner, n.Owner))
|
||||||
}
|
}
|
||||||
if oldnode == nil || !locationEqual(n.Location, oldnode.Location) {
|
if oldnode == nil || !locationEqual(n.Location, oldnode.Location) {
|
||||||
ssh.ExecuteOn(addr, fmt.Sprintf(SSHUpdateLocation, n.Location.Latitude, n.Location.Longtitude))
|
ssh.ExecuteOn(addr, fmt.Sprintf(SSHUpdateLocation, n.Location.Latitude, n.Location.Longitude))
|
||||||
}
|
}
|
||||||
if oldnode == nil || !wirelessEqual(n.Wireless, oldnode.Wireless) {
|
if oldnode == nil || !wirelessEqual(n.Wireless, oldnode.Wireless) {
|
||||||
ssh.ExecuteOn(addr, fmt.Sprintf(SSHUpdateWifiFreq24, n.Wireless.Channel24, n.Wireless.TxPower24, n.Wireless.Channel24, n.Wireless.TxPower24))
|
ssh.ExecuteOn(addr, fmt.Sprintf(SSHUpdateWifiFreq24, n.Wireless.Channel24, n.Wireless.TxPower24, n.Wireless.Channel24, n.Wireless.TxPower24))
|
||||||
ssh.ExecuteOn(addr, fmt.Sprintf(SSHUpdateWifiFreq5, n.Wireless.Channel5, n.Wireless.TxPower5, n.Wireless.Channel5, n.Wireless.TxPower5))
|
ssh.ExecuteOn(addr, fmt.Sprintf(SSHUpdateWifiFreq5, n.Wireless.Channel5, n.Wireless.TxPower5, n.Wireless.Channel5, n.Wireless.TxPower5))
|
||||||
ssh.ExecuteOn(addr, "wifi")
|
ssh.ExecuteOn(addr, "wifi")
|
||||||
// send warning for running wifi, because it kicks clients from node
|
// send warning for running wifi, because it kicks clients from node
|
||||||
log.Log.Warn("[cmd] wifi ", n.NodeID)
|
log.Warn("[cmd] wifi ", n.NodeID)
|
||||||
}
|
}
|
||||||
oldnode = n
|
oldnode = n
|
||||||
}
|
}
|
||||||
|
@ -89,7 +94,7 @@ func (n *Node) Update(node *yanicRuntime.Node) {
|
||||||
if nodeinfo := node.Nodeinfo; nodeinfo != nil {
|
if nodeinfo := node.Nodeinfo; nodeinfo != nil {
|
||||||
n.Hostname = nodeinfo.Hostname
|
n.Hostname = nodeinfo.Hostname
|
||||||
n.NodeID = nodeinfo.NodeID
|
n.NodeID = nodeinfo.NodeID
|
||||||
n.Address = node.Address
|
n.Address = node.Address.IP
|
||||||
|
|
||||||
if owner := nodeinfo.Owner; owner != nil {
|
if owner := nodeinfo.Owner; owner != nil {
|
||||||
n.Owner = owner.Contact
|
n.Owner = owner.Contact
|
||||||
|
@ -124,11 +129,11 @@ func (n *Node) IsEqual(node *Node) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func locationEqual(a, b data.Location) bool {
|
func locationEqual(a, b yanicData.Location) bool {
|
||||||
if a.Latitude != b.Latitude {
|
if a.Latitude != b.Latitude {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if a.Longtitude != b.Longtitude {
|
if a.Longitude != b.Longitude {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if a.Altitude != b.Altitude {
|
if a.Altitude != b.Altitude {
|
||||||
|
@ -137,7 +142,7 @@ func locationEqual(a, b data.Location) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func wirelessEqual(a, b data.Wireless) bool {
|
func wirelessEqual(a, b yanicData.Wireless) bool {
|
||||||
if a.Channel24 != b.Channel24 {
|
if a.Channel24 != b.Channel24 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,27 @@
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/FreifunkBremen/yanic/data"
|
yanicData "github.com/FreifunkBremen/yanic/data"
|
||||||
yanicRuntime "github.com/FreifunkBremen/yanic/runtime"
|
yanicRuntime "github.com/FreifunkBremen/yanic/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNode(t *testing.T) {
|
func TestNode(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
node1 := &yanicRuntime.Node{}
|
node1 := &yanicRuntime.Node{
|
||||||
|
Address: &net.UDPAddr{IP: net.ParseIP("ff02::1")},
|
||||||
|
}
|
||||||
n1 := NewNode(node1)
|
n1 := NewNode(node1)
|
||||||
assert.Nil(n1)
|
assert.Nil(n1)
|
||||||
|
|
||||||
node1.Nodeinfo = &data.NodeInfo{
|
node1.Nodeinfo = &yanicData.NodeInfo{
|
||||||
Owner: &data.Owner{Contact: "blub"},
|
Owner: &yanicData.Owner{Contact: "blub"},
|
||||||
Wireless: &data.Wireless{},
|
Wireless: &yanicData.Wireless{},
|
||||||
Location: &data.Location{Altitude: 13},
|
Location: &yanicData.Location{Altitude: 13},
|
||||||
}
|
}
|
||||||
n1 = NewNode(node1)
|
n1 = NewNode(node1)
|
||||||
assert.NotNil(n1)
|
assert.NotNil(n1)
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"os"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/FreifunkBremen/yanic/jsontime"
|
"github.com/FreifunkBremen/yanic/lib/jsontime"
|
||||||
yanic "github.com/FreifunkBremen/yanic/runtime"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/genofire/golang-lib/log"
|
|
||||||
|
yanicRuntime "github.com/FreifunkBremen/yanic/runtime"
|
||||||
|
"github.com/genofire/golang-lib/file"
|
||||||
|
|
||||||
"github.com/FreifunkBremen/freifunkmanager/ssh"
|
"github.com/FreifunkBremen/freifunkmanager/ssh"
|
||||||
)
|
)
|
||||||
|
@ -30,17 +30,17 @@ func NewNodes(path string, iface string, mgmt *ssh.Manager) *Nodes {
|
||||||
statePath: path,
|
statePath: path,
|
||||||
iface: iface,
|
iface: iface,
|
||||||
}
|
}
|
||||||
nodes.load()
|
file.ReadJSON(path, nodes)
|
||||||
return nodes
|
return nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nodes *Nodes) LearnNode(n *yanic.Node) {
|
func (nodes *Nodes) LearnNode(n *yanicRuntime.Node) {
|
||||||
node := NewNode(n)
|
node := NewNode(n)
|
||||||
if node == nil {
|
if node == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
node.Lastseen = jsontime.Now()
|
node.Lastseen = jsontime.Now()
|
||||||
logger := log.Log.WithField("method", "LearnNode").WithField("node_id", node.NodeID)
|
logger := log.WithField("method", "LearnNode").WithField("node_id", node.NodeID)
|
||||||
nodes.Lock()
|
nodes.Lock()
|
||||||
defer nodes.Unlock()
|
defer nodes.Unlock()
|
||||||
if lNode := nodes.List[node.NodeID]; lNode != nil {
|
if lNode := nodes.List[node.NodeID]; lNode != nil {
|
||||||
|
@ -83,7 +83,7 @@ func (nodes *Nodes) notify(node *Node, system bool) {
|
||||||
|
|
||||||
func (nodes *Nodes) UpdateNode(node *Node) {
|
func (nodes *Nodes) UpdateNode(node *Node) {
|
||||||
if node == nil {
|
if node == nil {
|
||||||
log.Log.Warn("no new node to update")
|
log.Warn("no new node to update")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
nodes.Lock()
|
nodes.Lock()
|
||||||
|
@ -91,7 +91,7 @@ func (nodes *Nodes) UpdateNode(node *Node) {
|
||||||
if n, ok := nodes.List[node.NodeID]; ok {
|
if n, ok := nodes.List[node.NodeID]; ok {
|
||||||
node.Address = n.Address
|
node.Address = n.Address
|
||||||
go node.SSHUpdate(nodes.ssh, nodes.iface, n)
|
go node.SSHUpdate(nodes.ssh, nodes.iface, n)
|
||||||
log.Log.Info("update node", node.NodeID)
|
log.Info("update node", node.NodeID)
|
||||||
}
|
}
|
||||||
nodes.List[node.NodeID] = node
|
nodes.List[node.NodeID] = node
|
||||||
nodes.notify(node, true)
|
nodes.notify(node, true)
|
||||||
|
@ -105,24 +105,12 @@ func (nodes *Nodes) Updater() {
|
||||||
go node.SSHUpdate(nodes.ssh, nodes.iface, n)
|
go node.SSHUpdate(nodes.ssh, nodes.iface, n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Log.Info("updater per ssh")
|
log.Info("updater per ssh")
|
||||||
}
|
|
||||||
|
|
||||||
func (nodes *Nodes) load() {
|
|
||||||
if f, err := os.Open(nodes.statePath); err == nil {
|
|
||||||
if err = json.NewDecoder(f).Decode(nodes); err == nil {
|
|
||||||
log.Log.Infof("loaded %d nodes", len(nodes.List))
|
|
||||||
} else {
|
|
||||||
log.Log.Error("failed to unmarshal nodes:", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Log.Error("failed to load cached nodes:", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nodes *Nodes) Saver() {
|
func (nodes *Nodes) Saver() {
|
||||||
nodes.Lock()
|
nodes.Lock()
|
||||||
yanic.SaveJSON(nodes, nodes.statePath)
|
file.SaveJSON(nodes.statePath, nodes)
|
||||||
nodes.Unlock()
|
nodes.Unlock()
|
||||||
log.Log.Debug("saved state file")
|
log.Debug("saved state file")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
databaseYanic "github.com/FreifunkBremen/yanic/database"
|
||||||
|
runtimeYanic "github.com/FreifunkBremen/yanic/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type YanicDB struct {
|
||||||
|
databaseYanic.Connection
|
||||||
|
nodes *Nodes
|
||||||
|
Statistics *runtimeYanic.GlobalStats
|
||||||
|
NotifyStats func(data *runtimeYanic.GlobalStats)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewYanicDB(nodes *Nodes) *YanicDB {
|
||||||
|
return &YanicDB{
|
||||||
|
nodes: nodes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *YanicDB) InsertNode(node *runtimeYanic.Node) {
|
||||||
|
conn.nodes.LearnNode(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *YanicDB) InsertLink(link *runtimeYanic.Link, time time.Time) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *YanicDB) InsertGlobals(stats *runtimeYanic.GlobalStats, time time.Time, site string, domain string) {
|
||||||
|
conn.Statistics = stats
|
||||||
|
if conn.NotifyStats != nil {
|
||||||
|
conn.NotifyStats(stats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *YanicDB) PruneNodes(deleteAfter time.Duration) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *YanicDB) Close() {
|
||||||
|
}
|
|
@ -3,23 +3,17 @@ package ssh
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/genofire/golang-lib/log"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m *Manager) ExecuteEverywhere(cmd string) {
|
|
||||||
m.clientsMUX.Lock()
|
|
||||||
defer m.clientsMUX.Unlock()
|
|
||||||
for host, client := range m.clients {
|
|
||||||
m.execute(host, client, cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) ExecuteOn(addr net.TCPAddr, cmd string) error {
|
func (m *Manager) ExecuteOn(addr net.TCPAddr, cmd string) error {
|
||||||
client, err := m.ConnectTo(addr)
|
client, err := m.ConnectTo(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer client.Close()
|
||||||
return m.execute(addr.IP.String(), client, cmd)
|
return m.execute(addr.IP.String(), client, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,14 +21,9 @@ func (m *Manager) execute(host string, client *ssh.Client, cmd string) error {
|
||||||
session, err := client.NewSession()
|
session, err := client.NewSession()
|
||||||
defer session.Close()
|
defer session.Close()
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Log.Warnf("can not create session on %s: %s", host, err)
|
|
||||||
delete(m.clients, host)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = session.Run(cmd)
|
err = session.Run(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log.Warnf("could not run %s on %s: %s", cmd, host, err)
|
log.Warnf("could not run %s on %s: %s", cmd, host, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -15,10 +15,9 @@ func TestExecute(t *testing.T) {
|
||||||
mgmt := NewManager("~/.ssh/id_rsa")
|
mgmt := NewManager("~/.ssh/id_rsa")
|
||||||
assert.NotNil(mgmt, "no new manager created")
|
assert.NotNil(mgmt, "no new manager created")
|
||||||
|
|
||||||
_, err := mgmt.ConnectTo(addr)
|
client, err := mgmt.ConnectTo(addr)
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
|
|
||||||
mgmt.ExecuteEverywhere("echo $HOSTNAME")
|
|
||||||
err = mgmt.ExecuteOn(addr, "uptime")
|
err = mgmt.ExecuteOn(addr, "uptime")
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
err = mgmt.ExecuteOn(addr, "echo $HOSTNAME")
|
err = mgmt.ExecuteOn(addr, "echo $HOSTNAME")
|
||||||
|
@ -26,5 +25,5 @@ func TestExecute(t *testing.T) {
|
||||||
err = mgmt.ExecuteOn(addr, "exit 1")
|
err = mgmt.ExecuteOn(addr, "exit 1")
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
|
|
||||||
mgmt.Close()
|
client.Close()
|
||||||
}
|
}
|
||||||
|
|
59
ssh/list.go
59
ssh/list.go
|
@ -1,59 +0,0 @@
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
)
|
|
||||||
|
|
||||||
type List struct {
|
|
||||||
Command string `json:"cmd"`
|
|
||||||
Clients map[string]*ListResult `json:"clients"`
|
|
||||||
sshManager *Manager
|
|
||||||
sync.Mutex
|
|
||||||
}
|
|
||||||
type ListResult struct {
|
|
||||||
ssh *ssh.Client
|
|
||||||
Running bool `json:"running"`
|
|
||||||
WithError bool `json:"with_error"`
|
|
||||||
Result string `json:"result"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) CreateList(cmd string) *List {
|
|
||||||
list := &List{
|
|
||||||
Command: cmd,
|
|
||||||
sshManager: m,
|
|
||||||
Clients: make(map[string]*ListResult),
|
|
||||||
}
|
|
||||||
m.clientsMUX.Lock()
|
|
||||||
defer m.clientsMUX.Unlock()
|
|
||||||
for host, client := range m.clients {
|
|
||||||
list.Clients[host] = &ListResult{Running: true, ssh: client}
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l List) Run() {
|
|
||||||
wg := new(sync.WaitGroup)
|
|
||||||
for host, client := range l.Clients {
|
|
||||||
wg.Add(1)
|
|
||||||
go l.runlistelement(host, client, wg)
|
|
||||||
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l List) runlistelement(host string, client *ListResult, wg *sync.WaitGroup) {
|
|
||||||
defer wg.Done()
|
|
||||||
result, err := l.sshManager.run(host, client.ssh, l.Command)
|
|
||||||
|
|
||||||
l.Lock()
|
|
||||||
defer l.Unlock()
|
|
||||||
|
|
||||||
client.Running = false
|
|
||||||
if err != nil {
|
|
||||||
client.WithError = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
client.Result = SSHResultToString(result)
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestList(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
addr := net.TCPAddr{IP: net.ParseIP("2a06:8782:ffbb:1337::127"), Port: 22}
|
|
||||||
|
|
||||||
mgmt := NewManager("~/.ssh/id_rsa")
|
|
||||||
assert.NotNil(mgmt, "no new manager created")
|
|
||||||
_, err := mgmt.ConnectTo(addr)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
list := mgmt.CreateList("exit 1")
|
|
||||||
assert.Len(list.Clients, 1)
|
|
||||||
client := list.Clients[addr.IP.String()]
|
|
||||||
assert.True(client.Running)
|
|
||||||
list.Run()
|
|
||||||
assert.False(client.Running)
|
|
||||||
assert.True(client.WithError)
|
|
||||||
assert.Equal("", client.Result)
|
|
||||||
|
|
||||||
list = mgmt.CreateList("echo 15")
|
|
||||||
assert.Len(list.Clients, 1)
|
|
||||||
client = list.Clients[addr.IP.String()]
|
|
||||||
assert.True(client.Running)
|
|
||||||
list.Run()
|
|
||||||
assert.False(client.Running)
|
|
||||||
assert.False(client.WithError)
|
|
||||||
assert.Equal("15", client.Result)
|
|
||||||
}
|
|
|
@ -7,14 +7,13 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/genofire/golang-lib/log"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
// the SSH Connection Manager for multiple connections
|
// the SSH Connection Manager for multiple connections
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
config *ssh.ClientConfig
|
config *ssh.ClientConfig
|
||||||
clients map[string]*ssh.Client
|
|
||||||
clientsBlacklist map[string]time.Time
|
clientsBlacklist map[string]time.Time
|
||||||
clientsMUX sync.Mutex
|
clientsMUX sync.Mutex
|
||||||
}
|
}
|
||||||
|
@ -36,7 +35,6 @@ func NewManager(file string) *Manager {
|
||||||
}
|
}
|
||||||
return &Manager{
|
return &Manager{
|
||||||
config: sshConfig,
|
config: sshConfig,
|
||||||
clients: make(map[string]*ssh.Client),
|
|
||||||
clientsBlacklist: make(map[string]time.Time),
|
clientsBlacklist: make(map[string]time.Time),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,27 +50,15 @@ func (m *Manager) ConnectTo(addr net.TCPAddr) (*ssh.Client, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if client, ok := m.clients[addr.IP.String()]; ok {
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := ssh.Dial("tcp", addr.String(), m.config)
|
client, err := ssh.Dial("tcp", addr.String(), m.config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "no supported methods remain") {
|
if strings.Contains(err.Error(), "no supported methods remain") {
|
||||||
m.clientsBlacklist[addr.IP.String()] = time.Now()
|
m.clientsBlacklist[addr.IP.String()] = time.Now()
|
||||||
log.Log.Warnf("node was set on the blacklist: %s", err)
|
log.Warnf("node was set on the blacklist: %s", err)
|
||||||
return nil, errors.New("node on blacklist")
|
return nil, errors.New("node on blacklist")
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
m.clients[addr.IP.String()] = client
|
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Close() {
|
|
||||||
for host, client := range m.clients {
|
|
||||||
client.Close()
|
|
||||||
delete(m.clients, host)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ func TestManager(t *testing.T) {
|
||||||
mgmt := NewManager("~/.ssh/id_rsa")
|
mgmt := NewManager("~/.ssh/id_rsa")
|
||||||
assert.NotNil(mgmt, "no new manager created")
|
assert.NotNil(mgmt, "no new manager created")
|
||||||
|
|
||||||
mgmt.ConnectTo(net.TCPAddr{IP: net.ParseIP("2a06:8782:ffbb:1337::127"), Port: 22})
|
client, _ := mgmt.ConnectTo(net.TCPAddr{IP: net.ParseIP("2a06:8782:ffbb:1337::127"), Port: 22})
|
||||||
|
client.Close()
|
||||||
mgmt.Close()
|
|
||||||
}
|
}
|
||||||
|
|
24
ssh/run.go
24
ssh/run.go
|
@ -4,7 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/genofire/golang-lib/log"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,22 +23,12 @@ func SSHResultToStringHandler(handler SSHResultHandler) SSHResultHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) RunEverywhere(cmd string, handler SSHResultHandler) {
|
|
||||||
m.clientsMUX.Lock()
|
|
||||||
defer m.clientsMUX.Unlock()
|
|
||||||
for host, client := range m.clients {
|
|
||||||
go func() {
|
|
||||||
result, err := m.run(host, client, cmd)
|
|
||||||
handler(host, result, err)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) RunOn(addr net.TCPAddr, cmd string) (string, error) {
|
func (m *Manager) RunOn(addr net.TCPAddr, cmd string) (string, error) {
|
||||||
client, err := m.ConnectTo(addr)
|
client, err := m.ConnectTo(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
defer client.Close()
|
||||||
return m.run(addr.IP.String(), client, cmd)
|
return m.run(addr.IP.String(), client, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,22 +37,18 @@ func (m *Manager) run(host string, client *ssh.Client, cmd string) (string, erro
|
||||||
defer session.Close()
|
defer session.Close()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log.Warnf("can not create session on %s: %s", host, err)
|
log.Warnf("can not create session on %s: %s", host, err)
|
||||||
m.clientsMUX.Lock()
|
|
||||||
delete(m.clients, host)
|
|
||||||
m.clientsMUX.Unlock()
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
buffer := &bytes.Buffer{}
|
buffer := &bytes.Buffer{}
|
||||||
session.Stdout = buffer
|
session.Stdout = buffer
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log.Warnf("can not create pipe for run on %s: %s", host, err)
|
log.Warnf("can not create pipe for run on %s: %s", host, err)
|
||||||
delete(m.clients, host)
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
err = session.Run(cmd)
|
err = session.Run(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log.Debugf("could not run %s on %s: %s", cmd, host, err)
|
log.Debugf("could not run %s on %s: %s", cmd, host, err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return buffer.String(), nil
|
return buffer.String(), nil
|
||||||
|
|
|
@ -14,14 +14,10 @@ func TestRun(t *testing.T) {
|
||||||
mgmt := NewManager("~/.ssh/id_rsa")
|
mgmt := NewManager("~/.ssh/id_rsa")
|
||||||
assert.NotNil(mgmt, "no new manager created")
|
assert.NotNil(mgmt, "no new manager created")
|
||||||
|
|
||||||
_, err := mgmt.ConnectTo(addr)
|
client, err := mgmt.ConnectTo(addr)
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
|
client.Close()
|
||||||
|
|
||||||
mgmt.RunEverywhere("echo 13", SSHResultToStringHandler(func(result string, err error) {
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
assert.Equal("13", result)
|
|
||||||
}))
|
|
||||||
result, err := mgmt.RunOn(addr, "echo 16")
|
result, err := mgmt.RunOn(addr, "echo 16")
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
|
|
||||||
|
@ -29,6 +25,4 @@ func TestRun(t *testing.T) {
|
||||||
resultInt, _ := strconv.Atoi(str)
|
resultInt, _ := strconv.Atoi(str)
|
||||||
|
|
||||||
assert.Equal(16, resultInt)
|
assert.Equal(16, resultInt)
|
||||||
|
|
||||||
mgmt.Close()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 79781076be6e810eff663064cf68295e7f6e6487
|
|
|
@ -24,7 +24,6 @@
|
||||||
<script src="/js/domlib.js"></script>
|
<script src="/js/domlib.js"></script>
|
||||||
<script src="/js/store.js"></script>
|
<script src="/js/store.js"></script>
|
||||||
<script src="/js/notify.js"></script>
|
<script src="/js/notify.js"></script>
|
||||||
<script src="/js/gui_console.js"></script>
|
|
||||||
<script src="/js/gui_list.js"></script>
|
<script src="/js/gui_list.js"></script>
|
||||||
<script src="/js/gui_map.js"></script>
|
<script src="/js/gui_map.js"></script>
|
||||||
<script src="/js/gui_node.js"></script>
|
<script src="/js/gui_node.js"></script>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* exported gui,router */
|
/* exported gui,router */
|
||||||
/* globals socket,notify,domlib,guiList,guiMap,guiStats,guiNode,guiConsole */
|
/* globals socket,notify,domlib,guiList,guiMap,guiStats,guiNode */
|
||||||
|
|
||||||
const gui = {},
|
const gui = {},
|
||||||
router = new Navigo(null, true, '#');
|
router = new Navigo(null, true, '#');
|
||||||
|
@ -58,9 +58,6 @@ const gui = {},
|
||||||
}
|
}
|
||||||
|
|
||||||
router.on({
|
router.on({
|
||||||
'/console': function routerConsole () {
|
|
||||||
setView(guiConsole);
|
|
||||||
},
|
|
||||||
'/list': function routerList () {
|
'/list': function routerList () {
|
||||||
setView(guiList);
|
setView(guiList);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,277 +0,0 @@
|
||||||
/* exported guiConsole */
|
|
||||||
/* globals domlib,store,socket */
|
|
||||||
const guiConsole = {};
|
|
||||||
|
|
||||||
(function init () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const view = guiConsole,
|
|
||||||
ownCMDs = ['0'],
|
|
||||||
cmdRow = {};
|
|
||||||
let container = null,
|
|
||||||
el = null,
|
|
||||||
output = null,
|
|
||||||
editing = false,
|
|
||||||
ownfilter = false;
|
|
||||||
|
|
||||||
function createID () {
|
|
||||||
let digit = new Date().getTime();
|
|
||||||
|
|
||||||
// Use high-precision timer if available
|
|
||||||
/* eslint-disable */
|
|
||||||
if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
|
|
||||||
digit += performance.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => {
|
|
||||||
const result = (digit + Math.random() * 16) % 16 | 0;
|
|
||||||
|
|
||||||
digit = Math.floor(digit / 16);
|
|
||||||
|
|
||||||
return (char === 'x'
|
|
||||||
? result
|
|
||||||
: result & 0x3 | 0x8).toString(16);
|
|
||||||
});
|
|
||||||
/* eslint-enable*/
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCMD (row, cmd) {
|
|
||||||
if (cmd.cmd === '' && cmd.timestemp === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
row.cmd.innerHTML = cmd.cmd;
|
|
||||||
row.timestemp.innerHTML = moment(cmd.timestemp).fromNow(true);
|
|
||||||
|
|
||||||
let running = 0,
|
|
||||||
failed = 0,
|
|
||||||
sum = 0;
|
|
||||||
|
|
||||||
if (cmd.clients) {
|
|
||||||
sum = Object.keys(cmd.clients).length;
|
|
||||||
|
|
||||||
Object.keys(cmd.clients).forEach((addr) => {
|
|
||||||
const client = cmd.clients[addr],
|
|
||||||
clientRow = row.clients[addr];
|
|
||||||
|
|
||||||
clientRow.status.classList.remove('running', 'failed', 'success');
|
|
||||||
if (client.running) {
|
|
||||||
running += 1;
|
|
||||||
clientRow.status.classList.add('running');
|
|
||||||
} else if (client.with_error) {
|
|
||||||
failed += 1;
|
|
||||||
clientRow.status.classList.add('failed');
|
|
||||||
} else {
|
|
||||||
clientRow.status.classList.add('success');
|
|
||||||
}
|
|
||||||
|
|
||||||
clientRow.result.innerHTML = client.result;
|
|
||||||
clientRow.host.innerHTML = addr;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
row.status.classList.remove('running', 'failed', 'success');
|
|
||||||
if (running > 0) {
|
|
||||||
row.status.innerHTML = `running (${running}`;
|
|
||||||
row.status.classList.add('running');
|
|
||||||
} else if (failed > 0 || sum === 0) {
|
|
||||||
row.status.innerHTML = `failed (${failed}`;
|
|
||||||
row.status.classList.add('failed');
|
|
||||||
} else {
|
|
||||||
row.status.innerHTML = `success (${sum}`;
|
|
||||||
row.status.classList.add('success');
|
|
||||||
}
|
|
||||||
row.status.innerHTML += `/${sum})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createRow (cmd) {
|
|
||||||
const row = {
|
|
||||||
'clients': {},
|
|
||||||
'clientsContainer': document.createElement('tr'),
|
|
||||||
'clientsEl': {},
|
|
||||||
'el': document.createElement('tr')
|
|
||||||
},
|
|
||||||
tab = domlib.newAt(row.clientsContainer, 'td'),
|
|
||||||
clientRow = domlib.newAt(tab, 'table');
|
|
||||||
|
|
||||||
tab.setAttribute('colspan', '3');
|
|
||||||
|
|
||||||
|
|
||||||
if (cmd.cmd === '' && cmd.timestemp === 0) {
|
|
||||||
const initRow = domlib.newAt(row.el, 'td');
|
|
||||||
|
|
||||||
initRow.setAttribute('colspan', '3');
|
|
||||||
initRow.innerHTML = '\n' +
|
|
||||||
' _______ ________ __\n' +
|
|
||||||
' | |.-----.-----.-----.| | | |.----.| |_\n' +
|
|
||||||
' | - || _ | -__| || | | || _|| _|\n' +
|
|
||||||
' |_______|| __|_____|__|__||________||__| |____|\n' +
|
|
||||||
' |__| W I R E L E S S F R E E D O M\n' +
|
|
||||||
' -----------------------------------------------------\n' +
|
|
||||||
' FreifunkManager shell for openwrt/Lede/gluon systems \n' +
|
|
||||||
' -----------------------------------------------------\n' +
|
|
||||||
' * 1 1/2 oz Gin Shake with a glassful\n' +
|
|
||||||
' * 1/4 oz Triple Sec of broken ice and pour\n' +
|
|
||||||
' * 3/4 oz Lime Juice unstrained into a goblet.\n' +
|
|
||||||
' * 1 1/2 oz Orange Juice\n' +
|
|
||||||
' * 1 tsp. Grenadine Syrup\n' +
|
|
||||||
' -----------------------------------------------------\n';
|
|
||||||
|
|
||||||
return row;
|
|
||||||
}
|
|
||||||
row.timestemp = domlib.newAt(row.el, 'td');
|
|
||||||
row.cmd = domlib.newAt(row.el, 'td');
|
|
||||||
row.status = domlib.newAt(row.el, 'td');
|
|
||||||
|
|
||||||
row.el.classList.add('cmd');
|
|
||||||
row.timestemp.classList.add('time');
|
|
||||||
row.status.classList.add('status');
|
|
||||||
|
|
||||||
if (cmd.clients) {
|
|
||||||
Object.keys(cmd.clients).forEach((addr) => {
|
|
||||||
const clientEl = domlib.newAt(clientRow, 'tr'),
|
|
||||||
clients = {
|
|
||||||
'host': domlib.newAt(clientEl, 'td'),
|
|
||||||
'result': domlib.newAt(clientEl, 'td'),
|
|
||||||
'status': domlib.newAt(clientEl, 'td')
|
|
||||||
};
|
|
||||||
|
|
||||||
clients.host.classList.add('host');
|
|
||||||
clients.status.classList.add('status');
|
|
||||||
|
|
||||||
row.clientsEl[addr] = clientEl;
|
|
||||||
row.clients[addr] = clients;
|
|
||||||
});
|
|
||||||
row.el.addEventListener('click', () => {
|
|
||||||
if (row.clientsContainer.parentElement) {
|
|
||||||
row.el.parentElement.removeChild(row.clientsContainer);
|
|
||||||
} else {
|
|
||||||
row.el.parentElement.insertBefore(row.clientsContainer, row.el.nextSibling);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
updateCMD(row, cmd);
|
|
||||||
|
|
||||||
return row;
|
|
||||||
}
|
|
||||||
|
|
||||||
function update () {
|
|
||||||
if (editing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let cmds = store.getCMDs();
|
|
||||||
|
|
||||||
if (ownfilter) {
|
|
||||||
const tmp = cmds;
|
|
||||||
|
|
||||||
cmds = {};
|
|
||||||
Object.keys(tmp).
|
|
||||||
forEach((id) => {
|
|
||||||
if (ownCMDs.indexOf(id) >= 0) {
|
|
||||||
cmds[id] = tmp[id];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Object.keys(cmdRow).forEach((id) => {
|
|
||||||
if (cmdRow[id].el.parentElement) {
|
|
||||||
output.removeChild(cmdRow[id].el);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(cmds).forEach((id) => {
|
|
||||||
const cmd = cmds[id];
|
|
||||||
|
|
||||||
if (cmdRow[id]) {
|
|
||||||
updateCMD(cmdRow[id], cmd);
|
|
||||||
} else {
|
|
||||||
cmdRow[id] = createRow(cmd);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(cmdRow).
|
|
||||||
sort((aID, bID) => {
|
|
||||||
if (!cmds[aID] || !cmds[bID]) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Date(cmds[aID].timestemp) - new Date(cmds[bID].timestemp);
|
|
||||||
}).
|
|
||||||
forEach((id) => {
|
|
||||||
if (cmds[id] && !cmdRow[id].el.parentElement) {
|
|
||||||
output.appendChild(cmdRow[id].el);
|
|
||||||
if (cmdRow[id].clientsContainer.parentElement) {
|
|
||||||
cmdRow[id].el.parentElement.insertBefore(cmdRow[id].clientsContainer, cmdRow[id].el.nextSibling);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
view.bind = function bind (bindEl) {
|
|
||||||
container = bindEl;
|
|
||||||
};
|
|
||||||
view.render = function render () {
|
|
||||||
if (!container) {
|
|
||||||
return;
|
|
||||||
} else if (el) {
|
|
||||||
container.appendChild(el);
|
|
||||||
update();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log('generate new view for console');
|
|
||||||
el = domlib.newAt(container, 'div');
|
|
||||||
|
|
||||||
store.updateCMD({
|
|
||||||
'cmd': '',
|
|
||||||
'id': '0',
|
|
||||||
'timestemp': 0
|
|
||||||
});
|
|
||||||
|
|
||||||
output = domlib.newAt(el, 'table');
|
|
||||||
output.classList.add('console');
|
|
||||||
|
|
||||||
const prompt = domlib.newAt(el, 'div'),
|
|
||||||
filterBtn = domlib.newAt(prompt, 'span'),
|
|
||||||
promptInput = domlib.newAt(prompt, 'input');
|
|
||||||
|
|
||||||
prompt.classList.add('prompt');
|
|
||||||
|
|
||||||
promptInput.addEventListener('keyup', (event) => {
|
|
||||||
// eslint-disable-next-line no-magic-numbers
|
|
||||||
if (event.keyCode !== 13) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const cmd = {
|
|
||||||
'cmd': promptInput.value,
|
|
||||||
'id': createID(),
|
|
||||||
'timestemp': new Date()
|
|
||||||
};
|
|
||||||
|
|
||||||
ownCMDs.push(cmd.id);
|
|
||||||
socket.sendcmd(cmd);
|
|
||||||
promptInput.value = '';
|
|
||||||
});
|
|
||||||
promptInput.addEventListener('focusin', () => {
|
|
||||||
editing = true;
|
|
||||||
});
|
|
||||||
promptInput.addEventListener('focusout', () => {
|
|
||||||
editing = false;
|
|
||||||
update();
|
|
||||||
});
|
|
||||||
|
|
||||||
filterBtn.classList.add('btn');
|
|
||||||
filterBtn.innerHTML = 'Show all';
|
|
||||||
filterBtn.addEventListener('click', () => {
|
|
||||||
ownfilter = !ownfilter;
|
|
||||||
filterBtn.classList.toggle('active');
|
|
||||||
filterBtn.innerHTML = ownfilter
|
|
||||||
? 'Show own'
|
|
||||||
: 'Show all';
|
|
||||||
update();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
update();
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -2,10 +2,8 @@ package websocket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/genofire/golang-lib/log"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/genofire/golang-lib/worker"
|
|
||||||
"golang.org/x/net/websocket"
|
"golang.org/x/net/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,7 +20,7 @@ type Client struct {
|
||||||
func NewClient(ip string, ws *websocket.Conn) *Client {
|
func NewClient(ip string, ws *websocket.Conn) *Client {
|
||||||
|
|
||||||
if ws == nil {
|
if ws == nil {
|
||||||
log.Log.Panic("ws cannot be nil")
|
log.Panic("ws cannot be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
|
@ -41,14 +39,14 @@ func (c *Client) Write(msg *Message) {
|
||||||
clientsMutex.Lock()
|
clientsMutex.Lock()
|
||||||
delete(clients, c.ip)
|
delete(clients, c.ip)
|
||||||
clientsMutex.Unlock()
|
clientsMutex.Unlock()
|
||||||
log.HTTP(c.ws.Request()).Error("client disconnected")
|
log.Error("client disconnected")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Close() {
|
func (c *Client) Close() {
|
||||||
c.writeQuit <- true
|
c.writeQuit <- true
|
||||||
c.readQuit <- true
|
c.readQuit <- true
|
||||||
log.HTTP(c.ws.Request()).Info("client disconnecting...")
|
log.Info("client disconnecting...")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen Write and Read request via chanel
|
// Listen Write and Read request via chanel
|
||||||
|
@ -68,11 +66,6 @@ func (c *Client) publishAllData() {
|
||||||
for _, node := range nodes.Current {
|
for _, node := range nodes.Current {
|
||||||
c.Write(&Message{Type: MessageTypeCurrentNode, Node: node})
|
c.Write(&Message{Type: MessageTypeCurrentNode, Node: node})
|
||||||
}
|
}
|
||||||
if commands != nil {
|
|
||||||
for _, cmd := range commands.List {
|
|
||||||
c.Write(&Message{Type: MessageTypeCommand, Command: cmd})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) handleMessage(msg *Message) {
|
func (c *Client) handleMessage(msg *Message) {
|
||||||
|
@ -80,22 +73,6 @@ func (c *Client) handleMessage(msg *Message) {
|
||||||
case MessageTypeSystemNode:
|
case MessageTypeSystemNode:
|
||||||
nodes.UpdateNode(msg.Node)
|
nodes.UpdateNode(msg.Node)
|
||||||
break
|
break
|
||||||
case MessageTypeCommand:
|
|
||||||
if commands == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
cmd := commands.AddCommand(msg.Command)
|
|
||||||
w := worker.NewWorker(time.Millisecond*300, func() {
|
|
||||||
cmd.Lock()
|
|
||||||
SendAll(Message{Type: MessageTypeCommand, Command: cmd})
|
|
||||||
cmd.Unlock()
|
|
||||||
})
|
|
||||||
go w.Start()
|
|
||||||
go cmd.Run(func() {
|
|
||||||
w.Close()
|
|
||||||
SendAll(Message{Type: MessageTypeCommand, Command: cmd})
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +114,7 @@ func (c *Client) listenRead() {
|
||||||
c.writeQuit <- true
|
c.writeQuit <- true
|
||||||
return
|
return
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
log.HTTP(c.ws.Request()).Error(err)
|
log.Error(err)
|
||||||
} else {
|
} else {
|
||||||
c.handleMessage(&msg)
|
c.handleMessage(&msg)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,15 +3,13 @@ package websocket
|
||||||
import "github.com/FreifunkBremen/freifunkmanager/runtime"
|
import "github.com/FreifunkBremen/freifunkmanager/runtime"
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Body interface{} `json:"body,omitempty"`
|
Body interface{} `json:"body,omitempty"`
|
||||||
Node *runtime.Node `json:"node,omitempty"`
|
Node *runtime.Node `json:"node,omitempty"`
|
||||||
Command *runtime.Command `json:"cmd,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MessageTypeSystemNode = "system"
|
MessageTypeSystemNode = "system"
|
||||||
MessageTypeCurrentNode = "current"
|
MessageTypeCurrentNode = "current"
|
||||||
MessageTypeStats = "stats"
|
MessageTypeStats = "stats"
|
||||||
MessageTypeCommand = "cmd"
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
|
|
||||||
runtimeYanic "github.com/FreifunkBremen/yanic/runtime"
|
runtimeYanic "github.com/FreifunkBremen/yanic/runtime"
|
||||||
httpLib "github.com/genofire/golang-lib/http"
|
httpLib "github.com/genofire/golang-lib/http"
|
||||||
"github.com/genofire/golang-lib/log"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/net/websocket"
|
"golang.org/x/net/websocket"
|
||||||
|
|
||||||
"github.com/FreifunkBremen/freifunkmanager/runtime"
|
"github.com/FreifunkBremen/freifunkmanager/runtime"
|
||||||
|
@ -16,11 +16,9 @@ var nodes *runtime.Nodes
|
||||||
var clients map[string]*Client
|
var clients map[string]*Client
|
||||||
var clientsMutex sync.Mutex
|
var clientsMutex sync.Mutex
|
||||||
var stats *runtimeYanic.GlobalStats
|
var stats *runtimeYanic.GlobalStats
|
||||||
var commands *runtime.Commands
|
|
||||||
|
|
||||||
func Start(nodeBind *runtime.Nodes, commandsBind *runtime.Commands) {
|
func Start(nodeBind *runtime.Nodes) {
|
||||||
nodes = nodeBind
|
nodes = nodeBind
|
||||||
commands = commandsBind
|
|
||||||
clients = make(map[string]*Client)
|
clients = make(map[string]*Client)
|
||||||
|
|
||||||
http.Handle("/websocket", websocket.Handler(func(ws *websocket.Conn) {
|
http.Handle("/websocket", websocket.Handler(func(ws *websocket.Conn) {
|
||||||
|
@ -32,10 +30,10 @@ func Start(nodeBind *runtime.Nodes, commandsBind *runtime.Commands) {
|
||||||
clientsMutex.Lock()
|
clientsMutex.Lock()
|
||||||
delete(clients, ip)
|
delete(clients, ip)
|
||||||
clientsMutex.Unlock()
|
clientsMutex.Unlock()
|
||||||
log.HTTP(r).Info("client disconnected")
|
log.Info("client disconnected")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.HTTP(r).Infof("new client")
|
log.Infof("new client")
|
||||||
|
|
||||||
client := NewClient(ip, ws)
|
client := NewClient(ip, ws)
|
||||||
clientsMutex.Lock()
|
clientsMutex.Lock()
|
||||||
|
@ -68,5 +66,5 @@ func SendAll(msg Message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Close() {
|
func Close() {
|
||||||
log.Log.Infof("websocket stopped with %d clients", len(clients))
|
log.Infof("websocket stopped with %d clients", len(clients))
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,8 @@ import (
|
||||||
func TestStart(t *testing.T) {
|
func TestStart(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
nodes := &runtime.Nodes{}
|
nodes := &runtime.Nodes{}
|
||||||
commands := &runtime.Commands{}
|
|
||||||
assert.Nil(clients)
|
assert.Nil(clients)
|
||||||
Start(nodes, commands)
|
Start(nodes)
|
||||||
assert.NotNil(clients)
|
assert.NotNil(clients)
|
||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
[respondd]
|
||||||
|
enable = true
|
||||||
|
collect_interval = "1m"
|
||||||
|
|
||||||
|
[[respondd.interfaces]]
|
||||||
|
ifname = "wlp4s0"
|
||||||
|
|
||||||
|
[webserver]
|
||||||
|
enable = false
|
||||||
|
|
||||||
|
[nodes]
|
||||||
|
state_path = "/tmp/state.json"
|
||||||
|
prune_after = "7d"
|
||||||
|
save_interval = "5s"
|
||||||
|
offline_after = "10m"
|
||||||
|
|
||||||
|
[[nodes.output.meshviewer-ffrgb]]
|
||||||
|
enable = true
|
||||||
|
path = "./webroot/data/meshviewer.json"
|
||||||
|
|
||||||
|
[nodes.output.meshviewer-ffrgb.filter]
|
||||||
|
no_owner = false
|
||||||
|
|
||||||
|
[database]
|
||||||
|
delete_after = "1y"
|
||||||
|
delete_interval = "1h"
|
||||||
|
|
||||||
|
[[database.connection.respondd]]
|
||||||
|
enable = true
|
||||||
|
type = "udp6"
|
||||||
|
address = "[::1]:10001"
|
Loading…
Reference in New Issue