---
name: sshp-deploy
description: Pública e atualiza a aplicação no servidor, e volta versão quando algo quebra. Detecta a estratégia de envio, pública em versão nova sem tocar na anterior, troca a versão ativa de forma atômica, verifica se a aplicação respondeu e oferece o retorno imediato. Use com "/sshp-deploy", "publicar", "subir a aplicação", "fazer deploy", "voltar versão", "rollback", "quebrou depois que eu publiquei".
license: MIT
compatibility: Claude Code, Codex, Cursor, Gemini CLI e demais agentes compativeis com Agent Skills.
metadata:
  author: sandeco
  version: "0.0.10"
  framework: sshepherd
  role: deploy
---

Você pública a aplicação e garante que dá para voltar atrás. Esta é a operação que a pessoa vai repetir toda semana, então ela precisa ser rápida de disparar é impossível de errar em silêncio.

## Pré-condição

O perfil precisa ter passado pelo provisionamento (chave `provision`). Sem `releases/`, `shared/` e serviço configurado, não ha onde publicar. Mande para `/sshp-provision`.

## Escolher a estratégia

| Situacao do projeto | Estratégia | Por que |
|---|---|---|
| Repositorio com origem remota acessivel | `git` | o servidor puxa direto, envio mínimo pela rede |
| Sem repositorio, ou repositorio privado sem chave no servidor | `rsync` | envia os arquivos da máquina local |
| `docker-compose.yml` na raiz | `compose` | envia e sobe os contêineres |

Verifique se a ferramenta existe na máquina local antes de escolher. `rsync` ausente elimina duas das três estratégias.

Mostre a escolha com a evidência, e permita corrigir.

## O que sobe e o que não sobe

Isso mora em `.sshepherdignore`, na raiz do projeto, e foi decidido no Dia Zero (`/sshp-setup`, estágio 8). Uma regra por linha, formato do rsync.

**Se o arquivo não existir, o script recusa publicar** (código 12). Não contorne com `--no-ignore-file`: mande para `/sshp-setup` montar o arquivo. Publicar a pasta inteira é como o material interno da pessoa vira página pública.

Três coisas para saber:

- **Só vale para `rsync` e `compose`.** Na estratégia `git` o servidor clona do repositório, então quem decide é o `.gitignore`. São arquivos diferentes e é comum divergirem.
- **Existe um piso de segurança no script**, aplicado sempre, que nenhuma edição do arquivo derruba: `.env`, `.env.*`, `.git/`, `.sshepherd/`, `*.pem`, `*.key`, `id_rsa`, `id_ed25519`.
- **As chaves vivem no cofre do servidor** e chegam à aplicação pelo systemd. Não existe `.env` na release, nem como link. Nada sobe da máquina local, e está certo assim. Cadastro e conferência são o `/sshp-secrets`. (Servidor por compose, ou provisionado antes do cofre: o link para `shared/.env` continua, como sempre foi.)
- **Na estratégia `git` o script confere duas vezes** se o repositório rastreia `.env`, `.env.*`, chave ou certificado: na cópia local antes de conectar, e no clone que chegou ao servidor. Se rastreia, recusa (código 12) e apaga a release. Não contorne: o segredo que está no histórico continua lá mesmo depois de apagado, e precisa ser trocado.

Quando a pessoa disser que um arquivo "sumiu" do servidor, olhe aqui primeiro. Costuma ser uma regra ampla demais — `*.json` ou `/data/` — pegando algo que a aplicação precisa.

## Antes de publicar

Três verificações que evitam surpresa:

1. **As regras de envio.** Leia o `.sshepherdignore` e mostre o resumo do que fica de fora. Se o projeto ganhou pasta nova desde o Dia Zero e ela não está classificada, pergunte antes de subir.
2. **Alteração não registrada no repositorio**, quando a estratégia e `git`: o servidor vai puxar do remoto, então o que esta só na máquina local **não vai subir**. Liste os arquivos pendentes e confirme antes de seguir.
3. **Diferença em relacao ao que está no ar**: diga o que muda desde a última publicação. Se der para saber pela revisão do repositorio, use.

Na estratégia `git`, some a essas uma checagem que ninguém faz e todo mundo devia: rode `git ls-files` e procure por `.reversa/`, `_reversa_*/`, `.claude/`, `.agents/`, `.specify/`, `.env`. Se qualquer um estiver versionado, **vai para o servidor**, e o `.sshepherdignore` não impede. Avise antes de publicar, não depois.

## Publicar

```
bash .sshepherd/scripts/sh/deploy.sh \
  --host <ip> --user deploy --key <chave> \
  --strategy <git|rsync|compose> [--repo <url>] [--branch main] [--src .] \
  --app-path /srv/app [--build "npm ci --omit=dev"] \
  [--shared-dir storage] [--shared-dir .next/cache] \
  [--ignore-file .sshepherdignore] \
  --health http://127.0.0.1/ --keep 5 --confirmed
```

O que acontece, em ordem:

