216 lines
11 KiB
Markdown
216 lines
11 KiB
Markdown
# Runbook de desenvolvimento e operação
|
|
|
|
## Desenvolvimento local
|
|
|
|
1. Copiar `.env.example` para `.env`.
|
|
2. Executar `docker compose build`.
|
|
3. Gerar a chave com `docker compose run --rm app php artisan key:generate`.
|
|
4. Iniciar com `docker compose up -d`.
|
|
5. Preparar dados com `docker compose exec app php artisan migrate --seed`.
|
|
6. Abrir o portal em `http://localhost:8000` e emails em `http://localhost:8025`.
|
|
|
|
## Diagnóstico
|
|
|
|
```bash
|
|
docker compose ps
|
|
docker compose logs --tail=100 app
|
|
docker compose logs --tail=100 queue
|
|
docker compose exec app php artisan about
|
|
docker compose exec app php artisan migrate:status
|
|
docker compose exec postgres pg_isready -U reserva -d reserva
|
|
docker compose exec redis redis-cli ping
|
|
curl --fail http://localhost:8000/health/live
|
|
curl --fail http://localhost:8000/health/ready
|
|
```
|
|
|
|
`/health/live` confirma apenas resposta HTTP. `/health/ready` verifica PostgreSQL e cache e deve ser usado pelo balanceador. Ambos omitem detalhes de serviços. Se readiness falhar, consultar os logs da aplicação e testar PostgreSQL/Redis separadamente com os comandos anteriores.
|
|
|
|
### Página em branco no desenvolvimento
|
|
|
|
Se o HTML responder mas a interface React não aparecer, confirmar que `public/hot` contém `http://localhost:5173` e não `http://0.0.0.0:5173`. Depois validar os módulos:
|
|
|
|
```bash
|
|
curl -I -H 'Origin: http://localhost:8000' http://localhost:5173/@vite/client
|
|
curl -I -H 'Origin: http://localhost:8000' http://localhost:5173/resources/js/app.tsx
|
|
```
|
|
|
|
Para executar a validação backend use `docker compose exec app composer test`, não `php artisan test` diretamente. O script isola as variáveis de teste das variáveis `local` injetadas pelo Docker Compose e evita que jobs únicos deixem locks no Redis de desenvolvimento.
|
|
|
|
Ambos devem responder com estado 200 e `Access-Control-Allow-Origin: http://localhost:8000`. Reiniciar o serviço com `docker compose restart node` se o ficheiro `public/hot` estiver desatualizado.
|
|
|
|
Confirmar também que o HTML inicial já contém conteúdo SSR:
|
|
|
|
```bash
|
|
curl -s http://localhost:8000/login | grep 'data-server-rendered="true"'
|
|
```
|
|
|
|
Se o endpoint Vite devolver um erro SSR, procurar primeiro componentes globais montados em `withApp` que chamem hooks Inertia como `usePage()`. Os elementos irmãos do argumento `app` não pertencem ao respetivo contexto; devem receber as propriedades através do segundo argumento de `withApp`.
|
|
|
|
### Login regressa ao mesmo ecrã
|
|
|
|
Um `POST /login` com resposta 302 seguido novamente de `/login` indica normalmente credenciais recusadas, não indisponibilidade do portal. Em desenvolvimento, confirmar primeiro se a base tem dados:
|
|
|
|
```bash
|
|
docker compose exec app php artisan tinker --execute="dump(App\\Models\\User::count());"
|
|
```
|
|
|
|
Se o resultado for zero e esta for a base local descartável, repor apenas os dados fictícios com:
|
|
|
|
```bash
|
|
docker compose exec app php artisan db:seed --force
|
|
```
|
|
|
|
As contas são `demo@escola.pt` e `admin@escola.pt`, ambas com a palavra-passe de desenvolvimento `password`. Nunca executar o seeder para corrigir acessos em produção; aí deve ser usado o processo administrativo de contas/recuperação.
|
|
|
|
## Atualização de código
|
|
|
|
```bash
|
|
docker compose exec app composer install
|
|
docker compose exec node npm install
|
|
docker compose exec app php artisan migrate --force
|
|
docker compose exec app php artisan optimize:clear
|
|
docker compose restart queue scheduler
|
|
```
|
|
|
|
## Integração Snipe-IT
|
|
|
|
Definir no ambiente, sem colocar tokens no repositório:
|
|
|
|
```dotenv
|
|
SNIPE_IT_URL=https://inventario.exemplo.pt
|
|
SNIPE_IT_READ_TOKEN=
|
|
SNIPE_IT_OPERATIONAL_TOKEN=
|
|
SNIPE_IT_TIMEOUT=10
|
|
SNIPE_IT_PAGE_SIZE=100
|
|
SNIPE_IT_RETRIES=3
|
|
SNIPE_IT_RETRY_DELAY_MS=250
|
|
```
|
|
|
|
O token de leitura deve pertencer a uma conta técnica com acesso apenas de leitura aos ativos. O token operacional deve ser separado e ter apenas as permissões necessárias para checkout/check-in. A aplicação acrescenta `/api/v1` automaticamente quando a URL não o contém.
|
|
|
|
Operações:
|
|
|
|
```bash
|
|
# Sincronização imediata e resultado no terminal
|
|
docker compose exec app php artisan snipe:sync-assets
|
|
|
|
# Colocar na fila
|
|
docker compose exec app php artisan snipe:sync-assets --queue
|
|
|
|
# Confirmar o agendamento horário
|
|
docker compose exec app php artisan schedule:list
|
|
```
|
|
|
|
Administradores podem testar a ligação, iniciar uma execução e consultar as últimas dez sincronizações em `http://localhost:8000/administracao/snipe-it`. Uma execução falhada mantém o último catálogo válido e não desativa registos ausentes. Consultar `sync_runs`, os logs do worker e o painel antes de repetir.
|
|
|
|
## Microsoft Entra ID
|
|
|
|
Registar uma aplicação **single tenant** do tipo Web. O redirect URI tem de corresponder exatamente ao domínio do portal e ao caminho `/auth/microsoft/callback`. Em produção é obrigatório HTTPS.
|
|
|
|
```dotenv
|
|
MICROSOFT_ENABLED=true
|
|
MICROSOFT_CLIENT_ID=
|
|
MICROSOFT_CLIENT_SECRET=
|
|
MICROSOFT_TENANT_ID=
|
|
MICROSOFT_REDIRECT_URI=https://reservas.exemplo.pt/auth/microsoft/callback
|
|
MICROSOFT_TIMEOUT=10
|
|
MICROSOFT_METADATA_CACHE_SECONDS=3600
|
|
```
|
|
|
|
Depois de aplicar a configuração e limpar a cache (`php artisan optimize:clear`):
|
|
|
|
1. entrar com a conta local;
|
|
2. abrir `Definições > Segurança` e confirmar novamente a palavra-passe;
|
|
3. escolher **Associar conta Microsoft** e concluir a autenticação institucional;
|
|
4. terminar sessão e validar **Entrar com a conta Microsoft** no login;
|
|
5. confirmar os eventos `identity.microsoft_linked` e `identity.microsoft_login` em auditoria.
|
|
|
|
O portal não associa contas por email. Uma identidade não associada recebe uma instrução para entrar localmente e fazer a associação explícita. Remover a associação também exige confirmação recente da palavra-passe e cria `identity.microsoft_unlinked`. Nunca copiar códigos ou tokens para tickets/logs.
|
|
|
|
## Entregas, devoluções e reconciliação
|
|
|
|
Utilizadores com perfil `inventory_operator` ou `administrator` acedem a `http://localhost:8000/operacoes`. A fila inclui as reservas do dia e todos os processos entregues, atrasados, devolvidos ou com incidente ainda pendente. O campo inicial aceita referência, nome, email, etiqueta, ID Snipe-IT ou a saída de um leitor QR/código de barras configurado como teclado.
|
|
|
|
Procedimento de entrega:
|
|
|
|
1. preparar a requisição;
|
|
2. confirmar fisicamente todos os equipamentos e o estado à saída;
|
|
3. confirmar o ID do destinatário no Snipe-IT;
|
|
4. registar a entrega e verificar a mensagem de sucesso;
|
|
5. se existir erro, não voltar a submeter antes de consultar o estado do ativo no Snipe-IT.
|
|
|
|
Procedimento de devolução:
|
|
|
|
1. localizar a requisição ou ler a etiqueta;
|
|
2. verificar equipamento, acessórios, danos e localização;
|
|
3. registar a devolução;
|
|
4. criar um incidente e bloqueio temporal quando o ativo não deva voltar ao catálogo;
|
|
5. concluir o processo apenas depois de todos os itens estarem devolvidos.
|
|
|
|
Uma operação parcial fica em `incident`. Consultar `handover_records` para movimentos confirmados e os eventos `reservation.delivery_failed`/`reservation.return_failed` em `audit_logs`. Comparar cada item com o estado atual no Snipe-IT; corrigir apenas os itens ainda `ready` ou `delivered`. Não alterar estados diretamente na base de dados nem repetir em bloco, porque as escritas externas são deliberadamente não idempotentes e não têm retry automático.
|
|
|
|
## Ciclo de vida das pré-reservas
|
|
|
|
As requisições pendentes têm, por defeito, um prazo de decisão de 48 horas. O scheduler coloca a expiração na fila a cada cinco minutos. Para operar ou diagnosticar manualmente:
|
|
|
|
```bash
|
|
# Expirar imediatamente os pedidos vencidos
|
|
docker compose exec app php artisan reservations:expire
|
|
|
|
# Colocar a operação na fila
|
|
docker compose exec app php artisan reservations:expire --queue
|
|
|
|
# Confirmar ambos os agendamentos
|
|
docker compose exec app php artisan schedule:list
|
|
```
|
|
|
|
Uma expiração ou cancelamento muda itens `held`, `reserved`, `ready` e `delivered` para `cancelled`, libertando o período. Confirmar os eventos `reservation.expired` ou `reservation.cancelled` em `audit_logs` quando existir uma divergência operacional.
|
|
|
|
## Aprovações
|
|
|
|
Utilizadores com perfil `approver` ou `administrator` acedem a `http://localhost:8000/aprovacoes`. Uma decisão válida deve criar uma linha em `approval_actions` e outra em `audit_logs`. Se a aprovação indicar que a disponibilidade mudou, confirmar primeiro a atribuição/estado sincronizado do ativo e conflitos entretanto criados; não alterar o estado diretamente na base de dados.
|
|
|
|
As decisões geram jobs de notificação. Em desenvolvimento, confirmar o email em `http://localhost:8025` e a notificação interna em `http://localhost:8000/notificacoes`. Se uma mensagem não chegar, consultar primeiro `docker compose logs --tail=100 queue` e a tabela `failed_jobs`; não repetir a decisão já confirmada.
|
|
|
|
## Administração e relatórios
|
|
|
|
Administradores usam:
|
|
|
|
- `http://localhost:8000/administracao/utilizadores` para contas, perfis e IDs Snipe-IT;
|
|
- `http://localhost:8000/administracao/regras` para políticas aplicadas a novos pedidos;
|
|
- `http://localhost:8000/administracao/notificacoes/modelos` para assuntos e mensagens;
|
|
- `http://localhost:8000/administracao/snipe-it` para diagnóstico da integração.
|
|
|
|
As alterações são registadas em `audit_logs`. A margem de uma reserva já submetida não muda quando a política é alterada; confirmar `buffer_starts_at` e `buffer_ends_at` do item ao investigar um conflito.
|
|
|
|
Administradores e auditores consultam `http://localhost:8000/relatorios` e `http://localhost:8000/auditoria`. O CSV usa `;`, UTF-8 com BOM e neutralização de fórmulas para abertura segura em aplicações de folha de cálculo. Cada pedido está limitado a 366 dias; para análises plurianuais, exportar períodos separados. Aprovadores, operadores, administradores e auditores consultam o planeamento consolidado em `http://localhost:8000/calendario-global`.
|
|
|
|
## Produção
|
|
|
|
Antes de disponibilizar:
|
|
|
|
- definir `APP_ENV=production`, `APP_DEBUG=false` e HTTPS;
|
|
- substituir todas as palavras-passe do exemplo;
|
|
- montar segredos fora do repositório;
|
|
- remover exposição pública das portas PostgreSQL, Vite e Mailpit;
|
|
- configurar volumes persistentes e backups cifrados;
|
|
- executar `php artisan optimize` e frontend compilado;
|
|
- definir health checks, alertas e rotação de logs;
|
|
- testar recuperação de backup e falha Snipe-IT.
|
|
|
|
## Backups
|
|
|
|
Salvaguardar diariamente PostgreSQL e uploads. Manter retenção semanal/mensal e uma cópia fora do servidor. Segredos são salvaguardados através de procedimento separado. Executar um teste de restauro periódico e registar o resultado.
|
|
|
|
```bash
|
|
# Cria dump custom, valida-o e acrescenta checksum SHA-256
|
|
ops/backup-postgres.sh /caminho/cifrado/backups
|
|
|
|
# Restaura numa base temporária isolada e elimina-a no fim
|
|
ops/test-restore-postgres.sh /caminho/cifrado/backups/reserva-AAAAMMDDTHHMMSSZ.dump
|
|
```
|
|
|
|
Os scripts obtêm o nome da base e utilizador diretamente do contentor PostgreSQL, recusam identificadores inesperados e nunca restauram sobre a base principal. O destino tem de estar cifrado e deve ser replicado para fora do servidor. Monitorizar a execução e a idade do último ficheiro; um dump sem ensaio de restauro não conta como recuperação validada.
|
|
|
|
Usar [docs/PRODUCTION_CHECKLIST.md](PRODUCTION_CHECKLIST.md) para registar responsáveis e evidências antes do piloto.
|