class Redis(Plugin): host = Plugin.Property(default="localhost") port = Plugin.Property(default=6379) channel = Plugin.Property(default="mark2-{server}") relay_events = Plugin.Property( default="StatPlayers,PlayerJoin,PlayerQuit,PlayerChat,PlayerDeath") def setup(self): channel = self.channel.format(server=self.parent.server_name) self.factory = RedisFactory(self, channel) reactor.connectTCP(self.host, self.port, self.factory) for ev in self.relay_events.split(','): ty = events.get_by_name(ev.strip()) if ty: self.register(self.on_event, ty) else: self.console("redis: couldn't bind to event: {}".format(ev)) def on_event(self, event): self.factory.relay(event.serialize())
class Push(Plugin): endpoints = Plugin.Property(default="") email_address = Plugin.Property(default="*****@*****.**") email_smtp_server = Plugin.Property(default="") email_smtp_user = Plugin.Property(default="") email_smtp_password = Plugin.Property(default="") email_smtp_security = Plugin.Property(default=False) pushover_token = Plugin.Property(default="") def setup(self): global _plugin _plugin = self self.pending = set() self.configure_endpoints() self.register(self.send_alert, ServerEvent, priority=EventPriority.MONITOR) self.eventid = reactor.addSystemEventTrigger('before', 'shutdown', self.finish) def teardown(self): reactor.removeSystemEventTrigger(self.eventid) def finish(self): return DeferredList(list(self.pending)) def configure_endpoints(self): eps = self.endpoints.split("\n") self._endpoints = [] for ep in eps: if not ep.strip(): continue try: bits = re.split(r"\s+", ep) url, md = bits[0], bits[1:] scheme, ee = re.split(":(?://)?", url) if scheme not in _endpoint: self.console("undefined endpoint requested: {}".format(url)) continue cls = _endpoint[scheme] inst = cls(self, ee) inst.url = url for k, v in [d.split("=") for d in md]: setattr(inst, k, v) self._endpoints.append(inst) except Exception as e: self.console("push: ERROR ({}) adding endpoint: {}".format(e, ep)) def send_alert(self, event): for ep in self._endpoints: if ep.filter(event): ep.push(event)
class RSS(Plugin): url = Plugin.Property(default="") check_interval = Plugin.Property(default=60) command = Plugin.Property(default="say {link} - {title}") def setup(self): self.poller = FeedPoller() def server_started(self, event): if self.url != "": self.repeating_task(self.check_feeds, self.check_interval) def check_feeds(self, event): d = getPage(self.url) d.addCallback(self.update_feeds) def update_feeds(self, data): for entry in self.poller.parse(data): m = reddit_link.match(entry['link']) if m: entry['link'] = "http://redd.it/" + m.group(1) self.send_format(self.command, **entry)
class Alert(Plugin): interval = Plugin.Property(default=200) command = Plugin.Property(default="say {message}") path = Plugin.Property(default="alerts.txt") messages = [] def setup(self): if self.path and os.path.exists(self.path): f = open(self.path, 'r') for l in f: l = l.strip() if l: self.messages.append(l) f.close() def server_started(self, event): if self.messages: self.repeating_task(self.repeater, self.interval) def repeater(self, event): self.send_format(self.command, message=random.choice(self.messages))
class Log(Plugin): path = Plugin.Property(default="logs/server.log") def setup(self): self.register(self.logger, Console) self.logger = logging.getLogger('MinecraftLogger') self.logger.setLevel(logging.DEBUG) self.logger.addHandler( logging.handlers.RotatingFileHandler(self.path, maxBytes=536870912, backupCount=5)) def logger(self, event): event.level self.logger.log(get_level(event.level), event.data)
class Mumble(Plugin): host = Plugin.Property(required=True) port = Plugin.Property(default=64738) timeout = Plugin.Property(default=10) trigger = Plugin.Property(default="!mumble") command_up = Plugin.Property(default=''' msg {username} &2host: &a{host} msg {username} &2port: &a{port} msg {username} &2status: &aup! users: {users_current}/{users_max} '''.strip()) command_down = Plugin.Property(default=''' msg {username} &2host: &a{host} msg {username} &2port: &a{port} msg {username} &2status: &adown. '''.strip()) def setup(self): self.users = [] self.protocol = MumbleProtocol(self, self.host, self.port) self.register(self.handle_trigger, ServerOutput, pattern="<([A-Za-z0-9_]{1,16})> " + re.escape(self.trigger)) reactor.listenUDP(0, self.protocol) def teardown(self): self.protocol.transport.loseConnection() def handle_trigger(self, event): username = event.match.group(1).encode('utf8') d = defer.Deferred() d.addCallback(lambda d: self.send_response( self.command_up, username=username, **d)) d.addErrback( lambda d: self.send_response(self.command_down, username=username)) #add a timeout self.delayed_task(self.got_timeout, self.timeout) self.users.append(d) self.protocol.ping() def got_response(self, d): for u in self.users: u.callback(d) self.users = [] self.stop_tasks() def got_timeout(self, e): for u in self.users: u.errback(TimeoutError()) self.users = [] self.stop_tasks() def send_response(self, command, **d): self.send_format(command, host=self.host, port=self.port, **d)
class Save(Plugin): warn_message = Plugin.Property(default="WARNING: saving map in {delay}.") message = Plugin.Property(default="MAP IS SAVING.") warn_command = Plugin.Property(default="say %s") save_command = Plugin.Property(default="save-all") save_off_command = Plugin.Property(default="save-off") save_on_command = Plugin.Property(default="save-on") save_allowed = True def setup(self): self.register(self.save, Hook, public=True, name='save', doc='save the map') self.register(self.save_off, Hook, public=True, name='save-plugin-off', doc='Disable save plugin.') self.register(self.save_on, Hook, public=True, name='save-plugin-on', doc='Enable save plugin.') def warn(self, delay): self.send_format(self.warn_command % self.warn_message, delay=delay) def save(self, event): if (self.save_allowed): action = self.save_real if event.args: warn_length, action = self.action_chain( event.args, self.warn, action) action() event.handled = True def save_real(self): if self.message: self.send(self.warn_command % self.message) self.send(self.save_command) def save_off(self, event): self.save_allowed = False self.send(self.save_off_command) event.handled = True def save_on(self, event): self.save_allowed = True self.send(self.save_on_command) event.handled = True
class ConsoleTracking(Plugin): lang_file_path = Plugin.Property(default=None) deaths = tuple() chat_events = tuple() def setup(self): if self.lang_file_path is None: lang = properties.load_jar(self.parent.jar_file, 'assets/minecraft/lang/en_US.lang', 'lang/en_US.lang', "assets/minecraft/lang/en_us.json") else: lang = properties.load(properties.Lang, self.lang_file_path) if lang is not None: self.deaths = tuple(lang.get_deaths()) self.register(self.death_handler, ServerOutput, pattern=".*") self.register_chat() def register_chat(self): ev = [] for key, e_ty in (('join', PlayerJoin), ('quit', PlayerQuit), ('chat', PlayerChat)): pattern = self.parent.config['mark2.regex.' + key] try: re.compile(pattern) except: return self.fatal_error( reason="mark2.regex.{} isn't a valid regex!".format(key)) ev.append( self.register(lambda e, e_ty=e_ty: self.dispatch( e_ty(**e.match.groupdict())), ServerOutput, pattern=pattern)) self.chat_events = tuple(ev) def death_handler(self, event): for name, (pattern, format) in self.deaths: m = re.match(pattern, event.data) if m: self.dispatch( PlayerDeath(cause=None, format=format, **m.groupdict())) break
class Monitor(Plugin): crash_enabled = Plugin.Property(default=True) crash_timeout = Plugin.Property(default=3) crash_warn = Plugin.Property(default=0) crash_unknown_cmd_message = Plugin.Property(default="Unknown.*command.*") crash_check_command = Plugin.Property(default="") oom_enabled = Plugin.Property(default=True) crash_report_enabled = Plugin.Property(default=True) jvm_crash_enabled = Plugin.Property(default=True) ping_enabled = Plugin.Property(default=True) ping_timeout = Plugin.Property(default=3) ping_warn = Plugin.Property(default=0) pcount_enabled = Plugin.Property(default=False) pcount_timeout = Plugin.Property(default=3) pcount_warn = Plugin.Property(default=0) def setup(self): do_step = False self.checks = {} if self.oom_enabled: self.register(self.handle_oom, ServerOutput, level='SEVERE', pattern=r'java\.lang\.OutOfMemoryError.*') if self.crash_report_enabled: self.register(self.handle_unknown_crash, ServerOutput, level='ERROR', pattern='This crash report has been saved to.*') if self.jvm_crash_enabled: self.register(self.handle_jvm_crash, ServerOutput, level='RAW', pattern='.*A fatal error has been detected by the Java Runtime Environment:.*') if self.crash_enabled: do_step = True self.checks['crash'] = Check(self, name="crash", timeout=self.crash_timeout, warn=self.crash_warn, message="server might have crashed: not accepting accepting console commands or crash-unknown-cmd-message is not set", warning="server might have crashed", event=("hang", "server didn't respond for {timeout}"), stop_reason="crashed") if self.ping_enabled: self.register(self.handle_ping, StatPlayerCount) do_step = True self.checks['ping'] = Check(self, name="ping", timeout=self.ping_timeout, warn=self.ping_warn, message="server might have crashed: not accepting connections or wrong port is being pinged.", warning="server might have stopped accepting connections", event=("ping", "server didn't respond for {timeout}"), stop_reason="not accepting connections") if self.pcount_enabled: self.register(self.handle_pcount, StatPlayerCount) do_step = True self.checks['pcount'] = Check(self, name="pcount", timeout=self.pcount_timeout, warn=self.pcount_warn, message="server might have crashed: has had 0 players for {timeout}", warning="server has 0 players, might be inaccessible", event=("player-count", "server had 0 players for {timeout}"), stop_reason="zero players") self.do_step = do_step def server_started(self, event): self.reset_counts() if self.do_step: self.repeating_task(self.step, 60) def load_state(self, state): self.server_started(None) def step(self, *a): for c in self.checks.values(): c.step() if self.crash_enabled: self.register(self.handle_crash_ok, ServerOutput, pattern=self.crash_unknown_cmd_message, track=False) self.send(self.crash_check_command) # Blank command to trigger 'Unknown command' def reset_counts(self): for c in self.checks.values(): c.reset() ### handlers # crash def handle_crash_ok(self, event): self.checks["crash"].reset() return Event.EAT | Event.UNREGISTER # out of memory def handle_oom(self, event): self.console('server out of memory, restarting...') self.dispatch(ServerEvent(cause='server/error/oom', data="server ran out of memory", priority=1)) self.dispatch(ServerStop(reason='out of memory', respawn=ServerStop.RESTART)) # unknown crash def handle_unknown_crash(self, event): self.console('server crashed for unknown reason, restarting...') self.dispatch(ServerEvent(cause='server/error/unknown', data="server crashed for unknown reason", priority=1)) self.dispatch(ServerStop(reason='unknown reason', respawn=ServerStop.RESTART)) # jvm crash def handle_jvm_crash(self, event): self.console('server jvm crashed, restarting...') self.dispatch(ServerEvent(cause='server/error/jvm', data="server jvm crashed", priority=1)) self.dispatch(ServerStop(reason='jvm crash', respawn=ServerStop.RESTART)) # ping def handle_ping(self, event): if event.source == 'ping': self.checks["ping"].reset() # pcount def handle_pcount(self, event): if event.players_current > 0: self.checks["pcount"].reset() else: self.checks["pcount"].alive = False
class MCBouncer(Plugin): api_base = Plugin.Property(default='http://mcbouncer.com/api') api_key = Plugin.Property(default=None) reason = Plugin.Property(default="Banned by an operator") proxy_mode = Plugin.Property(default=False) def setup(self): self.bouncer = BouncerAPI(self.api_base, self.api_key, self.on_error) self.register( self.on_login, ServerOutput, pattern= '([A-Za-z0-9_]{1,16})\[/([0-9\.]+):\d+\] logged in with entity id .+' ) self.register( self.on_ban, ServerOutput, pattern= '\[([A-Za-z0-9_]{1,16}): Banned player ([A-Za-z0-9_]{1,16})\]') self.register(self.on_ban, ServerOutput, pattern='Banned player ([A-Za-z0-9_]{1,16})') self.register( self.on_pardon, ServerOutput, pattern= '\[[A-Za-z0-9_]{1,16}: Unbanned player ([A-Za-z0-9_]{1,16})\]') self.register(self.on_pardon, ServerOutput, pattern='Unbanned player ([A-Za-z0-9_]{1,16})') def on_error(self, error): self.console("Couldn't contact mcbouncer! %s" % error.getErrorMessage()) def on_ban(self, event): g = event.match.groups() player = g[-1] issuer = g[0] if len(g) == 2 else 'console' o = self.bouncer.addBan(issuer, player, self.reason) def on_pardon(self, event): g = event.match.groups() self.bouncer.removeBan(g[0]) def on_login(self, event): g = event.match.groups() self.bouncer.getBanReason(g[0], callback=lambda d: self.ban_reason(g[0], d)) if not self.proxy_mode: self.bouncer.updateUser(g[0], g[1]) self.bouncer.getIPBanReason( g[1], callback=lambda d: self.ip_ban_reason(g[0], d)) def ban_reason(self, user, details): if details['is_banned']: self.send('kick %s Banned: %s' % (user, details['reason'])) def ip_ban_reason(self, user, details): if details['is_banned']: self.send('kick %s Banned: %s' % (user, details['reason']))
class IRC(Plugin): #connection host = Plugin.Property(required=True) port = Plugin.Property(required=True) server_password = Plugin.Property() channel = Plugin.Property(required=True) key = Plugin.Property() certificate = Plugin.Property() ssl = Plugin.Property(default=False) server_fingerprint = Plugin.Property() #user nickname = Plugin.Property(default="RelayBot") realname = Plugin.Property(default="mark2 IRC relay") ident = Plugin.Property(default="RelayBot") username = Plugin.Property(default="") password = Plugin.Property(default="") #general cancel_highlight = Plugin.Property(default=False, type_=False) cancel_highlight_str = Plugin.Property(default="_") #game -> irc settings game_columns = Plugin.Property(default=True) game_status_enabled = Plugin.Property(default=True) game_status_format = Plugin.Property(default="!, | server {what}.") game_chat_enabled = Plugin.Property(default=True) game_chat_format = Plugin.Property(default="{username}, | {message}") game_chat_private = Plugin.Property(default=None) game_join_enabled = Plugin.Property(default=True) game_join_format = Plugin.Property(default="*, | --> {username}") game_quit_enabled = Plugin.Property(default=True) game_quit_format = Plugin.Property(default="*, | <-- {username}") game_death_enabled = Plugin.Property(default=True) game_death_format = Plugin.Property(default="*, | {text}") game_server_message_enabled = Plugin.Property(default=True) game_server_message_format = Plugin.Property( default="#server, | {message}") #bukkit only game_me_enabled = Plugin.Property(default=True) game_me_format = Plugin.Property(default="*, | {username} {message}") #irc -> game settings irc_chat_enabled = Plugin.Property(default=True) irc_chat_command = Plugin.Property( default="say [IRC] <{nickname}> {message}") irc_action_command = Plugin.Property( default="say [IRC] * {nickname} {message}") irc_chat_status = Plugin.Property(default=None) irc_command_prefix = Plugin.Property(default="!") irc_command_status = Plugin.Property(default=None) irc_command_allow = Plugin.Property(default="") irc_command_mark2 = Plugin.Property(default=False) irc_players_enabled = Plugin.Property(default=True) irc_players_format = Plugin.Property( default="*, | players currently in game: {players}") def setup(self): self.players = [] self.factory = IRCBotFactory(self) if self.ssl: if have_ssl: cf = Mark2ClientContextFactory( self, cert=self.certificate, fingerprint=self.server_fingerprint) reactor.connectSSL(self.host, self.port, self.factory, cf) else: self.parent.console("Couldn't load SSL for IRC!") return else: reactor.connectTCP(self.host, self.port, self.factory) if self.game_status_enabled: self.register(self.handle_stopping, ServerStopping) self.register(self.handle_starting, ServerStarting) self.column_width = 16 if self.cancel_highlight == "insert": self.column_width += len(self.cancel_highlight_str) def register(event_type, format, filter_=None, *a, **k): def handler(event, format): d = event.match.groupdict() if hasattr( event, 'match') else event.serialize() if filter_ and 'message' in d: if filter_.match(d['message']): return if self.cancel_highlight and 'username' in d and d[ 'username'] in self.factory.client.users: d['username'] = self.mangle_username(d['username']) line = self.format(format, **d) self.factory.irc_relay(line) self.register(lambda e: handler(e, format), event_type, *a, **k) if self.game_chat_enabled: if self.game_chat_private: try: filter_ = re.compile(self.game_chat_private) register(PlayerChat, self.game_chat_format, filter_=filter_) except: self.console( "plugin.irc.game_chat_private must be a valid regex") register(PlayerChat, self.game_chat_format) else: register(PlayerChat, self.game_chat_format) if self.game_join_enabled: register(PlayerJoin, self.game_join_format) if self.game_quit_enabled: register(PlayerQuit, self.game_quit_format) if self.game_death_enabled: def handler(event): d = event.serialize() for k in 'username', 'killer': if k in d and d[k] and d[k] in self.factory.client.users: d[k] = self.mangle_username(d[k]) text = event.get_text(**d) line = self.format(self.game_death_format, text=text) self.factory.irc_relay(line) self.register(handler, PlayerDeath) if self.game_server_message_enabled and not ( self.irc_chat_enabled and self.irc_chat_command.startswith('say ')): register(ServerOutput, self.game_server_message_format, pattern=r'\[(?:Server|SERVER)\] (?P<message>.+)') if self.game_me_enabled: register( ServerOutput, self.game_me_format, pattern=r'\* (?P<username>[A-Za-z0-9_]{1,16}) (?P<message>.+)') if self.irc_chat_enabled: self.register(self.handle_players, StatPlayers) def teardown(self): self.factory.reconnect = False if self.factory.client: self.factory.client.quit("Plugin unloading.") def mangle_username(self, username): if not self.cancel_highlight: return username elif self.cancel_highlight == "insert": return username[:-1] + self.cancel_highlight_str + username[-1:] else: return self.cancel_highlight_str + username[1:] def format(self, format, **data): if self.game_columns: f = str(format).split(',', 1) f[0] = f[0].format(**data) if len(f) == 2: f[0] = f[0].rjust(self.column_width) f[1] = f[1].format(**data) return ''.join(f) else: return format.format(**data) def handle_starting(self, event): self.factory.irc_relay( self.format(self.game_status_format, what="starting")) def handle_stopping(self, event): self.factory.irc_relay( self.format(self.game_status_format, what="stopping")) def handle_players(self, event): self.players = sorted(event.players) def irc_message(self, user, message): if self.irc_chat_enabled: self.send_format(self.irc_chat_command, nickname=user, message=message) def irc_action(self, user, message): if self.irc_chat_enabled: self.console("{} {}".format(user, message)) self.send_format(self.irc_action_command, nickname=user, message=message)
class Shutdown(Plugin): restart_warn_message = Plugin.Property(default="WARNING: planned restart in {delay}.") stop_warn_message = Plugin.Property(default="WARNING: server going down for planned maintainence in {delay}.") restart_message = Plugin.Property(default="Server restarting.") stop_message = Plugin.Property(default="Server going down for maintainence.") restart_cancel_message = Plugin.Property(default="WARNING: planned restart cancelled.") restart_cancel_reason = Plugin.Property(default="WARNING: planned restart cancelled ({reason}).") stop_cancel_message = Plugin.Property(default="WARNING: planned maintenance cancelled.") stop_cancel_reason = Plugin.Property(default="WARNING: planned maintenance cancelled ({reason}).") kick_command = Plugin.Property(default="kick {player} {message}") kick_mode = Plugin.Property(default="all") failsafe = None cancel_preempt = 0 restart_on_empty = False restore = ('cancel_preempt', 'cancel', 'restart_on_empty') def setup(self): self.players = [] self.cancel = [] self.register(self.handle_players, StatPlayers) self.register(self.handle_player_count, StatPlayerCount) self.register(self.h_stop, Hook, public=True, name="stop", doc='cleanly stop the server. specify a delay like `~stop 2m`') self.register(self.h_restart, Hook, public=True, name="restart", doc='cleanly restart the server. specify a delay like `~restart 30s`') self.register(self.h_restart_empty, Hook, public=True, name="restart-empty",doc='restart the server next time it has 0 players') self.register(self.h_kill, Hook, public=True, name="kill", doc='kill the server') self.register(self.h_kill_restart, Hook, public=True, name="kill-restart", doc='kill the server and bring it back up') self.register(self.h_cancel, Hook, public=True, name="cancel", doc='cancel an upcoming shutdown or restart') def server_started(self, event): self.restart_on_empty = False self.cancel_preempt = 0 def warn_restart(self, delay): self.send_format("say %s" % self.restart_warn_message, delay=delay) def warn_stop(self, delay): self.send_format("say %s" % self.stop_warn_message, delay=delay) def warn_cancel(self, reason, thing): if reason: message = self.restart_cancel_reason if thing == "restart" else self.stop_cancel_reason else: message = self.restart_cancel_message if thing == "restart" else self.stop_cancel_message self.send_format("say %s" % message, reason=reason) def nice_stop(self, respawn, kill): if not kill: message = self.restart_message if respawn else self.stop_message if self.kick_mode == 'all': for player in self.players: self.send_format(self.kick_command, player=player, message=message) elif self.kick_mode == 'once': self.send_format(self.kick_command, message=message) self.dispatch(ServerStop(reason='console', respawn=respawn, kill=kill)) def handle_players(self, event): self.players = event.players def handle_player_count(self, event): if event.players_current == 0 and self.restart_on_empty: self.restart_on_empty = False self.nice_stop(True, False) def cancel_something(self, reason=None): thing, cancel = self.cancel.pop(0) cancel(reason, thing) def should_cancel(self): if self.cancel_preempt: self.cancel_preempt -= 1 return True else: return False #Hook handlers: def h_stop(self, event=None): if self.should_cancel(): self.console("I'm not stopping because this shutdown was cancelled with ~cancel") return action = lambda: self.nice_stop(False, False) if event and event.args: warn_length, action, cancel = self.action_chain_cancellable(event.args, self.warn_stop, action, self.warn_cancel) self.cancel.append(("stop", cancel)) action() def h_restart(self, event=None): if self.should_cancel(): self.console("I'm not restarting because this shutdown was cancelled with ~cancel") return action = lambda: self.nice_stop(True, False) if event and event.args: warn_length, action, cancel = self.action_chain_cancellable(event.args, self.warn_restart, action, self.warn_cancel) self.cancel.append(("restart", cancel)) action() def h_restart_empty(self, event): if self.restart_on_empty: self.console("I was already going to do that") else: self.console("I will restart the next time the server empties") self.restart_on_empty = True def h_kill(self, event): self.nice_stop(False, True) def h_kill_restart(self, event): self.nice_stop(True, True) def h_cancel(self, event): if self.cancel: self.cancel_something(event.args or None) else: self.cancel_preempt += 1 self.console("I will cancel the next thing")
class Process(Plugin): name = "process" protocol = None respawn = False service_stopping = None transport = None failsafe = None stat_process = None done_pattern = Plugin.Property(default='Done \\(([0-9\\.]+)s\\)\\!.*') stop_cmd = Plugin.Property(default='stop\n') java_path = Plugin.Property(default='java') server_args = Plugin.Property(default='') def setup(self): self.register(self.server_input, events.ServerInput, priority=EventPriority.MONITOR) self.register(self.server_start, events.ServerStart, priority=EventPriority.MONITOR) self.register(self.server_starting, events.ServerStarting) self.register(self._server_started, events.ServerOutput, pattern=self.done_pattern) self.register(self.server_stop, events.ServerStop, priority=EventPriority.MONITOR) self.register(self.server_stopping, events.ServerStopping, priority=EventPriority.MONITOR) self.register(self.server_stopped, events.ServerStopped, priority=EventPriority.MONITOR) reactor.addSystemEventTrigger('before', 'shutdown', self.before_reactor_stop) def build_command(self): cmd = [] cmd.append(self.java_path) #cmd.append('-server') cmd.extend(self.parent.config.get_jvm_options()) cmd.append('-jar') cmd.append(self.parent.jar_file) cmd.append('nogui') cmd.extend(shlex.split(self.server_args)) return cmd def server_start(self, e=None): self.parent.console("starting minecraft server") self.locale = locale.getpreferredencoding() self.protocol = ProcessProtocol(self.parent.events.dispatch, self.locale) cmd = self.build_command() self.transport = reactor.spawnProcess(self.protocol, cmd[0], cmd, env=None) if e: e.handled = True def server_input(self, e): if self.protocol and self.protocol.alive: l = e.line if not l.endswith('\n'): l += '\n' self.transport.write(l.encode(self.locale, 'ignore')) e.handled = True def server_starting(self, e): self.stat_process = task.LoopingCall(self.update_stat, psutil.Process(e.pid)) self.stat_process.start(self.parent.config['java.ps.interval']) def _server_started(self, e): self.parent.events.dispatch( events.ServerStarted(time=e.match.group(1))) @defer.inlineCallbacks def server_stop(self, e): e.handled = True if self.protocol is None or not self.protocol.alive: return if e.announce: yield self.parent.events.dispatch( events.ServerStopping(respawn=e.respawn, reason=e.reason, kill=e.kill)) if e.kill: self.failsafe = None self.parent.console("killing minecraft server") self.transport.signalProcess('KILL') else: self.parent.console("stopping minecraft server") self.transport.write(self.stop_cmd) self.failsafe = self.parent.events.dispatch_delayed( events.ServerStop(respawn=e.respawn, reason=e.reason, kill=True, announce=False), self.parent.config['mark2.shutdown_timeout']) def server_stopping(self, e): self.respawn = e.respawn def server_stopped(self, e): if self.stat_process and self.stat_process.running: self.stat_process.stop() if self.failsafe: self.failsafe.cancel() self.failsafe = None if self.respawn: self.parent.events.dispatch(events.ServerStart()) self.respawn = False elif self.service_stopping: self.service_stopping.callback(0) else: print "I'm stopping the reactor now" reactor.stop() def update_stat(self, process): try: self.parent.events.dispatch( events.StatProcess(cpu=process.get_cpu_percent(interval=0), memory=process.get_memory_percent())) except psutil.error.NoSuchProcess: pass def before_reactor_stop(self): if self.protocol and self.protocol.alive: self.parent.events.dispatch( events.ServerStop(reason="SIGINT", respawn=False)) self.service_stopping = defer.Deferred() return self.service_stopping
class Log(Plugin): gzip = Plugin.Property(default=True) path = Plugin.Property(default="logs/server-{timestamp}-{status}.log.gz") vanilla = Plugin.Property(default=False) log = u"" reason = "unknown" time_re = re.compile(r'(?:\d{2}:\d{2}:\d{2}) (.*)') restore = ('log', ) def setup(self): if self.vanilla: self.register(self.vanilla_logger, ServerOutput, pattern='.*') else: self.register(self.logger, Console) self.register(self.shutdown, ServerStopped) self.register(self.pre_shutdown, ServerStopping) def vanilla_logger(self, event): m = self.time_re.match(event.line) if m: self.log += u"{0} {1}\n".format(event.time, m.group(1)) else: self.log += u"{0}\n".format(event.line) def logger(self, event): self.log += u"{0}\n".format(event.value()) def pre_shutdown(self, event): self.reason = event.reason def shutdown(self, event): reason = self.reason if reason == None: reason = "ok" timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S") path = self.path.format(timestamp=timestamp, name=self.parent.name, status=reason) if not os.path.exists(os.path.dirname(path)): try: os.makedirs(os.path.dirname(path)) except IOError: self.console( "Warning: {0} does't exist and I can't create it".format( os.path.dirname(path)), kind='error') return if self.gzip: f = gzip.open(path, 'wb') else: f = open(path, 'w') f.write(self.log.encode('utf8')) f.close() self.console("server.log written to %s" % os.path.realpath(path)) self.log = ""
class Backup(Plugin): path = Plugin.Property(default="backups/{timestamp}.tar.gz") mode = Plugin.Property(default="include") spec = Plugin.Property(default="world*") tar_flags = Plugin.Property(default='-hpczf') flush_wait = Plugin.Property(default=5) backup_stage = 0 autosave_enabled = True proto = None done_backup = None def setup(self): self.register(self.backup, Hook, public=True, name='backup', doc='backup the server to a .tar.gz') self.register(self.autosave_changed, ServerOutput, pattern="(?P<username>[A-Za-z0-9_]{1,16}): (?P<action>Enabled|Disabled) level saving\.\.") self.register(self.autosave_changed, ServerOutput, pattern="Turned (?P<action>on|off) world auto-saving") self.register(self.server_stopped, ServerStopped, priority=EventPriority.HIGHEST) def server_started(self, event): self.autosave_enabled = True @EventPriority.HIGH @defer.inlineCallbacks def server_stopping(self, event): if self.backup_stage > 0: self.console("backup: delaying server stop until backup operation completes.") yield self.done_backup self.stop_tasks() self.autosave_enabled = False def server_stopped(self, event): self.autosave_enabled = False def save_state(self): if self.proto: self.console("stopping in-progress backup!") self.proto.transport.signalProcess('KILL') if self.done_backup: self.done_backup.callback(None) return self.autosave_enabled def load_state(self, state): self.autosave_enabled = state def autosave_changed(self, event): self.autosave_enabled = event.match.groupdict()['action'].lower() in ('on', 'enabled') if self.backup_stage == 1 and not self.autosave_enabled: self.backup_stage = 2 self.delayed_task(self.do_backup, self.flush_wait) elif self.backup_stage == 2: self.console("warning: autosave changed while backup was in progress!") def backup(self, event): if self.backup_stage > 0: self.console("backup already in progress!") return self.done_backup = defer.Deferred() self.console("map backup starting...") self.autosave_enabled_prev = self.autosave_enabled if self.autosave_enabled: self.backup_stage = 1 self.send('save-off') else: self.backup_stage = 2 self.do_backup() return self.done_backup def do_backup(self, *a): timestamp = time.strftime("%Y-%m-%d-%H-%M-%S", time.gmtime()) path = self.path.format(timestamp=timestamp, name=self.parent.server_name) if not os.path.exists(os.path.dirname(path)): try: os.makedirs(os.path.dirname(path)) except IOError: self.console("Warning: {0} does't exist and I can't create it".format(os.path.dirname(path)), kind='error') return if self.mode == "include": add = set() for e in self.spec.split(";"): add |= set(glob.glob(e)) elif self.mode == "exclude": add = set(glob.glob('*')) for e in self.spec.split(";"): add -= set(glob.glob(e)) cmd = ['tar'] cmd.extend(shlex.split(self.tar_flags)) cmd.append(path) cmd.extend(add) def p_ended(path): self.console("map backup saved to %s" % path) if self.autosave_enabled_prev: self.send('save-on') self.backup_stage = 0 self.proto = None if self.done_backup: d = self.done_backup self.done_backup = None d.callback(None) self.proto = protocol.ProcessProtocol() self.proto.processEnded = lambda reason: p_ended(path) self.proto.childDataReceived = lambda fd, d: self.console(d.strip()) reactor.spawnProcess(self.proto, "/bin/tar", cmd)
class Monitor(Plugin): crash_enabled = Plugin.Property(default=True) crash_timeout = Plugin.Property(default=3) crash_warn = Plugin.Property(default=0) oom_enabled = Plugin.Property(default=True) ping_enabled = Plugin.Property(default=True) ping_timeout = Plugin.Property(default=3) ping_warn = Plugin.Property(default=0) pcount_enabled = Plugin.Property(default=False) pcount_timeout = Plugin.Property(default=3) pcount_warn = Plugin.Property(default=0) def setup(self): do_step = False self.checks = {} if self.oom_enabled: self.register(self.handle_oom, ServerOutput, level='SEVERE', pattern='java\.lang\.OutOfMemoryError.*') if self.crash_enabled: do_step = True self.checks['crash'] = Check(self, name="crash", timeout=self.crash_timeout, warn=self.crash_warn, message="server has crashed", warning="server might have crashed", event=("hang", "server didn't respond for {timeout}"), stop_reason="crashed") if self.ping_enabled: self.register(self.handle_ping, StatPlayerCount) do_step = True self.checks['ping'] = Check(self, name="ping", timeout=self.ping_timeout, warn=self.ping_warn, message="server is not accepting connections", warning="server might have stopped accepting connections", event=("ping", "server didn't respond for {timeout}"), stop_reason="not accepting connections") if self.pcount_enabled: self.register(self.handle_pcount, StatPlayerCount) do_step = True self.checks['pcount'] = Check(self, name="pcount", timeout=self.pcount_timeout, warn=self.pcount_warn, message="server has had 0 players for {timeout}, something is wrong", warning="server has 0 players, might be inaccessible", event=("player-count", "server had 0 players for {timeout}"), stop_reason="zero players") self.do_step = do_step def server_started(self, event): self.reset_counts() if self.do_step: self.repeating_task(self.step, 60) def load_state(self, state): self.server_started(None) def step(self, *a): for c in self.checks.values(): c.step() if self.crash_enabled: self.register(self.handle_crash_ok, ServerOutput, pattern='Unknown command.*', track=False) self.send('') # Blank command to trigger 'Unknown command' def reset_counts(self): for c in self.checks.values(): c.reset() ### handlers # crash def handle_crash_ok(self, event): self.checks["crash"].reset() return Event.EAT | Event.UNREGISTER # out of memory def handle_oom(self, event): self.console('server out of memory, restarting...') self.dispatch(ServerEvent(cause='server/error/oom', data="server ran out of memory", priority=1)) self.dispatch(ServerStop(reason='out of memory', respawn=True)) # ping def handle_ping(self, event): if event.source == 'ping': self.checks["ping"].reset() # pcount def handle_pcount(self, event): if event.players_current > 0: self.checks["pcount"].reset() else: self.checks["pcount"].alive = False
class TelegramRelay(Plugin): #connection telegram_token = Plugin.Property(required=True) telegram_channel = Plugin.Property(required=True) #game -> irc settings game_columns = Plugin.Property(default=True) game_status_enabled = Plugin.Property(default=True) game_status_format = Plugin.Property(default=u"!, | server {what}.") game_chat_enabled = Plugin.Property(default=True) game_chat_format = Plugin.Property(default=u"{username}, | {message}") game_chat_private = Plugin.Property(default=None) game_join_enabled = Plugin.Property(default=True) game_join_format = Plugin.Property(default=u"*, | --> {username}") game_quit_enabled = Plugin.Property(default=True) game_quit_format = Plugin.Property(default=u"*, | <-- {username}") game_death_enabled = Plugin.Property(default=True) game_death_format = Plugin.Property(default=u"*, | {text}") game_server_message_enabled = Plugin.Property(default=True) game_server_message_format = Plugin.Property(default=u"#server, | {message}") #bukkit only game_me_enabled = Plugin.Property(default=True) game_me_format = Plugin.Property(default=u"*, | {username} {message}") #irc -> game settings telegram_chat_enabled = Plugin.Property(default=True) telegram_chat_command = Plugin.Property(default=u"say [TELEGRAM] <{nickname}> {message}") telegram_action_command = Plugin.Property(default=u"say [TELEGRAM] * {nickname} {message}") telegram_chat_status = Plugin.Property(default=None) telegram_command_prefix = Plugin.Property(default="!") telegram_command_status = Plugin.Property(default=None) telegram_command_allow = Plugin.Property(default="") telegram_command_mark2 = Plugin.Property(default=False) telegram_players_enabled = Plugin.Property(default=True) telegram_players_format = Plugin.Property(default=u"*, | players currently in game: {players}") def setup(self): self.players = [] self.bot = TelegramBot(self) self.bot.start() if self.game_status_enabled: self.register(self.handle_stopping, ServerStopping) self.register(self.handle_starting, ServerStarting) self.column_width = 16 self.register(self.restart_listener, Hook, public=True, name='telegramRestart', doc='Restart the listener') def register(event_type, format, filter_=None, *a, **k): def handler(event, format): d = event.match.groupdict() if hasattr(event, 'match') else event.serialize() if filter_ and 'message' in d: if filter_.match(d['message']): return line = self.format(format, **d) self.bot.relay(line) self.register(lambda e: handler(e, format), event_type, *a, **k) if self.game_chat_enabled: if self.game_chat_private: try: filter_ = re.compile(self.game_chat_private) register(PlayerChat, self.game_chat_format, filter_=filter_) except: self.console("plugin.telegram.game_chat_private must be a valid regex") register(PlayerChat, self.game_chat_format) else: register(PlayerChat, self.game_chat_format) if self.game_join_enabled: register(PlayerJoin, self.game_join_format) if self.game_quit_enabled: register(PlayerQuit, self.game_quit_format) if self.game_death_enabled: def handler(event): d = event.serialize() for k in 'username', 'killer': if k in d and d[k] and d[k] in self.factory.client.users: d[k] = self.mangle_username(d[k]) text = event.get_text(**d) line = self.format(self.game_death_format, text=text) self.bot.relay(line) self.register(handler, PlayerDeath) if self.game_server_message_enabled and not (self.telegram_chat_enabled and self.telegram_chat_command.startswith('say ')): register(ServerOutput, self.game_server_message_format, pattern=r'\[(?:Server|SERVER)\] (?P<message>.+)') if self.game_me_enabled: register(ServerOutput, self.game_me_format, pattern=r'\* (?P<username>[A-Za-z0-9_]{1,16}) (?P<message>.+)') if self.telegram_chat_enabled: self.register(self.handle_players, StatPlayers) def teardown(self): self.bot.stop() def mangle_username(self, username): return username def format(self, format, **data): if self.game_columns: f = unicode(format).split(',', 1) f[0] = f[0].format(**data) if len(f) == 2: f[0] = f[0].rjust(self.column_width) f[1] = f[1].format(**data) return ''.join(f) else: return format.format(**data) def handle_starting(self, event): self.bot.relay(self.format(self.game_status_format, what="starting")) self.bot.start() def handle_stopping(self, event): self.bot.relay(self.format(self.game_status_format, what="stopping")) self.bot.stop() def handle_players(self, event): self.players = sorted(event.players) def telegram_message(self, user, message): if self.telegram_chat_enabled: self.send_format(self.telegram_chat_command, nickname=user, message=message) def restart_listener(self,event): self.bot.stop() self.bot.start() print("Bot Restarted")
class Process(Plugin): name = "process" protocol = None respawn = False service_stopping = None transport = None failsafe = None stat_process = None done_pattern = Plugin.Property(default='Done \(([0-9\.]+)s\)!.*') stop_cmd = Plugin.Property(default='stop\n') java_path = Plugin.Property(default='java') server_args = Plugin.Property(default='') def setup(self): self.register(self.server_input, events.ServerInput, priority=EventPriority.MONITOR) self.register(self.server_start, events.ServerStart, priority=EventPriority.MONITOR) self.register(self.server_starting, events.ServerStarting) self.register(self._server_started, events.ServerOutput, pattern=self.done_pattern) self.register(self.server_stop, events.ServerStop, priority=EventPriority.MONITOR) self.register(self.server_stopping, events.ServerStopping, priority=EventPriority.MONITOR) self.register(self.server_stopped, events.ServerStopped, priority=EventPriority.MONITOR) reactor.addSystemEventTrigger('before', 'shutdown', self.before_reactor_stop) def build_command(self): cmd = [] cmd.append(self.java_path) cmd.extend(self.parent.config.get_jvm_options()) cmd.append('-jar') cmd.append(self.parent.jar_file) cmd.append('nogui') cmd.extend(shlex.split(self.server_args)) return cmd def server_start(self, e=None): # make sure the server is actually not running if self.protocol and self.protocol.alive: self.console("skipping start event as the server is already running") else: self.parent.console("starting %s" % self.parent.server_name) self.locale = locale.getpreferredencoding() self.protocol = ProcessProtocol(self.parent.events.dispatch, self.locale) cmd = self.build_command() self.transport = reactor.spawnProcess(self.protocol, cmd[0], cmd, env=None) if e: e.handled = True def server_input(self, e): if self.protocol and self.protocol.alive: l = e.line if not l.endswith('\n'): l += '\n' self.transport.write(l.encode(self.locale, 'ignore')) e.handled = True def server_starting(self, e): self.stat_process = task.LoopingCall(self.update_stat, psutil.Process(e.pid)) self.stat_process.start(self.parent.config['java.ps.interval']) self.parent.console("jar file: %s" % self.parent.jar_file) self.parent.console("pid: %s" % e.pid) self.parent.console("jvm options: %s" % ' '.join(self.parent.config.get_jvm_options())) def _server_started(self, e): self.parent.events.dispatch(events.ServerStarted()) @defer.inlineCallbacks def server_stop(self, e): e.handled = True if self.protocol is None or not self.protocol.alive: if e.respawn == events.ServerStop.TERMINATE: print("I'm stopping the reactor now! Reason: {}".format(e.reason)) reactor.stop() return else: self.parent.console("server is not running") return if e.announce: yield self.parent.events.dispatch(events.ServerStopping(respawn=e.respawn, reason=e.reason, kill=e.kill)) if self.failsafe and self.failsafe.active(): self.failsafe.cancel() self.failsafe = None if e.kill: self.failsafe = None self.parent.console("killing %s (caused by %s)" % (self.parent.server_name,e.reason)) self.transport.signalProcess('KILL') else: self.parent.console("stopping %s (caused by %s)" % (self.parent.server_name,e.reason)) self.transport.write(self.stop_cmd.encode(self.locale)) self.failsafe = self.parent.events.dispatch_delayed(events.ServerStop(respawn=e.respawn, reason=e.reason, kill=True, announce=False), self.parent.config['mark2.shutdown_timeout']) def server_stopping(self, e): self.respawn = e.respawn def server_stopped(self, e): if self.stat_process and self.stat_process.running: self.stat_process.stop() if self.failsafe and self.failsafe.active(): self.failsafe.cancel() self.failsafe = None if self.respawn == events.ServerStop.RESTART: self.parent.events.dispatch(events.ServerStart()) self.respawn = events.ServerStop.TERMINATE elif self.respawn == events.ServerStop.HOLD: self.respawn = events.ServerStop.TERMINATE return elif self.service_stopping: self.service_stopping.callback(0) else: print("I'm stopping the reactor now") reactor.stop() def update_stat(self, process): try: self.parent.events.dispatch(events.StatProcess(cpu=process.cpu_percent(interval=0), memory=process.memory_percent())) except psutil.NoSuchProcess: pass def before_reactor_stop(self): if self.protocol and self.protocol.alive: self.parent.events.dispatch(events.ServerStop(reason="SIGINT", respawn=False)) self.service_stopping = defer.Deferred() return self.service_stopping