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.

Servidor: build + 2223 testes + login real Plugins custom sem patch no core Stack Docker: smoke 26/26 Chrome → MU → no mundo Web no protocolo estendido Combate / drop: a testar

Estado atual

Tudo marcado como ✅ foi executado de verdade em 2026-10-03. Fonte da verdade: STATUS.md.
ComponenteEstadoEvidê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çoImagemExposição
postgrespostgres:18.6-alpinerede interna apenas
openmubuild próprio (OpenMU 204c11b5 + MuCustom)nenhuma porta publicada; jogo via ws-proxy; admin 8080 via Traefik
ws-proxybuild próprio (Babylon proxy, Bun 1.4.2)ws.DOMAIN
web-clientbuild próprio (Babylon + nginx 1.30.5)play.DOMAIN
docsnginx:1.30.5-alpinedocs.DOMAIN
reverse-proxytraefik:v3.7.1380 (local) · 80 + 443 (produção)
PortaFunçãoPública?
80 / 443HTTP(S) e WSS via Traefiksim
44406 · 55902/04/06ConnectServer e GameServers do protocolo estendido; o web chega pelo ws-proxynão (rede interna)
44405 · 55901/03/05 · 55980protocolo clássico e ChatServernão (sem uso)
5432 · 8080PostgreSQL · Admin diretonunca

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 localCredencial
Jogo (web)http://play.localhosttest0 / test0
Admin Panelhttp://admin.localhostOPENMU_ADMIN_USER / OPENMU_ADMIN_PASSWORD do .env
Docshttp://docs.localhost—
A primeira subida cria e popula o banco Season 6 (itens, mapas, monstros, 3 GameServers, contas de teste). Leva alguns minutos.

Operação diária

TarefaComando
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)
TransporteWebSocket → ws-proxy → TCP (rede interna)
ConnectServer44406
Versão no login20404 (protocolo estendido, Season 106/E3)
GS do servidor 055902
LoginLoginLongPassword (senha até 20)
Pacotes*Extended (dano/HP 32-bit, itens de 12 bits, aparência de 27 bytes)
Criptografia GSSimpleModulus + Xor32 (C→S), SimpleModulus (S→C), credenciais Xor3
Limitações: todo jogador web chega ao OpenMU com o IP do proxy, então o limite por jogador fica no Traefik (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) { /* ... */ }
}
FaseConteúdo
Custom 0S6 vanilla operacional (MVP v0.1)
Custom 1QoL: rates, auto pickup, comandos, reset/grand reset, ranking
Custom 2Itens: armas, sets, shields, acessórios, pets, opções
Custom 3Wings (depende do web usar appearance estendida)
Custom 4Monstros e bosses com fases 75/50/25
Custom 5Mapas
Custom 6Sistemas modernos: quests diárias, achievements, battle pass, marketplace…
Regra Servidor + Web (D15): uma feature só está pronta com ✅ em Server e Web.

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; o install-vps.sh já 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

O repositório do cliente web não declara licença e inclui dados originais do cliente MU Online (Webzen). Não publique imagens Docker com esses dados. Nosso repositório só o referencia como submodule. O cliente desktop foi removido do projeto (D15).

Arquivos de jogo que você possua ficam fora do repositório. Todo asset próprio é registrado em docs/assets.md.

Troubleshooting

SintomaSolução
required variable ... is missing./scripts/bootstrap-local.sh (cria o .env)
openmu demora a ficar healthyNormal na primeira subida, até cerca de 5 minutos. Acompanhe com ./scripts/logs.sh openmu
Erro de autenticação no PostgresA 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 conectaOs 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 403O alvo está fora de WS_ALLOW_TARGETS
Web não loga com senha longaO 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 429limite de conexões por IP no Traefik; aumente WS_MAX_CONN_PER_IP no .env
Certificado não emiteConfira o DNS (dig), a porta 80 aberta e os logs do reverse-proxy
Plugin custom não apareceProcure [MuCustom.StartupHook] loaded no log. O assembly precisa começar com MuCustom.

Roadmap

MilestoneEstado
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