azuki 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.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +71 -0
  3. data/bin/azuki +17 -0
  4. data/data/cacert.pem +3988 -0
  5. data/lib/azuki.rb +17 -0
  6. data/lib/azuki/auth.rb +339 -0
  7. data/lib/azuki/cli.rb +38 -0
  8. data/lib/azuki/client.rb +764 -0
  9. data/lib/azuki/client/azuki_postgresql.rb +141 -0
  10. data/lib/azuki/client/cisaurus.rb +26 -0
  11. data/lib/azuki/client/pgbackups.rb +113 -0
  12. data/lib/azuki/client/rendezvous.rb +108 -0
  13. data/lib/azuki/client/ssl_endpoint.rb +25 -0
  14. data/lib/azuki/command.rb +294 -0
  15. data/lib/azuki/command/account.rb +23 -0
  16. data/lib/azuki/command/accounts.rb +34 -0
  17. data/lib/azuki/command/addons.rb +305 -0
  18. data/lib/azuki/command/apps.rb +393 -0
  19. data/lib/azuki/command/auth.rb +86 -0
  20. data/lib/azuki/command/base.rb +230 -0
  21. data/lib/azuki/command/certs.rb +209 -0
  22. data/lib/azuki/command/config.rb +137 -0
  23. data/lib/azuki/command/db.rb +218 -0
  24. data/lib/azuki/command/domains.rb +85 -0
  25. data/lib/azuki/command/drains.rb +46 -0
  26. data/lib/azuki/command/fork.rb +164 -0
  27. data/lib/azuki/command/git.rb +64 -0
  28. data/lib/azuki/command/help.rb +179 -0
  29. data/lib/azuki/command/keys.rb +115 -0
  30. data/lib/azuki/command/labs.rb +147 -0
  31. data/lib/azuki/command/logs.rb +45 -0
  32. data/lib/azuki/command/maintenance.rb +61 -0
  33. data/lib/azuki/command/pg.rb +269 -0
  34. data/lib/azuki/command/pgbackups.rb +329 -0
  35. data/lib/azuki/command/plugins.rb +110 -0
  36. data/lib/azuki/command/ps.rb +232 -0
  37. data/lib/azuki/command/regions.rb +22 -0
  38. data/lib/azuki/command/releases.rb +124 -0
  39. data/lib/azuki/command/run.rb +180 -0
  40. data/lib/azuki/command/sharing.rb +89 -0
  41. data/lib/azuki/command/ssl.rb +43 -0
  42. data/lib/azuki/command/stack.rb +62 -0
  43. data/lib/azuki/command/status.rb +51 -0
  44. data/lib/azuki/command/update.rb +47 -0
  45. data/lib/azuki/command/version.rb +23 -0
  46. data/lib/azuki/deprecated.rb +5 -0
  47. data/lib/azuki/deprecated/help.rb +38 -0
  48. data/lib/azuki/distribution.rb +9 -0
  49. data/lib/azuki/excon.rb +9 -0
  50. data/lib/azuki/helpers.rb +517 -0
  51. data/lib/azuki/helpers/azuki_postgresql.rb +165 -0
  52. data/lib/azuki/helpers/log_displayer.rb +70 -0
  53. data/lib/azuki/plugin.rb +163 -0
  54. data/lib/azuki/updater.rb +171 -0
  55. data/lib/azuki/version.rb +3 -0
  56. data/lib/vendor/azuki/okjson.rb +598 -0
  57. data/spec/azuki/auth_spec.rb +256 -0
  58. data/spec/azuki/client/azuki_postgresql_spec.rb +71 -0
  59. data/spec/azuki/client/pgbackups_spec.rb +43 -0
  60. data/spec/azuki/client/rendezvous_spec.rb +62 -0
  61. data/spec/azuki/client/ssl_endpoint_spec.rb +48 -0
  62. data/spec/azuki/client_spec.rb +564 -0
  63. data/spec/azuki/command/addons_spec.rb +601 -0
  64. data/spec/azuki/command/apps_spec.rb +351 -0
  65. data/spec/azuki/command/auth_spec.rb +38 -0
  66. data/spec/azuki/command/base_spec.rb +109 -0
  67. data/spec/azuki/command/certs_spec.rb +178 -0
  68. data/spec/azuki/command/config_spec.rb +144 -0
  69. data/spec/azuki/command/db_spec.rb +110 -0
  70. data/spec/azuki/command/domains_spec.rb +87 -0
  71. data/spec/azuki/command/drains_spec.rb +34 -0
  72. data/spec/azuki/command/fork_spec.rb +56 -0
  73. data/spec/azuki/command/git_spec.rb +144 -0
  74. data/spec/azuki/command/help_spec.rb +93 -0
  75. data/spec/azuki/command/keys_spec.rb +120 -0
  76. data/spec/azuki/command/labs_spec.rb +100 -0
  77. data/spec/azuki/command/logs_spec.rb +60 -0
  78. data/spec/azuki/command/maintenance_spec.rb +51 -0
  79. data/spec/azuki/command/pg_spec.rb +236 -0
  80. data/spec/azuki/command/pgbackups_spec.rb +307 -0
  81. data/spec/azuki/command/plugins_spec.rb +104 -0
  82. data/spec/azuki/command/ps_spec.rb +195 -0
  83. data/spec/azuki/command/releases_spec.rb +130 -0
  84. data/spec/azuki/command/run_spec.rb +83 -0
  85. data/spec/azuki/command/sharing_spec.rb +59 -0
  86. data/spec/azuki/command/stack_spec.rb +46 -0
  87. data/spec/azuki/command/status_spec.rb +48 -0
  88. data/spec/azuki/command/version_spec.rb +16 -0
  89. data/spec/azuki/command_spec.rb +211 -0
  90. data/spec/azuki/helpers/azuki_postgresql_spec.rb +155 -0
  91. data/spec/azuki/helpers_spec.rb +48 -0
  92. data/spec/azuki/plugin_spec.rb +172 -0
  93. data/spec/azuki/updater_spec.rb +44 -0
  94. data/spec/helper/legacy_help.rb +16 -0
  95. data/spec/spec.opts +1 -0
  96. data/spec/spec_helper.rb +224 -0
  97. data/spec/support/display_message_matcher.rb +49 -0
  98. data/spec/support/openssl_mock_helper.rb +8 -0
  99. metadata +211 -0
