1350 lines
46 KiB
Python
1350 lines
46 KiB
Python
#!/usr/bin/env python
|
|
"""
|
|
EVENNIA SERVER LAUNCHER SCRIPT
|
|
|
|
This is the start point for running Evennia.
|
|
|
|
Sets the appropriate environmental variables and launches the server
|
|
and portal through the evennia_runner. Run without arguments to get a
|
|
menu. Run the script with the -h flag to see usage information.
|
|
|
|
"""
|
|
|
|
from builtins import input, range
|
|
|
|
import os
|
|
import sys
|
|
import signal
|
|
import shutil
|
|
import importlib
|
|
from distutils.version import LooseVersion
|
|
from argparse import ArgumentParser
|
|
from subprocess import Popen, check_output, call, CalledProcessError, STDOUT
|
|
import django
|
|
|
|
# Signal processing
|
|
SIG = signal.SIGINT
|
|
CTRL_C_EVENT = 0 # Windows SIGINT-like signal
|
|
|
|
# Set up the main python paths to Evennia
|
|
EVENNIA_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
import evennia
|
|
EVENNIA_LIB = os.path.join(os.path.dirname(os.path.abspath(evennia.__file__)))
|
|
EVENNIA_SERVER = os.path.join(EVENNIA_LIB, "server")
|
|
EVENNIA_RUNNER = os.path.join(EVENNIA_SERVER, "evennia_runner.py")
|
|
EVENNIA_TEMPLATE = os.path.join(EVENNIA_LIB, "game_template")
|
|
EVENNIA_PROFILING = os.path.join(EVENNIA_SERVER, "profiling")
|
|
EVENNIA_DUMMYRUNNER = os.path.join(EVENNIA_PROFILING, "dummyrunner.py")
|
|
|
|
TWISTED_BINARY = "twistd"
|
|
|
|
# Game directory structure
|
|
SETTINGFILE = "settings.py"
|
|
SERVERDIR = "server"
|
|
CONFDIR = os.path.join(SERVERDIR, "conf")
|
|
SETTINGS_PATH = os.path.join(CONFDIR, SETTINGFILE)
|
|
SETTINGS_DOTPATH = "server.conf.settings"
|
|
CURRENT_DIR = os.getcwd()
|
|
GAMEDIR = CURRENT_DIR
|
|
|
|
# Operational setup
|
|
SERVER_LOGFILE = None
|
|
PORTAL_LOGFILE = None
|
|
HTTP_LOGFILE = None
|
|
SERVER_PIDFILE = None
|
|
PORTAL_PIDFILE = None
|
|
SERVER_RESTART = None
|
|
PORTAL_RESTART = None
|
|
SERVER_PY_FILE = None
|
|
PORTAL_PY_FILE = None
|
|
TEST_MODE = False
|
|
ENFORCED_SETTING = False
|
|
|
|
# requirements
|
|
PYTHON_MIN = '2.7'
|
|
TWISTED_MIN = '16.0.0'
|
|
DJANGO_MIN = '1.11'
|
|
DJANGO_REC = '1.11'
|
|
|
|
sys.path[1] = EVENNIA_ROOT
|
|
|
|
#------------------------------------------------------------
|
|
#
|
|
# Messages
|
|
#
|
|
#------------------------------------------------------------
|
|
|
|
CREATED_NEW_GAMEDIR = \
|
|
"""
|
|
Welcome to Evennia!
|
|
Created a new Evennia game directory '{gamedir}'.
|
|
|
|
You can now optionally edit your new settings file
|
|
at {settings_path}. If you don't, the defaults
|
|
will work out of the box. When ready to continue, 'cd' to your
|
|
game directory and run:
|
|
|
|
evennia migrate
|
|
|
|
This initializes the database. To start the server for the first
|
|
time, run:
|
|
|
|
evennia start
|
|
|
|
Make sure to create a superuser when asked for it (the email can
|
|
be blank if you want). You should now be able to (by default)
|
|
connect to your server on 'localhost', port 4000 using a
|
|
telnet/mud client or http://localhost:4001 using your web browser.
|
|
If things don't work, check so those ports are open.
|
|
|
|
"""
|
|
|
|
ERROR_INPUT = \
|
|
"""
|
|
Command
|
|
{args} {kwargs}
|
|
raised an error: '{traceback}'.
|
|
"""
|
|
|
|
ERROR_NO_GAMEDIR = \
|
|
"""
|
|
ERROR: No Evennia settings file was found. Evennia looks for the
|
|
file in your game directory as server/conf/settings.py.
|
|
|
|
You must run this command from somewhere inside a valid game
|
|
directory first created with
|
|
|
|
evennia --init mygamename
|
|
|
|
If you are in a game directory but is missing a settings.py file,
|
|
it may be because you have git-cloned an existing game directory.
|
|
The settings.py file is not cloned by git (it's in .gitignore)
|
|
since it can contain sensitive and/or server-specific information.
|
|
You can create a new, empty settings file with
|
|
|
|
evennia --initsettings
|
|
|
|
If cloning the settings file is not a problem you could manually
|
|
copy over the old settings file or remove its entry in .gitignore
|
|
|
|
"""
|
|
|
|
WARNING_MOVING_SUPERUSER = \
|
|
"""
|
|
WARNING: Evennia expects an Account superuser with id=1. No such
|
|
Account was found. However, another superuser ('{other_key}',
|
|
id={other_id}) was found in the database. If you just created this
|
|
superuser and still see this text it is probably due to the
|
|
database being flushed recently - in this case the database's
|
|
internal auto-counter might just start from some value higher than
|
|
one.
|
|
|
|
We will fix this by assigning the id 1 to Account '{other_key}'.
|
|
Please confirm this is acceptable before continuing.
|
|
"""
|
|
|
|
WARNING_RUNSERVER = \
|
|
"""
|
|
WARNING: There is no need to run the Django development
|
|
webserver to test out Evennia web features (the web client
|
|
will in fact not work since the Django test server knows
|
|
nothing about MUDs). Instead, just start Evennia with the
|
|
webserver component active (this is the default).
|
|
"""
|
|
|
|
ERROR_SETTINGS = \
|
|
"""
|
|
ERROR: There was an error importing Evennia's config file
|
|
{settingspath}.
|
|
There is usually one of three reasons for this:
|
|
1) You are not running this command from your game directory.
|
|
Change directory to your game directory and try again (or
|
|
create a new game directory using evennia --init <dirname>)
|
|
2) The settings file contains a syntax error. If you see a
|
|
traceback above, review it, resolve the problem and try again.
|
|
3) Django is not correctly installed. This usually shows as
|
|
errors mentioning 'DJANGO_SETTINGS_MODULE'. If you run a
|
|
virtual machine, it might be worth to restart it to see if
|
|
this resolves the issue.
|
|
""".format(settingspath=SETTINGS_PATH)
|
|
|
|
ERROR_INITSETTINGS = \
|
|
"""
|
|
ERROR: 'evennia --initsettings' must be called from the root of
|
|
your game directory, since it tries to (re)create the new
|
|
settings.py file in a subfolder server/conf/.
|
|
"""
|
|
|
|
RECREATED_SETTINGS = \
|
|
"""
|
|
(Re)created an empty settings file in server/conf/settings.py.
|
|
|
|
Note that if you were using an existing database, the password
|
|
salt of this new settings file will be different from the old one.
|
|
This means that any existing accounts may not be able to log in to
|
|
their accounts with their old passwords.
|
|
"""
|
|
|
|
ERROR_DATABASE = \
|
|
"""
|
|
ERROR: Your database does not seem to be set up correctly.
|
|
(error was '{traceback}')
|
|
|
|
Standing in your game directory, run
|
|
|
|
evennia migrate
|
|
|
|
to initialize/update the database according to your settings.
|
|
"""
|
|
|
|
ERROR_WINDOWS_WIN32API = \
|
|
"""
|
|
ERROR: Unable to import win32api, which Twisted requires to run.
|
|
You may download it from:
|
|
|
|
http://sourceforge.net/projects/pywin32/files/pywin32/
|
|
|
|
If you are running in a virtual environment, browse to the
|
|
location of the latest win32api exe file for your computer and
|
|
Python version and copy the url to it; then paste it into a call
|
|
to easy_install:
|
|
|
|
easy_install http://<url to win32api exe>
|
|
"""
|
|
|
|
INFO_WINDOWS_BATFILE = \
|
|
"""
|
|
INFO: Since you are running Windows, a file 'twistd.bat' was
|
|
created for you. This is a simple batch file that tries to call
|
|
the twisted executable. Evennia determined this to be:
|
|
|
|
{twistd_path}
|
|
|
|
If you run into errors at startup you might need to edit
|
|
twistd.bat to point to the actual location of the Twisted
|
|
executable (usually called twistd.py) on your machine.
|
|
|
|
This procedure is only done once. Run evennia.py again when you
|
|
are ready to start the server.
|
|
"""
|
|
|
|
CMDLINE_HELP = \
|
|
"""
|
|
Starts or operates the Evennia MU* server. Allows for
|
|
initializing a new game directory and manages the game's database.
|
|
Most standard django-admin arguments and options can also be
|
|
passed.
|
|
"""
|
|
|
|
|
|
VERSION_INFO = \
|
|
"""
|
|
Evennia {version}
|
|
OS: {os}
|
|
Python: {python}
|
|
Twisted: {twisted}
|
|
Django: {django}{about}
|
|
"""
|
|
|
|
ABOUT_INFO = \
|
|
"""
|
|
Evennia MUD/MUX/MU* development system
|
|
|
|
Licence: BSD 3-Clause Licence
|
|
Web: http://www.evennia.com
|
|
Irc: #evennia on FreeNode
|
|
Forum: http://www.evennia.com/discussions
|
|
Maintainer (2010-): Griatch (griatch AT gmail DOT com)
|
|
Maintainer (2006-10): Greg Taylor
|
|
|
|
Use -h for command line options.
|
|
"""
|
|
|
|
HELP_ENTRY = \
|
|
"""
|
|
Enter 'evennia -h' for command-line options.
|
|
|
|
Use option (1) in a production environment. During development (2) is
|
|
usually enough, portal debugging is usually only useful if you are
|
|
adding new protocols or are debugging Evennia itself.
|
|
|
|
Reload with (5) to update the server with your changes without
|
|
disconnecting any accounts.
|
|
|
|
Note: Reload and stop are sometimes poorly supported in Windows. If you
|
|
have issues, log into the game to stop or restart the server instead.
|
|
"""
|
|
|
|
MENU = \
|
|
"""
|
|
+----Evennia Launcher-------------------------------------------+
|
|
| |
|
|
+--- Starting --------------------------------------------------+
|
|
| |
|
|
| 1) (normal): All output to logfiles |
|
|
| 2) (server devel): Server logs to terminal (-i option) |
|
|
| 3) (portal devel): Portal logs to terminal |
|
|
| 4) (full devel): Both Server and Portal logs to terminal |
|
|
| |
|
|
+--- Restarting ------------------------------------------------+
|
|
| |
|
|
| 5) Reload the Server |
|
|
| 6) Reload the Portal (only works with portal/full debug) |
|
|
| |
|
|
+--- Stopping --------------------------------------------------+
|
|
| |
|
|
| 7) Stopping both Portal and Server |
|
|
| 8) Stopping only Server |
|
|
| 9) Stopping only Portal |
|
|
| |
|
|
+---------------------------------------------------------------+
|
|
| h) Help i) About info q) Abort |
|
|
+---------------------------------------------------------------+
|
|
"""
|
|
|
|
ERROR_LOGDIR_MISSING = \
|
|
"""
|
|
ERROR: One or more log-file directory locations could not be
|
|
found:
|
|
|
|
{logfiles}
|
|
|
|
This is simple to fix: Just manually create the missing log
|
|
directory (or directories) and re-launch the server (the log files
|
|
will be created automatically).
|
|
|
|
(Explanation: Evennia creates the log directory automatically when
|
|
initializing a new game directory. This error usually happens if
|
|
you used git to clone a pre-created game directory - since log
|
|
files are in .gitignore they will not be cloned, which leads to
|
|
the log directory also not being created.)
|
|
|
|
"""
|
|
|
|
ERROR_PYTHON_VERSION = \
|
|
"""
|
|
ERROR: Python {pversion} used. Evennia requires version
|
|
{python_min} or higher (but not 3.x).
|
|
"""
|
|
|
|
ERROR_TWISTED_VERSION = \
|
|
"""
|
|
ERROR: Twisted {tversion} found. Evennia requires
|
|
version {twisted_min} or higher.
|
|
"""
|
|
|
|
ERROR_NOTWISTED = \
|
|
"""
|
|
ERROR: Twisted does not seem to be installed.
|
|
"""
|
|
|
|
ERROR_DJANGO_MIN = \
|
|
"""
|
|
ERROR: Django {dversion} found. Evennia requires version {django_min}
|
|
or higher.
|
|
|
|
If you are using a virtualenv, use the command `pip install --upgrade -e evennia` where
|
|
`evennia` is the folder to where you cloned the Evennia library. If not
|
|
in a virtualenv you can install django with for example `pip install --upgrade django`
|
|
or with `pip install django=={django_min}` to get a specific version.
|
|
|
|
It's also a good idea to run `evennia migrate` after this upgrade. Ignore
|
|
any warnings and don't run `makemigrate` even if told to.
|
|
"""
|
|
|
|
NOTE_DJANGO_MIN = \
|
|
"""
|
|
NOTE: Django {dversion} found. This will work, but v{django_rec}
|
|
is recommended for production.
|
|
"""
|
|
|
|
NOTE_DJANGO_NEW = \
|
|
"""
|
|
NOTE: Django {dversion} found. This is newer than Evennia's
|
|
recommended version (v{django_rec}). It might work, but may be new
|
|
enough to not be fully tested yet. Report any issues.
|
|
"""
|
|
|
|
ERROR_NODJANGO = \
|
|
"""
|
|
ERROR: Django does not seem to be installed.
|
|
"""
|
|
|
|
NOTE_KEYBOARDINTERRUPT = \
|
|
"""
|
|
STOP: Caught keyboard interrupt while in interactive mode.
|
|
"""
|
|
|
|
NOTE_TEST_DEFAULT = \
|
|
"""
|
|
TESTING: Using Evennia's default settings file (evennia.settings_default).
|
|
(use 'evennia --settings settings.py test .' to run tests on the game dir)
|
|
"""
|
|
|
|
NOTE_TEST_CUSTOM = \
|
|
"""
|
|
TESTING: Using specified settings file '{settings_dotpath}'.
|
|
|
|
(Obs: Evennia's full test suite may not pass if the settings are very
|
|
different from the default. Use 'test .' as arguments to run only tests
|
|
on the game dir.)
|
|
"""
|
|
|
|
#------------------------------------------------------------
|
|
#
|
|
# Functions
|
|
#
|
|
#------------------------------------------------------------
|
|
|
|
|
|
def evennia_version():
|
|
"""
|
|
Get the Evennia version info from the main package.
|
|
|
|
"""
|
|
version = "Unknown"
|
|
try:
|
|
version = evennia.__version__
|
|
except ImportError:
|
|
# even if evennia is not found, we should not crash here.
|
|
pass
|
|
try:
|
|
rev = check_output(
|
|
"git rev-parse --short HEAD",
|
|
shell=True, cwd=EVENNIA_ROOT, stderr=STDOUT).strip().decode()
|
|
version = "%s (rev %s)" % (version, rev)
|
|
except (IOError, CalledProcessError):
|
|
# move on if git is not answering
|
|
pass
|
|
return version
|
|
|
|
|
|
EVENNIA_VERSION = evennia_version()
|
|
|
|
|
|
def check_main_evennia_dependencies():
|
|
"""
|
|
Checks and imports the Evennia dependencies. This must be done
|
|
already before the paths are set up.
|
|
|
|
Returns:
|
|
not_error (bool): True if no dependency error was found.
|
|
|
|
"""
|
|
error = False
|
|
|
|
# Python
|
|
pversion = ".".join(str(num) for num in sys.version_info if isinstance(num, int))
|
|
if LooseVersion(pversion) < LooseVersion(PYTHON_MIN):
|
|
print(ERROR_PYTHON_VERSION.format(pversion=pversion, python_min=PYTHON_MIN))
|
|
error = True
|
|
# Twisted
|
|
try:
|
|
import twisted
|
|
tversion = twisted.version.short()
|
|
if LooseVersion(tversion) < LooseVersion(TWISTED_MIN):
|
|
print(ERROR_TWISTED_VERSION.format(
|
|
tversion=tversion, twisted_min=TWISTED_MIN))
|
|
error = True
|
|
except ImportError:
|
|
print(ERROR_NOTWISTED)
|
|
error = True
|
|
# Django
|
|
try:
|
|
dversion = ".".join(str(num) for num in django.VERSION if isinstance(num, int))
|
|
# only the main version (1.5, not 1.5.4.0)
|
|
dversion_main = ".".join(dversion.split(".")[:2])
|
|
if LooseVersion(dversion) < LooseVersion(DJANGO_MIN):
|
|
print(ERROR_DJANGO_MIN.format(
|
|
dversion=dversion_main, django_min=DJANGO_MIN))
|
|
error = True
|
|
elif LooseVersion(DJANGO_MIN) <= LooseVersion(dversion) < LooseVersion(DJANGO_REC):
|
|
print(NOTE_DJANGO_MIN.format(
|
|
dversion=dversion_main, django_rec=DJANGO_REC))
|
|
elif LooseVersion(DJANGO_REC) < LooseVersion(dversion_main):
|
|
print(NOTE_DJANGO_NEW.format(
|
|
dversion=dversion_main, django_rec=DJANGO_REC))
|
|
except ImportError:
|
|
print(ERROR_NODJANGO)
|
|
error = True
|
|
if error:
|
|
sys.exit()
|
|
# return True/False if error was reported or not
|
|
return not error
|
|
|
|
|
|
def set_gamedir(path):
|
|
"""
|
|
Set GAMEDIR based on path, by figuring out where the setting file
|
|
is inside the directory tree.
|
|
|
|
"""
|
|
global GAMEDIR
|
|
|
|
Ndepth = 10
|
|
settings_path = os.path.join("server", "conf", "settings.py")
|
|
for i in range(Ndepth):
|
|
gpath = os.getcwd()
|
|
if "server" in os.listdir(gpath):
|
|
if os.path.isfile(settings_path):
|
|
GAMEDIR = gpath
|
|
return
|
|
os.chdir(os.pardir)
|
|
print(ERROR_NO_GAMEDIR)
|
|
sys.exit()
|
|
|
|
|
|
def create_secret_key():
|
|
"""
|
|
Randomly create the secret key for the settings file
|
|
|
|
"""
|
|
import random
|
|
import string
|
|
secret_key = list((string.ascii_letters +
|
|
string.digits + string.punctuation).replace("\\", "")
|
|
.replace("'", '"').replace("{", "_").replace("}", "-"))
|
|
random.shuffle(secret_key)
|
|
secret_key = "".join(secret_key[:40])
|
|
return secret_key
|
|
|
|
|
|
def create_settings_file(init=True, secret_settings=False):
|
|
"""
|
|
Uses the template settings file to build a working settings file.
|
|
|
|
Args:
|
|
init (bool): This is part of the normal evennia --init
|
|
operation. If false, this function will copy a fresh
|
|
template file in (asking if it already exists).
|
|
secret_settings (bool, optional): If False, create settings.py, otherwise
|
|
create the secret_settings.py file.
|
|
|
|
"""
|
|
if secret_settings:
|
|
settings_path = os.path.join(GAMEDIR, "server", "conf", "secret_settings.py")
|
|
setting_dict = {"secret_key": "\'%s\'" % create_secret_key()}
|
|
else:
|
|
settings_path = os.path.join(GAMEDIR, "server", "conf", "settings.py")
|
|
setting_dict = {
|
|
"settings_default": os.path.join(EVENNIA_LIB, "settings_default.py"),
|
|
"servername": "\"%s\"" % GAMEDIR.rsplit(os.path.sep, 1)[1],
|
|
"secret_key": "\'%s\'" % create_secret_key()}
|
|
|
|
if not init:
|
|
# if not --init mode, settings file may already exist from before
|
|
if os.path.exists(settings_path):
|
|
inp = eval(input("%s already exists. Do you want to reset it? y/[N]> " % settings_path))
|
|
if not inp.lower() == 'y':
|
|
print ("Aborted.")
|
|
return
|
|
else:
|
|
print ("Reset the settings file.")
|
|
|
|
default_settings_path = os.path.join(EVENNIA_TEMPLATE, "server", "conf", "settings.py")
|
|
shutil.copy(default_settings_path, settings_path)
|
|
|
|
with open(settings_path, 'r') as f:
|
|
settings_string = f.read()
|
|
|
|
settings_string = settings_string.format(**setting_dict)
|
|
|
|
with open(settings_path, 'w') as f:
|
|
f.write(settings_string)
|
|
|
|
|
|
def create_game_directory(dirname):
|
|
"""
|
|
Initialize a new game directory named dirname
|
|
at the current path. This means copying the
|
|
template directory from evennia's root.
|
|
|
|
Args:
|
|
dirname (str): The directory name to create.
|
|
|
|
"""
|
|
global GAMEDIR
|
|
GAMEDIR = os.path.abspath(os.path.join(CURRENT_DIR, dirname))
|
|
if os.path.exists(GAMEDIR):
|
|
print("Cannot create new Evennia game dir: '%s' already exists." % dirname)
|
|
sys.exit()
|
|
# copy template directory
|
|
shutil.copytree(EVENNIA_TEMPLATE, GAMEDIR)
|
|
# rename gitignore to .gitignore
|
|
os.rename(os.path.join(GAMEDIR, 'gitignore'),
|
|
os.path.join(GAMEDIR, '.gitignore'))
|
|
|
|
# pre-build settings file in the new GAMEDIR
|
|
create_settings_file()
|
|
create_settings_file(secret_settings=True)
|
|
|
|
|
|
def create_superuser():
|
|
"""
|
|
Create the superuser account
|
|
|
|
"""
|
|
print(
|
|
"\nCreate a superuser below. The superuser is Account #1, the 'owner' "
|
|
"account of the server.\n")
|
|
django.core.management.call_command("createsuperuser", interactive=True)
|
|
|
|
|
|
def check_database():
|
|
"""
|
|
Check so the database exists.
|
|
|
|
Returns:
|
|
exists (bool): `True` if the database exists, otherwise `False`.
|
|
"""
|
|
# Check so a database exists and is accessible
|
|
from django.db import connection
|
|
tables = connection.introspection.get_table_list(connection.cursor())
|
|
if not tables or not isinstance(tables[0], str): # django 1.8+
|
|
tables = [tableinfo.name for tableinfo in tables]
|
|
if tables and 'accounts_accountdb' in tables:
|
|
# database exists and seems set up. Initialize evennia.
|
|
evennia._init()
|
|
# Try to get Account#1
|
|
from evennia.accounts.models import AccountDB
|
|
try:
|
|
AccountDB.objects.get(id=1)
|
|
except django.db.utils.OperationalError as e:
|
|
print(ERROR_DATABASE.format(traceback=e))
|
|
sys.exit()
|
|
except AccountDB.DoesNotExist:
|
|
# no superuser yet. We need to create it.
|
|
|
|
other_superuser = AccountDB.objects.filter(is_superuser=True)
|
|
if other_superuser:
|
|
# Another superuser was found, but not with id=1. This may
|
|
# happen if using flush (the auto-id starts at a higher
|
|
# value). Wwe copy this superuser into id=1. To do
|
|
# this we must deepcopy it, delete it then save the copy
|
|
# with the new id. This allows us to avoid the UNIQUE
|
|
# constraint on usernames.
|
|
other = other_superuser[0]
|
|
other_id = other.id
|
|
other_key = other.username
|
|
print(WARNING_MOVING_SUPERUSER.format(
|
|
other_key=other_key, other_id=other_id))
|
|
res = ""
|
|
while res.upper() != "Y":
|
|
# ask for permission
|
|
res = eval(input("Continue [Y]/N: "))
|
|
if res.upper() == "N":
|
|
sys.exit()
|
|
elif not res:
|
|
break
|
|
# continue with the
|
|
from copy import deepcopy
|
|
new = deepcopy(other)
|
|
other.delete()
|
|
new.id = 1
|
|
new.save()
|
|
else:
|
|
create_superuser()
|
|
check_database()
|
|
return True
|
|
|
|
|
|
def getenv():
|
|
"""
|
|
Get current environment and add PYTHONPATH.
|
|
|
|
Returns:
|
|
env (dict): Environment global dict.
|
|
|
|
"""
|
|
sep = ";" if os.name == 'nt' else ":"
|
|
env = os.environ.copy()
|
|
env['PYTHONPATH'] = sep.join(sys.path)
|
|
return env
|
|
|
|
|
|
def get_pid(pidfile):
|
|
"""
|
|
Get the PID (Process ID) by trying to access an PID file.
|
|
|
|
Args:
|
|
pidfile (str): The path of the pid file.
|
|
|
|
Returns:
|
|
pid (str or None): The process id.
|
|
|
|
"""
|
|
if os.path.exists(pidfile):
|
|
with open(pidfile, 'r') as f:
|
|
pid = f.read()
|
|
return pid
|
|
return None
|
|
|
|
|
|
def del_pid(pidfile):
|
|
"""
|
|
The pidfile should normally be removed after a process has
|
|
finished, but when sending certain signals they remain, so we need
|
|
to clean them manually.
|
|
|
|
Args:
|
|
pidfile (str): The path of the pid file.
|
|
|
|
"""
|
|
if os.path.exists(pidfile):
|
|
os.remove(pidfile)
|
|
|
|
|
|
def kill(pidfile, killsignal=SIG, succmsg="", errmsg="",
|
|
restart_file=SERVER_RESTART, restart=False):
|
|
"""
|
|
Send a kill signal to a process based on PID. A customized
|
|
success/error message will be returned. If clean=True, the system
|
|
will attempt to manually remove the pid file.
|
|
|
|
Args:
|
|
pidfile (str): The path of the pidfile to get the PID from.
|
|
killsignal (int, optional): Signal identifier for signal to send.
|
|
succmsg (str, optional): Message to log on success.
|
|
errmsg (str, optional): Message to log on failure.
|
|
restart_file (str, optional): Restart file location.
|
|
restart (bool, optional): Are we in restart mode or not.
|
|
|
|
"""
|
|
pid = get_pid(pidfile)
|
|
if pid:
|
|
if os.name == 'nt':
|
|
os.remove(pidfile)
|
|
# set restart/norestart flag
|
|
if restart:
|
|
django.core.management.call_command(
|
|
'collectstatic', interactive=False, verbosity=0)
|
|
with open(restart_file, 'w') as f:
|
|
f.write("reload")
|
|
else:
|
|
with open(restart_file, 'w') as f:
|
|
f.write("shutdown")
|
|
try:
|
|
if os.name == 'nt':
|
|
from win32api import GenerateConsoleCtrlEvent, SetConsoleCtrlHandler
|
|
try:
|
|
# Windows can only send a SIGINT-like signal to
|
|
# *every* process spawned off the same console, so we must
|
|
# avoid killing ourselves here.
|
|
SetConsoleCtrlHandler(None, True)
|
|
GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)
|
|
except KeyboardInterrupt:
|
|
# We must catch and ignore the interrupt sent.
|
|
pass
|
|
else:
|
|
# Linux can send the SIGINT signal directly
|
|
# to the specified PID.
|
|
os.kill(int(pid), killsignal)
|
|
|
|
except OSError:
|
|
print("Process %(pid)s cannot be stopped. "
|
|
"The PID file 'server/%(pidfile)s' seems stale. "
|
|
"Try removing it." % {'pid': pid, 'pidfile': pidfile})
|
|
return
|
|
print("Evennia:", succmsg)
|
|
return
|
|
print("Evennia:", errmsg)
|
|
|
|
|
|
def show_version_info(about=False):
|
|
"""
|
|
Display version info.
|
|
|
|
Args:
|
|
about (bool): Include ABOUT info as well as version numbers.
|
|
|
|
Returns:
|
|
version_info (str): A complete version info string.
|
|
|
|
"""
|
|
import sys
|
|
import twisted
|
|
|
|
return VERSION_INFO.format(
|
|
version=EVENNIA_VERSION, about=ABOUT_INFO if about else "",
|
|
os=os.name, python=sys.version.split()[0],
|
|
twisted=twisted.version.short(),
|
|
django=django.get_version())
|
|
|
|
|
|
def error_check_python_modules():
|
|
"""
|
|
Import settings modules in settings. This will raise exceptions on
|
|
pure python-syntax issues which are hard to catch gracefully with
|
|
exceptions in the engine (since they are formatting errors in the
|
|
python source files themselves). Best they fail already here
|
|
before we get any further.
|
|
|
|
"""
|
|
|
|
from django.conf import settings
|
|
|
|
def _imp(path, split=True):
|
|
"helper method"
|
|
mod, fromlist = path, "None"
|
|
if split:
|
|
mod, fromlist = path.rsplit('.', 1)
|
|
__import__(mod, fromlist=[fromlist])
|
|
|
|
# check the historical deprecations
|
|
from evennia.server import deprecations
|
|
try:
|
|
deprecations.check_errors(settings)
|
|
deprecations.check_warnings(settings)
|
|
except DeprecationWarning as err:
|
|
print(err)
|
|
sys.exit()
|
|
|
|
# core modules
|
|
_imp(settings.COMMAND_PARSER)
|
|
_imp(settings.SEARCH_AT_RESULT)
|
|
_imp(settings.CONNECTION_SCREEN_MODULE)
|
|
#imp(settings.AT_INITIAL_SETUP_HOOK_MODULE, split=False)
|
|
for path in settings.LOCK_FUNC_MODULES:
|
|
_imp(path, split=False)
|
|
|
|
from evennia.commands import cmdsethandler
|
|
if not cmdsethandler.import_cmdset(settings.CMDSET_UNLOGGEDIN, None):
|
|
print("Warning: CMDSET_UNLOGGED failed to load!")
|
|
if not cmdsethandler.import_cmdset(settings.CMDSET_CHARACTER, None):
|
|
print("Warning: CMDSET_CHARACTER failed to load")
|
|
if not cmdsethandler.import_cmdset(settings.CMDSET_ACCOUNT, None):
|
|
print("Warning: CMDSET_ACCOUNT failed to load")
|
|
# typeclasses
|
|
_imp(settings.BASE_ACCOUNT_TYPECLASS)
|
|
_imp(settings.BASE_OBJECT_TYPECLASS)
|
|
_imp(settings.BASE_CHARACTER_TYPECLASS)
|
|
_imp(settings.BASE_ROOM_TYPECLASS)
|
|
_imp(settings.BASE_EXIT_TYPECLASS)
|
|
_imp(settings.BASE_SCRIPT_TYPECLASS)
|
|
|
|
|
|
def init_game_directory(path, check_db=True):
|
|
"""
|
|
Try to analyze the given path to find settings.py - this defines
|
|
the game directory and also sets PYTHONPATH as well as the django
|
|
path.
|
|
|
|
Args:
|
|
path (str): Path to new game directory, including its name.
|
|
check_db (bool, optional): Check if the databae exists.
|
|
|
|
"""
|
|
# set the GAMEDIR path
|
|
set_gamedir(path)
|
|
|
|
# Add gamedir to python path
|
|
sys.path.insert(0, GAMEDIR)
|
|
|
|
if TEST_MODE:
|
|
if ENFORCED_SETTING:
|
|
print(NOTE_TEST_CUSTOM.format(settings_dotpath=SETTINGS_DOTPATH))
|
|
os.environ['DJANGO_SETTINGS_MODULE'] = SETTINGS_DOTPATH
|
|
else:
|
|
print(NOTE_TEST_DEFAULT)
|
|
os.environ['DJANGO_SETTINGS_MODULE'] = 'evennia.settings_default'
|
|
else:
|
|
os.environ['DJANGO_SETTINGS_MODULE'] = SETTINGS_DOTPATH
|
|
|
|
# required since django1.7
|
|
django.setup()
|
|
|
|
# test existence of the settings module
|
|
try:
|
|
from django.conf import settings
|
|
except Exception as ex:
|
|
if not str(ex).startswith("No module named"):
|
|
import traceback
|
|
print(traceback.format_exc().strip())
|
|
print(ERROR_SETTINGS)
|
|
sys.exit()
|
|
|
|
# this will both check the database and initialize the evennia dir.
|
|
if check_db:
|
|
check_database()
|
|
|
|
# set up the Evennia executables and log file locations
|
|
global SERVER_PY_FILE, PORTAL_PY_FILE
|
|
global SERVER_LOGFILE, PORTAL_LOGFILE, HTTP_LOGFILE
|
|
global SERVER_PIDFILE, PORTAL_PIDFILE
|
|
global SERVER_RESTART, PORTAL_RESTART
|
|
global EVENNIA_VERSION
|
|
|
|
SERVER_PY_FILE = os.path.join(EVENNIA_LIB, "server", "server.py")
|
|
PORTAL_PY_FILE = os.path.join(EVENNIA_LIB, "portal", "portal", "portal.py")
|
|
|
|
SERVER_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "server.pid")
|
|
PORTAL_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "portal.pid")
|
|
|
|
SERVER_RESTART = os.path.join(GAMEDIR, SERVERDIR, "server.restart")
|
|
PORTAL_RESTART = os.path.join(GAMEDIR, SERVERDIR, "portal.restart")
|
|
|
|
SERVER_LOGFILE = settings.SERVER_LOG_FILE
|
|
PORTAL_LOGFILE = settings.PORTAL_LOG_FILE
|
|
HTTP_LOGFILE = settings.HTTP_LOG_FILE
|
|
|
|
# verify existence of log file dir (this can be missing e.g.
|
|
# if the game dir itself was cloned since log files are in .gitignore)
|
|
logdirs = [logfile.rsplit(os.path.sep, 1)
|
|
for logfile in (SERVER_LOGFILE, PORTAL_LOGFILE, HTTP_LOGFILE)]
|
|
if not all(os.path.isdir(pathtup[0]) for pathtup in logdirs):
|
|
errstr = "\n ".join("%s (log file %s)" % (pathtup[0], pathtup[1]) for pathtup in logdirs
|
|
if not os.path.isdir(pathtup[0]))
|
|
print(ERROR_LOGDIR_MISSING.format(logfiles=errstr))
|
|
sys.exit()
|
|
|
|
if os.name == 'nt':
|
|
# We need to handle Windows twisted separately. We create a
|
|
# batchfile in game/server, linking to the actual binary
|
|
|
|
global TWISTED_BINARY
|
|
# Windows requires us to use the absolute path for the bat file.
|
|
server_path = os.path.dirname(os.path.abspath(__file__))
|
|
TWISTED_BINARY = os.path.join(server_path, "twistd.bat")
|
|
|
|
# add path so system can find the batfile
|
|
sys.path.insert(1, os.path.join(GAMEDIR, SERVERDIR))
|
|
|
|
try:
|
|
importlib.import_module("win32api")
|
|
except ImportError:
|
|
print(ERROR_WINDOWS_WIN32API)
|
|
sys.exit()
|
|
|
|
batpath = os.path.join(EVENNIA_SERVER, TWISTED_BINARY)
|
|
if not os.path.exists(batpath):
|
|
# Test for executable twisted batch file. This calls the
|
|
# twistd.py executable that is usually not found on the
|
|
# path in Windows. It's not enough to locate
|
|
# scripts.twistd, what we want is the executable script
|
|
# C:\PythonXX/Scripts/twistd.py. Alas we cannot hardcode
|
|
# this location since we don't know if user has Python in
|
|
# a non-standard location. So we try to figure it out.
|
|
twistd = importlib.import_module("twisted.scripts.twistd")
|
|
twistd_dir = os.path.dirname(twistd.__file__)
|
|
|
|
# note that we hope the twistd package won't change here, since we
|
|
# try to get to the executable by relative path.
|
|
# Update: In 2016, it seems Twisted 16 has changed the name of
|
|
# of its executable from 'twistd.py' to 'twistd.exe'.
|
|
twistd_path = os.path.abspath(
|
|
os.path.join(twistd_dir, os.pardir, os.pardir, os.pardir,
|
|
os.pardir, 'scripts', 'twistd.exe'))
|
|
|
|
with open(batpath, 'w') as bat_file:
|
|
# build a custom bat file for windows
|
|
bat_file.write("@\"%s\" %%*" % twistd_path)
|
|
|
|
print(INFO_WINDOWS_BATFILE.format(twistd_path=twistd_path))
|
|
|
|
|
|
def run_dummyrunner(number_of_dummies):
|
|
"""
|
|
Start an instance of the dummyrunner
|
|
|
|
Args:
|
|
number_of_dummies (int): The number of dummy accounts to start.
|
|
|
|
Notes:
|
|
The dummy accounts' behavior can be customized by adding a
|
|
`dummyrunner_settings.py` config file in the game's conf/
|
|
directory.
|
|
|
|
"""
|
|
number_of_dummies = str(int(number_of_dummies)) if number_of_dummies else 1
|
|
cmdstr = [sys.executable, EVENNIA_DUMMYRUNNER, "-N", number_of_dummies]
|
|
config_file = os.path.join(SETTINGS_PATH, "dummyrunner_settings.py")
|
|
if os.path.exists(config_file):
|
|
cmdstr.extend(["--config", config_file])
|
|
try:
|
|
call(cmdstr, env=getenv())
|
|
except KeyboardInterrupt:
|
|
# this signals the dummyrunner to stop cleanly and should
|
|
# not lead to a traceback here.
|
|
pass
|
|
|
|
|
|
def list_settings(keys):
|
|
"""
|
|
Display the server settings. We only display the Evennia specific
|
|
settings here. The result will be printed to the terminal.
|
|
|
|
Args:
|
|
keys (str or list): Setting key or keys to inspect.
|
|
|
|
"""
|
|
from importlib import import_module
|
|
from evennia.utils import evtable
|
|
|
|
evsettings = import_module(SETTINGS_DOTPATH)
|
|
if len(keys) == 1 and keys[0].upper() == "ALL":
|
|
# show a list of all keys
|
|
# a specific key
|
|
table = evtable.EvTable()
|
|
confs = [key for key in sorted(evsettings.__dict__) if key.isupper()]
|
|
for i in range(0, len(confs), 4):
|
|
table.add_row(*confs[i:i + 4])
|
|
else:
|
|
# a specific key
|
|
table = evtable.EvTable(width=131)
|
|
keys = [key.upper() for key in keys]
|
|
confs = dict((key, var) for key, var in list(evsettings.__dict__.items())
|
|
if key in keys)
|
|
for key, val in list(confs.items()):
|
|
table.add_row(key, str(val))
|
|
print(table)
|
|
|
|
|
|
def run_menu():
|
|
"""
|
|
This launches an interactive menu.
|
|
|
|
"""
|
|
while True:
|
|
# menu loop
|
|
|
|
print(MENU)
|
|
inp = eval(input(" option > "))
|
|
|
|
# quitting and help
|
|
if inp.lower() == 'q':
|
|
return
|
|
elif inp.lower() == 'h':
|
|
print(HELP_ENTRY)
|
|
eval(input("press <return> to continue ..."))
|
|
continue
|
|
elif inp.lower() in ('v', 'i', 'a'):
|
|
print(show_version_info(about=True))
|
|
eval(input("press <return> to continue ..."))
|
|
continue
|
|
|
|
# options
|
|
try:
|
|
inp = int(inp)
|
|
except ValueError:
|
|
print("Not a valid option.")
|
|
continue
|
|
if inp == 1:
|
|
# start everything, log to log files
|
|
server_operation("start", "all", False, False)
|
|
elif inp == 2:
|
|
# start everything, server interactive start
|
|
server_operation("start", "all", True, False)
|
|
elif inp == 3:
|
|
# start everything, portal interactive start
|
|
server_operation("start", "server", False, False)
|
|
server_operation("start", "portal", True, False)
|
|
elif inp == 4:
|
|
# start both server and portal interactively
|
|
server_operation("start", "server", True, False)
|
|
server_operation("start", "portal", True, False)
|
|
elif inp == 5:
|
|
# reload the server
|
|
server_operation("reload", "server", None, None)
|
|
elif inp == 6:
|
|
# reload the portal
|
|
server_operation("reload", "portal", None, None)
|
|
elif inp == 7:
|
|
# stop server and portal
|
|
server_operation("stop", "all", None, None)
|
|
elif inp == 8:
|
|
# stop server
|
|
server_operation("stop", "server", None, None)
|
|
elif inp == 9:
|
|
# stop portal
|
|
server_operation("stop", "portal", None, None)
|
|
else:
|
|
print("Not a valid option.")
|
|
continue
|
|
return
|
|
|
|
|
|
def server_operation(mode, service, interactive, profiler, logserver=False, doexit=False):
|
|
"""
|
|
Handle argument options given on the command line.
|
|
|
|
Args:
|
|
mode (str): Start/stop/restart and so on.
|
|
service (str): "server", "portal" or "all".
|
|
interactive (bool). Use interactive mode or daemon.
|
|
profiler (bool): Run the service under the profiler.
|
|
logserver (bool, optional): Log Server data to logfile
|
|
specified by settings.SERVER_LOG_FILE.
|
|
doexit (bool, optional): If True, immediately exit the runner after
|
|
starting the relevant processes. If the runner exits, Evennia
|
|
cannot be reloaded. This is meant to be used with an external
|
|
process manager like Linux' start-stop-daemon.
|
|
|
|
"""
|
|
|
|
cmdstr = [sys.executable, EVENNIA_RUNNER]
|
|
errmsg = "The %s does not seem to be running."
|
|
|
|
if mode == 'start':
|
|
|
|
# launch the error checker. Best to catch the errors already here.
|
|
error_check_python_modules()
|
|
|
|
# starting one or many services
|
|
if service == 'server':
|
|
if profiler:
|
|
cmdstr.append('--pserver')
|
|
if interactive:
|
|
cmdstr.append('--iserver')
|
|
if logserver:
|
|
cmdstr.append('--logserver')
|
|
cmdstr.append('--noportal')
|
|
elif service == 'portal':
|
|
if profiler:
|
|
cmdstr.append('--pportal')
|
|
if interactive:
|
|
cmdstr.append('--iportal')
|
|
cmdstr.append('--noserver')
|
|
django.core.management.call_command(
|
|
'collectstatic', verbosity=1, interactive=False)
|
|
else:
|
|
# all
|
|
# for convenience we don't start logging of
|
|
# portal, only of server with this command.
|
|
if profiler:
|
|
# this is the common case
|
|
cmdstr.append('--pserver')
|
|
if interactive:
|
|
cmdstr.append('--iserver')
|
|
if logserver:
|
|
cmdstr.append('--logserver')
|
|
django.core.management.call_command(
|
|
'collectstatic', verbosity=1, interactive=False)
|
|
if doexit:
|
|
cmdstr.append('--doexit')
|
|
cmdstr.extend([
|
|
GAMEDIR, TWISTED_BINARY, SERVER_LOGFILE,
|
|
PORTAL_LOGFILE, HTTP_LOGFILE])
|
|
# start the server
|
|
process = Popen(cmdstr, env=getenv())
|
|
|
|
if interactive:
|
|
try:
|
|
process.wait()
|
|
except KeyboardInterrupt:
|
|
server_operation("stop", "portal", False, False)
|
|
return
|
|
finally:
|
|
print(NOTE_KEYBOARDINTERRUPT)
|
|
|
|
elif mode == 'reload':
|
|
# restarting services
|
|
if os.name == 'nt':
|
|
print(
|
|
"Restarting from command line is not supported under Windows. "
|
|
"Use the in-game command (@reload by default) "
|
|
"or use 'evennia stop && evennia start' for a cold reboot.")
|
|
return
|
|
if service == 'server':
|
|
kill(SERVER_PIDFILE, SIG, "Server reloaded.",
|
|
errmsg % 'Server', SERVER_RESTART, restart=True)
|
|
elif service == 'portal':
|
|
print(
|
|
"Note: Portal usually doesnt't need to be reloaded unless you "
|
|
"are debugging in interactive mode. If Portal was running in "
|
|
"default Daemon mode, it cannot be restarted. In that case "
|
|
"you have to restart it manually with 'evennia.py "
|
|
"start portal'")
|
|
kill(PORTAL_PIDFILE, SIG,
|
|
"Portal reloaded (or stopped, if it was in daemon mode).",
|
|
errmsg % 'Portal', PORTAL_RESTART, restart=True)
|
|
else:
|
|
# all
|
|
# default mode, only restart server
|
|
kill(SERVER_PIDFILE, SIG,
|
|
"Server reload.",
|
|
errmsg % 'Server', SERVER_RESTART, restart=True)
|
|
|
|
elif mode == 'stop':
|
|
if os.name == "nt":
|
|
print (
|
|
"(Obs: You can use a single Ctrl-C to skip "
|
|
"Windows' annoying 'Terminate batch job (Y/N)?' prompts.)")
|
|
# stop processes, avoiding reload
|
|
if service == 'server':
|
|
kill(SERVER_PIDFILE, SIG,
|
|
"Server stopped.", errmsg % 'Server', SERVER_RESTART)
|
|
elif service == 'portal':
|
|
kill(PORTAL_PIDFILE, SIG,
|
|
"Portal stopped.", errmsg % 'Portal', PORTAL_RESTART)
|
|
else:
|
|
kill(PORTAL_PIDFILE, SIG,
|
|
"Portal stopped.", errmsg % 'Portal', PORTAL_RESTART)
|
|
kill(SERVER_PIDFILE, SIG,
|
|
"Server stopped.", errmsg % 'Server', SERVER_RESTART)
|
|
|
|
|
|
def main():
|
|
"""
|
|
Run the evennia launcher main program.
|
|
|
|
"""
|
|
|
|
# set up argument parser
|
|
|
|
parser = ArgumentParser(description=CMDLINE_HELP)
|
|
parser.add_argument(
|
|
'-v', '--version', action='store_true',
|
|
dest='show_version', default=False,
|
|
help="Show version info.")
|
|
parser.add_argument(
|
|
'-i', '--interactive', action='store_true',
|
|
dest='interactive', default=False,
|
|
help="Start given processes in interactive mode.")
|
|
parser.add_argument(
|
|
'-l', '--log', action='store_true',
|
|
dest="logserver", default=False,
|
|
help="Log Server data to log file.")
|
|
parser.add_argument(
|
|
'--init', action='store', dest="init", metavar="name",
|
|
help="Creates a new game directory 'name' at the current location.")
|
|
parser.add_argument(
|
|
'--list', nargs='+', action='store', dest='listsetting', metavar="key",
|
|
help=("List values for server settings. Use 'all' to list all "
|
|
"available keys."))
|
|
parser.add_argument(
|
|
'--profiler', action='store_true', dest='profiler', default=False,
|
|
help="Start given server component under the Python profiler.")
|
|
parser.add_argument(
|
|
'--dummyrunner', nargs=1, action='store', dest='dummyrunner',
|
|
metavar="N",
|
|
help="Test a running server by connecting N dummy accounts to it.")
|
|
parser.add_argument(
|
|
'--settings', nargs=1, action='store', dest='altsettings',
|
|
default=None, metavar="filename.py",
|
|
help=("Start evennia with alternative settings file from "
|
|
"gamedir/server/conf/. (default is settings.py)"))
|
|
parser.add_argument(
|
|
'--initsettings', action='store_true', dest="initsettings",
|
|
default=False,
|
|
help="Create a new, empty settings file as gamedir/server/conf/settings.py.")
|
|
parser.add_argument(
|
|
'--external-runner', action='store_true', dest="doexit",
|
|
default=False,
|
|
help="Handle server restart with an external process manager.")
|
|
parser.add_argument(
|
|
"operation", nargs='?', default="noop",
|
|
help="Operation to perform: 'start', 'stop', 'reload' or 'menu'.")
|
|
parser.add_argument(
|
|
"service", metavar="component", nargs='?', default="all",
|
|
help=("Which component to operate on: "
|
|
"'server', 'portal' or 'all' (default if not set)."))
|
|
parser.epilog = (
|
|
"Common usage: evennia start|stop|reload. Django-admin database commands:"
|
|
"evennia migration|flush|shell|dbshell (see the django documentation for more django-admin commands.)")
|
|
|
|
args, unknown_args = parser.parse_known_args()
|
|
|
|
# handle arguments
|
|
option, service = args.operation, args.service
|
|
|
|
# make sure we have everything
|
|
check_main_evennia_dependencies()
|
|
|
|
if not args:
|
|
# show help pane
|
|
print(CMDLINE_HELP)
|
|
sys.exit()
|
|
elif args.init:
|
|
# initialization of game directory
|
|
create_game_directory(args.init)
|
|
print(CREATED_NEW_GAMEDIR.format(
|
|
gamedir=args.init,
|
|
settings_path=os.path.join(args.init, SETTINGS_PATH)))
|
|
sys.exit()
|
|
|
|
if args.show_version:
|
|
# show the version info
|
|
print(show_version_info(option == "help"))
|
|
sys.exit()
|
|
|
|
if args.altsettings:
|
|
# use alternative settings file
|
|
sfile = args.altsettings[0]
|
|
global SETTINGSFILE, SETTINGS_DOTPATH, ENFORCED_SETTING
|
|
SETTINGSFILE = sfile
|
|
ENFORCED_SETTING = True
|
|
SETTINGS_DOTPATH = "server.conf.%s" % sfile.rstrip(".py")
|
|
print("Using settings file '%s' (%s)." % (
|
|
SETTINGSFILE, SETTINGS_DOTPATH))
|
|
|
|
if args.initsettings:
|
|
# create new settings file
|
|
global GAMEDIR
|
|
GAMEDIR = os.getcwd()
|
|
try:
|
|
create_settings_file(init=False)
|
|
print(RECREATED_SETTINGS)
|
|
except IOError:
|
|
print(ERROR_INITSETTINGS)
|
|
sys.exit()
|
|
|
|
if args.dummyrunner:
|
|
# launch the dummy runner
|
|
init_game_directory(CURRENT_DIR, check_db=True)
|
|
run_dummyrunner(args.dummyrunner[0])
|
|
elif args.listsetting:
|
|
# display all current server settings
|
|
init_game_directory(CURRENT_DIR, check_db=False)
|
|
list_settings(args.listsetting)
|
|
elif option == 'menu':
|
|
# launch menu for operation
|
|
init_game_directory(CURRENT_DIR, check_db=True)
|
|
run_menu()
|
|
elif option in ('start', 'reload', 'stop'):
|
|
# operate the server directly
|
|
init_game_directory(CURRENT_DIR, check_db=True)
|
|
server_operation(option, service, args.interactive, args.profiler, args.logserver, doexit=args.doexit)
|
|
elif option != "noop":
|
|
# pass-through to django manager
|
|
check_db = False
|
|
if option in ('runserver', 'testserver'):
|
|
print(WARNING_RUNSERVER)
|
|
if option in ("shell", "check"):
|
|
# some django commands requires the database to exist,
|
|
# or evennia._init to have run before they work right.
|
|
check_db = True
|
|
if option == "test":
|
|
global TEST_MODE
|
|
TEST_MODE = True
|
|
init_game_directory(CURRENT_DIR, check_db=check_db)
|
|
|
|
args = [option]
|
|
kwargs = {}
|
|
if service not in ("all", "server", "portal"):
|
|
args.append(service)
|
|
if unknown_args:
|
|
for arg in unknown_args:
|
|
if arg.startswith("--"):
|
|
print("arg:", arg)
|
|
if "=" in arg:
|
|
arg, value = [p.strip() for p in arg.split("=", 1)]
|
|
else:
|
|
value = True
|
|
kwargs[arg.lstrip("--")] = value
|
|
else:
|
|
args.append(arg)
|
|
try:
|
|
django.core.management.call_command(*args, **kwargs)
|
|
except django.core.management.base.CommandError as exc:
|
|
args = ", ".join(args)
|
|
kwargs = ", ".join(["--%s" % kw for kw in kwargs])
|
|
print(ERROR_INPUT.format(traceback=exc, args=args, kwargs=kwargs))
|
|
else:
|
|
# no input; print evennia info
|
|
print(ABOUT_INFO)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# start Evennia from the command line
|
|
main()
|