Tenho um script de shell que define variáveis de ambiente e executa um executável. Parece com isso:
export PATH=$PATH:/some/extra/binaries
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/some/extra/lib
cd /path/to/execuatble
DISPLAY=:0.0 nohup ./app &
Quando executo o script de shell como usuário do terminal, ele não encontra problemas. No entanto, também escrevi um arquivo de serviço que executa um script python que, entre outras coisas, inicia este shell. Exemplo de arquivo python:
import subprocess
import sys
import logging
logger = logging.getLogger(__file__)
logger.setLevel(logging.INFO)
try:
result = subprocess.run(["/path/to/shell/script/script.sh"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
logger.info(result)
except Exception as e:
logger.info("Error starting process: " + str(e), e)
O arquivo python está sendo executado dentro de um serviço systemd com um arquivo de serviço:
[Unit]
Description=App runner Service
After=network.target
[Service]
Environment=PYTHONUNBUFFERED=1
ExecStart=/usr/bin/python3 /path/to/python/script/script.py
Restart=always
RestartSec=5
User=root
[Install]
WantedBy=multi-user.target
Quando executo o serviço, vejo o que ele faz com journalctl -f -u servicename
, e posso ver o script de shell falhando quando tento executar o aplicativo com
No protocol specified \n QXcbConnection: Could not connect to display :0.0
Qual é a causa raiz e como posso consertá-la? Estou aberto a alterações do shell script, python ou do arquivo de serviço.
Eu suspeito que tenha a ver com o fato de que o systemd executa isso como root. BTW, eu não tenho ideia de por que eu precisaria de um servidor X já que esse aplicativo não tem nenhuma GUI. Talvez tenha a ver com o fato de que ele usa Qt (nós usamos Qt para um aplicativo não-gui já que queríamos um loop de eventos e utilizar o mecanismo de sinal /slot para passar eventos entre threads e objetos facilmente.
Se isso for relevante, ele roda no Ubuntu 16.04
Você precisa de várias coisas para se conectar ao display.
DISPLAY=...
é provavelmente o principal, masXAUTHORITY
também é bem crítico. Esse é um pouco difícil porque pode ser mais difícil de prever do queDISPLAY
, e é necessário obter permissão para trabalhar naquele display.XDG_RUNTIME_DIR
também pode ser importante.Há duas maneiras principais de lidar com isso:
Use o
Environment=
comando para especificar todas as variáveis de ambiente que você precisa. Eu realmente não gosto dessa opção porque você as está codificando, fazendo suposições como a qual display você está tentando se conectar.Use o
--user
bus. O--user
bus é independente do bus do sistema primário, mas tem várias vantagens aqui:DISPLAY
eXAUTHORITY
Acionar somente após a área de trabalho pode ser feito com
WantedBy=graphical-session.target
. Esta é provavelmente a solução real para um problema que você tentou mascarar com a combinaçãoRestart=always
/ . As pessoas costumam usar , mas isso pode começar antes de um usuário efetuar login, causando falha no serviço. também é popular, mas pode começar assim que a tela de login (exemplo gdm3) for mostrada. O usuário ainda não tem uma área de trabalho e, portanto, o serviço falhará.RestartSec=5
WantedBy=multi-user.target
WantedBy=graphical.target
A última peça desse quebra-cabeça é provavelmente seu
User=root
.User=
éroot
por padrão, então se você apenas colocarroot
como um placeholder, então não há mais problemas. Caso contrário, aUser=
configuração falhará com "permissão negada".Executar ferramentas GUI como
root
geralmente é desencorajado, e nadar contra a corrente tende a causar mais dores de cabeça do que você gostaria. Se você é o autor deste script python, considere permitir que ele seja executado como um usuário sem privilégios. Então usepolkit
oupkexec
para executar tarefas/scripts menores com permissões elevadas. Por padrão,pkexec
pedirá ao usuário para autenticar como um administrador antes de fazer trabalho privilegiado, mas você pode configurar tarefas específicas para sempre serem permitidas (possivelmente por um usuário específico).Para fazer isso funcionar no
--user
barramento, basta mover seu serviço para~/.config/systemd/user/
e executar o seguinte (semsudo
):Atualizar:
Você disse que seu script funciona bem sem permissões de root no terminal. Esse é o melhor cenário possível! Aqui vai uma solução tl;dr:
Mova seu
*.service
para~/.config/systemd/user/
.Altere seu arquivo de serviço para remover
User=
e alterar[Install]
para alvos disponíveis no--user bus
. Eu removiRestart=
porque não deveria mais ser necessário.