package main import ( "context" "embed" "html/template" "io/fs" "net/http" "os/signal" "syscall" "time" "github.com/gin-gonic/gin" "github.com/robfig/cron/v3" "github.com/sethvargo/go-password/password" log "github.com/sirupsen/logrus" ) type AdminConfig struct { Users []*User `json:"users"` Secrets *SecretsConfig `json:"secrets"` Addr string `json:"addr"` } type SecretsConfig struct { PasswordPepper string `json:"password_pepper"` ContextKey string `json:"context_key"` ContextExpiration int `json:"context_expiration"` ScryptN int `json:"scrypt_n"` ScryptR int `json:"scrypt_r"` ScryptP int `json:"scrypt_p"` } //go:embed assets var assets embed.FS func NewAdmin() *AdminConfig { log.WithFields(log.Fields{}).Debugf("starting") defer log.WithFields(log.Fields{}).Debugf("done") a := &AdminConfig{ Addr: "0.0.0.0:8080", Secrets: NewSecrets(), Users: make([]*User, 0), } return a } func NewSecrets() *SecretsConfig { log.WithFields(log.Fields{}).Debugf("starting") defer log.WithFields(log.Fields{}).Debugf("done") pepper, _ := password.Generate(20, 5, 0, false, false) ctx, _ := password.Generate(20, 5, 0, false, false) return &SecretsConfig{ PasswordPepper: pepper, ContextKey: ctx, ContextExpiration: 3600, ScryptN: 32768, ScryptR: 8, ScryptP: 1, } } func (a *AdminConfig) NewAdminUser() { log.WithFields(log.Fields{}).Debugf("starting") defer log.WithFields(log.Fields{}).Debugf("done") p, _ := password.Generate(20, 5, 0, false, false) u, _ := NewUser("admin", p) a.Users = append(a.Users, u) log.WithFields(log.Fields{}).Warnf("Admin user password : %s", p) return } func (a *AdminConfig) Run() { log.WithFields(log.Fields{}).Debugf("starting") defer log.WithFields(log.Fields{}).Debugf("done") // Create context that listens for the interrupt signal from the OS. ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() if !*debug { gin.SetMode(gin.ReleaseMode) } r := gin.Default() if t, err := template.ParseFS(assets, "assets/templates/*.html"); err != nil { log.WithFields(log.Fields{"call": "template.ParseFS", "error": err}).Errorf("") return } else { r.SetHTMLTemplate(t) } r.GET("/", HttpAnyIndex) r.POST("/", HttpAnyIndex) r.GET("/ping", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "pong", }) }) r.GET("/run", func(c *gin.Context) { cfg.Run() c.JSON(http.StatusOK, gin.H{ "message": "done", }) }) fsys, _ := fs.Sub(assets, "assets/static") r.StaticFS("/assets", http.FS(fsys)) protected := r.Group("p", HttpAuth()) protected.GET("test", HttpAnyHome) unprotected := r.Group("u", HttpNoAuth()) unprotected.GET("signin", HttpAnySignIn) unprotected.POST("signin", HttpAnySignIn) srv := &http.Server{ Addr: a.Addr, Handler: r, } go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.WithFields(log.Fields{"call": "http.ListenAndServe", "attr": a.Addr, "error": err}).Errorf("") } }() c := cron.New(cron.WithLocation(time.UTC)) if _, err := c.AddFunc("0 * * * *", func() { cfg.Run() }); err != nil { log.WithFields(log.Fields{"call": "cron.AddFunc", "error": err}).Errorf("") } c.Start() log.WithFields(log.Fields{"call": "cron.Start"}).Debugf("cron started") // Listen for the interrupt signal. <-ctx.Done() // Restore default behavior on the interrupt signal and notify user of shutdown. stop() log.WithFields(log.Fields{"call": "stop"}).Warnf("shutting down") // The context is used to inform the server it has 5 seconds to finish // the request it is currently handling ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.WithFields(log.Fields{"call": "http.Shutdown", "error": err}).Errorf("shutting down") } }