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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@
# Directory containing OTP hook plugins.
# GAMEND_CONTENT_PLUGINS_DIR=modules/plugins

# Directories of static files served ahead of the built-in ones (images/,
# game/, favicon.ico, robots.txt, theme.css), relative to the working
# directory and searched in order. Each is used when it exists.
# GAMEND_CONTENT_STATIC_DIRS=static,priv/static

# Path to the theme JSON. A single file serves every locale; its text is
# translated via the gettext `theme` domain.
# GAMEND_CONTENT_THEME_CONFIG=
Expand Down
246 changes: 246 additions & 0 deletions .github/workflows/build-and-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ env:
IMAGE: ghcr.io/${{ github.repository }}
ELIXIR_VERSION: "1.20.1"
OTP_VERSION: "29.0.2"
# Linked statically into the server binaries (rel/scripts/static-deps.sh).
OPENSSL_VERSION: "3.5.8"
LIBSRTP_VERSION: "2.7.0"

jobs:
build:
Expand Down Expand Up @@ -1275,3 +1278,246 @@ jobs:
# ExDoc's output does not contain one — publishing without this would
# delete it on the next push and take docs.gamend.org down.
cname: docs.gamend.org

# ── Server binaries ─────────────────────────────────────────────────────
# Downloadable releases of the server: `gamend` for macOS (Apple silicon)
# and Linux (x86_64, arm64), SQLite and Postgres builds, plus the gamend.org
# website as a project folder. Every push to main republishes them on the
# rolling `server-latest` release, which rel/install.sh and the
# actions/setup-gamend action download from. The scripts are rel/scripts/;
# guide: priv/docs/10-setup/15-standalone.md.
binaries-linux:
name: ${{ matrix.name }}
runs-on: ${{ matrix.runner }}
# Built in Ubuntu 22.04 rather than on the runner's own image, so the
# binaries need glibc 2.35, not whatever the runner has.
container: ubuntu:22.04
strategy:
fail-fast: false
matrix:
include:
- { name: gamend-linux-x86_64, runner: ubuntu-24.04, adapter: sqlite }
- { name: gamend-linux-x86_64-postgres, runner: ubuntu-24.04, adapter: postgres }
- { name: gamend-linux-arm64, runner: ubuntu-24.04-arm, adapter: sqlite }
- { name: gamend-linux-arm64-postgres, runner: ubuntu-24.04-arm, adapter: postgres }
services:
# For the Postgres builds' smoke test; the SQLite ones leave it idle.
postgres:
image: postgres:17
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 5s --health-timeout 5s --health-retries 10
env:
GAMEND_DB_ADAPTER: ${{ matrix.adapter }}
BUILD_ROOT: /opt/gamend-build
steps:
- name: System packages
env:
DEBIAN_FRONTEND: noninteractive
run: |
apt-get update
apt-get install -y --no-install-recommends \
build-essential autoconf pkg-config perl curl ca-certificates git \
libncurses-dev jq file binutils unzip xz-utils brotli \
imagemagick optipng pngquant

- uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Version
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
echo "GAMEND_CONTENT_APP_VERSION=1.0.$(git rev-list --count HEAD)" >> "$GITHUB_ENV"

- name: Cache static OpenSSL, libsrtp and OTP
uses: actions/cache@v6
with:
path: |
${{ env.BUILD_ROOT }}/deps
${{ env.BUILD_ROOT }}/otp
key: otp-${{ matrix.runner }}-ubuntu22-${{ env.OTP_VERSION }}-${{ env.OPENSSL_VERSION }}-${{ env.LIBSRTP_VERSION }}-${{ hashFiles('rel/scripts/static-deps.sh', 'rel/scripts/build-otp.sh') }}

- name: Build the release
run: rel/scripts/build.sh "$BUILD_ROOT"

- name: Package
run: rel/scripts/package.sh "${{ matrix.name }}" dist

- name: Smoke test
run: |
# Only the Postgres build gets a database URL: an empty one still
# counts as set, and the SQLite build warns about it at boot.
if [ "$GAMEND_DB_ADAPTER" = postgres ]; then
export GAMEND_DB_URL=ecto://postgres:postgres@postgres:5432/gamend_smoke
fi
rel/scripts/smoke.sh "dist/${{ matrix.name }}.tar.gz"

