Модул 6 · 6.3
Собствени услуги и таймери
Модул 6: Услуги и логове · Време за четене: 11 минути
Цел на урока: След този урок ще можете да стартирате собствена програма като systemd услуга под отделен потребител, с няколко защитни опции, и да планирате задачи със systemd timer или cron.
Основни понятия: unit файл, [Unit], [Service], [Install], ExecStart, User=, WorkingDirectory, EnvironmentFile, Restart=, WantedBy, NoNewPrivileges, ProtectSystem, ProtectHome, PrivateTmp, systemd-analyze security, timer, OnCalendar, Persistent, crontab
Защо не просто "nohup" или "screen"?
Който качи собствена програма на сървър (малко уеб приложение, бот, скрипт, който всяка нощ върши нещо), често я стартира първия път ръчно: node server.js & или в сесия на screen. Това работи, докато не излезете от системата, сървърът не се рестартира или програмата не се срине. Тогава тя изчезва и никой не забелязва.
Програма, която трябва да работи постоянно, трябва да бъде systemd услуга. Тогава:
- тя стартира автоматично при зареждане на системата;
- systemd я стартира отново, ако се срине;
- изходът ѝ отива в journal (урок 6.2);
- тя работи под потребител, когото Вие изберете, с правата, които Вие разрешите.
Собствен unit файл
Да предположим, че имате малко Node.js приложение в /opt/factuurapp, което слуша на 127.0.0.1:3000. По-късно nginx или Caddy ще го направят достъпно навън (модул 7).
Стъпка 1: отделен потребител без shell (урок 3.2). Приложението не е нужно да бъде root и не е нужно да влиза в системата:
anna@web1:~$ sudo adduser --system --group --home /opt/factuurapp --no-create-home factuurapp
anna@web1:~$ sudo chown -R factuurapp:factuurapp /opt/factuurapp/dataСамият код може да остане собственост на root (приложението не е нужно да променя себе си); само папката, в която то пише, принадлежи на потребителя на услугата.
Стъпка 2: unit файлът. Собствените units се поставят в /etc/systemd/system/:
# /etc/systemd/system/factuurapp.service
[Unit]
Description=Factuurapp (Node.js)
After=network-online.target
Wants=network-online.target
[Service]
User=factuurapp
Group=factuurapp
WorkingDirectory=/opt/factuurapp
EnvironmentFile=/etc/factuurapp/env
ExecStart=/usr/bin/node /opt/factuurapp/server.js
Restart=on-failure
RestartSec=5s
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/opt/factuurapp/data
[Install]
WantedBy=multi-user.targetТрите блока:
- [Unit]: описание и ред на стартиране.
After=network-online.targetзаедно сWants=гарантира, че услугата стартира едва когато мрежата е налична. - [Service]: как работи програмата.
ExecStartс пълния път (systemd не използва път за търсене като Вашия shell).User=иGroup=определят под кой потребител.EnvironmentFileчете настройки, например парола за база данни, от отделен файл, така че те да не стоят в unit файла. - [Install]: какво прави
enable.WantedBy=multi-user.targetозначава: стартирай при нормално зареждане на системата.
Стъпка 3: тайните отделно, със строги права.
anna@web1:~$ sudo mkdir /etc/factuurapp
anna@web1:~$ sudo install -m 600 -o root -g root /dev/null /etc/factuurapp/env
anna@web1:~$ sudoedit /etc/factuurapp/envСъдържанието може да бъде например DATABASE_URL=postgres://factuur:...@127.0.0.1/factuur. systemd чете файла като root, преди да превключи към потребителя на услугата, затова 600 със собственик root е достатъчно.
Стъпка 4: зареждане, стартиране, проверка.
anna@web1:~$ sudo systemctl daemon-reload
anna@web1:~$ sudo systemctl enable --now factuurapp
anna@web1:~$ systemctl status factuurapp
anna@web1:~$ journalctl -u factuurapp -fЗащитни опции: малко усилие, голяма разлика
Четирите реда под RestartSec в примера ограничават какво може да прави услугата, дори ако някой открие уязвимост във Вашето приложение:
| Опция | Ефект |
|---|---|
NoNewPrivileges=true | процесът и неговите дъщерни процеси никога не могат да получат повече права (нито чрез sudo, нито чрез setuid програми) |
ProtectSystem=strict | цялата файлова система е само за четене за услугата, освен това, което разрешите с ReadWritePaths= |
ProtectHome=true | /home, /root и /run/user са невидими |
PrivateTmp=true | собствена, празна /tmp, която другите програми не виждат |
systemd може да оцени Вашия unit:
anna@web1:~$ systemd-analyze security factuurapp
...
→ Overall exposure level for factuurapp.service: 4.9 OK 🙂Колкото по-ниско е числото, толкова по-добре. Услуга без опции получава около 9 ("UNSAFE"). Не е нужно да стигате до нула: всяка добавена опция може да счупи нещо, от което приложението се нуждае. Добавяйте ги една по една и тествайте след всяка стъпка. Ако нещо спре да работи, причината обикновено е в journal ("Read-only file system", "Permission denied").
Планиране на задачи: таймери
За нещо, което трябва да се случва в определено време (резервно копие, скрипт за почистване, отчет), Ви е нужен timer. Един systemd timer се състои от два units с едно и също име: .service, който казва какво трябва да се направи, и .timer, който казва кога.
# /etc/systemd/system/opruimen.service
[Unit]
Description=Oude exportbestanden opruimen
[Service]
Type=oneshot
User=factuurapp
ExecStart=/usr/bin/find /opt/factuurapp/data/export -type f -mtime +30 -delete# /etc/systemd/system/opruimen.timer
[Unit]
Description=Dagelijks oude exportbestanden opruimen
[Timer]
OnCalendar=*-*-* 03:15:00
RandomizedDelaySec=10m
Persistent=true
[Install]
WantedBy=timers.targetType=oneshot означава: програмата се изпълнява и след това приключва. OnCalendar е графикът: всеки ден в 03:15. Можете да напишете и daily, weekly или Mon *-*-* 02:00; systemd-analyze calendar "Mon *-*-* 02:00" показва кога ще бъде следващото изпълнение. Persistent=true е важно: ако сървърът е бил изключен в 03:15, задачата се изпълнява веднага след стартирането. RandomizedDelaySec разпределя малко момента на стартиране.
Включвате timer, а не service:
anna@web1:~$ sudo systemctl daemon-reload
anna@web1:~$ sudo systemctl enable --now opruimen.timer
anna@web1:~$ systemctl list-timers opruimen
NEXT LEFT LAST PASSED UNIT ACTIVATES
Tue 2026-11-10 03:19:42 CET 15h left - - opruimen.timer opruimen.service
anna@web1:~$ sudo systemctl start opruimen.service # сега еднократен ръчен тест
anna@web1:~$ journalctl -u opruimen.serviceА cron?
cron е по-старият и по-прост инструмент и все още работи добре. Ред в crontab на даден потребител (crontab -e):
15 3 * * * /usr/local/bin/opruimen.shПетте полета са минута, час, ден от месеца, месец и ден от седмицата. Предимства и недостатъци един до друг:
| cron | systemd timer | |
|---|---|---|
| Настройване | един ред | два файла |
| Пропуснато изпълнение (сървърът е изключен) | пропуска се | с Persistent=true се изпълнява по-късно |
| Изход и грешки | по имейл, ако сървърът може да изпраща поща; иначе се губят | в journal |
| Защитни опции, потребител | чрез самия скрипт | всичко от systemd (вижте по-горе) |
| Кога е било изпълнено | трудно се вижда | systemctl list-timers и journal |
За сървър, който администрирате сериозно, таймерите са по-добрият избор, особено за резервни копия (урок 8.2): искате да сте сигурни, че са се изпълнили и какво са съобщили. Ред в cron, който тихо се проваля, забелязвате едва когато Ви потрябва резервното копие.
При AlmaLinux и Rocky Linux
Units и таймерите работят по абсолютно същия начин. Една разлика: SELinux може да спре собствена услуга, която чете или пише файлове на място, което SELinux не очаква, или която слуша на необичаен порт. Съобщението се намира в /var/log/audit/audit.log. Не изключвайте SELinux, за да решите проблема; потърсете правилния етикет или правило (например с semanage).
Обобщение
- Програма, която трябва да работи постоянно, трябва да бъде systemd услуга: автоматично стартиране, рестартиране при срив, логове в journal.
- Собствените units са в
/etc/systemd/system/, сUser=отделен потребител без shell, тайните вEnvironmentFileс права 600. NoNewPrivileges,ProtectSystem=strictсReadWritePaths,ProtectHomeиPrivateTmpограничават щетите при уязвимост;systemd-analyze securityоценява unit файла.- Таймерът е
.serviceплюс.timerсOnCalendar;Persistent=trueнаваксва пропуснатите изпълнения. Включете таймера, а не service. - cron все още работи, но таймерите дават по-добри логове и не пропускат изпълнения.
Тест (quiz)
Задача
Задача O6.3: Собствена услуга с таймер. Създайте на сървъра си малка собствена услуга с таймер, например скрипт, който всяка нощ записва свободното дисково пространство и systemctl --failed във файл или в journal. Използвайте отделен потребител, Type=oneshot, поне две защитни опции и Persistent=true. Тествайте с systemctl start, прегледайте изхода в journal и запишете оценката от systemd-analyze security. Поставете своите .service и .timer и отговорете на въпросите в контролния списък.
Задача O6.3: Собствена услуга с таймер
Тази задача изпълнявате сами; никой не я проверява. Отбележете какво сте направили и маркирайте задачата като изпълнена.
С Persistent=true задачата се изпълнява след стартиране, ако е била пропусната. Оценка под 6 е добра за първа услуга; права за запис само чрез ReadWritePaths.
Вашите отметки и бележки остават само в този браузър; не ги изпращаме никъде. Ако сте влезли, в Моите курсове запазваме само че сте изпълнили задачата.
Още за четене
- Ръководство за DirectAdmin 15.1: Cron Jobs: автоматично изпълнение на задачи
- Ръководство
systemd.exec(5)(User=, ProtectSystem=, NoNewPrivileges=): https://manpages.debian.org/trixie/systemd/systemd.exec.5.en.html - Ръководство
systemd.timer(5)(OnCalendar=, Persistent=): https://manpages.debian.org/trixie/systemd/systemd.timer.5.en.html - Ръководство
systemd.time(7)(графици): https://manpages.debian.org/trixie/systemd/systemd.time.7.en.html
