omnibot 0.0.2

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,31 @@
1
+ OmniBot
2
+ ===============
3
+
4
+ Simple XMPP bot for server monitoring.
5
+ Works with AMQP for sending messages at server side.
6
+ Sends notifications to a user via XMPP.
7
+
8
+ Dependencies
9
+ ------------
10
+
11
+ * RabbitMQ or any other AMQP-compatible server
12
+ * amqp
13
+ * xmpp4r
14
+ * eventmachine
15
+
16
+ Installation
17
+ ------------
18
+
19
+ Configure omnibot configuration from examples/config.yaml to ~/.omnibot.yaml and adjust it.
20
+ Then execute command:
21
+
22
+ omnibot
23
+
24
+ Send messages to omnibot by AMQP by running:
25
+
26
+ omnisend 'Hello World!'
27
+
28
+ Support
29
+ -------
30
+
31
+ Tested on Mac OS X 10.6 with Ruby 1.8
@@ -0,0 +1,36 @@
1
+ begin
2
+ require "bundler"
3
+ Bundler.setup
4
+ rescue LoadError
5
+ $stderr.puts "You need to have Bundler installed to be able build this gem."
6
+ end
7
+
8
+ gemspec = eval(File.read(Dir["*.gemspec"].first))
9
+
10
+
11
+ desc "Validate the gemspec"
12
+ task :gemspec do
13
+ gemspec.validate
14
+ end
15
+
16
+ desc "Build gem locally"
17
+ task :build => :gemspec do
18
+ system "gem build #{gemspec.name}.gemspec"
19
+ FileUtils.mkdir_p "pkg"
20
+ FileUtils.mv "#{gemspec.name}-#{gemspec.version}.gem", "pkg"
21
+ end
22
+
23
+ desc "Install gem locally"
24
+ task :install => :build do
25
+ system "gem install pkg/#{gemspec.name}-#{gemspec.version}"
26
+ end
27
+
28
+ desc "Push gem"
29
+ task :push => :build do
30
+ system "gem push pkg/#{gemspec.name}-#{gemspec.version}.gem"
31
+ end
32
+
33
+ desc "Clean automatically generated files"
34
+ task :clean do
35
+ FileUtils.rm_rf "pkg"
36
+ end
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'omnibot'
4
+
5
+ OmniBot::Launcher.new.start(ARGV)
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'omnibot'
4
+
5
+ OmniBot::OmniSend.new.start(ARGV)
@@ -0,0 +1,7 @@
1
+ config:
2
+ omnibotuser: bot@example.com
3
+ omnibotpass: secret
4
+ notifyjid: owner@example.com
5
+ notifyresource: Adium.*
6
+ periodiccommands: [ 'vnstat' ]
7
+ logpath: '/tmp/omnibot.log'
@@ -0,0 +1,13 @@
1
+ require 'yaml'
2
+ require 'logger'
3
+
4
+ require 'amqp'
5
+ require 'mq'
6
+ require 'eventmachine'
7
+ require 'xmpp4r'
8
+ require 'xmpp4r/client'
9
+ require 'xmpp4r/roster'
10
+
11
+ %w[ helpers jabberbot amqpconsumer omnisend launcher periodiccommand ].each do |file|
12
+ require "omnibot/#{file}.rb"
13
+ end
@@ -0,0 +1,74 @@
1
+ module OmniBot
2
+
3
+ # AMQP consumer class
4
+ class AMQPConsumer
5
+
6
+ def send_message message
7
+ begin
8
+ @omnibot.add_message [Time.now, message]
9
+ rescue Object => e
10
+ OmniLog::error "Sending message error: #{e.message}\ntrace:\n#{Helpers::backtrace e}\nIgnoring..."
11
+ end
12
+ end
13
+
14
+ def initialize config
15
+ @config = config
16
+ end
17
+
18
+ def amqp_loop
19
+ # setup amqp
20
+ mq = MQ.new
21
+ exchange = mq.direct(Helpers::amqp_exchange_name)
22
+ queue = mq.queue("omnibot-consumerqueue", :exclusive => true)
23
+ queue.bind(exchange)
24
+
25
+ begin
26
+ OmniLog::info "Setup omnibot..."
27
+ @omnibot = JabberBot.new(Jabber::JID::new(@config['omnibotuser']), @config['omnibotpass'])
28
+ @omnibot.timer_provider = EM
29
+ @omnibot.set_subscriber Jabber::JID::new(@config['notifyjid']), @config['notifyresource']
30
+ @omnibot.connect
31
+
32
+ pause = 0
33
+ [@config['periodiccommands']].flatten.each do |command|
34
+ OmniLog::info "Setup command #{command}..."
35
+ periodic_command = PeriodicCommand.new command, pause
36
+ periodic_command.timer_provider = EM
37
+ periodic_command.set_jabber_messenger { |message| send_message message }
38
+ periodic_command.start
39
+ pause += 20
40
+ end
41
+
42
+ rescue Object => e
43
+ OmniLog::error "Sending message error: #{e.message}\ntrace:\n#{Helpers::backtrace e}\nExiting..."
44
+ AMQP.stop{ EM.stop }
45
+ end
46
+
47
+ OmniLog::info "==== AMQP is ready ===="
48
+
49
+ queue.subscribe do |msg|
50
+ message = Marshal.load msg
51
+ send_message message
52
+ end
53
+ end
54
+
55
+ # Main AMQP loop
56
+ def start
57
+
58
+ # exit hook
59
+ Signal.trap('INT') do
60
+ OmniLog::info "It's a trap, should exit..."
61
+ AMQP.stop{ EM.stop }
62
+ end
63
+
64
+ AMQP.start do
65
+ amqp_loop
66
+ end
67
+
68
+ OmniLog::info "Exited"
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+
@@ -0,0 +1,65 @@
1
+ module OmniBot
2
+
3
+ class OmniLog
4
+
5
+ def self.init_log
6
+ logger = Logger.new('omnibot.log')
7
+ logger.level = Logger::DEBUG
8
+ logger
9
+ end
10
+
11
+ def self.log=(value)
12
+ @logger = value
13
+ end
14
+
15
+ def self.log
16
+ @logger
17
+ end
18
+
19
+ def self.debug(progname = nil, &block); @logger.debug(progname, &block); end
20
+ def self.info(progname = nil, &block); @logger.info(progname, &block); end
21
+ def self.warn(progname = nil, &block); @logger.warn(progname, &block); end
22
+ def self.error(progname = nil, &block); @logger.error(progname, &block); end
23
+ def self.fatal(progname = nil, &block); @logger.fatal(progname, &block); end
24
+ end
25
+
26
+ # Helper class for counting reconnect attempts
27
+ class AttemptCounter
28
+ def report
29
+ OmniLog::debug "AttemptCounter: try #{@counter} of #{@max_attempts}"
30
+ end
31
+ public
32
+ def initialize max_attempts
33
+ @counter = 0
34
+ @max_attempts = max_attempts
35
+ OmniLog::debug "AttemptCounter inited"
36
+ end
37
+
38
+ def out_of_attempts?
39
+ @counter >= @max_attempts
40
+ end
41
+
42
+ def increase
43
+ @counter += 1
44
+ report
45
+ end
46
+ end
47
+
48
+ class Helpers
49
+ def self.backtrace e
50
+ e.respond_to?(:backtrace) && e.backtrace ? e.backtrace.join("\n\t") : ""
51
+ end
52
+
53
+ def self.same_day? t1, t2
54
+ t1.year == t2.year && t1.month == t2.month && t1.day == t2.day
55
+ end
56
+
57
+ def self.amqp_exchange_name
58
+ 'omnibot-exchange'
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+
65
+
@@ -0,0 +1,186 @@
1
+ module OmniBot
2
+
3
+ # Jabber bot with reconnection and dnd-care logic
4
+ class JabberBot
5
+
6
+ def dump_presence p
7
+ p ? "Presence status=#{p.status} type=#{p.type} show=#{p.show} from=#{p.from} to=#{p.to} xml=(#{p.to_s})" : "nil"
8
+ end
9
+
10
+ def on_message_handler m
11
+ OmniLog::debug "Got jabber message from #{m.from}:\n#{m.body}\n."
12
+ end
13
+
14
+ def is_needed_user? jid
15
+ jid.strip == @subscriber && @subscriber_resource.match((jid.resource or ''))
16
+ end
17
+
18
+ def on_presence_callback old_presence, new_presence
19
+ OmniLog::debug "Presence changed:\n...old #{dump_presence old_presence}\n...new #{dump_presence new_presence}"
20
+ if is_needed_user? old_presence.from
21
+ @subscriber_online = check_presence? old_presence
22
+ OmniLog::debug "Subscriber #{@subscriber} is #{@subscriber_online ? "ready" : "not ready"}"
23
+ pump_messages if @subscriber_online
24
+ end
25
+ end
26
+
27
+ def on_subscripton_request_callback item, pres
28
+ OmniLog::debug "Subscription request item=#{item} pres=#{dump_presence pres}"
29
+ end
30
+
31
+ def on_exception_handler e, stream, sym_where
32
+ OmniLog::error "Jabber exception happens at symbol \"#{sym_where}\": #{e}\nbacktrace\n#{Helpers::backtrace e}"
33
+ OmniLog::debug "stream is #{stream} vs client #{@client}"
34
+ on_generic_exception_handler e
35
+ end
36
+
37
+ def safe_reconnect
38
+ begin
39
+ reconnect
40
+ rescue Jabber::ClientAuthenticationFailure => e
41
+ OmniLog::error "Authentification error: #{e.class}: #{e}"
42
+ raise
43
+ rescue Exception => e
44
+ OmniLog::error "Reconnect hard error: #{e.class}: #{e}"
45
+ on_generic_exception_handler e
46
+ end
47
+ end
48
+
49
+ def on_generic_exception_handler e
50
+ if e && (e.kind_of?(Jabber::ServerDisconnected) || e.class.to_s =~ /^Errno::.+/)
51
+ OmniLog::error "No timer provider assigned" unless @timer_provider
52
+ # attempt counter is set when it's needed to connect
53
+ unless @ignore_reconnect
54
+ @timer_provider.add_timer(@reconnect_pause) { try_reconnect }
55
+ end
56
+ end
57
+ end
58
+
59
+ def reconnect
60
+ OmniLog::debug 'Going to reconnect'
61
+ @client.connect
62
+ @client.auth(@password)
63
+ @client.send(Jabber::Presence.new.set_type(:available))
64
+ end
65
+
66
+ def try_reconnect
67
+ return if @client.is_connected?
68
+
69
+ OmniLog::debug 'Called try_reconnect'
70
+
71
+ @attempt_counter = AttemptCounter.new(5) unless @attempt_counter
72
+ @attempt_counter.increase
73
+
74
+ if @attempt_counter.out_of_attempts?
75
+ OmniLog::warn "Can't reconect too often, sleep for #{@reconnect_long_pause/60} minutes..."
76
+ @attempt_counter = nil
77
+ @ignore_reconnect = true
78
+ @timer_provider.add_timer(@reconnect_long_pause) {
79
+ @ignore_reconnect = false
80
+ try_reconnect
81
+ }
82
+ return
83
+ end
84
+
85
+ safe_reconnect
86
+
87
+ if @client.is_connected?
88
+ @attempt_counter = nil
89
+ @roster = Jabber::Roster::Helper.new(@client)
90
+ @roster.add_subscription_request_callback { |item, pres| on_subscripton_request_callback item, pres }
91
+ end
92
+
93
+ OmniLog::debug "Client #{@client.is_connected? ? 'is' : 'isn\'t'} connected"
94
+ end
95
+
96
+ def check_presence? presence
97
+ raise 'No subscriber' unless @subscriber
98
+
99
+ OmniLog::debug "Subscriber #{@subscriber} is #{presence.show ? presence.show : 'online'}"
100
+ presence.show == nil || presence.show == :chat
101
+ end
102
+
103
+ def say_when_human orig, now
104
+ if Helpers::same_day? now, orig
105
+ amount = now - orig
106
+ if amount < 60
107
+ return "just now"
108
+ elsif amount < 60*60
109
+ return "less than a hour ago"
110
+ elsif amount < 60*60*6
111
+ return amount.div(60).to_s + " hours ago"
112
+ end
113
+ end
114
+ return orig.to_s
115
+ end
116
+
117
+
118
+ def pump_messages
119
+ while msg = @messages.shift
120
+ send msg
121
+ end
122
+ end
123
+
124
+ public
125
+
126
+ attr_writer :timer_provider
127
+
128
+ def initialize jid, password
129
+ @client = Jabber::Client::new(jid)
130
+ @password = password
131
+ raise 'No jid set' if jid.empty?
132
+ raise 'No password set' unless password
133
+
134
+ @ignore_reconnect = false
135
+ @reconnect_pause = 10
136
+ @reconnect_long_pause = 60*15
137
+
138
+ @messages = []
139
+ @subscriber_online = false
140
+
141
+ @client.on_exception { |e, stream, sym_where| on_exception_handler(e, stream, sym_where) }
142
+ @client.add_message_callback { |m| on_message_handler m }
143
+ @client.add_presence_callback { |from, to| on_presence_callback from, to }
144
+ end
145
+
146
+ def connect
147
+ try_reconnect
148
+ end
149
+
150
+ def disconnect
151
+ @client.close
152
+ end
153
+
154
+ def set_subscriber jid, resource=nil
155
+ @subscriber = jid
156
+ if resource == nil || resource == ''
157
+ @subscriber_resource = /.*/
158
+ else
159
+ @subscriber_resource = Regexp.new(resource)
160
+ end
161
+ end
162
+
163
+ def add_message message
164
+ OmniLog::debug "Register a message, " + (@subscriber_online ? "should send immediately" : "will send later")
165
+ @messages << message
166
+ pump_messages if @subscriber_online
167
+ end
168
+
169
+ def send message
170
+ raise 'Not connected' unless @client.is_connected?
171
+
172
+ OmniLog::info "Sending a message..."
173
+ orig = message[0]
174
+ content = message[1]
175
+
176
+ body = "Omnibot reported " + say_when_human(orig, Time.now) + ":\n" + content.to_s
177
+ OmniLog::debug body
178
+
179
+ msg = Jabber::Message::new(@subscriber, body)
180
+ msg.type = :chat
181
+ @client.send(msg)
182
+ end
183
+ end
184
+
185
+ end
186
+
@@ -0,0 +1,37 @@
1
+ module OmniBot
2
+
3
+ # entry point
4
+ class Launcher
5
+ def get_config_path args
6
+ return args[0] if args[0] && File.file?(args[0])
7
+ path = File.join(ENV['HOME'],'.omnibot.yaml')
8
+ return path if ENV['HOME'] && File.file?(path)
9
+ raise 'No config file found, checked command line and ~/.omnibot.yaml'
10
+ end
11
+
12
+ def get_log_path config
13
+ return config['logpath'] unless (config['logpath'] or '').empty?
14
+ return '/var/log/omnibot.log' if File.directory? '/var/log/'
15
+ return 'omnibot.log'
16
+ end
17
+
18
+ def start args
19
+ config_path = get_config_path args
20
+ puts "Using config at #{config_path}"
21
+ config = YAML.load_file(config_path)["config"]
22
+
23
+ log_path = get_log_path config
24
+ puts "Using log at #{log_path}"
25
+ OmniLog::log = Logger.new(log_path)
26
+ OmniLog::log.level = Logger::DEBUG
27
+
28
+ consumer = AMQPConsumer.new config
29
+ consumer.start
30
+
31
+ OmniLog::log.close
32
+ end
33
+ end
34
+ end
35
+
36
+
37
+
@@ -0,0 +1,23 @@
1
+ module OmniBot
2
+
3
+ class OmniSend
4
+
5
+ def start args
6
+ return 1 if args.empty?
7
+ message = args.join(' ')
8
+ puts "Sending message #{message}"
9
+ data = Marshal.dump(message)
10
+
11
+ Signal.trap('INT') { AMQP.stop{ EM.stop } }
12
+
13
+ AMQP.start do
14
+ mq = MQ.new
15
+ exchange = mq.direct(Helpers::amqp_exchange_name)
16
+ exchange.publish(data)
17
+ puts 'sent'
18
+ AMQP.stop{ EM.stop }
19
+ end
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,48 @@
1
+ module OmniBot
2
+
3
+ # Send to jabber user result of a daily command
4
+ class PeriodicCommand
5
+
6
+ def on_first_timer
7
+ OmniLog::debug "Okay, it's near of midnight"
8
+ on_periodic_timer
9
+ @timer_provider.add_periodic_timer(24*3600) { on_periodic_timer }
10
+ end
11
+
12
+ def on_periodic_timer
13
+ OmniLog::info "Reporting command #{@command}"
14
+ body = `#{@command}`
15
+ raise 'Error launching command ' if $? != 0
16
+ message_body = "Results of daily executed command #{@command}:\n" + body
17
+ @jabber_messenger.call message_body
18
+ end
19
+
20
+ public
21
+ attr_writer :timer_provider
22
+
23
+ def initialize command, pause
24
+ @command = command
25
+ @pause = pause
26
+
27
+ raise 'Wrong command' if (command == nil or command == '')
28
+ end
29
+
30
+ def start
31
+ `command -v #{@command}`
32
+ if $? != 0
33
+ OmniLog::warn "Command #{@command} is not available"
34
+ else
35
+ now = Time.now
36
+ next_report_time = Time.local(now.year, now.month, now.day+1, 1, 0, 0)
37
+ next_report_time = next_report_time + @pause
38
+ @timer_provider.add_timer(next_report_time - now) { on_first_timer }
39
+ end
40
+ end
41
+
42
+ def set_jabber_messenger &block
43
+ @jabber_messenger = block
44
+ end
45
+ end
46
+
47
+ end
48
+
@@ -0,0 +1,3 @@
1
+ module OmniBot
2
+ VERSION = '0.0.2'
3
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: omnibot
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - theirix
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-01-10 00:00:00 +03:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ hash: 3
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ prerelease: false
32
+ version_requirements: *id001
33
+ type: :runtime
34
+ name: xmpp4r
35
+ - !ruby/object:Gem::Dependency
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ hash: 3
42
+ segments:
43
+ - 0
44
+ version: "0"
45
+ prerelease: false
46
+ version_requirements: *id002
47
+ type: :runtime
48
+ name: eventmachine
49
+ - !ruby/object:Gem::Dependency
50
+ requirement: &id003 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ hash: 3
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ prerelease: false
60
+ version_requirements: *id003
61
+ type: :runtime
62
+ name: amqp
63
+ description: Works with AMQP for sending messages at server side.Sends notifications to an user via XMPP.Can monitor system by performing daily commands.
64
+ email: theirix@gmail.com
65
+ executables:
66
+ - omnibot
67
+ - omnisend
68
+ extensions: []
69
+
70
+ extra_rdoc_files: []
71
+
72
+ files:
73
+ - examples/config.yaml
74
+ - lib/omnibot/amqpconsumer.rb
75
+ - lib/omnibot/helpers.rb
76
+ - lib/omnibot/jabberbot.rb
77
+ - lib/omnibot/launcher.rb
78
+ - lib/omnibot/omnisend.rb
79
+ - lib/omnibot/periodiccommand.rb
80
+ - lib/omnibot/version.rb
81
+ - lib/omnibot.rb
82
+ - Rakefile
83
+ - README.md
84
+ - bin/omnibot
85
+ - bin/omnisend
86
+ has_rdoc: true
87
+ homepage: http://github.com/theirix/omnibot
88
+ licenses: []
89
+
90
+ post_install_message:
91
+ rdoc_options: []
92
+
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ hash: 31
101
+ segments:
102
+ - 1
103
+ - 8
104
+ version: "1.8"
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ hash: 3
111
+ segments:
112
+ - 0
113
+ version: "0"
114
+ requirements:
115
+ - AMQP-compatible server (for example, RabbitMQ)
116
+ rubyforge_project: nowarning
117
+ rubygems_version: 1.4.2
118
+ signing_key:
119
+ specification_version: 3
120
+ summary: Simple XMPP bot for server monitoring
121
+ test_files: []
122
+