Skip to content

Commit

Permalink
Better player monitoring (#8)
Browse files Browse the repository at this point in the history
# Improved Player Activity Monitoring

## Summary

This pull request addresses multiple improvements across different
aspects of the project, focusing on enhancing the player activity
monitoring, refactoring the default credential checks, redacting
sensitive information in the server start function, and updating
documentation for better clarity.

## Motivation and Context

The motivation behind these changes is to improve the overall user
experience, increase robustness in handling player activity, ensure
security in default credential checks, enhance server start
functionality, and provide comprehensive documentation updates.

And mainly to improve webhook experience with people being able to
customize the messages more

## Description

### Player Activity Monitoring
- Enhanced player monitor logic and respective logs and webhook messages
to handle player uid and steam id placeholders.
  - `PLAYER_NAME` for the in-game player name
  - `PLAYER_UID` for the player unique identifier
  - `PLAYER_STEAM_UID` for the player Steam ID
- Player name, UID, and Steam ID are now printed in the logs for easy
identification of the player.
- Improved handling of players with special characters, preventing RCON
timeout issues.
- Logged possible steam profile links and respective names in the
profile in docker logs to help identify players when Steam ID is
invalid. These will also be logged in the player join/leave messages if
`PLAYER_STEAM_UID` is present in them.

### Check Default Credentials
- Refactored the `check_default_credentials` function to always check
the PalWorldSettings.ini file.
- Now uses the PalWorldSettings.ini file to check if the default
credentials are still present, avoiding conditional statements and
ensuring compatibility with all configuration modes.

### RCON
- Fixed the `rconcli` wrapper to output cleanly without ANSI codes by
changing the last print to simple echo.

### Server Start
- Redacted RCON port setting in the server start function for enhanced
security.

### Documentation
- Updated documentation with logging format details and information
about invalid Steam IDs for better user understanding.

## Testing Instructions

These changes were tested thoroughly by:
- Executing `rconcli` with RCON commands and verifying clean outputs
(checking each ASCII code for every byte on the output).
- Monitoring player activities and ensuring accurate logs and webhook
messages.
- Checking default credentials using the `PalWorldSettings.ini` file.
- Starting the server and validating the redacted RCON port on the logs.
- Reviewing and validating documentation updates for clarity and
accuracy.

## Checklist

- [x] I have performed a self-review of my own code
- [x] I have updated the documentation (if necessary)
- [x] My changes do not introduce any breaking changes or bugs
  • Loading branch information
thejcpalma authored Mar 4, 2024
2 parents 71e7f42 + 626c625 commit e69b282
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 56 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,28 @@ Events monitored:

Player events are logged in the container logs and sent to the webhook if enabled.

Logging format is `> Player [joined/left]: PLAYER_NAME | UID: PLAYER_UID | Steam ID: PLAYER_STEAM_UID`.

If `PLAYER_STEAM_UID` is invalid (when players have spacial characters in their name, receiving 16 digits instead of 17 on the `RCON` `ShowPlayers` command), it will be logged but it will be missing the last digit.
When this happens, the server will give a warning message in the logs and list all possible steam profiles associated with the player that joined/left.
It does this by testing all 10 combos of the last digit from 0 to 9 (e.g. `"PLAYER_STEAM_UID" + "3"`).

Log example:
```shell
> Player joined: PLAYER_NAME | UID: PLAYER_UID | Steam ID: XXXXXXXXXXXXXXXX
>> Invalid Steam ID - Should have 17 digits but has 16 digits!
>> Possible Steam IDs:
> Profile name is: profile1 | Profile link: https://steamcommunity.com/profiles/XXXXXXXXXXXXXXXX1
> Profile name is: ___Profile2___ | Profile link: https://steamcommunity.com/profiles/XXXXXXXXXXXXXXXX3
> Profile name is: pROFILe_$%4 | Profile link: https://steamcommunity.com/profiles/XXXXXXXXXXXXXXXX7
```

> [!NOTE]
>
> You can use `PLAYER_NAME`, `PLAYER_UID` and `PLAYER_STEAM_UID` in the webhook messages.
> When Steam ID is invalid, the webhook message will always contain the possible Steam profiles associated with the player and change `PLAYER_STEAM_UID` to `###INVALID_STEAM_UID###`.
> See more info about this integration [here](/docs/ENV_VARS.md#--player-join-message).
## Backup Manager

> [!WARNING]
Expand Down
61 changes: 42 additions & 19 deletions docs/ENV_VARS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ Due to the extensive control options, the settings are split into categories:
- [Webhook Settings](#webhook-settings)
- [Webhook Configuration](#webhook-configuration)
- [Webhook Messages](#webhook-messages)
- [** Start server message**](#-start-server-message)
- [** Stop server message**](#-stop-server-message)
- [** Restart server message**](#-restart-server-message)
- [** Install server message**](#-install-server-message)
- [** Update server message**](#-update-server-message)
- [** Update and validation server message**](#-update-and-validation-server-message)
- [** Player join message**](#-player-join-message)
- [** Player leave message**](#-player-leave-message)
- [**- Start server message**](#--start-server-message)
- [**- Stop server message**](#--stop-server-message)
- [**- Restart server message**](#--restart-server-message)
- [**- Install server message**](#--install-server-message)
- [**- Update server message**](#--update-server-message)
- [**- Update and validation server message**](#--update-and-validation-server-message)
- [**- Player join message**](#--player-join-message)
- [**- Player leave message**](#--player-leave-message)

## Container Settings

Expand Down Expand Up @@ -298,7 +298,7 @@ Colors are represented in their decimal form.
Below are the environment variables for each type of webhook message:

#### ** Start server message**
#### **- Start server message**

| Variable | Description | Default Value |
| --------------------------- | ------------------------------------- | ------------------------------------ |
Expand All @@ -307,7 +307,7 @@ Below are the environment variables for each type of webhook message:
| `WEBHOOK_START_COLOR` | The color for the start message | `65280` |


#### ** Stop server message**
#### **- Stop server message**

| Variable | Description | Default Value |
| -------------------------- | ------------------------------------ | --------------------------------- |
Expand All @@ -316,7 +316,7 @@ Below are the environment variables for each type of webhook message:
| `WEBHOOK_STOP_COLOR` | The color for the stop message | `16711680` |


#### ** Restart server message**
#### **- Restart server message**

> [!NOTE]
>
Expand All @@ -329,57 +329,80 @@ Below are the environment variables for each type of webhook message:
| `WEBHOOK_RESTART_NOW_DESCRIPTION` | The description for the restart now message | `Server is restarting in now! :alarm_clock:` |
| `WEBHOOK_RESTART_COLOR` | The color for the restart message | `16750848` |

#### ** Install server message**
#### **- Install server message**

| Variable | Description | Default Value |
| ----------------------------- | --------------------------------------- | --------------------------- |
| `WEBHOOK_INSTALL_TITLE` | The title for the install message | `:tools: Installing server` |
| `WEBHOOK_INSTALL_DESCRIPTION` | The description for the install message | `Server is being installed` |
| `WEBHOOK_INSTALL_COLOR` | The color for the install message | `1644912` |

#### ** Update server message**
#### **- Update server message**

| Variable | Description | Default Value |
| ---------------------------- | -------------------------------------- | ------------------------- |
| `WEBHOOK_UPDATE_TITLE` | The title for the update message | `:new: Updating server` |
| `WEBHOOK_UPDATE_DESCRIPTION` | The description for the update message | `Server is being updated` |
| `WEBHOOK_UPDATE_COLOR` | The color for the update message | `16776960` |

#### ** Update and validation server message**
#### **- Update and validation server message**

| Variable | Description | Default Value |
| ------------------------------------- | ----------------------------------------------------- | -------------------------------------------------------- |
| `WEBHOOK_UPDATE_VALIDATE_TITLE` | The title for the update and validation message | `:ballot_box_with_check: Updating and validating server` |
| `WEBHOOK_UPDATE_VALIDATE_DESCRIPTION` | The description for the update and validation message | `Server is being updated and validated` |
| `WEBHOOK_UPDATE_VALIDATE_COLOR` | The color for the update and validation message | `16776960` |

#### ** Player join message**
#### **- Player join message**

> [!NOTE]
>
> `PLAYER_NAME` is a variable that will be replaced with the name of the player that joined the server.
> Use it on the title and/or description to show the player's name.
> `PLAYER_UID` is a variable that will be replaced with the UID of the player that joined the server.
> `PLAYER_STEAM_UID` is a variable that will be replaced with the Steam UID of the player that joined the server.
> Use it on the title and/or description to show the player's name, UID and/or Steam UID.
> [!WARNING]
>
> When **Steam ID is invalid** and **you have `PLAYER_STEAM_UID` in your title or description message**, the webhook message will always contain the possible Steam profiles associated with the player and change `PLAYER_STEAM_UID` to `###INVALID_STEAM_UID###`.
| Variable | Description | Default Value |
| --------------------------------- | ------------------------------------ | ---------------------- |
| `WEBHOOK_PLAYER_JOIN_TITLE` | The title for the join message | `:mage: Player joined` |
| `WEBHOOK_PLAYER_JOIN_DESCRIPTION` | The description for the join message | `### PLAYER_NAME` |
| `WEBHOOK_PLAYER_JOIN_COLOR` | The color for the join message | `1728512` |

#### **• Player leave message**
> [!TIP]
>
> You can use the `PLAYER_STEAM_UID` variable to link the Steam profile of the player that joined the server on the **description** (doesn't work on the title).
> E.g.:
> - `### PLAYER_NAME (Steam: [PLAYER_STEAM_UID](https://steamcommunity.com/profiles/PLAYER_STEAM_UID))` will show the player's name and link to their Steam profile.
> - `### [PLAYER_NAME](https://steamcommunity.com/profiles/PLAYER_STEAM_UID)` will show the player's name and their UID.
#### **- Player leave message**

> [!NOTE]
>
> `PLAYER_NAME` is a variable that will be replaced with the name of the player that left the server.
> `PLAYER_NAME` is a variable that will be replaced with the name of the player that left the server.
> `PLAYER_NAME` is a variable that will be replaced with the name of the player that left the server.
> `PLAYER_UID` is a variable that will be replaced with the UID of the player that joined the server.
> `PLAYER_STEAM_UID` is a variable that will be replaced with the Steam UID of the player that joined the server.
> Use it on the title and/or description to show the player's name.
> [!WARNING]
>
> When **Steam ID is invalid** and **you have `PLAYER_STEAM_UID` in your title or description message**, the webhook message will always contain the possible Steam profiles associated with the player and change `PLAYER_STEAM_UID` to `###INVALID_STEAM_UID###`.
| Variable | Description | Default Value |
| ---------------------------------- | ------------------------------------- | -------------------- |
| `WEBHOOK_PLAYER_LEAVE_TITLE` | The title for the leave message | `:dash: Player left` |
| `WEBHOOK_PLAYER_LEAVE_DESCRIPTION` | The description for the leave message | `### PLAYER_NAME` |
| `WEBHOOK_PLAYER_LEAVE_COLOR` | The color for the leave message | `6291482` |

> [!TIP]
>
> You can use the `PLAYER_STEAM_UID` variable to link the Steam profile of the player that joined the server on the **description** (doesn't work on the title).
> E.g.:
> - `### PLAYER_NAME (Steam: [PLAYER_STEAM_UID](https://steamcommunity.com/profiles/PLAYER_STEAM_UID))` will show the player's name and link to their Steam profile.
> - `### [PLAYER_NAME](https://steamcommunity.com/profiles/PLAYER_STEAM_UID)` will show the player's name and their UID.
[Back to main](../README.md#environment-variables)
2 changes: 1 addition & 1 deletion scripts/rcon/rconcli.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ rconcli() {
fi

log_info -n "> RCON: "
log_base "$output"
echo "$output"
}

rconcli "$*"
23 changes: 7 additions & 16 deletions scripts/server/check_default_credentials.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,15 @@ source "${SERVER_DIR}"/scripts/utils/logs.sh
function check_default_credentials() {
log_info "> Checking for existence of default credentials"

# If the 'ADMIN_PASSWORD' environment variable is set to 'adminPasswordHere', an error message is displayed and the function exits.
if [[ -n ${ADMIN_PASSWORD} ]] && [[ ${ADMIN_PASSWORD} == "adminPasswordHere" ]]; then
log_error ">>> Security thread detected: Please change the default admin password. Aborting server start ..."
exit 1
fi

# If the 'SERVER_PASSWORD' environment variable is set to 'serverPasswordHere', an error message is displayed and the function exits.
if [[ -n ${SERVER_PASSWORD} ]] && [[ ${SERVER_PASSWORD} == "serverPasswordHere" ]]; then
log_error ">>> Security thread detected: Please change the default server password. Aborting server start ..."
exit 1
fi

# If 'SERVER_SETTINGS_MODE' is set to 'manual' and the 'GAME_SETTINGS_FILE' exists and is not empty, the function extracts the admin password from the 'GAME_SETTINGS_FILE'.
# If the password is 'adminPasswordHere', an error message is displayed.
if [[ "${SERVER_SETTINGS_MODE,,}" == "manual" ]] && [[ -s "${GAME_SETTINGS_FILE}" ]]; then
if [[ -f "${GAME_SETTINGS_FILE}" ]]; then
admin_password=$(awk -F'AdminPassword="' '{print $2}' "${GAME_SETTINGS_FILE}" | awk -F'"' '{print $1}' | tr -d '\n')
if [[ "${admin_password}" == "adminPasswordHere" ]]; then
log_error ">>> Security thread detected: Please change the default server password. Aborting server start ..."
log_error ">>> Security thread detected: Please change the default admin password. Aborting server start..."
exit 1
fi
server_password=$(awk -F'ServerPassword="' '{print $2}' "${GAME_SETTINGS_FILE}" | awk -F'"' '{print $1}' | tr -d '\n')
if [[ "${server_password}" == "serverPasswordHere" ]]; then
log_error ">>> Security thread detected: Please change the default server password. Aborting server start..."
exit 1
fi
fi
Expand Down
2 changes: 1 addition & 1 deletion scripts/server/start_server.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function start_server() {
fi

if [[ -n $RCON_ENABLED ]] && [[ $RCON_ENABLED == "true" ]]; then
log_info "> Setting RCON port to ${RCON_PORT} on server start options"
log_info "> Setting RCON port on server start options"
START_OPTIONS+=("-RCONPort=${RCON_PORT}")
fi

Expand Down
55 changes: 51 additions & 4 deletions scripts/utils/player_activity_monitor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ declare -a previous_players
# and the player name (all fields before the last two).
# This ensures that we always split on the last two commas.
# This approach correctly handles player names that contain commas, spaces, and multibyte characters.

#
# Special chars also break RCON usage when fecthing Steam UDIs
# We get a Steam UID with 16 characters, but we need 17 to be valid
# We can add a number at the end of the Steam UID to get a valid one
# but we don't have a way to know which one is the correct

# Format: name,playeruid,steamid
# Function to get the current player list
Expand Down Expand Up @@ -51,6 +55,27 @@ get_current_players() {
done <<< "$player_list"
}

check_steam_profile() {
local clean_output=false
if [ "$1" = "clean" ]; then
clean_output=true
shift
fi

link="https://steamcommunity.com/profiles/$1"
content=$(curl -sL "${link}")
if echo "$content" | grep -q 'This user has not yet set up their Steam Community profile.'; then
return
else
profile_name=$(echo "$content" | grep -oPm 1 '(?<=<span class="actual_persona_name">).*(?=</span>)')
if [[ $clean_output = true ]]; then
echo "- [${profile_name}](<${link}>)"
else
log_info -n "> Profile name is: " && log_base -n "${profile_name}" && log_info -n " | Profile link: " && log_base "${link}"
fi
fi
}

# Function to compare the current player list with the previous one
compare_players() {
# Arrays to hold the players who have joined and left
Expand Down Expand Up @@ -79,10 +104,32 @@ compare_players() {
if [ ${#joined_players[@]} -ne 0 ]; then
for player in "${joined_players[@]}"; do
local player_name
local player_uid
local player_steam_uid
local possible_steam_ids

player_name=$(echo "$player" | awk 'BEGIN{FS=OFS=","} {NF-=2; print $0}' | sed 's/,*$//')
log_info -n "> Player joined: " && log_base "'$player_name'"
player_uid=$(echo "$player" | awk 'BEGIN{FS=OFS=","} {print $(NF-1)}')
player_steam_uid=$(echo "$player" | awk 'BEGIN{FS=OFS=","} {print $NF}')

log_info -n "> Player joined: " && log_base -n "'$player_name' " && \
log_info -n "| UID: " && log_base -n "$player_uid" && \
log_info -n "| Steam ID: " && log_base "$player_steam_uid"

if [ "${#player_steam_uid}" -ne 17 ]; then
log_warning ">> Invalid Steam ID (Player name has special characters!) - Should have 17 digits but has ${#player_steam_uid} digits!"
log_warning ">> Possible Steam IDs:"
for i in {0..9}; do
result=$(check_steam_profile "${player_steam_uid}${i}")
if [[ -n "$result" ]]; then
echo "$result"
possible_steam_ids+="$(check_steam_profile "clean" "${player_steam_uid}${i}")\n"
fi
done
player_steam_uid="###INVALID_STEAM_UID###"
fi
player_name=$(echo "$player" | awk 'BEGIN{FS=OFS=","} {NF-=2; print $0}' | sed 's/,*$//' | tr '`' "'" | sed 's/\\\\/\\\\\\\\/g')
send_player_join_notification "\`$player_name\`"
send_player_join_notification "\`$player_name\`" "$player_uid" "$player_steam_uid" "$possible_steam_ids"
done
fi

Expand All @@ -93,7 +140,7 @@ compare_players() {
player_name=$(echo "$player" | awk 'BEGIN{FS=OFS=","} {NF-=2; print $0}' | sed 's/,*$//')
log_info -n "> Player left: " && log_base "'$player_name'"
player_name=$(echo "$player" | awk 'BEGIN{FS=OFS=","} {NF-=2; print $0}' | sed 's/,*$//' | tr '`' "'" | sed 's/\\\\/\\\\\\\\/g')
send_player_leave_notification "\`$player_name\`"
send_player_leave_notification "\`$player_name\`" "$player_uid" "$player_steam_uid"
done
fi

Expand Down
43 changes: 41 additions & 2 deletions scripts/webhook/aliases.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,49 @@ function send_restart_notification() {

function send_player_join_notification() {
local player_name="$1"
send_webhook_notification "${WEBHOOK_PLAYER_JOIN_TITLE/PLAYER_NAME/$player_name}" "${WEBHOOK_PLAYER_JOIN_DESCRIPTION/PLAYER_NAME/$player_name}" "${WEBHOOK_PLAYER_JOIN_COLOR}"
local player_uid="$2"
local player_steam_uid="$3"
local possible_steam_ids="$4"

local title_message
local description_message

title_message="${WEBHOOK_PLAYER_JOIN_TITLE/PLAYER_NAME/$player_name}"
title_message="${title_message/PLAYER_UID/$player_uid}"
title_message="${title_message/PLAYER_STEAM_UID/$player_steam_uid}"

description_message="${WEBHOOK_PLAYER_JOIN_DESCRIPTION/PLAYER_NAME/$player_name}"
description_message="${description_message/PLAYER_UID/$player_uid}"
description_message="${description_message/PLAYER_STEAM_UID/$player_steam_uid}"

if [[ -n "${possible_steam_ids}" ]] && { [[ "${WEBHOOK_PLAYER_JOIN_TITLE}" == *"PLAYER_STEAM_UID"* ]] || [[ "${WEBHOOK_PLAYER_JOIN_DESCRIPTION}" == *"PLAYER_STEAM_UID"* ]]; }; then
send_webhook_notification "${title_message}" "${description_message}" "${WEBHOOK_PLAYER_JOIN_COLOR}" "Invalid STEAM UID (Player name has special characters!).\nPossible Profiles:" "${possible_steam_ids}"
else
send_webhook_notification "${title_message}" "${description_message}" "${WEBHOOK_PLAYER_JOIN_COLOR}"
fi

}

function send_player_leave_notification() {
local player_name="$1"
send_webhook_notification "${WEBHOOK_PLAYER_LEAVE_TITLE/PLAYER_NAME/$player_name}" "${WEBHOOK_PLAYER_LEAVE_DESCRIPTION/PLAYER_NAME/$player_name}" "${WEBHOOK_PLAYER_LEAVE_COLOR}"
local player_uid="$2"
local player_steam_uid="$3"
local possible_steam_ids="$4"

local title_message
local description_message

title_message="${WEBHOOK_PLAYER_LEAVE_TITLE/PLAYER_NAME/$player_name}"
title_message="${title_message/PLAYER_UID/$player_uid}"
title_message="${title_message/PLAYER_STEAM_UID/$player_steam_uid}"

description_message="${WEBHOOK_PLAYER_LEAVE_DESCRIPTION/PLAYER_NAME/$player_name}"
description_message="${description_message/PLAYER_UID/$player_uid}"
description_message="${description_message/PLAYER_STEAM_UID/$player_steam_uid}"

if [[ -n "${possible_steam_ids}" ]] && { [[ "${WEBHOOK_PLAYER_LEAVE_TITLE}" == *"PLAYER_STEAM_UID"* ]] || [[ "${WEBHOOK_PLAYER_LEAVE_DESCRIPTION}" == *"PLAYER_STEAM_UID"* ]]; }; then
send_webhook_notification "${title_message}" "${description_message}" "${WEBHOOK_PLAYER_LEAVE_COLOR}" "Invalid STEAM UID (Player name has special characters!).\nPossible Profiles:" "${possible_steam_ids}"
else
send_webhook_notification "${title_message}" "${description_message}" "${WEBHOOK_PLAYER_LEAVE_COLOR}"
fi
}
Loading

0 comments on commit e69b282

Please sign in to comment.