feat(installer): support ARM64 VPS deployments

This commit is contained in:
mauriciobera1990-droid
2026-09-29 14:12:19 -03:00
parent 65a01861e3
commit 5997f31228
17 changed files with 256 additions and 170 deletions
+7
View File
@@ -0,0 +1,7 @@
---
impacto: capacidade_nova
secao: adicionado
titulo: Instalação em VPS ARM64
---
O instalador padrão agora atende VPS ARM64/aarch64, como Oracle Ampere A1, usando as imagens nativas do DeskcommCRM e a variante NOWEB ARM64 oficial do WAHA. O caminho usa Supabase externo e não exige compilação na VPS. O instalador alternativo que hospeda o Supabase junto continua limitado a amd64.
+45 -18
View File
@@ -262,6 +262,9 @@ jobs:
labels: |
org.opencontainers.image.title=${{ matrix.title }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Buildx
uses: docker/setup-buildx-action@v4
@@ -270,8 +273,8 @@ jobs:
with:
context: .
file: ${{ matrix.dockerfile }}
# linux/amd64: o VPS HostGator e a imagem do WAHA são amd64.
platforms: linux/amd64
# Build multi-arquitetura no CI; o cliente só puxa o manifesto nativo.
platforms: linux/amd64,linux/arm64
# PR de fork não tem permissão de escrita no GHCR, e publicar imagem
# de código não revisado seria pior que não gatear. Em PR: constrói
# (que é o gate) e descarta.
@@ -309,9 +312,17 @@ jobs:
# Na `main` e em tag esta imagem vira a que o parque instala: ela se constrói
# nas máquinas do GitHub, nunca numa máquina nossa. Guarda contra fork:
# comentário do e2e-parte no e2e.yml.
runs-on: ${{ vars.EXECUTOR_PROPRIO == 'ligado' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && 'deskcomm-proprio' || 'ubuntu-latest' }}
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || (vars.EXECUTOR_PROPRIO == 'ligado' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && 'deskcomm-proprio' || 'ubuntu-latest') }}
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
platform: linux/amd64
- arch: arm64
platform: linux/arm64
concurrency:
group: ${{ github.workflow }}-imagem-do-app-sobe-${{ github.event.pull_request.number || github.ref }}${{ github.event_name == 'pull_request' && github.run_attempt != '1' && format('-reentrada-{0}', github.event.pull_request.head.sha) || '' }}
group: ${{ github.workflow }}-imagem-do-app-sobe-${{ matrix.arch }}-${{ github.event.pull_request.number || github.ref }}${{ github.event_name == 'pull_request' && github.run_attempt != '1' && format('-reentrada-{0}', github.event.pull_request.head.sha) || '' }}
cancel-in-progress: ${{ github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main') }}
permissions:
contents: read
@@ -326,7 +337,7 @@ jobs:
with:
context: .
file: Dockerfile
platforms: linux/amd64
platforms: ${{ matrix.platform }}
push: false
load: true
tags: deskcomm-smoke:pr
@@ -334,11 +345,13 @@ jobs:
APP_VERSION=smoke
# Mesmo escopo de cache do build-and-push: as camadas são idênticas,
# então na prática este job reaproveita aquele build em vez de repeti-lo.
cache-from: type=gha,scope=deskcommcrm
cache-from: |
type=gha,scope=deskcommcrm-${{ matrix.arch }}
type=gha,scope=deskcommcrm
# Em PR o build-and-push não roda, então quem grava o cache do PR é
# este job — sem isto o segundo push do mesmo PR reconstruiria do
# zero. Fora de PR continua só lendo: quem grava é o build-and-push.
cache-to: ${{ github.event_name == 'pull_request' && 'type=gha,mode=max,scope=deskcommcrm' || '' }}
cache-to: ${{ github.event_name == 'pull_request' && format('type=gha,mode=max,scope=deskcommcrm-{0}', matrix.arch) || '' }}
- name: O container chega a servir?
run: |
@@ -526,9 +539,17 @@ jobs:
# Na `main` e em tag esta imagem vira a que o parque instala: ela se constrói
# nas máquinas do GitHub, nunca numa máquina nossa. Guarda contra fork:
# comentário do e2e-parte no e2e.yml.
runs-on: ${{ vars.EXECUTOR_PROPRIO == 'ligado' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && 'deskcomm-proprio' || 'ubuntu-latest' }}
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || (vars.EXECUTOR_PROPRIO == 'ligado' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && 'deskcomm-proprio' || 'ubuntu-latest') }}
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
platform: linux/amd64
- arch: arm64
platform: linux/arm64
concurrency:
group: ${{ github.workflow }}-imagens-de-fundo-sobem-${{ github.event.pull_request.number || github.ref }}${{ github.event_name == 'pull_request' && github.run_attempt != '1' && format('-reentrada-{0}', github.event.pull_request.head.sha) || '' }}
group: ${{ github.workflow }}-imagens-de-fundo-sobem-${{ matrix.arch }}-${{ github.event.pull_request.number || github.ref }}${{ github.event_name == 'pull_request' && github.run_attempt != '1' && format('-reentrada-{0}', github.event.pull_request.head.sha) || '' }}
cancel-in-progress: ${{ github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main') }}
# Só leitura, explícita: sem o bloco o GITHUB_TOKEN herda o default do
# REPOSITÓRIO — configuração que vive fora do repo, muda num clique e não
@@ -546,7 +567,7 @@ jobs:
with:
context: .
file: Dockerfile.worker
platforms: linux/amd64
platforms: ${{ matrix.platform }}
push: false
load: true
tags: deskcomm-worker:pr
@@ -555,21 +576,25 @@ jobs:
# de um push e quente nos seguintes — "reaproveita aquele build" só
# vale a partir do segundo. Medido em 16/09/2026 no primeiro run deste
# gate: manifesto importado, camadas reconstruídas.
cache-from: type=gha,scope=deskcommcrm-worker
cache-from: |
type=gha,scope=deskcommcrm-worker-${{ matrix.arch }}
type=gha,scope=deskcommcrm-worker
# Mesma razão do job do app: em PR, quem grava o cache é este job.
cache-to: ${{ github.event_name == 'pull_request' && 'type=gha,mode=max,scope=deskcommcrm-worker' || '' }}
cache-to: ${{ github.event_name == 'pull_request' && format('type=gha,mode=max,scope=deskcommcrm-worker-{0}', matrix.arch) || '' }}
- name: Build local da imagem do scheduler (sem push)
uses: docker/build-push-action@v7
with:
context: .
file: Dockerfile.scheduler
platforms: linux/amd64
platforms: ${{ matrix.platform }}
push: false
load: true
tags: deskcomm-scheduler:pr
cache-from: type=gha,scope=deskcommcrm-scheduler
cache-to: ${{ github.event_name == 'pull_request' && 'type=gha,mode=max,scope=deskcommcrm-scheduler' || '' }}
cache-from: |
type=gha,scope=deskcommcrm-scheduler-${{ matrix.arch }}
type=gha,scope=deskcommcrm-scheduler
cache-to: ${{ github.event_name == 'pull_request' && format('type=gha,mode=max,scope=deskcommcrm-scheduler-{0}', matrix.arch) || '' }}
# A QUARTA imagem (módulo opcional de telefonia, #677). Ela entrou na
# `main` sem nunca ter sido construída: `build-and-push` tem a matriz com
@@ -584,12 +609,14 @@ jobs:
with:
context: .
file: Dockerfile.voice-agent
platforms: linux/amd64
platforms: ${{ matrix.platform }}
push: false
load: true
tags: deskcomm-voice-agent:pr
cache-from: type=gha,scope=deskcommcrm-voice-agent
cache-to: ${{ github.event_name == 'pull_request' && 'type=gha,mode=max,scope=deskcommcrm-voice-agent' || '' }}
cache-from: |
type=gha,scope=deskcommcrm-voice-agent-${{ matrix.arch }}
type=gha,scope=deskcommcrm-voice-agent
cache-to: ${{ github.event_name == 'pull_request' && format('type=gha,mode=max,scope=deskcommcrm-voice-agent-{0}', matrix.arch) || '' }}
- name: O laço do event_log CARREGA dentro da imagem do worker?
run: |
+1 -1
View File
@@ -483,7 +483,7 @@ Lei completa em [`docs/doctrine/packaging.md`](docs/doctrine/packaging.md). O n
declara `image:` de uma imagem publicada; `build:` só existe **ao lado**, como escape.
Serviço `build:`-only é pulado por `docker compose pull` e imune a `up -d` sem `--build` —
ele não é só caro de instalar, ele **nunca é atualizado**.
- **Publicação é ato do CI**, nunca da sua máquina: build ARM local não roda na VPS amd64.
- **Publicação é ato do CI**, nunca da sua máquina: as imagens publicadas atendem linux/amd64 e linux/arm64.
- **Instalação de cliente aponta para número de versão**, nunca para tag móvel. Aqui `latest`
significa **topo da `main`**, não última release — quem quer a última release usa `stable`.
- **Dependência upstream é referenciada com tag fixa, nunca republicada** (WAHA é licenciado).
+4 -3
View File
@@ -140,7 +140,7 @@ DeskcommCRM é um sistema operacional de vendas open source com agentes de IA na
- Action audit obrigatória: `lgpd.data_request_received`, `lgpd.export_generated`, `lgpd.redact_executed`, `lgpd.consent_changed`
### WAHA
- Default fixo `devlikeapro/waha:latest-2026.7.2`, NOWEB. A prova local criou duas sessões CORE simultâneas até `SCAN_QR_CODE`; não prova pairing, duas contas `WORKING` nem envio. Não bloquear segunda sessão por tier: conferir resposta estruturada e pós-condição da operação.
- Imagem NOWEB pinada por arquitetura: `latest-2026.7.2` em x86 e `noweb-arm-2026.7.2` em ARM64. A prova local criou duas sessões CORE simultâneas até `SCAN_QR_CODE`; não prova pairing, duas contas `WORKING` nem envio. Não bloquear segunda sessão por tier: conferir resposta estruturada e pós-condição da operação.
- Engine NOWEB default; WEBJS apenas se precisar stickers animados / botões
- Auth: env do WAHA recebe **hash SHA512 hex** da api key; cliente envia plaintext em `X-Api-Key`
- Webhooks: HMAC SHA512 com `crypto.timingSafeEqual`
@@ -277,8 +277,9 @@ O não-negociável, em quatro linhas:
só existe **ao lado**, como escape. Serviço `build:`-only é invisível para
`docker compose pull` e imune a `up -d` sem `--build` — ele não é só caro de
instalar, ele **nunca é atualizado**.
2. **Publicação é ato do CI.** Nunca da sua máquina: build ARM local não roda
na VPS amd64 do cliente, e a falha só aparece no `up -d` dele. O job
2. **Publicação é ato do CI.** Nunca da sua máquina: o CI publica as imagens
nativas para linux/amd64 e linux/arm64, e a falha de arquitetura precisa
aparecer antes do `up -d` do cliente. O job
`imagens-ok` reprova quando qualquer uma das três imagens não constrói, e
**é status check obrigatório desde 2026-08-13** — a branch protection tem
`verify, build-and-size, invariants, e2e, imagens-ok`. (Este parágrafo dizia
+4
View File
@@ -90,6 +90,10 @@ Se faltar Docker, o instalador pergunta e instala sozinho.
| **IA** | Uma chave de **OpenRouter**, **Anthropic** ou **OpenAI** — o instalador pergunta qual você quer |
| **WhatsApp** | Seu número, conectado por QR code no onboarding (ou o canal oficial da Meta) |
O instalador padrão também atende VPS ARM64/aarch64, como a Oracle Ampere A1, e escolhe a imagem
NOWEB oficial do WAHA compatível com essa arquitetura. Esse caminho usa Supabase externo. O
instalador alternativo que coloca o Supabase na mesma VPS continua restrito a amd64.
> 💡 **O Supabase pode ser criado pelo próprio instalador.** Exporte um
> `SUPABASE_ACCESS_TOKEN` antes de rodar e ele cria o projeto, espera o banco ficar saudável,
> busca as 4 credenciais e descobre o host do pooler testando conexão real — sem copiar e colar.
+4 -5
View File
@@ -127,11 +127,10 @@ services:
# precisar de multi-número/retry/S3 (licença paga).
#
# Tag PINADA: `devlikeapro/waha` sem tag é `:latest`, e o `dc pull` do
# update.sh entregava ao cliente qualquer versão que o upstream tivesse
# publicado — inclusive uma nunca testada por nós, no meio de uma
# atualização. `latest-2026.7.2` é o mesmo digest de `latest` hoje
# (65e593e30bb7…), então o pin não muda um bit; muda quem escolhe quando
# o WhatsApp do cliente troca de motor. Bumpar faz parte do release.
# update.sh entregaria ao cliente qualquer versão que o upstream tivesse
# publicado. O instalador grava `latest-2026.7.2` em x86 e
# `noweb-arm-2026.7.2` em ARM64; as duas tags fixam a versão NOWEB.
# Bumpar faz parte do release.
image: ${WAHA_IMAGE:-devlikeapro/waha:latest-2026.7.2}
restart: unless-stopped
environment:
+1 -1
View File
@@ -231,7 +231,7 @@ No menu lateral → **Storage** → **New bucket**:
## 3. WAHA — WhatsApp
**O que é:** Servidor que se conecta ao WhatsApp e expõe API HTTP. O default fixo é `devlikeapro/waha:latest-2026.7.2`, engine NOWEB. A prova local desta versão criou duas sessões CORE simultâneas até `SCAN_QR_CODE`; não houve pairing nem envio real. Versão/engine e pós-condição da operação determinam a compatibilidade; o tier sozinho não bloqueia um segundo número.
**O que é:** Servidor que se conecta ao WhatsApp e expõe API HTTP. O instalador fixa a imagem NOWEB por arquitetura: `devlikeapro/waha:latest-2026.7.2` em x86 e `devlikeapro/waha:noweb-arm-2026.7.2` em ARM64. A prova local desta versão criou duas sessões CORE simultâneas até `SCAN_QR_CODE`; não houve pairing nem envio real. Versão/engine e pós-condição da operação determinam a compatibilidade; o tier sozinho não bloqueia um segundo número.
### Passo 1 — gerar a API key (plaintext + hash)
+4 -3
View File
@@ -94,10 +94,11 @@ worker:
### 2. Publicação é ato do CI, e carrega procedência
Imagem nossa só existe se saiu de `.github/workflows/publish-image.yml`. Ela carrega os labels
OCI — no mínimo `source`, `revision`, `version`, `licenses` — e é construída para `linux/amd64`.
OCI — no mínimo `source`, `revision`, `version`, `licenses` — e é construída para `linux/amd64` e `linux/arm64`.
- **Por quê:** duas razões distintas. **(a) Arquitetura:** um `docker build` num Mac ARM produz
imagem que não roda na VPS amd64 do cliente, e a falha aparece só no `up -d` dele. **(b)
- **Por quê:** duas razões distintas. **(a) Arquitetura:** um `docker build` local pode produzir
imagem para a plataforma errada; o CI publica manifestos linux/amd64 e linux/arm64 e os testa
em runners nativos, antes de qualquer cliente chegar ao `up -d`. **(b)
Rastreabilidade:** sem `org.opencontainers.image.revision` não existe resposta para "que
código está rodando neste cliente?", e o suporte vira adivinhação.
- **Verificação:** o job **`imagens-ok`** de `publish-image.yml` reprova quando qualquer uma
+5 -4
View File
@@ -13,7 +13,7 @@ Este kit sobe o **DeskcommCRM** no seu servidor VPS da HostGator. Você tem dois
> ```
> **Outra hospedagem?** O kit é feito para a HostGator (é a parceria do projeto e o caminho
> testado de ponta a ponta), mas roda em qualquer VPS **x86_64/amd64** com Docker. Se a sua já vem com um
> testado de ponta a ponta), mas roda em qualquer VPS **x86_64/amd64 ou ARM64/aarch64** com Docker. Se a sua já vem com um
> **proxy reverso próprio** ocupando as portas 80/443 — caso de Hostinger, Coolify, Dokploy
> e CapRover —, o instalador **detecta isso sozinho** e publica o CRM através dele, em vez
> de tentar subir um Caddy que não caberia. Ver
@@ -104,9 +104,10 @@ Owner/Admin. Não dá para hospedar vários clientes numa conta só.
## Requisitos do VPS
- **Arquitetura x86_64/amd64.** As imagens oficiais publicadas atualmente são `linux/amd64`.
VPS ARM64/aarch64 ainda não são suportadas pelo kit; use uma VPS x86_64/amd64 enquanto
não houver imagens multi-arquitetura.
- **Arquitetura x86_64/amd64 ou ARM64/aarch64.** As imagens DeskcommCRM são publicadas para
`linux/amd64` e `linux/arm64`; o instalador seleciona a variante oficial ARM64 NOWEB do WAHA.
Este caminho usa Supabase externo. O instalador alternativo com Supabase self-hosted na mesma
VPS continua limitado a amd64.
- **4 GB RAM recomendados.** A imagem é pré-buildada, então o servidor não compila nada e a
stack SOBE com 2 GB — mas operar é outra coisa: são 7 contêineres, e o WAHA consome
~150 MB por sessão de WhatsApp além de ~300 MB de overhead do Node. Com 2 GB você roda
+43 -26
View File
@@ -18,37 +18,47 @@ COMPOSE_NPM="docker-compose.npm.yml"
COMPOSE_BUILD="docker-compose.build.yml"
# ── Arquitetura das imagens publicadas ───────────────────────────────────────
# O registry publica hoje somente linux/amd64. Sem esta guarda, ARM64 chega até
# o pull e morre com "no matching manifest"; o update.sh traduzia isso como
# pacote ainda publicando/privado, um diagnóstico que manda repetir algo que
# nunca vai funcionar nessa máquina.
# O CI publica app, worker, scheduler e agente de voz em linux/amd64 e
# linux/arm64. O instalador escolhe também a imagem oficial WAHA NOWEB ARM64;
# Redis, Caddy e SRH já publicam manifestos ARM64. Sem reconhecer a arquitetura
# aqui, uma VPS A1 seria recusada antes de chegar ao fluxo que já tem imagens
# nativas para ela.
#
# A decisão fica pura no argumento para os testes simularem a arquitetura sem
# depender do runner. A leitura de `uname -m` é o único ponto ligado ao host.
arquitetura_suportada_pelo_kit() {
case "${1:-}" in
x86_64|amd64) return 0 ;;
x86_64|amd64|aarch64|arm64) return 0 ;;
*) return 1 ;;
esac
}
# WAHA publica NOWEB em tags distintas por arquitetura. O instalador grava
# essa referência no .env; `enter_project` também cobre um .env antigo sem a
# variável. A tag é fixa e corresponde à mesma versão funcional em ambas.
imagem_waha_padrao_para_host() {
case "$(uname -m 2>/dev/null || true)" in
aarch64|arm64) printf '%s' 'devlikeapro/waha:noweb-arm-2026.7.2' ;;
*) printf '%s' 'devlikeapro/waha:latest-2026.7.2' ;;
esac
}
# ── JÁ EXISTE UMA INSTALAÇÃO REAL AQUI? (#1266, corrigido pelo #1778) ───────
#
# A guarda do #1042 vivia no TOPO dos dois scripts, e por isso matava antes de
# chegar ao `construir_aqui_e_subir` (#1060/#1143) — a recuperação por build
# local que existe exatamente para a VPS cuja arquitetura não bate com a das
# imagens publicadas. As duas mudanças tinham teste verde isoladamente e
# ninguém rodou as duas juntas: o resultado foi um `exit 1` na PRIMEIRA linha,
# que deixava quem já tinha uma instalação ARM funcionando PERMANENTEMENTE sem
# poder rodar `update.sh` de novo, e sem bandeira nenhuma.
# local para uma arquitetura sem imagem publicada. As duas mudanças tinham
# teste verde isoladamente e ninguém rodou as duas juntas: o resultado foi um
# `exit 1` na PRIMEIRA linha, que deixava uma instalação existente nessa
# arquitetura sem poder rodar `update.sh` de novo, e sem bandeira nenhuma.
#
# O sinal NÃO pode ser o estado do DIRETÓRIO. "compose + `.env`" chega junto
# numa instalação NOVA: o `.env` pode ter sido copiado de outra máquina, gerado
# por automação, ou deixado por uma rodada anterior do `--yes` que parou no
# meio. Com esse critério, um `install.sh --yes` numa VPS ARM NOVA com o `.env`
# já preenchido passava pela guarda como se fosse instalação existente e ia
# construir as imagens na própria VPS (15–25 min) — exatamente o que a guarda
# do #1042 existe para impedir. E o `.env` sozinho nunca provou nada: o
# meio. Com esse critério, um `install.sh --yes` numa VPS de arquitetura sem
# imagem publicada e com o `.env` já preenchido passava pela guarda como se
# fosse instalação existente e ia construir as imagens na própria VPS (15–25
# min) — exatamente o que a guarda do #1042 existe para impedir. E o `.env` sozinho nunca provou nada: o
# `git clone` de uma instalação nova pode trazer um `.env` de exemplo.
#
# O sinal do DIRETÓRIO entra como CONDIÇÃO, nunca como prova: sem compose nem
@@ -141,14 +151,17 @@ marcar_instalacao_feita() { # marcar_instalacao_feita [versão]
chmod 600 "$marca" 2>/dev/null || true
}
# Ecoa: amd64 | recuperar | nova
# Ecoa: amd64 | arm64 | recuperar | nova
#
# A decisão é PURA no que recebe: `uname` e a leitura do disco ficam fora, para
# o teste simular as três respostas sem depender do runner nem de um diretório
# de verdade. Quem traduz em mensagem é `verificar_arquitetura_do_kit`.
veredito_da_arquitetura() { # veredito_da_arquitetura <arquitetura> [0=nova | 1=instalação existente]
local arch="${1:-}" existe="${2:-0}"
arquitetura_suportada_pelo_kit "$arch" && { printf 'amd64'; return 0; }
case "$arch" in
x86_64|amd64) printf 'amd64'; return 0 ;;
aarch64|arm64) printf 'arm64'; return 0 ;;
esac
[ "$existe" = 1 ] && { printf 'recuperar'; return 0; }
printf 'nova'
}
@@ -157,17 +170,17 @@ verificar_arquitetura_do_kit() {
local arch existe=0
arch="$(uname -m 2>/dev/null || t "desconhecida")"
# O sinal de "instalação real" SÓ é perguntado quando a arquitetura não é
# suportada. Em amd64 o veredito já é `amd64` e a guarda atravessa, então
# perguntar seria trabalho inútil — e, com o critério do #1778, trabalho que
# chama o `docker` no TOPO do install.sh, antes de qualquer passo do
# instalador. A seção 4 do teste de #1778 mede isso: em x86_64 a guarda não
# fala com o Docker.
# suportada. Em amd64 ou arm64 o veredito já está definido e a guarda
# atravessa, então perguntar seria trabalho inútil — e, com o critério do
# #1778, trabalho que chama o `docker` no TOPO do install.sh, antes de
# qualquer passo do instalador. A seção 4 do teste de #1778 mede isso em
# x86_64.
if ! arquitetura_suportada_pelo_kit "$arch"; then
instalacao_real_do_kit_aqui && existe=1
fi
case "$(veredito_da_arquitetura "$arch" "$existe")" in
amd64) return 0 ;;
amd64|arm64) return 0 ;;
recuperar)
# O update.sh relê este arquivo depois do checkout da versão nova, e a
# guarda roda de novo no topo: sem esta trava o dono lia o mesmo aviso
@@ -180,15 +193,15 @@ verificar_arquitetura_do_kit() {
# recusa logo abaixo). O aviso vai para o STDERR, como a recusa: o
# agent.sh manda a saída do update.sh para arquivo e o dono lê o fim dela.
printf '%s\n' \
"⚠ $(t "Este servidor usa arquitetura '{1}', e as imagens publicadas do DeskcommCRM são só linux/amd64." "$arch")" \
"⚠ $(t "Este servidor usa arquitetura '{1}', e as imagens publicadas do DeskcommCRM são linux/amd64 e linux/arm64." "$arch")" \
" $(t "Como esta instalação JÁ EXISTE, sigo em frente: as imagens da versão alvo serão construídas nesta própria VPS.")" \
" $(t "Leva de 15 a 25 minutos. Uma instalação NOVA nesta arquitetura precisaria de imagens multi-arquitetura, que o DeskcommCRM ainda não publica.")" >&2
" $(t "Leva de 15 a 25 minutos. Uma instalação NOVA exige x86_64/amd64 ou ARM64/aarch64 com imagens publicadas.")" >&2
return 0 ;;
esac
printf '%s\n' \
"✖ $(t "Este servidor usa arquitetura '{1}', mas as imagens publicadas do DeskcommCRM hoje são linux/amd64." "$arch")" \
" $(t ' Use uma VPS x86_64/amd64. Repetir o download não resolve; ARM64 só será suportado quando houver imagens multi-arquitetura.' | sed 's/^ //')" >&2
"✖ $(t "Este servidor usa arquitetura '{1}', mas o DeskcommCRM não publica imagens para ela (linux/amd64 e linux/arm64 estão disponíveis)." "$arch")" \
" $(t ' Use uma VPS x86_64/amd64 ou ARM64/aarch64. Repetir o download não resolve.' | sed 's/^ //')" >&2
return 1
}
@@ -1061,6 +1074,10 @@ enter_project() {
else die "$(t "Não achei {1}. Rode a partir da pasta do projeto." "$COMPOSE")"; fi
[ -f .env ] || die "$(t "Falta o .env (rode install.sh primeiro).")"
load_env .env
if [ -z "${WAHA_IMAGE:-}" ]; then
WAHA_IMAGE="$(imagem_waha_padrao_para_host)"
export WAHA_IMAGE
fi
PROJECT_DIR="$(pwd)"
}
+5 -5
View File
@@ -33,11 +33,11 @@ _I18N_TABELA=0
if [ $((BASH_VERSINFO[0] * 100 + BASH_VERSINFO[1])) -ge "${I18N_BASH_MINIMO:-404}" ]; then
_I18N_TABELA=1
declare -A _ES=(
["Este servidor usa arquitetura '{1}', mas as imagens publicadas do DeskcommCRM hoje são linux/amd64."]="Este servidor usa arquitectura '{1}', pero las imágenes publicadas de DeskcommCRM hoy son linux/amd64."
[" Use uma VPS x86_64/amd64. Repetir o download não resolve; ARM64 só será suportado quando houver imagens multi-arquitetura."]=" Usa una VPS x86_64/amd64. Repetir la descarga no soluciona nada; ARM64 solo se admitirá cuando existan imágenes multiarquitectura."
["Este servidor usa arquitetura '{1}', e as imagens publicadas do DeskcommCRM são só linux/amd64."]="Este servidor usa arquitectura '{1}', y las imágenes publicadas de DeskcommCRM son solo linux/amd64."
["Este servidor usa arquitetura '{1}', mas o DeskcommCRM não publica imagens para ela (linux/amd64 e linux/arm64 estão disponíveis)."]="Este servidor usa arquitectura '{1}', pero DeskcommCRM no publica imágenes para ella (linux/amd64 y linux/arm64 están disponibles)."
[" Use uma VPS x86_64/amd64 ou ARM64/aarch64. Repetir o download não resolve."]=" Usa una VPS x86_64/amd64 o ARM64/aarch64. Repetir la descarga no soluciona nada."
["Este servidor usa arquitetura '{1}', e as imagens publicadas do DeskcommCRM são linux/amd64 e linux/arm64."]="Este servidor usa arquitectura '{1}', y las imágenes publicadas de DeskcommCRM son linux/amd64 y linux/arm64."
["Como esta instalação JÁ EXISTE, sigo em frente: as imagens da versão alvo serão construídas nesta própria VPS."]="Como esta instalación YA EXISTE, sigo adelante: las imágenes de la versión destino se construirán en esta misma VPS."
["Leva de 15 a 25 minutos. Uma instalação NOVA nesta arquitetura precisaria de imagens multi-arquitetura, que o DeskcommCRM ainda não publica."]="Tarda de 15 a 25 minutos. Una instalación NUEVA en esta arquitectura necesitaría imágenes multiarquitectura, que DeskcommCRM todavía no publica."
["Leva de 15 a 25 minutos. Uma instalação NOVA exige x86_64/amd64 ou ARM64/aarch64 com imagens publicadas."]="Tarda de 15 a 25 minutos. Una instalación NUEVA requiere x86_64/amd64 o ARM64/aarch64 con imágenes publicadas."
[" (rede '{1}' criada — é por ela que o Traefik alcança o CRM)"]=" (red '{1}' creada: por ella Traefik llega al CRM)"
["A rede Docker '{1}' (a do Nginx Proxy Manager) não existe.
Rode 'docker network ls', identifique a rede do seu NPM (Settings > a que o
@@ -120,7 +120,7 @@ sin eso un contenedor de compose común no puede entrar en ella."
["derruba o que subiu"]="derriba lo que se levantó"
["começa de novo"]="empieza de nuevo"
["apaga o marcador desta instalação"]="borra el marcador de esta instalación"
["⚠ Não consegui gravar o marcador desta instalação (arquivo .deskcomm-instalado). O CRM está no ar; numa VPS ARM a atualização pode pedir a VPS x86_64 até o marcador existir."]="⚠ No pude escribir el marcador de esta instalación (archivo .deskcomm-instalado). El CRM está en línea; en una VPS ARM la actualización puede pedir la VPS x86_64 hasta que el marcador exista."
["⚠ Não consegui gravar o marcador desta instalação (arquivo .deskcomm-instalado). O CRM está no ar; numa arquitetura sem imagens publicadas, a atualização pode pedir uma VPS suportada até o marcador existir."]="⚠ No pude escribir el marcador de esta instalación (archivo .deskcomm-instalado). El CRM está en línea; en una arquitectura sin imágenes publicadas, la actualización puede pedir una VPS compatible hasta que exista el marcador."
["Se o schema chegou a ser aplicado e você quer o banco limpo de novo,"]="Si el esquema llegó a aplicarse y quieres la base de datos limpia de nuevo,"
["abra o Supabase > SQL Editor e rode (ATENÇÃO: apaga todos os dados):"]="abre Supabase > SQL Editor y ejecuta (ATENCIÓN: borra todos los datos):"
+7 -9
View File
@@ -1781,12 +1781,9 @@ esac
printf '# Sem isto o número segue pareado no volume e MUDO até alguém abrir a tela\n'
printf '# e clicar Reconectar — nada entra nem sai nesse meio-tempo.\n'
envq WHATSAPP_RESTART_ALL_SESSIONS "${WHATSAPP_RESTART_ALL_SESSIONS:-True}"
# PINADA. Sem a tag, `devlikeapro/waha` é `:latest`, e esta linha gravava isso
# no .env de todo cliente — por cima do default pinado do compose, que então
# nunca chegava a ninguém. O `dc pull` de cada update entregava qualquer versão
# que o upstream tivesse publicado, sem ninguém ter testado.
# `latest-2026.7.2` é o mesmo digest de `latest` hoje (65e593e30bb7…).
envq WAHA_IMAGE "${WAHA_IMAGE:-devlikeapro/waha:latest-2026.7.2}"
# WAHA publica variantes x86 e ARM separadas para NOWEB. O padrão acompanha
# uname -m; uma WAHA_IMAGE escolhida pelo operador continua prevalecendo.
envq WAHA_IMAGE "${WAHA_IMAGE:-$(imagem_waha_padrao_para_host)}"
envq WAHA_DEFAULT_ENGINE "${WAHA_DEFAULT_ENGINE:-NOWEB}"
envq UPSTASH_REDIS_REST_URL "http://srh:80"
envq UPSTASH_REDIS_REST_TOKEN "$UPSTASH_REDIS_REST_TOKEN"
@@ -2278,8 +2275,9 @@ setup_update_agent_cron
# "instalação nova" de "instalação que já está no ar", e ela não pode usar
# "tem compose e tem `.env`" como prova: o `.env` chega pronto numa instalação
# NOVA (copiado, gerado por automação, ou deixado por um `--yes` que parou no
# meio), e com esse critério uma VPS ARM nova começava a instalação construindo
# as imagens na própria VPS — o que a guarda existe para impedir.
# meio), e com esse critério uma VPS numa arquitetura sem imagens publicadas
# começava a instalação construindo as imagens na própria VPS — o que a guarda
# existe para impedir.
#
# O marcador vai aqui, e não antes, porque só a partir daqui é verdade que a
# instalação EXISTE: os contêineres subiram e o app respondeu. Gravar antes
@@ -2290,7 +2288,7 @@ setup_update_agent_cron
# contêiner, que é o mesmo que ela usava para quem instalou numa versão
# anterior.
if [ "${APP_SAUDAVEL:-0}" = 1 ]; then
marcar_instalacao_feita "$VERSAO_ALVO" || c_ylw "$(t "⚠ Não consegui gravar o marcador desta instalação (arquivo .deskcomm-instalado). O CRM está no ar; numa VPS ARM a atualização pode pedir a VPS x86_64 até o marcador existir.")"
marcar_instalacao_feita "$VERSAO_ALVO" || c_ylw "$(t "⚠ Não consegui gravar o marcador desta instalação (arquivo .deskcomm-instalado). O CRM está no ar; numa arquitetura sem imagens publicadas, a atualização pode pedir uma VPS suportada até o marcador existir.")"
fi
# ── Final ───────────────────────────────────────────────────────────────────
+5 -7
View File
@@ -77,13 +77,11 @@ crontab -l >"$CRONTAB_REAL_ANTES" 2>/dev/null || : >"$CRONTAB_REAL_ANTES"
# dublar_uname_amd64 <diretório bin do sandbox>
#
# O `_common.sh` recusa, logo que é carregado, todo install.sh/update.sh que não
# roda em amd64 — a imagem publicada é só linux/amd64. Os cenários que executam
# esses scripts de verdade medem o INSTALADOR, não o processador de quem roda a
# suíte: sem este dublê, num Mac Apple Silicon (`arm64`) todos eles paravam na
# guarda (medido: 22 asserções vermelhas, a maioria "inconclusivo"). A recusa de
# ARM tem prova própria em tests/shell/arquitetura-kit.test.sh. Só `uname -m` é
# dublado; qualquer outro uso vai ao `uname` real.
# Os cenários abaixo medem o instalador, não o processador de quem roda a
# suíte. Fixamos o ambiente em x86_64 para que os fixtures e valores de imagem
# desses casos permaneçam determinísticos; a aceitação de ARM64 tem prova
# própria em tests/shell/arquitetura-kit.test.sh. Só `uname -m` é dublado;
# qualquer outro uso vai ao `uname` real.
UNAME_REAL="$(command -v uname)"
dublar_uname_amd64() {
cat > "$1/uname" <<STUB
+5 -5
View File
@@ -726,11 +726,11 @@ step "Conferindo se o app voltou no ar"
ok=""
wait_app_healthy 20 3 >/dev/null && ok=1
if [ -n "$ok" ]; then
# O marcador que a guarda de ARM lê (#1778). Instalação ARM nova é recusada,
# então toda instalação ARM que existe veio de antes do marcador e só seria
# reconhecida pelo contêiner — que um `down` sem `-v` apaga. Gravar aqui, com
# o app saudável, fecha esse caso a partir desta atualização. Falhar em
# gravar não desfaz nada: a guarda segue caindo no sinal do contêiner.
# O marcador que a guarda de arquitetura lê (#1778). Instalações antigas só
# seriam reconhecidas pelo contêiner — que um `down` sem `-v` apaga. Gravar
# aqui, com o app saudável, fecha esse caso a partir desta atualização.
# Falhar em gravar não desfaz nada: a guarda segue caindo no sinal do
# contêiner para arquiteturas que ainda não têm imagens publicadas.
marcar_instalacao_feita "$TARGET_TAG" || true
if [ -n "$BANCO_INCOMPLETO" ]; then
c_ylw "⚠ App no ar e saudável, mas o banco NÃO terminou limpo — o que fazer está no fim desta saída."
+41 -12
View File
@@ -69,33 +69,62 @@ SH
}
for nome in install.sh update.sh; do
rodar_como "$nome" aarch64
rc="$(cat "$TMP/rc-$nome-aarch64")"
out="$(cat "$TMP/out-$nome-aarch64")"
rodar_como "$nome" riscv64
rc="$(cat "$TMP/rc-$nome-riscv64")"
out="$(cat "$TMP/out-$nome-riscv64")"
if [ "$rc" -eq 0 ]; then
printf '✗ %s aceitou aarch64\n' "$nome"; fail=1
elif ! printf '%s' "$out" | grep -q 'aarch64'; then
printf '✗ %s aceitou riscv64\n' "$nome"; fail=1
elif ! printf '%s' "$out" | grep -q 'riscv64'; then
printf '✗ %s recusou sem dizer a arquitetura encontrada\n' "$nome"; fail=1
elif ! printf '%s' "$out" | grep -q 'linux/amd64'; then
printf '✗ %s recusou sem dizer qual imagem existe hoje\n' "$nome"; fail=1
elif ! printf '%s' "$out" | grep -q 'x86_64/amd64'; then
printf '✗ %s recusou sem orientar a arquitetura de VPS suportada\n' "$nome"; fail=1
elif ! printf '%s' "$out" | grep -q 'linux/amd64 e linux/arm64'; then
printf '✗ %s recusou sem dizer quais imagens estão disponíveis\n' "$nome"; fail=1
elif ! printf '%s' "$out" | grep -q 'x86_64/amd64 ou ARM64/aarch64'; then
printf '✗ %s recusou sem orientar as arquiteturas de VPS suportadas\n' "$nome"; fail=1
elif printf '%s' "$out" | grep -q 'DEPOIS_DA_GUARDA'; then
printf '✗ %s continuou depois da recusa\n' "$nome"; fail=1
else
printf '✓ %s recusa ARM64 com a causa correta\n' "$nome"
printf '✓ %s recusa riscv64 com a causa correta\n' "$nome"
fi
done
for cmd in docker curl git; do
if [ -e "$TMP/tocou-$cmd" ]; then
printf '✗ a guarda tocou em %s antes de recusar ARM64\n' "$cmd"; fail=1
printf '✗ a guarda tocou em %s antes de recusar riscv64\n' "$cmd"; fail=1
else
printf '✓ _common.sh recusa ARM64 antes de tocar em %s\n' "$cmd"
printf '✓ _common.sh recusa riscv64 antes de tocar em %s\n' "$cmd"
fi
done
# Contrapeso: ARM64 agora atravessa a guarda sem consultar Docker nem tentar
# construir imagem na VPS; a seleção de WAHA fica a cargo do instalador.
for arch in aarch64 arm64; do
rodar_como install.sh "$arch"
rc="$(cat "$TMP/rc-install.sh-$arch")"
out="$(cat "$TMP/out-install.sh-$arch")"
if [ "$rc" -ne 0 ] || ! printf '%s' "$out" | grep -q 'DEPOIS_DA_GUARDA'; then
printf '✗ ARM64 (%s) deveria atravessar a guarda\n' "$arch"; fail=1
else
printf '✓ ARM64 (%s) atravessa a guarda\n' "$arch"
fi
imagem_waha="$(cd "$TMP" && env FAKE_ARCH="$arch" PATH="$TMP/bin:$PATH" \
bash -c '. "$1"; imagem_waha_padrao_para_host' _ "$COMMON")"
if [ "$imagem_waha" != 'devlikeapro/waha:noweb-arm-2026.7.2' ]; then
printf '✗ ARM64 (%s) escolheu WAHA inesperado: %s\n' "$arch" "$imagem_waha"; fail=1
else
printf '✓ ARM64 (%s) escolhe WAHA NOWEB ARM64 pinado\n' "$arch"
fi
done
imagem_waha="$(cd "$TMP" && env FAKE_ARCH=x86_64 PATH="$TMP/bin:$PATH" \
bash -c '. "$1"; imagem_waha_padrao_para_host' _ "$COMMON")"
if [ "$imagem_waha" != 'devlikeapro/waha:latest-2026.7.2' ]; then
printf '✗ x86_64 escolheu WAHA inesperado: %s\n' "$imagem_waha"; fail=1
else
printf '✓ x86_64 mantém o WAHA NOWEB pinado atual\n'
fi
# Contrapeso: a guarda não pode transformar o requisito amd64 numa recusa geral.
rodar_como update.sh x86_64
rc="$(cat "$TMP/rc-update.sh-x86_64")"
@@ -4,25 +4,27 @@
# O DEFEITO, e por que os dois testes que já existiam não o pegaram:
#
# A guarda de arquitetura (#1042) estava no TOPO do `source _common.sh`, e
# saía com `exit 1` assim que `uname -m` não era x86_64/amd64. A recuperação
# saía com `exit 1` assim que `uname -m` não era uma arquitetura publicada.
# A recuperação
# por build local (#1060/#1143, `construir_aqui_e_subir` + o overlay
# `docker-compose.build.yml`) vive bem DEPOIS, no corpo do update.sh. As duas
# mudanças tinham teste verde ISOLADAMENTE — `arquitetura-kit.test.sh` prova
# que a recusa acontece; nenhum teste rodava `update.sh` de ponta a ponta com
# um `uname` de ARM. Juntas, a recusa matava o script na primeira linha e a
# recuperação ficava inalcançável: quem JÁ TINHA uma instalação ARM
# funcionando ficava sem poder rodar `update.sh` nunca mais, sem bandeira.
# um `uname` de arquitetura sem imagem. Juntas, a recusa matava o script na
# primeira linha e a recuperação ficava inalcançável: quem JÁ TINHA uma
# instalação nessa arquitetura funcionando ficava sem poder rodar `update.sh`
# nunca mais, sem bandeira.
#
# O QUE ESTE ARQUIVO PROVA, e o que NÃO prova:
#
# Prova que com `uname -m` = aarch64 e uma instalação que JÁ EXISTE
# Prova que com `uname -m` = riscv64 (arquitetura ainda não publicada) e uma instalação que JÁ EXISTE
# (compose + `.env`), o `update.sh` ALCANÇA o caminho de recuperação: o
# `docker compose -f docker-compose.build.yml build` é executado e o update
# termina com o CRM no ar. Prova também que a instalação NOVA em ARM
# continua recusada com a mesma mensagem do #1042, e que x86_64 não mudou
# termina com o CRM no ar. Prova também que uma instalação NOVA em riscv64
# continua recusada com a mensagem atual, ARM64 nova é aceita e x86_64 não mudou
# nada.
#
# NÃO prova nada numa VPS ARM de verdade: `docker`, `curl`, `crontab`, `psql`
# NÃO prova nada numa VPS de verdade: `docker`, `curl`, `crontab`, `psql`
# e `uname` são dublês, o repositório git é descartável (mktemp) e nenhum
# contêiner sobe. O que se prova é o CAMINHO DO SCRIPT, que é onde as duas
# mudanças se cruzavam.
@@ -201,7 +203,7 @@ rodar_update() {
}
# ─────────────────────────────────────────────────────────────────────────────
echo '── 1. A DEFEITO: aarch64 numa instalação que JÁ EXISTE tem que alcançar a'
echo '── 1. A RECUPERAÇÃO: riscv64 numa instalação que JÁ EXISTE alcança a'
echo ' recuperação por build local, e não morrer na guarda do #1042'
# É o relato da issue, palavra por palavra: um clone de ARM, com o CRM no ar,
# e o `bash hostgator-setup-kit/update.sh` não passando de "Procurando
@@ -209,13 +211,13 @@ echo ' recuperação por build local, e não morrer na guarda do #1042'
# o overlay de build foi MESMO executado, e o update terminou bem.
R1="$WORK/caso1"; mkdir -p "$R1"; montar_instalacao "$R1" 0
OUT1="$WORK/saida1.txt"
rodar_update "$R1" aarch64 1 "$OUT1"; RC1="$RC"
rodar_update "$R1" riscv64 1 "$OUT1"; RC1="$RC"
check "o update.sh NÃO morre na guarda de arquitetura (o 'Procurando atualizações' aparece)" \
grep -q 'Procurando atualizações' "$OUT1"
check "a guarda em vez de recusar AVISA que a instalação já existe" \
grep -q 'JÁ EXISTE' "$OUT1"
check "a arquitetura encontrada é dita ao dono (aarch64)" \
grep -q 'aarch64' "$OUT1"
check "a arquitetura encontrada é dita ao dono (riscv64)" \
grep -q 'riscv64' "$OUT1"
# O update.sh relê o _common.sh depois do checkout (update.sh, passo 3), e a
# guarda roda de novo no topo dele: sem a trava por processo, o mesmo aviso
# saía duas vezes na mesma atualização.
@@ -231,29 +233,28 @@ check "e o serviço subiu pelo mesmo overlay" \
check "a atualização terminou com o CRM no ar" \
grep -q 'containers no ar\|Atualização concluída\|construídas aqui' "$OUT1"
check "o update.sh saiu com 0 (rc=$RC1)" test "$RC1" -eq 0
# O aviso da guarda não pode ser a recusa: a recusa do #1042 começa com '✖' e
# manda usar VPS x86_64, e é a mensagem que o dono lia antes.
check "a saída NÃO diz 'Use uma VPS x86_64/amd64' (a recusa do #1042)" \
nao_contem 'Use uma VPS x86_64/amd64' "$OUT1"
# O aviso de recuperação não pode virar a recusa de uma instalação nova.
check "a saída NÃO traz a recusa de instalação nova" \
nao_contem 'Use uma VPS x86_64/amd64 ou ARM64/aarch64' "$OUT1"
# ─────────────────────────────────────────────────────────────────────────────
echo
echo '── 2. A GUARDA DO #1042 CONTINUA: instalação NOVA em aarch64 é recusada'
echo '── 2. A GUARDA CONTINUA: instalação NOVA numa arquitetura sem imagem é recusada'
# Contrapeso obrigatório, e é o motivo de a guarda não ter sido removida: numa
# instalação nova não existe build local para recuperar — não há imagem, não há
# `.env`, e o `construir_aqui_e_subir` só é alcançado DEPOIS do provisionamento
# do banco. O que muda é que a recusa volta a ser a do #1042, com a causa certa.
R2="$WORK/caso2"; mkdir -p "$R2"; montar_instalacao "$R2" 1
OUT2="$WORK/saida2.txt"
rodar_update "$R2" aarch64 1 "$OUT2"; RC2="$RC"
check "a instalação NOVA em ARM é recusada (rc=$RC2, e != 0)" test "$RC2" -ne 0
rodar_update "$R2" riscv64 1 "$OUT2"; RC2="$RC"
check "a instalação NOVA em riscv64 é recusada (rc=$RC2, e != 0)" test "$RC2" -ne 0
check "a recusa diz qual arquitetura foi encontrada" \
grep -q 'aarch64' "$OUT2"
check "a recusa diz qual é a imagem que existe hoje" \
grep -q 'linux/amd64' "$OUT2"
check "a recusa orienta a VPS suportada (o texto do #1042, intacto)" \
grep -q 'Use uma VPS x86_64/amd64' "$OUT2"
check "a instalação NOVA em ARM NÃO tenta construir imagens aqui" \
grep -q 'riscv64' "$OUT2"
check "a recusa lista as arquiteturas publicadas" \
grep -q 'linux/amd64 e linux/arm64' "$OUT2"
check "a recusa orienta a VPS suportada" \
grep -q 'Use uma VPS x86_64/amd64 ou ARM64/aarch64' "$OUT2"
check "a instalação NOVA em arquitetura sem imagem NÃO tenta construir imagens aqui" \
nao_contem '-f docker-compose.build.yml build' "$DOCKER_LOG"
# ─────────────────────────────────────────────────────────────────────────────
@@ -276,14 +277,14 @@ check "em x86_64 a atualização pullou a imagem do app" \
# ─────────────────────────────────────────────────────────────────────────────
echo
echo '── 4. A DECISÃO, isolada: a função pura que decide'
# As três respostas, sem `uname` e sem disco — é o que permite ao gate ler a
# decisão sem montar instalação nenhuma, e cobre o limite: em amd64, existir ou
# não uma instalação é a MESMA resposta.
# As respostas, sem `uname` e sem disco — é o que permite ao gate ler a decisão
# sem montar instalação nenhuma. amd64 e arm64 são nativas; só uma arquitetura
# sem imagem depende de haver instalação anterior para recuperar.
R4="$WORK/caso4"; mkdir -p "$R4"
OUT4="$R4/veredito.txt"
(
. "$REPO_ROOT/hostgator-setup-kit/_common.sh"
for par in "x86_64 0" "amd64 1" "aarch64 0" "aarch64 1" "arm64 1" "riscv64 0"; do
for par in "x86_64 0" "amd64 1" "aarch64 0" "aarch64 1" "arm64 1" "riscv64 0" "riscv64 1"; do
# shellcheck disable=SC2086
set -- $par
printf '%s %s → %s\n' "$1" "$2" "$(veredito_da_arquitetura "$1" "$2")"
@@ -293,21 +294,24 @@ check "amd64 (nova ou existente) → amd64" \
grep -q '^x86_64 0 → amd64$' "$OUT4"
check "amd64 em instalação existente também é amd64" \
grep -q '^amd64 1 → amd64$' "$OUT4"
check "aarch64 em instalação NOVA → nova (a recusa do #1042)" \
grep -q '^aarch64 0 → nova$' "$OUT4"
check "aarch64 em instalação existente → recuperar" \
grep -q '^aarch64 1 → recuperar$' "$OUT4"
check "arm64 em instalação existente → recuperar" \
grep -q '^arm64 1 → recuperar$' "$OUT4"
check "outra arquitetura em instalação NOVA → nova" \
check "aarch64 em instalação NOVA → arm64" \
grep -q '^aarch64 0 → arm64$' "$OUT4"
check "aarch64 em instalação existente → arm64" \
grep -q '^aarch64 1 → arm64$' "$OUT4"
check "arm64 em instalação existente → arm64" \
grep -q '^arm64 1 → arm64$' "$OUT4"
check "arquitetura sem imagem em instalação NOVA → nova" \
grep -q '^riscv64 0 → nova$' "$OUT4"
check "arquitetura sem imagem em instalação existente → recuperar" \
grep -q '^riscv64 1 → recuperar$' "$OUT4"
# ─────────────────────────────────────────────────────────────────────────────
echo
echo '── 5. QUEM JÁ ESTÁ PRESO: o kit do disco é o da guarda velha, e o passo'
echo ' único que o fragmento de release ensina tem de funcionar'
# O update.sh dá `source` no _common.sh que está NO DISCO antes do checkout da
# versão nova. Numa VPS ARM que já tem a guarda do #1042 (v1.35.0 em diante),
# versão nova. Num kit antigo, uma VPS ARM ainda era bloqueada pela guarda
# (#1042, v1.35.0 em diante),
# esse kit velho morre no topo e nunca baixa este conserto — nem pelo terminal
# nem pelo botão "Atualizar", que roda o mesmo update.sh. A saída é trocar o
# código à mão uma vez e rodar o update.sh da versão nova. Este caso prova as
@@ -315,7 +319,7 @@ echo ' único que o fragmento de release ensina tem de funcionar'
# publicado no fragmento `.changes/guarda-arm-nao-mata-a-recuperacao.md` a solta.
#
# O kit "velho" é o do próprio PR com a detecção de instalação desligada, que
# é exatamente o comportamento da guarda do #1042: recusa ARM sem olhar nada.
# é exatamente o comportamento da guarda antiga do #1042: recusa ARM sem olhar nada.
R5="$WORK/caso5"; mkdir -p "$R5"; montar_instalacao "$R5" 0
(
cd "$R5/deskcommcrm" || exit 1
@@ -346,10 +350,10 @@ check "depois do checkout à mão, o update.sh da versão nova sai com 0 (rc=$RC
check "e ele chega à recuperação por build local" \
grep -q -- '-f docker-compose.build.yml build' "$DOCKER_LOG"
printf '\nstatus: caso1(ARM+recuperação)=%s caso2(nova ARM)=%s caso3(amd64)=%s\n' \
printf '\nstatus: caso1(arquitetura sem imagem+recuperação)=%s caso2(nova arquitetura sem imagem)=%s caso3(amd64)=%s\n' \
"$RC1" "$RC2" "$RC3"
if [ "$FAILS" -eq 0 ]; then
echo "OK — a guarda não mata a recuperação (#1266) e continua recusando a instalação nova (#1042)."
echo "OK — a guarda preserva a recuperação de arquiteturas sem imagem e aceita as arquiteturas publicadas."
else
echo "FALHOU — $FAILS prova(s)."
fi
@@ -1,17 +1,17 @@
#!/usr/bin/env bash
# ── #1778 — a guarda de ARM só considera instalação REAL ─────────────────────
# ── #1778 — a guarda de arquitetura sem suporte só considera instalação REAL ───────────────────────
#
# O DEFEITO, e por que o teste do #1775 não podia pegá-lo:
#
# O #1775 separou instalação NOVA (recusa em não-x86_64) de instalação
# EXISTENTE (aviso, e o script segue até o build local). O critério de
# "existente" lia o estado do DIRETÓRIO — compose E um `.env` — e um
# `install.sh --yes` numa VPS ARM NOVA com o `.env` já preenchido (copiado de
# `install.sh --yes` numa VPS com arquitetura sem suporte e `.env` já preenchido (copiado de
# outra máquina, gerado por automação, ou deixado por uma rodada anterior que
# parou no meio) passava como existente e ia construir as imagens na própria
# VPS. O caminho alcançado é o mesmo build local que o install.sh já tem
# (por volta da linha 2209), então não é risco de segurança: é a instalação
# NOVA em ARM deixar de ser recusada, que é o que a guarda do #1042 existe
# NOVA em arquitetura sem suporte deixar de ser recusada, que é o que a guarda do #1042 existe
# para impedir.
#
# O teste do #1775 não podia ver isso porque a fixture dele (`montar_instalacao`)
@@ -24,13 +24,13 @@
#
# Prova que a guarda decide pelo que a instalação DEIXOU (marcador, contêiner
# do projeto, contêiner do Supabase single-server) e não pelo que chegou
# pronto na pasta: com `.env` e nada instalado, aarch64 é recusada com a
# pronto na pasta: com `.env` e nada instalado, riscv64 é recusada com a
# recusa do #1042. Prova que os três sinais de instalação real continuam
# passando, que o filtro de contêiner é por PROJETO (o de outra instalação na
# mesma VPS não vale), que `docker` indisponível não vira instalação inventada,
# e que em x86_64 o `docker` nem é chamado — a guarda não perguntou nada.
#
# NÃO prova nada numa VPS ARM de verdade: `uname` e `docker` são dublês, o
# NÃO prova nada numa VPS com arquitetura sem suporte de verdade: `uname` e `docker` são dublês, o
# diretório é descartável (mktemp) e nenhum contêiner sobe. O que se prova é
# a DECISÃO da guarda, que é onde o critério mora.
#
@@ -70,7 +70,7 @@ mkdir -p "$WORK/bin"
# binário de verdade.
cat > "$WORK/bin/uname" <<STUB
#!/usr/bin/env bash
[ "\$*" = "-m" ] && { printf '%s\n' "\${FAKE_ARCH:-aarch64}"; exit 0; }
[ "\$*" = "-m" ] && { printf '%s\n' "\${FAKE_ARCH:-riscv64}"; exit 0; }
exec "$REAL_UNAME" "\$@"
STUB
# `docker` responde ao ÚNICO comando que a guarda faz: `ps -a -q --filter
@@ -160,21 +160,21 @@ nome_derivado() { basename "$1" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9_-'
# ─────────────────────────────────────────────────────────────────────────────
echo '── 1. A DEFÉITO DA #1778: .env preenchido + NADA instalado é instalação'
echo ' NOVA, e aarch64 é recusada com a recusa do #1042'
# A receita do relato: `install.sh --yes` numa VPS ARM NOVA cujo `.env` veio
echo ' NOVA, e riscv64 é recusada com a recusa do #1042'
# A receita do relato: `install.sh --yes` numa VPS com arquitetura sem suporte e instalação NOVA cujo `.env` veio
# pronto (copiado de outra máquina, gerado por automação, ou deixado por uma
# rodada que parou no meio). Compose e `.env` estão lá — que era o que bastava.
# O resultado tem de ser a RECUSA, com o texto do #1042, e sem a palavra do
# aviso de instalação existente.
R1="$WORK/caso1"; mkdir -p "$R1"; montar_pasta "$R1/deskcommcrm" 0
guarda "$R1" aarch64
guarda "$R1" riscv64
check ".env + compose e NADA instalado → a instalação NÃO é real" \
test "$VEREDITO" = NOVA
check "e aarch64 é recusada (rc=$RC, e != 0)" test "$RC" -ne 0
check "e riscv64 é recusada (rc=$RC, e != 0)" test "$RC" -ne 0
check "a recusa é a do #1042 (orienta a VPS suportada)" \
grep -q 'Use uma VPS x86_64/amd64' "$WORK/guarda.out"
grep -q 'Use uma VPS x86_64/amd64 ou ARM64/aarch64' "$WORK/guarda.out"
check "a recusa diz qual arquitetura foi encontrada" \
grep -q 'aarch64' "$WORK/guarda.out"
grep -q 'riscv64' "$WORK/guarda.out"
check "e NÃO diz que a instalação já existe (o aviso do #1775)" \
nao_contem 'JÁ EXISTE' "$WORK/guarda.out"
@@ -182,7 +182,7 @@ check "e NÃO diz que a instalação já existe (o aviso do #1775)" \
echo
echo '── 2. OS TRÊS SINAIS DE INSTALAÇÃO REAL CONTINUAM PASSANDO (#1775 intacto)'
# O contrapeso obrigatório: o que o #1775 consertou não pode ser desfeito. Sem
# esta seção, um conserto que recusasse TUDO em ARM também ficaria verde.
# esta seção, um conserto que recusasse TUDO em arquitetura sem suporte também ficaria verde.
R2="$WORK/caso2"; mkdir -p "$R2"; montar_pasta "$R2/deskcommcrm" 0
marcar_instalacao() { # marcar_instalacao <diretório> [versão]
printf 'instalado_em=2026-09-27T00:00:00Z\nversao=%s\n' "${2:-0.9.0}" \
@@ -192,30 +192,30 @@ marcar_instalacao() { # marcar_instalacao <diretório> [versão]
# 2a. O marcador, que o install.sh grava com a stack no ar. É o sinal mais
# forte: a instalação em si escreveu que terminou.
marcar_instalacao "$R2/deskcommcrm"
guarda "$R2" aarch64
guarda "$R2" riscv64
check "o marcador .deskcomm-instalado → a instalação é real" test "$VEREDITO" = REAL
check "e aarch64 passa pela guarda (rc=$RC, e == 0)" test "$RC" -eq 0
check "e riscv64 passa pela guarda (rc=$RC, e == 0)" test "$RC" -eq 0
check "com o aviso de que a instalação já existe, e não com a recusa" \
grep -q 'JÁ EXISTE' "$WORK/guarda.out"
check "e a recusa do #1042 NÃO aparece" \
nao_contem 'Use uma VPS x86_64/amd64' "$WORK/guarda.out"
nao_contem 'Use uma VPS x86_64/amd64 ou ARM64/aarch64' "$WORK/guarda.out"
# 2b. O contêiner do projeto, que é o caso de quem instalou numa versão
# anterior e por isso não tem marcador. Precisa ser o contêiner do projeto
# DESTA instalação — daí o nome derivado da pasta, e não um nome qualquer.
R2B="$WORK/caso2b"; mkdir -p "$R2B"; montar_pasta "$R2B/deskcommcrm" 0
guarda "$R2B" aarch64 "$(nome_derivado "$R2B/deskcommcrm")"
guarda "$R2B" riscv64 "$(nome_derivado "$R2B/deskcommcrm")"
check "contêiner do projeto DESTA instalação → é real" test "$VEREDITO" = REAL
check "e aarch64 passa pela guarda (rc=$RC, e == 0)" test "$RC" -eq 0
check "e riscv64 passa pela guarda (rc=$RC, e == 0)" test "$RC" -eq 0
check "com o aviso de instalação existente" grep -q 'JÁ EXISTE' "$WORK/guarda.out"
# 2c. O contêiner do Supabase single-server, que o modo single-server cria com
# o sufixo `-supabase` no nome do projeto. Sem este sinal, quem instalou em
# single-server passaria a ser recusado numa VPS ARM — uma regressão nova.
# single-server passaria a ser recusado numa VPS com arquitetura sem suporte — uma regressão nova.
R2C="$WORK/caso2c"; mkdir -p "$R2C"; montar_pasta "$R2C/deskcommcrm" 0
guarda "$R2C" aarch64 "$(nome_derivado "$R2C/deskcommcrm")-supabase"
guarda "$R2C" riscv64 "$(nome_derivado "$R2C/deskcommcrm")-supabase"
check "contêiner do Supabase single-server → é real" test "$VEREDITO" = REAL
check "e aarch64 passa pela guarda (rc=$RC, e == 0)" test "$RC" -eq 0
check "e riscv64 passa pela guarda (rc=$RC, e == 0)" test "$RC" -eq 0
# 2d. O COMPOSE_PROJECT_NAME do `.env` manda sobre o nome derivado da pasta, e
# o guard tem de procurar pelo que os contêineres carregam de verdade. Uma
@@ -223,10 +223,10 @@ check "e aarch64 passa pela guarda (rc=$RC, e == 0)" test "$RC" -eq 0
# nomeia o projeto no `.env` justamente para não disputar o parque com a outra.
R2D="$WORK/caso2d"; mkdir -p "$R2D"; montar_pasta "$R2D/deskcommcrm" 0
printf 'COMPOSE_PROJECT_NAME=deskcommcrm-2\n' >> "$R2D/deskcommcrm/.env"
guarda "$R2D" aarch64 "deskcommcrm-2"
guarda "$R2D" riscv64 "deskcommcrm-2"
check "COMPOSE_PROJECT_NAME do .env manda sobre o nome da pasta → é real" \
test "$VEREDITO" = REAL
check "e aarch64 passa pela guarda (rc=$RC, e == 0)" test "$RC" -eq 0
check "e riscv64 passa pela guarda (rc=$RC, e == 0)" test "$RC" -eq 0
# ─────────────────────────────────────────────────────────────────────────────
echo
@@ -238,19 +238,19 @@ echo ' que não respondeu'
# projeto, qualquer VPS com Docker vira "instalação existente" e a recusa do
# #1042 deixa de existir em cima de um `docker ps` qualquer.
R3="$WORK/caso3"; mkdir -p "$R3"; montar_pasta "$R3/deskcommcrm" 0
guarda "$R3" aarch64 "wordpress_app_pljr imobplus-server-app-1 traefik"
guarda "$R3" riscv64 "wordpress_app_pljr imobplus-server-app-1 traefik"
check "contêiner de OUTRO programa na mesma VPS → a instalação NÃO é real" \
test "$VEREDITO" = NOVA
check "e aarch64 é recusada (rc=$RC, e != 0)" test "$RC" -ne 0
check "a recusa é a do #1042" grep -q 'Use uma VPS x86_64/amd64' "$WORK/guarda.out"
check "e riscv64 é recusada (rc=$RC, e != 0)" test "$RC" -ne 0
check "a recusa é a do #1042" grep -q 'Use uma VPS x86_64/amd64 ou ARM64/aarch64' "$WORK/guarda.out"
# Um `docker` que não responde (fora do PATH, daemon parado, sem permissão no
# socket) devolve vazio, e vazio é "não achei" — a resposta que manda RECUSAR.
# O contrário seria adivinhar instalação a partir de um Docker que não falou.
R3B="$WORK/caso3b"; mkdir -p "$R3B"; montar_pasta "$R3B/deskcommcrm" 0
guarda "$R3B" aarch64
guarda "$R3B" riscv64
: > "$DOCKER_LOG"
( cd "$R3B" && env -i PATH="/usr/bin:/bin" HOME="${HOME:-/root}" FAKE_ARCH=aarch64 \
( cd "$R3B" && env -i PATH="/usr/bin:/bin" HOME="${HOME:-/root}" FAKE_ARCH=riscv64 \
bash -c '. "$0"
if instalacao_real_do_kit_aqui; then echo REAL; else echo NOVA; fi' "$COMMON" \
) > "$WORK/sem-docker.txt" 2>&1
@@ -260,10 +260,10 @@ check "sem docker no PATH, a instalação NÃO é inventada" \
# Sem `.env` não há nem nome de projeto para procurar — e a pasta nem é uma
# instalação. O guard nem deve chamar o docker aqui.
R3C="$WORK/caso3c"; mkdir -p "$R3C"; montar_pasta "$R3C/deskcommcrm" 1
guarda "$R3C" aarch64 "$(nome_derivado "$R3C/deskcommcrm")"
guarda "$R3C" riscv64 "$(nome_derivado "$R3C/deskcommcrm")"
check "sem .env (e com contêiner do projeto lá) → a instalação NÃO é real" \
test "$VEREDITO" = NOVA
check "e aarch64 é recusada (rc=$RC, e != 0)" test "$RC" -ne 0
check "e riscv64 é recusada (rc=$RC, e != 0)" test "$RC" -ne 0
check "e o docker nem é chamado sem .env" docker_nao_falou
# ─────────────────────────────────────────────────────────────────────────────
@@ -359,7 +359,7 @@ check "e o painel de recuperação do install.sh também o apaga" \
check "e o marcador está no .gitignore (estado da VPS, não do repositório)" \
grep -qx '.deskcomm-instalado' "$REPO_ROOT/.gitignore"
# O update.sh também grava, com o app saudável. Depois deste conserto, uma
# instalação ARM NOVA é recusada — então toda instalação ARM que existe veio
# instalação NOVA numa arquitetura sem suporte é recusada — então toda instalação nessa arquitetura que existe veio
# de antes e NÃO tem marcador; o único sinal dela seria o contêiner, e um
# `down` sem `-v` (ou um `prune`) a faria ser recusada como nova, o mesmo
# defeito do #1775. Gravar no update fecha isso a partir da atualização