@@ -0,0 +1,165 @@
1
+ require "azuki/helpers"
2
+
3
+ module Azuki::Helpers::AzukiPostgresql
4
+
5
+ extend self
6
+ extend Azuki::Helpers
7
+
8
+ class Attachment
9
+ attr_reader :config_var, :resource_name, :url, :addon, :plan
10
+ def initialize(raw)
11
+ @raw = raw
12
+ @config_var = raw['config_var']
13
+ @resource_name = raw['resource']['name']
14
+ @url = raw['resource']['value']
15
+ @addon, @plan = raw['resource']['type'].split(':')
16
+ end
17
+
18
+ def starter_plan?
19
+ plan =~ /dev|basic/
20
+ end
21
+
22
+ def display_name
23
+ config_var + (primary_attachment? ? " (DATABASE_URL)" : '')
24
+ end
25
+
26
+ def primary_attachment!
27
+ @primary_attachment = true
28
+ end
29
+
30
+ def primary_attachment?
31
+ @primary_attachment
32
+ end
33
+ end
34
+
35
+ def hpg_addon_name
36
+ if ENV['SHOGUN']
37
+ "shogun-#{ENV['SHOGUN']}"
38
+ else
39
+ ENV['AZUKI_POSTGRESQL_ADDON_NAME'] || 'azuki-postgresql'
40
+ end
41
+ end
42
+
43
+ def app_config_vars
44
+ @app_config_vars ||= api.get_config_vars(app).body
45
+ end
46
+
47
+ def app_attachments
48
+ @app_attachments ||= api.get_attachments(app).body.map { |raw| Attachment.new(raw) }
49
+ end
50
+
51
+ def hpg_databases
52
+ return @hpg_databases if @hpg_databases
53
+ pairs = app_attachments.select {|att|
54
+ att.addon == hpg_addon_name
55
+ }.map { |att|
56
+ [att.config_var, att]
57
+ }
58
+ @hpg_databases = Hash[ pairs ]
59
+
60
+ if find_database_url_real_attachment
61
+ @hpg_databases['DATABASE_URL'] = find_database_url_real_attachment
62
+ end
63
+
64
+ if app_config_vars['SHARED_DATABASE_URL']
65
+ @hpg_databases['SHARED_DATABASE'] = Attachment.new({
66
+ 'config_var' => 'SHARED_DATABASE',
67
+ 'resource' => {
68
+ 'name' => 'SHARED_DATABASE',
69
+ 'value' => app_config_vars['SHARED_DATABASE_URL'],
70
+ 'type' => 'shared:database'
71
+ }
72
+ })
73
+ end
74
+
75
+ return @hpg_databases
76
+ end
77
+
78
+ def resource_url(resource)
79
+ api.get_resource(resource).body['value']
80
+ end
81
+
82
+ def forget_config!
83
+ @hpg_databases = nil
84
+ @app_config_vars = nil
85
+ @app_attachments = nil
86
+ end
87
+
88
+ def find_database_url_real_attachment
89
+ raw_primary_db_url = app_config_vars['DATABASE_URL']
90
+ return unless raw_primary_db_url
91
+
92
+ primary_db_url = raw_primary_db_url.split("?").first
93
+ return unless primary_db_url && !primary_db_url.empty?
94
+
95
+ real_config = app_config_vars.detect {|k,v| k != 'DATABASE_URL' && v == primary_db_url }
96
+ if real_config
97
+ real = hpg_databases[real_config.first]
98
+ real.primary_attachment! if real
99
+ return real
100
+ else
101
+ return nil
102
+ end
103
+ end
104
+
105
+ def match_attachments_by_name(name)
106
+ return [] if name.empty?
107
+ return [name] if hpg_databases[name]
108
+ hpg_databases.keys.grep(%r{#{ name }}i)
109
+ end
110
+
111
+ def hpg_resolve(name, default=nil)
112
+ name = '' if name.nil?
113
+ name = 'DATABASE_URL' if name == 'DATABASE'
114
+
115
+ if hpg_databases.empty?
116
+ error("Your app has no databases.")
117
+ end
118
+
119
+ found_attachment = nil
120
+ candidates = match_attachments_by_name(name)
121
+ if default && name.empty? && app_config_vars[default]
122
+ found_attachment = hpg_databases[default]
123
+ elsif candidates.size == 1
124
+ found_attachment = hpg_databases[candidates.first]
125
+ end
126
+
127
+ if found_attachment.nil?
128
+ error("Unknown database#{': ' + name unless name.empty?}. Valid options are: #{hpg_databases.keys.sort.join(", ")}")
129
+ end
130
+
131
+ return found_attachment
132
+ end
133
+
134
+ def hpg_translate_fork_and_follow(addon, config)
135
+ if addon =~ /^#{hpg_addon_name}/
136
+ %w[fork follow].each do |opt|
137
+ if val = config[opt]
138
+ unless val.is_a?(String)
139
+ error("--#{opt} requires a database argument.")
140
+ end
141
+
142
+ uri = URI.parse(val) rescue nil
143
+ if uri && uri.scheme
144
+ argument_url = uri.to_s
145
+ else
146
+ attachment = hpg_resolve(val)
147
+ if attachment.starter_plan?
148
+ error("#{opt.tr 'f', 'F'} is only available on production databases.")
149
+ end
150
+ argument_url = attachment.url
151
+ end
152
+
153
+ config[opt] = argument_url
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ private
160
+
161
+ def hpg_promote(url)
162
+ api.put_config_vars(app, "DATABASE_URL" => url)
163
+ end
164
+
165
+ end
@@ -0,0 +1,70 @@
1
+ require "azuki/helpers"
2
+
3
+ module Azuki::Helpers
4
+ class LogDisplayer
5
+
6
+ include Azuki::Helpers
7
+
8
+ attr_reader :azuki, :app, :opts
9
+
10
+ def initialize(azuki, app, opts)
11
+ @azuki, @app, @opts = azuki, app, opts
12
+ end
13
+
14
+ def display_logs
15
+ @assigned_colors = {}
16
+ @line_start = true
17
+ @token = nil
18
+
19
+ azuki.read_logs(app, opts) do |chunk|
20
+ unless chunk.empty?
21
+ if STDOUT.isatty && ENV.has_key?("TERM")
22
+ display(colorize(chunk))
23
+ else
24
+ display(chunk)
25
+ end
26
+ end
27
+ end
28
+ rescue Errno::EPIPE
29
+ rescue Interrupt => interrupt
30
+ if STDOUT.isatty && ENV.has_key?("TERM")
31
+ display("\e[0m")
32
+ end
33
+ raise(interrupt)
34
+ end
35
+
36
+ COLORS = %w( cyan yellow green magenta red )
37
+ COLOR_CODES = {
38
+ "red" => 31,
39
+ "green" => 32,
40
+ "yellow" => 33,
41
+ "magenta" => 35,
42
+ "cyan" => 36,
43
+ }
44
+
45
+ def colorize(chunk)
46
+ lines = []
47
+ chunk.split("\n").map do |line|
48
+ if parsed_line = parse_log(line)
49
+ header, identifier, body = parsed_line
50
+ @assigned_colors[identifier] ||= COLORS[@assigned_colors.size % COLORS.size]
51
+ lines << [
52
+ "\e[#{COLOR_CODES[@assigned_colors[identifier]]}m",
53
+ header,
54
+ "\e[0m",
55
+ body,
56
+ ].join("")
57
+ elsif not line.empty?
58
+ lines << line
59
+ end
60
+ end
61
+ lines.join("\n")
62
+ end
63
+
64
+ def parse_log(log)
65
+ return unless parsed = log.match(/^(.*?\[(\w+)([\d\.]+)?\]:)(.*)?$/)
66
+ [1, 2, 4].map { |i| parsed[i] }
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,163 @@
1
+ # based on the Rails Plugin
2
+
3
+ module Azuki
4
+ class Plugin
5
+ include Azuki::Helpers
6
+ extend Azuki::Helpers
7
+
8
+ class ErrorUpdatingSymlinkPlugin < StandardError; end
9
+
10
+ DEPRECATED_PLUGINS = %w(
11
+ azuki-cedar
12
+ azuki-certs
13
+ azuki-credentials
14
+ azuki-kill
15
+ azuki-labs
16
+ azuki-logging
17
+ azuki-netrc
18
+ azuki-pgdumps
19
+ azuki-postgresql
20
+ azuki-releases
21
+ azuki-shared-postgresql
22
+ azuki-sql-console
23
+ azuki-status
24
+ azuki-stop
25
+ azuki-suggest
26
+ pgbackups-automate
27
+ pgcmd
28
+ azuki-fork
29
+ )
30
+
31
+ attr_reader :name, :uri
32
+
33
+ def self.directory
34
+ File.expand_path("#{home_directory}/.azuki/plugins")
35
+ end
36
+
37
+ def self.list
38
+ Dir["#{directory}/*"].sort.map do |folder|
39
+ File.basename(folder)
40
+ end
41
+ end
42
+
43
+ def self.load!
44
+ list.each do |plugin|
45
+ check_for_deprecation(plugin)
46
+ next if skip_plugins.include?(plugin)
47
+ load_plugin(plugin)
48
+ end
49
+ # check to see if we are using ddollar/azuki-accounts
50
+ if list.include?('azuki-accounts') && Azuki::Auth.methods.include?(:fetch_from_account)
51
+ # setup netrc to match the default, if one exists
52
+ if default_account = %x{ git config azuki.account }.chomp
53
+ account = Azuki::Auth.extract_account rescue nil
54
+ if account && Azuki::Auth.read_credentials != [Azuki::Auth.user, Azuki::Auth.password]
55
+ Azuki::Auth.credentials = [Azuki::Auth.user, Azuki::Auth.password]
56
+ Azuki::Auth.write_credentials
57
+ load("#{File.dirname(__FILE__)}/command/accounts.rb")
58
+ # kill memoization in case '--account' was passed
59
+ Azuki::Auth.instance_variable_set(:@account, nil)
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ def self.load_plugin(plugin)
66
+ begin
67
+ folder = "#{self.directory}/#{plugin}"
68
+ $: << "#{folder}/lib" if File.directory? "#{folder}/lib"
69
+ load "#{folder}/init.rb" if File.exists? "#{folder}/init.rb"
70
+ rescue ScriptError, StandardError => error
71
+ styled_error(error, "Unable to load plugin #{plugin}.")
72
+ false
73
+ end
74
+ end
75
+
76
+ def self.remove_plugin(plugin)
77
+ FileUtils.rm_rf("#{self.directory}/#{plugin}")
78
+ end
79
+
80
+ def self.check_for_deprecation(plugin)
81
+ return unless STDIN.isatty
82
+
83
+ if DEPRECATED_PLUGINS.include?(plugin)
84
+ if confirm "The plugin #{plugin} has been deprecated. Would you like to remove it? (y/N)"
85
+ remove_plugin(plugin)
86
+ end
87
+ end
88
+ end
89
+
90
+ def self.skip_plugins
91
+ @skip_plugins ||= ENV["SKIP_PLUGINS"].to_s.split(/[ ,]/)
92
+ end
93
+
94
+ def initialize(uri)
95
+ @uri = uri
96
+ guess_name(uri)
97
+ end
98
+
99
+ def to_s
100
+ name
101
+ end
102
+
103
+ def path
104
+ "#{self.class.directory}/#{name}"
105
+ end
106
+
107
+ def install
108
+ if File.directory?(path)
109
+ uninstall
110
+ end
111
+ FileUtils.mkdir_p(self.class.directory)
112
+ Dir.chdir(self.class.directory) do
113
+ git("clone #{uri}")
114
+ unless $?.success?
115
+ FileUtils.rm_rf path
116
+ return false
117
+ end
118
+ end
119
+ true
120
+ end
121
+
122
+ def uninstall
123
+ ensure_plugin_exists
124
+ FileUtils.rm_r(path)
125
+ end
126
+
127
+ def update
128
+ ensure_plugin_exists
129
+ if File.symlink?(path)
130
+ raise Azuki::Plugin::ErrorUpdatingSymlinkPlugin
131
+ else
132
+ Dir.chdir(path) do
133
+ unless git('config --get branch.master.remote').empty?
134
+ message = git("pull")
135
+ unless $?.success?
136
+ error("Unable to update #{name}.\n" + message)
137
+ end
138
+ else
139
+ error(<<-ERROR)
140
+ #{name} is a legacy plugin installation.
141
+ Enable updating by reinstalling with `azuki plugins:install`.
142
+ ERROR
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ private
149
+
150
+ def ensure_plugin_exists
151
+ unless File.directory?(path)
152
+ error("#{name} plugin not found.")
153
+ end
154
+ end
155
+
156
+ def guess_name(url)
157
+ @name = File.basename(url)
158
+ @name = File.basename(File.dirname(url)) if @name.empty?
159
+ @name.gsub!(/\.git$/, '') if @name =~ /\.git$/
160
+ end
161
+
162
+ end
163
+ end
@@ -0,0 +1,171 @@
1
+ require "digest"
2
+ require "fileutils"
3
+ require "azuki/helpers"
4
+
5
+ module Azuki
6
+ module Updater
7
+
8
+ def self.error(message)
9
+ raise Azuki::Command::CommandFailed.new(message)
10
+ end
11
+
12
+ def self.updating_lock_path
13
+ File.join(Azuki::Helpers.home_directory, ".azuki", "updating")
14
+ end
15
+
16
+ def self.installed_client_path
17
+ File.expand_path("../../..", __FILE__)
18
+ end
19
+
20
+ def self.updated_client_path
21
+ File.join(Azuki::Helpers.home_directory, ".azuki", "client")
22
+ end
23
+
24
+ def self.latest_local_version
25
+ installed_version = client_version_from_path(installed_client_path)
26
+ updated_version = client_version_from_path(updated_client_path)
27
+ if compare_versions(updated_version, installed_version) > 0
28
+ updated_version
29
+ else
30
+ installed_version
31
+ end
32
+ end
33
+
34
+ def self.client_version_from_path(path)
35
+ version_file = File.join(path, "lib/azuki/version.rb")
36
+ if File.exists?(version_file)
37
+ File.read(version_file).match(/VERSION = "([^"]+)"/)[1]
38
+ else
39
+ '0.0.0'
40
+ end
41
+ end
42
+
43
+ def self.disable(message=nil)
44
+ @disable = message if message
45
+ @disable
46
+ end
47
+
48
+ def self.check_disabled!
49
+ if disable
50
+ Azuki::Helpers.error(disable)
51
+ end
52
+ end
53
+
54
+ def self.wait_for_lock(path, wait_for=5, check_every=0.5)
55
+ start = Time.now.to_i
56
+ while File.exists?(path)
57
+ sleep check_every
58
+ if (Time.now.to_i - start) > wait_for
59
+ Azuki::Helpers.error "Unable to acquire update lock"
60
+ end
61
+ end
62
+ begin
63
+ FileUtils.touch path
64
+ ret = yield
65
+ ensure
66
+ FileUtils.rm_f path
67
+ end
68
+ ret
69
+ end
70
+
71
+ def self.autoupdate?
72
+ true
73
+ end
74
+
75
+ def self.update(url, autoupdate=false)
76
+ wait_for_lock(updating_lock_path, 5) do
77
+ require "excon"
78
+ require "azuki"
79
+ require "azuki/excon"
80
+ require "tmpdir"
81
+ require "zip/zip"
82
+
83
+ latest_version = Excon.get_with_redirect("http://assets.azukiapp.com/azuki-client/VERSION", :nonblock => false).body.chomp
84
+
85
+ if compare_versions(latest_version, latest_local_version) > 0
86
+ Dir.mktmpdir do |download_dir|
87
+ File.open("#{download_dir}/azuki.zip", "wb") do |file|
88
+ file.print Excon.get_with_redirect(url, :nonblock => false).body
89
+ end
90
+
91
+ hash = Digest::SHA256.file("#{download_dir}/azuki.zip").hexdigest
92
+ official_hash = Excon.get_with_redirect("https://toolbelt.azukiapp.com/update/hash", :nonblock => false).body.chomp
93
+
94
+ error "Update hash signature mismatch" unless hash == official_hash
95
+
96
+ Zip::ZipFile.open("#{download_dir}/azuki.zip") do |zip|
97
+ zip.each do |entry|
98
+ target = File.join(download_dir, entry.to_s)
99
+ FileUtils.mkdir_p File.dirname(target)
100
+ zip.extract(entry, target) { true }
101
+ end
102
+ end
103
+
104
+ FileUtils.rm "#{download_dir}/azuki.zip"
105
+
106
+ old_version = latest_local_version
107
+ new_version = client_version_from_path(download_dir)
108
+
109
+ if compare_versions(new_version, old_version) < 0 && !autoupdate
110
+ Azuki::Helpers.error("Installed version (#{old_version}) is newer than the latest available update (#{new_version})")
111
+ end
112
+
113
+ FileUtils.rm_rf updated_client_path
114
+ FileUtils.mkdir_p File.dirname(updated_client_path)
115
+ FileUtils.cp_r download_dir, updated_client_path
116
+
117
+ new_version
118
+ end
119
+ else
120
+ false # already up to date
121
+ end
122
+ end
123
+ ensure
124
+ FileUtils.rm_f(updating_lock_path)
125
+ end
126
+
127
+ def self.compare_versions(first_version, second_version)
128
+ first_version.split('.').map {|part| Integer(part) rescue part} <=> second_version.split('.').map {|part| Integer(part) rescue part}
129
+ end
130
+
131
+ def self.inject_libpath
132
+ old_version = client_version_from_path(installed_client_path)
133
+ new_version = client_version_from_path(updated_client_path)
134
+
135
+ if compare_versions(new_version, old_version) > 0
136
+ $:.unshift File.join(updated_client_path, "lib")
137
+ vendored_gems = Dir[File.join(updated_client_path, "vendor", "gems", "*")]
138
+ vendored_gems.each do |vendored_gem|
139
+ $:.unshift File.join(vendored_gem, "lib")
140
+ end
141
+ load('azuki/updater.rb') # reload updated updater
142
+ end
143
+
144
+ background_update!
145
+ end
146
+
147
+ def self.last_autoupdate_path
148
+ File.join(Azuki::Helpers.home_directory, ".azuki", "autoupdate.last")
149
+ end
150
+
151
+ def self.background_update!
152
+ # if we've updated in the last 300 seconds, dont try again
153
+ if File.exists?(last_autoupdate_path)
154
+ return if (Time.now.to_i - File.mtime(last_autoupdate_path).to_i) < 300
155
+ end
156
+ log_path = File.join(Azuki::Helpers.home_directory, '.azuki', 'autoupdate.log')
157
+ FileUtils.mkdir_p File.dirname(log_path)
158
+ azuki_binary = File.expand_path($0)
159
+ pid = if defined?(RUBY_VERSION) and RUBY_VERSION =~ /^1\.8\.\d+/
160
+ fork do
161
+ exec("\"#{azuki_binary}\" update &> #{log_path} 2>&1")
162
+ end
163
+ else
164
+ spawn("\"#{azuki_binary}\" update", {:err => log_path, :out => log_path})
165
+ end
166
+ Process.detach(pid)
167
+ FileUtils.mkdir_p File.dirname(last_autoupdate_path)
168
+ FileUtils.touch last_autoupdate_path
169
+ end
170
+ end
171
+ end