diff --git a/.gitignore b/.gitignore index a38df26..0c12c4d 100644 --- a/.gitignore +++ b/.gitignore @@ -124,6 +124,11 @@ __pycache__ # IDE's go .idea/ +# webroot +webroot/node_modules +webroot/data +webroot/app.js +webroot/app.js.map # go project profile.cov diff --git a/config/main_test.go b/config/main_test.go deleted file mode 100644 index 6712e4d..0000000 --- a/config/main_test.go +++ /dev/null @@ -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") -} diff --git a/config/testdata/config_panic.conf b/config/testdata/config_panic.conf deleted file mode 100644 index 3d07101..0000000 --- a/config/testdata/config_panic.conf +++ /dev/null @@ -1 +0,0 @@ -not unmarshalable diff --git a/config_example.conf b/config_example.conf index b623c57..a6321d7 100644 --- a/config_example.conf +++ b/config_example.conf @@ -1,12 +1,14 @@ +state_path = "/tmp/freifunkmanager.json" + webserver_bind = ":8080" webroot = "./webroot" -state_path = "/tmp/freifunkmanager.json" -ssh_key = "/etc/id_rsa" +ssh_key = "~/.ssh/id_rsa" ssh_interface = "wlp4s0" [yanic] -enable = true -type = "tcp" -address = "localhost:8081" +enable = true +ifname = "lo" +address = "::1" +port = 10001 diff --git a/controller/main.go b/controller/main.go deleted file mode 100644 index b99d683..0000000 --- a/controller/main.go +++ /dev/null @@ -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 - - } - } - - })) -} diff --git a/cmd/freifunkmanager/main.go b/main.go similarity index 53% rename from cmd/freifunkmanager/main.go rename to main.go index 9508d6f..1bbd947 100644 --- a/cmd/freifunkmanager/main.go +++ b/main.go @@ -8,68 +8,70 @@ import ( "syscall" "time" - yanic "github.com/FreifunkBremen/yanic/database/socket/client" - runtimeYanic "github.com/FreifunkBremen/yanic/runtime" "github.com/NYTimes/gziphandler" + "github.com/genofire/golang-lib/file" httpLib "github.com/genofire/golang-lib/http" - "github.com/genofire/golang-lib/log" "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/ssh" "github.com/FreifunkBremen/freifunkmanager/websocket" ) var ( - configFile string - config *configPackage.Config - nodes *runtime.Nodes - commands *runtime.Commands - yanicDialer *yanic.Dialer - stats *runtimeYanic.GlobalStats + configFile string + config = &runtime.Config{} + nodes *runtime.Nodes + collector *respondYanic.Collector + verbose bool ) func main() { flag.StringVar(&configFile, "config", "config.conf", "path of configuration file (default:config.conf)") + flag.BoolVar(&verbose, "v", false, "verbose logging") 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) 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) - 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 controller.Start() + go nodesUpdateWorker.Start() - websocket.Start(nodes, commands) + websocket.Start(nodes) + db.NotifyStats = websocket.NotifyStats if config.Yanic.Enable { - yanicDialer = yanic.Dial(config.Yanic.Type, config.Yanic.Address) - yanicDialer.NodeHandler = nodes.LearnNode - yanicDialer.GlobalsHandler = func(data *runtimeYanic.GlobalStats) { - stats = data - websocket.NotifyStats(data) - } - go yanicDialer.Start() + collector = respondYanic.NewCollector(db, nodesYanic, make(map[string][]string), []respondYanic.InterfaceConfig{respondYanic.InterfaceConfig{ + InterfaceName: config.Yanic.InterfaceName, + IPAddress: config.Yanic.Address, + Port: config.Yanic.Port, + }}) + defer collector.Close() } // Startwebserver http.HandleFunc("/nodes", func(w http.ResponseWriter, r *http.Request) { httpLib.Write(w, nodes) - log.HTTP(r).Info("done") }) http.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) { - httpLib.Write(w, stats) - log.HTTP(r).Info("done") + httpLib.Write(w, db.Statistics) }) http.Handle("/", gziphandler.GzipHandler(http.FileServer(http.Dir(config.Webroot)))) @@ -79,27 +81,26 @@ func main() { go func() { if err := srv.ListenAndServe(); err != nil { - log.Log.Panic(err) + log.Panic(err) } }() - log.Log.Info("started") + log.Info("started") // Wait for system signal sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) sig := <-sigs - // Stop services websocket.Close() + + // Stop services srv.Close() - controller.Close() if config.Yanic.Enable { - yanicDialer.Close() + collector.Close() } nodesSaveWorker.Close() - // nodesUpdateWorker.Close() - sshmanager.Close() + nodesUpdateWorker.Close() - log.Log.Info("stop recieve:", sig) + log.Info("stop recieve:", sig) } diff --git a/runtime/cmd.go b/runtime/cmd.go deleted file mode 100644 index da17c7b..0000000 --- a/runtime/cmd.go +++ /dev/null @@ -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() -} diff --git a/config/main.go b/runtime/config.go similarity index 50% rename from config/main.go rename to runtime/config.go index 153607f..4718581 100644 --- a/config/main.go +++ b/runtime/config.go @@ -1,19 +1,12 @@ -package config - -import ( - "io/ioutil" - - "github.com/BurntSushi/toml" - "github.com/genofire/golang-lib/log" -) +package runtime //config file of this daemon (for more the config_example.conf in git repository) type Config struct { // prevent crashes StatePath string `toml:"state_path"` + // address on which the api and static content webserver runs WebserverBind string `toml:"webserver_bind"` - // path to deliver static content Webroot string `toml:"webroot"` @@ -23,23 +16,9 @@ type Config struct { // yanic socket Yanic struct { - Enable bool `toml:"enable"` - Type string `toml:"type"` - Address string `toml:"address"` + Enable bool `toml:"enable"` + InterfaceName string `toml:"ifname"` + Address string `toml:"address"` + Port int `toml:"port"` } `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 -} diff --git a/runtime/node.go b/runtime/node.go index 57d1255..4279f27 100644 --- a/runtime/node.go +++ b/runtime/node.go @@ -5,10 +5,10 @@ import ( "fmt" "net" - "github.com/genofire/golang-lib/log" + log "github.com/sirupsen/logrus" - "github.com/FreifunkBremen/yanic/data" - "github.com/FreifunkBremen/yanic/jsontime" + yanicData "github.com/FreifunkBremen/yanic/data" + "github.com/FreifunkBremen/yanic/lib/jsontime" yanicRuntime "github.com/FreifunkBremen/yanic/runtime" "github.com/FreifunkBremen/freifunkmanager/ssh" @@ -23,16 +23,16 @@ const ( ) type Node struct { - Lastseen jsontime.Time `json:"lastseen"` - NodeID string `json:"node_id"` - Hostname string `json:"hostname"` - Location data.Location `json:"location"` - Wireless data.Wireless `json:"wireless"` - Owner string `json:"owner"` - Address net.IP `json:"-"` + Lastseen jsontime.Time `json:"lastseen"` + NodeID string `json:"node_id"` + Hostname string `json:"hostname"` + Location yanicData.Location `json:"location"` + Wireless yanicData.Wireless `json:"wireless"` + Owner string `json:"owner"` + Address net.IP `json:"-"` Stats struct { - Wireless data.WirelessStatistics `json:"wireless"` - Clients data.Clients `json:"clients"` + Wireless yanicData.WirelessStatistics `json:"wireless"` + Clients yanicData.Clients `json:"clients"` } `json:"statistics"` } @@ -41,7 +41,12 @@ func NewNode(nodeOrigin *yanicRuntime.Node) *Node { node := &Node{ Hostname: nodeinfo.Hostname, 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 { 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)) } 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) { 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, "wifi") // 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 } @@ -89,7 +94,7 @@ func (n *Node) Update(node *yanicRuntime.Node) { if nodeinfo := node.Nodeinfo; nodeinfo != nil { n.Hostname = nodeinfo.Hostname n.NodeID = nodeinfo.NodeID - n.Address = node.Address + n.Address = node.Address.IP if owner := nodeinfo.Owner; owner != nil { n.Owner = owner.Contact @@ -124,11 +129,11 @@ func (n *Node) IsEqual(node *Node) bool { return true } -func locationEqual(a, b data.Location) bool { +func locationEqual(a, b yanicData.Location) bool { if a.Latitude != b.Latitude { return false } - if a.Longtitude != b.Longtitude { + if a.Longitude != b.Longitude { return false } if a.Altitude != b.Altitude { @@ -137,7 +142,7 @@ func locationEqual(a, b data.Location) bool { return true } -func wirelessEqual(a, b data.Wireless) bool { +func wirelessEqual(a, b yanicData.Wireless) bool { if a.Channel24 != b.Channel24 { return false } diff --git a/runtime/node_test.go b/runtime/node_test.go index 010ae0f..9cbb4ec 100644 --- a/runtime/node_test.go +++ b/runtime/node_test.go @@ -1,24 +1,27 @@ package runtime import ( + "net" "testing" "github.com/stretchr/testify/assert" - "github.com/FreifunkBremen/yanic/data" + yanicData "github.com/FreifunkBremen/yanic/data" yanicRuntime "github.com/FreifunkBremen/yanic/runtime" ) func TestNode(t *testing.T) { assert := assert.New(t) - node1 := &yanicRuntime.Node{} + node1 := &yanicRuntime.Node{ + Address: &net.UDPAddr{IP: net.ParseIP("ff02::1")}, + } n1 := NewNode(node1) assert.Nil(n1) - node1.Nodeinfo = &data.NodeInfo{ - Owner: &data.Owner{Contact: "blub"}, - Wireless: &data.Wireless{}, - Location: &data.Location{Altitude: 13}, + node1.Nodeinfo = &yanicData.NodeInfo{ + Owner: &yanicData.Owner{Contact: "blub"}, + Wireless: &yanicData.Wireless{}, + Location: &yanicData.Location{Altitude: 13}, } n1 = NewNode(node1) assert.NotNil(n1) diff --git a/runtime/nodes.go b/runtime/nodes.go index df0a4a6..2f8dabe 100644 --- a/runtime/nodes.go +++ b/runtime/nodes.go @@ -1,13 +1,13 @@ package runtime import ( - "encoding/json" - "os" "sync" - "github.com/FreifunkBremen/yanic/jsontime" - yanic "github.com/FreifunkBremen/yanic/runtime" - "github.com/genofire/golang-lib/log" + "github.com/FreifunkBremen/yanic/lib/jsontime" + log "github.com/sirupsen/logrus" + + yanicRuntime "github.com/FreifunkBremen/yanic/runtime" + "github.com/genofire/golang-lib/file" "github.com/FreifunkBremen/freifunkmanager/ssh" ) @@ -30,17 +30,17 @@ func NewNodes(path string, iface string, mgmt *ssh.Manager) *Nodes { statePath: path, iface: iface, } - nodes.load() + file.ReadJSON(path, nodes) return nodes } -func (nodes *Nodes) LearnNode(n *yanic.Node) { +func (nodes *Nodes) LearnNode(n *yanicRuntime.Node) { node := NewNode(n) if node == nil { return } 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() defer nodes.Unlock() 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) { if node == nil { - log.Log.Warn("no new node to update") + log.Warn("no new node to update") return } nodes.Lock() @@ -91,7 +91,7 @@ func (nodes *Nodes) UpdateNode(node *Node) { if n, ok := nodes.List[node.NodeID]; ok { node.Address = n.Address 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.notify(node, true) @@ -105,24 +105,12 @@ func (nodes *Nodes) Updater() { go node.SSHUpdate(nodes.ssh, nodes.iface, n) } } - log.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) - } + log.Info("updater per ssh") } func (nodes *Nodes) Saver() { nodes.Lock() - yanic.SaveJSON(nodes, nodes.statePath) + file.SaveJSON(nodes.statePath, nodes) nodes.Unlock() - log.Log.Debug("saved state file") + log.Debug("saved state file") } diff --git a/runtime/yanic.go b/runtime/yanic.go new file mode 100644 index 0000000..a1ba5c4 --- /dev/null +++ b/runtime/yanic.go @@ -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() { +} diff --git a/ssh/execute.go b/ssh/execute.go index 6a7008b..253b8c1 100644 --- a/ssh/execute.go +++ b/ssh/execute.go @@ -3,23 +3,17 @@ package ssh import ( "net" - "github.com/genofire/golang-lib/log" + log "github.com/sirupsen/logrus" + "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 { client, err := m.ConnectTo(addr) if err != nil { return err } + defer client.Close() 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() 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) 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 nil diff --git a/ssh/execute_test.go b/ssh/execute_test.go index f70f16b..ebf13f6 100644 --- a/ssh/execute_test.go +++ b/ssh/execute_test.go @@ -15,10 +15,9 @@ func TestExecute(t *testing.T) { mgmt := NewManager("~/.ssh/id_rsa") assert.NotNil(mgmt, "no new manager created") - _, err := mgmt.ConnectTo(addr) + client, err := mgmt.ConnectTo(addr) assert.NoError(err) - mgmt.ExecuteEverywhere("echo $HOSTNAME") err = mgmt.ExecuteOn(addr, "uptime") assert.NoError(err) err = mgmt.ExecuteOn(addr, "echo $HOSTNAME") @@ -26,5 +25,5 @@ func TestExecute(t *testing.T) { err = mgmt.ExecuteOn(addr, "exit 1") assert.Error(err) - mgmt.Close() + client.Close() } diff --git a/ssh/list.go b/ssh/list.go deleted file mode 100644 index 7aa0c10..0000000 --- a/ssh/list.go +++ /dev/null @@ -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) -} diff --git a/ssh/list_test.go b/ssh/list_test.go deleted file mode 100644 index 30bc73f..0000000 --- a/ssh/list_test.go +++ /dev/null @@ -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) -} diff --git a/ssh/manager.go b/ssh/manager.go index 6690ff2..710a07f 100644 --- a/ssh/manager.go +++ b/ssh/manager.go @@ -7,14 +7,13 @@ import ( "sync" "time" - "github.com/genofire/golang-lib/log" + log "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" ) // the SSH Connection Manager for multiple connections type Manager struct { config *ssh.ClientConfig - clients map[string]*ssh.Client clientsBlacklist map[string]time.Time clientsMUX sync.Mutex } @@ -36,7 +35,6 @@ func NewManager(file string) *Manager { } return &Manager{ config: sshConfig, - clients: make(map[string]*ssh.Client), 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) if err != nil { if strings.Contains(err.Error(), "no supported methods remain") { 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, err } - m.clients[addr.IP.String()] = client return client, nil } - -func (m *Manager) Close() { - for host, client := range m.clients { - client.Close() - delete(m.clients, host) - } -} diff --git a/ssh/manager_test.go b/ssh/manager_test.go index 85be983..1d7fed2 100644 --- a/ssh/manager_test.go +++ b/ssh/manager_test.go @@ -13,7 +13,6 @@ func TestManager(t *testing.T) { mgmt := NewManager("~/.ssh/id_rsa") assert.NotNil(mgmt, "no new manager created") - mgmt.ConnectTo(net.TCPAddr{IP: net.ParseIP("2a06:8782:ffbb:1337::127"), Port: 22}) - - mgmt.Close() + client, _ := mgmt.ConnectTo(net.TCPAddr{IP: net.ParseIP("2a06:8782:ffbb:1337::127"), Port: 22}) + client.Close() } diff --git a/ssh/run.go b/ssh/run.go index 2cdf030..e4365db 100644 --- a/ssh/run.go +++ b/ssh/run.go @@ -4,7 +4,7 @@ import ( "bytes" "net" - "github.com/genofire/golang-lib/log" + log "github.com/sirupsen/logrus" "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) { client, err := m.ConnectTo(addr) if err != nil { return "", err } + defer client.Close() 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() if err != nil { - log.Log.Warnf("can not create session on %s: %s", host, err) - m.clientsMUX.Lock() - delete(m.clients, host) - m.clientsMUX.Unlock() + log.Warnf("can not create session on %s: %s", host, err) return "", err } buffer := &bytes.Buffer{} session.Stdout = buffer if err != nil { - log.Log.Warnf("can not create pipe for run on %s: %s", host, err) - delete(m.clients, host) + log.Warnf("can not create pipe for run on %s: %s", host, err) return "", err } err = session.Run(cmd) 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 buffer.String(), nil diff --git a/ssh/run_test.go b/ssh/run_test.go index 3026631..54f78ac 100644 --- a/ssh/run_test.go +++ b/ssh/run_test.go @@ -14,14 +14,10 @@ func TestRun(t *testing.T) { mgmt := NewManager("~/.ssh/id_rsa") assert.NotNil(mgmt, "no new manager created") - _, err := mgmt.ConnectTo(addr) + client, err := mgmt.ConnectTo(addr) 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") assert.NoError(err) @@ -29,6 +25,4 @@ func TestRun(t *testing.T) { resultInt, _ := strconv.Atoi(str) assert.Equal(16, resultInt) - - mgmt.Close() } diff --git a/vendor/github.com/FreifunkBremen/yanic b/vendor/github.com/FreifunkBremen/yanic deleted file mode 160000 index 7978107..0000000 --- a/vendor/github.com/FreifunkBremen/yanic +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 79781076be6e810eff663064cf68295e7f6e6487 diff --git a/webroot/index.html b/webroot/index.html index 0d957a6..7f5302f 100644 --- a/webroot/index.html +++ b/webroot/index.html @@ -24,7 +24,6 @@ - diff --git a/webroot/js/gui.js b/webroot/js/gui.js index efe1b94..1d0ce4d 100644 --- a/webroot/js/gui.js +++ b/webroot/js/gui.js @@ -1,5 +1,5 @@ /* exported gui,router */ -/* globals socket,notify,domlib,guiList,guiMap,guiStats,guiNode,guiConsole */ +/* globals socket,notify,domlib,guiList,guiMap,guiStats,guiNode */ const gui = {}, router = new Navigo(null, true, '#'); @@ -58,9 +58,6 @@ const gui = {}, } router.on({ - '/console': function routerConsole () { - setView(guiConsole); - }, '/list': function routerList () { setView(guiList); }, diff --git a/webroot/js/gui_console.js b/webroot/js/gui_console.js deleted file mode 100644 index d485b7a..0000000 --- a/webroot/js/gui_console.js +++ /dev/null @@ -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(); - }; -})(); diff --git a/websocket/client.go b/websocket/client.go index b884595..ce419a0 100644 --- a/websocket/client.go +++ b/websocket/client.go @@ -2,10 +2,8 @@ package websocket import ( "io" - "time" - "github.com/genofire/golang-lib/log" - "github.com/genofire/golang-lib/worker" + log "github.com/sirupsen/logrus" "golang.org/x/net/websocket" ) @@ -22,7 +20,7 @@ type Client struct { func NewClient(ip string, ws *websocket.Conn) *Client { if ws == nil { - log.Log.Panic("ws cannot be nil") + log.Panic("ws cannot be nil") } return &Client{ @@ -41,14 +39,14 @@ func (c *Client) Write(msg *Message) { clientsMutex.Lock() delete(clients, c.ip) clientsMutex.Unlock() - log.HTTP(c.ws.Request()).Error("client disconnected") + log.Error("client disconnected") } } func (c *Client) Close() { c.writeQuit <- true c.readQuit <- true - log.HTTP(c.ws.Request()).Info("client disconnecting...") + log.Info("client disconnecting...") } // Listen Write and Read request via chanel @@ -68,11 +66,6 @@ func (c *Client) publishAllData() { for _, node := range nodes.Current { 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) { @@ -80,22 +73,6 @@ func (c *Client) handleMessage(msg *Message) { case MessageTypeSystemNode: nodes.UpdateNode(msg.Node) 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 return } else if err != nil { - log.HTTP(c.ws.Request()).Error(err) + log.Error(err) } else { c.handleMessage(&msg) } diff --git a/websocket/msg.go b/websocket/msg.go index bd755dc..b655f29 100644 --- a/websocket/msg.go +++ b/websocket/msg.go @@ -3,15 +3,13 @@ package websocket import "github.com/FreifunkBremen/freifunkmanager/runtime" type Message struct { - Type string `json:"type"` - Body interface{} `json:"body,omitempty"` - Node *runtime.Node `json:"node,omitempty"` - Command *runtime.Command `json:"cmd,omitempty"` + Type string `json:"type"` + Body interface{} `json:"body,omitempty"` + Node *runtime.Node `json:"node,omitempty"` } const ( MessageTypeSystemNode = "system" MessageTypeCurrentNode = "current" MessageTypeStats = "stats" - MessageTypeCommand = "cmd" ) diff --git a/websocket/server.go b/websocket/server.go index 23a192a..62aa78d 100644 --- a/websocket/server.go +++ b/websocket/server.go @@ -6,7 +6,7 @@ import ( runtimeYanic "github.com/FreifunkBremen/yanic/runtime" httpLib "github.com/genofire/golang-lib/http" - "github.com/genofire/golang-lib/log" + log "github.com/sirupsen/logrus" "golang.org/x/net/websocket" "github.com/FreifunkBremen/freifunkmanager/runtime" @@ -16,11 +16,9 @@ var nodes *runtime.Nodes var clients map[string]*Client var clientsMutex sync.Mutex var stats *runtimeYanic.GlobalStats -var commands *runtime.Commands -func Start(nodeBind *runtime.Nodes, commandsBind *runtime.Commands) { +func Start(nodeBind *runtime.Nodes) { nodes = nodeBind - commands = commandsBind clients = make(map[string]*Client) http.Handle("/websocket", websocket.Handler(func(ws *websocket.Conn) { @@ -32,10 +30,10 @@ func Start(nodeBind *runtime.Nodes, commandsBind *runtime.Commands) { clientsMutex.Lock() delete(clients, ip) 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) clientsMutex.Lock() @@ -68,5 +66,5 @@ func SendAll(msg Message) { } func Close() { - log.Log.Infof("websocket stopped with %d clients", len(clients)) + log.Infof("websocket stopped with %d clients", len(clients)) } diff --git a/websocket/server_test.go b/websocket/server_test.go index 3a3318b..4782bb5 100644 --- a/websocket/server_test.go +++ b/websocket/server_test.go @@ -10,9 +10,8 @@ import ( func TestStart(t *testing.T) { assert := assert.New(t) nodes := &runtime.Nodes{} - commands := &runtime.Commands{} assert.Nil(clients) - Start(nodes, commands) + Start(nodes) assert.NotNil(clients) Close() } diff --git a/yanic_example.conf b/yanic_example.conf new file mode 100644 index 0000000..820164e --- /dev/null +++ b/yanic_example.conf @@ -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"