diff --git a/.travis.yml b/.travis.yml index 3c3818a..e601586 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,5 +8,4 @@ install: - go get golang.org/x/tools/cmd/cover script: - ./.test-coverage - - go install github.com/FreifunkBremen/yanic/cmd/yanic - - go install github.com/FreifunkBremen/yanic/cmd/yanic-query + - go install github.com/FreifunkBremen/yanic diff --git a/README.md b/README.md index 6961e6f..0a2621d 100644 --- a/README.md +++ b/README.md @@ -28,16 +28,77 @@ Recently seen nodes that does not reply are requested via a unicast message. * [Mobi](https://www.gitbook.com/download/mobi/book/freifunkbremen/yanic) * [ePUB](https://www.gitbook.com/download/epub/book/freifunkbremen/yanic) +## Configuration +Read comments in [config_example.toml](config_example.toml) for more information. -## Quick startup +## Running + +Yanic provides several commands: ### Usage + +Run Yanic without any arguments to get the usage information: + ``` -Usage of ./yanic: - -config path/to/config.toml +Usage: + yanic [command] + +Available Commands: + help Help about any command + import Imports global statistics from the given RRD files, requires InfluxDB + query Sends a query on the interface to the destination and waits for a response + serve Runs the yanic server + +Flags: + -h, --help help for yanic + --timestamps Enables timestamps for log output + +Use "yanic [command] --help" for more information about a command. ``` -### Configuration -Read comments in [config_example.toml](config_example.toml) for more information. + +#### Serve + +``` +Usage: + yanic serve [flags] + +Examples: + yanic serve -config /etc/yanic.toml + +Flags: + -c, --config string Path to configuration file (default "config.toml") + -h, --help help for serve +``` + +#### Import + +``` +Usage: + yanic import [flags] + +Examples: + yanic import -config /etc/yanic.toml olddata.rrd + +Flags: + -c, --config string Path to configuration file (default "config.toml") + -h, --help help for import +``` + + +#### Query + +``` +Usage: + yanic query [flags] + +Examples: + yanic query wlan0 "[fe80::eade:27ff:dead:beef%wlp4s0]:1001" + +Flags: + -h, --help help for query + --wait int Seconds to wait for a response (default 1) +``` + ### Live * [meshviewer](https://map.bremen.freifunk.net) **Freifunk Bremen** with a patch to show state-version of `nodes.json` diff --git a/cmd/config.go b/cmd/config.go new file mode 100644 index 0000000..35beb4f --- /dev/null +++ b/cmd/config.go @@ -0,0 +1,26 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/FreifunkBremen/yanic/database" + "github.com/FreifunkBremen/yanic/respond" + "github.com/FreifunkBremen/yanic/runtime" +) + +var ( + configPath string + collector *respond.Collector + connections database.Connection + nodes *runtime.Nodes +) + +func loadConfig() *runtime.Config { + config, err := runtime.ReadConfigFile(configPath) + if err != nil { + fmt.Fprintln(os.Stderr, "unable to load config file:", err) + os.Exit(2) + } + return config +} diff --git a/cmd/import.go b/cmd/import.go new file mode 100644 index 0000000..52a132f --- /dev/null +++ b/cmd/import.go @@ -0,0 +1,47 @@ +package cmd + +import ( + "log" + + "github.com/FreifunkBremen/yanic/database" + "github.com/FreifunkBremen/yanic/database/all" + "github.com/FreifunkBremen/yanic/rrd" + "github.com/FreifunkBremen/yanic/runtime" + "github.com/spf13/cobra" +) + +// importCmd represents the import command +var importCmd = &cobra.Command{ + Use: "import ", + Short: "Imports global statistics from the given RRD files, requires InfluxDB", + Example: "yanic import -config /etc/yanic.toml olddata.rrd", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + path := args[0] + config := loadConfig() + + connections, err := all.Connect(config.Database.Connection) + if err != nil { + panic(err) + } + database.Start(connections, config) + defer database.Close(connections) + + log.Println("importing RRD from", path) + + for ds := range rrd.Read(path) { + connections.InsertGlobals( + &runtime.GlobalStats{ + Nodes: uint32(ds.Nodes), + Clients: uint32(ds.Clients), + }, + ds.Time, + ) + } + }, +} + +func init() { + RootCmd.AddCommand(importCmd) + importCmd.Flags().StringVarP(&configPath, "config", "c", "config.toml", "Path to configuration file") +} diff --git a/cmd/query.go b/cmd/query.go new file mode 100644 index 0000000..a5e2a7b --- /dev/null +++ b/cmd/query.go @@ -0,0 +1,44 @@ +package cmd + +import ( + "log" + "net" + "time" + + "github.com/FreifunkBremen/yanic/respond" + "github.com/FreifunkBremen/yanic/runtime" + "github.com/spf13/cobra" +) + +var wait int + +// queryCmd represents the query command +var queryCmd = &cobra.Command{ + Use: "query ", + Short: "Sends a query on the interface to the destination and waits for a response", + Example: `yanic query wlan0 "[fe80::eade:27ff:dead:beef%wlp4s0]:1001"`, + Args: cobra.ExactArgs(2), + Run: func(cmd *cobra.Command, args []string) { + iface := args[0] + dstAddress := args[1] + + log.Printf("Sending request address=%s iface=%s", dstAddress, iface) + + nodes := runtime.NewNodes(&runtime.Config{}) + + collector := respond.NewCollector(nil, nodes, iface, 0) + defer collector.Close() + collector.SendPacket(net.ParseIP(dstAddress)) + + time.Sleep(time.Second * time.Duration(wait)) + + for id, data := range nodes.List { + log.Printf("%s: %+v", id, data) + } + }, +} + +func init() { + RootCmd.AddCommand(queryCmd) + queryCmd.Flags().IntVar(&wait, "wait", 1, "Seconds to wait for a response") +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..fa44800 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,46 @@ +package cmd + +import ( + "fmt" + "log" + "os" + + "github.com/spf13/cobra" +) + +var ( + timestamps bool +) + +// RootCmd represents the base command when called without any subcommands +var RootCmd = &cobra.Command{ + Use: "yanic", + Short: "Yet another node info collector", + Long: `A respondd client that fetches, stores and publishes information about a Freifunk network.`, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := RootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + RootCmd.PersistentFlags().BoolVar(×tamps, "timestamps", false, "Enables timestamps for log output") +} + +func initConfig() { + if timestamps { + log.SetFlags(log.Lshortfile) + } else { + log.SetFlags(log.LstdFlags | log.Lshortfile) + } +} diff --git a/cmd/serve.go b/cmd/serve.go new file mode 100644 index 0000000..4288749 --- /dev/null +++ b/cmd/serve.go @@ -0,0 +1,70 @@ +package cmd + +import ( + "log" + "os" + "os/signal" + "syscall" + "time" + + "github.com/FreifunkBremen/yanic/database" + "github.com/FreifunkBremen/yanic/database/all" + "github.com/FreifunkBremen/yanic/meshviewer" + "github.com/FreifunkBremen/yanic/respond" + "github.com/FreifunkBremen/yanic/runtime" + "github.com/FreifunkBremen/yanic/webserver" + "github.com/spf13/cobra" +) + +// serveCmd represents the serve command +var serveCmd = &cobra.Command{ + Use: "serve", + Short: "Runs the yanic server", + Example: "yanic serve -config /etc/yanic.toml", + Run: func(cmd *cobra.Command, args []string) { + config := loadConfig() + + connections, err := all.Connect(config.Database.Connection) + if err != nil { + panic(err) + } + database.Start(connections, config) + defer database.Close(connections) + + nodes = runtime.NewNodes(config) + nodes.Start() + meshviewer.Start(config, nodes) + + if config.Webserver.Enable { + log.Println("starting webserver on", config.Webserver.Bind) + srv := webserver.New(config.Webserver.Bind, config.Webserver.Webroot) + go srv.Close() + } + + if config.Respondd.Enable { + // Delaying startup to start at a multiple of `duration` since the zero time. + if duration := config.Respondd.Synchronize.Duration; duration > 0 { + now := time.Now() + delay := duration - now.Sub(now.Truncate(duration)) + log.Printf("delaying %0.1f seconds", delay.Seconds()) + time.Sleep(delay) + } + + collector = respond.NewCollector(connections, nodes, config.Respondd.Interface, config.Respondd.Port) + collector.Start(config.Respondd.CollectInterval.Duration) + defer collector.Close() + } + + // Wait for INT/TERM + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + sig := <-sigs + log.Println("received", sig) + + }, +} + +func init() { + RootCmd.AddCommand(serveCmd) + serveCmd.Flags().StringVarP(&configPath, "config", "c", "config.toml", "Path to configuration file") +} diff --git a/cmd/yanic-query/main.go b/cmd/yanic-query/main.go deleted file mode 100644 index f981c25..0000000 --- a/cmd/yanic-query/main.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "log" - "net" - "os" - "time" - - "github.com/FreifunkBremen/yanic/respond" - "github.com/FreifunkBremen/yanic/runtime" -) - -// Usage: yanic-query wlp4s0 "[fe80::eade:27ff:dead:beef%wlp4s0]:1001" -func main() { - iface := os.Args[1] - dstAddress := os.Args[2] - - log.Printf("Sending request address=%s iface=%s", dstAddress, iface) - - nodes := runtime.NewNodes(&runtime.Config{}) - - collector := respond.NewCollector(nil, nodes, iface, 0) - collector.SendPacket(net.ParseIP(dstAddress)) - - time.Sleep(time.Second) - - for id, data := range nodes.List { - log.Printf("%s: %+v", id, data) - } - - collector.Close() -} diff --git a/cmd/yanic/main.go b/cmd/yanic/main.go deleted file mode 100644 index 1f90e90..0000000 --- a/cmd/yanic/main.go +++ /dev/null @@ -1,101 +0,0 @@ -package main - -import ( - "flag" - "log" - "os" - "os/signal" - "syscall" - "time" - - "github.com/FreifunkBremen/yanic/database" - "github.com/FreifunkBremen/yanic/database/all" - "github.com/FreifunkBremen/yanic/meshviewer" - "github.com/FreifunkBremen/yanic/respond" - "github.com/FreifunkBremen/yanic/rrd" - "github.com/FreifunkBremen/yanic/runtime" - "github.com/FreifunkBremen/yanic/webserver" -) - -var ( - configFile string - config *runtime.Config - collector *respond.Collector - connections database.Connection - nodes *runtime.Nodes -) - -func main() { - var importPath string - var timestamps bool - flag.StringVar(&importPath, "import", "", "import global statistics from the given RRD file, requires influxdb") - flag.StringVar(&configFile, "config", "config.toml", "path of configuration file (default:config.yaml)") - flag.BoolVar(×tamps, "timestamps", true, "print timestamps in output") - flag.Parse() - - if !timestamps { - log.SetFlags(0) - } - log.Println("Yanic say hello") - - config, err := runtime.ReadConfigFile(configFile) - if err != nil { - panic(err) - } - - nodes = runtime.NewNodes(config) - - connections, err = all.Connect(config.Database.Connection) - if err != nil { - panic(err) - } - database.Start(connections, config) - defer database.Close(connections) - - if connections != nil && importPath != "" { - importRRD(importPath) - return - } - - nodes.Start() - meshviewer.Start(config, nodes) - - if config.Webserver.Enable { - log.Println("starting webserver on", config.Webserver.Bind) - srv := webserver.New(config.Webserver.Bind, config.Webserver.Webroot) - go srv.Close() - } - - if config.Respondd.Enable { - // Delaying startup to start at a multiple of `duration` since the zero time. - if duration := config.Respondd.Synchronize.Duration; duration > 0 { - now := time.Now() - delay := duration - now.Sub(now.Truncate(duration)) - log.Printf("delaying %0.1f seconds", delay.Seconds()) - time.Sleep(delay) - } - - collector = respond.NewCollector(connections, nodes, config.Respondd.Interface, config.Respondd.Port) - collector.Start(config.Respondd.CollectInterval.Duration) - defer collector.Close() - } - - // Wait for INT/TERM - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) - sig := <-sigs - log.Println("received", sig) -} - -func importRRD(path string) { - log.Println("importing RRD from", path) - for ds := range rrd.Read(path) { - connections.InsertGlobals( - &runtime.GlobalStats{ - Nodes: uint32(ds.Nodes), - Clients: uint32(ds.Clients), - }, - ds.Time, - ) - } -} diff --git a/contrib/init/linux-systemd/yanic.service b/contrib/init/linux-systemd/yanic.service index 82e44a5..298635d 100644 --- a/contrib/init/linux-systemd/yanic.service +++ b/contrib/init/linux-systemd/yanic.service @@ -4,7 +4,7 @@ Description=yanic [Service] Type=simple User=yanic -ExecStart=/opt/go/bin/yanic -config /etc/yanic.conf +ExecStart=/opt/go/bin/yanic serve -config /etc/yanic.conf Restart=always RestartSec=5s Environment=PATH=/usr/bin:/usr/local/bin diff --git a/main.go b/main.go new file mode 100644 index 0000000..7f432e3 --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/FreifunkBremen/yanic/cmd" + +func main() { + cmd.Execute() +} diff --git a/runtime/config.go b/runtime/config.go index b6b3846..4e446fc 100644 --- a/runtime/config.go +++ b/runtime/config.go @@ -45,12 +45,12 @@ func ReadConfigFile(path string) (config *Config, err error) { file, err := ioutil.ReadFile(path) if err != nil { - panic(err) + return nil, err } err = toml.Unmarshal(file, config) if err != nil { - panic(err) + return nil, err } return