commit 7b05eebe6a66ab7c0a2c6ec391b4f97aa56463b7 Author: Knut Ahlers Date: Sun May 22 15:04:37 2016 +0200 Initial version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d7b786 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +duplicity-backup diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ceed82a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: go + +go: + - 1.5 + - 1.6 + - tip + +install: make setup-testenv +script: make test diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json new file mode 100644 index 0000000..f68e419 --- /dev/null +++ b/Godeps/Godeps.json @@ -0,0 +1,168 @@ +{ + "ImportPath": "github.com/Luzifer/duplicity-backup", + "GoVersion": "go1.6", + "GodepVersion": "v62", + "Deps": [ + { + "ImportPath": "github.com/Luzifer/go_helpers/which", + "Comment": "v1.3.0", + "Rev": "6abbbafaada02b63dd8f9e185921fd8b3c35b6c2" + }, + { + "ImportPath": "github.com/Luzifer/rconfig", + "Comment": "v1.0.3-2-g2677653", + "Rev": "26776536e61487fdffbd3ce87f827177a5903f98" + }, + { + "ImportPath": "github.com/asaskevich/govalidator", + "Comment": "v4-6-gdf81827", + "Rev": "df81827fdd59d8b4fb93d8910b286ab7a3919520" + }, + { + "ImportPath": "github.com/mitchellh/go-homedir", + "Rev": "981ab348d865cf048eb7d17e78ac7192632d8415" + }, + { + "ImportPath": "github.com/nightlyone/lockfile", + "Rev": "b30dcbfa86e3a1eaa4e6622de2ce57be2c138c10" + }, + { + "ImportPath": "github.com/onsi/ginkgo", + "Comment": "v1.2.0-14-g1b59c57", + "Rev": "1b59c57df76ede42c08590546916e6a18685857d" + }, + { + "ImportPath": "github.com/onsi/ginkgo/config", + "Comment": "v1.2.0-14-g1b59c57", + "Rev": "1b59c57df76ede42c08590546916e6a18685857d" + }, + { + "ImportPath": "github.com/onsi/ginkgo/internal/codelocation", + "Comment": "v1.2.0-14-g1b59c57", + "Rev": "1b59c57df76ede42c08590546916e6a18685857d" + }, + { + "ImportPath": "github.com/onsi/ginkgo/internal/containernode", + "Comment": "v1.2.0-14-g1b59c57", + "Rev": "1b59c57df76ede42c08590546916e6a18685857d" + }, + { + "ImportPath": "github.com/onsi/ginkgo/internal/failer", + "Comment": "v1.2.0-14-g1b59c57", + "Rev": "1b59c57df76ede42c08590546916e6a18685857d" + }, + { + "ImportPath": "github.com/onsi/ginkgo/internal/leafnodes", + "Comment": "v1.2.0-14-g1b59c57", + "Rev": "1b59c57df76ede42c08590546916e6a18685857d" + }, + { + "ImportPath": "github.com/onsi/ginkgo/internal/remote", + "Comment": "v1.2.0-14-g1b59c57", + "Rev": "1b59c57df76ede42c08590546916e6a18685857d" + }, + { + "ImportPath": "github.com/onsi/ginkgo/internal/spec", + "Comment": "v1.2.0-14-g1b59c57", + "Rev": "1b59c57df76ede42c08590546916e6a18685857d" + }, + { + "ImportPath": "github.com/onsi/ginkgo/internal/specrunner", + "Comment": "v1.2.0-14-g1b59c57", + "Rev": "1b59c57df76ede42c08590546916e6a18685857d" + }, + { + "ImportPath": "github.com/onsi/ginkgo/internal/suite", + "Comment": "v1.2.0-14-g1b59c57", + "Rev": "1b59c57df76ede42c08590546916e6a18685857d" + }, + { + "ImportPath": "github.com/onsi/ginkgo/internal/testingtproxy", + "Comment": "v1.2.0-14-g1b59c57", + "Rev": "1b59c57df76ede42c08590546916e6a18685857d" + }, + { + "ImportPath": "github.com/onsi/ginkgo/internal/writer", + "Comment": "v1.2.0-14-g1b59c57", + "Rev": "1b59c57df76ede42c08590546916e6a18685857d" + }, + { + "ImportPath": "github.com/onsi/ginkgo/reporters", + "Comment": "v1.2.0-14-g1b59c57", + "Rev": "1b59c57df76ede42c08590546916e6a18685857d" + }, + { + "ImportPath": "github.com/onsi/ginkgo/reporters/stenographer", + "Comment": "v1.2.0-14-g1b59c57", + "Rev": "1b59c57df76ede42c08590546916e6a18685857d" + }, + { + "ImportPath": "github.com/onsi/ginkgo/types", + "Comment": "v1.2.0-14-g1b59c57", + "Rev": "1b59c57df76ede42c08590546916e6a18685857d" + }, + { + "ImportPath": "github.com/onsi/gomega", + "Comment": "v1.0-55-g6331bf5", + "Rev": "6331bf5a5b5e7a832348789eb3cedff7a6917103" + }, + { + "ImportPath": "github.com/onsi/gomega/format", + "Comment": "v1.0-55-g6331bf5", + "Rev": "6331bf5a5b5e7a832348789eb3cedff7a6917103" + }, + { + "ImportPath": "github.com/onsi/gomega/internal/assertion", + "Comment": "v1.0-55-g6331bf5", + "Rev": "6331bf5a5b5e7a832348789eb3cedff7a6917103" + }, + { + "ImportPath": "github.com/onsi/gomega/internal/asyncassertion", + "Comment": "v1.0-55-g6331bf5", + "Rev": "6331bf5a5b5e7a832348789eb3cedff7a6917103" + }, + { + "ImportPath": "github.com/onsi/gomega/internal/testingtsupport", + "Comment": "v1.0-55-g6331bf5", + "Rev": "6331bf5a5b5e7a832348789eb3cedff7a6917103" + }, + { + "ImportPath": "github.com/onsi/gomega/matchers", + "Comment": "v1.0-55-g6331bf5", + "Rev": "6331bf5a5b5e7a832348789eb3cedff7a6917103" + }, + { + "ImportPath": "github.com/onsi/gomega/matchers/support/goraph/bipartitegraph", + "Comment": "v1.0-55-g6331bf5", + "Rev": "6331bf5a5b5e7a832348789eb3cedff7a6917103" + }, + { + "ImportPath": "github.com/onsi/gomega/matchers/support/goraph/edge", + "Comment": "v1.0-55-g6331bf5", + "Rev": "6331bf5a5b5e7a832348789eb3cedff7a6917103" + }, + { + "ImportPath": "github.com/onsi/gomega/matchers/support/goraph/node", + "Comment": "v1.0-55-g6331bf5", + "Rev": "6331bf5a5b5e7a832348789eb3cedff7a6917103" + }, + { + "ImportPath": "github.com/onsi/gomega/matchers/support/goraph/util", + "Comment": "v1.0-55-g6331bf5", + "Rev": "6331bf5a5b5e7a832348789eb3cedff7a6917103" + }, + { + "ImportPath": "github.com/onsi/gomega/types", + "Comment": "v1.0-55-g6331bf5", + "Rev": "6331bf5a5b5e7a832348789eb3cedff7a6917103" + }, + { + "ImportPath": "github.com/spf13/pflag", + "Rev": "b084184666e02084b8ccb9b704bf0d79c466eb1d" + }, + { + "ImportPath": "gopkg.in/yaml.v2", + "Rev": "53feefa2559fb8dfa8d81baad31be332c97d6c77" + } + ] +} diff --git a/Godeps/Readme b/Godeps/Readme new file mode 100644 index 0000000..4cdaa53 --- /dev/null +++ b/Godeps/Readme @@ -0,0 +1,5 @@ +This directory tree is generated automatically by godep. + +Please do not edit. + +See https://github.com/tools/godep for more information. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c9e2dd3 --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +bindata: + go-bindata help.txt + +setup-testenv: + go get github.com/onsi/ginkgo/ginkgo + go get github.com/onsi/gomega + +test: + $(GOPATH)/bin/ginkgo diff --git a/bindata.go b/bindata.go new file mode 100644 index 0000000..dcc815a --- /dev/null +++ b/bindata.go @@ -0,0 +1,235 @@ +// Code generated by go-bindata. +// sources: +// help.txt +// DO NOT EDIT! + +package main + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _helpTxt = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xac\x93\x3f\x73\xdb\x30\x0c\xc5\x77\x7d\x0a\x8c\xed\xa0\x64\xcf\xd6\x8b\x2f\x53\x87\x5e\x7b\xbd\x0e\x3e\x0f\x30\x05\x49\xbc\xd0\xa4\x0e\x04\x93\xda\x43\x3f\x7b\xc1\x3f\x76\xe4\xa4\xde\xca\x4d\xe4\xc3\x8f\x4f\x8f\xc0\x2f\xc6\x65\x21\x06\xe4\x90\xfc\x00\x43\x5a\x9c\x35\x56\x8e\x20\x01\x0c\x13\x0a\xc1\x1e\xcd\x73\x5a\xe0\xd5\xca\x0c\x8e\x62\x84\x19\x4f\x27\x47\x5d\xf7\x33\xe2\x44\x0f\x1d\xbc\x55\xf5\x4d\xbb\x35\xe1\x70\x40\x3f\xec\xba\xee\xcb\x0b\x5a\x87\x7b\x47\xf0\x58\xf7\x62\xae\x68\xba\x1b\xeb\xf1\xea\x62\x34\x26\xf0\x60\xfd\x94\x3d\xc9\x7c\xd9\xe7\xa4\x6e\x94\x65\x1c\xa1\xbf\x09\xdb\x90\x23\x65\xe5\x3a\xfa\x2d\x8c\x9e\x42\x8a\xab\xff\x1c\x6d\xa5\x38\x1b\xa5\x37\x89\x99\xbc\xf4\x65\x73\x4d\xf9\xaa\xa7\xb1\x40\xea\x91\x09\x5e\xd0\x7a\x1a\xc0\xfa\x95\x27\xe5\x30\x45\x09\x4c\xb0\xcd\x42\x58\x50\xe6\x1d\x6c\x05\x79\x22\xd9\x01\x7c\xaf\xa7\x11\xa2\xfe\x8f\xab\x34\xb8\x87\xc1\x72\xf9\xb9\x22\xcb\x5f\x64\x54\x76\x5c\xe3\x2e\x88\xf5\xba\xe0\xe8\x85\xf8\x28\xf3\x39\xa4\x8f\x9c\x28\x28\x29\xde\x0a\xfc\x47\xd2\xa7\x61\x7b\xaa\x39\x35\x6d\x18\xaf\xd2\xa6\x25\x44\xdb\x68\x7a\x9b\x1d\x8f\x37\x9f\x2f\x1c\x16\xcc\xae\x5a\x69\x0e\x4b\x53\x8d\x80\x93\x66\x16\x05\x5c\x30\xe8\x5a\xf2\xdd\x93\xc3\xa9\xf4\x44\xdf\xab\x70\xb4\x53\xdf\x42\xe9\xc7\x6b\x68\x3e\x4b\x8c\x62\x83\x87\x31\x68\x60\xb3\x5d\x3f\xe4\x6b\xed\xe4\xee\x96\xab\xf3\xfa\xb4\xa1\x11\x93\x93\x07\xf8\x73\x7f\x57\x6f\xbc\x7f\xdf\xc0\x77\x47\x3c\xb8\xcf\xc5\x93\x7a\x7d\xbe\x38\x72\xd7\xa8\xa7\xbc\xad\x79\xcf\xc1\x0d\x25\xab\x2c\x7e\xf3\xd6\x1c\x69\xdf\x91\x49\xd9\xf6\xff\xf1\x96\x2f\xa9\xde\x06\xda\xa7\x29\xfb\x1a\x3e\xa2\xbe\xb1\xf5\xb2\x8a\xa7\x4d\x64\xcc\x7e\x43\x92\x25\x49\x45\xb0\xf4\x9c\x7c\x86\xf8\xf7\x88\x4d\x00\x04\xd1\x16\x2b\x8a\x3c\xff\x5a\x08\x66\x46\x3f\x95\x91\xe9\x7b\xed\x83\x98\x9f\xe3\x5f\xab\x18\xa8\x33\xd3\xc6\x0a\x16\x0e\x13\xe3\x01\xce\x65\xea\x47\xc3\xb1\x12\xbb\xbf\x01\x00\x00\xff\xff\xd7\x01\xa8\x37\x87\x04\x00\x00") + +func helpTxtBytes() ([]byte, error) { + return bindataRead( + _helpTxt, + "help.txt", + ) +} + +func helpTxt() (*asset, error) { + bytes, err := helpTxtBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "help.txt", size: 1159, mode: os.FileMode(420), modTime: time.Unix(1463914687, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "help.txt": helpTxt, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} +var _bintree = &bintree{nil, map[string]*bintree{ + "help.txt": &bintree{helpTxt, map[string]*bintree{}}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} + diff --git a/config.example.yaml b/config.example.yaml new file mode 100644 index 0000000..a3032ce --- /dev/null +++ b/config.example.yaml @@ -0,0 +1,172 @@ +### +# Backup source +### +# +# Root of the backup to create. If no explicit excludes or includes are +# defined all files inside this directory will be backed up. +root: /home + +# Hostname for notifications (if left out the hostname of the machine +# is used for this. +#hostname: mystation + +### +# Backup destination +### +# +# Destination for the backup. Check the duplicity man-page for all +# possible destinations to use. +dest: s3+http://foobar-backup-bucket/backup-folder/ + +# Some examples of destinations usable for your backup: +#dest: ftp://user[:password]@other.host[:port]/some_dir +#dest: rsync://user@host.com[:port]//absolute_path +#dest: ssh://user[:password]@other.host[:port]/[/]some_dir + +# The "ftp_password" is used for several backends despite the options +# name. You can use this option instead of passing the password in the +# `dest` parameter as that one is visible in the process list during +# the backup is running. +#ftp_password: password + +### +# Amazon WebServices S3 configuration +### +# +# Uncomment the lines in this section if you're using Amazon S3 +aws: +# access_key_id: foobar_aws_key_id +# secret_access_key: foobar_aws_access_key + +# Without setting the storage class the standard storage is used. With +# this option you can switch to "infrequent access" (--s3-use-ia) or +# "reduced redundancy" (--s3-use-rrs) storage class. +# storage_class: --s3-use-ia + +### +# Google Cloud Storage configuration +### +# +# Uncomment the lines in this section if you're using GCS +google_cloud: +# access_key_id: foobar_gcs_key_id +# secret_access_key: foobar_gcs_secret_id + +### +# OpenStack Object Storage (Swift) configuration +### +# +# Uncomment the lines in this section if you're using OpenStack +swift: +# username: foobar_swift_tenant:foobar_swift_username +# password: foobar_swift_password +# auth_url: foobar_swift_authurl +# auth_version: 2 + +### +# Include list of directories +### +# +# Specify directories inside your `root` to backup only these ones +# You can't specify directories outside the root. If you want to backup +# all the data inside the root leave this list empty. +inclist: + - /home/myuser/projects + +### +# Exclude list of directories +### +# +# Specify directories inside your `root` to exclude these ones +# You can't specify directories outside the root. If you want to backup +# all the data inside the root leave this list empty. +exclist: + - /home/muser/projects/testproject + +### +# Other file selection options +### +# +# Instead managing the inclist / exclist parameters in this file you +# can write a text file containing lines with +/- patterns to include +# or exclude files and directories from the backup. +# See http://duplicity.nongnu.org/duplicity.1.html#sect9 for details +#incexcfile: /home/myuser/.config/backup-files.txt + +# Exclude all device files. This can be useful for security/permissions +# reasons or if rdiff-backup is not handling device files correctly. +#excdevicefiles: true + +### +# Encryption configuration +### +# +encryption: +# Enable / disable encryption of your backup. If you enable encryption +# you either need to specify a password or a GPG key. + enable: true + +# If you're using a `gpg_sign_key` to sign your backup this password is +# used to unlock the GPG key. If you're not using a GPG key it is used +# to symmetrically encrypt the backup. + passphrase: foobar_gpg_passphrase + +# Specify the GPG key(s) to use for encryption / signing the backup. +# You may use different keys for those tasks. If you specify a signing +# key you need to specify the password above to unlock the key. +# gpg_encryption_key: foobar_gpg_key +# gpg_sign_key: foobar_gpg_key + +# If you want to hide the GPG key IDs for security reasons you can +# enable this option. +# hide_key_id: true + +# You can specify the keyring which contains your above specified keys +# in case they are not present in the default keyring. +# secret_keyring: /home/myuser/.gnupg/duplicity.gpg + +### +# Static backup options +### +# +# Here you can specify other options for duplicity not handled in this +# configuration file. Reference is the manpage of duplicity. Please +# ensure you're specifying the options in command array format. +static_options: ["--full-if-older-than", "14D", "--s3-use-new-style"] + +### +# Backup cleanup options +### +# +cleanup: +# Chose the cleanup type and the configured value for that cleanup type: +# remove-older-than