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()
}