1 Commits

Author SHA1 Message Date
e1e0bb6928 Added docker support
- Added the multi-staged Dockerfile
- Added the docker-compose.yml file
  - It also includes a MongoDB service
- Added the entrypoint for the Docker image
2023-12-14 23:53:37 +01:00
4 changed files with 301 additions and 0 deletions

38
Dockerfile Normal file
View File

@ -0,0 +1,38 @@
FROM gradle:8.5.0-jdk17-alpine as builder
WORKDIR /app
COPY ./ /app/
RUN gradle jar --no-daemon
FROM bitnami/git:2.43.0-debian-11-r1 as data
ARG DATA_BRANCH=4.0
WORKDIR /app
RUN git clone --branch ${DATA_BRANCH} --depth 1 https://gitlab.com/YuukiPS/GC-Resources.git
FROM bitnami/java:21.0.1-12
RUN apt-get update && apt-get install unzip
WORKDIR /app
# Install bun for generating the configuration file
RUN curl -fsSL https://bun.sh/install | bash -s "bun-v1.0.0"
# Copy built assets
COPY --from=builder /app/grasscutter-1.7.4.jar /app/grasscutter.jar
COPY --from=builder /app/keystore.p12 /app/keystore.p12
# Copy the resources
COPY --from=data /app/GC-Resources/Resources /app/resources/
# Copy startup files
COPY ./entrypoint.sh ./generate-config.ts /app/
CMD [ "sh", "/app/entrypoint.sh" ]
EXPOSE 80 443 8888 22102

30
docker-compose.yml Normal file
View File

@ -0,0 +1,30 @@
version: "3.8"
services:
grasscutter:
image: grasscutter:latest
build: .
ports:
- "80:80"
- "443:443"
- "8080:8080"
- "8888:8888"
- "22102:22102"
environment:
DATABASE_INFO_SERVER_CONNECTION_URI: "mongodb://lawnmower:grasscutter@database:27017"
DATABASE_INFO_SERVER_COLLECTION: grasscutter
DATABASE_INFO_GAME_CONNECTION_URI: "mongodb://lawnmower:grasscutter@database:27017"
DATABASE_INFO_GAME_COLLECTION: grasscutter
stdin_open: true
database:
image: mongo:7.0.4
environment:
MONGO_INITDB_ROOT_USERNAME: lawnmower
MONGO_INITDB_ROOT_PASSWORD: grasscutter
MONGO_INITDB_DATABASE: grasscutter
volumes:
- mongodata:/data/db
volumes:
mongodata:

5
entrypoint.sh Executable file
View File

@ -0,0 +1,5 @@
#/bin/sh
$HOME/.bun/bin/bun run /app/generate-config.ts
java -jar /app/grasscutter.jar

228
generate-config.ts Normal file
View File

