Toda rota passa por um before_request que redireciona para /login quem não tem sessão — inclusive os POSTs, então /exportar e /salvar-sd sem cookie não chegam ao handler. Ficam de fora só a própria entrada e /static (a tela de login precisa da folha de estilo). Credencial única em constante (admin@iasis.com.br / admin123), comparada com compare_digest nos dois campos e com a mesma mensagem para usuário errado e senha errada — dizer qual dos dois falhou entrega metade da credencial. Sem banco, sem cadastro e sem recuperação de senha: a app é ferramenta interna de uso individual, e isto existe para o upload e o writeback não ficarem abertos a quem alcançar a porta quando ela roda hospedada. SECRET_KEY não tem default assado na imagem — chave fixa numa imagem vale para toda cópia dela. Sem a variável, o servidor sorteia uma por processo e o login cai a cada restart, que é a mesma vida útil que a sessão da SD já tem; é seguro porque o gunicorn roda com -w 1. APP_USUARIO/APP_SENHA seguem a convenção do Dockerfile: default lá, compose só espelha. O Sair encerra a sessão do navegador sem descartar a SD em memória — é a mesma máquina, o mesmo processo. E o teste_e2e faz login uma vez no test client, senão todo request dele voltaria 302. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
83 lines
4.2 KiB
YAML
83 lines
4.2 KiB
YAML
# Deploy da Exportação da SD — a imagem é a do Dockerfile; este arquivo é UM dos
|
|
# dois jeitos de subi-la no Coolify, e serve também para rodar local.
|
|
#
|
|
# ── MODO 1 · Application (Dockerfile) — o que está em produção (2026-09-04) ──
|
|
#
|
|
# Neste modo o Coolify IGNORA este arquivo por completo: builda o Dockerfile e
|
|
# configura tudo pela interface. O que o volume e as variáveis daqui dizem tem
|
|
# de ser reproduzido lá:
|
|
#
|
|
# Persistent storage → Add mount → "Directory mount"
|
|
# Source Path: /data/coolify/applications/<uuid-da-app>/clientes
|
|
# (o próprio Coolify sugere esse caminho; ele sobrevive
|
|
# a redeploy — é a pasta de storage da app, não o clone)
|
|
# Destination Path: /app/clientes
|
|
# Mesmo esquema para _derivados, só se for usar a linha de comando.
|
|
#
|
|
# Dados: a pasta do host nasce VAZIA. Copie o conteúdo para dentro dela —
|
|
# aponte para "ses-mg", não para "clientes", ou o nível duplica
|
|
# (.../clientes/clientes/ses-mg):
|
|
#
|
|
# scp -r "D:/Exporta-SD/clientes/ses-mg" usuario@vps:/data/coolify/applications/<uuid>/clientes/
|
|
#
|
|
# e confira no host: ls /data/coolify/applications/<uuid>/clientes/ses-mg/contrato/
|
|
# Tem de listar itens.yaml E linhas-os.yaml — sem o segundo, o fallback da
|
|
# linha de OS desliga e `linha_os` vira pendência só no VPS.
|
|
#
|
|
# Variáveis: aba Environment Variables (nomes e defaults em .env.example).
|
|
# Réplicas: 1 (ver o comentário de -w 1 no Dockerfile).
|
|
#
|
|
# ── MODO 2 · Docker Compose (ou local) ──────────────────────────────────────
|
|
#
|
|
# O Coolify lê este arquivo. Ponha os dados num caminho fixo do host, fora da
|
|
# pasta que ele clona, e aponte CLIENTES_DIR para lá (Environment Variables no
|
|
# Coolify, ou um .env ao lado deste arquivo — copie .env.example). Sem a
|
|
# variável, o padrão ./clientes cai dentro do clone, o compose cria o
|
|
# diretório VAZIO sem avisar e o primeiro upload morre com "Arquivo canônico
|
|
# ausente".
|
|
#
|
|
# Nos dois modos não há chown manual: o entrypoint.sh roda como root ao subir
|
|
# o container, ajusta o dono do volume para o usuário `sd` e só então derruba
|
|
# privilégio e inicia o gunicorn. Um scp novo devolve os arquivos ao usuário do
|
|
# ssh; resolve sozinho no próximo restart.
|
|
|
|
services:
|
|
exporta-sd:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
image: exporta-sd:local
|
|
# Sem `ports:` de propósito: no Coolify o Traefik alcança o container pela
|
|
# rede interna. `expose` publica a porta só para dentro dessa rede.
|
|
# Para rodar fora do Coolify, troque por: ports: ["5000:5000"].
|
|
expose:
|
|
- "5000"
|
|
# Só ESPELHA os defaults do Dockerfile — o valor de verdade mora lá (ENV e
|
|
# ${VAR:-default} no CMD), porque no Modo 1 este bloco nem é lido. Aqui a
|
|
# única função é deixar o operador trocar pelo .env sem editar YAML.
|
|
environment:
|
|
PORT: ${PORT:-5000}
|
|
TZ: ${TZ:-America/Sao_Paulo}
|
|
GUNICORN_TIMEOUT: ${GUNICORN_TIMEOUT:-120}
|
|
GUNICORN_LOG_LEVEL: ${GUNICORN_LOG_LEVEL:-info}
|
|
# Credencial única da tela de login (não há banco nem cadastro).
|
|
APP_USUARIO: ${APP_USUARIO:-admin@iasis.com.br}
|
|
APP_SENHA: ${APP_SENHA:-admin123}
|
|
# Vazio de propósito: sem valor, o servidor sorteia a chave por
|
|
# processo e o login cai a cada restart. Defina no .env para durar.
|
|
SECRET_KEY: ${SECRET_KEY:-}
|
|
volumes:
|
|
# OBRIGATÓRIO. O app lê clientes/ses-mg/contrato/itens.yaml a cada
|
|
# análise (e contrato/linhas-os.yaml, se existir, como fallback da linha
|
|
# de OS) e grava SD.md em clientes/ses-mg/projetos/. O caminho no
|
|
# container é fixo: caminhos.py resolve clientes/ a partir da raiz do
|
|
# código, que é o WORKDIR /app.
|
|
- ${CLIENTES_DIR:-./clientes}:/app/clientes
|
|
# Saída da linha de comando (exporta_sd.py --dir _derivados/export).
|
|
# A aplicação web entrega o JSON pelo download, não por aqui.
|
|
- ${DERIVADOS_DIR:-./_derivados}:/app/_derivados
|
|
restart: unless-stopped
|
|
# Uma SD por vez, em memória de processo: reiniciar descarta a sessão
|
|
# aberta (o SD.md de origem no volume fica intacto).
|
|
stop_grace_period: 10s
|