nines 1.0.0

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.
@@ -0,0 +1,10 @@
1
+ # log and pid files
2
+ log/nines.log
3
+ tmp/nines.pid
4
+
5
+ # config files
6
+ nines.rb
7
+ nines.yml
8
+
9
+ # built gem
10
+ nines-*.gem
data/.rvmrc ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env bash
2
+
3
+ ruby_string="ruby-1.9.3-p327"
4
+ gemset_name="nines"
5
+
6
+ alias rails='bundle exec rails'
7
+
8
+ if rvm list strings | grep -q "${ruby_string}" ; then
9
+
10
+ # Load or create the specified environment
11
+ if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
12
+ && -s "${rvm_path:-$HOME/.rvm}/environments/${ruby_string}@${gemset_name}" ]] ; then
13
+ \. "${rvm_path:-$HOME/.rvm}/environments/${ruby_string}@${gemset_name}"
14
+ else
15
+ rvm --create "${ruby_string}@${gemset_name}"
16
+ fi
17
+
18
+ # (
19
+ # # Ensure that Bundler is installed, install it if it is not.
20
+ # if ! command -v bundle ; then
21
+ # gem install bundler
22
+ # fi
23
+ #
24
+ # # Bundle while reducing excess noise.
25
+ # bundle | grep -v 'Using' | grep -v 'complete' | sed '/^$/d'
26
+ # )&
27
+
28
+ else
29
+
30
+ # Notify the user to install the desired interpreter before proceeding.
31
+ echo "${ruby_string} was not found, please run 'rvm install ${ruby_string}' and then cd back into the project directory."
32
+
33
+ fi
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Aaron Namba <aaron@biggerbird.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,24 @@
1
+ =nines
2
+
3
+ Nines is a simple server monitoring tool written in Ruby. It reads in hand-coded YAML config files (see config.yml.sample). Rename to config.yml and edit as needed before first run.
4
+
5
+ When run, it forks into the background and runs in a continuous loop. If there are bugs in the code (likely) it may die, so keep it running with monit, init, etc.
6
+
7
+ =Usage
8
+
9
+ git clone git://github.com/anamba/nines.git && cd nines && bundle install && bundle exec ./nines
10
+ To stop: bundle exec ./nines stop
11
+
12
+ =Dependencies
13
+
14
+ Developed and tested with MRI ruby 1.9.3.
15
+
16
+ Dependencies:
17
+ * trollop (commandline options)
18
+ * net-ping (http/ping testing)
19
+ * dnsruby (dns resolution)
20
+ * mail (email)
21
+
22
+ =License & Copyright
23
+
24
+ Distributed under MIT license. Copyright (c) 2012 Aaron Namba <aaron@biggerbird.com>
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'trollop'
4
+ require 'nines/version'
5
+
6
+ opts = Trollop::options do
7
+ version "nines #{Nines::VERSION} (c) Aaron Namba"
8
+ banner "nines #{Nines::VERSION} (c) Aaron Namba\nNote: Command line parameters override config file values."
9
+
10
+ opt 'config-file', "Path to YAML configuration file", :short => '-f', :default => 'nines.yml'
11
+ opt 'verbose', "Enable detailed logging", :type => :boolean
12
+ opt 'debug', "Run each check once, then exit", :type => :boolean
13
+
14
+ stop_on [ 'start', 'stop' ]
15
+ end
16
+
17
+ # absolutize config file path
18
+ opts['config-file'] = opts['config-file'] =~ /^\// ? opts['config-file'] : File.expand_path(opts['config-file'], Dir.pwd)
19
+
20
+ unless File.exists?(opts['config-file'])
21
+ puts "Config file #{opts['config-file']} not found (or not accessible)"
22
+ exit 1
23
+ end
24
+
25
+ # args seem okay, load up the app
26
+ require 'nines'
27
+
28
+ # instantiate Nines::App using config file
29
+ app = Nines::App.new(File.expand_path(opts['config-file']))
30
+ Nines::App.debug ||= opts['debug']
31
+ Nines::App.verbose ||= opts['verbose']
32
+
33
+ unless Nines::App.debug
34
+ unless app.logfile_writable
35
+ puts "Couldn't open #{app.logfile} for logging"
36
+ exit 1
37
+ end
38
+ unless app.pidfile_writable
39
+ puts "Couldn't write pid to #{app.pidfile}"
40
+ exit 1
41
+ end
42
+ end
43
+
44
+ # process subcommands
45
+ cmd = ARGV.shift
46
+ case cmd
47
+ when 'start'
48
+ cmd_opts = Trollop.options do
49
+ end
50
+
51
+ app.start(cmd_opts)
52
+
53
+ when 'stop'
54
+ cmd_opts = Trollop.options do
55
+ end
56
+
57
+ app.stop(cmd_opts)
58
+
59
+ else
60
+ app.start
61
+ end
@@ -0,0 +1,8 @@
1
+ # example logrotate config
2
+ /var/log/nines.log {
3
+ daily
4
+ rotate 30
5
+ copytruncate
6
+ compress
7
+ dateext
8
+ }
@@ -0,0 +1,90 @@
1
+ require 'rbconfig'
2
+ require 'find'
3
+ require 'fileutils'
4
+
5
+ include RbConfig
6
+
7
+ $ruby = CONFIG['ruby_install_name']
8
+
9
+ ##
10
+ # Install a binary file. We patch in on the way through to
11
+ # insert a #! line. If this is a Unix install, we name
12
+ # the command (for example) 'rake' and let the shebang line
13
+ # handle running it. Under windows, we add a '.rb' extension
14
+ # and let file associations to their stuff
15
+ #
16
+
17
+ def installBIN(from, opfile)
18
+
19
+ tmp_dir = nil
20
+ for t in [".", "/tmp", "c:/temp", $bindir]
21
+ stat = File.stat(t) rescue next
22
+ if stat.directory? and stat.writable?
23
+ tmp_dir = t
24
+ break
25
+ end
26
+ end
27
+
28
+ fail "Cannot find a temporary directory" unless tmp_dir
29
+ tmp_file = File.join(tmp_dir, "_tmp")
30
+
31
+ File.open(from) do |ip|
32
+ File.open(tmp_file, "w") do |op|
33
+ ruby = File.join($realbindir, $ruby)
34
+ op.puts "#!#{ruby} -w"
35
+ op.write ip.read
36
+ end
37
+ end
38
+
39
+ opfile += ".rb" if CONFIG["target_os"] =~ /mswin/i
40
+ FileUtils.install(tmp_file, File.join($bindir, opfile),
41
+ {:mode => 0755, :verbose => true})
42
+ File.unlink(tmp_file)
43
+ end
44
+
45
+ $sitedir = CONFIG["sitelibdir"]
46
+ unless $sitedir
47
+ version = CONFIG["MAJOR"]+"."+CONFIG["MINOR"]
48
+ $libdir = File.join(CONFIG["libdir"], "ruby", version)
49
+ $sitedir = $:.find {|x| x =~ /site_ruby/}
50
+ if !$sitedir
51
+ $sitedir = File.join($libdir, "site_ruby")
52
+ elsif $sitedir !~ Regexp.quote(version)
53
+ $sitedir = File.join($sitedir, version)
54
+ end
55
+ end
56
+
57
+ $bindir = CONFIG["bindir"]
58
+
59
+ $realbindir = $bindir
60
+
61
+ bindir = CONFIG["bindir"]
62
+ if (destdir = ENV['DESTDIR'])
63
+ $bindir = destdir + $bindir
64
+ $sitedir = destdir + $sitedir
65
+
66
+ FileUtils.mkdir_p($bindir)
67
+ FileUtils.mkdir_p($sitedir)
68
+ end
69
+
70
+ rake_dest = File.join($sitedir, "rake")
71
+ FileUtils.mkdir_p(rake_dest, {:verbose => true})
72
+ File.chmod(0755, rake_dest)
73
+
74
+ # The library files
75
+
76
+ files = Dir.chdir('lib') { Dir['**/*.rb'].sort }
77
+
78
+ for fn in files
79
+ fn_dir = File.dirname(fn)
80
+ target_dir = File.join($sitedir, fn_dir)
81
+ if ! File.exist?(target_dir)
82
+ FileUtils.mkdir_p(target_dir)
83
+ end
84
+ FileUtils.install(File.join('lib', fn), File.join($sitedir, fn),
85
+ {:mode => 0644, :verbose => true})
86
+ end
87
+
88
+ # and the executable
89
+
90
+ installBIN("bin/nines", "nines")
@@ -0,0 +1,194 @@
1
+ require 'yaml'
2
+ require 'erb'
3
+ require 'dnsruby'
4
+ require 'mail'
5
+
6
+ module Nines
7
+ class App
8
+ class << self
9
+ attr_accessor :root, :config, :continue,
10
+ :debug, :verbose, :logfile, :pidfile, :logger, :notifier,
11
+ :email_from, :email_subject_prefix
12
+ end
13
+
14
+ def initialize(config_file)
15
+ self.class.root = File.expand_path('../../../', __FILE__)
16
+
17
+ # load config files
18
+ case File.extname(config_file)
19
+ when '.yml' then self.class.config = YAML.load(ERB.new(File.read(config_file)).result)
20
+ when '.rb' then require config_file
21
+ end
22
+ self.class.config = stringify_keys_and_symbols(self.class.config)
23
+
24
+ # set main parameters
25
+ self.class.debug = config['debug']
26
+ self.class.verbose = config['verbose']
27
+
28
+ self.class.logfile = config['logfile'] || 'nines.log'
29
+ self.class.pidfile = config['pidfile'] || 'nines.pid'
30
+ self.class.email_from = config['email_from'] || 'Nines Notifier <no-reply@example.com>'
31
+ self.class.email_subject_prefix = config['email_subject_prefix'] || ''
32
+ end
33
+
34
+ # shortcuts
35
+ def config ; self.class.config ; end
36
+ def logfile ; self.class.logfile ; end
37
+ def pidfile ; self.class.pidfile ; end
38
+ def debug ; self.class.debug ; end
39
+ def logger ; self.class.logger ; end
40
+
41
+ def logfile_writable
42
+ begin
43
+ File.open(logfile, 'a') { }
44
+ true
45
+ rescue Exception => e
46
+ puts "Exception: #{e}"
47
+ false
48
+ end
49
+ end
50
+
51
+ def pidfile_writable
52
+ begin
53
+ File.open(pidfile, 'a') { }
54
+ true
55
+ rescue Exception => e
56
+ puts "Exception: #{e}"
57
+ false
58
+ end
59
+ end
60
+
61
+ # make sure you're not using OpenDNS or something else that resolves invalid names
62
+ def check_hostnames
63
+ all_good = true
64
+
65
+ @check_groups.each do |group|
66
+ group.checks.each do |check|
67
+ unless check.hostname && Dnsruby::Resolv.getaddress(check.hostname)
68
+ puts "Error: check #{check.name} has invalid hostname '#{check.hostname}'"
69
+ all_good = false
70
+ end
71
+ end
72
+ end
73
+
74
+ all_good
75
+ end
76
+
77
+ def configure_smtp
78
+ if config['smtp'].is_a?(Hash)
79
+ Mail.defaults do
80
+ delivery_method :smtp, {
81
+ :address => Nines::App.config['smtp']['address'] || 'localhost',
82
+ :port => Nines::App.config['smtp']['port'] || 25,
83
+ :domain => Nines::App.config['smtp']['domain'],
84
+ :user_name => Nines::App.config['smtp']['user_name'],
85
+ :password => Nines::App.config['smtp']['password'],
86
+ :authentication => (Nines::App.config['smtp']['authentication'] || 'plain').to_sym,
87
+ :enable_starttls_auto => Nines::App.config['smtp']['enable_starttls_auto'],
88
+ :tls => Nines::App.config['smtp']['tls'],
89
+ }
90
+ end
91
+ end
92
+ end
93
+
94
+ def stringify_keys_and_symbols(obj)
95
+ case obj.class.to_s
96
+ when 'Array'
97
+ obj.map! { |el| stringify_keys_and_symbols(el) }
98
+ when 'Hash'
99
+ obj.stringify_keys!
100
+ obj.each { |k,v| obj[k] = stringify_keys_and_symbols(v) }
101
+ when 'Symbol'
102
+ obj = obj.to_s
103
+ end
104
+
105
+ obj
106
+ end
107
+
108
+ def start(options = {})
109
+ # set up logger
110
+ self.class.logger = Logger.new(debug ? STDOUT : File.open(logfile, 'a'))
111
+ logger.sync = 1 # makes it possible to tail the logfile
112
+
113
+ # use it
114
+ logger.puts "[#{Time.now}] - nines starting"
115
+
116
+ # set up notifier
117
+ configure_smtp
118
+ self.class.notifier = Notifier.new(config['contacts'])
119
+
120
+ # set up check_groups (uses logger and notifier)
121
+ if !config['check_groups'].is_a?(Array) || config['check_groups'].empty?
122
+ raise Exception.new("No check groups configured, nothing to do.")
123
+ end
124
+
125
+ @check_groups = []
126
+ config['check_groups'].each do |options|
127
+ @check_groups << CheckGroup.new(options)
128
+ end
129
+
130
+ # TODO: this is a little awkwardly placed, but can fix later
131
+ unless check_hostnames
132
+ puts "Invalid hostnames found in config file"
133
+ exit 1
134
+ end
135
+
136
+ # fork and detach
137
+ if pid = fork
138
+ File.open(pidfile, 'w') { |f| f.print pid }
139
+ puts "Background process started with pid #{pid} (end it using `#{$0} stop`)"
140
+ puts "Debug mode enabled, background process will log to STDOUT and exit after running each check once." if debug
141
+ exit 0
142
+ end
143
+
144
+ #
145
+ # rest of this method runs as background process
146
+ #
147
+
148
+ # trap signals before spawning threads
149
+ self.class.continue = true
150
+ trap("INT") { Nines::App.continue = false ; puts "Caught SIGINT, will exit after current checks complete or time out." }
151
+ trap("TERM") { Nines::App.continue = false ; puts "Caught SIGTERM, will exit after current checks complete or time out." }
152
+
153
+ # iterate through config, spawning check threads as we go
154
+ @threads = []
155
+
156
+ @check_groups.each do |group|
157
+ group.checks.each do |check|
158
+ @threads << Thread.new(Thread.current) { |parent|
159
+ begin
160
+ check.run
161
+ rescue Exception => e
162
+ parent.raise e
163
+ end
164
+ }
165
+ end
166
+ end
167
+
168
+ @threads.each { |t| t.join if t.alive? }
169
+
170
+ logger.puts "[#{Time.now}] - nines finished"
171
+ logger.close
172
+
173
+ puts "Background process finished"
174
+ end
175
+
176
+ def stop(options = {})
177
+ begin
178
+ pid = File.read(self.class.pidfile).to_i
179
+ rescue Errno::ENOENT => e
180
+ STDERR.puts "Couldn't open pid file #{self.class.pidfile}, please check your config."
181
+ exit 1
182
+ end
183
+
184
+ begin
185
+ Process.kill "INT", pid
186
+ exit 0
187
+ rescue Errno::ESRCH => e
188
+ STDERR.puts "Couldn't kill process with pid #{pid}. Are you sure it's running?"
189
+ exit 1
190
+ end
191
+ end
192
+
193
+ end
194
+ end
@@ -0,0 +1,67 @@
1
+ module Nines
2
+ class Check
3
+ attr_accessor :group, :name, :hostname, :address, :timeout, :port, :interval, :logger, :notifier, :up, :since, :cycles
4
+
5
+ def initialize(group, options)
6
+ @group = group
7
+ @hostname = options['hostname']
8
+ @name = options['name'] || @hostname
9
+ @timeout = options['timeout_sec'] || 10
10
+ @port = options['port']
11
+ @interval = options['interval_sec'] || 60
12
+
13
+ @logger = Nines::App.logger || STDOUT
14
+ @notifier = Nines::App.notifier
15
+
16
+ @times_notified = {}
17
+ @up = true
18
+ @since = Time.now.utc
19
+ @cycles = 0
20
+ end
21
+
22
+ def log_status(up, description)
23
+ if up
24
+ logger.puts "[#{Time.now}] - #{name} - Check passed: #{description}"
25
+
26
+ case @up
27
+ when true
28
+ @cycles += 1
29
+ when false
30
+ @up = true
31
+ @since = Time.now.utc
32
+ @cycles = 0
33
+
34
+ # back up notification
35
+ if notifier
36
+ @times_notified.keys.each do |contact_name|
37
+ logger.puts "[#{Time.now}] - #{name} - UP again, notifying contact '#{contact_name}'"
38
+ notifier.notify!(contact_name, self)
39
+ end
40
+ @times_notified = {}
41
+ end
42
+ end
43
+ else
44
+ logger.puts "[#{Time.now}] - #{name} - Check FAILED: #{description}"
45
+
46
+ case @up
47
+ when false
48
+ @cycles += 1
49
+ when true
50
+ @up = false
51
+ @since = Time.now.utc
52
+ @cycles = 0
53
+ end
54
+
55
+ if notifier && to_notify = group.contacts_to_notify(@cycles, @times_notified)
56
+ to_notify.each do |contact_name|
57
+ logger.puts "[#{Time.now}] - #{name} - Notifying contact '#{contact_name}'"
58
+ notifier.notify!(contact_name, self)
59
+ @times_notified[contact_name] ||= 0
60
+ @times_notified[contact_name] += 1
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,58 @@
1
+ module Nines
2
+ class CheckGroup
3
+ attr_accessor :contacts, :checks
4
+
5
+ def initialize(options = {})
6
+ options.stringify_keys!
7
+ @name = options['name']
8
+ @description = options['description']
9
+
10
+ parameters = options['parameters'] || {}
11
+ parameters.stringify_keys!
12
+ @check_type = parameters.delete('type')
13
+
14
+ @contacts = []
15
+ notify = options['notify'] || []
16
+ notify.each do |contact|
17
+ contact.stringify_keys!
18
+ @contacts << {
19
+ 'name' => contact['contact'],
20
+ 'after' => contact['after'] || 2,
21
+ 'every' => contact['every'] || 5,
22
+ 'upto' => contact['upto'] || 5
23
+ }
24
+ end
25
+
26
+ @checks = []
27
+ checks = options['checks'] || []
28
+ checks.each do |check|
29
+ check = { hostname: check } unless check.is_a?(Hash)
30
+ check.stringify_keys!
31
+ case @check_type
32
+ when 'http'
33
+ @checks << HttpCheck.new(self, parameters.merge(check))
34
+ when 'ping'
35
+ @checks << PingCheck.new(self, parameters.merge(check))
36
+ else
37
+ raise Exception.new("Unknown check type: #{@check_type} (supported values: http, ping)")
38
+ end
39
+ end
40
+ end
41
+
42
+ # times_notified must be a hash with contact names as keys
43
+ def contacts_to_notify(cycles, times_notified)
44
+ # cycles starts at 0, but user generally expects first down event to be cycle 1
45
+
46
+ to_notify = []
47
+ @contacts.each do |contact|
48
+ next if times_notified[contact['name']].to_i >= contact['upto']
49
+ next if (cycles+1) < contact['after']
50
+ next if (cycles+1 - contact['after']) % contact['every'] != 0
51
+ to_notify << contact['name']
52
+ end
53
+
54
+ to_notify
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,3 @@
1
+ <%= check.name %> is <%= check.up ? 'UP' : 'DOWN' %> since <%= check.since %> (<%= human_duration(check.interval * check.cycles) %>)
2
+
3
+ <%= details %>
@@ -0,0 +1,42 @@
1
+ require 'net/ping'
2
+ require 'dnsruby'
3
+
4
+ module Nines
5
+ class HttpCheck < Check
6
+ attr_accessor :uri, :up_statuses, :user_agent
7
+
8
+ def initialize(group, options)
9
+ super(group, options)
10
+
11
+ @uri = options['uri'] || "http://#{hostname}:#{port}/"
12
+ @up_statuses = options['up_statuses'] || [ 200 ]
13
+ @user_agent = options['user_agent'] || "nines/1.0"
14
+ end
15
+
16
+ # shortcuts
17
+ def debug ; Nines::App.debug ; end
18
+
19
+ def run
20
+ while Nines::App.continue do
21
+ check_started = Time.now
22
+ @address = Dnsruby::Resolv.getaddress(hostname)
23
+
24
+ @pinger = Net::Ping::HTTP.new(uri, port, timeout)
25
+ @pinger.user_agent = user_agent
26
+
27
+ # the check
28
+ log_status(@pinger.ping?, "#{uri} (#{address})#{@pinger.warning ? " [warning: #{@pinger.warning}]" : ''}")
29
+
30
+ break if debug
31
+
32
+ wait = interval.to_f - (Time.now - check_started)
33
+ while wait > 0 do
34
+ break unless Nines::App.continue
35
+ sleep [1, wait].min
36
+ wait -= 1
37
+ end
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,26 @@
1
+ module Nines
2
+ class Logger
3
+
4
+ def initialize(io)
5
+ @mutex = Mutex.new
6
+ @io = io
7
+ end
8
+
9
+ def sync ; @io.sync ; end
10
+ def sync=(val) ; @io.sync = val ; end
11
+
12
+ def puts(*args)
13
+ @mutex.synchronize { @io.puts args }
14
+ end
15
+ alias_method :error, :puts
16
+
17
+ def debug(*args)
18
+ @mutex.synchronize { @io.puts args } if Nines::App.verbose
19
+ end
20
+
21
+ def close
22
+ @io.close unless @io == STDOUT || @io == STDERR
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,32 @@
1
+ require 'mail'
2
+
3
+ module Nines
4
+ class Notifier
5
+
6
+ def initialize(contacts)
7
+ @contacts = contacts
8
+ end
9
+
10
+ def notify!(contact_name, check, details = '')
11
+ contact = @contacts[contact_name]
12
+ email_body = ERB.new(File.open(Nines::App.root + '/lib/nines/email_templates/notification.text.erb').read).result(binding)
13
+
14
+ Mail.deliver do
15
+ from Nines::App.email_from
16
+ to contact['email']
17
+ subject "#{Nines::App.email_subject_prefix}#{check.name} is #{check.up ? 'UP' : 'DOWN'}"
18
+ body email_body
19
+ end
20
+ end
21
+
22
+ def human_duration(seconds)
23
+ case
24
+ when seconds < 60 then "#{seconds} sec"
25
+ when seconds < 3600 then "#{seconds/60} min #{seconds%60} sec"
26
+ when seconds < 86400 then "#{seconds/3600} hr #{seconds%3600/60} min #{seconds%60} sec"
27
+ else "#{seconds/86400} day #{seconds%86400/3600} hr #{seconds%3600/60} min #{seconds%60} sec"
28
+ end
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,50 @@
1
+ require 'net/ping'
2
+ require 'dnsruby'
3
+
4
+ module Nines
5
+ class PingCheck < Check
6
+ attr_accessor :protocol
7
+
8
+ def initialize(group, options)
9
+ super(group, options)
10
+
11
+ @protocol = (options['protocol'] || 'icmp').downcase
12
+ end
13
+
14
+ # shortcuts
15
+ def debug ; Nines::App.debug ; end
16
+
17
+ def run
18
+ while Nines::App.continue do
19
+ check_started = Time.now
20
+ @address = Dnsruby::Resolv.getaddress(hostname)
21
+
22
+ @pinger = case protocol
23
+ when 'tcp' then Net::Ping::TCP.new(hostname, nil, timeout)
24
+ when 'udp' then Net::Ping::UDP.new(hostname, nil, timeout)
25
+ when 'icmp'
26
+ if Process::UID == 0
27
+ Net::Ping::ICMP.new(hostname, nil, timeout)
28
+ else
29
+ Net::Ping::External.new(hostname, nil, timeout)
30
+ end
31
+ else "invalid ping protocol #{protocol}"
32
+ end
33
+
34
+ # the check
35
+ log_status(@pinger.ping?, "#{protocol == 'icmp' ? 'icmp' : "#{protocol}/#{port}"} ping on #{hostname} (#{address})")
36
+
37
+ break if debug
38
+
39
+ wait = interval.to_f - (Time.now - check_started)
40
+ while wait > 0 do
41
+ break unless Nines::App.continue
42
+ sleep [1, wait].min
43
+ wait -= 1
44
+ end
45
+ end
46
+ end
47
+
48
+
49
+ end
50
+ end
@@ -0,0 +1,3 @@
1
+ module Nines
2
+ VERSION = "1.0.0"
3
+ end
File without changes
@@ -0,0 +1,29 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require "nines/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.platform = Gem::Platform::RUBY
6
+ s.name = 'nines'
7
+ s.version = Nines::VERSION
8
+ s.summary = 'Simple server monitoring tool written in pure Ruby.'
9
+ s.description = 'Nines is a simple server monitoring tool written in Ruby.'
10
+
11
+ s.required_ruby_version = '>= 1.9.3'
12
+ s.required_rubygems_version = '>= 1.8.11'
13
+
14
+ s.author = "Aaron Namba"
15
+ s.email = "aaron@biggerbird.com"
16
+ s.homepage = "https://github.com/anamba/nines"
17
+ s.license = 'MIT'
18
+
19
+ s.bindir = 'bin'
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
24
+
25
+ s.add_dependency 'net-ping', '~> 1.5.3'
26
+ s.add_dependency 'dnsruby', '~> 1.53'
27
+ s.add_dependency 'mail', '~> 2.4'
28
+ s.add_dependency 'trollop', '~> 2.0'
29
+ end
@@ -0,0 +1,56 @@
1
+ Nines::App.config = {
2
+ debug: false,
3
+ verbose: false,
4
+
5
+ logfile: 'log/nines.log',
6
+ pidfile: 'tmp/nines.pid',
7
+ email_from: 'Nines Notifier <no-reply@example.com>',
8
+ email_subject_prefix: '[NINES] ',
9
+
10
+ contacts: {
11
+ 'admin-standard' => { email: 'admin@example.com' },
12
+ 'admin-urgent' => { email: 'admin@example.com', twitter: 'admin_oncall', sms: '19875551234', phone: '19875554321' }
13
+ },
14
+
15
+ check_groups: [
16
+ { name: 'Web Sites - High Priority',
17
+ description: 'Check often, notify immediately',
18
+ parameters: { type: :http, port: 80, timeout_sec: 5, interval_sec: 30 },
19
+ notify: [ { contact: 'admin-urgent', after: 2, every: 2, upto: 100 } ],
20
+ checks: [ 'www.corporation.com', 'www.clientabc.com' ]
21
+ },
22
+ { name: 'Web Sites - Low Priority',
23
+ description: 'Check infrequently, use urgent contact only after extended downtime',
24
+ parameters: { type: :http, port: 80, timeout_sec: 15, interval_sec: 300 },
25
+ notify: [
26
+ { contact: 'admin-standard', after: 2, every: 2, upto: 10 },
27
+ { contact: 'admin-urgent', after: 25, every: 10, upto: 10 }
28
+ ],
29
+ checks: [
30
+ 'blog.johndoe.name',
31
+ { name: 'Blog Redirect', hostname: 'www.oldblogsite.com', up_statuses: [ 301 ] }
32
+ ]
33
+ },
34
+ { name: 'Servers',
35
+ description: 'Simple ping tests',
36
+ parameters: { type: :ping, protocol: :icmp, interval_sec: 60 },
37
+ notify: [
38
+ { contact: 'admin-standard', after: 2, every: 2, upto: 10 },
39
+ { contact: 'admin-urgent', after: 25, every: 10, upto: 10 }
40
+ ],
41
+ checks: [
42
+ 'web1.hostingco.com',
43
+ { name: 'web1 admin IP', hostname: 'web1.hostingco.net' }
44
+ ]
45
+ },
46
+ ],
47
+
48
+ smtp: {
49
+ address: 'smtp.sendgrid.net',
50
+ port: 587,
51
+ user_name: 'myusername',
52
+ password: 'mypassword',
53
+ authentication: 'plain',
54
+ enable_starttls_auto: true
55
+ }
56
+ }
@@ -0,0 +1,83 @@
1
+ ---
2
+ debug: false
3
+ verbose: false
4
+
5
+ logfile: log/nines.log
6
+ pidfile: tmp/nines.pid
7
+ email_from: 'Nines Notifier <no-reply@calchost.com>'
8
+ email_subject_prefix: '[NINES] '
9
+
10
+ contacts:
11
+ admin-standard:
12
+ email: admin@example.com
13
+ admin-urgent:
14
+ email: admin@example.com
15
+ twitter: admin_oncall
16
+ sms: '19875551234'
17
+ phone: '19875554321'
18
+
19
+ check_groups:
20
+ - name: Web Sites - High Priority
21
+ description: Check often, notify immediately
22
+ parameters:
23
+ type: :http
24
+ port: 80
25
+ timeout_sec: 5
26
+ interval_sec: 30
27
+ notify:
28
+ - contact: admin-urgent
29
+ after: 2
30
+ every: 2
31
+ upto: 100
32
+ checks:
33
+ - www.corporation.com
34
+ - www.clientabc.com
35
+ - name: Web Sites - Low Priority
36
+ description: Check infrequently, use urgent contact only after extended downtime
37
+ parameters:
38
+ type: http
39
+ port: 80
40
+ timeout_sec: 15
41
+ interval_sec: 300
42
+ notify:
43
+ - contact: admin-standard
44
+ after: 2
45
+ every: 2
46
+ upto: 10
47
+ - contact: admin-urgent
48
+ after: 25
49
+ every: 10
50
+ upto: 10
51
+ checks:
52
+ - blog.johndoe.name
53
+ - name: Blog Redirect
54
+ hostname: www.oldblogsite.com
55
+ up_statuses:
56
+ - 301
57
+ - name: Servers
58
+ description: Simple ping tests
59
+ parameters:
60
+ type: ping
61
+ protocol: icmp
62
+ interval_sec: 60
63
+ notify:
64
+ - contact: admin-standard
65
+ after: 2
66
+ every: 2
67
+ upto: 10
68
+ - contact: admin-urgent
69
+ after: 25
70
+ every: 10
71
+ upto: 10
72
+ checks:
73
+ - web1.hostingco.com
74
+ - name: web1 admin IP
75
+ hostname: web1.hostingco.net
76
+
77
+ smtp:
78
+ address: smtp.sendgrid.net
79
+ port: 587
80
+ user_name: myusername
81
+ password: mypassword
82
+ authentication: plain
83
+ enable_starttls_auto: true
File without changes
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nines
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Aaron Namba
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: net-ping
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.5.3
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.5.3
30
+ - !ruby/object:Gem::Dependency
31
+ name: dnsruby
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '1.53'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '1.53'
46
+ - !ruby/object:Gem::Dependency
47
+ name: mail
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '2.4'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.4'
62
+ - !ruby/object:Gem::Dependency
63
+ name: trollop
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '2.0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '2.0'
78
+ description: Nines is a simple server monitoring tool written in Ruby.
79
+ email: aaron@biggerbird.com
80
+ executables:
81
+ - nines
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - .gitignore
86
+ - .rvmrc
87
+ - MIT-LICENSE.txt
88
+ - README.rdoc
89
+ - bin/nines
90
+ - doc/logrotate.conf
91
+ - install.rb
92
+ - lib/nines.rb
93
+ - lib/nines/app.rb
94
+ - lib/nines/check.rb
95
+ - lib/nines/check_group.rb
96
+ - lib/nines/email_templates/notification.text.erb
97
+ - lib/nines/http_check.rb
98
+ - lib/nines/logger.rb
99
+ - lib/nines/notifier.rb
100
+ - lib/nines/ping_check.rb
101
+ - lib/nines/version.rb
102
+ - log/.gitkeep
103
+ - nines.gemspec
104
+ - nines.rb.sample
105
+ - nines.yml.sample
106
+ - tmp/.gitkeep
107
+ homepage: https://github.com/anamba/nines
108
+ licenses:
109
+ - MIT
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ! '>='
118
+ - !ruby/object:Gem::Version
119
+ version: 1.9.3
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: 1.8.11
126
+ requirements: []
127
+ rubyforge_project:
128
+ rubygems_version: 1.8.24
129
+ signing_key:
130
+ specification_version: 3
131
+ summary: Simple server monitoring tool written in pure Ruby.
132
+ test_files: []