2023-07-31 18:11:29 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-08-20 16:57:53 +02:00
|
|
|
"embed"
|
|
|
|
"html/template"
|
|
|
|
"io/fs"
|
2023-07-31 18:11:29 +02:00
|
|
|
"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 {
|
2023-08-20 16:57:53 +02:00
|
|
|
Users []*User `json:"users"`
|
|
|
|
Secrets *SecretsConfig `json:"secrets"`
|
|
|
|
Addr string `json:"addr"`
|
2023-07-31 18:11:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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"`
|
|
|
|
}
|
|
|
|
|
2023-08-20 16:57:53 +02:00
|
|
|
//go:embed assets
|
|
|
|
var assets embed.FS
|
|
|
|
|
2023-07-31 18:11:29 +02:00
|
|
|
func NewAdmin() *AdminConfig {
|
2023-08-20 16:57:53 +02:00
|
|
|
log.WithFields(log.Fields{}).Debugf("starting")
|
|
|
|
defer log.WithFields(log.Fields{}).Debugf("done")
|
2023-07-31 18:11:29 +02:00
|
|
|
|
|
|
|
a := &AdminConfig{
|
2023-08-20 16:57:53 +02:00
|
|
|
Addr: "0.0.0.0:8080",
|
|
|
|
Secrets: NewSecrets(),
|
|
|
|
Users: make([]*User, 0),
|
2023-07-31 18:11:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
|
2023-08-20 16:57:53 +02:00
|
|
|
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
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-07-31 18:11:29 +02:00
|
|
|
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()
|
|
|
|
|
2023-07-31 18:19:28 +02:00
|
|
|
if !*debug {
|
|
|
|
gin.SetMode(gin.ReleaseMode)
|
|
|
|
}
|
|
|
|
|
2023-07-31 18:11:29 +02:00
|
|
|
r := gin.Default()
|
2023-08-20 16:57:53 +02:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2023-07-31 18:11:29 +02:00
|
|
|
r.GET("/ping", func(c *gin.Context) {
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
|
"message": "pong",
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2023-07-31 20:47:24 +02:00
|
|
|
r.GET("/run", func(c *gin.Context) {
|
|
|
|
cfg.Run()
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
|
"message": "done",
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2023-08-20 16:57:53 +02:00
|
|
|
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)
|
|
|
|
|
2023-07-31 18:11:29 +02:00
|
|
|
srv := &http.Server{
|
2023-07-31 18:19:28 +02:00
|
|
|
Addr: a.Addr,
|
2023-07-31 18:11:29 +02:00
|
|
|
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))
|
2023-08-01 10:09:37 +02:00
|
|
|
if _, err := c.AddFunc("0 * * * *", func() { cfg.Run() }); err != nil {
|
2023-08-01 01:22:59 +02:00
|
|
|
log.WithFields(log.Fields{"call": "cron.AddFunc", "error": err}).Errorf("")
|
|
|
|
}
|
2023-08-01 00:15:54 +02:00
|
|
|
c.Start()
|
2023-08-01 09:35:45 +02:00
|
|
|
log.WithFields(log.Fields{"call": "cron.Start"}).Debugf("cron started")
|
2023-07-31 18:11:29 +02:00
|
|
|
|
|
|
|
// 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")
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|