Running servers¶
Start, stop, status¶
dzo start deerisle
dzo stop deerisle # stops and stays stopped
dzo status [deerisle] # build, mods, running, uptime, players, pending updates
dzo logs deerisle -f # server console from journald
dzo start returns when the server answers Steam queries, not just when the
container runs. Under the hood these are plain systemd user units
(dzo-deerisle.service), so systemctl --user and journalctl --user
work too.
Stopping the server¶
Every stop goes through the unit: dzo stop, every restart, an update, a reboot.
stop.method in instance.yaml (or the site defaults) chooses how:
rcon(default)dzo instance shutdownsends#shutdownover RCon and waits up tostop.timeout(30 s) for the process to exit; then podman stops the container (SIGTERM,SIGKILLafter 120 s). The restart is immediate.killNo request:
SIGTERMand, one second later,SIGKILL.
Why stdin matters. The experimental (diag) builds of DayZ raise an assertion at
shutdown: Assertion failed ... enf_scriptmodule.cpp ... Script is leaking! Check
log! followed by (A)bort (R)etry (I)gnore. Vanilla leaks a script instance
(BunkerBroadcastManager) at every shutdown, so this happens without any mod. The
build waits for the answer on standard input. With stdin at end-of-file (a container, a
systemd service, nohup) the read never blocks and never ends: the process spins at
100 % of a core after “Destroying game” and has to be killed. Seen on 1.30
experimental (1.30.164014); the stable 1.29 server shows no such prompt. Other builds
or modded maps may raise assertions of their own at shutdown.
dzo therefore starts every server with a stdin that holds the answer i (Ignore,
stop.ignore_asserts, default on). Measured on 1.30 experimental, a stop then takes
3 seconds (SIGTERM) to 13 seconds (RCon #shutdown) instead of hanging. A server
that is stuck anyway is killed after the timeouts, and the unit does not stay “failed”
after a clean stop.
Graceful restarts¶
dzo restart deerisle --minutes 30 --lock 7 --kick 3 --text "Restart"
dzo restart deerisle --now
dzo restart deerisle --cancel
A graceful restart runs on a timeline. Every number is minutes before the restart:
minutesthe countdown starts (30 in the example)
lockthe server is locked against new logins (7)
kickthe players are kicked (3). The countdown ends here; the service is stopped when the restart time is reached.
The countdown: the first message when it starts, then at 20 and 15 minutes
left over RCon (say), and from 10 minutes left on once a minute as an on-screen notification, plus
“<text> NOW” 10 seconds before the kick. All times count down to the kick. With 30/7/3 the first message reads “in 27 minutes”.
Each message is <text> in <time>; the lock is announced as well (in the same notification when
one is due then). The kick tries up to three passes, then kicks everyone. The countdown is dzo’s own; the server’s
built-in countdown and shutdown of messages.xml are never used (see messages in
Configuration).
How the countdown reaches the players is announce.ways (in restarts.announce and in updates.restart_announce), a list of rules:
restarts:
announce:
minutes: 30
lock: 7
kick: 3
ways:
- {way: rcon, at: [30m, 20m, 15m]} # the default, above 10 minutes
- {within: 10m, way: notification, every: 1m, now: 10s} # the default, the last 10 minutes
# or, with DayZ Expansion: {within: 10m, way: expansion, type: baguette, every: 1m, now: 10s}
wayrcon(BattlEyesay, works withoutdzo-admin),chat(a red chat line throughdzo-admin),notification(the on-screen notification of the game) orexpansion(an Expansion notification;typeistoast,baguette,activity,killfeed,marketorgarage).withinthe rule applies while at most this much is left until the kick. Without it, the rule covers the whole countdown, above the other rules’
within.everyoratthe interval, or the exact times left (not both). On screen (
notification,expansion) the announcements must be at least a minute apart.nowone more announcement “<text> NOW” this long before the kick; only one rule may set it.
Notifications go through dzo-admin and the API of dzo serve: the restart needs an API token, $DZO_TOKEN or
the file announce.token in the secrets directory. dzo instance apply (and dzo units sync) creates that file with a token of the announcer role (it can only read the instance’s status and broadcast) whenever an instance runs dzo-admin and the file is missing, or its token was revoked or has expired (tokens last a year, so run apply again by then). If the mod is not
connected, the API or the token is missing, or the message is refused, the same text goes out over RCon say and the
restart continues on time. expansion becomes a plain notification when the mod does not report Expansion. A dzo-admin from before these ways (it runs until its instance is restarted onto the new mod) reports no features: the on-screen notifications still arrive, as plain notifications of ten seconds titled “Server”, and the title and duration of a rule are ignored.
dzo talks to the server over its own BattlEye RCon client. The restart itself goes through the unit’s stop (see Stopping the server). If RCon does not answer, the server is restarted through systemd right away. If a RCon command fails in the middle of the countdown (a timeout, a dropped connection), dzo logs it, skips that command and connects again for the next one; a failed lock or kick is tried once more on the new connection. The server is stopped at the time asked for in any case.
The restart runs as its own systemd unit, so it continues if your SSH session ends. When a maintenance restart and an update restart fall together, they are merged into one.
Stopping with a countdown¶
dzo stop stops at once. To be nice to the players, give it the countdown of a restart:
dzo stop deerisle --graceful # the restarts.announce numbers
dzo stop deerisle --minutes 10 --lock 3 --kick 1 --text "Server maintenance"
dzo stop deerisle --cancel # end a countdown, unlock the server
Any of --graceful, --minutes, --lock, --kick and --text turns the countdown on;
the numbers you leave out come from the instance’s restarts.announce. The timeline, the
messages and the RCon handling are those of the restart above, with the messages saying
“Server shutdown” (or your --text) instead of “restart”. When the time is up the service is
stopped and stays stopped. An instance that is not running is not announced to, and without RCon
the stop goes ahead at once. dzo stop <name> --cancel (and dzo restart <name> --cancel)
ends either countdown.
The countdown runs in the command itself (only scheduled restarts run as a unit of their own), so
it ends with your session. For a long one, run it inside tmux, or as
systemd-run --user dzo stop deerisle --graceful.
Scheduled restarts¶
restarts.schedule in instance.yaml lists the times of day the server restarts, as HH:MM, or
Mon,Thu HH:MM for some weekdays only (Mon, Tue, Wed, Thu, Fri, Sat, Sun). *:MM restarts every
hour at that minute, and hourly is short for *:00:
restarts:
schedule: ["02:00", "06:00", "10:00", "14:00", "18:00", "22:00"]
announce:
minutes: 30
lock: 7
kick: 3
text: "Restart" # the message; ways: is described above
dzo instance apply <name> turns them into dzo-restart-<name>.timer. The times are the
restart itself: the timer fires announce.minutes earlier, when the countdown starts (04:00 with
minutes: 30 fires at 03:30). Give every server its own times, so they do not go down together.
HTTPS for the web interface and the API, certificate renewal and port 443 are in HTTPS.
One-off restarts (“today at 18:30”) can be scheduled in the web interface; they also become systemd timers.
Before any planned restart dzo renders the instance in a dry run while the server is still running. If that fails, the restart is skipped, the server keeps running and you are alerted. A broken change in the site repository therefore never takes a server down.
Health checks and crash loops¶
Every server has two checks, run inside the container:
- startup
waits until the server answers Steam queries. Long mission loads do not count as failures until
health.startup_timeout.- liveness
probes the running server. After
interval×retrieswithout an answer (default 5 minutes) the container is killed and systemd restarts it.
When the render or a pre_start hook fails in ExecStartPre, dzo prints a
banner of !!! lines with the instance name and the error to stderr. Under
systemd the lines have the error priority, so journalctl -u <unit> and
systemctl status show them prominently.
Three brakes stop endless restarts:
the pre-restart dry-run render described above;
a failed render during a crash restart marks the instance
failed-render. It is not tried again until its inputs change or you rundzo instance ack-failure <name>;restart_limit: afterburststarts withinintervalthe unit stays failed, Icinga turns critical and Discord reports the crash loop.
Console and RCon¶
dzo rcon exec --instance deerisle players # one command, the instance's own port and password
dzo rcon exec --addr 127.0.0.1:2303 --password <password> players
dzo rcon console deerisle # one command per line; server messages (connects, chat, kicks) as they come
dzo rcon rotate deerisle --restart # a new password; the server reads it at its start
The console reconnects when the server restarts. A new RCon password takes effect at the next start, so
rotate refuses a running instance unless --restart is given. The password lives in
<paths.secrets>/rcon/<instance>.
The RCon password is generated per instance. RCon is reachable on the host only (127.0.0.1)
unless rcon_bind in instance.yaml or in the site defaults says otherwise, see
Configuration. To let external RCon tools connect, set rcon_bind: 0.0.0.0 (or one
address of the host) and allow the RCon port in your firewall for those tools only.
Logs¶
Server console: journald (
dzo logs <name>).Profile logs are rotated into an archive outside the instance, see below.
After an unclean exit the unit’s
ExecStopPostrunsdzo logs crash-summary: the last 25 lines of the newesterror.log, script log, crash log and RPT go to the journal and to Discord. It runs before the next start rotates the files.
Audit log¶
Every admin action (player and vehicle actions, bans, evidence, logins and logouts) is appended to
<paths.db>/audit.jsonl, one JSON object per line, with the actor, where it came from, the instance and the
result. Actions of the web interface name the person (<token>:<Steam ID>) and carry the browser’s
address in client. That address is personal data and expires: serve.audit_ip_retention_days
(default 90, 0 keeps it) says how long; once a day dzo serve blanks the address of older entries and
keeps the entries. The file is 0600. Next to it lies audit.jsonl.lock, which the writers (dzo serve
and dzo user) take so that the daily rewrite never loses a line. Read the log in the web interface
(admin) or with GET /api/v1/audit; the login attempts are in the service log of dzo serve.
Profile log rotation¶
The profiles directory collects logs from the server, BattlEye and mods. logs: in instance.yaml (or
the site defaults) is a list of regular expressions, matched against the path below profiles/:
logs:
rotate: # added to the site's list; any rule here replaces the default list (the server's own files)
- {match: '^DayZServer(_x64)?_.*\.RPT$'}
- {match: '^script_.*\.log$'}
- {match: '\.mdmp$', max_age: 14d}
- {match: '^VPPAdminTools/Logs/.*\.txt$'}
- {match: '^battleye/.*\.log$', keep_newest: 2}
keep_newest: 1 # default 1; per rule, the newest matches stay in place
min_age: 10m # default; a file changed more recently is never touched
archive:
compress: gzip # default; or none
max_age: 90d # default
max_size: 50GiB # default: no limit
A rule that needs no options can be a plain string: - '^RFMosquito_.*\.log$' is the same as - {match: '^RFMosquito_.*\.log$'}.
Per rule the newest keep_newest files stay (the server may still write them) and every older match is
moved to <paths.logs>/<instance>/<date>/<path>.gz: copied through gzip, synced, and only then removed.
Only regular files below profiles/ are touched, links are not followed, and serverDZ.cfg, the
BattlEye configs, dzo-admin/ and every .json/.xml file are never moved, even if a rule matches
them. The default rules are the *.RPT, script_*.log, crash_*.log, error_*.log, warning_*.log and *.ADM of the server and
*.mdmp crash dumps (14 days).
It runs before every start (ExecStartPre, a failure does not stop the start) and hourly for all
instances (dzo-logs.timer), which also applies the archive’s max_age and max_size (oldest
first). dzo logs rotate <name> --dry-run lists what it would move and the large files no rule matches
(new mod logs show up there, except those of dzo_logz in dzo/logz/logs/); dzo logs archive <name> [--since 7d] [--match <regexp>] lists the archive.
Instances¶
dzo instance create <name>
dzo instance apply <name> # regenerate units and timers after config changes
dzo instance clone <old> <new> # copy the data into a new instance of the site repository
dzo instance remove <name> # stops it, removes units and data, asks before
dzo wipe <name> # wipe the world, after confirmation and a snapshot
clone needs the new instance in the site repository (its own ports and settings) and the same map. It
copies the old instance’s directory with a btrfs snapshot, gives the copy its own profiles, keys and
RCon password, and keeps the server build the old one runs. A running source needs --force (the copy
is crash-consistent). remove deletes the data directory and, unless --keep-snapshots, the
snapshots, and removes the instance’s units; then delete instances/<name>/ from the site repository.
Development: dzo instance render (render only, nothing is started) and dzo shell (a container with
the instance’s mounts and a shell, without starting the server) are the “development mode”; a server is
started only by dzo start.
Instances cannot be renamed in place, because the name is part of unit names, paths and the database. Clone to the new name and remove the old instance.