mirror of
https://github.com/qdm12/ddns-updater.git
synced 2026-04-05 08:54:09 -04:00
Fix #54 periodic backup to zip files
This commit is contained in:
@@ -47,6 +47,8 @@ ENV DELAY=10m \
|
||||
NODE_ID=0 \
|
||||
HTTP_TIMEOUT=10s \
|
||||
GOTIFY_URL= \
|
||||
GOTIFY_TOKEN=
|
||||
GOTIFY_TOKEN= \
|
||||
BACKUP_PERIOD=0 \
|
||||
BACKUP_DIRECTORY=/updater/data
|
||||
COPY --from=builder --chown=1000 /tmp/gobuild/app /updater/app
|
||||
COPY --chown=1000 ui/* /updater/ui/
|
||||
|
||||
@@ -210,6 +210,8 @@ DDNSS.de:
|
||||
| `HTTP_TIMEOUT` | `10s` | Timeout for all HTTP requests |
|
||||
| `GOTIFY_URL` | | (optional) HTTP(s) URL to your Gotify server |
|
||||
| `GOTIFY_TOKEN` | | (optional) Token to access your Gotify server |
|
||||
| `BACKUP_PERIOD` | `0` | Set to a period (i.e. `72h15m`) to enable zip backups of data/config.json and data/updates.json in a zip file |
|
||||
| `BACKUP_DIRECTORY` | `/updater/data` | Directory to write backup zip files to if `BACKUP_PERIOD` is not `0`.
|
||||
|
||||
### Host firewall
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
libparams "github.com/qdm12/golibs/params"
|
||||
"github.com/qdm12/golibs/server"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/backup"
|
||||
"github.com/qdm12/ddns-updater/internal/data"
|
||||
"github.com/qdm12/ddns-updater/internal/handlers"
|
||||
"github.com/qdm12/ddns-updater/internal/healthcheck"
|
||||
@@ -31,12 +32,12 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
os.Exit(_main(context.Background()))
|
||||
os.Exit(_main(context.Background(), time.Now))
|
||||
// returns 1 on error
|
||||
// returns 2 on os signal
|
||||
}
|
||||
|
||||
func _main(ctx context.Context) int {
|
||||
func _main(ctx context.Context, timeNow func() time.Time) int {
|
||||
if libhealthcheck.Mode(os.Args) {
|
||||
// Running the program in a separate instance through the Docker
|
||||
// built-in healthcheck, in an ephemeral fashion to query the
|
||||
@@ -65,34 +66,7 @@ func _main(ctx context.Context) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
listeningPort, warning, err := paramsReader.GetListeningPort()
|
||||
if len(warning) > 0 {
|
||||
logger.Warn(warning)
|
||||
}
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
notify(4, err)
|
||||
return 1
|
||||
}
|
||||
rootURL, err := paramsReader.GetRootURL()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
notify(4, err)
|
||||
return 1
|
||||
}
|
||||
defaultPeriod, err := paramsReader.GetDelay(libparams.Default("10m"))
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
notify(4, err)
|
||||
return 1
|
||||
}
|
||||
dir, err := paramsReader.GetExeDir()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
notify(4, err)
|
||||
return 1
|
||||
}
|
||||
dataDir, err := paramsReader.GetDataDir(dir)
|
||||
dir, dataDir, listeningPort, rootURL, defaultPeriod, backupPeriod, backupDirectory, err := getParams(paramsReader)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
notify(4, err)
|
||||
@@ -179,6 +153,8 @@ func _main(ctx context.Context) int {
|
||||
)
|
||||
}()
|
||||
|
||||
go backupRunLoop(ctx, backupPeriod, dir, backupDirectory, logger, timeNow)
|
||||
|
||||
osSignals := make(chan os.Signal, 1)
|
||||
signal.Notify(osSignals,
|
||||
syscall.SIGINT,
|
||||
@@ -230,3 +206,69 @@ func setupGotify(paramsReader params.Reader, logger logging.Logger) (notify func
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getParams(paramsReader params.Reader) (
|
||||
dir, dataDir,
|
||||
listeningPort, rootURL string,
|
||||
defaultPeriod time.Duration,
|
||||
backupPeriod time.Duration, backupDirectory string,
|
||||
err error) {
|
||||
dir, err = paramsReader.GetExeDir()
|
||||
if err != nil {
|
||||
return "", "", "", "", 0, 0, "", err
|
||||
}
|
||||
dataDir, err = paramsReader.GetDataDir(dir)
|
||||
if err != nil {
|
||||
return "", "", "", "", 0, 0, "", err
|
||||
}
|
||||
listeningPort, _, err = paramsReader.GetListeningPort()
|
||||
if err != nil {
|
||||
return "", "", "", "", 0, 0, "", err
|
||||
}
|
||||
rootURL, err = paramsReader.GetRootURL()
|
||||
if err != nil {
|
||||
return "", "", "", "", 0, 0, "", err
|
||||
}
|
||||
defaultPeriod, err = paramsReader.GetDelay(libparams.Default("10m"))
|
||||
if err != nil {
|
||||
return "", "", "", "", 0, 0, "", err
|
||||
}
|
||||
|
||||
backupPeriod, err = paramsReader.GetBackupPeriod()
|
||||
if err != nil {
|
||||
return "", "", "", "", 0, 0, "", err
|
||||
}
|
||||
backupDirectory, err = paramsReader.GetBackupDirectory()
|
||||
if err != nil {
|
||||
return "", "", "", "", 0, 0, "", err
|
||||
}
|
||||
return dir, dataDir, listeningPort, rootURL, defaultPeriod, backupPeriod, backupDirectory, nil
|
||||
}
|
||||
|
||||
func backupRunLoop(ctx context.Context, backupPeriod time.Duration, exeDir, outputDir string,
|
||||
logger logging.Logger, timeNow func() time.Time) {
|
||||
logger = logger.WithPrefix("backup: ")
|
||||
if backupPeriod == 0 {
|
||||
logger.Info("disabled")
|
||||
return
|
||||
}
|
||||
logger.Info("each %s; writing zip files to directory %s", backupPeriod, outputDir)
|
||||
ziper := backup.NewZiper()
|
||||
timer := time.NewTimer(backupPeriod)
|
||||
for {
|
||||
filepath := fmt.Sprintf("%s/ddns-updater-backup-%d.zip", outputDir, timeNow().UnixNano())
|
||||
if err := ziper.ZipFiles(
|
||||
filepath,
|
||||
fmt.Sprintf("%s/data/updates.json", exeDir),
|
||||
fmt.Sprintf("%s/data/config.json", exeDir)); err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
select {
|
||||
case <-timer.C:
|
||||
timer.Reset(backupPeriod)
|
||||
case <-ctx.Done():
|
||||
timer.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,4 +18,6 @@ services:
|
||||
- HTTP_TIMEOUT=10s
|
||||
- GOTIFY_URL=
|
||||
- GOTIFY_TOKEN=
|
||||
- BACKUP_PERIOD=0
|
||||
- BACKUP_DIRECTORY=/updater/data
|
||||
restart: always
|
||||
|
||||
67
internal/backup/zip.go
Normal file
67
internal/backup/zip.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Ziper interface {
|
||||
ZipFiles(outputFilepath string, inputFilepaths ...string) error
|
||||
}
|
||||
|
||||
type ziper struct {
|
||||
createFile func(name string) (*os.File, error)
|
||||
openFile func(name string) (*os.File, error)
|
||||
ioCopy func(dst io.Writer, src io.Reader) (written int64, err error)
|
||||
}
|
||||
|
||||
func NewZiper() Ziper {
|
||||
return &ziper{
|
||||
createFile: os.Create,
|
||||
openFile: os.Open,
|
||||
ioCopy: io.Copy,
|
||||
}
|
||||
}
|
||||
|
||||
func (z *ziper) ZipFiles(outputFilepath string, inputFilepaths ...string) error {
|
||||
f, err := z.createFile(outputFilepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
w := zip.NewWriter(f)
|
||||
defer w.Close()
|
||||
for _, filepath := range inputFilepaths {
|
||||
if err := z.addFile(w, filepath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (z *ziper) addFile(w *zip.Writer, filepath string) error {
|
||||
f, err := z.openFile(filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Using FileInfoHeader() above only uses the basename of the file. If we want
|
||||
// to preserve the folder structure we can overwrite this with the full path.
|
||||
// header.Name = filepath
|
||||
header.Method = zip.Deflate
|
||||
ioWriter, err := w.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = z.ioCopy(ioWriter, f)
|
||||
return err
|
||||
}
|
||||
@@ -22,6 +22,8 @@ type Reader interface {
|
||||
GetDelay(setters ...libparams.GetEnvSetter) (duration time.Duration, err error)
|
||||
GetExeDir() (dir string, err error)
|
||||
GetHTTPTimeout() (duration time.Duration, err error)
|
||||
GetBackupPeriod() (duration time.Duration, err error)
|
||||
GetBackupDirectory() (directory string, err error)
|
||||
|
||||
// Version getters
|
||||
GetVersion() string
|
||||
@@ -88,3 +90,15 @@ func (r *reader) GetExeDir() (dir string, err error) {
|
||||
func (r *reader) GetHTTPTimeout() (duration time.Duration, err error) {
|
||||
return r.envParams.GetHTTPTimeout(libparams.Default("10s"))
|
||||
}
|
||||
|
||||
func (r *reader) GetBackupPeriod() (duration time.Duration, err error) {
|
||||
s, err := r.envParams.GetEnv("BACKUP_PERIOD", libparams.Default("0"))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return time.ParseDuration(s)
|
||||
}
|
||||
|
||||
func (r *reader) GetBackupDirectory() (directory string, err error) {
|
||||
return r.envParams.GetEnv("BACKUP_DIRECTORY", libparams.Default("./data"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user