Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,19 @@ jobs:
- 'Directory.Build.props'
- '.github/workflows/ci.yaml'
- '.github/workflows/plugin-build.yaml'
# each game-server image builds both its match plugin and the
# practice plugin, so either source tree rebuilds it
# each game-server image builds its match plugin, the practice
# plugin and the player management plugin, so any of those source
# trees rebuilds it
css:
- *shared
- 'apps/counterstrikesharp/**'
- 'apps/utility-css/**'
- 'apps/player-management-css/**'
sw:
- *shared
- 'apps/swiftly/**'
- 'apps/utility-sw/**'
- 'apps/player-management-sw/**'
validator:
- 'apps/gamedata-validator/**'
- 'shared/gamedata/**'
Expand All @@ -75,7 +78,7 @@ jobs:
version_tag_regex: '^(?:css-)?v0\.0\.(\d+)$'
channel: ${{ needs.changes.outputs.channel }}
run_tests: true
extra_test_dir: apps/utility-css
extra_test_dirs: apps/utility-css
secrets: inherit

swiftly:
Expand All @@ -91,7 +94,8 @@ jobs:
version_floor: 41
channel: ${{ needs.changes.outputs.channel }}
run_tests: true
extra_test_dir: apps/utility-sw
# the player management suite covers the shared code both runtimes build
extra_test_dirs: apps/utility-sw apps/player-management-sw
secrets: inherit

gamedata-validator:
Expand Down
18 changes: 12 additions & 6 deletions .github/workflows/plugin-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ on:
required: false
default: false
type: boolean
extra_test_dir:
description: "second app whose suite ships in this image (the practice plugin)"
extra_test_dirs:
description: "space-separated apps whose suites ship in this image (the practice and player management plugins)"
required: false
default: ""
type: string
Expand All @@ -54,12 +54,12 @@ jobs:
if: inputs.run_tests
env:
APP_DIR: ${{ inputs.app_dir }}
EXTRA_TEST_DIR: ${{ inputs.extra_test_dir }}
EXTRA_TEST_DIRS: ${{ inputs.extra_test_dirs }}
run: |
dotnet test "$APP_DIR/test/FiveStack.Tests.csproj" -c Release
if [ -n "$EXTRA_TEST_DIR" ]; then
dotnet test "$EXTRA_TEST_DIR/test/FiveStack.Tests.csproj" -c Release
fi
for dir in $EXTRA_TEST_DIRS; do
dotnet test "$dir/test/FiveStack.Tests.csproj" -c Release
done

- name: Resolve version and tags
id: resolve
Expand Down Expand Up @@ -87,6 +87,7 @@ jobs:
core.setOutput('release_tag', `${prefix}-${sha}`);
core.setOutput('release_name', `Build ${prefix} ${sha}`);
core.setOutput('zip', `FiveStack-${prefix}-${sha}.zip`);
core.setOutput('player_management_zip', `PlayerManagement-${prefix}-${sha}.zip`);
core.setOutput('draft', 'true');
core.setOutput('tags', [repoTag('beta'), repoTag(`beta-${sha}`)].join('\n'));
return;
Expand All @@ -111,6 +112,9 @@ jobs:
core.setOutput('release_tag', `${prefix}-v${version}`);
core.setOutput('release_name', `${prefix}-v${version}`);
core.setOutput('zip', `FiveStack-${prefix}-v${version}.zip`);
// the web links servers outside a node straight to this asset by
// tag, so its name must stay derivable from the release tag
core.setOutput('player_management_zip', `PlayerManagement-${prefix}-v${version}.zip`);
core.setOutput('draft', 'false');
// the package name already namespaces the image, so its version tag stays bare
core.setOutput('tags', [repoTag('latest'), repoTag(sha), repoTag(`v${version}`)].join('\n'));
Expand Down Expand Up @@ -140,6 +144,7 @@ jobs:
run: |
docker create --name temp mod-builder:latest
docker cp temp:/mod-release.zip ./${{ steps.resolve.outputs.zip }}
docker cp temp:/player-management-release.zip ./${{ steps.resolve.outputs.player_management_zip }}
docker rm temp

- name: Build and Push server Docker image
Expand All @@ -165,6 +170,7 @@ jobs:
prerelease: ${{ steps.resolve.outputs.draft }}
files: |
./${{ steps.resolve.outputs.zip }}
./${{ steps.resolve.outputs.player_management_zip }}

