From 4ef1a7a4c80326d4a5ec71030dc5675ad784550d Mon Sep 17 00:00:00 2001 From: WanderMotta Date: Fri, 4 Sep 2026 14:32:01 -0300 Subject: [PATCH] =?UTF-8?q?Corrige=20permiss=C3=A3o=20do=20volume=20de=20c?= =?UTF-8?q?lientes/=20via=20entrypoint,=20n=C3=A3o=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RUN chown no Dockerfile só atinge a imagem: o bind mount de clientes/ substitui o diretório (dono incluído) ao subir o container, apagando qualquer chown feito em build. entrypoint.sh roda como root no início do container, ajusta o dono do volume recém-montado e derruba privilégio para o usuário sd (via gosu) antes do gunicorn. .gitattributes fixa LF em *.sh: autocrlf do Git no Windows senão reescreveria o script para CRLF, quebrando o shebang dentro do container. Co-Authored-By: Claude Sonnet 5 --- .gitattributes | 5 +++++ Dockerfile | 22 ++++++++++++++++++---- docker-compose.yml | 8 +++++--- entrypoint.sh | 13 +++++++++++++ 4 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 .gitattributes create mode 100644 entrypoint.sh diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..353dfe6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# entrypoint.sh roda dentro do container Linux (gosu/sh) — CRLF quebra a +# execução ("no such file or directory", shebang ilegível). Sem esta regra, o +# autocrlf=true do Git no Windows reescreve o arquivo na próxima operação que +# o toque e o build local passa a falhar mesmo com a imagem correta. +*.sh text eol=lf diff --git a/Dockerfile b/Dockerfile index 9bb3242..afa9c04 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,6 +23,12 @@ ENV PYTHONUNBUFFERED=1 \ WORKDIR /app +# gosu: o entrypoint precisa iniciar como root (só root faz chown no volume +# recém-montado) e depois derrubar privilégio para `sd` antes do gunicorn. +RUN apt-get update \ + && apt-get install -y --no-install-recommends gosu \ + && rm -rf /var/lib/apt/lists/* + # Dependências em camada própria: mudar código não reinstala pacote. # requirements.txt é o mesmo do venv Windows; o gunicorn fica separado porque # é só do deploy (no Windows ele não instala). @@ -33,13 +39,21 @@ RUN pip install --no-cache-dir -r requirements.txt -r requirements-deploy.txt COPY scripts/ ./scripts/ COPY app/ ./app/ -# Usuário sem privilégio. Precisa poder escrever em /app/clientes (o "Salvar -# SD.md") e em /app/_derivados — o dono do volume no host tem de bater com -# este UID, ou a gravação recusa com permissão negada. +# Usuário sem privilégio — quem roda o gunicorn, via entrypoint.sh. RUN mkdir -p /app/clientes /app/_derivados \ && useradd --create-home --uid 10001 sd \ && chown -R sd:sd /app -USER sd + +# chmod aqui, e não só no arquivo do host: git em Windows não carrega bit de +# execução, então sem isso o entrypoint chega ao Coolify sem permissão de +# executar. +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +# Container começa como root DE PROPÓSITO — é o entrypoint.sh que faz o +# chown do volume recém-montado e derruba para `sd` antes do gunicorn. Ver +# entrypoint.sh. +ENTRYPOINT ["/entrypoint.sh"] EXPOSE 5000 diff --git a/docker-compose.yml b/docker-compose.yml index 1a48f2c..504809b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,10 +5,12 @@ # clientes/ está no .gitignore, então o clone nunca traz esses dados: # # scp -r clientes/ usuario@vps:/dados/exporta-sd/clientes -# ssh usuario@vps 'sudo chown -R 10001:10001 /dados/exporta-sd/clientes' # -# O chown usa o UID do usuário `sd` da imagem. Sem ele, a leitura funciona e o -# "Salvar SD.md" falha com permissão negada — falha parcial, difícil de ler. +# Não precisa de chown manual: o entrypoint.sh da imagem roda como root no +# início do container e ajusta o dono do volume para o usuário `sd` antes de +# derrubar privilégio e subir o gunicorn — ver entrypoint.sh e o ENTRYPOINT +# no Dockerfile. (Uma reconsequência: cada `scp` novo devolve os arquivos ao +# usuário do ssh no host; some sozinho no próximo restart do container.) # Depois, no Coolify → Environment Variables, aponte o volume para lá: # # CLIENTES_DIR=/dados/exporta-sd/clientes diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..0ee4bf0 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# Roda como root no início do container — DEPOIS que os volumes já estão +# montados. É por isso que este chown existe aqui e não no Dockerfile: um +# RUN chown no build só atinge a imagem, e o bind mount de clientes/ (e de +# _derivados/) substitui o diretório — dono incluído — ao subir o container, +# apagando qualquer chown feito em tempo de build. +# +# Sem `|| true` de propósito: se o chown falhar, é melhor a subida quebrar +# alto agora do que o "Salvar SD.md" falhar baixo, depois, com permissão +# negada. +set -e +chown -R sd:sd /app/clientes /app/_derivados +exec gosu sd "$@"