Systemd Timer

Systemd Timer #

Quad-Ops ships systemd unit files in build/package/ that run it as a oneshot service triggered by a timer. On each timer tick the service runs quad-ops sync, which pulls the latest Git configuration, generates Quadlet units, pre-pulls container images, and starts services.

Unit Files #

quad-ops.service — System-Wide Service #

[Unit]
Description=Quad-Ops Container Manager
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/quad-ops sync

Runs as root. The oneshot type means systemd waits for the command to finish. The service requires network connectivity via the network-online.target dependency.

quad-ops.timer — System-Wide Timer #

[Unit]
Description=Quad-Ops Sync Timer

[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
Persistent=true

[Install]
WantedBy=timers.target
DirectiveDescription
OnBootSec=1minFirst run 1 minute after boot
OnUnitActiveSec=5minSubsequent runs every 5 minutes
Persistent=trueIf a scheduled run was missed (e.g. system was off), run immediately on next boot

quad-ops@.service — Template Service (Per-User) #

[Unit]
Description=Quad-Ops Container Manager for %i
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
User=%i
ExecStart=/usr/local/bin/quad-ops sync

A systemd template unit — the %i specifier is replaced by the instance name (the part after @). When enabled as quad-ops@alice.service, the service runs as user alice and manages that user’s rootless containers.

quad-ops@.timer — Template Timer (Per-User) #

[Unit]
Description=Quad-Ops Sync Timer for %i

[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
Persistent=true

[Install]
WantedBy=timers.target

Same schedule as the system-wide timer but associated with a specific user’s service instance.

Installation #

System-Wide #

sudo curl -L -o /etc/systemd/system/quad-ops.service \
  https://raw.githubusercontent.com/trly/quad-ops/main/build/package/quad-ops.service
sudo curl -L -o /etc/systemd/system/quad-ops.timer \
  https://raw.githubusercontent.com/trly/quad-ops/main/build/package/quad-ops.timer

sudo systemctl daemon-reload
sudo systemctl enable --now quad-ops.timer

Per-User (Template) #

sudo curl -L -o /etc/systemd/system/quad-ops@.service \
  https://raw.githubusercontent.com/trly/quad-ops/main/build/package/quad-ops@.service
sudo curl -L -o /etc/systemd/system/quad-ops@.timer \
  https://raw.githubusercontent.com/trly/quad-ops/main/build/package/quad-ops@.timer

sudo systemctl daemon-reload
sudo systemctl enable --now quad-ops@username.timer

Replace username with the target user account.

Managing the Timer #

# Check timer status
sudo systemctl status quad-ops.timer

# List all active timers
systemctl list-timers --all | grep quad-ops

# Trigger an immediate run without waiting for the timer
sudo systemctl start quad-ops.service

# View service logs
sudo journalctl -u quad-ops -f

# For a template instance
sudo systemctl status quad-ops@username.timer
sudo systemctl start quad-ops@username.service
sudo journalctl -u quad-ops@username -f

Customization #

Use systemd drop-in overrides to change behavior without editing the shipped unit files.

Custom Config Path #

sudo systemctl edit quad-ops

# Add to the override file:
[Service]
ExecStart=
ExecStart=/usr/local/bin/quad-ops sync --config /path/to/config.yaml
When overriding `ExecStart` in a `oneshot` service, you must first clear it with an empty `ExecStart=` line, then provide the new command.

Verbose Logging #

sudo systemctl edit quad-ops

[Service]
ExecStart=
ExecStart=/usr/local/bin/quad-ops sync --verbose

Custom Timer Interval #

sudo systemctl edit quad-ops.timer

[Timer]
OnBootSec=
OnBootSec=2min
OnUnitActiveSec=
OnUnitActiveSec=10min

After any override change, reload and restart:

sudo systemctl daemon-reload
sudo systemctl restart quad-ops.timer