require 'net/ssh' class Server include DataMapper::Resource property :id, Serial property :host, Text property :runner, Text belongs_to :role, :required => false has n, :captures after :create do self.add_captures :output, :errors end def narrate_as self.host end def execute_command(command) self.clear_captures! narrate "executing: #{command}" self.connection.open_channel do |channel, success| # Works without this with passwordless sudo locally - with it on, errors end up in on_data, not on_extended_data, annoyingly. channel.request_pty channel.exec command do |ch, success| # "on_extended_data" is called when the process writes something to stderr ch.on_extended_data do |c, type, data| self.capture :errors, data end # "on_data" is called when the process writes something to stdout ch.on_data do |c, data| self.capture :output, data end end end self.connection.loop end def output self.captures.first(:name => 'output').contents end def errors self.captures.first(:name => 'errors').contents end def accounts return [] unless self.role @accounts ||= self.role.users.collect { |user| Account.create(:user => user, :server => self) } end def should_account_exist_for?(user) self.accounts.include?(user) end def account_exists_for?(user) self.execute_command("id #{user.name}") if self.output =~ /uid=/ true else false end end def delete_account_for(user) narrate "Deleting dead account for #{user.name}" self.execute_command("sudo /usr/sbin/userdel --remove #{user.name}") end def install_accounts! narrate "Installing accounts" self.accounts.each do |account| account.add_user account.add_ssh_directory account.write_ssh_key account.add_to_groups end end def remove_dead_accounts accounts_to_remove.each do |user| self.delete_account_for(user) end narrate "Dead accounts removed" end def accounts_to_add self.role.users.select do |user| not self.account_exists_for?(user) end end def accounts_to_remove narrate "Checking for dead accounts" users = User.all users.reject do |user| self.should_account_exist_for?(user) and self.account_exists_for?(user) end end def create_group(group) self.execute_command("sudo /usr/sbin/groupadd #{group.name}") end def is_running_plesk? self.execute_command("if [ -d /usr/local/psa ]; then echo 'plesk'; else echo 'not plesk'; fi") case self.output.strip when 'plesk' then true when 'not plesk' then false else raise "Couldn't detect if this server is running plesk." end end protected attr_writer :connection def connection @connection ||= Net::SSH.start(self.host, self.runner) end def add_captures(*names) names.each do |name| self.captures.create(:name => name) end end def capture(capture, data) self.captures.first(:name => capture.to_s).append(data) end def clear_captures! self.captures.each do |capture| capture.clear! end end end