├── .github └── workflows │ ├── build.yml │ └── run.yml ├── .gitignore ├── LICENSE.md ├── Makefile ├── README.md ├── data ├── egg-rustraw.json └── raw.service ├── go.mod ├── go.sum ├── internal ├── autoaddservers │ └── pterodactyl.go ├── config │ ├── def.go │ ├── load.go │ └── write_default.go └── wipe │ ├── data.go │ ├── data_env.go │ ├── files.go │ ├── hostname.go │ ├── server.go │ ├── warnings.go │ └── worldinfo.go ├── main.go └── pkg ├── chttp └── http.go ├── debug └── print.go ├── format └── data.go ├── misc └── helpers.go └── pterodactyl ├── api.go ├── def.go └── error.go /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | workflow_call: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v3 18 | with: 19 | go-version: 1.18 20 | 21 | - name: Build Go program 22 | run: go build -o raw 23 | 24 | - name: Store build artifacts 25 | uses: actions/upload-artifact@master 26 | with: 27 | name: build-output 28 | path: raw -------------------------------------------------------------------------------- /.github/workflows/run.yml: -------------------------------------------------------------------------------- 1 | name: Run 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | reuse_build: 11 | uses: gamemann/Rust-Auto-Wipe/.github/workflows/build.yml@master 12 | run: 13 | needs: reuse_build 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | 18 | - name: Download artifact from Build workflow 19 | uses: actions/download-artifact@master 20 | with: 21 | name: build-output 22 | 23 | - name: Set up Go 24 | uses: actions/setup-go@v3 25 | with: 26 | go-version: 1.18 27 | 28 | - name: List files 29 | run: ls -la 30 | 31 | - name: Add execute permissions to executable file 32 | run: sudo chmod +x ./raw 33 | 34 | - name: Run RAW by printing out help menu. 35 | run: sudo ./raw -h 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Rust-Auto-Wipe 2 | raw -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Christian Deacon 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | build: 3 | go get github.com/robfig/cron/v3 4 | go build -o raw 5 | install: 6 | mkdir -p /etc/raw 7 | cp ./raw /usr/bin/rawapp 8 | cp -n data/raw.service /etc/systemd/system/ 9 | .DEFAULT: build -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust Auto Wipe 2 | [![RAW Build Workflow](https://github.com/gamemann/Rust-Auto-Wipe/actions/workflows/build.yml/badge.svg)](https://github.com/gamemann/Rust-Auto-Wipe/actions/workflows/build.yml) [![RAW Run Workflow](https://github.com/gamemann/Rust-Auto-Wipe/actions/workflows/run.yml/badge.svg)](https://github.com/gamemann/Rust-Auto-Wipe/actions/workflows/run.yml) 3 | 4 | ## Description 5 | An application made in Go (latest version works) for Rust servers operating with [Pterodactyl](https://pterodactyl.io/) (latest API version works). This application automatically wipes server(s) based off of cron jobs. The program is aimed to be as flexible as possible. With that said, there are many features including the following. 6 | 7 | * Allow rotating of world settings (map/level, world size, and world seed). 8 | * Allow automatically changing the host name on each wipe with format support (including option replacements like `{day_two}` and `{month_two}`). 9 | * Deletion of files with the option to except specific types (e.g. don't delete player data such as states, identities, tokens, and more). 10 | * A flexible configuration and uses a cron job system (support for multiple cron jobs per server). 11 | * Support for retrieving servers automatically from Pterodactyl API. 12 | * Pterodactyl egg variable overrides to allow others to change these settings without having access to the application's file system. 13 | * Pre and post wipe hooks (sends POST data to specific endpoints that can be used for Discord web hooks for example). 14 | 15 | **Note** - This is only tested on Linux. However, technically it should work for Windows as well. 16 | 17 | ## Command Line Usage 18 | The below is the command line usage from the help menu for this program. 19 | 20 | ```bash 21 | Help Options 22 | -cfg= --cfg -cfg > Path to config file override. 23 | -l --list > Print out full config. 24 | -v --version > Print out version and exit. 25 | -h --help > Display help menu. 26 | ``` 27 | 28 | ## Configuration 29 | All configuration is done inside a config file on disk via JSON or through environmental variable overrides. The default config path is `/etc/raw/raw.conf` and may be changed via the `-cfg` command line flag. 30 | 31 | Any wipe-specific configuration at the top-level of the configuration is used as the default values for each server. Each server may override these by specifiying the same key => value pairs inside of the server array. 32 | 33 | When the program is ran, but no configuration file is found, it will attempt to create the file (likely requiring root privileges by default if trying to create inside of `/etc/`, however). The below are all JSON settings along with their default values, but with added comments. Remember that JSON does **not** support comments. Therefore, copying the below contents with comments will result in errors. This is why it's recommended to allow the program to create a default configuration file. 34 | 35 | ``` 36 | { 37 | // The URL to the panel (make sure to include a trailing /). Ex: http://ptero.something.internal/ 38 | "apiurl": "", 39 | 40 | // The Pterodactyl client token (should start with "ptlc_"). Create under user account settings in Pterodactyl panel. 41 | "apitoken": "", 42 | 43 | // Debug level from 0 - 4. 44 | "debuglevel": 1, 45 | 46 | // The application token (required for automatically adding servers from Pterodactyl). 47 | "apptoken": "", 48 | 49 | // Automatically add servers from Pterodactyl (servers require 'WORLD_SEED' and 'HOSTNAME' environmental variables). 50 | "autoaddservers": false, 51 | 52 | // Path starting from /home/container to the server files we need to delete (should be /server/rust with default Rust egg). 53 | "pathtoserverfiles": "/server/rust", 54 | 55 | // Timezone for Cron jobs to run with. 56 | "timezone": "America/Chicago", 57 | 58 | // Either a single string or a slice/array of strings representing when the server should be wiped and processed via Cron format. 59 | // I would recommend using a Cron generator (there are many online). 60 | // With that said, the default value wipes at 3:30 PM every Thursday. 61 | "cronstr": "30 15 * * 4", 62 | 63 | // Whether to delete map files (includes *.map and *.sav files). 64 | "deletemap": true, 65 | 66 | // Whether to delete player blueprints (includes any files with blueprints in the file name). 67 | "deletebp": true, 68 | 69 | // Whether to delete deaths (includes any files with deaths in the file name). 70 | "deletedeaths": true, 71 | 72 | // Whether to delete states (includes any files with states in the file name). 73 | "deletestates": true, 74 | 75 | // Whether to delete identities (includes any files with identities in the file name). 76 | "deleteidentities": true, 77 | 78 | // Whether to delete tokens (includes any files with tokens in the file name). 79 | "deletetokens": true, 80 | 81 | // Whether to merge the top-level additional files array and the server-specific files array. 82 | "deletefilesmerge": false, 83 | 84 | // Additional deletion of files using an array with the array items including "root" as the directory path starting from /home/container (make sure to include beginning /) and another array of strings for file deletion (wildcards included). Ex: [{"root": "/server/rust", "files": [".log"]}] 85 | "deletefiles": null, 86 | 87 | // Whether to delete server data/files (includes any files with sv.files in the file name). 88 | "deletesv": true, 89 | 90 | // Whether to change the world info. 91 | "changeworldinfo": false, 92 | 93 | // An array with the map, world size, and world seed. (e.g. [{"map": "Procedural Map", "worldsize": 4000, "worldseed": 9213913}]) 94 | "worldinfo": null, 95 | 96 | // World info pick type (1 = pick the next world, otherwise, pick a random world). 97 | "worldinfopicktype": 1, 98 | 99 | // Whether to merge world information arrays. 100 | "worldinfomerge": false, 101 | 102 | // Whether to change the hostname. 103 | "changehostname": true, 104 | 105 | // The hostname format. 106 | // Would recommend looking here for a cheatsheet on Golang's format library -> https://gosamples.dev/date-time-format-cheatsheet/ 107 | // Replacements include: 108 | // {seconds_left} = Amount of seconds left until next wipe (only valid for warning messages). 109 | // 110 | // {tz_one} = Timezone in TTT format (e.g. MST). 111 | // {tz_two} = Timezone offset in ±hhmm format (e.g. +0100). 112 | // {tz_three} = Timezone offset in ±hh format (e.g. +01). 113 | // 114 | // {month_str_short} = Month in TTT format (e.g. Jan). 115 | // {month_str_long} = Full month string (e.g. January). 116 | // 117 | // {week_day_str_short} = Week day in TTT format (e.g. Mon). 118 | // {week_day_str_long} = Full week day string (e.g. Monday). 119 | // 120 | // {year_one} = Year in TT format (e.g. 22). 121 | // {year_two} = Full year (e.g. 2022). 122 | // 123 | // {month_one} = Month in TT format (e.g. 01). 124 | // {month_two} = Month in T format (e.g. 1). 125 | // {month_three} = Month in _T format (e.g. 1). 126 | // 127 | // {day_one} = Day in TT format (e.g. 01). 128 | // {day_two} = Day in T format (e.g. 1). 129 | // {day_three} = Day in _T format (e.g. 1). 130 | // 131 | // {hour_one} = Hour in TT 12 HR format (e.g. 05). 132 | // {hour_two} = Hour in T 12 HR format (e.g. 5). 133 | // {hour_three} = Hour in TT 24 HR format (e.g. 17). 134 | // 135 | // {min_one} = Minute in TT format (e.g. 06). 136 | // {min_two} = Minute in T format (e.g. 6). 137 | // 138 | // {sec_one} = Second in TT format (e.g. 07). 139 | // {sec_two} = Second in T format (e.g. 7). 140 | // 141 | // {mark_one} = 12-HR mark as TT format (e.g. PM or AM). 142 | // {mark_two} = 12-HR mark as tt format (e.g. pm or am). 143 | "hostname": "Vanilla | FULL WIPE {month_two}/{day_two}", 144 | 145 | // Whether to merge both server-specific and global warning messages. 146 | "mergewarnings": false, 147 | 148 | // Warning messages list 149 | "warningmessages": [ 150 | { 151 | // The prewarn time (e.g. this would warn one second before wipe whereas if the warning time was 10, it would warn 10 seconds before wipe time). 152 | "warningtime": 1, 153 | 154 | // The message (use formatting from hostname documentation). 155 | "message": "Wiping server in {seconds_left} seconds. Please join back!" 156 | }, 157 | { 158 | "warningtime": 2, 159 | "message": "Wiping server in {seconds_left} seconds. Please join back!" 160 | }, 161 | { 162 | "warningtime": 3, 163 | "message": "Wiping server in {seconds_left} seconds. Please join back!" 164 | }, 165 | { 166 | "warningtime": 4, 167 | "message": "Wiping server in {seconds_left} seconds. Please join back!" 168 | }, 169 | { 170 | "warningtime": 5, 171 | "message": "Wiping server in {seconds_left} seconds. Please join back!" 172 | }, 173 | { 174 | "warningtime": 6, 175 | "message": "Wiping server in {seconds_left} seconds. Please join back!" 176 | }, 177 | { 178 | "warningtime": 7, 179 | "message": "Wiping server in {seconds_left} seconds. Please join back!" 180 | }, 181 | { 182 | "warningtime": 8, 183 | "message": "Wiping server in {seconds_left} seconds. Please join back!" 184 | }, 185 | { 186 | "warningtime": 9, 187 | "message": "Wiping server in {seconds_left} seconds. Please join back!" 188 | }, 189 | { 190 | "warningtime": 10, 191 | "message": "Wiping server in {seconds_left} seconds. Please join back!" 192 | } 193 | ], 194 | 195 | // Hooks (submits POST data in JSON with "id", "uuid", "identifier", "name", "ip", and "port" to the hook string/URL). The auth string for each is set to the "Authorization" header (e.g. "Bearer xxxxx"). 196 | // Pre wipe hook. 197 | prehook: "", 198 | prehookauth: "", 199 | 200 | // Post wipe hook. 201 | posthook: "", 202 | posthookauth: "", 203 | 204 | // Server list (null by default). 205 | "servers": null 206 | } 207 | ``` 208 | 209 | The servers array includes the following: 210 | 211 | ``` 212 | { 213 | "servers": [ 214 | { 215 | // Whether to enable the server or not (enabled by default). 216 | "enabled": true, 217 | 218 | // The number ID of the server (should be retrieved automatically if the server exists). 219 | "id": 0, 220 | 221 | // The (short) identifier of the server. Characters before the first "-" in the long UUID. 222 | "uuid": "", 223 | 224 | // Overrides (retrieve definition from top-level comments above). 225 | "apiurl": "", 226 | "apitoken": "", 227 | "debuglevel": 1, 228 | "pathtoserverfiles": "/server/rust", 229 | "timezone": "America/Chicago", 230 | "cronstr": "30 15 * * 4", 231 | "deletemap": true, 232 | "deletebp": true, 233 | "deletedeaths": true, 234 | "deletestates": true, 235 | "deleteidentities": true, 236 | "deletetokens": true, 237 | "deletefilesmerge": false, 238 | "deletefiles": null, 239 | "deletesv": true, 240 | "changeworldinfo": false, 241 | "worldinfo": null, 242 | "worldinfopicktype": 1, 243 | "worldinfomerge": false, 244 | "changehostname": true, 245 | "hostname": "Vanilla | FULL WIPE {month_two}/{day_two}", 246 | "mergewarnings": false, 247 | "warningmessages": null, 248 | 249 | // Extras. 250 | // Wipe server when the program is first started. 251 | "wipefirst": false 252 | } 253 | ... 254 | ] 255 | } 256 | ``` 257 | 258 | **Note** - When writing to the default file after creation, it will try to make the JSON data pretty (AKA pretty print by idents). 259 | 260 | ## Environmental Overrides With Auto-Added Servers 261 | There are environmental overrides for servers that are added from the Pterodactyl API. This allows you to distribute access easier from within the Pterodactyl panel itself. 262 | 263 | With that said, the file `data/egg-rustraw.json` file in this respository is an exported egg that has the standard Rust egg settings from Pterodactyl along with the additional RAW settings. If you import this egg into Pterodactyl under Rust and change the server's egg in the administrator settings, it will easily migrate all existing variable settings over including the Rust egg's default variables. 264 | 265 | Each variable is also parsed as a string within the application's code. For true/false, use the 1/0 integers respectfully. 266 | 267 | The following is a list of environmental names you can create variables within Pterodactyl Nests/Eggs for overrides. 268 | * **RAW_ENABLED** - Enabled override. 269 | * **RAW_PATHTOFILES** - Path to files override. 270 | * **RAW_TIMEZONE** - Timezone override. 271 | * **RAW_CRONMERGE** - Cron merge override. 272 | * **RAW_CRONSTR** - Cron string override (this is a special case, cron string(s) can be a single string or a string array as a JSON string (e.g. `["*/5 * * * *", "*/2 * * * *"]`)). 273 | * **RAW_DELETEMAP** - Delete map override. 274 | * **RAW_DELETEBP** - Delete blueprints override. 275 | * **RAW_DELETEDEATHS** - Delete deaths override. 276 | * **RAW_DELETESTATES** - Delete states override. 277 | * **RAW_DELETEIDENTITIES** - Delete identities override. 278 | * **RAW_DELETEFILESMERGE** - Whether to merge additional deletion of files override (top-level config and server-specific). 279 | * **RAW_DELETEFILES** - Additional files to delete override. (e.g. `[{"root": "/server/rust", "files": [".log"]}]` as a string). 280 | * **RAW_DELETESV** - Delete server files/data override. 281 | * **RAW_CHANGEWORLDINFO** - Change world info override. 282 | * **RAW_WORLDINFO** - World info override. An array with the map, world size, and world seed. (e.g. `[{"map": "Procedural Map", "worldsize": 4000, "worldseed": 9213913}]` as a string). 283 | * **RAW_WORLDINFOPICKTYPE** - Change world info pick type override. 284 | * **RAW_WORLDINFOMERGE** - Change world info merge override. 285 | * **RAW_CHANGEHOSTNAME** - Change hostname override. 286 | * **RAW_HOSTNAME** - Hostname override. 287 | * **RAW_MERGEWARNINGS** - Merge warnings override. 288 | * **RAW_WARNINGMESSAGES** - Warning messages override (another special case, this should be a JSON string of the normal `warningmessages` JSON item). (e.g. `[{"warningtime": 5, "message": "{seconds_left} until wipe!"}]` as a string). 289 | * **RAW_WIPEFIRST** - Wipe first override. 290 | 291 | ## Building And Running Project (Manually) 292 | Building the project is simple. We only require `git` and Go. 293 | 294 | ```bash 295 | # Clone repository. 296 | git clone https://github.com/gamemann/Rust-Auto-Wipe.git 297 | 298 | # Change directory to repository. 299 | cd Rust-Auto-Wipe/ 300 | 301 | # Build using Go into `raw` executable. This will also retrieve needed packages such as Cronv3. 302 | go build -o raw 303 | ``` 304 | 305 | Using `Makefile` as described below is my personal recommendation on installing this application for Linux-based systems. 306 | 307 | ## Using Makefile + Systemd (Recommended) 308 | You may also use a Makefile I made to build the application and install a Systemd file. 309 | 310 | ```bash 311 | # Build project (go build -o raw). 312 | make 313 | 314 | # Install `rawapp` (/usr/bin/rawapp) and Systemd process. 315 | sudo make install 316 | ``` 317 | 318 | To have the application run on startup, and/or in the background, you may do the following. 319 | 320 | ```bash 321 | # Reload Systemd daemon (may be needed after install of Systemd service). 322 | sudo systemctl daemon-reload 323 | 324 | # Enable (on startup) and start service. 325 | sudo systemctl enable --now raw 326 | 327 | # Start service. 328 | sudo systemctl start raw 329 | 330 | # Restart service. 331 | sudo systemctl restart raw 332 | 333 | # Stop service. 334 | sudo systemctl stop raw 335 | 336 | # Disable (on startup) and stop service. 337 | sudo systemctl disable --now raw 338 | ``` 339 | 340 | ## Credits 341 | * [Christian Deacon](https://github.com/gamemann) -------------------------------------------------------------------------------- /data/egg-rustraw.json: -------------------------------------------------------------------------------- 1 | { 2 | "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", 3 | "meta": { 4 | "version": "PTDL_v2", 5 | "update_url": null 6 | }, 7 | "exported_at": "2022-08-07T04:49:48-04:00", 8 | "name": "Rust (RAW)", 9 | "author": "christian@lbgaming.co", 10 | "description": "Duplicate of Rust egg, but with RAW (Rust Auto Wipe) variables.", 11 | "features": [ 12 | "steam_disk_space" 13 | ], 14 | "docker_images": { 15 | "quay.io\/pterodactyl\/core:rust": "quay.io\/pterodactyl\/core:rust" 16 | }, 17 | "file_denylist": [], 18 | "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}}", 19 | "config": { 20 | "files": "{}", 21 | "startup": "{\r\n \"done\": \"Server startup complete\"\r\n}", 22 | "logs": "{}", 23 | "stop": "quit" 24 | }, 25 | "scripts": { 26 | "installation": { 27 | "script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\nSRCDS_APPID=258550\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n echo -e \"steam user is not set.\\n\"\r\n echo -e \"Using anonymous user.\\n\"\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nelse\r\n echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +force_install_dir \/mnt\/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so", 28 | "container": "ghcr.io\/pterodactyl\/installers:debian", 29 | "entrypoint": "bash" 30 | } 31 | }, 32 | "variables": [ 33 | { 34 | "name": "Server Name", 35 | "description": "The name of your server in the public server list.", 36 | "env_variable": "HOSTNAME", 37 | "default_value": "A Rust Server", 38 | "user_viewable": true, 39 | "user_editable": true, 40 | "rules": "required|string|max:60", 41 | "field_type": "text" 42 | }, 43 | { 44 | "name": "OxideMod", 45 | "description": "Set whether you want the server to use and auto update OxideMod or not. Valid options are \"1\" for true and \"0\" for false.", 46 | "env_variable": "OXIDE", 47 | "default_value": "0", 48 | "user_viewable": true, 49 | "user_editable": true, 50 | "rules": "required|boolean", 51 | "field_type": "text" 52 | }, 53 | { 54 | "name": "Level", 55 | "description": "The world file for Rust to use.", 56 | "env_variable": "LEVEL", 57 | "default_value": "Procedural Map", 58 | "user_viewable": true, 59 | "user_editable": true, 60 | "rules": "required|string|max:20", 61 | "field_type": "text" 62 | }, 63 | { 64 | "name": "Description", 65 | "description": "The description under your server title. Commonly used for rules & info. Use \\n for newlines.", 66 | "env_variable": "DESCRIPTION", 67 | "default_value": "Powered by Pterodactyl", 68 | "user_viewable": true, 69 | "user_editable": true, 70 | "rules": "required|string", 71 | "field_type": "text" 72 | }, 73 | { 74 | "name": "URL", 75 | "description": "The URL for your server. This is what comes up when clicking the \"Visit Website\" button.", 76 | "env_variable": "SERVER_URL", 77 | "default_value": "http:\/\/pterodactyl.io", 78 | "user_viewable": true, 79 | "user_editable": true, 80 | "rules": "nullable|url", 81 | "field_type": "text" 82 | }, 83 | { 84 | "name": "World Size", 85 | "description": "The world size for a procedural map.", 86 | "env_variable": "WORLD_SIZE", 87 | "default_value": "3000", 88 | "user_viewable": true, 89 | "user_editable": true, 90 | "rules": "required|integer", 91 | "field_type": "text" 92 | }, 93 | { 94 | "name": "World Seed", 95 | "description": "The seed for a procedural map.", 96 | "env_variable": "WORLD_SEED", 97 | "default_value": "", 98 | "user_viewable": true, 99 | "user_editable": true, 100 | "rules": "nullable|string", 101 | "field_type": "text" 102 | }, 103 | { 104 | "name": "Max Players", 105 | "description": "The maximum amount of players allowed in the server at once.", 106 | "env_variable": "MAX_PLAYERS", 107 | "default_value": "40", 108 | "user_viewable": true, 109 | "user_editable": true, 110 | "rules": "required|integer", 111 | "field_type": "text" 112 | }, 113 | { 114 | "name": "Server Image", 115 | "description": "The header image for the top of your server listing.", 116 | "env_variable": "SERVER_IMG", 117 | "default_value": "", 118 | "user_viewable": true, 119 | "user_editable": true, 120 | "rules": "nullable|url", 121 | "field_type": "text" 122 | }, 123 | { 124 | "name": "RCON Port", 125 | "description": "Port for RCON connections.", 126 | "env_variable": "RCON_PORT", 127 | "default_value": "28016", 128 | "user_viewable": true, 129 | "user_editable": false, 130 | "rules": "required|integer", 131 | "field_type": "text" 132 | }, 133 | { 134 | "name": "RCON Password", 135 | "description": "RCON access password.", 136 | "env_variable": "RCON_PASS", 137 | "default_value": "CHANGEME", 138 | "user_viewable": true, 139 | "user_editable": true, 140 | "rules": "required|regex:\/^[\\w.-]*$\/|max:64", 141 | "field_type": "text" 142 | }, 143 | { 144 | "name": "Save Interval", 145 | "description": "Sets the server\u2019s auto-save interval in seconds.", 146 | "env_variable": "SAVEINTERVAL", 147 | "default_value": "60", 148 | "user_viewable": true, 149 | "user_editable": true, 150 | "rules": "required|integer", 151 | "field_type": "text" 152 | }, 153 | { 154 | "name": "Additional Arguments", 155 | "description": "Add additional startup parameters to the server.", 156 | "env_variable": "ADDITIONAL_ARGS", 157 | "default_value": "", 158 | "user_viewable": true, 159 | "user_editable": true, 160 | "rules": "nullable|string", 161 | "field_type": "text" 162 | }, 163 | { 164 | "name": "App Port", 165 | "description": "Port for the Rust+ App. -1 to disable.", 166 | "env_variable": "APP_PORT", 167 | "default_value": "28082", 168 | "user_viewable": true, 169 | "user_editable": false, 170 | "rules": "required|integer", 171 | "field_type": "text" 172 | }, 173 | { 174 | "name": "Server Logo", 175 | "description": "The circular server logo for the Rust+ app.", 176 | "env_variable": "SERVER_LOGO", 177 | "default_value": "", 178 | "user_viewable": true, 179 | "user_editable": true, 180 | "rules": "nullable|url", 181 | "field_type": "text" 182 | }, 183 | { 184 | "name": "Custom Map URL", 185 | "description": "Overwrites the map with the one from the direct download URL. Invalid URLs will cause the server to crash.", 186 | "env_variable": "MAP_URL", 187 | "default_value": "", 188 | "user_viewable": true, 189 | "user_editable": true, 190 | "rules": "nullable|url", 191 | "field_type": "text" 192 | }, 193 | { 194 | "name": "RAW Enabled", 195 | "description": "Whether to enable RAW for this server.", 196 | "env_variable": "RAW_ENABLED", 197 | "default_value": "1", 198 | "user_viewable": true, 199 | "user_editable": true, 200 | "rules": "int|nullable", 201 | "field_type": "text" 202 | }, 203 | { 204 | "name": "RAW Path To Server Files", 205 | "description": "Path to server files override.", 206 | "env_variable": "RAW_PATHTOSERVERFILES", 207 | "default_value": "", 208 | "user_viewable": true, 209 | "user_editable": true, 210 | "rules": "string|nullable", 211 | "field_type": "text" 212 | }, 213 | { 214 | "name": "RAW Timezone", 215 | "description": "Timezone override.", 216 | "env_variable": "RAW_TIMEZONE", 217 | "default_value": "", 218 | "user_viewable": true, 219 | "user_editable": true, 220 | "rules": "string|nullable", 221 | "field_type": "text" 222 | }, 223 | { 224 | "name": "RAW Cron String", 225 | "description": "Cron string override.", 226 | "env_variable": "RAW_CRONSTR", 227 | "default_value": "", 228 | "user_viewable": true, 229 | "user_editable": true, 230 | "rules": "string|nullable", 231 | "field_type": "text" 232 | }, 233 | { 234 | "name": "RAW Cron Merge", 235 | "description": "Cron merge override.", 236 | "env_variable": "RAW_CRONMERGE", 237 | "default_value": "", 238 | "user_viewable": true, 239 | "user_editable": true, 240 | "rules": "int|nullable", 241 | "field_type": "text" 242 | }, 243 | { 244 | "name": "RAW Delete Map", 245 | "description": "Delete map override.", 246 | "env_variable": "RAW_DELETEMAP", 247 | "default_value": "", 248 | "user_viewable": true, 249 | "user_editable": true, 250 | "rules": "int|nullable", 251 | "field_type": "text" 252 | }, 253 | { 254 | "name": "RAW Delete Blueprints", 255 | "description": "Delete blueprints override.", 256 | "env_variable": "RAW_DELETEBP", 257 | "default_value": "", 258 | "user_viewable": true, 259 | "user_editable": true, 260 | "rules": "int|nullable", 261 | "field_type": "text" 262 | }, 263 | { 264 | "name": "RAW Delete Deaths", 265 | "description": "Delete deaths override.", 266 | "env_variable": "RAW_DELETEDEATHS", 267 | "default_value": "", 268 | "user_viewable": true, 269 | "user_editable": true, 270 | "rules": "int|nullable", 271 | "field_type": "text" 272 | }, 273 | { 274 | "name": "RAW Delete States", 275 | "description": "Delete states override.", 276 | "env_variable": "RAW_DELETESTATES", 277 | "default_value": "", 278 | "user_viewable": true, 279 | "user_editable": true, 280 | "rules": "int|nullable", 281 | "field_type": "text" 282 | }, 283 | { 284 | "name": "RAW Delete Identities", 285 | "description": "Delete identities override.", 286 | "env_variable": "RAW_DELETEIDENTITIES", 287 | "default_value": "", 288 | "user_viewable": true, 289 | "user_editable": true, 290 | "rules": "int|nullable", 291 | "field_type": "text" 292 | }, 293 | { 294 | "name": "RAW Delete Tokens", 295 | "description": "Delete tokens override.", 296 | "env_variable": "RAW_DELETETOKENS", 297 | "default_value": "", 298 | "user_viewable": true, 299 | "user_editable": true, 300 | "rules": "int|nullable", 301 | "field_type": "text" 302 | }, 303 | { 304 | "name": "RAW Additional Delete Files Merge", 305 | "description": "Merge additional deletion of files from top-level config..", 306 | "env_variable": "RAW_DELETEFILESMERGE", 307 | "default_value": "", 308 | "user_viewable": true, 309 | "user_editable": true, 310 | "rules": "int|nullable", 311 | "field_type": "text" 312 | }, 313 | { 314 | "name": "RAW Additional Delete Files", 315 | "description": "Additional deletion of files.", 316 | "env_variable": "RAW_DELETEFILES", 317 | "default_value": "", 318 | "user_viewable": true, 319 | "user_editable": true, 320 | "rules": "string|nullable", 321 | "field_type": "text" 322 | }, 323 | { 324 | "name": "RAW Delete Server Files\/Data", 325 | "description": "Delete server files\/data override.", 326 | "env_variable": "RAW_DELETESV", 327 | "default_value": "", 328 | "user_viewable": true, 329 | "user_editable": true, 330 | "rules": "int|nullable", 331 | "field_type": "text" 332 | }, 333 | { 334 | "name": "RAW Change World Info", 335 | "description": "Change world info (e.g. map, size, and seed).", 336 | "env_variable": "RAW_CHANGEWORLDINFO", 337 | "default_value": "", 338 | "user_viewable": true, 339 | "user_editable": true, 340 | "rules": "int|nullable", 341 | "field_type": "text" 342 | }, 343 | { 344 | "name": "RAW World Info", 345 | "description": "World info override.", 346 | "env_variable": "RAW_WORLDINFO", 347 | "default_value": "", 348 | "user_viewable": true, 349 | "user_editable": true, 350 | "rules": "string|nullable", 351 | "field_type": "text" 352 | }, 353 | { 354 | "name": "RAW World Info Pick Type", 355 | "description": "World info pick type override.", 356 | "env_variable": "RAW_WORLDINFOPICKTYPE", 357 | "default_value": "", 358 | "user_viewable": true, 359 | "user_editable": true, 360 | "rules": "int|nullable", 361 | "field_type": "text" 362 | }, 363 | { 364 | "name": "RAW World Info Merge", 365 | "description": "World info merge override.", 366 | "env_variable": "RAW_WORLDINFOMERGE", 367 | "default_value": "", 368 | "user_viewable": true, 369 | "user_editable": true, 370 | "rules": "int|nullable", 371 | "field_type": "text" 372 | }, 373 | { 374 | "name": "RAW Change Hostname", 375 | "description": "Change hostname override.", 376 | "env_variable": "RAW_CHANGEHOSTNAME", 377 | "default_value": "", 378 | "user_viewable": true, 379 | "user_editable": true, 380 | "rules": "int|nullable", 381 | "field_type": "text" 382 | }, 383 | { 384 | "name": "RAW Hostname", 385 | "description": "Hostname override.", 386 | "env_variable": "RAW_HOSTNAME", 387 | "default_value": "", 388 | "user_viewable": true, 389 | "user_editable": true, 390 | "rules": "string|nullable", 391 | "field_type": "text" 392 | }, 393 | { 394 | "name": "RAW Merge Warnings", 395 | "description": "Merge warnings override.", 396 | "env_variable": "RAW_MERGEWARNINGS", 397 | "default_value": "", 398 | "user_viewable": true, 399 | "user_editable": true, 400 | "rules": "int|nullable", 401 | "field_type": "text" 402 | }, 403 | { 404 | "name": "RAW Warning Messages", 405 | "description": "Warning messages override.", 406 | "env_variable": "RAW_WARNINGMESSAGES", 407 | "default_value": "", 408 | "user_viewable": true, 409 | "user_editable": true, 410 | "rules": "string|nullable", 411 | "field_type": "text" 412 | }, 413 | { 414 | "name": "RAW Wipe First", 415 | "description": "Wipe first override.", 416 | "env_variable": "RAW_WIPEFIRST", 417 | "default_value": "", 418 | "user_viewable": true, 419 | "user_editable": true, 420 | "rules": "int|nullable", 421 | "field_type": "text" 422 | } 423 | ] 424 | } -------------------------------------------------------------------------------- /data/raw.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Rust Auto Wipe (RAW). 3 | After=network-online.target 4 | Requires=network-online.target 5 | 6 | [Service] 7 | ExecStart=/usr/bin/rawapp 8 | Restart=always 9 | 10 | [Install] 11 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gamemann/Rust-Auto-Wipe 2 | 3 | go 1.13 4 | 5 | require github.com/robfig/cron/v3 v3.0.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gamemann/Rust-Auto-Wipe v0.0.0-20220721174449-a7ebd9d59533 h1:B0G+moYSZGrZb2WmC5A7SWtkzkDCqfeo4RagmgRyzcA= 2 | github.com/gamemann/Rust-Auto-Wipe v0.0.0-20220721174449-a7ebd9d59533/go.mod h1:ErPo4YxsrNZew7MnLvByHi46E3xQb1jYjqr94UGIpsc= 3 | github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E= 4 | github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= 5 | -------------------------------------------------------------------------------- /internal/autoaddservers/pterodactyl.go: -------------------------------------------------------------------------------- 1 | package autoaddservers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | "time" 9 | 10 | "github.com/gamemann/Rust-Auto-Wipe/internal/config" 11 | "github.com/gamemann/Rust-Auto-Wipe/pkg/debug" 12 | "github.com/gamemann/Rust-Auto-Wipe/pkg/pterodactyl" 13 | ) 14 | 15 | type WarningMessageJSON struct { 16 | WarningTime uint `json:"warningtime"` 17 | Message string `json:"message"` 18 | } 19 | 20 | type WarningMessageOverride struct { 21 | Data []WarningMessageJSON `json:"data"` 22 | } 23 | 24 | type RawEnv struct { 25 | WorldSeed *string `json:"WORLD_SEED"` 26 | HostName *string `json:"HOSTNAME"` 27 | ServerIP *string `json:"SERVER_IP"` 28 | ServerPort *string `json:"SERVER_PORT"` 29 | 30 | RAW_Enabled *string `json:"RAW_ENABLED"` 31 | RAW_PathToServerFiles *string `json:"RAW_PATHTOSERVERFILES"` 32 | RAW_Timezone *string `json:"RAW_TIMEZONE"` 33 | RAW_CronMerge *string `json:"RAW_CRONMERGE"` 34 | RAW_CronStr *string `json:"RAW_CRONSTR"` 35 | RAW_DeleteMap *string `json:"RAW_DELETEMAP"` 36 | RAW_DeleteBP *string `json:"RAW_DELETEBP"` 37 | RAW_DeleteDeaths *string `json:"RAW_DELETEDEATHS"` 38 | RAW_DeleteStates *string `json:"RAW_DELETESTATES"` 39 | RAW_DeleteIdentities *string `json:"RAW_DELETEIDENTITIES"` 40 | RAW_DeleteTokens *string `json:"RAW_DELETETOKENS"` 41 | RAW_DeleteFilesMerge *string `json:"RAW_DELETEFILESMERGE"` 42 | RAW_DeleteFiles *string `json:"RAW_DELETEFILES"` 43 | RAW_DeleteSv *string `json:"RAW_DELETESV"` 44 | RAW_ChangeWorldInfo *string `json:"RAW_CHANGEWORLDINFO"` 45 | RAW_WorldInfo *string `json:"RAW_WORLDINFO"` 46 | RAW_WorldInfoPickType *string `json:"RAW_WORLDINFOPICKTYPE"` 47 | RAW_WorldInfoMerge *string `json:"RAW_WORLDINFOMERGE"` 48 | RAW_ChangeHostname *string `json:"RAW_CHANGEHOSTNAME"` 49 | RAW_Hostname *string `json:"RAW_HOSTNAME"` 50 | RAW_MergeWarnings *string `json:"RAW_MERGEWARNINGS"` 51 | RAW_WarningMessages *string `json:"RAW_WARNINGMESSAGES"` 52 | RAW_WipeFirst *string `json:"RAW_WIPEFIRST"` 53 | } 54 | 55 | type ServerListResp struct { 56 | Object string `json:"object"` 57 | Data []struct { 58 | Object string `json:"object"` 59 | Attributes struct { 60 | ID int `json:"id"` 61 | ExternalID interface{} `json:"external_id"` 62 | UUID string `json:"uuid"` 63 | Identifier string `json:"identifier"` 64 | Name string `json:"name"` 65 | Description string `json:"description"` 66 | Status interface{} `json:"status"` 67 | Suspended bool `json:"suspended"` 68 | Limits struct { 69 | Memory int `json:"memory"` 70 | Swap int `json:"swap"` 71 | Disk int `json:"disk"` 72 | Io int `json:"io"` 73 | CPU int `json:"cpu"` 74 | Threads interface{} `json:"threads"` 75 | OomDisabled bool `json:"oom_disabled"` 76 | } `json:"limits"` 77 | FeatureLimits struct { 78 | Databases int `json:"databases"` 79 | Allocations int `json:"allocations"` 80 | Backups int `json:"backups"` 81 | } `json:"feature_limits"` 82 | User int `json:"user"` 83 | Node int `json:"node"` 84 | Allocation int `json:"allocation"` 85 | Nest int `json:"nest"` 86 | Egg int `json:"egg"` 87 | Container struct { 88 | StartupCommand string `json:"startup_command"` 89 | Image string `json:"image"` 90 | Installed int `json:"installed"` 91 | Environment RawEnv `json:"environment"` 92 | } `json:"container"` 93 | UpdatedAt time.Time `json:"updated_at"` 94 | CreatedAt time.Time `json:"created_at"` 95 | } `json:"attributes"` 96 | } `json:"data"` 97 | Meta struct { 98 | Pagination struct { 99 | Total int `json:"total"` 100 | Count int `json:"count"` 101 | PerPage int `json:"per_page"` 102 | CurrentPage int `json:"current_page"` 103 | TotalPages int `json:"total_pages"` 104 | Links struct { 105 | } `json:"links"` 106 | } `json:"pagination"` 107 | } `json:"meta"` 108 | } 109 | 110 | func AddServers(cfg *config.Config) error { 111 | var err error 112 | 113 | // Page number. 114 | p := 1 115 | 116 | // Retrieve list of all servers from Pterodactyl application API. 117 | for true { 118 | ep := "application/servers?p=" + strconv.Itoa(p) 119 | 120 | d, _, err := pterodactyl.SendAPIRequest(cfg.APIURL, cfg.AppToken, "GET", ep, nil) 121 | 122 | debug.SendDebugMsg("AUTOADD", cfg.DebugLevel, 3, "Sending request. Request => "+cfg.APIURL+"api/"+ep+". Post data => nil.") 123 | debug.SendDebugMsg("AUTOADD", cfg.DebugLevel, 4, "Update Variable return data => "+d+".") 124 | 125 | if err != nil { 126 | break 127 | } 128 | 129 | // Convert JSON to structure. 130 | var server_list ServerListResp 131 | 132 | err = json.Unmarshal([]byte(d), &server_list) 133 | 134 | if err != nil { 135 | break 136 | } 137 | 138 | // Now loop through each data object (server). 139 | for _, v := range server_list.Data { 140 | var uuid_split []string 141 | var srv config.Server 142 | 143 | // We must make sure the Rust environmental variables are valid if we're going to add said server. 144 | env := &v.Attributes.Container.Environment 145 | 146 | ip := "" 147 | 148 | if env.ServerIP != nil { 149 | ip = *env.ServerIP 150 | } 151 | 152 | port := "" 153 | 154 | if env.ServerPort != nil { 155 | port = *env.ServerPort 156 | } 157 | 158 | // Loop through all current servers and make sure we update the ID if necessary. 159 | if !cfg.AutoAddServers { 160 | goto updateid 161 | } 162 | 163 | // If WORLD_SEED doesn't exist (empty field), don't add server. 164 | if env.WorldSeed == nil { 165 | continue 166 | } 167 | 168 | // If HOSTNAME doesn't exist (empty field), don't add server. 169 | if env.HostName == nil { 170 | continue 171 | } 172 | 173 | // Enable by default. 174 | srv.Enabled = true 175 | 176 | // Split UUID by -. 177 | uuid_split = strings.Split(v.Attributes.UUID, "-") 178 | 179 | // Assign short UUID. 180 | srv.UUID = uuid_split[0] 181 | 182 | // Append to CFG server slice. 183 | cfg.Servers = append(cfg.Servers, srv) 184 | 185 | updateid: 186 | // Get server information 187 | for i := 0; i < len(cfg.Servers); i++ { 188 | srv := &cfg.Servers[i] 189 | 190 | if srv.UUID == v.Attributes.Identifier { 191 | srv.ID = v.Attributes.ID 192 | srv.Name = v.Attributes.Name 193 | srv.LongID = v.Attributes.UUID 194 | srv.IP = ip 195 | srv.Port, err = strconv.Atoi(port) 196 | 197 | if err != nil { 198 | fmt.Println(err) 199 | } 200 | } 201 | } 202 | } 203 | 204 | // Check if we can exit now. 205 | if server_list.Meta.Pagination.CurrentPage >= server_list.Meta.Pagination.TotalPages { 206 | break 207 | } 208 | 209 | // Increment page number. 210 | p++ 211 | } 212 | 213 | return err 214 | } 215 | -------------------------------------------------------------------------------- /internal/config/def.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type WarningMessage struct { 4 | WarningTime uint `json:"warningtime"` 5 | Message *string `json:"message"` 6 | } 7 | 8 | type WorldInfo struct { 9 | Map *string `json:"map"` 10 | WorldSize *int `json:"worldsize"` 11 | WorldSeed *int `json:"worldseed"` 12 | } 13 | 14 | type AdditionalFiles struct { 15 | Root string `json:"root"` 16 | Files []string `json:"files"` 17 | } 18 | 19 | type Server struct { 20 | Enabled bool `json:"enabled"` 21 | 22 | // Server ID from Pterodactyl. 23 | ID int `json:"id"` 24 | LongID string `json:"uuidlong"` 25 | UUID string `json:"uuid"` 26 | IP string `json:"ip"` 27 | Port int `json:"port"` 28 | Name string `json:"name"` 29 | 30 | // API/Debug. 31 | APIURL *string `json:"apiurl"` 32 | APIToken *string `json:"apitoken"` 33 | DebugLevel *int `json:"debuglevel"` 34 | 35 | // Paths (e.g. /server/rust). 36 | PathToServerFiles *string `json:"pathtoserverfiles"` 37 | 38 | // Wipe date/times. 39 | Timezone *string `json:"timezone"` 40 | CronStr *interface{} `json:"cronstr"` 41 | CronMerge *bool `json:"cronmerge"` 42 | 43 | // Files/data that should be deleted. 44 | DeleteMap *bool `json:"deletemap"` 45 | DeleteBP *bool `json:"deletebp"` 46 | DeleteDeaths *bool `json:"deletedeaths"` 47 | DeleteIdentities *bool `json:"deleteidentities"` 48 | DeleteStates *bool `json:"deletestates"` 49 | DeleteTokens *bool `json:"deletetokens"` 50 | DeleteSv *bool `json:"deletesv"` 51 | DeleteFiles *[]AdditionalFiles `json:"deletefiles"` 52 | DeleteFilesMerge *bool `json:"deletefilesmerge"` 53 | 54 | // Map seeds/sizes. 55 | ChangeWorldInfo *bool `json:"changeworldinfo"` 56 | WorldInfo *[]WorldInfo `json:"worldinfo"` 57 | WorldInfoPickType *int `json:"worldinfopicktype"` 58 | WorldInfoMerge *bool `json:"worldinfomerge"` 59 | 60 | // Host name. 61 | ChangeHostName *bool `json:"changehostname"` 62 | HostName *string `json:"hostname"` 63 | 64 | // Warning chat messages. 65 | MergeWarnings *bool `json:"mergewarnings"` 66 | WarningMessages *[]WarningMessage `json:"warningmessages"` 67 | 68 | // Extras (e.g. development testing, etc.). 69 | WipeFirst bool `json:"wipefirst"` 70 | } 71 | 72 | type Config struct { 73 | // Pterodactyl API (there are overrides for these). 74 | APIURL string `json:"apiurl"` 75 | APIToken string `json:"apitoken"` 76 | DebugLevel int `json:"debuglevel"` 77 | 78 | // Auto add servers. 79 | AppToken string `json:"apptoken"` 80 | AutoAddServers bool `json:"autoaddservers"` 81 | 82 | // Paths (e.g. /home/container/server/rust). 83 | PathToServerFiles string `json:"pathtoserverfiles"` 84 | 85 | // Wipe date times. 86 | Timezone string `json:"timezone"` 87 | CronStr interface{} `json:"cronstr"` 88 | CronMerge bool `json:"cronmerge"` 89 | 90 | // Files/data that should be deleted. 91 | DeleteMap bool `json:"deletemap"` 92 | DeleteBP bool `json:"deletebp"` 93 | DeleteDeaths bool `json:"deletedeaths"` 94 | DeleteStates bool `json:"deletestates"` 95 | DeleteIdentities bool `json:"deleteidentities"` 96 | DeleteTokens bool `json:"deletetokens"` 97 | DeleteSv bool `json:"deletesv"` 98 | DeleteFiles []AdditionalFiles `json:"deletefiles"` 99 | DeleteFilesMerge bool `json:"deletefilesmerge"` 100 | 101 | // Maps, seeds, and world sizes. 102 | ChangeWorldInfo bool `json:"changeworldinfo"` 103 | WorldInfo []WorldInfo `json:"worldinfo"` 104 | WorldInfoPickType int `json:"worldinfopicktype"` 105 | WorldInfoMerge bool `json:"worldinfomerge"` 106 | 107 | // Host name. 108 | ChangeHostName bool `json:"changehostname"` 109 | HostName string `json:"hostname"` 110 | 111 | // Warning chat messages. 112 | MergeWarnings bool `json:"mergewarnings"` 113 | WarningMessages []WarningMessage `json:"warningmessages"` 114 | 115 | // Hooks that send information to an endpoint with POST data. Useful for sending updates to Discord for example. 116 | // Pre wipe hook. 117 | PreHookAuth string `json:"prehookauth"` 118 | PreHook string `json:"prehook"` 119 | 120 | // POST wipe hook. 121 | PostHookAuth string `json:"posthookauth"` 122 | PostHook string `json:"posthook"` 123 | 124 | Servers []Server `json:"servers"` 125 | } 126 | 127 | // Sets config's values. 128 | func (cfg *Config) SetDefaults() { 129 | cfg.DebugLevel = 1 130 | 131 | cfg.PathToServerFiles = "/server/rust" 132 | 133 | cfg.Timezone = "America/Chicago" 134 | cfg.CronStr = "30 15 * * 4" 135 | cfg.CronMerge = true 136 | 137 | cfg.DeleteMap = true 138 | cfg.DeleteBP = true 139 | cfg.DeleteDeaths = true 140 | cfg.DeleteStates = true 141 | cfg.DeleteIdentities = true 142 | cfg.DeleteTokens = true 143 | 144 | cfg.DeleteSv = true 145 | 146 | cfg.WorldInfoPickType = 1 147 | 148 | cfg.ChangeHostName = true 149 | cfg.HostName = "Vanilla | FULL WIPE {month_two}/{day_two}" 150 | 151 | // Warn each second for the last 10 seconds before the wipe. 152 | for i := 1; i <= 10; i++ { 153 | var warning WarningMessage 154 | tmp := "Wiping server in {seconds_left} seconds. Please join back!" 155 | 156 | warning.Message = &tmp 157 | 158 | *warning.Message = "Wiping server in {seconds_left} seconds. Please join back!" 159 | warning.WarningTime = uint(i) 160 | 161 | cfg.WarningMessages = append(cfg.WarningMessages, warning) 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /internal/config/load.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | ) 7 | 8 | // Reads a config file based off of the file name (string) and returns a Config struct. 9 | func (cfg *Config) LoadConfig(path string) error { 10 | file, err := os.Open(path) 11 | 12 | if err != nil { 13 | return err 14 | } 15 | 16 | defer file.Close() 17 | 18 | stat, _ := file.Stat() 19 | 20 | data := make([]byte, stat.Size()) 21 | 22 | _, err = file.Read(data) 23 | 24 | if err != nil { 25 | return err 26 | } 27 | 28 | err = json.Unmarshal([]byte(data), cfg) 29 | 30 | return err 31 | } 32 | -------------------------------------------------------------------------------- /internal/config/write_default.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "path" 7 | ) 8 | 9 | func (cfg *Config) WriteDefaultsToFile(file string) error { 10 | var err error 11 | 12 | dir := path.Dir(file) 13 | 14 | err = os.MkdirAll(dir, 0755) 15 | 16 | // If we have an error and it doesn't look like an "already exist" error, return the error. 17 | if err != nil && !os.IsExist(err) { 18 | return err 19 | } 20 | 21 | fp, err := os.Create(file) 22 | 23 | if err != nil { 24 | return err 25 | } 26 | 27 | data, err := json.MarshalIndent(cfg, "", " ") 28 | 29 | if err != nil { 30 | // Close file. 31 | fp.Close() 32 | 33 | return err 34 | } 35 | 36 | _, err = fp.Write(data) 37 | 38 | if err != nil { 39 | // Close file. 40 | fp.Close() 41 | 42 | return err 43 | } 44 | 45 | return err 46 | } 47 | -------------------------------------------------------------------------------- /internal/wipe/data.go: -------------------------------------------------------------------------------- 1 | package wipe 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | "strconv" 9 | 10 | "github.com/gamemann/Rust-Auto-Wipe/internal/config" 11 | "github.com/gamemann/Rust-Auto-Wipe/pkg/debug" 12 | "github.com/gamemann/Rust-Auto-Wipe/pkg/pterodactyl" 13 | ) 14 | 15 | const ( 16 | Default_Map = "Procedural Map" 17 | Default_WorldSize = 3000 18 | Default_WorldSeed = 0 19 | 20 | Default_WarningMessage = "Wiping server in {seconds_left} seconds. Please join back!" 21 | ) 22 | 23 | type Internal struct { 24 | LatestVersion uint64 25 | LatestWorld uint 26 | } 27 | 28 | type Data struct { 29 | Enabled bool 30 | APIURL string 31 | APIToken string 32 | DebugLevel int 33 | 34 | PathToServerFiles string 35 | 36 | TimeZone string 37 | CronStr []string 38 | CronMerge bool 39 | 40 | DeleteMap bool 41 | DeleteBP bool 42 | DeleteDeaths bool 43 | DeleteStates bool 44 | DeleteIdentities bool 45 | DeleteTokens bool 46 | DeleteSv bool 47 | DeleteFiles []config.AdditionalFiles 48 | DeleteFilesMerge bool 49 | 50 | ChangeWorldInfo bool 51 | WorldInfo []config.WorldInfo 52 | WorldInfoPickType uint 53 | WorldInfoMerge bool 54 | 55 | NextMapSeed int 56 | 57 | ChangeHostName bool 58 | HostName string 59 | NextHostName string 60 | 61 | MergeWarnings bool 62 | WarningMessages []config.WarningMessage 63 | 64 | InternalData Internal 65 | } 66 | 67 | func ProcessData(data *Data, cfg *config.Config, srv *config.Server) error { 68 | // Make sure we have a valid server. This should never be the case since the array is preallocated to my understanding (and therefore never nil). 69 | if srv == nil { 70 | return errors.New("Could not find server at index.") 71 | } 72 | 73 | // Check for API URL override. 74 | apiurl := cfg.APIURL 75 | 76 | if srv.APIURL != nil { 77 | if len(*srv.APIURL) > 0 { 78 | apiurl = *srv.APIURL 79 | } 80 | } 81 | 82 | data.APIURL = apiurl 83 | 84 | // Check for API token override. 85 | apitoken := cfg.APIToken 86 | 87 | if srv.APIToken != nil { 88 | if len(*srv.APIToken) > 0 { 89 | apitoken = *srv.APIToken 90 | } 91 | } 92 | 93 | data.APIToken = apitoken 94 | 95 | // Check for debug level override. 96 | debuglevel := cfg.DebugLevel 97 | 98 | if srv.DebugLevel != nil { 99 | debuglevel = *srv.DebugLevel 100 | } 101 | 102 | data.DebugLevel = debuglevel 103 | 104 | // Check for path to server files override. 105 | pathtoserverfiles := cfg.PathToServerFiles 106 | 107 | if srv.PathToServerFiles != nil { 108 | if len(*srv.PathToServerFiles) > 0 { 109 | pathtoserverfiles = *srv.PathToServerFiles 110 | } 111 | } 112 | 113 | data.PathToServerFiles = pathtoserverfiles 114 | 115 | // Check for time zone override. 116 | timezone := cfg.Timezone 117 | 118 | if srv.Timezone != nil && len(*srv.Timezone) > 0 { 119 | if len(*srv.Timezone) > 0 { 120 | timezone = *srv.Timezone 121 | } 122 | } 123 | 124 | data.TimeZone = timezone 125 | 126 | // Check for cron merge override. 127 | cron_merge := cfg.CronMerge 128 | 129 | if srv.CronMerge != nil { 130 | cron_merge = *srv.CronMerge 131 | } 132 | 133 | data.CronMerge = cron_merge 134 | 135 | // Check for wipe time override. 136 | var crons []string 137 | 138 | cron_str := cfg.CronStr 139 | 140 | // Add defaults to cron string slice. 141 | s := reflect.ValueOf(cron_str) 142 | 143 | if s.Kind() == reflect.String && s.Len() > 0 { 144 | crons = append(crons, s.String()) 145 | } else if s.Kind() == reflect.Slice { 146 | for i := 0; i < s.Len(); i++ { 147 | new_cron := s.Index(i).Interface().(string) 148 | 149 | if len(new_cron) < 1 { 150 | continue 151 | } 152 | 153 | crons = append(crons, new_cron) 154 | } 155 | } 156 | 157 | if srv.CronStr != nil { 158 | s = reflect.ValueOf(*srv.CronStr) 159 | 160 | // Check if string. 161 | if s.Kind() == reflect.String && s.Len() > 0 { 162 | tmp := s.String() 163 | 164 | if data.CronMerge { 165 | crons = append(crons, tmp) 166 | } else { 167 | crons = []string{tmp} 168 | } 169 | } else if s.Kind() == reflect.Slice { 170 | for i := 0; i < s.Len(); i++ { 171 | new_cron := s.Index(i).Interface().(string) 172 | 173 | if len(new_cron) < 1 { 174 | continue 175 | } 176 | 177 | if !data.CronMerge { 178 | crons = []string{} 179 | } 180 | 181 | crons = append(crons, new_cron) 182 | } 183 | } 184 | } 185 | 186 | data.CronStr = crons 187 | 188 | // Check for delete map override. 189 | deletemap := cfg.DeleteMap 190 | 191 | if srv.DeleteMap != nil { 192 | deletemap = *srv.DeleteMap 193 | } 194 | 195 | data.DeleteMap = deletemap 196 | 197 | // Check for delete blueprint override. 198 | deletebp := cfg.DeleteBP 199 | 200 | if srv.DeleteBP != nil { 201 | deletebp = *srv.DeleteBP 202 | } 203 | 204 | data.DeleteBP = deletebp 205 | 206 | // Check for delete deaths override. 207 | delete_deaths := cfg.DeleteDeaths 208 | 209 | if srv.DeleteDeaths != nil { 210 | delete_deaths = *srv.DeleteDeaths 211 | } 212 | 213 | data.DeleteDeaths = delete_deaths 214 | 215 | // Check for delete states override. 216 | delete_states := cfg.DeleteStates 217 | 218 | if srv.DeleteStates != nil { 219 | delete_states = *srv.DeleteStates 220 | } 221 | 222 | data.DeleteStates = delete_states 223 | 224 | // Check for delete identities override. 225 | delete_identities := cfg.DeleteIdentities 226 | 227 | if srv.DeleteIdentities != nil { 228 | delete_identities = *srv.DeleteIdentities 229 | } 230 | 231 | data.DeleteIdentities = delete_identities 232 | 233 | // Check for delete tokens override. 234 | delete_tokens := cfg.DeleteTokens 235 | 236 | if srv.DeleteTokens != nil { 237 | delete_tokens = *srv.DeleteTokens 238 | } 239 | 240 | data.DeleteTokens = delete_tokens 241 | 242 | // Check for delete player data override. 243 | deletesv := cfg.DeleteSv 244 | 245 | if srv.DeleteSv != nil { 246 | deletesv = *srv.DeleteSv 247 | } 248 | 249 | data.DeleteSv = deletesv 250 | 251 | // Check for warnings merge override. 252 | delete_files_merge := cfg.DeleteFilesMerge 253 | 254 | if srv.DeleteFilesMerge != nil { 255 | delete_files_merge = *srv.DeleteFilesMerge 256 | } 257 | 258 | data.DeleteFilesMerge = delete_files_merge 259 | 260 | // Check for warnings override. 261 | delete_files := cfg.DeleteFiles 262 | 263 | // Check if we need to merge warning messages or override. 264 | if srv.DeleteFiles != nil { 265 | if delete_files_merge { 266 | for _, v := range *srv.DeleteFiles { 267 | delete_files = append(delete_files, v) 268 | } 269 | } else { 270 | delete_files = *srv.DeleteFiles 271 | } 272 | } 273 | 274 | data.DeleteFiles = delete_files 275 | 276 | // Check for change world info override. 277 | changeworldinfo := cfg.ChangeWorldInfo 278 | 279 | if srv.ChangeWorldInfo != nil { 280 | changeworldinfo = *srv.ChangeWorldInfo 281 | } 282 | 283 | data.ChangeWorldInfo = changeworldinfo 284 | 285 | // Check for world info merge in server-specific settings. 286 | worldinfomerge := false 287 | 288 | if srv.WorldInfoMerge != nil { 289 | worldinfomerge = *srv.WorldInfoMerge 290 | } 291 | 292 | data.WorldInfoMerge = worldinfomerge 293 | 294 | // Check for world info override. 295 | world_info := cfg.WorldInfo 296 | 297 | if srv.WorldInfo != nil { 298 | if data.WorldInfoMerge { 299 | for _, v := range *srv.WorldInfo { 300 | world_info = append(world_info, v) 301 | } 302 | } else { 303 | world_info = *srv.WorldInfo 304 | } 305 | } 306 | 307 | data.WorldInfo = world_info 308 | 309 | // Check for world seed pick type override. 310 | world_info_pick_type := cfg.WorldInfoPickType 311 | 312 | if srv.WorldInfoPickType != nil { 313 | world_info_pick_type = *srv.WorldInfoPickType 314 | } 315 | 316 | data.WorldInfoPickType = uint(world_info_pick_type) 317 | 318 | // Check for change host name override. 319 | changehostname := cfg.ChangeHostName 320 | 321 | if srv.ChangeHostName != nil { 322 | changehostname = *srv.ChangeHostName 323 | } 324 | 325 | data.ChangeHostName = changehostname 326 | 327 | // Check for host name override. 328 | hostname := cfg.HostName 329 | 330 | if srv.HostName != nil { 331 | if len(*srv.HostName) > 0 { 332 | hostname = *srv.HostName 333 | } 334 | } 335 | 336 | data.HostName = hostname 337 | 338 | // Check for warnings merge override. 339 | merge_warnings := cfg.MergeWarnings 340 | 341 | if srv.MergeWarnings != nil { 342 | merge_warnings = *srv.MergeWarnings 343 | } 344 | 345 | data.MergeWarnings = merge_warnings 346 | 347 | // Check for warnings override. 348 | warning_messages := cfg.WarningMessages 349 | 350 | // Check if we need to merge warning messages or override. 351 | if srv.WarningMessages != nil { 352 | if merge_warnings { 353 | for _, v := range *srv.WarningMessages { 354 | warning_messages = append(warning_messages, v) 355 | } 356 | } else { 357 | warning_messages = *srv.WarningMessages 358 | } 359 | } 360 | 361 | data.WarningMessages = warning_messages 362 | 363 | // Fill out null data for world information. 364 | ep := "client/servers/" + srv.UUID + "/startup" 365 | 366 | // We first need to retrieve the current variable. 367 | d, _, err := pterodactyl.SendAPIRequest(data.APIURL, data.APIToken, "GET", ep, nil) 368 | 369 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 3, "Sending request. Request => "+data.APIURL+"api/"+ep+". Post data => nil.") 370 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 4, "List Variable return data => "+d+".") 371 | 372 | if pterodactyl.IsError(d) { 373 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 0, "Could not list startup variables. Please enable debugging level 4 for body response including errors.") 374 | 375 | return errors.New("Could not list startup variables.") 376 | } 377 | 378 | if err != nil { 379 | return err 380 | } 381 | 382 | // We want to parse the response with the startup response structure. 383 | var EnvVars pterodactyl.StartupResp 384 | 385 | // Convert to JSON. 386 | err = json.Unmarshal([]byte(d), &EnvVars) 387 | 388 | if err != nil { 389 | return err 390 | } 391 | 392 | for _, seed := range EnvVars.Data { 393 | if seed.Attributes.Env_Variable == "WORLD_SEED" && len(seed.Attributes.Env_Variable) > 0 { 394 | s := seed.Attributes.Srv_Value 395 | 396 | for i := 0; i < len(data.WorldInfo); i++ { 397 | if data.WorldInfo[i].WorldSeed == nil { 398 | s_int, err := strconv.Atoi(s) 399 | 400 | if err != nil { 401 | fmt.Println(err) 402 | 403 | continue 404 | } 405 | 406 | data.WorldInfo[i].WorldSeed = &s_int 407 | } 408 | } 409 | } 410 | 411 | if seed.Attributes.Env_Variable == "WORLD_SIZE" && len(seed.Attributes.Env_Variable) > 0 { 412 | s := seed.Attributes.Srv_Value 413 | 414 | for i := 0; i < len(data.WorldInfo); i++ { 415 | if data.WorldInfo[i].WorldSize == nil { 416 | s_int, err := strconv.Atoi(s) 417 | 418 | if err != nil { 419 | fmt.Println(err) 420 | 421 | continue 422 | } 423 | 424 | data.WorldInfo[i].WorldSize = &s_int 425 | } 426 | } 427 | } 428 | 429 | if seed.Attributes.Env_Variable == "LEVEL" && len(seed.Attributes.Env_Variable) > 0 { 430 | s := seed.Attributes.Srv_Value 431 | 432 | for i := 0; i < len(data.WorldInfo); i++ { 433 | if data.WorldInfo[i].Map == nil { 434 | data.WorldInfo[i].Map = &s 435 | } 436 | } 437 | } 438 | } 439 | 440 | // Loop through world information one more time and fill out anything missed with constants. 441 | for i := 0; i < len(data.WorldInfo); i++ { 442 | if data.WorldInfo[i].Map == nil { 443 | tmp := Default_Map 444 | 445 | data.WorldInfo[i].Map = &tmp 446 | } 447 | 448 | if data.WorldInfo[i].WorldSize == nil { 449 | tmp := Default_WorldSeed 450 | 451 | data.WorldInfo[i].WorldSize = &tmp 452 | } 453 | 454 | if data.WorldInfo[i].WorldSeed == nil { 455 | tmp := Default_WorldSize 456 | 457 | data.WorldInfo[i].WorldSize = &tmp 458 | } 459 | } 460 | 461 | // Loop through warning messages and fill out warning message string if nil. 462 | for i := 0; i < len(data.WarningMessages); i++ { 463 | if data.WarningMessages[i].Message == nil { 464 | tmp := Default_WarningMessage 465 | 466 | data.WarningMessages[i].Message = &tmp 467 | } 468 | } 469 | 470 | return nil 471 | } 472 | -------------------------------------------------------------------------------- /internal/wipe/data_env.go: -------------------------------------------------------------------------------- 1 | package wipe 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/gamemann/Rust-Auto-Wipe/internal/autoaddservers" 9 | "github.com/gamemann/Rust-Auto-Wipe/internal/config" 10 | "github.com/gamemann/Rust-Auto-Wipe/pkg/debug" 11 | "github.com/gamemann/Rust-Auto-Wipe/pkg/pterodactyl" 12 | ) 13 | 14 | type ServerDetails struct { 15 | Object string `json:"object"` 16 | Attributes struct { 17 | ID int `json:"id"` 18 | ExternalID interface{} `json:"external_id"` 19 | UUID string `json:"uuid"` 20 | Identifier string `json:"identifier"` 21 | Name string `json:"name"` 22 | Description string `json:"description"` 23 | Status interface{} `json:"status"` 24 | Suspended bool `json:"suspended"` 25 | Limits struct { 26 | Memory int `json:"memory"` 27 | Swap int `json:"swap"` 28 | Disk int `json:"disk"` 29 | Io int `json:"io"` 30 | CPU int `json:"cpu"` 31 | Threads interface{} `json:"threads"` 32 | OomDisabled bool `json:"oom_disabled"` 33 | } `json:"limits"` 34 | FeatureLimits struct { 35 | Databases int `json:"databases"` 36 | Allocations int `json:"allocations"` 37 | Backups int `json:"backups"` 38 | } `json:"feature_limits"` 39 | User int `json:"user"` 40 | Node int `json:"node"` 41 | Allocation int `json:"allocation"` 42 | Nest int `json:"nest"` 43 | Egg int `json:"egg"` 44 | Container struct { 45 | StartupCommand string `json:"startup_command"` 46 | Image string `json:"image"` 47 | Installed int `json:"installed"` 48 | Environment autoaddservers.RawEnv `json:"environment"` 49 | } `json:"container"` 50 | UpdatedAt time.Time `json:"updated_at"` 51 | CreatedAt time.Time `json:"created_at"` 52 | } `json:"attributes"` 53 | } 54 | 55 | func EnvOverride(cfg *config.Config, srv *config.Server) error { 56 | var err error 57 | 58 | ep := "application/servers/" + strconv.Itoa(srv.ID) 59 | 60 | d, _, err := pterodactyl.SendAPIRequest(cfg.APIURL, cfg.AppToken, "GET", ep, nil) 61 | 62 | debug.SendDebugMsg(srv.UUID, cfg.DebugLevel, 3, "Sending request. Request => "+cfg.APIURL+"api/"+ep+". Post data => nil.") 63 | debug.SendDebugMsg(srv.UUID, cfg.DebugLevel, 4, "List Server Details return data => "+d+".") 64 | 65 | if err != nil { 66 | return err 67 | } 68 | 69 | // Convert JSON to structure. 70 | var server_details ServerDetails 71 | 72 | err = json.Unmarshal([]byte(d), &server_details) 73 | 74 | if err != nil { 75 | return err 76 | } 77 | 78 | env := server_details.Attributes.Container.Environment 79 | 80 | // Enabled override. 81 | if env.RAW_Enabled != nil && len(*env.RAW_Enabled) > 0 { 82 | srv.Enabled, _ = strconv.ParseBool(*env.RAW_Enabled) 83 | } 84 | 85 | // Path to server files override. 86 | if env.RAW_PathToServerFiles != nil && len(*env.RAW_PathToServerFiles) > 0 { 87 | // Make sure we don't need to allocate memory. 88 | if srv.PathToServerFiles == nil { 89 | var val string 90 | srv.PathToServerFiles = &val 91 | } 92 | 93 | *srv.PathToServerFiles = *env.RAW_PathToServerFiles 94 | } 95 | 96 | // Timezone override. 97 | if env.RAW_Timezone != nil && len(*env.RAW_Timezone) > 0 { 98 | // Make sure we don't need to allocate memory. 99 | if srv.Timezone == nil { 100 | var val string 101 | srv.Timezone = &val 102 | } 103 | 104 | *srv.Timezone = *env.RAW_Timezone 105 | } 106 | 107 | // Cron merge override. 108 | if env.RAW_CronMerge != nil && len(*env.RAW_CronMerge) > 0 { 109 | // Make sure we don't need to allocate memory. 110 | if srv.CronMerge == nil { 111 | var val bool 112 | srv.CronMerge = &val 113 | } 114 | 115 | *srv.CronMerge, _ = strconv.ParseBool(*env.RAW_CronMerge) 116 | } 117 | 118 | // Cron string override. 119 | if env.RAW_CronStr != nil && len(*env.RAW_CronStr) > 0 { 120 | // Make sure we don't need to allocate memory. 121 | if srv.CronStr == nil { 122 | var val interface{} 123 | srv.CronStr = &val 124 | } 125 | 126 | s := *env.RAW_CronStr 127 | var tmp []string 128 | 129 | // Try to parse as JSON, if fails, parse as string. 130 | err := json.Unmarshal([]byte(s), &tmp) 131 | 132 | var str interface{} = "" 133 | 134 | if srv.CronStr == nil { 135 | srv.CronStr = &str 136 | } 137 | 138 | if err != nil { 139 | *srv.CronStr = *env.RAW_CronStr 140 | } else { 141 | *srv.CronStr = tmp 142 | } 143 | } 144 | 145 | // Delete map override. 146 | if env.RAW_DeleteMap != nil && len(*env.RAW_DeleteMap) > 0 { 147 | // Make sure we don't need to allocate memory. 148 | if srv.DeleteMap == nil { 149 | var val bool 150 | srv.DeleteMap = &val 151 | } 152 | 153 | *srv.DeleteMap, _ = strconv.ParseBool(*env.RAW_DeleteMap) 154 | } 155 | 156 | // Delete blueprints override. 157 | if env.RAW_DeleteBP != nil && len(*env.RAW_DeleteBP) > 0 { 158 | // Make sure we don't need to allocate memory. 159 | if srv.DeleteBP == nil { 160 | var val bool 161 | srv.DeleteBP = &val 162 | } 163 | 164 | *srv.DeleteBP, _ = strconv.ParseBool(*env.RAW_DeleteBP) 165 | } 166 | 167 | // Delete deaths override. 168 | if env.RAW_DeleteDeaths != nil && len(*env.RAW_DeleteDeaths) > 0 { 169 | // Make sure we don't need to allocate memory. 170 | if srv.DeleteDeaths == nil { 171 | var val bool 172 | srv.DeleteDeaths = &val 173 | } 174 | 175 | *srv.DeleteDeaths, _ = strconv.ParseBool(*env.RAW_DeleteDeaths) 176 | } 177 | 178 | // Delete states override. 179 | if env.RAW_DeleteStates != nil && len(*env.RAW_DeleteStates) > 0 { 180 | // Make sure we don't need to allocate memory. 181 | if srv.DeleteStates == nil { 182 | var val bool 183 | srv.DeleteStates = &val 184 | } 185 | 186 | *srv.DeleteStates, _ = strconv.ParseBool(*env.RAW_DeleteStates) 187 | } 188 | 189 | // Delete identities override. 190 | if env.RAW_DeleteIdentities != nil && len(*env.RAW_DeleteIdentities) > 0 { 191 | // Make sure we don't need to allocate memory. 192 | if srv.DeleteIdentities == nil { 193 | var val bool 194 | srv.DeleteIdentities = &val 195 | } 196 | 197 | *srv.DeleteIdentities, _ = strconv.ParseBool(*env.RAW_DeleteIdentities) 198 | } 199 | // Delete tokens override. 200 | if env.RAW_DeleteTokens != nil && len(*env.RAW_DeleteTokens) > 0 { 201 | // Make sure we don't need to allocate memory. 202 | if srv.DeleteTokens == nil { 203 | var val bool 204 | srv.DeleteTokens = &val 205 | } 206 | 207 | *srv.DeleteTokens, _ = strconv.ParseBool(*env.RAW_DeleteTokens) 208 | } 209 | 210 | // Merge delete files override. 211 | if env.RAW_DeleteFilesMerge != nil && len(*env.RAW_DeleteFilesMerge) > 0 { 212 | // Make sure we don't need to allocate memory. 213 | if srv.DeleteFilesMerge == nil { 214 | var val bool 215 | srv.DeleteFilesMerge = &val 216 | } 217 | 218 | *srv.DeleteFilesMerge, _ = strconv.ParseBool(*env.RAW_DeleteFilesMerge) 219 | } 220 | 221 | // Additional file deletions 222 | if env.RAW_DeleteFiles != nil && len(*env.RAW_DeleteFiles) > 0 { 223 | // Make sure we don't need to allocate memory. 224 | if srv.DeleteFiles == nil { 225 | var val []config.AdditionalFiles 226 | srv.DeleteFiles = &val 227 | } 228 | 229 | // Parse as string. 230 | data := *env.RAW_DeleteFiles 231 | 232 | // Create structure for expected format. 233 | var delete_file []config.AdditionalFiles 234 | 235 | // Convert string to structure via JSON. 236 | err := json.Unmarshal([]byte(data), &delete_file) 237 | 238 | if err == nil { 239 | *srv.DeleteFiles = delete_file 240 | } 241 | } 242 | 243 | // Delete server files/data override. 244 | if env.RAW_DeleteSv != nil && len(*env.RAW_DeleteSv) > 0 { 245 | // Make sure we don't need to allocate memory. 246 | if srv.DeleteSv == nil { 247 | var val bool 248 | srv.DeleteSv = &val 249 | } 250 | 251 | *srv.DeleteSv, _ = strconv.ParseBool(*env.RAW_DeleteSv) 252 | } 253 | 254 | // Change world info override. 255 | if env.RAW_ChangeWorldInfo != nil && len(*env.RAW_ChangeWorldInfo) > 0 { 256 | // Make sure we don't need to allocate memory. 257 | if srv.ChangeWorldInfo == nil { 258 | var val bool 259 | srv.ChangeWorldInfo = &val 260 | } 261 | 262 | *srv.ChangeWorldInfo, _ = strconv.ParseBool(*env.RAW_ChangeWorldInfo) 263 | } 264 | 265 | // World info override. 266 | if env.RAW_WorldInfo != nil && len(*env.RAW_WorldInfo) > 0 { 267 | // Make sure we don't need to allocate memory. 268 | if srv.WorldInfo == nil { 269 | var val []config.WorldInfo 270 | srv.WorldInfo = &val 271 | } 272 | 273 | s := *env.RAW_WorldInfo 274 | var tmp []config.WorldInfo 275 | 276 | // Try to parse as JSON, if fails, parse as string. 277 | err := json.Unmarshal([]byte(s), &tmp) 278 | 279 | if err == nil { 280 | *srv.WorldInfo = tmp 281 | } 282 | } 283 | 284 | // Change world info pick type override. 285 | if env.RAW_WorldInfoPickType != nil && len(*env.RAW_WorldInfoPickType) > 0 { 286 | // Make sure we don't need to allocate memory. 287 | if srv.WorldInfoPickType == nil { 288 | var val int 289 | srv.WorldInfoPickType = &val 290 | } 291 | 292 | val, _ := strconv.ParseInt(*env.RAW_WorldInfoPickType, 10, 16) 293 | 294 | *srv.WorldInfoPickType = int(val) 295 | } 296 | 297 | // Change world info merge override. 298 | if env.RAW_WorldInfoMerge != nil && len(*env.RAW_WorldInfoMerge) > 0 { 299 | // Make sure we don't need to allocate memory. 300 | if srv.WorldInfoMerge == nil { 301 | var val bool 302 | srv.WorldInfoMerge = &val 303 | } 304 | 305 | *srv.WorldInfoMerge, _ = strconv.ParseBool(*env.RAW_WorldInfoMerge) 306 | } 307 | 308 | // Change hostname override. 309 | if env.RAW_ChangeHostname != nil && len(*env.RAW_ChangeHostname) > 0 { 310 | // Make sure we don't need to allocate memory. 311 | if srv.ChangeHostName == nil { 312 | var val bool 313 | srv.ChangeHostName = &val 314 | } 315 | 316 | *srv.ChangeHostName, _ = strconv.ParseBool(*env.RAW_ChangeHostname) 317 | } 318 | 319 | // Hostname override. 320 | if env.RAW_Hostname != nil && len(*env.RAW_Hostname) > 0 { 321 | // Make sure we don't need to allocate memory. 322 | if srv.HostName == nil { 323 | var val string 324 | srv.HostName = &val 325 | } 326 | 327 | *srv.HostName = *env.RAW_Hostname 328 | } 329 | 330 | // Merge warnings override. 331 | if env.RAW_MergeWarnings != nil && len(*env.RAW_MergeWarnings) > 0 { 332 | // Make sure we don't need to allocate memory. 333 | if srv.MergeWarnings == nil { 334 | var val bool 335 | srv.MergeWarnings = &val 336 | } 337 | 338 | *srv.MergeWarnings, _ = strconv.ParseBool(*env.RAW_MergeWarnings) 339 | } 340 | 341 | // Wipe first override. 342 | if env.RAW_WipeFirst != nil && len(*env.RAW_WipeFirst) > 0 { 343 | srv.WipeFirst, _ = strconv.ParseBool(*env.RAW_WipeFirst) 344 | } 345 | 346 | // Warning messages override (another special case). 347 | if env.RAW_WarningMessages != nil && len(*env.RAW_WarningMessages) > 0 { 348 | // Make sure we don't need to allocate memory. 349 | if srv.WarningMessages == nil { 350 | var val []config.WarningMessage 351 | srv.WarningMessages = &val 352 | } 353 | 354 | // Parse as string. 355 | data := *env.RAW_WarningMessages 356 | 357 | // Create structure for expected format. 358 | var warning_msg []config.WarningMessage 359 | 360 | // Convert string to structure via JSON. 361 | err := json.Unmarshal([]byte(data), &warning_msg) 362 | 363 | if err == nil { 364 | *srv.WarningMessages = warning_msg 365 | } 366 | } 367 | 368 | return err 369 | } 370 | -------------------------------------------------------------------------------- /internal/wipe/files.go: -------------------------------------------------------------------------------- 1 | package wipe 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/url" 7 | "strings" 8 | 9 | "github.com/gamemann/Rust-Auto-Wipe/pkg/debug" 10 | "github.com/gamemann/Rust-Auto-Wipe/pkg/misc" 11 | "github.com/gamemann/Rust-Auto-Wipe/pkg/pterodactyl" 12 | ) 13 | 14 | // Processes files. Deletes files depending on the type of wipe. 15 | func ProcessFiles(data *Data, UUID string) bool { 16 | var files_to_delete []string 17 | 18 | // Make sure to URL encode the query string (directory path). 19 | dir := url.QueryEscape(data.PathToServerFiles) 20 | 21 | ep := "client/servers/" + UUID + "/files/list?directory=" + dir 22 | 23 | // We first need to retrieve the current variable. 24 | d, _, err := pterodactyl.SendAPIRequest(data.APIURL, data.APIToken, "GET", ep, nil) 25 | 26 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "Sending request. Request => "+data.APIURL+"api/"+ep+". Post data => nil.") 27 | debug.SendDebugMsg(UUID, data.DebugLevel, 4, "List Files return data => "+d+".") 28 | 29 | if pterodactyl.IsError(d) { 30 | debug.SendDebugMsg(UUID, data.DebugLevel, 0, "Could not list files in directory ("+dir+"). Please enable debugging level 4 for body response including errors.") 31 | 32 | return false 33 | } 34 | 35 | if err != nil { 36 | fmt.Println(err) 37 | 38 | return false 39 | } 40 | 41 | // We want to parse the response with the startup response structure. 42 | var files_list pterodactyl.ListFilesResp 43 | 44 | // Convert to JSON. 45 | err = json.Unmarshal([]byte(d), &files_list) 46 | 47 | if err != nil { 48 | fmt.Println(err) 49 | 50 | return false 51 | } 52 | 53 | // Loop through all files. 54 | for _, file := range files_list.Data { 55 | // Make sure we're not dealing with a directory or link. 56 | if !file.Attributes.IsFile || file.Attributes.IsSymlink { 57 | continue 58 | } 59 | 60 | add_to_del := false 61 | 62 | // Check if we want to wipe the map/save files. 63 | if data.DeleteMap { 64 | if strings.Contains(file.Attributes.Name, ".sav") || strings.Contains(file.Attributes.Name, ".map") { 65 | // Append file to list of files to delete. 66 | add_to_del = true 67 | } 68 | } 69 | 70 | // Check if we want to wipe blue prints. 71 | if data.DeleteBP { 72 | if strings.Contains(file.Attributes.Name, "blueprints") { 73 | add_to_del = true 74 | } 75 | } 76 | 77 | // Check if we want to wipe deaths. 78 | if data.DeleteDeaths { 79 | if strings.Contains(file.Attributes.Name, "deaths") { 80 | add_to_del = true 81 | } 82 | } 83 | 84 | // Check if we want to wipe states. 85 | if data.DeleteStates { 86 | if strings.Contains(file.Attributes.Name, "states") { 87 | add_to_del = true 88 | } 89 | } 90 | 91 | // Check if we want to wipe identities. 92 | if data.DeleteIdentities { 93 | if strings.Contains(file.Attributes.Name, "identities") { 94 | add_to_del = true 95 | } 96 | } 97 | 98 | // Check if we want to wipe tokens. 99 | if data.DeleteTokens { 100 | if strings.Contains(file.Attributes.Name, "tokens") { 101 | add_to_del = true 102 | } 103 | } 104 | 105 | // Check if we want to wipe server files/data. 106 | if data.DeleteSv { 107 | if strings.Contains(file.Attributes.Name, "sv.files") { 108 | add_to_del = true 109 | } 110 | } 111 | 112 | // If we want to delete the file, add it to the delete list. 113 | if add_to_del { 114 | files_to_delete = append(files_to_delete, file.Attributes.Name) 115 | } 116 | } 117 | 118 | // Prepare to delete files. 119 | post_data := make(map[string]interface{}) 120 | post_data["root"] = data.PathToServerFiles 121 | post_data["files"] = files_to_delete 122 | 123 | // Debug. 124 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "Deleting files => "+strings.Join(files_to_delete, ", ")+" (directory = "+data.PathToServerFiles+").") 125 | 126 | ep = "client/servers/" + UUID + "/files/delete" 127 | 128 | // We first need to retrieve the current variable. 129 | d, _, err = pterodactyl.SendAPIRequest(data.APIURL, data.APIToken, "POST", ep, post_data) 130 | 131 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "Sending request. Request => "+data.APIURL+"api/"+ep+". Post data => "+misc.CreateKeyPairs(post_data)+".") 132 | debug.SendDebugMsg(UUID, data.DebugLevel, 4, "Delete Files return data => "+d+".") 133 | 134 | if pterodactyl.IsError(d) { 135 | debug.SendDebugMsg(UUID, data.DebugLevel, 0, "Could not delete files in directory ("+dir+"). Please enable debugging level 4 for body response including errors.") 136 | 137 | return false 138 | } 139 | 140 | if err != nil { 141 | fmt.Println(err) 142 | 143 | return false 144 | } 145 | 146 | if len(data.DeleteFiles) > 0 { 147 | debug.SendDebugMsg(UUID, data.DebugLevel, 2, "Deleting additional files...") 148 | } 149 | 150 | // Now delete additional files. 151 | for _, v := range data.DeleteFiles { 152 | var files_to_delete []string 153 | 154 | // Firstly, to support wildcards and such, we need to list all the files and then add them to the array if found (supports full paths as well). 155 | // Make sure to URL encode the query string (directory/root path). 156 | dir := url.QueryEscape(v.Root) 157 | 158 | ep := "client/servers/" + UUID + "/files/list?directory=" + dir 159 | 160 | // We first need to retrieve the current variable. 161 | d, _, err := pterodactyl.SendAPIRequest(data.APIURL, data.APIToken, "GET", ep, nil) 162 | 163 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "Sending request. Request => "+data.APIURL+"api/"+ep+". Post data => nil.") 164 | debug.SendDebugMsg(UUID, data.DebugLevel, 4, "List Files return data => "+d+".") 165 | 166 | if pterodactyl.IsError(d) { 167 | debug.SendDebugMsg(UUID, data.DebugLevel, 0, "Could not list files in directory ("+dir+"). Please enable debugging level 4 for body response including errors.") 168 | 169 | return false 170 | } 171 | 172 | if err != nil { 173 | fmt.Println(err) 174 | 175 | return false 176 | } 177 | 178 | // We want to parse the response with the startup response structure. 179 | var files_list pterodactyl.ListFilesResp 180 | 181 | // Convert to JSON. 182 | err = json.Unmarshal([]byte(d), &files_list) 183 | 184 | if err != nil { 185 | fmt.Println(err) 186 | 187 | return false 188 | } 189 | 190 | for _, file := range files_list.Data { 191 | // Make sure we're not dealing with a directory or link. 192 | if !file.Attributes.IsFile || file.Attributes.IsSymlink { 193 | continue 194 | } 195 | 196 | // Now loop through all of our additional files and see if any in the directory contain the string. If so, add it to the delete files list. 197 | for _, file_str := range v.Files { 198 | if strings.Contains(file.Attributes.Name, file_str) { 199 | // Append file to list since found. 200 | files_to_delete = append(files_to_delete, file.Attributes.Name) 201 | } 202 | } 203 | } 204 | 205 | // Make new POST request. 206 | post_data := make(map[string]interface{}) 207 | post_data["root"] = v.Root 208 | post_data["files"] = files_to_delete 209 | 210 | // Debug. 211 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "Deleting additional files => "+strings.Join(files_to_delete, ", ")+" (directory = "+data.PathToServerFiles+").") 212 | 213 | ep = "client/servers/" + UUID + "/files/delete" 214 | 215 | // We first need to retrieve the current variable. 216 | d, _, err = pterodactyl.SendAPIRequest(data.APIURL, data.APIToken, "POST", ep, post_data) 217 | 218 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "Sending request. Request => "+data.APIURL+"api/"+ep+". Post data => "+misc.CreateKeyPairs(post_data)+".") 219 | debug.SendDebugMsg(UUID, data.DebugLevel, 4, "Additional Delete Files return data => "+d+".") 220 | 221 | if pterodactyl.IsError(d) { 222 | debug.SendDebugMsg(UUID, data.DebugLevel, 0, "Could not delete additional files in directory ("+dir+"). Please enable debugging level 4 for body response including errors.") 223 | 224 | return false 225 | } 226 | 227 | if err != nil { 228 | fmt.Println(err) 229 | 230 | return false 231 | } 232 | } 233 | 234 | return true 235 | } 236 | -------------------------------------------------------------------------------- /internal/wipe/hostname.go: -------------------------------------------------------------------------------- 1 | package wipe 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gamemann/Rust-Auto-Wipe/pkg/debug" 7 | "github.com/gamemann/Rust-Auto-Wipe/pkg/format" 8 | "github.com/gamemann/Rust-Auto-Wipe/pkg/misc" 9 | "github.com/gamemann/Rust-Auto-Wipe/pkg/pterodactyl" 10 | ) 11 | 12 | // Sets the next host name to use. 13 | func ProcessHostName(data *Data, UUID string) bool { 14 | hostname := data.HostName 15 | 16 | // Format hostname. 17 | format.FormatString(&hostname, 0) 18 | 19 | // Now convert to proper POST data. 20 | post_data := make(map[string]interface{}) 21 | post_data["key"] = "HOSTNAME" 22 | post_data["value"] = hostname 23 | 24 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "Setting hostname => \""+hostname+"\".") 25 | 26 | ep := "client/servers/" + UUID + "/startup/variable" 27 | 28 | // Send API request to update host name variable. 29 | d, _, err := pterodactyl.SendAPIRequest(data.APIURL, data.APIToken, "PUT", ep, post_data) 30 | 31 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "Sending request. Request => "+data.APIURL+"api/"+ep+". Post data => "+misc.CreateKeyPairs(post_data)+".") 32 | debug.SendDebugMsg(UUID, data.DebugLevel, 4, "Update Variable return data => "+d+".") 33 | 34 | if pterodactyl.IsError(d) { 35 | debug.SendDebugMsg(UUID, data.DebugLevel, 0, "Could not update hostname. Please enable debugging level 4 for body response including errors.") 36 | 37 | return false 38 | } 39 | 40 | if err != nil { 41 | fmt.Println(err) 42 | 43 | return false 44 | } 45 | 46 | return true 47 | } 48 | -------------------------------------------------------------------------------- /internal/wipe/server.go: -------------------------------------------------------------------------------- 1 | package wipe 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | 7 | "github.com/gamemann/Rust-Auto-Wipe/pkg/debug" 8 | "github.com/gamemann/Rust-Auto-Wipe/pkg/misc" 9 | "github.com/gamemann/Rust-Auto-Wipe/pkg/pterodactyl" 10 | ) 11 | 12 | type ServerResources struct { 13 | Object string `json:"object"` 14 | Attributes struct { 15 | CurrentState string `json:"current_state"` 16 | IsSuspended bool `json:"is_suspended"` 17 | Resources struct { 18 | MemoryBytes int64 `json:"memory_bytes"` 19 | CPUAbsolute float64 `json:"cpu_absolute"` 20 | DiskBytes int64 `json:"disk_bytes"` 21 | NetworkRxBytes int `json:"network_rx_bytes"` 22 | NetworkTxBytes int `json:"network_tx_bytes"` 23 | Uptime int `json:"uptime"` 24 | } `json:"resources"` 25 | } `json:"attributes"` 26 | } 27 | 28 | func StartServer(data *Data, UUID string) error { 29 | 30 | err := SendPowerCommand(data, UUID, "start") 31 | 32 | return err 33 | } 34 | 35 | func StopServer(data *Data, UUID string) error { 36 | 37 | err := SendPowerCommand(data, UUID, "stop") 38 | 39 | return err 40 | } 41 | 42 | func KillServer(data *Data, UUID string) error { 43 | 44 | err := SendPowerCommand(data, UUID, "kill") 45 | 46 | return err 47 | } 48 | 49 | func GetServerState(data *Data, UUID string) (string, error) { 50 | var state string = "stopped" 51 | var err error 52 | 53 | ep := "client/servers/" + UUID + "/resources" 54 | 55 | d, _, err := pterodactyl.SendAPIRequest(data.APIURL, data.APIToken, "GET", ep, nil) 56 | 57 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "Sending request. Request => "+data.APIURL+"api/"+ep+". Post data => nil.") 58 | debug.SendDebugMsg(UUID, data.DebugLevel, 4, "Running State return data => "+d+".") 59 | 60 | if err != nil { 61 | return state, err 62 | } 63 | 64 | if pterodactyl.IsError(d) { 65 | debug.SendDebugMsg(UUID, data.DebugLevel, 0, "Could not get server's running state. Please enable debugging level 4 for body response including errors.") 66 | 67 | return state, err 68 | } 69 | 70 | var resources ServerResources 71 | 72 | err = json.Unmarshal([]byte(d), &resources) 73 | 74 | if err != nil { 75 | return state, err 76 | } 77 | 78 | state = resources.Attributes.CurrentState 79 | 80 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "Server state => "+state+".") 81 | 82 | return state, err 83 | } 84 | 85 | func IsServerRunning(data *Data, UUID string) (bool, error) { 86 | var running bool 87 | var err error 88 | 89 | state, err := GetServerState(data, UUID) 90 | 91 | if err != nil { 92 | return running, err 93 | } 94 | 95 | if state == "running" { 96 | running = true 97 | } 98 | 99 | return running, err 100 | } 101 | 102 | func SendPowerCommand(data *Data, UUID string, cmd string) error { 103 | post_data := make(map[string]interface{}) 104 | post_data["signal"] = cmd 105 | 106 | ep := "client/servers/" + UUID + "/power" 107 | 108 | d, _, err := pterodactyl.SendAPIRequest(data.APIURL, data.APIToken, "POST", ep, post_data) 109 | 110 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "Sending request. Request => "+data.APIURL+"api/"+ep+". Post data => "+misc.CreateKeyPairs(post_data)+".") 111 | debug.SendDebugMsg(UUID, data.DebugLevel, 4, "Power Command return data => "+d+".") 112 | 113 | if pterodactyl.IsError(d) { 114 | debug.SendDebugMsg(UUID, data.DebugLevel, 0, "Could not send power command. Please enable debugging level 4 for body response including errors.") 115 | 116 | return errors.New("Could not send power command.") 117 | } 118 | 119 | return err 120 | } 121 | -------------------------------------------------------------------------------- /internal/wipe/warnings.go: -------------------------------------------------------------------------------- 1 | package wipe 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/gamemann/Rust-Auto-Wipe/pkg/debug" 7 | "github.com/gamemann/Rust-Auto-Wipe/pkg/pterodactyl" 8 | ) 9 | 10 | func SendMessage(data *Data, UUID string, message string) error { 11 | var err error 12 | 13 | // Now convert to proper POST data. 14 | post_data := make(map[string]interface{}) 15 | post_data["command"] = "say " + message 16 | 17 | ep := "client/servers/" + UUID + "/command" 18 | 19 | // Send API request to update host name variable. 20 | d, _, err := pterodactyl.SendAPIRequest(data.APIURL, data.APIToken, "POST", ep, post_data) 21 | 22 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "Sending request. Request => "+data.APIURL+"api/"+ep+". Post data => nil.") 23 | debug.SendDebugMsg(UUID, data.DebugLevel, 4, "Command return data => "+d+".") 24 | 25 | if err != nil { 26 | return err 27 | } 28 | 29 | if pterodactyl.IsError(d) { 30 | debug.SendDebugMsg(UUID, data.DebugLevel, 0, "Could not send command to server (warning say). Please enable debugging level 4 for body response including errors.") 31 | 32 | return errors.New("Could not send command to server (warning say).") 33 | } 34 | 35 | return err 36 | } 37 | -------------------------------------------------------------------------------- /internal/wipe/worldinfo.go: -------------------------------------------------------------------------------- 1 | package wipe 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/gamemann/Rust-Auto-Wipe/internal/config" 10 | "github.com/gamemann/Rust-Auto-Wipe/pkg/debug" 11 | "github.com/gamemann/Rust-Auto-Wipe/pkg/misc" 12 | "github.com/gamemann/Rust-Auto-Wipe/pkg/pterodactyl" 13 | ) 14 | 15 | // Processes world info such as map, size, and seed. 16 | func ProcessWorldInfo(data *Data, UUID string) bool { 17 | cur_world := &data.WorldInfo[data.InternalData.LatestWorld] 18 | 19 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "[WI] Current map => \""+*cur_world.Map+"\".") 20 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "[WI] Current size => \""+strconv.Itoa(*cur_world.WorldSize)+"\".") 21 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "[WI] Current seed => \""+strconv.Itoa(*cur_world.WorldSeed)+"\".") 22 | 23 | // Now get the next world using the GetNextWorld() method. 24 | next_world := GetNextWorld(data) 25 | 26 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "[WI] Next map => \""+*next_world.Map+"\".") 27 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "[WI] Next size => \""+strconv.Itoa(*next_world.WorldSize)+"\".") 28 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "[WI] Next seed => \""+strconv.Itoa(*next_world.WorldSeed)+"\".") 29 | 30 | // First, set world map. 31 | post_data := make(map[string]interface{}) 32 | post_data["key"] = "LEVEL" 33 | post_data["value"] = *next_world.Map 34 | 35 | ep := "client/servers/" + UUID + "/startup/variable" 36 | 37 | // Send API request. 38 | d, _, err := pterodactyl.SendAPIRequest(data.APIURL, data.APIToken, "PUT", ep, post_data) 39 | 40 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "Sending request. Request => "+data.APIURL+"api/"+ep+". Post data => "+misc.CreateKeyPairs(post_data)+".") 41 | debug.SendDebugMsg(UUID, data.DebugLevel, 4, "Update Variable (LEVEL) return data => "+d+".") 42 | 43 | if pterodactyl.IsError(d) { 44 | debug.SendDebugMsg(UUID, data.DebugLevel, 0, "Could not update startup LEVEL variable. Please enable debugging level 4 for body response including errors.") 45 | 46 | return false 47 | } 48 | 49 | if err != nil { 50 | fmt.Println(err) 51 | 52 | return false 53 | } 54 | 55 | // Now, set world size. 56 | post_data = make(map[string]interface{}) 57 | post_data["key"] = "WORLD_SIZE" 58 | post_data["value"] = strconv.Itoa(*next_world.WorldSize) 59 | 60 | ep = "client/servers/" + UUID + "/startup/variable" 61 | 62 | // Send API request. 63 | d, _, err = pterodactyl.SendAPIRequest(data.APIURL, data.APIToken, "PUT", ep, post_data) 64 | 65 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "Sending request. Request => "+data.APIURL+"api/"+ep+". Post data => "+misc.CreateKeyPairs(post_data)+".") 66 | debug.SendDebugMsg(UUID, data.DebugLevel, 4, "Update Variable (WORLD_SIZE) return data => "+d+".") 67 | 68 | if pterodactyl.IsError(d) { 69 | debug.SendDebugMsg(UUID, data.DebugLevel, 0, "Could not update startup WORLD_SIZE variable. Please enable debugging level 4 for body response including errors.") 70 | 71 | return false 72 | } 73 | 74 | if err != nil { 75 | fmt.Println(err) 76 | 77 | return false 78 | } 79 | 80 | // Finally, set the world seed if needed. 81 | if *next_world.WorldSeed > 0 { 82 | post_data := make(map[string]interface{}) 83 | post_data["key"] = "WORLD_SEED" 84 | post_data["value"] = strconv.Itoa(*next_world.WorldSeed) 85 | 86 | ep := "client/servers/" + UUID + "/startup/variable" 87 | 88 | // Send API request. 89 | d, _, err := pterodactyl.SendAPIRequest(data.APIURL, data.APIToken, "PUT", ep, post_data) 90 | 91 | debug.SendDebugMsg(UUID, data.DebugLevel, 3, "Sending request. Request => "+data.APIURL+"api/"+ep+". Post data => "+misc.CreateKeyPairs(post_data)+".") 92 | debug.SendDebugMsg(UUID, data.DebugLevel, 4, "Update Variable (WORLD_SEED) return data => "+d+".") 93 | 94 | if pterodactyl.IsError(d) { 95 | debug.SendDebugMsg(UUID, data.DebugLevel, 0, "Could not update startup WORLD_SEED variable. Please enable debugging level 4 for body response including errors.") 96 | 97 | return false 98 | } 99 | 100 | if err != nil { 101 | fmt.Println(err) 102 | 103 | return false 104 | } 105 | } 106 | 107 | return true 108 | } 109 | 110 | // Gets the world from the array. 111 | func GetNextWorld(data *Data) config.WorldInfo { 112 | // Make new variables for better looking code. 113 | world := config.WorldInfo{} 114 | worlds := data.WorldInfo 115 | pick_type := data.WorldInfoPickType 116 | 117 | // Check pick type. 118 | if pick_type == 1 { 119 | next_idx := &data.InternalData.LatestWorld 120 | 121 | world = data.WorldInfo[*next_idx] 122 | 123 | // Increment current (technically next) world. 124 | *next_idx++ 125 | 126 | // Make sure we don't need to reset. 127 | if *next_idx >= uint(len(data.WorldInfo)) { 128 | *next_idx = 0 129 | } 130 | 131 | } else { 132 | rand.Seed(time.Now().UnixNano()) 133 | min := 0 134 | max := len(worlds) - 1 135 | world = data.WorldInfo[rand.Intn(max-min+1)+min] 136 | } 137 | 138 | return world 139 | } 140 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "os/signal" 9 | "strings" 10 | "syscall" 11 | "time" 12 | 13 | "github.com/gamemann/Rust-Auto-Wipe/internal/autoaddservers" 14 | "github.com/gamemann/Rust-Auto-Wipe/internal/config" 15 | "github.com/gamemann/Rust-Auto-Wipe/internal/wipe" 16 | "github.com/gamemann/Rust-Auto-Wipe/pkg/chttp" 17 | "github.com/gamemann/Rust-Auto-Wipe/pkg/debug" 18 | "github.com/gamemann/Rust-Auto-Wipe/pkg/format" 19 | "github.com/gamemann/Rust-Auto-Wipe/pkg/misc" 20 | cron "github.com/robfig/cron/v3" 21 | ) 22 | 23 | const HELP_MENU = "Help Options\n\t-cfg= --cfg -cfg > Path to config file override.\n\t-l --list > Print out full config.\n\t-v --version > Print out version and exit.\n\t-h --help > Display help menu.\n\n" 24 | const VERSION = "1.0.0" 25 | 26 | func wipe_server(cfg *config.Config, srv *config.Server, data *wipe.Data) { 27 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 1, "Wiping server...") 28 | 29 | // Add data for hooks. 30 | hook_data := make(map[string]interface{}) 31 | hook_data["identifier"] = srv.UUID 32 | hook_data["uuid"] = srv.LongID 33 | hook_data["name"] = srv.Name 34 | hook_data["ip"] = srv.IP 35 | hook_data["port"] = srv.Port 36 | hook_data["pre"] = false 37 | hook_data["post"] = false 38 | 39 | // Check if we need to send a prehook. 40 | if len(cfg.PreHook) > 0 { 41 | // Set wipe type. 42 | hook_data["pre"] = true 43 | 44 | // Send HTTP request to prehook endpoint. 45 | d, _, err := chttp.SendHTTPReq(cfg.PreHook, cfg.PreHookAuth, "POST", hook_data) 46 | 47 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 3, "Sending prehook. Request => "+cfg.PreHook+". Post data => "+misc.CreateKeyPairs(hook_data)+".") 48 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 4, "Prehook return data => "+d+".") 49 | 50 | if err != nil { 51 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 0, "Failed to send prehook request.") 52 | fmt.Println(err) 53 | } 54 | } 55 | 56 | // Process world info. 57 | if data.ChangeWorldInfo { 58 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 2, "Processing world info...") 59 | 60 | wipe.ProcessWorldInfo(data, srv.UUID) 61 | } 62 | 63 | // Process host name. 64 | if data.ChangeHostName { 65 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 2, "Processing hostname...") 66 | 67 | wipe.ProcessHostName(data, srv.UUID) 68 | } 69 | 70 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 2, "Stopping server...") 71 | 72 | // We should stop the server 73 | wipe.StopServer(data, srv.UUID) 74 | 75 | // Wait until the server is confirmed stopped. 76 | i := 0 77 | 78 | for true { 79 | // Check if the server is running and when it is confirmed stop, break the loop. 80 | state, err := wipe.GetServerState(data, srv.UUID) 81 | 82 | // Check for error. Otherwise, break if we're not running. 83 | if err != nil { 84 | fmt.Println(err) 85 | } else { 86 | if state == "offline" { 87 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 4, "Found server offline. Continuing..") 88 | 89 | break 90 | } 91 | } 92 | 93 | // Increment i. 94 | i++ 95 | 96 | // Kill the server after 15 seconds. 97 | if i == 15 { 98 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 2, "Found up for 15 seconds. Trying to kill server...") 99 | wipe.KillServer(data, srv.UUID) 100 | } 101 | 102 | // Give up after a minute. 103 | if i > 60 { 104 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 2, "Server halt timed out...") 105 | 106 | break 107 | } 108 | 109 | // Sleep every second to avoid unnecessary CPU cycles. 110 | time.Sleep(time.Duration(time.Second)) 111 | } 112 | 113 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 2, "Processing files...") 114 | 115 | // Process and delete files. 116 | wipe.ProcessFiles(data, srv.UUID) 117 | 118 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 2, "Starting server back up...") 119 | 120 | // Start server back up. 121 | wipe.StartServer(data, srv.UUID) 122 | 123 | // Make sure the server starts back up. 124 | i = 0 125 | failed := 0 126 | 127 | for true { 128 | // Check if the server is running or starting. If confirmed, break loop. 129 | state, err := wipe.GetServerState(data, srv.UUID) 130 | 131 | // Check for error. Otherwise, break if we're not running. 132 | if err != nil { 133 | fmt.Println(err) 134 | } else { 135 | if state == "starting" || state == "running" { 136 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 4, "Found server starting/running. Continuing..") 137 | 138 | break 139 | } 140 | } 141 | 142 | // Increment i. 143 | i++ 144 | 145 | // If we hit 15, start server again and reset i. 146 | if i == 15 { 147 | wipe.StartServer(data, srv.UUID) 148 | failed++ 149 | 150 | i = 0 151 | } 152 | 153 | // Give up after 5 attempts. 154 | if failed > 5 { 155 | break 156 | } 157 | 158 | // Sleep every second to avoid unnecessary CPU cycles. 159 | time.Sleep(time.Duration(time.Second)) 160 | } 161 | 162 | // Check if we need to send a posthook. 163 | if len(cfg.PostHook) > 0 { 164 | // Set wipe type. 165 | hook_data["post"] = true 166 | 167 | // Send HTTP request to posthook endpoint. 168 | d, _, err := chttp.SendHTTPReq(cfg.PostHook, cfg.PostHook, "POST", hook_data) 169 | 170 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 3, "Sending posthook. Request => "+cfg.PostHook+". Post data => "+misc.CreateKeyPairs(hook_data)+".") 171 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 4, "Posthook return data => "+d+".") 172 | 173 | if err != nil { 174 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 0, "Failed to send posthook request.") 175 | fmt.Println(err) 176 | } 177 | } 178 | } 179 | 180 | func srv_handler(cfg *config.Config, srv *config.Server) error { 181 | var err error 182 | 183 | // Environmental overrides. 184 | wipe.EnvOverride(cfg, srv) 185 | 186 | // We need to retrieve the wipe data information first. 187 | var data wipe.Data 188 | 189 | // Process wipe data first. 190 | wipe.ProcessData(&data, cfg, srv) 191 | 192 | // Create cron job handler. 193 | c := cron.New() 194 | 195 | // If we have a single string, spawn a single cron job. 196 | for _, c_str := range data.CronStr { 197 | _, err = c.AddFunc("CRON_TZ="+data.TimeZone+" "+c_str, func() { 198 | wipe_server(cfg, srv, &data) 199 | 200 | next_wipe := "N/A" 201 | earliest := int64(1<<63 - 1) 202 | 203 | for _, cron := range c.Entries() { 204 | wipe_time := cron.Next.Unix() 205 | 206 | // If it's earlier, choose this one. 207 | //fmt.Println(strconv.FormatInt(wipe_time, 10) + " < " + strconv.FormatInt(earliest, 10)) 208 | 209 | if wipe_time < earliest { 210 | earliest = wipe_time 211 | 212 | tz, err := time.LoadLocation(data.TimeZone) 213 | 214 | if err != nil { 215 | fmt.Println(err) 216 | 217 | continue 218 | } 219 | 220 | next_wipe = cron.Next.In(tz).Format("01-02-2006 3:04 PM MST") 221 | } 222 | } 223 | 224 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 1, "Server wiped. Next wipe date => "+next_wipe+".") 225 | }) 226 | 227 | if err != nil { 228 | fmt.Println(err) 229 | } 230 | } 231 | 232 | // Start cron job. 233 | c.Start() 234 | 235 | // Loop through each cron entry and print the next wipe date for debug. 236 | for _, job := range c.Entries() { 237 | tz, err := time.LoadLocation(data.TimeZone) 238 | 239 | if err != nil { 240 | fmt.Println(err) 241 | 242 | continue 243 | } 244 | 245 | // Retrieve the next time the job will be ran (Unix timestamp). 246 | next := job.Next.In(tz).Format("01-02-2006 3:04 PM MST") 247 | 248 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 1, "Next wipe date => "+next+".") 249 | } 250 | 251 | // See if we need to do a startup/first wipe. 252 | if srv.WipeFirst { 253 | wipe_server(cfg, srv, &data) 254 | } 255 | 256 | for true { 257 | // Loop through each cron entry. 258 | for _, job := range c.Entries() { 259 | // Retrieve the next time the job will be ran (Unix timestamp). 260 | now := time.Now().Unix() 261 | next := job.Next.Unix() 262 | 263 | // Loop through warning messages. 264 | for _, warning := range data.WarningMessages { 265 | wt := next - now 266 | 267 | // If what's remaining equals the warning time, we need to warn. 268 | if wt == int64(warning.WarningTime) { 269 | // Check if we're in running state. 270 | state, err := wipe.GetServerState(&data, srv.UUID) 271 | 272 | if err != nil { 273 | time.Sleep(time.Duration(time.Second)) 274 | 275 | continue 276 | } 277 | 278 | if state != "running" { 279 | time.Sleep(time.Duration(time.Second)) 280 | 281 | continue 282 | } 283 | 284 | warning_msg := *warning.Message 285 | format.FormatString(&warning_msg, int(wt)) 286 | 287 | debug.SendDebugMsg(srv.UUID, data.DebugLevel, 2, "Sending warning message => "+warning_msg+".") 288 | 289 | err = wipe.SendMessage(&data, srv.UUID, warning_msg) 290 | 291 | if err != nil { 292 | fmt.Println(err) 293 | } 294 | } 295 | } 296 | } 297 | 298 | // Sleep for one second to avoid unnecessary CPU cycles. 299 | time.Sleep(time.Duration(time.Second)) 300 | } 301 | 302 | return err 303 | } 304 | 305 | func main() { 306 | var list bool 307 | var version bool 308 | var help bool 309 | 310 | // Setup simple flags (booleans). 311 | flag.BoolVar(&list, "list", false, "Print out config and exit.") 312 | flag.BoolVar(&list, "l", false, "Print out config and exit.") 313 | 314 | flag.BoolVar(&version, "version", false, "Print out version and exit.") 315 | flag.BoolVar(&version, "v", false, "Print out version and exit.") 316 | 317 | flag.BoolVar(&help, "help", false, "Print out help menu and exit.") 318 | flag.BoolVar(&help, "h", false, "Print out help menu and exit.") 319 | 320 | // Look for 'cfg' flag in command line arguments (default path: /etc/raw/raw.conf). 321 | configFile := flag.String("cfg", "/etc/raw/raw.conf", "The path to the Rust Auto Wipe config file.") 322 | 323 | // Parse flags. 324 | flag.Parse() 325 | 326 | // Check for version flag. 327 | if version { 328 | fmt.Print(VERSION) 329 | 330 | os.Exit(0) 331 | } 332 | 333 | // Check for help flag. 334 | if help { 335 | fmt.Print(HELP_MENU) 336 | 337 | os.Exit(0) 338 | } 339 | 340 | // Create config struct. 341 | cfg := config.Config{} 342 | 343 | // Set config defaults. 344 | cfg.SetDefaults() 345 | 346 | // Attempt to read config. 347 | err := cfg.LoadConfig(*configFile) 348 | 349 | // If we have no config, create the file with the defaults. 350 | if err != nil { 351 | // If there's an error and it contains "no such file", try to create the file with defaults. 352 | if strings.Contains(err.Error(), "no such file") { 353 | err = cfg.WriteDefaultsToFile(*configFile) 354 | 355 | if err != nil { 356 | fmt.Println("Failed to open config file and cannot create file.") 357 | fmt.Println(err) 358 | 359 | os.Exit(1) 360 | } 361 | } 362 | 363 | fmt.Println("WARNING - No config file found. Created config file at " + *configFile + " with defaults.") 364 | } else { 365 | // See if we want to automatically add servers. 366 | autoaddservers.AddServers(&cfg) 367 | } 368 | 369 | // Check for list flag. 370 | if list { 371 | 372 | // Process environmental data before returning list. 373 | for i := 0; i < len(cfg.Servers); i++ { 374 | srv := &cfg.Servers[i] 375 | 376 | wipe.EnvOverride(&cfg, srv) 377 | } 378 | // Encode config as JSON string. 379 | json_data, err := json.MarshalIndent(cfg, "", " ") 380 | 381 | if err != nil { 382 | fmt.Println(err) 383 | 384 | os.Exit(1) 385 | } 386 | 387 | fmt.Println(string(json_data)) 388 | 389 | os.Exit(0) 390 | } 391 | 392 | // If we don't have any servers, what's the point? 393 | if len(cfg.Servers) < 1 { 394 | fmt.Println("[ERR] No servers found.") 395 | 396 | os.Exit(1) 397 | } 398 | 399 | // Loop through each server and execute Go routine. 400 | for _, srv := range cfg.Servers { 401 | // Check if we're enabled. 402 | if !srv.Enabled { 403 | continue 404 | } 405 | 406 | var srv_two config.Server = srv 407 | 408 | // Spawn Go routine. 409 | go srv_handler(&cfg, &srv_two) 410 | } 411 | 412 | // Signal. 413 | sigc := make(chan os.Signal, 1) 414 | signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) 415 | <-sigc 416 | 417 | os.Exit(0) 418 | } 419 | -------------------------------------------------------------------------------- /pkg/chttp/http.go: -------------------------------------------------------------------------------- 1 | package chttp 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | "time" 11 | ) 12 | 13 | // Sends API request to Pterodactyl API (or perhaps technically any endpoint with /api/ :)). 14 | func SendHTTPReq(endpoint string, auth string, request_type string, form_data map[string]interface{}) (string, int, error) { 15 | // Initialize data and return code (status code). 16 | d := "" 17 | rc := -1 18 | 19 | var post_body io.Reader 20 | 21 | // Check to see if we need to send post data. 22 | if request_type == "POST" || request_type == "PUT" { 23 | // Convert to JSON and use as body. 24 | j, err := json.Marshal(form_data) 25 | 26 | if err != nil { 27 | return d, rc, err 28 | } 29 | 30 | // Read byte array into IO reader. 31 | post_body = bytes.NewBuffer(j) 32 | 33 | if err != nil { 34 | return d, rc, err 35 | } 36 | } 37 | 38 | // Setup HTTP GET request. 39 | client := &http.Client{Timeout: time.Second * 5} 40 | req, err := http.NewRequest(request_type, endpoint, post_body) 41 | 42 | if err != nil { 43 | fmt.Println(err) 44 | 45 | return d, rc, err 46 | } 47 | 48 | // Set Application API token. 49 | req.Header.Set("Authorization", auth) 50 | 51 | // Accept only JSON. 52 | req.Header.Set("Accept", "application/json") 53 | 54 | // Set content stype. 55 | req.Header.Set("Content-Type", "application/json") 56 | 57 | // Perform HTTP request and check for errors. 58 | resp, err := client.Do(req) 59 | 60 | if err != nil { 61 | fmt.Println(err) 62 | 63 | return d, rc, err 64 | } 65 | 66 | // Set return code. 67 | rc = resp.StatusCode 68 | 69 | // Read body. 70 | body, err := ioutil.ReadAll(resp.Body) 71 | 72 | if err != nil { 73 | fmt.Println(err) 74 | 75 | return d, rc, err 76 | } 77 | 78 | // Return data as a string. 79 | d = string(body) 80 | 81 | return d, rc, nil 82 | } 83 | -------------------------------------------------------------------------------- /pkg/debug/print.go: -------------------------------------------------------------------------------- 1 | package debug 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "time" 7 | ) 8 | 9 | func SendDebugMsg(UUID string, debug_level int, required_level int, message string) { 10 | if debug_level >= required_level { 11 | now := time.Now().Local() 12 | 13 | pre := "ERR" 14 | 15 | if required_level > 0 { 16 | pre = strconv.Itoa(required_level) 17 | } 18 | fmt.Println("[" + pre + "][" + now.Format("1-2 03:04:05 PM") + "] " + UUID + " :: " + message) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pkg/format/data.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | // Formats hostname for our needs. 10 | func FormatString(str *string, secs_left int) { 11 | *str = strings.Replace(*str, "{seconds_left}", strconv.Itoa(secs_left), -1) 12 | 13 | // Gain some information about the time. 14 | tz_one := time.Now().Format("MST") 15 | tz_two := time.Now().Format("-0700") 16 | tz_three := time.Now().Format("-07") 17 | 18 | month_str_short := time.Now().Format("Jan") 19 | month_str_long := time.Now().Format("January") 20 | 21 | week_day_str_short := time.Now().Format("Mon") 22 | week_day_str_long := time.Now().Format("Monday") 23 | 24 | year_one := time.Now().Format("06") 25 | year_two := time.Now().Format("2006") 26 | 27 | month_one := time.Now().Format("01") 28 | month_two := time.Now().Format("1") 29 | month_three := time.Now().Format("_1") 30 | 31 | day_one := time.Now().Format("02") 32 | day_two := time.Now().Format("2") 33 | day_three := time.Now().Format("_2") 34 | 35 | hour_one := time.Now().Format("03") 36 | hour_two := time.Now().Format("3") 37 | hour_three := time.Now().Format("15") 38 | 39 | min_one := time.Now().Format("04") 40 | min_two := time.Now().Format("4") 41 | 42 | sec_one := time.Now().Format("05") 43 | sec_two := time.Now().Format("5") 44 | 45 | mark_one := time.Now().Format("PM") 46 | mark_two := time.Now().Format("pm") 47 | 48 | *str = strings.Replace(*str, "{tz_one}", tz_one, -1) 49 | *str = strings.Replace(*str, "{tz_two}", tz_two, -1) 50 | *str = strings.Replace(*str, "{tz_three}", tz_three, -1) 51 | 52 | *str = strings.Replace(*str, "{month_str_short}", month_str_short, -1) 53 | *str = strings.Replace(*str, "{month_str_long}", month_str_long, -1) 54 | 55 | *str = strings.Replace(*str, "{week_day_str_short}", week_day_str_short, -1) 56 | *str = strings.Replace(*str, "{week_day_str_long}", week_day_str_long, -1) 57 | 58 | *str = strings.Replace(*str, "{year_one}", year_one, -1) 59 | *str = strings.Replace(*str, "{year_two}", year_two, -1) 60 | 61 | *str = strings.Replace(*str, "{month_one}", month_one, -1) 62 | *str = strings.Replace(*str, "{month_two}", month_two, -1) 63 | *str = strings.Replace(*str, "{month_three}", month_three, -1) 64 | 65 | *str = strings.Replace(*str, "{day_one}", day_one, -1) 66 | *str = strings.Replace(*str, "{day_two}", day_two, -1) 67 | *str = strings.Replace(*str, "{day_three}", day_three, -1) 68 | 69 | *str = strings.Replace(*str, "{hour_one}", hour_one, -1) 70 | *str = strings.Replace(*str, "{hour_two}", hour_two, -1) 71 | *str = strings.Replace(*str, "{hour_three}", hour_three, -1) 72 | 73 | *str = strings.Replace(*str, "{min_one}", min_one, -1) 74 | *str = strings.Replace(*str, "{min_two}", min_two, -1) 75 | 76 | *str = strings.Replace(*str, "{sec_one}", sec_one, -1) 77 | *str = strings.Replace(*str, "{sec_two}", sec_two, -1) 78 | 79 | *str = strings.Replace(*str, "{mark_one}", mark_one, -1) 80 | *str = strings.Replace(*str, "{mark_two}", mark_two, -1) 81 | } 82 | -------------------------------------------------------------------------------- /pkg/misc/helpers.go: -------------------------------------------------------------------------------- 1 | package misc 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | func CreateKeyPairs(m map[string]interface{}) string { 9 | b := new(bytes.Buffer) 10 | 11 | for key, value := range m { 12 | fmt.Fprintf(b, "%s=\"%s\"\n", key, value) 13 | } 14 | 15 | return b.String() 16 | } 17 | -------------------------------------------------------------------------------- /pkg/pterodactyl/api.go: -------------------------------------------------------------------------------- 1 | package pterodactyl 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | "time" 11 | ) 12 | 13 | // Sends API request to Pterodactyl API (or perhaps technically any endpoint with /api/ :)). 14 | func SendAPIRequest(url string, token string, request_type string, request_endpoint string, form_data map[string]interface{}) (string, int, error) { 15 | // Initialize data and return code (status code). 16 | d := "" 17 | rc := -1 18 | 19 | var post_body io.Reader 20 | 21 | // Check to see if we need to send post data. 22 | if request_type == "POST" || request_type == "PUT" { 23 | // Convert to JSON and use as body. 24 | j, err := json.Marshal(form_data) 25 | 26 | if err != nil { 27 | return d, rc, err 28 | } 29 | 30 | // Read byte array into IO reader. 31 | post_body = bytes.NewBuffer(j) 32 | 33 | if err != nil { 34 | return d, rc, err 35 | } 36 | } 37 | 38 | // Compile our URL. 39 | urlstr := url + "api/" + request_endpoint 40 | 41 | // Setup HTTP GET request. 42 | client := &http.Client{Timeout: time.Second * 5} 43 | req, err := http.NewRequest(request_type, urlstr, post_body) 44 | 45 | if err != nil { 46 | fmt.Println(err) 47 | 48 | return d, rc, err 49 | } 50 | 51 | // Set Application API token. 52 | req.Header.Set("Authorization", "Bearer "+token) 53 | 54 | // Accept only JSON. 55 | req.Header.Set("Accept", "application/json") 56 | 57 | // Set content stype. 58 | req.Header.Set("Content-Type", "application/json") 59 | 60 | // Perform HTTP request and check for errors. 61 | resp, err := client.Do(req) 62 | 63 | if err != nil { 64 | fmt.Println(err) 65 | 66 | return d, rc, err 67 | } 68 | 69 | // Set return code. 70 | rc = resp.StatusCode 71 | 72 | // Read body. 73 | body, err := ioutil.ReadAll(resp.Body) 74 | 75 | if err != nil { 76 | fmt.Println(err) 77 | 78 | return d, rc, err 79 | } 80 | 81 | // Return data as a string. 82 | d = string(body) 83 | 84 | // Next, make sure we're not 85 | 86 | return d, rc, nil 87 | } 88 | -------------------------------------------------------------------------------- /pkg/pterodactyl/def.go: -------------------------------------------------------------------------------- 1 | package pterodactyl 2 | 3 | import "time" 4 | 5 | type StartupResp struct { 6 | Object string `json:"object"` 7 | Data []struct { 8 | Object string `json:"object"` 9 | Attributes struct { 10 | Env_Variable string `json:"env_variable"` 11 | Srv_Value string `json:"server_value"` 12 | } `json:"attributes"` 13 | } `json:"data"` 14 | } 15 | 16 | type ListFilesResp struct { 17 | Object string `json:"object"` 18 | Data []struct { 19 | Object string `json:"object"` 20 | Attributes struct { 21 | Name string `json:"name"` 22 | Mode string `json:"mode"` 23 | ModeBits string `json:"mode_bits"` 24 | Size int `json:"size"` 25 | IsFile bool `json:"is_file"` 26 | IsSymlink bool `json:"is_symlink"` 27 | Mimetype string `json:"mimetype"` 28 | CreatedAt time.Time `json:"created_at"` 29 | ModifiedAt time.Time `json:"modified_at"` 30 | } `json:"attributes"` 31 | } `json:"data"` 32 | } 33 | 34 | type SendCmdReq struct { 35 | Command string `json:"command"` 36 | } 37 | 38 | type SendPowerCmdReq struct { 39 | Signal string `json:"signal"` 40 | } 41 | 42 | type DeleteFileReq struct { 43 | Root string `json:"root"` 44 | Files []string `json:"files"` 45 | } 46 | 47 | type PteroResp struct { 48 | Errors []struct { 49 | Code string `json:"code"` 50 | Status string `json:"status"` 51 | Detail string `json:"detail"` 52 | } `json:"errors"` 53 | } 54 | -------------------------------------------------------------------------------- /pkg/pterodactyl/error.go: -------------------------------------------------------------------------------- 1 | package pterodactyl 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | func IsError(body string) bool { 8 | var ptero_resp PteroResp 9 | 10 | err := json.Unmarshal([]byte(body), &ptero_resp) 11 | 12 | // Likely not an error if it can't parse into the error structure (though, I guess it could be an error). 13 | if err != nil { 14 | return false 15 | } 16 | 17 | // If we have more than one error, just return true. 18 | if len(ptero_resp.Errors) > 0 { 19 | return true 20 | } 21 | 22 | return false 23 | } 24 | --------------------------------------------------------------------------------