Skip to content

Commit

Permalink
add backup cleanup codez (#789)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidnewhall authored Jul 27, 2024
2 parents ed35815 + 7919229 commit ba3e1b5
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 37 deletions.
1 change: 0 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ linters:
- dupl # Maybe one day we can reduce the duplicate code with generics.
- nlreturn # Not needed because wsl is good enough; better actually.
- godot # Does not work with annotations.
- musttag # Broken in 1.59: https://github.com/go-simpler/musttag/issues/98
- depguard # Not even sure why this is useful. We have too many deps to care.
run:
timeout: 5m
Expand Down
1 change: 1 addition & 0 deletions pkg/client/handlers_gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,7 @@ func (c *Client) mergeAndValidateNewConfig(config *configfile.Config, request *h
config.Commands = nil
config.Service = nil
config.Snapshot.Plugins.MySQL = nil
config.Snapshot.Plugins.Nvidia = nil

// for k, v := range request.PostForm {
// c.Errorf("Config Post: %s = %+v", k, v)
Expand Down
2 changes: 1 addition & 1 deletion pkg/logs/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const (
// LogConfig allows sending logs to rotating files.
// Setting an AppName will force log creation even if LogFile and HTTPLog are empty.
type LogConfig struct {
AppName string `json:"-"`
AppName string `json:"-" toml:"-" xml:"-" yaml:"-"`
LogFile string `json:"logFile" toml:"log_file" xml:"log_file" yaml:"logFile"`
DebugLog string `json:"debugLog" toml:"debug_log" xml:"debug_log" yaml:"debugLog"`
HTTPLog string `json:"httpLog" toml:"http_log" xml:"http_log" yaml:"httpLog"`
Expand Down
14 changes: 7 additions & 7 deletions pkg/snapshot/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ import (

// MySQLConfig allows us to gather a process list for the snapshot.
type MySQLConfig struct {
Name string `toml:"name" xml:"name"`
Host string `toml:"host" xml:"host"`
User string `toml:"user" xml:"user"`
Pass string `toml:"pass" xml:"pass"`
Timeout cnfg.Duration `toml:"timeout" xml:"timeout"`
// only used by service checks, snapshot interval is used for mysql.
Interval cnfg.Duration `toml:"interval" xml:"interval"`
Name string `json:"name" toml:"name" xml:"name"`
Host string `json:"host" toml:"host" xml:"host"`
User string `json:"-" toml:"user" xml:"user"`
Pass string `json:"-" toml:"pass" xml:"pass"`
Timeout cnfg.Duration `json:"timeout" toml:"timeout" xml:"timeout"`
// Only used by service checks, snapshot interval is used for mysql.
Interval cnfg.Duration `json:"interval" toml:"interval" xml:"interval"`
}

// MySQLProcesses allows us to manipulate our list with methods.
Expand Down
4 changes: 1 addition & 3 deletions pkg/snapshot/nvidia.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,7 @@ func (s *Snapshot) GetNvidia(ctx context.Context, config *NvidiaConfig) error {
return fmt.Errorf("unable to locate nvidia-smi at provided path '%s': %w", cmdPath, err)
}
} else if cmdPath, err = exec.LookPath(nvidiaSMIname()); err != nil {
// do not throw an error if nvidia-smi is missing.
// return fmt.Errorf("nvidia-smi missing! %w", err)
return nil
return fmt.Errorf("nvidia-smi missing from PATH! %w", err)
}

cmd := exec.CommandContext(ctx, cmdPath, "--format=csv,noheader", "--query-gpu="+
Expand Down
1 change: 0 additions & 1 deletion pkg/snapshot/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ type Config struct {
IPMI bool `json:"ipmi" toml:"ipmi" xml:"ipmi"` // get ipmi sensor info.
IPMISudo bool `json:"ipmiSudo" toml:"ipmiSudo" xml:"ipmiSudo"` // use sudo to get ipmi sensor info.
Plugins
// Debug bool `toml:"debug" xml:"debug" json:"debug"`
}

// Plugins is optional configuration for "plugins".
Expand Down
2 changes: 1 addition & 1 deletion pkg/triggers/autoupdate/autoupdate.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func (c *cmd) updateNow(ctx context.Context, u *update.Update, msg website.Event

cmd := &update.Command{
URL: u.CurrURL,
Logger: c.Logger.DebugLog,
Logger: c.Logger,
Args: []string{"--restart", "--config", c.ConfigFile},
Path: os.Args[0],
}
Expand Down
66 changes: 66 additions & 0 deletions pkg/update/cleanup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package update

import (
"os"
"path/filepath"
"sort"
"strings"
"time"

"github.com/Notifiarr/notifiarr/pkg/mnd"
)

const keepBackups = 3

type file struct {
name string
when time.Time
}

type fileList []file

func (f fileList) Len() int {
return len(f)
}

func (f fileList) Less(i, j int) bool {
return f[i].when.UnixMicro() < f[j].when.UnixMicro()
}

func (f fileList) Swap(i, j int) {
f[i], f[j] = f[j], f[i]
}

func (u *Command) cleanOldBackups() {
dir := filepath.Dir(u.Path)
pfx := strings.TrimSuffix(filepath.Base(u.Path), dotExe) + ".backup."

entries, err := os.ReadDir(dir)
if err != nil {
return
}

consider := fileList{}

for _, entry := range entries {
if !strings.HasPrefix(entry.Name(), pfx) || len(entry.Name()) < len(pfx)+len(backupTimeFormat) {
continue
}

date := strings.TrimSuffix(strings.TrimPrefix(entry.Name(), pfx), dotExe)
if when, err := time.Parse(backupTimeFormat, date); err == nil {
consider = append(consider, file{name: entry.Name(), when: when})
}
}

sort.Sort(consider)

for idx, file := range consider {
if len(consider)-idx <= keepBackups {
return
}

err := os.Remove(filepath.Join(dir, file.name))
u.Printf("[UPDATE] Deleted old backup file: %s%s (error: %v)", file.name, mnd.DurationAge(file.when), err)
}
}
47 changes: 24 additions & 23 deletions pkg/update/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
Expand All @@ -25,14 +24,18 @@ import (
"github.com/Notifiarr/notifiarr/pkg/mnd"
)

const downloadTimeout = 5 * time.Minute
const (
downloadTimeout = 5 * time.Minute
backupTimeFormat = "060102T150405"
dotExe = ".exe"
)

// Command is the input data to perform an in-place update.
type Command struct {
URL string // file to download.
Path string // file to be updated.
Args []string // optional, but non-nil will crash.
*log.Logger // debug logs.
URL string // file to download.
Path string // file to be updated.
Args []string // optional, but non-nil will crash.
mnd.Logger // debug logs.
}

// Errors. Don't trigger these.
Expand All @@ -42,11 +45,13 @@ var (
)

// Restart is meant to be called from a special flag that reloads the app after an upgrade.
func Restart(u *Command) error {
func Restart(cmd *Command) error {
// A small pause to give the parent time to exit.
time.Sleep(time.Second)
// A small pause to give the new app time to fork.
defer time.Sleep(time.Second)

if err := exec.Command(u.Path, u.Args...).Start(); err != nil { //nolint:gosec
if err := exec.Command(cmd.Path, cmd.Args...).Start(); err != nil { //nolint:gosec
return fmt.Errorf("executing command %w", err)
}

Expand Down Expand Up @@ -109,30 +114,26 @@ func (u *Command) replaceFile(ctx context.Context) (string, error) {
}

suff := ""
if strings.HasSuffix(u.Path, ".exe") {
suff = ".exe"
if strings.HasSuffix(u.Path, dotExe) {
suff = dotExe
}

backupFile := strings.TrimSuffix(u.Path, ".exe")
backupFile += ".backup." + time.Now().Format("060102T150405") + suff
u.Printf("[UPDATE] Renaming %s => %s", u.Path, backupFile)
backupFile := strings.TrimSuffix(u.Path, dotExe)
backupFile += ".backup." + time.Now().Format(backupTimeFormat) + suff
u.Debugf("[UPDATE] Renaming %s => %s", u.Path, backupFile)

if err := os.Rename(u.Path, backupFile); err != nil {
return backupFile, fmt.Errorf("renaming original file: %w", err)
}

u.Printf("[UPDATE] Renaming %s => %s", tempFile, u.Path)
u.Debugf("[UPDATE] Renaming %s => %s", tempFile, u.Path)

u.cleanOldBackups()

if err := os.Rename(tempFile, u.Path); err != nil {
return backupFile, fmt.Errorf("renaming downloaded file: %w", err)
}
/* // Hack used for testing.
u.Printf("[UPDATE] Renaming [HACK] %s => %s", backupFile, u.Path)

if err := os.Rename(backupFile, u.Path); err != nil {
return backupFile, fmt.Errorf("renaming downloaded file %w", err)
}
/**/
return backupFile, nil
}

Expand All @@ -143,7 +144,7 @@ func (u *Command) writeFile(ctx context.Context, folderPath string) (string, err
}
defer tempFile.Close()

u.Printf("[UPDATE] Primed Temp File: %s", tempFile.Name())
u.Debugf("[UPDATE] Primed Temp File: %s", tempFile.Name())

req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.URL, nil)
if err != nil {
Expand Down Expand Up @@ -211,7 +212,7 @@ func (u *Command) writeZipFile(tempFile *os.File, body []byte, size int64) error

// Find the exe file and write that.
for _, zipFile := range zipReader.File {
if runtime.GOOS == mnd.Windows && strings.HasSuffix(zipFile.Name, ".exe") {
if runtime.GOOS == mnd.Windows && strings.HasSuffix(zipFile.Name, dotExe) {
zipOpen, err := zipFile.Open()
if err != nil {
return fmt.Errorf("reading zipped exe file: %w", err)
Expand All @@ -226,7 +227,7 @@ func (u *Command) writeZipFile(tempFile *os.File, body []byte, size int64) error
}
}

u.Println("[UPDATE] exe file not found in zip file")
u.Errorf("[UPDATE] exe file not found in zip file")

return nil
}

0 comments on commit ba3e1b5

Please sign in to comment.