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.servicetemReadWritePaths=/var/lib/peart(banco, evidências, renditions, relatórios, trabalho).peart-portal.servicetemReadWritePaths=/var/lib/peart/dbapenas. 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ãomode=ro, não o sistema de arquivos. Ele nunca recebe o diretório de evidências nem a chave.peart-render.servicetrabalha a partir de/var/lib/peart/worke 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.