# 06 — Deploy e operação

> **Cópias literais:** [`arquivos/deploy.sh`](arquivos/deploy.sh) —
> SHA-256 `1c17a307485db5638dfc1f2acd255bc384301f761e5a60bc08cff51b57402fb1`;
> [`arquivos/composer.json`](arquivos/composer.json);
> [`arquivos/env.example`](arquivos/env.example).
>
> **Fontes:** `deploy.sh`, `composer.json`, `correcao-bootstrap-cache-l2.md`,
> `WHM.md`, `correcao-php-ini-recomendado.md`.

---

## 1. Visão geral do fluxo

```
WHM (gzip, OPcache, APCu, php.ini system)
        ↓
Conta (php.ini / .user.ini, public/.htaccess)
        ↓
Deploy (código + composer + storage:link + composer prod)
        ↓
Reset de OPcache no FPM
        ↓
Validação (headers, cache, 304, compressão, formulários)
```

**Ordem importa:** não adianta publicar o app antes de o servidor estar com
OPcache/compressão configurados; e, com `validate_timestamps=0`, não adianta
publicar código sem resetar o OPcache.

---

## 2. `deploy.sh` — conteúdo literal

```bash
#!/bin/bash
set -euo pipefail

cd "$(dirname "$0")"

echo "==> Instalando dependencias (producao, sem dev)..."
composer install --no-dev --prefer-dist --no-interaction --optimize-autoloader

# .env: cria a partir do exemplo SOMENTE se nao existir (redeploy nao sobrescreve)
if [ ! -f .env ]; then
    cp -v .env.example .env
    echo "==> .env criado a partir de .env.example"
else
    echo "==> .env ja existe; pulando copia (nao sobrescreve)"
fi

# APP_KEY: gera apenas se ausente/vazia (idempotente; evita rotacionar a chave em redeploys)
if ! grep -q '^APP_KEY=[^[:space:]]' .env; then
    php artisan key:generate
    echo "==> APP_KEY gerada"
else
    echo "==> APP_KEY ja presente; pulando key:generate"
fi

echo "==> Link de storage publico (idempotente)..."
php artisan storage:link || true

echo "==> Cadeia de otimizacao de producao (composer prod)..."
composer prod

echo "==> Seed auxiliar (ExtrasSeeder)..."
php artisan db:seed --class=ExtrasSeeder
```

### 2.1 O que o script faz

| Passo | Comando | Observação |
|---|---|---|
| 1 | `composer install --no-dev --prefer-dist --no-interaction --optimize-autoloader` | Produção, sem dev. |
| 2 | copia `.env.example` → `.env` **somente se não existir** | Redeploy não sobrescreve. |
| 3 | `php artisan key:generate` **somente se `APP_KEY` ausente/vazia** | Idempotente; evita rotacionar chave. |
| 4 | `php artisan storage:link` | Idempotente (`|| true`). |
| 5 | `composer prod` | Cadeia de otimização (abaixo). |
| 6 | `php artisan db:seed --class=ExtrasSeeder` | Seed auxiliar. |

### 2.2 O que o script **não** faz

- **Não** roda reset de OPcache.
- **Não** roda `npm`/build de assets (build é commitado).
- **Não** reinicia o PHP-FPM.
- **Não** ajusta `.env` para o host real.
- **Não** valida headers/cache.
- **Não** gera/valida `composer.lock` (o repositório não o versiona; F-04).
- **Não** trata o seed como opcional: `db:seed --class=ExtrasSeeder` roda sempre.
- **Não** tem `--force` no seed (em produção o Laravel pode pedir confirmação;
  o comportamento depende da versão/prompt).

Esses passos são operacionais e precisam ser executados separadamente.

---

## 3. `composer prod` — cadeia literal

```json
"prod": [
  "composer dump-autoload --optimize --classmap-authoritative --apcu --no-dev",
  "php artisan images:redigest-webp --limit=100000",
  "php artisan cache:clear",
  "php artisan config:clear",
  "php artisan view:clear",
  "php artisan route:clear",
  "php artisan clear-compiled",
  "php artisan migrate:status",
  "php artisan migrate --force",
  "php artisan config:cache",
  "php artisan route:cache",
  "echo 'Reinicie o PHP-FPM no WHM para aplicar as atualizações'"
]
```

### 3.1 Ordem e justificativa

