# Deploy da telemetria na VPS (jogo.xcool.cc)

O receptor recebe os eventos dos jogos publicados em `https://jogo.xcool.cc/midia/previa/<jogo>/` e das tarefas,
grava em jsonl e serve o painel. Tem um arquivo só, `telemetria_receptor.py`, usa só a biblioteca padrão do Python 3.7+
e escuta apenas em `127.0.0.1`, atrás do nginx. Os mocks locais (`servidor.py`) importam o mesmo arquivo, então a
validação é uma só.

As rotas ficam sob o prefixo `/midia/telemetria`, e o nginx repassa a URI sem cortar:

| Rota | Quem acessa | O que faz |
|---|---|---|
| `POST /midia/telemetria` | público (a página do jogo) | recebe o lote `{eventos:[…]}`, com até 512 KB e 1000 eventos |
| `GET /midia/telemetria/telemetria.js` | público | o componente da página |
| `GET /midia/telemetria/painel/` | basic auth | o painel (abas Tarefas e Jogos) |
| `GET /midia/telemetria/dados/…` | basic auth | os dados do painel (`lista`, `eventos?tarefa=` / `eventos?jogo=`) |

A porta abaixo é **8790**. Antes de usar, confira que ela está livre na VPS com `ss -ltn | grep 8790`. Se não
estiver, troque nos três lugares: unit, nginx e conferência.

## 1. Arquivos

```bash
sudo mkdir -p /opt/xcool-telemetria/painel
# da máquina de desenvolvimento (llm-local/):
scp telemetria_receptor.py telemetria.js  <vps>:/tmp/
scp painel/index.html                     <vps>:/tmp/painel-index.html
# na VPS:
sudo install -m 644 /tmp/telemetria_receptor.py /tmp/telemetria.js /opt/xcool-telemetria/
sudo install -m 644 /tmp/painel-index.html /opt/xcool-telemetria/painel/index.html
```

As páginas `painel/demo*.html` são da máquina local e **não** vão para a VPS, porque apontam para `/_llm/`.
Os dados ficam em `/var/lib/private/xcool-telemetria/` (com `DynamicUser=yes`, `/var/lib/xcool-telemetria` é só um symlink para lá): `<tarefa>.jsonl` e `jogos/<jogo>.jsonl`. Quem cria esse diretório é o systemd. Use sempre o caminho real: o logrotate não age sobre symlink.

## 2. Unit systemd — `/etc/systemd/system/xcool-telemetria.service`

```ini
[Unit]
Description=X|COOL telemetria (receptor + painel)
After=network.target

[Service]
ExecStart=/usr/bin/python3 /opt/xcool-telemetria/telemetria_receptor.py --dados /var/lib/xcool-telemetria --porta 8790 --prefixo /midia/telemetria
DynamicUser=yes
StateDirectory=xcool-telemetria
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
Restart=on-failure
RestartSec=2

[Install]
WantedBy=multi-user.target
```

```bash
sudo systemctl daemon-reload
sudo systemctl enable --now xcool-telemetria
systemctl status xcool-telemetria --no-pager
journalctl -u xcool-telemetria -n 5 --no-pager   # "telemetria: /var/lib/xcool-telemetria on http://127.0.0.1:8790/midia/telemetria"
```

Com `StateDirectory`, o diretório `/var/lib/xcool-telemetria` passa a pertencer ao usuário dinâmico do serviço.
O log do serviço vai para o journald, que já o rotaciona, e não leva o endereço do cliente (`CDC-001`).

## 3. nginx

Siga a regra da VPS: edite o vhost em `sites-available/`. `sites-enabled/` é symlink versionado, e um backup deixado
ali vira vhost ativo.

**Contexto `http`**, num arquivo novo `/etc/nginx/conf.d/xcool-telemetria.conf`:

```nginx
limit_req_zone $binary_remote_addr zone=xcool_tel:10m rate=5r/s;
```

**Dentro do `server { … }` de `jogo.xcool.cc`** (https), junto das outras `location /midia/…`:

```nginx
# Recebe os eventos. Só POST (sendBeacon e fetch usam POST). Sem access log: nenhum IP guardado junto da telemetria (CDC-001).
location = /midia/telemetria {
    limit_except POST { deny all; }
    limit_req zone=xcool_tel burst=40 nodelay;
    client_max_body_size 512k;
    access_log off;
    proxy_pass http://127.0.0.1:8790;
    proxy_set_header Host $host;
}

# O componente da página: público.
location = /midia/telemetria/telemetria.js {
    proxy_pass http://127.0.0.1:8790;
}

# Painel e dados: basic auth, só leitura.
location ^~ /midia/telemetria/ {
    auth_basic "Telemetria X|COOL";
    auth_basic_user_file /etc/nginx/xcool-telemetria.htpasswd;
    limit_except GET { deny all; }
    proxy_pass http://127.0.0.1:8790;
    proxy_set_header Host $host;
}
```

