How to build a CI pipeline for MU Online server changes
Structure a continuous integration (CI) pipeline for your MU Online server's configurations and scripts, with version control, a staging environment, automated tests, and controlled production deploys.
Changing a MU Online server's configuration files in production with no process — editing directly on the server via RDP, with no backup, with no prior testing — is the most common recipe for incidents: a GameServer that won't boot, an event that doesn't trigger, an item duplicated by a misconfigure
Changing a MU Online server's configuration files in production with no process — editing directly on the server via RDP, with no backup, with no prior testing — is the most common recipe for incidents: a GameServer that won't boot, an event that doesn't trigger, an item duplicated by a misconfigured condition. A CI (continuous integration) pipeline brings the same discipline used in software development to private server operations: every change goes through version control, automated validation, and a test environment before reaching production. This tutorial shows how to structure that pipeline specifically for the particularities of a MU server (configuration files, SQL scripts, client binaries).
Why treat server configuration as code
The files that define your server's behavior — Item.txt, MonsterSetBase.txt, drop settings, event scripts, SQL procedures — are, in practice, code: they determine behavior, have dependencies among each other, and can break the system if edited incorrectly. Treating them as code means applying the same practices: versioning, review before applying, an isolated test environment, and an auditable history of who changed what and when.
Recommended repository structure
Organize a (private) Git repository mirroring the server's folder structure, clearly separating what is versionable configuration from what is generated or too large a binary to version directly:
mu-server-config/
├── GameServer/
│ ├── Data/ItemList.xml
│ ├── Data/MonsterSetBase.txt
│ └── GameServerInfo.dat
├── ConnectServer/
│ └── ConnectServerInfo.dat
├── sql/
│ ├── migrations/
│ │ ├── 001_add_socket_columns.sql
│ │ └── 002_add_event_log_table.sql
│ └── procedures/
├── scripts/
│ ├── deploy_staging.ps1
│ └── deploy_production.ps1
└── .github/workflows/
└── ci.yml
Large binary files (BMD models for custom items/wings, textures) go in a separate repository or use Git LFS, so they don't bloat the history of the main configuration repository.
Environments: local, staging, and production
A CI pipeline only works with at least two environments beyond the development machine: staging (a faithful replica of production, but isolated, with no real players) and production (the server players connect to). Every change follows the same path: edit locally → commit → automatic deploy to staging → validation → manual approval → deploy to production.
| Environment | Purpose | Who accesses it |
|---|---|---|
| Local/dev | Editing and initial isolated testing | Developer/admin |
| Staging | Production replica for full validation | Internal team, test GMs |
| Production | Real server, players connected | Players |
Defining the pipeline in GitHub Actions
A basic CI workflow for this scenario covers three stages: syntax validation, staging deploy, and, pending approval, production deploy.
name: MU Server CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
validate:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Validate configuration file syntax
run: powershell -File scripts/validate_config.ps1
- name: Validate SQL scripts (dry-run)
run: powershell -File scripts/validate_sql.ps1
deploy-staging:
needs: validate
if: github.ref == 'refs/heads/main'
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Deploy to staging
run: powershell -File scripts/deploy_staging.ps1
- name: Smoke test (login, create character)
run: powershell -File scripts/smoke_test.ps1
deploy-production:
needs: deploy-staging
if: github.ref == 'refs/heads/main'
runs-on: self-hosted
environment:
name: production
steps:
- name: Deploy to production (with manual approval)
run: powershell -File scripts/deploy_production.ps1
The deploy-production job uses a protected environment on GitHub, requiring a manual approval from a responsible person before running — this prevents any merge from triggering a direct production deploy without a final human review.
Self-hosted runners: why they're needed here
Unlike a typical web application pipeline, deploying a MU server needs to run on a Windows machine with direct access to MuServer and SQL Server — usually the server machine itself or an intermediary on the same network. Because of this, the deploy stages use self-hosted runners (GitHub Actions agents installed on your own infrastructure), while syntax validation, which doesn't depend on the real environment, can run on hosted runners (windows-latest).
Automated tests possible in this context
| Test type | What it validates | When it runs |
|---|---|---|
| Syntax validation | Config file is well-formed (valid XML/INI) | Every commit |
| SQL dry-run | SQL migration runs without error on a test database | Every commit touching sql/ |
| Smoke test | GameServer boots, login works, character is created and equips a basic item | After staging deploy |
| Custom system regression test | Sockets, custom wings, events keep working | Before production deploy, when the PR touches these files |
Smoke tests can be scripted with an automated test client or, at minimum, a quick manual checklist run by a GM before production approval.
Managing database migrations with versioning
SQL scripts that alter the schema (new columns, log tables, procedures) should follow sequential numbering and be idempotent whenever possible — that is, safe to run more than once without duplicating the effect:
-- 002_add_event_log_table.sql
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'EventLog')
BEGIN
CREATE TABLE EventLog (
EventLogID INT IDENTITY PRIMARY KEY,
EventName VARCHAR(100),
CreateDate DATETIME DEFAULT GETDATE()
);
END
Keep a control table (SchemaVersion) tracking which migrations have already run in each environment, avoiding reapplying scripts that already executed.
Rollback: planning the exit before the entrance
Every deploy needs a documented rollback plan before it's executed, not improvised after an incident. For configuration files, that means always keeping the previous version available (Git already covers this via git revert); for SQL schema changes, every migration should have a corresponding rollback script (002_add_event_log_table_rollback.sql) tested in staging before going to production.
Approval and audit trail
Use pull requests as the formal review mechanism: no configuration change goes to the main branch without at least one approval from another team member (or, on smaller teams, a second self-check on a different day, to reduce the "looks right because I just wrote it" bias). Git history, combined with GitHub's approval log, becomes the audit trail: who changed what, when, and who approved it.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Deploy breaks production with no warning | No staging before the real deploy | Always go through staging with a smoke test before production |
| SQL migration fails in production but passed in staging | Data/schema difference between environments | Periodically sync a production snapshot into staging |
| No one knows how to revert a change | No documented rollback script | Require a tested rollback as part of every schema migration PR |
| Pipeline stalls for lack of a runner | Self-hosted runner offline | Monitor the runner machine and set up an availability alert |
| Critical change applied without review | Direct deploy on the main branch without a PR | Protect the main branch requiring PR and approval |
CI pipeline checklist
- Git repository created with a structure mirroring the server's folders.
- Isolated staging environment configured as a production replica.
- CI workflow with syntax validation on every commit.
- Automated smoke tests or manual checklist after staging deploy.
- Manual approval required before production deploy.
- SQL migration scripts numbered, idempotent, and with tested rollback.
- Main branch protected, requiring pull request and review.
- Audit trail (who changed what, when) accessible through Git history.
With the pipeline running, every configuration change, custom system, or SQL script becomes traceable and reversible — a solid foundation for safely evolving the server's more advanced systems, like the ones described in the MU Online server creation tutorial.
Frequently asked questions
Is it worth setting up CI for a small/hobby server?
A simplified version pays off even on small projects: at minimum version control (Git) for configurations and a staging environment to test before applying to production. The full pipeline with automated tests pays off more on servers with a team and frequent changes.
How do I version-control MuServer's binary files (like item .bmd files)?
Git doesn't handle binary diffs well, but it's still worth versioning — use Git LFS (Large File Storage) for large/binary files, keeping the history without bloating the main repository with full blobs on every change.
Is it safe to automate deployment straight to production?
Not without an intermediate staging step. The recommended flow is always commit → staging → validation (manual or automated) → approval → production. Skipping staging is the most common cause of breaking the server at peak hours due to an untested configuration change.
What kind of automated test makes sense for MU server configuration?
Syntax tests (the configuration file is valid and the GameServer boots without error), smoke tests (login, create character, equip a basic item), and regression tests on custom systems (sockets, wings, events) before every deploy that touches those files.
Do I need a dedicated CI server (Jenkins, GitHub Actions) for this?
Not necessarily from scratch. GitHub Actions or GitLab CI (with your own runners, since the build/deploy likely runs on a Windows machine with MuServer) handle most cases well with no additional infrastructure cost beyond the runner itself.