# never prune the latest channel: game_server_nodes.pin_plugin_version resolves
# against its v0.0.N image tags
Expand Down
22 changes: 21 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
strategy:
fail-fast: false
matrix:
plugin: [counterstrikesharp, swiftly]
plugin: [counterstrikesharp, swiftly, player-management-sw]
steps:
- uses: actions/checkout@v7

Expand All @@ -32,3 +32,23 @@ jobs:

- name: Test
run: dotnet test apps/${{ matrix.plugin }}/test/FiveStack.Tests.csproj -c Release

# The player management plugins are only compiled by the image build after
# merge, and that build also releases the match plugins, so a broken plugin
# would block both; build them on every PR instead.
player-management-build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
app: [player-management-css, player-management-sw]
steps:
- uses: actions/checkout@v7

- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: "10.0.x"

- name: Build
run: dotnet build apps/${{ matrix.app }}/src/PlayerManagement.csproj -c Release
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This repo holds the CS2 game server images and the tooling that keeps them hones
| --- | --- | --- |
| [`apps/counterstrikesharp`](apps/counterstrikesharp) | CS2 server + 5stack plugin on CounterStrikeSharp | `ghcr.io/5stackgg/game-server-css` |
| [`apps/swiftly`](apps/swiftly) | the same plugin on SwiftlyS2 | `ghcr.io/5stackgg/game-server-sw` |
| [`apps/player-management-css`](apps/player-management-css) / [`apps/player-management-sw`](apps/player-management-sw) | enforces panel bans, mutes and gags on community servers | ships inside both game-server images |
| [`apps/gamedata-validator`](apps/gamedata-validator) | checks our byte-pattern signatures still resolve after a CS2 update | `ghcr.io/5stackgg/gamedata-validator` |

`shared/` holds what the two plugins have in common: the server `cfg/`, the setup `scripts/`,
Expand All @@ -22,3 +23,22 @@ version independently and share one tag namespace, so pick by prefix:

- CounterStrikeSharp — `css-v0.0.N`, asset `FiveStack-css-v0.0.N.zip`
- SwiftlyS2 — `sw-v0.0.N`, asset `FiveStack-sw-v0.0.N.zip`

# Player Management

Community (non-Ranked) dedicated servers run no match plugin, so the sanctions a moderator sets in
the panel reach them through this plugin instead. It syncs every 30 seconds and whenever the panel
sends `player_management_refresh`, kicks banned players, mutes voice and blocks chat. It has no
byte-pattern signatures, so a CS2 update does not break it.

A server on a 5stack node loads it automatically. Anywhere else, take the asset from the same
release as the match plugin and extract it into `game/csgo`:

- CounterStrikeSharp: `PlayerManagement-css-v0.0.N.zip`, configured in
`addons/counterstrikesharp/configs/plugins/PlayerManagement/PlayerManagement.json`
- SwiftlyS2: `PlayerManagement-sw-v0.0.N.zip`, configured in
`addons/swiftlys2/configs/plugins/PlayerManagement/config.jsonc` (under a `PlayerManagement` key)

Either config takes `API_DOMAIN`, `SERVER_ID` and `SERVER_API_PASSWORD`; the panel's player
management card shows the values for each server. Environment variables of the same names win over
the file. Over RCON, `player_management_status` reports what the plugin sees.
40 changes: 36 additions & 4 deletions apps/counterstrikesharp/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,47 @@ RUN dotnet build -c Release apps/utility-css/src/UtilityPractice.csproj -o relea

RUN rm -f /mod/release/CounterStrikeSharp.API.dll

# Community servers load this in place of the match plugin; its zip is also
# published beside the match plugin's for servers run outside a 5stack node.
FROM dotnet-sdk AS player-management-build

WORKDIR /mod

COPY Directory.Build.props ./
COPY apps/player-management-css/src/PlayerManagement.csproj apps/player-management-css/src/

RUN dotnet restore apps/player-management-css/src/PlayerManagement.csproj

COPY shared shared
COPY apps/player-management-css apps/player-management-css

ARG RELEASE_VERSION
ENV RELEASE_VERSION=${RELEASE_VERSION}

RUN sed -i "s/__RELEASE_VERSION__/${RELEASE_VERSION}/" apps/player-management-css/src/PlayerManagementPlugin.cs

RUN dotnet build -c Release apps/player-management-css/src/PlayerManagement.csproj -o release

RUN rm -f /mod/release/CounterStrikeSharp.API.dll

# New stage for creating the zip file
FROM debian:bookworm-slim AS zip-creator

