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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ceb2a6a4709db5bbf91211533d0ab6d7d036f396d49fa4fe282f0418b191eca3
4
- data.tar.gz: fc7b5aa17b7533b475d926f21cd4f66eccec39a5adc11687c78f2de8d39bc85e
3
+ metadata.gz: 6c1f7494bc312030d905a1cbec89e8ffd1dd89434a6ad10401d8c3c23e695354
4
+ data.tar.gz: b8b88229daf6d237b5a69024bcc18180f2032096c45f5d7351513acd219a9f05
5
5
  SHA512:
6
- metadata.gz: a977239d78a49ac3c6d82127ad3951d5af983de183e111916a587a18141e90e1e219eb9ea6ca783d68d664d622678a52e6892145cdfb104a43c4ea6603777806
7
- data.tar.gz: 8c1ccbcb343be1f852b574af0c59c08e0b6863f07b6567c4787bb46f3315ea32b8852b6f1d86ff8cf64f225fff910eade0a37513a21c9af8c44ca032993e08df
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 Rclone (with optional Duplicity backend for Rclone) which allows to sync/restore your application files/database.
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
- **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: `$ sudo snap install duplicity --classic`
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
- `$ gem install rcloner`
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
- Write a config file, example:
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
 
@@ -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
- # Create tmp/ folder insite root_path:
11
- Dir.chdir(@config['root_path']) do
12
- FileUtils.mkdir_p(File.join @config['root_path'], 'tmp')
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
- if @config['items'].any? { |item| item['duplicity'] }
16
- if ENV['PASSPHRASE'].nil? || ENV['PASSPHRASE'].empty?
17
- raise "One of your items has duplicity backend but `PASSPHRASE` env variable is not set"
18
- end
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
- def backup!
23
- sync_items(to: :remote)
24
- end
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
- def restore!
27
- sync_items(to: :local)
32
+ validate_config!
28
33
  end
29
34
 
30
- private
35
+ def backup!
36
+ if before_command = @config.dig('on_backup', 'before')
37
+ puts "Perform before command: #{before_command}"
31
38
 
32
- def sync_items(to:)
33
- @config['items'].each do |item|
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
- def sync_pgdatabase(item, to:)
46
- db_url =
47
- if item['read_db_url_from_env']
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
- db_backup_filename = URI.parse(db_url).path.sub('/', '') + '.dump'
58
- relative_db_backup_filepath = 'tmp/' + db_backup_filename
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
- case to
63
- when :remote
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
- if ENV['RESTORE_PGDATABASE'] == 'true'
71
- command = %W(bundle exec postgressor restoredb #{local_db_backup_file_path})
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 sync_file(item, to:)
80
- local_file_path = File.join(@config['root_path'], item['path'])
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
- execute %W(rclone mkdir #{to_path})
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
- execute %W(rclone sync #{from_path} #{to_path})
66
+ to = @config['origin']
129
67
  end
130
68
 
131
- puts "Synced folder `#{folder_name}` from `#{from_path}` to `#{to_path}`"
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
- def restore
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
@@ -1,3 +1,3 @@
1
1
  module Rcloner
2
- VERSION = "0.1.0"
2
+ VERSION = "0.3.0"
3
3
  end
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.1.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: 2020-05-11 00:00:00.000000000 Z
11
+ date: 2021-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor