rcloner 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|