Compare commits
45 Commits
e7ed6cb2fd
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3aa0a852a2 | ||
|
|
54d85767a7 | ||
|
|
f47ddd1873 | ||
|
|
ad88d9fe88 | ||
|
|
fc81c38ffd | ||
|
|
fd1c14831f | ||
|
|
c8715679f8 | ||
|
|
ee94b077b4 | ||
|
|
d92380cccb | ||
|
|
a1ed1035e9 | ||
|
|
6a0c8006d6 | ||
|
|
1890050a30 | ||
|
|
3e867da45f | ||
|
|
a1ba422429 | ||
|
|
05054be795 | ||
|
|
73f9551c8f | ||
|
|
ace13b68a8 | ||
|
|
6ae863b60c | ||
|
|
2aca8b1ceb | ||
|
|
3c7838b10a | ||
|
|
fd02cfdfc3 | ||
|
|
6cf998997e | ||
|
|
963fd34724 | ||
|
|
a31ff56055 | ||
|
|
060933aa27 | ||
|
|
3bd57a4e98 | ||
|
|
b83b0e8c4f | ||
|
|
6ed38972e7 | ||
|
|
757b6b1627 | ||
|
|
376da04727 | ||
|
|
9a86d82460 | ||
|
|
ad1a8decc3 | ||
|
|
d217ba310e | ||
|
|
49417729ec | ||
|
|
d8cfb3cf00 | ||
|
|
cd94f48b8a | ||
|
|
9a9db972a7 | ||
|
|
86da86684b | ||
|
|
6fcf8b421c | ||
|
|
e7af7ce2fd | ||
|
|
3bcd6664a6 | ||
|
|
e1806fd27a | ||
|
|
26c324c43c | ||
|
|
2ca0695a1f | ||
|
|
3b1d0fc850 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
config.json
|
config.json
|
||||||
backup
|
backup
|
||||||
|
backup.json
|
||||||
|
|||||||
16
addr.go
16
addr.go
@@ -84,7 +84,7 @@ func (a Addr) ValidSnapshots() ([]*ZfsSnapshot, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Addr) SetManaged(val bool) error {
|
func (a Addr) SetManaged(managed bool) error {
|
||||||
log.WithFields(log.Fields{"addr": a}).Debugf("starting")
|
log.WithFields(log.Fields{"addr": a}).Debugf("starting")
|
||||||
defer log.WithFields(log.Fields{"addr": a}).Debugf("done")
|
defer log.WithFields(log.Fields{"addr": a}).Debugf("done")
|
||||||
|
|
||||||
@@ -97,11 +97,11 @@ func (a Addr) SetManaged(val bool) error {
|
|||||||
log.WithFields(log.Fields{"addr": a, "path": a.Path(), "error": err}).Errorf("")
|
log.WithFields(log.Fields{"addr": a, "path": a.Path(), "error": err}).Errorf("")
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
fs.mx.Lock()
|
fs.Lock()
|
||||||
defer fs.mx.Unlock()
|
defer fs.Unlock()
|
||||||
if fs.managed != val {
|
if fs.managed != managed {
|
||||||
var cmd string
|
var cmd string
|
||||||
if val {
|
if managed {
|
||||||
cmd = fmt.Sprintf("zfs set %s=+ %s", zfsManagedPropertyName, a.Path())
|
cmd = fmt.Sprintf("zfs set %s=+ %s", zfsManagedPropertyName, a.Path())
|
||||||
} else {
|
} else {
|
||||||
cmd = fmt.Sprintf("zfs set %s=- %s", zfsManagedPropertyName, a.Path())
|
cmd = fmt.Sprintf("zfs set %s=- %s", zfsManagedPropertyName, a.Path())
|
||||||
@@ -111,7 +111,7 @@ func (a Addr) SetManaged(val bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fs.managed = val
|
fs.managed = managed
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,8 +129,8 @@ func (a Addr) SetBackedUp(val bool) error {
|
|||||||
log.WithFields(log.Fields{"addr": a, "path": a.Path(), "error": err}).Errorf("")
|
log.WithFields(log.Fields{"addr": a, "path": a.Path(), "error": err}).Errorf("")
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
fs.mx.Lock()
|
fs.Lock()
|
||||||
defer fs.mx.Unlock()
|
defer fs.Unlock()
|
||||||
fs.backedUp = val
|
fs.backedUp = val
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
101
admin.go
101
admin.go
@@ -3,8 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"embed"
|
"embed"
|
||||||
"html/template"
|
|
||||||
"io/fs"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -12,23 +10,11 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
"github.com/sethvargo/go-password/password"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AdminConfig struct {
|
type AdminConfig struct {
|
||||||
Users []*User `json:"users"`
|
Addr string `json:"addr"`
|
||||||
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
|
//go:embed assets
|
||||||
@@ -39,46 +25,12 @@ func NewAdmin() *AdminConfig {
|
|||||||
defer log.WithFields(log.Fields{}).Debugf("done")
|
defer log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
a := &AdminConfig{
|
a := &AdminConfig{
|
||||||
Addr: "0.0.0.0:8080",
|
Addr: "0.0.0.0:8080",
|
||||||
Secrets: NewSecrets(),
|
|
||||||
Users: make([]*User, 0),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return a
|
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() {
|
func (a *AdminConfig) 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")
|
||||||
@@ -93,38 +45,33 @@ func (a *AdminConfig) Run() {
|
|||||||
|
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
|
||||||
if t, err := template.ParseFS(assets, "assets/templates/*.html"); err != nil {
|
r.GET("/run", ApiRun)
|
||||||
log.WithFields(log.Fields{"call": "template.ParseFS", "error": err}).Errorf("")
|
r.GET("/run/:app", ApiRunApp)
|
||||||
return
|
|
||||||
} else {
|
|
||||||
r.SetHTMLTemplate(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.GET("/", HttpAnyIndex)
|
r.GET("/snapshot/add/:app/:schedule", ApiSnapshotAdd)
|
||||||
r.POST("/", HttpAnyIndex)
|
r.GET("/snapshot/del/:app/:name", ApiSnapshotDel)
|
||||||
|
r.GET("/snapshot/list/:app", ApiSnapshotList)
|
||||||
|
|
||||||
r.GET("/ping", func(c *gin.Context) {
|
r.GET("/schedule/list", ApiScheduleList)
|
||||||
c.JSON(http.StatusOK, gin.H{
|
r.GET("/schedule/add/:name/:duration", ApiScheduleAdd)
|
||||||
"message": "pong",
|
r.GET("/schedule/del/:name", ApiScheduleDel)
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
r.GET("/run", func(c *gin.Context) {
|
r.GET("/save", ApiSave)
|
||||||
cfg.Run()
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"message": "done",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
fsys, _ := fs.Sub(assets, "assets/static")
|
r.GET("/config", ApiConfig)
|
||||||
r.StaticFS("/assets", http.FS(fsys))
|
r.GET("/config/:app", ApiConfigApp)
|
||||||
|
|
||||||
protected := r.Group("p", HttpAuth())
|
r.GET("/app/list", ApiAppList)
|
||||||
protected.GET("test", HttpAnyHome)
|
r.GET("/app/list/schedule/:schedule", ApiListSchedule)
|
||||||
|
r.GET("/app/add/:app", ApiAppAdd)
|
||||||
|
r.GET("/app/del/:app", ApiAppDel)
|
||||||
|
|
||||||
unprotected := r.Group("u", HttpNoAuth())
|
r.GET("/app/activate/:app", ApiAppActivate)
|
||||||
unprotected.GET("signin", HttpAnySignIn)
|
r.GET("/app/deactivate/:app", ApiAppDeactivate)
|
||||||
unprotected.POST("signin", HttpAnySignIn)
|
|
||||||
|
r.GET("/app/:app/source/list", ApiAppSourceList)
|
||||||
|
r.GET("/app/:app/source/add/*src", ApiAppSourceAdd)
|
||||||
|
r.GET("/app/:app/source/del/*src", ApiAppSourceDel)
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: a.Addr,
|
Addr: a.Addr,
|
||||||
@@ -138,7 +85,7 @@ func (a *AdminConfig) Run() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
c := cron.New(cron.WithLocation(time.UTC))
|
c := cron.New(cron.WithLocation(time.UTC))
|
||||||
if _, err := c.AddFunc("0 * * * *", func() { cfg.Run() }); err != nil {
|
if _, err := c.AddFunc("0 * * * *", func() { cfg.Run(true) }); err != nil {
|
||||||
log.WithFields(log.Fields{"call": "cron.AddFunc", "error": err}).Errorf("")
|
log.WithFields(log.Fields{"call": "cron.AddFunc", "error": err}).Errorf("")
|
||||||
}
|
}
|
||||||
c.Start()
|
c.Start()
|
||||||
|
|||||||
636
api.go
Normal file
636
api.go
Normal file
@@ -0,0 +1,636 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/tidwall/pretty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ApiRun(c *gin.Context) {
|
||||||
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
|
cfg.Run(true)
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "done",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiSnapshotAdd(c *gin.Context) {
|
||||||
|
log.WithFields(log.Fields{"app": c.Param("app")}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{"app": c.Param("app")}).Debugf("done")
|
||||||
|
|
||||||
|
if app, ok := cfg.apps[c.Param("app")]; ok {
|
||||||
|
schedule := c.Param("schedule")
|
||||||
|
if _, ok := app.schedule[schedule]; ok {
|
||||||
|
if err := app.RunStandaloneSchedule(schedule); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "done",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "no schedule found",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "no app found",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiSnapshotDel(c *gin.Context) {
|
||||||
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "not implemented",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiSnapshotList(c *gin.Context) {
|
||||||
|
log.WithFields(log.Fields{"app": c.Param("app")}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{"app": c.Param("app")}).Debugf("done")
|
||||||
|
|
||||||
|
if app, ok := cfg.apps[c.Param("app")]; ok {
|
||||||
|
if snapshots, err := app.Snapshots(); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "done",
|
||||||
|
"snapshots": snapshots,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "no app found",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME
|
||||||
|
func ApiScheduleAdd(c *gin.Context) {
|
||||||
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "not implemented",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME
|
||||||
|
func ApiScheduleDel(c *gin.Context) {
|
||||||
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "not implemented",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME
|
||||||
|
func ApiScheduleList(c *gin.Context) {
|
||||||
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "not implemented",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiRunApp(c *gin.Context) {
|
||||||
|
log.WithFields(log.Fields{"app": c.Param("app")}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{"app": c.Param("app")}).Debugf("done")
|
||||||
|
|
||||||
|
if app, ok := cfg.apps[c.Param("app")]; ok {
|
||||||
|
if err := app.RunStandaloneTime(time.Now()); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "done",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "no app found",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiSave(c *gin.Context) {
|
||||||
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
|
if err := cfg.Save(true); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "done",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiConfig(c *gin.Context) {
|
||||||
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
|
if b, err := cfg.Pretty(true); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
c.Data(http.StatusOK, "application/json", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiConfigApp(c *gin.Context) {
|
||||||
|
log.WithFields(log.Fields{"app": c.Param("app")}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{"app": c.Param("app")}).Debugf("done")
|
||||||
|
|
||||||
|
name := c.Param("app")
|
||||||
|
found := false
|
||||||
|
|
||||||
|
CfgLock()
|
||||||
|
defer CfgUnlock()
|
||||||
|
|
||||||
|
for _, app := range cfg.Apps {
|
||||||
|
if app.Name == name {
|
||||||
|
found = true
|
||||||
|
if b, err := app.Pretty(false); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
c.Data(http.StatusOK, "application/json", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "no app found",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiAppList(c *gin.Context) {
|
||||||
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
|
list := make([]string, 0)
|
||||||
|
|
||||||
|
CfgLock()
|
||||||
|
defer CfgUnlock()
|
||||||
|
|
||||||
|
for _, app := range cfg.Apps {
|
||||||
|
list = append(list, app.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.Sort(list)
|
||||||
|
|
||||||
|
b, err := json.Marshal(list)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Data(http.StatusOK, "application/json", pretty.PrettyOptions(b, &pretty.Options{Indent: " "}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiListSchedule(c *gin.Context) {
|
||||||
|
log.WithFields(log.Fields{"schedule": c.Param("schedule")}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{"schedule": c.Param("schedule")}).Debugf("done")
|
||||||
|
|
||||||
|
name := c.Param("schedule")
|
||||||
|
list := make([]string, 0)
|
||||||
|
|
||||||
|
CfgLock()
|
||||||
|
defer CfgUnlock()
|
||||||
|
|
||||||
|
for _, app := range cfg.Apps {
|
||||||
|
for _, sched := range app.Schedule {
|
||||||
|
if sched == name && !slices.Contains(list, app.Name) {
|
||||||
|
list = append(list, app.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.Sort(list)
|
||||||
|
|
||||||
|
b, err := json.Marshal(list)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Data(http.StatusOK, "application/json", pretty.PrettyOptions(b, &pretty.Options{Indent: " "}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiAppAdd(c *gin.Context) {
|
||||||
|
log.WithFields(log.Fields{"app": c.Param("app")}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{"app": c.Param("app")}).Debugf("done")
|
||||||
|
|
||||||
|
name := c.Param("app")
|
||||||
|
found := false
|
||||||
|
|
||||||
|
CfgLock()
|
||||||
|
defer CfgUnlock()
|
||||||
|
|
||||||
|
for _, app := range cfg.Apps {
|
||||||
|
if app.Name == name {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "app already exist",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app := &AppConfig{
|
||||||
|
Name: name,
|
||||||
|
Active: false,
|
||||||
|
}
|
||||||
|
cfg.Apps = append(cfg.Apps, app)
|
||||||
|
|
||||||
|
err := cfg.Save(false)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "done",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiAppDel(c *gin.Context) {
|
||||||
|
log.WithFields(log.Fields{"app": c.Param("app")}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{"app": c.Param("app")}).Debugf("done")
|
||||||
|
|
||||||
|
name := c.Param("app")
|
||||||
|
found := false
|
||||||
|
|
||||||
|
CfgLock()
|
||||||
|
defer CfgUnlock()
|
||||||
|
|
||||||
|
for id, app := range cfg.Apps {
|
||||||
|
if app.Name == name {
|
||||||
|
if app.Active {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "app still active",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
cfg.Apps[id] = cfg.Apps[len(cfg.Apps)-1]
|
||||||
|
cfg.Apps = cfg.Apps[:len(cfg.Apps)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "no app found",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cfg.Save(false)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "done",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiAppActivate(c *gin.Context) {
|
||||||
|
log.WithFields(log.Fields{"app": c.Param("app")}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{"app": c.Param("app")}).Debugf("done")
|
||||||
|
|
||||||
|
name := c.Param("app")
|
||||||
|
|
||||||
|
CfgLock()
|
||||||
|
defer CfgUnlock()
|
||||||
|
|
||||||
|
for _, app := range cfg.Apps {
|
||||||
|
if app.Name == name {
|
||||||
|
if app.Active {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "app already active",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a, err := cfg.NewApp(app.Name, app.Sources, app.Destinations, app.Schedule, app.Before, app.After)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.apps[app.Name] = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cfg.Save(false)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "done",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiAppDeactivate(c *gin.Context) {
|
||||||
|
log.WithFields(log.Fields{"app": c.Param("app")}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{"app": c.Param("app")}).Debugf("done")
|
||||||
|
|
||||||
|
name := c.Param("app")
|
||||||
|
|
||||||
|
CfgLock()
|
||||||
|
defer CfgUnlock()
|
||||||
|
|
||||||
|
if _, ok := cfg.apps[name]; ok {
|
||||||
|
delete(cfg.apps, name)
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "app is not active",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, app := range cfg.Apps {
|
||||||
|
if app.Name == name {
|
||||||
|
app.Active = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cfg.Save(false)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "done",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiAppSourceList(c *gin.Context) {
|
||||||
|
log.WithFields(log.Fields{"app": c.Param("app")}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{"app": c.Param("app")}).Debugf("done")
|
||||||
|
|
||||||
|
name := c.Param("app")
|
||||||
|
found := false
|
||||||
|
app := &AppConfig{}
|
||||||
|
|
||||||
|
CfgLock()
|
||||||
|
defer CfgUnlock()
|
||||||
|
|
||||||
|
for _, a := range cfg.Apps {
|
||||||
|
if a.Name == name {
|
||||||
|
found = true
|
||||||
|
app = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "app does not exist",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(app.Sources)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Data(http.StatusOK, "application/json", pretty.PrettyOptions(b, &pretty.Options{Indent: " "}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiAppSourceAdd(c *gin.Context) {
|
||||||
|
app := c.Param("app")
|
||||||
|
src := c.Param("src")
|
||||||
|
src = src[1:] // cut first char from wildcard match
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{"app": app, "src": src}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{"app": app, "src": src}).Debugf("done")
|
||||||
|
|
||||||
|
found := false
|
||||||
|
ac := &AppConfig{}
|
||||||
|
|
||||||
|
CfgLock()
|
||||||
|
defer CfgUnlock()
|
||||||
|
|
||||||
|
for _, a := range cfg.Apps {
|
||||||
|
if a.Name == app {
|
||||||
|
found = true
|
||||||
|
ac = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "app does not exist",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s := Addr(src)
|
||||||
|
if s.Box() == "" {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "source box incorrect",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := cfg.box[s.Box()]; !ok {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "source box doesn't exist",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Path() == "" {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "source path incorrect",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(ac.Sources, s.String()) {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "source already exists",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ac.Sources = append(ac.Sources, s.String())
|
||||||
|
|
||||||
|
if ac.Active {
|
||||||
|
a, err := cfg.NewApp(ac.Name, ac.Sources, ac.Destinations, ac.Schedule, ac.Before, ac.After)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.apps[a.name] = a
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cfg.Save(false)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "done",
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiAppSourceDel(c *gin.Context) {
|
||||||
|
app := c.Param("app")
|
||||||
|
src := c.Param("src")
|
||||||
|
src = src[1:] // cut first char from wildcard match
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{"app": app, "src": src}).Debugf("starting")
|
||||||
|
log.WithFields(log.Fields{"app": app, "src": src}).Debugf("done")
|
||||||
|
|
||||||
|
found := false
|
||||||
|
ac := &AppConfig{}
|
||||||
|
|
||||||
|
CfgLock()
|
||||||
|
defer CfgUnlock()
|
||||||
|
|
||||||
|
for _, a := range cfg.Apps {
|
||||||
|
if a.Name == app {
|
||||||
|
found = true
|
||||||
|
ac = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "app does not exist",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !slices.Contains(ac.Sources, src) {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": "source does not exist",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for id, s := range ac.Sources {
|
||||||
|
if s == src {
|
||||||
|
ac.Sources[id] = ac.Sources[len(ac.Sources)-1]
|
||||||
|
ac.Sources = ac.Sources[:len(ac.Sources)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ac.Active {
|
||||||
|
a, err := cfg.NewApp(ac.Name, ac.Sources, ac.Destinations, ac.Schedule, ac.Before, ac.After)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.apps[a.name] = a
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cfg.Save(false)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": "error",
|
||||||
|
"error": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "done",
|
||||||
|
})
|
||||||
|
}
|
||||||
283
app.go
283
app.go
@@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -22,6 +23,30 @@ func (c *Config) NewApp(name string, sources, destinations, schedule []string, b
|
|||||||
log.WithFields(log.Fields{"name": name}).Debugf("starting")
|
log.WithFields(log.Fields{"name": name}).Debugf("starting")
|
||||||
defer log.WithFields(log.Fields{"name": name}).Debugf("done")
|
defer log.WithFields(log.Fields{"name": name}).Debugf("done")
|
||||||
|
|
||||||
|
if _, ok := c.apps[name]; ok {
|
||||||
|
err := errors.New("app already exist")
|
||||||
|
log.WithFields(log.Fields{"app": name, "error": err}).Errorf("")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sources) == 0 {
|
||||||
|
err := errors.New("no sources")
|
||||||
|
log.WithFields(log.Fields{"app": name, "error": err}).Errorf("")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(destinations) == 0 {
|
||||||
|
err := errors.New("no destinations")
|
||||||
|
log.WithFields(log.Fields{"app": name, "error": err}).Errorf("")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(schedule) == 0 {
|
||||||
|
err := errors.New("no schedule")
|
||||||
|
log.WithFields(log.Fields{"app": name, "error": err}).Errorf("")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
a := &App{
|
a := &App{
|
||||||
name: name,
|
name: name,
|
||||||
sources: make([]Addr, 0),
|
sources: make([]Addr, 0),
|
||||||
@@ -258,6 +283,11 @@ func (a *App) RunSchedule(schedule string, now time.Time) error {
|
|||||||
log.WithFields(log.Fields{"app": a.name, "schedule": schedule, "now": now}).Debugf("starting")
|
log.WithFields(log.Fields{"app": a.name, "schedule": schedule, "now": now}).Debugf("starting")
|
||||||
defer log.WithFields(log.Fields{"app": a.name, "schedule": schedule, "now": now}).Debugf("done")
|
defer log.WithFields(log.Fields{"app": a.name, "schedule": schedule, "now": now}).Debugf("done")
|
||||||
|
|
||||||
|
if err := a.SanityCheck(); err != nil {
|
||||||
|
log.WithFields(log.Fields{"app": a.name, "now": now, "call": "SanityCheck", "error": err}).Errorf("")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
snapshotName := SnapshotName(schedule, now)
|
snapshotName := SnapshotName(schedule, now)
|
||||||
log.WithFields(log.Fields{"app": a.name, "schedule": schedule, "now": now, "snapshot": snapshotName}).Debugf("snapshot name")
|
log.WithFields(log.Fields{"app": a.name, "schedule": schedule, "now": now, "snapshot": snapshotName}).Debugf("snapshot name")
|
||||||
|
|
||||||
@@ -277,19 +307,31 @@ func (a *App) RunSchedule(schedule string, now time.Time) error {
|
|||||||
log.WithFields(log.Fields{"app": a.name, "schedule": schedule, "now": now, "call": "RunAfter", "attr": schedule, "error": err}).Errorf("")
|
log.WithFields(log.Fields{"app": a.name, "schedule": schedule, "now": now, "call": "RunAfter", "attr": schedule, "error": err}).Errorf("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, src := range a.sources {
|
||||||
|
if err := src.SetManaged(true); err != nil {
|
||||||
|
log.WithFields(log.Fields{"app": a.name, "call": "src.SetManaged", "error": err}).Errorf("")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.Transfer(); err != nil {
|
||||||
|
log.WithFields(log.Fields{"app": a.name, "call": "Transfer", "error": err}).Errorf("")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.Cleanup(now); err != nil {
|
||||||
|
log.WithFields(log.Fields{"app": a.name, "call": "Cleanup", "error": err}).Errorf("")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) Run(now time.Time) (string, error) {
|
func (a *App) RunTime(now time.Time) (string, error) {
|
||||||
log.WithFields(log.Fields{"app": a.name, "now": now}).Debugf("starting")
|
log.WithFields(log.Fields{"app": a.name, "now": now}).Debugf("starting")
|
||||||
defer log.WithFields(log.Fields{"app": a.name, "now": now}).Debugf("done")
|
defer log.WithFields(log.Fields{"app": a.name, "now": now}).Debugf("done")
|
||||||
|
|
||||||
if err := a.SanityCheck(); err != nil {
|
|
||||||
log.WithFields(log.Fields{"app": a.name, "now": now, "call": "SanityCheck", "error": err}).Errorf("")
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
schedule, err := a.NextSchedule(now)
|
schedule, err := a.NextSchedule(now)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{"app": a.name, "call": "NextSchedule", "error": err}).Errorf("")
|
log.WithFields(log.Fields{"app": a.name, "call": "NextSchedule", "error": err}).Errorf("")
|
||||||
@@ -297,31 +339,7 @@ func (a *App) Run(now time.Time) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.WithFields(log.Fields{"app": a.name, "now": now, "schedule": schedule}).Debugf("schedule")
|
log.WithFields(log.Fields{"app": a.name, "now": now, "schedule": schedule}).Debugf("schedule")
|
||||||
if schedule != "" {
|
return schedule, a.RunSchedule(schedule, now)
|
||||||
if err := a.RunSchedule(schedule, now); err != nil {
|
|
||||||
log.WithFields(log.Fields{"app": a.name, "call": "NextSchedule", "error": err}).Errorf("")
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, src := range a.sources {
|
|
||||||
if err := src.SetManaged(true); err != nil {
|
|
||||||
log.WithFields(log.Fields{"app": a.name, "call": "src.SetManaged", "error": err}).Errorf("")
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := a.Transfer(); err != nil {
|
|
||||||
log.WithFields(log.Fields{"app": a.name, "call": "Transfer", "error": err}).Errorf("")
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := a.Cleanup(now); err != nil {
|
|
||||||
log.WithFields(log.Fields{"app": a.name, "call": "Cleanup", "error": err}).Errorf("")
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return schedule, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) NextSchedule(now time.Time) (string, error) {
|
func (a *App) NextSchedule(now time.Time) (string, error) {
|
||||||
@@ -478,7 +496,7 @@ func (a *App) Transfer() error {
|
|||||||
defer log.WithFields(log.Fields{"app": a.name}).Debugf("done")
|
defer log.WithFields(log.Fields{"app": a.name}).Debugf("done")
|
||||||
|
|
||||||
for _, src := range a.sources {
|
for _, src := range a.sources {
|
||||||
backedUp := false
|
dests := make([]Addr, 0)
|
||||||
for _, dest := range a.destinations {
|
for _, dest := range a.destinations {
|
||||||
dest2 := dest.Append("/" + src.Box() + "/" + src.Path())
|
dest2 := dest.Append("/" + src.Box() + "/" + src.Path())
|
||||||
if dest2.Online() {
|
if dest2.Online() {
|
||||||
@@ -486,18 +504,17 @@ func (a *App) Transfer() error {
|
|||||||
log.WithFields(log.Fields{"app": a.name, "call": "Mkdir", "attr": dest, "error": err}).Errorf("")
|
log.WithFields(log.Fields{"app": a.name, "call": "Mkdir", "attr": dest, "error": err}).Errorf("")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := TransferZfs(src, dest2); err != nil {
|
|
||||||
log.WithFields(log.Fields{"app": a.name, "call": "TransferZfs", "src": src, "dest": dest, "error": err}).Errorf("")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := dest2.SetManaged(true); err != nil {
|
if err := dest2.SetManaged(true); err != nil {
|
||||||
log.WithFields(log.Fields{"app": a.name, "call": "SetManaged", "src": src, "dest": dest, "error": err}).Errorf("")
|
log.WithFields(log.Fields{"app": a.name, "call": "SetManaged", "src": src, "dest": dest, "error": err}).Errorf("")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
backedUp = true
|
dests = append(dests, dest2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if backedUp {
|
if n, err := TransferZfs(src, dests); err != nil {
|
||||||
|
log.WithFields(log.Fields{"app": a.name, "call": "TransferZfs", "src": src, "dests": dests, "error": err}).Errorf("")
|
||||||
|
return err
|
||||||
|
} else if n > 0 {
|
||||||
if err := src.SetBackedUp(true); err != nil {
|
if err := src.SetBackedUp(true); err != nil {
|
||||||
log.WithFields(log.Fields{"app": a.name, "call": "SetBackedUp", "src": src, "error": err}).Errorf("")
|
log.WithFields(log.Fields{"app": a.name, "call": "SetBackedUp", "src": src, "error": err}).Errorf("")
|
||||||
return err
|
return err
|
||||||
@@ -508,3 +525,193 @@ func (a *App) Transfer() error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) AllBoxes() []*Box {
|
||||||
|
log.WithFields(log.Fields{"app": a.name}).Debugf("starting")
|
||||||
|
defer log.WithFields(log.Fields{"app": a.name}).Debugf("done")
|
||||||
|
|
||||||
|
bm := make(map[string]struct{})
|
||||||
|
|
||||||
|
for _, s := range a.sources {
|
||||||
|
bm[s.Box()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range a.destinations {
|
||||||
|
bm[d.Box()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, b := range a.before {
|
||||||
|
bm[b.Box()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, af := range a.after {
|
||||||
|
bm[af.Box()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
bx := make([]*Box, 0)
|
||||||
|
|
||||||
|
for n := range bm {
|
||||||
|
bx = append(bx, cfg.box[n])
|
||||||
|
}
|
||||||
|
|
||||||
|
return bx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) SourceBoxes() []*Box {
|
||||||
|
log.WithFields(log.Fields{"app": a.name}).Debugf("starting")
|
||||||
|
defer log.WithFields(log.Fields{"app": a.name}).Debugf("done")
|
||||||
|
|
||||||
|
bm := make(map[string]struct{})
|
||||||
|
|
||||||
|
for _, s := range a.sources {
|
||||||
|
bm[s.Box()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, b := range a.before {
|
||||||
|
bm[b.Box()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, af := range a.after {
|
||||||
|
bm[af.Box()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
bx := make([]*Box, 0)
|
||||||
|
|
||||||
|
for n := range bm {
|
||||||
|
bx = append(bx, cfg.box[n])
|
||||||
|
}
|
||||||
|
|
||||||
|
return bx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) RunStandaloneTime(now time.Time) error {
|
||||||
|
log.WithFields(log.Fields{"app": a.name}).Debugf("starting")
|
||||||
|
defer log.WithFields(log.Fields{"app": a.name}).Debugf("done")
|
||||||
|
|
||||||
|
if cfgRun {
|
||||||
|
err := errors.New("backup already running")
|
||||||
|
log.WithFields(log.Fields{"app": a.name, "error": err}).Errorf("")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
CfgLock()
|
||||||
|
defer CfgUnlock()
|
||||||
|
|
||||||
|
cfgRun = true
|
||||||
|
defer func() { cfgRun = false }()
|
||||||
|
|
||||||
|
boxes := a.AllBoxes()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Open boxes
|
||||||
|
for _, b := range boxes {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(box *Box) {
|
||||||
|
defer wg.Done()
|
||||||
|
if err := box.Open(); err != nil {
|
||||||
|
log.WithFields(log.Fields{"name": box.name, "call": "Open", "error": err}).Errorf("")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(b)
|
||||||
|
defer b.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
if sched, err := a.RunTime(now); err != nil {
|
||||||
|
log.WithFields(log.Fields{"call": "Run", "error": err}).Errorf("")
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
if sched == "" {
|
||||||
|
err := errors.New("no backup needed")
|
||||||
|
log.WithFields(log.Fields{"app": a.name, "error": err}).Errorf("")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) RunStandaloneSchedule(name string) error {
|
||||||
|
log.WithFields(log.Fields{"app": a.name, "name": name}).Debugf("starting")
|
||||||
|
defer log.WithFields(log.Fields{"app": a.name, "name": name}).Debugf("done")
|
||||||
|
|
||||||
|
if cfgRun {
|
||||||
|
err := errors.New("backup already running")
|
||||||
|
log.WithFields(log.Fields{"app": a.name, "error": err}).Errorf("")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
CfgLock()
|
||||||
|
defer CfgUnlock()
|
||||||
|
|
||||||
|
cfgRun = true
|
||||||
|
defer func() { cfgRun = false }()
|
||||||
|
|
||||||
|
boxes := a.AllBoxes()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Open boxes
|
||||||
|
for _, b := range boxes {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(box *Box) {
|
||||||
|
defer wg.Done()
|
||||||
|
if err := box.Open(); err != nil {
|
||||||
|
log.WithFields(log.Fields{"name": box.name, "call": "Open", "error": err}).Errorf("")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(b)
|
||||||
|
defer b.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
return a.RunSchedule(name, time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Snapshots() ([]string, error) {
|
||||||
|
log.WithFields(log.Fields{"app": a.name}).Debugf("starting")
|
||||||
|
defer log.WithFields(log.Fields{"app": a.name}).Debugf("done")
|
||||||
|
|
||||||
|
CfgLock()
|
||||||
|
defer CfgUnlock()
|
||||||
|
|
||||||
|
cfgRun = true
|
||||||
|
defer func() { cfgRun = false }()
|
||||||
|
|
||||||
|
boxes := a.SourceBoxes()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Open boxes
|
||||||
|
for _, b := range boxes {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(box *Box) {
|
||||||
|
defer wg.Done()
|
||||||
|
if err := box.Open(); err != nil {
|
||||||
|
log.WithFields(log.Fields{"name": box.name, "call": "Open", "error": err}).Errorf("")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(b)
|
||||||
|
defer b.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
names := make([]string, 0)
|
||||||
|
|
||||||
|
for _, src := range a.sources {
|
||||||
|
if snapshots, err := src.ValidSnapshots(); err != nil {
|
||||||
|
log.WithFields(log.Fields{"call": "ValidSnapshots", "attr": src, "error": err}).Errorf("")
|
||||||
|
return names, err
|
||||||
|
} else {
|
||||||
|
for _, snapshot := range snapshots {
|
||||||
|
names = append(names, snapshot.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return names, nil
|
||||||
|
}
|
||||||
|
|||||||
5002
assets/static/css/bootstrap-grid.css
vendored
5002
assets/static/css/bootstrap-grid.css
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
assets/static/css/bootstrap-grid.min.css
vendored
7
assets/static/css/bootstrap-grid.min.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
5001
assets/static/css/bootstrap-grid.rtl.css
vendored
5001
assets/static/css/bootstrap-grid.rtl.css
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
assets/static/css/bootstrap-grid.rtl.min.css
vendored
7
assets/static/css/bootstrap-grid.rtl.min.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
426
assets/static/css/bootstrap-reboot.css
vendored
426
assets/static/css/bootstrap-reboot.css
vendored
@@ -1,426 +0,0 @@
|
|||||||
/*!
|
|
||||||
* Bootstrap Reboot v5.0.2 (https://getbootstrap.com/)
|
|
||||||
* Copyright 2011-2021 The Bootstrap Authors
|
|
||||||
* Copyright 2011-2021 Twitter, Inc.
|
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
|
||||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
|
||||||
*/
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
|
||||||
:root {
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: #212529;
|
|
||||||
background-color: #fff;
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
margin: 1rem 0;
|
|
||||||
color: inherit;
|
|
||||||
background-color: currentColor;
|
|
||||||
border: 0;
|
|
||||||
opacity: 0.25;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr:not([size]) {
|
|
||||||
height: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h6, h5, h4, h3, h2, h1 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: calc(1.375rem + 1.5vw);
|
|
||||||
}
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
h1 {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: calc(1.325rem + 0.9vw);
|
|
||||||
}
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
h2 {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: calc(1.3rem + 0.6vw);
|
|
||||||
}
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
h3 {
|
|
||||||
font-size: 1.75rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
font-size: calc(1.275rem + 0.3vw);
|
|
||||||
}
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
h4 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h6 {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
abbr[title],
|
|
||||||
abbr[data-bs-original-title] {
|
|
||||||
-webkit-text-decoration: underline dotted;
|
|
||||||
text-decoration: underline dotted;
|
|
||||||
cursor: help;
|
|
||||||
-webkit-text-decoration-skip-ink: none;
|
|
||||||
text-decoration-skip-ink: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
address {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
font-style: normal;
|
|
||||||
line-height: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol,
|
|
||||||
ul {
|
|
||||||
padding-left: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol,
|
|
||||||
ul,
|
|
||||||
dl {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol ol,
|
|
||||||
ul ul,
|
|
||||||
ol ul,
|
|
||||||
ul ol {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
dt {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
|
||||||
margin: 0 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
b,
|
|
||||||
strong {
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
small {
|
|
||||||
font-size: 0.875em;
|
|
||||||
}
|
|
||||||
|
|
||||||
mark {
|
|
||||||
padding: 0.2em;
|
|
||||||
background-color: #fcf8e3;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub,
|
|
||||||
sup {
|
|
||||||
position: relative;
|
|
||||||
font-size: 0.75em;
|
|
||||||
line-height: 0;
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub {
|
|
||||||
bottom: -0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
sup {
|
|
||||||
top: -0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #0d6efd;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #0a58ca;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre,
|
|
||||||
code,
|
|
||||||
kbd,
|
|
||||||
samp {
|
|
||||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
||||||
font-size: 1em;
|
|
||||||
direction: ltr /* rtl:ignore */;
|
|
||||||
unicode-bidi: bidi-override;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
display: block;
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
overflow: auto;
|
|
||||||
font-size: 0.875em;
|
|
||||||
}
|
|
||||||
pre code {
|
|
||||||
font-size: inherit;
|
|
||||||
color: inherit;
|
|
||||||
word-break: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-size: 0.875em;
|
|
||||||
color: #d63384;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
a > code {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
kbd {
|
|
||||||
padding: 0.2rem 0.4rem;
|
|
||||||
font-size: 0.875em;
|
|
||||||
color: #fff;
|
|
||||||
background-color: #212529;
|
|
||||||
border-radius: 0.2rem;
|
|
||||||
}
|
|
||||||
kbd kbd {
|
|
||||||
padding: 0;
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
figure {
|
|
||||||
margin: 0 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
img,
|
|
||||||
svg {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
caption-side: bottom;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
caption {
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
color: #6c757d;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
text-align: inherit;
|
|
||||||
text-align: -webkit-match-parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
thead,
|
|
||||||
tbody,
|
|
||||||
tfoot,
|
|
||||||
tr,
|
|
||||||
td,
|
|
||||||
th {
|
|
||||||
border-color: inherit;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:focus:not(:focus-visible) {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
button,
|
|
||||||
select,
|
|
||||||
optgroup,
|
|
||||||
textarea {
|
|
||||||
margin: 0;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
line-height: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
button,
|
|
||||||
select {
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[role=button] {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
word-wrap: normal;
|
|
||||||
}
|
|
||||||
select:disabled {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
[list]::-webkit-calendar-picker-indicator {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
button,
|
|
||||||
[type=button],
|
|
||||||
[type=reset],
|
|
||||||
[type=submit] {
|
|
||||||
-webkit-appearance: button;
|
|
||||||
}
|
|
||||||
button:not(:disabled),
|
|
||||||
[type=button]:not(:disabled),
|
|
||||||
[type=reset]:not(:disabled),
|
|
||||||
[type=submit]:not(:disabled) {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-moz-focus-inner {
|
|
||||||
padding: 0;
|
|
||||||
border-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset {
|
|
||||||
min-width: 0;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
legend {
|
|
||||||
float: left;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
font-size: calc(1.275rem + 0.3vw);
|
|
||||||
line-height: inherit;
|
|
||||||
}
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
legend {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
legend + * {
|
|
||||||
clear: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-datetime-edit-fields-wrapper,
|
|
||||||
::-webkit-datetime-edit-text,
|
|
||||||
::-webkit-datetime-edit-minute,
|
|
||||||
::-webkit-datetime-edit-hour-field,
|
|
||||||
::-webkit-datetime-edit-day-field,
|
|
||||||
::-webkit-datetime-edit-month-field,
|
|
||||||
::-webkit-datetime-edit-year-field {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-inner-spin-button {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
[type=search] {
|
|
||||||
outline-offset: -2px;
|
|
||||||
-webkit-appearance: textfield;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* rtl:raw:
|
|
||||||
[type="tel"],
|
|
||||||
[type="url"],
|
|
||||||
[type="email"],
|
|
||||||
[type="number"] {
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
::-webkit-search-decoration {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-color-swatch-wrapper {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
::file-selector-button {
|
|
||||||
font: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-file-upload-button {
|
|
||||||
font: inherit;
|
|
||||||
-webkit-appearance: button;
|
|
||||||
}
|
|
||||||
|
|
||||||
output {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
iframe {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
summary {
|
|
||||||
display: list-item;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
progress {
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
[hidden] {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
|
||||||
File diff suppressed because one or more lines are too long
8
assets/static/css/bootstrap-reboot.min.css
vendored
8
assets/static/css/bootstrap-reboot.min.css
vendored
@@ -1,8 +0,0 @@
|
|||||||
/*!
|
|
||||||
* Bootstrap Reboot v5.0.2 (https://getbootstrap.com/)
|
|
||||||
* Copyright 2011-2021 The Bootstrap Authors
|
|
||||||
* Copyright 2011-2021 Twitter, Inc.
|
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
|
||||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
|
||||||
*/*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}
|
|
||||||
/*# sourceMappingURL=bootstrap-reboot.min.css.map */
|
|
||||||
File diff suppressed because one or more lines are too long
423
assets/static/css/bootstrap-reboot.rtl.css
vendored
423
assets/static/css/bootstrap-reboot.rtl.css
vendored
@@ -1,423 +0,0 @@
|
|||||||
/*!
|
|
||||||
* Bootstrap Reboot v5.0.2 (https://getbootstrap.com/)
|
|
||||||
* Copyright 2011-2021 The Bootstrap Authors
|
|
||||||
* Copyright 2011-2021 Twitter, Inc.
|
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
|
||||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
|
||||||
*/
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
|
||||||
:root {
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: #212529;
|
|
||||||
background-color: #fff;
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
margin: 1rem 0;
|
|
||||||
color: inherit;
|
|
||||||
background-color: currentColor;
|
|
||||||
border: 0;
|
|
||||||
opacity: 0.25;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr:not([size]) {
|
|
||||||
height: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h6, h5, h4, h3, h2, h1 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: calc(1.375rem + 1.5vw);
|
|
||||||
}
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
h1 {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: calc(1.325rem + 0.9vw);
|
|
||||||
}
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
h2 {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: calc(1.3rem + 0.6vw);
|
|
||||||
}
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
h3 {
|
|
||||||
font-size: 1.75rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
font-size: calc(1.275rem + 0.3vw);
|
|
||||||
}
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
h4 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h6 {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
abbr[title],
|
|
||||||
abbr[data-bs-original-title] {
|
|
||||||
-webkit-text-decoration: underline dotted;
|
|
||||||
text-decoration: underline dotted;
|
|
||||||
cursor: help;
|
|
||||||
-webkit-text-decoration-skip-ink: none;
|
|
||||||
text-decoration-skip-ink: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
address {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
font-style: normal;
|
|
||||||
line-height: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol,
|
|
||||||
ul {
|
|
||||||
padding-right: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol,
|
|
||||||
ul,
|
|
||||||
dl {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol ol,
|
|
||||||
ul ul,
|
|
||||||
ol ul,
|
|
||||||
ul ol {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
dt {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
|
||||||
margin: 0 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
b,
|
|
||||||
strong {
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
small {
|
|
||||||
font-size: 0.875em;
|
|
||||||
}
|
|
||||||
|
|
||||||
mark {
|
|
||||||
padding: 0.2em;
|
|
||||||
background-color: #fcf8e3;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub,
|
|
||||||
sup {
|
|
||||||
position: relative;
|
|
||||||
font-size: 0.75em;
|
|
||||||
line-height: 0;
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub {
|
|
||||||
bottom: -0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
sup {
|
|
||||||
top: -0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #0d6efd;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #0a58ca;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre,
|
|
||||||
code,
|
|
||||||
kbd,
|
|
||||||
samp {
|
|
||||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
||||||
font-size: 1em;
|
|
||||||
direction: ltr ;
|
|
||||||
unicode-bidi: bidi-override;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
display: block;
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
overflow: auto;
|
|
||||||
font-size: 0.875em;
|
|
||||||
}
|
|
||||||
pre code {
|
|
||||||
font-size: inherit;
|
|
||||||
color: inherit;
|
|
||||||
word-break: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-size: 0.875em;
|
|
||||||
color: #d63384;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
a > code {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
kbd {
|
|
||||||
padding: 0.2rem 0.4rem;
|
|
||||||
font-size: 0.875em;
|
|
||||||
color: #fff;
|
|
||||||
background-color: #212529;
|
|
||||||
border-radius: 0.2rem;
|
|
||||||
}
|
|
||||||
kbd kbd {
|
|
||||||
padding: 0;
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
figure {
|
|
||||||
margin: 0 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
img,
|
|
||||||
svg {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
caption-side: bottom;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
caption {
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
color: #6c757d;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
text-align: inherit;
|
|
||||||
text-align: -webkit-match-parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
thead,
|
|
||||||
tbody,
|
|
||||||
tfoot,
|
|
||||||
tr,
|
|
||||||
td,
|
|
||||||
th {
|
|
||||||
border-color: inherit;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:focus:not(:focus-visible) {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
button,
|
|
||||||
select,
|
|
||||||
optgroup,
|
|
||||||
textarea {
|
|
||||||
margin: 0;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
line-height: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
button,
|
|
||||||
select {
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[role=button] {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
word-wrap: normal;
|
|
||||||
}
|
|
||||||
select:disabled {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
[list]::-webkit-calendar-picker-indicator {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
button,
|
|
||||||
[type=button],
|
|
||||||
[type=reset],
|
|
||||||
[type=submit] {
|
|
||||||
-webkit-appearance: button;
|
|
||||||
}
|
|
||||||
button:not(:disabled),
|
|
||||||
[type=button]:not(:disabled),
|
|
||||||
[type=reset]:not(:disabled),
|
|
||||||
[type=submit]:not(:disabled) {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-moz-focus-inner {
|
|
||||||
padding: 0;
|
|
||||||
border-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset {
|
|
||||||
min-width: 0;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
legend {
|
|
||||||
float: right;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
font-size: calc(1.275rem + 0.3vw);
|
|
||||||
line-height: inherit;
|
|
||||||
}
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
legend {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
legend + * {
|
|
||||||
clear: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-datetime-edit-fields-wrapper,
|
|
||||||
::-webkit-datetime-edit-text,
|
|
||||||
::-webkit-datetime-edit-minute,
|
|
||||||
::-webkit-datetime-edit-hour-field,
|
|
||||||
::-webkit-datetime-edit-day-field,
|
|
||||||
::-webkit-datetime-edit-month-field,
|
|
||||||
::-webkit-datetime-edit-year-field {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-inner-spin-button {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
[type=search] {
|
|
||||||
outline-offset: -2px;
|
|
||||||
-webkit-appearance: textfield;
|
|
||||||
}
|
|
||||||
|
|
||||||
[type="tel"],
|
|
||||||
[type="url"],
|
|
||||||
[type="email"],
|
|
||||||
[type="number"] {
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
::-webkit-search-decoration {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-color-swatch-wrapper {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
::file-selector-button {
|
|
||||||
font: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-file-upload-button {
|
|
||||||
font: inherit;
|
|
||||||
-webkit-appearance: button;
|
|
||||||
}
|
|
||||||
|
|
||||||
output {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
iframe {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
summary {
|
|
||||||
display: list-item;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
progress {
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
[hidden] {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,8 +0,0 @@
|
|||||||
/*!
|
|
||||||
* Bootstrap Reboot v5.0.2 (https://getbootstrap.com/)
|
|
||||||
* Copyright 2011-2021 The Bootstrap Authors
|
|
||||||
* Copyright 2011-2021 Twitter, Inc.
|
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
|
||||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
|
||||||
*/*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-right:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-right:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:right}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:right;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:right}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}[type=email],[type=number],[type=tel],[type=url]{direction:ltr}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}
|
|
||||||
/*# sourceMappingURL=bootstrap-reboot.rtl.min.css.map */
|
|
||||||
File diff suppressed because one or more lines are too long
4752
assets/static/css/bootstrap-utilities.css
vendored
4752
assets/static/css/bootstrap-utilities.css
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4743
assets/static/css/bootstrap-utilities.rtl.css
vendored
4743
assets/static/css/bootstrap-utilities.rtl.css
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
10837
assets/static/css/bootstrap.css
vendored
10837
assets/static/css/bootstrap.css
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
assets/static/css/bootstrap.min.css
vendored
7
assets/static/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
10813
assets/static/css/bootstrap.rtl.css
vendored
10813
assets/static/css/bootstrap.rtl.css
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
assets/static/css/bootstrap.rtl.min.css
vendored
7
assets/static/css/bootstrap.rtl.min.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6748
assets/static/js/bootstrap.bundle.js
vendored
6748
assets/static/js/bootstrap.bundle.js
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
assets/static/js/bootstrap.bundle.min.js
vendored
7
assets/static/js/bootstrap.bundle.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4967
assets/static/js/bootstrap.esm.js
vendored
4967
assets/static/js/bootstrap.esm.js
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
assets/static/js/bootstrap.esm.min.js
vendored
7
assets/static/js/bootstrap.esm.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
5016
assets/static/js/bootstrap.js
vendored
5016
assets/static/js/bootstrap.js
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
assets/static/js/bootstrap.min.js
vendored
7
assets/static/js/bootstrap.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,29 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
||||||
<meta name="description" content="">
|
|
||||||
<title>Login :: zBackup</title>
|
|
||||||
|
|
||||||
<!-- Bootstrap core CSS -->
|
|
||||||
<link href="/assets/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
<body class="text-center">
|
|
||||||
<div class="container">
|
|
||||||
<form class="form-signin" method="POST" action="/u/submit">
|
|
||||||
<h1 class="h3 mb-3 font-weight-normal">Sign in</h1>
|
|
||||||
{{if (ne .Error "")}}<div class="alert alert-danger">{{.Error}}</div>{{end}}
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="username" class="form-control" id="username" placeholder="Username" name="username" required autofocus>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="password" class="form-control" id="pwd" placeholder="Password" name="password" required>
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-lg btn-primary btn-block" type="submit" >Sign in</button>
|
|
||||||
<p class="mt-5 mb-3 text-muted">© 2023</p>
|
|
||||||
</form>
|
|
||||||
<a href="/u/recover">Lost password</a>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -49,6 +49,7 @@ func main() {
|
|||||||
} else if c, err := LoadConfigFile("backup.json"); err == nil {
|
} else if c, err := LoadConfigFile("backup.json"); err == nil {
|
||||||
cfg = c
|
cfg = c
|
||||||
} else {
|
} else {
|
||||||
|
log.Debugf("loading default config")
|
||||||
cfg, _ = LoadConfigByte(sampleCfg)
|
cfg, _ = LoadConfigByte(sampleCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,15 +57,9 @@ func main() {
|
|||||||
if cfg.Admin == nil {
|
if cfg.Admin == nil {
|
||||||
cfg.Admin = NewAdmin()
|
cfg.Admin = NewAdmin()
|
||||||
}
|
}
|
||||||
if cfg.Admin.Secrets == nil {
|
|
||||||
cfg.Admin.Secrets = NewSecrets()
|
|
||||||
}
|
|
||||||
if len(cfg.Admin.Users) == 0 {
|
|
||||||
cfg.Admin.NewAdminUser()
|
|
||||||
}
|
|
||||||
cfg.Admin.Run()
|
cfg.Admin.Run()
|
||||||
} else {
|
} else {
|
||||||
cfg.Run()
|
cfg.Run(true)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
124
box.go
124
box.go
@@ -7,31 +7,31 @@ import (
|
|||||||
|
|
||||||
"github.com/silenceper/pool"
|
"github.com/silenceper/pool"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Box struct {
|
type Box struct {
|
||||||
name string
|
name string
|
||||||
addr string
|
addr string
|
||||||
user string
|
user string
|
||||||
key string
|
key string
|
||||||
zfs *BoxZfs
|
zfs *BoxZfs
|
||||||
sshPool pool.Pool
|
sshPool pool.Pool
|
||||||
created bool
|
created bool
|
||||||
online bool
|
online bool
|
||||||
allowDirectConnect bool
|
mx sync.Mutex
|
||||||
mx sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type BoxSshPool struct {
|
func (b *Box) Lock() {
|
||||||
signer ssh.Signer
|
log.WithFields(log.Fields{"name": b.name}).Debugf("starting")
|
||||||
config *ssh.ClientConfig
|
b.mx.Lock()
|
||||||
client *ssh.Client
|
|
||||||
logged bool
|
|
||||||
mx sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) NewBox(name, addr, user, key string, direct bool) (b *Box, err error) {
|
func (b *Box) Unlock() {
|
||||||
|
log.WithFields(log.Fields{"name": b.name}).Debugf("starting")
|
||||||
|
b.mx.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) NewBox(name, addr, user, key string) (b *Box, err error) {
|
||||||
log.WithFields(log.Fields{"name": name, "addr": addr, "user": user, "key": key}).Debugf("starting")
|
log.WithFields(log.Fields{"name": name, "addr": addr, "user": user, "key": key}).Debugf("starting")
|
||||||
defer log.WithFields(log.Fields{"name": name, "addr": addr, "user": user, "key": key}).Debugf("done")
|
defer log.WithFields(log.Fields{"name": name, "addr": addr, "user": user, "key": key}).Debugf("done")
|
||||||
|
|
||||||
@@ -56,10 +56,9 @@ func (c *Config) NewBox(name, addr, user, key string, direct bool) (b *Box, err
|
|||||||
zfs: &BoxZfs{
|
zfs: &BoxZfs{
|
||||||
online: false,
|
online: false,
|
||||||
},
|
},
|
||||||
sshPool: p,
|
sshPool: p,
|
||||||
online: false,
|
online: false,
|
||||||
created: true,
|
created: true,
|
||||||
allowDirectConnect: true, //FIXME use direct
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b.zfs.box = b
|
b.zfs.box = b
|
||||||
@@ -71,8 +70,8 @@ func (b *Box) Open() error {
|
|||||||
log.WithFields(log.Fields{"name": b.name}).Debugf("starting")
|
log.WithFields(log.Fields{"name": b.name}).Debugf("starting")
|
||||||
defer log.WithFields(log.Fields{"name": b.name}).Debugf("done")
|
defer log.WithFields(log.Fields{"name": b.name}).Debugf("done")
|
||||||
|
|
||||||
b.mx.Lock()
|
b.Lock()
|
||||||
defer b.mx.Unlock()
|
defer b.Unlock()
|
||||||
|
|
||||||
if b.online {
|
if b.online {
|
||||||
return nil
|
return nil
|
||||||
@@ -100,8 +99,8 @@ func (b *Box) Close() error {
|
|||||||
log.WithFields(log.Fields{"name": b.name}).Debugf("starting")
|
log.WithFields(log.Fields{"name": b.name}).Debugf("starting")
|
||||||
defer log.WithFields(log.Fields{"name": b.name}).Debugf("done")
|
defer log.WithFields(log.Fields{"name": b.name}).Debugf("done")
|
||||||
|
|
||||||
b.mx.Lock()
|
b.Lock()
|
||||||
defer b.mx.Unlock()
|
defer b.Unlock()
|
||||||
|
|
||||||
if !b.online {
|
if !b.online {
|
||||||
return nil
|
return nil
|
||||||
@@ -139,22 +138,34 @@ func (b *Box) Exec(cmd string) (r string, err error) {
|
|||||||
return s.Exec(cmd)
|
return s.Exec(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TransferZfs(from, to Addr) error {
|
func TransferZfs(from Addr, to []Addr) (int, error) {
|
||||||
|
log.WithFields(log.Fields{"from": from, "to": to}).Debugf("starting")
|
||||||
|
defer log.WithFields(log.Fields{"from": from, "to": to}).Debugf("done")
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
|
||||||
|
for _, dest := range to {
|
||||||
|
if err := TransferDirectZfs(from, dest); err != nil {
|
||||||
|
log.WithFields(log.Fields{"from": from, "to": to, "call": "TransferDirectZfs", "attr": dest, "error": err}).Errorf("")
|
||||||
|
return count, err
|
||||||
|
} else {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TransferDirectZfs(from, to Addr) error {
|
||||||
log.WithFields(log.Fields{"from": from, "to": to}).Debugf("starting")
|
log.WithFields(log.Fields{"from": from, "to": to}).Debugf("starting")
|
||||||
defer log.WithFields(log.Fields{"from": from, "to": to}).Debugf("done")
|
defer log.WithFields(log.Fields{"from": from, "to": to}).Debugf("done")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
fromSnapshots, toSnapshots []*ZfsSnapshot
|
fromSnapshots, toSnapshots []*ZfsSnapshot
|
||||||
directTransfer bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if cfg.box[from.Box()].allowDirectConnect && cfg.box[to.Box()].allowDirectConnect {
|
|
||||||
directTransfer = true
|
|
||||||
} else {
|
|
||||||
directTransfer = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if fromSnapshots, err = from.ValidSnapshots(); err != nil {
|
if fromSnapshots, err = from.ValidSnapshots(); err != nil {
|
||||||
log.WithFields(log.Fields{"from": from, "to": to, "call": "ValidSnapshots", "attr": from, "error": err}).Errorf("")
|
log.WithFields(log.Fields{"from": from, "to": to, "call": "ValidSnapshots", "attr": from, "error": err}).Errorf("")
|
||||||
return err
|
return err
|
||||||
@@ -171,29 +182,30 @@ func TransferZfs(from, to Addr) error {
|
|||||||
|
|
||||||
if len(toSnapshots) == 0 {
|
if len(toSnapshots) == 0 {
|
||||||
log.WithFields(log.Fields{"from": from, "to": to}).Debugf("initiating destination")
|
log.WithFields(log.Fields{"from": from, "to": to}).Debugf("initiating destination")
|
||||||
if directTransfer {
|
if _, err := to.BoxExec("ssh " + from.Box() + " zfs send " + fromSnapshots[0].String() + " | zfs recv -F " + to.Path()); err != nil {
|
||||||
if _, err := to.BoxExec("ssh " + from.Box() + " zfs send " + fromSnapshots[0].String() + " | zfs recv -F " + to.Path()); err != nil {
|
log.WithFields(log.Fields{"from": from, "to": to, "call": "BoxExec", "error": err}).Errorf("")
|
||||||
log.WithFields(log.Fields{"from": from, "to": to, "call": "BoxExec", "error": err}).Errorf("")
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
newToSnapshot := &ZfsSnapshot{name: fromSnapshots[0].name, fs: cfg.box[to.Box()].zfs.filesystems[to.Path()]}
|
|
||||||
toSnapshots = append(toSnapshots, newToSnapshot)
|
|
||||||
cfg.box[to.Box()].zfs.filesystems[to.Path()].AddSnapshot(newToSnapshot)
|
|
||||||
} else {
|
|
||||||
//handle indirect transfer
|
|
||||||
}
|
}
|
||||||
|
newToSnapshot := &ZfsSnapshot{name: fromSnapshots[0].name, fs: cfg.box[to.Box()].zfs.filesystems[to.Path()]}
|
||||||
|
toSnapshots = append(toSnapshots, newToSnapshot)
|
||||||
|
cfg.box[to.Box()].zfs.filesystems[to.Path()].AddSnapshot(newToSnapshot)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fromFromSnapshotId := -1
|
fromFromSnapshotId := len(fromSnapshots) - 1
|
||||||
lastToSnapshot := toSnapshots[len(toSnapshots)-1]
|
fromToSnapshotId := -1
|
||||||
log.WithFields(log.Fields{"from": from, "to": to}).Debugf("searching last snapshot %s", lastToSnapshot.String())
|
for fromFromSnapshotId >= 0 {
|
||||||
for id, v := range fromSnapshots {
|
fromToSnapshotId = len(toSnapshots) - 1
|
||||||
if v.name == lastToSnapshot.name {
|
for fromToSnapshotId >= 0 {
|
||||||
fromFromSnapshotId = id
|
if fromSnapshots[fromFromSnapshotId].name == toSnapshots[fromToSnapshotId].name {
|
||||||
log.WithFields(log.Fields{"from": from, "to": to}).Debugf("found %s", v.String())
|
break
|
||||||
|
}
|
||||||
|
fromToSnapshotId = fromToSnapshotId - 1
|
||||||
|
}
|
||||||
|
if fromToSnapshotId >= 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
fromFromSnapshotId = fromFromSnapshotId - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if fromFromSnapshotId == -1 {
|
if fromFromSnapshotId == -1 {
|
||||||
@@ -204,13 +216,9 @@ func TransferZfs(from, to Addr) error {
|
|||||||
|
|
||||||
if fromFromSnapshotId < len(fromSnapshots)-1 {
|
if fromFromSnapshotId < len(fromSnapshots)-1 {
|
||||||
log.WithFields(log.Fields{"from": from, "to": to}).Debugf("transfering from %s to %s", fromSnapshots[fromFromSnapshotId].name, fromSnapshots[len(fromSnapshots)-1].name)
|
log.WithFields(log.Fields{"from": from, "to": to}).Debugf("transfering from %s to %s", fromSnapshots[fromFromSnapshotId].name, fromSnapshots[len(fromSnapshots)-1].name)
|
||||||
if directTransfer {
|
if _, err := to.BoxExec("ssh " + from.Box() + " zfs send -I " + fromSnapshots[fromFromSnapshotId].String() + " " + fromSnapshots[len(fromSnapshots)-1].String() + " | zfs recv -F " + to.Path()); err != nil {
|
||||||
if _, err := to.BoxExec("ssh " + from.Box() + " zfs send -I " + fromSnapshots[fromFromSnapshotId].String() + " " + fromSnapshots[len(fromSnapshots)-1].String() + " | zfs recv -F " + to.Path()); err != nil {
|
log.WithFields(log.Fields{"from": from, "to": to, "call": "BoxExec", "error": err}).Errorf("")
|
||||||
log.WithFields(log.Fields{"from": from, "to": to, "call": "BoxExec", "error": err}).Errorf("")
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// handle indirect transfer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range fromSnapshots[fromFromSnapshotId+1:] {
|
for _, v := range fromSnapshots[fromFromSnapshotId+1:] {
|
||||||
|
|||||||
199
config.go
199
config.go
@@ -13,19 +13,19 @@ import (
|
|||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/tailscale/hujson"
|
"github.com/tailscale/hujson"
|
||||||
|
"github.com/tidwall/pretty"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ScheduleDuration map[string]string `json:"schedule"`
|
ScheduleDuration map[string]string `json:"schedule,omitempty"`
|
||||||
Box map[string]BoxConfig `json:"box"`
|
Box map[string]*BoxConfig `json:"box,omitempty"`
|
||||||
Email EmailConfig `json:"email"`
|
Email *EmailConfig `json:"email,omitempty"`
|
||||||
Apps []AppConfig `json:"apps"`
|
Apps []*AppConfig `json:"apps,omitempty"`
|
||||||
Timezone string `json:"timezone"`
|
Timezone string `json:"timezone,omitempty"`
|
||||||
Admin *AdminConfig `json:"admin"`
|
Admin *AdminConfig `json:"admin,omitempty"`
|
||||||
Debug bool `json:"debug"`
|
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:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -37,10 +37,9 @@ var (
|
|||||||
var sampleCfg []byte
|
var sampleCfg []byte
|
||||||
|
|
||||||
type BoxConfig struct {
|
type BoxConfig struct {
|
||||||
Addr string `json:"addr"`
|
Addr string `json:"addr"`
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
AllowDirectConnect bool `json:"allow_direct_connect"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppConfig struct {
|
type AppConfig struct {
|
||||||
@@ -48,9 +47,27 @@ type AppConfig struct {
|
|||||||
Schedule []string `json:"schedule"`
|
Schedule []string `json:"schedule"`
|
||||||
Sources []string `json:"src"`
|
Sources []string `json:"src"`
|
||||||
Destinations []string `json:"dest"`
|
Destinations []string `json:"dest"`
|
||||||
Before map[string]string `json:"before"`
|
Before map[string]string `json:"before,omitempty"`
|
||||||
After map[string]string `json:"after"`
|
After map[string]string `json:"after,omitempty"`
|
||||||
Active bool `json:"active"`
|
Active bool `json:"active,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmailConfig struct {
|
||||||
|
Active bool `json:"active"`
|
||||||
|
SmtpHost string `json:"smtp,omitempty"`
|
||||||
|
FromEmail string `json:"email_from,omitempty"`
|
||||||
|
ToEmail []string `json:"email_to,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func CfgLock() {
|
||||||
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
|
cfgMx.Lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func CfgUnlock() {
|
||||||
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
|
cfgMx.Unlock()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load config from file
|
// Load config from file
|
||||||
@@ -96,23 +113,25 @@ func LoadConfigByte(conf []byte) (*Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Email.Active {
|
if c.Email != nil {
|
||||||
if len(c.Email.SmtpHost) == 0 {
|
if c.Email.Active {
|
||||||
err := fmt.Errorf("no smtp")
|
if len(c.Email.SmtpHost) == 0 {
|
||||||
log.WithFields(log.Fields{"error": err}).Errorf("")
|
err := errors.New("no smtp")
|
||||||
return nil, err
|
log.WithFields(log.Fields{"error": err}).Errorf("")
|
||||||
}
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if len(c.Email.FromEmail) == 0 {
|
if len(c.Email.FromEmail) == 0 {
|
||||||
err := fmt.Errorf("no email from")
|
err := errors.New("no email from")
|
||||||
log.WithFields(log.Fields{"error": err}).Errorf("")
|
log.WithFields(log.Fields{"error": err}).Errorf("")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.Email.ToEmail) == 0 {
|
if len(c.Email.ToEmail) == 0 {
|
||||||
err := fmt.Errorf("no email to")
|
err := errors.New("no email to")
|
||||||
log.WithFields(log.Fields{"error": err}).Errorf("")
|
log.WithFields(log.Fields{"error": err}).Errorf("")
|
||||||
return nil, err
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +155,7 @@ func LoadConfigByte(conf []byte) (*Config, error) {
|
|||||||
|
|
||||||
c.box = make(map[string]*Box)
|
c.box = make(map[string]*Box)
|
||||||
for k, v := range c.Box {
|
for k, v := range c.Box {
|
||||||
if b, err := c.NewBox(k, v.Addr, v.User, v.Key, v.AllowDirectConnect); err != nil {
|
if b, err := c.NewBox(k, v.Addr, v.User, v.Key); err != nil {
|
||||||
log.WithFields(log.Fields{"call": "NewBox", "attr": k, "error": err}).Errorf("")
|
log.WithFields(log.Fields{"call": "NewBox", "attr": k, "error": err}).Errorf("")
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
@@ -151,28 +170,30 @@ func LoadConfigByte(conf []byte) (*Config, error) {
|
|||||||
|
|
||||||
c.apps = make(map[string]*App)
|
c.apps = make(map[string]*App)
|
||||||
for _, v := range c.Apps {
|
for _, v := range c.Apps {
|
||||||
if a, err := c.NewApp(v.Name, v.Sources, v.Destinations, v.Schedule, v.Before, v.After); err != nil {
|
if v.Active {
|
||||||
log.WithFields(log.Fields{"call": "NewApp", "attr": v.Name, "error": err}).Errorf("")
|
if a, err := c.NewApp(v.Name, v.Sources, v.Destinations, v.Schedule, v.Before, v.After); err != nil {
|
||||||
return nil, err
|
log.WithFields(log.Fields{"call": "NewApp", "attr": v.Name, "error": err}).Errorf("")
|
||||||
} else {
|
|
||||||
if _, ok := c.apps[v.Name]; ok {
|
|
||||||
err := errors.New("app already exists")
|
|
||||||
log.WithFields(log.Fields{"app": v.Name, "error": err}).Errorf("")
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
} else {
|
||||||
c.apps[v.Name] = a
|
if _, ok := c.apps[v.Name]; ok {
|
||||||
for k := range a.schedule {
|
err := errors.New("app already exists")
|
||||||
if dur, ok := c.ScheduleDuration[k]; ok {
|
log.WithFields(log.Fields{"app": v.Name, "error": err}).Errorf("")
|
||||||
re := regexp.MustCompile(`^forever|([0-9]+(h|d|m|y))+$`)
|
return nil, err
|
||||||
if !re.MatchString(dur) {
|
}
|
||||||
err := errors.New("incorrect schedule duration")
|
c.apps[v.Name] = a
|
||||||
|
for k := range a.schedule {
|
||||||
|
if dur, ok := c.ScheduleDuration[k]; ok {
|
||||||
|
re := regexp.MustCompile(`^forever|([0-9]+(h|d|m|y))+$`)
|
||||||
|
if !re.MatchString(dur) {
|
||||||
|
err := errors.New("incorrect schedule duration")
|
||||||
|
log.WithFields(log.Fields{"app": v.Name, "schedule": k, "error": err}).Errorf("")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := errors.New("undefined schedule duration")
|
||||||
log.WithFields(log.Fields{"app": v.Name, "schedule": k, "error": err}).Errorf("")
|
log.WithFields(log.Fields{"app": v.Name, "schedule": k, "error": err}).Errorf("")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
err := errors.New("undefined schedule duration")
|
|
||||||
log.WithFields(log.Fields{"app": v.Name, "schedule": k, "error": err}).Errorf("")
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,8 +202,72 @@ func LoadConfigByte(conf []byte) (*Config, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pretty config
|
||||||
|
func (c *Config) Pretty(lock bool) ([]byte, error) {
|
||||||
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
|
defer log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
|
if lock {
|
||||||
|
CfgLock()
|
||||||
|
defer CfgUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"error": err, "call": "json.Marshal"}).Errorf("")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pretty.PrettyOptions(b, &pretty.Options{Indent: " "}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pretty App Config
|
||||||
|
func (a *AppConfig) Pretty(lock bool) ([]byte, error) {
|
||||||
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
|
defer log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
|
if lock {
|
||||||
|
CfgLock()
|
||||||
|
defer CfgUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"error": err, "call": "json.Marshal"}).Errorf("")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pretty.PrettyOptions(b, &pretty.Options{Indent: " "}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save config
|
||||||
|
func (c *Config) Save(lock bool) error {
|
||||||
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
|
defer log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
|
r, err := cfg.Pretty(lock)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"error": err, "call": "cfg.Pretty"}).Errorf("")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(*cfgFile)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"error": err, "call": "os.Open"}).Errorf("")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := f.Write(r); err != nil {
|
||||||
|
log.WithFields(log.Fields{"error": err, "call": "File.Write"}).Errorf("")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Run config
|
// Run config
|
||||||
func (c *Config) Run() {
|
func (c *Config) Run(lock bool) {
|
||||||
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")
|
||||||
|
|
||||||
@@ -190,8 +275,10 @@ func (c *Config) Run() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfgMx.Lock()
|
if lock {
|
||||||
defer cfgMx.Unlock()
|
CfgLock()
|
||||||
|
defer CfgUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
cfgRun = true
|
cfgRun = true
|
||||||
defer func() { cfgRun = false }()
|
defer func() { cfgRun = false }()
|
||||||
@@ -219,7 +306,7 @@ func (c *Config) Run() {
|
|||||||
for _, a := range cfg.apps {
|
for _, a := range cfg.apps {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(app *App) {
|
go func(app *App) {
|
||||||
if sched, err := app.Run(e.startTime); err != nil {
|
if sched, err := app.RunTime(e.startTime); err != nil {
|
||||||
e.AddItem(fmt.Sprintf(" - App : Error running %s (%s)", app.name, err))
|
e.AddItem(fmt.Sprintf(" - App : Error running %s (%s)", app.name, err))
|
||||||
} else if *debug {
|
} else if *debug {
|
||||||
if sched != "" {
|
if sched != "" {
|
||||||
@@ -282,6 +369,4 @@ func (c *Config) Run() {
|
|||||||
log.WithFields(log.Fields{"call": "email.Send", "error": err}).Errorf("")
|
log.WithFields(log.Fields{"call": "email.Send", "error": err}).Errorf("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|||||||
14
email.go
14
email.go
@@ -6,7 +6,6 @@ import (
|
|||||||
"net/smtp"
|
"net/smtp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -15,14 +14,6 @@ import (
|
|||||||
type Email struct {
|
type Email struct {
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
items []string
|
items []string
|
||||||
mx sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
type EmailConfig struct {
|
|
||||||
Active bool `json:"active"`
|
|
||||||
SmtpHost string `json:"smtp"`
|
|
||||||
FromEmail string `json:"email_from"`
|
|
||||||
ToEmail []string `json:"email_to"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEmail(now time.Time) *Email {
|
func NewEmail(now time.Time) *Email {
|
||||||
@@ -35,8 +26,9 @@ func NewEmail(now time.Time) *Email {
|
|||||||
func (e *Email) AddItem(item string) {
|
func (e *Email) AddItem(item string) {
|
||||||
log.WithFields(log.Fields{"item": item}).Debugf("starting")
|
log.WithFields(log.Fields{"item": item}).Debugf("starting")
|
||||||
defer log.WithFields(log.Fields{"item": item}).Debugf("done")
|
defer log.WithFields(log.Fields{"item": item}).Debugf("done")
|
||||||
|
if cfg.Email.Active {
|
||||||
e.items = append(e.items, item)
|
e.items = append(e.items, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Email) Send(addr, from string, to []string) error {
|
func (e *Email) Send(addr, from string, to []string) error {
|
||||||
|
|||||||
43
go.mod
43
go.mod
@@ -1,13 +1,40 @@
|
|||||||
module git.siteop.biz/shoopea/backup
|
module git.siteop.biz/shoopea/backup
|
||||||
|
|
||||||
go 1.16
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/sethvargo/go-password v0.2.0 // indirect
|
github.com/sethvargo/go-password v0.2.0
|
||||||
github.com/silenceper/pool v1.0.0 // indirect
|
github.com/silenceper/pool v1.0.0
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
|
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a
|
||||||
golang.org/x/crypto v0.9.0
|
github.com/tidwall/pretty v1.2.1
|
||||||
|
golang.org/x/crypto v0.29.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/sonic v1.9.1 // indirect
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // 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
|
||||||
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
|
golang.org/x/net v0.21.0 // indirect
|
||||||
|
golang.org/x/sys v0.27.0 // indirect
|
||||||
|
golang.org/x/text v0.20.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.30.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
73
go.sum
73
go.sum
@@ -5,6 +5,7 @@ github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F
|
|||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
@@ -12,6 +13,7 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
|
|||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
@@ -23,6 +25,7 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
|||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
@@ -30,7 +33,6 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
|
|||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
@@ -43,6 +45,7 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
|||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
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 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
@@ -50,7 +53,6 @@ github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc
|
|||||||
github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE=
|
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/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
@@ -65,77 +67,38 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=
|
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=
|
||||||
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
|
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
|
||||||
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||||
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0=
|
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
|
||||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
|
||||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
|
||||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM=
|
|
||||||
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
|
||||||
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|
||||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
67
http.go
67
http.go
@@ -1,67 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func HttpAuth() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
// Search signed-in userID
|
|
||||||
userID := 0
|
|
||||||
if userID == 0 {
|
|
||||||
// Return 404 and abort handlers chain.
|
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
|
||||||
c.AbortWithStatus(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func HttpNoAuth() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func HttpAnySignIn(c *gin.Context) {
|
|
||||||
if GetWebSessionUserID(c) > 0 {
|
|
||||||
c.Redirect(http.StatusTemporaryRedirect, "/p/home")
|
|
||||||
} else {
|
|
||||||
SetCSRFToken(c)
|
|
||||||
warning, _ := c.Cookie("warning")
|
|
||||||
c.SetCookie("warning", "", -1, "/", cfg.Admin.Addr, false, true)
|
|
||||||
c.HTML(http.StatusOK, "page-signin.html", gin.H{
|
|
||||||
"Error": warning,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func HttpAnyIndex(c *gin.Context) {
|
|
||||||
if GetWebSessionUserID(c) > 0 {
|
|
||||||
c.Redirect(http.StatusTemporaryRedirect, "/p/home")
|
|
||||||
} else {
|
|
||||||
c.Redirect(http.StatusTemporaryRedirect, "/u/signin")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func HttpAnyHome(c *gin.Context) {
|
|
||||||
if GetWebSessionUserID(c) == 0 {
|
|
||||||
c.Redirect(http.StatusTemporaryRedirect, "/u/signin")
|
|
||||||
} else {
|
|
||||||
SetCSRFToken(c)
|
|
||||||
c.HTML(http.StatusOK, "page-home.html", gin.H{})
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetWebSessionUserID(c *gin.Context) int64 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetCSRFToken(c *gin.Context) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
6
ssh.go
6
ssh.go
@@ -137,8 +137,8 @@ func (s *Ssh) ExecPipe(cmd string) error {
|
|||||||
}
|
}
|
||||||
s.session = session
|
s.session = session
|
||||||
|
|
||||||
if s.session.Setenv("TZ", cfg.Timezone); err != nil {
|
if err = s.session.Setenv("TZ", cfg.Timezone); err != nil {
|
||||||
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "session.Setenv", "error": err}).Errorf("")
|
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "session.Setenv", "attr": cfg.Timezone, "error": err}).Errorf("")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ func (s *Ssh) ExecPipe(cmd string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err = s.session.Start(cmd); err != nil {
|
if err = s.session.Start(cmd); err != nil {
|
||||||
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "session.Start", "error": err}).Errorf("")
|
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "session.Start", "attr": cmd, "error": err}).Errorf("")
|
||||||
s.session.Close()
|
s.session.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
76
user.go
76
user.go
@@ -1,76 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
10
version.go
10
version.go
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by version.sh (@generated) DO NOT EDIT.
|
// Code generated by version.sh (@generated) DO NOT EDIT.
|
||||||
package main
|
package main
|
||||||
var githash = "1a1713e"
|
var githash = "54d8576"
|
||||||
var branch = "v2"
|
var branch = "master"
|
||||||
var buildstamp = "2023-08-21_12:35:47"
|
var buildstamp = "2025-12-28_21:17:45"
|
||||||
var commits = "83"
|
var commits = "129"
|
||||||
var version = "1a1713e-b83 - 2023-08-21_12:35:47"
|
var version = "54d8576-b129 - 2025-12-28_21:17:45"
|
||||||
|
|||||||
51
zfs.go
51
zfs.go
@@ -36,12 +36,32 @@ type ZfsSnapshot struct {
|
|||||||
fs *ZfsFs
|
fs *ZfsFs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (z *BoxZfs) Lock() {
|
||||||
|
log.WithFields(log.Fields{"name": z.box.name}).Debugf("starting")
|
||||||
|
z.mx.Lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *BoxZfs) Unlock() {
|
||||||
|
log.WithFields(log.Fields{"name": z.box.name}).Debugf("starting")
|
||||||
|
z.mx.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *ZfsFs) Lock() {
|
||||||
|
log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path}).Debugf("starting")
|
||||||
|
fs.mx.Lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *ZfsFs) Unlock() {
|
||||||
|
log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path}).Debugf("starting")
|
||||||
|
fs.mx.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
func (z *BoxZfs) Open() error {
|
func (z *BoxZfs) Open() error {
|
||||||
log.WithFields(log.Fields{"name": z.box.name}).Debugf("starting")
|
log.WithFields(log.Fields{"name": z.box.name}).Debugf("starting")
|
||||||
defer log.WithFields(log.Fields{"name": z.box.name}).Debugf("done")
|
defer log.WithFields(log.Fields{"name": z.box.name}).Debugf("done")
|
||||||
|
|
||||||
z.mx.Lock()
|
z.Lock()
|
||||||
defer z.mx.Unlock()
|
defer z.Unlock()
|
||||||
|
|
||||||
if z.online {
|
if z.online {
|
||||||
return nil
|
return nil
|
||||||
@@ -146,12 +166,12 @@ func (z *BoxZfs) Close() error {
|
|||||||
log.WithFields(log.Fields{"name": z.box.name}).Debugf("starting")
|
log.WithFields(log.Fields{"name": z.box.name}).Debugf("starting")
|
||||||
defer log.WithFields(log.Fields{"name": z.box.name}).Debugf("done")
|
defer log.WithFields(log.Fields{"name": z.box.name}).Debugf("done")
|
||||||
|
|
||||||
z.mx.Lock()
|
z.Lock()
|
||||||
defer z.mx.Unlock()
|
defer z.Unlock()
|
||||||
|
|
||||||
for _, fs := range z.filesystems {
|
for _, fs := range z.filesystems {
|
||||||
fs.mx.Lock()
|
fs.Lock()
|
||||||
defer fs.mx.Unlock()
|
defer fs.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
z.online = false
|
z.online = false
|
||||||
@@ -169,8 +189,8 @@ func (z *BoxZfs) Mkdir(path string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
z.mx.Lock()
|
z.Lock()
|
||||||
defer z.mx.Unlock()
|
defer z.Unlock()
|
||||||
|
|
||||||
b := z.box
|
b := z.box
|
||||||
if !b.online {
|
if !b.online {
|
||||||
@@ -230,8 +250,8 @@ func (fs *ZfsFs) TakeSnapshot(name string) (*ZfsSnapshot, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.mx.Lock()
|
fs.Lock()
|
||||||
defer fs.mx.Unlock()
|
defer fs.Unlock()
|
||||||
|
|
||||||
if _, ok := fs.snapshots[name]; ok {
|
if _, ok := fs.snapshots[name]; ok {
|
||||||
err := errors.New("already exists")
|
err := errors.New("already exists")
|
||||||
@@ -264,8 +284,8 @@ func (fs *ZfsFs) DelSnapshot(name string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.mx.Lock()
|
fs.Lock()
|
||||||
defer fs.mx.Unlock()
|
defer fs.Unlock()
|
||||||
|
|
||||||
if _, ok := fs.snapshots[name]; !ok {
|
if _, ok := fs.snapshots[name]; !ok {
|
||||||
err := errors.New("doesn't exist")
|
err := errors.New("doesn't exist")
|
||||||
@@ -296,8 +316,8 @@ func (fs *ZfsFs) AddSnapshot(s *ZfsSnapshot) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.mx.Lock()
|
fs.Lock()
|
||||||
defer fs.mx.Unlock()
|
defer fs.Unlock()
|
||||||
|
|
||||||
if _, ok := fs.snapshots[s.name]; ok {
|
if _, ok := fs.snapshots[s.name]; ok {
|
||||||
err := errors.New("already exist")
|
err := errors.New("already exist")
|
||||||
@@ -311,6 +331,9 @@ func (fs *ZfsFs) AddSnapshot(s *ZfsSnapshot) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fs *ZfsFs) ValidSnapshots() []*ZfsSnapshot {
|
func (fs *ZfsFs) ValidSnapshots() []*ZfsSnapshot {
|
||||||
|
log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path}).Debugf("starting")
|
||||||
|
defer log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path}).Debugf("done")
|
||||||
|
|
||||||
tab := make([]*ZfsSnapshot, 0)
|
tab := make([]*ZfsSnapshot, 0)
|
||||||
|
|
||||||
for _, s := range fs.snapshots {
|
for _, s := range fs.snapshots {
|
||||||
|
|||||||
Reference in New Issue
Block a user