ghost-backup 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: dc4061b469cecc705d4778038378fb28d280d4dd
4
+ data.tar.gz: f6bf6f78cf4a3f84d0e37993951d937df7eef8c5
5
+ SHA512:
6
+ metadata.gz: 70f52837c26735267a5618fe138628cf102dd070c5aa349c5dfd8a4d599af28f4eb8be59055163fd9bc39f446c3c590c2847b4e3236b026c652589317144c198
7
+ data.tar.gz: 3a5fe36c990bfba123e7d322d1bd8f189fb3cb009a8e3afaaea30c1a6368c39ce6ae38f82edb3f58bdbdb50eabefcd9ea137c3e48f5f688990ef2621689c216f
@@ -0,0 +1,40 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /vendor/bundle
26
+ /lib/bundler/man/
27
+
28
+ # for a library or gem, you might want to ignore these files since the code is
29
+ # intended to run in multiple environments; otherwise, check them in:
30
+ # Gemfile.lock
31
+ # .ruby-version
32
+ # .ruby-gemset
33
+
34
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
35
+ .rvmrc
36
+
37
+ Gemfile.lock
38
+ *.yml
39
+ *.json
40
+ contents
data/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ Copyright (c) 2015, Min RK
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ * Neither the name of ghost-backup nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
@@ -0,0 +1,69 @@
1
+ # ghost-backup
2
+
3
+ Backup [ghost](https://ghost.org/) blog data.
4
+
5
+ This does two things:
6
+
7
+ 1. Download the JSON export of the ghost post database
8
+ 2. Make a copy of the ghost content directory (image assets, etc.)
9
+
10
+ Part 2 is only run
11
+
12
+ ## Install
13
+
14
+ gem install ghost-backup
15
+
16
+ ## Usage
17
+
18
+ ### Config
19
+
20
+ The first step is to generate `ghost-backup.yml`:
21
+
22
+ $> ghost-backup config
23
+ Writing config to ghost-backup.yml
24
+
25
+ The edit this file:
26
+
27
+ ```yaml
28
+ ---
29
+ username:
30
+ backup_dir: ghost-backup/%Y-%m-%d-%H%M%S
31
+ ghost_dir: /var/www/ghost
32
+ refresh_token:
33
+ base_url: https://localhost
34
+ ```
35
+
36
+ - **username:** the username you use to login to the ghost server (optional)
37
+ - **backup_dir:** where backups should go. This passed to `strftime` at expor time
38
+ - **ghost_dir:** where on the local filesystem ghost is installed. If this location does not exist,
39
+ local assets will not be backed up, only the post database.
40
+ - **refresh_token:** ignore; this will be populated by the program when you login.
41
+ - **base_url:** the base URL of your ghost instance, e.g. `https://myblog.plumbing`
42
+
43
+ ### Login
44
+
45
+ Once you have your `ghost-backup.yml`, you can login to ghost:
46
+
47
+ $> ghost-backup login
48
+ Logging in to https://my.blog/ghost/api/v0.1/authentication/token
49
+ Need username, password to generate new token (only used once, not stored).
50
+ Username: |you@email.com|
51
+ Password (not stored): *****
52
+ Writing config to ghost-backup.yml
53
+
54
+ Ghost tokens expire after a week, so to keep them alive you have to login at least once a week:
55
+
56
+ $> ghost-backup login
57
+ Logging in to https://my.blog/ghost/api/v0.1/authentication/token
58
+
59
+ Unless your token expires, logins after the first will not require a username or password.
60
+
61
+ ### Backup
62
+
63
+ Now you can run a backup:
64
+
65
+ $> ghost-backup
66
+ Backing up 'https://my.blog' to '/backup/ghost-backup-2015-09-15-1306'
67
+ Copying /var/www/ghost/content → /backup/ghost-backup-2015-09-15-1306/content
68
+ Downloading ghost database https://blog.jupyter.org/ghost/api/v0.1/db/
69
+ Backing up database to '/backup/ghost-backup-2015-09-15-1306/ghost-db.json'
@@ -0,0 +1,224 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright (c) Min RK.
4
+ # Distributed under the terms of the Modified BSD License.
5
+
6
+ require 'date'
7
+ require 'json'
8
+ require 'yaml'
9
+
10
+ # gems
11
+ require 'rest-client'
12
+ require 'commander/import'
13
+ require 'highline/import'
14
+
15
+ program :version, '0.0.1'
16
+ program :description, 'Backup ghost blog data'
17
+
18
+ class GhostBackup
19
+ CONFIG_KEYS = [
20
+ 'username',
21
+ 'backup_dir',
22
+ 'ghost_dir',
23
+ 'refresh_token',
24
+ 'base_url',
25
+ ]
26
+
27
+ API_VERSION = 'v0.1'
28
+
29
+ def initialize(config_file: "ghost-backup.yml")
30
+ @dirty = false
31
+ if config_file.nil?
32
+ @config_file = "ghost-backup.yml"
33
+ else
34
+ @config_file = config_file
35
+ end
36
+ @username = nil
37
+ # backup_dir is passed to strftime
38
+ @backup_dir = 'ghost-backup/%Y-%m-%d-%H%M%S'
39
+ @ghost_dir = "/var/www/ghost"
40
+ @base_url = 'https://localhost'
41
+ @refresh_token = nil
42
+ load_config
43
+ unless @base_url.include? '://'
44
+ @base_url = 'https://' + @base_url
45
+ end
46
+ end
47
+
48
+ attr_accessor :config_file
49
+
50
+ # gen config as a hash
51
+ def cfg
52
+ Hash[
53
+ CONFIG_KEYS.map { |key|
54
+ [key, self.instance_variable_get("@#{key}")]
55
+ }
56
+ ]
57
+ end
58
+
59
+ # load yaml config file
60
+ def load_config
61
+ if not File.exists? @config_file
62
+ @dirty = true
63
+ return
64
+ end
65
+ d = YAML.load_file @config_file
66
+ if not d
67
+ return
68
+ end
69
+ if not d.instance_of? Hash
70
+ throw Error("Bad config: #{config_file}")
71
+ end
72
+ d.each_pair do |key, value|
73
+ self.instance_variable_set("@#{key}", value)
74
+ end
75
+ end
76
+
77
+ # save yaml config file, if there have been changes
78
+ def save_config
79
+ return unless @dirty
80
+ puts "Writing config to #{@config_file}"
81
+ File.open(@config_file, 'w') do |f|
82
+ f.write(YAML.dump(cfg))
83
+ end
84
+ @dirty = false
85
+ end
86
+
87
+ # the base API url
88
+ def api_url
89
+ "#{@base_url}/ghost/api/#{API_VERSION}"
90
+ end
91
+
92
+ # the url for getting a new token
93
+ def token_url
94
+ "#{api_url}/authentication/token"
95
+ end
96
+
97
+ # the url for downloading the database as JSON
98
+ def db_url
99
+ "#{api_url}/db/"
100
+ end
101
+
102
+ # generate a new token with username/password via API request
103
+ def new_token(username, password)
104
+ reply = JSON.parse RestClient.post token_url, {
105
+ :grant_type => 'password',
106
+ :username => username,
107
+ :password => password,
108
+ :client_id => 'ghost-admin',
109
+ }
110
+ @refresh_token = reply['refresh_token']
111
+ @access_token = reply['access_token']
112
+ @dirty = true
113
+ end
114
+
115
+ # refresh authorization token
116
+ def refresh
117
+ reply = JSON.parse RestClient.post token_url, {
118
+ :grant_type => 'refresh_token',
119
+ :refresh_token => @refresh_token,
120
+ :client_id => 'ghost-admin',
121
+ }
122
+ @access_token = reply['access_token']
123
+ end
124
+
125
+ # ensure we are logged in
126
+ # refreshes token if we are,
127
+ # prompts for user/password if we are not
128
+ def authorize
129
+ if @refresh_token
130
+ begin
131
+ refresh
132
+ rescue RestClient::Exception
133
+ STDERR.puts "refresh token expired"
134
+ @refresh_token = nil
135
+ end
136
+ end
137
+
138
+ if @refresh_token.nil?
139
+ puts "Need username, password to generate new token (only used once, not stored)."
140
+ username = @username = ask("Username: ") do |q|
141
+ q.default = @username
142
+ q.validate = /\S+/
143
+ end
144
+ password = ask("Password (not stored): ") { |q| q.echo = '*' }
145
+ new_token(username, password)
146
+ puts "Login success!"
147
+ end
148
+ end
149
+
150
+ # backup methods
151
+
152
+ # download the JSON dump of the database
153
+ def download_db
154
+ puts "Downloading ghost database #{db_url}"
155
+ if @access_token.nil?
156
+ authorize
157
+ end
158
+ RestClient.get db_url, {:params => {:access_token => @access_token}}
159
+ end
160
+
161
+ # run a real backup
162
+ def backup
163
+ now = DateTime.now
164
+ dest_root = now.strftime(@backup_dir)
165
+ puts "Backing up '#{@base_url}' to '#{dest_root}'"
166
+ FileUtils.mkdir_p(dest_root)
167
+ if File.exists? @ghost_dir
168
+ from = File.join(@ghost_dir, 'content')
169
+ to = File.join(dest_root, 'content')
170
+ puts "Copying #{from} → #{to}"
171
+ FileUtils.cp_r(from, to)
172
+ else
173
+ STDERR.puts "Ghost directory '#{@ghost_dir}' doesn't exist, not backing up assets."
174
+ end
175
+ db_dest = File.join(dest_root, "ghost-db.json")
176
+ db = download_db
177
+ puts "Backing up database to '#{db_dest}'"
178
+ File.open(db_dest, 'w') { |f| f.write(db) }
179
+ end
180
+ end
181
+
182
+ global_option('-f', '--config-file CONFIG_FILE', 'Specify config file to use')
183
+
184
+
185
+ command :login do |c|
186
+ c.syntax = 'ghost-backup login'
187
+ c.summary = 'Login to the ghost server'
188
+ c.description = 'Login to the ghost server and generate an auth token.
189
+ On first run, requires a username and password,
190
+ and records the refresh token in the config file.
191
+ Must be run at least once a week to keep the token active
192
+ and avoid re-entering username and password.'
193
+ c.action do |args, options|
194
+ gb = GhostBackup.new(:config_file => options.config_file)
195
+ puts "Logging in to #{gb.token_url}"
196
+ gb.authorize
197
+ gb.save_config
198
+ end
199
+ end
200
+
201
+
202
+ command :backup do |c|
203
+ c.syntax = 'ghost-backup backup'
204
+ c.summary = 'backup a ghost blog instance'
205
+ c.description = 'Downloads the post database and, if available, copies the local assets directory.'
206
+ c.action do |args, options|
207
+ gb = GhostBackup.new(:config_file => options.config_file)
208
+ gb.backup
209
+ gb.save_config
210
+ end
211
+ end
212
+
213
+
214
+ command :config do |c|
215
+ c.syntax = 'ghost-backup config'
216
+ c.summary = 'Generate default ghost-backup.yml'
217
+ c.description = 'Generate default ghost-backup.yml. Edit this file to configure the backup.'
218
+ c.action do |args, options|
219
+ gb = GhostBackup.new(:config_file => options.config_file)
220
+ gb.save_config
221
+ end
222
+ end
223
+
224
+ default_command :backup
@@ -0,0 +1,19 @@
1
+ require 'date'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'ghost-backup'
5
+ s.version = '0.0.1'
6
+ s.date = Date.today.to_s
7
+ s.summary = 'Backup ghost blog data'
8
+ s.description = 'Dump ghost database and assets to local disk'
9
+ s.authors = ['Min RK']
10
+ s.email = 'benjaminrk@gmail.com'
11
+ s.homepage = 'http://github.com/minrk/ghost-backup'
12
+ s.license = 'BSD'
13
+ s.executables = ['ghost-backup']
14
+ s.files = `git ls-files`.split($/)
15
+ s.require_paths = ['.']
16
+ s.add_runtime_dependency 'commander', '~> 4.3'
17
+ s.add_runtime_dependency 'highline', '~> 1.7'
18
+ s.add_runtime_dependency 'rest-client', '~> 1.8'
19
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ghost-backup
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Min RK
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-09-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: commander
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: highline
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rest-client
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.8'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.8'
55
+ description: Dump ghost database and assets to local disk
56
+ email: benjaminrk@gmail.com
57
+ executables:
58
+ - ghost-backup
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - LICENSE
64
+ - README.md
65
+ - bin/ghost-backup
66
+ - ghost-backup.gemspec
67
+ homepage: http://github.com/minrk/ghost-backup
68
+ licenses:
69
+ - BSD
70
+ metadata: {}
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - "."
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubyforge_project:
87
+ rubygems_version: 2.4.5.1
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: Backup ghost blog data
91
+ test_files: []
92
+ has_rdoc: