Salta ai contenuti

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:

Terminal window
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.

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.

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 = 2593
game_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:

Terminal window
docker volume create moongate-data
docker run --rm --entrypoint /app/mgctl -v moongate-data:/data moongate:local init /data

Copia la configurazione generata dal volume in un file host privo di segreti:

Terminal window
docker create --name moongate-config -v moongate-data:/data moongate:local
docker cp moongate-config:/data/config/moongate.toml ./moongate.toml
docker rm moongate-config

Modifica ./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:

Terminal window
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:

Terminal window
(
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:

Terminal window
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.

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.

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 stop e 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 init su /data prima 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-init lo salta.
  • Help e versione. docker run --rm image --help e --version ora rispondono per il launcher. Quelli del server sono docker run --rm --entrypoint /app/mgserver image --help.
  • Modalità one-shot. --initialize-root e --persistence-schema del server si eseguono con --entrypoint /app/mgserver, o tramite mgctl init e mgctl migrate come sopra: dietro il launcher una che fallisce viene ritentata qualche altra volta prima di arrendersi.

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:

Terminal window
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.

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:

Terminal window
cd tools/convert
uv run moongate-convert uox \
--source /path/to/uox3/data/dfndata/items \
--destination /path/to/templates/items --loot-destination /path/to/templates/loots

Vedi Migrare da UOX3 per comportamento e limiti attuali del convertitore.

  • La connessione database fallisce: localhost dentro 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 /uo in sola lettura e proprietà di /data per l’utente non root dell’immagine.

Vedi Primo avvio, Configurazione e Diagnostica per le operazioni a livello server.

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.

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

Offrimi un caffè