Brazil's biggest MU Online portal — since 2003
Tutorial Advanced Infrastructure

How to build a continuous deployment (CI/CD) script for your MU Online server

Build a continuous deployment pipeline for your MU Online server, automating emulator builds, database migrations, client file distribution, and safe GameServer/ConnectServer restarts.

BR Bruno · Updated on Mar 20, 2025 · ⏱ 16 min read
Quick answer

Keeping a private MU Online server up to date by hand — copying DLLs, editing configs, and restarting services manually — works while the project is small, but becomes a source of human error the moment the team grows or the update frequency increases. A continuous deployment (CI/CD) script fixes th

Keeping a private MU Online server up to date by hand — copying DLLs, editing configs, and restarting services manually — works while the project is small, but becomes a source of human error the moment the team grows or the update frequency increases. A continuous deployment (CI/CD) script fixes this: every change to the emulator code, configuration files, or database goes through a repeatable, tested, and reversible pipeline. This tutorial shows how to structure that pipeline from scratch, from infrastructure requirements to the rollback script, covering GameServer, ConnectServer, the database, and configuration files.

Why automate deployment for an MU server

A typical MU server has multiple components that need to be updated together: the GameServer binary (compiled from the emulator source, e.g. IGCN/MuEMU/OpenMU), the ConnectServer, the .txt/.xml configuration files for items and monsters, and the database schema (MSSQL or MySQL, depending on the emulator). Updating these components out of order — for example, pushing a new ItemList.txt without the matching stored procedure — is the most common cause of post-update crashes. A continuous deployment pipeline guarantees everything goes up in the right order, with validation at every step.

