Friday, December 02, 2011

I wouldn't be at all surprised to find out I'm the only person in the world running a Counter-Strike Source server on systemd, although I'd be happy to find out that's not the case. Since it was quite painful for me to find useful info on the subject, I figured I'd write up a bit here.

Installing on Linux
First off, Valve's installer won't work if you don't have uncompress on your system. On Fedora, you can make sure with:
yum -y install ncompress

Next, you need to get and run Valve's installer. If you want to keep this stuff in a particular directory, go there, then run:
wget http://storefront.steampowered.com/download/hldsupdatetool.bin
chmod +x hldsupdatetool.bin
./hldsupdatetool.bin
./steam

You will have to type "yes" to the agreement to do the install. Running ./steam should update the platform to the latest version. Next, run this, which could take awhile, to actually install the game server:
./steam -command update -game "Counter-Strike Source" -dir .

After tinkering around, I found that the following command worked well to start the server:
./css/srcds_run -game cstrike -ip 0.0.0.0 -port 27016 -maxplayers 32 -autoupdate +map de_dust

srcds_run is a wrapper that will try to run the right architecture-optimized binary for your system. -ip 0.0.0.0 makes the game listen on all available network interfaces - you probably only really care that it's running on whatever address your system uses to go out to the internet, but the server had trouble figuring which one to use, and this probably won't hurt you. -port 27016 is really optional, but it seems kind of the norm for CS:S servers, at least if you also have a 1.6 server running on 27015, the default. -maxplayers is obvious. -autoupdate will automatically move to any newer versions of the server. +map is different from the other options because it is actually a command that gets run immediately on the server console, not a normal cli option, but you might as well set it here for the sake of automation. FYI, if you have netfilter/iptables running, you could add the following line to /etc/sysconfig/iptables to open up the firewall:
-A INPUT -m state --state NEW -m tcp -p tcp --dport 27016 -j ACCEPT
then run:
service iptables restart

I also had an issue with selinux stopping srcds from from binding to the socket (listening on the network). While I'm sure the "right" thing to do would be to specifically except srcds or even write a policy module for it, I took the easy way out and dropped selinux into Permissive mode. You can do this at runtime by running setenforce 0, and in a persistent way by setting the following in /etc/sysconfig/selinux:
SELINUX=permissive

Now, try running the application, and it should work fine. Yay!

Using systemd
Okay, so first off, I had previously done this with a System V init script, and although it was easier to find examples online regarding how to do that, in the end I'm much happier with my systemd implementation. After searching fruitlessly for help (there's lots of help on running systemctl commands, but not on building unit files), I ended up just using a couple of examples, along with man pages. I started looking at httpd's (apache) unit file, and it turns out it's a little overly-complicated, since httpd understands running as a daemon, so its unit file has Start, Reload and Stop logic. For a simple, non-daemonized application like srcds, I wanted a wrapper that would start it, run it in the background, and just kill it when I send it a stop command. Still, httpd wasn't a bad place to start, and as far as man pages go, systemctl(1)'s SEE ALSO section pointed me to systemd.unit(5), which led me to systemd.service(5) and systemd.exec(5), which were all very helpful as far as understanding directives in the unit file. In the end, here's what I came up with, placed in /lib/systemd/system/cstrike-source.service:

[Unit]
Description=Counter-Strike Source Server
After=syslog.target network.target mysqld.target

[Service]
PIDFile=/var/run/cstrike-source.pid
User=steam
Group=steam
ExecStart=/home/steam/css/srcds_run -game cstrike -ip 0.0.0.0 -port 27016 -maxplayers 32 -autoupdate +map de_dust
KillMode=control-group

[Install]
WantedBy=multi-user.target

The description is arbitrary text, while the "after" section ensures these other services will get started before srcds is run. mysqld was important to me because I use the psychostats (http://www.psychostats.com/) plugin for srcds/sourcemod, with live stats population and in-game stats lookup, which uses mysql as a backend. The PIDFile will be used to track application state. Running as steam prevents some potential security issues. The ExecStart directive gives the actual command to run. Using httpd's unit as an example, I think one could use an EnvironmentFile to configure the command line to run, but I'm keeping everything in this file. KillMode is important, since setting it to "process" would only kill the command systemd ran directly, while the server process under it could just stay running. WantedBy tells systemd what "target" (in SysV land, this is roughly similar to runlevel) to attach this service to. Once this file is created, you can run the following:
chmod +x /lib/systemd/system/cstrike-source.service
systemctl --system daemon-reload
systemctl enable cstrike-source.service
systemctl start cstrike-source.service
systemctl status cstrike-source.service

daemon-reload ensures systemd reads the latest saved version of the unit file. enable turns this service on for starting at boot time. start runs it right now. status tells you it started. At this point, stuff that would've normally printed to your terminal had you run the command yourself will be going to /var/log/messages (assuming a default syslog or syslog-ng configuration). But how will you actually issue commands? One solution is to set an rcon_password in cstrike/cfg/server.cfg, then just run commands over rcon from the game. You could also set up an administration system like mani (http://www.mani-admin-plugin.com/joomla/index.php) or sourcemod (http://www.sourcemod.net/) - in fact, I recommend doing so. However, you can also get rcon access without having a game client running.

Python RCON
A search revealed several libraries and cli tools out there for RCON access, but I ended up going with SourceLib (https://github.com/frostschutz/SourceLib), mostly because I like Python. So far, it works great, and I've made a little command line wrapper around it. Someday I might add command line history, or even autocompletion, but this works for now. :)

#!/usr/bin/env python
import argparse
from SourceLib.SourceRcon import SourceRcon
def main():
parser = argparse.ArgumentParser(
description='Simple RCON client implemented in Python, based on SourceLib')
parser.add_argument('--password', '-p', required=True,
help='password for server')
parser.add_argument('--host', '-H', required=True,
help='hostname/ip address of server')
parser.add_argument('--port', '-P', default=27015, type=int,
help='listening port of server - Default=%(default)s')
parser.add_argument('--timeout', '-t', default=1.0, type=float, metavar='SECONDS',
help='timout to connect to RCON server - default=%(default)s')
args = parser.parse_args()
conn = SourceRcon(host=args.host, port=args.port, password=args.password,
timeout=args.timeout)

while True:
content = raw_input('>> ')
if content == 'exit':
exit(0)
elif content in ['quit']:
choice = raw_input('"quit" will stop the server. "exit" will stop this program. Really quit? (y/N)')
if choice not in ['y', 'Y']:
continue
response = conn.rcon(content)
print response

if __name__ == "__main__":
main()

Save it, chmod +x it, and run it with --help to get an idea how to use it. Once it's running, you can issue RCON commands, set CVARs, etcetera, to your server. Yay!