Iniciando e interrompendo serviços do systemd com OnCalendar por um período de tempo, todos os dias

Iniciando e interrompendo serviços do systemd com OnCalendar por um período de tempo, todos os dias

Tenho dois serviços (um reprodutor de vídeo e um daemon de reconhecimento de imagem) que quero iniciar às 9h e terminar às 23h todos os dias (bem como na inicialização, embora o sistema funcione continuamente). Para cada serviço criei um -startup.servicee um arquivo -shutdown.service. Os serviços são combinados em a daemon-on.targete a daemon-off.targetque são então acionados por daemon-on.timere daemon-off.timerrespectivamente.

Após algumas tentativas e testes, descobri que os temporizadores inicialmente acionam os alvos conforme esperado, mas entram no estado com o horário NEXT definido como n/a.

É muito trabalhoso apenas colocar dois aplicativos em execução com um cronômetro. Tenho certeza de que está faltando algo óbvio e agradeceria qualquer dica!

mpv-startup.service inicia o serviço de player de vídeo:

[Unit]
Description=MPV Video Player Startup
After=xorg.target
Requires=xorg.target

[Service]
Environment=DISPLAY=:0
ExecStart=/usr/bin/python3 /opt/videoplayer/app.py
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=10

[Install]
Also=daemon-on.timer

recog-startup.service inicia o serviço de reconhecimento de imagem:

[Unit]
Description=Recog Startup Service

[Service]
Type=simple
WorkingDirectory=/opt/recog
ExecStart=/opt/recog/recog run
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=30

[Install]
Also=daemon-on.timer

mpv-shutdown.service interrompe o serviço do player de vídeo como um comando oneshot

[Unit]
Description=MPV Video Player Shutdown

[Service]
Type=oneshot
ExecStart=/bin/systemctl --user stop mpv-startup.service

[Install]
Also=daemon-off.timer

recog-shutdown.service interrompe o serviço de reconhecimento de imagem como um comando oneshot:

[Unit]
Description=Recog Shutdown Service

[Service]
Type=oneshot
ExecStart=/bin/systemctl --user stop recog-startup.service

[Install]
Also=daemon-off.timer

daemon-on.target combina dois serviços de inicialização acima e também é habilitado na inicialização com default.target:

[Unit]
Description=Daemon Startup Target
Wants=recog-startup.service mpv-startup.service
After=recog-startup.service mpv-startup.service

[Service]
Type=oneshot

[Install]
WantedBy=default.target
Also=daemon-on.timer

daemon-off.target combina dois serviços de desligamento acima:

[Unit]
Description=Daemon Shutdown Target
Wants=recog-shutdown.service mpv-shutdown.service
After=recog-shutdown.service mpv-shutdown.service

[Service]
Type=oneshot

[Install]
Also=daemon-off.timer

daemon-on.timer está configurado para disparar para acionar daemon-on.target às 9h:

[Unit]
Description=Daemon Startup Schedule

[Timer]
OnCalendar=9:00
Unit=recog-on.target
Persistent=true

[Install]
WantedBy=timers.target

daemon-off.timer está configurado para acionar daemon-off.target às 23h:

[Unit]
Description=Daemon Shutdown Schedule

[Timer]
OnCalendar=23:00
Unit=daemon-off.target
Persistent=true

[Install]
WantedBy=timers.target

Depois de colocar tudo isso em ~/.config/systemd/user/eu os habilito da seguinte maneira:

systemctl --user enable --now daemon-on.target
systemctl --user enable --now daemon-on.timer
systemctl --user enable --now daemon-off.timer

O que iniciará automaticamente o daemon a cada inicialização e aqui espero que os temporizadores iniciem/parem o daemon de acordo com a OnCalendar=opção. Neste ponto, systemctl --user list-timersme mostra que meus dois cronômetros serão acionados nos momentos certos (observe que eu configurei os cronômetros com apenas 3 minutos de intervalo para economizar tempo de espera):

NEXT                         LEFT         LAST                         PASSED    UNIT            ACTIVATES
Wed 2019-06-26 12:12:00 JST  1min 4s left Wed 2019-06-26 11:35:02 JST  35min ago daemon-off.timer daemon-off.target
Wed 2019-06-26 12:15:00 JST  4min 4s left Wed 2019-06-26 11:40:48 JST  30min ago daemon-on.timer  daemon-on.target

O cronômetro de desligamento é então acionado e meu daemon para, após alguns minutos o cronômetro de ativação é acionado e o daemon inicia conforme previsto. No entanto, olhando novamente para os temporizadores, vejo que eles foram redefinidos e os campos NEXT/LEFT estão definidos como n/a e nunca mais acionarão o daemon. Qual é o problema aqui?

