Daemon & Services
Configure the daemon, web service, and postmortem timer.
The installer and deb/rpm packages register the daemon and web systemd units. The repository also includes a user-level postmortem timer for the author's deployment; packages do not install it. On Docker, the entrypoint runs the daemon and web server as background processes instead; see Docker.
Setup wizards enable the daemon for boot (systemctl enable --now) when the unit exists. Fresh-build web install enables only plex2jellyfin-web until the wizard runs; fresh-build CLI install leaves units installed but stopped until plex2jellyfin setup.
systemctl status plex2jellyfin-daemon # daemon
systemctl status plex2jellyfin-web # web UI on :5522
systemctl --user status plex2jellyfin-postmortem.timer # scheduled evidence collection
journalctl -u plex2jellyfin-daemon -fplex2jellyfin-daemon.service
[Unit]
Description=Plex2Jellyfin Media File Watcher
After=network.target
[Service]
Type=simple
User=root
Group=root
ExecStart=/usr/bin/plex2jellyfin-daemon
Restart=on-failure
RestartSec=10
PrivateTmp=true
CapabilityBoundingSet=CAP_CHOWN CAP_FOWNER CAP_DAC_OVERRIDE
AmbientCapabilities=CAP_CHOWN CAP_FOWNER CAP_DAC_OVERRIDE
[Install]
WantedBy=multi-user.targetRuns as root, but with CapabilityBoundingSet/AmbientCapabilities restricted to only CAP_CHOWN, CAP_FOWNER, and CAP_DAC_OVERRIDE — the minimum needed to chown moved files to a different user when [permissions] is configured (see Configuration). NoNewPrivileges is deliberately left disabled because the daemon needs to retain the ability to change file ownership after moving a file.
Watches configured [watch] directories, parses and renames incoming files, runs the periodic convergence scan (scan_frequency in [daemon]), and executes the housekeeping queue. Exposes a Unix-domain control socket used by plex2jellyfin daemon {status|reload|stop} and by plex2jellyfin-web.
plex2jellyfin-web.service
[Unit]
Description=Plex2Jellyfin Web UI Server
Documentation=https://github.com/Nomadcxx/plex2jellyfin
After=network.target plex2jellyfin-daemon.service
Wants=plex2jellyfin-daemon.service
[Service]
Type=simple
User=root
Group=root
ExecStart=/usr/bin/plex2jellyfin-web --host 127.0.0.1 --port 5522
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=plex2jellyfin-web
NoNewPrivileges=true
ProtectSystem=full
PrivateTmp=true
[Install]
WantedBy=multi-user.targetplex2jellyfin-web depends on plex2jellyfin-daemon and talks to it exclusively over the Unix-domain control socket — no TCP between them. It hosts the embedded dashboard and proxies API calls to the daemon.
The web unit listens on loopback. To open it from another machine, create an SSH tunnel with ssh -L 5522:127.0.0.1:5522 user@server and browse to http://127.0.0.1:5522/ on your computer. A direct remote bind requires a configured web password; put a trusted TLS proxy in front of it.
ProtectHome is deliberately left unset: the web server needs read/write access to the user's config directory (config.toml, lock files, media.db live under ~/.config/plex2jellyfin), and web-triggered library maintenance actions (e.g. consolidation) need write access to non-system media mounts under normal filesystem permissions rather than a hardcoded allow-list.
Work in progress
The dashboard trails the CLI and daemon in maturity. Prefer the CLI for anything destructive; treat the web UI as a status/review surface first.
Postmortem timer
systemctl --user status plex2jellyfin-postmortem.timer# plex2jellyfin-postmortem.timer
[Timer]
OnCalendar=daily
Persistent=true
Unit=plex2jellyfin-postmortem.service# plex2jellyfin-postmortem.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/plex2jellyfin postmortem collect --since 96h --if-dueA user unit (not system-wide), installed under systemd/user/. Wakes daily (OnCalendar=daily); Persistent=true catches up after downtime. The service passes --if-due, so collection only proceeds when the latest successful bundle is at least 96 hours old — preserving a rolling 96-hour cadence without collecting after every boot. Manual plex2jellyfin postmortem collect (without --if-due) remains unconditional. Each successful run writes an evidence bundle at ~/.config/plex2jellyfin/reports/latest/.
Review the bundle yourself, or hand agent-prompt.md from the bundle to an LLM for a summarized health check. See plex2jellyfin postmortem collect --help and the CLI Reference for manual invocation.
Managing services
# start/stop/restart the underlying systemd units
sudo systemctl enable --now plex2jellyfin-daemon plex2jellyfin-web
sudo systemctl restart plex2jellyfin-daemon
sudo systemctl stop plex2jellyfin-web plex2jellyfin-daemon
# talk to a running daemon over its control socket
plex2jellyfin daemon status
plex2jellyfin daemon reload # reload config without restarting the process
plex2jellyfin daemon stop # stop via the control socket instead of systemctl
# tail daemon activity without journalctl
plex2jellyfin monitordaemon reload is the preferred way to pick up a config.toml change without dropping in-flight file watches — a full systemctl restart also works but re-establishes the watcher and control socket from scratch.
Docker equivalent
There's no systemd inside the container. The image's entrypoint.sh starts plex2jellyfin-daemon and plex2jellyfin-web as background processes under tini, forwards TERM/INT to both on docker stop, and waits on the web process to determine the container's exit status. See the Docker page for volumes, PUID/PGID, and permission handling in that environment — the postmortem timer isn't run automatically in the container; invoke plex2jellyfin postmortem collect manually or via docker exec from a host-side cron job if you want it there.