Module: Doable::Helpers::Linux

Includes:
Exceptions::Linux, OS
Defined in:
lib/doable/helpers/linux.rb

Instance Method Summary collapse

Methods included from OS

#die_if_dir_exists, #find_directory, #find_file, #find_or_create_directory, #find_or_create_file, #must_run_as, #remove, #sed, #tee, #touch, #validate_host

Instance Method Details

#add_bashrc_entry(user, entry) ⇒ Object

Add an entry to a user’s ~/.bashrc

Parameters:

  • user (String)

    User to add bashrc entries to

  • entry (String)

    Entry to add to user’s .bashrc



58
59
60
# File 'lib/doable/helpers/linux.rb', line 58

def add_bashrc_entry(user, entry)
  File.open(File.expand_path("~#{user}/.bashrc"), "a") {|f| f.puts entry }
end

#apply_patch(options, patch_file, path = nil, user = nil) ⇒ Object

Apply a patch file

Parameters:

  • options (String)

    Options to be added to the ‘patch` system command

  • patch_file (String)

    Path to the patch file to apply

  • path (String) (defaults to: nil)

    Optionally cd to this directory before patching

  • user (String) (defaults to: nil)

    Optionally apply patch as this user



41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/doable/helpers/linux.rb', line 41

def apply_patch(options, patch_file, path = nil, user = nil)
  cmd = "patch #{options} < #{patch_file}"
    
  olddir = `pwd`.chomp
  Dir.chdir(path) if path
  tee 'pwd'
  if user
    run_as_user user, cmd
  else
    tee cmd
  end
  Dir.chdir(olddir) if path
end

#check_disk_space(directory, min_space) ⇒ Object

Finds the mount point for a directory, then checks it for a minimum number of kilobytes

Parameters:

  • directory (String)

    Directory from which check starts

  • min_space (Fixnum)

    Minumum amount of space required on mount-point discovered from install_dir



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/doable/helpers/linux.rb', line 224

def check_disk_space(directory, min_space)
  all_filesystems = `df -PB 1024`.chomp.split("\n").collect {|fs| fs.split if fs.match(/^\/dev/) }.compact
  best_fit = nil
  current_test = directory
  until best_fit
    matching_fs = all_filesystems.collect {|fs| fs if File.expand_path(fs[5]) == File.expand_path(current_test) }.compact
    if matching_fs.size == 1
      best_fit = matching_fs.flatten
    else
      current_test = File.dirname(current_test)
    end
  end

  log "Closest matching filesystem is '#{best_fit[5]}'. Testing for free space..."
  if Integer(best_fit[3]) >= min_space
    log "Found sufficient space #{best_fit[3]} for install on #{best_fit[5]} with an install directory of #{install_dir}."
  else
    log "Insufficient space #{best_fit[3]} for install on #{best_fit[5]} with an install directory of #{install_dir}!", :error
  end
end

#check_for_rpms(rpms) ⇒ Object

Check for RPMs (kind of RHEL / SuSE specific) #return [Boolean]

Parameters:

  • rpms (Array<String>)

    RPMs to verify

Raises:



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/doable/helpers/linux.rb', line 101

def check_for_rpms(rpms)
  raise InvalidRPMList unless rpms.kind_of?(Array)
  # Split the list of all installed RPMs into an Array containing only the RPM's base name
  system_rpms = `rpm -qa`.chomp.split("\n").collect {|rpm| rpm.split(/-[0-9]/)[0]}
  i686_rpms = `rpm -qa`.chomp.split("\n").collect {|rpm| rpm.split(/-[0-9]/)[0] if rpm.match(/\.i686$/)}.compact
  raise NotApplicable unless $?.success?
  missing_rpms = rpms.collect {|rpm| rpm unless rpm.match(/\.i686$/) }.compact - system_rpms  # calculate what RPMs are not installed
  missing_rpms = (missing_rpms + (rpms.collect {|rpm| rpm.split('.')[0] if rpm.match(/\.i686$/) }.compact - i686_rpms).compact.map {|r| "#{r}.i686" } ).compact
  if missing_rpms.size > 0
    log "Missing RPMs: #{missing_rpms.join(', ')}", :error
    raise MissingRPMs
  else
    return true
  end
end

#chmod(permission, file_list, options = {recursive: false}) ⇒ Object

Used like Unix chmod

Parameters:

  • permission (String, Fixnum)

    Permissions to set

  • file_list (Array<String>, String)

    List of files to change the permissions of



26
27
28
29
30
31
32
33
34
# File 'lib/doable/helpers/linux.rb', line 26

def chmod(permission, file_list, options = {recursive: false})
  if options[:recursive]
    options.delete :recursive
    FileUtils.chmod_R(permission, file_list, options)
  else
    options.delete(:recursive) if options.has_key?(:recursive)
    FileUtils.chmod(permission, file_list, options)
  end
end

#chown(user, group, file_list, options = {recursive: false}) ⇒ Object

Used like Unix chown

Parameters:

  • user (String)

    User to set as the owner

  • group (String)

    Group to set as the owner

  • file_list (Array<String>, String)

    List of files to change the owner of



13
14
15
16
17
18
19
20
21
# File 'lib/doable/helpers/linux.rb', line 13

def chown(user, group, file_list, options = {recursive: false})
  if options[:recursive]
    options.delete :recursive
    FileUtils.chown_R(user, group, file_list, options)
  else
    options.delete(:recursive) if options.has_key?(:recursive)
    FileUtils.chown(user, group, file_list, options)
  end
end

#find_executable(name) ⇒ Object

Require an executable to be in $PATH

Parameters:

  • name (String)

    Required executable

Raises:



91
92
93
# File 'lib/doable/helpers/linux.rb', line 91

def find_executable(name)
  have_executable?(name) ? true : raise(MissingExecutable, name)
end

#find_or_create_group(group) ⇒ Boolean

Ensure a group exists

Parameters:

  • group (String)

    Group to create or verify

Returns:

  • (Boolean)

Raises:



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/doable/helpers/linux.rb', line 189

def find_or_create_group(group)
  all_groups = `getent group`.chomp.split("\n").map {|line| line.split(':')}
  this_group = all_groups.collect {|g| g if g[0] == group}.compact
  if this_group.size > 1
    log "Multiple instances of group #{group} found. This is known to cause problems.", :error
    raise DuplicateGroups
  elsif this_group.size == 1
    log "Group '#{group}' already exists with gid: #{this_group.flatten[2]}. Skipping creation of group...", :warn
  else
    log "Creating group '#{group}'..."
    `groupadd -r "#{group}"`
    raise FailedGroupAdd unless $?.success?
    return $?.success?
  end
end

#find_or_create_user(user, homedir, options = {}) ⇒ Boolean

Ensure a user exists on the system

Parameters:

  • user (String)

    User to add or verify

  • homedir (String)

    User home directory

Returns:

  • (Boolean)

Raises:



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/doable/helpers/linux.rb', line 123

def find_or_create_user(user, homedir, options = {})
  # Create an Array of Arrays storing all known system users and their attributes
  all_users = `getent passwd`.chomp.split("\n").map {|line| line.split(':')}
  # Determine if the user we want exists
  this_user = all_users.collect {|u| u if u[0] == user}.compact
  if this_user.size > 1
    log "Multiple instances of user #{user} found. This is known to cause problems.", :error
    raise DuplicateUsers
  elsif this_user.size == 1
    log "User '#{user}' already exists with uid: #{this_user.flatten[2]}. Skipping creation of user...", :warn
    full_home = File.expand_path(homedir)
    current_home = File.expand_path(this_user.flatten[5])
    if (full_home != current_home) and (homedir != current_home)
      log "User's home directory is set to #{current_home}, not #{homedir}!", :error
      raise WrongHomedir
    end
  else
    log "Creating #{user} user..."
    shell = options[:shell] ? options[:shell] : '/bin/bash'
    `useradd -d "#{homedir}" -s "#{shell}" -mr "#{user}"`
  end
    
  return true
end

#find_processes(regex) ⇒ Array<String>

Get a list of process matching a Regexp

Parameters:

  • regex (Regexp)

    Regular expression to use for finding processes

Returns:

  • (Array<String>)

    Array of PIDs that match the regex



65
66
67
# File 'lib/doable/helpers/linux.rb', line 65

def find_processes(regex)
  `ps aux`.chomp.split("\n").map {|l| l.split(nil, 11) }.collect {|p| p if p[10].match(regex)}.compact
end

#find_user(user, noisy = false) ⇒ Array

Die if a user doesn’t exist

Parameters:

  • user (String)

    User to verify

  • noisy (Boolean) (defaults to: false)

    Should extra info be logged?

Returns:

  • (Array)

Raises:



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/doable/helpers/linux.rb', line 167

def find_user(user, noisy = false)
  # Create an Array of Arrays storing all known system users and their attributes
  all_users = `getent passwd`.chomp.split("\n").map {|line| line.split(':')}
  # Determine if the user we want exists
  this_user = all_users.collect {|u| u if u[0] == user}.compact
  if this_user.size > 1
    log "Multiple instances of user #{user} found. This is known to cause problems.", :error
    raise DuplicateUsers
  elsif this_user.size < 1
    log "Missing Critical User '#{user}'!", :error
    raise MissingUser
  else
    log "Found required user '#{user}' with uid #{this_user.flatten[2]}..." if noisy
  end
  return this_user.flatten
end

#have_executable?(name) ⇒ String, false

Does the system have an executable in $PATH? If not, return false.

Parameters:

  • name (String)

    Executable to verify

Returns:

  • (String, false)


79
80
81
82
83
84
85
86
# File 'lib/doable/helpers/linux.rb', line 79

def have_executable?(name)
  app_path = `which #{name}`.chomp
  if app_path.match(name)
    return app_path
  else
    return false
  end
