Автозапуск и сервисы: чтобы работало всегда

Проект, который надо запускать вручную после каждой перезагрузки, — это не умный дом, а ручной. Настоящая автоматизация работает сама.

Чтобы 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. Дальше — куда расти.

Проверьте себя
1. Зачем оформлять программу умного дома как systemd-сервис?
AЧтобы она работала быстрее
BЧтобы она запускалась автоматически при включении Pi и перезапускалась при падении, не завися от SSH-сессии
CЧтобы уменьшить размер кода
DЭто требование Python
2. Какой командой посмотреть логи systemd-сервиса при отладке?
Als -l
Bjournalctl -u имя_сервиса
Cpwd
Dcd ..