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 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