1. **`dump-autoload --optimize --classmap-authoritative --apcu --no-dev`** —
   classmap otimizado + APCu (se disponível) + sem dev.
2. **`images:redigest-webp`** — digestão em background do acervo de imagens.
3. **Limpeza de caches** (`cache`, `config`, `view`, `route`, `compiled`) —
   garante que os caches antigos não sobrevivam ao novo código.
4. **`migrate:status` + `migrate --force`** — aplica migrations (inclui índices
   L3.4).
5. **`config:cache` + `route:cache`** — caches de bootstrap.
6. **`echo`** — lembrete manual para reiniciar o PHP-FPM.

> **Gap crítico:** não há `opcache:clear`/`opcache_reset()`/restart. Com
> `opcache.validate_timestamps=0`, o FPM continuará servindo o código antigo até
> o restart. Ver §4.

---

## 4. OPcache no deploy — o passo que falta

### 4.1 Por que é obrigatório

No WHM, `opcache.validate_timestamps=0`. O OPcache **não** checa mtime. Sem reset,
arquivos PHP novos não são carregados.

### 4.2 Formas de reset (da mais confiável para a menos)

```bash
# 1) Reiniciar/reload do PHP-FPM (root) — confiável
whmapi1 restart_service service=php-fpm
# ou:
/scripts/restartsrv_apache_php_fpm
# ou:
service php-fpm restart

# 2) Endpoint HTTP temporário executando opcache_reset() no SAPI FPM
#    (criar, acessar via HTTPS, remover em seguida)

# 3) Comando artisan, se existir no projeto/pacote
php artisan opcache:clear

# 4) Tocar o entrypoint (não é garantido em todos os setups)
touch public/index.php
```

> ⚠️ **Nuance:** `php -r 'opcache_reset();'` no **CLI** limpa o OPcache do
> processo CLI, **não** o do FPM. Para o FPM, use restart/reload ou endpoint web.
> A documentação original lista `php -r 'opcache_reset();'` como “opção 1”; isso é
> insuficiente para o FPM.

### 4.3 Recomendação de patch no `composer prod`

Adicionar, após `route:cache` e antes do `echo`:

```json
"php -r \"if (function_exists('opcache_reset')) { opcache_reset(); }\"",
```

> Ainda assim, isso roda no CLI se o `composer prod` for executado por CLI. Para
> o FPM, o restart é necessário. Alternativa: criar um comando artisan que faça
> uma chamada HTTP autenticada ao endpoint de reset no próprio host.

### 4.4 Validação pós-reset

```bash
# No servidor: verificar que o OPcache está ativo e com os valores esperados
/opt/cpanel/ea-php72/root/usr/bin/php -i \
  | grep -E 'opcache.enable|opcache.validate_timestamps|opcache.memory_consumption'

# Na aplicação: confirmar que o código novo está no ar (ex.: canário/versão)
curl -sI https://SEU_HOST/ | grep -i x-hardening-version
```

---

## 5. `.env` de produção — checklist

O `.env.example` é a base, mas **não** serve pronto para cPanel. Ajustar:

| Variável | Valor de exemplo | Produção cPanel |
|---|---|---|
| `APP_ENV` | `production` | `production` (nunca `prod`; o `config/app.php` normaliza, mas prefira o valor canônico) |
| `APP_DEBUG` | `false` | `false` |
| `APP_URL` | `http://localhost` | `https://SEU_DOMINIO` |
| `API_HOST` | `http://localhost` | `https://SEU_DOMINIO` |
| `APP_KEY` | vazio | gerado por `php artisan key:generate` |
| `DB_CONNECTION` | `pgsql` | `pgsql` ou `mysql` conforme o servidor |
| `DB_HOST` | `postgres` (Docker) | `127.0.0.1`/host real do banco |
| `DB_DATABASE` | `prefeituras_v5` | nome real |
| `DB_USERNAME`/`DB_PASSWORD` | `postgres`/`postgres` | credenciais reais |
| `CACHE_DRIVER` | `file` | `file` (ou `redis` após validar) |
| `SESSION_DRIVER` | `file` | `file` (ou `redis` após validar) |
| `QUEUE_DRIVER` | `sync` | conforme operação |
| `PAGE_CACHE_ENABLED` | `true` | `true` |
| `PAGE_CACHE_TTL` | `3600` no exemplo | **300** recomendado (alinha ao `s-maxage`) |
| `SESSION_SECURE_COOKIE` | `true` | `true` (HTTPS) |
| `SESSION_SAME_SITE` | `lax` | `lax` |
| `APP_LOG` | `daily` | `daily` |
| `APP_LOG_MAX_FILES` | `180` | conforme retenção |
| `DEBUGBAR_ENABLED` | `false` | `false` |
| `MAIL_*` | Mailtrap | SMTP real |
| `RECAPTCHA_*` | vazio | chaves reais |
| `CDN_PURGE_ENABLED` | `false` | `false` até contratar borda |

