machines 0.5.4 → 0.5.6

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 (93) hide show
  1. data/.yardopts +7 -1
  2. data/CHANGELOG.md +16 -4
  3. data/INSTALL.md +3 -0
  4. data/LICENSE +1 -2
  5. data/README.md +47 -28
  6. data/Rakefile +0 -8
  7. data/TODO.md +66 -59
  8. data/bin/machines +1 -2
  9. data/lib/machines.rb +16 -1
  10. data/lib/machines/app_settings.rb +1 -1
  11. data/lib/machines/cloud_machine.rb +1 -1
  12. data/lib/machines/command.rb +0 -2
  13. data/lib/machines/commandline.rb +23 -29
  14. data/lib/machines/commands/checks.rb +67 -0
  15. data/lib/machines/commands/configuration.rb +50 -0
  16. data/lib/machines/commands/database.rb +18 -0
  17. data/lib/machines/commands/file_operations.rb +105 -0
  18. data/lib/machines/commands/installation.rb +184 -0
  19. data/lib/machines/commands/questions.rb +16 -0
  20. data/lib/machines/commands/services.rb +26 -0
  21. data/lib/machines/core.rb +55 -25
  22. data/lib/machines/logger.rb +0 -2
  23. data/lib/machines/named_buffer.rb +7 -6
  24. data/lib/machines/version.rb +1 -1
  25. data/lib/packages/awstats.rb +2 -2
  26. data/lib/packages/docky.rb +0 -1
  27. data/lib/packages/dwm.rb +5 -0
  28. data/lib/packages/nginx_logrotate.rb +2 -2
  29. data/lib/packages/timezone.rb +2 -4
  30. data/lib/template/Machinesfile +2 -1
  31. data/lib/template/config.yml +3 -0
  32. data/spec/lib/machines/app_settings_spec.rb +13 -12
  33. data/spec/lib/machines/cloud_machine_spec.rb +9 -8
  34. data/spec/lib/machines/commandline_spec.rb +69 -90
  35. data/spec/lib/machines/{checks_spec.rb → commands/checks_spec.rb} +1 -1
  36. data/spec/lib/machines/{configuration_spec.rb → commands/configuration_spec.rb} +2 -3
  37. data/spec/lib/machines/{database_spec.rb → commands/database_spec.rb} +4 -10
  38. data/spec/lib/machines/{file_operations_spec.rb → commands/file_operations_spec.rb} +3 -7
  39. data/spec/lib/machines/{installation_spec.rb → commands/installation_spec.rb} +10 -4
  40. data/spec/lib/machines/{questions_spec.rb → commands/questions_spec.rb} +1 -3
  41. data/spec/lib/machines/{services_spec.rb → commands/services_spec.rb} +1 -4
  42. data/spec/lib/machines/core_spec.rb +81 -65
  43. data/spec/lib/packages/abiword_spec.rb +1 -5
  44. data/spec/lib/packages/amazon_mp3_spec.rb +0 -4
  45. data/spec/lib/packages/awstats_spec.rb +3 -4
  46. data/spec/lib/packages/base_spec.rb +0 -1
  47. data/spec/lib/packages/chrome_spec.rb +0 -4
  48. data/spec/lib/packages/cruisecontrol_spec.rb +1 -2
  49. data/spec/lib/packages/dependencies_spec.rb +1 -2
  50. data/spec/lib/packages/docky_spec.rb +0 -4
  51. data/spec/lib/packages/dotfiles_spec.rb +5 -4
  52. data/spec/lib/packages/dwm_spec.rb +23 -0
  53. data/spec/lib/packages/file_roller_spec.rb +1 -5
  54. data/spec/lib/packages/firefox_spec.rb +0 -4
  55. data/spec/lib/packages/gedit_spec.rb +1 -5
  56. data/spec/lib/packages/git_spec.rb +0 -4
  57. data/spec/lib/packages/gmate_spec.rb +1 -5
  58. data/spec/lib/packages/gnome_spec.rb +0 -4
  59. data/spec/lib/packages/gnumeric_spec.rb +1 -5
  60. data/spec/lib/packages/hosts_spec.rb +0 -1
  61. data/spec/lib/packages/load_machines_spec.rb +16 -15
  62. data/spec/lib/packages/monit_spec.rb +0 -1
  63. data/spec/lib/packages/mysql_spec.rb +1 -3
  64. data/spec/lib/packages/nginx_logrotate_spec.rb +17 -18
  65. data/spec/lib/packages/nginx_spec.rb +0 -1
  66. data/spec/lib/packages/openbox_spec.rb +0 -4
  67. data/spec/lib/packages/passenger_nginx_spec.rb +0 -1
  68. data/spec/lib/packages/passenger_spec.rb +0 -1
  69. data/spec/lib/packages/postfix_spec.rb +1 -5
  70. data/spec/lib/packages/questions_spec.rb +3 -4
  71. data/spec/lib/packages/rbenv_spec.rb +1 -4
  72. data/spec/lib/packages/rvm_spec.rb +1 -4
  73. data/spec/lib/packages/save_machines_spec.rb +0 -1
  74. data/spec/lib/packages/slim_spec.rb +1 -2
  75. data/spec/lib/packages/sqlserver_spec.rb +0 -4
  76. data/spec/lib/packages/timezone_spec.rb +2 -3
  77. data/spec/lib/packages/unison_spec.rb +1 -2
  78. data/spec/lib/packages/virtualbox_guest_spec.rb +0 -4
  79. data/spec/lib/packages/virtualbox_spec.rb +1 -2
  80. data/spec/lib/packages/webapps_spec.rb +1 -3
  81. data/spec/spec_helper.rb +59 -61
  82. data/spec/support/minitest.rb +4 -62
  83. metadata +27 -28
  84. data/lib/machines/base.rb +0 -13
  85. data/lib/machines/checks.rb +0 -63
  86. data/lib/machines/configuration.rb +0 -49
  87. data/lib/machines/database.rb +0 -17
  88. data/lib/machines/file_operations.rb +0 -104
  89. data/lib/machines/installation.rb +0 -171
  90. data/lib/machines/machinesfile.rb +0 -25
  91. data/lib/machines/questions.rb +0 -15
  92. data/lib/machines/services.rb +0 -24
  93. data/spec/lib/machines/machinesfile_spec.rb +0 -34
