Ir para o conteúdo

Rodar como serviço (systemd)

Uma instalação bare-metal roda os três processos como serviços systemd. As units estão em deploy/systemd/: peart.service, peart-portal.service e peart-render.service, mais o peart-render.socket. Instale-as, depois defina os arquivos de ambiente por serviço sob /etc/peart (peart.env, portal.env, render.env).

O peart-render.service roda /opt/peart/render/.venv/bin/python /opt/peart/render/app.py, então o sidecar precisa estar no lugar antes de você habilitá-lo. Os passos estão em Instalação direta.

sudo cp deploy/systemd/*.service deploy/systemd/*.socket /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now peart-render.socket
sudo systemctl enable --now peart.service
sudo systemctl enable --now peart-portal.service

Habilite o socket, e não o peart-render.service. O serviço não tem rede própria (PrivateNetwork=yes, abaixo): o socket abre o 127.0.0.1:8090 fora desse isolamento e entrega o descritor já ouvindo, que é o que torna o sidecar alcançável sem lhe dar rede. O serviço sobe na primeira conexão e não tem seção [Install], então habilitá-lo direto falha, em vez de produzir em silêncio um renderizador ouvindo onde ninguém chama.

O peart.service declara Wants=peart-render.socket, então o socket está ouvindo antes de a superfície interna subir. Cada unit roda como seu próprio usuário (peart, peart-portal, peart-render) e carrega o endurecimento padrão do systemd (NoNewPrivileges, ProtectSystem=strict, PrivateTmp, um filtro de chamadas de sistema @system-service, e por aí).

Os ReadOnlyPaths= e InaccessiblePaths= das units são uma camada, e valem só enquanto o processo roda sob elas. O sistema de arquivos precisa afirmar a mesma fronteira sozinho, que é o que a Primeira execução e configuração prepara.

O que vai em cada arquivo de ambiente

O /etc/peart guarda um arquivo por unit, root:root 0600, criado vazio pela página Primeira execução e configuração. O systemd os lê como PID 1 ao preparar a unit, antes de baixar para o User=, então o usuário do serviço nunca os abre -- e é isso que faz deles o lugar certo para um segredo, e do config.yaml, lido pelas duas superfícies, o lugar errado.

# /etc/peart/peart.env
PEART_RENDER_TOKEN=...      # o mesmo valor que o arquivo do renderizador carrega
PEART_WEBHOOK_SECRET=...    # só se o webhook.url estiver definido no config.yaml

# /etc/peart/render.env
PEART_RENDER_TOKEN=...      # byte a byte igual ao de cima

# /etc/peart/portal.env
# nada ainda; o arquivo existe porque a unit o nomeia

O PEART_RENDER_TOKEN é um segredo só, em dois arquivos, e os dois precisam bater exatamente: o sidecar o confere em toda requisição. Gere uma vez, com openssl rand -hex 32, e cole a mesma string nos dois. O PEART_WEBHOOK_SECRET é da superfície interna sozinha, e só importa quando o bloco de webhook do config.yaml nomeia uma URL. Os dois sobrescrevem o que o config.yaml disser, e variável não definida não muda nada, então quem guarda os valores no arquivo continua funcionando como antes.

O portal.env fica vazio de propósito. O portal não tem segredo próprio, e arquivo sem nada dentro é a forma honesta de dizer isso -- a unit o nomeia com EnvironmentFile= sem traço na frente, então ele precisa existir.

Se o diretório de dados está num volume separado

O After=network.target é tudo o que as units declaram sobre ordem, o que basta enquanto /var/lib/peart vive no sistema de arquivos raiz. Ponha esse diretório num volume próprio, cifrado ou não, e uma unit pode subir antes de o volume montar: o serviço então escreve no ponto de montagem vazio, na raiz, e o dado que você esperava encontrar depois está num disco onde ninguém escreveu. Montar em cima depois esconde esses arquivos em vez de perdê-los, o que é pior, porque a instalação parece vazia e um restore parece necessário.

Declare a dependência em cada unit que toca o diretório, por drop-in, e não editando o arquivo entregue:

sudo systemctl edit peart.service
[Unit]
RequiresMountsFor=/var/lib/peart

Repita para o peart-portal.service (o diretório do banco) e para o peart-render.service (o diretório de trabalho), se eles estiverem no mesmo volume. A diretiva é inofensiva quando o caminho está na raiz, então não há razão para deixá-la de fora depois que o volume vira possibilidade.

O isolamento de rede do renderizador não é opcional

O peart-render.service carrega duas diretivas que são o equivalente bare-metal da rede internal: true do caminho por container:

PrivateNetwork=yes
IPAddressDeny=any

O renderizador processa HTML derivado de conteúdo hostil, e não pode ser capaz de alcançar a internet nem o endpoint de metadados da nuvem. Remover qualquer uma das diretivas reabre em silêncio uma superfície de server-side request forgery. Essa é a terceira camada de defesa do pipeline de renderização, e é um requisito, não um botão de ajuste. O renderizador também roda com MemoryDenyWriteExecute=yes.

O isolamento é também a razão de o sidecar subir pelo peart-render.socket. Um serviço com namespace de rede próprio tem loopback próprio, então um servidor que faz bind em 127.0.0.1 lá dentro não é o 127.0.0.1 que a superfície interna disca. O socket abre a porta fora do namespace e entrega o descritor ouvindo ao serviço, que nunca faz bind.

Se você rodou uma instalação anterior com o contorno que trocava PrivateNetwork=yes por IPAddressAllow=localhost num drop-in, remova-o ao atualizar: veja Atualização.

O que cada unit pode tocar

As units codificam as fronteiras de confiança como permissões de arquivo:

  • peart.service tem ReadWritePaths=/var/lib/peart (banco, evidências, renditions, relatórios, trabalho).
  • peart-portal.service tem ReadWritePaths=/var/lib/peart/db apenas. Ele precisa de escrita no diretório do banco puramente porque o SQLite em modo WAL escreve arquivos companheiros para leitores; a imposição de somente leitura é a string de conexão mode=ro, não o sistema de arquivos. Ele nunca recebe o diretório de evidências nem a chave.
  • peart-render.service trabalha a partir de /var/lib/peart/work e lê o token do seu ambiente.

A chave mestra é somente leitura e mora fora de /var/lib/peart, então um backup do diretório de dados nunca pode contê-la.