rcloner 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +54 -10
- data/lib/rcloner/backuper.rb +79 -102
- data/lib/rcloner/cli.rb +3 -2
- data/lib/rcloner/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6c1f7494bc312030d905a1cbec89e8ffd1dd89434a6ad10401d8c3c23e695354
|
4
|
+
data.tar.gz: b8b88229daf6d237b5a69024bcc18180f2032096c45f5d7351513acd219a9f05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8fdadf6c7f5a78dce6f9bf256512d1c85893f773c68f5616b7c922c8d1f219423248eb748b6b2b6d8c87e6823cac4b90c5ba152988c8b8bef077da0ca10fea1c
|
7
|
+
data.tar.gz: 06e8093496e5e0a01df644cd25c33b6eae661da09b42540ee49d91f68fe2f071ffba228b146ab5bf58748f2b4828d75e7c792f1956a74c91cc7e2ed97ff15569
|
data/README.md
CHANGED
@@ -2,17 +2,50 @@
|
|
2
2
|
|
3
3
|
> README in progress. Project is on the early stage, use it at your own risk.
|
4
4
|
|
5
|
-
Simple wrapper for
|
5
|
+
Simple wrapper for Duplicity (with optional Rclone backend) which allows to sync/restore your application files/database from a remote storage. All files stored on a remote storage are encrypted and backups are incremental, thanks to Duplicity.
|
6
|
+
|
7
|
+
Example config (`rcloner.yml`)
|
8
|
+
|
9
|
+
```yml
|
10
|
+
origin: /home/deploy/myapp
|
11
|
+
destination: webdavs://login:pass@webdav.yandex.ru/myapp_backup
|
12
|
+
include:
|
13
|
+
- 'public/images'
|
14
|
+
- 'config/master.key'
|
15
|
+
- '.env'
|
16
|
+
- 'tmp/db.dump'
|
17
|
+
on_backup:
|
18
|
+
before: 'postgressor dumpdb tmp/db.dump'
|
19
|
+
after: 'rm tmp/db.dump'
|
20
|
+
```
|
21
|
+
|
22
|
+
Where:
|
23
|
+
* `origin` is the root folder path if your application. If not provided - current directory will be taken. Can be also provided as `RCLONER_ORIGIN` env variable.
|
24
|
+
* `destination` - remote path in dyplicity format. Can be also provided as env variable `RCLONER_DESTINATION`.
|
25
|
+
* `include` if provided - only this paths will be taken for backup, not the whole `origin`. Urls should be provided in relative paths to `origin` folder.
|
26
|
+
* `on_backup` (`before`/`after`) custom optional commands/scripts to execute before/after backup. Commands will be executed into `origin` directory.
|
27
|
+
|
28
|
+
Note that rcloner by default loads env variables from .env file (if exists) in current category.
|
6
29
|
|
7
30
|
## Installation
|
8
31
|
|
9
|
-
**1)** [Install](https://rclone.org/install/) **rclone
|
32
|
+
**1)** [Install](https://rclone.org/install/) **rclone**:
|
10
33
|
|
11
|
-
|
34
|
+
```bash
|
35
|
+
$ curl https://rclone.org/install.sh | sudo bash
|
36
|
+
```
|
37
|
+
|
38
|
+
**2)** [Install](http://duplicity.nongnu.org/) **duplicity**, minimal supported version is `0.8.09`. If you're using Ubuntu, the most simple way to install latest version is via snap:
|
39
|
+
|
40
|
+
```bash
|
41
|
+
$ sudo snap install duplicity --classic
|
42
|
+
```
|
12
43
|
|
13
44
|
**3)** Install gem **rcloner**:
|
14
45
|
|
15
|
-
|
46
|
+
```bash
|
47
|
+
$ gem install rcloner
|
48
|
+
```
|
16
49
|
|
17
50
|
Or you can install gem directly from github using [specific_install](https://github.com/rdp/specific_install):
|
18
51
|
|
@@ -27,16 +60,24 @@ Another option is to add gem to your application Gemfile:
|
|
27
60
|
gem 'rcloner', git: 'https://github.com/vifreefly/rcloner', require: false
|
28
61
|
```
|
29
62
|
|
63
|
+
<!-- **4) Install gem postgressor (optional for postgres database)**:
|
64
|
+
|
65
|
+
```bash
|
66
|
+
$ gem install postgressor
|
67
|
+
```
|
68
|
+
|
30
69
|
## Configuration
|
31
70
|
|
32
71
|
First you will need to configure your Rclone `remote` storage.
|
33
72
|
|
34
73
|
1. Install rclone
|
35
|
-
2. Configure rclone remote storage (run `$ rclone config`), name it `remote`.
|
74
|
+
2. Configure rclone remote storage (run `$ rclone config`), name it `remote`. -->
|
36
75
|
|
37
76
|
## Usage
|
38
77
|
|
39
|
-
|
78
|
+
Readme in progress...
|
79
|
+
|
80
|
+
<!-- Write a config file, example:
|
40
81
|
|
41
82
|
```yml
|
42
83
|
# rcloner.yml
|
@@ -59,7 +100,7 @@ At the moment 3 types of items are supported:
|
|
59
100
|
|
60
101
|
* `file` - sync a single file
|
61
102
|
* `folder` - sync a directory. For `folder` type there is optional `duplicity` flag. If `duplicity: true`, folder will be synced using duplicity with compression. It's a good option if folder contains a lot of files which syncing each by each will take A LOT of time. Duplicity puts all the files in archive file before copying it to a remote storage.
|
62
|
-
* `pgdatabase` - sunc application database (postgres only). [Postgressor](https://github.com/vifreefly/postgressor) gem is used under the hood.
|
103
|
+
* `pgdatabase` - sunc application database (postgres only). [Postgressor](https://github.com/vifreefly/postgressor) gem is used under the hood. -->
|
63
104
|
|
64
105
|
### backup
|
65
106
|
|
@@ -67,7 +108,8 @@ To sync all items from local to remote rclone storage use `backup` command:
|
|
67
108
|
|
68
109
|
```
|
69
110
|
deploy@server:~/my_app$ rcloner backup --config rcloner.yml
|
70
|
-
|
111
|
+
```
|
112
|
+
<!--
|
71
113
|
Dumped database my_app_database to /home/deploy/my_app/tmp/my_app_database.dump file.
|
72
114
|
Synced file `my_app_database.dump` from `/home/deploy/my_app/tmp/my_app_database.dump` to `remote:my_app_backup/my_app_database.dump`
|
73
115
|
|
@@ -76,7 +118,7 @@ Synced folder `images` from `/home/deploy/my_app/public/images` to `remote:my_ap
|
|
76
118
|
Synced file `master.key` from `/home/deploy/my_app/config/master.key` to `remote:my_app_backup/master.key`
|
77
119
|
```
|
78
120
|
|
79
|
-
> Note: to backup database, Rcloner use gem [postgressor](https://github.com/vifreefly/postgressor) under the hood.
|
121
|
+
> Note: to backup database, Rcloner use gem [postgressor](https://github.com/vifreefly/postgressor) under the hood. -->
|
80
122
|
|
81
123
|
### restore
|
82
124
|
|
@@ -84,7 +126,9 @@ To sync all items from remote rclone storage to local server use `restore` comma
|
|
84
126
|
|
85
127
|
```
|
86
128
|
deploy@server:~/my_app$ rcloner restore --config rcloner.yml
|
129
|
+
```
|
87
130
|
|
131
|
+
<!--
|
88
132
|
Synced file `my_app_database.dump` from `remote:my_app_backup/my_app_database.dump` to `/home/deploy/my_app/tmp/my_app_database.dump`
|
89
133
|
|
90
134
|
Synced folder `images` from `remote:my_app_backup/images` to `/home/deploy/my_app/public/images`
|
@@ -102,7 +146,7 @@ Example:
|
|
102
146
|
|
103
147
|
```
|
104
148
|
deploy@server:~/my_app$ SWITCH_TO_SUPERUSER=true RESTORE_PGDATABASE=true rcloner restore --config rcloner.yml
|
105
|
-
```
|
149
|
+
``` -->
|
106
150
|
|
107
151
|
## How to run backup with a cron
|
108
152
|
|
data/lib/rcloner/backuper.rb
CHANGED
@@ -1,144 +1,121 @@
|
|
1
1
|
require 'uri'
|
2
|
-
require 'dotenv'
|
2
|
+
require 'dotenv/load'
|
3
|
+
require 'pathname'
|
3
4
|
|
4
5
|
module Rcloner
|
5
6
|
class Backuper
|
7
|
+
class ConfigError < StandardError; end
|
8
|
+
|
6
9
|
def initialize(config)
|
7
10
|
@config = config
|
8
|
-
@project_folder = @config['name'] + '_backup'
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
12
|
+
@config['origin'] ||= ENV['RCLONER_ORIGIN'] || ENV['PWD']
|
13
|
+
@config['destination'] ||= ENV['RCLONER_DESTINATION']
|
14
|
+
|
15
|
+
# Provide absolute paths for files in include list
|
16
|
+
if @config['include']
|
17
|
+
@config['include'] = @config['include'].map { |path| full_local_path(path) }
|
13
18
|
end
|
14
19
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
20
|
+
# If no entry provided for exclude list and include list exists,
|
21
|
+
# that probably means we should only backup these derectories,
|
22
|
+
# and skip all the rest - so for that we should provide '**' operator:
|
23
|
+
unless @config['exclude']
|
24
|
+
@config['exclude'] = ['**']
|
19
25
|
end
|
20
|
-
end
|
21
26
|
|
22
|
-
|
23
|
-
|
24
|
-
|
27
|
+
# Create tmp/ folder inside root_path:
|
28
|
+
Dir.chdir(@config['origin']) do
|
29
|
+
FileUtils.mkdir_p(File.join @config['origin'], 'tmp')
|
30
|
+
end
|
25
31
|
|
26
|
-
|
27
|
-
sync_items(to: :local)
|
32
|
+
validate_config!
|
28
33
|
end
|
29
34
|
|
30
|
-
|
35
|
+
def backup!
|
36
|
+
if before_command = @config.dig('on_backup', 'before')
|
37
|
+
puts "Perform before command: #{before_command}"
|
31
38
|
|
32
|
-
|
33
|
-
|
34
|
-
case item['type']
|
35
|
-
when 'folder'
|
36
|
-
sync_folder(item, to: to)
|
37
|
-
when 'file'
|
38
|
-
sync_file(item, to: to)
|
39
|
-
when 'pgdatabase'
|
40
|
-
sync_pgdatabase(item, to: to)
|
39
|
+
Dir.chdir(@config['origin']) do
|
40
|
+
execute before_command
|
41
41
|
end
|
42
42
|
end
|
43
|
-
end
|
44
43
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
Dir.chdir(@config['root_path']) do
|
49
|
-
`bundle exec postgressor print_db_url`.strip
|
50
|
-
end
|
51
|
-
else
|
52
|
-
item['db_url']
|
53
|
-
end
|
54
|
-
|
55
|
-
raise 'Cant read pgdatabase item db_url' unless db_url
|
44
|
+
@command = %W(duplicity)
|
45
|
+
@config['include'].each { |i| @command.push('--include', i) }
|
46
|
+
@config['exclude'].each { |i| @command.push('--exclude', i) }
|
56
47
|
|
57
|
-
|
58
|
-
|
59
|
-
local_db_backup_file_path = File.join(@config['root_path'], relative_db_backup_filepath)
|
60
|
-
item = { 'path' => relative_db_backup_filepath }
|
48
|
+
@command.push(@config['origin'], @config['destination'])
|
49
|
+
execute @command
|
61
50
|
|
62
|
-
|
63
|
-
|
64
|
-
env = { 'DATABASE_URL' => db_url }
|
65
|
-
execute %W(bundle exec postgressor dumpdb #{local_db_backup_file_path}), env: env
|
66
|
-
sync_file(item, to: :remote)
|
67
|
-
when :local
|
68
|
-
sync_file(item, to: :local)
|
51
|
+
if after_command = @config.dig('on_backup', 'after')
|
52
|
+
puts "Perform after command: #{after_command}"
|
69
53
|
|
70
|
-
|
71
|
-
|
72
|
-
command << '--switch_to_superuser' if ENV['SWITCH_TO_SUPERUSER'] == 'true'
|
73
|
-
|
74
|
-
execute command, { 'DATABASE_URL' => db_url }
|
54
|
+
Dir.chdir(@config['origin']) do
|
55
|
+
execute after_command
|
75
56
|
end
|
76
57
|
end
|
77
58
|
end
|
78
59
|
|
79
|
-
def
|
80
|
-
|
81
|
-
file_name = Pathname.new(local_file_path).basename.to_s
|
82
|
-
remote_file_path = File.join(@project_folder, file_name)
|
83
|
-
|
84
|
-
case to
|
85
|
-
when :remote
|
86
|
-
to_path = "remote:#{remote_file_path}"
|
87
|
-
from_path = local_file_path
|
88
|
-
when :local
|
89
|
-
to_path = local_file_path
|
90
|
-
from_path = "remote:#{remote_file_path}"
|
91
|
-
end
|
92
|
-
|
93
|
-
execute %W(rclone copyto #{from_path} #{to_path})
|
94
|
-
puts "Synced file `#{file_name}` from `#{from_path}` to `#{to_path}`"
|
95
|
-
end
|
96
|
-
|
97
|
-
def sync_folder(item, to:)
|
98
|
-
local_folder_path = File.join(@config['root_path'], item['path'])
|
99
|
-
folder_name = Pathname.new(local_folder_path).basename.to_s
|
100
|
-
remote_folder_path = File.join(@project_folder, folder_name)
|
101
|
-
|
102
|
-
case to
|
103
|
-
when :remote
|
104
|
-
to_path = "remote:#{remote_folder_path}"
|
105
|
-
from_path = local_folder_path
|
106
|
-
when :local
|
107
|
-
to_path = local_folder_path
|
108
|
-
from_path = "remote:#{remote_folder_path}"
|
109
|
-
end
|
60
|
+
def restore!(to = nil, force = false)
|
61
|
+
@command = %W(duplicity)
|
110
62
|
|
111
|
-
|
112
|
-
|
113
|
-
if item['duplicity']
|
114
|
-
case to
|
115
|
-
when :remote
|
116
|
-
dup_to_path = "rclone://remote:#{remote_folder_path}"
|
117
|
-
dup_from_path = local_folder_path
|
118
|
-
dup_command = %W(duplicity #{dup_from_path} #{dup_to_path})
|
119
|
-
when :local
|
120
|
-
dup_to_path = local_folder_path
|
121
|
-
dup_from_path = "rclone://remote:#{remote_folder_path}"
|
122
|
-
dup_command = %W(duplicity restore #{dup_from_path} #{dup_to_path} --force)
|
123
|
-
end
|
124
|
-
|
125
|
-
puts "Start syncing folder `#{folder_name}` from `#{from_path}` to `#{to_path}` using duplicity backend..."
|
126
|
-
execute dup_command
|
63
|
+
if to
|
64
|
+
to = File.expand_path(to)
|
127
65
|
else
|
128
|
-
|
66
|
+
to = @config['origin']
|
129
67
|
end
|
130
68
|
|
131
|
-
|
69
|
+
@command.push('restore', @config['destination'], to)
|
70
|
+
@command.push('--force') if force
|
71
|
+
execute @command
|
132
72
|
end
|
133
73
|
|
74
|
+
private
|
75
|
+
|
134
76
|
###
|
135
77
|
|
136
78
|
def execute(command, env: {}, path: nil)
|
79
|
+
if ENV['VERBOSE'] == 'true'
|
80
|
+
print_command =
|
81
|
+
if command.class == Array
|
82
|
+
command.join(' ')
|
83
|
+
else
|
84
|
+
command
|
85
|
+
end
|
86
|
+
|
87
|
+
puts "Execute: `#{print_command}`\n\n"
|
88
|
+
end
|
89
|
+
|
137
90
|
if path
|
138
91
|
system env, *command, chdir: path
|
139
92
|
else
|
140
93
|
system env, *command
|
141
94
|
end
|
142
95
|
end
|
96
|
+
|
97
|
+
###
|
98
|
+
|
99
|
+
def full_local_path(relative_path)
|
100
|
+
File.join(@config['origin'], relative_path)
|
101
|
+
end
|
102
|
+
|
103
|
+
# def create_symlink!(from_path, to_path)
|
104
|
+
# if execute %W(ln -s #{from_path} #{to_path})
|
105
|
+
# puts "Created symlink `#{from_path}` -> `#{to_path}`"
|
106
|
+
# end
|
107
|
+
# end
|
108
|
+
|
109
|
+
###
|
110
|
+
|
111
|
+
def validate_config!
|
112
|
+
if ENV['PASSPHRASE'].nil? || ENV['PASSPHRASE'].empty?
|
113
|
+
raise ConfigError, '`PASSPHRASE` env variable is not set'
|
114
|
+
end
|
115
|
+
|
116
|
+
unless @config['destination']
|
117
|
+
raise ConfigError, 'Please provide a destination option'
|
118
|
+
end
|
119
|
+
end
|
143
120
|
end
|
144
121
|
end
|
data/lib/rcloner/cli.rb
CHANGED
@@ -20,9 +20,10 @@ module Rcloner
|
|
20
20
|
|
21
21
|
desc 'restore', 'Restore all items from a remote storage'
|
22
22
|
option :config, type: :string, required: true, banner: 'Path to a config file'
|
23
|
-
|
23
|
+
option :force, type: :boolean, default: false, banner: 'Allow to overwrite existing directory'
|
24
|
+
def restore(to = nil)
|
24
25
|
backuper = create_backuper
|
25
|
-
backuper.restore!
|
26
|
+
backuper.restore!(to, options['force'])
|
26
27
|
end
|
27
28
|
|
28
29
|
private
|
data/lib/rcloner/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rcloner
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Victor Afanasev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-02-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|