@@ -1,5 +1,5 @@
1
1
  module Machines
2
- module AppSettings
2
+ class AppSettings
3
3
  class AppBuilder < OpenStruct
4
4
  def get_binding
5
5
  binding
@@ -1,5 +1,5 @@
1
1
  module Machines
2
- module CloudMachine
2
+ class CloudMachine
3
3
  def connect_to_cloud
4
4
  begin
5
5
  require 'fog'
@@ -1,5 +1,3 @@
1
- require 'machines/logger'
2
-
3
1
  module Machines
4
2
  class Command
5
3
  class << self
@@ -1,13 +1,29 @@
1
1
  module Machines
2
- module Commandline
2
+ class Commandline
3
+
4
+ def initialize
5
+ @core = Core.new
6
+ end
7
+
8
+ # Execute a command (build dryrun generate htpasswd packages override tasks)
9
+ def self.execute options
10
+ help = Help.new
11
+ action = options.shift
12
+ if help.actions.include?(action)
13
+ new.send action.gsub('new', 'generate'), options
14
+ else
15
+ say help.syntax
16
+ end
17
+ end
18
+
3
19
  # Loads Machinesfile, opens an SCP connection and runs all commands and file uploads
4
20
  def build options
5
21
  $conf.machine_name = options.shift
6
22
  return say(Help.new.syntax) unless $conf.machine_name
7
23
  init
8
- load_machinesfile
24
+ @core.package 'Machinesfile'
9
25
 
10
- task options if options.any?
26
+ @core.task options if options.any?
11
27
 
12
28
  ssh_options = {:paranoid => false}
13
29
  if $conf.machine.cloud
@@ -43,18 +59,6 @@ module Machines
43
59
  build options
44
60
  end
45
61
 
46
- # Execute a given command e.g. dryrun, build, generate, htpasswd, packages, override, tasks
47
- def execute options
48
- help = Help.new
49
- action = options.shift
50
- if help.actions.include?(action)
51
- action = 'generate' if action == 'new'
52
- send action, options
53
- else
54
- say help.syntax
55
- end
56
- end
57
-
58
62
  def generate options
59
63
  dir = options.first || './'
