niedziela, 13 stycznia 2013

How set up WOL (Wake On Lan) on Thin Client with ubuntu

I set up small home server using HP TERMINAL HP T5000 (Thin Client). I installed Ubuntu server on it. It works great - but recently I realized that I need to have this server up only for very limited number of scenarios i.e. when I'm working on one of my PCs (so I want to have access to server resources e.g. samba, cups, DNS server etc) or I'm downloading something from the internet using transmission-daemon. To optimize my server power consumption I decided to set up Wake On Lan on my ubuntu and wrote 2 simple scripts in ruby.

How to set up WOL on ubuntu?
Log in as administrator.
Install ethtool:
sudo apt-get install ethtool
Change to the startup script directory and start editing a new file:
cd /etc/init.d/
nano wakeonlanconfig
Paste, or type this into the file, replacing eth0 with your network device, repeat the ethtool line as many times for your devices before the exit line:
#!/bin/bash
ethtool -s eth0 wol g
exit
Set the permissions of the file:
chmod a+x wakeonlanconfig
Make the script run on startup:
update-rc.d -f wakeonlanconfig defaults
You should see something like:
 Adding system startup for /etc/init.d/wakeonlanconfig ...
   /etc/rc0.d/K20wakeonlanconfig -> ../init.d/wakeonlanconfig
   /etc/rc1.d/K20wakeonlanconfig -> ../init.d/wakeonlanconfig
   /etc/rc6.d/K20wakeonlanconfig -> ../init.d/wakeonlanconfig
   /etc/rc2.d/S20wakeonlanconfig -> ../init.d/wakeonlanconfig
   /etc/rc3.d/S20wakeonlanconfig -> ../init.d/wakeonlanconfig
   /etc/rc4.d/S20wakeonlanconfig -> ../init.d/wakeonlanconfig
   /etc/rc5.d/S20wakeonlanconfig -> ../init.d/wakeonlanconfig
Now finish by running it, and making sure there are no errors.
/etc/init.d/wakeonlanconfig
Now when the server can be started remotely I want to present you 2 scrips which I wrote in ruby to make my life easier.

First for client machines.
All I need to do in this script is checking state of my server and if it is down send magic packet to power it on.
begin
  require 'Win32/Console/ANSI'
rescue LoadError
  raise 'You must gem install win32console to use color on Windows'
end

require 'logger'
require 'net/ping'
require File.dirname(__FILE__) + "/wakeonlan.rb"

class String
    def red; colorize(self, "\e[1m\e[31m"); end
    def green; colorize(self, "\e[1m\e[32m"); end
    def dark_green; colorize(self, "\e[32m"); end
    def yellow; colorize(self, "\e[1m\e[33m"); end
    def blue; colorize(self, "\e[1m\e[34m"); end
    def dark_blue; colorize(self, "\e[34m"); end
    def pur; colorize(self, "\e[1m\e[35m"); end
    def colorize(text, color_code)  "#{color_code}#{text}\e[0m" end
end

log = Logger.new(File.dirname(__FILE__) + '/logs/log.txt', 'monthly')
log.debug "Script has been started"

puts "Ping ubuntu server"
log.debug "Ping ubuntu server"
p = Net::Ping::TCP.new('my_server_IP', 'http').ping?

if !p
 puts "Getting server up"
 log.debug "Getting server up"
 puts "Sending magic packet to ubuntu server"
 wol=WakeOnLan.new
 wol.wake("my_server_mac_address", "broadcast", "my_server_IP")
 # args: MAC_address Broadcast IP_address
 wol.close
else
 puts "Server is running".dark_green
 log.debug "Server is running"
end
Below I present code for WakeOnLan class which I used in above code:
# K.Kodama 2003-04-20 revised/bug fix
# K.Kodama 2000-05-10
# This program is distributed freely 
# in the sense of GNU General Public License or ruby's.

require "socket"

