| title | Systemd Installation |
|---|---|
| description | Quick start installation with systemd services |
This guide describes a classic Linux installation with systemd.
It starts two services:
incidentrelay.service # HTTP API, UI, webhooks
incidentrelay-scheduler.service # reminders, escalations, periodic jobs
The scheduler must run as a separate service. Do not start it inside every web worker.
/var/www/incidentrelay # application directory
/var/www/incidentrelay/venv # Python virtual environment
/etc/incidentrelay/incidentrelay.conf # configuration file
/var/lib/incidentrelay # SQLite database or runtime state
/var/log/incidentrelay # logs
/usr/local/lib/incidentrelay/voice_providers # custom voice providers
IncidentRelay reads the configuration path from:
INCIDENTRELAY_CONFIG_FILE
The old ONCALL_CONFIG_FILE variable should not be used.
Debian / Ubuntu example:
sudo apt-get update
sudo apt-get install -y \
git \
python3 \
python3-venv \
python3-pip \
build-essential \
curlIf you use PostgreSQL, also install PostgreSQL build/runtime dependencies:
sudo apt-get install -y libpq-devsudo mkdir -p /var/www
sudo git clone https://github.com/roxy-wi/IncidentRelay.git /var/www/incidentrelay
cd /var/www/incidentrelaysudo python3 -m venv /var/www/incidentrelay/venv
sudo /var/www/incidentrelay/venv/bin/pip install --upgrade pip
sudo /var/www/incidentrelay/venv/bin/pip install -r /var/www/incidentrelay/requirements.txt
sudo /var/www/incidentrelay/venv/bin/pip install gunicornFor PostgreSQL installations:
sudo /var/www/incidentrelay/venv/bin/pip install psycopg2-binarysudo mkdir -p /etc/incidentrelay
sudo mkdir -p /var/lib/incidentrelay
sudo mkdir -p /var/log/incidentrelay
sudo mkdir -p /usr/local/lib/incidentrelay/voice_providersSet ownership:
sudo chown -R www-data:www-data /var/www/incidentrelay
sudo chown -R www-data:www-data /var/lib/incidentrelay
sudo chown -R www-data:www-data /var/log/incidentrelayCustom voice provider files are executable Python code. Keep this directory writable only by administrators:
sudo chown root:root /usr/local/lib/incidentrelay/voice_providers
sudo chmod 755 /usr/local/lib/incidentrelay/voice_providersCreate:
/etc/incidentrelay/incidentrelay.conf
SQLite example:
[main]
log_level = INFO
log_file = /var/log/incidentrelay/incidentrelay.log
[server]
host = 0.0.0.0
port = 8080
public_base_url = http://localhost:8080
[database]
type = sqlite
path = /var/lib/incidentrelay/incidentrelay.db
[sqlite]
wal = true
busy_timeout = 5000
[voice]
provider = stub
providers_dir = /usr/local/lib/incidentrelay/voice_providers
callback_secret = change-meFor production behind Nginx or HAProxy, set public_base_url to the real external URL:
[server]
public_base_url = https://incidentrelay.example.compublic_base_url is used for generated links and callback URLs.
Copy service files:
sudo cp /var/www/incidentrelay/systemd/incidentrelay.service /etc/systemd/system/
sudo cp /var/www/incidentrelay/systemd/incidentrelay-scheduler.service /etc/systemd/system/Reload systemd:
sudo systemctl daemon-reloadcd /var/www/incidentrelay
sudo -u www-data \
INCIDENTRELAY_CONFIG_FILE=/etc/incidentrelay/incidentrelay.conf \
/var/www/incidentrelay/venv/bin/python app/migrate.py migratecd /var/www/incidentrelay
sudo -u www-data \
INCIDENTRELAY_CONFIG_FILE=/etc/incidentrelay/incidentrelay.conf \
/var/www/incidentrelay/venv/bin/python manage.py create-admin \
--username admin \
--password 'change-me-123' \
--email admin@example.comsudo systemctl enable incidentrelay
sudo systemctl enable incidentrelay-scheduler
sudo systemctl start incidentrelay
sudo systemctl start incidentrelay-schedulerCheck status:
sudo systemctl status incidentrelay
sudo systemctl status incidentrelay-schedulerOpen:
http://SERVER_IP:8080/login
Web logs:
journalctl -u incidentrelay -fScheduler logs:
journalctl -u incidentrelay-scheduler -fApplication log file:
tail -f /var/log/incidentrelay/incidentrelay.log[Unit]
Description=IncidentRelay Web service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/incidentrelay
Environment=INCIDENTRELAY_CONFIG_FILE=/etc/incidentrelay/incidentrelay.conf
Environment=INCIDENTRELAY_SERVICE=web
Environment=PYTHONUNBUFFERED=1
ExecStart=/var/www/incidentrelay/venv/bin/gunicorn \
--bind 0.0.0.0:8080 \
--workers 1 \
--threads 4 \
--timeout 120 \
--access-logfile - \
--error-logfile - \
"app:create_app()"
Restart=always
RestartSec=5
KillSignal=SIGTERM
TimeoutStopSec=30
[Install]
WantedBy=multi-user.target[Unit]
Description=IncidentRelay Scheduler service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/incidentrelay
Environment=INCIDENTRELAY_CONFIG_FILE=/etc/incidentrelay/incidentrelay.conf
Environment=INCIDENTRELAY_SERVICE=scheduler
Environment=PYTHONUNBUFFERED=1
ExecStart=/var/www/incidentrelay/venv/bin/python -m app.scheduler_worker
Restart=always
RestartSec=5
KillSignal=SIGTERM
TimeoutStopSec=30
[Install]
WantedBy=multi-user.targetThe web process must not start the scheduler automatically.
Correct model:
web service:
create_app()
no scheduler autostart
scheduler service:
create_app()
start_scheduler()
If scheduler startup currently happens inside create_app(), guard it:
import os
if os.getenv("INCIDENTRELAY_SERVICE") == "scheduler":
start_scheduler()If app.scheduler_worker starts the scheduler explicitly, it is usually better to remove automatic scheduler startup from create_app() completely.
For production, it is usually better to bind Gunicorn to localhost and expose IncidentRelay through Nginx or HAProxy with HTTPS.
Change web service ExecStart:
ExecStart=/var/www/incidentrelay/venv/bin/gunicorn \
--bind 127.0.0.1:8080 \
--workers 1 \
--threads 4 \
--timeout 120 \
--access-logfile - \
--error-logfile - \
"app:create_app()"Then set:
[server]
public_base_url = https://incidentrelay.example.comFor larger installations, use PostgreSQL.
Example config section:
[database]
type = postgresql
host = 127.0.0.1
port = 5432
name = incidentrelay
user = incidentrelay
password = change-meFor PostgreSQL, increase web workers if needed:
ExecStart=/var/www/incidentrelay/venv/bin/gunicorn \
--bind 127.0.0.1:8080 \
--workers 4 \
--threads 4 \
--timeout 120 \
--access-logfile - \
--error-logfile - \
"app:create_app()"For SQLite, keep --workers 1.
cd /var/www/incidentrelay
sudo systemctl stop incidentrelay-scheduler
sudo systemctl stop incidentrelay
sudo git pull
sudo /var/www/incidentrelay/venv/bin/pip install -r requirements.txt
sudo -u www-data \
INCIDENTRELAY_CONFIG_FILE=/etc/incidentrelay/incidentrelay.conf \
/var/www/incidentrelay/venv/bin/python app/migrate.py migrate
sudo systemctl start incidentrelay
sudo systemctl start incidentrelay-schedulerCheck:
systemctl show incidentrelay --property=Environment
systemctl show incidentrelay-scheduler --property=EnvironmentCheck file exists:
ls -l /etc/incidentrelay/incidentrelay.confCheck permissions:
sudo -u www-data test -w /var/lib/incidentrelay
sudo -u www-data test -r /etc/incidentrelay/incidentrelay.confCheck that the scheduler is not running inside web workers and that only one scheduler service is active:
systemctl status incidentrelay-scheduler
ps aux | grep schedulerCheck logs:
journalctl -u incidentrelay -n 100 --no-pager
journalctl -u incidentrelay-scheduler -n 100 --no-pager