- uses: actions/upload-artifact@v7
with:
name: ${{ matrix.name }}
path: dist/${{ matrix.name }}.tar.gz
if-no-files-found: error

binaries-macos:
name: ${{ matrix.name }}
runs-on: macos-15
strategy:
fail-fast: false
matrix:
include:
- { name: gamend-macos-arm64, adapter: sqlite }
- { name: gamend-macos-arm64-postgres, adapter: postgres }
env:
GAMEND_DB_ADAPTER: ${{ matrix.adapter }}
BUILD_ROOT: ${{ github.workspace }}/../gamend-build
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Version
run: echo "GAMEND_CONTENT_APP_VERSION=1.0.$(git rev-list --count HEAD)" >> "$GITHUB_ENV"

# `mix assets.deploy` cuts the theme's responsive images with ImageMagick
# and optimizes PNGs, as the Docker build does.
- name: Image tools
run: |
for tool in imagemagick optipng pngquant brotli; do
brew list "$tool" > /dev/null 2>&1 || brew install "$tool"
done

- name: Cache static OpenSSL, libsrtp and OTP
uses: actions/cache@v6
with:
path: |
${{ env.BUILD_ROOT }}/deps
${{ env.BUILD_ROOT }}/otp
key: otp-macos-15-${{ env.OTP_VERSION }}-${{ env.OPENSSL_VERSION }}-${{ env.LIBSRTP_VERSION }}-${{ hashFiles('rel/scripts/static-deps.sh', 'rel/scripts/build-otp.sh') }}

- name: Build the release
run: rel/scripts/build.sh "$BUILD_ROOT"

- name: Import the signing certificate
if: github.event_name != 'pull_request'
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
if [ -z "$BUILD_CERTIFICATE_BASE64" ]; then
echo "No signing certificate configured; the macOS build ships unsigned."
exit 0
fi
cert="$RUNNER_TEMP/certificate.p12"
keychain="$RUNNER_TEMP/signing.keychain-db"
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o "$cert"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$keychain"
security set-keychain-settings -lut 21600 "$keychain"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$keychain"
security import "$cert" -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k "$keychain"
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$keychain" > /dev/null
security list-keychain -d user -s "$keychain"
identity=$(security find-identity -v -p codesigning "$keychain" | grep "Developer ID Application" | head -1 | awk -F'"' '{print $2}')
echo "APPLE_SIGNING_IDENTITY=$identity" >> "$GITHUB_ENV"

- name: Package (signed and notarized on main)
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: rel/scripts/package.sh "${{ matrix.name }}" dist

# The Postgres build needs a database macOS runners do not have; its
# linkage is the SQLite build's, checked by package.sh.
- name: Smoke test
if: matrix.adapter == 'sqlite'
run: rel/scripts/smoke.sh "dist/${{ matrix.name }}.tar.gz"

- uses: actions/upload-artifact@v7
with:
name: ${{ matrix.name }}
path: dist/${{ matrix.name }}.tar.gz
if-no-files-found: error

binaries-website:
name: gamend-website
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- run: rel/scripts/website.sh dist
- uses: actions/upload-artifact@v7
with:
name: gamend-website
path: dist/gamend-website.tar.gz
if-no-files-found: error

# The Linux archive is built on Ubuntu 22.04; prove it runs, with nothing
# installed beyond curl, on the distributions people actually deploy to.
binaries-distros:
name: runs on ${{ matrix.image }}
needs: binaries-linux
runs-on: ubuntu-24.04
container: ${{ matrix.image }}
strategy:
fail-fast: false
matrix:
image: ["debian:12-slim", "ubuntu:24.04", "fedora:42"]
steps:
- name: curl and tar
run: |
if command -v apt-get > /dev/null; then
apt-get update && apt-get install -y --no-install-recommends curl ca-certificates
else
# Fedora ships curl-minimal; --allowerasing lets dnf keep or swap it.
dnf install -y --allowerasing curl tar gzip findutils
fi
- uses: actions/checkout@v7
- uses: actions/download-artifact@v8
with:
name: gamend-linux-x86_64
path: dist
- run: rel/scripts/smoke.sh dist/gamend-linux-x86_64.tar.gz

