v2 #2
95
admin.go
Normal file
95
admin.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdmin() *AdminConfig {
|
||||||
|
pepper, _ := password.Generate(20, 5, 0, false, false)
|
||||||
|
ctx, _ := password.Generate(20, 5, 0, false, false)
|
||||||
|
|
||||||
|
a := &AdminConfig{
|
||||||
|
Addr: "0.0.0.0:8080",
|
||||||
|
Secrets: SecretsConfig{
|
||||||
|
PasswordPepper: pepper,
|
||||||
|
ContextKey: ctx,
|
||||||
|
ContextExpiration: 3600,
|
||||||
|
ScryptN: 32768,
|
||||||
|
ScryptR: 8,
|
||||||
|
ScryptP: 1,
|
||||||
|
},
|
||||||
|
Users: make([]User, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
r := gin.Default()
|
||||||
|
r.GET("/ping", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "pong",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Addr: ":8080",
|
||||||
|
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))
|
||||||
|
c.AddFunc("00 * * *", func() { cfg.Run() })
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
backup.go
20
backup.go
@ -51,22 +51,12 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if *isDaemon {
|
if *isDaemon {
|
||||||
cfg.Start(nil)
|
if cfg.Admin == nil {
|
||||||
server := NewServer(cfg.Admin.Addr, cfg.Admin.Username, cfg.Admin.Password)
|
cfg.Admin = NewAdmin()
|
||||||
server.Run()
|
|
||||||
} else {
|
|
||||||
e := NewEmail(time.Now())
|
|
||||||
cfg.Start(e)
|
|
||||||
defer cfg.Stop(e)
|
|
||||||
|
|
||||||
cfg.Run(e)
|
|
||||||
|
|
||||||
cfg.Cleanup(e)
|
|
||||||
|
|
||||||
if err := e.Send(); err != nil {
|
|
||||||
log.Printf("Cannot send email (%s)", err)
|
|
||||||
}
|
}
|
||||||
|
cfg.Admin.Run()
|
||||||
|
} else {
|
||||||
|
cfg.Run()
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
47
config.go
47
config.go
@ -19,19 +19,13 @@ type Config struct {
|
|||||||
Email EmailConfig `json:"email"`
|
Email EmailConfig `json:"email"`
|
||||||
Apps []AppConfig `json:"apps"`
|
Apps []AppConfig `json:"apps"`
|
||||||
Timezone string `json:"timezone"`
|
Timezone string `json:"timezone"`
|
||||||
|
Admin *AdminConfig `json:"admin"`
|
||||||
Debug bool `json:"debug"`
|
Debug bool `json:"debug"`
|
||||||
Admin AdminConfig `json:"admin"`
|
|
||||||
box map[string]*Box `json:"-"`
|
box map[string]*Box `json:"-"`
|
||||||
apps map[string]*App `json:"-"`
|
apps map[string]*App `json:"-"`
|
||||||
timezone *time.Location `json:"-"`
|
timezone *time.Location `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdminConfig struct {
|
|
||||||
Addr string `json:"addr"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BoxConfig struct {
|
type BoxConfig struct {
|
||||||
Addr string `json:"addr"`
|
Addr string `json:"addr"`
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
@ -159,34 +153,31 @@ func (c *Config) LoadFile(path string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) Start(e *Email) {
|
// Run config
|
||||||
|
func (c *Config) Run() {
|
||||||
log.WithFields(log.Fields{}).Debugf("starting")
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
defer log.WithFields(log.Fields{}).Debugf("done")
|
defer log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
|
e := NewEmail(time.Now())
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Setup boxes
|
||||||
for _, b := range c.box {
|
for _, b := range c.box {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(box *Box) {
|
go func(box *Box) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if err := box.Open(); err != nil {
|
if err := box.Open(); err != nil {
|
||||||
log.WithFields(log.Fields{"name": box.name, "call": "Open", "error": err}).Errorf("")
|
log.WithFields(log.Fields{"name": box.name, "call": "Open", "error": err}).Errorf("")
|
||||||
if e != nil {
|
|
||||||
e.AddItem(fmt.Sprintf(" - Box : %s is down", box.name))
|
e.AddItem(fmt.Sprintf(" - Box : %s is down", box.name))
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}(b)
|
}(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
|
||||||
|
|
||||||
// Run config
|
// Run each app
|
||||||
func (c *Config) Run(e *Email) {
|
|
||||||
log.WithFields(log.Fields{}).Debugf("starting")
|
|
||||||
defer log.WithFields(log.Fields{}).Debugf("done")
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for _, a := range cfg.apps {
|
for _, a := range cfg.apps {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(app *App) {
|
go func(app *App) {
|
||||||
@ -199,13 +190,7 @@ func (c *Config) Run(e *Email) {
|
|||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
return
|
// Cleanup
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Cleanup(e *Email) {
|
|
||||||
log.WithFields(log.Fields{}).Debugf("starting")
|
|
||||||
defer log.WithFields(log.Fields{}).Debugf("done")
|
|
||||||
|
|
||||||
for _, a := range cfg.apps {
|
for _, a := range cfg.apps {
|
||||||
for _, src := range a.sources {
|
for _, src := range a.sources {
|
||||||
if b, ok := c.box[src.Box()]; ok {
|
if b, ok := c.box[src.Box()]; ok {
|
||||||
@ -233,23 +218,15 @@ func (c *Config) Cleanup(e *Email) {
|
|||||||
log.WithFields(log.Fields{"box": b.name, "fs": fs.path}).Warnf("not backed up")
|
log.WithFields(log.Fields{"box": b.name, "fs": fs.path}).Warnf("not backed up")
|
||||||
e.AddItem(fmt.Sprintf(" - Src : Folder not backed up (%s)", b.name+":"+fs.path))
|
e.AddItem(fmt.Sprintf(" - Src : Folder not backed up (%s)", b.name+":"+fs.path))
|
||||||
}
|
}
|
||||||
if len(fs.destApps) == 0 && len(fs.srcApps) == 0 && fs.managed {
|
if len(fs.destApps) == 0 && !fs.backedUp && fs.managed {
|
||||||
log.WithFields(log.Fields{"box": b.name, "fs": fs.path}).Warnf("managed")
|
log.WithFields(log.Fields{"box": b.name, "fs": fs.path}).Warnf("managed")
|
||||||
e.AddItem(fmt.Sprintf(" - Dest : Folder managed (%s)", b.name+":"+fs.path))
|
e.AddItem(fmt.Sprintf(" - Dest : Folder managed (%s)", b.name+":"+fs.path))
|
||||||
}
|
}
|
||||||
if fs.managed {
|
|
||||||
log.WithFields(log.Fields{"box": b.name, "fs": fs.path, "src": len(fs.srcApps), "dest": len(fs.destApps)}).Warnf("managed")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
// Stop
|
||||||
|
|
||||||
func (c *Config) Stop(e *Email) {
|
|
||||||
log.WithFields(log.Fields{}).Debugf("starting")
|
|
||||||
defer log.WithFields(log.Fields{}).Debugf("done")
|
|
||||||
|
|
||||||
for _, b := range c.box {
|
for _, b := range c.box {
|
||||||
if err := b.Close(); err != nil {
|
if err := b.Close(); err != nil {
|
||||||
log.WithFields(log.Fields{"name": b.name, "call": "Close", "error": err}).Errorf("")
|
log.WithFields(log.Fields{"name": b.name, "call": "Close", "error": err}).Errorf("")
|
||||||
@ -261,4 +238,6 @@ func (c *Config) Stop(e *Email) {
|
|||||||
log.WithFields(log.Fields{"call": "email.Send", "error": err}).Errorf("")
|
log.WithFields(log.Fields{"call": "email.Send", "error": err}).Errorf("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
2
go.mod
2
go.mod
@ -4,6 +4,8 @@ go 1.16
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
|
github.com/sethvargo/go-password v0.2.0 // indirect
|
||||||
github.com/silenceper/pool v1.0.0 // indirect
|
github.com/silenceper/pool v1.0.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
|
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
|
||||||
|
4
go.sum
4
go.sum
@ -44,6 +44,10 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
|
|||||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
|
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/silenceper/pool v1.0.0 h1:JTCaA+U6hJAA0P8nCx+JfsRCHMwLTfatsm5QXelffmU=
|
github.com/silenceper/pool v1.0.0 h1:JTCaA+U6hJAA0P8nCx+JfsRCHMwLTfatsm5QXelffmU=
|
||||||
github.com/silenceper/pool v1.0.0/go.mod h1:3DN13bqAbq86Lmzf6iUXWEPIWFPOSYVfaoceFvilKKI=
|
github.com/silenceper/pool v1.0.0/go.mod h1:3DN13bqAbq86Lmzf6iUXWEPIWFPOSYVfaoceFvilKKI=
|
||||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||||
|
44
server.go
44
server.go
@ -1,44 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Server struct {
|
|
||||||
Addr string
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewServer(addr, username, password string) *Server {
|
|
||||||
s := &Server{
|
|
||||||
Addr: serverAddr,
|
|
||||||
Username: serverUsername,
|
|
||||||
Password: serverPassword,
|
|
||||||
}
|
|
||||||
|
|
||||||
if addr != "" {
|
|
||||||
s.Addr = addr
|
|
||||||
}
|
|
||||||
if username != "" {
|
|
||||||
s.Username = username
|
|
||||||
}
|
|
||||||
if password != "" {
|
|
||||||
s.Password = password
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Run() {
|
|
||||||
r := gin.Default()
|
|
||||||
r.GET("/ping", func(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"message": "pong",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
r.Run(s.Addr) // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
|
|
||||||
}
|
|
76
user.go
Normal file
76
user.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/crypto/scrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Salt string `json:"salt"`
|
||||||
|
Passwd string `json:"passwd"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUser(name, passwd string) (*User, error) {
|
||||||
|
log.WithFields(log.Fields{"name": name}).Debugf("starting")
|
||||||
|
defer log.WithFields(log.Fields{"name": name}).Debugf("done")
|
||||||
|
|
||||||
|
for _, v := range cfg.Admin.Users {
|
||||||
|
if v.Username == name {
|
||||||
|
err := errors.New("user already exists")
|
||||||
|
log.WithFields(log.Fields{"name": name, "error": err}).Errorf("")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u := &User{
|
||||||
|
Username: name,
|
||||||
|
}
|
||||||
|
|
||||||
|
salt := make([]byte, 32)
|
||||||
|
if _, err := rand.Read(salt); err != nil {
|
||||||
|
log.WithFields(log.Fields{"name": name, "call": "rand.Read", "error": err}).Errorf("")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
u.Salt = hex.EncodeToString(salt)
|
||||||
|
|
||||||
|
if pass, err := u.HashPassword(passwd); err != nil {
|
||||||
|
log.WithFields(log.Fields{"name": name, "call": "HashPassword", "error": err}).Errorf("")
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
u.Passwd = hex.EncodeToString(pass)
|
||||||
|
}
|
||||||
|
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) HashPassword(passwd string) ([]byte, error) {
|
||||||
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
|
defer log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
|
//peppering the pass
|
||||||
|
hash := hmac.New(sha256.New, []byte(cfg.Admin.Secrets.PasswordPepper))
|
||||||
|
hash.Write([]byte(passwd))
|
||||||
|
hashPass := hash.Sum(nil)
|
||||||
|
|
||||||
|
//salting the hash
|
||||||
|
salt := make([]byte, 32)
|
||||||
|
if _, err := hex.Decode(salt, []byte(u.Salt)); err != nil {
|
||||||
|
log.WithFields(log.Fields{"call": "hex.Decode", "error": err}).Errorf("")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if h, err := scrypt.Key(hashPass, salt, cfg.Admin.Secrets.ScryptN, cfg.Admin.Secrets.ScryptR, cfg.Admin.Secrets.ScryptP, 32); err != nil {
|
||||||
|
log.WithFields(log.Fields{"call": "scrypt.Key", "error": err}).Errorf("")
|
||||||
|
return h, err
|
||||||
|
} else {
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user