diff --git a/.gemini/config.yaml b/.gemini/config.yaml
new file mode 100644
index 0000000..1729494
--- /dev/null
+++ b/.gemini/config.yaml
@@ -0,0 +1,13 @@
+have_fun: false
+memory_config:
+ disabled: false
+code_review:
+ disable: false
+ comment_severity_threshold: MEDIUM
+ max_review_comments: -1
+ pull_request_opened:
+ help: false
+ summary: true
+ code_review: true
+ include_drafts: false
+ignore_patterns: []
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..c816588
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,10 @@
+# Auto detect text files and perform LF normalization
+* text=auto
+
+# Explicitly declare text files you want to always be normalized and converted
+# to native line endings on checkout.
+*.cpp text
+*.h text
+*.cs text
+*.csproj text
+*.sln text
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..f3d5c41
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,38 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+ - OS: [e.g. iOS]
+ - Browser [e.g. chrome, safari]
+ - Version [e.g. 22]
+
+**Smartphone (please complete the following information):**
+ - Device: [e.g. iPhone6]
+ - OS: [e.g. iOS8.1]
+ - Browser [e.g. stock browser, safari]
+ - Version [e.g. 22]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..11fc491
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: enhancement
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/ISSUE_TEMPLATE/other-request.md b/.github/ISSUE_TEMPLATE/other-request.md
new file mode 100644
index 0000000..f5a2d36
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/other-request.md
@@ -0,0 +1,10 @@
+---
+name: Other request
+about: Ask questions or similar
+title: ''
+labels: question
+assignees: ''
+
+---
+
+
diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml
new file mode 100644
index 0000000..8430007
--- /dev/null
+++ b/.github/workflows/claude.yml
@@ -0,0 +1,41 @@
+name: Claude Code
+
+on:
+ issue_comment:
+ types: [created]
+ pull_request_review_comment:
+ types: [created]
+ issues:
+ types: [opened, assigned]
+ pull_request_review:
+ types: [submitted]
+
+jobs:
+ claude:
+ if: |
+ github.actor == 'sven-n' && (
+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
+ (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
+ (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
+ (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
+ )
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: write
+ issues: write
+ id-token: write
+ actions: read
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Run Claude Code
+ id: claude
+ uses: anthropics/claude-code-action@v1
+ with:
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
+ additional_permissions: |
+ actions: read
diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml
new file mode 100644
index 0000000..f5ce606
--- /dev/null
+++ b/.github/workflows/dotnetcore.yml
@@ -0,0 +1,17 @@
+name: .NET Core
+
+on: [push]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v1
+ - name: Setup .NET Core
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 10.0.x
+ - name: Build with dotnet
+ run: dotnet publish src/Startup/MUnique.OpenMU.Startup.csproj --configuration Release -p:ci=true
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..4d7804c
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,40 @@
+name: publish to nuget
+on:
+ push:
+ branches:
+ - master
+jobs:
+ publish:
+ name: Publish packages to nuget
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup .NET Core
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 10.0.x
+
+ - name: publish MUnique.OpenMU.PlugIns on version change
+ uses: rohith/publish-nuget@5fdc86fb81a38b37521c9f153bfb60ac44e7ba93
+ with:
+ PROJECT_FILE_PATH: src/PlugIns/MUnique.OpenMU.PlugIns.csproj
+ TAG_COMMIT: false
+ NUGET_KEY: ${{secrets.NUGET_KEY}}
+ PACKAGE_NAME: munique.openmu.plugins
+
+ - name: publish MUnique.OpenMU.Network on version change
+ uses: rohith/publish-nuget@5fdc86fb81a38b37521c9f153bfb60ac44e7ba93
+ with:
+ PROJECT_FILE_PATH: src/Network/MUnique.OpenMU.Network.csproj
+ TAG_COMMIT: false
+ NUGET_KEY: ${{secrets.NUGET_KEY}}
+ PACKAGE_NAME: munique.openmu.network
+
+ - name: publish MUnique.OpenMU.Network.Packets on version change
+ uses: rohith/publish-nuget@5fdc86fb81a38b37521c9f153bfb60ac44e7ba93
+ with:
+ PROJECT_FILE_PATH: src/Network/Packets/MUnique.OpenMU.Network.Packets.csproj
+ TAG_COMMIT: true
+ TAG_FORMAT: v*
+ NUGET_KEY: ${{secrets.NUGET_KEY}}
+ PACKAGE_NAME: munique.openmu.network.packets
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b61a968
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,325 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+**/content/bundles/*.*
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+**/Properties/launchSettings.json
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Typescript v1 declaration files
+typings/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# VS Code
+.vscode/
+*.lscache
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# SubVersion
+.svn/
+
+# Pandoc html result files
+doc/*.htm
+
+# text files; for documentation etc. use markdown
+*.txt
+
+# bundles
+src/AdminPanel/wwwroot/content/css/app.css
+src/AdminPanel/wwwroot/content/css/app.min.css
+src/AdminPanel/wwwroot/content/js/app.js
+src/AdminPanel/wwwroot/content/js/app.js.map
+
+# Data
+.data/
+/out/
+
+# Mac OS filesystem files
+.DS_Store
+
+# AI
+.opencode/
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..544a0fd
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,36 @@
+# Contributions
+
+Contributions are welcome if they meet the following criteria:
+
+* Language is english.
+
+* Code should be StyleCop compliant - this project uses the [StyleCop.Analyzers](https://www.nuget.org/packages/StyleCop.Analyzers/)
+ for VS2019 so you should see issues directly as warnings.
+
+* Coding style (naming, etc.) and quality should fit to the current state.
+
+* No code copied/converted from the well-known decompiled source of the
+ original server.
+
+If you want to contribute, please create a new issue for the feature or bug (if
+the issue doesn't exist yet) so we can see who is working on something and can
+discuss possible solutions. If it's a small thing, you can also just send a
+pull request without adding an issue.
+
+Apart of that, contributions from non-developers are welcome as well. You can
+test the server, submit issues or suggestions, packet descriptions or
+documentations about the concepts and mechanics of the game itself. Please use
+markdown files/syntax for this purpose.
+
+If you have questions about that, don't hesitate to ask in our [discord channel](https://discord.gg/2u5Agkd)
+or by submitting an issue.
+
+## How to contribute code
+
+If you want to contribute code, please do the following steps:
+
+1. fork this project from the original MUnique OpenMU Project.
+2. create a feature branch from the master branch
+3. commit your changes to your feature branch
+4. submit a pull request to the original master branch
+5. lean back, wait for the code review and merge :)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..cda6bd8
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,7 @@
+Copyright 2017 MUnique
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/QuickStart.md b/QuickStart.md
new file mode 100644
index 0000000..3561ad0
--- /dev/null
+++ b/QuickStart.md
@@ -0,0 +1,145 @@
+# Quick Start OpenMU
+
+General requirements:
+
+* Free TCP ports:
+ * 80 (admin panel)
+ * 55901 - 55906 (game servers)
+ * 44405 - 44406 (connect servers)
+ * 44405: default connection port for the original client
+ * 44406: connection port especially for the [open source client](https://github.com/sven-n/MuMain)
+ * 55980 (chat server)
+
+* A game client (check [our Discord](https://discord.gg/2u5Agkd) FAQs)
+* Knowledge or way to start the game client, so that it connects to the server. Our Launcher will do that.
+
+ * Launcher binaries: [MUnique.OpenMU.ClientLauncher v0.9.6.zip](https://github.com/MUnique/OpenMU/releases/download/v0.9.0/MUnique.OpenMU.ClientLauncher_0.9.6.zip)
+ * It requires the [.NET 10 runtime](https://dotnet.microsoft.com/download/dotnet/10.0) or higher
+ * If your server and client runs on your local host, use any IP of 127.x.x.x, except 127.0.0.1, because this one is blocked by the client. For example, you could use 127.127.127.127
+
+This guide describes two ways of starting the server. Use Docker, if you just
+want to play around. If you want to develop or debug the server, choose the
+manual way.
+
+As you can see on the connect server ports, the server is initialized for two different clients by default.
+They can connect to the same game servers through different ports. However, if you connect to the wrong port,
+it may currently still work all correctly, you'll just get warnings in the logs. However, as soon as
+we change encryption keys or methods, this will change.
+
+## Docker
+
+Please take a look at the deploy-folder of this project. There you'll find a more
+detailed guide about how to set up this project.
+
+### Environment Variables
+
+It's possible to define additional environment variables to influence the
+postgres database connection strings.
+
+| Name | Description |
+|------|---------------------|
+| DB_HOST | The hostname of the database. If the local configuration file is still configured to use 'localhost', the value of this variable replaces it |
+| DB_ADMIN_USER | The user name of the postgres admin account. If the local configuration file is still configured to use 'postgres' for the user name of the admin (first entry in the ConnectionSettings.xml), the value of this variable replaces it. |
+| DB_ADMIN_PW | The password of the postgres admin account. If the local configuration file is still configured to use 'admin' for the user password of the admin (first entry in the ConnectionSettings.xml), the value of this variable replaces it. |
+
+## Manually
+
+Use this way, if you want to develop or debug for OpenMU.
+
+Requirements:
+
+* Windows OS, 10 or higher
+
+ * It runs under Linux and MacOS, too. However, this guide describes it for
+ windows.
+
+ * PostgreSQL installed
+
+ * Visual Studio 2026 installed, with workloads for ASP.NET Web development
+ and .NET Desktop development. Please keep it up-to-date to prevent any issues.
+
+ * Visual Studio Extension "Web Compiler 2022+", if you plan to edit SCSS files
+ for the admin panel.
+ * https://marketplace.visualstudio.com/items?itemName=Failwyn.WebCompiler64
+
+ * [.NET SDK 10](https://dotnet.microsoft.com/download/dotnet/10.0)
+ (it should be included in Visual Studio 2026)
+ * `winget install Microsoft.DotNet.SDK.10`
+
+ * [NodeJS 16+](https://nodejs.org) installed
+ * `winget install OpenJS.NodeJS.LTS`
+
+ * This repository cloned
+
+If you have that, you'll need to do:
+
+* Open the solution of OpenMU with Visual Studio
+
+* Right click the solution and 'Restore NuGet Packages'
+
+* Edit src\Persistence\EntityFramework\ConnectionSettings.xml, so that the
+ connection strings are correct - however only the user/password of the first
+ and second connection string need to be correct. The server will try to create
+ the other roles specified by the settings.
+
+* Build the solution
+
+* Start MUnique.OpenMU.Startup
+
+ * If required, it will create the database schemas, the required roles and
+ gives permissions to this roles.
+
+ * Optional: You can reinitialize the database by adding a ```-reinit``` parameter.
+
+* When the Admin Panel is initialized, go to . Then you
+ should see three gameservers, the chat server and two connect servers. Start
+ the connect servers and at least one gameserver.
+
+* If you update to a newer state of the master-branch, it could be possible
+ that you have to update the database and configuration.
+ You can find updates in the admin panel.
+
+* Then you can connect to the server through the game client.
+
+## Helpful (optional) steps
+
+* __Auto Start__: If you don't want to start each server listener after starting
+ the process, you can either activate "Auto Start"
+
+ * in the admin panel at ```Configuration -> System```,
+
+ * or with the start parameter ```-autostart```.
+
+* __IP Resolving__: If you encounter disconnects after selecting a server, it's most
+ likely a wrong setting for the IP resolver. You can change it easily over the
+ admin panel at ```Configuration -> System``` as well.
+ You may also change the setting by providing start parameters or environment
+ parameters, however I just recommend this for experienced users. Look at this
+ [Readme](src/Startup/Readme.md) for more information.
+
+* __Changing the game version__: If you want to player another version than
+ season 6, you may initialize the database with another game version.
+ You can do this over the admin panel as well, at the ```Setup``` page.
+
+## Test Accounts
+
+To test some features of the server, test accounts are created automatically
+when the database is initialized.
+
+These are the user names:
+
+* test0 - test9: General test accounts, level 1 to 90, in 10 level steps
+
+Season 6 only:
+* test300: General test account with level 300
+* test400: General test account with level 400, master characters
+* testgm: Test account of a game master
+* testgm2: Test account of a game master with summoner and rage fighter characters
+* testunlock: Test account without characters, but unlocked character classes
+* quest1: Test account for the level 150 quests
+* quest2: Test account for the level 220 quests
+* quest3: Test account for the level 400 quests
+* ancient: Test account with ancient item sets, level 330 characters
+* socket: Test account with socket item sets, level 380 characters
+
+The __passwords__ of these accounts are the __same as the user name__.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..709cc45
--- /dev/null
+++ b/README.md
@@ -0,0 +1,131 @@
+# OpenMU Project
+
+[](LICENSE)
+[](https://www.codacy.com/gh/MUnique/OpenMU/dashboard?utm_source=github.com&utm_medium=referral&utm_content=MUnique/OpenMU&utm_campaign=Badge_Grade)
+[](https://deepwiki.com/MUnique/OpenMU)
+[](https://discord.gg/2u5Agkd)
+
+| Platform |Build Status |
+|----------------|----------------------|
+| Windows |  |
+| Linux (Docker) | [](https://hub.docker.com/r/munique/openmu) |
+
+| NuGet Packages | |
+|----------------|---|
+| MUnique.OpenMU.Network | [](https://www.nuget.org/packages/MUnique.OpenMU.Network/) |
+| MUnique.OpenMU.Network.Packets | [](https://www.nuget.org/packages/MUnique.OpenMU.Network.Packets/) |
+
+This project aims to create an easy to use, extendable and customizable server
+for a MMORPG called "MU Online".
+The server supports multiple versions of the game, but the main focus is
+version of Season 6 Episode 3 using the ENG (english) protocol. Additionally,
+the long-term focus is on the [open source client](https://github.com/sven-n/MuMain)
+which supports a slightly extended network protocol.
+However, parts of the software can also be suitable for the development of
+other games, even for other kind of games.
+
+The code is a complete rewrite from scratch - it's not based on pre-existing
+projects, and it's also explicitly not based on decompiled server sources or
+their countless derivates.
+
+There also exists a [blog](https://munique.net) which may contain some valuable
+information about this development.
+
+## Current project state
+
+This project is currently under development without any release.
+You can try the current state by using the available docker image, also
+mentioned in the [quick start guide](QuickStart.md).
+
+## Licensing
+
+This project is released under the MIT license (see LICENSE file).
+
+## Used technologies
+
+The project is mainly written in C# and targets .NET 10.0.
+
+The servers admin panel is hosted on an embedded ASP.NET Core webserver (Kestrel)
+and implemented as Blazor Server App.
+
+At the moment the persistence layer uses the [Entity Framework Core](https://github.com/aspnet/EntityFrameworkCore)
+and [PostgreSQL](https://www.postgresql.org) as database. Additionally, it's
+also possible to start it in a non-persistent in-memory mode.
+
+The project is prepared to be hosted in a single process or distributed in multiple processes.
+For the communication between the processes, we use [Dapr](https://dapr.io/).
+
+## Deployment
+
+We provide Docker images and docker-compose files for easy deployment.
+Please take a look at the deploy-folder of this project.
+
+## Contributions
+
+Contributions are welcome if they meet the following criteria:
+
+* Language is english.
+
+* Code should be StyleCop compliant - this project uses the [StyleCop.Analyzers](https://www.nuget.org/packages/StyleCop.Analyzers/)
+ for VS2022 so you should see issues directly as warnings.
+
+* Coding style (naming, etc.) and quality should fit to the current state.
+
+* No code copied/converted from the well-known decompiled source of the
+ original server.
+
+If you want to contribute, please create a new issue for the feature or bug (if
+the issue doesn't exist yet) so we can see who is working on something and can
+discuss possible solutions. If it's a small thing, you can also just send a
+pull request without adding an issue.
+
+Apart of that, contributions from non-developers are welcome as well. You can
+test the server, submit issues or suggestions, packet descriptions or
+documentations about the concepts and mechanics of the game itself. Please use
+markdown files/syntax for this purpose.
+
+If you have questions about that, don't hesitate to ask in our [discord channel](https://discord.gg/2u5Agkd)
+or by submitting an issue.
+
+## How to contribute code
+
+If you want to contribute code, please do the following steps:
+
+1. fork this project from the original MUnique OpenMU Project.
+2. create a feature branch from the master branch
+3. commit your changes to your feature branch
+4. please test your changes, don't send AI generated code without testing it yourself
+5. submit a pull request to the original master branch
+6. wait for the code review and merge :)
+
+## How to use
+
+Please have a look at the [quick start guide](QuickStart.md).
+
+## Gameplay differences to the original server
+
+This project doesn't have the goal to copy the original MU Online server
+behavior to 100 %. This is not entirely possible, because the original server
+is written in another programming language and has a completely different
+architecture.
+With some points we make our life easier in this project, with other points we
+try to improve the gameplay.
+
+### Calculations
+
+The calculations of attribute values (like character damage decrement etc.) are
+done with 32 bit float numbers and without rounding off, like the original
+server does at some places.
+E.g. distributed stat points always have effect, while in the original server
+effects might get rounded down. For example, when 4 points of strength gives 1
+base damage, the original server doesn't calculate a fraction of 1 damage for
+3 points, while OpenMU calculates 0.75 damage. This damage
+has then an effect in further calculations.
+
+### Countdown when changing character or sub-server
+
+The original server uses a five second countdown when a player wants to change
+his character or the sub-server. Maybe this was done for some performance
+reasons, as the original server would then save the character/account data.
+We think that's really annoying and see no real value in that, so we don't use
+a countdown.
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
new file mode 100644
index 0000000..dc2185a
--- /dev/null
+++ b/azure-pipelines.yml
@@ -0,0 +1,36 @@
+# ASP.NET Core
+# Build and test ASP.NET Core projects targeting .NET Core.
+# Add steps that run tests, create a NuGet package, deploy, and more:
+# https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core
+
+trigger:
+- master
+
+pool:
+ vmImage: 'windows-2022'
+
+variables:
+ buildConfiguration: 'Release'
+
+steps:
+- task: UseDotNet@2
+ displayName: 'Install .NET Core SDK'
+ inputs:
+ version: 10.0.x
+ performMultiLevelLookup: true
+ includePreviewVersions: true # Required for preview versions
+
+- script: dotnet restore
+ workingDirectory: src
+ displayName: 'dotnet restore'
+
+- script: dotnet build --configuration $(buildConfiguration) -p:ci=true
+ workingDirectory: src
+ displayName: 'dotnet build $(buildConfiguration)'
+
+- task: DotNetCoreCLI@2
+ displayName: 'dotnet test'
+ inputs:
+ command: test
+ projects: 'tests/*Tests/*.csproj'
+ arguments: '--configuration $(buildConfiguration) --no-build'
diff --git a/deploy/README.md b/deploy/README.md
new file mode 100644
index 0000000..bbfe9ec
--- /dev/null
+++ b/deploy/README.md
@@ -0,0 +1,90 @@
+# Deployment
+
+The recommended way to deploy OpenMU is through Docker. Depending on the scale
+you need, we have multiple ways to do that.
+
+## All-in-one
+
+The [all-in-one deployment](/all-in-one/) is recommended, if you want to host on
+a small machine with a low amount of players.
+In this case, all kinds of OpenMU subsystems (ConnectServer, GameServer, LoginServer,
+AdminPanel, ...) are running in one process.
+
+### Pros
+
+* No communication overhead between subsystems, therefore slightly faster
+* Simpler deployment
+* Smaller memory footprint. Since we run all in one process, we don't have the
+ overhead of multiple processes, runtimes and can share data.
+* Easier to observe and debug, no additional tools required
+
+### Cons
+
+* Harder to scale - only by scaling up your single machine
+* Lower resiliency. If one subsystem crashes the process, the whole thing goes
+ down
+* It's a more or less self-contained system which is harder to extend
+
+## All-in-one with Traefik as Reverse Proxy
+
+The [all-in-one with traefik deployment](/deploy/all-in-one-traefik/) is recommended,
+if you want to host on a small machine with a low amount of players and want to host
+your MuOnline Website on the same machine.
+
+Once Traefik works as a Reverse Proxy, you can handle miltiple website without
+change the default port to HTTP/HTTPS connections.
+
+Addin a few labels to your container, you will tell Traefik how to handle incoming
+requests and he will redirect to the correct website.
+
+### Pros
+
+* No communication overhead between subsystems, therefore slightly faster
+* Simpler deployment
+* Smaller memory footprint. Since we run all in one process, we don't have the
+ overhead of multiple processes, runtimes and can share data.
+* Easier to observe and debug, no additional tools required
+* You can have multi websites with auto renew SSL Certificates
+* Expose only 80 and 443 ports for websites and admin panel.
+ Traefik knows what to do
+
+### Cons
+
+* Harder to scale - only by scaling up your single machine
+* Lower resiliency. If one subsystem crashes the process, the whole thing goes
+ down
+* It's a more or less self-contained system which is harder to extend
+
+## Distributed
+
+*!!! CURRENTLY BROKEN AND UNSUPPORTED, DOCS ARE OUT OF DATE !!!*
+
+It's also possible to host OpenMU in a [distributed](/distributed/) way.
+However, this introduces a lot more complexity and you should know what you're doing.
+The communication between the subsystems is handled with Dapr.
+
+Warnings:
+
+* It's currently unsupported due to several issues we have to resolve first.
+ Feel free to contribute for the following issues:
+* This way of hosting OpenMU is more complex and requires a good understanding
+ of distributed systems.
+* This deployment requires more resources (CPU, RAM, Disk, Network).
+
+### Pros
+
+* Easier to scale. For example, if you need additional game servers you simply
+ add more containers.
+* Higher resiliency. If one subsystem crashes, the others are not affected.
+* It's easier to add more subsystems, even custom ones.
+ For example, one could subscribe on already published events like guild messages
+ or letters.
+ Such a subsystem could forward messages to other systems (E-Mail, Discord, etc.).
+
+### Cons
+
+* Communication overhead between subsystems.
+* Higher memory footprint, since we run multiple docker containers
+ (each with their own .net runtime) which can't share some data.
+* Harder to observe and debug. We added some stuff to compensate that (Loki,
+ Grafana, Prometheus, Zipkin), but they require additional resources, too.
diff --git a/deploy/all-in-one-traefik/.dockerignore b/deploy/all-in-one-traefik/.dockerignore
new file mode 100644
index 0000000..3729ff0
--- /dev/null
+++ b/deploy/all-in-one-traefik/.dockerignore
@@ -0,0 +1,25 @@
+**/.classpath
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/azds.yaml
+**/bin
+**/charts
+**/docker-compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+LICENSE
+README.md
\ No newline at end of file
diff --git a/deploy/all-in-one-traefik/.env.example b/deploy/all-in-one-traefik/.env.example
new file mode 100644
index 0000000..f81bc7d
--- /dev/null
+++ b/deploy/all-in-one-traefik/.env.example
@@ -0,0 +1 @@
+DOMAIN=domain.com
diff --git a/deploy/all-in-one-traefik/.gitignore b/deploy/all-in-one-traefik/.gitignore
new file mode 100644
index 0000000..d2fef88
--- /dev/null
+++ b/deploy/all-in-one-traefik/.gitignore
@@ -0,0 +1,2 @@
+data-traefik/acme.json
+.env
diff --git a/deploy/all-in-one-traefik/.htpasswd b/deploy/all-in-one-traefik/.htpasswd
new file mode 100644
index 0000000..1fa6225
--- /dev/null
+++ b/deploy/all-in-one-traefik/.htpasswd
@@ -0,0 +1,7 @@
+# This file contains the passwords for the admin panel and tracing.
+# Change these passwords in production!
+# default is admin:openmu, hashed with bcrypt.
+
+admin:$2y$10$xYL2d/QEukwGmX0uNZubsunL0qcANuTYkpapRVdlu5q3ymCpvOEh.
+
+# this file should always end with an empty line!
diff --git a/deploy/all-in-one-traefik/README.md b/deploy/all-in-one-traefik/README.md
new file mode 100644
index 0000000..40ea717
--- /dev/null
+++ b/deploy/all-in-one-traefik/README.md
@@ -0,0 +1,140 @@
+# All-in-one-traefik deployment
+
+The all in one (with traefik) deployment is recommended, if you want to host
+on a small machine with a low amount of players and want to host your
+MuOnline Website on the same machine.
+
+Once Traefik works as a Reverse Proxy, you can handle multiple websites
+without changing the default port to HTTP/HTTPS connections.
+
+Adding a few labels to your container, you will tell Traefik how to
+handle incoming requests and it will redirect to the correct website.
+
+```yaml
+services:
+ admin-panel:
+ ...
+ labels:
+ - "traefik.enable=true"
+ - "traefik.docker.network=proxy"
+ - "traefik.http.routers.adm.entrypoints=websecure"
+ - "traefik.http.routers.adm.rule=Host(`admin.domain.com`)"
+ - "traefik.http.routers.adm.middlewares=auth"
+ - "traefik.http.middlewares.auth.basicauth.usersfile=.htpasswd"
+
+ muonline-website:
+ ...
+ labels:
+ - "traefik.enable=true"
+ - "traefik.docker.network=proxy"
+ - "traefik.http.routers.muonline.entrypoints=websecure"
+ - "traefik.http.routers.muonline.rule=Host(`muonline.domain.com`)"
+```
+
+You can even add multiple domains and/or subdomains to your host label
+
+``` yaml
+- "traefik.http.routers.muonline.rule=Host(`www.domain1.com`,`domain1.com`,`sub.domain1.com`)"
+```
+
+In this case, all kinds of OpenMU subsystems (ConnectServer, GameServer, LoginServer,
+AdminPanel, ...) are running in one process, but you can run
+AdminPanel and other websites separately.
+
+## Deployment with docker-compose
+
+### Install GIT
+
+See [https://github.com/git-guides/install-git](https://github.com/git-guides/install-git).
+
+### Clone the repository
+
+``` bash
+git clone https://github.com/MUnique/OpenMU.git
+```
+
+It will create a new folder OpenMU with the repository contents inside.
+
+### Navigate to the docker-compose files
+
+Navigate to the folder `deploy/all-in-one-traefik`
+
+``` bash
+cd deploy/all-in-one-traefik
+```
+
+### Create a new docker network
+
+You need to have communication between containers from different docker compose files
+
+``` bash
+docker network create proxy
+```
+
+### Option A - for local testing
+
+To run the official docker image on your local traefik server, just run:
+
+``` bash
+docker compose -f docker-compose.yml up -d
+```
+
+And that's it. It's then available on your local computer through a loopback
+ip. Your AdminPanel URL is `http://admin.docker.localhost`. You can change it
+in the `docker-compose.yml` file.
+
+However, if you want to make it available through the internet, you should choose
+Option B:
+
+### Option B - with HTTPS
+
+If you want to share your server with the world, it's recommended to set up HTTPS
+and Traefik can handle it for you. Otherwise, traffic from and to the admin
+panel is not encrypted.
+
+#### Set your domain name as environment variable
+
+Make a copy of `.env.example` called `.env` and edit the `.env` file with your
+domain/subdomain to the AdminPanel URL.
+
+``` bash
+cp .env.example .env
+```
+
+Make a copy of `data-traefik/acme.example.json` called `data-traefik/acme.json`
+and apply permission 600 to `acme.json`
+
+``` bash
+cp data-traefik/acme.example.json data-traefik/acme.json
+chmod 600 data-traefik/acme.json
+```
+
+#### Run it
+
+``` bash
+docker compose -f docker-compose.prod.yml up -d
+```
+
+#### Important
+
+Avoid editing the .htpasswd manually. Instead, access the admin panel
+and add a new user. If you are using the _all-in-one-traefik_ you
+need to restart Traefik after add a new user to it takes effect.
+
+## What's next
+
+The server is automatically started and initialized for Season 6. You can start
+playing, if you want :-)
+
+Additionally, take a look at the AdminPanel.
+
+If your containers run on docker at your local machine, you can simply go to `http://admin.docker.localhost/`.
+The default username is 'admin', the password 'openmu'. You should change that later.
+
+If you want to run another game version, you can go to the setup page through
+the navigation menu, where you can select your desired game version,
+number of game servers (just the data of it), and if test accounts
+should be created.
+
+If you click on 'Install', wait a bit until the database is set up and
+filled with the data and voila, OpenMU is ready to use.
diff --git a/deploy/all-in-one-traefik/data-traefik/acme.example.json b/deploy/all-in-one-traefik/data-traefik/acme.example.json
new file mode 100644
index 0000000..e69de29
diff --git a/deploy/all-in-one-traefik/data-traefik/configurations/dynamic.yml b/deploy/all-in-one-traefik/data-traefik/configurations/dynamic.yml
new file mode 100644
index 0000000..bc3a999
--- /dev/null
+++ b/deploy/all-in-one-traefik/data-traefik/configurations/dynamic.yml
@@ -0,0 +1,26 @@
+# Dynamic configuration
+http:
+ middlewares:
+ nofloc:
+ headers:
+ customResponseHeaders:
+ Permissions-Policy: "interest-cohort=()"
+ secureHeaders:
+ headers:
+ sslRedirect: true
+ forceSTSHeader: true
+ stsIncludeSubdomains: true
+ stsPreload: true
+ stsSeconds: 31536000
+
+tls:
+ options:
+ default:
+ cipherSuites:
+ - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
+ - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
+ - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
+ - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+ - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
+ - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
+ minVersion: VersionTLS12
diff --git a/deploy/all-in-one-traefik/data-traefik/traefik.yml b/deploy/all-in-one-traefik/data-traefik/traefik.yml
new file mode 100644
index 0000000..edf9845
--- /dev/null
+++ b/deploy/all-in-one-traefik/data-traefik/traefik.yml
@@ -0,0 +1,47 @@
+api:
+ dashboard: true
+
+entryPoints:
+ web:
+ address: :80
+ http:
+ redirections:
+ entryPoint:
+ to: websecure
+
+ websecure:
+ address: :443
+ http:
+ middlewares:
+ - secureHeaders@file
+ - nofloc@file
+ tls:
+ certResolver: letsencrypt
+
+pilot:
+ dashboard: false
+
+providers:
+ docker:
+ endpoint: "unix:///var/run/docker.sock"
+ exposedByDefault: false
+ file:
+ filename: /configurations/dynamic.yml
+
+certificatesResolvers:
+ letsencrypt:
+ acme:
+ email: admin@domain.com
+ storage: acme.json
+ keyType: EC384
+ httpChallenge:
+ entryPoint: web
+
+ buypass:
+ acme:
+ email: admin@domain.com
+ storage: acme.json
+ caServer: https://api.buypass.com/acme/directory
+ keyType: EC256
+ httpChallenge:
+ entryPoint: web
diff --git a/deploy/all-in-one-traefik/docker-compose-all-in-one-traefik.dcproj b/deploy/all-in-one-traefik/docker-compose-all-in-one-traefik.dcproj
new file mode 100644
index 0000000..37f9cbd
--- /dev/null
+++ b/deploy/all-in-one-traefik/docker-compose-all-in-one-traefik.dcproj
@@ -0,0 +1,24 @@
+
+
+
+ 2.1
+ Linux
+ 4430d6a8-54b5-412b-9dba-07b3e3580af7
+ None
+ {Scheme}://localhost:{ServicePort}
+
+
+ openmu-startup-traefik
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/deploy/all-in-one-traefik/docker-compose.prod.yml b/deploy/all-in-one-traefik/docker-compose.prod.yml
new file mode 100644
index 0000000..2cff780
--- /dev/null
+++ b/deploy/all-in-one-traefik/docker-compose.prod.yml
@@ -0,0 +1,71 @@
+services:
+ openmu-startup:
+ image: munique/openmu
+ container_name: openmu-startup
+ networks:
+ - proxy
+ ports:
+ - "55901:55901"
+ - "55902:55902"
+ - "55903:55903"
+ - "55904:55904"
+ - "55905:55905"
+ - "55906:55906"
+ - "44405:44405"
+ - "55980:55980"
+ environment:
+ DB_HOST: database
+ working_dir: /app/
+ volumes:
+ - ./.htpasswd:/etc/nginx/.htpasswd
+ depends_on:
+ - database
+ labels:
+ - "traefik.enable=true"
+ - "traefik.docker.network=proxy"
+ - "traefik.http.routers.openmu.entrypoints=websecure"
+ - "traefik.http.routers.openmu.rule=Host(`${DOMAIN}`)"
+ - "traefik.http.routers.openmu.middlewares=auth"
+ - "traefik.http.middlewares.auth.basicauth.usersfile=.htpasswd"
+
+ database:
+ image: postgres
+ container_name: database
+ environment:
+ POSTGRES_PASSWORD: admin
+ POSTGRES_DB: openmu
+ POSTGRES_USER: postgres
+ networks:
+ - proxy
+ ports:
+ - "5432"
+ volumes:
+ - dbdata:/var/lib/postgresql #store data on volume
+
+ traefik:
+ image: traefik
+ container_name: traefik
+ restart: always
+ security_opt:
+ - no-new-privileges:true
+ ports:
+ - 80:80
+ - 443:443
+ volumes:
+ - /etc/localtime:/etc/localtime:ro
+ - /var/run/docker.sock:/var/run/docker.sock:ro
+ - ./data-traefik/traefik.yml:/traefik.yml:ro
+ - ./data-traefik/acme.json:/acme.json
+ - ./data-traefik/configurations:/configurations
+ - "./.htpasswd:/.htpasswd"
+ networks:
+ - proxy
+ labels:
+ - "traefik.docker.network=proxy"
+
+networks:
+ proxy:
+ external: true
+
+volumes:
+ dbdata:
diff --git a/deploy/all-in-one-traefik/docker-compose.yml b/deploy/all-in-one-traefik/docker-compose.yml
new file mode 100644
index 0000000..a0fca11
--- /dev/null
+++ b/deploy/all-in-one-traefik/docker-compose.yml
@@ -0,0 +1,68 @@
+services:
+ openmu-startup:
+ image: munique/openmu
+ container_name: openmu-startup
+ networks:
+ - proxy
+ ports:
+ - "55901:55901"
+ - "55902:55902"
+ - "55903:55903"
+ - "55904:55904"
+ - "55905:55905"
+ - "55906:55906"
+ - "44405:44405"
+ - "55980:55980"
+ environment:
+ DB_HOST: database
+ working_dir: /app/
+ volumes:
+ - ./.htpasswd:/etc/nginx/.htpasswd
+ depends_on:
+ - database
+ labels:
+ - "traefik.enable=true"
+ - "traefik.docker.network=proxy"
+ - "traefik.http.routers.openmu.entrypoints=web"
+ - "traefik.http.routers.openmu.rule=Host(`admin.docker.localhost`)"
+ - "traefik.http.routers.openmu.middlewares=auth"
+ - "traefik.http.middlewares.auth.basicauth.usersfile=.htpasswd"
+
+ database:
+ image: postgres
+ container_name: database
+ environment:
+ POSTGRES_PASSWORD: admin
+ POSTGRES_DB: openmu
+ POSTGRES_USER: postgres
+ networks:
+ - proxy
+ ports:
+ - "5432:5432"
+ volumes:
+ - dbdata:/var/lib/postgresql #store data on volume
+
+ traefik:
+ image: "traefik"
+ container_name: "traefik"
+ restart: always
+ command:
+ - "--log.level=DEBUG"
+ - "--api.insecure=true"
+ - "--providers.docker=true"
+ - "--providers.docker.exposedbydefault=false"
+ - "--entrypoints.web.address=:80"
+ ports:
+ - "80:80"
+ volumes:
+ - "/var/run/docker.sock:/var/run/docker.sock:ro"
+ - "./.htpasswd:/.htpasswd"
+ networks:
+ - proxy
+
+networks:
+ proxy:
+ external: true
+
+volumes:
+ dbdata:
diff --git a/deploy/all-in-one/.dockerignore b/deploy/all-in-one/.dockerignore
new file mode 100644
index 0000000..3729ff0
--- /dev/null
+++ b/deploy/all-in-one/.dockerignore
@@ -0,0 +1,25 @@
+**/.classpath
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/azds.yaml
+**/bin
+**/charts
+**/docker-compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+LICENSE
+README.md
\ No newline at end of file
diff --git a/deploy/all-in-one/.htpasswd b/deploy/all-in-one/.htpasswd
new file mode 100644
index 0000000..1fa6225
--- /dev/null
+++ b/deploy/all-in-one/.htpasswd
@@ -0,0 +1,7 @@
+# This file contains the passwords for the admin panel and tracing.
+# Change these passwords in production!
+# default is admin:openmu, hashed with bcrypt.
+
+admin:$2y$10$xYL2d/QEukwGmX0uNZubsunL0qcANuTYkpapRVdlu5q3ymCpvOEh.
+
+# this file should always end with an empty line!
diff --git a/deploy/all-in-one/README.md b/deploy/all-in-one/README.md
new file mode 100644
index 0000000..034b64d
--- /dev/null
+++ b/deploy/all-in-one/README.md
@@ -0,0 +1,89 @@
+# All-in-one deployment
+
+The all in one deployment is recommended, if you want to host on a small machine
+with a low amount of players.
+
+In this case, all kinds of OpenMU subsystems (ConnectServer, GameServer, LoginServer,
+AdminPanel, ...) are running in one process.
+
+## Deployment with docker-compose
+
+### Install GIT
+
+See [https://github.com/git-guides/install-git](https://github.com/git-guides/install-git).
+
+### Clone the repository
+
+`git clone https://github.com/MUnique/OpenMU.git`
+
+It will create a new folder OpenMU with the repository contents inside.
+
+### Navigate to the docker-compose files
+
+Navigate to the folder deploy/all-in-one
+
+`cd deploy/all-in-one`
+
+### Option A - for local testing
+
+To use the official docker image, just run:
+
+`docker compose up -d --no-build`
+
+And that's it. It's then available on your local computer through a loopback ip.
+
+However, if you want to make it available through the internet, you should choose
+Option B:
+
+### Option B - with HTTPS
+
+If you want to share your server with the world, it's recommended to set up HTTPS
+for nginx. Otherwise, traffic from and to the admin panel is not encrypted.
+
+#### Set your domain name as environment variable
+
+Specify the environment variable ```DOMAIN_NAME``` for docker compose.
+This can be done in [various ways](https://docs.docker.com/compose/environment-variables/set-environment-variables/),
+e.g. by editing the ```docker-compose.prod.yml``` or
+[setting it in your shell](https://phoenixnap.com/kb/linux-set-environment-variable).
+
+This variable is replaced in the nginx template config files which get included
+in the other configuration files.
+
+#### Run it
+
+`docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d`
+
+#### Run certbot explicitly
+
+Hint: replace "example.org" with your domain.
+
+`docker compose -f docker-compose.yml -f docker-compose.prod.yml run --rm certbot certonly --webroot --webroot-path /var/www/certbot/ -d example.org`
+
+#### Set up certificate renewal
+
+Because your certificates expire after 3 months, it's recommended to renew them regularly.
+To renew it, run this command:
+
+`docker compose -f docker-compose.yml -f docker-compose.prod.yml run --rm certbot renew`
+
+Of course, it would make sense to add a cron job (e.g. once a week) on your host
+machine for that.
+
+## What's next
+
+The server is automatically started and initialized for Season 6. You can start
+playing, if you want :-)
+
+Additionally, take a look at the AdminPanel.
+
+If your containers run on docker at your local machine, you can simply go to `http://localhost/`.
+The default username is 'admin', the password 'openmu'. You should change that later.
+
+If you want to run another game version, you can go to the setup page through
+the navigation menu, where you can select your desired game version,
+number of game servers (just the data of it), and if test accounts
+should be created.
+
+If you click on 'Install', wait a bit until the database is set up and filled with the
+data and voila, OpenMU is ready to use.
diff --git a/deploy/all-in-one/docker-compose-all-in-one.dcproj b/deploy/all-in-one/docker-compose-all-in-one.dcproj
new file mode 100644
index 0000000..e2f1e1b
--- /dev/null
+++ b/deploy/all-in-one/docker-compose-all-in-one.dcproj
@@ -0,0 +1,30 @@
+
+
+
+ 2.1
+ Linux
+ 6518d58a-5f07-4341-87af-fcf3715d2554
+ None
+ {Scheme}://localhost:{ServicePort}
+
+
+ openmu-startup
+
+
+
+ docker-compose.yml
+
+
+ docker-compose.yml
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/deploy/all-in-one/docker-compose.override.yml b/deploy/all-in-one/docker-compose.override.yml
new file mode 100644
index 0000000..1c7a21a
--- /dev/null
+++ b/deploy/all-in-one/docker-compose.override.yml
@@ -0,0 +1,13 @@
+services:
+
+ openmu-startup:
+ build:
+ context: ../../src
+ dockerfile: Startup/Dockerfile
+ restart: "no"
+ ports:
+ - "8081:8080"
+
+ database:
+ ports:
+ - "5433:5432"
diff --git a/deploy/all-in-one/docker-compose.prod.yml b/deploy/all-in-one/docker-compose.prod.yml
new file mode 100644
index 0000000..086b12f
--- /dev/null
+++ b/deploy/all-in-one/docker-compose.prod.yml
@@ -0,0 +1,46 @@
+services:
+ openmu-startup:
+ restart: "unless-stopped"
+ environment:
+ ASPNETCORE_ENVIRONMENT: Production
+
+ database:
+ restart: "unless-stopped"
+
+ nginx-80:
+ restart: "unless-stopped"
+ environment:
+ DOMAIN_NAME: ${DOMAIN_NAME}
+ volumes:
+ - ./nginx/nginx.prod80.conf:/etc/nginx/nginx.conf:ro
+ - ./.htpasswd:/etc/nginx/.htpasswd
+ - ./certbot/www:/var/www/certbot/:ro
+ - ./nginx/templates/nginx.server_name.conf.template:/etc/nginx/templates/nginx.server_name.conf.template:ro
+
+# We add another nginx here, just for HTTPs.
+# The reason is, that it's not starting when we combine port 80 and 443 until
+# the certificates are created.
+# So, when running the certbot the first time, we need to be able to access
+# the port 80 - and we solve this by separating the server configs.
+ nginx-443:
+ image: nginx:alpine
+ container_name: nginx-443
+ restart: "unless-stopped"
+ environment:
+ DOMAIN_NAME: ${DOMAIN_NAME}
+ ports:
+ - "443:443"
+ volumes:
+ - ./nginx/nginx.prod443.conf:/etc/nginx/nginx.conf:ro
+ - ./.htpasswd:/etc/nginx/.htpasswd
+ - ./certbot/conf/:/etc/nginx/ssl/:ro
+ - ./nginx/templates/nginx.server_name.conf.template:/etc/nginx/templates/nginx.server_name.conf.template:ro
+ - ./nginx/templates/nginx.prod.certificates.conf.template:/etc/nginx/templates/nginx.prod.certificates.conf.template:ro
+ depends_on:
+ - openmu-startup
+
+ certbot:
+ image: certbot/certbot:latest
+ volumes:
+ - ./certbot/www/:/var/www/certbot/:rw
+ - ./certbot/conf/:/etc/letsencrypt/:rw
\ No newline at end of file
diff --git a/deploy/all-in-one/docker-compose.yml b/deploy/all-in-one/docker-compose.yml
new file mode 100644
index 0000000..725620b
--- /dev/null
+++ b/deploy/all-in-one/docker-compose.yml
@@ -0,0 +1,49 @@
+services:
+ nginx-80:
+ image: nginx:alpine
+ container_name: nginx-80
+ ports:
+ - "80:80"
+ volumes:
+ - ./nginx/nginx.dev.conf:/etc/nginx/nginx.conf:ro
+ - ./.htpasswd:/etc/nginx/.htpasswd
+ depends_on:
+ - openmu-startup
+
+ openmu-startup:
+ image: munique/openmu
+ container_name: openmu-startup
+ ports:
+ - "8080"
+ - "55901:55901"
+ - "55902:55902"
+ - "55903:55903"
+ - "55904:55904"
+ - "55905:55905"
+ - "55906:55906"
+ - "44405:44405"
+ - "44406:44406"
+ - "55980:55980"
+ environment:
+ DB_HOST: database
+ ASPNETCORE_URLS: http://+:8080
+ working_dir: /app/
+ volumes:
+ - ./.htpasswd:/etc/nginx/.htpasswd
+ depends_on:
+ - database
+
+ database:
+ image: postgres
+ container_name: database
+ environment:
+ POSTGRES_PASSWORD: admin
+ POSTGRES_DB: openmu
+ POSTGRES_USER: postgres
+ ports:
+ - "5432"
+ volumes:
+ - dbdata:/var/lib/postgresql #store data on volume
+
+volumes:
+ dbdata:
\ No newline at end of file
diff --git a/deploy/all-in-one/nginx/nginx.dev.conf b/deploy/all-in-one/nginx/nginx.dev.conf
new file mode 100644
index 0000000..040f106
--- /dev/null
+++ b/deploy/all-in-one/nginx/nginx.dev.conf
@@ -0,0 +1,26 @@
+events {
+}
+
+http {
+ # this is required to proxy Grafana Live WebSocket connections.
+ map $http_upgrade $connection_upgrade {
+ default upgrade;
+ '' close;
+ }
+
+ server {
+ auth_basic "Protected Site";
+ auth_basic_user_file /etc/nginx/.htpasswd;
+
+ listen 80;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+
+ resolver 127.0.0.11 ipv6=off;
+
+ location / {
+ proxy_pass http://openmu-startup:8080;
+ }
+ }
+}
\ No newline at end of file
diff --git a/deploy/all-in-one/nginx/nginx.prod443.conf b/deploy/all-in-one/nginx/nginx.prod443.conf
new file mode 100644
index 0000000..d6e569a
--- /dev/null
+++ b/deploy/all-in-one/nginx/nginx.prod443.conf
@@ -0,0 +1,32 @@
+events {
+}
+
+http {
+ # this is required to proxy Grafana Live WebSocket connections.
+ map $http_upgrade $connection_upgrade {
+ default upgrade;
+ '' close;
+ }
+
+ server {
+ listen 443 default_server ssl http2;
+ listen [::]:443 ssl http2;
+
+ include conf.d/nginx.server_name.conf;
+ include conf.d/nginx.prod.certificates.conf;
+
+ auth_basic "Protected Site";
+ auth_basic_user_file /etc/nginx/.htpasswd;
+
+ listen 80;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+
+ resolver 127.0.0.11 ipv6=off;
+
+ location / {
+ proxy_pass http://openmu-startup:8080;
+ }
+ }
+}
\ No newline at end of file
diff --git a/deploy/all-in-one/nginx/nginx.prod80.conf b/deploy/all-in-one/nginx/nginx.prod80.conf
new file mode 100644
index 0000000..8a8a1a7
--- /dev/null
+++ b/deploy/all-in-one/nginx/nginx.prod80.conf
@@ -0,0 +1,20 @@
+events {
+}
+
+http {
+ server {
+ listen 80;
+ listen [::]:80;
+
+ include conf.d/nginx.server_name.conf;
+ server_tokens off;
+
+ location /.well-known/acme-challenge/ {
+ root /var/www/certbot;
+ }
+
+ location / {
+ return 301 https://$server_name$request_uri;
+ }
+ }
+}
\ No newline at end of file
diff --git a/deploy/all-in-one/nginx/templates/nginx.prod.certificates.conf.template b/deploy/all-in-one/nginx/templates/nginx.prod.certificates.conf.template
new file mode 100644
index 0000000..c7943fe
--- /dev/null
+++ b/deploy/all-in-one/nginx/templates/nginx.prod.certificates.conf.template
@@ -0,0 +1,2 @@
+ ssl_certificate /etc/nginx/ssl/live/$DOMAIN_NAME/fullchain.pem;
+ ssl_certificate_key /etc/nginx/ssl/live/$DOMAIN_NAME/privkey.pem;
\ No newline at end of file
diff --git a/deploy/all-in-one/nginx/templates/nginx.server_name.conf.template b/deploy/all-in-one/nginx/templates/nginx.server_name.conf.template
new file mode 100644
index 0000000..e0d9020
--- /dev/null
+++ b/deploy/all-in-one/nginx/templates/nginx.server_name.conf.template
@@ -0,0 +1 @@
+ server_name $DOMAIN_NAME;
\ No newline at end of file
diff --git a/deploy/distributed/.dockerignore b/deploy/distributed/.dockerignore
new file mode 100644
index 0000000..3729ff0
--- /dev/null
+++ b/deploy/distributed/.dockerignore
@@ -0,0 +1,25 @@
+**/.classpath
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/azds.yaml
+**/bin
+**/charts
+**/docker-compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+LICENSE
+README.md
\ No newline at end of file
diff --git a/deploy/distributed/.htpasswd b/deploy/distributed/.htpasswd
new file mode 100644
index 0000000..1fa6225
--- /dev/null
+++ b/deploy/distributed/.htpasswd
@@ -0,0 +1,7 @@
+# This file contains the passwords for the admin panel and tracing.
+# Change these passwords in production!
+# default is admin:openmu, hashed with bcrypt.
+
+admin:$2y$10$xYL2d/QEukwGmX0uNZubsunL0qcANuTYkpapRVdlu5q3ymCpvOEh.
+
+# this file should always end with an empty line!
diff --git a/deploy/distributed/README.md b/deploy/distributed/README.md
new file mode 100644
index 0000000..e60fbc1
--- /dev/null
+++ b/deploy/distributed/README.md
@@ -0,0 +1,121 @@
+# Distributed
+
+*!!! CURRENTLY BROKEN AND UNSUPPORTED, DOCS ARE OUT OF DATE !!!*
+
+## Deployment with docker-compose
+
+Currently, we just have a docker-compose file for the deployment.
+docker-compose has the limitation, that all runs on the same physical machine.
+
+For an even more distributed environment, with more machines, kubernetes can be
+used. However, we don't have a finished configuration for kubernetes, yet. If you
+are familiar with kubernetes, all contributions are welcome for kubernetes
+configuration files.
+
+So, these are the steps, if you want to deploy it with docker-compose:
+
+### Install GIT
+
+See [https://github.com/git-guides/install-git](https://github.com/git-guides/install-git).
+
+### Clone the repository
+
+`git clone https://github.com/MUnique/OpenMU.git`
+
+It will create a new folder OpenMU with the repository contents inside.
+
+### Navigate to the docker-compose files
+
+Navigate to the folder deploy/distributed
+
+`cd deploy/distributed`
+
+### Option A - for local testing
+
+To use the official docker images, just run:
+
+`docker compose up -d --no-build`
+
+And that's it. It's then available on your local computer through a loopback ip.
+
+However, if you want to make it available through the internet, you should choose
+Option B:
+
+### Option B - with HTTPS
+
+If you want to share your server with the world, it's recommended to set up HTTPS
+for nginx. Otherwise, traffic from and to the admin panel is not encrypted.
+
+#### Set your domain name as environment variable
+
+Specify the environment variable ```DOMAIN_NAME``` for docker compose.
+This can be done in [various ways](https://docs.docker.com/compose/environment-variables/set-environment-variables/),
+e.g. by editing the ```docker-compose.prod.yml``` or
+[setting it in your shell](https://phoenixnap.com/kb/linux-set-environment-variable).
+
+This variable is replaced in the nginx template config files which get included
+in the other configuration files.
+
+#### Run it
+
+`docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d`
+
+#### Run certbot explicitly
+
+Hint: replace "example.org" with your domain.
+
+`docker compose -f docker-compose.yml -f docker-compose.prod.yml run --rm certbot certonly --webroot --webroot-path /var/www/certbot/ -d example.org`
+
+#### Set up certificate renewal
+
+Because your certificates expire after 3 months, it's recommended to renew them regularly.
+To renew it, run this command:
+
+`docker compose -f docker-compose.yml -f docker-compose.prod.yml run --rm certbot renew`
+
+Of course, it would make sense to add a cron job (e.g. once a week) on your host
+machine for that.
+
+## What's next
+
+Now, when you have deployed OpenMU, it's time to discover the AdminPanel.
+
+If your containers run on docker at your local machine, you can simply go to `http://localhost/admin`.
+The default username is 'admin', the password 'openmu'. You should change that later.
+
+There you'll find a setup in the navigation menu, where you can select your desired
+game version, number of game servers (just the data of it), and if test accounts
+should be created.
+
+Click on 'Install', wait a bit until the database is set up and filled with the
+data.
+OpenMU should now be ready to use.
+
+## Environment variables
+
+The openmu images which are used in this docker-compose consider the following
+environment variables:
+
+### ASPNETCORE_ENVIRONMENT
+
+It's usually specified correctly in the docker-compose files. It has effect on
+the ip resolver, see below.
+
+### RESOLVE_IP
+
+Similar to the -resolveIp starting parameter of the all-in-one startup project,
+you're able to control the ip resolving with this variable. The defaults usually
+work fine here, so you should try not to set this variable.
+
+| Value | Description |
+|-------|---------------------|
+| local | Default value in a *Development* environment. Determines a local ip. If none is found, a loopback IP is used (127.127.127.127). |
+| public | Default value in a *Production* environment. The public ip is automatically determined by an [external API](https://www.ipify.org/). |
+| loopback | Returns *127.127.127.127*, usefully only if you run your server and client on the same machine. |
+| [custom ip] | You can specify a custom ip, if needed. Example: *192.168.0.1* |
+
+### GS_ID
+
+It's usually specified correctly in the docker-compose files or each game server.
+Specifies the id of a game server, and is used to retrieve the GameServerConfiguration
+from the database.
diff --git a/deploy/distributed/dapr-components/config.yaml b/deploy/distributed/dapr-components/config.yaml
new file mode 100644
index 0000000..16e348d
--- /dev/null
+++ b/deploy/distributed/dapr-components/config.yaml
@@ -0,0 +1,11 @@
+apiVersion: dapr.io/v1alpha1
+kind: Configuration
+metadata:
+ name: daprConfig
+ namespace: default
+spec:
+ tracing:
+ enabled: true
+ samplingRate: "1"
+ zipkin:
+ endpointAddress: "http://zipkin:9411/api/v2/spans"
diff --git a/deploy/distributed/dapr-components/localSecretStore.yaml b/deploy/distributed/dapr-components/localSecretStore.yaml
new file mode 100644
index 0000000..9d81d6d
--- /dev/null
+++ b/deploy/distributed/dapr-components/localSecretStore.yaml
@@ -0,0 +1,13 @@
+apiVersion: dapr.io/v1alpha1
+kind: Component
+metadata:
+ name: secrets
+ namespace: default
+spec:
+ type: secretstores.local.file
+ version: v1
+ metadata:
+ - name: secretsFile
+ value: components/secrets.json
+ - name: nestedSeparator
+ value: ":"
diff --git a/deploy/distributed/dapr-components/login-state.yaml b/deploy/distributed/dapr-components/login-state.yaml
new file mode 100644
index 0000000..ff5c96e
--- /dev/null
+++ b/deploy/distributed/dapr-components/login-state.yaml
@@ -0,0 +1,13 @@
+apiVersion: dapr.io/v1alpha1
+kind: Component
+metadata:
+ name: login-state
+spec:
+ spec:
+ type: state.redis
+ version: v1
+ metadata:
+ - name: redisHost
+ value: redis-state:6379
+ #- name: ttlInSeconds
+ # value: # Optional
\ No newline at end of file
diff --git a/deploy/distributed/dapr-components/pubsub.yaml b/deploy/distributed/dapr-components/pubsub.yaml
new file mode 100644
index 0000000..5377637
--- /dev/null
+++ b/deploy/distributed/dapr-components/pubsub.yaml
@@ -0,0 +1,20 @@
+apiVersion: dapr.io/v1alpha1
+kind: Component
+metadata:
+ name: pubsub
+spec:
+ type: pubsub.rabbitmq
+ version: v1
+ metadata:
+ - name: host
+ value: "amqp://rabbit:5672"
+ - name: durable
+ value: "true"
+ - name: deletedWhenUnused
+ value: "false"
+ - name: autoAck
+ value: "false"
+ - name: reconnectWait
+ value: "0"
+ - name: concurrency
+ value: parallel
\ No newline at end of file
diff --git a/deploy/distributed/dapr-components/secrets.json b/deploy/distributed/dapr-components/secrets.json
new file mode 100644
index 0000000..38da013
--- /dev/null
+++ b/deploy/distributed/dapr-components/secrets.json
@@ -0,0 +1,11 @@
+{
+ "connectionStrings": {
+ "MUnique.OpenMU.Persistence.EntityFramework.EntityDataContext": "Server=database;Port=5432;User Id=postgres;Password=admin;Database=openmu;Command Timeout=120;",
+ "MUnique.OpenMU.Persistence.EntityFramework.TypedContext": "Server=database;Port=5432;User Id=postgres;Password=admin;Database=openmu;Command Timeout=120;",
+ "MUnique.OpenMU.Persistence.EntityFramework.ConfigurationContext": "Server=database;Port=5432;User Id=config;Password=config;Database=openmu;Command Timeout=120;",
+ "MUnique.OpenMU.Persistence.EntityFramework.AccountContext": "Server=database;Port=5432;User Id=account;Password=account;Database=openmu;Command Timeout=120;",
+ "MUnique.OpenMU.Persistence.EntityFramework.TradeContext": "Server=database;Port=5432;User Id=account;Password=account;Database=openmu;Command Timeout=120;",
+ "MUnique.OpenMU.Persistence.EntityFramework.FriendContext": "Server=database;Port=5432;User Id=friend;Password=friend;Database=openmu;Command Timeout=120;",
+ "MUnique.OpenMU.Persistence.EntityFramework.GuildContext": "Server=database;Port=5432;User Id=guild;Password=guild;Database=openmu;Command Timeout=120;"
+ }
+}
\ No newline at end of file
diff --git a/deploy/distributed/docker-compose.dcproj b/deploy/distributed/docker-compose.dcproj
new file mode 100644
index 0000000..3c16b2f
--- /dev/null
+++ b/deploy/distributed/docker-compose.dcproj
@@ -0,0 +1,39 @@
+
+
+
+ 2.1
+ Linux
+ 0dfecd55-7aa2-4263-b6fd-ec1c4e6a59b3
+ None
+ {Scheme}://localhost:{ServicePort}
+
+
+ openmu-distributed
+
+
+
+
+
+
+
+
+ docker-compose.yml
+
+
+ docker-compose.yml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/deploy/distributed/docker-compose.override.yml b/deploy/distributed/docker-compose.override.yml
new file mode 100644
index 0000000..2374934
--- /dev/null
+++ b/deploy/distributed/docker-compose.override.yml
@@ -0,0 +1,70 @@
+services:
+ database:
+ ports:
+ - "5433:5432"
+ loki:
+ ports:
+ - "3100:3100"
+ prometheus:
+ ports:
+ - "9000:9090"
+
+ redis-state:
+ ports:
+ - "6379:6379"
+
+ gameServer0:
+ environment:
+ ASPNETCORE_ENVIRONMENT: Development
+ ports:
+ - "81:8080"
+
+ gameServer1:
+ environment:
+ ASPNETCORE_ENVIRONMENT: Development
+ ports:
+ - "82:8080"
+
+ connectServer:
+ build:
+ context: ../../src
+ dockerfile: Dapr/ConnectServer.Host/Dockerfile
+ environment:
+ ASPNETCORE_ENVIRONMENT: Development
+
+ loginServer:
+ build:
+ context: ../../src
+ dockerfile: Dapr/LoginServer.Host/Dockerfile
+ environment:
+ ASPNETCORE_ENVIRONMENT: Development
+
+ friendServer:
+ build:
+ context: ../../src
+ dockerfile: Dapr/FriendServer.Host/Dockerfile
+ environment:
+ ASPNETCORE_ENVIRONMENT: Development
+
+ guildServer:
+ build:
+ context: ../../src
+ dockerfile: Dapr/GuildServer.Host/Dockerfile
+ environment:
+ ASPNETCORE_ENVIRONMENT: Development
+
+ chatServer:
+ build:
+ context: ../../src
+ dockerfile: Dapr/ChatServer.Host/Dockerfile
+ environment:
+ ASPNETCORE_ENVIRONMENT: Development
+
+ adminPanel:
+ build:
+ context: ../../src
+ dockerfile: Dapr/AdminPanel.Host/Dockerfile
+ environment:
+ ASPNETCORE_ENVIRONMENT: Development
+ ports:
+ - "88:8080"
\ No newline at end of file
diff --git a/deploy/distributed/docker-compose.prod.yml b/deploy/distributed/docker-compose.prod.yml
new file mode 100644
index 0000000..1f8a7b9
--- /dev/null
+++ b/deploy/distributed/docker-compose.prod.yml
@@ -0,0 +1,103 @@
+volumes:
+ certificates:
+ certbot-www:
+
+services:
+ nginx-80:
+ restart: "unless-stopped"
+ environment:
+ DOMAIN_NAME: ${DOMAIN_NAME}
+ volumes:
+ - ./nginx.server_name.conf.template:/etc/nginx/templates/nginx.server_name.conf.template
+ - ./nginx.prod80.conf:/etc/nginx/nginx.conf:ro
+ - ./.htpasswd:/etc/nginx/.htpasswd
+ - certbot-www:/var/www/certbot/:ro
+
+# We add another nginx here, just for HTTPs.
+# The reason is, that it's not starting when we combine port 80 and 443 until
+# the certificates are created.
+# So, when running the certbot the first time, we need to be able to access
+# the port 80 - and we solve this by separating the server configs.
+ nginx-443:
+ image: nginx:alpine
+ container_name: nginx-443
+ restart: "unless-stopped"
+ ports:
+ - "443:443"
+ environment:
+ DOMAIN_NAME: ${DOMAIN_NAME}
+ volumes:
+ - ./nginx.server_name.conf.template:/etc/nginx/templates/nginx.server_name.conf.template
+ - ./nginx.prod.certificates.conf.template:/etc/nginx/templates/nginx.prod.certificates.conf.template
+ - ./nginx.prod443.conf:/etc/nginx/nginx.conf:ro
+ - ./.htpasswd:/etc/nginx/.htpasswd
+ - certificates:/etc/nginx/ssl/:ro
+ depends_on:
+ - grafana
+ - zipkin
+ - prometheus
+
+ certbot:
+ image: certbot/certbot:latest
+ volumes:
+ - certbot-www:/var/www/certbot/:rw
+ - certificates:/etc/letsencrypt/:rw
+
+ grafana:
+ restart: "unless-stopped"
+ prometheus:
+ restart: "unless-stopped"
+ loki:
+ restart: "unless-stopped"
+ minio:
+ restart: "unless-stopped"
+ redis-state:
+ restart: "unless-stopped"
+ dapr-placement:
+ restart: "unless-stopped"
+ rabbit:
+ restart: "unless-stopped"
+ database:
+ restart: "unless-stopped"
+ zipkin:
+ restart: "unless-stopped"
+
+ connectServer:
+ restart: "unless-stopped"
+ environment:
+ ASPNETCORE_ENVIRONMENT: Production
+
+ loginServer:
+ restart: "unless-stopped"
+ environment:
+ ASPNETCORE_ENVIRONMENT: Production
+
+ friendServer:
+ restart: "unless-stopped"
+ environment:
+ ASPNETCORE_ENVIRONMENT: Production
+
+ guildServer:
+ restart: "unless-stopped"
+ environment:
+ ASPNETCORE_ENVIRONMENT: Production
+
+ chatServer:
+ restart: "unless-stopped"
+ environment:
+ ASPNETCORE_ENVIRONMENT: Production
+
+ adminPanel:
+ restart: "unless-stopped"
+ environment:
+ ASPNETCORE_ENVIRONMENT: Production
+
+ gameServer0:
+ restart: "unless-stopped"
+ environment:
+ ASPNETCORE_ENVIRONMENT: Production
+
+ gameServer1:
+ restart: "unless-stopped"
+ environment:
+ ASPNETCORE_ENVIRONMENT: Production
diff --git a/deploy/distributed/docker-compose.yml b/deploy/distributed/docker-compose.yml
new file mode 100644
index 0000000..18c2725
--- /dev/null
+++ b/deploy/distributed/docker-compose.yml
@@ -0,0 +1,352 @@
+volumes:
+ dbdata:
+ prometheus-data:
+ minio-data:
+
+services:
+ nginx-80:
+ image: nginx:alpine
+ container_name: nginx-80
+ restart: always
+ ports:
+ - "80:80"
+ volumes:
+ - ./nginx.dev.conf:/etc/nginx/nginx.conf
+ - ./.htpasswd:/etc/nginx/.htpasswd
+ depends_on:
+ - grafana
+ - zipkin
+ - prometheus
+
+ grafana:
+ image: grafana/grafana:9.5.14
+ container_name: grafana
+ volumes:
+ - ./grafana.ini:/etc/grafana/grafana.ini
+ - ./grafana_datasources.yaml:/etc/grafana/provisioning/datasources/ds.yaml
+ depends_on:
+ - loki
+ - prometheus
+ ports:
+ - 3000
+
+ prometheus:
+ image: prom/prometheus:v2.21.0
+ ports:
+ - 9090
+ volumes:
+ - ./prometheus.yml:/etc/prometheus/prometheus.yml
+ - prometheus-data:/prometheus
+ command: --web.enable-lifecycle --config.file=/etc/prometheus/prometheus.yml
+
+ loki:
+ image: grafana/loki:2.5.0
+ container_name: loki
+ command: "-config.file=/etc/loki/config.yaml -target=all"
+ ports:
+ - 3100
+ - 7946
+ - 9095
+ volumes:
+ - ./loki-config.yaml:/etc/loki/config.yaml
+ depends_on:
+ - minio
+
+ minio:
+ image: minio/minio
+ entrypoint:
+ - sh
+ - -euc
+ - |
+ mkdir -p /data/loki-data && \
+ mkdir -p /data/loki-ruler && \
+ minio server /data
+ environment:
+ - MINIO_ACCESS_KEY=loki
+ - MINIO_SECRET_KEY=supersecret
+ - MINIO_PROMETHEUS_AUTH_TYPE=public
+ - MINIO_UPDATE=off
+ ports:
+ - 9000
+ volumes:
+ - minio-data:/data
+
+ redis-state:
+ image: redis
+ restart: always
+ ports:
+ - 6379
+ environment:
+ ALLOW_EMPTY_PASSWORD: "yes"
+
+ dapr-placement:
+ image: "daprio/dapr"
+ container_name: dapr_placement
+ command: ["./placement", "-port", "50006"]
+ ports:
+ - "50006"
+
+ rabbit:
+ image: rabbitmq:3-management-alpine
+ container_name: pubsub_rabbitmq
+ restart: always
+ ports:
+ - "5672"
+
+ database:
+ image: postgres
+ container_name: postgres
+ restart: always
+ environment:
+ POSTGRES_PASSWORD: admin
+ POSTGRES_DB: openmu
+ POSTGRES_USER: postgres
+ ports:
+ - "5432"
+ volumes:
+ - dbdata:/var/lib/postgresql
+
+ zipkin:
+ image: openzipkin/zipkin
+ container_name: tracing_zipkin
+ restart: always
+ ports:
+ - "9411:9411"
+
+ connectServer:
+ image: munique/openmu-connect
+ container_name: connectServer
+ ports:
+ - "50001"
+ - "44405:44405"
+ - "9464" # Prometheus
+ environment:
+ APPID: connectServer
+ ASPNETCORE_URLS: http://+:8080
+ depends_on:
+ - rabbit
+ - zipkin
+ - database
+
+ connectServer-dapr:
+ image: "daprio/daprd:latest"
+ container_name: connectServer_dapr
+ command: [ "./daprd", "-app-id", "connectServer", "-app-port", "8080",
+ "-components-path", "/components",
+ "-config", "/components/config.yaml",
+ "-placement-host-address", "dapr-placement:50006" ]
+ depends_on:
+ - connectServer
+ network_mode: "service:connectServer"
+ volumes:
+ - "./dapr-components/:/components"
+
+ loginServer:
+ image: munique/openmu-login
+ container_name: loginServer
+ ports:
+ - "50001"
+ - "9464" # Prometheus
+ environment:
+ ASPNETCORE_URLS: http://+:8080
+ depends_on:
+ - zipkin
+ - database
+
+ loginServer-dapr:
+ image: "daprio/daprd:latest"
+ container_name: loginServer_dapr
+ command: [ "./daprd", "-app-id", "loginServer", "-app-port", "8080",
+ "-components-path", "/components",
+ "-config", "/components/config.yaml",
+ "-placement-host-address", "dapr-placement:50006" ]
+ depends_on:
+ - loginServer
+ network_mode: "service:loginServer"
+ volumes:
+ - "./dapr-components/:/components"
+
+ friendServer:
+ image: munique/openmu-friend
+ container_name: friendServer
+ ports:
+ - "50001"
+ - "9464" # Prometheus
+ environment:
+ ASPNETCORE_URLS: http://+:8080
+ depends_on:
+ - chatServer-dapr
+ - database
+ - zipkin
+
+ friendServer-dapr:
+ image: "daprio/daprd:latest"
+ container_name: friendServer_dapr
+ command: [ "./daprd", "-app-id", "friendServer", "-app-port", "8080",
+ "-components-path", "/components",
+ "-config", "/components/config.yaml",
+ "-placement-host-address", "dapr-placement:50006" ]
+ depends_on:
+ - friendServer
+ network_mode: "service:friendServer"
+ volumes:
+ - "./dapr-components/:/components"
+
+ guildServer:
+ image: munique/openmu-guild
+ container_name: guildServer
+ ports:
+ - "50001"
+ - "9464" # Prometheus
+ environment:
+ ASPNETCORE_URLS: http://+:8080
+ depends_on:
+ - database
+ - zipkin
+
+ guildServer-dapr:
+ image: "daprio/daprd:latest"
+ container_name: guildServer_dapr
+ command: [ "./daprd", "-app-id", "guildServer", "-app-port", "8080",
+ "-components-path", "/components",
+ "-config", "/components/config.yaml",
+ "-placement-host-address", "dapr-placement:50006" ]
+ depends_on:
+ - guildServer
+ network_mode: "service:guildServer"
+ volumes:
+ - "./dapr-components/:/components"
+
+ chatServer:
+ image: munique/openmu-chat
+ container_name: chatServer
+ environment:
+ APPID: chatServer
+ ASPNETCORE_URLS: http://+:8080
+ ports:
+ - "55980:55980"
+ - "50001"
+ - "9464" # Prometheus
+ depends_on:
+ - database
+ - zipkin
+
+ chatServer-dapr:
+ image: "daprio/daprd:latest"
+ container_name: chatServer_dapr
+ command: [ "./daprd", "-app-id", "chatServer", "-app-port", "8080",
+ "-components-path", "/components",
+ "-config", "/components/config.yaml",
+ "-placement-host-address", "dapr-placement:50006" ]
+ depends_on:
+ - chatServer
+ network_mode: "service:chatServer"
+ volumes:
+ - "./dapr-components/:/components"
+
+
+ adminPanel:
+ image: munique/openmu-admin
+ container_name: adminPanel
+ depends_on:
+ - database
+ ports:
+ - "8080"
+ - "9464" # Prometheus
+ environment:
+ ASPNETCORE_URLS: http://+:8080
+ PATH_BASE: /admin/
+ volumes:
+ - ./.htpasswd:/etc/nginx/.htpasswd
+
+ adminPanel-dapr:
+ image: "daprio/daprd:latest"
+ container_name: adminPanel_dapr
+ command: [ "./daprd", "-app-id", "adminPanel", "-app-port", "8080",
+ "-components-path", "/components",
+ "-config", "/components/config.yaml",
+ "-placement-host-address", "dapr-placement:50006" ]
+
+ depends_on:
+ - adminPanel
+ network_mode: "service:adminPanel"
+ volumes:
+ - "./dapr-components/:/components"
+
+ gameServer0:
+ image: munique/openmu-game
+ container_name: gameServer0
+ build:
+ context: ../../src
+ dockerfile: Dapr/GameServer.Host/Dockerfile
+ ports:
+ - "8080"
+ - "50001"
+ - "55901:55901"
+ - "55902:55902"
+ - "9464" # Prometheus
+ environment:
+ GS_ID: 0
+ APPID: gameServer0
+ PATH_BASE: /gameServer/0/
+ ASPNETCORE_URLS: http://+:8080
+ depends_on:
+ - connectServer-dapr
+ - loginServer-dapr
+ - friendServer-dapr
+ - rabbit
+ - zipkin
+ - database
+ - loki
+
+ gameServer0-dapr:
+ image: "daprio/daprd:latest"
+ container_name: gameserver0_dapr
+ command: [ "./daprd", "-app-id", "gameServer0", "-app-port", "8080",
+ "-components-path", "/components",
+ "-config", "/components/config.yaml",
+ "-placement-host-address", "dapr-placement:50006" ]
+ depends_on:
+ - gameServer0
+ network_mode: "service:gameServer0"
+ volumes:
+ - "./dapr-components/:/components"
+
+ gameServer1:
+ image: munique/openmu-game
+ container_name: gameServer1
+ build:
+ context: ../../src
+ dockerfile: Dapr/GameServer.Host/Dockerfile
+ ports:
+ - "8080"
+ - "50001"
+ - "55903:55903"
+ - "55904:55904"
+ - "9464" # Prometheus
+ environment:
+ GS_ID: 1
+ APPID: gameServer1
+ PATH_BASE: /gameServer/1/
+ ASPNETCORE_URLS: http://+:8080
+ depends_on:
+ - connectServer-dapr
+ - loginServer-dapr
+ - friendServer-dapr
+ - rabbit
+ - zipkin
+ - database
+ - loki
+
+ gameServer1-dapr:
+ image: "daprio/daprd:latest"
+ container_name: gameserver1_dapr
+ command: [ "./daprd", "-app-id", "gameServer1", "-app-port", "8080",
+ "-components-path", "/components",
+ "-config", "/components/config.yaml",
+ "-placement-host-address", "dapr-placement:50006" ]
+ depends_on:
+ - gameServer1
+ network_mode: "service:gameServer1"
+ volumes:
+ - "./dapr-components/:/components"
\ No newline at end of file
diff --git a/deploy/distributed/grafana.ini b/deploy/distributed/grafana.ini
new file mode 100644
index 0000000..1d9cd7f
--- /dev/null
+++ b/deploy/distributed/grafana.ini
@@ -0,0 +1,1157 @@
+##################### Grafana Configuration Example #####################
+#
+# Everything has defaults so you only need to uncomment things you want to
+# change
+
+# possible values : production, development
+;app_mode = production
+
+# instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty
+;instance_name = ${HOSTNAME}
+
+# force migration will run migrations that might cause dataloss
+;force_migration = false
+
+#################################### Paths ####################################
+[paths]
+# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
+;data = /var/lib/grafana
+
+# Temporary files in `data` directory older than given duration will be removed
+;temp_data_lifetime = 24h
+
+# Directory where grafana can store logs
+;logs = /var/log/grafana
+
+# Directory where grafana will automatically scan and look for plugins
+;plugins = /var/lib/grafana/plugins
+
+# folder that contains provisioning config files that grafana will apply on startup and while running.
+provisioning = /etc/grafana/provisioning
+
+#################################### Server ####################################
+[server]
+# Protocol (http, https, h2, socket)
+;protocol = http
+
+# The ip address to bind to, empty will bind to all interfaces
+;http_addr =
+
+# The http port to use
+;http_port = 3000
+
+# The public facing domain name used to access grafana from a browser
+domain = localhost
+
+# Redirect to correct domain if host header does not match domain
+# Prevents DNS rebinding attacks
+;enforce_domain = false
+
+# The full public facing url you use in browser, used for redirects and emails
+# If you use reverse proxy and sub path specify full url (with sub path)
+;root_url = %(protocol)s://%(domain)s:%(http_port)s/
+root_url = http://localhost/grafana/
+
+# Serve Grafana from subpath specified in `root_url` setting. By default it is set to `false` for compatibility reasons.
+serve_from_sub_path = true
+
+# Log web requests
+;router_logging = false
+
+# the path relative working path
+;static_root_path = public
+
+# enable gzip
+;enable_gzip = false
+
+# https certs & key file
+;cert_file =
+;cert_key =
+
+# Unix socket path
+;socket =
+
+# CDN Url
+;cdn_url =
+
+# Sets the maximum time using a duration format (5s/5m/5ms) before timing out read of an incoming request and closing idle connections.
+# `0` means there is no timeout for reading the request.
+;read_timeout = 0
+
+#################################### Database ####################################
+[database]
+# You can configure the database connection by specifying type, host, name, user and password
+# as separate properties or as on string using the url properties.
+
+# Either "mysql", "postgres" or "sqlite3", it's your choice
+;type = sqlite3
+;host = 127.0.0.1:3306
+;name = grafana
+;user = root
+# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;"""
+;password =
+
+# Use either URL or the previous fields to configure the database
+# Example: mysql://user:secret@host:port/database
+;url =
+
+# For "postgres" only, either "disable", "require" or "verify-full"
+;ssl_mode = disable
+
+# Database drivers may support different transaction isolation levels.
+# Currently, only "mysql" driver supports isolation levels.
+# If the value is empty - driver's default isolation level is applied.
+# For "mysql" use "READ-UNCOMMITTED", "READ-COMMITTED", "REPEATABLE-READ" or "SERIALIZABLE".
+;isolation_level =
+
+;ca_cert_path =
+;client_key_path =
+;client_cert_path =
+;server_cert_name =
+
+# For "sqlite3" only, path relative to data_path setting
+;path = grafana.db
+
+# Max idle conn setting default is 2
+;max_idle_conn = 2
+
+# Max conn setting default is 0 (mean not set)
+;max_open_conn =
+
+# Connection Max Lifetime default is 14400 (means 14400 seconds or 4 hours)
+;conn_max_lifetime = 14400
+
+# Set to true to log the sql calls and execution times.
+;log_queries =
+
+# For "sqlite3" only. cache mode setting used for connecting to the database. (private, shared)
+;cache_mode = private
+
+# For "mysql" only if lockingMigration feature toggle is set. How many seconds to wait before failing to lock the database for the migrations, default is 0.
+;locking_attempt_timeout_sec = 0
+
+################################### Data sources #########################
+[datasources]
+# Upper limit of data sources that Grafana will return. This limit is a temporary configuration and it will be deprecated when pagination will be introduced on the list data sources API.
+;datasource_limit = 5000
+
+#################################### Cache server #############################
+[remote_cache]
+# Either "redis", "memcached" or "database" default is "database"
+;type = database
+
+# cache connectionstring options
+# database: will use Grafana primary database.
+# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=0,ssl=false`. Only addr is required. ssl may be 'true', 'false', or 'insecure'.
+# memcache: 127.0.0.1:11211
+;connstr =
+
+#################################### Data proxy ###########################
+[dataproxy]
+
+# This enables data proxy logging, default is false
+;logging = false
+
+# How long the data proxy waits to read the headers of the response before timing out, default is 30 seconds.
+# This setting also applies to core backend HTTP data sources where query requests use an HTTP client with timeout set.
+;timeout = 30
+
+# How long the data proxy waits to establish a TCP connection before timing out, default is 10 seconds.
+;dialTimeout = 10
+
+# How many seconds the data proxy waits before sending a keepalive probe request.
+;keep_alive_seconds = 30
+
+# How many seconds the data proxy waits for a successful TLS Handshake before timing out.
+;tls_handshake_timeout_seconds = 10
+
+# How many seconds the data proxy will wait for a server's first response headers after
+# fully writing the request headers if the request has an "Expect: 100-continue"
+# header. A value of 0 will result in the body being sent immediately, without
+# waiting for the server to approve.
+;expect_continue_timeout_seconds = 1
+
+# Optionally limits the total number of connections per host, including connections in the dialing,
+# active, and idle states. On limit violation, dials will block.
+# A value of zero (0) means no limit.
+;max_conns_per_host = 0
+
+# The maximum number of idle connections that Grafana will keep alive.
+;max_idle_connections = 100
+
+# How many seconds the data proxy keeps an idle connection open before timing out.
+;idle_conn_timeout_seconds = 90
+
+# If enabled and user is not anonymous, data proxy will add X-Grafana-User header with username into the request, default is false.
+;send_user_header = false
+
+# Limit the amount of bytes that will be read/accepted from responses of outgoing HTTP requests.
+;response_limit = 0
+
+# Limits the number of rows that Grafana will process from SQL data sources.
+;row_limit = 1000000
+
+#################################### Analytics ####################################
+[analytics]
+# Server reporting, sends usage counters to stats.grafana.org every 24 hours.
+# No ip addresses are being tracked, only simple counters to track
+# running instances, dashboard and error counts. It is very helpful to us.
+# Change this option to false to disable reporting.
+;reporting_enabled = true
+
+# The name of the distributor of the Grafana instance. Ex hosted-grafana, grafana-labs
+;reporting_distributor = grafana-labs
+
+# Set to false to disable all checks to https://grafana.com
+# for new versions of grafana. The check is used
+# in some UI views to notify that a grafana update exists.
+# This option does not cause any auto updates, nor send any information
+# only a GET request to https://raw.githubusercontent.com/grafana/grafana/main/latest.json to get the latest version.
+;check_for_updates = true
+
+# Set to false to disable all checks to https://grafana.com
+# for new versions of plugins. The check is used
+# in some UI views to notify that a plugin update exists.
+# This option does not cause any auto updates, nor send any information
+# only a GET request to https://grafana.com to get the latest versions.
+;check_for_plugin_updates = true
+
+# Google Analytics universal tracking code, only enabled if you specify an id here
+;google_analytics_ua_id =
+
+# Google Tag Manager ID, only enabled if you specify an id here
+;google_tag_manager_id =
+
+# Rudderstack write key, enabled only if rudderstack_data_plane_url is also set
+;rudderstack_write_key =
+
+# Rudderstack data plane url, enabled only if rudderstack_write_key is also set
+;rudderstack_data_plane_url =
+
+# Rudderstack SDK url, optional, only valid if rudderstack_write_key and rudderstack_data_plane_url is also set
+;rudderstack_sdk_url =
+
+# Rudderstack Config url, optional, used by Rudderstack SDK to fetch source config
+;rudderstack_config_url =
+
+# Controls if the UI contains any links to user feedback forms
+;feedback_links_enabled = true
+
+#################################### Security ####################################
+[security]
+# disable creation of admin user on first start of grafana
+;disable_initial_admin_creation = false
+
+# default admin user, created on startup
+;admin_user = admin
+
+# default admin password, can be changed before first start of grafana, or in profile settings
+;admin_password = admin
+
+# used for signing
+;secret_key = SW2YcwTIb9zpOOhoPsMm
+
+# current key provider used for envelope encryption, default to static value specified by secret_key
+;encryption_provider = secretKey.v1
+
+# list of configured key providers, space separated (Enterprise only): e.g., awskms.v1 azurekv.v1
+;available_encryption_providers =
+
+# disable gravatar profile images
+;disable_gravatar = false
+
+# data source proxy whitelist (ip_or_domain:port separated by spaces)
+;data_source_proxy_whitelist =
+
+# disable protection against brute force login attempts
+;disable_brute_force_login_protection = false
+
+# set to true if you host Grafana behind HTTPS. default is false.
+;cookie_secure = false
+
+# set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict", "none" and "disabled"
+;cookie_samesite = lax
+
+# set to true if you want to allow browsers to render Grafana in a ,