Skip to content
Draft
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
67 changes: 67 additions & 0 deletions .github/workflows/directory-bundle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Isolated directory bundle
on:
push:
paths:
- 'packaging/directory/**'
- 'src/Civ13/ServerDirectory/**'
- 'scripts/directory/**'
- 'tests/server-directory*.php'
- 'tests/fixtures/server-directory*.json'
- '.github/workflows/directory-bundle.yml'
pull_request:
paths:
- 'packaging/directory/**'
- 'src/Civ13/ServerDirectory/**'
- 'scripts/directory/**'
- 'tests/server-directory*.php'
- 'tests/fixtures/server-directory*.json'
- '.github/workflows/directory-bundle.yml'
workflow_dispatch:
permissions:
contents: read
jobs:
bundle:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.3', '8.4', '8.5']
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: none
- name: Install only the committed directory dependency lock
run: |
test -f packaging/directory/composer.lock
composer install --working-dir=packaging/directory --no-dev --no-scripts --no-plugins --prefer-dist --no-interaction
composer validate --working-dir=packaging/directory --no-check-publish
composer check-platform-reqs --working-dir=packaging/directory --no-dev
composer audit --working-dir=packaging/directory --locked --no-dev
- name: Build and verify isolated bundle
run: |
python3 packaging/directory/test_verify.py
python3 packaging/directory/build.py
python3 packaging/directory/verify_bundle.py build/directory/civ13-directory-*.tar.gz
cd build/directory/civ13-directory
sha256sum --check SHA256SUMS > /dev/null
- name: Test only the bundled dependency environment
run: |
find build/directory/civ13-directory/src build/directory/civ13-directory/scripts -name '*.php' -print0 | xargs -0 -n1 php -l
php tests/server-directory.php build/directory/civ13-directory/vendor/autoload.php
php tests/server-directory-remote.php build/directory/civ13-directory/vendor/autoload.php
php tests/server-directory-addresses.php build/directory/civ13-directory/vendor/autoload.php
php tests/server-directory-standalone.php build/directory/civ13-directory/vendor/autoload.php
- uses: actions/upload-artifact@v4
if: matrix.php == '8.4'
with:
name: civ13-directory-${{ github.event.pull_request.head.sha || github.sha }}
path: |
build/directory/*.tar.gz
build/directory/*.sha256
build/directory/verify_bundle.py
if-no-files-found: error
retention-days: 14
18 changes: 18 additions & 0 deletions .github/workflows/verifier-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ on:
- 'src/**'
- 'tests/verifier-integration.php'
- 'tests/http-dependency.php'
- 'tests/server-directory.php'
- 'tests/server-directory-startup.php'
- 'tests/server-directory-remote.php'
- 'tests/server-directory-standalone.php'
- 'scripts/directory/**'
- 'tests/fixtures/server-directory*.json'
- 'tests/economy.php'
- 'tests/economy-service.php'
- '.github/workflows/verifier-integration.yml'
Expand All @@ -16,6 +22,12 @@ on:
- 'src/**'
- 'tests/verifier-integration.php'
- 'tests/http-dependency.php'
- 'tests/server-directory.php'
- 'tests/server-directory-startup.php'
- 'tests/server-directory-remote.php'
- 'tests/server-directory-standalone.php'
- 'scripts/directory/**'
- 'tests/fixtures/server-directory*.json'
- 'tests/economy.php'
- 'tests/economy-service.php'
- '.github/workflows/verifier-integration.yml'
Expand Down Expand Up @@ -45,6 +57,12 @@ jobs:
- run: composer audit --locked
- run: find src -name '*.php' -print0 | xargs -0 -n1 php -l
- run: php tests/http-dependency.php
- run: php tests/server-directory.php
- run: php tests/server-directory-startup.php
- run: php tests/server-directory-remote.php
- run: php tests/server-directory-addresses.php
- run: php -l scripts/directory/serve.php
- run: php tests/server-directory-standalone.php
- run: php tests/verifier-integration.php
- run: php tests/economy.php
- run: php tests/economy-service.php
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,8 @@ test.php
/tools/
/phive.xml
/.phive/

# Standalone directory dependencies are CI-built; its minimal lock is reviewed.
/packaging/directory/vendor/
/packaging/directory/__pycache__/
!packaging/directory/composer.lock
1 change: 1 addition & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Civilizationbot is [Civ13](https://civ13.com)'s official Discord bot designed to
- Chat moderation and relay
- Interaction commands and slash commands
- Automated status updates
- Opt-in, fresh online-only [launcher server directory](docs/launcher-directory.md)

## Installation

Expand Down
2 changes: 2 additions & 0 deletions bot.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ function loadEnv(string $filePath): void
//'verify_url' => 'http://valzargaming.com:8080/verified/', // Leave this blank if you do not want to use the webserver, ckeys will be stored locally as provisional
'verify_url' => getenv('VERIFIER_HOST_ADDR').':'.getenv('VERIFIER_HOST_PORT').'/verified/', // Local/Integrated Verifier Server
// 'serverinfo_url' => '', // URL of the serverinfo.json file, defaults to the webserver if left blank
// 'launcher_directory' => ['civ13.com' => ['tdm', 'nomads']], // Opt-in public status; see docs/launcher-directory.md
// 'launcher_directory_status_targets' => [], // Separate opt-in status-only inventory; never enables GameServer/pers
'ooc_badwords' => $ooc_badwords,
'ic_badwords' => $ic_badwords,
'folders' => [
Expand Down
95 changes: 95 additions & 0 deletions docs/launcher-directory-standalone.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Isolated directory runtime

Use this optional entrypoint only when restarting the existing bot is unsafe or
unnecessary. It composes the same Directory, Endpoint and public-only BYOND
probes; it never constructs Civ13, GameServer or Discord, and has no bot control
routes. It does not replace the existing bot or its configured game lifecycle.

## Approval and dependency gate

Deploy a reviewed, immutable source revision to a **separate** directory. Do not
checkout/pull/reset the running bot, replace its vendor tree, touch untracked
custom handlers/secrets, or stop/restart its processes. The operator must first
verify the existing PHP runtime and Composer dependency package versions and
hashes, including every Composer `autoload_files.php` entry. No application-root,
untracked configuration, secret file or unexpected autoload side effect is
acceptable. A separately pinned dependency artifact is preferred; an existing
vendor tree may be reused read-only only after this review and with changes held
until the directory is stopped/revalidated. Merely finding vendor/autoload.php
is not evidence that it is safe or compatible. The preflight must not execute it.

The source namespace is explicitly prepended to the verified Composer loader,
so the standalone runtime uses its own pinned directory classes rather than an
older bot checkout's classes. Bot.php, .env and secret bootstrap files are never
included. Empty or malformed inventory exits before executing the autoloader.

## Pinned CI bundle

The `Isolated directory bundle` workflow installs only
`packaging/directory/composer.lock`, with plugins/scripts disabled and Composer
audit enabled. It builds and tests the minimal dependency environment on PHP
8.3, 8.4 and 8.5. The artifact from PHP 8.4 includes only directory sources,
locked dependencies, empty example inventory, documentation and license. It
contains no bot entrypoint, Discord dependency, credentials or live settings.
ReactPHP HTTP is pinned to patched 1.11.1; the older live bot's 1.11.0 dependency
must not be reused for this endpoint.

Before transfer/installation, obtain the artifact from the reviewed exact commit
and compare its external SHA256 with the trusted CI/download record. Run the
supplied `verify_bundle.py` against the archive **before extraction**. It checks
bounded sizes, safe paths/modes, no links/devices, complete file checksums, source
commit, lock hash, package inventory and empty default inventory. This establishes
artifact consistency, not independent authenticity: the trusted commit/artifact
channel and separately communicated outer hash are still required. Source and
per-file hashes are also stored inside the archive. The build produces no
production installation or public release.

Extract only into a new versioned directory outside the running bot checkout,
and use the bundle's own vendor/autoload.php. Do not replace bot dependencies.

Use a bundle containing the reviewed multi-address status selection fix when
an approved hostname has several public A records. The old single-address
selection can omit a healthy server if another public address is unavailable.

## Configuration and operation

`scripts/directory/inventory.example.json` contains an empty, disabled inventory.
The example's loopback port is illustrative, not a production reservation. The
only permitted top-level fields are integer `listen_port` (1024–65535) and list
`targets`; each target has exactly the five status-only fields documented in
[the directory contract](launcher-directory.md). Both approved public hostnames
are allowed, but only verified game ports should be configured. An unconfigured
provider remains unavailable, not an invented successful empty roster.

The entrypoint syntax is:

```
php scripts/directory/serve.php inventory.json /verified/vendor/autoload.php
```

The listener is hardwired to 127.0.0.1. There is no listen-host override. Select
an unused local port after inspecting current sockets. Only the two exact
civ13.com provider-query URLs are served; all game probes require fresh validated
public IPv4 A answers. Inventory never chooses an arbitrary URL or private probe
address. If an on-host public hostname cannot reach its game, inspect routing;
do not bypass the public-address policy by inventing an IP override.

Use an independently supervised non-root process, with no bot/game descendants
or shared lifecycle dependency. Stop affects only that listener/timer/process.
No process manager configuration is installed by this patch. A shared bounded
600-request/minute budget protects the loopback endpoint without trusting
forwarded IPs; the existing Apache proxy should enforce its normal public limits.
No credentials or raw exceptions/status appear in runtime logs. Controlled
startup diagnostics identify inventory, autoload, runtime or bind phases.

Before public routing, verify the isolated listener locally using Host civ13.com
and both exact provider queries, fresh timestamps, and real BYOND status. Then
review a narrowly scoped Apache route in the existing civ13.com SSL vhost,
preserving the raw query and Host, bypassing any general redirect, and disabling
caching/stale fallback. Review existing ProxyPass/Rewrite ordering first. Validate
Apache syntax before an approved graceful reload; do not restart Apache, Gaia,
Help, or the old bot. Retain a route/service rollback plan before activation.

This document is a deployment plan, not permission to change host configuration.
The CI tests validate composition and fail-closed behavior, not host deployment
or successful live game joins.
Loading
Loading