Skip to main content

Scale-to-zero any server

Project description

zeroscale

Build Status Coverage Status Requirements Status

PyPI Package latest release PyPI Wheel Supported versions

Scale-to-zero any server

Some servers don't idle well. Either they constantly compute things (like Minecraft keeping spawn chunks always loaded), or they do things you don't want them to. zeroscale sits in front of a server and only spins it up when someone tries to connect to it, proxying the connection.

This won't save you any cost if you pay for uptime, but it will save CPU cycles.

Usage

usage: zeroscale [-h] [--idle_shutdown IDLE_SHUTDOWN]
                 [--working_directory WORKING_DIRECTORY]
                 [--plugin_argument PLUGIN_ARGUMENT] [--info] [--debug]
                 server_plugin listen_port server_port

Scale a server to zero.

positional arguments:
  server_plugin         Package name of the server plugin. Must be in plugins
                        dir.
  listen_port           Port for the proxy server, where clients will connect.
  server_port           Port that the real server will be listening on.

optional arguments:
  -h, --help            show this help message and exit
  --idle_shutdown IDLE_SHUTDOWN, -t IDLE_SHUTDOWN
                        Time in seconds after last client disconects to kill
                        the server.
  --working_directory WORKING_DIRECTORY, -w WORKING_DIRECTORY
                        Directory to start the server process.
  --plugin_argument PLUGIN_ARGUMENT, -a PLUGIN_ARGUMENT
                        Arguments to pass to the Server() constructor in the
                        plugin. Can be called multiple times.
  --ignore_bad_clients, -b
                        Disable checking for a bad client connection. This
                        would prevent port scanners from starting servers, but
                        if your real clients are failing the check, you can
                        disable it.
  --info, -i            Enable info logging.
  --debug, -d           Enable debug logging. Default is WARNING

Example

$ zeroscale minecraft 25565 25575 --debug
DEBUG:zeroscale.zeroscale:Listening on ('::', 25565, 0, 0)
DEBUG:zeroscale.zeroscale:Listening on ('0.0.0.0', 25565)
...
DEBUG:zeroscale.zeroscale:New connection, server is stopped
DEBUG:zeroscale.zeroscale:Invalid client attempted connection  # Detects invalid client
...
DEBUG:zeroscale.zeroscale:New connection, server is stopped
DEBUG:zeroscale.zeroscale:Sending fake response  # Actually shows valid message in client!
INFO:zeroscale.plugins.minecraft:Starting Minecraft server
...
INFO:zeroscale.plugins.minecraft:Minecraft server online
DEBUG:zeroscale.zeroscale:Scheduling Server server stop
...
DEBUG:zeroscale.zeroscale:New connection, server is running
DEBUG:zeroscale.zeroscale:New connection, total clients: 1
DEBUG:zeroscale.zeroscale:Canceling Server server stop
...
DEBUG:zeroscale.zeroscale:Lost connection, total clients: 0
DEBUG:zeroscale.zeroscale:Scheduling Server server stop
...
DEBUG:zeroscale.zeroscale:No clients online for 15 seconds
INFO:zeroscale.plugins.minecraft:Stopping Minecraft server
INFO:zeroscale.plugins.minecraft:Minecraft server offline

Plugins

Any server can run behind the proxy, simply fill out the template for a package in the "plugins/" directory:

class Server():
    def __init__(self,
            # Any other parameters, will come from --plugin_argument params
            working_directory: str = None):

        self.working_directory = working_directory

        self.status = Status.stopped

    async def start(self):
        if self.status is not Status.stopped:
            return

        logger.info('Starting server')
        self.status = Status.starting

        # Whatever to run the server, probably an await asyncio.create_subprocess_exec()

        logger.info('Server online')
        self.status = Status.running

    async def stop(self):
        if self.status is not Status.running:
            return

        logger.info('Stopping server')
        self.status = Status.stopping

        # Whatever to stop the server

        logger.info('Server offline')
        self.status = Status.stopped

    async def is_valid_connection(self, client_reader):
        return # If the connection is from a valid client (to stop port scanners)

    def fake_status(self) -> bytes:
        return # Some bytes for when a client tries to connect and the server is not online

Systemd

Example systemd configs are located in systemd/ to accompany the plugins.

Known issues

  • Plugins that use subprocess pipes to read stdin, stdout, or stderr don't work on Cygwin, as the OS is seen as posix and thus doesn't ship with the ProactorEventLoop, but since the backend OS is Windows, the default event loop won't work. This is a bug in the Cygwin Python package.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zeroscale-0.4.2.tar.gz (9.5 kB view hashes)

Uploaded Source

Built Distribution

zeroscale-0.4.2-py3-none-any.whl (23.7 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page