> O `.env.example` também documenta `PHP_OPCACHE_VALIDATE_TIMESTAMPS=0`; essa
> variável é usada pelo container de dev, não pelo EA4. No cPanel, o OPcache é
> governado pelo WHM.

### 5.1 `config.json`

O `config.json` é gerado em runtime pelo save da categoria “site” no CMS. Em
instalação nova/produção virgem, ele pode não existir. O código foi corrigido para
ler com fallback (`@file_get_contents` + `env()`), então o boot não quebra. Após o
primeiro save, o arquivo passa a existir.

### 5.2 `APP_KEY`

Nunca rotacionar em produção sem necessidade: invalida sessões/dados
criptografados. O `deploy.sh` só gera se estiver ausente/vazia.

---

## 6. Permissões e ownership

Recomendação para cPanel (usuário da conta, ex.: `USUARIO`):

```bash
# Dono: usuário da conta; grupo: usuário da conta (ou nobody, conforme host)
chown -R USUARIO:USUARIO /home/USUARIO/public_html

# Diretórios 755, arquivos 644 (evitar 777)
find /home/USUARIO/public_html -type d -exec chmod 755 {} \;
find /home/USUARIO/public_html -type f -exec chmod 644 {} \;

# Graváveis pelo PHP-FPM
chmod -R u+rwX storage bootstrap/cache
```

- `storage/` e `bootstrap/cache/` precisam ser graváveis pelo usuário do PHP-FPM.
- `public/storage` deve ser symlink para `storage/app/public`
  (`php artisan storage:link`).
- Não usar `777`; se o PHP-FPM roda como outro usuário, ajustar grupo/ACL.

---

## 7. Sequência de deploy recomendada

### 7.1 Pré-deploy

- [ ] Backup do banco e do release atual.
- [ ] Confirmar `ea-php72` e OPcache no WHM.
- [ ] Confirmar compressão (Optimize Website).
- [ ] Confirmar APCu (`php -m | grep apcu`) **se** for usado; ausência não bloqueia o deploy.
- [ ] Confirmar `public/.htaccess` com `X-Hardening-Version`.
- [ ] Confirmar `.env` de produção (DB, APP_URL, APP_KEY, session).
- [ ] Rodar suíte de testes no canário (`php vendor/bin/phpunit`).

### 7.2 Deploy

```bash
# 1) Publicar código (git/rsync conforme o processo da equipe)
# 2) Dependências
composer install --no-dev --prefer-dist --no-interaction --optimize-autoloader

# 3) Storage link (idempotente)
php artisan storage:link || true

# 4) Cadeia de produção
composer prod

# 5) Reset de OPcache no FPM (obrigatório com validate_timestamps=0)
whmapi1 restart_service service=php-fpm

# 6) Se necessário, semear extras
php artisan db:seed --class=ExtrasSeeder
```

### 7.3 Pós-deploy

```bash
# Compressão
curl -sI --compressed https://SEU_HOST/ | grep -i content-encoding

# HTML público: cache + ETag + Vary + sem cookie
curl -s -D - -o /dev/null https://SEU_HOST/ \
  | grep -iE 'cache-control|etag|vary|set-cookie|x-page-cache'

# Estático: immutable + canário
curl -sI https://SEU_HOST/assets/css/app.css \
  | grep -iE 'cache-control|etag|x-hardening-version'

# 304
ETAG=$(curl -sI https://SEU_HOST/ | awk -F': ' 'tolower($1)=="etag"{print $2}' | tr -d '\r')
curl -s -o /dev/null -w '%{http_code}\n' -H "If-None-Match: $ETAG" https://SEU_HOST/

# FPC
curl -s -D - -o /dev/null https://SEU_HOST/ | grep -i x-page-cache
curl -s -D - -o /dev/null https://SEU_HOST/ | grep -i x-page-cache

# Admin: não cacheado
curl -s -D - -o /dev/null https://SEU_HOST/cms \
  | grep -iE 'cache-control|set-cookie'

# Formulários: submit sem 419 (contato/ouvidoria/newsletter)
```

