mirror of
https://git.zavage.net/Zavage-Software/smileyface.git
synced 2024-12-22 12:49:20 -07:00
first python imlementation, 80% of script converted
This commit is contained in:
parent
b21506b0d5
commit
edb0f70e2e
0
README.md
Normal file
0
README.md
Normal file
0
edit-config.sh
Normal file
0
edit-config.sh
Normal file
42
setup.py
Executable file
42
setup.py
Executable file
@ -0,0 +1,42 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
#
|
||||||
|
# First, enable the python environment you want to install to, or if installing
|
||||||
|
# system-wide then ensure you're logged in with sufficient permissions
|
||||||
|
# (admin or root to install to system directories)
|
||||||
|
#
|
||||||
|
# installation:
|
||||||
|
#
|
||||||
|
# $ ./setup.py install
|
||||||
|
#
|
||||||
|
# de-installation:
|
||||||
|
#
|
||||||
|
# $ pip uninstall <app>
|
||||||
|
|
||||||
|
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
__project__ = 'SmileyFace UT4 Server Panel'
|
||||||
|
__version__ = '0.1.0'
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name = __project__,
|
||||||
|
version = __version__,
|
||||||
|
description = 'Unreal Tournament 4 Server Admin and Control Panel',
|
||||||
|
author = 'Mathew Guest',
|
||||||
|
author_email = 't3h.zavage@gmail.com',
|
||||||
|
url = 'https://git-mirror.zavage-software.com',
|
||||||
|
|
||||||
|
# Third-party dependencies; will be automatically installed
|
||||||
|
install_requires = (
|
||||||
|
'app_skellington'
|
||||||
|
),
|
||||||
|
|
||||||
|
# Local packages to be installed (our packages)
|
||||||
|
packages = (
|
||||||
|
'smileyface',
|
||||||
|
),
|
||||||
|
|
||||||
|
)
|
||||||
|
|
4
smileyface.py
Executable file
4
smileyface.py
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
import smileyface
|
||||||
|
smileyface.start_app()
|
||||||
|
|
38
smileyface/__init__.py
Normal file
38
smileyface/__init__.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Module parameters and constants
|
||||||
|
APP_NAME = 'SmileyFace Unreal Tournament 4 Server Panel'
|
||||||
|
APP_AUTHOR = 'Mathew Guest'
|
||||||
|
APP_VERSION = '0.1.0'
|
||||||
|
|
||||||
|
APP_CONFIG_FILENAME = 'config.ini'
|
||||||
|
|
||||||
|
# config.spec is relative to the module src directory and is the
|
||||||
|
# config specification (structure, names, and types of config file)
|
||||||
|
APP_CONFIGSPEC_FILENAME = 'config.spec'
|
||||||
|
|
||||||
|
# Check and gracefully fail if the user needs to install a 3rd-party dep.
|
||||||
|
required_lib_names = ['appdirs', 'configobj', 'colorlog']
|
||||||
|
def check_env_has_dependencies(required_lib_names):
|
||||||
|
"""
|
||||||
|
Attempts to import each module and gracefully fails if it doesn't
|
||||||
|
exist.
|
||||||
|
"""
|
||||||
|
rc = True
|
||||||
|
for libname in required_lib_names:
|
||||||
|
try:
|
||||||
|
__import__(libname)
|
||||||
|
except ImportError as ex:
|
||||||
|
print('missing third-part library: ', ex, file=sys.stderr)
|
||||||
|
rc = False
|
||||||
|
except Exception as ex:
|
||||||
|
print(ex, type(ex))
|
||||||
|
rc = False
|
||||||
|
return rc
|
||||||
|
if not check_env_has_dependencies(required_lib_names):
|
||||||
|
print('refusing to load program without installed dependencies', file=sys.stderr)
|
||||||
|
raise ImportError('python environment needs third-party dependencies installed')
|
||||||
|
|
||||||
|
# Exposed from sub-modules:
|
||||||
|
from .app import start_app
|
137
smileyface/app.py
Normal file
137
smileyface/app.py
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import app_skellington
|
||||||
|
from app_skellington import _util
|
||||||
|
|
||||||
|
from . import model
|
||||||
|
|
||||||
|
class SmileyFace(app_skellington.ApplicationContainer):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
filename = 'config.spec'
|
||||||
|
self.configspec_filepath = _util.get_asset(__name__, filename)
|
||||||
|
|
||||||
|
super().__init__(
|
||||||
|
configspec_filepath=self.configspec_filepath,
|
||||||
|
app_name = 'SmileyFace UT4 Server Panel',
|
||||||
|
app_author = 'Mathew Guest',
|
||||||
|
app_version = '0.1',
|
||||||
|
*args,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
# super().__init__(
|
||||||
|
# app_name = 'SmileyFace UT4 Server Panel',
|
||||||
|
# app_author = 'Mathew Guest',
|
||||||
|
# app_version = '0.1'
|
||||||
|
# )
|
||||||
|
self._load_config()
|
||||||
|
|
||||||
|
def _load_config(self, config_file=None):
|
||||||
|
"""
|
||||||
|
Parse the config file, (todo) environment variables, and command-line
|
||||||
|
arguments to determine runtime configuration.
|
||||||
|
"""
|
||||||
|
if config_file is None:
|
||||||
|
config_file = self._get_config_filepath(
|
||||||
|
'app_name',
|
||||||
|
'app_author',
|
||||||
|
'app_config_filename'
|
||||||
|
)
|
||||||
|
|
||||||
|
rc = self.ctx.config.load_config_from_file(config_file)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _cli_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _command_menu(self):
|
||||||
|
sm_root = self.cli.init_submenu('command')
|
||||||
|
# sm_root.register_command(model.foo)
|
||||||
|
# sm_root.register_command(model.bar)
|
||||||
|
_util.register_class_as_commands(
|
||||||
|
self, sm_root,
|
||||||
|
model.UT4ServerMachine
|
||||||
|
)
|
||||||
|
|
||||||
|
def _services(self):
|
||||||
|
self['model'] = lambda: model.UTServerMachine(self.ctx)
|
||||||
|
|
||||||
|
def interactive_shell(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def invoke_from_cli(self):
|
||||||
|
rc = self.load_command()
|
||||||
|
if not rc:
|
||||||
|
print('Invalid command. Try -h for usage')
|
||||||
|
return
|
||||||
|
# load config
|
||||||
|
self.invoke_command()
|
||||||
|
|
||||||
|
def usage(self):
|
||||||
|
s = """
|
||||||
|
Unreal Tournament 4 Server Build and Deploy Script
|
||||||
|
|
||||||
|
A list of commands is shown below.
|
||||||
|
|
||||||
|
List commands and usage:
|
||||||
|
./ut4-server-ctl.sh
|
||||||
|
|
||||||
|
Show help for a specific command:
|
||||||
|
./ut4-server-ctl.sh --help <COMMAND>
|
||||||
|
|
||||||
|
Here is the list of sub-commands with short syntax reminder:
|
||||||
|
./ut4-server-ctl.sh 1click-deploy
|
||||||
|
./ut4-server-ctl.sh clean-instance
|
||||||
|
./ut4-server-ctl.sh create-directories
|
||||||
|
./ut4-server-ctl.sh download-linux-server
|
||||||
|
./ut4-server-ctl.sh download-logs
|
||||||
|
./ut4-server-ctl.sh generate-instance
|
||||||
|
./ut4-server-ctl.sh start-server
|
||||||
|
./ut4-server-ctl.sh stop-server
|
||||||
|
./ut4-server-ctl.sh upload-redirects
|
||||||
|
./ut4-server-ctl.sh upload-server
|
||||||
|
|
||||||
|
Typical Usage:
|
||||||
|
1) You need to either configure the remote server hostnames (both game server and remote redirect server)
|
||||||
|
in the vars files. Edit vars with a text editor, or edit the defaults in this file, or manually type
|
||||||
|
them interactively when prompted* (coming soon).
|
||||||
|
|
||||||
|
e.g.:
|
||||||
|
PROJECT_DIR="/path/to/this/script/directory/on/local/machine"
|
||||||
|
REMOTE_GAME_HOST="54.123.456.10"
|
||||||
|
REMOTE_GAME_DIR="/home/ut4/hub-instance"
|
||||||
|
|
||||||
|
REMOTE_REDIRECT_HOST="45.321.654.10"
|
||||||
|
REMOTE_REDIRECT_DIR="/srv/ut4-redirect/"
|
||||||
|
|
||||||
|
2) You need to download the latest Linux Server release from Epic. Do
|
||||||
|
this with the 'download-server' command.
|
||||||
|
|
||||||
|
e.g.:
|
||||||
|
./ut4-server-ctl.sh download-server
|
||||||
|
|
||||||
|
3) Add and configure custom maps, mutators, rulesets, and hub configuration to your *local* project folder.
|
||||||
|
This is done by modifying the files in the project directory. With the current environment variables,
|
||||||
|
this is set to be:
|
||||||
|
|
||||||
|
"$PROJECT_DIR"
|
||||||
|
|
||||||
|
This is the fun part! Connect with UTCC or UTZONE.DE (unaffiliated) to find custom content to put on
|
||||||
|
your hub.
|
||||||
|
|
||||||
|
4) 1click-deploy to update the remote hub and redirect with your latest content. You're done! Rinse and repeat.
|
||||||
|
|
||||||
|
e.g.:
|
||||||
|
./ut4-server-ctl.sh 1click-deploy
|
||||||
|
|
||||||
|
Alternatively, this command is equivalent to running the separate commands. If you'd like more fine-grained
|
||||||
|
control, you can run them individually.
|
||||||
|
./ut4-server-ctl.sh generate-instance
|
||||||
|
./ut4-server-ctl.sh upload-redirects
|
||||||
|
./ut4-server-ctl.sh upload-server
|
||||||
|
./ut4-server-ctl.sh restart-server
|
||||||
|
"""
|
||||||
|
print(s)
|
||||||
|
|
||||||
|
def start_app():
|
||||||
|
app = SmileyFace()
|
||||||
|
app.invoke_from_cli()
|
||||||
|
|
0
smileyface/cmd_menu.py
Normal file
0
smileyface/cmd_menu.py
Normal file
24
smileyface/config.spec
Normal file
24
smileyface/config.spec
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
[app]
|
||||||
|
project_dir = string(max=255, default='')
|
||||||
|
|
||||||
|
config_dir = string(max=255, default='')
|
||||||
|
|
||||||
|
download_url = string(max=255, default='https://s3.amazonaws.com/unrealtournament/ShippedBuilds/%2B%2BUT%2BRelease-Next-CL-3525360/UnrealTournament-Server-XAN-3525360-Linux.zip')
|
||||||
|
|
||||||
|
download_filename = string(max=255, default='UnrealTournament-Server-XAN-3525360-Linux.zip')
|
||||||
|
|
||||||
|
download_md5 = string(max=255, default='cad730ad6793ba6261f9a341ad7396eb')
|
||||||
|
|
||||||
|
skip_validate = boolean(default=False)
|
||||||
|
|
||||||
|
redirect_protocol = string(max=255, default='')
|
||||||
|
|
||||||
|
redirect_url = string(max=255, default='')
|
||||||
|
|
||||||
|
remote_game_host = string(max=255, default='')
|
||||||
|
|
||||||
|
remote_game_directory = string(max=255, default='')
|
||||||
|
|
||||||
|
remote_redirect_host = string(max=255, default='')
|
||||||
|
|
||||||
|
[logging]
|
404
smileyface/model.py
Normal file
404
smileyface/model.py
Normal file
@ -0,0 +1,404 @@
|
|||||||
|
from app_skellington import _util
|
||||||
|
from functools import partial
|
||||||
|
import collections
|
||||||
|
import configparser
|
||||||
|
import glob
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import pathlib
|
||||||
|
import configobj
|
||||||
|
import sys
|
||||||
|
|
||||||
|
class UT4ServerMachine:
|
||||||
|
def __init__(self, ctx):
|
||||||
|
self.ctx = ctx
|
||||||
|
|
||||||
|
if not self._validate_env_vars():
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def oneclickdeploy(self):
|
||||||
|
self.generate_instance()
|
||||||
|
self.upload_redirects()
|
||||||
|
self.upload_server()
|
||||||
|
|
||||||
|
def clean_instance(self, x):
|
||||||
|
"""
|
||||||
|
Deletes the generated instance on the local machine.
|
||||||
|
"""
|
||||||
|
self.ctx.log['ut4'].info('Clearing .pak folder...')
|
||||||
|
cmd = 'rm -rv "$PROJECT_DIR"/instance/LinuxServer/UnrealTournament/Content/Paks/*'
|
||||||
|
self._invoke_command(cmd)
|
||||||
|
|
||||||
|
def create_directories(self):
|
||||||
|
"""
|
||||||
|
Create required directories which the user installs maps, mutators, and config to.
|
||||||
|
"""
|
||||||
|
dirs = (
|
||||||
|
'base',
|
||||||
|
'files/config',
|
||||||
|
'files/maps',
|
||||||
|
'files/mutators',
|
||||||
|
'files/rulesets',
|
||||||
|
'files/unused'
|
||||||
|
)
|
||||||
|
project_dir = self.ctx.config['app']['project_dir']
|
||||||
|
if len(project_dir.strip()) == 0:
|
||||||
|
project_dir = '.'
|
||||||
|
print('project_dir:', project_dir)
|
||||||
|
fullpaths = [
|
||||||
|
'/'.join([project_dir, d]) for d in dirs
|
||||||
|
]
|
||||||
|
|
||||||
|
for fp in fullpaths:
|
||||||
|
cmd = 'mkdir -p {}'.format(fp)
|
||||||
|
self._invoke_command(cmd)
|
||||||
|
|
||||||
|
def download_linux_server(self, x):
|
||||||
|
"""
|
||||||
|
Download the latest Linux Unreal Tournament 4 Server from Epic
|
||||||
|
"""
|
||||||
|
self.ctx.log['ut4'].info('Downloading Linux Server Binary from Epic.')
|
||||||
|
|
||||||
|
def download_logs(self):
|
||||||
|
"""
|
||||||
|
Download the logs from the target hub.
|
||||||
|
"""
|
||||||
|
config_dir = self.ctx.config['app']['config_dir']
|
||||||
|
remote_game_host = self.ctx.config['app']['remote_game_host']
|
||||||
|
remote_game_dir = self.ctx.config['app']['remote_game_dir']
|
||||||
|
|
||||||
|
self.ctx.log['ut4'].info('Downloading instance logs from target hub.')
|
||||||
|
cmd = '''
|
||||||
|
rsync -ravzp {remote_game_host}:{remote_game_dir}/LinuxServer/UnrealTournament/Saved/Logs/ {config_dir}/downloaded-logs/
|
||||||
|
'''\
|
||||||
|
.format(**{
|
||||||
|
'config_dir': config_dir,
|
||||||
|
'remote_game_host': remote_game_host,
|
||||||
|
'remote_game_dir': remote_game_dir
|
||||||
|
})
|
||||||
|
self._invoke_command(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
# Delete logs on remote game server if successfully transferred to local:
|
||||||
|
self.ctx.log['ut4'].info('')
|
||||||
|
cmd = '''
|
||||||
|
ssh {remote_game_host} rm {remote_game_dir}/LinuxServer/UnrealTournament/Saved/Logs/* -r'
|
||||||
|
'''\
|
||||||
|
.format(**{
|
||||||
|
'config_dir': config_dir,
|
||||||
|
'remote_game_host': remote_game_host,
|
||||||
|
'remote_game_dir': remote_game_dir
|
||||||
|
})
|
||||||
|
# self._invoke_command(cmd)
|
||||||
|
|
||||||
|
def generate_instance(self):
|
||||||
|
"""
|
||||||
|
Takes the current coniguration and outputs the application files which
|
||||||
|
can be copied to the server.
|
||||||
|
"""
|
||||||
|
self.ctx.log['ut4'].info('Generating server instance from custom files...')
|
||||||
|
project_dir = self.ctx.config['app']['project_dir']
|
||||||
|
|
||||||
|
# rsync
|
||||||
|
src = '/'.join([project_dir, 'base/LinuxServer'])
|
||||||
|
dst = '/'.join([project_dir, 'instance/'])
|
||||||
|
cmd = 'rsync -ravzp {src} {dst}'.format(**{
|
||||||
|
'src': src,
|
||||||
|
'dst': dst
|
||||||
|
})
|
||||||
|
self._invoke_command(cmd)
|
||||||
|
|
||||||
|
# cp 1
|
||||||
|
src = '/'.join([project_dir, 'start-server.sh'])
|
||||||
|
dst = '/'.join([project_dir, 'instance/'])
|
||||||
|
cmd = 'cp {src} {dst}'.format(**{
|
||||||
|
'src': src,
|
||||||
|
'dst': dst
|
||||||
|
})
|
||||||
|
self._invoke_command(cmd)
|
||||||
|
|
||||||
|
# cp 2
|
||||||
|
src = '/'.join([project_dir, 'stop-server.sh'])
|
||||||
|
dst = '/'.join([project_dir, 'instance/'])
|
||||||
|
cmd = 'cp {src} {dst}'.format(**{
|
||||||
|
'src': src,
|
||||||
|
'dst': dst
|
||||||
|
})
|
||||||
|
self._invoke_command(cmd)
|
||||||
|
|
||||||
|
# self._first_run()
|
||||||
|
self._install_config()
|
||||||
|
self._install_paks()
|
||||||
|
self._install_redirect_lines()
|
||||||
|
self._install_rulesets()
|
||||||
|
|
||||||
|
def restart_server(self):
|
||||||
|
self.stop_server()
|
||||||
|
self.start_server()
|
||||||
|
|
||||||
|
def start_server(self):
|
||||||
|
"""
|
||||||
|
Flip on the target hub on for Fragging!
|
||||||
|
"""
|
||||||
|
self.ctx.log['ut4'].info('Starting hub...')
|
||||||
|
|
||||||
|
cmd = '''
|
||||||
|
ssh {remote_game_host} {remote_game_dir}/start-server.sh
|
||||||
|
'''
|
||||||
|
self._invoke_command(cmd)
|
||||||
|
|
||||||
|
def stop_server(self):
|
||||||
|
"""
|
||||||
|
Stop UT4 Hub processes on the server.
|
||||||
|
"""
|
||||||
|
self.ctx.log['ut4'].info('Stopping hub.')
|
||||||
|
cmd = '''
|
||||||
|
ssh {remote_game_host} {remote_game_dir}/stop-server.sh
|
||||||
|
'''
|
||||||
|
self._invoke_command(cmd)
|
||||||
|
|
||||||
|
def upload_redirects(self):
|
||||||
|
"""
|
||||||
|
Upload paks to redirect server.
|
||||||
|
"""
|
||||||
|
self.ctx.log['ut4'].info('Uploading redirects (maps, mutators, etc.) to target hub.')
|
||||||
|
pass
|
||||||
|
|
||||||
|
def upload_server(self):
|
||||||
|
"""
|
||||||
|
Upload all required game files to the hub server.
|
||||||
|
"""
|
||||||
|
self.ctx.log['ut4'].info('Uploading customized server')
|
||||||
|
project_dir = self.ctx.config['app']['project_dir']
|
||||||
|
remote_game_host = self.ctx.config['app']['remote_game_host']
|
||||||
|
remote_game_dir = self.ctx.config['app']['remote_game_dir']
|
||||||
|
cwd = None
|
||||||
|
|
||||||
|
# transfer #1
|
||||||
|
cmd = '''
|
||||||
|
rsync -ravzp \
|
||||||
|
--delete \
|
||||||
|
--exclude ".KEEP" \
|
||||||
|
--exclude "Mods.db" \
|
||||||
|
--exclude "Mod.ini" \
|
||||||
|
--exclude "Logs" \
|
||||||
|
--exclude "ut4-server.log" \
|
||||||
|
--exclude "Saved/*.ai" \
|
||||||
|
--exclude "Saved/Crashes/*" \
|
||||||
|
--exclude "Saved/Logs/*" \
|
||||||
|
{project_dir}/instance/ \
|
||||||
|
{remote_game_host}:{remote_game_dir}"
|
||||||
|
'''\
|
||||||
|
.format(**{
|
||||||
|
'project_dir': project_dir,
|
||||||
|
'remote_game_host': remote_game_host,
|
||||||
|
'remote_game_dir': remote_game_dir
|
||||||
|
})
|
||||||
|
subprocess.run(cmd, cwd=cwd) # should be invoke_command?
|
||||||
|
|
||||||
|
# transfer #2
|
||||||
|
cmd = '''
|
||||||
|
rsync -avzp {project_dir}/ut4-server-ctl.sh {remote_game_host}:{remote_game_dir}
|
||||||
|
'''
|
||||||
|
subprocess.run(cmd, cwd=cwd)
|
||||||
|
|
||||||
|
# transfer #3
|
||||||
|
cmd = '''
|
||||||
|
scp {project_dir}/instance/ut4-server.service {remote_game_host}:/etc/systemd/system/
|
||||||
|
'''
|
||||||
|
subprocess.run(cmd, cwd=cwd)
|
||||||
|
|
||||||
|
# transfer #4
|
||||||
|
cmd = '''
|
||||||
|
ssh {remote_game_host} chown ut4.ut4 {remote_game_dir} -R
|
||||||
|
'''
|
||||||
|
subprocess.run(cmd, cwd=cwd)
|
||||||
|
|
||||||
|
def _first_run(self):
|
||||||
|
self.ctx.log['ut4'].info('Starting instance once to get UID.')
|
||||||
|
self.ctx.log['ut4'].info('Unfortunately, this takes 20 seconds. Just wait.')
|
||||||
|
|
||||||
|
# cd "$PROJECT_DIR"/instance/LinuxServer/Engine/Binaries/Linux
|
||||||
|
# chmod 770 UE4Server-Linux-Shipping
|
||||||
|
# ./UE4Server-Linux-Shipping UnrealTournament UT-Entry?Game=Lobby -log &>/dev/null &
|
||||||
|
# cd - >/dev/null
|
||||||
|
|
||||||
|
self.ctx.log['ut4'].info('sleeping 20 seconds and then we\'ll kill the server we started just now.')
|
||||||
|
# sleep 20
|
||||||
|
# stop_server
|
||||||
|
# # TODO(MG) get uid and export
|
||||||
|
|
||||||
|
def _install_config(self):
|
||||||
|
files = (
|
||||||
|
'Game.ini',
|
||||||
|
'Engine.ini'
|
||||||
|
)
|
||||||
|
project_dir = self.ctx.config['app']['project_dir']
|
||||||
|
config_dir = self.ctx.config['app']['config_dir']
|
||||||
|
for fn in files:
|
||||||
|
self.ctx.log['ut4'].info('Installing file: %s', fn)
|
||||||
|
# src = '/'.join([config_dir, fn])
|
||||||
|
# dst = '/'.join([project_dir, 'instance']) # needs corrected
|
||||||
|
cmd = 'cp {src} {dst}'.format(**{
|
||||||
|
'src': '/'.join([config_dir, fn]),
|
||||||
|
'dst': '/'.join([project_dir, 'instance']) # needs corrected
|
||||||
|
})
|
||||||
|
self._invoke_command(cmd)
|
||||||
|
|
||||||
|
def _install_paks(self):
|
||||||
|
project_dir = self.ctx.config['app']['project_dir']
|
||||||
|
|
||||||
|
self.ctx.log['ut4'].info('Installing maps...')
|
||||||
|
cmd = 'rsync -ravzp {src} {dst}'.format(**{
|
||||||
|
'src': '/'.join([project_dir, 'files/maps']),
|
||||||
|
'dst': '/'.join([project_dir, 'instance/LinuxServer/UnrealTournament/Content/Paks/'])
|
||||||
|
})
|
||||||
|
self._invoke_command(cmd)
|
||||||
|
|
||||||
|
self.ctx.log['ut4'].info('Installing mutators...')
|
||||||
|
cmd = 'rsync -ravzp {src} {dst}'.format(**{
|
||||||
|
'src': '/'.join([project_dir, 'files/mutators']),
|
||||||
|
'dst': '/'.join([project_dir, 'instance/LinuxServer/UnrealTournament/Content/Paks/'])
|
||||||
|
})
|
||||||
|
self._invoke_command(cmd)
|
||||||
|
|
||||||
|
def _install_redirect_lines(self):
|
||||||
|
return self.install_redirect_lines()
|
||||||
|
|
||||||
|
def install_redirect_lines(self):
|
||||||
|
self.ctx.log['ut4'].info('Generating redirect references...')
|
||||||
|
redirect_protocol = self.ctx.config['app']['redirect_protocol']
|
||||||
|
redirect_url = self.ctx.config['app']['redirect_url']
|
||||||
|
project_dir = self.ctx.config['app']['project_dir']
|
||||||
|
mod_dir = '/'.join([project_dir, 'files'])
|
||||||
|
files = glob.iglob('{}/**/*.pak'.format(mod_dir))
|
||||||
|
redirect_lines = []
|
||||||
|
for idx, filename in enumerate(files):
|
||||||
|
if idx > 5:
|
||||||
|
break
|
||||||
|
md5sum = self._md5sum_file(filename)
|
||||||
|
p = pathlib.Path(filename)
|
||||||
|
relative_path = p.relative_to(mod_dir)
|
||||||
|
basename = p.name
|
||||||
|
|
||||||
|
line = '\
|
||||||
|
RedirectReferences=(PackageName="{basename}",\
|
||||||
|
PackageURLProtocol="{redirect_protocol}",\
|
||||||
|
PackageURL="{redirect_url}/{relative_path}",\
|
||||||
|
PackageChecksum="{md5sum}")'\
|
||||||
|
.format(**{
|
||||||
|
'basename': basename,
|
||||||
|
'redirect_protocol': redirect_protocol,
|
||||||
|
'redirect_url': redirect_url,
|
||||||
|
'relative_path': relative_path,
|
||||||
|
'md5sum': md5sum
|
||||||
|
})
|
||||||
|
self.ctx.log['ut4'].debug("redirect line = '%s'", line)
|
||||||
|
redirect_lines.append(line)
|
||||||
|
# end loop - for filename in files:
|
||||||
|
|
||||||
|
# START HERE --trying to dynamically add redirect references and
|
||||||
|
# gonna need to be able to work with the ini file, including support
|
||||||
|
# for duplicate keys. Alternatively, maybe do the redirect lines through
|
||||||
|
# a patch
|
||||||
|
|
||||||
|
# I found out the hard way, configobj doesn't seem the best suited for
|
||||||
|
# duplicate keys...
|
||||||
|
|
||||||
|
# install into Game.ini
|
||||||
|
game_ini_filepath = '/'.join([project_dir, 'files/config/Game.ini'])
|
||||||
|
# game_ini_data = configobj.ConfigObj(game_ini_filepath)
|
||||||
|
config = configparser.ConfigParser(
|
||||||
|
# game_ini_filepath,
|
||||||
|
# dict_type=MultiOrderedDict,
|
||||||
|
strict=False
|
||||||
|
)
|
||||||
|
s = config.sections()
|
||||||
|
print(s)
|
||||||
|
# config.set('/Script/UnrealTournament.UTBaseGameMode', 'RedirectReferences', line)
|
||||||
|
config.read(game_ini_filepath)
|
||||||
|
l = config.write(sys.stdout)
|
||||||
|
print('l', l)
|
||||||
|
|
||||||
|
|
||||||
|
def _install_rulesets(self):
|
||||||
|
self.ctx.log['ut4'].info('Concatenating rulesets for game modes...')
|
||||||
|
project_dir = self.ctx.config['app']['project_dir']
|
||||||
|
|
||||||
|
src_dir = '/'.join([project_dir, 'files/rulesets'])
|
||||||
|
out_dir = '/'.join([project_dir, '/instance/LinuxServer/UnrealTournament/Saved/Rulesets'])
|
||||||
|
out_filename='/'.join([out_dir, 'ruleset.json'])
|
||||||
|
|
||||||
|
cmd = 'mkdir -pv {out_dir}'.format(**{
|
||||||
|
'out_dir': out_dir
|
||||||
|
})
|
||||||
|
self._invoke_command(cmd)
|
||||||
|
|
||||||
|
self.ctx.log['ut4'].info('out filename=%s', out_filename)
|
||||||
|
|
||||||
|
# echo {\"rules\":[ > "$OUT_FILENAME"
|
||||||
|
cmd = 'echo {\"rules\":[ > "{out_filename}"'
|
||||||
|
self._invoke_command(cmd)
|
||||||
|
|
||||||
|
cmd = 'for f in "{src_dir}"/*.json ; do ; cat "$f" >> "{out_filename}" ; done'.format(
|
||||||
|
**{
|
||||||
|
'src_dir': src_dir,
|
||||||
|
'out_filename': out_filename
|
||||||
|
})
|
||||||
|
self._invoke_command(cmd)
|
||||||
|
|
||||||
|
cmd = 'echo "]}}" >> "{out_filename}"'.format(
|
||||||
|
**{
|
||||||
|
'out_filename': out_filename
|
||||||
|
})
|
||||||
|
self._invoke_command(cmd)
|
||||||
|
self.ctx.log['ut4'].info('output ruleset is at "%s"', out_filename)
|
||||||
|
|
||||||
|
def _invoke_command(self, cmd, msg=None):
|
||||||
|
assert isinstance(cmd, str), 'cmd input must be string: %s'.format(cmd)
|
||||||
|
if msg is None:
|
||||||
|
msg = cmd
|
||||||
|
print(msg)
|
||||||
|
self.ctx.log['ut4'].info('running cmd: %s', cmd)
|
||||||
|
cwd = None # todo(mg) ?
|
||||||
|
# os.system(cmd)
|
||||||
|
|
||||||
|
def _md5sum_file(self, filename):
|
||||||
|
with open(filename, mode='rb') as f:
|
||||||
|
d = hashlib.md5()
|
||||||
|
for buf in iter(partial(f.read, 128), b''):
|
||||||
|
d.update(buf)
|
||||||
|
h = d.hexdigest()
|
||||||
|
return h
|
||||||
|
|
||||||
|
def _validate_env_vars(self):
|
||||||
|
variable_names = (
|
||||||
|
'project_dir',
|
||||||
|
'download_url',
|
||||||
|
'download_filename',
|
||||||
|
'download_md5',
|
||||||
|
'redirect_protocol',
|
||||||
|
'redirect_url',
|
||||||
|
'remote_game_host',
|
||||||
|
'remote_game_directory',
|
||||||
|
'remote_redirect_host'
|
||||||
|
)
|
||||||
|
for name in variable_names:
|
||||||
|
value = self.ctx.config['app'][name]
|
||||||
|
self.ctx.log['ut4'].info('%s: %s', name, value)
|
||||||
|
|
||||||
|
i = input('Continue with above configuration? (y/N):')
|
||||||
|
if i.lower() != 'y':
|
||||||
|
self.ctx.log['ut4'].info('Doing nothing.')
|
||||||
|
return False
|
||||||
|
self.ctx.log['ut4'].info('Continuing.')
|
||||||
|
return True
|
||||||
|
|
||||||
|
class MultiOrderedDict(collections.OrderedDict):
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
if isinstance(value, list) and key in self:
|
||||||
|
self[key].extend(value)
|
||||||
|
else:
|
||||||
|
super().__setitem__(key, value)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user