Documentation menu

Lifecycle Hooks

Lifecycle hooks let you run scripts (.bat, .cmd, .ps1, or .py) automatically at specific points in a DayZ server's lifecycle. They're useful for pre-start health checks, post-stop cleanup, crash alerting, or hooking into your own infrastructure (Slack pages, Datadog events, S3 backups, etc.) without modifying Citadel itself.

Where they live

For each server, drop scripts under:

<installDir>/lifecycle_hooks/

<installDir> is the path you configured for the DayZ server in the dashboard — the same directory that holds DayZServer_x64.exe. The lifecycle_hooks/ directory is created automatically the first time you save a script there from the file browser.

Events

Citadel calls scripts whose filename matches the event name. All four events are optional — only the events you actually have a script for fire.

EventFilenameBehaviour
pre-startpre-start.{bat,cmd,ps1,py}Runs before the DayZ server process starts. Blocking — Citadel waits for the script to exit. A non-zero exit code aborts the start and the failure is surfaced in the dashboard. Use for health checks, mounting drives, regenerating configs.
startedstarted.{bat,cmd,ps1,py}Runs after the server is up and accepting connections. Fire-and-forget — Citadel doesn't wait. Use for "server X is up" notifications, updating external status boards.
stoppedstopped.{bat,cmd,ps1,py}Runs after a graceful stop completes. Fire-and-forget. Use for log rotation, backup uploads, "going offline" notifications.
crashedcrashed.{bat,cmd,ps1,py}Runs when Citadel detects the DayZ process exited unexpectedly (non-zero exit code that wasn't a graceful stop). Fire-and-forget. Use for paging, crash-dump uploads, auto-restart-throttling logic.

If two scripts match the same event with different extensions, the first one Citadel finds (in the order above) wins. Don't rely on dual-script behavior.

Environment variables

Hook scripts get the following environment variables injected so they can act on the right server without hard-coding:

VariableExampleDescription
CITADEL_SERVER_IDsrv_4f3aCitadel's internal server id
CITADEL_SERVER_NAMEChernarus PvEDisplay name from the dashboard
CITADEL_SERVER_INSTALL_DIRC:\DayZServerThe server's install directory
CITADEL_SERVER_PORT2302Game port
CITADEL_SERVER_RCON_PORT2303RCON port
CITADEL_EVENTpre-startThe event that triggered this script
CITADEL_EXIT_CODE139(crashed event only) The non-zero exit code from DayZ

Scripts are launched with the lifecycle_hooks/ directory as the working directory.

Editing scripts from the dashboard

Writing scripts via the file browser requires the files.edit-scripts permission on your Citadel role. This is intentional: anyone who can save a .bat or .ps1 to disk can effectively run code on the server box, so the permission is a separate gate from the general files.edit permission used for editing config files. None of the built-in roles include files.edit-scripts by default — grant it explicitly to roles that need it under Settings → Users & Roles.

The path constraint matters too:

Script writes (.bat / .cmd / .ps1 / .sh) only succeed when the resolved destination path is inside <installDir>/lifecycle_hooks/. Writes outside that directory return 403 with file.write-blocked in the audit log.

This means you can't use the file browser to drop a .ps1 into C:\Windows\System32\ — the editor refuses, even with files.edit-scripts. If you need to script outside lifecycle_hooks/, do it via the OS shell on the server, not via Citadel.

Examples

pre-start health check (PowerShell)

# pre-start.ps1 — refuse to start if the data drive is < 5 GB free
$drive = Get-PSDrive C
$freeGB = [math]::Round($drive.Free / 1GB, 1)
if ($freeGB -lt 5) {
  Write-Error "Refusing to start ${env:CITADEL_SERVER_NAME}: only $freeGB GB free on C:"
  exit 1
}
exit 0

crashed → Slack alert (PowerShell)

# crashed.ps1 — POST to a Slack incoming webhook
$payload = @{
  text = ":rotating_light: ${env:CITADEL_SERVER_NAME} crashed (exit $env:CITADEL_EXIT_CODE)"
} | ConvertTo-Json
Invoke-RestMethod -Uri 'https://hooks.slack.com/services/...' -Method Post -Body $payload -ContentType 'application/json'

started → log rotation (Batch)

:: started.bat — archive yesterday's RPT before the new one fills up
forfiles /p "%CITADEL_SERVER_INSTALL_DIR%\profiles" /m *.RPT /d -1 /c "cmd /c move @file archive\@file"

Audit trail

Every hook execution is recorded in the Citadel audit log with the event name, server id, exit code, and duration. See lifecycle.hook.start / lifecycle.hook.complete / lifecycle.hook.error in the Audit Log Codes reference.

Why a separate permission

Allowing arbitrary script edits via the same permission used for editing serverDZ.cfg would mean any user with config-edit access could escalate to remote code execution on the box. Splitting files.edit-scripts out lets operators give "config editor" access to people they trust with config but not with shell — which is the common case.