From 63e01f9aee1686c923a4543940c03802e4dc2c4d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 2 Oct 2021 08:21:04 -0700 Subject: [PATCH 1/9] Update SECURITY.md --- SECURITY.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 5b412f853..a00178658 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -5,14 +5,9 @@ The following versions of Pterodactyl are receiving active support and maintenan | Panel | Daemon | Supported | | ----- | ------------ | ------------------ | -| 1.4.x | wings@1.4.x | :white_check_mark: | -| 1.3.x | wings@1.3.x | :x: | -| 1.2.x | wings@1.2.x | :x: | -| 1.1.x | wings@1.1.x | :x: | -| 1.0.x | wings@1.0.x | :x: | +| 1.6.x | wings@1.5.x | :white_check_mark: | | 0.7.x | daemon@0.6.x | :x: | -| 0.6.x | daemon@0.5.x | :x: | -| 0.5.x | daemon@0.4.x | :x: | + ## Reporting a Vulnerability From 81ba333270b7ed9594f72c1917d6675a9f9c590d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 3 Oct 2021 12:59:44 -0700 Subject: [PATCH 2/9] If uptime is present in stats output, display it for the server; closes #3653 --- .../components/server/ServerDetailsBlock.tsx | 14 +++++++++++--- .../scripts/components/server/UptimeDuration.tsx | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 resources/scripts/components/server/UptimeDuration.tsx diff --git a/resources/scripts/components/server/ServerDetailsBlock.tsx b/resources/scripts/components/server/ServerDetailsBlock.tsx index 6df4a764c..a87dc3b01 100644 --- a/resources/scripts/components/server/ServerDetailsBlock.tsx +++ b/resources/scripts/components/server/ServerDetailsBlock.tsx @@ -7,14 +7,16 @@ import TitledGreyBox from '@/components/elements/TitledGreyBox'; import { ServerContext } from '@/state/server'; import CopyOnClick from '@/components/elements/CopyOnClick'; import { SocketEvent, SocketRequest } from '@/components/server/events'; +import UptimeDuration from '@/components/server/UptimeDuration'; interface Stats { memory: number; cpu: number; disk: number; + uptime: number; } -function statusToColor (status: string|null, installing: boolean): TwStyle { +function statusToColor (status: string | null, installing: boolean): TwStyle { if (installing) { status = ''; } @@ -30,7 +32,7 @@ function statusToColor (status: string|null, installing: boolean): TwStyle { } const ServerDetailsBlock = () => { - const [ stats, setStats ] = useState({ memory: 0, cpu: 0, disk: 0 }); + const [ stats, setStats ] = useState({ memory: 0, cpu: 0, disk: 0, uptime: 0 }); const status = ServerContext.useStoreState(state => state.status.value); const connected = ServerContext.useStoreState(state => state.socket.connected); @@ -48,6 +50,7 @@ const ServerDetailsBlock = () => { memory: stats.memory_bytes, cpu: stats.cpu_absolute, disk: stats.disk_bytes, + uptime: stats.uptime || 0, }); }; @@ -69,7 +72,7 @@ const ServerDetailsBlock = () => { const isTransferring = ServerContext.useStoreState(state => state.server.data!.isTransferring); const limits = ServerContext.useStoreState(state => state.server.data!.limits); const primaryAllocation = ServerContext.useStoreState(state => state.server.data!.allocations.filter(alloc => alloc.isDefault).map( - allocation => (allocation.alias || allocation.ip) + ':' + allocation.port + allocation => (allocation.alias || allocation.ip) + ':' + allocation.port, )).toString(); const diskLimit = limits.disk ? megabytesToHuman(limits.disk) : 'Unlimited'; @@ -88,6 +91,11 @@ const ServerDetailsBlock = () => { ]} />  {!status ? 'Connecting...' : (isInstalling ? 'Installing' : (isTransferring) ? 'Transferring' : status)} + {stats.uptime > 0 && + + () + + }