NEXT LEFT LAST                         PASSED       UNIT            ACTIVATES
n/a  n/a  Wed 2019-06-26 12:12:06 JST  4min 18s ago daemon-off.timer daemon-off.target
n/a  n/a  Wed 2019-06-26 12:15:33 JST  50s ago      daemon-on.timer  daemon-on.target

Responder1

Depois de pesquisar um pouco mais e ler as páginas de manual do systemd consegui simplificar um pouco as coisas e também obter o comportamento desejado dos meus serviços.

Em primeiro lugar, cometi erros ao colocar seções [Serviço] em meus .targetarquivos, o que não fazia sentido. Em segundo lugar, descobri uma PartOf=declaração que me permitiu fazer uma aplicação de nível superior com reprodutor de vídeo e serviços de reconhecimento como componentes. E por último, usando Conflicts=a opção eu poderia iniciar/parar meu aplicativo usando dois alvos que entram em conflito entre si. Os .timers anexados a esses alvos alternarão entre si, desabilitando ou habilitando minha cadeia de aplicativos e respectivos arquivos .targets. O resultado ainda está contido em 7 arquivos, o que é mais do que eu gostaria de manter, mas funciona conforme necessário.

app.service é o aplicativo de nível superior, embora seja um manequim, ele pode executar outra coisa:

[Unit]
Description=App Service

[Service]
Type=oneshot
ExecStart=/bin/true
RemainAfterExit=yes

[Install]
WantedBy=default.target
Also=app-on.timer app-off.timer

app-mpv.service é o componente reprodutor de vídeo do aplicativo e, portanto, também depende do xorg:

[Unit]
Description=App Video Player Service
PartOf=app.service
After=app.service
Requires=xorg.target
After=xorg.target

[Service]
Environment=DISPLAY=:0
ExecStart=/usr/bin/python3 /opt/videoplayer/app.py
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=10

[Install]
WantedBy=app.service

app-recog.service é o componente de reconhecimento de imagem do aplicativo e não depende do player de vídeo:

[Unit]
Description=App Recognition Service
PartOf=app.service
After=app.service

[Service]
WorkingDirectory=/opt/recog
ExecStart=/opt/recog/recog run
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=30

[Install]
WantedBy=app.service

app-on.target é um alvo virtual, ele se torna ativo assim que o app-on.timer é acionado e, por sua vez, habilita o app.service:

[Unit]
Description=App Startup Target
Conflicts=app-off.target
Wants=app.service
After=app.service
RefuseManualStart=yes

[Install]
Also=app-on.timer

app-off.target é um alvo virtual, ele se torna ativo assim que o app-off.timer é acionado e, por sua vez, desativa o app.service:

[Unit]
Description=App Shutdown Target
Conflicts=app.service app-on.target
RefuseManualStart=yes

[Install]
Also=app-off.timer

app-on.timer simplesmente aciona app-on.target:

[Unit]
Description=App Startup Schedule

[Timer]
OnCalendar=9:00
Unit=app-on.target
Persistent=true

[Install]
WantedBy=timers.target

app-off.timer simplesmente aciona app-off.target:

[Unit]
Description=App Shutdown Schedule

[Timer]
OnCalendar=23:00
Unit=app-off.target
Persistent=true

[Install]
WantedBy=timers.target

Possibilitando toda essa bagunça (com a ajuda deEste artigo):

systemctl --user enable app app-mpv app-recog
systemctl --user enable --now app-on.timer app-off.timer
systemctl --user start app

A aplicação começa a rodar e se eu verificar os timers, ambos estão ativos e o que ocorrer primeiro será executado primeiro:

NEXT                         LEFT          LAST PASSED UNIT            ACTIVATES
Wed 2019-06-26 14:00:00 JST  1min 25s left n/a  n/a    app-off.timer app-off.target
Wed 2019-06-26 14:01:00 JST  2min 25s left n/a  n/a    app-on.timer  app-on.target

Depois que o cronômetro desligado é acionado, ele interrompe o aplicativo e o app-off.targetalvo permanece agendado:

NEXT                         LEFT     LAST                         PASSED  UNIT            ACTIVATES
Wed 2019-06-26 14:01:00 JST  55s left n/a                          n/a     app-on.timer  app-on.target
n/a                          n/a      Wed 2019-06-26 14:00:04 JST  3ms ago app-off.timer app-off.target

Quando o temporizador ligado é acionado, ele inicia o aplicativo e desativa o app-on.target, mas reativa o temporizador desligado novamente:

NEXT                         LEFT     LAST                         PASSED  UNIT            ACTIVATES
Thu 2019-06-27 14:00:00 JST  23h left Wed 2019-06-26 14:00:04 JST  57s ago app-off.timer app-off.target
n/a                          n/a      Wed 2019-06-26 14:01:01 JST  5ms ago app-on.timer  app-on.target

E o ciclo continua. Ainda duvido que esta seja a melhor forma de configurar isso e adoraria ouvir outras sugestões!

informação relacionada