publish-binaries:
name: Publish server-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [binaries-linux, binaries-macos, binaries-website, binaries-distros]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0

- uses: actions/download-artifact@v8
with:
path: dist
merge-multiple: true

- name: Checksums and installer
run: |
cp rel/install.sh dist/install.sh
(cd dist && sha256sum *.tar.gz > SHA256SUMS)
cat dist/SHA256SUMS

- name: Move server-latest to this commit and upload
env:
GH_TOKEN: ${{ github.token }}
run: |
version="1.0.$(git rev-list --count HEAD)"
notes="Gamend server $version, built from ${GITHUB_SHA::12}.

Install: \`curl -fsSL https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/main/rel/install.sh | sh\`
Guide: https://gamend.org/docs/standalone"

git tag -f server-latest "$GITHUB_SHA"
git push -f origin refs/tags/server-latest

if gh release view server-latest > /dev/null 2>&1; then
gh release edit server-latest --prerelease --title "Gamend server (latest)" --notes "$notes"
else
gh release create server-latest --prerelease --title "Gamend server (latest)" --notes "$notes" --verify-tag
fi
gh release upload server-latest dist/* --clobber
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ runnable host app at the repository root.
- `mix setup` once, then `mix dev.start` (creates the DB, migrates, builds assets, runs `phx.server`).
- The endpoint module is `GamendWeb.Endpoint`; the host OTP app starts it.
- `GamendHost.Application` starts `GamendWeb.HostSupervision.children/1`. A core process goes in that list; a host-only one goes in its `:extra` option.
- The downloadable release ([guide](priv/docs/10-setup/15-standalone.md)): `rel/overlays/bin/gamend` runs the server and hands every other command to `GamendWeb.CLI`, under the name of the mix task it mirrors (`db.migrate`, `demo.seed`, …). A new `db.*`/seed mix task gets its `GamendWeb.CLI` twin, with the logic in a module both call (`Gamend.DemoSeed`, `Gamend.Release`). A release reads everything relative to the working directory, never `RELEASE_ROOT`. Starter templates live in `priv/starter/<name>`; packaging is `rel/scripts/*` and the `binaries-*` jobs of `.github/workflows/build-and-check.yml`.

### Routing ownership / extension point

Expand Down Expand Up @@ -224,7 +225,7 @@ Web-side features with no context: the site search palette (`GamendWeb.SearchInd

### Hooks

- Plugins implement `Gamend.Hooks`. They load from `modules/plugins/*` (`GAMEND_CONTENT_PLUGINS_DIR`) as bundled `ebin/`; run `mix plugin.bundle` after changing one. Examples live in `modules/plugins_examples/`.
- Plugins implement `Gamend.Hooks`. They load from `modules/plugins/*` (`GAMEND_CONTENT_PLUGINS_DIR`) as bundled `ebin/`; run `mix plugin.bundle` after changing one. A release, which has no Mix, builds them in-process with `Gamend.Hooks.PluginBuilder` (`build/1`, `build_all/0`). Examples live in `modules/plugins_examples/`.
- `before_*` hooks are pipelines: return `{:ok, value}` to allow (optionally modified) or `{:error, reason}` to block. `after_*` hooks run asynchronously via `Gamend.Async.run/1`.
- **Never** dispatch a hook or broadcast inside a transaction or lock. Open transactions with `Gamend.AfterCommit.transaction/2` and broadcast with `Gamend.Broadcast.publish/2`, which wait for the commit; run a `before_*` hook before taking the lock. See [CONTRIBUTING.md](CONTRIBUTING.md#hooks-so-plugins-can-extend-the-feature).
- Adding a callback touches six places: [CONTRIBUTING.md](CONTRIBUTING.md#hooks-so-plugins-can-extend-the-feature). The full hook list is in the [server scripting guide](priv/docs/40-gameplay/90-server-scripting.md).
Expand Down
Loading
Loading