Что было. Нужно было запускать серверное приложение при загрузке виртуальной машины. Написали юнит с Type=simple и ExecStart, указывающий на shell-скрипт. Скрипт запускал приложение в фоне и завершался. Systemd видел, что основной процесс завершился, считал сервис упавшим и останавливал всё, что тот успел породить.
Почему. При Type=simple systemd считает «главным» именно процесс из ExecStart. Если он уходит в фон и завершается, сервис для systemd закончился. Демонизация внутри скрипта и systemd работают друг против друга.
Что помогло автору. Type=oneshot вместе с RemainAfterExit=yes: скрипт выполняется один раз, а сервис остаётся в состоянии «активен» после его завершения. Дальше стандартные шаги: файл в /etc/systemd/system/, права на скрипт 755, systemctl daemon-reload, systemctl enable --now, проверка systemctl status.
Более правильный путь для постоянно работающих программ. Не демонизировать программу самостоятельно, а отдать её systemd на переднем плане: Type=simple (или exec) с ExecStart, указывающим прямо на исполняемый файл, плюс Restart=on-failure. Тогда systemd сам следит за процессом, перезапускает его и собирает журнал через journalctl -u имя. oneshot лучше оставить для разовых действий при загрузке.
Типовые ошибки в юнитах, из-за которых «вручную работает, при загрузке нет».
- Зависимость от сети:
After=network-online.targetиWants=network-online.target, а не простоnetwork.target, если программе нужен реальный доступ в сеть. - Пустое окружение: systemd не читает
~/.bashrc; переменные задаются черезEnvironment=илиEnvironmentFile=, пути пишутся полностью. - Рабочий каталог:
WorkingDirectory=, иначе относительные пути в программе ведут в/. - Пользователь:
User=, чтобы приложение не работало от root. - Читать
journalctl -u имя -b: причина почти всегда написана там.
Параметры юнитов описаны в документации systemd.