Skip to content

Commit b3c99ef

Browse files
authored
feat: various CLI and feature improvements (#23)
Merge pull request #23 from d-Rickyy-b/cli-improvements
2 parents 4a6d279 + 8f1a23c commit b3c99ef

File tree

8 files changed

+204
-36
lines changed

8 files changed

+204
-36
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,8 @@
2828
config.yml
2929

3030
# Exclude compiled binaries
31-
backmeup_*-*
31+
backmeup_*-*
32+
33+
# Exclude archive files
34+
*.zip
35+
*.tar.gz

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
### Fixed
1212
### Docs
1313

14-
## [0.1.4]
14+
## [0.1.4] - 2021-02-11
1515
### Added
1616
- Symlink support for tar files ([5d75752](https://github.com/d-Rickyy-b/backmeup/commit/5d757525bbde26429e90a30ea5fba8d721db6f72))
17+
- Following symlinks (aka replacing a symlink to a file with the actual file) ([a98fe65](https://github.com/d-Rickyy-b/backmeup/commit/a98fe65d8188cd8f5abac2d766cffa594c032757))
18+
- Ability to only run backups of certain units via `-u`/`--unit` CLI parameter ([92db794](https://github.com/d-Rickyy-b/backmeup/commit/92db794365448c67379f20ff3e2d6bfb998f1f57))
19+
- Check if archive already exists ([fd88626](https://github.com/d-Rickyy-b/backmeup/commit/fd886263038d6c97cb0f481e9ff0140187d5283e))
20+
- Add `-t`/`--test-path` CLI parameter for checking exclusion for given paths ([1b13e44](https://github.com/d-Rickyy-b/backmeup/commit/1b13e44a38faa0e472ecaea4b8864cfffc2ab147))
21+
- Add `-v`/`--version` CLI parameter to just print the tool's version ([2c51b05](https://github.com/d-Rickyy-b/backmeup/commit/2c51b058723e1eb3e46ba2e8ee0b2260ad39b362))
1722
### Changed
1823
- Move archive code to archiver package ([d8666cb](https://github.com/d-Rickyy-b/backmeup/commit/d8666cb5d3acc25a77f3d84f92c52301687dd6ae))
1924
- Move config code to config package ([0a03807](https://github.com/d-Rickyy-b/backmeup/commit/0a038077a21c88781abf77b85a6a9da7b60df9f6))

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1+
![backmeup logo](https://raw.githubusercontent.com/d-Rickyy-b/backmeup/master/docs/backmeup_logo_transparent.png)
12
# backmeup - a lightweight backup utility for the CLI
23
[![build](https://github.com/d-Rickyy-b/backmeup/workflows/build/badge.svg)](https://github.com/d-Rickyy-b/backmeup/actions?query=workflow%3Abuild)
34

4-
When managing several servers, you often find yourself in a need of making backups. I searched for tools online that could make it into a painless experience but never found an easy-to-use, lightweight, portable, CLI tool which is easy to configure and does not need a remote server for backups.
5+
When managing several servers, you often find yourself in a need of making backups.
6+
I searched for tools online that could make it into a painless experience but never found an easy-to-use, lightweight, portable, CLI tool which is easy to configure and does not need a remote server for backups.
57
That's why I created **backmeup**.
68

79
### Key features
810
- Easy to use
911
- Define multiple backups in a single config file
1012
- **Portable** - you can copy the **single executable** with a configuration file on all your machines
11-
- **Lightweight** - the executables are ~1 mb
13+
- **Lightweight** - the executables are < 10 mb
1214
- Exclude files and paths with .gitignore-like syntax
1315
- Group together multiple source paths into one backup
1416
- Config files written in yaml
@@ -115,6 +117,7 @@ The name of your backup is the key at root level. Starting from there you can co
115117
| add_subfolder | boolean | No | `false` | Creates a new subfolder in `<destination>` for this unit if set to true |
116118
| enabled | boolean | No | `true` | Switch to disable each unit individually |
117119
| use_absolute_paths | boolean | No | `true` | Uses absolute file paths in the archive (see [#11](https://github.com/d-Rickyy-b/backmeup/issues/11)) |
120+
| follow_symlinks | boolean | No | `false` | If set to `true`, the targets of symlinks (regular files only) will be included in the archive. Only works for tar archives! |
118121

119122
Be careful when using quotes in paths. For most strings you don't even need to use quotes at all. When using double quotes (`"`), you must escape backslashes (`\`) when you want to use them as literal characters (such as in Windows paths).
120123
Check [this handy article](https://www.yaml.info/learn/quote.html) for learning more about quotes in yaml.

archiver/archive.go

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ type BackupFileMetadata struct {
1919
BackupBasePath string
2020
}
2121

22-
func getPathInArchive(filePath string, backupBasePath string, unit config.Unit) string {
22+
var currentUnitConfig config.Unit
23+
24+
func getPathInArchive(filePath string, backupBasePath string) string {
2325
// Remove the base Path from the file Path within the archiver, if option is set
2426
pathInArchive := filePath
2527

26-
if !unit.UseAbsolutePaths {
28+
if !currentUnitConfig.UseAbsolutePaths {
2729
parentBasePath := filepath.Dir(backupBasePath)
2830
pathInArchive = strings.ReplaceAll(filePath, parentBasePath, "")
2931

@@ -34,6 +36,8 @@ func getPathInArchive(filePath string, backupBasePath string, unit config.Unit)
3436
}
3537

3638
func WriteArchive(backupArchivePath string, filesToBackup []BackupFileMetadata, unit config.Unit) {
39+
// Store the current config for other methods to access config parameters
40+
currentUnitConfig = unit
3741
archiveFile, err := os.Create(backupArchivePath)
3842
if err != nil {
3943
log.Fatalln(err)
@@ -42,15 +46,15 @@ func WriteArchive(backupArchivePath string, filesToBackup []BackupFileMetadata,
4246

4347
switch unit.ArchiveType {
4448
case "tar.gz":
45-
writeTar(archiveFile, filesToBackup, unit)
49+
writeTar(archiveFile, filesToBackup)
4650
case "zip":
47-
writeZip(archiveFile, filesToBackup, unit)
51+
writeZip(archiveFile, filesToBackup)
4852
default:
4953
log.Fatalf("Can't handle archiver type '%s'", unit.ArchiveType)
5054
}
5155
}
5256

53-
func writeTar(archiveFile *os.File, filesToBackup []BackupFileMetadata, unit config.Unit) {
57+
func writeTar(archiveFile *os.File, filesToBackup []BackupFileMetadata) {
5458
// set up the gzip and tar writer
5559
gw := gzip.NewWriter(archiveFile)
5660
defer gw.Close()
@@ -59,13 +63,15 @@ func writeTar(archiveFile *os.File, filesToBackup []BackupFileMetadata, unit con
5963
defer tw.Close()
6064

6165
// Init progress bar
62-
bar := pb.StartNew(len(filesToBackup))
66+
bar := pb.New(len(filesToBackup))
67+
bar.SetMaxWidth(100)
68+
bar.Start()
6369

6470
for i := range filesToBackup {
6571
fileMetadata := filesToBackup[i]
6672
filePath := fileMetadata.Path
6773

68-
pathInArchive := getPathInArchive(filePath, fileMetadata.BackupBasePath, unit)
74+
pathInArchive := getPathInArchive(filePath, fileMetadata.BackupBasePath)
6975

7076
if err := addFileToTar(tw, filePath, pathInArchive); err != nil {
7177
log.Println(err)
@@ -77,17 +83,19 @@ func writeTar(archiveFile *os.File, filesToBackup []BackupFileMetadata, unit con
7783
bar.Finish()
7884
}
7985

80-
func writeZip(archiveFile *os.File, filesToBackup []BackupFileMetadata, unit config.Unit) {
86+
func writeZip(archiveFile *os.File, filesToBackup []BackupFileMetadata) {
8187
zw := zip.NewWriter(archiveFile)
8288
defer zw.Close()
8389

84-
bar := pb.StartNew(len(filesToBackup))
90+
bar := pb.New(len(filesToBackup))
91+
bar.SetMaxWidth(100)
92+
bar.Start()
8593

8694
for i := range filesToBackup {
8795
fileMetadata := filesToBackup[i]
8896
filePath := fileMetadata.Path
8997

90-
pathInArchive := getPathInArchive(filePath, fileMetadata.BackupBasePath, unit)
98+
pathInArchive := getPathInArchive(filePath, fileMetadata.BackupBasePath)
9199

92100
if err := addFileToZip(zw, filePath, pathInArchive); err != nil {
93101
log.Println(err)
@@ -100,23 +108,36 @@ func writeZip(archiveFile *os.File, filesToBackup []BackupFileMetadata, unit con
100108
}
101109

102110
func addFileToTar(tw *tar.Writer, path string, pathInArchive string) error {
103-
file, err := os.Open(path)
104-
if err != nil {
105-
return err
106-
}
107-
defer file.Close()
108-
109111
if stat, err := os.Lstat(path); err == nil {
110112
var linkTarget string
111113
// Check if file is symlink
112114
if stat.Mode()&os.ModeSymlink != 0 {
113-
log.Printf("Found link: %s", path)
114115
var err error
115116
linkTarget, err = os.Readlink(path)
116117
if err != nil {
117118
return fmt.Errorf("%s: readlink: %v", stat.Name(), err)
118119
}
120+
121+
// In case the user wants to follow symlinks we eval the symlink target
122+
if currentUnitConfig.FollowSymlinks {
123+
if linkTargetPath, err := filepath.EvalSymlinks(path); err == nil {
124+
if linkTargetInfo, statErr := os.Stat(linkTargetPath); statErr == nil {
125+
if linkTargetInfo.Mode().IsRegular() {
126+
// If file is regular, we can simply replace the symlink with the actual file
127+
path = linkTargetPath
128+
linkTarget = ""
129+
stat = linkTargetInfo
130+
}
131+
}
132+
}
133+
}
134+
}
135+
136+
file, err := os.Open(path)
137+
if err != nil {
138+
return err
119139
}
140+
defer file.Close()
120141

121142
// now lets create the header as needed for this file within the tarball
122143
header, err := tar.FileInfoHeader(stat, filepath.ToSlash(linkTarget))
@@ -131,7 +152,7 @@ func addFileToTar(tw *tar.Writer, path string, pathInArchive string) error {
131152
}
132153

133154
// Check for regular files
134-
if header.Typeflag == tar.TypeReg {
155+
if stat.Mode().IsRegular() {
135156
// copy the file data to the tarball
136157
_, err := io.Copy(tw, file)
137158
if err != nil {

config.sample.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ backup_unit_name:
99
- "*.rar"
1010
archive_type: "tar.gz"
1111
add_subfolder: false
12+
follow_symlinks: false
1213
enabled: true
1314

1415
other_unit:

config/config.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type Unit struct {
1717
AddSubfolder bool
1818
Enabled bool
1919
UseAbsolutePaths bool
20+
FollowSymlinks bool
2021
}
2122

2223
type Config struct {
@@ -32,10 +33,11 @@ type yamlUnit struct {
3233
AddSubfolder *bool `yaml:"add_subfolder"`
3334
Enabled *bool `yaml:"enabled"`
3435
UseAbsolutePaths *bool `yaml:"use_absolute_paths"`
36+
FollowSymlinks *bool `yaml:"follow_symlinks"`
3537
}
3638

39+
// FromYaml creates a config struct from a given yaml file as bytes
3740
func (config Config) FromYaml(yamlData []byte) (Config, error) {
38-
// Create a config object from yaml byte array
3941
unitMap := make(map[string]yamlUnit)
4042

4143
log.Println("Parsing config yaml")
@@ -75,6 +77,11 @@ func (config Config) FromYaml(yamlData []byte) (Config, error) {
7577
unit.UseAbsolutePaths = *yamlUnit.UseAbsolutePaths
7678
}
7779

80+
unit.FollowSymlinks = false
81+
if yamlUnit.FollowSymlinks != nil {
82+
unit.FollowSymlinks = *yamlUnit.FollowSymlinks
83+
}
84+
7885
if yamlUnit.Sources == nil || yamlUnit.Destination == nil {
7986
log.Fatalf("Sources or destination can't be parsed for unit '%s'", unitName)
8087
} else {
@@ -90,8 +97,9 @@ func (config Config) FromYaml(yamlData []byte) (Config, error) {
9097
return config, nil
9198
}
9299

100+
// validatePath checks if a given file/directory exists
101+
// It returns true if it exists, otherwise false
93102
func validatePath(path string, mustBeDir bool) bool {
94-
// Checks if a file/directory exists
95103
file, err := os.Stat(path)
96104

97105
if err != nil {
@@ -142,8 +150,8 @@ func (config *Config) validate() error {
142150
return nil
143151
}
144152

153+
// ReadConfig reads a config file from a given path
145154
func ReadConfig(configPath string) (Config, error) {
146-
// Read config file at configPath
147155
log.Printf("Trying to read config file '%s'!", configPath)
148156
data, err := ioutil.ReadFile(configPath)
149157

docs/backmeup_logo_transparent.png

29.7 KB
Loading

0 commit comments

Comments
 (0)