Example #1
0
class Exec(Plugin):
    command = Plugin.Property(default="msg {user} {message}")
    path = Plugin.Property(default="triggerExec.txt")
    
    triggers = {}
    
    def setup(self):
        if self.path and os.path.exists(self.path):
            f = open(self.path, 'r')
            for l in f:
                m = re.match('^\!?([^,]+),(.+)$', l)
                if m:
                    a, b = m.groups()
                    c = self.triggers.get(a, [])
                    c.append(b)
                    self.triggers[a] = c
            f.close()
            
            if self.triggers:
                self.register(self.trigger, ServerOutput, pattern='<([A-Za-z0-9_]{1,16})> \!(\w+)')
    
    def trigger(self, event):
        user, trigger = event.match.groups()
        if trigger in self.triggers:
            for line in self.triggers[trigger]:
                os.system(line)
Example #2
0
class Alert(Plugin):
    interval = Plugin.Property(default=200)
    command  = Plugin.Property(default="say {message}")
    path     = Plugin.Property(default="alerts.txt")
    min_pcount = Plugin.Property(default=0)
    
    messages = []
    requirements_met = True
    
    def setup(self):
        self.register(self.count_check, StatPlayerCount)
        if self.path and os.path.exists(self.path):
            f = open(self.path)
            for l in f:
                l = l.strip()
                if l:
                    self.messages.append(l)
            f.close()

    def count_check(self, event):
        if event.players_current >= self.min_pcount:
            self.requirements_met = True
        else:
            self.requirements_met = False

    def server_started(self, event):
        if self.messages:
            self.repeating_task(self.repeater, self.interval)

    def repeater(self, event):
        if self.requirements_met:
            self.send_format(self.command, message=random.choice(self.messages))
Example #3
0
File: save.py Project: wuvvy/mark2
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")

    def setup(self):
        self.register(self.save,
                      Hook,
                      public=True,
                      name='save',
                      doc='save the map')

    def warn(self, delay):
        self.send_format(self.warn_command % self.warn_message, delay=delay)

    def save(self, event):
        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)
Example #4
0
class Script(Plugin):
    path = Plugin.Property(default='scripts.txt')
    shell = Plugin.Property(default='/bin/sh')

    def setup(self):
        self.scripts = []
        if not os.path.isfile(self.path):
            return

        with open(self.path) as f:
            for line in f:
                line = line.strip()
                if line.startswith('#') or line == '':
                    continue
                try:
                    self.scripts.append(ScriptEntry(self, line))
                except Exception as e:
                    self.console('invalid script line: %s' % line,
                                 kind='error')
                    self.console(str(e))

        for script in self.scripts:
            if script.type == 'time':
                self.delayed_task(
                    lambda a: self.repeating_task(self.step, 60, now=True),
                    max(0, 60 - localtime().tm_sec) % 60 + 1)
                break

    def step(self, event):
        for script in self.scripts:
            script.step()

    def server_stopping(self, event):
        pass  # don't cancel tasks
Example #5
0
class Log(Plugin):
    gzip      = Plugin.Property(default=True)
    path      = Plugin.Property(default="logs/server-{timestamp}-{status}.log.gz")
    vanilla   = Plugin.Property(default=False)
    
    log = ""
    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 += "{} {}\n".format(event.time, m.group(1))
        else:
            self.log += "{}\n".format(event.line)
    
    def logger(self, event):
        self.log += "{}\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 OSError:
                self.console("Warning: {} 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 = ""
Example #6
0
class Ping(Plugin):
    alive = False
    event_id = None

    interval = Plugin.Property(default=10)

    def setup(self):
        self.host = self.parent.properties['server_ip'] or '127.0.0.1'

        self.task = task.LoopingCall(self.loop)
        self.task.start(self.interval, now=False)

    def server_started(self, event):
        if self.event_id:
            self.parent.events.unregister(self.event_id)
        pattern = r"\s*(?:/{0}:\d+ lost connection|Reached end of stream for /{0})"
        self.event_id = self.parent.events.register(lambda ev: Event.EAT,
                                                    ServerOutput,
                                                    pattern=pattern.format(
                                                        self.host))

    def loop(self):
        host = self.parent.properties['server_ip'] or '127.0.0.1'
        port = self.parent.properties['server_port']

        factory = PingFactory(self.parent.events.dispatch)

        reactor.connectTCP(host, port, factory, bindAddress=(self.host, 0))
Example #7
0
File: push.py Project: wuvvy/mark2
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("\s+", ep)
                url, md = bits[0], bits[1:]
                scheme, ee = re.split(":(?://)?", url)
                if scheme not in _endpoint:
                    self.console(
                        "undefined endpoint requested: {0}".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 ({0}) adding endpoint: {1}".format(
                    e, ep))

    def send_alert(self, event):
        for ep in self._endpoints:
            if ep.filter(event):
                ep.push(event)
Example #8
0
File: su.py Project: vemacs/mark2
class Su(Plugin):
    command = Plugin.Property(default="sudo -su {user} -- {command}")
    mode = Plugin.Property(default="include")
    proc = Plugin.Property(default="ban;unban")

    def setup(self):
        self.register(self.uinput, UserInput)

    def uinput(self, event):
        handled = False
        for p in self.proc.split(";"):
            if event.line.startswith(p):
                handled = True
                break

        if (self.mode == 'exclude') ^ handled:
            event.line = self.command.format(user=event.user,
                                             command=event.line)
Example #9
0
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: {0}".format(ev))

    def on_event(self, event):
        self.factory.relay(event.serialize())
Example #10
0
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))
Example #11
0
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)
Example #12
0
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)
Example #13
0
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)
Example #14
0
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
Example #15
0
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)
Example #16
0
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
Example #17
0
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
Example #18
0
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']))
Example #19
0
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
Example #20
0
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")
Example #21
0
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")
Example #22
0
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
Example #23
0
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)