Infrastructure prerequisites

  • A version-controlled repository (Git) with the emulator source code and configuration files.
  • A staging environment — a replica of the production server, even if smaller, to validate builds before release.
  • SSH/WinRM access to the production server(s), using a dedicated service account (not the administrator's personal account).
  • A CI runner (GitHub Actions, GitLab CI, Jenkins, or even a scheduled script via Task Scheduler/cron) to execute the pipeline.
  • Automated backup of the database and configuration files before any deploy.

Repository structure

Organize the repository by clearly separating what is compilable source code from what is game data configuration:

mu-server/
├── src/               # emulator source code (GameServer, ConnectServer)
├── config/            # environment configuration .ini/.txt files
├── db/
│   ├── migrations/    # sequentially numbered .sql scripts
│   └── seed/          # initial data (items, monsters, maps)
├── deploy/
│   ├── deploy.sh       # main deploy script
│   ├── rollback.sh     # rollback script
│   └── healthcheck.sh  # post-deploy validation
└── .github/workflows/deploy.yml

This separation lets you version gameplay changes (a new event, a new item) independently from infrastructure changes (a bug fix in the emulator core).

Step 1 — Building the emulator

The pipeline starts by compiling the GameServer/ConnectServer source code. Here's an example build step in bash, assuming a C++ project using CMake:

#!/bin/bash
set -euo pipefail

echo "[deploy] Building GameServer..."
cd src/GameServer
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release -j"$(nproc)"

if [ ! -f build/GameServer.exe ]; then
  echo "[deploy] ERROR: build failed, binary was not generated."
  exit 1
fi
echo "[deploy] Build completed successfully."

If your emulator is distributed as a closed binary (no recompilation), this step becomes just an integrity check (checksum) of the downloaded package before proceeding.

Step 2 — Database migration

Every schema change must live in a numbered, versioned migration file, never applied manually via SSMS/HeidiSQL directly in production. Here's an example script for applying incremental migrations:

#!/bin/bash
set -euo pipefail
DB_HOST="$1"
LAST_APPLIED=$(sqlcmd -S "$DB_HOST" -Q "SELECT MAX(version) FROM SchemaMigrations" -h -1)

for file in db/migrations/*.sql; do
  version=$(basename "$file" | cut -d'_' -f1)
  if [ "$version" -gt "$LAST_APPLIED" ]; then
    echo "[deploy] Applying migration $file"
    sqlcmd -S "$DB_HOST" -i "$file"
    sqlcmd -S "$DB_HOST" -Q "INSERT INTO SchemaMigrations (version, applied_at) VALUES ($version, GETDATE())"
  fi
done

Never use a destructive DROP TABLE or ALTER COLUMN in a migration without first confirming the pre-deploy backup completed successfully.

Step 3 — Automatic backup before deploy

The backup step must be blocking: if it fails, the entire pipeline stops and nothing gets deployed. A minimal example:

#!/bin/bash
set -euo pipefail
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups/mu/$TIMESTAMP"
mkdir -p "$BACKUP_DIR"

sqlcmd -S "$DB_HOST" -Q "BACKUP DATABASE MuOnline TO DISK='$BACKUP_DIR/MuOnline.bak'"
cp -r /opt/mu-server/config "$BACKUP_DIR/config"
tar -czf "$BACKUP_DIR/binaries.tar.gz" /opt/mu-server/bin

echo "[deploy] Backup saved to $BACKUP_DIR"

Keep at least the last 7 daily backups and 4 weekly backups, with automatic rotation so you don't run out of disk space.

Step 4 — Distributing files to the server

With the build validated and the backup complete, the pipeline copies the artifacts to the production server(s). Using rsync over SSH:

rsync -avz --delete \
  --exclude 'config/local.ini' \
  build/GameServer.exe deploy/ \
  user@mu-prod:/opt/mu-server/bin/

The --exclude flag matters here: environment-specific configuration files (database passwords, license keys) should never be overwritten by the pipeline — they live outside version control, managed via environment variables or a secrets vault (Vault, AWS Secrets Manager).

Step 5 — Safe service restart

Restarting the GameServer drops every connected player, so this step must be timed and communicated. A controlled restart script:

#!/bin/bash
set -euo pipefail
echo "[deploy] Warning players about restart in 5 minutes..."
echo "notice Server will restart in 5 minutes for maintenance" > /opt/mu-server/gs_console_pipe

sleep 300

systemctl stop mu-gameserver
systemctl stop mu-connectserver
cp /tmp/new_build/GameServer.exe /opt/mu-server/bin/
systemctl start mu-connectserver
systemctl start mu-gameserver

Always restart the ConnectServer last, so it only starts accepting new connections once the GameServer is already up.

Step 6 — Post-deploy healthcheck

After the restart, automatically validate that services came up correctly before declaring the deploy complete:

#!/bin/bash
RETRIES=10
for i in $(seq 1 $RETRIES); do
  if nc -z localhost 44405; then
    echo "[deploy] ConnectServer responding on port 44405."
    exit 0
  fi
  echo "[deploy] Waiting for ConnectServer to come up... ($i/$RETRIES)"
  sleep 10
done
echo "[deploy] ERROR: ConnectServer did not respond. Starting rollback."
exit 1

If the healthcheck fails, the pipeline should automatically trigger the rollback script, without waiting for manual intervention.

Step 7 — Rollback script

The rollback restores the backup generated in Step 3 and reverts the binaries:

#!/bin/bash
set -euo pipefail
BACKUP_DIR="$1"

systemctl stop mu-gameserver mu-connectserver
sqlcmd -S "$DB_HOST" -Q "RESTORE DATABASE MuOnline FROM DISK='$BACKUP_DIR/MuOnline.bak' WITH REPLACE"
tar -xzf "$BACKUP_DIR/binaries.tar.gz" -C /
systemctl start mu-connectserver mu-gameserver

echo "[deploy] Rollback completed from $BACKUP_DIR"

Test this script periodically in staging — a rollback that's never been tested has a high chance of failing exactly when you need it most.

Integrating with GitHub Actions

An example workflow that orchestrates the steps above, triggered on a push to the main branch after pull request approval:

name: Deploy MU Server
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build
        run: ./deploy/build.sh
      - name: Backup production
        run: ./deploy/backup.sh
      - name: Migrate database
        run: ./deploy/migrate.sh ${{ secrets.DB_HOST }}
      - name: Distribute files
        run: ./deploy/rsync-deploy.sh
      - name: Restart services
        run: ssh deploy@mu-prod './deploy/restart.sh'
      - name: Healthcheck
        run: ./deploy/healthcheck.sh || ./deploy/rollback.sh

Keep secrets (database host, SSH keys) in the repository's Secrets, never hardcoded in the YAML.

Staging environment as a mandatory gate

No build should go straight to production. Configure the pipeline to first deploy to staging, run an automated smoke test (login, character creation, teleport, item drop), and only then release the production deploy — manually or automatically, depending on the team's maturity. This gate is what separates continuous deployment from "deploying blind."

Common errors and fixes

SymptomLikely causeFix
GameServer won't start after deployDLL/binary incompatible with the current database schemaRun migrations before swapping the binary, never after
Rollback failsRollback script never tested in stagingTest the rollback on every major release, not just document it
Players get dropped without warningRestart not announced in advanceAdd an automated in-game/Discord message before the restart
Pipeline hangs on backupDisk full on the database serverConfigure rotation of old backups and monitor free space
Local configuration overwrittenrsync without excluding environment filesExplicitly list the config files that don't come from the repository

Pipeline implementation checklist

  • Versioned repository with separation between code, config, and database migrations.
  • Functional staging environment to validate builds before production.
  • Blocking automated backup script before every deploy.
  • Numbered database migrations applied in order.
  • Restart script with advance notice to players.
  • Automated post-deploy healthcheck.
  • Rollback script tested periodically in staging.
  • Secrets (passwords, keys) kept out of version control.

With the continuous deployment pipeline up and running, the next step is to apply this same automation discipline to the initial environment setup — see the MU Online server creation tutorial to understand the foundation this pipeline was built on.

Frequently asked questions

Do I need a dedicated CI server (Jenkins/GitLab) for this?

It's not mandatory. You can start with a bash/PowerShell script triggered manually or by a scheduled task, and evolve into GitHub Actions/GitLab CI/Jenkins as the team grows. What matters is that the process is repeatable and versioned, not the tool itself.

Is it safe to automate deployment on a live MU server?

Yes, as long as the pipeline includes an automatic backup before every deploy, a staging environment to validate the build, and a tested rollback step. Manual deployment tends to be riskier than a well-designed pipeline, because it depends on human memory.

How should I handle the game client in continuous deployment?

The client (the files players download) usually has a separate pipeline from the server, publishing update packages through the launcher. The server deploy should check protocol version compatibility before releasing the client update.

What should I do if a deploy breaks the GameServer in production?

The pipeline should have an automatic rollback step: restore the previous binary/DLL and the pre-deploy database dump, then restart the services. This rollback needs to be tested periodically, not just written and forgotten.

Can I deploy without dropping online players?

Partially. Swapping GameServer DLLs usually requires a process restart, so the best approach is to schedule deploys during low-traffic windows and notify the community in advance. For ConnectServer and web services, zero-downtime deployment is possible using a simple load balancer.

BR
Events, maps & items editor

Bruno specializes in MU Online events, maps, bosses and item economy. He documents every detail based on real gameplay.

Keep reading

Related articles