60
64
  if File.exists? dir
@@ -85,25 +89,15 @@ module Machines
85
89
  $conf.tasks = {}
86
90
  $conf.load('config.yml')
87
91
 
88
- Command.file ||= Machines::Logger.new File.open('log/output.log', 'w')
89
- Command.debug ||= Machines::Logger.new File.open('log/debug.log', 'w')
90
- Command.console ||= Machines::Logger.new STDOUT, :truncate => true
92
+ Command.file ||= Logger.new File.open('log/output.log', 'w')
93
+ Command.debug ||= Logger.new File.open('log/debug.log', 'w')
94
+ Command.console ||= Logger.new STDOUT, :truncate => true
91
95
  end
92
96
 
93
97
  def list notused
94
98
  say Help.new.machine_list
95
99
  end
96
100
 
97
- def load_machinesfile
98
- eval File.read('Machinesfile'), nil, "eval: Machinesfile"
99
- rescue LoadError => e
100
- if e.message =~ /Machinesfile/
101
- raise LoadError, "Machinesfile does not exist. Use `machines new <DIR>` to create a template."
102
- else
103
- raise
104
- end
105
- end
106
-
107
101
  def packages notused
108
102
  say 'Default packages'
109
103
  Dir[File.join($conf.application_dir, 'packages', '**/*.rb')].each do |package|
@@ -136,7 +130,7 @@ module Machines
136
130
 
137
131
  $conf.log_only = true
138
132
  init
139
- load_machinesfile
133
+ @core.package 'Machinesfile'
140
134
  say 'Tasks'
141
135
  $conf.tasks.each do |task_name, settings|
142
136
  say " %-20s #{settings[:description]}" % task_name
