ghost-backup 0.0.1

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.
@@ -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: