Developing MineOS 0.5.0

From MineOS
Jump to: navigation, search

MineOS and its updates have traditionally been centralized in mineos.py, located in /usr/games/minecraft. While mineos.py remains the central and core functionality of MineOS, updates and testing now have a more solid, recognizable framework for deployment.

custom.py extends the mc() class from mineos.py. This means that any code added to custom.py will add to or supercede code written in mineos.py.

Contents

Example of overriding methods

Take for example the following code from mineos.py:

 
    def kill(self):
        '''
        Kills an errant server via os.kill
        '''
        if self.up:
            from signal import SIGTERM
            pid = self.pid
            logging.info('Killing server %s (pid %s)', self.server_name, pid)
            os.kill(pid, SIGTERM)
        else:
            logging.warning('Ignoring command {kill}; no live processes found for server %s', self.server_name)
            raise RuntimeWarning('Ignoring command {kill}; no live processes found for server %s' % self.server_name)

If, for example, you wished to force server-killing to have a delay, a warning, and a last attempt at stopping, you could write a new snippet of code--a function named kill in custom.py

 
    def kill(self):
        '''
        Kills an errant server via os.kill
        '''
        if self.up:
            from signal import SIGTERM
            pid = self.pid
            logging.info('ATTEMPTING TO KILL SERVER %s (pid %s)', self.server_name, pid)
            self.command('console', 'say I am taking down the server!  log off now!')
            sleep(5)
            self.command('stop')
            sleep(5)
            os.kill(pid, SIGTERM)
        else:
            logging.warning('Ignoring command {kill}; no live processes found for server %s', self.server_name)
            raise RuntimeWarning('Ignoring command {kill}; no live processes found for server %s' % self.server_name)

By creating a new method that already exists in mineos.py, it supercedes (is the method executed in place of) the original method. Of course, this is strictly an example--adding a delay to the kill function is not entirely practical, since a better approach would be to create a brand new function which includes the delay, the stop, and the kill. Thus, you might instead name the above custom method delayed_kill, a separate function, allowing you access to both an immediate and a delayed process closer.

 
    def delayed_kill(self, args):
        ...

Writing new methods / Default command() behavior

You might notice that 0.5.0 code differs vastly in structure than previous 0.4.x code. The most notable difference is the funnelling of all commands through the command() method.

    def command(self, shortcut, arguments=None):
        if shortcut in mc.TRUSTED_FUNCTIONS:
            getattr(self, shortcut)(arguments)
        elif shortcut in mc.DEREFERENCE_FUNCTIONS:
            getattr(self, shortcut)(**arguments)
        elif shortcut in [i for i in dir(self) if callable(getattr(self,i))]:
            getattr(self, shortcut)()
        elif shortcut in ['stuff', 'console']:
            self.__command_stuff(arguments)
        elif self.up:
            self.__command_stuff(shortcut.split()[0].strip())
        else:
            logging.warning('Ignored command {%s} with args %s', shortcut, arguments)
            return False
        return True

How to use

Below is the functions help documentation, which outlines usage of the command() method:

Customfunc.png

Primary function for utilizing mc-class and derivatives.

First, 'shortcut' is checked against known, named trusted functions. Trusted functions are functions which require the passing of an argument. A few are listed at the top of the mc class code. Any functions defined in derivative classes are also trusted.

Even if not a known and named trusted function, it may still be a safe function, since the function accepts no arguments. start() is an example of one such function, which is safe from injection.

Next, 'shortcut' is checked if a deliberate 'screen-stuffing'. If so, immediately go to __command_stuff with sanitized arguments.

If 'shortcut' is ambiguous at this point, such as 'list' or 'stop', these also will get screen-stuffed, but will only contain the first word of the sequence, e.g., 'stop this server!' will only execute 'stop'. Note, because of the order of checks, a manually defined function of the same name will take precedence. For example, in the derivative class, one could define "def stop(self, argv)" which would then intercept all "command('stop')" calls, as stop() would automatically become a trusted function.

If none of these apply because the above check was on a not up() server, it will simply errorlog out.

Additional Explanation

Even though class method exist that are familiar, such as start(), create(), restore(), and others, these commands are not recommended to be called directly, but instead execute through the command() arbiter.

This form of writing serves a very specific purpose--to enforce input integrity (data sanitization) and also to allow new or re-written functionality to co-exist without having to endanger the stability of the scripts.

Security

Whereas previously any content could be sent to say() which would then be typed directly into the server's console, now this is done through the __command_stuff() method. This method assures the safety of the input by running it through a sanitization mechanism, which removes potentially crashing or exploitative characters.

__command_stuff(), however, cannot be directly utilized (which is indicated by the double leading underscores), and therefore must be used via the following syntax:

command('console', 'say hello there, the server!')

By this design, any non-ascii characters passed would be stripped out--characters that the console would reject and could potentially crash from.

Stability

If one is editing mineos.py directly, anything as simple as a poorly-indented line to a typo'd letter somewhere in the function could cause the entire mineos.py to crash and accept no commands. Since then even the methods to update mineos scripts would then fail, the user would have to resort to manually downloading the files and replace them with working ones.

Put another way, errors are bound to be introduced when writing new code. By introducing new code through custom.py, if new bugs get introduced (or if new features are rejected), the user can simply delete the custom.py file, allowing the "Stable" mineos.py (free from the newest testing code) to run.

Basically, this gives users a sure-fire way to backtrack against errors I introduce into code, and also allows users to produce their own code which will persist from update to update. In other words, if you create a fantastic new 'do_mega_function()', you don't have to save it somewhere and transplant it back in after I release the next version stepping (e.g., 0.5.1)--that code will persist in your custom.py

This also means that code that is developed by the community can be SHARED via custom.py, simply by allowing users to copy-paste new code in and use it immediately--all with the easy fail-safe of "if it doesn't work the way I expect, I can delete custom.py".

Unnamed command functionality

One of the most drastic changes to the code is the elimination of many hard-coded commands. Where before if you wanted to stop the server, a stop() was coded called, now a new paradigm called duck-typing is employed.

You'll notice that stop() is not actually an existing function. What happens is this:

  1. command('stop') is called
  2. 'stop' is not recognized as a TRUSTED_FUNCTION
  3. 'stop' is not recognized as a function in mc() (mineos.py or the subclass mc_deriv() (custom.py)
  4. 'stop' is not recognized as a deliberate console command
  5. 'stop' is then sent to the console if the server is found UP

This could of course be rewritten as:

self.command('stop')
OR
self.command('console', 'stop')

This effectively stops a running server, just as how 'list' would make the console produce online players or how 'help' would show all the help commands. Therefore, even without coding it, stopping a server is still possible. More importantly, though, is that now a user could create a function stop() which would then be considered a TRUSTED_FUNCTION:

def stop(self, throwaway=None):
    if self.up:
        self.command('console', 'say IM STOPPING THE SERVER!')
        sleep(5)
        self.command('console', 'say YOUVE BEEN WARNED!')
        self.command('console', 'stop')