# Instalação e deploy

## 1. Requisitos

| Item | Mínimo | Observação |
|---|---|---|
| PHP | 8.1 | Extensões: `pdo_mysql`, `openssl`, `mbstring`, `json`, `fileinfo` |
| MySQL / MariaDB | 8.0 / 10.4 | `utf8mb4`, InnoDB |
| Servidor web | Apache 2.4 ou Nginx | Com TLS |
| Disco | conforme a biblioteca | Um vídeo de 1080p/60s ≈ 30–60 MB |

Opcionais: `gd` (miniaturas), Argon2id (senhas), MediaMTX ou ffmpeg (câmeras RTSP).

---

## 2. Preparar o banco

```sql
CREATE DATABASE signage CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

CREATE USER 'signage_app'@'localhost' IDENTIFIED BY 'TROQUE_POR_UMA_SENHA_FORTE';

-- Só o necessário. Nada de SUPER, FILE ou GRANT.
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX, REFERENCES
  ON signage.* TO 'signage_app'@'localhost';

FLUSH PRIVILEGES;
```

---

## 3. Enviar os arquivos

```bash
cd /var/www
# envie o conteúdo do projeto para /var/www/signage

chown -R www-data:www-data /var/www/signage
find /var/www/signage -type d -exec chmod 750 {} \;
find /var/www/signage -type f -exec chmod 640 {} \;
chmod 750 /var/www/signage/bin/*.php
```

---

## 4. Rodar o instalador

```bash
cd /var/www/signage
php bin/install.php
```

Ele verifica requisitos, cria o `.env`, gera `APP_KEY` e `URL_KEY`, importa o
schema, cria o superadministrador e ajusta os diretórios de escrita.

Se as credenciais do banco ainda não estiverem no `.env`, ele avisa e para —
edite o arquivo e rode de novo. É idempotente.

Depois, revise o `.env`:

```ini
APP_ENV=production
APP_DEBUG=false
APP_URL=https://tv.seudominio.com.br
COOKIE_SECURE=true
```

---

## 5. Servidor web

### Apache

```apache
<VirtualHost *:443>
    ServerName tv.seudominio.com.br
    DocumentRoot /var/www/signage/public

    SSLEngine on
    SSLCertificateFile      /etc/letsencrypt/live/tv.seudominio.com.br/fullchain.pem
    SSLCertificateKeyFile   /etc/letsencrypt/live/tv.seudominio.com.br/privkey.pem

    <Directory /var/www/signage/public>
        Options -Indexes -MultiViews
        AllowOverride All
        Require all granted
    </Directory>

    # Nada fora de public/ é acessível.
    <Directory /var/www/signage>
        Require all denied
    </Directory>

    ErrorLog  ${APACHE_LOG_DIR}/signage-error.log
    CustomLog ${APACHE_LOG_DIR}/signage-access.log combined
</VirtualHost>

<VirtualHost *:80>
    ServerName tv.seudominio.com.br
    Redirect permanent / https://tv.seudominio.com.br/
</VirtualHost>
```

```bash
a2enmod rewrite headers ssl
systemctl reload apache2
```

### Nginx

```nginx
server {
    listen 443 ssl http2;
    server_name tv.seudominio.com.br;
    root /var/www/signage/public;
    index index.php;

    ssl_certificate     /etc/letsencrypt/live/tv.seudominio.com.br/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/tv.seudominio.com.br/privkey.pem;

    client_max_body_size 512M;   # acompanhe o UPLOAD_MAX_MB

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;

        # Uploads e vídeos grandes precisam de folga.
        fastcgi_read_timeout 600;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
    }

    # X-Accel-Redirect: o Nginx entrega o arquivo, o PHP sai do caminho.
    # Requer ajuste no MediaController; opcional, mas recomendado com muitas TVs.
    location /protected/ {
        internal;
        alias /var/www/signage/storage/uploads/;
    }

    location ~ /\.(env|git) { deny all; }
    location ~ \.(sql|log|md)$ { deny all; }

    # Assets versionados podem cachear; o HTML nunca.
    location ~* \.(css|js|png|jpg|jpeg|gif|webp|svg|woff2?)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    access_log /var/log/nginx/signage-access.log;
    error_log  /var/log/nginx/signage-error.log;
}

server {
    listen 80;
    server_name tv.seudominio.com.br;
    return 301 https://$host$request_uri;
}
```

---

## 6. Ajustes do PHP

