azuki 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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