@ -0,0 +1,228 @@
import { writeFileSync } from "fs";
const configToSave = {
folderStructure: {
resources: getStringFromEnv("FOLDER_STRUCTURE_RESOURCES", "./resources/"),
data: getStringFromEnv("FOLDER_STRUCTURE_DATA", "./data/"),
packets: getStringFromEnv("FOLDER_STRUCTURE_PACKETS", "./packets/"),
scripts: getStringFromEnv("FOLDER_STRUCTURE_SCRIPTS", "./resources/Scripts/"),
plugins: getStringFromEnv("FOLDER_STRUCTURE_PLUGINS", "./plugins/"),
},
databaseInfo: {
server: {
connectionUri: getStringFromEnv("DATABASE_INFO_SERVER_CONNECTION_URI", "mongodb://localhost:27017"),
collection: getStringFromEnv("DATABASE_INFO_SERVER_COLLECTION", "grasscutter"),
},
game: {
connectionUri: getStringFromEnv("DATABASE_INFO_GAME_CONNECTION_URI", "mongodb://localhost:27017"),
collection: getStringFromEnv("DATABASE_INFO_GAME_COLLECTION", "grasscutter"),
},
},
language: {
language: getStringFromEnv("LANGUAGE_LANGUAGE", "en_US"),
fallback: getStringFromEnv("LANGUAGE_FALLBACK", "en_US"),
document: getStringFromEnv("LANGUAGE_DOCUMENT", "EN"),
},
account: {
autoCreate: getBoolFromEnv("ACCOUNT_AUTO_CREATE", false),
EXPERIMENTAL_RealPassword: getBoolFromEnv("ACCOUNT_EXPERIMENTAL_REAL_PASSWORD", false),
defaultPermissions: getStringArrayFromEnv("ACCOUNT_DEFAULT_PERMISSIONS", []),
maxPlayer: getIntFromEnv("ACCOUNT_MAX_PLAYER", -1),
},
server: {
debugWhitelist: getStringArrayFromEnv("SERVER_DEBUG_WHITELIST", []),
debugBlacklist: getStringArrayFromEnv("SERVER_DEBUG_BLACKLIST", []),
runMode: getStringFromEnv("SERVER_RUN_MODE", "HYBRID"),
logCommands: getBoolFromEnv("SERVER_LOG_COMMANDS", false),
http: {
bindAddress: getStringFromEnv("SERVER_HTTP_BIND_ADDRESS", "0.0.0.0"),
bindPort: getIntFromEnv("SERVER_HTTP_BIND_PORT", 443),
accessAddress: getStringFromEnv("SERVER_HTTP_ACCESS_ADDRESS", "127.0.0.1"),
accessPort: getIntFromEnv("SERVER_HTTP_ACCESS_PORT", 0),
encryption: {
useEncryption: getBoolFromEnv("SERVER_HTTP_ENCRYPTION_USE_ENCRYPTION", true),
useInRouting: getBoolFromEnv("SERVER_HTTP_ENCRYPTION_USE_IN_ROUTING", true),
keystore: getStringFromEnv("SERVER_HTTP_ENCRYPTION_KEYSTORE", "./keystore.p12"),
keystorePassword: getStringFromEnv("SERVER_HTTP_ENCRYPTION_KEYSTORE_PASSWORD", "123456"),
},
policies: {
cors: {
enabled: getBoolFromEnv("SERVER_HTTP_POLICIES_CORS_ENABLED", false),
allowedOrigins: getStringArrayFromEnv("SERVER_HTTP_POLICIES_CORS_ALLOWED_ORIGINS", ["*"]),
},
},
files: {
indexFile: getStringFromEnv("SERVER_HTTP_FILES_INDEX_FILE", "./index.html"),
errorFile: getStringFromEnv("SERVER_HTTP_FILES_ERROR_FILE", "./404.html"),
},
},
game: {
bindAddress: getStringFromEnv("SERVER_GAME_BIND_ADDRESS", "0.0.0.0"),
bindPort: getIntFromEnv("SERVER_GAME_BIND_PORT", 22102),
accessAddress: getStringFromEnv("SERVER_GAME_ACCESS_ADDRESS", "127.0.0.1"),
accessPort: getIntFromEnv("SERVER_GAME_ACCESS_PORT", 0),
loadEntitiesForPlayerRange: getIntFromEnv("SERVER_GAME_LOAD_ENTITIES_FOR_PLAYER_RANGE", 100),
enableScriptInBigWorld: getBoolFromEnv("SERVER_GAME_ENABLE_SCRIPT_IN_BIG_WORLD", false),
enableConsole: getBoolFromEnv("SERVER_GAME_ENABLE_CONSOLE", true),
kcpInterval: getIntFromEnv("SERVER_GAME_KCP_INTERVAL", 20),
logPackets: getStringFromEnv("SERVER_GAME_LOG_PACKETS", "NONE"),
gameOptions: {
inventoryLimits: {
weapons: getIntFromEnv("SERVER_GAME_GAME_OPTIONS_INVENTORY_LIMITS_WEAPONS", 2000),
relics: getIntFromEnv("SERVER_GAME_GAME_OPTIONS_INVENTORY_LIMITS_RELICS", 2000),
materials: getIntFromEnv("SERVER_GAME_GAME_OPTIONS_INVENTORY_LIMITS_MATERIALS", 2000),
furniture: getIntFromEnv("SERVER_GAME_GAME_OPTIONS_INVENTORY_LIMITS_FURNITURE", 2000),
all: getIntFromEnv("SERVER_GAME_GAME_OPTIONS_INVENTORY_LIMITS_ALL", 30000),
},
avatarLimits: {
singlePlayerTeam: getIntFromEnv("SERVER_GAME_GAME_OPTIONS_AVATAR_LIMITS_SINGLE_PLAYER_TEAM", 4),
multiplayerTeam: getIntFromEnv("SERVER_GAME_GAME_OPTIONS_AVATAR_LIMITS_MULTIPLAYER_TEAM", 4),
},
sceneEntityLimit: getIntFromEnv("SERVER_GAME_GAME_OPTIONS_SCENE_ENTITY_LIMIT", 1000),
watchGachaConfig: getBoolFromEnv("SERVER_GAME_GAME_OPTIONS_WATCH_GACHA_CONFIG", false),
enableShopItems: getBoolFromEnv("SERVER_GAME_GAME_OPTIONS_ENABLE_SHOP_ITEMS", true),
staminaUsage: getBoolFromEnv("SERVER_GAME_GAME_OPTIONS_STAMINA_USAGE", true),
energyUsage: getBoolFromEnv("SERVER_GAME_GAME_OPTIONS_ENERGY_USAGE", true),
fishhookTeleport: getBoolFromEnv("SERVER_GAME_GAME_OPTIONS_FISHHOOK_TELEPORT", true),
resinOptions: {
resinUsage: getBoolFromEnv("SERVER_GAME_GAME_OPTIONS_RESIN_OPTIONS_RESIN_USAGE", false),
cap: getIntFromEnv("SERVER_GAME_GAME_OPTIONS_RESIN_OPTIONS_CAP", 160),
rechargeTime: getIntFromEnv("SERVER_GAME_GAME_OPTIONS_RESIN_OPTIONS_RECHARGE_TIME", 480),
},
rates: {
adventureExp: getFloatFromEnv("SERVER_GAME_GAME_OPTIONS_RATES_ADVENTURE_EXP", 1.0),
mora: getFloatFromEnv("SERVER_GAME_GAME_OPTIONS_RATES_MORA", 1.0),
leyLines: getFloatFromEnv("SERVER_GAME_GAME_OPTIONS_RATES_LEY_LINES", 1.0),
},
},
joinOptions: {
welcomeEmotes: [2007, 1002, 4010],
welcomeMessage: getStringFromEnv(
"SERVER_GAME_JOIN_OPTIONS_WELCOME_MESSAGE",
"Welcome to a Grasscutter server."
),
welcomeMail: {
title: getStringFromEnv("SERVER_GAME_JOIN_OPTIONS_WELCOME_MAIL_TITLE", "Welcome to Grasscutter!"),
content: getStringFromEnv(
"SERVER_GAME_JOIN_OPTIONS_WELCOME_MAIL_CONTENT",
'Hi there!\r\nFirst of all, welcome to Grasscutter. If you have any issues, please let us know so that Lawnmower can help you! \r\n\r\nCheck out our:\r\n\u003ctype\u003d"browser" text\u003d"Discord" href\u003d"https://discord.gg/T5vZU6UyeG"/\u003e\n'
),
sender: getStringFromEnv("SERVER_GAME_JOIN_OPTIONS_WELCOME_MAIL_SENDER", "Lawnmower"),
items: getItemsFromEnv("SERVER_GAME_JOIN_OPTIONS_WELCOME_MAIL_ITEMS", [
{
itemId: 13509,
itemCount: 1,
itemLevel: 1,
},
{
itemId: 201,
itemCount: 99999,
itemLevel: 1,
},
]),
},
},
serverAccount: {
avatarId: getIntFromEnv("SERVER_GAME_SERVER_ACCOUNT_AVATAR_ID", 10000007),
nameCardId: getIntFromEnv("SERVER_GAME_SERVER_ACCOUNT_NAME_CARD_ID", 210001),
adventureRank: getIntFromEnv("SERVER_GAME_SERVER_ACCOUNT_ADVENTURE_RANK", 1),
worldLevel: getIntFromEnv("SERVER_GAME_SERVER_ACCOUNT_WORLD_LEVEL", 0),
nickName: getStringFromEnv("SERVER_GAME_SERVER_ACCOUNT_NICK_NAME", "Server"),
signature: getStringFromEnv("SERVER_GAME_SERVER_ACCOUNT_SIGNATURE", "Welcome to Grasscutter!"),
},
},
dispatch: {
regions: getStringArrayFromEnv("SERVER_DISPATCH_REGIONS", []),
defaultName: getStringFromEnv("SERVER_DISPATCH_DEFAULT_NAME", "Grasscutter"),
logRequests: getStringFromEnv("SERVER_DISPATCH_LOG_REQUESTS", "NONE"),
},
},
version: 4,
};
writeFileSync("./config.json", JSON.stringify(configToSave, null, 4));
function getStringFromEnv(key: string, defaultValue: string): string {
return process.env[key] || defaultValue;
}
function getBoolFromEnv(key: string, defaultValue: boolean): boolean {
switch (process.env[key]) {
case "true":
case "on":
case "1":
return true;
case "false":
case "off":
case "0":
return false;
default:
return defaultValue;
}
}
function getIntFromEnv(key: string, defaultValue: number): number {
const currentValue = process.env[key];
if (currentValue === undefined || currentValue === null) {
return defaultValue;
}
try {
return parseInt(currentValue, 10);
} catch (error) {
return defaultValue;
}
}
function getFloatFromEnv(key: string, defaultValue: number): number {
const currentValue = process.env[key];
if (currentValue === undefined || currentValue === null) {
return defaultValue;
}
try {
return parseFloat(currentValue);
} catch (error) {
return defaultValue;
}
}
function getStringArrayFromEnv(key: string, defaultValue: string[], separator: string = ","): string[] {
const currentValue = process.env[key];
if (currentValue === undefined || currentValue === null) {
return defaultValue;
}
return currentValue.split(separator);
}
type ItemInfo = {
itemId: number;
itemCount: number;
itemLevel: number;
};
function getItemsFromEnv(key: string, defaultValue: ItemInfo[]): ItemInfo[] {
const currentValue = process.env[key];
if (currentValue === undefined || currentValue === null) {
return defaultValue;
}
const parts = currentValue.split("|");
return parts.map((part: string) => {
const [rawItemId, rawItemCount, rawItemLevel] = part.split(",");
return {
itemId: parseInt(rawItemId, 10),
itemCount: parseInt(rawItemCount, 10),
itemLevel: parseInt(rawItemLevel, 10),
};
});
}