├── .gitignore ├── sniper ├── .dockerignore ├── hooks │ ├── push │ └── build ├── etc │ ├── post.sh │ ├── pre.sh │ ├── server.cfg │ └── entry.sh └── Dockerfile ├── examples ├── cs2.cfg.tgz └── docker-compose.yml ├── .github └── workflows │ ├── docker-image.yml │ └── docker-publish.yml ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /sniper/.dockerignore: -------------------------------------------------------------------------------- 1 | hooks/ 2 | -------------------------------------------------------------------------------- /sniper/hooks/push: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker push --all-tags ${DOCKER_REPO} 3 | -------------------------------------------------------------------------------- /examples/cs2.cfg.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CM2Walki/CS2/HEAD/examples/cs2.cfg.tgz -------------------------------------------------------------------------------- /sniper/etc/post.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # POST HOOK 4 | # Make your customisation here 5 | echo "post-hook: noop" 6 | -------------------------------------------------------------------------------- /sniper/etc/pre.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # PRE HOOK 4 | # Make your customisation here 5 | echo "pre-hook: noop" 6 | -------------------------------------------------------------------------------- /sniper/hooks/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 4 | 5 | docker build --target=sniper-base -t $DOCKER_REPO:latest -t $DOCKER_REPO:base ${SCRIPT_DIR}/.. 6 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Build the Docker image 18 | working-directory: ./sniper 19 | run: docker build . --file Dockerfile --tag joedwards32/cs2:latest 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 John Edwards 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 | -------------------------------------------------------------------------------- /sniper/etc/server.cfg: -------------------------------------------------------------------------------- 1 | // Server Defaults 2 | 3 | hostname "{{SERVER_HOSTNAME}}" // Set server hostname 4 | sv_cheats {{SERVER_CHEATS}} // Enable or disable cheats 5 | sv_hibernate_when_empty {{SERVER_HIBERNATE}} // Disable server hibernation 6 | 7 | // Passwords 8 | 9 | rcon_password "{{SERVER_RCON_PW}}" // Set rcon password 10 | sv_password "{{SERVER_PW}}" // Set server password 11 | 12 | // CSTV 13 | 14 | sv_hibernate_postgame_delay 30 // Delay server hibernation after all clients disconnect 15 | 16 | tv_allow_camera_man 1 // Auto director allows spectators to become camera man 17 | tv_allow_static_shots 1 // Auto director uses fixed level cameras for shots 18 | tv_autorecord {{TV_AUTORECORD}} // Automatically records all games as CSTV demos: 0=off, 1=on. 19 | tv_chatgroupsize 0 // Set the default chat group size 20 | tv_chattimelimit 8 // Limits spectators to chat only every n seconds 21 | tv_debug 0 // CSTV debug info. 22 | tv_delay {{TV_DELAY}} // CSTV broadcast delay in seconds 23 | tv_delaymapchange 1 // Delays map change until broadcast is complete 24 | tv_deltacache 2 // Enable delta entity bit stream cache 25 | tv_dispatchmode 1 // Dispatch clients to relay proxies: 0=never, 1=if appropriate, 2=always 26 | tv_enable {{TV_ENABLE}} // Activates CSTV on server: 0=off, 1=on. 27 | tv_maxclients 10 // Maximum client number on CSTV server. 28 | tv_maxrate {{TV_MAXRATE}} // Max CSTV spectator bandwidth rate allowed, 0 == unlimited 29 | tv_name "{{SERVER_HOSTNAME}} CSTV" // CSTV host name 30 | tv_overridemaster 0 // Overrides the CSTV master root address. 31 | tv_port {{TV_PORT}} // Host SourceTV port 32 | tv_password "{{TV_PW}}" // CSTV password for clients 33 | tv_relaypassword "{{TV_RELAY_PW}}" // CSTV password for relay proxies 34 | tv_relayvoice 1 // Relay voice data: 0=off, 1=on 35 | tv_timeout 60 // CSTV connection timeout in seconds. 36 | tv_title "{{SERVER_HOSTNAME}} CSTV" // Set title for CSTV spectator UI 37 | tv_transmitall 1 // Transmit all entities (not only director view) 38 | 39 | // Logs 40 | 41 | log {{SERVER_LOG}} // Turns logging 'on' or 'off', defaults to 'on' 42 | mp_logmoney {{SERVER_LOG_MONEY}} // Turns money logging on/off: 0=off, 1=on 43 | mp_logdetail {{SERVER_LOG_DETAIL}} // Combat damage logging: 0=disabled, 1=enemy, 2=friendly, 3=all 44 | mp_logdetail_items {{SERVER_LOG_ITEMS}} // Turns item logging on/off: 0=off, 1=on 45 | -------------------------------------------------------------------------------- /sniper/Dockerfile: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | # Dockerfile that builds a CS2 Gameserver 3 | ########################################################### 4 | 5 | # Global ARGs 6 | 7 | ARG PUID=1000 8 | ARG PGID=1000 9 | ARG USER=steam 10 | ARG HOMEDIR="/home/steam" 11 | ARG STEAMCMDDIR="${HOMEDIR}/steamcmd" 12 | 13 | # SteamCMD Stage 14 | 15 | FROM registry.gitlab.steamos.cloud/steamrt/sniper/platform as steamcmd 16 | 17 | LABEL maintainer="joedwards32@gmail.com" 18 | 19 | ARG PUID 20 | ARG PGID 21 | ARG USER 22 | ARG HOMEDIR 23 | ARG STEAMCMDDIR 24 | 25 | RUN set -x \ 26 | # Install, update & upgrade packages 27 | && apt-get update \ 28 | && apt-get install -y --no-install-recommends --no-install-suggests \ 29 | wget \ 30 | ca-certificates \ 31 | lib32z1 \ 32 | simpleproxy \ 33 | libicu-dev \ 34 | unzip \ 35 | jq \ 36 | && useradd -u "${PUID}" -m "${USER}" \ 37 | && mkdir -p "${STEAMCMDDIR}" \ 38 | # Add entry script 39 | && chown -R "${PUID}:${PGID}" "${HOMEDIR}" \ 40 | # Clean up 41 | && apt-get clean \ 42 | && find /var/lib/apt/lists/ -type f -delete \ 43 | && su "${USER}" -c \ 44 | "curl -fsSL 'https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz' | tar xvzf - -C \"${STEAMCMDDIR}\" \ 45 | && \"./${STEAMCMDDIR}/steamcmd.sh\" +quit " 46 | 47 | # CS2 48 | 49 | FROM steamcmd AS cs2 50 | 51 | ARG PUID 52 | ARG PGID 53 | ARG USER 54 | ARG HOMEDIR 55 | ARG STEAMCMDDIR 56 | 57 | ENV STEAMAPPID=730 58 | ENV STEAMAPP=cs2 59 | ENV STEAMCMDDIR="${STEAMCMDDIR}" 60 | ENV STEAMAPPDIR="${HOMEDIR}/${STEAMAPP}-dedicated" 61 | ENV STEAMAPPVALIDATE=0 62 | 63 | COPY etc/entry.sh "${HOMEDIR}/entry.sh" 64 | COPY etc/server.cfg "/etc/server.cfg" 65 | COPY etc/pre.sh "/etc/pre.sh" 66 | COPY etc/post.sh "/etc/post.sh" 67 | 68 | ENV CS2_SERVERNAME="cs2 private server" \ 69 | CS2_CHEATS=0 \ 70 | CS2_IP=0.0.0.0 \ 71 | CS2_SERVER_HIBERNATE=0 \ 72 | CS2_PORT=27015 \ 73 | CS2_RCON_PORT="" \ 74 | CS2_MAXPLAYERS=10 \ 75 | CS2_RCONPW="changeme" \ 76 | CS2_MAPGROUP="mg_active" \ 77 | CS2_STARTMAP="de_inferno" \ 78 | CS2_GAMEALIAS="" \ 79 | CS2_GAMETYPE=0 \ 80 | CS2_GAMEMODE=1 \ 81 | CS2_LAN=0 \ 82 | TV_AUTORECORD=0 \ 83 | TV_ENABLE=0 \ 84 | TV_PORT=27020 \ 85 | TV_PW="changeme" \ 86 | TV_RELAY_PW="changeme" \ 87 | TV_MAXRATE=0 \ 88 | TV_DELAY=0 \ 89 | SRCDS_TOKEN="" \ 90 | CS2_CFG_URL="" \ 91 | CS2_LOG="on" \ 92 | CS2_LOG_MONEY=0 \ 93 | CS2_LOG_DETAIL=0 \ 94 | CS2_LOG_ITEMS=0 \ 95 | CS2_ADDITIONAL_ARGS="" 96 | 97 | # Set permissions on STEAMAPPDIR 98 | # Permissions may need to be reset if persistent volume mounted 99 | RUN set -x \ 100 | && mkdir -p "${STEAMAPPDIR}" \ 101 | && chmod +x "${HOMEDIR}/entry.sh" \ 102 | && chown -R "${PUID}:${PGID}" "${STEAMAPPDIR}" \ 103 | && chmod 0777 "${STEAMAPPDIR}" 104 | 105 | # Switch to user 106 | USER ${PUID}:${PGID} 107 | 108 | WORKDIR ${HOMEDIR} 109 | 110 | CMD ["bash", "entry.sh"] 111 | 112 | # Expose ports 113 | EXPOSE 27015/tcp \ 114 | 27015/udp \ 115 | 27020/udp 116 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker Build and Publish 2 | 3 | # This workflow uses actions that are not certified by GitHub. 4 | # They are provided by a third-party and are governed by 5 | # separate terms of service, privacy policy, and support 6 | # documentation. 7 | 8 | on: 9 | schedule: 10 | - cron: '21 5 * * 0' 11 | push: 12 | branches: [ "main" ] 13 | # Publish semver tags as releases. 14 | tags: [ 'v*.*.*' ] 15 | pull_request: 16 | branches: [ "main" ] 17 | 18 | env: 19 | REGISTRY: docker.io 20 | GHCR_REGISTRY: ghcr.io 21 | # github.repository as / 22 | IMAGE_NAME: ${{ github.repository }} 23 | 24 | jobs: 25 | build: 26 | 27 | runs-on: ubuntu-latest 28 | permissions: 29 | contents: read 30 | packages: write 31 | # This is used to complete the identity challenge 32 | # with sigstore/fulcio when running outside of PRs. 33 | id-token: write 34 | 35 | steps: 36 | - name: Check out the repo 37 | uses: actions/checkout@v4 38 | 39 | # Login against a Docker registry except on PR 40 | # https://github.com/docker/login-action 41 | - name: Log in to Docker Hub 42 | if: github.event_name != 'pull_request' 43 | uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a 44 | with: 45 | username: ${{ secrets.DOCKER_USERNAME }} 46 | password: ${{ secrets.DOCKER_PASSWORD }} 47 | 48 | # Login to GitHub Container Registry 49 | - name: Log in to GHCR 50 | if: github.event_name != 'pull_request' 51 | uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a 52 | with: 53 | registry: ${{ env.GHCR_REGISTRY }} 54 | username: ${{ github.actor }} 55 | password: ${{ secrets.GITHUB_TOKEN }} 56 | 57 | # Extract metadata (tags, labels) for both registires 58 | # https://github.com/docker/metadata-action 59 | - name: Extract Docker metadata 60 | id: meta 61 | uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 62 | with: 63 | images: | 64 | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 65 | ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }} 66 | tags: | 67 | type=raw,value=latest,enable={{is_default_branch}} 68 | type=semver,pattern={{version}} 69 | 70 | - name: Pelican Docker metadata 71 | id: meta_pelican 72 | uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 73 | with: 74 | images: | 75 | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 76 | ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }} 77 | tags: | 78 | type=raw,value=latest-pelican,enable={{is_default_branch}} 79 | type=semver,pattern={{version}}-pelican 80 | 81 | # Build and push Docker image with Buildx (don't push on PR) 82 | # https://github.com/docker/build-push-action 83 | - name: Build and push Docker image 84 | uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 85 | with: 86 | context: ./sniper 87 | file: ./sniper/Dockerfile 88 | push: ${{ github.event_name != 'pull_request' }} 89 | tags: ${{ steps.meta.outputs.tags }} 90 | labels: ${{ steps.meta.outputs.labels }} 91 | 92 | - name: Build and push Pelican image 93 | uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 94 | with: 95 | context: ./sniper 96 | file: ./sniper/Dockerfile 97 | build-args: | 98 | USER=container 99 | PUID=999 100 | PGID=988 101 | push: ${{ github.event_name != 'pull_request' }} 102 | tags: ${{ steps.meta_pelican.outputs.tags }} 103 | labels: ${{ steps.meta_pelican.outputs.labels }} 104 | -------------------------------------------------------------------------------- /examples/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | cs2-server: 3 | image: joedwards32/cs2 4 | container_name: cs2-dedicated 5 | environment: 6 | # Server configuration 7 | - SRCDS_TOKEN # Game Server Token from https://steamcommunity.com/dev/managegameservers 8 | - DEBUG=0 # (0 - off, 1 - steamcmd, 2 - cs2, 3 - all) 9 | - STEAMAPPVALIDATE=0 # (0 - no validation, 1 - enable validation) 10 | - CS2_SERVERNAME=changeme # (Set the visible name for your private server.) 11 | - CS2_CHEATS=0 # (0 - disable cheats, 1 - enable cheats) 12 | - CS2_PORT=27015 # (CS2 server listen port tcp_udp) 13 | - CS2_SERVER_HIBERNATE=0 # (Put server in a low CPU state when there are no players. 0 - hibernation disabled, 1 - hibernation enabled) 14 | - CS2_RCON_PORT # (Optional, use a simple TCP proxy to have RCON listen on an alternative port. Useful for services like AWS Fargate which do not support mixed protocol ports.) 15 | - CS2_LAN=0 # (0 - LAN mode disabled, 1 - LAN Mode enabled) 16 | - CS2_RCONPW=changeme # (RCON password) 17 | - CS2_PW # (Optional, CS2 server password) 18 | - CS2_MAXPLAYERS=10 # (Max players) 19 | - CS2_ADDITIONAL_ARGS # (Optional additional arguments to pass into cs2) 20 | - CS2_CFG_URL # HTTP/HTTPS URL to fetch a Tar Gzip bundle, Tar or Zip archive of configuration files/mods 21 | # Game modes 22 | - CS2_GAMEALIAS # (Game type, e.g. casual, competitive, deathmatch. See https://developer.valvesoftware.com/wiki/Counter-Strike_2/Dedicated_Servers) 23 | - CS2_GAMETYPE=0 # (Used if CS2_GAMEALIAS not defined. See https://developer.valvesoftware.com/wiki/Counter-Strike_2/Dedicated_Servers) 24 | - CS2_GAMEMODE=1 # (Used if CS2_GAMEALIAS not defined. See https://developer.valvesoftware.com/wiki/Counter-Strike_2/Dedicated_Servers) 25 | - CS2_MAPGROUP=mg_active # (Map pool. Ignored if Workshop maps are defined.) 26 | - CS2_STARTMAP=de_inferno # (Start map. Ignored if Workshop maps are defined.) 27 | # Workshop Maps 28 | - CS2_HOST_WORKSHOP_COLLECTION # The workshop collection to use 29 | - CS2_HOST_WORKSHOP_MAP # The workshop map to use. If collection is also defined, this is the starting map. 30 | # Bots 31 | - CS2_BOT_DIFFICULTY # (0 - easy, 1 - normal, 2 - hard, 3 - expert) 32 | - CS2_BOT_QUOTA # (Number of bots) 33 | - CS2_BOT_QUOTA_MODE # (fill, competitive) 34 | # TV 35 | - TV_AUTORECORD=0 # Automatically records all games as CSTV demos: 0=off, 1=on. 36 | - TV_ENABLE=0 # Activates CSTV on server: 0=off, 1=on. 37 | - TV_PORT=27020 # Host SourceTV port 38 | - TV_PW=changeme # CSTV password for clients 39 | - TV_RELAY_PW=changeme # CSTV password for relay proxies 40 | - TV_MAXRATE=0 # World snapshots to broadcast per second. Affects camera tickrate. 41 | - TV_DELAY=0 # CSTV broadcast delay in seconds 42 | # Logs 43 | - CS2_LOG=on # 'on'/'off' 44 | - CS2_LOG_MONEY=0 # Turns money logging on/off: (0=off, 1=on) 45 | - CS2_LOG_DETAIL=0 # Combat damage logging: (0=disabled, 1=enemy, 2=friendly, 3=all) 46 | - CS2_LOG_ITEMS=0 # Turns item logging on/off: (0=off, 1=on) 47 | volumes: 48 | - cs2:/home/steam/cs2-dedicated/ # Persistent data volume mount point inside container 49 | ports: 50 | - "27015:27015/tcp" # TCP 51 | - "27015:27015/udp" # UDP 52 | - "27020:27020/udp" # UDP 53 | stdin_open: true # Add local console for docker attach, docker attach --sig-proxy=false cs2-dedicated 54 | tty: true # Add local console for docker attach, docker attach --sig-proxy=false cs2-dedicated 55 | volumes: 56 | cs2: 57 | -------------------------------------------------------------------------------- /sniper/etc/entry.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Debug 4 | 5 | ## Steamcmd debugging 6 | if [[ $DEBUG -eq 1 ]] || [[ $DEBUG -eq 3 ]]; then 7 | STEAMCMD_SPEW="+set_spew_level 4 4" 8 | fi 9 | ## CS2 server debugging 10 | if [[ $DEBUG -eq 2 ]] || [[ $DEBUG -eq 3 ]]; then 11 | CS2_LOG="on" 12 | CS2_LOG_MONEY=1 13 | CS2_LOG_DETAIL=3 14 | CS2_LOG_ITEMS=1 15 | fi 16 | 17 | # Create App Dir 18 | mkdir -p "${STEAMAPPDIR}" || true 19 | 20 | # Download Updates 21 | if [[ "$STEAMAPPVALIDATE" -eq 1 ]]; then 22 | VALIDATE="validate" 23 | else 24 | VALIDATE="" 25 | fi 26 | 27 | ## SteamCMD can fail to download 28 | ## Retry logic 29 | MAX_ATTEMPTS=3 30 | attempt=0 31 | while [[ $steamcmd_rc != 0 ]] && [[ $attempt -lt $MAX_ATTEMPTS ]]; do 32 | ((attempt+=1)) 33 | if [[ $attempt -gt 1 ]]; then 34 | echo "Retrying SteamCMD, attempt ${attempt}" 35 | # Stale appmanifest data can lead for HTTP 401 errors when requesting old 36 | # files from SteamPipe CDN 37 | echo "Removing steamapps (appmanifest data)..." 38 | rm -rf "${STEAMAPPDIR}/steamapps" 39 | fi 40 | eval bash "${STEAMCMDDIR}/steamcmd.sh" "${STEAMCMD_SPEW}"\ 41 | +force_install_dir "${STEAMAPPDIR}" \ 42 | +@bClientTryRequestManifestWithoutCode 1 \ 43 | +login anonymous \ 44 | +app_update "${STEAMAPPID}" "${VALIDATE}"\ 45 | +quit 46 | steamcmd_rc=$? 47 | done 48 | 49 | ## Exit if steamcmd fails 50 | if [[ $steamcmd_rc != 0 ]]; then 51 | exit $steamcmd_rc 52 | fi 53 | 54 | # FIX: steamclient.so fix 55 | mkdir -p ~/.steam/sdk64 56 | ln -sfT ${STEAMCMDDIR}/linux64/steamclient.so ~/.steam/sdk64/steamclient.so 57 | 58 | # Install server.cfg 59 | mkdir -p $STEAMAPPDIR/game/csgo/cfg 60 | cp /etc/server.cfg "${STEAMAPPDIR}"/game/csgo/cfg/server.cfg 61 | 62 | # Install hooks if they don't already exist 63 | if [[ ! -f "${STEAMAPPDIR}/pre.sh" ]] ; then 64 | cp /etc/pre.sh "${STEAMAPPDIR}/pre.sh" 65 | fi 66 | if [[ ! -f "${STEAMAPPDIR}/post.sh" ]] ; then 67 | cp /etc/post.sh "${STEAMAPPDIR}/post.sh" 68 | fi 69 | 70 | # Download and extract custom config bundle 71 | if [[ ! -z $CS2_CFG_URL ]]; then 72 | echo "Downloading config pack from ${CS2_CFG_URL}" 73 | 74 | TEMP_DIR=$(mktemp -d) 75 | TEMP_FILE="${TEMP_DIR}/$(basename ${CS2_CFG_URL})" 76 | wget -qO "${TEMP_FILE}" "${CS2_CFG_URL}" 77 | 78 | case "${TEMP_FILE}" in 79 | *.zip) 80 | echo "Extracting ZIP file..." 81 | unzip -o -q "${TEMP_FILE}" -d "${STEAMAPPDIR}" 82 | ;; 83 | *.tar.gz | *.tgz) 84 | echo "Extracting TAR.GZ or TGZ file..." 85 | tar xvzf "${TEMP_FILE}" -C "${STEAMAPPDIR}" 86 | ;; 87 | *.tar) 88 | echo "Extracting TAR file..." 89 | tar xvf "${TEMP_FILE}" -C "${STEAMAPPDIR}" 90 | ;; 91 | *) 92 | echo "Unsupported file type" 93 | rm -rf "${TEMP_DIR}" 94 | exit 1 95 | ;; 96 | esac 97 | 98 | rm -rf "${TEMP_DIR}" 99 | fi 100 | 101 | # Rewrite Config Files 102 | 103 | sed -i -e "s/{{SERVER_HOSTNAME}}/${CS2_SERVERNAME}/g" \ 104 | -e "s/{{SERVER_CHEATS}}/${CS2_CHEATS}/g" \ 105 | -e "s/{{SERVER_HIBERNATE}}/${CS2_SERVER_HIBERNATE}/g" \ 106 | -e "s/{{SERVER_PW}}/${CS2_PW}/g" \ 107 | -e "s/{{SERVER_RCON_PW}}/${CS2_RCONPW}/g" \ 108 | -e "s/{{TV_ENABLE}}/${TV_ENABLE}/g" \ 109 | -e "s/{{TV_PORT}}/${TV_PORT}/g" \ 110 | -e "s/{{TV_AUTORECORD}}/${TV_AUTORECORD}/g" \ 111 | -e "s/{{TV_PW}}/${TV_PW}/g" \ 112 | -e "s/{{TV_RELAY_PW}}/${TV_RELAY_PW}/g" \ 113 | -e "s/{{TV_MAXRATE}}/${TV_MAXRATE}/g" \ 114 | -e "s/{{TV_DELAY}}/${TV_DELAY}/g" \ 115 | -e "s/{{SERVER_LOG}}/${CS2_LOG}/g" \ 116 | -e "s/{{SERVER_LOG_MONEY}}/${CS2_LOG_MONEY}/g" \ 117 | -e "s/{{SERVER_LOG_DETAIL}}/${CS2_LOG_DETAIL}/g" \ 118 | -e "s/{{SERVER_LOG_ITEMS}}/${CS2_LOG_ITEMS}/g" \ 119 | "${STEAMAPPDIR}"/game/csgo/cfg/server.cfg 120 | 121 | if [[ ! -z $CS2_BOT_DIFFICULTY ]] ; then 122 | sed -i "s/bot_difficulty.*/bot_difficulty ${CS2_BOT_DIFFICULTY}/" "${STEAMAPPDIR}"/game/csgo/cfg/* 123 | fi 124 | if [[ ! -z $CS2_BOT_QUOTA ]] ; then 125 | sed -ri "s/bot_quota[[:space:]]+.*/bot_quota ${CS2_BOT_QUOTA}/" "${STEAMAPPDIR}"/game/csgo/cfg/* 126 | fi 127 | if [[ ! -z $CS2_BOT_QUOTA_MODE ]] ; then 128 | sed -i "s/bot_quota_mode.*/bot_quota_mode ${CS2_BOT_QUOTA_MODE}/" "${STEAMAPPDIR}"/game/csgo/cfg/* 129 | fi 130 | 131 | # Switch to server directory 132 | cd "${STEAMAPPDIR}/game/" 133 | 134 | # Pre Hook 135 | source "${STEAMAPPDIR}/pre.sh" 136 | 137 | # Construct server arguments 138 | 139 | if [[ -z $CS2_GAMEALIAS ]]; then 140 | # If CS2_GAMEALIAS is undefined then default to CS2_GAMETYPE and CS2_GAMEMODE 141 | CS2_GAME_MODE_ARGS="+game_type ${CS2_GAMETYPE} +game_mode ${CS2_GAMEMODE}" 142 | else 143 | # Else, use alias to determine game mode 144 | CS2_GAME_MODE_ARGS="+game_alias ${CS2_GAMEALIAS}" 145 | fi 146 | 147 | if [[ -z $CS2_IP ]]; then 148 | CS2_IP_ARGS="" 149 | else 150 | CS2_IP_ARGS="-ip ${CS2_IP}" 151 | fi 152 | 153 | if [[ ! -z $SRCDS_TOKEN ]]; then 154 | SV_SETSTEAMACCOUNT_ARGS="+sv_setsteamaccount ${SRCDS_TOKEN}" 155 | fi 156 | 157 | if [[ ! -z $CS2_HOST_WORKSHOP_COLLECTION ]] || [[ ! -z $CS2_HOST_WORKSHOP_MAP ]]; then 158 | CS2_MP_MATCH_END_CHANGELEVEL="+mp_match_end_changelevel true" # https://github.com/joedwards32/CS2/issues/57#issuecomment-2245595368 159 | CS2_STARTMAP="\" # https://github.com/joedwards32/CS2/issues/57#issuecomment-2245595368 160 | CS2_MAPGROUP_ARGS= 161 | else 162 | CS2_MAPGROUP_ARGS="+mapgroup ${CS2_MAPGROUP}" 163 | fi 164 | 165 | if [[ ! -z $CS2_HOST_WORKSHOP_COLLECTION ]]; then 166 | CS2_HOST_WORKSHOP_COLLECTION_ARGS="+host_workshop_collection ${CS2_HOST_WORKSHOP_COLLECTION}" 167 | fi 168 | 169 | if [[ ! -z $CS2_HOST_WORKSHOP_MAP ]]; then 170 | CS2_HOST_WORKSHOP_MAP_ARGS="+host_workshop_map ${CS2_HOST_WORKSHOP_MAP}" 171 | fi 172 | 173 | if [[ ! -z $CS2_PW ]]; then 174 | CS2_PW_ARGS="+sv_password ${CS2_PW}" 175 | fi 176 | 177 | # Start Server 178 | 179 | if [[ ! -z $CS2_RCON_PORT ]]; then 180 | echo "Establishing Simpleproxy for ${CS2_RCON_PORT} to 127.0.0.1:${CS2_PORT}" 181 | simpleproxy -L "${CS2_RCON_PORT}" -R 127.0.0.1:"${CS2_PORT}" & 182 | fi 183 | 184 | echo "Starting CS2 Dedicated Server" 185 | eval "./cs2.sh" -dedicated \ 186 | "${CS2_IP_ARGS}" -port "${CS2_PORT}" \ 187 | -console \ 188 | -usercon \ 189 | -maxplayers "${CS2_MAXPLAYERS}" \ 190 | "${CS2_GAME_MODE_ARGS}" \ 191 | "${CS2_MAPGROUP_ARGS}" \ 192 | +map "${CS2_STARTMAP}" \ 193 | "${CS2_HOST_WORKSHOP_COLLECTION_ARGS}" \ 194 | "${CS2_HOST_WORKSHOP_MAP_ARGS}" \ 195 | "${CS2_MP_MATCH_END_CHANGELEVEL}" \ 196 | +rcon_password "${CS2_RCONPW}" \ 197 | "${SV_SETSTEAMACCOUNT_ARGS}" \ 198 | "${CS2_PW_ARGS}" \ 199 | +sv_lan "${CS2_LAN}" \ 200 | +tv_port "${TV_PORT}" \ 201 | "${CS2_ADDITIONAL_ARGS}" 202 | 203 | # Post Hook 204 | source "${STEAMAPPDIR}/post.sh" 205 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Docker Image CI](https://github.com/joedwards32/CS2/actions/workflows/docker-image.yml/badge.svg?branch=main)](https://github.com/joedwards32/CS2/actions/workflows/docker-image.yml) [![Docker Build and Publish](https://github.com/joedwards32/CS2/actions/workflows/docker-publish.yml/badge.svg)](https://github.com/joedwards32/CS2/actions/workflows/docker-publish.yml) 2 | 3 | # What is Counter-Strike 2? 4 | For over two decades, Counter-Strike has offered an elite competitive experience, one shaped by millions of players from across the globe. And now the next chapter in the CS story is about to begin. This is Counter-Strike 2. 5 | This Docker image contains the dedicated server of the game. 6 | 7 | > [CS2](https://store.steampowered.com/app/730/CounterStrike_2/) 8 | 9 | logo 10 | 11 | # How to use this image 12 | 13 | ## Available Container Image Repositories 14 | 15 | * Docker Hub: `joedwards32/cs2` 16 | * GitHub: `ghcr.io/joedwards32/cs2` 17 | 18 | ## Hosting a simple game server 19 | 20 | Running using Docker: 21 | ```console 22 | $ SRCDS_TOKEN="..." # check https://steamcommunity.com/dev/managegameservers 23 | $ docker run -d --name=cs2 -e SRCDS_TOKEN="$SRCDS_TOKEN" -p 27015:27015/tcp -p 27015:27015/udp -p 27020:27020/udp joedwards32/cs2 24 | ``` 25 | 26 | Running using a bind mount for data persistence on container recreation: 27 | ```console 28 | $ mkdir -p $(pwd)/cs2-data 29 | $ chown 1000:1000 $(pwd)/cs2-data # Makes sure the directory is writeable by the unprivileged container user with uid 1000, known as steam 30 | $ SRCDS_TOKEN="..." # check https://steamcommunity.com/dev/managegameservers 31 | $ docker run -d --name=cs2 -e SRCDS_TOKEN="$SRCDS_TOKEN" -v $(pwd)/cs2-data:/home/steam/cs2-dedicated/ -p 27015:27015/tcp -p 27015:27015/udp -p 27020:27020/udp joedwards32/cs2 32 | ``` 33 | 34 | or using docker-compose, see [examples](https://github.com/joedwards32/CS2/blob/main/examples/docker-compose.yml): 35 | ```console 36 | # Remember to update passwords and SRCDS_TOKEN in your compose file 37 | $ docker compose --file examples/docker-compose.yml up -d cs2-server 38 | ``` 39 | 40 | You must have at least **60GB** of free disk space! See [System Requirements](./#system-requirements). 41 | 42 | **The container will automatically update the game on startup, so if there is a game update just restart the container.** 43 | 44 | # Configuration 45 | 46 | ## System Requirements 47 | 48 | Minimum system requirements are: 49 | 50 | * 2 CPUs 51 | * 2GiB RAM 52 | * 60GB of disk space for the container or mounted as a persistent volume on `/home/steam/cs2-dedicated/` 53 | * Note: More space may be required if you plan to install mods 54 | 55 | ## Environment Variables 56 | Feel free to overwrite these environment variables, using -e (--env): 57 | 58 | **Note:** `/` characters in Counter-Strike-related environment variables **must be escaped** as `\/` (e. g. `CS2_SERVERNAME="My Server 1\/3` will result in `My Server 1/3` in-game). Otherwise, this may cause unexpected behavior during configuration processing 59 | 60 | ### Server Configuration 61 | 62 | ```dockerfile 63 | SRCDS_TOKEN="" (Game Server Token from https://steamcommunity.com/dev/managegameservers) 64 | CS2_SERVERNAME="changeme" (Set the visible name for your private server.) 65 | CS2_CHEATS=0 (0 - disable cheats, 1 - enable cheats) 66 | CS2_SERVER_HIBERNATE=0 (Put server in a low CPU state when there are no players. 67 | 0 - hibernation disabled, 1 - hibernation enabled 68 | n.b. hibernation has been observed to trigger server crashes) 69 | CS2_IP="" (CS2 server listening IP address, 0.0.0.0 - all IP addresses on the local machine, empty - IP identified automatically) 70 | CS2_PORT=27015 (CS2 server listen port tcp_udp) 71 | CS2_RCON_PORT="" (Optional, use a simple TCP proxy to have RCON listen on an alternative port. 72 | Useful for services like AWS Fargate which do not support mixed protocol ports.) 73 | CS2_LAN="0" (0 - LAN mode disabled, 1 - LAN Mode enabled) 74 | CS2_RCONPW="changeme" (RCON password) 75 | CS2_PW="" (Optional, CS2 server password) 76 | CS2_MAXPLAYERS=10 (Max players) 77 | CS2_ADDITIONAL_ARGS="" (Optional additional arguments to pass into cs2) 78 | ``` 79 | 80 | **Note:** When using `CS2_RCON_PORT` don't forget to map the port chosen with TCP protocol (e.g., add `-p 27050:27050/tcp` on the `docker run` command or add the port to the `docker-compose.yml` file). 81 | 82 | ### Game Modes 83 | 84 | ```dockerfile 85 | CS2_GAMEALIAS="" (Game type, e.g. casual, competitive, deathmatch. 86 | See https://developer.valvesoftware.com/wiki/Counter-Strike_2/Dedicated_Servers) 87 | CS2_GAMETYPE=0 (Used if CS2_GAMEALIAS not defined. See https://developer.valvesoftware.com/wiki/Counter-Strike_2/Dedicated_Servers) 88 | CS2_GAMEMODE=1 (Used if CS2_GAMEALIAS not defined. See https://developer.valvesoftware.com/wiki/Counter-Strike_2/Dedicated_Servers) 89 | CS2_MAPGROUP="mg_active" (Map pool. Ignored if workshop maps are defined.) 90 | CS2_STARTMAP="de_inferno" (Start map. Ignored if workshop maps are defined.) 91 | ``` 92 | 93 | ### Bots 94 | 95 | ```dockerfile 96 | CS2_BOT_DIFFICULTY="" (0 - easy, 1 - normal, 2 - hard, 3 - expert) 97 | CS2_BOT_QUOTA="" (Number of bots) 98 | CS2_BOT_QUOTA_MODE="" (fill, competitive) 99 | ``` 100 | 101 | ### CSTV/SourceTV 102 | 103 | ```dockerfile 104 | TV_ENABLE=0 (0 - disable, 1 - enable) 105 | TV_PORT=27020 (SourceTV/CSTV port to bind to) 106 | TV_AUTORECORD=0 (Automatically record all games as CSTV demos: 0=off, 1=on) 107 | TV_PW="changeme" (CSTV password for clients) 108 | TV_RELAY_PW="changeme" (CSTV password for relay proxies) 109 | TV_MAXRATE=0 (Max CSTV spectator bandwidth rate allowed, 0 == unlimited) 110 | TV_DELAY=0 (CSTV broadcast delay in seconds) 111 | ``` 112 | 113 | ### Logs 114 | 115 | ```dockerfile 116 | CS2_LOG="on" ('on'/'off') 117 | CS2_LOG_MONEY=0 (Turns money logging on/off: 0=off, 1=on) 118 | CS2_LOG_DETAIL=0 (Combat damage logging: 0=disabled, 1=enemy, 2=friendly, 3=all) 119 | CS2_LOG_ITEMS=0 (Turns item logging on/off: 0=off, 1=on) 120 | ``` 121 | 122 | ### Steam Workshop 123 | 124 | Support for Steam Workshop is experimental! 125 | 126 | ```dockerfile 127 | CS2_HOST_WORKSHOP_MAP="" (Steam Workshop Map ID to load on server start) 128 | CS2_HOST_WORKSHOP_COLLECTION="" (Steam Workshop Collection ID to download) 129 | ``` 130 | 131 | If a Workshop Collection is set, maps can be selected via rcon. E.g: 132 | 133 | ``` 134 | ds_workshop_listmaps 135 | ds_workshop_changelevel $map_name 136 | ``` 137 | 138 | # Customizing this Container 139 | 140 | ## Debug Logging 141 | 142 | If you want to increase the verbosity of log output set the `DEBUG` environment variable: 143 | 144 | ```dockerfile 145 | DEBUG=0 (0=none, 1=steamcmd, 2=cs2, 3=all) 146 | ``` 147 | 148 | ## Validating Game Files 149 | 150 | If you break the game through your customisations and want steamcmd to validate and redownload then set the `STEAMAPPVALIDATE` environment variable to `1`: 151 | 152 | ```dockerfile 153 | STEAMAPPVALIDATE=0 (0=skip validation, 1=validate game files) 154 | ``` 155 | 156 | ## Pre and Post Hooks 157 | 158 | The container includes two scripts for executing custom actions: 159 | 160 | * `/home/steam/cs2-dedicated/pre.sh` is executed before the CS2 server starts 161 | * `/home/steam/cs2-dedicated/post.sh` is executed after the CS2 server stops 162 | 163 | When using a persient volume mounted at `/home/steam/cs2-dedicated/` you may edit these scripts to perform custom actions, such as enabling metamod. 164 | 165 | Alternatively, you may have docker mount files from outside the container to override these files. E.g.: 166 | 167 | ``` 168 | -v /path/to/pre.sh:/home/steam/cs2-dedicated/pre.sh 169 | ``` 170 | 171 | ## Overriding Game Mode Defaults 172 | 173 | The default configurations for each game mode are stored in `/home/steam/cs2-dedicated/game/csgo/cfg/`. For example, the Competitive mode defaults are set by `gamemode_competitive.cfg`. 174 | 175 | When using a persistent volume mounted at `/home/steam/cs2-dedicated/`, these defaults can be overridden by adding your own settings to `gamemode_competitive_server.cfg`. 176 | 177 | ``` 178 | // Game Mode Competitive Server Overrides 179 | 180 | mp_maxrounds 16 // Shorter games 181 | ``` 182 | 183 | ## Customisation Bundle 184 | 185 | The container can be instructed to download a extract a Tar Gzip bundle, Tar or Zip archive of configuration files and other customisations from a given URL. 186 | 187 | ```dockerfile 188 | CS2_CFG_URL="" (HTTP/HTTPS URL to fetch a Tar Gzip bundle, Tar or Zip archive of configuration files/mods) 189 | ``` 190 | 191 | See [examples](https://github.com/joedwards32/CS2/blob/main/examples/cs2.cfg.tgz) for a correctly formatted Tar Gzip customisation bundle, the same format applies to all archive types. 192 | 193 | 194 | # Credits 195 | 196 | This container leans heavily on the work of [CM2Walki](https://github.com/CM2Walki/), especially his [SteamCMD](https://github.com/CM2Walki/steamcmd) container image. GG! 197 | --------------------------------------------------------------------------------