Um Discord só nosso. Servidor auto-hospedado + aplicação web e desktop — no espírito de um servidor de Minecraft: alguém sobe o servidor, o resto entra.
O Stapp oferece canais, mensagens diretas, amizades, privacidade, bloqueio, chamadas de voz, vídeo e transmissão de tela com contas locais por servidor. Cada host controla seus próprios usuários; não existe conta Stapp global nem um serviço central recebendo credenciais.
O Stapp é distribuído como um executável único e autossuficiente para Windows e Linux. Não requer Node.js, bancos de dados externos nem ferramentas de compilação:
- Baixe o pacote pré-compilado da sua plataforma na aba Releases (
.zippara Windows ou.tar.gzpara Linux). - Extraia o conteúdo e execute o binário:
# Linux / macOS ./stapp-server # Windows (PowerShell) .\stapp-server.exe
- Bootstrapping Automático: na primeira inicialização, o Stapp gera sozinho o arquivo de configuração comentado
stapp.toml(a partir do template versionadostapp.example.toml), a pasta de dadosdata/, inicializa o banco SQLite via SQLx e serve o cliente web moderno já embutido no executável emhttp://localhost:8787.
O executável possui suporte nativo a certificados TLS via Let's Encrypt (ACME TLS-ALPN-01) e certificados manuais (.pem), dispensando proxies externos como Nginx ou Caddy:
- Edite o bloco
[tls]nostapp.tomlgerado:[tls] enabled = true port = 443 domains = ["chat.meudominio.com"] email = "admin@meudominio.com" production = true http_redirect_port = 80 # Redireciona http:// automaticamente para https://
- Ou defina as variáveis de ambiente:
STAPP_TLS_ENABLED=true,STAPP_TLS_DOMAINS=chat.meudominio.com,STAPP_TLS_HTTP_REDIRECT_PORT=80.
Você pode executar o contêiner oficial do Stapp publicado no GitHub Container Registry:
docker run -d \ --name stapp-server \ -p 8787:8787 \ -v ./data:/app/data \ ghcr.io/adeondev/stapp:latest
Para ambientes com suporte a chamadas de voz e vídeo em larga escala via SFU LiveKit:
# 1. Clone o repositório git clone https://github.com/adeondev/Stapp.git cd Stapp # 2. Configure as variáveis de ambiente cp .env.example .env # (Opcional) Edite o .env para definir STAPP_HOST_IP com o IP da sua LAN/Tailscale/VPN # 3. Inicie todos os serviços docker compose up -d
Acesse http://localhost:8787 (ou no IP configurado). O frontend web já vem compilado e servido diretamente pelo binário!
Caso queira utilizar o Caddy como proxy reverso com LiveKit:
docker compose --profile caddy up -d
O executável do Stapp conta com comandos integrados para gestão de usuários:
# Executável Standalone: ./stapp-server user add daniel ./stapp-server user list ./stapp-server user passwd daniel ./stapp-server user disable daniel ./stapp-server user enable daniel # Via Docker Compose: docker compose exec stapp-server /usr/local/bin/stapp-server user add daniel docker compose exec stapp-server /usr/local/bin/stapp-server user list docker compose exec stapp-server /usr/local/bin/stapp-server user passwd daniel # Via Cargo local: cd server cargo run -- user add daniel cargo run -- user list cargo run -- user passwd daniel
cd server
cargo runSobe em http://localhost:8787. Configurações no server/stapp.toml gerado na primeira execução — o template versionado é server/stapp.example.toml — ou via variáveis de ambiente com prefixo STAPP_.
cd web
pnpm install
pnpm devAbre em http://localhost:5173.
cd web pnpm app # Roda o app nativo Tauri apontando para o dev server pnpm app:build # Gera o instalador standalone
Dica sobre o Microfone: Acessar o cliente via navegador web remoto em HTTP simples bloqueia o microfone por restrição de segurança dos navegadores. O App Desktop (Tauri) roda em contexto seguro (
tauri://) e funciona nativamente com voz/vídeo em qualquer IP de rede local ou VPN sem precisar de HTTPS.
├── compose.yaml # Orquestração Docker do stapp-server, livekit e caddy
├── server/ # Backend em Rust (Axum + Tokio + SQLite bundled)
│ ├── Dockerfile # Build multi-stage (Node SPA + Rust Release + Debian Runtime)
│ ├── Dockerfile.release # Empacota o binário já compilado no CI (não compila nada)
│ ├── src/ # Auth HTTP, WebSocket, presença, chat, chamadas e SQLite
│ └── stapp.example.toml # Template da configuração; o stapp.toml vivo é gerado e fica fora do git
├── web/ # Frontend Vite + React + TypeScript
│ ├── src/ # UI flat sem sombras, cliente de voz e gerência de estado
│ └── src-tauri/ # Casca desktop nativa (Windows, Linux, macOS)
└── infra/
├── caddy/ # Configuração de proxy reverso e terminação TLS
└── livekit/ # Configurações e scripts para o SFU de voz e vídeo WebRTC
- Áudio Avançado e Volume Boost Real: Elementos
<audio>padrão limitam o ganho estritamente a 1.0 (100%). O Stapp utiliza uma abstração dePlaybackGraphsobre a Web Audio API conectandoMediaStreamAudioSourceNode -> GainNode -> destination, viabilizando boosts reais de até 200% por participante e no master, com nós ancorados para proteção contra garbage collection e limiter no pipeline de microfone. - Pipeline Nativo de Captura e Streaming de Tela de Alta Performance (Zero-Copy GPU): O compartilhamento de tela opera inteiramente em hardware no Windows: captura via Windows Graphics Capture (
Direct3D11CaptureFramePool), escala e conversão de cores bilinear BGRA→NV12 viaID3D11VideoProcessorna GPU (~0.1ms), codificação H.264 por hardware (NVIDIA NVENC, AMD AMF ou Intel QuickSync via Media Foundation Transform), transferência por canais IPC binários puros de 32 bytes (MAGIC_STAPpara vídeo eMAGIC_SAUDpara áudio PCM) e ingestão no frontend via WebCodecs (VideoDecoderacelerado por hardware) direto paraMediaStreamTrackGenerator, contornando canvas e blobs. Suporta 1080p60 fluido e estável com uso residual de CPU no host e sem engasgos em jogos. A arquitetura futura já está costurada para publicação nativa direta via SDK Rust do LiveKit (livekit-rust), zerando o envolvimento da WebView2 no streaming. - Navegação Segura de Links Externos (WebView2): Prevenção contra travamento e janelas em branco na WebView2 através do módulo centralizado
openExternalLink, delegando para@tauri-apps/plugin-openerno desktop nativo e mantendo fallback web comrel="noopener noreferrer". - Estabilidade de Layout e Embeds de Vídeo Seguros: Chat com layout shifts zero através de reserva de proporção de aspecto (
aspect-ratio) em imagens e skeletons, ancoragem inteligente do scroll viaResizeObservere crawler no backend Rust com proteção rigorosa contra SSRF e renderização sob demanda em iframes com sandbox estrita.
A versão 0.1.0-beta.8 é uma leva de correção: a beta.7 chegou com um conjunto de regressões que só apareceram em uso real. Cada item abaixo traz a causa raiz, não só o sintoma.
- Query de Entrega Recusada: O extrator
axum::extract::Queryusaserde_urlencoded, que só aceitatrue/falsecomo booleano. Como o próprio servidor publica?static=1e?gif=1emavatar_static_urleavatar_gif_url, toda requisição de avatar respondia400 Bad Requestantes de chegar ao handler — derrubando avatares, GIFs animados e os cards da chamada de uma vez. - Desserialização Tolerante: As flags aceitam
1/0,true/falsee chave vazia; valor irreconhecível vale como "não pediu" em vez de derrubar a resposta.
- Script Inline Bloqueado: O
<script>de tema noindex.htmlera barrado pela diretivascript-src 'self' 'wasm-unsafe-eval'dos dois lados — o middleware deserver/src/app.rse otauri.conf.json. Servida pelo próprio Stapp ou pelo app desktop, a tela abria sem tema nenhum. - Módulo Externo: O código migrou para
web/src/theme-init.ts, compilado pelo Vite e servido pela mesma origem. Nenhum script inline permanece na página.
- Segundo Processo ao Reabrir: Com o Stapp escondido na bandeja, clicar no executável novamente subia um segundo processo — duas conexões, o mesmo apelido duplicado na lista e a janela original permanecendo oculta.
tauri-plugin-single-instance: Registrado como primeiro plugin do builder. O processo duplicado apenas avisa o que já está rodando e encerra; a janela existente é restaurada tratando os três estados na ordem correta (oculta →show, minimizada →unminimize, entãoset_focus), preservando sessão e chamada em andamento.
- Reserva Vencendo Antes do Grant: A reserva de vaga durava 15s contra 60s do grant. Entre
voice.joinevoice.connectedo cliente ainda baixa olivekit-client, abre a conexão com o SFU, pede o microfone e carrega o WASM do RNNoise — em máquina fria isso ultrapassa os 15s.RESERVATION_TTLpassou a ser igual aGRANT_TTL. - Takeover Canônico: Sem reserva válida, mas com o SFU confirmando que a identidade está na sala, o servidor recusava justamente a sessão nova — a única realmente conectada — enquanto a antiga (queda abrupta, troca de aparelho) segurava a vaga. Agora a sessão nova desaloja as anteriores da conta e entra imediatamente.
- Limiar do Gate: A faixa automática ia de -60 a -30 dBFS. Quem fala em volume médio, longe do microfone ou com ganho modesto fica perto de -45: o gate não abria, o LiveKit recebia silêncio e ninguém era anunciado como falante. A faixa desceu para -75/-45 nos dois worklets, com histerese de 6 dB contra o picote em torno do limiar.
- Detecção no SFU: O padrão do LiveKit (
active_level: 35, ou -35 dBov, com 40% do intervalo) só reconhece voz projetada. A configuração passou aactive_level: 45emin_percentile: 15na stack avulsa e nocompose.yaml. - Medidor ao Vivo: O deslizante de sensibilidade ganhou o nível de entrada em tempo real com a marca do limiar no mesmo eixo — dá para ver, falando, se a voz passa do ponto em que o anel verde acende.
- Apelidos do Windows: O sistema entrega o mesmo microfone três vezes — o hardware real e os apelidos
defaultecommunications, com id e rótulo diferentes. A deduplicação passou a comparar hardware:deviceId,groupIde rótulo sem o prefixo do sistema. - Troca Presa no Dispositivo Antigo: Com
stopMicTrackOnMute: false, desligar o microfone apenas silencia a faixa, esetMicrophoneEnabled(true, opções)reusa essa faixa ignorando as opções novas. A troca passa porswitchActiveDevice, e o restart despublica de verdade antes de republicar — o que também faz valer as mudanças de supressão de ruído e cancelamento de eco.
- Selects que Não Abriam: O
PopupMenué renderizado por portal nodocument.bodycomz-index: 300, abaixo do--z-modal: 400do próprio diálogo de configurações. A lista nascia atrás do scrim: invisível e sem receber clique. Token novo--z-menu: 450, entre modal e toast. - Escape em Diálogo sobre Diálogo: Dois modais escutando
keydownna janela em captura faziam o de baixo receber o evento primeiro — fechar o de cima levava os dois junto. OOverlaymantém uma pilha e só o diálogo do topo responde ao teclado.
- Interruptores por Tipo: As notificações nativas do sistema operacional disparavam sempre, sem lugar nenhum para desligá-las. A categoria voltou às Configurações com controles independentes para chamadas, mensagens diretas e menções.
- Decisão Junto do Disparo: O interruptor é consultado dentro de
notifyIncomingCall,notifyNewDmenotifyMention, de modo que nenhuma tela nova possa esquecer de verificá-lo. A preferência é local ao aparelho, como tema e voz.
- Banner Invisível no Desktop:
banner_urlchega relativa ao servidor; usada crua, era resolvida contra a origem da página —tauri://localhostno app e:5173no dev, nunca o servidor. A imagem existia e respondia 200, mas aparecia apenas a faixa de cor. A resolução foi centralizada emresolveMediaUrl. - Enquadramento de Imagem: O servidor sempre cortou pelo centro (1:1 no avatar, 8:3 no banner), o que comia o rosto em retratos de celular. O cliente passa a enquadrar antes do envio — arrastar e aproximar — entregando a imagem já na proporção final, sem nenhum campo novo no protocolo. GIF sobe intacto, senão o avatar animado viraria imagem parada.
- Exclusividade entre Imagem e Cor: Escolher uma imagem de banner limpa a cor sólida e vice-versa. No caminho apareceram duas derivas reais:
banner_colorfaltava no espelhoweb/src/protocol.ts, e o cliente enviavaundefined— que o servidor lê como "não mexe", fazendo a cor antiga sobreviver por trás da imagem nova. - Fundo Sólido Vazando no Avatar: O contêiner pintava
--avatar-accentmesmo com foto, vazando pela borda arredondada e por qualquer transparência do PNG, além de piscar um retângulo colorido durante o carregamento. Com imagem, o token passa atransparent.
- Dois Botões de Revelar Senha: O Edge e a WebView2 desenham um olho e um "x" próprios dentro de
input[type=password]. Ao lado do controle do Stapp viravam dois olhos no mesmo campo, um deles sem rótulo acessível. Os nativos foram suprimidos; o controle da aplicação permanece.
A versão 0.1.0-beta.7 consolida um salto significativo em escalabilidade de infraestrutura, fidelidade de áudio, ergonomia de chat e integração com o sistema operacional desktop:
- Suporte Nativo a S3/R2: Armazenamento distribuído de anexos, avatares e mídias compatível com AWS S3, Cloudflare R2 e MinIO, configurável via bloco
[storage]nostapp.tomlou variáveis de ambienteSTAPP_STORAGE_*. - Fallback Local Transparente: Em ambientes sem configuração de nuvem, o servidor utiliza automaticamente o armazenamento local em disco com expiração de uploads órfãos.
- URLs Pré-assinadas: Downloads e uploads diretos eliminam gargalos de banda e I/O no servidor central.
- Distribuição GHCR: Pipeline de CI/CD rápida com publicação de imagens multi-arquitetura diretamente no GitHub Container Registry (
ghcr.io/adeondev/stapp).
- Singleton AudioContext (BC-2): Ciclo de vida unificado para todos os grafos de reprodução e efeitos, impedindo esgotamento de contextos do navegador e dessincronização de relógios de amostragem.
- Máquina de Estados de Chamada Aprimorada: Toque unificado (
ringing) sincronizado entre remetente e destinatário, encerramento confiável dos loops sonoros e notificação persistente de chamadas perdidas. - Troca de Microfone Dinâmica e VAD Estável: Alternância a quente de dispositivos de entrada sem necessidade de reiniciar a chamada, combinada com detecção de voz (VAD) ajustada contra ruídos transitórios.
- Accordions por Servidor: Lista de Mensagens Diretas agrupada por servidor de origem em seções colapsáveis, persistindo as preferências de expansão/recolhimento no
localStorage. - Menu de Contexto no Trilho: Clique com botão direito nos servidores do
ServerRailexibe menu nativo com opções para "Marcar como lido", "Copiar endereço", "Convidar amigos" e "Sair do servidor". - Inicialização Ágil (Skip Connect): Usuários com sessões pré-autenticadas e servidores ativos em cache ignoram automaticamente a tela
Connect, entrando diretamente na interface principal.
- Jumbojis (2.75rem): Mensagens contendo estritamente entre 1 e 10 emojis (Unicode ou Twemoji) são renderizadas com dimensões ampliadas (
.chat__emoji--jumbo), preservando o tamanho convencional quando mescladas a texto. - Proporção Real de Imagens: Remoção de cortes forçados em anexos visuais com cálculo intrínseco de
aspect-ratioe transparência total preservada para arquivos PNG, WebP e GIF. - Favoritos de GIFs: Nova aba dedicada no seletor de GIFs (
GifPicker), permitindo favoritar mídias frequentemente usadas com salvamento local instantâneo.
- Close to Tray no Tauri v2: Interceptação do fechamento da janela (
CloseRequested), mantendo o Stapp ativo na bandeja do sistema sem desconectar chamadas de voz ou WebSockets. - Menu da Bandeja: Acesso rápido por clique duplo ou menu de contexto da bandeja: "Abrir Stapp", "Mutar Microfone" e "Sair Definitivamente".
- Notificações Nativas do SO: Avisos do sistema operacional para menções (
@nome,@everyone), mensagens diretas e chamadas entrantes com foco automático ao clicar.
- Avatar GIF Reativo: Extração do primeiro quadro estático no upload (
avatar_static_url), ativando a reprodução animada (avatar_gif_url) exclusivamente quando o usuário fala em canais de voz. - Banners de Perfil Customizados: Suporte a cores sólidas hexadecimais e imagens de banner em 960x360, com popover rápido e atalho direto para iniciar conversa.
- 5 Temas de Cores com Persistência: Escuro (padrão), Ônix (OLED), Cinza Neutro, Claro e Personalizado (com personalização livre de fundo e cor de destaque).
MIT