1. **Regras de envio** carregadas do `.sshepherdignore`, somadas ao piso de segurança. Sem o arquivo, para aqui.
2. **Trava** contra publicação simultanea. Duas sessões publicando ao mesmo tempo e receita de estado quebrado.
3. **Espaço em disco** conferido antes de enviar. Menos de 200 MB livres interrompe.
4. **Envio** para `releases/<carimbo de data>`. A versão em uso não e tocada em nenhum momento.
5. **Compartilhados ligados**: `logs/` e cada `--shared-dir` apontam para a pasta que sobrevive a todas as versões. Chaves vêm do cofre, pelo systemd.
6. **Construção**, quando o projeto declara uma. Roda como `deploy`, sem acesso ao cofre, e a saída chega redigida. Se falhar aqui, **a versão em uso continua intacta** é a publicação para.
7. **Permissões**: o código fica legível e executável para o usuário de serviço, e sem escrita para ele. O que a aplicação escreve mora em `shared/`.
8. **Troca do atalho**: o `current` passa a apontar para a versão nova de uma vez só. Não existe um instante sequer em que ele aponte para o vazio — é isso que evita o visitante pegar o site pela metade.
9. **Reinício** da aplicação.
10. **Verificação de saude**: até 6 tentativas, 5 segundos de intervalo. Serviço ativo não e prova de que a aplicação responde.
11. **O cofre que subiu vira "o último que funcionou"**: é para ele que o `/sshp-secrets` volta se uma chave nova derrubar a aplicação.
12. **Limpeza** das versões antigas acima do limite. A versão ativa nunca e removida.

### Pastas que a aplicação escreve

Rust compila no servidor, numa pasta de compilação fora das releases, e o binário vira `app` na raiz da release (o serviço roda `current/app`). Troque `<binario>` pelo nome em `[[bin]]` ou `[package] name` do `Cargo.toml`:

```
--build "cargo build --release --locked --target-dir /srv/app/build-cache && install -m 755 /srv/app/build-cache/release/<binario> app"
```

A aplicação não escreve na release: `storage/` do Laravel, `.next/cache` do Next.js, `uploads/`, cache de imagens. Cada uma dessas é declarada com `--shared-dir <pasta>` e vira um link para `shared/<pasta>`, que sobrevive às versões e é gravável pelo usuário de serviço. Descubra pela stack, na primeira publicação, e grave a lista no perfil (`shared_dirs`) para repetir nas próximas. Se a aplicação reclamar de `EACCES` ou `Permission denied` ao escrever, é uma pasta que faltou declarar.

Build que precisa de segredo (ex.: Next.js com chamada de API em tempo de build) não funciona neste desenho: o build roda sem o cofre, de propósito. Diga isso antes de tentar e sugira mover a chamada para o tempo de execução.

## Quando a saude falha

Isso é o cenário que justifica o framework inteiro. Faça nesta ordem, sem enrolar:

1. Mostre as últimas linhas do log do serviço, como o script entregou (ele já redige segredo; você não pede nem mostra nada além disso).
2. Diga qual era a versão anterior.
3. **Ofereça o rollback como primeira ação**, com o comando pronto.

> A aplicação não respondeu depois da publicação. Últimas linhas do log:
>
> ```
> <saída do script>
> ```
>
> A versão anterior (`<id>`) continua no servidor, intacta. Quer voltar para ela agora? Leva menos de 30 segundos. A gente investiga com calma depois, com o site no ar.

Não tente consertar antes de oferecer o rollback. Site no ar primeiro, diagnóstico depois.

## Voltar versão

```
bash .sshepherd/scripts/sh/rollback.sh \
  --host <ip> --user deploy --key <chave> \
  --app-path /srv/app [--to <release>] --confirmed
```

Sem `--to`, volta para a imediatamente anterior. Com `--to`, para a versão indicada.

O script muda o atalho de volta, reinicia e confere se respondeu. Como a versão antiga nunca saiu do servidor, o caminho todo leva segundos — não tem nada para enviar de novo.

**Se o rollback aplicar mas a aplicação continuar sem responder**, isso e informação valiosa: a causa não era a publicação. Mande para `/sshp-doctor`.

## Sempre mostre o caminho de volta

Mesmo quando o deploy da certo, termine com o comando de rollback visivel:

> Publicado: `20260805143000`. Saude confirmada.
>
> Se aparecer algum problema nas próximas horas, voltar é um comando:
> `/sshp-deploy` e peça rollback.

A pessoa precisa saber que pode voltar **antes** de precisar voltar. E isso que tira o medo de publicar.

## O que este componente não faz

- **Não executa migração de banco de dados**, e não reverte migração no rollback. Reverter mudança de esquema não tem solução genérica segura. Se a aplicação tem migração, avise que ela e responsabilidade da pessoa e que o rollback do código não desfaz o banco.
- **Não faz publicação sem interrupcao** com dois ambientes paralelos. A janela é o tempo do reinício, poucos segundos.
- **Não pública em vários servidores** de forma coordenada.

## Primeira publicação

Não existe versão anterior. Diga isso de forma explicita: se algo quebrar, não ha para onde voltar, só para frente. A partir da segunda, o rollback existe.
