mirror of
https://github.com/nxshock/backuper.git
synced 2025-04-18 22:01:53 +05:00
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
d334256d0b | |||
3a37c29de4 | |||
dc3eb7d9ef | |||
ca8193ad06 | |||
465cfae874 | |||
d34d504754 |
58
README.md
58
README.md
@ -1,2 +1,58 @@
|
||||
# backuper
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
### Incremental backup
|
||||
|
||||
```sh
|
||||
backuper i <config file path>
|
||||
```
|
||||
|
||||
### Full backup
|
||||
|
||||
```sh
|
||||
backuper f <config file path>
|
||||
```
|
||||
|
||||
### Search files in backup
|
||||
|
||||
```sh
|
||||
backuper s <config file path> <mask>
|
||||
```
|
||||
|
||||
### Recover files from backup
|
||||
|
||||
```sh
|
||||
backuper r <config file path> <mask> <files datetime> <path to recover>
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```sh
|
||||
# Recover Go files relevant as of 01.01.2023 to /home/user/go directory
|
||||
backuper r config.conf "*.go" "01.01.2023" "/home/user/go"
|
||||
```
|
||||
|
||||
### Test backup for errors
|
||||
|
||||
```sh
|
||||
backuper t <config file path>
|
||||
```
|
||||
|
||||
## Basic config example
|
||||
|
||||
Backup config files from `/etc` and sqlite files from `/var`:
|
||||
|
||||
```toml
|
||||
FileName = "backup"
|
||||
|
||||
[[Patterns]]
|
||||
Path = "/etc"
|
||||
FileNamePatternList = ["*.conf", "*.toml", "*.ini", "*.yaml"]
|
||||
Recursive = true
|
||||
|
||||
[[Patterns]]
|
||||
Path = "/var"
|
||||
FileNamePatternList = ["*.sqlite"]
|
||||
Recursive = true
|
||||
```
|
||||
|
@ -46,6 +46,8 @@ func (b *Config) fileList(fileNames chan FileInfo) {
|
||||
if b.StopOnAnyError {
|
||||
return fmt.Errorf("get file info error: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
file := FileInfo{
|
||||
|
2
go.mod
2
go.mod
@ -4,7 +4,7 @@ go 1.20
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.2.1
|
||||
github.com/klauspost/compress v1.16.3
|
||||
github.com/klauspost/compress v1.16.4
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/tidwall/match v1.1.1
|
||||
)
|
||||
|
4
go.sum
4
go.sum
@ -3,8 +3,8 @@ github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
|
||||
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/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY=
|
||||
github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU=
|
||||
github.com/klauspost/compress v1.16.4/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
12
index.go
12
index.go
@ -62,7 +62,8 @@ func (index Index) Save(fileName string) error {
|
||||
|
||||
enc, err := zstd.NewWriter(f, zstd.WithEncoderLevel(zstd.SpeedBestCompression))
|
||||
if err != nil {
|
||||
f.Close() // TODO: удалить частичный файл?
|
||||
f.Close()
|
||||
os.Remove(fileName)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -82,7 +83,8 @@ func (index Index) Save(fileName string) error {
|
||||
err := csvWriter.Write([]string{fileName, historyItem.ArchiveFileName, strconv.Itoa(int(historyItem.ModificationTime.Unix()))})
|
||||
if err != nil {
|
||||
enc.Close()
|
||||
f.Close() // TODO: удалить частичный файл?
|
||||
f.Close()
|
||||
os.Remove(fileName)
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -91,13 +93,15 @@ func (index Index) Save(fileName string) error {
|
||||
csvWriter.Flush()
|
||||
if err := csvWriter.Error(); err != nil {
|
||||
enc.Close()
|
||||
f.Close() // TODO: удалить частичный файл?
|
||||
f.Close()
|
||||
os.Remove(fileName)
|
||||
return err
|
||||
}
|
||||
|
||||
err = enc.Close()
|
||||
if err != nil {
|
||||
f.Close() // TODO: удалить частичный файл?
|
||||
f.Close()
|
||||
os.Remove(fileName)
|
||||
return err
|
||||
}
|
||||
|
||||
|
23
main.go
23
main.go
@ -4,7 +4,6 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -56,25 +55,9 @@ func main() {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
var t time.Time
|
||||
switch len(os.Args[4]) {
|
||||
case len("02.01.2006"):
|
||||
t, err = time.Parse("02.01.2006 15:04", os.Args[4])
|
||||
if err != nil {
|
||||
config.fatalln("time parse error:", err)
|
||||
}
|
||||
case len("02.01.2006 15:04"):
|
||||
t, err = time.Parse("02.01.2006 15:04", os.Args[4])
|
||||
if err != nil {
|
||||
config.fatalln("time parse error:", err)
|
||||
}
|
||||
case len("02.01.2006 15:04:05"):
|
||||
t, err = time.Parse("02.01.2006 15:04:05", os.Args[4])
|
||||
if err != nil {
|
||||
config.fatalln("time parse error:", err)
|
||||
}
|
||||
default:
|
||||
config.fatalln(`wrong time format, must be ["DD.MM.YYYY", "DD.MM.YYYY hh:mm", "DD.MM.YYYY hh:mm:ss"]`)
|
||||
t, err := parseTime(os.Args[4])
|
||||
if err != nil {
|
||||
config.fatalln(err)
|
||||
}
|
||||
|
||||
plan, err := config.extractionPlan(os.Args[3], t)
|
||||
|
17
utils.go
17
utils.go
@ -1,9 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/match"
|
||||
)
|
||||
@ -32,7 +34,7 @@ func sizeToApproxHuman(s int64) string {
|
||||
return fmt.Sprintf("%d B", s)
|
||||
}
|
||||
|
||||
// clean убирает невозможнын комбинации символов из пути
|
||||
// clean убирает невозможные комбинации символов из пути
|
||||
func clean(s string) string {
|
||||
s = strings.ReplaceAll(s, ":", "")
|
||||
s = strings.ReplaceAll(s, `\\`, `\`)
|
||||
@ -71,3 +73,16 @@ func isFilePathMatchPatterns(patterns []string, fileName string) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func parseTime(s string) (time.Time, error) {
|
||||
switch len(s) {
|
||||
case len("02.01.2006"):
|
||||
return time.ParseInLocation("02.01.2006", s, time.Local)
|
||||
case len("02.01.2006 15:04"):
|
||||
return time.ParseInLocation("02.01.2006 15:04", s, time.Local)
|
||||
case len("02.01.2006 15:04:05"):
|
||||
return time.ParseInLocation("02.01.2006 15:04:05", s, time.Local)
|
||||
}
|
||||
|
||||
return time.Time{}, errors.New("unknown time format")
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -10,3 +11,21 @@ func TestSizeToApproxHuman(t *testing.T) {
|
||||
assert.Equal(t, "1.0 KiB", sizeToApproxHuman(1024))
|
||||
assert.Equal(t, "1.1 KiB", sizeToApproxHuman(1126))
|
||||
}
|
||||
|
||||
func TestParseTime(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected time.Time
|
||||
}{
|
||||
{"02.01.2006", time.Date(2006, 01, 02, 0, 0, 0, 0, time.Local)},
|
||||
{"02.01.2006 15:04", time.Date(2006, 01, 02, 15, 4, 0, 0, time.Local)},
|
||||
{"02.01.2006 15:04:05", time.Date(2006, 01, 02, 15, 4, 5, 0, time.Local)},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got, err := parseTime(test.input)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, got)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user