```ini
; /etc/php/8.2/fpm/conf.d/99-signage.ini

upload_max_filesize = 512M
post_max_size       = 528M
memory_limit        = 256M
max_execution_time  = 600
max_input_time      = 600

expose_php   = Off
display_errors = Off
log_errors     = On

; OPcache — diferença grande de desempenho com muitas TVs consultando a API
opcache.enable                  = 1
opcache.memory_consumption      = 128
opcache.max_accelerated_files   = 10000
opcache.validate_timestamps     = 0   ; produção: recarregue o PHP-FPM ao publicar
```

```bash
systemctl restart php8.2-fpm
```

---

## 7. Cron

```bash
crontab -u www-data -e
```

```cron
*/5 * * * * php /var/www/signage/bin/maintenance.php >> /var/log/signage-cron.log 2>&1
```

Sem isso o sistema funciona, mas `playback_logs` cresce sem limite —
100 TVs × 1 peça a cada 15 s ≈ 17 milhões de linhas/mês.

---

## 8. Backup

```bash
#!/bin/bash
# /usr/local/bin/signage-backup.sh
set -euo pipefail

DEST=/var/backups/signage
DATE=$(date +%F)
mkdir -p "$DEST"

# Banco
mysqldump --single-transaction --quick --routines \
  -u signage_backup -p"$MYSQL_PWD" signage | gzip > "$DEST/db-$DATE.sql.gz"

# Arquivos de mídia (incremental)
rsync -a --delete /var/www/signage/storage/uploads/ "$DEST/uploads/"

# Configuração — contém as chaves de criptografia
cp /var/www/signage/.env "$DEST/env-$DATE.bak"
chmod 600 "$DEST/env-$DATE.bak"

find "$DEST" -name 'db-*.sql.gz' -mtime +30 -delete
find "$DEST" -name 'env-*.bak'   -mtime +90 -delete
```

```cron
0 3 * * * /usr/local/bin/signage-backup.sh >> /var/log/signage-backup.log 2>&1
```

> **O `.env` no backup é crítico.** Sem a `APP_KEY`, as credenciais das câmeras
> restauradas do banco ficam ilegíveis. Guarde-o cifrado e fora do mesmo servidor.

---

## 9. Self-host dos assets (opcional, recomendado)

Por padrão o painel carrega Bootstrap do jsDelivr com SRI. Para eliminar a
dependência externa e fechar mais a CSP:

```bash
mkdir -p public/assets/vendor && cd public/assets/vendor

curl -LO https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css
curl -LO https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js
curl -L https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css \
     -o bootstrap-icons.min.css
curl -L https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/fonts/bootstrap-icons.woff2 \
     --create-dirs -o fonts/bootstrap-icons.woff2
```

Depois esvazie a variável no `.env`:

```ini
ASSET_CDN=
```

A CSP passa a ser estritamente `'self'`.

---

## 10. Verificação pós-deploy

```bash
# Devem falhar:
curl -I https://tv.seudominio.com.br/.env
curl -I https://tv.seudominio.com.br/storage/uploads/
curl -I https://tv.seudominio.com.br/database/schema.sql

# Deve redirecionar:
curl -I http://tv.seudominio.com.br/login

# Deve devolver 401:
curl -I https://tv.seudominio.com.br/api/v1/playlist

# Deve carregar a tela de pareamento:
curl -s https://tv.seudominio.com.br/player | grep -q 'Signage TV' && echo OK
```

---

## 11. Problemas comuns

| Sintoma | Causa provável | Solução |
|---|---|---|
| Tela branca | `APP_DEBUG=false` escondendo o erro | Veja `storage/logs/app-AAAA-MM-DD.log` |
| "Variável obrigatória ausente" | `.env` incompleto | Compare com `.env.example` |
| Upload falha sem mensagem | Limite do PHP menor que o do app | Alinhe `upload_max_filesize` ao `UPLOAD_MAX_MB` |
| TVs recebem 401 sempre | Header `Authorization` não repassado | Confira a regra `HTTP_AUTHORIZATION` no `.htaccess` |
| Sessão cai o tempo todo | `COOKIE_SECURE=true` sem HTTPS | Instale TLS, ou `false` **apenas** em dev |
| Vídeo não toca na TV | Codec incompatível | Recodifique em H.264 baseline/main + AAC |
| Mural não atualiza sozinho | JavaScript bloqueado pela CSP | Verifique o console; scripts precisam do nonce |
