From 301b244f58626b5c3ad2f03a568bbb7a4cc2a13b Mon Sep 17 00:00:00 2001 From: Leonard Jonathan Oh Date: Sat, 1 Oct 2022 02:36:49 +0000 Subject: [PATCH] Docs: Add a full BF2 stack example in `docs/full-bf2-stack-example` --- README.md | 7 - docker-compose.example.yml | 110 ------- docs/full-bf2-stack-example/.env | 1 + docs/full-bf2-stack-example/README.md | 161 ++++++++++ .../config/ASP/armyAbbreviationMap.php | 204 +++++++++++++ .../config/ASP/backendAwards.php | 235 +++++++++++++++ .../config/ASP/config.php | 54 ++++ .../config/ASP/nginx/nginx.conf | 121 ++++++++ .../config/ASP/php-fpm.d/www.conf | 10 + .../config/ASP/php/conf.d/php.ini | 27 ++ .../config/ASP/ranks.php | 253 ++++++++++++++++ .../config/bf2sclone/config.inc.php | 42 +++ .../config/bf2sclone/nginx/nginx.conf | 105 +++++++ .../config/bf2sclone/php-fpm.d/www.conf | 10 + .../config/bf2sclone/php/conf.d/php.ini | 27 ++ .../config/coredns/Corefile | 22 ++ .../config/coredns/hosts | 12 + docs/full-bf2-stack-example/config/db/my.cnf | 43 +++ .../full-bf2-stack-example/docker-compose.yml | 278 ++++++++++++++++++ 19 files changed, 1605 insertions(+), 117 deletions(-) delete mode 100644 docker-compose.example.yml create mode 100644 docs/full-bf2-stack-example/.env create mode 100644 docs/full-bf2-stack-example/README.md create mode 100644 docs/full-bf2-stack-example/config/ASP/armyAbbreviationMap.php create mode 100644 docs/full-bf2-stack-example/config/ASP/backendAwards.php create mode 100644 docs/full-bf2-stack-example/config/ASP/config.php create mode 100644 docs/full-bf2-stack-example/config/ASP/nginx/nginx.conf create mode 100644 docs/full-bf2-stack-example/config/ASP/php-fpm.d/www.conf create mode 100644 docs/full-bf2-stack-example/config/ASP/php/conf.d/php.ini create mode 100644 docs/full-bf2-stack-example/config/ASP/ranks.php create mode 100644 docs/full-bf2-stack-example/config/bf2sclone/config.inc.php create mode 100644 docs/full-bf2-stack-example/config/bf2sclone/nginx/nginx.conf create mode 100644 docs/full-bf2-stack-example/config/bf2sclone/php-fpm.d/www.conf create mode 100644 docs/full-bf2-stack-example/config/bf2sclone/php/conf.d/php.ini create mode 100644 docs/full-bf2-stack-example/config/coredns/Corefile create mode 100644 docs/full-bf2-stack-example/config/coredns/hosts create mode 100644 docs/full-bf2-stack-example/config/db/my.cnf create mode 100644 docs/full-bf2-stack-example/docker-compose.yml diff --git a/README.md b/README.md index ed50ffef..8acc276f 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,6 @@ The new BF2Statistics 3.0 ASP, currently in public Beta. The GameSpy server to m See [docker-compose.example.yml](docker-compose.example.yml) example showing how to deploy BF2Statistics using `docker-compose`. -Notes: -- Mount the [`config.php`](./config/ASP/config.php) with write permissions, or else `ASP` dashboard will throw an error. Use `System > Edit Configuration` as reference to customize the config file. -- Optional: Mount your customized [`armyAbbreviationMap.php`](./config/ASP/armyAbbreviationMap.php), [`backendAwards.php`](./config/ASP/backendAwards.php), and [`ranks.php`](./config/ASP/ranks.php) config files if you are using a customized mod. Unlike `config.php`, they don't need write permissions. -- [Backup the DB](#development) using `mysqldump` instead of the ASP. `System > Backup Stats Database` will not be allowed since the DB is on remote host. This means there is no need for provisioning a `backups-volume` volume. -- Optional: For better security, define `MARIADB_USER` and `MARIADB_PASSWORD` for the `db` service, so that a regular `mariadb` user is created on the first run, instead of using the `root` user. Note that this hasn't been tested, but it seems to work nicely, although it might break some modules in the `ASP` dashboard if they rely on `root` privileges (any at all?). - ## Development - Install `docker` and `docker-compose` @@ -38,7 +32,6 @@ docker-compose -f docker-compose.test.yml up # Test production builds locally docker build -t startersclan/asp:nginx -f Dockerfile.nginx.prod . docker build -t startersclan/asp:php -f Dockerfile.php.prod . -docker-compose -f docker-compose.example.yml up # Dump the DB docker exec $( docker-compose ps | grep db | awk '{print $1}' ) mysqldump -uroot -pascent bf2stats | gzip > bf2stats.sql.gz diff --git a/docker-compose.example.yml b/docker-compose.example.yml deleted file mode 100644 index 6e006a5c..00000000 --- a/docker-compose.example.yml +++ /dev/null @@ -1,110 +0,0 @@ -version: '2.2' -services: - init-container: - image: alpine:latest - volumes: - - backups-volume:/src/ASP/system/backups # This volume is effectively unused since ASP doesn't allow DB backups for a remote DB, but mount it anyway to avoid errors. - - cache-volume:/src/ASP/system/cache - - logs-volume:/src/ASP/system/logs - - snapshots-volume:/src/ASP/system/snapshots - - db-volume:/var/lib/mysql - entrypoint: - - /bin/sh - command: - - -c - - | - set -eu - - echo "Granting php's 'www-data' user write permissions" - chown -R 82:82 /src/ASP/system/backups - find /src/ASP/system/backups -type d -exec chmod 750 {} \; - find /src/ASP/system/backups -type f -exec chmod 640 {} \; - - chown -R 82:82 /src/ASP/system/cache - find /src/ASP/system/cache -type d -exec chmod 750 {} \; - find /src/ASP/system/cache -type f -exec chmod 640 {} \; - - chown -R 82:82 /src/ASP/system/logs - find /src/ASP/system/logs -type d -exec chmod 750 {} \; - find /src/ASP/system/logs -type f -exec chmod 640 {} \; - - mkdir -p /src/ASP/system/snapshots/failed - mkdir -p /src/ASP/system/snapshots/processed - mkdir -p /src/ASP/system/snapshots/unauthorized - mkdir -p /src/ASP/system/snapshots/unprocessed - chown -R 82:82 /src/ASP/system/snapshots - find /src/ASP/system/snapshots -type d -exec chmod 750 {} \; - find /src/ASP/system/snapshots -type f -exec chmod 640 {} \; - - echo "Granting db's 'mysql' user write permissions" - chown -R 999:999 /var/lib/mysql - - nginx: - image: startersclan/asp:3.1.0-nginx - volumes: - - ./config/nginx/nginx.conf:/etc/nginx/nginx.conf:ro # Customize as needed - ports: - - 8081:80 - networks: - - bf2-network - depends_on: - - init-container - - php - restart: unless-stopped - - php: - image: startersclan/asp:3.1.0-php - volumes: - - ./config/ASP/config.php:/src/ASP/system/config/config.php # Main config file. Must be writeable or else ASP will throw an exception. Customize as needed - # - ./config/ASP/armyAbbreviationMap.php:/src/ASP/system/config/armyAbbreviationMap.php:ro # Optional: Customize as needed if using a custom mod - # - ./config/ASP/backendAwards.php:/src/ASP/system/config/backendAwards.php:ro # Optional: Customize as needed if using a custom mod - # - ./config/ASP/ranks.php:/src/ASP/system/config/ranks.php:ro # Optional: Customize as needed if using a custom mod - - ./config/php/conf.d/php.ini:/usr/local/etc/php/conf.d/php.ini:ro # Customize as needed - - ./config/php-fpm.d/www.conf:/usr/local/etc/php-fpm.d/www.conf:ro # Customize as needed - - backups-volume:/src/ASP/system/backups # This volume is effectively unused since ASP doesn't allow DB backups for a remote DB, but mount it anyway to avoid errors. - - cache-volume:/src/ASP/system/cache - - logs-volume:/src/ASP/system/logs - - snapshots-volume:/src/ASP/system/snapshots - networks: - - bf2-network - depends_on: - - init-container - restart: unless-stopped - - db: - image: mariadb:10.8 - environment: - - MARIADB_ROOT_PASSWORD=ascent - # - MARIADB_USER=myuser # Uncomment this if you want to create a regular user - # - MARIADB_PASSWORD=mypassword # Uncomment this if you want to create a regular user - - MARIADB_DATABASE=bf2stats - volumes: - - ./config/db/my.cnf:/etc/my.cnf:ro - - db-volume:/var/lib/mysql - networks: - - bf2-network - depends_on: - - init-container - restart: unless-stopped - - phpmyadmin: - image: phpmyadmin:5.2 - environment: - # - PMA_ABSOLUTE_URI=https://phpmyadmin.example.com # Enable this if behind a reverse proxy - - PMA_HOST=db - ports: - - 8082:80 - networks: - - bf2-network - restart: unless-stopped - -networks: - bf2-network: - name: bf2-network - -volumes: - backups-volume: - cache-volume: - logs-volume: - snapshots-volume: - db-volume: diff --git a/docs/full-bf2-stack-example/.env b/docs/full-bf2-stack-example/.env new file mode 100644 index 00000000..9a2f2c31 --- /dev/null +++ b/docs/full-bf2-stack-example/.env @@ -0,0 +1 @@ +COMPOSE_PROJECT_NAME=bf2stats diff --git a/docs/full-bf2-stack-example/README.md b/docs/full-bf2-stack-example/README.md new file mode 100644 index 00000000..9a764725 --- /dev/null +++ b/docs/full-bf2-stack-example/README.md @@ -0,0 +1,161 @@ +# Full Battlefield 2 stack example + +This example deploys a stack with `bf2stats` `ASP` v3. If you prefer `bf2stats` v2, see [here](https://github.com/startersclan/bf2stats). + +Note that `bf2sclone` doesn't work with `ASP` v3, see [here](#q-query-failed-unknown-column-rank-in-field-list-in-bf2sclone). + +## Usage + +1. First, start the full stack: + +```sh +docker-compose up +``` + +2. If there is error `listen udp4 0.0.0.0:53: bind: address already in use` or something similar, the OS might already have a DNS server running on localhost UDP port `53`, e.g. `systemd-resolved` or docker DNS server. + +To get around that, change `coredns` in `docker-compose.yml` to bind to your external interface's IP. Change this + +```yaml + ports: + - 53:53/udp +``` + +to + +```yaml + ports: + - 192.168.1.100:53:53/udp +``` + +assuming `192.168.1.100` is your machine's external IP address. + +3. If there is a similar error for TCP port `80` or `443`, you should may have an existing web server running. Stop the web server first. Run `docker-compose up` again. + +4. Wait for the containers to start. You should see something like: + +```sh +$ docker-compose up +[+] Running 10/0 + ⠿ Container bf2stats-prmasterserver-1 Running 0.0s + ⠿ Container bf2stats-phpmyadmin-1 Running 0.0s + ⠿ Container bf2stats-bf2-1 Running 0.0s + ⠿ Container bf2stats-traefik-1 Running 0.0s + ⠿ Container bf2stats-init-container-1 Created 0.0s + ⠿ Container bf2stats-db-1 Running 0.0s + ⠿ Container bf2stats-asp-php-1 Running 0.0s + ⠿ Container bf2stats-bf2sclone-php-1 Running 0.0s + ⠿ Container bf2stats-asp-nginx-1 Running 0.0s + ⠿ Container bf2stats-bf2sclone-nginx-1 Running 0.0s +Attaching to bf2stats-asp-nginx-1, bf2stats-asp-php-1, bf2stats-bf2-1, bf2stats-bf2sclone-nginx-1, bf2stats-bf2sclone-php-1, bf2stats-coredns-1, bf2stats-db-1, bf2stats-init-container-1, bf2stats-phpmyadmin-1, bf2stats-prmasterserver-1, bf2stats-traefik-1 +... +``` + +5. The full stack is now ready: +- Battlefield 2 1.5 server with `bf2stats` `2.2.0` support available on your external IP address on UDP ports `16567` and `29900` on your external IP address +- Gamespy server [`PRMasterServer`](https://github.com/PRMasterServer) available at your external IP address on TCP ports `29900`, `29901`, `28910`, and UDP ports `27900` and `29910` on your external IP address +- `coredns` available on your external IP address on UDP port `53` on your external IP address +- `traefik` (reverse web proxy) available on port `80` and `443` on your external IP address +- `ASP` available at https://asp.example.com on your external IP address. Login using `$admin_user` and `$admin_pass` defined in its [config file](./config/ASP/config.php) +- `bf2sclone` available at https://bf2sclone.example.com on your external IP address. +- `phpmyadmin` available at https://phpmyadmin.example.com on your external IP address. Login using `MARIADB_USER` and `MARIADB_PASSWORD` defined on the `db` service in [docker-compose.yml](./docker-compose.yml). You may also login using user `root` and password `MARIADB_ROOT_PASSWORD`. + +Notes: +- Mount the [`config.php`](./config/ASP/config.php) with write permissions, or else `ASP` dashboard will throw an error. Use `System > Edit Configuration` as reference to customize the config file. +- Optional: Mount your customized [`armyAbbreviationMap.php`](./config/ASP/armyAbbreviationMap.php), [`backendAwards.php`](./config/ASP/backendAwards.php), and [`ranks.php`](./config/ASP/ranks.php) config files if you are using a customized mod. Unlike `config.php`, they don't need write permissions. +- If traefik hasn't got a certificate via `ACME`, it will serve the `TRAEFIK DEFAULT CERT`. The browser will show a security issue when visiting https://asp.example.com, https://bf2sclone.example.com, and https://phpmyadmin.example.com. Simply click "visit site anyway" button to get past the security check. +- Setup the DB on the first time you login to the `ASP`, using `$db_host`,`$db_port`,`$db_name`,`$db_user`,`$db_pass` you defined in [`config.php`](./config/ASP/config.php) +- [Backup the DB](#scripts) using `mysqldump` instead of the ASP. `System > Backup Stats Database` will not be allowed since the DB is on remote host. This means there is no need for provisioning a `backups-volume` volume. +- Optional: For better security, define `MARIADB_USER` and `MARIADB_PASSWORD` for the `db` service, so that a regular `mariadb` user is created on the first run, instead of using the `root` user. Note that this hasn't been tested, but it seems to work nicely, although it might break some modules in the `ASP` dashboard if they rely on `root` privileges (any at all?). + +6. If you are behind NAT, you will need to forward all of the above TCP and UDP ports to your external IP address, in order for clients to reach your server over the internet. + +## Spoofing gamespy DNS for BF2 clients + +Problem: The Battlefield 2 client and server binaries are hardcoded with gamespy DNS records, e.g. `bf2web.gamespy.com`. Because gamespy has shut down, the DNS records no longer exist on public DNS servers. In order to keep the game's multiplayer working, we need: +1. A gamespy replacement - solved by `PRMasterServer` +2. DNS resolution for gamespy DNS records - solved by either hex patching the game binaries, spoofing DNS server responses, or spoofing DNS records via `HOSTS` file. + +### Option 1: Hex patching game binaries + +This is what [`bf2hub.com`](https://bf2hub.com) and many BF2 mods do. + +Pros: +- Simple. Patch every client with the new binaries + +Cons: +- Difficult to change to another gamespy server +- Difficult to distribute because it requires installation on each client +- Trust issues. Binaries may be patched with malicious code + +### Option 2: Spoof DNS at the DNS server + +Pros: +- Most scalable. You configure the DNS server via `DHCP`, so that every client that connects to a `DHCP` server (e.g. router) are configured to use the DNS server. No client configuration needed +- Easy to change to another gamespy server + +Cons: +- Dangerous. The DNS server may be used as an attack vector against clients to steal cookies and direct clients to malicious websites. + +### Option 3: Use DNS records in the local machine + +Pros: +- Safest. DNS records only apply to the local machine + +Cons: +- Difficult to change to another gamespy server +- Tedious to hand edit. See an example of a hosts file [here](./config/coredns/hosts) +- Requires administrative privileges to update the machine's `hosts` file + +Solutions: +- The [`BF2statisticsClientLauncher.exe`](/Tools/Client%20Files) was made to do this +- The [`BF2GamespyRedirector`](https://github.com/BF2Statistics/BF2GamespyRedirector) improves on `BF2statisticsClientLauncher.exe` by allowing users to save IP addresses of their favourite gamespy servers, and easily switch between them. Read more [here](https://bf2statistics.com/threads/bf2statistics-v3-1-0-full-release.3010/) + +### Which is the best? + +The best solution depends on one's setup. If one often needs to switch between gamespy servers, `3.` is best. If one doesn't want clients to have to install anything but wants things to "just work", use `2.`. If one prefers a single gamespy server run by a trustworthy community, use `1.`. + +This example opted for `2.` which is DNS spoofing using `coredns`. It can be used on a single machine and multiple machine setups. + +## Scripts + +These one-liners may be handy for adminstration of the stack. + +```sh +# Start +docker-compose up + +# Edit config/coredns/hosts and replace gamespy's DNS records with your machine's external IP address. Save it to immediately apply it +vi config/coredns/hosts + +# If you are testing this stack locally, you may need these DNS records in your hosts file +echo '127.0.0.1 asp.example.com' | sudo tee -a /etc/hosts +echo '127.0.0.1 bf2sclone.example.com' | sudo tee -a /etc/hosts +echo '127.0.0.1 phpmyadmin.example.com' | sudo tee -a /etc/hosts + +# Dump the DB +docker exec $( docker-compose ps | grep db | awk '{print $1}' ) mysqldump -uroot -padmin bf2stats | gzip > bf2stats.sql.gz + +# Restore the DB +zcat bf2stats.sql.gz | docker exec -i $( docker-compose ps | grep db | awk '{print $1}' ) mysql -uroot -padmin bf2stats + +# Stop +docker-compose down + +# Cleanup. Warning: This destroys the all data! +docker-compose down +docker volume rm bf2stats_prmasterserver-volume +docker volume rm bf2stats_traefik-acme-volume +docker volume rm bf2stats_backups-volume +docker volume rm bf2stats_cache-volume +docker volume rm bf2stats_logs-volume +docker volume rm bf2stats_snapshots-volume +docker volume rm bf2stats_bf2sclone-cache-volume +docker volume rm bf2stats_db-volume +``` + +## FAQ + +### Q: `Query failed: Unknown column 'rank' in 'field list'` in `bf2sclone`. + +A: `bf2sclone` `2.2.0` does not work with `ASP` v3, but it is included in this example for demonstrative purposes. The community has tried to [fix it](https://bf2statistics.com/threads/bf2sclone-v3.2972/), but so far none seem to have shared their working changes. Wilson212, the orignal author of BF2Statistics did mention to be working on it, see [here](https://bf2statistics.com/threads/bf2statistics-v3-1-0-full-release.3010/), so we might see it soon. diff --git a/docs/full-bf2-stack-example/config/ASP/armyAbbreviationMap.php b/docs/full-bf2-stack-example/config/ASP/armyAbbreviationMap.php new file mode 100644 index 00000000..3fa3c298 --- /dev/null +++ b/docs/full-bf2-stack-example/config/ASP/armyAbbreviationMap.php @@ -0,0 +1,204 @@ + [ + 'flag' => 0, + 'name' => 'United States Marine Corps' + ], + 'mec' => [ + 'flag' => 1, + 'name' => 'Middle Eastern Coalition' + ], + 'ch' => [ + 'flag' => 2, + 'name' => 'People\'s Liberation Army' + ], + + // Xpack 1 Special Forces + 'seal' => [ + 'flag' => 0, + 'name' => 'United States Navy Seals' + ], + 'mecsf' => [ + 'flag' => 1, + 'name' => 'Middle Eastern Coalition Special Forces' + ], + 'sas' => [ + 'flag' => 4, + 'name' => 'British Special Air Service' + ], + 'spetz' => [ + 'flag' => 5, + 'name' => 'Russian Spetsnaz' + ], + 'chinsurgent' => [ + 'flag' => 7, + 'name' => 'Rebels' + ], + 'meinsurgent' => [ + 'flag' => 8, + 'name' => 'Insurgents' + ], + + // Booster Pack 1 EuroForce + 'eu' => [ + 'flag' => 9, + 'name' => 'European Union' + ], + + // POE2 + 'ger' => [ + 'flag' => 10, + 'name' => 'German Forces' + ], + 'ukr' => [ + 'flag' => 12, + 'name' => 'Ukrainian Forces' + ], + + // AIX + 'un' => [ + 'flag' => 13, + 'name' => 'United Nations' + ], + + // Hard Justice + 'us2' => [ + 'flag' => 0, + 'name' => 'United States Marine Corps' + ], + 'us3' => [ + 'flag' => 0, + 'name' => 'United States Marine Corps' + ], + 'mec2' => [ + 'flag' => 1, + 'name' => 'Middle Eastern Coalition' + ], + 'mec3' => [ + 'flag' => 1, + 'name' => 'Middle Eastern Coalition' + ], + 'ch2' => [ + 'flag' => 2, + 'name' => 'People\'s Liberation Army' + ], + 'ch3' => [ + 'flag' => 2, + 'name' => 'People\'s Liberation Army' + ], + 'ca' => [ + 'flag' => 13, + 'name' => 'Canadian Forces' + ], + + // Nations at war 8.0 + 'blackwater' => [ + 'flag' => 14, + 'name' => 'Blackwater Military Contractors' + ], + 'taliban' => [ + 'flag' => 15, + 'name' => 'Taliban Forces' + ], + 'au' => [ + 'flag' => 16, + 'name' => 'Australian Forces' + ], + 'ru' => [ + 'flag' => 17, + 'name' => 'Russian Forces' + ], + 'gb' => [ + 'flag' => 18, + 'name' => 'British Forces' + ], + 'nato' => [ + 'flag' => 19, + 'name' => 'NATO Forces' + ], + 'isis' => [ + 'flag' => 20, + 'name' => 'ISIS Forces' + ], + 'iraqa' => [ + 'flag' => 21, + 'name' => 'Iraqi Forces' + ], + 'usmc' => [ + 'flag' => 0, + 'name' => 'United States Marines Corps' + ], + 'somalia' => [ + 'flag' => 23, + 'name' => 'Somalian Forces' + ], + 'rangers' => [ + 'flag' => 0, + 'name' => 'U.S Army Rangers' + ], + 'idf' => [ + 'flag' => 25, + 'name' => 'Israel Defense Force' + ], + 'chsf' => [ + 'flag' => 2, + 'name' => 'Chinese Special Forces' + ], + 'paras' => [ + 'flag' => 27, + 'name' => 'British Paratroop Regiment' + ], + 'casf' => [ + 'flag' => 28, + 'name' => 'Canadian Special Forces' + ], + 'hamas' => [ + 'flag' => 29, + 'name' => 'Hamas Forces' + ], + 'hezbollah' => [ + 'flag' => 30, + 'name' => 'Hezbollah Forces' + ], + 'iran' => [ + 'flag' => 31, + 'name' => 'Iran Forces' + ], + 'saudi_arabia' => [ + 'flag' => 32, + 'name' => 'Saudi Arabia Forces' + ], + 'syria' => [ + 'flag' => 33, + 'name' => 'Syrian Forces' + ], + 'egypt' => [ + 'flag' => 34, + 'name' => 'Egyptian Army' + ], + 'pakistan' => [ + 'flag' => 35, + 'name' => 'Pakistan Army' + ], + 'india' => [ + 'flag' => 36, + 'name' => 'Indian Armed forces' + ], +); \ No newline at end of file diff --git a/docs/full-bf2-stack-example/config/ASP/backendAwards.php b/docs/full-bf2-stack-example/config/ASP/backendAwards.php new file mode 100644 index 00000000..053add94 --- /dev/null +++ b/docs/full-bf2-stack-example/config/ASP/backendAwards.php @@ -0,0 +1,235 @@ + 180000); +}; + +/** + * Vanilla BF2 Medal Criteria + * + * @param array $row The AwardCriteria resulting row + * @param int $timesAwarded the number of times this award has been awarded + * + * @return bool true if the player is eligible to receive the award, false otherwise + */ +$vServiceMedalCriteria = function($row, $timesAwarded) +{ + $level = $timesAwarded + 1; + $best = (int)$row['brnd']; + $time = ((int)$row['time']) / 3600; // Convert to hours + $wins = (int)$row['wins']; + return ($best >= (100 * $level) && $time >= (100 * $level) && $wins >= (100 * $level)); +}; + +/** + * Special Forces Medal Criteria + * + * @param array $row The AwardCriteria resulting row + * @param int $timesAwarded the number of times this award has been awarded + * + * @return bool true if the player is eligible to receive the award, false otherwise + */ +$xServiceMedalCriteria = function($row, $timesAwarded) +{ + $level = $timesAwarded + 1; + $best = (int)$row['brnd']; + $time = ((int)$row['time']) / 3600; // Convert to hours + $wins = (int)$row['wins']; + return ($best >= (100 * $level) && $time >= (50 * $level) && $wins >= (50 * $level)); +}; + + +/** + * ------------------------------------------------------------------ + * Return Backend Awards and Criteria + * ------------------------------------------------------------------ + */ + +return array( + /** Service Ribbons */ + + // Middle Eastern Service Ribbon + new BackendAward(3191305, [ + new AwardCriteria('player_map', 'count(*) AS result', 'map_id IN (0,1,2,3,4,5,6) AND time >= 1', + function($row, $timesAwarded) + { + return ($timesAwarded == 0 && $row['result'] == 7); + } + ) + ]), + // Far East Service Ribbon + new BackendAward(3190605, [ + new AwardCriteria('player_map', 'count(*) AS result', 'map_id IN (100,101,102,103,105,601) AND time >= 1', + function($row, $timesAwarded) + { + return ($timesAwarded == 0 && $row['result'] == 6); + } + ) + ]), + // European Union Service Ribbon + new BackendAward(3270519, [ + new AwardCriteria('player_map', 'count(*) AS result', 'map_id IN (10,11,110) AND time >= 1', + function($row, $timesAwarded) + { + return ($timesAwarded == 0 && $row['result'] == 3); + } + ), + new AwardCriteria('player_map', 'sum(time) AS result', 'map_id IN (10,11,110)', + function($row, $timesAwarded) + { + return ($timesAwarded == 0 && $row['result'] == 180000); + } + ), + ]), + // North American Service Ribbon + new BackendAward(3271401, [ + new AwardCriteria('player_map', 'count(*) AS result', 'map_id IN (200,201,202) AND time >= 1', + function($row, $timesAwarded) + { + return ($timesAwarded == 0 && $row['result'] == 3); + } + ), + new AwardCriteria('player_map', 'sum(time) AS result', 'map_id IN (200,201,202)', + function($row, $timesAwarded) + { + return ($timesAwarded == 0 && $row['result'] == 90000); + } + ), + ]), + + /** Xpack Service Ribbons */ + + // Navy Seal Special Service Ribbon + new BackendAward(3261919, [ + new AwardCriteria('player_army', 'time AS result', 'army_id = 3', $sRibbonCriteria), + new AwardCriteria('player_map', 'count(*) AS result', 'map_id IN (300,301,304) AND time >= 1', + function($row, $timesAwarded) + { + return ($timesAwarded == 0 && $row['result'] == 3); + } + ), + ]), + // SAS Special Service Ribbon + new BackendAward(3261901, [ + new AwardCriteria('player_army', 'time AS result', 'army_id = 4', $sRibbonCriteria), + new AwardCriteria('player_map', 'count(*) AS result', 'map_id IN (302,303,307) AND time >= 1', + function($row, $timesAwarded) + { + return ($timesAwarded == 0 && $row['result'] == 3); + } + ), + ]), + // SPETZNAS Service Ribbon + new BackendAward(3261819, [ + new AwardCriteria('player_army', 'time AS result', 'army_id = 5', $sRibbonCriteria), + new AwardCriteria('player_map', 'count(*) AS result', 'map_id IN (305,306,307) AND time >= 1', + function($row, $timesAwarded) + { + return ($timesAwarded == 0 && $row['result'] == 3); + } + ), + ]), + // MECSF Service Ribbon + new BackendAward(3261319, [ + new AwardCriteria('player_army', 'time AS result', 'army_id = 6', $sRibbonCriteria), + new AwardCriteria('player_map', 'count(*) AS result', 'map_id IN (300,301,304) AND time >= 1', + function($row, $timesAwarded) + { + return ($timesAwarded == 0 && $row['result'] == 3); + } + ), + ]), + // Rebel Service Ribbon + new BackendAward(3261805, [ + new AwardCriteria('player_army', 'time AS result', 'army_id = 7', $sRibbonCriteria), + new AwardCriteria('player_map', 'count(*) AS result', 'map_id IN (305,306) AND time >= 1', + function($row, $timesAwarded) + { + return ($timesAwarded == 0 && $row['result'] == 2); + } + ), + ]), + // Insurgent Service Ribbon + new BackendAward(3260914, [ + new AwardCriteria('player_army', 'time AS result', 'army_id = 8', $sRibbonCriteria), + new AwardCriteria('player_map', 'count(*) AS result', 'map_id IN (302,303) AND time >= 1', + function($row, $timesAwarded) + { + return ($timesAwarded == 0 && $row['result'] == 2); + } + ), + ]), + + /** Service Medals */ + + // Navy Cross + new BackendAward(2021403, [ + new AwardCriteria('player_army', 'time, wins, brnd', 'army_id = 0', $vServiceMedalCriteria), + ]), + // Golden Scimitar + new BackendAward(2020719, [ + new AwardCriteria('player_army', 'time, wins, brnd', 'army_id = 1', $vServiceMedalCriteria), + ]), + // Peoples Medallion + new BackendAward(2021613, [ + new AwardCriteria('player_army', 'time, wins, brnd', 'army_id = 2', $vServiceMedalCriteria), + ]), + // European Union Service Medal + new BackendAward(2270521, [ + new AwardCriteria('player_army', 'time, wins, brnd', 'army_id = 9', $xServiceMedalCriteria), + ]), + + /** Xpack Service Medals */ + + // Navy Seal Special Service Medal + new BackendAward(2261913, [ + new AwardCriteria('player_army', 'time, wins, brnd', 'army_id = 3', $xServiceMedalCriteria), + ]), + // SAS Special Service Medal + new BackendAward(2261919, [ + new AwardCriteria('player_army', 'time, wins, brnd', 'army_id = 4', $xServiceMedalCriteria), + ]), + // SPETZ Special Service Medal + new BackendAward(2261613, [ + new AwardCriteria('player_army', 'time, wins, brnd', 'army_id = 5', $xServiceMedalCriteria), + ]), + // MECSF Special Service Medal + new BackendAward(2261303, [ + new AwardCriteria('player_army', 'time, wins, brnd', 'army_id = 6', $xServiceMedalCriteria), + ]), + // Rebels Special Service Medal + new BackendAward(2261802, [ + new AwardCriteria('player_army', 'time, wins, brnd', 'army_id = 7', $xServiceMedalCriteria), + ]), + // Insurgent Special Service Medal + new BackendAward(2260914, [ + new AwardCriteria('player_army', 'time, wins, brnd', 'army_id = 8', $xServiceMedalCriteria), + ]), +); \ No newline at end of file diff --git a/docs/full-bf2-stack-example/config/ASP/config.php b/docs/full-bf2-stack-example/config/ASP/config.php new file mode 100644 index 00000000..4fa69033 --- /dev/null +++ b/docs/full-bf2-stack-example/config/ASP/config.php @@ -0,0 +1,54 @@ + array( + 'title' => 'Private', + 'points' => 0, + 'time' => 0, + 'skip' => false, + 'backend' => false, + 'has_rank' => 0, + 'has_awards' => array() + ), + 1 => array( + 'title' => 'Private First Class', + 'points' => 150, + 'time' => 0, + 'skip' => false, + 'backend' => false, + 'has_rank' => 0, + 'has_awards' => array() + ), + 2 => array( + 'title' => 'Lance Corporal', + 'points' => 500, + 'time' => 0, + 'skip' => false, + 'backend' => false, + 'has_rank' => 1, + 'has_awards' => array() + ), + 3 => array( + 'title' => 'Corporal', + 'points' => 800, + 'time' => 0, + 'skip' => false, + 'backend' => false, + 'has_rank' => 2, + 'has_awards' => array() + ), + 4 => array( + 'title' => 'Sergeant', + 'points' => 2500, + 'time' => 0, + 'skip' => false, + 'backend' => false, + 'has_rank' => 3, + 'has_awards' => array() + ), + 5 => array( + 'title' => 'Staff Sergeant', + 'points' => 5000, + 'time' => 0, + 'skip' => false, + 'backend' => false, + 'has_rank' => 4, + 'has_awards' => array() + ), + 6 => array( + 'title' => 'Gunnery Sergeant', + 'points' => 8000, + 'time' => 0, + 'skip' => false, + 'backend' => false, + 'has_rank' => 5, + 'has_awards' => array() + ), + 7 => array( + 'title' => 'Master Sergeant', + 'points' => 20000, + 'time' => 0, + 'skip' => false, + 'backend' => false, + 'has_rank' => 6, + 'has_awards' => array() + ), + 8 => array( + 'title' => 'First Sergeant', + 'points' => 20000, + 'time' => 0, + 'skip' => false, + 'backend' => false, + 'has_rank' => 6, + 'has_awards' => array( + '1031105' => 1, // Engineer Combat Badge + '1031109' => 1, // Sniper Combat Badge + '1031113' => 1, // Medic Combat Badge + '1031115' => 1, // Spec Ops Combat Badge + '1031119' => 1, // Assault Combat Badge + '1031120' => 1, // Anti-tank Combat Badge + '1031121' => 1, // Support Combat Badge + '1031406' => 1, // Knife Combat Badge + '1031619' => 1 // Pistol Combat Badge + ) + ), + 9 => array( + 'title' => 'Master Gunnery Sergeant', + 'points' => 50000, + 'time' => 0, + 'skip' => false, + 'backend' => false, + 'has_rank' => array(7, 8), + 'has_awards' => array() + ), + 10 => array( + 'title' => 'Sergeant Major', + 'points' => 50000, + 'time' => 0, + 'skip' => false, + 'backend' => false, + 'has_rank' => array(7, 8), + 'has_awards' => array( + '1031923' => 1, // Ground Defense + '1220104' => 1, // Air Defense + '1220118' => 1, // Armor Badge + '1220122' => 1, // Aviator Badge + '1220803' => 1, // Helicopter Badge + '1222016' => 1 // Transport Badge + ) + ), + 11 => array( + 'title' => 'Sergeant Major of the Corp', + 'points' => 50000, + 'time' => 0, + 'skip' => true, + 'backend' => true, + 'has_rank' => 10, + 'has_awards' => array() + ), + 12 => array( + 'title' => '2nd Lieutenant', + 'points' => 60000, + 'time' => 0, + 'skip' => false, + 'backend' => false, + 'has_rank' => array(9, 10, 11), + 'has_awards' => array() + ), + 13 => array( + 'title' => '1st Lieutenant', + 'points' => 75000, + 'time' => 0, + 'skip' => false, + 'backend' => false, + 'has_rank' => 12, + 'has_awards' => array() + ), + 14 => array( + 'title' => 'Captain', + 'points' => 90000, + 'time' => 0, + 'skip' => false, + 'backend' => false, + 'has_rank' => 13, + 'has_awards' => array() + ), + 15 => array( + 'title' => 'Major', + 'points' => 115000, + 'time' => 0, + 'skip' => false, + 'backend' => false, + 'has_rank' => 14, + 'has_awards' => array() + ), + 16 => array( + 'title' => 'Lieutenant Colonel', + 'points' => 125000, + 'time' => 0, + 'skip' => false, + 'backend' => false, + 'has_rank' => 15, + 'has_awards' => array() + ), + 17 => array( + 'title' => 'Colonel', + 'points' => 150000, + 'time' => 0, + 'skip' => false, + 'backend' => false, + 'has_rank' => 16, + 'has_awards' => array() + ), + 18 => array( + 'title' => 'Brigadier General', + 'points' => 180000, + 'time' => 3888000, // In seconds + 'skip' => false, + 'backend' => false, + 'has_rank' => 17, + 'has_awards' => array( + '1031105' => 2, // Engineer Combat Badge + '1031109' => 2, // Sniper Combat Badge + '1031113' => 2, // Medic Combat Badge + '1031115' => 2, // Spec Ops Combat Badge + '1031119' => 2, // Assault Combat Badge + '1031120' => 2, // Anti-tank Combat Badge + '1031121' => 2, // Support Combat Badge + '1031406' => 2, // Knife Combat Badge + '1031619' => 2 // Pistol Combat Badge + ) + ), + 19 => array( + 'title' => 'Major General', + 'points' => 180000, + 'time' => 4500000, // In seconds + 'skip' => false, + 'backend' => false, + 'has_rank' => 18, + 'has_awards' => array( + '1031923' => 2, // Ground Defense + '1220104' => 2, // Air Defense + '1220118' => 2, // Armor Badge + '1220122' => 2, // Aviator Badge + '1220803' => 2, // Helicopter Badge + '1222016' => 2 // Transport Badge + ) + ), + 20 => array( + 'title' => 'Lieutenant General', + 'points' => 200000, + 'time' => 5184000, // In seconds + 'skip' => false, + 'backend' => false, + 'has_rank' => 19, + 'has_awards' => array() + ), + 21 => array( + 'title' => 'General', + 'points' => 200000, + 'time' => 0, + 'skip' => false, + 'backend' => true, + 'has_rank' => 20, + 'has_awards' => array() + ), +); \ No newline at end of file diff --git a/docs/full-bf2-stack-example/config/bf2sclone/config.inc.php b/docs/full-bf2-stack-example/config/bf2sclone/config.inc.php new file mode 100644 index 00000000..e8f7e568 --- /dev/null +++ b/docs/full-bf2-stack-example/config/bf2sclone/config.inc.php @@ -0,0 +1,42 @@ + default: 600 seconds (10 minutes) + +// Number of players to show on the leaderboard frontpage +define ('LEADERBOARD_COUNT', 25); + + + +// === DONOT EDIT BELOW THIS LINE == // + + + +// Determine our http hostname, and site directory +$host = rtrim($_SERVER['HTTP_HOST'], '/'); +$site_dir = dirname( $_SERVER['PHP_SELF'] ); +$site_url = str_replace('//', '/', $host .'/'. $site_dir); +while(strpos($site_url, '//') !== FALSE) $site_url = str_replace('//', '/', $site_url); + +// Root url to bf2sclone +$ROOT = ( isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ) || ( isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' ) ? str_replace( '\\', '', 'https://' . rtrim($site_url, '/') .'/' ) : str_replace( '\\', '', 'http://' . rtrim($site_url, '/') .'/' ); + +// Your domain name (eg: www.example.com) +$DOMAIN = preg_replace('@^(http(s)?)://@i', '', $host); + +// cleanup +unset($host, $site_dir, $site_url); + +// Setup the database connection +$link = mysqli_connect($DBIP, $DBLOGIN, $DBPASSWORD) or die('Could not connect: ' . mysqli_error($GLOBALS['link'])); +mysqli_select_db($GLOBALS['link'], $DBNAME) or die('Could not select database'); +?> diff --git a/docs/full-bf2-stack-example/config/bf2sclone/nginx/nginx.conf b/docs/full-bf2-stack-example/config/bf2sclone/nginx/nginx.conf new file mode 100644 index 00000000..2c508792 --- /dev/null +++ b/docs/full-bf2-stack-example/config/bf2sclone/nginx/nginx.conf @@ -0,0 +1,105 @@ +user nginx; +worker_processes 1; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + server { + listen 80; + # Ensure our redirects don't go to other ports, see https://serverfault.com/questions/351212/nginx-redirects-to-port-8080-when-accessing-url-without-slash + port_in_redirect off; + # Ensure our redirects are scheme-agnostic. Important behind a SSL-terminated load balancer. + absolute_redirect off; + + root /src/bf2sclone; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + index index.php index.html index.htm; + + # Increase upload max body size + client_max_body_size 1m; + client_body_buffer_size 1m; + + # Restore IP from Proxy + set_real_ip_from 127.0.0.0/8; + set_real_ip_from 10.0.0.0/8; + set_real_ip_from 172.16.0.0/12; + set_real_ip_from 192.168.0.0/16; + real_ip_header X-Real-IP; + + # We don't want to pass calls to css, script, and image files to the index, + # whether they exist or not. So quit right here, and allow direct access + # to common format files. Add formats here to allow direct link access + location ~ \.disabled$ { + return 401; + } + location ~ \.(gif|png|jpe?g|bmp|css|js|swf|wav|avi|mpg|ttf|woff|ico)$ { + add_header Cache-Control "public, s-maxage=600, maxage=600"; + try_files $uri =404; + } + + + # Deny access to hidden files + location ~ /\.[^/]+$ { + return 401; + } + # Deny access to certain files + location ~ \.inc.php$ { + return 401; + } + # Deny access certain directories + location ~ ^/(cache|queries|template) { + return 401; + } + + location ~ \.php$ { + # Check that the PHP script exists before passing it + try_files $fastcgi_script_name =404; + + # Disable fastcgi output buffering + fastcgi_buffering off; + + # Set fastcgi max execution response time + fastcgi_read_timeout 300s; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_index index.php; + fastcgi_pass bf2sclone-php:9000; + } + + location / { + limit_except GET POST { + deny all; + } + + # Pass everything else to php + try_files $uri $uri/ index.php?$args; + } + } +} diff --git a/docs/full-bf2-stack-example/config/bf2sclone/php-fpm.d/www.conf b/docs/full-bf2-stack-example/config/bf2sclone/php-fpm.d/www.conf new file mode 100644 index 00000000..b931b2cf --- /dev/null +++ b/docs/full-bf2-stack-example/config/bf2sclone/php-fpm.d/www.conf @@ -0,0 +1,10 @@ +[www] +user = www-data +group = www-data +security.limit_extensions = .php +pm = dynamic +pm.max_children = 5 +pm.start_servers = 2 +pm.min_spare_servers = 1 +pm.max_spare_servers = 3 +pm.status_path = /status.php diff --git a/docs/full-bf2-stack-example/config/bf2sclone/php/conf.d/php.ini b/docs/full-bf2-stack-example/config/bf2sclone/php/conf.d/php.ini new file mode 100644 index 00000000..a79c6f23 --- /dev/null +++ b/docs/full-bf2-stack-example/config/bf2sclone/php/conf.d/php.ini @@ -0,0 +1,27 @@ +[hardening] +disable_functions = exec,passthru,shell_exec,system,proc_open,popen,parse_ini_file +cgi.fix_pathinfo = 0 +cgi.force_redirect = 1 +allow_url_fopen = 0 +allow_url_include = 0 +expose_php = 0 + +open_basedir = /src:/tmp +upload_tmp_dir = /tmp + +display_errors = 0 +log_errors = 1 +error_reporting = E_ALL +ignore_repeated_errors = 1 +#error_log = /var/lib/php/logs/error.log + +max_execution_time = 35 +max_input_time = 35 +file_uploads = 1 +post_max_size = 5M +upload_max_filesize = 5M + +[opcache] +opcache.enable=1 +opcache.memory_consumption = 64 +opcache.fast_shutdown = 0 diff --git a/docs/full-bf2-stack-example/config/coredns/Corefile b/docs/full-bf2-stack-example/config/coredns/Corefile new file mode 100644 index 00000000..41c902f6 --- /dev/null +++ b/docs/full-bf2-stack-example/config/coredns/Corefile @@ -0,0 +1,22 @@ +# DNS over UDP +.:53 { + # Try resolving DNS using the hosts file first + hosts /hosts { + ttl 300 # DNS TTL + no_reverse # Do not generate reverse DNS records + reload 5s # hosts file reload interval + fallthrough # If no host is matched, continue down the plugin chain + } + + # If it didn't match any records in the hosts file, forward this DNS request to upstream DNS servers + forward . tls://1.1.1.1 tls://1.0.0.1 { + tls_servername cloudflare-dns.com + health_check 5s + } + + # Log everything. There might be a performance hit, so remove it for better performance + log + + # Log errors + errors +} diff --git a/docs/full-bf2-stack-example/config/coredns/hosts b/docs/full-bf2-stack-example/config/coredns/hosts new file mode 100644 index 00000000..76e7dcb4 --- /dev/null +++ b/docs/full-bf2-stack-example/config/coredns/hosts @@ -0,0 +1,12 @@ +# Replace these gamespy's DNS records with your machine's external IP addresses +192.168.1.100 eapusher.dice.se +192.168.1.100 battlefield2.available.gamespy.com +192.168.1.100 battlefield2.master.gamespy.com +192.168.1.100 battlefield2.ms14.gamespy.com +192.168.1.100 gamestats.gamespy.com +192.168.1.100 master.gamespy.com +192.168.1.100 motd.gamespy.com +192.168.1.100 gpsp.gamespy.com +192.168.1.100 gpcm.gamespy.com +192.168.1.100 gamespy.com +192.168.1.100 bf2web.gamespy.com diff --git a/docs/full-bf2-stack-example/config/db/my.cnf b/docs/full-bf2-stack-example/config/db/my.cnf new file mode 100644 index 00000000..42e67bad --- /dev/null +++ b/docs/full-bf2-stack-example/config/db/my.cnf @@ -0,0 +1,43 @@ +[client] +port=3306 +default_character_set=utf8mb4 + +[mysql] +default_character_set=utf8mb4 + +[mysqldump] +default_character_set=utf8mb4 +quick +quote_names +max_allowed_packet = 16M + +[mysqld] +skip_name_resolve +character_set_client=utf8mb4 +character_set_server=utf8mb4 +collation_server=utf8mb4_unicode_ci +local_infile=0 +innodb_strict_mode +innodb_file_per_table +# Size of each log file in a log group. You should set the combined size +# of log files to about 25%-100% of your buffer pool size to avoid +# unneeded buffer pool flush activity on log file overwrite. However, +# note that a larger logfile size will increase the time needed for the +# recovery process. +innodb_log_file_size=8M + +# The size of the buffer InnoDB uses for buffering log data. As soon as +# it is full, InnoDB will have to flush it to disk. As it is flushed +# once per second anyway, it does not make sense to have it very large +# (even with long transactions). +innodb_log_buffer_size=8M + +# InnoDB, unlike MyISAM, uses a buffer pool to cache both indexes and +# row data. The bigger you set this the less disk I/O is needed to +# access data in tables. On a dedicated database server you may set this +# parameter up to 80% of the machine physical memory size. Do not set it +# too large, though, because competition of the physical memory may +# cause paging in the operating system. Note that on 32bit systems you +# might be limited to 2-3.5G of user level memory per process, so do not +# set it too high. +innodb_buffer_pool_size=32M diff --git a/docs/full-bf2-stack-example/docker-compose.yml b/docs/full-bf2-stack-example/docker-compose.yml new file mode 100644 index 00000000..80a5b6d8 --- /dev/null +++ b/docs/full-bf2-stack-example/docker-compose.yml @@ -0,0 +1,278 @@ +version: '2.2' +services: + # Battlefield 2 1.5 server with bf2stats 3.1.0 python scripts + bf2: + image: startersclan/docker-bf2:v1.5.3153.0-bf2stats-3.1.0 + ports: + - 16567:16567/udp + - 29900:29900/udp + networks: + - bf2-network + restart: unless-stopped + tty: true + stdin_open: true + + # The gamespy server + prmasterserver: + image: startersclan/prmasterserver:v0.1.0 + ports: + - 29900:29900/tcp # Login server + - 29901:29901/tcp # Login server + - 28910:28910/tcp # Master server + - 27900:27900/udp # Master server + - 29910:29910/udp # CD key server + volumes: + - prmasterserver-volume:/data + networks: + - bf2-network + restart: unless-stopped + + # A DNS server to spoof gamespy's DNS records for BF2 clients + # Tips: + # Clients should only use trusted DNS servers. This should only be used in a private network. + # If there is a port conflict, the OS might already have a DNS server running on localhost, e.g. systemd-resolved or docker dns + # To get around that, bind to your external interface's IP. For example: + # ports: + # - 192.168.1.100:53:53/udp + # Test DNS records to ensure coredns responds with your machine's external IP address. For example: + # nslookup bf2web.gamespy.com 192.168.1.100 + # Response should be: + # Server: 192.168.1.100 + # Address: 192.168.1.100#53 + # + # Name: bf2web.gamespy.com + # Address: 192.168.1.100 + # Then configure BF2 client machines to use this machine's external IP as their primary DNS server. + coredns: + image: coredns/coredns:1.9.3 + ports: + - 53:53/udp + volumes: + - ./config/coredns/Corefile:/Corefile:ro + - ./config/coredns/hosts:/hosts:ro + restart: unless-stopped + entrypoint: + - /coredns + - -conf + - /Corefile + + # The reverse proxy for our web containers + # See https://github.com/traefik/traefik/tree/v2.7/docs/content/user-guides/docker-compose for some examples for enabling HTTPS using ACME + # You will need a domain name. E.g. 'example.com' + traefik: + image: traefik:v2.7 + volumes: + # Allow traefik to listen to the Docker events + - /var/run/docker.sock:/var/run/docker.sock:ro + - traefik-acme-volume:/letsencrypt + ports: + - 80:80 + - 443:443 + networks: + - traefik-public-network + - traefik-network + restart: unless-stopped + command: + - --global.checknewversion=false + # - --log.level=DEBUG + - --providers.docker=true + - --providers.docker.exposedbydefault=false + - --entrypoints.web.address=:80 + - --entrypoints.websecure.address=:443 + - "--certificatesresolvers.myresolver.acme.dnschallenge=true" + - "--certificatesresolvers.myresolver.acme.dnschallenge.provider=ovh" + # - "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory" + - "--certificatesresolvers.myresolver.acme.email=postmaster@example.com" + - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json" + + init-container: + image: alpine:latest + volumes: + - ./config/ASP/config.php:/src/ASP/system/config/config.php + - backups-volume:/src/ASP/system/backups # This volume is effectively unused since ASP doesn't allow DB backups for a remote DB, but mount it anyway to avoid errors. + - cache-volume:/src/ASP/system/cache + - logs-volume:/src/ASP/system/logs + - snapshots-volume:/src/ASP/system/snapshots + - bf2sclone-cache-volume:/src/bf2sclone/cache + - db-volume:/var/lib/mysql + entrypoint: + - /bin/sh + command: + - -c + - | + set -eu + + echo "Granting ASP php write permissions" + chmod 666 /src/ASP/system/config/config.php + + chown -R 82:82 /src/ASP/system/backups + find /src/ASP/system/backups -type d -exec chmod 750 {} \; + find /src/ASP/system/backups -type f -exec chmod 640 {} \; + + chown -R 82:82 /src/ASP/system/cache + find /src/ASP/system/cache -type d -exec chmod 750 {} \; + find /src/ASP/system/cache -type f -exec chmod 640 {} \; + + chown -R 82:82 /src/ASP/system/logs + find /src/ASP/system/logs -type d -exec chmod 750 {} \; + find /src/ASP/system/logs -type f -exec chmod 640 {} \; + + mkdir -p /src/ASP/system/snapshots/failed + mkdir -p /src/ASP/system/snapshots/processed + mkdir -p /src/ASP/system/snapshots/unauthorized + mkdir -p /src/ASP/system/snapshots/unprocessed + chown -R 82:82 /src/ASP/system/snapshots + find /src/ASP/system/snapshots -type d -exec chmod 750 {} \; + find /src/ASP/system/snapshots -type f -exec chmod 640 {} \; + + echo "Granting bf2sclone php write permissions" + chown -R 82:82 /src/bf2sclone/cache + find /src/bf2sclone/cache -type d -exec chmod 750 {} \; + find /src/bf2sclone/cache -type f -exec chmod 640 {} \; + + echo "Granting db's 'mysql' user write permissions" + chown -R 999:999 /var/lib/mysql + + # The gamespy ASP. The dashboard is available at https://asp.example.com/ASP + asp-nginx: + image: startersclan/asp:3.1.0-nginx + labels: + - "traefik.enable=true" + - "traefik.docker.network=traefik-network" + # traefik v2 + # http + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-asp-nginx-http.entrypoints=web" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-asp-nginx-http.rule=Host(`asp.example.com`)" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-asp-nginx-http.middlewares=${COMPOSE_PROJECT_NAME}-asp-nginx-http-myRedirectScheme" + - "traefik.http.middlewares.${COMPOSE_PROJECT_NAME}-asp-nginx-http-myRedirectScheme.redirectScheme.scheme=https" + # https + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-asp-nginx.entrypoints=websecure" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-asp-nginx.tls" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-asp-nginx.rule=Host(`asp.example.com`)" + - "traefik.http.services.${COMPOSE_PROJECT_NAME}-asp-nginx.loadbalancer.server.port=80" + # volumes: + # - ./config/ASP/nginx/nginx.conf:/etc/nginx/nginx.conf:ro # Customize only if needed + networks: + - traefik-network + - bf2-network + depends_on: + - init-container + - asp-php + restart: unless-stopped + + asp-php: + image: startersclan/asp:3.1.0-php + volumes: + - ./config/ASP/config.php:/src/ASP/system/config/config.php # Main config file. Must be writeable or else ASP will throw an exception. Customize only if needed + # - ./config/ASP/armyAbbreviationMap.php:/src/ASP/system/config/armyAbbreviationMap.php:ro # Optional: Customize as needed if using a custom mod + # - ./config/ASP/backendAwards.php:/src/ASP/system/config/backendAwards.php:ro # Optional: Customize as needed if using a custom mod + # - ./config/ASP/ranks.php:/src/ASP/system/config/ranks.php:ro # Optional: Customize as needed if using a custom mod + # - ./config/ASP/php/conf.d/php.ini:/usr/local/etc/php/conf.d/php.ini:ro # Customize only if needed + # - ./config/ASP/php-fpm.d/www.conf:/usr/local/etc/php-fpm.d/www.conf:ro # Customize only if needed + - backups-volume:/src/ASP/system/backups # This volume is effectively unused since ASP doesn't allow DB backups for a remote DB, but mount it anyway to avoid errors. + - cache-volume:/src/ASP/system/cache + - logs-volume:/src/ASP/system/logs + - snapshots-volume:/src/ASP/system/snapshots + networks: + - bf2-network + depends_on: + - init-container + restart: unless-stopped + + # The bf2sclone for viewing BFHQ on the web. It is available at https://bf2sclone.example.com + bf2sclone-nginx: + image: startersclan/bf2stats:2.2.0-bf2sclone-nginx + labels: + - "traefik.enable=true" + - "traefik.docker.network=traefik-network" + # traefik v2 + # http + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-bf2sclone-nginx-http.entrypoints=web" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-bf2sclone-nginx-http.rule=Host(`bf2sclone.example.com`)" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-bf2sclone-nginx-http.middlewares=${COMPOSE_PROJECT_NAME}-bf2sclone-nginx-http-myRedirectScheme" + - "traefik.http.middlewares.${COMPOSE_PROJECT_NAME}-bf2sclone-nginx-http-myRedirectScheme.redirectScheme.scheme=https" + # https + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-bf2sclone-nginx.entrypoints=websecure" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-bf2sclone-nginx.tls" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-bf2sclone-nginx.rule=Host(`bf2sclone.example.com`)" + - "traefik.http.services.${COMPOSE_PROJECT_NAME}-bf2sclone-nginx.loadbalancer.server.port=80" + # volumes: + # - ./config/bf2sclone/nginx/nginx.conf:/etc/nginx/nginx.conf:ro # Customize only if needed + networks: + - traefik-network + - bf2-network + depends_on: + - init-container + - bf2sclone-php + restart: unless-stopped + + bf2sclone-php: + image: startersclan/bf2stats:2.2.0-bf2sclone-php + volumes: + - ./config/bf2sclone/config.inc.php:/src/bf2sclone/config.inc.php:ro # Main config file. Customize as needed + # - ./config/bf2sclone/php/conf.d/php.ini:/usr/local/etc/php/conf.d/php.ini:ro # Customize only if needed + # - ./config/bf2sclone/php-fpm.d/www.conf:/usr/local/etc/php-fpm.d/www.conf:ro # Customize only if needed + - bf2sclone-cache-volume:/src/bf2sclone/cache + networks: + - bf2-network + depends_on: + - init-container + restart: unless-stopped + + db: + image: mariadb:10.8 + environment: + - MARIADB_ROOT_PASSWORD=ascent # Change this to a strong password + - MARIADB_USER=admin + - MARIADB_PASSWORD=admin # Change this to a strong password + - MARIADB_DATABASE=bf2stats + volumes: + - db-volume:/var/lib/mysql + networks: + - bf2-network + depends_on: + - init-container + restart: unless-stopped + + # The phpmyadmin interface for administrating the DB. It is available at phpmyadmin.example.com + phpmyadmin: + image: phpmyadmin:5.2 + labels: + - "traefik.enable=true" + - "traefik.docker.network=traefik-network" + # traefik v2 + # http + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-phpmyadmin-http.entrypoints=web" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-phpmyadmin-http.rule=Host(`phpmyadmin.example.com`)" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-phpmyadmin-http.middlewares=${COMPOSE_PROJECT_NAME}-phpmyadmin-http-myRedirectScheme" + - "traefik.http.middlewares.${COMPOSE_PROJECT_NAME}-phpmyadmin-http-myRedirectScheme.redirectScheme.scheme=https" + # https + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-phpmyadmin.entrypoints=websecure" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-phpmyadmin.tls" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}-phpmyadmin.rule=Host(`phpmyadmin.example.com`)" + - "traefik.http.services.${COMPOSE_PROJECT_NAME}-phpmyadmin.loadbalancer.server.port=80" + environment: + - PMA_ABSOLUTE_URI=https://phpmyadmin.example.com # Enable this if behind a reverse proxy + - PMA_HOST=db + networks: + - traefik-network + - bf2-network + restart: unless-stopped + +networks: + traefik-public-network: + traefik-network: + name: traefik-network + internal: true + bf2-network: + name: bf2-network + +volumes: + prmasterserver-volume: + traefik-acme-volume: + backups-volume: + cache-volume: + logs-volume: + snapshots-volume: + bf2sclone-cache-volume: + db-volume: