Автозапуск и сервисы: чтобы работало всегда
Проект, который надо запускать вручную после каждой перезагрузки, — это не умный дом, а ручной. Настоящая автоматизация работает сама.
Чтобы Pi стал по-настоящему автономным, программа должна стартовать сама при включении и перезапускаться, если упала. За это отвечает systemd.
Пока ты запускаешь скрипт из терминала, он живёт, только пока открыта сессия SSH. Закрыл ноутбук — программа умерла. Чтобы умный дом работал круглосуточно, его оформляют как сервис (службу) с помощью systemd — менеджера служб в Linux.
Подумай о холодильнике. Ты не запускаешь его каждое утро кнопкой и не следишь, не выключился ли он ночью — он просто работает всегда, сам по себе, и включается снова после отключения света. Вот к такому уровню надёжности мы и стремимся для умного дома. Программа, которую надо стартовать руками после каждого сбоя питания, бесполезна: ты уедешь на неделю, дома моргнёт свет — и весь твой проект встанет. Сервис решает эту проблему: система берёт на себя заботу о запуске и поддержании программы в живом состоянии.
Чем сервис отличается от запуска из терминала? Когда ты пишешь команду в терминале, программа становится "ребёнком" твоей сессии: закрылась сессия — закрылся и ребёнок. Сервис же подчиняется напрямую системе, которая живёт всё время, пока включён Pi. Поэтому ему не страшно ни закрытие SSH, ни выход из аккаунта — он держится за самого "родителя" всех процессов.
Как работает под капотом
systemd — это "дирижёр", который запускает программы при старте системы, следит за ними и перезапускает упавшие. Ты описываешь свой сервис в небольшом файле .service, и дальше система сама им управляет.
Включение Pi
|
[ systemd стартует ]
|--> smarthome.service (твоя программа)
| перезапускает при падении
|--> ssh.service
|--> сеть, время и т.д.
Каждый сервис у systemd проходит через понятные состояния. Сразу после команды запуска он переходит в activating, потом, если всё хорошо, в active (running). Если программа завершилась с ошибкой, он попадает в failed — и тут вступает правило перезапуска. Вот этот жизненный путь:
inactive -> activating -> active (running)
|
процесс упал
|
failed -> (Restart=on-failure) -> activating
Файл сервиса задаёт не только команду запуска, но и правила поведения: под каким пользователем работать, что делать при падении (Restart=on-failure), сколько ждать между перезапусками. systemd ведёт собственный журнал — каждое сообщение программы он складывает в общий лог, который потом можно читать через journalctl. Это очень удобно: тебе не нужно самому придумывать, куда писать ошибки.
Файл сервиса и команды управления — пример для Pi:
# файл /etc/systemd/system/smarthome.service описывает запуск
sudo systemctl enable smarthome # включить автозапуск
sudo systemctl start smarthome # запустить сейчас
sudo systemctl status smarthome # проверить состояние
sudo systemctl restart smarthome # перезапустить после правок
journalctl -u smarthome -f # смотреть логи вживую
А логику супервизора — "если процесс упал, перезапусти, но не бесконечно" — отладим на чистом Python. Это упрощённая модель того, что делает systemd. Попробуй сам ▶
# Мини-супервизор: перезапускаем упавший сервис с ограничением попыток
max_restarts = 3
restarts = 0
# имитируем жизнь процесса: True=работает, False=упал
life = [True, True, False, False, False, False]
for tick, alive in enumerate(life):
if alive:
print(f"тик {tick}: сервис работает")
else:
if restarts < max_restarts:
restarts += 1
print(f"тик {tick}: УПАЛ -> перезапуск #{restarts}")
else:
print(f"тик {tick}: упал, лимит перезапусков исчерпан -> стоп")
break
print(f"Всего перезапусков: {restarts}")
Зачем вообще ограничивать число перезапусков? Представь, что программа падает сразу при старте из-за ошибки в коде. Без лимита система будет запускать её снова и снова тысячи раз в секунду, зря грея процессор и забивая логи. Лимит — это предохранитель: если за короткое время случилось слишком много падений, systemd сдаётся и оставляет сервис в состоянии failed, чтобы ты пришёл и разобрался в причине.
Настоящие супервизоры идут ещё дальше и добавляют паузу между попытками, которая растёт с каждым падением — сначала ждём секунду, потом две, потом четыре. Это называется экспоненциальной задержкой и не даёт системе захлебнуться. Смоделируем такую паузу. Попробуй сам ▶
# Растущая пауза между перезапусками
delay = 1
for attempt in range(1, 6):
print(f"попытка #{attempt}: ждём {delay} сек перед перезапуском")
delay = delay * 2
print("Дальше — сдаёмся и зовём человека")
Частые ошибки
- Скрипт работает только при SSH. Без сервиса программа умирает с закрытием сессии — оформляй через systemd.
- Сервис падает в цикле. Если программа сразу крашится, systemd будет бесконечно её перезапускать — смотри логи через
journalctl. - Относительные пути. systemd запускает сервис из другого окружения — пиши абсолютные пути.
- Забыл
enable. Командаstartзапускает сервис сейчас, но безenableон не поднимется после перезагрузки. - Правил файл, но не перезагрузил конфиг. После изменения
.serviceнуженsystemctl daemon-reload, иначе systemd видит старую версию.
Best practices
- Оформляй боевые проекты как systemd-сервис с
Restart=on-failure. - Всегда проверяй логи через
journalctl -u имяпри отладке. - Тестируй скрипт вручную до того, как ставить на автозапуск.
- Не забывай
enable, иначе после выключения света сервис не вернётся. - После правок файла сервиса делай
daemon-reloadи только потомrestart.
Итоги. systemd запускает программы при старте Pi, проводит их через состояния от activating до active, следит за ними и перезапускает упавшие, ведя общий журнал. Свой проект оформляют файлом сервиса с правилами перезапуска. Логику супервизора с лимитом и растущей паузой легко смоделировать на Python. Дальше — куда расти.