MU Custom — documentação operacional
Um servidor OpenMU (Season 6 Episode 3) jogado no navegador, com o cliente web falando o protocolo estendido do OpenMU (D15/D16).
Arquivos-fonte desta página: README.md e docs/*.md no repositório.
Estado atual
STATUS.md.| Componente | Estado | Evidência |
|---|---|---|
| OpenMU build + testes | ✅ | 8 projetos de teste, 2223 passaram, 0 falharam |
| OpenMU em execução | ✅ | CS 44406, GS 55902/04/06 (rede interna), Admin HTTP 200 (modo demo) |
| Login real | ✅ | LoginProbe e ws-login-probe (versão 20404, LoginLongPassword) → Okay |
| Camada custom | ✅ | /serverinfo ativo no Admin → Plugins |
| Stack Docker | ✅ | 6 serviços healthy; smoke test 26/26; backup → restore |
| Cliente web (Chrome), protocolo estendido | ✅ | login → lista de personagens → mundo → movimento → inventário → mover item (persistiu) → posição persistida |
| Multiplayer | ✅ | dois jogadores no mesmo mapa se veem nos dois sentidos |
| Zoom pela roda do mouse | ✅ | afasta e aproxima no jogo (patch 0001) |
| Trocar de personagem | ✅ | B13 corrigido no servidor (D19): o herói reaparece ao voltar ao mundo |
| Combate / EXP / drop | ✅ | goblins mortos, EXP gravada, morte e respawn, joia largada e recolhida |
| Comandos no chat | ✅ | lista do servidor no autocompletar (/serverinfo) |
| Personagem transformado | ✅ | /skin visto por outro jogador (dragão, esqueleto) e desfeito |
Arquitetura
Fluxos
Browser → WSS → Proxy → OpenMU
Bundle em play.*; WebSocket em ws.*/?host=openmu&port=44406. O proxy só disca alvos de ALLOW_TARGETS. O CS anuncia um IP fixo (127.127.127.127:55902); o web reconecta por openmu:55902.
Protocolo estendido
O web envia versão 20404 (build season6x): itens de 12 bits, aparência de 27 bytes (wings custom visíveis), dano/HP 32-bit, senha até 20.
Dentro do OpenMU
ConnectServer → GameServer → GameLogic (plugins, incluindo os nossos) → Persistence (EF Core) → PostgreSQL.
Mesmo mundo
Todos os jogadores web entram pelo mesmo endpoint estendido de cada GameServer. Nenhuma porta do jogo é publicada no host.
Componentes e portas
| Serviço | Imagem | Exposição |
|---|---|---|
postgres | postgres:18.6-alpine | rede interna apenas |
openmu | build próprio (OpenMU 204c11b5 + MuCustom) | nenhuma porta publicada; jogo via ws-proxy; admin 8080 via Traefik |
ws-proxy | build próprio (Babylon proxy, Bun 1.4.2) | ws.DOMAIN |
web-client | build próprio (Babylon + nginx 1.30.5) | play.DOMAIN |
docs | nginx:1.30.5-alpine | docs.DOMAIN |
reverse-proxy | traefik:v3.7.13 | 80 (local) · 80 + 443 (produção) |
| Porta | Função | Pública? |
|---|---|---|
| 80 / 443 | HTTP(S) e WSS via Traefik | sim |
| 44406 · 55902/04/06 | ConnectServer e GameServers do protocolo estendido; o web chega pelo ws-proxy | não (rede interna) |
| 44405 · 55901/03/05 · 55980 | protocolo clássico e ChatServer | não (sem uso) |
| 5432 · 8080 | PostgreSQL · Admin direto | nunca |
Instalação local
Requisitos no host: Docker (Docker Desktop com WSL 2 no Windows) + Compose v2.17+, Git, curl, bash (Git Bash no Windows).
# clonar com os upstreams (submodules)
git clone --recurse-submodules <URL_DO_REPO> mu-custom
cd mu-custom
./scripts/bootstrap-local.sh
docker compose up -d --build
./scripts/healthcheck.sh
./scripts/smoke-test.sh
| O quê | URL local | Credencial |
|---|---|---|
| Jogo (web) | http://play.localhost | test0 / test0 |
| Admin Panel | http://admin.localhost | OPENMU_ADMIN_USER / OPENMU_ADMIN_PASSWORD do .env |
| Docs | http://docs.localhost | — |
Operação diária
| Tarefa | Comando |
|---|---|
| Iniciar e esperar health | ./scripts/start.sh |
| Parar (mantém dados) | ./scripts/stop.sh |
| Reiniciar serviço | ./scripts/restart.sh openmu |
| Status e URLs | ./scripts/status.sh |
| Logs / só erros | ./scripts/logs.sh openmu · ./scripts/logs.sh openmu --errors |
| Health check (cron) | ./scripts/healthcheck.sh --quiet |
| Smoke test funcional | ./scripts/smoke-test.sh |
| Backup / restore | ./scripts/backup.sh · ./scripts/restore.sh backups/AAAA-MM-DD_HH-MM |
| Atualizar | ./scripts/update.sh |
| Mover upstream | ./scripts/bump-upstream.sh openmu|web [ref] |
| Bloquear contas de teste | ./scripts/disable-test-accounts.sh |
O que o smoke test valida
1. PostgreSQL responde; banco inicializado (contas, GameConfiguration, mapas)
2. ConnectServer / GameServer dentro da rede docker
3. portas TCP do jogo NÃO publicadas no host (D15)
4. admin/play/docs via Traefik (+ Content-Type)
5. WebSocket: upgrade 101 para openmu:44406; 403 para 44405, postgres:5432 e example.com:80 (anti relay aberto)
6. login real pelo protocolo estendido (LoginProbe, 44406)
7. login real pelo caminho WEB (WebSocket → proxy → 44406 → GameServer, versão 20404)
Protocolo do cliente web
Web (build season6x) | |
|---|---|
| Transporte | WebSocket → ws-proxy → TCP (rede interna) |
| ConnectServer | 44406 |
| Versão no login | 20404 (protocolo estendido, Season 106/E3) |
| GS do servidor 0 | 55902 |
| Login | LoginLongPassword (senha até 20) |
| Pacotes | *Extended (dano/HP 32-bit, itens de 12 bits, aparência de 27 bytes) |
| Criptografia GS | SimpleModulus + Xor32 (C→S), SimpleModulus (S→C), credenciais Xor3 |
WS_MAX_CONN_PER_IP) e o do CS 44406 vira teto global (D20). Detalhes em docs/protocol-compatibility.md.Customizações
Ordem: configuração → plugin → módulo custom → extensão → core (último caso). Plugins C# ficam em custom/server/src/MuCustom.* e são carregados pelo MuCustom.StartupHook (DOTNET_STARTUP_HOOKS), sem alterar o OpenMU.
[Guid("5C0F3E0B-6B5E-4E43-9D3B-7A1D2C3E4F01")] // fixo para sempre
[PlugIn]
[Display(Name = "MuCustom: /serverinfo", Description = "...")]
public class ServerInfoChatCommandPlugIn : IChatCommandPlugIn
{
public string Key => "/serverinfo";
public CharacterStatus MinCharacterStatusRequirement => CharacterStatus.Normal;
public async ValueTask HandleCommandAsync(Player player, string command) { /* ... */ }
}
| Fase | Conteúdo |
|---|---|
| Custom 0 | S6 vanilla operacional (MVP v0.1) |
| Custom 1 | QoL: rates, auto pickup, comandos, reset/grand reset, ranking |
| Custom 2 | Itens: armas, sets, shields, acessórios, pets, opções |
| Custom 3 | Wings (depende do web usar appearance estendida) |
| Custom 4 | Monstros e bosses com fases 75/50/25 |
| Custom 5 | Mapas |
| Custom 6 | Sistemas modernos: quests diárias, achievements, battle pass, marketplace… |
Deploy em VPS
Ubuntu 24.04 (ou 22.04). Crie registros DNS A para play, admin, ws, docs, www e o domínio raiz.
git clone --recurse-submodules <URL_DO_REPO> /opt/mu && cd /opt/mu
sudo BASE_DOMAIN=meumu.com.br LETSENCRYPT_EMAIL=voce@exemplo.com ./scripts/install-vps.sh
O script é idempotente. Ele instala o Docker oficial, gera o .env com senhas que nunca são impressas, detecta o IP público, configura o UFW sem bloquear o SSH, sobe tudo com HTTPS/WSS, espera os health checks e bloqueia as contas de teste.
Checklist antes de abrir ao público
- Criar seu próprio usuário administrador no Admin e ativar 2FA.
- Confirmar contas de teste bloqueadas:
./scripts/disable-test-accounts.sh --list. - Conferir o teto do ConnectServer 44406 (
scripts/set-connection-limit.sh --show; oinstall-vps.shjá aplica 1000) e o limite por IP real no Traefik (WS_MAX_CONN_PER_IP). - Agendar backups e copiá-los para fora da VPS.
- Lembrar que portas publicadas pelo Docker não passam pelo UFW.
Segurança
Segredos
Só no .env (chmod 600, fora do Git). O .env.example não tem segredos e os scripts nunca imprimem senhas.
Banco
PostgreSQL em rede internal: true, nunca publicado.
Admin
Só via Traefik, com bootstrap user, 2FA recomendado e sessões persistidas no volume adminpanel-keys.
Proxy WS
ALLOW_TARGETS obrigatório; sem ele o proxy vira relay TCP aberto. Sem dump de pacotes. TLS terminado no Traefik.
Supply chain
Imagens fixadas por digest e upstreams por SHA. Traefik sem docker.sock.
Pendências
Limite de conexões por IP em ws.* e Postgres sem superusuário em runtime (M12).
Assets e licenças
Arquivos de jogo que você possua ficam fora do repositório. Todo asset próprio é registrado em docs/assets.md.
Troubleshooting
| Sintoma | Solução |
|---|---|
required variable ... is missing | ./scripts/bootstrap-local.sh (cria o .env) |
openmu demora a ficar healthy | Normal na primeira subida, até cerca de 5 minutos. Acompanhe com ./scripts/logs.sh openmu |
| Erro de autenticação no Postgres | A senha só vale na criação do volume. Restaure a senha antiga; down -v apaga os dados |
| Página do jogo abre mas não conecta | Os WEB_WS_* do build não batem com a URL real. Refaça o build do web-client ou use ?ws=ws://ws.localhost:80 |
| WebSocket retorna 403 | O alvo está fora de WS_ALLOW_TARGETS |
| Web não loga com senha longa | O cliente web aceita senha de até 20 caracteres (LoginLongPassword) |
| "Maximum Connections per IP reached" | ./scripts/set-connection-limit.sh (teto do CS 44406) |
| WebSocket recusado com 429 | limite de conexões por IP no Traefik; aumente WS_MAX_CONN_PER_IP no .env |
| Certificado não emite | Confira o DNS (dig), a porta 80 aberta e os logs do reverse-proxy |
| Plugin custom não aparece | Procure [MuCustom.StartupHook] loaded no log. O assembly precisa começar com MuCustom. |
Roadmap
| Milestone | Estado |
|---|---|
| M0 Infraestrutura | ✅ stack Docker validada (smoke + healthcheck) |
| M1 OpenMU vanilla | ✅ em Docker com PostgreSQL |
| M2 Web client (protocolo estendido) | ✅ login, mundo, inventário, multiplayer; combate a testar |
| M3 Desktop client | ❌ removido do projeto (D15) |
| M4 Multiplayer | ✅ jogadores web se veem no mesmo mapa |
| M5 Framework custom | 🟡 hook e primeiro plugin validados |
| M6–M10 Itens, wings, bosses, mapas, progressão | ⬜ |
| M11 Plataforma web (portal, registro, SSO por token) | ⬜ |
| M12 Hardening de produção | ⬜ |
Referências
- MUnique/OpenMU · openmu-docs.munique.net
- Ignies/OpenMu-Client-Babylon
- Arquivos detalhados: architecture.md · protocol-compatibility.md · compatibility-matrix.md · custom-content.md · dungeons.md · quests-npc.md · boss-fases.md · mapa-detalhado.md · decisions.md · installation.md · development.md · vps-deployment.md · ci-cd.md ·assets.md · troubleshooting.md