end

#kill(pid, signal = "TERM") ⇒ Object

Kill a process

Parameters:

  • pid (Fixnum, String)

    PID to kill

  • signal (String) (defaults to: "TERM")

    Signal to kill PID with



72
73
74
# File 'lib/doable/helpers/linux.rb', line 72

def kill(pid, signal = "TERM")
  Process.kill(signal, pid)
end

#run_as_user(user, command) ⇒ Object

Linux specific way to run something as another user. Command must be passed as a String.

Parameters:

  • user (String)

    User that will run command

  • command (String)

    Command to be run by user



248
249
250
251
# File 'lib/doable/helpers/linux.rb', line 248

def run_as_user(user, command)
  log "Running '#{command}' as '#{user}'..."
  tee("su -l #{user} -c \"#{command}\"")
end

#set_nofile(user, limit) ⇒ Boolean

Set the max number of open files for a user (nofiles) (sets both hard and soft, errors out if user is already listed in limits.conf) Currently does NOT verify the existence of the specified user on the system

Parameters:

  • user (String)

    User to set ‘nofile’ ulimit security setting for

  • limit (Fixnum)

    Limit to set for user

Returns:

  • (Boolean)


259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/doable/helpers/linux.rb', line 259

def set_nofile(user, limit)
  # See if the user is already in the limits.conf file
  user_entries = `grep -E '^#{user}(\s|\t)+' /etc/security/limits.conf`.chomp.split("\n")
  if user_entries.empty?
    # Doing this the lazy way for now... this is probably dangerous for very large files

    # Open and copy the current content of the limits file plus a few extra lines towards the end
    current_file  = File.readlines("/etc/security/limits.conf")
    temporary     = Tempfile.new("new_limits")
    current_file[0..(current_file.size - 2)].each {|l| temporary.write l }

    temporary.write "#{user}\t\tsoft\tnofile\t\t#{limit}\n"
    temporary.write "#{user}\t\thard\tnofile\t\t#{limit}\n"
    temporary.write "\n"
    temporary.write current_file[current_file.size - 1]
    temporary.rewind

    # Create a backup of the current file
    FileUtils.cp "/etc/security/limits.conf", "/etc/security/limits.conf.#{Time.now.to_i}"
    # Clear out the current file
    File.truncate("/etc/security/limits.conf", 0)
    # Copy the temporary (new) file to the existing
    current_file = File.new("/etc/security/limits.conf", "w")
    temporary.each {|l| current_file.puts l }
    # Close everything up
    temporary.close
    temporary.unlink
    current_file.close
    return true
  else
    log "User '#{user}' already has a specific entry in /etc/security/limits.conf. Ensure 'nofile' is at least #{limit}.", :warn
    return false
  end