@@ -0,0 +1,67 @@
1
+ module Machines
2
+ module Commands
3
+ # The `Checks` module is used to execute a command to check whether the
4
+ # previous command was successful. These methods are useful when writing
5
+ # your own custom commands.
6
+ module Checks
7
+ def echo_result
8
+ '&& echo CHECK PASSED || echo CHECK FAILED'
9
+ end
10
+
11
+ def check_package package, exists = true
12
+ "dpkg --get-selections | grep #{package}.*#{exists ? '' : 'de'}install #{echo_result}"
13
+ end
14
+
15
+ def check_gem gem, version = nil
16
+ version = " -v #{version}" if version
17
+ "gem search #{gem}#{version} --installed #{echo_result}"
18
+ end
19
+
20
+ def check_file file, exists = true
21
+ "test #{exists ? '' : '! '}-s #{file} #{echo_result}"
22
+ end
23
+
24
+ def check_link link
25
+ "test -L #{link} #{echo_result}"
26
+ end
27
+
28
+ def check_dir dir, exists = true
29
+ "test #{exists ? '' : '! '}-d #{dir} #{echo_result}"
30
+ end
31
+
32
+ def check_perms perms, path
33
+ perms = perms.to_s
34
+ mods = %w(--- --x -w- -wx r-- r-x rw- rwx)
35
+ "ls -la #{path} | grep #{mods[perms[0..0].to_i]}#{mods[perms[1..1].to_i]}#{mods[perms[2..2].to_i]} #{echo_result}"
36
+ end
37
+
38
+ def check_owner user, path
39
+ "ls -la #{path} | grep \"#{user}.*#{user}\" #{echo_result}"
40
+ end
41
+
42
+ def check_string string, file
43
+ "grep \"#{string}\" #{file} #{echo_result}"
44
+ end
45
+
46
+ def check_daemon daemon, exists = true
47
+ command = 'ps aux'
48
+ search_for = "| grep #{daemon}"
49
+ remove_grep = '| grep -v grep'
50
+ negative = "| grep -v #{daemon} " unless exists
51
+ "#{command} #{search_for} #{remove_grep} #{negative}#{echo_result}"
52
+ end
53
+
54
+ def check_init_d name
55
+ "test -L /etc/rc0.d/K20#{name} #{echo_result}"
56
+ end
57
+
58
+ def check_command command, match = nil
59
+ if match
60
+ "#{command} | grep #{match} #{echo_result}"
61
+ else
62
+ "#{command} #{echo_result}"
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,50 @@
1
+ module Machines
2
+ module Commands
3
+ module Configuration
4
+ # Add a new user
5
+ # (uses the lowlevel useradd so doesn't set a password unless specified)
6
+ # @param [String] login User name to create
7
+ # @param [Hash] options
8
+ # @option options [String] :password
9
+ # @option options [Boolean] :admin Adds the user to the admin group when true
10
+ def add_user login, options = {}
11
+ password = "-p #{`openssl passwd #{options[:password]}`.gsub("\n", '')} " if options[:password]
12
+ admin = "-G admin " if options[:admin]
13
+ Command.new(
14
+ "useradd -s /bin/bash -d /home/#{login} -m #{password}#{admin}#{login}",
15
+ check_dir("/home/#{login}")
16
+ )
17
+ end
18
+
19
+ # Add an existing user to a secondary group
20
+ # @param [Hash] options
21
+ # @option options [String] :user The user to add
22
+ # @option options [String] :to Adds an existing user to the specified group
23
+ def add options
24
+ required_options options, [:user, :to]
25
+ Command.new("usermod -a -G #{options[:to]} #{options[:user]}", check_command("groups #{options[:user]}", options[:to]))
26
+ end
27
+
28
+ # Sets gconf key value pairs
29
+ # @param [Hash] options One or many key/value pairs to set
30
+ def configure options
31
+ options.map do |key, value|
32
+ types = {String => 'string', Fixnum => 'int', TrueClass => 'bool',
33
+ FalseClass => 'bool', Float => 'float', Array => 'list --list-type=string'}
34
+ type = types[value.class]
35
+ raise 'Invalid type for configure' unless type
36
+ value = value.to_json if value.is_a?(Array)
37
+ value = %("#{value}") if type == 'string'
38
+ check = "gconftool-2 --get \"#{key}\" | grep #{value} #{echo_result}"
39
+ Command.new("gconftool-2 --set \"#{key}\" --type #{type} #{value}", check)
40
+ end
41
+ end
42
+
43
+ # Removes a user, home and any other related files
44
+ # @param [String] login User name to remove
45
+ def del_user login
46
+ Command.new("deluser #{login} --remove-home -q", check_file('/home/login', false))
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,18 @@
1
+ module Machines
2
+ module Commands
3
+ module Database
4
+ # Write the database.yml file from webapps.yml
5
+ # @param [AppBuilder] app
6
+ def write_database_yml app
7
+ yml = {$conf.environment.to_s => {
8
+ 'adapter' => 'mysql',
9
+ 'database' => app.database || app.name,
10
+ 'username' => app.username || app.name,
11
+ 'password' => app.password,
12
+ 'host' => $conf.db_server.address,
13
+ 'encoding' => 'utf8'}}.to_yaml
14
+ write yml, :to => File.join(app.path, 'shared/config/database.yml'), :name => 'database.yml'
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,105 @@
1
+ module Machines
2
+ module Commands
3
+ module FileOperations
4
+ # Add a line of text to the end of a file unless it already exists
5
+ # @param [String] text Text to add
6
+ # @param [Hash] options
7
+ # @option options [String] :to File to append to
8
+ def append text, options
9
+ text = text.gsub(/([\\$"`])/, '\\\\\1')
10
+ Command.new("grep \"#{text}\" #{options[:to]} || echo \"#{text}\" >> #{options[:to]}", check_string(text, options[:to]))
11
+ end
12
+
13
+ # Change permissions of a path
14
+ # @param [String, Integer] mode chmod permissions to set
15
+ # @param [String] path Path to set
16
+ def chmod mode, path
17
+ Command.new("chmod #{mode} #{path}", check_perms(mode, path))
18
+ end
19
+
20
+ # Change ownership of a path
21
+ # @param [String] user sets user and group unless user:group is specified
22
+ # @param [String] path Path to set
23
+ # @param [Hash] options
24
+ # @option options [String] :recursive Chowns recursively if true
25
+ def chown user, path, options = {}
26
+ recursive = '-R ' if options[:recursive]
27
+ user = "#{user}:#{user}" unless user.index(':')
28
+ Command.new("chown #{recursive}#{user} #{path}", check_owner(user, path))
29
+ end
30
+
31
+ # Copy a remote file or folder (will overwrite)
32
+ # @param [String] from Existing path
33
+ # @param [String] to Path to copy to
34
+ def copy from, to
35
+ Command.new("cp -rf #{from} #{to}", check_file(to))
36
+ end
37
+
38
+ # Write a file from an ERB template
39
+ # @param [String] erb_path Path to the ERB file to process
40
+ # @param [Hash] options
41
+ # @option options [AppBuilder] :settings Contains the settings as OpenStruct method calls for calling from the template
42
+ # @option options [String] :to File to write to
43
+ def create_from erb_path, options
44
+ erb = ERB.new(File.read(erb_path), nil, '<>')
45
+ binding = options[:settings] ? options[:settings].get_binding : nil
46
+ options[:name] = erb_path
47
+ write erb.result(binding), options
48
+ end
49
+
50
+ # Add a symlink
51
+ # @param [String] target Existing path to link
52
+ # @param [String] link_name path name for the link
53
+ def link target, link_name
54
+ Command.new("ln -sf #{target} #{link_name}", check_link(link_name))
55
+ end
56
+
57
+ # Create a path or paths on the remote host (Uses -p to be safe and create full path)
58
+ # @param [String, Array] dirs A single path, multiple paths or array of paths to create
59
+ def mkdir *dirs
60
+ dirs.flatten.map do |dir|
61
+ Command.new("mkdir -p #{dir}", check_dir(dir))
62
+ end
63
+ end
64
+
65
+ # Rename a remote file or folder
66
+ # @param [String] oldname Existing filename
67
+ # @param [String] newname Rename to this
68
+ def rename oldname, newname
69
+ Command.new("mv -f #{oldname} #{newname}", check_file(newname))
70
+ end
71
+
72
+ # Remove a remote file or folder
73
+ # @param [String] file or folder to remove (uses rm with -rf which ignores non-existent files and is recursive)
74
+ def remove file
75
+ Command.new("rm -rf #{file}", check_file(file, false))
76
+ end
77
+
78
+ # Take off the version numbers from a path name
79
+ # @param [String] name Name of the path to rename
80
+ def remove_version_info name
81
+ Command.new("find . -maxdepth 1 -name \"#{name}*\" -a -type d | xargs -I xxx mv xxx #{name}", check_file(name))
82
+ end
83
+
84
+ # Replace some text in a file
85
+ # @param [String] regex The expression to search for
86
+ # @param [Hash] options
87
+ # @option options [String] :with Text to use as the replacement
88
+ # @option options [String] :in Filename to replace text in
89
+ def replace regex, options
90
+ required_options options, [:with, :in]
91
+ with = options[:with].gsub(/([\n\/\\$"`])/, '\\\\\1')
92
+ Command.new("sed -i \"s/#{regex}/#{with}/\" #{options[:in]}", check_string(with, options[:in]))
93
+ end
94
+
95
+ # (Over)write a file with the specified content
96
+ # @param [String] text Text to add
97
+ # @param [Hash] options
98
+ # @option options [String] :to File to write to
99
+ # @option options [String] :name Give the buffer a displayable name (e.g. when generated from a template)
100
+ def write text, options
101
+ Upload.new(NamedBuffer.new(options[:name], text), options[:to], check_string(text, options[:to]))
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,184 @@
1
+ module Machines
2
+ module Commands
3
+ module Installation
4
+ include Commands::FileOperations
5
+
6
+ APTGET_QUIET = 'apt-get -q -y'
7
+
8
+ # Adds a PPA source and updates apt
9
+ # @param [String] name Name of the PPA
10
+ # @param [String] key_name What to check in apt-key list to ensure it installed
11
+ # @example
12
+ # add_ppa 'mozillateam/firefox-stable', 'mozilla'
13
+ def add_ppa name, key_name
14
+ [
15
+ Command.new("add-apt-repository ppa:#{name}", "apt-key list | grep -i #{key_name} #{echo_result}"),
16
+ update
17
+ ]
18
+ end
19
+
20
+ # Adds a DEB source
21
+ # @param [String] source URL of the package. If `YOUR_UBUNTU_VERSION_HERE` is included then it
22
+ # is replaced by the Ubuntu version name
23
+ # @param [Hash] options
24
+ # @option options [String] :key URL of key
25
+ # @option options [String] :name Used to check `apt-key list` to ensure it installed
26
+ # @example
27
+ # sudo deb 'http://dl.google.com/linux/deb/ stable main',
28
+ # key: 'https://dl-ssl.google.com/linux/linux_signing_key.pub',
29
+ # name: 'Google'
30
+ def deb source, options
31
+ command = "echo deb #{source} >> /etc/apt/sources.list"
32
+ if source =~ /YOUR_UBUNTU_VERSION_HERE/
33
+ command = "expr substr `cat /etc/lsb-release | grep DISTRIB_CODENAME` 18 20 | xargs -I YOUR_UBUNTU_VERSION_HERE #{command}"
34
+ end
35
+ [
36
+ Command.new(command, check_string(source.gsub(/ .*$/, ''), '/etc/apt/sources.list')),
37
+ Command.new("wget -q #{options[:key]} -O - | apt-key add -", "apt-key list | grep -i #{options[:name]} #{echo_result}"),
38
+ update
39
+ ]
40
+ end
41
+
42
+ # Preseed debconf to allow silent installs
43
+ # @param [String] app Name of application to configure
44
+ # @param [String] setting The setting to set
45
+ # @param [String] type Data type of the value
46
+ # @param value The value to set (Ruby types supported)
47
+ def debconf app, setting, type, value
48
+ command = "echo #{app} #{setting} #{type} #{value} | debconf-set-selections"
49
+ check = "debconf-get-selections | grep #{app} #{echo_result}"
50
+ Command.new(command, check)
51
+ end
52
+
53
+ # Download, extract, and remove an archive. Currently supports `zip`, `tar.gz`, `tar.bz2`.
54
+ # @param [String] package Package name to extract
55
+ # @param [Hash] options
56
+ # @option options [Optional String] :to folder to clone or extract to (defaults to `/usr/local/src`)
57
+ # @example
58
+ # sudo extract 'http://dl.suckless.org/dwm/dwm-6.0.tar.gz'
59
+ def extract package, options = {}
60
+ name = File.basename(package)
61
+ if package[/.zip/]
62
+ cmd = 'unzip -qq'
63
+ elsif package[/.tar.gz/]
64
+ cmd = 'tar -zxf'
65
+ elsif package[/.tar.bz2/]
66
+ cmd = 'tar -jxf'
67
+ else
68
+ raise "extract: Unknown extension for #{package}"
69
+ end
70
+ dir = cmd =~ /unzip/ ? File.basename(name, '.zip') : File.basename(name).gsub(/\.tar.*/, '')
71
+ dest = options[:to] || '/usr/local/src'
72
+ Command.new("cd #{dest} && wget #{package} && #{cmd} #{name} && rm #{name} && cd -", check_dir("#{File.join(dest, dir)}"))
73
+ end
74
+
75
+ # Install a gem
76
+ # @param [String] package Name of the gem
77
+ # @param [Hash] options
78
+ # @option options [String] :version Optional version number
79
+ def gem package, options = {}
80
+ version = " -v \"#{options[:version]}\"" if options[:version]
81
+ Command.new("gem install #{package}#{version}", check_gem(package, options[:version]))
82
+ end
83
+
84
+ # Update gems
85
+ # @example Update Rubygems
86
+ # gem_update '--system'
87
+ def gem_update options = ''
88
+ Command.new("gem update #{options}", nil)
89
+ end
90
+
91
+ # Clone (or update) a project from a Git repository
92
+ # @param [String] url URL to clone
93
+ # @param [Hash] options
94
+ # @option options [Optional String] :to Folder to clone to
95
+ # @option options [Optional String] :tag Checkout this tag after cloning (requires :to)
96
+ # @option options [Optional String] :branch Switch this branch when cloning
97
+ def git_clone url, options = {}
98
+ raise ArgumentError.new('git_clone Must include a url and folder') if url.nil? || url.empty?
99
+ raise ArgumentError.new('specifying :tag also requires :to') if options[:tag] && options[:to].nil?
100
+ branch = "--branch #{options[:branch]} " if options[:branch]
101
+ dir = options[:to] || url.gsub(/^.*\/|.git/, '')
102
+ command = "test -d #{dir} && (cd #{dir} && git pull) || git clone --quiet #{branch}#{url}"
103
+ command << " #{options[:to]}" if options[:to]
104
+ command = Command.new(command, check_dir(options[:to]))
105
+ command = [command, Command.new("cd #{options[:to]} && git checkout #{options[:tag]}", "git name-rev --name-only HEAD | grep #{options[:tag]}")] if options[:tag]
106
+ command
107
+ end
108
+
109
+ # Installs one or more packages using apt, deb or git clone and install.sh
110
+ # (See `extract` to just uncompress tar.gz or zip files).
111
+ # Packages are installed separately to aid progress feedback.
112
+ # Ensure this is the main package as dpkg get-selections is used to validate installation.
113
+ # @param [Symbol, String, Array] packages URL to download and install, string or array of apt packages
114
+ # @param [Hash] options
115
+ # @option options [Optional Symbol] :as Specify `:dpkg` to download URL and run `dpkg`
116
+ # @option options [Optional String] :bin Specify the bin file to link to (e.g. '/bin/executable'
117
+ # will create a link /usr/local/bin/executable that points to /usr/local/lib/bin/executable)
118
+ # @option options [Optional Boolean] :make Set to true to run `sudo make clean install`
119
+ # @example Install apt packages
120
+ # install %w(build-essential libssl-dev mysql-server)
121
+ # @example Download and install a deb using dpkg then remove the deb
122
+ # install 'http://example.com/my_package.deb'
123
+ # @example Download and phantomjs to `/usr/local/lib` and add `bin/phantomjs` to `/usr/local/bin`
124
+ # install 'http://phantomjs.googlecode.com/files/phantomjs-1.7.0-linux-x86_64.tar.bz2', bin: 'bin/phantomjs'
125
+ # @example Download dwm into `/usr/local/src/dwm-6.0` and install
126
+ # install 'http://dl.suckless.org/dwm/dwm-6.0.tar.gz', make: true
127
+ def install packages, options = {}
128
+ if packages.is_a?(String)
129
+ if packages =~ /^http:\/\//
130
+ commands = []
131
+ if packages =~ /\.deb$/i
132
+ name = File.basename(packages)
133
+ commands << Command.new("cd /tmp && wget #{packages} && dpkg -i --force-architecture #{name} && rm #{name} && cd -", nil)
134
+ else
135
+ commands << extract(packages, :to => '/tmp')
136
+ name = File.basename(packages).gsub(/\.(tar|zip).*/, '')
137
+
138
+ if options[:as] == :dpkg
139
+ commands << Command.new("cd /tmp/#{name} && dpkg -i --force-architecture *.deb && cd - && rm -rf /tmp/#{name}", nil)
140
+ elsif options[:bin]
141
+ commands << rename("/tmp/#{name}", "/usr/local/lib")
142
+ commands << link("/usr/local/lib/#{name}/#{options[:bin]}", "/usr/local/bin/#{File.basename(options[:bin])}")
143
+ elsif options[:make]
144
+ commands << rename("/tmp/#{name}", "/usr/local/src")
145
+ commands << Command.new("cd /usr/local/src/#{name} && make clean install", nil)
146
+ end
147
+ end
148
+ return commands
149
+ else
150
+ packages = [packages]
151
+ end
152
+ end
153
+
154
+ if packages.is_a?(Array)
155
+ commands = packages.map do |package|
156
+ Command.new("#{APTGET_QUIET} install #{package}", check_package(package))
157
+ end
158
+ end
159
+ commands
160
+ end
161
+
162
+ # Remove one or more Ubuntu packages
163
+ # @param [Array] packages Packages to remove
164
+ def uninstall packages
165
+ packages.map do |package|
166
+ Command.new("#{APTGET_QUIET} purge #{package}", check_package(package, false))
167
+ end
168
+ end
169
+
170
+ # Updates Ubuntu packages
171
+ def update
172
+ Command.new("#{APTGET_QUIET} update > /tmp/apt-update.log", check_string('Reading package lists', '/tmp/apt-update.log'))
173
+ end
174
+
175
+ # Update, upgrade, autoremove, autoclean apt packages
176
+ def upgrade
177
+ # TODO: Check that check_command really checks the correct command with `echo $?`
178
+ %w(update upgrade autoremove autoclean).map do |command|
179
+ Command.new("#{APTGET_QUIET} #{command}", check_command('echo $?', '0'))
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end