# the venv
/opt/etl/.venv/bin/python -c 'import sys, httpx; print(sys.prefix)' # the venv is chosen by the EXECUTABLE
cat /opt/etl/.venv/pyvenv.cfg # what makes that symlink "the venv's python"
/opt/etl/.venv/bin/pip install -r requirements.txt # dependencies go here, never into /usr
# activate = PATH + VIRTUAL_ENV + prompt. Not needed in units, cron or containers.
# a unit for a Python job
# [Service]
# Type=oneshot
# User=etl
# WorkingDirectory=/opt/etl (default for system units is /)
# ExecStart=/opt/etl/.venv/bin/python /opt/etl/report.py
# StateDirectory=etl (creates /var/lib/etl owned by etl)
# LoadCredential=token:/etc/etl/token (→ $CREDENTIALS_DIRECTORY/token)
# NoNewPrivileges=yes ProtectSystem=strict ReadWritePaths=/var/lib/etl PrivateTmp=yes
systemd-analyze security etl-report.service
# the timer
# [Timer] OnBootSec=5s OnUnitActiveSec=5min (monotonic: after boot, then after each run)
# OnCalendar=*:0/5 Persistent=true (wall clock; RandomizedDelaySec= on fleets)
# [Install] WantedBy=timers.target
systemctl enable --now etl-report.timer # the TIMER; the service is static
systemctl list-timers --all etl-report.timer # NEXT / LAST (a never-started timer is not listed)
# run it the way systemd runs it
systemctl start etl-report.service # now, with the unit's real environment
systemd-run --wait --pipe -p User=etl -p WorkingDirectory=/opt/etl CMD # try a config first
sudo journalctl -u etl-report.service -b -n 20 --no-pager
systemctl show -p Result -p ExecMainStatus -p User etl-report.service
# ownership
chown -R etl:etl /var/lib/etl # what it writes
chown etl:etl /etc/etl/token ; chmod 600 /etc/etl/token # what only it reads
# code in /opt/etl: readable, NOT writable by the service account
# proving "unattended"
stat -c '%y' /var/lib/etl/reports/latest.json ; uptime -s # report newer than the boot