end

#set_user_password(user, new_pass) ⇒ Boolean

Note:

This may only work on RHEL (and the like), as chpasswd may not be available on other OS’s

Sets a user’s password

Parameters:

  • user (String)

    User to set password for

  • new_pass (String)

    New password for user

Returns:

  • (Boolean)

Raises:



154
155
156
157
158
159
# File 'lib/doable/helpers/linux.rb', line 154

def set_user_password(user, new_pass)
  log "Setting password for #{user}..."
  `echo "#{user}:#{new_pass}" | chpasswd`
  raise FailedPasswordChange unless $?.success?
  return $?.success?
end

#update_user_gid(user, gid) ⇒ Object

Set a user’s gid (default group)

Parameters:

  • user (String)

    User to update

  • gid (String, Fixnum)

    Group to set as default for user

Raises:



209
210
211
212
213
214
215
216
217
218
219
# File 'lib/doable/helpers/linux.rb', line 209

def update_user_gid(user, gid)
  user_details = find_user(user)
  if user_details[3] != gid
    log "Setting default group for #{user} to #{gid}..."
    `usermod -g #{gid} "#{user}"`
    raise FailedUserModification unless $?.success?
    log "Fixing permissions..."
    `chown -R #{user}:#{gid} "#{user_details[5]}"`
  end
  return gid
end