RUN apt-get update && \
apt-get install -y --no-install-recommends zip && \
rm -rf /var/lib/apt/lists/*

WORKDIR /zip-content

COPY --from=build /mod/release ./addons/counterstrikesharp/plugins/FiveStack/./

RUN apt-get update && \
apt-get install -y --no-install-recommends zip && \
zip -r /mod-release.zip . && \
rm -rf /var/lib/apt/lists/*
RUN zip -r /mod-release.zip .

WORKDIR /player-management-zip-content

COPY --from=player-management-build /mod/release ./addons/counterstrikesharp/plugins/PlayerManagement/./

RUN zip -r /player-management-release.zip .

FROM sniper

Expand All @@ -94,6 +124,7 @@ ENV PLUGINS_DIR="/opt/custom-plugins"

ENV INSTALL_5STACK_PLUGIN=true
ENV INSTALL_UTILITY_PRACTICE_PLUGIN=false
ENV INSTALL_PLAYER_MANAGEMENT_PLUGIN=false

ENV GAME_ID="730"
ENV GAME_PARAMS=""
Expand Down Expand Up @@ -161,6 +192,7 @@ COPY shared/scripts /opt/scripts
COPY apps/counterstrikesharp/scripts /opt/scripts
COPY --from=build /mod/release /opt/mod
COPY --from=utility-build /mod/release /opt/utility-practice
COPY --from=player-management-build /mod/release /opt/player-management

RUN mv /opt/metamod/addons /opt/addons && \
cp -R /opt/counterstrikesharp/addons/metamod /opt/addons && \
Expand Down
12 changes: 12 additions & 0 deletions apps/counterstrikesharp/scripts/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,18 @@ if $INSTALL_UTILITY_PRACTICE_PLUGIN = true ; then
fi
fi

# A community server's sanctions: it runs no match plugin, so without this a
# mute or gag set in the panel never reaches it.
if $INSTALL_PLAYER_MANAGEMENT_PLUGIN = true ; then
echo "---Install Player Management---"
PLAYER_MANAGEMENT_PLUGIN_DIR="${INSTANCE_SERVER_DIR}/game/csgo/addons/counterstrikesharp/plugins/PlayerManagement"
if [ ! -e "$PLAYER_MANAGEMENT_PLUGIN_DIR" ]; then
ln -s "/opt/player-management" "$PLAYER_MANAGEMENT_PLUGIN_DIR"
else
echo "---Player Management: plugin dir already present, skipping /opt/player-management symlink---"
fi
fi

if [ ! -e "$INSTANCE_SERVER_DIR/game/csgo/addons/counterstrikesharp/configs/core.json" ]; then
cp "/opt/server-cfg/core.json" "$INSTANCE_SERVER_DIR/game/csgo/addons/counterstrikesharp/configs"
fi
Expand Down
25 changes: 25 additions & 0 deletions apps/player-management-css/src/PlayerManagement.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AssemblyName>PlayerManagement</AssemblyName>
<RootNamespace>PlayerManagement</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.376" />
</ItemGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Entities/PlayerManagement/**/*.cs" LinkBase="Shared" />
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Enums/eSanctionChange.cs" LinkBase="Shared" />
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Utilities/SanctionBook.cs" LinkBase="Shared" />
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Utilities/SanctionsClient.cs" LinkBase="Shared" />
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Utilities/SanctionSyncLoop.cs" LinkBase="Shared" />
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Utilities/PlayerManagementReport.cs" LinkBase="Shared" />
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Utilities/ChatUtility.cs" LinkBase="Shared" />
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Utilities/HttpClientProvider.cs" LinkBase="Shared" />
</ItemGroup>
<ItemGroup>
<None Include="lang/**/*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
25 changes: 25 additions & 0 deletions apps/player-management-css/src/PlayerManagementConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using CounterStrikeSharp.API.Core;
using FiveStack.Entities.PlayerManagement;

namespace PlayerManagement;

// CounterStrikeSharp writes this to
// addons/counterstrikesharp/configs/plugins/PlayerManagement/PlayerManagement.json
// on first load, which is where a server outside a 5stack node sets it.
public class PlayerManagementConfig : IBasePluginConfig
{
public int Version { get; set; } = 1;
public string API_DOMAIN { get; set; } = "https://api.5stack.gg";
public string SERVER_ID { get; set; } = "";
public string SERVER_API_PASSWORD { get; set; } = "";

public PlayerManagementSettings Settings()
{
return new PlayerManagementSettings
{
API_DOMAIN = API_DOMAIN,
SERVER_ID = SERVER_ID,
SERVER_API_PASSWORD = SERVER_API_PASSWORD,
}.Resolve(Environment.GetEnvironmentVariable);
}
}
Loading
Loading