├── .gitignore ├── ContainerManager.iml ├── Dockerfile ├── DockerizedCraft.iml ├── LICENSE ├── README.md ├── docker-compose.yml ├── docs ├── README.bbcode ├── container-manager-demo.gif ├── logo-small.png └── logo.png ├── pom.xml ├── scripts └── kubernetes │ └── deployment.yaml └── src └── main ├── java └── de │ └── craftmania │ └── dockerizedcraft │ ├── DockerizedCraft.java │ ├── connection │ └── balancer │ │ ├── BalancedReconnectHandler.java │ │ ├── ConnectionBalancer.java │ │ ├── command │ │ └── GroupCommand.java │ │ ├── model │ │ └── Group.java │ │ ├── session │ │ ├── RedisSessionStorage.java │ │ └── SessionStorage.java │ │ └── strategy │ │ ├── BalanceStrategy.java │ │ └── Strategy.java │ ├── container │ └── inspector │ │ ├── IContainerInspector.java │ │ ├── docker │ │ ├── DockerClientFactory.java │ │ ├── DockerContainerInspector.java │ │ └── ResultCallback.java │ │ ├── events │ │ └── ContainerEvent.java │ │ └── kubernetes │ │ ├── KubernetesContainerInspector.java │ │ └── PodWatcher.java │ ├── plugin │ └── notifier │ │ ├── AbstractNotifier.java │ │ └── serverlist │ │ └── ServerListPluginNotifier.java │ └── server │ └── updater │ ├── ServerUpdater.java │ └── events │ ├── PostAddServerEvent.java │ ├── PostRemoveServerEvent.java │ ├── PreAddServerEvent.java │ └── PreRemoveServerEvent.java └── resources ├── bungee.yml ├── connection-balancer.yml ├── container-inspector.yml ├── plugin-notifier.yml └── server-updater.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | target 26 | 27 | .idea 28 | -------------------------------------------------------------------------------- /ContainerManager.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven as build 2 | 3 | WORKDIR /build 4 | COPY src /build/src 5 | COPY pom.xml /build/ 6 | RUN mvn package 7 | 8 | FROM itzg/bungeecord 9 | 10 | COPY --from=build /build/target/assembly/DockerizedCraft.jar /server/plugins/DockerizedCraft.jar 11 | -------------------------------------------------------------------------------- /DockerizedCraft.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Alexander Mührenberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![DockerizedCraft](/docs/logo-small.png) 2 | 3 | Dockerized Craft - Core 4 | ====================== 5 | 6 | __Never maintain your Bungeecord manually again! Automatically listens to Docker events and adds servers to BungeeCord.__ 7 | 8 | Additionally supporting plugin messaging, connection balancing and proxy server list updates. 9 | 10 | ![DockerizedCraft Preview](/docs/container-manager-demo.gif) 11 | 12 | ## Container Inspector 13 | 14 | ### Features 15 | 16 | - Automatically listens on docker events 17 | - Extracts environment variables from containers 18 | - Triggers BungeeCord Custom events on Docker container events 19 | 20 | ### Configuration 21 | 22 | Check [container-inspector.yml](/src/main/resources/container-inspector.yml) 23 | 24 | ## Server Updater 25 | 26 | ### Features 27 | 28 | - Automatically adds Server to Bungeecord 29 | - Automatically removes Server to Bungeecord 30 | - Configurable add/remove actions 31 | - Supports Health Checks 32 | 33 | ### Configuration 34 | 35 | Check [server-updater.yml](/src/main/resources/server-updater.yml) 36 | 37 | ## Connection Balancer 38 | 39 | ### Features 40 | 41 | - Server Groups 42 | - Default connection group (Fallback/Default servers) 43 | - Forced hosts for groups (i.e. eu lobbies and us lobbies) 44 | - Listens on Container events to add servers to groups based on their environment variables (i.e SERVER_GROUP=lobby) 45 | - Connection Balancing Strategies per group 46 | - balanced: Connect Players to the Server of the group with the fewest players 47 | - More will follow! 48 | - Does not overwrite restrictions! 49 | 50 | 51 | ### Configuration 52 | 53 | Check [connection-balancer.yml](/src/main/resources/connection-balancer.yml) 54 | 55 | 56 | ## Plugin Notifier 57 | 58 | ### Server List Payload 59 | 60 | Send Server information to the single servers. 61 | This can be used to add Sever Selectors etc. 62 | 63 | Add as many information as you want by easy to use environment variable mapping. 64 | 65 | __Example Payload__ (Channel: ContainerManager, Subchannel: ServerData) 66 | 67 | ````json 68 | { 69 | "us-lobby-1":{ 70 | "address":"172.19.0.5:25565", 71 | "host":"172.19.0.5", 72 | "port":25565, 73 | "motd":"A Minecraft Server Instance", 74 | "name":"us-lobby-1", 75 | "proxied_players":12, 76 | "type":"spigot", 77 | "category":"us-lobby", 78 | "tags":"some,awesome,tags" 79 | }, 80 | "eu-lobby-2":{ 81 | "address":"172.19.0.4:25565", 82 | "host":"172.19.0.4", 83 | "port":25565, 84 | "motd":"A Minecraft Server Instance", 85 | "name":"eu-lobby-2", 86 | "proxied_players":5, 87 | "type":"spigot", 88 | "category":"eu-lobby", 89 | "tags":"" 90 | }, 91 | "eu-lobby-1":{ 92 | "address":"172.19.0.3:25565", 93 | "host":"172.19.0.3", 94 | "port":25565, 95 | "motd":"A Minecraft Server Instance", 96 | "name":"eu-lobby-1", 97 | "proxied_players":0, 98 | "type":"spigot", 99 | "category":"eu-lobby", 100 | "tags":"" 101 | } 102 | } 103 | ```` 104 | 105 | ### Configuration 106 | 107 | Check [plugin-notifier.yml](/src/main/resources/plugin-notifier.yml) 108 | 109 | ## Try it yourself 110 | 111 | 1. Checkout the repository 112 | 2. Build the .jar with maven or copy an attached one from the last releases. 113 | 3. run `docker-compose --project-name minecraft up -d` 114 | 4. Wait until all containers did start and connect to localhost with you Minecraft client 115 | 116 | ## Todo's 117 | 118 | - Reduce .jar size -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | bungeecord: 4 | image: itzg/bungeecord 5 | environment: 6 | - UID=1000 7 | ports: 8 | - 25565:25577 9 | volumes: 10 | - ./target/assembly/DockerizedCraft.jar:/server/plugins/DockerizedCraft.jar 11 | - //var/run/docker.sock:/var/run/docker.sock 12 | networks: 13 | - local 14 | 15 | redis: 16 | image: bitnami/redis:latest 17 | environment: 18 | - ALLOW_EMPTY_PASSWORD=yes 19 | networks: 20 | - local 21 | 22 | eu-lobby-1: 23 | image: itzg/minecraft-server 24 | environment: 25 | - VERSION=1.12.2 26 | - TYPE=spigot 27 | - SERVER_PORT=25565 28 | - SERVER_NAME=eu-lobby-1 29 | - SERVER_GROUP=eu-lobby 30 | - SERVER_CATEGORY=eu-lobby 31 | - ONLINE_MODE=FALSE 32 | - EULA=true 33 | networks: 34 | - local 35 | 36 | eu-lobby-2: 37 | image: itzg/minecraft-server 38 | environment: 39 | - VERSION=1.12.2 40 | - TYPE=spigot 41 | - SERVER_PORT=25565 42 | - SERVER_NAME=eu-lobby-2 43 | - SERVER_GROUP=eu-lobby 44 | - SERVER_CATEGORY=eu-lobby 45 | - ONLINE_MODE=FALSE 46 | - EULA=true 47 | networks: 48 | - local 49 | 50 | us-lobby-1: 51 | image: itzg/minecraft-server 52 | environment: 53 | - VERSION=1.12.2 54 | - TYPE=spigot 55 | - SERVER_PORT=25565 56 | - SERVER_NAME=us-lobby-1 57 | - SERVER_GROUP=us-lobby 58 | - SERVER_CATEGORY=us-lobby 59 | - SERVER_TAGS=some,awesome,tags 60 | - ONLINE_MODE=FALSE 61 | - EULA=true 62 | networks: 63 | - local 64 | networks: 65 | local: -------------------------------------------------------------------------------- /docs/README.bbcode: -------------------------------------------------------------------------------- 1 | [B][SIZE=6][COLOR=rgb(255, 128, 0)]DockerizedCraft - Core[/COLOR][/SIZE][/B] 2 | 3 | [IMG]https://github.com/DockerizedCraft/Core/raw/master/docs/logo-small.png[/IMG] 4 | 5 | [B]Never maintain your Bungeecord manually again! Automatically listens to Docker events and adds servers to BungeeCord.[/B] 6 | 7 | Additionally supporting plugin messaging, connection balancing and proxy server list updates. 8 | 9 | [IMG]https://raw.githubusercontent.com/DockerizedCraft/ContainerManager/master/docs/container-manager-demo.gif[/IMG] 10 | 11 | [SIZE=5][COLOR=#ff8000][B]Container Inspector[/B][/COLOR][/SIZE] 12 | 13 | [SIZE=4][B]Features[/B][/SIZE] 14 | 15 | [LIST] 16 | [*]Automatically listens on docker events 17 | [*]Extracts environment variables from containers 18 | [*]Triggers BungeeCord Custom events on Docker container events 19 | [/LIST] 20 | 21 | [SIZE=4][B]Configuration[/B][/SIZE] 22 | 23 | [SPOILER="container-inspector.yml"][code=YAML]# Is the docker listener enabled? This is the core functionality and should stay enabled 24 | # Type: Boolean 25 | enabled: true 26 | 27 | # Outputs extra information 28 | # Type: Boolean 29 | debug: true 30 | 31 | # Ensure the read permissions 32 | # available schemas: tcp or unix 33 | # Type: String 34 | host: unix:///var/run/docker.sock 35 | 36 | # Type: Boolean 37 | tsl-verify: false 38 | 39 | # Type: String|null 40 | cert-path: ~ 41 | 42 | # Type: Section 43 | registry: 44 | # Type: String|null 45 | username: ~ 46 | # Type: String|null 47 | password: ~ 48 | # Type: String|null 49 | email: ~ 50 | # Type: String|null 51 | url: ~ 52 | 53 | # The network name to resolve IP addresses 54 | # Only one network is possible to avoid confusion about which ip to use 55 | # check your networks with `docker network ls` 56 | network: "minecraft_local" 57 | [/code][/SPOILER] 58 | 59 | [SIZE=5][COLOR=#ff8000][B]Server Updater[/B][/COLOR][/SIZE] 60 | 61 | [SIZE=4][B]Features[/B][/SIZE] 62 | 63 | [LIST] 64 | [*]Automatically adds servers to Bungeecord 65 | [*]Automatically removes servers from Bungeecord 66 | [*]Configurable add/remove actions 67 | [*]Supports Health Checks 68 | [/LIST] 69 | 70 | [SIZE=4][B]Configuration[/B][/SIZE] 71 | 72 | [SPOILER="server-updater.yml"][code=YAML]# Is the Server update enabled? 73 | # Type: Boolean 74 | enabled: true 75 | 76 | # Extra output 77 | # Type: Boolean 78 | debug: false 79 | 80 | # Container events actions to listen on for adding server 81 | # i.e.: "start", "health-status: healthy" 82 | # Recommended "start", "bootstrap" and health checks 83 | # the "bootstrap" events is triggered when connectionbalancer starts to register all running containers. Should ne be removed 84 | # If you want to add only healthy containers be aware of removing "start" action 85 | # @see https://docs.docker.com/engine/reference/commandline/events/#object-types 86 | # Type: List 87 | add-actions: 88 | - "bootstrap" 89 | - "start" 90 | - "unpause" 91 | 92 | # Container events actions to listen on for remving server 93 | # i.e.: "kill", "die" 94 | # Recommended "die" and health checks 95 | # If you want to remove unhealthy containers add i.e: "health_status: unhealthy" 96 | # @see https://docs.docker.com/engine/reference/commandline/events/#object-types 97 | # Type: List 98 | remove-actions: 99 | - "kill" 100 | - "die" 101 | - "stop" 102 | - "pause" 103 | 104 | 105 | 106 | # Environment variables of the containers 107 | # Type: Section 108 | environment-variables: 109 | # The events listener will only add server with the defined environment variable 110 | # ie. docker run -e SERVER_TYPE=minecraft_spigot my_server 111 | # Type: String 112 | identifier: SERVER_TYPE 113 | 114 | # Be default the first exposed port is taken if the container exposes multiple ports you can 115 | # set it by setting the PORT environment variable in the container 116 | # If you exposed multiple ports its highly recommended to set the environment variable 117 | # Type: String 118 | port: SERVER_PORT 119 | 120 | # Setting the motd in the Bungeecord setting 121 | # i.e. docker run -e SERVER_FORCED_HOST="Another Minecraft Server" playerworld:latest 122 | # Type: String 123 | motd: SERVER_MOTD 124 | 125 | # Setting the server to restricted 126 | # If not set it is false, only excepts: "restricted" or "unrestricted" 127 | # i.e. docker run -e SERVER_RESTRICTED=true playerworld:latest 128 | # Type: String 129 | restricted: SERVER_RESTRICTED 130 | 131 | # Each server name needs to be unique 132 | # If you are not able to control if it is unique (autoscaling or whatever) you should not set it in your container 133 | # If you do not set the environment variable the container name itself will be used 134 | # Two server with the same name will overwrite each other 135 | # Type: String 136 | name: SERVER_NAME 137 | [/code][/SPOILER] 138 | 139 | [SIZE=5][COLOR=#ff8000][B]Connection Balancer[/B][/COLOR][/SIZE] 140 | 141 | [SIZE=4][B]Features[/B][/SIZE] 142 | 143 | [LIST] 144 | [*]Server Groups 145 | [*]Default connection group (Fallback/Default servers) 146 | [*]Join Group Commands (like /lobby, /hub or /game-xy) 147 | [*]Forced hosts for groups (i.e. eu lobbies and us lobbies) 148 | [*]Listens on Container events to add servers to groups based on their environment variables (i.e SERVER_GROUP=lobby) 149 | [*]Connection Balancing Strategies per group 150 | [LIST] 151 | [*]balanced: Connect Players to the Server of the group with the fewest players 152 | [*]More will follow! 153 | [/LIST] 154 | [*]Does not overwrite restrictions! 155 | [*]Stores Player sessions in Redis to handle reconnections 156 | [/LIST] 157 | 158 | [SIZE=4][B]Configuration[/B][/SIZE] 159 | 160 | [SPOILER="connection-balancer.yml"][code=YAML]# Care if you disable it you will need to configure default, priority and fallback servers by hand 161 | # Or use an different connection balancer handler plugin (Is it compatible?) 162 | # Type: Boolean 163 | enabled: true 164 | 165 | # Outputs extra information 166 | # Type: Boolean 167 | debug: true 168 | 169 | # To store player session for the reconnect handler 170 | # Type: Section 171 | session-store: 172 | # Type: Section 173 | redis: 174 | # Type: String 175 | host: "redis" 176 | # Type: String 177 | password: ~ 178 | # Type: Integer 179 | port: 6379 180 | # Type: Boolean 181 | ssl: true 182 | 183 | # Environment variables of the containers 184 | # Type: Section 185 | environment-variables: 186 | # If the environment variable is set the server will be added to the priority list of connectionbalancer 187 | # This plugin implemented a custom load balancer which will use defined groups 188 | # Leaving the env variable blank will add the server to the default group 189 | # Type: String 190 | group: SERVER_GROUP 191 | 192 | # To enable forced host for the single instance. 193 | # You can also configure a forced host for a whole group. See groups config. I would recommend to do so events with single instances 194 | # i.e. docker run -e SERVER_FORCED_HOST=muehre.craftmania.de playerworld:latest 195 | # Type: String 196 | forced-host: SERVER_FORCED_HOST 197 | 198 | # Type: Section 199 | # check docker.event_listener.environment_variables.group_key 200 | groups: 201 | # Server group configuration 202 | # default group is used if a a container does not have a group environment variable. You can also configure it here 203 | # Type: Section 204 | # Group: 205 | # - strategy(String): balance is the only strategy atm. I will implement more as soon as they are required 206 | # - forced_host(String) Optional: people joining over this host will be send to this group 207 | # - can-reconnect(Boolean) Default: false: if a player can reconnect to this group. Usefull to disable for i.e minigames 208 | # - restricted(Boolean) Default: false: Is a permission required? 209 | eu-lobby: 210 | strategy: balance 211 | can-reconnect: true 212 | restricted: false 213 | 214 | game-xy: 215 | strategy: balance 216 | can-reconnect: false 217 | restricted: false 218 | 219 | private: 220 | strategy: balance 221 | can-reconnect: true 222 | restricted: false 223 | 224 | us-lobby: 225 | strategy: balance 226 | can-reconnect: true 227 | restricted: false 228 | 229 | # The default group a user is connected to if he freshly joins or his group was restricted in re-connections. 230 | # And not forced host is matching 231 | # Type: String 232 | default-group: eu-lobby 233 | 234 | # Commands that players can execute to join a certain group 235 | # Type Section 236 | join-commands: 237 | lobby: eu-lobby 238 | hub: eu-lobby 239 | 240 | # Setting force hosts. Use {dot} placeholder for dots to not break yaml syntax 241 | # Type: Section 242 | forced-hosts: 243 | "us{dot}mynetwork{dot}net": "us-lobby" 244 | "eu{dot}mynetwork{dot}net": "eu-lobby" 245 | [/code][/SPOILER] 246 | 247 | [SIZE=5][COLOR=#ff8000][B]Plugin Notifier[/B][/COLOR][/SIZE] 248 | 249 | [SIZE=4][B]Server List Payload[/B][/SIZE] 250 | 251 | Send Server information to the single servers. This can be used to add Sever Selectors etc. 252 | 253 | Add as many information as you want by easy to use environment variable mapping. 254 | 255 | [B]Example Payload [/B](Channel: ContainerManager, Subchannel: ServerData) 256 | 257 | [code]{ 258 | "us-lobby-1":{ 259 | "address":"172.19.0.5:25565", 260 | "host":"172.19.0.5", 261 | "port":25565, 262 | "motd":"A Minecraft Server Instance", 263 | "name":"us-lobby-1", 264 | "proxied_players":12, 265 | "type":"spigot", 266 | "category":"us-lobby", 267 | "tags":"some,awesome,tags" 268 | }, 269 | "eu-lobby-2":{ 270 | "address":"172.19.0.4:25565", 271 | "host":"172.19.0.4", 272 | "port":25565, 273 | "motd":"A Minecraft Server Instance", 274 | "name":"eu-lobby-2", 275 | "proxied_players":5, 276 | "type":"spigot", 277 | "category":"eu-lobby", 278 | "tags":"" 279 | }, 280 | "eu-lobby-1":{ 281 | "address":"172.19.0.3:25565", 282 | "host":"172.19.0.3", 283 | "port":25565, 284 | "motd":"A Minecraft Server Instance", 285 | "name":"eu-lobby-1", 286 | "proxied_players":0, 287 | "type":"spigot", 288 | "category":"eu-lobby", 289 | "tags":"" 290 | } 291 | }[/code] 292 | 293 | [B]Configuration[/B] 294 | 295 | [SPOILER="plugin-notifier.yml"][code=YAML]# If you disable it no plugin which depends on this data will work 296 | # Type: Boolean 297 | enabled: true 298 | 299 | # Outputs extra information 300 | # Type: Boolean 301 | debug: true 302 | 303 | # Server updates will be sent after any add or remove anyway but the interval is usefull to update i.e. ProxiedPlayer Information 304 | # Type: Integer 305 | refresh-interval: 30 306 | 307 | # Maps environment variables and forwards them with the specified key to the bukkit client plugin 308 | # ie. docker run -e CATEGORY=factions 309 | # Reserved keys as they will be handed down anyway: name, address, motd, restricted (In case you want to overwrite you can do so) 310 | # Type: Section 311 | # Type MetaDataConfig: 312 | # - environment-variable (string): The environemnt variable to access 313 | # - required (bool): Is this value required? If not given and no default 314 | # is defined the server will not be added to connectionbalancer 315 | # - default (string): If the environment variable is not defined will fall 316 | # back to the given default 317 | meta-data-mapper: 318 | # make the SERVER_TYPE also accessible 319 | type: 320 | required: true 321 | environment-variable: SERVER_TYPE 322 | 323 | # Category for i.e create server selector menus based on categories 324 | category: 325 | environment-variable: SERVER_CATEGORY 326 | required: true 327 | default: "none" 328 | 329 | tags: 330 | environment-variable: SERVER_TAGS 331 | required: true 332 | default: "" 333 | [/code][/SPOILER] 334 | 335 | [SIZE=5][COLOR=#ff8000][B]Try it yourself[/B][/COLOR][/SIZE] 336 | 337 | [LIST=1] 338 | [*]Checkout the repository 339 | [*]Build the .jar with maven or copy an attached one from the last releases. 340 | [*]run docker-compose --project-name minecraft up -d 341 | [*]Wait until all containers did start and connect to localhost with you Minecraft client 342 | [/LIST] 343 | 344 | [SIZE=5][COLOR=#ff8000][B]Todo's[/B][/COLOR][/SIZE] 345 | 346 | [LIST] 347 | [*]Reduce .jar size 348 | [/LIST] -------------------------------------------------------------------------------- /docs/container-manager-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DockerizedCraft/Core/65a6ae9c75e075870ecbf850678d7da6edf9140d/docs/container-manager-demo.gif -------------------------------------------------------------------------------- /docs/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DockerizedCraft/Core/65a6ae9c75e075870ecbf850678d7da6edf9140d/docs/logo-small.png -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DockerizedCraft/Core/65a6ae9c75e075870ecbf850678d7da6edf9140d/docs/logo.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | de.craftmania 8 | DockerizedCraft 9 | 0.2.2 10 | 11 | 12 | 13 | 14 | org.apache.maven.plugins 15 | maven-shade-plugin 16 | 3.1.1 17 | 18 | 19 | package 20 | 21 | shade 22 | 23 | 24 | target/assembly/${project.artifactId}.jar 25 | 26 | 27 | org.bstats 28 | de.craftmania.dockerizedcraft 29 | 30 | 31 | 32 | 33 | 34 | 35 | true 36 | false 37 | false 38 | 39 | 40 | *:* 41 | 42 | META-INF/*.SF 43 | META-INF/*.DSA 44 | META-INF/*.RSA 45 | 46 | 47 | 48 | 49 | 50 | 51 | org.apache.maven.plugins 52 | maven-compiler-plugin 53 | 54 | 8 55 | 8 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | bungeecord-repo 64 | https://oss.sonatype.org/content/repositories/snapshots 65 | 66 | 67 | CodeMC 68 | https://repo.codemc.org/repository/maven-public 69 | 70 | 71 | 72 | 73 | 74 | net.md-5 75 | bungeecord-api 76 | 1.15-SNAPSHOT 77 | jar 78 | provided 79 | 80 | 81 | net.md-5 82 | bungeecord-api 83 | 1.15-SNAPSHOT 84 | javadoc 85 | provided 86 | 87 | 88 | com.google.code.gson 89 | gson 90 | 2.8.6 91 | 92 | 93 | io.fabric8 94 | kubernetes-client 95 | 4.8.0 96 | 97 | 98 | com.github.docker-java 99 | docker-java 100 | 3.1.5 101 | compile 102 | 103 | 104 | org.slf4j 105 | slf4j-log4j12 106 | 2.0.0-alpha1 107 | 108 | 109 | redis.clients 110 | jedis 111 | 3.2.0 112 | jar 113 | compile 114 | 115 | 116 | org.bstats 117 | bstats-bungeecord 118 | 1.7 119 | compile 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /scripts/kubernetes/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: minecraft 5 | --- 6 | apiVersion: rbac.authorization.k8s.io/v1 7 | kind: Role 8 | metadata: 9 | namespace: minecraft 10 | name: dockerizedcraft-pod-reader 11 | rules: 12 | - apiGroups: [""] 13 | resources: ["pods"] 14 | verbs: ["get", "watch", "list"] 15 | --- 16 | apiVersion: v1 17 | kind: ServiceAccount 18 | metadata: 19 | namespace: minecraft 20 | name: dockerizedcraft-account 21 | --- 22 | apiVersion: rbac.authorization.k8s.io/v1 23 | kind: RoleBinding 24 | metadata: 25 | name: dockerizedcraft-pod-reader-rolebinding 26 | namespace: minecraft 27 | subjects: 28 | - kind: ServiceAccount 29 | name: dockerizedcraft-account 30 | namespace: minecraft 31 | roleRef: 32 | kind: Role 33 | name: dockerizedcraft-pod-reader 34 | apiGroup: rbac.authorization.k8s.io 35 | --- 36 | apiVersion: v1 37 | data: 38 | connection-balancer.yml: |- 39 | # Care if you disable it you will need to configure default, priority and fallback servers by hand 40 | # Or use an different connection balancer handler plugin (Is it compatible?) 41 | # Type: Boolean 42 | enabled: true 43 | 44 | # Outputs extra information 45 | # Type: Boolean 46 | debug: true 47 | 48 | # To store player session for the reconnect handler 49 | # Type: Section 50 | session-store: 51 | # Type: Section 52 | redis: 53 | # Type: String 54 | host: "redis" 55 | # Type: String 56 | password: ~ 57 | # Type: Integer 58 | port: 6379 59 | # Type: Boolean 60 | ssl: true 61 | 62 | # Environment variables of the containers 63 | # Type: Section 64 | environment-variables: 65 | # If the environment variable is set the server will be added to the priority list of connectionbalancer 66 | # This plugin implemented a custom load balancer which will use defined groups 67 | # Leaving the env variable blank will add the server to the default group 68 | # Type: String 69 | group: SERVER_GROUP 70 | 71 | # To enable forced host for the single instance. 72 | # You can also configure a forced host for a whole group. See groups config. I would recommend to do so events with single instances 73 | # i.e. docker run -e SERVER_FORCED_HOST=muehre.craftmania.de playerworld:latest 74 | # Type: String 75 | forced-host: SERVER_FORCED_HOST 76 | 77 | # Type: Section 78 | # check docker.event_listener.environment_variables.group_key 79 | groups: 80 | # Server group configuration 81 | # default group is used if a a container does not have a group environment variable. You can also configure it here 82 | # Type: Section 83 | # Group: 84 | # - strategy(String): balance is the only strategy atm. I will implement more as soon as they are required 85 | # - forced_host(String) Optional: people joining over this host will be send to this group 86 | # - can-reconnect(Boolean) Default: false: if a player can reconnect to this group. Usefull to disable for i.e minigames 87 | # - restricted(Boolean) Default: false: Is a permission required? 88 | lobby: 89 | strategy: balance 90 | can-reconnect: true 91 | restricted: false 92 | 93 | server: 94 | strategy: balance 95 | can-reconnect: false 96 | restricted: false 97 | 98 | # The default group a user is connected to if he freshly joins or his group was restricted in re-connections. 99 | # And not forced host is matching 100 | # Type: String 101 | default-group: lobby 102 | 103 | # Commands that players can execute to join a certain group 104 | # Type Section 105 | join-commands: 106 | lobby: lobby 107 | hub: lobby 108 | 109 | # Setting force hosts. Use {dot} placeholder for dots to not break yaml syntax 110 | # Type: Section 111 | forced-hosts: 112 | # "us{dot}mynetwork{dot}net": "us-lobby" 113 | # "eu{dot}mynetwork{dot}net": "eu-lobby" 114 | container-inspector.yml: |- 115 | # Is the docker listener enabled? This is the core functionality and should stay enabled 116 | # Type: Boolean 117 | enabled: true 118 | 119 | # Outputs extra information 120 | # Type: Boolean 121 | debug: true 122 | 123 | # Backend type (docker or kubernetes) 124 | backend: kubernetes 125 | 126 | # Ensure the read permissions 127 | # available schemas: tcp or unix 128 | # Type: String 129 | docker: 130 | host: unix:///var/run/docker.sock 131 | 132 | # Type: Boolean 133 | tsl-verify: false 134 | 135 | # Type: String|null 136 | cert-path: ~ 137 | 138 | # Type: Section 139 | registry: 140 | # Type: String|null 141 | username: ~ 142 | # Type: String|null 143 | password: ~ 144 | # Type: String|null 145 | email: ~ 146 | # Type: String|null 147 | url: ~ 148 | 149 | # The network name to resolve IP addresses 150 | # Only one network is possible to avoid confusion about which ip to use 151 | # check your networks with `docker network ls` 152 | network: "minecraft_local" 153 | 154 | kubernetes: 155 | # The namespace that bungeecord and all minecraft servers reside in 156 | namespace: minecraft 157 | plugin-notifier.yml: |- 158 | # If you disable it no plugin which depends on this data will work 159 | # Type: Boolean 160 | enabled: true 161 | 162 | # Outputs extra information 163 | # Type: Boolean 164 | debug: true 165 | 166 | # Server updates will be sent after any add or remove anyway but the interval is usefull to update i.e. ProxiedPlayer Information 167 | # Type: Integer 168 | refresh-interval: 30 169 | 170 | # Maps environment variables and forwards them with the specified key to the bukkit client plugin 171 | # ie. docker run -e CATEGORY=factions 172 | # Reserved keys as they will be handed down anyway: name, address, motd, restricted (In case you want to overwrite you can do so) 173 | # Type: Section 174 | # Type MetaDataConfig: 175 | # - environment-variable (string): The environemnt variable to access 176 | # - required (bool): Is this value required? If not given and no default 177 | # is defined the server will not be added to connectionbalancer 178 | # - default (string): If the environment variable is not defined will fall 179 | # back to the given default 180 | meta-data-mapper: 181 | # make the SERVER_TYPE also accessible 182 | type: 183 | required: true 184 | environment-variable: SERVER_TYPE 185 | 186 | # Category for i.e create server selector menus based on categories 187 | category: 188 | environment-variable: SERVER_CATEGORY 189 | required: true 190 | default: "none" 191 | 192 | tags: 193 | environment-variable: SERVER_TAGS 194 | required: true 195 | default: "" 196 | server-updater.yml: |- 197 | # Is the Server update enabled? 198 | # Type: Boolean 199 | enabled: true 200 | 201 | # Extra output 202 | # Type: Boolean 203 | debug: false 204 | 205 | # Container events actions to listen on for adding server 206 | # i.e.: "start", "health-status: healthy" 207 | # Recommended "start", "bootstrap" and health checks 208 | # the "bootstrap" events is triggered when connectionbalancer starts to register all running containers. Should ne be removed 209 | # If you want to add only healthy containers be aware of removing "start" action 210 | # @see https://docs.docker.com/engine/reference/commandline/events/#object-types 211 | # Type: List 212 | add-actions: 213 | - "bootstrap" 214 | - "start" 215 | - "health_status: healthy" 216 | - "unpause" 217 | - "ADDED" 218 | 219 | # Container events actions to listen on for remving server 220 | # i.e.: "kill", "die" 221 | # Recommended "die" and health checks 222 | # If you want to remove unhealthy containers add i.e: "health_status: unhealthy" 223 | # @see https://docs.docker.com/engine/reference/commandline/events/#object-types 224 | # Type: List 225 | remove-actions: 226 | - "kill" 227 | - "die" 228 | - "stop" 229 | - "pause" 230 | - "MODIFIED" 231 | - "PENDING" 232 | - "DELETED" 233 | 234 | 235 | 236 | # Environment variables of the containers 237 | # Type: Section 238 | environment-variables: 239 | # The events listener will only add server with the defined environment variable 240 | # ie. docker run -e SERVER_TYPE=minecraft_spigot my_server 241 | # Type: String 242 | identifier: SERVER_TYPE 243 | 244 | # Be default the first exposed port is taken if the container exposes multiple ports you can 245 | # set it by setting the PORT environment variable in the container 246 | # If you exposed multiple ports its highly recommended to set the environment variable 247 | # Type: String 248 | port: SERVER_PORT 249 | 250 | # Setting the motd in the Bungeecord setting 251 | # i.e. docker run -e SERVER_FORCED_HOST="Another Minecraft Server" playerworld:latest 252 | # Type: String 253 | motd: SERVER_MOTD 254 | 255 | # Setting the server to restricted 256 | # If not set it is false, only excepts: "restricted" or "unrestricted" 257 | # i.e. docker run -e SERVER_RESTRICTED=true playerworld:latest 258 | # Type: String 259 | restricted: SERVER_RESTRICTED 260 | 261 | # Each server name needs to be unique 262 | # If you are not able to control if it is unique (autoscaling or whatever) you should not set it in your container 263 | # If you do not set the environment variable the container name itself will be used 264 | # Two server with the same name will overwrite each other 265 | # Type: String 266 | name: SERVER_NAME 267 | kind: ConfigMap 268 | metadata: 269 | name: dockerizedcraft-config 270 | namespace: minecraft 271 | --- 272 | apiVersion: extensions/v1beta1 273 | kind: Deployment 274 | metadata: 275 | name: bungeecord 276 | namespace: minecraft 277 | spec: 278 | replicas: 1 279 | selector: 280 | matchLabels: 281 | dockerizedcraft/set: bungeecord 282 | template: 283 | metadata: 284 | labels: 285 | dockerizedcraft/set: bungeecord 286 | spec: 287 | containers: 288 | - env: 289 | - name: UID 290 | value: "1000" 291 | image: itzg/bungeecord 292 | imagePullPolicy: Always 293 | name: bungeecord 294 | ports: 295 | - containerPort: 25577 296 | hostPort: 25565 297 | name: 25577tcp255650 298 | protocol: TCP 299 | volumeMounts: 300 | - mountPath: /server/plugins/DockerizedCraft.jar 301 | name: bungeecord-claim-dockerizedcraft 302 | subPath: DockerizedCraft.jar 303 | - mountPath: /server/plugins/DockerizedCraft/ 304 | name: dockerizedcraft-config 305 | serviceAccount: dockerizedcraft-account 306 | serviceAccountName: dockerizedcraft-account 307 | volumes: 308 | - name: bungeecord-claim-dockerizedcraft 309 | persistentVolumeClaim: 310 | claimName: bungeecord-claim-dockerizedcraft 311 | - configMap: 312 | defaultMode: 256 313 | name: dockerizedcraft-config 314 | optional: false 315 | name: dockerizedcraft-config 316 | --- 317 | apiVersion: extensions/v1beta1 318 | kind: Deployment 319 | metadata: 320 | name: lobby 321 | namespace: minecraft 322 | spec: 323 | replicas: 1 324 | selector: 325 | matchLabels: 326 | dockerizedcraft/set: lobby 327 | template: 328 | metadata: 329 | labels: 330 | dockerizedcraft/enabled: "true" 331 | dockerizedcraft/set: lobby 332 | spec: 333 | containers: 334 | - env: 335 | - name: EULA 336 | value: "true" 337 | - name: ONLINE_MODE 338 | value: "FALSE" 339 | - name: SERVER_GROUP 340 | value: lobby 341 | - name: SERVER_PORT 342 | value: "25565" 343 | - name: SERVER_TYPE 344 | value: spigot 345 | - name: VERSION 346 | value: 1.12.2 347 | image: itzg/minecraft-server 348 | imagePullPolicy: Always 349 | name: lobby 350 | --- 351 | apiVersion: extensions/v1beta1 352 | kind: Deployment 353 | metadata: 354 | name: server 355 | namespace: minecraft 356 | spec: 357 | replicas: 5 358 | selector: 359 | matchLabels: 360 | dockerizedcraft/set: server 361 | template: 362 | metadata: 363 | labels: 364 | dockerizedcraft/enabled: "true" 365 | dockerizedcraft/set: server 366 | spec: 367 | containers: 368 | - env: 369 | - name: EULA 370 | value: "true" 371 | - name: ONLINE_MODE 372 | value: "FALSE" 373 | - name: SERVER_GROUP 374 | value: server 375 | - name: SERVER_PORT 376 | value: "25565" 377 | - name: SERVER_TYPE 378 | value: spigot 379 | - name: VERSION 380 | value: 1.12.2 381 | image: itzg/minecraft-server 382 | imagePullPolicy: Always 383 | name: server -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/DockerizedCraft.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft; 2 | 3 | import de.craftmania.dockerizedcraft.connection.balancer.BalancedReconnectHandler; 4 | import de.craftmania.dockerizedcraft.connection.balancer.ConnectionBalancer; 5 | import de.craftmania.dockerizedcraft.connection.balancer.session.RedisSessionStorage; 6 | import de.craftmania.dockerizedcraft.connection.balancer.session.SessionStorage; 7 | import de.craftmania.dockerizedcraft.container.inspector.IContainerInspector; 8 | import de.craftmania.dockerizedcraft.container.inspector.docker.DockerContainerInspector; 9 | import de.craftmania.dockerizedcraft.container.inspector.kubernetes.KubernetesContainerInspector; 10 | import de.craftmania.dockerizedcraft.plugin.notifier.serverlist.ServerListPluginNotifier; 11 | import de.craftmania.dockerizedcraft.server.updater.ServerUpdater; 12 | import net.md_5.bungee.api.ReconnectHandler; 13 | import net.md_5.bungee.api.plugin.Plugin; 14 | import net.md_5.bungee.config.*; 15 | import org.bstats.bungeecord.Metrics; 16 | 17 | import java.io.IOException; 18 | import java.io.File; 19 | import java.io.InputStream; 20 | import java.nio.file.Files; 21 | import java.util.*; 22 | import java.util.concurrent.TimeUnit; 23 | 24 | 25 | @SuppressWarnings("unused") 26 | public class DockerizedCraft extends Plugin { 27 | private Map configuration; 28 | 29 | /** 30 | * Enabled sub-packages depending on the given configuration 31 | */ 32 | @Override 33 | public void onEnable() { 34 | // bStats 35 | //Metrics metrics = new Metrics(this); 36 | 37 | try { 38 | this.loadConfiguration(); 39 | } catch (IOException e) { 40 | getLogger().warning("Not able to write Configuration File."); 41 | getLogger().warning("Check write Permissions to the plugin directory."); 42 | getLogger().warning("Stopped Plugin enabling (Plugin will not work!)"); 43 | e.printStackTrace(); 44 | return; 45 | } 46 | 47 | if (getConfiguration().get("connection-balancer").getBoolean("enabled")) { 48 | getLogger().info("[Connection ConnectionBalancer] Enabled!"); 49 | bootstrapConnectionBalancer(getConfiguration().get("connection-balancer")); 50 | } 51 | 52 | 53 | if (getConfiguration().get("server-updater").getBoolean("enabled")) { 54 | getLogger().info("[Server Updater] Enabled!"); 55 | bootstrapServerUpdater(getConfiguration().get("server-updater")); 56 | } 57 | 58 | if (getConfiguration().get("plugin-notifier").getBoolean("enabled")) { 59 | getLogger().info("[Plugin Notification] Enabled!"); 60 | bootstrapPluginNotifier(getConfiguration().get("plugin-notifier")); 61 | } 62 | 63 | if (getConfiguration().get("container-inspector").getBoolean("enabled")) { 64 | getLogger().info("[Container Inspector] Enabled!"); 65 | bootstrapContainerInspector( 66 | getConfiguration().get("container-inspector") 67 | ); 68 | } 69 | } 70 | 71 | /** 72 | * Bootstraps the Connection Balancer, sets the reconnect handler and adds the registered listener 73 | * @param configuration The connection balancer configuration 74 | */ 75 | private void bootstrapConnectionBalancer(Configuration configuration) { 76 | ConnectionBalancer connectionBalancer = new ConnectionBalancer( 77 | configuration, 78 | getLogger(), 79 | this 80 | ); 81 | 82 | SessionStorage sessionStorage = new RedisSessionStorage( 83 | configuration.getString("session-store.redis.host"), 84 | configuration.getString("session-store.redis.password"), 85 | configuration.getInt("session-store.redis.port"), 86 | configuration.getBoolean("session-store.redis.ssl") 87 | ); 88 | 89 | ReconnectHandler reconnectHandler = new BalancedReconnectHandler(connectionBalancer, sessionStorage, this.getLogger()); 90 | getProxy().setReconnectHandler(reconnectHandler); 91 | getProxy().getPluginManager().registerListener(this, connectionBalancer); 92 | } 93 | 94 | /** 95 | * Bootstraps the server update and adds it the the registered listeners 96 | * @param configuration The server updater configuration 97 | */ 98 | private void bootstrapServerUpdater(Configuration configuration) { 99 | ServerUpdater serverUpdater = new ServerUpdater(configuration, getProxy(), getLogger()); 100 | getProxy().getPluginManager().registerListener(this, serverUpdater); 101 | } 102 | 103 | /** 104 | * Bootstraps the plugin notifier and adds scheduled interval tasks 105 | * @param configuration The plugin notifier configuration 106 | */ 107 | private void bootstrapPluginNotifier(Configuration configuration) { 108 | ServerListPluginNotifier notifier = new ServerListPluginNotifier( 109 | configuration.getSection("meta-data-mapper"), 110 | getLogger() 111 | ); 112 | 113 | getProxy().getPluginManager().registerListener(this, notifier); 114 | getProxy().getScheduler().schedule( 115 | this, 116 | notifier::sendUpdate, 117 | 0, 118 | configuration.getInt("refresh-interval"), 119 | TimeUnit.SECONDS 120 | ); 121 | } 122 | 123 | /** 124 | * Bootstraps the container inspector and runs the inspector and listener as async task through the scheduler 125 | * @param configuration The container inspector configuration 126 | */ 127 | private void bootstrapContainerInspector(Configuration configuration) { 128 | 129 | IContainerInspector containerInspector; 130 | if (configuration.getString("backend").equals("docker")) { 131 | containerInspector = new DockerContainerInspector(configuration, getProxy(), getLogger()); 132 | } else { 133 | containerInspector = new KubernetesContainerInspector(configuration, getProxy(), getLogger()); 134 | } 135 | 136 | getProxy().getScheduler().runAsync(this, containerInspector::runContainerInspection); 137 | getProxy().getScheduler().runAsync(this, containerInspector::runContainerListener); 138 | } 139 | 140 | /** 141 | * Loads the configurations 142 | * @throws IOException On missing write access 143 | */ 144 | private void loadConfiguration() throws IOException { 145 | 146 | List configNames = Arrays.asList( 147 | "connection-balancer", 148 | "plugin-notifier", 149 | "container-inspector", 150 | "server-updater" 151 | ); 152 | Map configuration = new HashMap<>(configNames.size()); 153 | 154 | 155 | if (!getDataFolder().exists()) { 156 | if (!getDataFolder().mkdir()) { 157 | throw new IOException("Not able to generate Plugin Data Folder"); 158 | } 159 | } 160 | 161 | 162 | for (String configName : configNames) { 163 | 164 | 165 | File file = new File(getDataFolder(), configName + ".yml"); 166 | 167 | if (!file.exists()) { 168 | try (InputStream in = getResourceAsStream(configName + ".yml")) { 169 | Files.copy(in, file.toPath()); 170 | } catch (IOException e) { 171 | e.printStackTrace(); 172 | } 173 | } 174 | configuration.put(configName, ConfigurationProvider.getProvider(YamlConfiguration.class) 175 | .load(new File(getDataFolder(), configName + ".yml") 176 | )); 177 | } 178 | 179 | this.configuration = configuration; 180 | } 181 | 182 | /** 183 | * @return Map containing the sub-package configurations 184 | */ 185 | private Map getConfiguration() { 186 | return configuration; 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/connection/balancer/BalancedReconnectHandler.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.connection.balancer; 2 | 3 | import com.google.common.base.Preconditions; 4 | import de.craftmania.dockerizedcraft.connection.balancer.session.SessionStorage; 5 | import net.md_5.bungee.api.ReconnectHandler; 6 | import net.md_5.bungee.api.config.ServerInfo; 7 | import net.md_5.bungee.api.connection.ProxiedPlayer; 8 | import java.util.logging.Logger; 9 | 10 | public class BalancedReconnectHandler implements ReconnectHandler { 11 | private ConnectionBalancer connectionBalancer; 12 | private SessionStorage sessionStorage; 13 | private Logger logger; 14 | 15 | public BalancedReconnectHandler(ConnectionBalancer connectionBalancer, SessionStorage sessionStorage, Logger logger) { 16 | this.connectionBalancer = connectionBalancer; 17 | this.sessionStorage = sessionStorage; 18 | this.logger = logger; 19 | } 20 | 21 | @Override 22 | public ServerInfo getServer(ProxiedPlayer proxiedPlayer) { 23 | ServerInfo serverInfo = null; 24 | 25 | logger.info("Looking for a server to match: hostname="+proxiedPlayer.getPendingConnection().getVirtualHost().getHostName()+", session="+this.sessionStorage.getReconnectServer(proxiedPlayer.getUniqueId())); 26 | if (proxiedPlayer.getPendingConnection().getVirtualHost().getHostName() != null) { 27 | serverInfo = this.connectionBalancer.getForcedServer(proxiedPlayer.getPendingConnection().getVirtualHost().getHostName()); 28 | } 29 | 30 | if (serverInfo == null && this.sessionStorage.getReconnectServer(proxiedPlayer.getUniqueId()) != null) { 31 | serverInfo = this.connectionBalancer.getReconnectServer(this.sessionStorage.getReconnectServer(proxiedPlayer.getUniqueId())); 32 | } 33 | 34 | if (serverInfo == null) { 35 | serverInfo = this.connectionBalancer.getFallbackServer(); 36 | } 37 | 38 | Preconditions.checkState( serverInfo != null, "Default server or group not defined" ); 39 | 40 | return serverInfo; 41 | } 42 | 43 | @Override 44 | public void setServer(ProxiedPlayer proxiedPlayer) { 45 | this.sessionStorage.setReconnectServer( 46 | proxiedPlayer.getUniqueId(), 47 | ( proxiedPlayer.getReconnectServer() != null ) 48 | ? proxiedPlayer.getReconnectServer().getName() 49 | : proxiedPlayer.getServer().getInfo().getName() 50 | ); 51 | } 52 | 53 | @Override 54 | public void save() { 55 | 56 | } 57 | 58 | @Override 59 | public void close() { 60 | } 61 | 62 | } 63 | 64 | -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/connection/balancer/ConnectionBalancer.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.connection.balancer; 2 | 3 | import de.craftmania.dockerizedcraft.connection.balancer.command.GroupCommand; 4 | import de.craftmania.dockerizedcraft.connection.balancer.strategy.BalanceStrategy; 5 | import de.craftmania.dockerizedcraft.connection.balancer.strategy.Strategy; 6 | import de.craftmania.dockerizedcraft.server.updater.events.PostAddServerEvent; 7 | import de.craftmania.dockerizedcraft.server.updater.events.PreRemoveServerEvent; 8 | import de.craftmania.dockerizedcraft.connection.balancer.model.Group; 9 | import net.md_5.bungee.api.config.ServerInfo; 10 | import net.md_5.bungee.api.event.PluginMessageEvent; 11 | import net.md_5.bungee.api.plugin.Listener; 12 | import net.md_5.bungee.api.plugin.Plugin; 13 | import net.md_5.bungee.api.plugin.PluginManager; 14 | import net.md_5.bungee.config.Configuration; 15 | import net.md_5.bungee.event.EventHandler; 16 | 17 | import java.io.ByteArrayInputStream; 18 | import java.io.DataInputStream; 19 | import java.util.Collections; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | import java.util.logging.Logger; 23 | 24 | public class ConnectionBalancer implements Listener { 25 | 26 | private static String defaultGroupName; 27 | 28 | static { 29 | defaultGroupName = "default"; 30 | } 31 | 32 | private String groupEnvironmentVariable; 33 | private Group defaultGroup; 34 | private Map groups; 35 | 36 | private Map servers; 37 | private Map serverGroups; 38 | 39 | private String forcedHostEnvironmentVariable; 40 | private Map forcedHostServers; 41 | private Map forcedHostGroups; 42 | 43 | private Logger logger; 44 | private Plugin plugin; 45 | 46 | public static final Map balanceStrategies; 47 | 48 | static { 49 | Map strategies = new HashMap<>(1); 50 | strategies.put("balance", new BalanceStrategy()); 51 | balanceStrategies = Collections.unmodifiableMap(strategies); 52 | } 53 | 54 | public ConnectionBalancer( 55 | Configuration configuration, 56 | Logger logger, 57 | Plugin plugin 58 | ) { 59 | this.logger = logger; 60 | this.plugin = plugin; 61 | this.servers = new HashMap<>(); 62 | this.serverGroups = new HashMap<>(); 63 | this.forcedHostServers = new HashMap<>(); 64 | 65 | this.groupEnvironmentVariable = configuration.getString("environment-variables.group"); 66 | this.forcedHostEnvironmentVariable = configuration.getString("environment-variables.forced_host"); 67 | 68 | this.groups = this.getGroupsByConfiguration(configuration.getSection("groups")); 69 | this.logger.info("[Connection Balancer] Added " + this.groups.size() + " server groups."); 70 | 71 | this.defaultGroup = 72 | this.getGroup(configuration.getString("default-group")) != null 73 | ? this.getGroup(configuration.getString("default-group")) 74 | : this.getGroup(ConnectionBalancer.defaultGroupName); 75 | 76 | assert this.defaultGroup != null; 77 | this.logger.info("[Connection Balancer] Setting default group: " + this.defaultGroup.getName()); 78 | 79 | this.forcedHostGroups = this.getGroupsForcedHosts(this.groups, configuration.getSection("forced-hosts")); 80 | 81 | for (Map.Entry entry : this.forcedHostGroups.entrySet()) { 82 | this.logger.info( 83 | "[Connection Balancer] Added forced host: " 84 | + entry.getKey() 85 | + " > " 86 | + entry.getValue().getName() 87 | ); 88 | } 89 | 90 | this.loadCommands(configuration.getSection("join-commands")); 91 | } 92 | 93 | private void loadCommands(Configuration joinCommandsConfiguration) { 94 | for (String commandName : joinCommandsConfiguration.getKeys()) { 95 | if (this.groups.containsKey(joinCommandsConfiguration.getString(commandName))) { 96 | 97 | GroupCommand command = new GroupCommand( 98 | commandName, 99 | this.groups.get(joinCommandsConfiguration.getString(commandName)), 100 | this 101 | ); 102 | this.plugin.getProxy().getPluginManager().registerCommand(this.plugin, command); 103 | this.logger.info( 104 | "[Connection Balancer] Registered Group join Command: " 105 | + commandName 106 | + " > " 107 | + joinCommandsConfiguration.getString(commandName) 108 | ); 109 | } 110 | } 111 | } 112 | 113 | @SuppressWarnings("WeakerAccess") 114 | public ServerInfo getReconnectServer(String name) { 115 | if (this.servers.containsKey(name)) { 116 | if (this.groups.get(this.serverGroups.get(name)).getCanReconnect()) { 117 | return this.servers.get(name); 118 | } 119 | 120 | return null; 121 | } 122 | 123 | return null; 124 | } 125 | 126 | @SuppressWarnings("WeakerAccess") 127 | public ServerInfo getForcedServer(String hostname) { 128 | if (this.forcedHostGroups.containsKey(hostname)) { 129 | return this.forcedHostGroups.get(hostname).getStrategy().getServer(this.forcedHostGroups.get(hostname).getServers()); 130 | } 131 | 132 | if (this.forcedHostServers.containsKey(hostname)) { 133 | return this.forcedHostServers.get(hostname); 134 | } 135 | 136 | return null; 137 | } 138 | 139 | @SuppressWarnings("WeakerAccess") 140 | public ServerInfo getFallbackServer() { 141 | this.logger.info("Found " + this.defaultGroup.getServers().size() + " default servers"); 142 | return this.defaultGroup.getStrategy().getServer(this.defaultGroup.getServers()); 143 | } 144 | 145 | private Group getGroup(String name) { 146 | if (this.groups.containsKey(name)) { 147 | return this.groups.get(name); 148 | } 149 | 150 | return null; 151 | } 152 | 153 | private Map getGroupsForcedHosts(Map groups, Configuration forcedHostConfiguration) { 154 | 155 | Map forcedHosts = new HashMap<>(forcedHostConfiguration.getKeys().size()); 156 | 157 | for (String key : forcedHostConfiguration.getKeys()) { 158 | 159 | if (groups.containsKey(forcedHostConfiguration.getString(key))) { 160 | forcedHosts.put(key.replace("{dot}", "."), groups.get(forcedHostConfiguration.getString(key))); 161 | } else { 162 | this.logger.warning( 163 | "[Connection Balancer] Could not add forced host " 164 | + key.replace("{dot}", ".") 165 | + ": Group " 166 | + forcedHostConfiguration.getString(key) 167 | + " does not exists." 168 | ); 169 | } 170 | } 171 | 172 | return forcedHosts; 173 | } 174 | 175 | private Map getGroupsByConfiguration(Configuration groupConfig) { 176 | // Add default group if not configured 177 | if (!groupConfig.contains(ConnectionBalancer.defaultGroupName)) { 178 | 179 | Configuration defaultGroupConfig = new Configuration(); 180 | defaultGroupConfig.set("strategy", "balance"); 181 | defaultGroupConfig.set("can-reconnect", true); 182 | defaultGroupConfig.set("restricted", false); 183 | groupConfig.set(ConnectionBalancer.defaultGroupName, defaultGroupConfig); 184 | } 185 | 186 | // Reset groups maps 187 | Map groups = new HashMap<>(groupConfig.getKeys().size()); 188 | 189 | // Now lets add configured groups 190 | for (String key : groupConfig.getKeys()) { 191 | groups.put(key, new Group( 192 | key, 193 | groupConfig.getString(key + ".strategy"), 194 | groupConfig.getBoolean(key + ".restricted"), 195 | groupConfig.getBoolean(key + ".can_reconnect") 196 | )); 197 | } 198 | 199 | return groups; 200 | } 201 | 202 | @SuppressWarnings("WeakerAccess") 203 | public void addServer(ServerInfo server) { 204 | this.servers.put(server.getName(), server); 205 | } 206 | 207 | @SuppressWarnings("WeakerAccess") 208 | public void removeServer(ServerInfo server) { 209 | this.servers.remove(server.getName()); 210 | } 211 | 212 | @SuppressWarnings("unused") 213 | public void removeServer(String serverName) { 214 | this.servers.remove(serverName); 215 | } 216 | 217 | @SuppressWarnings("unused") 218 | public ServerInfo getServer(ServerInfo server) { 219 | return this.servers.get(server.getName()); 220 | } 221 | 222 | @SuppressWarnings({"WeakerAccess", "unused"}) 223 | public ServerInfo getServer(String name) { 224 | return this.servers.get(name); 225 | } 226 | 227 | @SuppressWarnings({"WeakerAccess", "unused"}) 228 | public String getServerGroup(String name) { 229 | return this.serverGroups.get(name); 230 | } 231 | 232 | @SuppressWarnings({"WeakerAccess", "unused"}) 233 | public String getServerGroup(ServerInfo server) { 234 | return this.serverGroups.get(server.getName()); 235 | } 236 | 237 | @EventHandler 238 | @SuppressWarnings("unused") 239 | public void onPostAddServer(PostAddServerEvent event) { 240 | String groupName = ConnectionBalancer.defaultGroupName; 241 | if (event.getEnvironmentVariables().containsKey(this.groupEnvironmentVariable)) { 242 | groupName = event.getEnvironmentVariables().get(this.groupEnvironmentVariable); 243 | } 244 | 245 | if (!this.groups.containsKey(groupName)) { 246 | groupName = ConnectionBalancer.defaultGroupName; 247 | } 248 | 249 | this.addServer(event.getServerInfo()); 250 | this.logger.info("[Connection Balancer] Added Server: " + event.getServerInfo().getName()); 251 | this.groups.get(groupName).addServer(event.getServerInfo()); 252 | this.serverGroups.put(event.getServerInfo().getName(), groupName); 253 | this.logger.info("[Connection Balancer] Added Server to group: " + event.getServerInfo().getName() + " > " + groupName); 254 | if (event.getEnvironmentVariables().containsKey(this.forcedHostEnvironmentVariable)) { 255 | String forcedHost = event.getEnvironmentVariables().get(this.forcedHostEnvironmentVariable); 256 | 257 | if (this.forcedHostServers.containsKey(forcedHost)) { 258 | this.logger.warning( 259 | "[Connection Balancer] Overwriting existing Forced Host: " 260 | + forcedHost 261 | + " > " 262 | + this.forcedHostServers.get(forcedHost).getName() 263 | ); 264 | } 265 | 266 | this.logger.info("[Connection Balancer] Adding Server Forced Host: " 267 | + forcedHost 268 | + " > " 269 | + event.getServerInfo().getName()); 270 | this.forcedHostServers.put( 271 | forcedHost, 272 | event.getServerInfo() 273 | ); 274 | } 275 | } 276 | 277 | @EventHandler 278 | @SuppressWarnings("unused") 279 | public void onPreRemoveServer(PreRemoveServerEvent event) { 280 | if (!this.servers.containsKey(event.getServerInfo().getName())) { 281 | return; 282 | } 283 | ServerInfo server = this.servers.get(event.getServerInfo().getName()); 284 | 285 | this.logger.info("[Connection Balancer] Removing Server from group: " + event.getServerInfo().getName() + " < " + this.serverGroups.get(server.getName())); 286 | this.groups.get(this.serverGroups.get(server.getName())).removeServer(server); 287 | this.serverGroups.remove(server.getName()); 288 | this.logger.info("[Connection Balancer] Removing Server: " + event.getServerInfo().getName()); 289 | this.removeServer(server); 290 | 291 | for (String entry : this.forcedHostServers.keySet()) { 292 | if (this.forcedHostServers.get(entry).getName().equals(server.getName())) { 293 | this.logger.info("[Connection Balancer] Removing Server Forced Host: " 294 | + entry 295 | + " > " 296 | + server.getName()); 297 | 298 | this.forcedHostServers.remove(entry); 299 | break; 300 | } 301 | } 302 | } 303 | 304 | @EventHandler 305 | @SuppressWarnings("unused") 306 | public void onPluginMessage(PluginMessageEvent event) { 307 | DataInputStream in = new DataInputStream(new ByteArrayInputStream(event.getData())); 308 | try { 309 | String subchanncel = in.readUTF(); 310 | this.logger.info(subchanncel); 311 | } catch (Exception ignored) { 312 | } 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/connection/balancer/command/GroupCommand.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.connection.balancer.command; 2 | 3 | import de.craftmania.dockerizedcraft.connection.balancer.ConnectionBalancer; 4 | import de.craftmania.dockerizedcraft.connection.balancer.model.Group; 5 | import net.md_5.bungee.api.ChatColor; 6 | import net.md_5.bungee.api.CommandSender; 7 | import net.md_5.bungee.api.chat.ComponentBuilder; 8 | import net.md_5.bungee.api.connection.ProxiedPlayer; 9 | import net.md_5.bungee.api.plugin.Command; 10 | 11 | public class GroupCommand extends Command { 12 | 13 | private Group group; 14 | 15 | private ConnectionBalancer connectionBalancer; 16 | 17 | public GroupCommand(String name, Group group, ConnectionBalancer connectionBalancer) { 18 | super(name); 19 | this.group = group; 20 | this.connectionBalancer = connectionBalancer; 21 | } 22 | 23 | @Override 24 | public void execute(CommandSender commandSender, String[] strings) { 25 | if(commandSender instanceof ProxiedPlayer){ 26 | ProxiedPlayer player = (ProxiedPlayer) commandSender; 27 | 28 | String currentServer = player.getServer().getInfo().getName(); 29 | if (connectionBalancer.getServerGroup(currentServer).equalsIgnoreCase(group.getName())) { 30 | player.sendMessage(new ComponentBuilder("You are already connected to this server group!").color(ChatColor.RED).create()); 31 | return; 32 | } 33 | 34 | player.connect(group.getStrategy().getServer(group.getServers())); 35 | 36 | }else{ 37 | commandSender.sendMessage(new ComponentBuilder("This command can only be run by a player!").color(ChatColor.RED).create()); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/connection/balancer/model/Group.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.connection.balancer.model; 2 | 3 | import de.craftmania.dockerizedcraft.connection.balancer.ConnectionBalancer; 4 | import de.craftmania.dockerizedcraft.connection.balancer.strategy.Strategy; 5 | import net.md_5.bungee.api.config.ServerInfo; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class Group { 11 | private String name; 12 | 13 | private Strategy strategy; 14 | 15 | private Boolean restricted; 16 | 17 | private Boolean canReconnect; 18 | 19 | private Map servers; 20 | 21 | public Group(String name, Strategy strategy, Boolean restricted, Boolean canReconnect) { 22 | this.name = name; 23 | this.strategy = strategy; 24 | this.restricted = restricted; 25 | this.canReconnect = canReconnect; 26 | this.servers = new HashMap<>(); 27 | } 28 | 29 | public Group(String name, String strategy, Boolean restricted, Boolean canReconnect) { 30 | this.name = name; 31 | this.setStrategy(strategy); 32 | this.restricted = restricted; 33 | this.canReconnect = canReconnect; 34 | this.servers = new HashMap<>(); 35 | } 36 | 37 | 38 | @SuppressWarnings({"WeakerAccess", "unused"}) 39 | public String getName() { 40 | return name; 41 | } 42 | 43 | @SuppressWarnings({"WeakerAccess", "unused"}) 44 | public void setName(String name) { 45 | this.name = name; 46 | } 47 | 48 | @SuppressWarnings({"WeakerAccess", "unused"}) 49 | public Strategy getStrategy() { 50 | return strategy; 51 | } 52 | 53 | @SuppressWarnings({"WeakerAccess", "unused"}) 54 | public void setStrategy(Strategy strategy) { 55 | this.strategy = strategy; 56 | } 57 | 58 | @SuppressWarnings({"WeakerAccess", "unused"}) 59 | public void setStrategy(String strategy) { 60 | if (ConnectionBalancer.balanceStrategies.containsKey(strategy)) { 61 | this.strategy = ConnectionBalancer.balanceStrategies.get(strategy); 62 | } else { 63 | this.strategy = ConnectionBalancer.balanceStrategies.get("balance"); 64 | } 65 | } 66 | 67 | @SuppressWarnings({"WeakerAccess", "unused"}) 68 | public Boolean getRestricted() { 69 | return restricted; 70 | } 71 | 72 | @SuppressWarnings({"WeakerAccess", "unused"}) 73 | public void setRestricted(Boolean restricted) { 74 | this.restricted = restricted; 75 | } 76 | 77 | @SuppressWarnings({"WeakerAccess", "unused"}) 78 | public Boolean getCanReconnect() { 79 | return canReconnect; 80 | } 81 | 82 | @SuppressWarnings({"WeakerAccess", "unused"}) 83 | public void setCanReconnect(Boolean canReconnect) { 84 | this.canReconnect = canReconnect; 85 | } 86 | 87 | @Override 88 | public String toString() { 89 | return super.toString() + "{" + this.name + "," + this.strategy + "}"; 90 | } 91 | 92 | @SuppressWarnings({"WeakerAccess", "unused"}) 93 | public Map getServers() { 94 | return this.servers; 95 | } 96 | 97 | @SuppressWarnings({"WeakerAccess", "unused"}) 98 | public void addServer(ServerInfo server) { 99 | this.servers.put(server.getName(), server); 100 | } 101 | 102 | @SuppressWarnings({"WeakerAccess", "unused"}) 103 | public ServerInfo getServer(String name) { 104 | return this.servers.get(name); 105 | } 106 | 107 | 108 | @SuppressWarnings({"WeakerAccess", "unused"}) 109 | public void removeServer(ServerInfo server) { 110 | this.servers.remove(server.getName()); 111 | } 112 | 113 | 114 | @SuppressWarnings({"WeakerAccess", "unused"}) 115 | public void removeServer(String server) { 116 | this.servers.remove(server); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/connection/balancer/session/RedisSessionStorage.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.connection.balancer.session; 2 | 3 | import redis.clients.jedis.Jedis; 4 | import redis.clients.jedis.JedisPool; 5 | import redis.clients.jedis.exceptions.JedisConnectionException; 6 | 7 | import java.util.UUID; 8 | 9 | public class RedisSessionStorage implements SessionStorage { 10 | 11 | static private String prefix; 12 | 13 | static { 14 | prefix = "player/session/"; 15 | } 16 | 17 | private JedisPool jedisPool; 18 | private String password; 19 | 20 | public RedisSessionStorage(String hostname, String password, Integer port, Boolean ssl) { 21 | this.jedisPool = new JedisPool(hostname, port); 22 | this.password = password; 23 | } 24 | 25 | @Override 26 | public void setReconnectServer(UUID uuid, String serverName) { 27 | try (Jedis jedis = this.jedisPool.getResource()) { 28 | if (this.password != null && !this.password.equals("")) { 29 | jedis.auth(this.password); 30 | } 31 | jedis.set((RedisSessionStorage.prefix + uuid.toString()), serverName); 32 | } 33 | } 34 | 35 | @Override 36 | public String getReconnectServer(UUID uuid) { 37 | try (Jedis jedis = this.jedisPool.getResource()) { 38 | if (this.password != null && !this.password.equals("")) { 39 | jedis.auth(this.password); 40 | } 41 | System.out.print(jedis.get((RedisSessionStorage.prefix + uuid.toString()))); 42 | return jedis.get((RedisSessionStorage.prefix + uuid.toString())); 43 | } catch (Exception e) { 44 | return null; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/connection/balancer/session/SessionStorage.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.connection.balancer.session; 2 | 3 | import java.util.UUID; 4 | 5 | public interface SessionStorage { 6 | void setReconnectServer(UUID uuid, String serverName); 7 | 8 | String getReconnectServer(UUID uuid); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/connection/balancer/strategy/BalanceStrategy.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.connection.balancer.strategy; 2 | 3 | import net.md_5.bungee.api.config.ServerInfo; 4 | 5 | import java.util.Map; 6 | 7 | public class BalanceStrategy implements Strategy { 8 | @Override 9 | public ServerInfo getServer(Map server) { 10 | ServerInfo selectedServer = null; 11 | 12 | for (String key: server.keySet()) { 13 | if (selectedServer == null) { 14 | selectedServer = server.get(key); 15 | } else if (selectedServer.getPlayers().size() > server.get(key).getPlayers().size()) { 16 | selectedServer = server.get(key); 17 | } 18 | } 19 | 20 | return selectedServer; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/connection/balancer/strategy/Strategy.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.connection.balancer.strategy; 2 | 3 | import net.md_5.bungee.api.config.ServerInfo; 4 | 5 | import java.util.Map; 6 | 7 | public interface Strategy { 8 | ServerInfo getServer(Map server); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/container/inspector/IContainerInspector.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.container.inspector; 2 | 3 | public interface IContainerInspector { 4 | void runContainerInspection(); 5 | void runContainerListener(); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/container/inspector/docker/DockerClientFactory.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.container.inspector.docker; 2 | 3 | import com.github.dockerjava.api.DockerClient; 4 | import com.github.dockerjava.core.DefaultDockerClientConfig; 5 | import com.github.dockerjava.core.DockerClientBuilder; 6 | import com.github.dockerjava.core.DockerClientConfig; 7 | import net.md_5.bungee.config.Configuration; 8 | 9 | class DockerClientFactory { 10 | 11 | static DockerClient getByConfiguration(Configuration configuration) { 12 | return DockerClientFactory.get( 13 | configuration.getString("docker.host"), 14 | configuration.getBoolean("docker.tsl-verify"), 15 | configuration.getString("docker.cert-path"), 16 | configuration.getString("docker.registry.username"), 17 | configuration.getString("docker.registry.password"), 18 | configuration.getString("docker.registry.email"), 19 | configuration.getString("docker.registry.url") 20 | ); 21 | } 22 | 23 | private static DockerClient get( 24 | String host, 25 | Boolean tlsVerify, 26 | String certPath, 27 | String registryUsername, 28 | String registryPass, 29 | String registryMail, 30 | String registryUrl 31 | ) { 32 | DefaultDockerClientConfig.Builder configBuilder = DefaultDockerClientConfig.createDefaultConfigBuilder() 33 | .withDockerHost(host); 34 | 35 | configBuilder.withDockerTlsVerify(tlsVerify); 36 | 37 | if (!certPath.equals("")) { 38 | configBuilder.withDockerCertPath(certPath); 39 | } 40 | 41 | if(!registryUrl.equals("")) { 42 | configBuilder.withRegistryUrl(registryUrl); 43 | if (!registryUsername.equals("")) { 44 | configBuilder.withRegistryUsername(registryUsername); 45 | } 46 | 47 | if (!registryMail.equals("")) { 48 | configBuilder.withRegistryEmail(registryMail); 49 | } 50 | 51 | if (!registryPass.equals("")) { 52 | configBuilder.withRegistryPassword(registryPass); 53 | } 54 | } 55 | 56 | DockerClientConfig config = configBuilder.build(); 57 | 58 | return DockerClientBuilder.getInstance(config).build(); 59 | } 60 | } -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/container/inspector/docker/DockerContainerInspector.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.container.inspector.docker; 2 | 3 | import com.github.dockerjava.api.DockerClient; 4 | import com.github.dockerjava.api.model.Container; 5 | import com.github.dockerjava.api.model.Event; 6 | import com.github.dockerjava.api.model.EventType; 7 | import com.github.dockerjava.core.command.EventsResultCallback; 8 | import de.craftmania.dockerizedcraft.container.inspector.IContainerInspector; 9 | import net.md_5.bungee.api.ProxyServer; 10 | import net.md_5.bungee.config.Configuration; 11 | 12 | import java.io.IOException; 13 | import java.util.List; 14 | import java.util.logging.Logger; 15 | 16 | public class DockerContainerInspector implements IContainerInspector { 17 | private DockerClient dockerClient; 18 | 19 | private ProxyServer proxyServer; 20 | 21 | private String network; 22 | 23 | private Logger logger; 24 | 25 | public DockerContainerInspector(Configuration configuration, ProxyServer proxyServer, Logger logger) { 26 | this.proxyServer = proxyServer; 27 | this.network = configuration.getString("docker.network"); 28 | this.logger = logger; 29 | this.dockerClient = DockerClientFactory.getByConfiguration(configuration); 30 | } 31 | 32 | public void runContainerInspection() { 33 | this.logger.info("[Docker Container Inspector] Running initial inspection."); 34 | 35 | EventsResultCallback callback = this.getEventResultCallback(); 36 | List containers = this.dockerClient.listContainersCmd().exec(); 37 | 38 | // Trigger fake Event to use same Result Callback 39 | for (Container container: containers) { 40 | Event event = new Event("start", container.getId(), container.getImage(), System.currentTimeMillis()) 41 | .withAction("bootstrap") 42 | .withType(EventType.forValue("container")); 43 | 44 | callback.onNext(event); 45 | } 46 | } 47 | 48 | public void runContainerListener() { 49 | this.logger.info("[Docker Container Inspector] Running listener."); 50 | try { 51 | this.dockerClient.eventsCmd().exec(this.getEventResultCallback()).awaitCompletion().close(); 52 | } catch (IOException |InterruptedException e) { 53 | e.printStackTrace(); 54 | } 55 | } 56 | 57 | private EventsResultCallback getEventResultCallback() { 58 | return new ResultCallback( 59 | this.dockerClient, 60 | this.proxyServer, 61 | this.network 62 | ); 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/container/inspector/docker/ResultCallback.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.container.inspector.docker; 2 | 3 | import com.github.dockerjava.api.DockerClient; 4 | import com.github.dockerjava.api.command.InspectContainerResponse; 5 | import com.github.dockerjava.api.exception.NotFoundException; 6 | import com.github.dockerjava.api.model.Event; 7 | import com.github.dockerjava.api.model.ExposedPort; 8 | import com.github.dockerjava.api.model.Ports; 9 | import com.github.dockerjava.core.command.EventsResultCallback; 10 | import de.craftmania.dockerizedcraft.container.inspector.events.ContainerEvent; 11 | import net.md_5.bungee.api.ProxyServer; 12 | 13 | import java.net.InetAddress; 14 | import java.net.UnknownHostException; 15 | import java.util.*; 16 | 17 | public class ResultCallback extends EventsResultCallback { 18 | private DockerClient dockerClient; 19 | private ProxyServer proxyServer; 20 | private String network; 21 | 22 | ResultCallback(DockerClient dockerClient, ProxyServer proxyServer, String network) { 23 | this.dockerClient = dockerClient; 24 | this.proxyServer = proxyServer; 25 | this.network = network; 26 | } 27 | 28 | @Override 29 | public void onNext(Event event) { 30 | if (event.getType() == null || !event.getType().getValue().equals("container")) { 31 | super.onNext(event); 32 | return; 33 | } 34 | 35 | ContainerEvent containerEvent = new ContainerEvent(event.getId(), event.getAction()); 36 | 37 | // Not lets inspect the container, we won't fire any events without network information 38 | try { 39 | InspectContainerResponse info = this.dockerClient.inspectContainerCmd(event.getId()).exec(); 40 | 41 | containerEvent.setName(info.getName()); 42 | containerEvent.setEnvironmentVariables(this.getEnvironmentVariables(info)); 43 | containerEvent.setPort(this.getPort(info)); 44 | containerEvent.setIp(this.getIp(info, this.network)); 45 | 46 | } catch (NotFoundException e) { 47 | super.onNext(event); 48 | return; 49 | } 50 | 51 | super.onNext(event); 52 | this.proxyServer.getPluginManager().callEvent(containerEvent); 53 | 54 | } 55 | 56 | 57 | private Integer getPort(InspectContainerResponse info) { 58 | Map portBindings = info.getNetworkSettings().getPorts().getBindings(); 59 | 60 | if (portBindings.keySet().size() > 0 && portBindings.keySet().iterator().next().getPort() != 0) { 61 | return portBindings.keySet().iterator().next().getPort(); 62 | } 63 | 64 | return null; 65 | } 66 | 67 | private InetAddress getIp(InspectContainerResponse info, String network) { 68 | if (!info.getNetworkSettings().getNetworks().containsKey(network)) { 69 | return null; 70 | } 71 | 72 | try { 73 | return InetAddress.getByName(info.getNetworkSettings().getNetworks().get(network).getIpAddress()); 74 | } catch (UnknownHostException e) { 75 | return null; 76 | } 77 | } 78 | 79 | private Map getEnvironmentVariables(InspectContainerResponse info) { 80 | String[] unformattedArray = info.getConfig().getEnv(); 81 | 82 | if(unformattedArray == null || unformattedArray.length == 0) { 83 | return new HashMap<>(0); 84 | } 85 | 86 | Map formattedMap = new HashMap<>(unformattedArray.length); 87 | 88 | for (String environmentVariable: unformattedArray) { 89 | String[] parts = environmentVariable.split("="); 90 | if (parts.length == 2) { 91 | formattedMap.put(parts[0], parts[1]); 92 | } 93 | } 94 | return formattedMap; 95 | } 96 | } -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/container/inspector/events/ContainerEvent.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.container.inspector.events; 2 | 3 | import net.md_5.bungee.api.plugin.Event; 4 | 5 | import java.net.InetAddress; 6 | import java.util.Map; 7 | 8 | public class ContainerEvent extends Event { 9 | private String id; 10 | 11 | private String action; 12 | 13 | private InetAddress ip; 14 | 15 | private Integer port; 16 | 17 | private String name; 18 | 19 | private Map environmentVariables; 20 | 21 | public ContainerEvent(String id, String action) { 22 | this.id = id; 23 | this.action = action; 24 | } 25 | 26 | public String getId() { 27 | return id; 28 | } 29 | 30 | public String getAction() { 31 | return action; 32 | } 33 | 34 | public InetAddress getIp() { 35 | return ip; 36 | } 37 | 38 | public void setIp(InetAddress ip) { 39 | this.ip = ip; 40 | } 41 | 42 | public Integer getPort() { 43 | return port; 44 | } 45 | 46 | public void setPort(Integer port) { 47 | this.port = port; 48 | } 49 | 50 | public String getName() { 51 | return name; 52 | } 53 | 54 | public void setName(String name) { 55 | this.name = name; 56 | } 57 | 58 | public Map getEnvironmentVariables() { 59 | return environmentVariables; 60 | } 61 | 62 | public void setEnvironmentVariables(Map environmentVariables) { 63 | this.environmentVariables = environmentVariables; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/container/inspector/kubernetes/KubernetesContainerInspector.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.container.inspector.kubernetes; 2 | import de.craftmania.dockerizedcraft.container.inspector.IContainerInspector; 3 | import io.fabric8.kubernetes.client.Config; 4 | import io.fabric8.kubernetes.client.DefaultKubernetesClient; 5 | import io.fabric8.kubernetes.client.KubernetesClient; 6 | 7 | import net.md_5.bungee.api.ProxyServer; 8 | import net.md_5.bungee.config.Configuration; 9 | 10 | import java.util.logging.Logger; 11 | 12 | public class KubernetesContainerInspector implements IContainerInspector { 13 | private ProxyServer proxyServer; 14 | private Logger logger; 15 | private Configuration configuration; 16 | private KubernetesClient client; 17 | 18 | public KubernetesContainerInspector(Configuration configuration, ProxyServer proxyServer, Logger logger) { 19 | this.proxyServer = proxyServer; 20 | this.logger = logger; 21 | this.configuration = configuration; 22 | this.client = new DefaultKubernetesClient(); 23 | } 24 | 25 | public void runContainerInspection() { 26 | this.logger.info("[Kubernetes Container Inspector] Connecting to kubernetes."); 27 | } 28 | 29 | public void runContainerListener() { 30 | this.logger.info("[Kubernetes Container Inspector] Running listener."); 31 | String namespace = configuration.getString("kubernetes.namespace"); 32 | if(namespace == null ||namespace.isEmpty()) this.logger.severe("kubernetes.namespace not set."); 33 | this.client.pods().inNamespace(namespace).watch(new PodWatcher(proxyServer, logger)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/container/inspector/kubernetes/PodWatcher.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.container.inspector.kubernetes; 2 | import net.md_5.bungee.api.ProxyServer; 3 | import io.fabric8.kubernetes.api.model.Pod; 4 | import io.fabric8.kubernetes.client.Watcher; 5 | import de.craftmania.dockerizedcraft.container.inspector.events.ContainerEvent; 6 | import io.fabric8.kubernetes.client.KubernetesClientException; 7 | import io.fabric8.kubernetes.api.model.EnvVar; 8 | import java.util.logging.Logger; 9 | import java.net.InetAddress; 10 | import java.util.*; 11 | 12 | public class PodWatcher implements Watcher { 13 | 14 | private ProxyServer proxyServer; 15 | private Logger logger; 16 | PodWatcher(ProxyServer proxyServer, Logger logger) { 17 | this.proxyServer = proxyServer; 18 | this.logger = logger; 19 | } 20 | 21 | @Override 22 | public void eventReceived(Action action, Pod resource) { 23 | try { 24 | logger.info("action: "+action); 25 | logger.info("phase: " +resource.getStatus().getPhase()); 26 | Map labels = resource.getMetadata().getLabels(); 27 | logger.info("labels: "+labels.toString()); 28 | if(!labels.containsKey("dockerizedcraft/enabled") || !labels.get("dockerizedcraft/enabled").equals("true")) return; 29 | 30 | String dockerAction = "stop"; 31 | if(resource.getStatus().getPhase().equals("Running")){ 32 | dockerAction = "start"; 33 | } 34 | 35 | ContainerEvent containerEvent = new ContainerEvent(resource.getMetadata().getName(), dockerAction); 36 | containerEvent.setName(resource.getMetadata().getName()); 37 | logger.info("name: " +resource.getMetadata().getName()); 38 | Map environmentVariables = new HashMap<>(); 39 | for (EnvVar i : resource.getSpec().getContainers().get(0).getEnv()) environmentVariables.put(i.getName(),i.getValue()); 40 | containerEvent.setEnvironmentVariables(environmentVariables); 41 | logger.info("env:" + environmentVariables); 42 | containerEvent.setPort(Integer.parseInt(environmentVariables.get("SERVER_PORT"))); 43 | logger.info("port: "+environmentVariables.get("SERVER_PORT")); 44 | containerEvent.setIp(InetAddress.getByName(resource.getStatus().getPodIP())); 45 | logger.info("ip: "+resource.getStatus().getPodIP()); 46 | this.proxyServer.getPluginManager().callEvent(containerEvent); 47 | }catch(java.net.UnknownHostException ex){ 48 | logger.severe(ex.getMessage()); 49 | } 50 | 51 | } 52 | 53 | @Override 54 | public void onClose(KubernetesClientException cause) { 55 | logger.warning("Watcher close due to " + cause); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/plugin/notifier/AbstractNotifier.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.plugin.notifier; 2 | 3 | import net.md_5.bungee.api.config.ServerInfo; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.DataOutputStream; 7 | import java.io.IOException; 8 | 9 | public abstract class AbstractNotifier { 10 | @SuppressWarnings("SameParameterValue") 11 | protected void sendMessage(ServerInfo serverInfo, String channel, String subchannel, String message) { 12 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); 13 | DataOutputStream out = new DataOutputStream(stream); 14 | try { 15 | out.writeUTF(subchannel); 16 | out.writeUTF(message); 17 | } catch (IOException e) { 18 | e.printStackTrace(); 19 | } 20 | 21 | serverInfo.sendData(channel, stream.toByteArray()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/plugin/notifier/serverlist/ServerListPluginNotifier.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.plugin.notifier.serverlist; 2 | 3 | 4 | import com.google.gson.JsonObject; 5 | import de.craftmania.dockerizedcraft.plugin.notifier.AbstractNotifier; 6 | import de.craftmania.dockerizedcraft.server.updater.events.PostAddServerEvent; 7 | import de.craftmania.dockerizedcraft.server.updater.events.PreRemoveServerEvent; 8 | import net.md_5.bungee.api.config.ServerInfo; 9 | import net.md_5.bungee.api.plugin.Listener; 10 | import net.md_5.bungee.config.Configuration; 11 | import net.md_5.bungee.event.EventHandler; 12 | 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | import java.util.logging.Logger; 16 | 17 | public class ServerListPluginNotifier extends AbstractNotifier implements Listener { 18 | private static String channel; 19 | static { 20 | channel = "DockerizedCraft"; 21 | } 22 | 23 | private Configuration mappingConfig; 24 | private Logger logger; 25 | 26 | private JsonObject serverInfos; 27 | private Map servers; 28 | 29 | public ServerListPluginNotifier(Configuration mappingConfig, Logger logger) { 30 | this.logger = logger; 31 | this.mappingConfig = mappingConfig; 32 | this.serverInfos = new JsonObject(); 33 | this.servers = new HashMap<>(); 34 | } 35 | 36 | public void sendUpdate() { 37 | this.sendServerList(this.servers); 38 | } 39 | 40 | private void sendServerList(Map servers) { 41 | this.logger.info("[Plugin Notification] Sending update to servers"); 42 | for (String serverName: servers.keySet()) { 43 | this.sendMessage( 44 | this.servers.get(serverName), 45 | ServerListPluginNotifier.channel, 46 | "ServerData", 47 | this.serverInfos.toString() 48 | ); 49 | } 50 | 51 | } 52 | 53 | private JsonObject getMetaData(ServerInfo server, Map environmentVariables) { 54 | JsonObject serverMetaData = new JsonObject(); 55 | serverMetaData.addProperty("address", server.getAddress().getAddress().getHostAddress() + ':' + server.getAddress().getPort()); 56 | serverMetaData.addProperty("host", server.getAddress().getAddress().getHostAddress()); 57 | serverMetaData.addProperty("port", server.getAddress().getPort()); 58 | serverMetaData.addProperty("motd", server.getMotd()); 59 | serverMetaData.addProperty("name", server.getName()); 60 | serverMetaData.addProperty("proxied_players", server.getPlayers().size()); 61 | 62 | for (String configKey: this.mappingConfig.getKeys()) { 63 | Configuration metaConfig = this.mappingConfig.getSection(configKey); 64 | String value = null; 65 | 66 | if (environmentVariables.containsKey(metaConfig.getString("environment-variable"))) { 67 | value = environmentVariables.get(metaConfig.getString("environment-variable")); 68 | } else if (metaConfig.contains("default")) { 69 | value = metaConfig.getString("default"); 70 | } 71 | 72 | if (value == null) { 73 | if (metaConfig.getBoolean("required")) { 74 | serverMetaData.addProperty(configKey, (String) null); 75 | } 76 | } else { 77 | serverMetaData.addProperty(configKey, value); 78 | } 79 | } 80 | 81 | return serverMetaData; 82 | } 83 | 84 | @EventHandler 85 | @SuppressWarnings("unused") 86 | public void onPostAddServer(PostAddServerEvent event) { 87 | this.serverInfos.add(event.getServerInfo().getName(), this.getMetaData(event.getServerInfo(), event.getEnvironmentVariables())); 88 | this.servers.put(event.getServerInfo().getName(), event.getServerInfo()); 89 | 90 | this.logger.info("[Plugin Notification] Added Server meta data: " + event.getServerInfo().getName()); 91 | this.sendServerList(this.servers); 92 | 93 | } 94 | 95 | @EventHandler 96 | @SuppressWarnings("unused") 97 | public void onPreRemoveServer(PreRemoveServerEvent event) { 98 | this.serverInfos.remove(event.getServerInfo().getName()); 99 | this.servers.remove(event.getServerInfo().getName()); 100 | 101 | this.logger.info("[Plugin Notification] Removed Server meta data: " + event.getServerInfo().getName()); 102 | this.sendServerList(this.servers); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/server/updater/ServerUpdater.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.server.updater; 2 | 3 | import de.craftmania.dockerizedcraft.container.inspector.events.ContainerEvent; 4 | import de.craftmania.dockerizedcraft.server.updater.events.PostAddServerEvent; 5 | import de.craftmania.dockerizedcraft.server.updater.events.PostRemoveServerEvent; 6 | import de.craftmania.dockerizedcraft.server.updater.events.PreAddServerEvent; 7 | import de.craftmania.dockerizedcraft.server.updater.events.PreRemoveServerEvent; 8 | import net.md_5.bungee.api.ProxyServer; 9 | import net.md_5.bungee.api.config.ServerInfo; 10 | import net.md_5.bungee.api.plugin.Listener; 11 | import net.md_5.bungee.config.Configuration; 12 | import net.md_5.bungee.event.EventHandler; 13 | 14 | import java.net.InetSocketAddress; 15 | import java.util.List; 16 | import java.util.logging.Logger; 17 | 18 | public class ServerUpdater implements Listener { 19 | 20 | private String identifierKey; 21 | 22 | private String nameKey; 23 | 24 | private String portKey; 25 | 26 | private String motdKey; 27 | 28 | private String restrictedKey; 29 | 30 | private List addActionList; 31 | 32 | private List removeActionList; 33 | 34 | private Boolean debug; 35 | 36 | private ProxyServer proxyServer; 37 | 38 | private Logger logger; 39 | 40 | public ServerUpdater(Configuration configuration, ProxyServer proxyServer, Logger logger) { 41 | this.identifierKey = configuration.getString("environment-variables.identifier"); 42 | this.nameKey = configuration.getString("environment-variables.name"); 43 | this.portKey = configuration.getString("environment-variables.port"); 44 | this.motdKey = configuration.getString("environment-variables.motd"); 45 | this.restrictedKey = configuration.getString("environment-variables.restricted"); 46 | this.addActionList = configuration.getStringList("add-actions"); 47 | this.removeActionList = configuration.getStringList("remove-actions"); 48 | this.debug = configuration.getBoolean("debug"); 49 | this.proxyServer = proxyServer; 50 | this.logger = logger; 51 | } 52 | 53 | @EventHandler 54 | @SuppressWarnings("unused") 55 | public void onDockerEvent(ContainerEvent event) { 56 | if (!event.getEnvironmentVariables().containsKey(this.identifierKey)) { 57 | logger.info("missing identifier" + this.identifierKey); 58 | return; 59 | } 60 | 61 | if (this.addActionList.contains(event.getAction())) { 62 | this.addServer(event); 63 | } 64 | 65 | else if(this.removeActionList.contains(event.getAction())) { 66 | this.removeServer(event); 67 | } 68 | else 69 | logger.info("unknown action on event: "+event.getAction()); 70 | } 71 | 72 | 73 | private void addServer(ContainerEvent eventData) { 74 | 75 | ServerInfo serverInfo = this.getServerInfoForEvent(eventData); 76 | 77 | if (serverInfo.getAddress().getHostName() == null) { 78 | this.logger.warning("[Server Updater] Could not add server:" + serverInfo.getName()); 79 | this.logger.warning("[Server Updater] > Reason: No IP, is you network fine?"); 80 | this.logger.warning("[Server Updater] > Trigger-Event-Action: " + eventData.getAction()); 81 | 82 | return; 83 | } 84 | 85 | if (this.proxyServer.getServers().containsKey(serverInfo.getName())) { 86 | if (this.debug) { 87 | this.logger.warning("[Server Updater] Server with id " + serverInfo.getName() + " already exists in Bungeecord Proxy."); 88 | } 89 | 90 | InetSocketAddress currentAddress = this.proxyServer.getServers().get(serverInfo.getName()).getAddress(); 91 | 92 | if (!currentAddress.equals(serverInfo.getAddress())) { 93 | if (this.debug) { 94 | this.logger.warning("[Server Updater] > Server address of " + serverInfo.getName() + "changed!"); 95 | this.logger.warning("[Server Updater] >> Current: " + currentAddress.toString()); 96 | this.logger.warning("[Server Updater] >> New: " + serverInfo.getAddress().toString()); 97 | this.logger.warning("[Server Updater] >> Server removed from proxy to re-add it"); 98 | } 99 | this.proxyServer.getServers().remove(serverInfo.getName()); 100 | } else { 101 | if (this.debug) { 102 | this.logger.warning("[Server Updater] > Skipped!"); 103 | this.logger.warning("[Server Updater] > Trigger-Event-Action: " + eventData.getAction()); 104 | } 105 | return; 106 | } 107 | } 108 | 109 | 110 | this.proxyServer.getPluginManager().callEvent(new PreAddServerEvent( 111 | serverInfo, 112 | eventData.getEnvironmentVariables() 113 | )); 114 | 115 | this.proxyServer.getServers().put(serverInfo.getName(), serverInfo); 116 | this.logger.info("[Server Updater] Added server: " + serverInfo.getName()); 117 | this.logger.info("[Server Updater] > Address: " + serverInfo.getAddress().toString()); 118 | this.logger.info("[Server Updater] > MOTD: " + serverInfo.getMotd()); 119 | this.logger.info("[Server Updater] > Trigger-Event-Action: " + eventData.getAction()); 120 | 121 | this.proxyServer.getPluginManager().callEvent(new PostAddServerEvent( 122 | serverInfo, 123 | eventData.getEnvironmentVariables() 124 | )); 125 | } 126 | 127 | private void removeServer (ContainerEvent eventData) { 128 | // server id 129 | String id = this.getServerId(eventData); 130 | 131 | if (!this.proxyServer.getServers().containsKey(id)) { 132 | if(this.debug) { 133 | this.logger.warning("[Server Updater] Could not remove server: " + id); 134 | this.logger.warning("[Server Updater] > Reason: Not exists"); 135 | this.logger.warning("[Server Updater] > Trigger-Event-Action: " + eventData.getAction()); 136 | } 137 | 138 | return; 139 | } 140 | 141 | this.proxyServer.getPluginManager().callEvent(new PreRemoveServerEvent( 142 | this.getServerInfoForEvent(eventData), 143 | eventData.getEnvironmentVariables() 144 | )); 145 | 146 | this.proxyServer.getServers().remove(id); 147 | this.logger.info("[Server Updater] Removing Server: " + id); 148 | this.logger.info("[Server Updater] > Trigger-Event-Action: " + eventData.getAction()); 149 | 150 | this.proxyServer.getPluginManager().callEvent(new PostRemoveServerEvent(id)); 151 | } 152 | 153 | private ServerInfo getServerInfoForEvent(ContainerEvent eventData) { 154 | // server id 155 | String id = this.getServerId(eventData); 156 | 157 | // Getting the address to create 158 | int port = eventData.getEnvironmentVariables().get(this.portKey) != null 159 | ? Integer.parseInt(eventData.getEnvironmentVariables().get(this.portKey)) 160 | : (eventData.getPort() != null ? eventData.getPort() : 25565); 161 | 162 | InetSocketAddress inetSocketAddress = new InetSocketAddress(eventData.getIp(), port); 163 | 164 | 165 | // Getting the motd 166 | String motd = eventData.getEnvironmentVariables().get(this.motdKey) != null 167 | ? eventData.getEnvironmentVariables().get(this.motdKey) 168 | : "A Minecraft Server Instance"; 169 | 170 | // Getting restricted bool 171 | boolean restricted = 172 | eventData.getEnvironmentVariables().get(this.restrictedKey) != null && 173 | eventData.getEnvironmentVariables().get(this.restrictedKey).equals("restricted"); 174 | 175 | return ProxyServer.getInstance().constructServerInfo( 176 | id, 177 | inetSocketAddress, 178 | motd, 179 | restricted 180 | ); 181 | } 182 | 183 | private String getServerId(ContainerEvent eventData) { 184 | if (eventData.getEnvironmentVariables().get(this.nameKey) != null) { 185 | return eventData.getEnvironmentVariables().get(this.nameKey); 186 | } 187 | 188 | if (eventData.getName() != null) { 189 | return eventData.getName().replace("/", ""); 190 | } 191 | 192 | return eventData.getId(); 193 | } 194 | 195 | } 196 | -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/server/updater/events/PostAddServerEvent.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.server.updater.events; 2 | 3 | import net.md_5.bungee.api.config.ServerInfo; 4 | import net.md_5.bungee.api.plugin.Event; 5 | 6 | import java.util.Map; 7 | 8 | public class PostAddServerEvent extends Event { 9 | private ServerInfo serverInfo; 10 | private Map environmentVariables; 11 | 12 | public PostAddServerEvent(ServerInfo serverInfo, Map environmentVariables) { 13 | this.serverInfo = serverInfo; 14 | this.environmentVariables = environmentVariables; 15 | } 16 | 17 | @SuppressWarnings("unused") 18 | public ServerInfo getServerInfo() { 19 | return serverInfo; 20 | } 21 | 22 | @SuppressWarnings("unused") 23 | public Map getEnvironmentVariables() { 24 | return environmentVariables; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/server/updater/events/PostRemoveServerEvent.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.server.updater.events; 2 | 3 | import net.md_5.bungee.api.plugin.Event; 4 | 5 | public class PostRemoveServerEvent extends Event { 6 | 7 | private String name; 8 | 9 | @SuppressWarnings("unused") 10 | public PostRemoveServerEvent(String name) { 11 | this.name = name; 12 | } 13 | 14 | @SuppressWarnings("unused") 15 | public String getName() { 16 | return name; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/server/updater/events/PreAddServerEvent.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.server.updater.events; 2 | 3 | import net.md_5.bungee.api.config.ServerInfo; 4 | import net.md_5.bungee.api.plugin.Event; 5 | 6 | import java.util.Map; 7 | 8 | public class PreAddServerEvent extends Event { 9 | private ServerInfo serverInfo; 10 | private Map environmentVariables; 11 | 12 | public PreAddServerEvent(ServerInfo serverInfo, Map environmentVariables) { 13 | this.serverInfo = serverInfo; 14 | this.environmentVariables = environmentVariables; 15 | } 16 | 17 | @SuppressWarnings("unused") 18 | public ServerInfo getServerInfo() { 19 | return serverInfo; 20 | } 21 | 22 | @SuppressWarnings("unused") 23 | public Map getEnvironmentVariables() { 24 | return environmentVariables; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/de/craftmania/dockerizedcraft/server/updater/events/PreRemoveServerEvent.java: -------------------------------------------------------------------------------- 1 | package de.craftmania.dockerizedcraft.server.updater.events; 2 | 3 | import net.md_5.bungee.api.config.ServerInfo; 4 | import net.md_5.bungee.api.plugin.Event; 5 | 6 | import java.util.Map; 7 | 8 | public class PreRemoveServerEvent extends Event { 9 | private ServerInfo serverInfo; 10 | private Map environmentVariables; 11 | 12 | public PreRemoveServerEvent(ServerInfo serverInfo, Map environmentVariables) { 13 | this.serverInfo = serverInfo; 14 | this.environmentVariables = environmentVariables; 15 | } 16 | 17 | @SuppressWarnings("unused") 18 | public ServerInfo getServerInfo() { 19 | return serverInfo; 20 | } 21 | 22 | @SuppressWarnings("unused") 23 | public Map getEnvironmentVariables() { 24 | return environmentVariables; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/bungee.yml: -------------------------------------------------------------------------------- 1 | name: DockerizedCraft 2 | main: de.craftmania.dockerizedcraft.DockerizedCraft 3 | version: 0.2.2 4 | author: Muehre -------------------------------------------------------------------------------- /src/main/resources/connection-balancer.yml: -------------------------------------------------------------------------------- 1 | # Care if you disable it you will need to configure default, priority and fallback servers by hand 2 | # Or use an different connection balancer handler plugin (Is it compatible?) 3 | # Type: Boolean 4 | enabled: true 5 | 6 | # Outputs extra information 7 | # Type: Boolean 8 | debug: true 9 | 10 | # To store player session for the reconnect handler 11 | # Type: Section 12 | session-store: 13 | # Type: Section 14 | redis: 15 | # Type: String 16 | host: "redis" 17 | # Type: String 18 | password: ~ 19 | # Type: Integer 20 | port: 6379 21 | # Type: Boolean 22 | ssl: true 23 | 24 | # Environment variables of the containers 25 | # Type: Section 26 | environment-variables: 27 | # If the environment variable is set the server will be added to the priority list of connectionbalancer 28 | # This plugin implemented a custom load balancer which will use defined groups 29 | # Leaving the env variable blank will add the server to the default group 30 | # Type: String 31 | group: SERVER_GROUP 32 | 33 | # To enable forced host for the single instance. 34 | # You can also configure a forced host for a whole group. See groups config. I would recommend to do so events with single instances 35 | # i.e. docker run -e SERVER_FORCED_HOST=muehre.craftmania.de playerworld:latest 36 | # Type: String 37 | forced-host: SERVER_FORCED_HOST 38 | 39 | # Type: Section 40 | # check docker.event_listener.environment_variables.group_key 41 | groups: 42 | # Server group configuration 43 | # default group is used if a a container does not have a group environment variable. You can also configure it here 44 | # Type: Section 45 | # Group: 46 | # - strategy(String): balance is the only strategy atm. I will implement more as soon as they are required 47 | # - forced_host(String) Optional: people joining over this host will be send to this group 48 | # - can-reconnect(Boolean) Default: false: if a player can reconnect to this group. Usefull to disable for i.e minigames 49 | # - restricted(Boolean) Default: false: Is a permission required? 50 | eu-lobby: 51 | strategy: balance 52 | can-reconnect: true 53 | restricted: false 54 | 55 | game-xy: 56 | strategy: balance 57 | can-reconnect: false 58 | restricted: false 59 | 60 | private: 61 | strategy: balance 62 | can-reconnect: true 63 | restricted: false 64 | 65 | us-lobby: 66 | strategy: balance 67 | can-reconnect: true 68 | restricted: false 69 | 70 | # The default group a user is connected to if he freshly joins or his group was restricted in re-connections. 71 | # And not forced host is matching 72 | # Type: String 73 | default-group: eu-lobby 74 | 75 | # Commands that players can execute to join a certain group 76 | # Type Section 77 | join-commands: 78 | lobby: eu-lobby 79 | hub: eu-lobby 80 | 81 | # Setting force hosts. Use {dot} placeholder for dots to not break yaml syntax 82 | # Type: Section 83 | forced-hosts: 84 | "us{dot}mynetwork{dot}net": "us-lobby" 85 | "eu{dot}mynetwork{dot}net": "eu-lobby" 86 | -------------------------------------------------------------------------------- /src/main/resources/container-inspector.yml: -------------------------------------------------------------------------------- 1 | # Is the docker listener enabled? This is the core functionality and should stay enabled 2 | # Type: Boolean 3 | enabled: true 4 | 5 | # Outputs extra information 6 | # Type: Boolean 7 | debug: true 8 | 9 | # Backend type (docker or kubernetes) 10 | backend: docker 11 | 12 | # Ensure the read permissions 13 | # available schemas: tcp or unix 14 | # Type: String 15 | docker: 16 | host: unix:///var/run/docker.sock 17 | 18 | # Type: Boolean 19 | tsl-verify: false 20 | 21 | # Type: String|null 22 | cert-path: ~ 23 | 24 | # Type: Section 25 | registry: 26 | # Type: String|null 27 | username: ~ 28 | # Type: String|null 29 | password: ~ 30 | # Type: String|null 31 | email: ~ 32 | # Type: String|null 33 | url: ~ 34 | 35 | # The network name to resolve IP addresses 36 | # Only one network is possible to avoid confusion about which ip to use 37 | # check your networks with `docker network ls` 38 | network: "minecraft_local" 39 | 40 | kubernetes: 41 | # The namespace that bungeecord and all minecraft servers reside in 42 | namespace: minecraft -------------------------------------------------------------------------------- /src/main/resources/plugin-notifier.yml: -------------------------------------------------------------------------------- 1 | # If you disable it no plugin which depends on this data will work 2 | # Type: Boolean 3 | enabled: true 4 | 5 | # Outputs extra information 6 | # Type: Boolean 7 | debug: true 8 | 9 | # Server updates will be sent after any add or remove anyway but the interval is usefull to update i.e. ProxiedPlayer Information 10 | # Type: Integer 11 | refresh-interval: 30 12 | 13 | # Maps environment variables and forwards them with the specified key to the bukkit client plugin 14 | # ie. docker run -e CATEGORY=factions 15 | # Reserved keys as they will be handed down anyway: name, address, motd, restricted (In case you want to overwrite you can do so) 16 | # Type: Section 17 | # Type MetaDataConfig: 18 | # - environment-variable (string): The environemnt variable to access 19 | # - required (bool): Is this value required? If not given and no default 20 | # is defined the server will not be added to connectionbalancer 21 | # - default (string): If the environment variable is not defined will fall 22 | # back to the given default 23 | meta-data-mapper: 24 | # make the TYPE also accessible 25 | type: 26 | required: true 27 | environment-variable: TYPE 28 | 29 | # Category for i.e create server selector menus based on categories 30 | category: 31 | environment-variable: SERVER_CATEGORY 32 | required: true 33 | default: "none" 34 | 35 | tags: 36 | environment-variable: SERVER_TAGS 37 | required: true 38 | default: "" 39 | -------------------------------------------------------------------------------- /src/main/resources/server-updater.yml: -------------------------------------------------------------------------------- 1 | # Is the Server update enabled? 2 | # Type: Boolean 3 | enabled: true 4 | 5 | # Extra output 6 | # Type: Boolean 7 | debug: false 8 | 9 | # Container events actions to listen on for adding server 10 | # i.e.: "start", "health-status: healthy" 11 | # Recommended "start", "bootstrap" and health checks 12 | # the "bootstrap" events is triggered when connectionbalancer starts to register all running containers. Should ne be removed 13 | # If you want to add only healthy containers be aware of removing "start" action 14 | # @see https://docs.docker.com/engine/reference/commandline/events/#object-types 15 | # Type: List 16 | add-actions: 17 | - "bootstrap" 18 | - "start" 19 | - "health_status: healthy" 20 | - "unpause" 21 | 22 | # Container events actions to listen on for remving server 23 | # i.e.: "kill", "die" 24 | # Recommended "die" and health checks 25 | # If you want to remove unhealthy containers add i.e: "health_status: unhealthy" 26 | # @see https://docs.docker.com/engine/reference/commandline/events/#object-types 27 | # Type: List 28 | remove-actions: 29 | - "kill" 30 | - "die" 31 | - "stop" 32 | - "pause" 33 | 34 | 35 | 36 | # Environment variables of the containers 37 | # Type: Section 38 | environment-variables: 39 | # The events listener will only add server with the defined environment variable 40 | # ie. docker run -e TYPE=minecraft_spigot my_server 41 | # Type: String 42 | identifier: TYPE 43 | 44 | # Be default the first exposed port is taken if the container exposes multiple ports you can 45 | # set it by setting the PORT environment variable in the container 46 | # If you exposed multiple ports its highly recommended to set the environment variable 47 | # Type: String 48 | port: SERVER_PORT 49 | 50 | # Setting the motd in the Bungeecord setting 51 | # i.e. docker run -e SERVER_FORCED_HOST="Another Minecraft Server" playerworld:latest 52 | # Type: String 53 | motd: SERVER_MOTD 54 | 55 | # Setting the server to restricted 56 | # If not set it is false, only excepts: "restricted" or "unrestricted" 57 | # i.e. docker run -e SERVER_RESTRICTED=true playerworld:latest 58 | # Type: String 59 | restricted: SERVER_RESTRICTED 60 | 61 | # Each server name needs to be unique 62 | # If you are not able to control if it is unique (autoscaling or whatever) you should not set it in your container 63 | # If you do not set the environment variable the container name itself will be used 64 | # Two server with the same name will overwrite each other 65 | # Type: String 66 | name: SERVER_NAME 67 | --------------------------------------------------------------------------------