---

### 7.4 CI/CD (`.gitlab-ci.yml`) — o que o pipeline faz e não faz

O snapshot tem dois jobs (`deploy_homolog` e `deploy_production`) que executam via
SSH:

```text
[1/4] git fetch/reset --hard origin/<branch>
[2/4] composer install --no-scripts
      composer dump-autoload --no-scripts -q
      php artisan package:discover
      php artisan storage:link
      php artisan view:clear
[3/4] php artisan migrate --force
      php artisan db:seed --class=ExtrasSeeder
[4/4] composer dump-autoload --no-scripts -q
      find/chmod (produção)
```

**Lacunas em relação ao `deploy.sh`/produção otimizada:**

| Item | `deploy.sh` | CI/CD |
|---|---|---|
| `composer install` com scripts | sim (default) | **`--no-scripts`** |
| `composer prod` (config/route cache, migrate, webp) | sim | **não** |
| `config:cache`/`route:cache` | via `composer prod` | **não** |
| reset de OPcache/FPM | não (manual) | **não** |
| seed opcional | não (sempre) | não (sempre) |
| `composer.lock` | não versionado | não versionado |
| `public/.htaccess` | vem do git | vem do git (ok) |
| build de assets | não (commitado) | não (commitado) |

> **Consequência:** um deploy feito **somente** pelo CI pode ficar **sem**
> `config:cache`/`route:cache` e sem reset de OPcache. Se o objetivo é a cadeia
> de produção, o job precisa rodar `composer prod` (ou os comandos equivalentes)
> e o restart do FPM. Caso contrário, documente que o CI é apenas “código +
> migrate”.
>
> O `composer install --no-scripts` também **não** executa
> `post-autoload-dump` (`package:discover`); o job chama `package:discover`
> manualmente, o que cobre parte do efeito, mas não o `dump-autoload` otimizado.
> O `dump-autoload --no-scripts` posterior também não é `--optimize`.

### 7.5 Checklist de deploy (copiar/colar)

```bash
# 0) pré-requisitos
php -v                                   # 7.2.34
composer validate --strict               # se houver lock
test -f .env || { echo "crie .env"; exit 1; }

# 1) código/deps
composer install --no-dev --prefer-dist --no-interaction --optimize-autoloader
php artisan package:discover
php artisan storage:link || true

# 2) cadeia de produção
composer prod

# 3) reset de OPcache (FPM) — obrigatório com validate_timestamps=0
whmapi1 restart_service service=php-fpm

# 4) validação
curl -s -D - -o /dev/null https://SEU_HOST/ | grep -iE 'cache-control|etag|vary|x-page-cache'
curl -sI https://SEU_HOST/assets/css/app.css | grep -iE 'cache-control|x-hardening-version'
```

---

## 8. Comandos de cache e manutenção

```bash
# Laravel
php artisan cache:clear          # cache de aplicação
php artisan config:clear         # remove bootstrap/cache/config.php
php artisan route:clear          # remove bootstrap/cache/routes.php
php artisan view:clear           # limpa views compiladas
php artisan clear-compiled       # limpa services/compiled
php artisan config:cache         # recria config cache
php artisan route:cache          # recria route cache
php artisan optimize             # L5.5: services/packages
php artisan cache:page:clear     # FPC + layout (comando do projeto)

# OPcache (FPM)
whmapi1 restart_service service=php-fpm
# ou /scripts/restartsrv_apache_php_fpm

# APCu (após rebuild do EA4)
/opt/cpanel/ea-php72/root/usr/bin/php -m | grep -i apcu
composer dump-autoload -v
```

> **Não** rodar `route:cache`/`config:cache` em desenvolvimento sem depois limpar;
> `env()` fica `null` em runtime após `config:cache`.

---

## 9. Rollback

