Eseguire con Docker
Moongate pubblica immagini Linux su Docker Hub. Usa la documentazione della versione in esecuzione; il changelog identifica i rilasci pubblicati. Per il deployment completo fino al primo login, parti dall’Esempio Compose consigliato sotto. Per eseguire il sorgente corrente, usa un checkout di questo repository e compila dalla sua radice:
docker build -f src/Moongate.Server/Dockerfile -t moongate:local .L’immagine viene eseguita come utente non root con MOONGATE_ROOT=/data. Monta lì
un volume persistente scrivibile e monta in sola lettura i tuoi file client Ultima
Online; i file client non sono distribuiti con Moongate. Include mgctl,
che prepara la radice e applica le migrazioni,
l’SQL core, i file dei dati dello shard, i template
e gli script di esempio. La destinazione di build
sample-plugin aggiunge il bundle del plugin di esempio.
Esempio Compose consigliato
Section titled “Esempio Compose consigliato”L’esempio con login e due istanze game è un deployment completo compilato dal sorgente con:
- Un processo login sulla porta TCP UO 2593 e due processi game sulle porte host 2595 e 2596.
- Database PostgreSQL Accounts, Realm 1 e Realm 2 separati e ruoli schema/runtime.
- Un servizio Redis privato per lease attivi dei realm e ticket di handoff del login monouso.
- Segreti Compose da Bitwarden, job schema, volumi persistenti server/PostgreSQL e uno smoke test usa e getta.
Segui quella guida per .env, esportazione dei segreti, build, applicazione SQL e
comandi di avvio esatti. Redis non viene pubblicato sull’host. Non ci sono listener
API interni o scambio di certificati in questa topologia.
Container standalone
Section titled “Container standalone”mode = "standalone" esegue entrambi i ruoli UO in un processo, con listener login
e game separati. Pubblica entrambe le porte configurate. Standalone richiede anche
database PostgreSQL Accounts e Realm raggiungibili e il servizio Redis condiviso;
annuncia il proprio realm locale tramite Redis. Il TOML della radice include:
mode = "standalone"
[network]login_port = 2593game_port = 2595
[ultima]ultima_path = "/uo"
[persistence.accounts]connection_string = "$MOONGATE_ACCOUNTS_DATABASE"
[persistence.realm]connection_string = "$MOONGATE_REALM_DATABASE"
[redis]connection_string = "$MOONGATE_REDIS_CONNECTION_STRING"handoff_secret = "$MOONGATE_HANDOFF_SECRET"Fornisci le quattro variabili referenziate da un gestore dei segreti nell’ambiente
del container. Le variabili PostgreSQL sono URI postgres:// per database specifici
per ruolo. La variabile Redis è una stringa di connessione StackExchange.Redis come
redis:6379,password=<secret> su una rete Docker privata; il segreto di handoff è
un valore diverso. Non committare nessuno dei valori in TOML, Compose o .env.
Il riferimento della configurazione copre le altre impostazioni.
Prepara una radice prima dell’avvio (questo copia anche file dei dati dello shard,
template e script di esempio in /data/data, /data/templates e /data/scripts),
poi applica l’SQL Auth e World ai rispettivi database:
docker volume create moongate-datadocker run --rm --entrypoint /app/mgctl -v moongate-data:/data moongate:local init /dataCopia la configurazione generata dal volume in un file host privo di segreti:
docker create --name moongate-config -v moongate-data:/data moongate:localdocker cp moongate-config:/data/config/moongate.toml ./moongate.tomldocker rm moongate-configModifica ./moongate.toml, conservando le sezioni generate e applicando le
impostazioni sopra. Mantieni la shell in questa directory: le esecuzioni seguenti
montano proprio questo file in sola lettura sopra /data/config/moongate.toml.
Imposta il percorso assoluto dei dati client sull’host:
export UO_DATA_PATH="/absolute/path/to/your/ultima-client"Prepara prima entrambi i database, ruoli schema/runtime e Redis privato. Segui i
passaggi 2–4 del primo avvio per prerequisito
Bitwarden, nomi degli elementi, esportazioni delle variabili runtime e permessi.
Gli URI e l’indirizzo Redis devono essere raggiungibili dal container;
localhost indicherebbe il container stesso. Con le credenziali runtime caricate
nella shell principale, usa le credenziali schema solo in questa subshell di migrazione:
( set -e MOONGATE_ACCOUNTS_DATABASE="$(bw get password moongate-accounts-schema-uri)" MOONGATE_REALM_DATABASE="$(bw get password moongate-realm-schema-uri)" export MOONGATE_ACCOUNTS_DATABASE MOONGATE_REALM_DATABASE docker run --rm --entrypoint /app/mgctl -v moongate-data:/data \ -v "$(pwd)/moongate.toml:/data/config/moongate.toml:ro" \ -e MOONGATE_ACCOUNTS_DATABASE moongate:local migrate apply --root-directory /data --target auth docker run --rm --entrypoint /app/mgctl -v moongate-data:/data \ -v "$(pwd)/moongate.toml:/data/config/moongate.toml:ro" \ -e MOONGATE_REALM_DATABASE moongate:local migrate apply --root-directory /data --target world)Continua solo quando entrambe le applicazioni riescono e i permessi runtime sono assegnati. Avvia dalla shell principale, che conserva solo gli URI database runtime:
docker run --rm -it --name moongate-standalone --stop-timeout 120 \ -p 2593:2593 -p 2595:2595 \ -v moongate-data:/data \ -v "$(pwd)/moongate.toml:/data/config/moongate.toml:ro" \ -v "$UO_DATA_PATH:/uo:ro" \ -e MOONGATE_ACCOUNTS_DATABASE -e MOONGATE_REALM_DATABASE \ -e MOONGATE_REDIS_CONNECTION_STRING -e MOONGATE_HANDOFF_SECRET \ moongate:local --root-directory /data-it abilita la console locale interattiva. Attendi i controlli PostgreSQL e Redis
riusciti e i listener login/game. Premi * e crea il primo account, poi segui
Collegarsi con ClassicUO, usando la
porta login 2593. Vedi Primo avvio per sintassi
account e verifica dell’ingresso nel mondo. Premi Ctrl+C e lascia terminare il
salvataggio finale; anche docker stop -t 120 moongate-standalone da un altro
terminale lo arresta normalmente.
Per migrazioni successive, arresta questo runtime prima di applicare SQL revisionato. Database, connessione Redis o migrazione obbligatoria mancanti fanno fallire l’avvio; il server non crea database né applica automaticamente SQL non revisionato. Vedi Generare, revisionare e applicare.
Porte, storage e aggiornamenti
Section titled “Porte, storage e aggiornamenti”L’immagine corrente dichiara porte client UO 2593 e 2595 e porta UDP 12000 per il
server ping. Pubblica 12000/udp solo per il processo il cui indirizzo viene
contattato dai ping dei client; due processi dietro un indirizzo host non possono
usarla entrambi. EXPOSE non pubblica una porta host; configura ports per login
e ogni listener game che i client devono raggiungere. Mantieni Redis e PostgreSQL
su una rete privata. Ogni processo Moongate in esecuzione richiede un volume /data
proprio; ogni realm un database Realm proprio. Non condividere una radice o un
database Realm tra processi game in esecuzione.
Dopo aver scaricato un’immagine più recente, avviala: il suo entrypoint, mgctl run, prepara la radice come fa mgctl init prima di ogni avvio, quindi aggiunge i file di dati e l’SQL di base che la nuova versione introduce e mantiene i file già presenti nella radice (passa --no-init per saltarlo). Eseguire mgctl a mano sul volume, come sopra, fa lo stesso senza avviare il server. L’entrypoint dell’esempio Compose passa dallo stesso launcher.
sopra: aggiunge file dati e SQL core introdotti dal nuovo rilascio e conserva i
file già nella radice. L’esempio Compose lo fa nel proprio entrypoint a ogni avvio.
docker compose down conserva i volumi nominati. Aggiungere --volumes elimina
radici dei server e dati PostgreSQL; usalo solo per un ambiente usa e getta.
I salvataggi del mondo non sono backup PostgreSQL. Arresta normalmente i servizi
così il salvataggio finale può completarsi: Docker termina forzatamente un container
10 secondi dopo avergli chiesto di fermarsi, e il primo salvataggio dopo un avvio
richiede 8–15 secondi per il mondo distribuito, quindi concedi più tempo con
stop_grace_period: 2m nel file Compose o docker stop -t 120.
Per aggiornare un’istanza, leggi il changelog del rilascio di destinazione, arrestala, segui la politica di backup del database, cambia il tag dell’immagine e riavvia con gli stessi volumi. Se l’avvio segnala migrazioni in sospeso, arresta il server interessato e applica l’SQL revisionato prima di riavviarlo. Volumi e database creati prima che i cataloghi diventassero un solo file ciascuno richiedono prima Passare ai cataloghi a file unico.
Con persistence.auto_apply_migrations = true in config/moongate.toml il server
esegue entrambi i passaggi all’avvio: aggiunge l’SQL core della nuova immagine a
/data/migrations e applica ciò che è in sospeso, quindi un aggiornamento consiste
in nuovo tag immagine e riavvio. Vedi Applicare all’avvio
per requisiti e ciò che ancora lo interrompe; file dati, template e script aggiunti
da un rilascio provengono comunque da mgctl init.
Il server si riavvia da solo
Section titled “Il server si riavvia da solo”L’entrypoint dell’immagine è mgctl run, che avvia mgserver come processo figlio e lo
riavvia dopo il comando restart (exit code 75) o un crash.
docker run image <server options> funziona come prima: le opzioni arrivano a mgserver invariate.
- Un riavvio resta nel container. Il container, i suoi volumi, la sua rete e il suo pod non cambiano; cambia solo il processo del server. Un server che fallisce cinque volte di fila, ciascuna entro un minuto, termina il container con exit code 1, e la policy di riavvio di Docker o Kubernetes subentra come prima.
- Arresto.
docker stope la cancellazione di un pod mandano SIGTERM al launcher, che lo passa al server e aspetta il salvataggio finale del mondo. Il periodo di grazia (stop_grace_period,docker stop -t,terminationGracePeriodSeconds) deve coprire quel salvataggio: due minuti al massimo. - La radice viene preparata a ogni avvio. Il launcher esegue
mgctl initsu/dataprima di ogni avvio e non stampa nulla quando funziona; quando fallisce (per esempio un volume le cui migrazioni sono in conflitto con quelle incluse) stampa ciò che ha detto e il container termina con exit code 1.docker run image --no-initlo salta. - Help e versione.
docker run --rm image --helpe--versionora rispondono per il launcher. Quelli del server sonodocker run --rm --entrypoint /app/mgserver image --help. - Modalità one-shot.
--initialize-roote--persistence-schemadel server si eseguono con--entrypoint /app/mgserver, o tramitemgctl initemgctl migratecome sopra: dietro il launcher una che fallisce viene ritentata qualche altra volta prima di arrendersi.
Cache della build
Section titled “Cache della build”Il Dockerfile ripristina le dipendenze prima di copiare i sorgenti, poi pubblica ogni
eseguibile incluso con --no-restore. Ripristino e pubblicazione usano la stessa
configurazione, architettura di destinazione e impostazione self-contained.
Non c’è una compilazione separata del server prima della pubblicazione.
Il contesto di build include sorgenti, migrazioni core, file dei dati dello shard,
template e script di esempio (moongate_root/data, templates, scripts, senza
definitions.lua e .luarc.json generati), impostazioni di build, licenze e plugin
di esempio. Documentazione, test, sito, directory locali bin/obj e file di ambiente
sono esclusi. Il sorgente del plugin di esempio viene copiato solo nella propria
destinazione di build. Quando aggiungi un nuovo input all’immagine, aggiorna insieme
.dockerignore e le istruzioni COPY pertinenti.
I flussi di sviluppo e rilascio compilano su runner il cui daemon Docker conserva i layer, quindi nessuna cache di build viene pubblicata su un registry. I pacchetti NuGet restano nel layer di ripristino, così una ricompilazione li recupera da quella cache. Una prima build senza cache funziona comunque normalmente.
Per una build locale:
docker buildx build --load -f src/Moongate.Server/Dockerfile -t moongate:local .Ripeti la build con --progress=plain per esaminare i passaggi in cache. Modifiche
alla sola documentazione dovrebbero mantenere in cache i layer di ripristino e
pubblicazione. Usa --pull per verificare immagini base .NET aggiornate, o
--no-cache per ricompilare tutti i passaggi. Le immagini pubblicate restano Linux amd64.
Conversione dei contenuti UOX3
Section titled “Conversione dei contenuti UOX3”L’immagine non converte contenuti: i convertitori sono uno strumento Python, moongate-convert, che si lancia da un checkout del repository (servono uv e Python 3.13 o successivo, non serve .NET). Eseguilo sull’host, con i dati UOX3 in una cartella leggibile, e scrivi l’output dove il volume /data del container è montato, oppure copialo dopo:
cd tools/convertuv run moongate-convert uox \ --source /path/to/uox3/data/dfndata/items \ --destination /path/to/templates/items --loot-destination /path/to/templates/lootsVedi Migrare da UOX3 per comportamento e limiti attuali del convertitore.
Risoluzione dei problemi
Section titled “Risoluzione dei problemi”- La connessione database fallisce:
localhostdentro un container significa quel container. Usa il nome del servizio raggiungibile o l’indirizzo di rete privata e conferma che il ruolo abbia accesso al database di destinazione. - La connessione Redis fallisce: verifica servizio privato, salute, credenziale
condivisa e politica
noeviction. Un riavvio Redis cancella ticket e lease; i game ripubblicano i lease dopo la riconnessione. - Realm assente dal login: controlla log game e TTL dei lease Redis come nella guida Compose.
- Il redirect client non può connettersi: IPv4 e porta annunciati dal game devono essere raggiungibili dal client e corrispondere al mapping host pubblicato.
- Dati client mancanti o radice non scrivibile: controlla mount
/uoin sola lettura e proprietà di/dataper l’utente non root dell’immagine.
Vedi Primo avvio, Configurazione e Diagnostica per le operazioni a livello server.
Kubernetes
Section titled “Kubernetes”Il chart Helm esegue la stessa topologia con login e realm su Kubernetes ed è pubblicato nel registry Helm dell’organizzazione su Codeberg a ogni rilascio.
Endpoint di amministrazione privato
Section titled “Endpoint di amministrazione privato”La porta 2590 è riservata all’amministrazione gRPC facoltativa e resta disabilitata nella configurazione predefinita immagine/Compose. Usa TLS del server sulla rete privata; non servono mTLS o mapping di porte pubbliche. Vedi la guida all’amministrazione e la configurazione Compose facoltativa.
Per TLS amministrativo facoltativo in una radice standalone, aggiungi
--generate-admin-certificate dopo /data, con --admin-certificate-hosts che
indica DNS/IP usato dai client. Vedi Configurazione certificati di mgctl
per file generati e fiducia del client. Il binding predefinito resta loopback;
configura un’interfaccia privata prima di connetterti da un altro container.
L’override Compose multiprocesso usa invece TOML montati e certificati forniti
dall’operatore, come descritto nel README dell’esempio.
Se usi il file di configurazione host sopra, copiaci o unisci le modifiche
[admin_api] generate prima del riavvio; il mount in sola lettura ha precedenza
sulla configurazione memorizzata nel volume.
Creato con ❤️ da Tom
