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
38 changes: 0 additions & 38 deletions .github/SOURCEMOD-LICENSE.txt

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ jobs:
org.opencontainers.image.url=https://sbpp.github.io
org.opencontainers.image.source=https://github.com/sbpp/sourcebans-pp
org.opencontainers.image.documentation=https://sbpp.github.io/getting-started/quickstart-docker/
org.opencontainers.image.licenses=CC-BY-NC-SA-3.0 AND GPL-3.0-or-later
org.opencontainers.image.licenses=Elastic-2.0
org.opencontainers.image.vendor=SourceBans++ Dev Team
org.opencontainers.image.revision=${{ github.sha }}

Expand Down
86 changes: 82 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,56 @@ jobs:
cp game/addons/sourcemod/plugins/*.smx "/tmp/${pkg}/common/addons/sourcemod/plugins/"
cp -R game/addons/sourcemod/configs "/tmp/${pkg}/common/addons/sourcemod/"
cp -R game/addons/sourcemod/translations "/tmp/${pkg}/common/addons/sourcemod/"
# GPLv3 §4 requires the licence text be conveyed with every
# redistribution of the covered work. Ship it at the tarball
# root so a self-hoster who only extracts the addons/ subtree
# still gets the licence side-by-side with the .smx files.
# The file is renamed from `LICENSE-plugins.txt` (the
# disambiguated source-tree name — the source tree carries
# both LICENSE.txt for the web panel + LICENSE-plugins.txt
# for the plugins) to the GPL-convention `LICENSE.txt` for
# the tarball root: GPLv3-licensed releases by convention
# ship the licence as `LICENSE` / `LICENSE.txt` next to the
# binaries, NOT under a non-default name. Operators
# extracting the plugin tarball expect that convention; an
# operator who extracts both tarballs into the same parent
# dir would clobber the webpanel zip's LICENSE.txt with the
# plugins' GPLv3 text, but the two install paths are
# disjoint by design (panel → web server's docroot,
# plugins → game server's `addons/sourcemod/`) so the
# collision is theoretical.
cp LICENSE-plugins.txt "/tmp/${pkg}/LICENSE.txt"
# THIRD-PARTY-NOTICES.txt carries the SourceBans 1.4.11 +
# SourceComms upstream-lineage attributions the plugins
# inherit (sbpp_comms.sp descends from SourceComms; the
# rest of the .sp files descend from the original
# SourceBans 1.4.x plugin set). Ship it next to LICENSE.txt
# so the GPLv3 §5 "preserve all notices" obligation is
# discoverable without cracking open the tarball's
# per-file headers.
cp THIRD-PARTY-NOTICES.txt "/tmp/${pkg}/THIRD-PARTY-NOTICES.txt"
tar -C /tmp -czf "${pkg}.tar.gz" "${pkg}"

# Smoke-check that the required licence files actually landed
# in the tarball. Future shell refactors that silently drop
# the cp commands above would ship a GPLv3-non-compliant
# tarball; fail the release instead.
- name: Verify required files are bundled in the plugin tarball
run: |
pkg="sourcebans-pp-${GITHUB_REF_NAME}.plugin-only"
missing=0
for f in \
"${pkg}/LICENSE.txt" \
"${pkg}/THIRD-PARTY-NOTICES.txt"; do
if ! tar -tzf "${pkg}.tar.gz" "${f}" >/dev/null 2>&1; then
echo "::error::Plugin tarball is missing ${f}; release would ship GPLv3-non-compliant."
missing=1
fi
done
if [ "$missing" -ne 0 ]; then
exit 1
fi

- uses: actions/upload-artifact@v4
with:
name: plugin
Expand Down Expand Up @@ -80,6 +128,26 @@ jobs:
run: |
pkg="sourcebans-pp-${GITHUB_REF_NAME}.webpanel-only"
cp -R web "/tmp/${pkg}"
# ELv2 "Notices" requires preserving the licence text + the
# ELASTIC LICENSE attribution notice when redistributing the
# Software. THIRD-PARTY-NOTICES.txt carries the
# upstream-lineage + bundled-third-party attributions a
# self-hoster needs to comply with (LightOpenID MIT,
# SourceBans 1.4.x + SourceComms + theme.conf.php
# CC-BY-NC-SA 3.0 historical attributions per the file's
# preamble). LICENSE-plugins.txt ships alongside so the
# install wizard's step 1 promise ("LICENSE-plugins.txt
# shipped at the root of this install" — see
# `web/install/pages/page.1.php`) is honoured even though
# the webpanel zip doesn't ship the plugins themselves;
# this also lets a self-hoster who only ever pulls the
# webpanel zip discover the GPLv3 terms governing the
# paired plugin tarball without a docs round-trip. All
# three files ship at the package root next to README.md /
# install/.
cp LICENSE.txt "/tmp/${pkg}/LICENSE.txt"
cp LICENSE-plugins.txt "/tmp/${pkg}/LICENSE-plugins.txt"
cp THIRD-PARTY-NOTICES.txt "/tmp/${pkg}/THIRD-PARTY-NOTICES.txt"
cat > "/tmp/${pkg}/configs/version.json" <<EOF
{
"version": "${GITHUB_REF_NAME}",
Expand All @@ -98,28 +166,38 @@ jobs:
# Smoke-check the actual zip (not the staged tree) so a future
# change that makes `zip` silently drop vendor/ — a stray
# .zipignore, an exclusion flag, etc. — fails the build loudly
# instead of shipping a broken artifact. Two anchor files:
# instead of shipping a broken artifact. Three anchor files:
# - includes/vendor/autoload.php — the file init.php gates on.
# - includes/vendor/smarty/smarty/src/Smarty.php — the highest-
# value transitive dep (templates won't render without it).
# This is the file that backs `use Smarty\Smarty;` in
# web/init.php via Smarty 5.x's PSR-4 autoload (`Smarty\` =>
# `src/`); the legacy libs/Smarty.class.php is only a back-
# compat shim for non-composer callers, which the panel isn't.
- name: Verify vendor/ is bundled in the zip
# - LICENSE.txt + LICENSE-plugins.txt + THIRD-PARTY-NOTICES.txt
# — ELv2 "Notices" compliance gate + the install wizard's
# step 1 contract (`web/install/pages/page.1.php` tells
# the user LICENSE-plugins.txt is "shipped at the root of
# this install" — without it the user-visible promise lies).
# A release that silently drops any of the three makes every
# downstream redistribution non-compliant.
- name: Verify required files are bundled in the zip
run: |
pkg="sourcebans-pp-${GITHUB_REF_NAME}.webpanel-only"
missing=0
for f in \
"${pkg}/includes/vendor/autoload.php" \
"${pkg}/includes/vendor/smarty/smarty/src/Smarty.php"; do
"${pkg}/includes/vendor/smarty/smarty/src/Smarty.php" \
"${pkg}/LICENSE.txt" \
"${pkg}/LICENSE-plugins.txt" \
"${pkg}/THIRD-PARTY-NOTICES.txt"; do
if ! unzip -l "${pkg}.zip" "${f}" >/dev/null 2>&1; then
echo "::error::Missing required artifact file in ${pkg}.zip: ${f}"
missing=1
fi
done
if [ "$missing" -ne 0 ]; then
echo "::error::Webpanel zip is missing bundled composer dependencies; release would ship broken to self-hosters."
echo "::error::Webpanel zip is missing required bundled files; release would ship broken or ELv2-non-compliant to self-hosters."
exit 1
fi

Expand Down
50 changes: 33 additions & 17 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2611,11 +2611,13 @@ the spec, target a 1920px viewport, not 1440px.
### Contributor License Agreement gate (`web/**`)

Pull requests that touch `web/**` are gated on a signed Contributor
License Agreement. The web panel is dual-licensable (free for hobby /
community use under CC BY-NC-SA 3.0; separate commercial licence for
production use by hosting providers), and the CLA is the mechanism
that lets the maintainer relicense future contributions without
contacting every contributor individually.
License Agreement. The web panel is dual-licensable (free under the
Elastic License 2.0 for hobby / community / self-hosted use; separate
commercial licence for game-server hosts offering the panel as a
managed service to third parties — see [`LICENSE.txt`](LICENSE.txt)
and the README "License" section for the contract details), and the
CLA is the mechanism that lets the maintainer relicense future
contributions without contacting every contributor individually.

- Agreement text: [`CLA.md`](CLA.md). Ten sections, ~1 page. Contributor
keeps copyright; maintainer gets a perpetual, irrevocable, worldwide,
Expand Down Expand Up @@ -2655,10 +2657,20 @@ contacting every contributor individually.
`custom-pr-sign-comment`, the `if:` matches against the same string
to gate execution, and CLA.md §10 is what the contributor is told
to type. Drift between any two silently breaks the signing flow.
- Historical contributors haven't signed; their pre-CLA web-panel
contributions aren't covered by the new grant. Retroactive sign-off
is a separate piece of work (opt-in follow-up) and isn't blocked
by the workflow being in place.
- Historical-contributor coverage was the precondition for the
`goals#4` ELv2 relicense and was resolved by the `goals#3` audit:
every pre-CLA `web/**` contribution of substance was authored by
`rumblefrog`, who as the project maintainer is also the licensor
under ELv2. The handful of small one-off external PRs from before
the CLA workflow landed either (a) survived intact through the
v2.0 panel rewrite (in which case their authors are listed on
`goals#3`'s audit and the CLA's §3(b) relicense grant covers them
going forward — pending sign-off), or (b) were removed during the
v2.0 rewrite (in which case the question is moot; `goals#5` Route
B rewrites covered the dead-code surface). Future inbound PRs are
covered by the workflow as designed. Retroactive sign-off for the
surviving-author set is still an opt-in follow-up but is not on
the critical path for the ELv2 relicense.

## Anti-patterns (do NOT reintroduce)

Expand All @@ -2670,8 +2682,8 @@ contacting every contributor individually.
```php
<?php
// SourceBans++ (c) 2014-2026 SourceBans++ Dev Team
// Licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0.
// See LICENSE.md for the full license text and THIRD-PARTY-NOTICES.txt for attributions.
// Licensed under the Elastic License 2.0.
// See LICENSE.txt for the full license text and THIRD-PARTY-NOTICES.txt for attributions.
```

Smarty `.tpl` files use the `{* … *}` shape with the same three
Expand All @@ -2681,12 +2693,16 @@ contacting every contributor individually.
was 100% v2.0 expression), it shipped two parallel year ranges
(`2014-2024` and `2014-2026`) with no automated drift gate, and
it pointed at `creativecommons.org/licenses/by-nc-sa/3.0/` for
the licence text but `LICENSE.md` is the actual licence. The
source-of-truth attribution surface for upstream lineage
(SourceBans 1.4.x, SourceComms, InterWave Studios theme.conf,
LightOpenID, TinyMCE) is `THIRD-PARTY-NOTICES.txt`. Issue
`goals#5` swept all 36 files; new files take the 4-line shape
from day one. Files with their own licence (`web/includes/Auth/openid.php`,
the licence text but the project's actual root license file
(today `LICENSE.txt`, formerly `LICENSE.md`) is the source of
truth. The source-of-truth attribution surface for upstream
lineage (SourceBans 1.4.x, SourceComms, InterWave Studios
theme.conf, LightOpenID, TinyMCE) is `THIRD-PARTY-NOTICES.txt`.
Issue `goals#5` swept all 36 files onto the 4-line shape; the
license-name + filename swap from CC-BY-NC-SA / `LICENSE.md` to
ELv2 / `LICENSE.txt` rode the same PR that landed `LICENSE.txt`
(`goals#4` Phase 2). New files take the 4-line shape from day
one. Files with their own licence (`web/includes/Auth/openid.php`,
`web/includes/tinymce/`) keep their original headers.
- Inline `echo '<form action="…">'` HTML blobs at the top of
admin page handlers (`web/pages/admin.edit.<x>.php`) → build a
Expand Down
5 changes: 4 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ plugins are stable and updated less often.
├── AGENTS.md Conventions for AI agents / contributors
├── CHANGELOG.md Release notes
├── docs/ Starlight docs site (install / upgrade / configure)
└── LICENSE.md
├── LICENSE.txt Elastic License 2.0 (web panel)
├── LICENSE-plugins.txt GPLv3 (SourceMod plugins)
├── CLA.md Contributor License Agreement (web/** PRs)
└── THIRD-PARTY-NOTICES.txt
```

## Web panel (`web/`)
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ Legend:
> Custom themes that forked the pre-2.0 default keep working but
> render in a compatibility mode; see the migration notes in the
> release for details.
>
> **The web panel licence has changed** from CC BY-NC-SA 3.0 to the
> Elastic License 2.0. Hobby / community self-hosting stays free
> (most operators see no operational change). Offering SourceBans++
> to third parties as a hosted or managed service is now reserved
> by ELv2 and needs a separate commercial licence. SourceMod
> plugins remain GPLv3. Full breakdown:
> [Upgrading 1.8 → 2.0 — Licence change](https://sbpp.github.io/updating/1-8-to-2-0/#licence-change).
```
01. * Rebuilt default theme: drawer-based admin nav, command palette
(Ctrl/Cmd-K), light/dark/system theming, accessibility-first
Expand Down
35 changes: 29 additions & 6 deletions CLA.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ tiers of sublicensees.

For clarity, the licence granted in Section 3(b) is the practical
mechanism by which the Maintainer may dual-license the Project (for
example, offering it under both a community open-source licence and a
separate commercial licence) and by which the Maintainer may relicense
the Project under different terms in the future without seeking your
further permission.
example, offering it under both the Elastic License 2.0
[`LICENSE.txt`](LICENSE.txt) and a separate commercial licence) and
by which the Maintainer may relicense the Project under different
terms in the future without seeking your further permission.

## 4. Patent Grant

Expand Down Expand Up @@ -126,8 +126,9 @@ This Agreement applies **only** to Contributions to the `web/`
directory of the Project. Contributions to other parts of the
SourceBans++ repository, including the SourceMod plugins under
`game/addons/sourcemod/`, are governed by the licence terms of those
subprojects (currently GPLv3 for the SourceMod plugins) and are not
covered by this Agreement.
subprojects (currently GPLv3 for the SourceMod plugins, see
[`LICENSE-plugins.txt`](LICENSE-plugins.txt)) and are not covered by
this Agreement.

## 9. Miscellaneous

Expand All @@ -147,3 +148,25 @@ pull request that the project's CLA bot has flagged:
Your signature is recorded automatically by the bot, applies to all
future Contributions you make to the Project, and is effective as of
the date the bot records it.

### Pre-CLA contribution history

Contributions made to the Project before this CLA workflow landed
(initial workflow rollout in `sbpp/goals#4` Phase 2) are addressed by
the audit recorded at
[`sbpp/goals#3`](https://github.com/sbpp/goals/issues/3). That audit
confirmed that the surviving substantive `web/**` contributions
prior to the CLA workflow are attributable to the project
maintainer ([@rumblefrog](https://github.com/rumblefrog)), whose
authorship clears the path for the
[`sbpp/goals#4`](https://github.com/sbpp/goals/issues/4) relicense
from CC BY-NC-SA 3.0 to the Elastic License 2.0 without contacting
third-party contributors individually. The handful of small one-off
external pre-CLA `web/**` PRs identified by the audit are listed in
that issue; their authors are welcome to retroactively sign this
Agreement against any subsequent PR so the §3(b) relicense grant
applies to their historical contributions too, but the project's
ELv2 standing does not depend on those retroactive signatures.

This subsection is informational and does not modify the substantive
terms of the Agreement above.
Loading
Loading