diff --git a/resources/scripts/components/server/UptimeDuration.tsx b/resources/scripts/components/server/UptimeDuration.tsx new file mode 100644 index 000000000..1406450fa --- /dev/null +++ b/resources/scripts/components/server/UptimeDuration.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +export default ({ uptime }: { uptime: number }) => { + const hours = Math.floor(Math.floor(uptime) / 60 / 60); + const remainder = Math.floor(uptime - (hours * 60 * 60)); + const minutes = Math.floor(remainder / 60); + const seconds = remainder % 60; + + return ( + <> + {hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')} + + ); +}; From de0d5c9b8afd88cff9bb345a376f818853b5090f Mon Sep 17 00:00:00 2001 From: Cyra Date: Mon, 4 Oct 2021 08:23:10 -0700 Subject: [PATCH 3/9] Updated CHS sponsor entry to use new domain (#3659) Updated CHS sponsor entry to use new domain Updated from captiolsolutions.cloud to chs.gg --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 42e02d5c0..6d0eec5ad 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ I would like to extend my sincere thanks to the following sponsors for helping f | [**Spill Hosting**](https://spillhosting.no/) | Spill Hosting is a Norwegian hosting service, which aims for inexpensive services on quality servers. Premium i9-9900K processors will run your game like a dream. | | [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. | | [**HostBend**](https://hostbend.com/) | HostBend offers a variety of solutions for developers, students, and others who have a tight budget but don't want to compromise quality and support. | -| [**Capitol Hosting Solutions**](https://capitolsolutions.cloud/) | CHS is *the* budget friendly hosting company for Australian and American gamers, offering a variety of plans from Web Hosting to Game Servers; Custom Solutions too! | +| [**Capitol Hosting Solutions**](https://chs.gg/) | CHS is *the* budget friendly hosting company for Australian and American gamers, offering a variety of plans from Web Hosting to Game Servers; Custom Solutions too! | | [**ByteAnia**](https://byteania.com/?utm_source=pterodactyl) | ByteAnia offers the best performing and most affordable **Ryzen 5000 Series hosting** on the market for *unbeatable prices*! | | [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. | | [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa.| From 4fa38b8e9cb5f35f3bbd3a0dd7d96bf2f8a8b39e Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Thu, 7 Oct 2021 09:46:09 -0600 Subject: [PATCH 4/9] Fix wings receiving wrong suspended status on sync (#3667) Due to wings pulling the server configuration rather than the Panel pushing it, wings gets the wrong status for a server if both the status update and sync request are ran in a transaction due to the status not being persisted in the database. Fixes #3639 --- app/Services/Servers/SuspensionService.php | 23 +++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/app/Services/Servers/SuspensionService.php b/app/Services/Servers/SuspensionService.php index f7d0f77b1..27bc622f5 100644 --- a/app/Services/Servers/SuspensionService.php +++ b/app/Services/Servers/SuspensionService.php @@ -58,15 +58,20 @@ class SuspensionService throw new ConflictHttpException('Cannot toggle suspension status on a server that is currently being transferred.'); } - $this->connection->transaction(function () use ($action, $server, $isSuspending) { - $server->update([ - 'status' => $isSuspending ? Server::STATUS_SUSPENDED : null, - ]); + // Update the server's suspension status. + $server->update([ + 'status' => $isSuspending ? Server::STATUS_SUSPENDED : null, + ]); - // Only trigger a Wings server sync if it is not currently being transferred. - if (is_null($server->transfer)) { - $this->daemonServerRepository->setServer($server)->sync(); - } - }); + try { + // Tell wings to re-sync the server state. + $this->daemonServerRepository->setServer($server)->sync(); + } catch (\Exception $exception) { + // Rollback the server's suspension status if wings fails to sync the server. + $server->update([ + 'status' => $isSuspending ? null : Server::STATUS_SUSPENDED, + ]); + throw $exception; + } } } From 8b236c6907f84a46b885f6d71e6969ce9c97e19c Mon Sep 17 00:00:00 2001 From: Waseem Hassan Shahid Date: Sat, 9 Oct 2021 19:31:29 +0200 Subject: [PATCH 5/9] Fix SSL config docker (#3616) * Don't copy default nginx config at build time * Use http.d folder for nginx configs * Add default config back * Change the panel config name --- .github/docker/entrypoint.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/docker/entrypoint.sh b/.github/docker/entrypoint.sh index e6e1b0966..e2df6439c 100644 --- a/.github/docker/entrypoint.sh +++ b/.github/docker/entrypoint.sh @@ -30,7 +30,7 @@ else fi echo "Checking if https is required." -if [ -f /etc/nginx/conf.d/default.conf ]; then +if [ -f /etc/nginx/http.d/panel.conf ]; then echo "Using nginx config already in place." if [ $LE_EMAIL ]; then echo "Checking for cert update" @@ -42,15 +42,17 @@ else echo "Checking if letsencrypt email is set." if [ -z $LE_EMAIL ]; then echo "No letsencrypt email is set using http config." - cp .github/docker/default.conf /etc/nginx/conf.d/default.conf + cp .github/docker/default.conf /etc/nginx/http.d/panel.conf else echo "writing ssl config" - cp .github/docker/default_ssl.conf /etc/nginx/conf.d/default.conf + cp .github/docker/default_ssl.conf /etc/nginx/http.d/panel.conf echo "updating ssl config for domain" - sed -i "s||$(echo $APP_URL | sed 's~http[s]*://~~g')|g" /etc/nginx/conf.d/default.conf + sed -i "s||$(echo $APP_URL | sed 's~http[s]*://~~g')|g" /etc/nginx/http.d/panel.conf echo "generating certs" certbot certonly -d $(echo $APP_URL | sed 's~http[s]*://~~g') --standalone -m $LE_EMAIL --agree-tos -n fi + echo "Removing the default nginx config" + rm -rf /etc/nginx/http.d/default.conf fi ## check for DB up before starting the panel From 5b6de4df6f76d971944ad3c5b950ddda3007c776 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 9 Oct 2021 20:31:47 +0300 Subject: [PATCH 6/9] eggs(rust): custom map url (#3625) Introduces custom map URL variable. If none is provided, it will default to using normal map size and seed. Otherwise, it will use the custom map and remove map size/seed from the startup as required. --- database/Seeders/eggs/rust/egg-rust.json | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/database/Seeders/eggs/rust/egg-rust.json b/database/Seeders/eggs/rust/egg-rust.json index 70eaadbad..60144bfa7 100644 --- a/database/Seeders/eggs/rust/egg-rust.json +++ b/database/Seeders/eggs/rust/egg-rust.json @@ -4,7 +4,7 @@ "version": "PTDL_v1", "update_url": null }, - "exported_at": "2021-05-29T19:02:43-04:00", + "exported_at": "2021-09-15T17:07:50-04:00", "name": "Rust", "author": "support@pterodactyl.io", "description": "The only aim in Rust is to survive. To do this you will need to overcome struggles such as hunger, thirst and cold. Build a fire. Build a shelter. Kill animals for meat. Protect yourself from other players, and kill them for meat. Create alliances with other players and form a town. Do whatever it takes to survive.", @@ -13,11 +13,11 @@ "quay.io\/pterodactyl\/core:rust" ], "file_denylist": [], - "startup": ".\/RustDedicated -batchmode +server.port {{SERVER_PORT}} +server.identity \"rust\" +rcon.port {{RCON_PORT}} +rcon.web true +server.hostname \\\"{{HOSTNAME}}\\\" +server.level \\\"{{LEVEL}}\\\" +server.description \\\"{{DESCRIPTION}}\\\" +server.url \\\"{{SERVER_URL}}\\\" +server.headerimage \\\"{{SERVER_IMG}}\\\" +server.logoimage \\\"{{SERVER_LOGO}}\\\" +server.worldsize \\\"{{WORLD_SIZE}}\\\" +server.seed \\\"{{WORLD_SEED}}\\\" +server.maxplayers {{MAX_PLAYERS}} +rcon.password \\\"{{RCON_PASS}}\\\" +server.saveinterval {{SAVEINTERVAL}} +app.port {{APP_PORT}} {{ADDITIONAL_ARGS}}", + "startup": ".\/RustDedicated -batchmode +server.port {{SERVER_PORT}} +server.identity \"rust\" +rcon.port {{RCON_PORT}} +rcon.web true +server.hostname \\\"{{HOSTNAME}}\\\" +server.level \\\"{{LEVEL}}\\\" +server.description \\\"{{DESCRIPTION}}\\\" +server.url \\\"{{SERVER_URL}}\\\" +server.headerimage \\\"{{SERVER_IMG}}\\\" +server.logoimage \\\"{{SERVER_LOGO}}\\\" +server.maxplayers {{MAX_PLAYERS}} +rcon.password \\\"{{RCON_PASS}}\\\" +server.saveinterval {{SAVEINTERVAL}} +app.port {{APP_PORT}} $( [ -z ${MAP_URL} ] && printf %s \"+server.worldsize \\\"{{WORLD_SIZE}}\\\" +server.seed \\\"{{WORLD_SEED}}\\\"\" || printf %s \"+server.levelurl {{MAP_URL}}\" ) {{ADDITIONAL_ARGS}}", "config": { "files": "{}", - "startup": "{\r\n \"done\": \"Server startup complete\",\r\n \"userInteraction\": []\r\n}", - "logs": "{\r\n \"custom\": false,\r\n \"location\": \"latest.log\"\r\n}", + "startup": "{\r\n \"done\": \"Server startup complete\"\r\n}", + "logs": "{}", "stop": "quit" }, "scripts": { @@ -162,6 +162,15 @@ "user_viewable": true, "user_editable": true, "rules": "nullable|url" + }, + { + "name": "Custom Map URL", + "description": "Overwrites the map with the one from the direct download URL. Invalid URLs will cause the server to crash.", + "env_variable": "MAP_URL", + "default_value": "", + "user_viewable": true, + "user_editable": true, + "rules": "nullable|url" } ] -} +} \ No newline at end of file From c12f1463b084f27fcc4074cc1639a4733fd5fffe Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 10 Oct 2021 20:50:01 +0300 Subject: [PATCH 7/9] eggs(forge): Add support for 1.17+ Forge (#3676) Support new 1.17+ Forge JPMS arguments that don't ship any executable jar. It will use unix_args.txt file for 1.17+ when one exists, otherwise defaults to using the jar file Fix forge latest build version option to actually use latest instead of recommended Set build version input rules to only accept valid values of the latest and recommended Remove spaces from the version variables to avoid issues with curl. Forge site displays versions with spaces to end users --- .../Seeders/eggs/minecraft/egg-forge-minecraft.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/database/Seeders/eggs/minecraft/egg-forge-minecraft.json b/database/Seeders/eggs/minecraft/egg-forge-minecraft.json index fa750a449..6ac6eaaf7 100644 --- a/database/Seeders/eggs/minecraft/egg-forge-minecraft.json +++ b/database/Seeders/eggs/minecraft/egg-forge-minecraft.json @@ -4,7 +4,7 @@ "version": "PTDL_v1", "update_url": null }, - "exported_at": "2021-07-04T19:18:55-04:00", + "exported_at": "2021-10-10T07:10:13-04:00", "name": "Forge Minecraft", "author": "support@pterodactyl.io", "description": "Minecraft Forge Server. Minecraft Forge is a modding API (Application Programming Interface), which makes it easier to create mods, and also make sure mods are compatible with each other.", @@ -18,16 +18,16 @@ "ghcr.io\/pterodactyl\/yolks:java_16" ], "file_denylist": [], - "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}", + "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -Dterminal.jline=false -Dterminal.ansi=true $( [ ! -f unix_args.txt ] && printf %s \"-jar {{SERVER_JARFILE}}\" || printf %s \"@unix_args.txt\" )", "config": { "files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"enable-query\": \"true\",\r\n \"server-port\": \"{{server.build.default.port}}\",\r\n \"query.port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}", "startup": "{\r\n \"done\": \")! For help, type \"\r\n}", - "logs": "{\r\n \"custom\": false,\r\n \"location\": \"logs\/latest.log\"\r\n}", + "logs": "{}", "stop": "stop" }, "scripts": { "installation": { - "script": "#!\/bin\/bash\r\n# Forge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napt update\r\napt install -y curl jq\r\n\r\n#Go into main direction\r\nif [ ! -d \/mnt\/server ]; then\r\n mkdir \/mnt\/server\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\nif [ ! -z ${FORGE_VERSION} ]; then\r\n DOWNLOAD_LINK=https:\/\/maven.minecraftforge.net\/net\/minecraftforge\/forge\/${FORGE_VERSION}\/forge-${FORGE_VERSION}\r\n FORGE_JAR=forge-${FORGE_VERSION}*.jar\r\nelse\r\n JSON_DATA=$(curl -sSL https:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/promotions_slim.json)\r\n\r\n if [ \"${MC_VERSION}\" == \"latest\" ] || [ \"${MC_VERSION}\" == \"\" ] ; then\r\n echo -e \"getting latest recommended version of forge.\"\r\n MC_VERSION=$(echo -e ${JSON_DATA} | jq -r '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains(\"recommended\")) | split(\"-\")[0]' | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n | tail -1)\r\n \tBUILD_TYPE=recommended\r\n fi\r\n\r\n if [ \"${BUILD_TYPE}\" != \"recommended\" ] && [ \"${BUILD_TYPE}\" != \"latest\" ]; then\r\n BUILD_TYPE=recommended\r\n fi\r\n\r\n echo -e \"minecraft version: ${MC_VERSION}\"\r\n echo -e \"build type: ${BUILD_TYPE}\"\r\n\r\n ## some variables for getting versions and things\r\n\tFILE_SITE=https:\/\/maven.minecraftforge.net\/net\/minecraftforge\/forge\/\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" --arg BUILD_TYPE \"${BUILD_TYPE}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains($BUILD_TYPE))')\r\n\r\n ## locating the forge version\r\n if [ \"${VERSION_KEY}\" == \"\" ] && [ \"${BUILD_TYPE}\" == \"recommended\" ]; then\r\n echo -e \"dropping back to latest from recommended due to there not being a recommended version of forge for the mc version requested.\"\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains(\"recommended\"))')\r\n fi\r\n\r\n ## Error if the mc version set wasn't valid.\r\n if [ \"${VERSION_KEY}\" == \"\" ] || [ \"${VERSION_KEY}\" == \"null\" ]; then\r\n \techo -e \"The install failed because there is no valid version of forge for the version on minecraft selected.\"\r\n \texit 1\r\n fi\r\n\r\n FORGE_VERSION=$(echo -e ${JSON_DATA} | jq -r --arg VERSION_KEY \"$VERSION_KEY\" '.promos | .[$VERSION_KEY]')\r\n\r\n if [ \"${MC_VERSION}\" == \"1.7.10\" ] || [ \"${MC_VERSION}\" == \"1.8.9\" ]; then\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}.jar\r\n if [ \"${MC_VERSION}\" == \"1.7.10\" ]; then\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}-universal.jar\r\n fi\r\n else\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}.jar\r\n fi\r\nfi\r\n\r\n\r\n#Adding .jar when not eding by SERVER_JARFILE\r\nif [[ ! $SERVER_JARFILE = *\\.jar ]]; then\r\n SERVER_JARFILE=\"$SERVER_JARFILE.jar\"\r\nfi\r\n\r\n#Downloading jars\r\necho -e \"Downloading forge version ${FORGE_VERSION}\"\r\necho -e \"Download link is ${DOWNLOAD_LINK}\"\r\nif [ ! -z \"${DOWNLOAD_LINK}\" ]; then \r\n if curl --output \/dev\/null --silent --head --fail ${DOWNLOAD_LINK}-installer.jar; then\r\n echo -e \"installer jar download link is valid.\"\r\n else\r\n echo -e \"link is invalid closing out\"\r\n exit 2\r\n fi\r\nelse\r\n echo -e \"no download link closing out\"\r\n exit 3\r\nfi\r\n\r\ncurl -s -o installer.jar -sS ${DOWNLOAD_LINK}-installer.jar\r\n\r\n#Checking if downloaded jars exist\r\nif [ ! -f .\/installer.jar ]; then\r\n echo \"!!! Error by downloading forge version ${FORGE_VERSION} !!!\"\r\n exit\r\nfi\r\n\r\n#Installing server\r\necho -e \"Installing forge server.\\n\"\r\njava -jar installer.jar --installServer || { echo -e \"install failed\"; exit 4; }\r\n\r\nmv $FORGE_JAR $SERVER_JARFILE\r\n\r\n#Deleting installer.jar\r\necho -e \"Deleting installer.jar file.\\n\"\r\nrm -rf installer.jar", + "script": "#!\/bin\/bash\r\n# Forge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napt update\r\napt install -y curl jq\r\n\r\nif [ ! -d \/mnt\/server ]; then\r\n mkdir \/mnt\/server\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\n# Remove spaces from the version number to avoid issues with curl\r\nFORGE_VERSION=\"$(echo \"$FORGE_VERSION\" | tr -d ' ')\"\r\nMC_VERSION=\"$(echo \"$MC_VERSION\" | tr -d ' ')\"\r\n\r\nif [ ! -z ${FORGE_VERSION} ]; then\r\n DOWNLOAD_LINK=https:\/\/maven.minecraftforge.net\/net\/minecraftforge\/forge\/${FORGE_VERSION}\/forge-${FORGE_VERSION}\r\n FORGE_JAR=forge-${FORGE_VERSION}*.jar\r\nelse\r\n JSON_DATA=$(curl -sSL https:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/promotions_slim.json)\r\n\r\n if [ \"${MC_VERSION}\" == \"latest\" ] || [ \"${MC_VERSION}\" == \"\" ] ; then\r\n echo -e \"getting latest version of forge.\"\r\n MC_VERSION=$(echo -e ${JSON_DATA} | jq -r '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains(\"recommended\")) | split(\"-\")[0]' | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n | tail -1)\r\n \tBUILD_TYPE=latest\r\n fi\r\n\r\n if [ \"${BUILD_TYPE}\" != \"recommended\" ] && [ \"${BUILD_TYPE}\" != \"latest\" ]; then\r\n BUILD_TYPE=recommended\r\n fi\r\n\r\n echo -e \"minecraft version: ${MC_VERSION}\"\r\n echo -e \"build type: ${BUILD_TYPE}\"\r\n\r\n ## some variables for getting versions and things\r\n\tFILE_SITE=https:\/\/maven.minecraftforge.net\/net\/minecraftforge\/forge\/\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" --arg BUILD_TYPE \"${BUILD_TYPE}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains($BUILD_TYPE))')\r\n\r\n ## locating the forge version\r\n if [ \"${VERSION_KEY}\" == \"\" ] && [ \"${BUILD_TYPE}\" == \"recommended\" ]; then\r\n echo -e \"dropping back to latest from recommended due to there not being a recommended version of forge for the mc version requested.\"\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains(\"recommended\"))')\r\n fi\r\n\r\n ## Error if the mc version set wasn't valid.\r\n if [ \"${VERSION_KEY}\" == \"\" ] || [ \"${VERSION_KEY}\" == \"null\" ]; then\r\n \techo -e \"The install failed because there is no valid version of forge for the version of minecraft selected.\"\r\n \texit 1\r\n fi\r\n\r\n FORGE_VERSION=$(echo -e ${JSON_DATA} | jq -r --arg VERSION_KEY \"$VERSION_KEY\" '.promos | .[$VERSION_KEY]')\r\n\r\n if [ \"${MC_VERSION}\" == \"1.7.10\" ] || [ \"${MC_VERSION}\" == \"1.8.9\" ]; then\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}.jar\r\n if [ \"${MC_VERSION}\" == \"1.7.10\" ]; then\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}-universal.jar\r\n fi\r\n else\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}.jar\r\n fi\r\nfi\r\n\r\n\r\n#Adding .jar when not eding by SERVER_JARFILE\r\nif [[ ! $SERVER_JARFILE = *\\.jar ]]; then\r\n SERVER_JARFILE=\"$SERVER_JARFILE.jar\"\r\nfi\r\n\r\n#Downloading jars\r\necho -e \"Downloading forge version ${FORGE_VERSION}\"\r\necho -e \"Download link is ${DOWNLOAD_LINK}\"\r\n\r\nif [ ! -z \"${DOWNLOAD_LINK}\" ]; then\r\n if curl --output \/dev\/null --silent --head --fail ${DOWNLOAD_LINK}-installer.jar; then\r\n echo -e \"installer jar download link is valid.\"\r\n else\r\n echo -e \"link is invalid. Exiting now\"\r\n exit 2\r\n fi\r\nelse\r\n echo -e \"no download link provided. Exiting now\"\r\n exit 3\r\nfi\r\n\r\ncurl -s -o installer.jar -sS ${DOWNLOAD_LINK}-installer.jar\r\n\r\n\r\n#Checking if downloaded jars exist\r\nif [ ! -f .\/installer.jar ]; then\r\n echo \"!!! Error by downloading forge version ${FORGE_VERSION} !!!\"\r\n exit\r\nfi\r\n\r\n# Delete args file to support downgrading to versions below 1.17\r\nif [ -f unix_args.txt ]; then\r\n rm unix_args.txt\r\n exit\r\nfi\r\n\r\n\r\n#Installing server\r\necho -e \"Installing forge server.\\n\"\r\njava -jar installer.jar --installServer || { echo -e \"install failed\"; exit 4; }\r\n\r\nif [[ $MC_VERSION == *\"1.17.\"* || $MC_VERSION == *\"1.18.\"* || $FORGE_VERSION == *\"1.17.\"* || $FORGE_VERSION == *\"1.18.\"* ]]; then\r\n # Create a symlink for 1.17+ Forge args\r\n ln -sf libraries\/net\/minecraftforge\/forge\/*\/unix_args.txt unix_args.txt\r\n else\r\n # For versions below 1.17 that ship with jar delete installer.jar\r\n mv $FORGE_JAR $SERVER_JARFILE\r\n echo -e \"Deleting installer.jar file.\\n\"\r\n rm -rf installer.jar\r\nfi", "container": "openjdk:8-jdk-slim", "entrypoint": "bash" } @@ -35,7 +35,7 @@ "variables": [ { "name": "Server Jar File", - "description": "The name of the Jarfile to use when running Forge Mod.", + "description": "The name of the Jarfile to use when running Forge version below 1.17.", "env_variable": "SERVER_JARFILE", "default_value": "server.jar", "user_viewable": true, @@ -58,7 +58,7 @@ "default_value": "recommended", "user_viewable": true, "user_editable": true, - "rules": "required|string|max:20" + "rules": "required|string|in:recommended,latest" }, { "name": "Forge Version", From f77932a6174ba5d9df7ede454bf05646cdb6a491 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 10 Oct 2021 21:08:22 +0300 Subject: [PATCH 8/9] cmd(upgrade): Attempt to gain users attention during upgrade (#3678) * cmd(upgrade): Attempt to gain users attention during upgrade Changes color of the user and group to gain attention, common issue is having wrong user/group which breaks the panel. Outputs termination message when users spam enter skipping the upgrade wondering why it didn't upgrade. Reminder to update wings, because users forget it. * cmd(upgrade): Display wings upgrade documentation link --- app/Console/Commands/UpgradeCommand.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/Console/Commands/UpgradeCommand.php b/app/Console/Commands/UpgradeCommand.php index feb21ec60..224329869 100644 --- a/app/Console/Commands/UpgradeCommand.php +++ b/app/Console/Commands/UpgradeCommand.php @@ -57,7 +57,7 @@ class UpgradeCommand extends Command $userDetails = posix_getpwuid(fileowner('public')); $user = $userDetails['name'] ?? 'www-data'; - if (!$this->confirm("Your webserver user has been detected as [{$user}]: is this correct?", true)) { + if (!$this->confirm("Your webserver user has been detected as [{$user}]: is this correct?", true)) { $user = $this->anticipate( 'Please enter the name of the user running your webserver process. This varies from system to system, but is generally "www-data", "nginx", or "apache".', [ @@ -73,7 +73,7 @@ class UpgradeCommand extends Command $groupDetails = posix_getgrgid(filegroup('public')); $group = $groupDetails['name'] ?? 'www-data'; - if (!$this->confirm("Your webserver group has been detected as [{$group}]: is this correct?", true)) { + if (!$this->confirm("Your webserver group has been detected as [{$group}]: is this correct?", true)) { $group = $this->anticipate( 'Please enter the name of the group running your webserver process. Normally this is the same as your user.', [ @@ -86,6 +86,7 @@ class UpgradeCommand extends Command } if (!$this->confirm('Are you sure you want to run the upgrade process for your Panel?')) { + $this->warn('Upgrade process terminated by user.'); return; } } @@ -173,8 +174,8 @@ class UpgradeCommand extends Command $this->call('up'); }); - $this->newLine(); - $this->info('Finished running upgrade.'); + $this->newLine(2); + $this->info('Panel has been successfully upgraded. Please ensure you also update any Wings instances: https://pterodactyl.io/wings/1.0/upgrading.html'); } protected function withProgress(ProgressBar $bar, Closure $callback) From 22a8b2b3a2e3277155440b8e73a070b21ea402f7 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 23 Oct 2021 12:17:16 -0700 Subject: [PATCH 9/9] Use more standardized rate limiting in Laravel; apply limits to auth routes --- app/Http/Kernel.php | 1 - app/Providers/RouteServiceProvider.php | 106 ++++++++++++++++++------- routes/auth.php | 20 +++-- 3 files changed, 90 insertions(+), 37 deletions(-) diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index d2e0f2cc7..30fcd8ce4 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -112,7 +112,6 @@ class Kernel extends HttpKernel 'bindings' => SubstituteBindings::class, 'recaptcha' => VerifyReCaptcha::class, 'node.maintenance' => MaintenanceMiddleware::class, - // API Specific Middleware 'api..key' => AuthenticateKey::class, ]; diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 0ea33b5da..2dedacb4a 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -2,7 +2,10 @@ namespace Pterodactyl\Providers; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; +use Illuminate\Cache\RateLimiting\Limit; +use Illuminate\Support\Facades\RateLimiter; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; class RouteServiceProvider extends ServiceProvider @@ -17,43 +20,86 @@ class RouteServiceProvider extends ServiceProvider protected $namespace = 'Pterodactyl\Http\Controllers'; /** - * Define the routes for the application. + * Define your route model bindings, pattern filters, etc. */ - public function map() + public function boot() { - Route::middleware(['web', 'auth', 'csrf']) - ->namespace($this->namespace . '\Base') - ->group(base_path('routes/base.php')); + $this->configureRateLimiting(); - Route::middleware(['web', 'auth', 'admin', 'csrf'])->prefix('/admin') - ->namespace($this->namespace . '\Admin') - ->group(base_path('routes/admin.php')); + $this->routes(function () { + Route::middleware(['web', 'auth', 'csrf']) + ->namespace("$this->namespace\\Base") + ->group(base_path('routes/base.php')); - Route::middleware(['web', 'csrf'])->prefix('/auth') - ->namespace($this->namespace . '\Auth') - ->group(base_path('routes/auth.php')); + Route::middleware(['web', 'auth', 'admin', 'csrf'])->prefix('/admin') + ->namespace("$this->namespace\\Admin") + ->group(base_path('routes/admin.php')); - Route::middleware(['web', 'csrf', 'auth', 'server', 'node.maintenance']) - ->prefix('/api/server/{server}') - ->namespace($this->namespace . '\Server') - ->group(base_path('routes/server.php')); + Route::middleware(['web', 'csrf'])->prefix('/auth') + ->namespace("$this->namespace\\Auth") + ->group(base_path('routes/auth.php')); - Route::middleware([ - sprintf('throttle:%s,%s', config('http.rate_limit.application'), config('http.rate_limit.application_period')), - 'api', - ])->prefix('/api/application') - ->namespace($this->namespace . '\Api\Application') - ->group(base_path('routes/api-application.php')); + Route::middleware(['web', 'csrf', 'auth', 'server', 'node.maintenance']) + ->prefix('/api/server/{server}') + ->namespace("$this->namespace\\Server") + ->group(base_path('routes/server.php')); - Route::middleware([ - sprintf('throttle:%s,%s', config('http.rate_limit.client'), config('http.rate_limit.client_period')), - 'client-api', - ])->prefix('/api/client') - ->namespace($this->namespace . '\Api\Client') - ->group(base_path('routes/api-client.php')); + Route::middleware(['api', 'throttle:api.application']) + ->prefix('/api/application') + ->namespace("$this->namespace\\Api\\Application") + ->group(base_path('routes/api-application.php')); - Route::middleware(['daemon'])->prefix('/api/remote') - ->namespace($this->namespace . '\Api\Remote') - ->group(base_path('routes/api-remote.php')); + Route::middleware(['client-api', 'throttle:api.client']) + ->prefix('/api/client') + ->namespace("$this->namespace\\Api\\Client") + ->group(base_path('routes/api-client.php')); + + Route::middleware(['daemon'])->prefix('/api/remote') + ->namespace("$this->namespace\\Api\\Remote") + ->group(base_path('routes/api-remote.php')); + }); + } + + /** + * Configure the rate limiters for the application. + */ + protected function configureRateLimiting() + { + // Authentication rate limiting. For login and checkpoint endpoints we'll apply + // a limit of 10 requests per minute, for the forgot password endpoint apply a + // limit of two per minute for the requester so that there is less ability to + // trigger email spam. + RateLimiter::for('authentication', function (Request $request) { + if ($request->route()->named('auth.post.forgot-password')) { + return Limit::perMinute(2)->by($request->ip()); + } + + return Limit::perMinute(10); + }); + + // Configure the throttles for both the application and client APIs below. + // This is configurable per-instance in "config/http.php". By default this + // limiter will be tied to the specific request user, and falls back to the + // request IP if there is no request user present for the key. + // + // This means that an authenticated API user cannot use IP switching to get + // around the limits. + RateLimiter::for('api.client', function (Request $request) { + $key = optional($request->user())->uuid ?: $request->ip(); + + return Limit::perMinutes( + config('http.rate_limit.client_period'), + config('http.rate_limit.client') + )->by($key); + }); + + RateLimiter::for('api.application', function (Request $request) { + $key = optional($request->user())->uuid ?: $request->ip(); + + return Limit::perMinutes( + config('http.rate_limit.application_period'), + config('http.rate_limit.application') + )->by($key); + }); } } diff --git a/routes/auth.php b/routes/auth.php index 4bdb72206..2e9a01eaf 100644 --- a/routes/auth.php +++ b/routes/auth.php @@ -15,13 +15,21 @@ Route::group(['middleware' => 'guest'], function () { Route::get('/password', 'LoginController@index')->name('auth.forgot-password'); Route::get('/password/reset/{token}', 'LoginController@index')->name('auth.reset'); - // Login endpoints. - Route::post('/login', 'LoginController@login')->middleware('recaptcha'); - Route::post('/login/checkpoint', 'LoginCheckpointController')->name('auth.login-checkpoint'); + // Apply a throttle to authentication action endpoints, in addition to the + // recaptcha endpoints to slow down manual attack spammers even more. 🤷‍ + // + // @see \Pterodactyl\Providers\RouteServiceProvider + Route::middleware(['throttle:authentication'])->group(function () { + // Login endpoints. + Route::post('/login', 'LoginController@login')->middleware('recaptcha'); + Route::post('/login/checkpoint', 'LoginCheckpointController')->name('auth.login-checkpoint'); - // Forgot password route. A post to this endpoint will trigger an - // email to be sent containing a reset token. - Route::post('/password', 'ForgotPasswordController@sendResetLinkEmail')->middleware('recaptcha'); + // Forgot password route. A post to this endpoint will trigger an + // email to be sent containing a reset token. + Route::post('/password', 'ForgotPasswordController@sendResetLinkEmail') + ->name('auth.post.forgot-password') + ->middleware('recaptcha'); + }); // Password reset routes. This endpoint is hit after going through // the forgot password routes to acquire a token (or after an account