Rodando seu próprio retransmissor

O retransmissor leva os conteúdos criptografados entre os seus dispositivos. Ele nunca vê nada além de um hash de sala e texto cifrado, e não guarda nada — mas você não precisa acreditar nisso, porque pode rodar o seu. É um arquivo Python, sem banco de dados.

Rode

docker run -p 8000:8000 ghcr.io/akshaynikhare/realtimeclipboard-relay

Ou a partir de um clone, sem contêiner nenhum:

pip install -r backend/requirements.txt
python -m uvicorn main:app --app-dir backend --port 8000

É o retransmissor inteiro. Sem banco de dados, sem volume, sem passo de migração e sem nada para fazer backup — cada sessão vive na memória e some quando o processo termina, o que é o projeto e não uma limitação.

Confira se funcionou

curl http://localhost:8000/health

Ele responde com o identificador da instância. Esse campo não é enfeite: é assim que um cliente percebe que foi balanceado para outra réplica no meio da sessão, que é a falha com a qual este projeto precisa tomar cuidado — veja a seção sobre réplicas mais abaixo.

Aponte o aplicativo para ele

  1. Abra o aplicativo e vá em Configurações → Retransmissor.
  2. Digite a URL do seu retransmissor — ws://localhost:8000 para um teste local, wss://clip.example.com para valer.
  3. A barra de status mostra Conectado assim que ele responder. Faça o mesmo num segundo dispositivo e sincronize um conteúdo entre os dois; nada passou pelo retransmissor público.

O cliente de linha de comando aceita --relay ou REALTIMECLIPBOARD_RELAY para a mesma coisa.

TLS e um nome de domínio de verdade

cd deploy
REALTIMECLIPBOARD_DOMAIN=clip.example.com docker compose up -d

O diretório deploy tem um arquivo compose que põe o Caddy na frente do retransmissor e obtém o certificado automaticamente, e ao lado dele um chart do Helm para Kubernetes.

Na prática, TLS não é opcional. Os navegadores só expõem crypto.subtle em contexto seguro, então uma página servida por HTTP puro num endereço de rede local não consegue nem derivar uma chave — o aplicativo não tem como funcionar ali, faça o retransmissor o que fizer.

Antes de escalar

Fixe as réplicas em uma, ou dê um Redis para ele. Uma sala fica na memória do processo que a atende, então duas réplicas atrás de um balanceador round-robin colocam as duas metades de uma sessão em máquinas diferentes, e nenhuma enxerga a outra. Isso aparece como conteúdos que simplesmente não chegam, o que parece um bug de cliente e não é. O chart do Helm recusa replicaCount > 1 sem Redis exatamente por esse motivo. As notas de auto-hospedagem cobrem as variáveis de ambiente e o caminho de alta disponibilidade por inteiro.

Quando não funciona

Conectado, mas nada chega

Quase sempre mais de uma réplica sem estado compartilhado — veja acima. Compare o identificador de /health em cada dispositivo: se forem diferentes, a resposta é essa.

O aplicativo não alcança o retransmissor

Duas suspeitas. A Content-Security-Policy da própria página lista as origens de retransmissor a que ela pode se conectar, então uma cópia auto-hospedada do site estático precisa do connect-src atualizado no index.html, no app.html e em cada página dentro de src/pages/. E se você estiver usando o site hospedado com o seu próprio retransmissor, a política de lá não vai permitir — hospede as duas metades.

Conecta e cai, repetidamente

Um proxy reverso que não encaminha os upgrades de WebSocket. O cliente então cai para server-sent events mais POST e continua funcionando, mais devagar, e é por isso que isso aparece como lentidão e não como falha. Encaminhe Upgrade e Connection.

Uma sessão ociosa some

As salas são recolhidas quando ficam vazias. Isso é deliberado — um retransmissor que não guarda nada não tem nada a vazar.

Desligando

docker compose down            # or: docker rm -f <container>

Não há dado nenhum para apagar. Tudo o que o retransmissor guardou estava na memória, e era texto cifrado.

As notas operacionais completas, incluindo a tabela de variáveis de ambiente e o caminho de alta disponibilidade: docs/SELF-HOSTING.md. Todas as plataformas: o guia de instalação. Algo errado ou desatualizado aqui? Abra uma issue.