class WakeOnLan
 attr :socket
 def initialize
  @socket=UDPSocket.open()
  @socket.setsockopt(Socket::SOL_SOCKET,Socket::SO_BROADCAST,1)
 end;
 def close; @socket.close; @socket=""; end
 def wake(mac_addr, broadcast="", ip_addr="")
  wol_magic=(0xff.chr)*6+(mac_addr.split(/:/).pack("H*H*H*H*H*H*"))*16
  if broadcast==""; # Set broadcast. Assume that standard IP-class.
   ips=ip_addr.split(/\./);c=ips[0].to_i
   if c<=127; ips[1]="255";end # class A:1--127
   if c<=191; ips[2]="255";end # class B:128--191
   if c<=223; ips[3]="255";end # class C:192--223
   # class D:224--239 multicast
   broadcast=ips.join(".")
  end
  3.times{ @socket.send(wol_magic,0,broadcast,"discard") }
 end
end

if $0 == __FILE__
=begin
Sample for WakeOnLan class.
Use as: ruby wakeonlan.rb [broadcast_address] < data_text
line format of data_text: ip-address  hostname  MAC-address
Data sample: 10.20.30.40  wake-pc  100:110:120:130:140:150
Note.  To check WOL packet, use tcpdump -x.
=end

 if ARGV.size>=1; broadcast=ARGV[0]; ARGV.clear; else broadcast=""; end
 wol=WakeOnLan.new
 while gets
  ip,name,hw=$_.sub(/^\s+/,"").split(/\s+/)
  wol.wake(hw, broadcast, ip)
 end
 wol.close
end

Second for server.
I'm checking here state of network i.e. if any of my PCs is working. In case when all my PCs are offline server could be turn off but before I'll send halt command I have to check status of transmission-daemon - check if there are any elements being downloaded at the moment. If all conditions passed server is going to be shut down.
require 'net/ping'
require 'logger'
require 'transmission_api'

class TorrentStatus
 attr_accessor :all_torrents_finished
 
 def initialize()
  @transmission_api = TransmissionApi.new(
   :username => "my_user",
   :password => "password",
   :url      => "http://myserver:9091/transmission/rpc"
  )
  
  torrents = @transmission_api.all

  @all_torrents_finished = true
  torrents.each{|t|
   #puts "#{t["name"]} - [#{t["percentDone"]}]"
   @all_torrents_finished &= (t["percentDone"] == 1)
  }
  end
end

class LanStatus
 
 def initialize()
  @interfaces = ["192.168.zzz.xxx","192.168.zzz.yyy"]
 end
 
 def active_computers_in_network?
  active = false
  @interfaces.each{|ip|
   active |= Net::Ping::External.new(ip).ping?
  }
  active
 end
 
end

log = Logger.new(File.dirname(__FILE__) + '/logs/shutdown_log.txt')
log.debug "Script has been started"

puts "Checking system state"
log.debug "Checking system state"
print " * Torrent status "
torrent_status = TorrentStatus.new
all_torrents_finished = torrent_status.all_torrents_finished
puts "- [#{all_torrents_finished ? "idle" : "active"}]"

log.debug " * Torrent status - [#{all_torrents_finished ? "idle" : "active"}]"

print " * Lan status"
lan_status = LanStatus.new
active_computers_in_network = lan_status.active_computers_in_network?
puts "- [#{active_computers_in_network ? "active" : "inactive"}]"

log.debug " * Lan status - [#{active_computers_in_network ? "active" : "inactive"}]"

if (all_torrents_finished && !active_computers_in_network)
 puts "Sending HALT command to system"
 log.debug "Sending HALT command to system"
 system "sudo shutdown -h now"
end
There was still problem how to make it automated process. From the server side i use cron to schedule job which will execute in every 5 minutes and run ruby script. To read more about setting cron please read this post.
I added to crontab below entry:
0/5 * * * * ruby /home/luke/scripts/shut_down.rb
All my clients work under windows system and they are not attached to domain. So i just added ruby script to logon scripts. To add logon script for Windows 7 Home Premium: I've created a script at C:\Windows\System32\repl\import\scripts\start.bat Which executes command:
ruby /path_to_my_script/login.rb
Then I invoked from command line
net user USERNAME /scriptpath:start.bat

Brak komentarzy:

Prześlij komentarz