O `proxy_pass` vai **sem** URI de propósito: assim o nginx repassa `/midia/telemetria…` inteiro, e o receptor foi
iniciado com `--prefixo /midia/telemetria`. O `=` e o `^~` ganham da `location /midia/` que já existir e de qualquer
`location ~ \.js$`.

Senha do painel:

```bash
printf 'oda:%s\n' "$(openssl passwd -apr1)" | sudo tee /etc/nginx/xcool-telemetria.htpasswd >/dev/null
sudo chmod 640 /etc/nginx/xcool-telemetria.htpasswd && sudo chown root:www-data /etc/nginx/xcool-telemetria.htpasswd
sudo nginx -t && sudo systemctl reload nginx
```

(O grupo é o do usuário do nginx na VPS; troque `www-data` se lá for outro.)

## 4. Rotação dos dados — `/etc/logrotate.d/xcool-telemetria`

```
/var/lib/private/xcool-telemetria/*.jsonl /var/lib/private/xcool-telemetria/jogos/*.jsonl {
    monthly
    rotate 12
    compress
    delaycompress
    missingok
    notifempty
    nocreate
}
```

O receptor abre o arquivo a cada lote, então renomear é seguro: o lote seguinte recria o arquivo, e não é preciso
`copytruncate` nem reiniciar. O painel lê só o arquivo corrente, ou seja, o mês em curso. Os meses anteriores ficam em
`*.jsonl.1`, `*.jsonl.2.gz`… para análise. Além disso, o receptor recusa lotes num arquivo que passou de 200 MB,
o que protege o disco num endpoint público.

## 5. Conferir

```bash
TS=$(date +%s%3N)
# válido → {"ok": true, "gravados": 1, "recusados": []}
curl -s -X POST https://jogo.xcool.cc/midia/telemetria -H 'Content-Type: application/json' \
  -d "{\"eventos\":[{\"v\":1,\"tipo\":\"jogo.inicio\",\"ts\":$TS,\"t_ms\":0,\"sessao\":\"sdeploy1\",\"encontro\":\"pdeploy1\",\"jogo\":\"conferencia\",\"carga\":{\"fase\":1}}]}"; echo
# inválido (jogo com tokens) → 400 {"ok": false, "gravados": 0, "recusados": [{"i": 0, "erro": "minigame measures no skill and grants no tokens (…): tokens"}]}
curl -s -X POST https://jogo.xcool.cc/midia/telemetria -H 'Content-Type: application/json' -w ' %{http_code}\n' \
  -d "{\"eventos\":[{\"v\":1,\"tipo\":\"jogo.fim\",\"ts\":$TS,\"t_ms\":9,\"sessao\":\"sdeploy1\",\"encontro\":\"pdeploy1\",\"jogo\":\"conferencia\",\"carga\":{\"motivo\":\"ganhou\",\"tokens\":5}}]}"
# corpo grande → 413
head -c 600000 /dev/zero | curl -s -o /dev/null -w '%{http_code}\n' -X POST https://jogo.xcool.cc/midia/telemetria -H 'Content-Type: application/json' --data-binary @-
# componente público → 200 ; painel sem senha → 401 ; com senha → 200 ; GET no endpoint de POST → 403
curl -s -o /dev/null -w '%{http_code}\n' https://jogo.xcool.cc/midia/telemetria/telemetria.js
curl -s -o /dev/null -w '%{http_code}\n' https://jogo.xcool.cc/midia/telemetria/painel/
curl -s -o /dev/null -w '%{http_code}\n' -u oda https://jogo.xcool.cc/midia/telemetria/painel/
curl -s -o /dev/null -w '%{http_code}\n' https://jogo.xcool.cc/midia/telemetria
# o arquivo do evento válido
sudo tail -1 /var/lib/private/xcool-telemetria/jogos/conferencia.jsonl
```

Depois de conferir, apague o arquivo de teste: `sudo rm /var/lib/private/xcool-telemetria/jogos/conferencia.jsonl`.

## 6. Nos jogos (sessão games4)

```html
<script src="/midia/telemetria/telemetria.js" data-endpoint="/midia/telemetria"></script>
```

O `data-endpoint` (ou `Tele.config({endpoint: "/midia/telemetria"})`) manda os lotes para a mesma origem, em https e
sem CORS. As chamadas `Jogo.*` estão em `LEIA-ME.txt`, seção TELEMETRIA.

## Atualizar

Copie de novo os três arquivos do passo 1 e rode `sudo systemctl restart xcool-telemetria`. Os dados não mudam.
