diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 00000000..2dacca83 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,63 @@ +package cmd + +import ( + "fmt" + "os" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/systemli/ticker/internal/bridge" + "github.com/systemli/ticker/internal/config" +) + +var ( + GitCommit string + GitVersion string + + configPath string + cfg config.Config + + rootCmd = &cobra.Command{ + Use: "ticker", + Short: "Service to distribute short messages", + Long: "Service to distribute short messages in support of events, demonstrations, or other time-sensitive events.", + } +) + +func init() { + cobra.OnInitialize(initConfig) + rootCmd.PersistentFlags().StringVar(&configPath, "config", "config.yml", "path to config.yml") +} + +func initConfig() { + cfg = config.LoadConfig(configPath) + //TODO: Improve startup routine + if cfg.TelegramEnabled() { + user, err := bridge.BotUser(cfg.TelegramBotToken) + if err != nil { + log.WithError(err).Error("Unable to retrieve the user information for the Telegram Bot") + } else { + cfg.TelegramBotUser = user + } + } + + lvl, err := log.ParseLevel(cfg.LogLevel) + if err != nil { + panic(err) + } + if cfg.LogFormat == "json" { + log.SetFormatter(&log.JSONFormatter{}) + } else { + log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) + } + log.SetLevel(lvl) +} + +func Execute() { + rootCmd.AddCommand(runCmd) + + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/cmd/run.go b/cmd/run.go new file mode 100644 index 00000000..7893270d --- /dev/null +++ b/cmd/run.go @@ -0,0 +1,99 @@ +package cmd + +import ( + "context" + "net/http" + "os" + "os/signal" + "time" + + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/sethvargo/go-password/password" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/systemli/ticker/internal/api" + "github.com/systemli/ticker/internal/config" + "github.com/systemli/ticker/internal/storage" +) + +var ( + runCmd = &cobra.Command{ + Use: "run", + Short: "Run the ticker", + Run: func(cmd *cobra.Command, args []string) { + log.Infof("starting ticker api on %s", cfg.Listen) + if GitCommit != "" && GitVersion != "" { + log.Infof("build info: %s (commit: %s)", GitVersion, GitCommit) + } + + go func() { + http.Handle("/metrics", promhttp.Handler()) + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(` + Ticker Metrics Exporter + +

Ticker Metrics Exporter

+

Metrics

+ + `)) + }) + log.Fatal(http.ListenAndServe(cfg.MetricsListen, nil)) + }() + + store := storage.NewStorage(cfg.Database, cfg.UploadPath) + router := api.API(cfg, store) + server := &http.Server{ + Addr: cfg.Listen, + Handler: router, + } + + firstRun(store, cfg) + + go func() { + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatal(err) + } + }() + + // Wait for interrupt signal to gracefully shutdown the server with a timeout of 5 seconds. + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + + log.Infoln("Shutdown Ticker") + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if err := server.Shutdown(ctx); err != nil { + log.Fatal(err) + } + }, + } +) + +func firstRun(store storage.TickerStorage, config config.Config) { + count, err := store.CountUser() + if err != nil { + log.Fatal("error using database") + } + + if count == 0 { + pw, err := password.Generate(24, 3, 3, false, false) + if err != nil { + log.Fatal(err) + } + + user, err := storage.NewAdminUser(config.Initiator, pw) + if err != nil { + log.Fatal("could not create first user") + } + + err = store.SaveUser(&user) + if err != nil { + log.Fatal("could not persist first user") + } + + log.WithField("email", user.Email).WithField("password", pw).Info("admin user created (change password now!)") + } +} diff --git a/docs/assets/ticker-api.service b/docs/assets/ticker-api.service index 4a819fb0..6983c281 100644 --- a/docs/assets/ticker-api.service +++ b/docs/assets/ticker-api.service @@ -10,8 +10,8 @@ User=ticker # If you build ticker on this machine, include the first "ExecStart" # If you downloaded ticker from github, include the second "ExecStart" # If you put ticker in a different location, you can probably figure out what to change. ;) -#ExecStart=/var/www/ticker/build/ticker -config /var/www/ticker/config.yml -#ExecStart=/var/www/ticker/ticker -config /var/www/ticker/config.yml +#ExecStart=/var/www/ticker/build/ticker run --config /var/www/ticker/config.yml +#ExecStart=/var/www/ticker/ticker run --config /var/www/ticker/config.yml WorkingDirectory=/var/www/ticker [Install] WantedBy=multi-user.target diff --git a/docs/index.md b/docs/index.md index 35ddca63..bb52a2c6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,15 +13,17 @@ the [Systemli Ticker Project](https://www.systemli.org/en/service/ticker.html). ## First run - make sure you have pulled the repository -```shell -git clone git@github.com:systemli/ticker.git -``` + + ```shell + git clone git@github.com:systemli/ticker.git + ``` - start the ticker -```shell -cp config.yml.dist config.yml -go run . -``` + + ```shell + cp config.yml.dist config.yml + go run . run + ``` Now you have a running ticker API! diff --git a/go.mod b/go.mod index dec6f113..241846ab 100644 --- a/go.mod +++ b/go.mod @@ -40,6 +40,7 @@ require ( github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/leodido/go-urn v1.2.4 // indirect @@ -55,6 +56,7 @@ require ( github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect diff --git a/go.sum b/go.sum index 9a903276..76bd968c 100644 --- a/go.sum +++ b/go.sum @@ -66,6 +66,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -200,6 +201,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -269,6 +272,7 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI= github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -277,6 +281,8 @@ github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= diff --git a/main.go b/main.go index 28259bb1..edf0e294 100644 --- a/main.go +++ b/main.go @@ -1,127 +1,7 @@ package main -import ( - "context" - "flag" - "net/http" - "os" - "os/signal" - "time" - - "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/systemli/ticker/internal/api" - "github.com/systemli/ticker/internal/bridge" - "github.com/systemli/ticker/internal/config" - "github.com/systemli/ticker/internal/storage" - - "github.com/sethvargo/go-password/password" - - log "github.com/sirupsen/logrus" -) - -var ( - GitCommit string - GitVersion string -) +import "github.com/systemli/ticker/cmd" func main() { - var configPath string - flag.StringVar(&configPath, "config", "config.yml", "path to config.yml") - flag.Parse() - - config := config.LoadConfig(configPath) - //TODO: Improve startup routine - if config.TelegramEnabled() { - user, err := bridge.BotUser(config.TelegramBotToken) - if err != nil { - log.WithError(err).Error("Unable to retrieve the user information for the Telegram Bot") - } else { - config.TelegramBotUser = user - } - } - - lvl, err := log.ParseLevel(config.LogLevel) - if err != nil { - panic(err) - } - if config.LogFormat == "json" { - log.SetFormatter(&log.JSONFormatter{}) - } else { - log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) - } - log.SetLevel(lvl) - - log.Infof("starting ticker api on %s", config.Listen) - if GitCommit != "" && GitVersion != "" { - log.Infof("build info: %s (commit: %s)", GitVersion, GitCommit) - } - - go func() { - http.Handle("/metrics", promhttp.Handler()) - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - _, _ = w.Write([]byte(` - Ticker Metrics Exporter - -

Ticker Metrics Exporter

-

Metrics

- - `)) - }) - log.Fatal(http.ListenAndServe(config.MetricsListen, nil)) - }() - - store := storage.NewStorage(config.Database, config.UploadPath) - router := api.API(config, store) - server := &http.Server{ - Addr: config.Listen, - Handler: router, - } - - firstRun(store, config) - - go func() { - if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatal(err) - } - }() - - // Wait for interrupt signal to gracefully shutdown the server with a timeout of 5 seconds. - quit := make(chan os.Signal, 1) - signal.Notify(quit, os.Interrupt) - <-quit - - log.Infoln("Shutdown Ticker") - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - if err := server.Shutdown(ctx); err != nil { - log.Fatal(err) - } -} - -func firstRun(store storage.TickerStorage, config config.Config) { - count, err := store.CountUser() - if err != nil { - log.Fatal("error using database") - } - - if count == 0 { - pw, err := password.Generate(24, 3, 3, false, false) - if err != nil { - log.Fatal(err) - } - - user, err := storage.NewAdminUser(config.Initiator, pw) - if err != nil { - log.Fatal("could not create first user") - } - - err = store.SaveUser(&user) - if err != nil { - log.Fatal("could not persist first user") - } - - log.WithField("email", user.Email).WithField("password", pw).Info("admin user created (change password now!)") - } + cmd.Execute() }