| Camada | Ação |
|---|---|
| Código | Voltar release anterior; `composer install --no-dev`; `composer prod`; reset OPcache. |
| `.htaccess` | Restaurar versão anterior; validar `X-Hardening-Version`. |
| `php.ini` conta | Restaurar no MultiPHP INI Editor / remover `.user.ini`; reload FPM. |
| `php.ini` sistema | Restaurar valores no WHM System; reload FPM. |
| OPcache | Restaurar valores originais; reload FPM. |
| APCu | Remover/comentar `zzz-apcu.ini`; reload FPM. |
| FPC | `PAGE_CACHE_ENABLED=false` ou `php artisan cache:page:clear`. |
| CSP | `config/csp.php` → `enabled => false`; `config:cache`. |
| CDN purge | `CDN_PURGE_ENABLED=false` (default). |
| Migrations L3.4 | `php artisan migrate:rollback --step=1` (avaliar bloqueio de índices). |

**Sempre após rollback:** resetar OPcache do FPM e validar `X-Hardening-Version`.

---

## 10. Troubleshooting

| Sintoma | Causa provável | Diagnóstico/ação |
|---|---|---|
| Código novo não aparece | OPcache `validate_timestamps=0` sem reset | Restart/reload do FPM; validar. |
| HTTP 500 após editar `.htaccess` | `<IfModule mod_headers.c>` ou diretiva inválida | Remover wrapper; checar error_log. |
| Compressão ausente | `mod_deflate` no `.htaccess` (sobreposto) | Usar cPanel Optimize Website/WHM. |
| `Set-Cookie` em página pública | `CacheControl` não rodou (HEAD, rota não-Portal, status ≥400) | Testar GET real; conferir ordem do Kernel. |
| `304` não dispara | ETag forte + compressão, corpo variando por CSRF **ou CSP ligado** (nonce antes do ETag) | ETag fraco + normalização; strip de token; com CSP, corrigir F-01. |
| Admin sem estilo | `app.css` 0 bytes / build não publicado | Rodar build/commit do CSS; conferir `mix()`. |
| WebP não renderiza | Falta `AddType image/webp` | Aplicar `[L5]` no `.htaccess`. |
| `sw.js` preso | `immutable` vencendo exceção | Garantir `[L0.3b]` depois de `[L0.3]`. |
| Cache de borda baixo | Borda não configurada para HTML público | A4 Multi-CDN: `s-maxage`, sem cookie, `Vary` correto. |
| APCu sumiu após update | Rebuild do EA4 removeu `apcu.so` | Reinstalar via `pecl`; revalidar `php -m`. |
| `config:cache` quebra | `env()` fora de `config/` | Mover para `config()`; `config:clear`. |
| `route:cache` quebra | Closure em ação/after de rota | Remover Closure; usar controller/`Route::redirect`. |
| Formulário 419 | Token CSRF removido sem client-side | Confirmar `csrf-inject` + `/csrf-token`; validar submit. |
| CSP bloqueia recurso | Política/host-allowlist incompleta | Log de violações/devtools; ajustar política. |
| FPC serve conteúdo velho | TTL/invalidação | `cache:page:clear`; conferir observers. |
| `config:cache`/`route:cache` ausentes após deploy via CI | Pipeline não roda `composer prod` | Rodar `composer prod` no job ou manualmente (F-17). |
| Versões de dependências mudam entre deploys | `composer.lock` ausente | Gerar/versionar lock (F-04). |
| Imagem/logo antigos após troca no CMS | `immutable`/`Expires 1 year` em upload | Aplicar F-02 (escopar cache). |

---

## 11. Lacunas conhecidas (para backlog)

1. **Reset de OPcache não automatizado** no `deploy.sh`/`composer prod`.
2. **`deploy.sh` copia `.env.example` com `DB_HOST=postgres`**; produção precisa
   de ajuste manual/arquivo `.env` pré-existente.
3. **CSP desligada**; falta decisão sobre conteúdo runtime de libs vendor para
   enforce.
4. **APCu via pecl manual** é frágil a rebuild do EA4.
5. **`public/sitemap.xml` legado** ainda versionado; validar rewrite/remover.
6. **`Vary`** depende de vencer a camada WHM; validar em produção.
7. **`select2` sem versão no path** (regra `immutable` sem hash no nome).
8. **Redis** com informações conflitantes; só adotar após validação real.
9. **`composer.lock` ausente** e ignorado; instalações não reproduzíveis (F-04).
10. **CI/CD não roda `composer prod`** nem reset de OPcache; um deploy só pelo
    pipeline pode não ativar `config:cache`/`route:cache` (F-17).
11. **`immutable` amplo** em uploads sem hash (F-02).
12. **CSP ligado quebra ETag/FPC** (F-01); manter desligado até o patch.
