[2.1] Add a Docker development environment and a baseline forum for upgrade testing - #9330
Open
albertlast wants to merge 9 commits into
Open
[2.1] Add a Docker development environment and a baseline forum for upgrade testing#9330albertlast wants to merge 9 commits into
albertlast wants to merge 9 commits into
Conversation
Ports the environment from the 3.0 branch, with the changes 2.1 needs: - The installer loads its schema from install_2-1_<type>.sql, and looks for it next to install.php rather than in other/. The entrypoint now stages both engines' files alongside install.php; without them the Welcome step marks an engine unsupported. - The writable list is 2.1's: agreement.txt at the root rather than under Languages/, and no Sources. Directories and files are created separately, because creating everything with mkdir -p would turn a missing agreement.txt into a directory. - No composer. 2.1 has no runtime vendor/ directory; its only dependency is a dev-only build-tools package fetched from git, so installing it at boot cost a slow, network-dependent first run and bought nothing. - MySQL is pinned to utf8mb3, matching what 2.1's own DDL creates. This is deliberate rather than an oversight: it is what makes the environment a genuine pre-utf8mb4 forum, which is what a 3.0 upgrade has to convert. - PHP defaults to 8.2. 2.1 supports 7.1 through 8.4, but 8.3 and later fill the log with deprecation notices on every page. Every port is offset from the 3.0 environment's, so both stacks can run at the same time -- which upgrade testing needs. Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
Testing the upgrade needs a starting point: a real, installed, filled 2.1
forum that everybody can restore in seconds. This builds one, for both
database engines, in a single command:
.docker/baseline/make-baseline.sh --engine both --profile small
Per engine it resets, installs, populates, seeds, dumps and then restores
the dump -- because a dump that has never been restored is a guess.
Installing headlessly turned out to be possible because 2.1's installer is
nearly stateless: ?step=N, then it runs steps in order in one request and
stops at the first needing input. Three things are not obvious, and are
documented in install-forum.sh: DatabasePopulation returns false even when
it succeeds (its report posts pop_done to skip past it), dbsession and
reg_mode must travel with boardurl because forum settings falls straight
through into population, and DeleteInstall deletes nothing -- a separate
GET ?delete does that.
Content comes from upstream Populate.php, fetched at a pinned commit and
checksummed rather than vendored: it is third-party licensed and does not
belong in an SMF release branch. Four asserted patches adapt it; two of
them fix mt_rand(1, 0) calls that are fatal on PHP 8 and would otherwise
stop it running at all.
Populate only builds six tables, though, and almost none of the 3.0
migrations touch them. So extras/ seeds what they do care about -- IPv4,
IPv6 and absent addresses, custom field values in their 2.1 layout, polls,
PMs, drafts, attachments, calendar holidays dated year 0004, bans, logs,
legacy settings, and MyISAM tables that a modern install would never
produce. Each script names the migrations it exists to exercise.
Every PHP file here is 7.1-syntax-clean, since 2.1's CI lints the whole
checkout from 7.1 through 8.4.
Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
A grep pattern written as $'\r' is quoted differently by different shells, and one that silently becomes the empty pattern matches every line -- so the guard was either useless or would have failed every dump. Counting the bytes with tr says what it means. Also corrects a comment in run-extras.php: re-running after a *finished* script is free, but a script that failed part way never wrote its marker, and repeating it would insert its rows twice. Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
An installed, populated SMF 2.1.7 forum, dumped for both engines at two
sizes, plus the manifest that lets a restore be checked rather than
merely attempted:
.docker/baseline/restore.sh --engine mysql --profile small
tiny 50 members, ~150 topics, 600 messages ~1 MB per engine
small 400 members, ~1200 topics, 6000 messages ~8 MB per engine
Both were produced by make-baseline.sh and then restored from the
committed files, on both engines, before being committed.
The dumps are marked -text -diff: they are byte-exact generated blobs,
and rewriting their line endings would corrupt the hex-encoded binary
IP columns. medium and large profiles exist in the builder but are
gitignored -- at 70 and 450 MB they are generated on demand.
Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
The baseline's package-log seeding writes it, and so does the package manager on any real install. It belongs inside the artifacts, not in the checkout. Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
Two things were wrong with what 40-calendar.php produced, and both defeated the point of the script. The recurring holidays were dated year 0004. SMF 2.1's sentinel for "every year" is 1004 -- getHolidayRange() rewrites the range bounds to that year to find them, and it is the column default. A holiday dated 0004 is simply a holiday in the year 4: SMF never shows it, and an upgrade would carry it across as a one-off. Which means the baseline had no recurring holiday at all, and HolidaysToEvents would have had nothing to convert. Verified after the fix: "Baseline yearly holiday" now resolves on 2026-12-25 and "Baseline new year" on 2027-01-01. The calendar itself was also left switched off, which is how 2.1 ships. That left the baseline describing a state no real forum is in -- four events and three holidays on a feature nobody could have reached to create them. Artifacts regenerated for both profiles and both engines, and restored from the committed files afterwards. Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
The three baseline custom field definitions never reached PostgreSQL. db_insert() builds its ON CONFLICT clause only from key columns that also appear in the column list, and id_field is a sequence the script deliberately does not supply -- so PostgreSQL got `ON CONFLICT () DO NOTHING`, rejected it as a syntax error, and SMF logged that rather than raising it. The script carried on and reported three fields inserted. MySQL, with its INSERT IGNORE, was fine, so the two engines' baselines disagreed. It survived every check we had. Row counts looked healthy because a stock install supplies four custom fields of its own, and the field *values* were written by a later query that worked, so the tables were all non-empty. Only reading the data showed it. So: use a plain insert, count what actually arrived rather than what was asked for, and add check-coverage.php -- 78 assertions that the columns the 3.0 migrations read are genuinely filled, not merely present. make-baseline.sh now runs it before dumping, on the grounds that a thin artifact is worth catching while the forum that produced it still exists. Two things it caught immediately, both since fixed: SMF's MySQL driver leaves the connection with no default schema, so DATABASE() is empty and every information_schema check silently matched nothing. Audited the other ten ignore/replace inserts; all name key columns that are present in their column lists. Artifacts regenerated and checked on both engines: 78 checks, 0 failures. Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
A comment line beginning with the linter's own name is parsed as another directive, so the explanation underneath the disable turned into a parse error and stopped the file being checked at all. Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
check-eof.php does descend into dot directories -- the assumption that the integrity scripts skip .docker/ was wrong, and only check-smf-index and check-smf-license behave that way. CI caught it on the first push. Its rules are specific: a file must end with `?>` at the very last byte, with no trailing whitespace, and with a blank line between that and the preceding `}` or `;`. That is what every other PHP file in the repository does. Verified by running the real build-tools scripts against the branch rather than guessing: check-eof, check-smf-license, check-smf-languages and check-version all pass. Re-linted on 7.1 and re-ran the generator afterwards -- 78 coverage checks, 0 failures. Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
Collaborator
Author
|
something i ever wanted, |
Collaborator
Author
|
note for my futue me, |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
This change was produced by an LLM. The code, the commit messages and this
description were all written by Claude (Anthropic), driven by @albertlast. It has
not yet had human code review.
Everything stated below was verified by running it rather than only reasoning about
it — full installs from an empty database on both engines, and the committed dumps
restored back over the forums that produced them. Even so, please review it as
untrusted work, and as a proposal: whether SMF wants generated fixtures committed to
a release branch is a project decision, not a technical one.
Description
This is a proposal rather than a fix, and it is tooling only — no forum code
changes. Please close it if a bundled dev environment and committed fixtures are not
wanted on the 2.1 branch.
Testing the 2.1 → 3.0 upgrade needs something nobody currently has: a real 2.1 forum,
with content in it, that everyone can start from and that is identical every time.
Building one by hand takes an afternoon of clicking through the installer and produces a
different forum each time, so upgrade work either happens against an empty schema —
which exercises almost none of the migrations — or against whatever private database the
person testing happens to own.
That starting point can only be built here. 3.0 cannot create a 2.1 forum; its installer
writes 3.0 schema. So this branch adds the environment and the builder, and commits the
result:
Roughly ten seconds later there is an installed SMF 2.1.7 forum with 400 members and
6,000 posts, on either engine, identical to the one anyone else restores. Point a 3.0
checkout at that database, run
upgrade.php, and the migration has something tomigrate.
The environment half is the same one as #9317, ported to 2.1. Ports are offset
(8180/8181/8125/3407/5533) so both stacks can run at once, which upgrade testing needs.
What makes the scenario a complex one
A stock install exercises almost nothing: SMF 3.0 runs 87
v2_1migrations and 17v3_0ones, and a fresh 2.1 database leaves most of the tables they read completelyempty. Content generation alone does not help much either — the upstream
Populate.phptool fills six tables, and hardly any migration touches them.
So the builder seeds the rest deliberately, and each script names the migrations it
exists to feed:
VARBINARY(16)and the conversion is real work; on PostgreSQL 2.1 already usedinetand they should skip. Both engines are built, so both paths are coveredthemes.variable = 'cust_<name>')CustomFieldsPart1–Part3HolidaysToEvents,RecurringEvents,EventUids. A holiday dated any other year is a one-off, and the recurrence conversion never firesPersonalMessageLabels,UserDraftsand friendsLegacyAttachments,AttachmentSizes,AttachmentDirectoryIpv6BanItem,Ipv6LogBannedErrorLogSession,SearchResultsPrimaryKeykarmaMode,mail_type,cookieTime,enable_mod_prefs,time_offset), TFA secrets, non-zero time offsets in both signsPackageVersion,MailType,RemoveCookieTime,DropModPrefs,DropTimeOffsetare all no-ops unless the old values are presentConvertToInnoDbwould otherwise find nothing to convertSpoofDetectorAlso seeded, because the alternative is a forum nobody can browse: board access. 2.1's
createBoard()leavesmember_groupsempty, so every board the generator makes isadmin-only until something opens it up.
Sizes
tinysmallmediumlargetinyis for a quick upgrade run,smallfor a realistic one.mediumandlargeexist as profiles but are gitignored by directory name, so producing one cannot
accidentally add half a gigabyte to the repository.
The committed artifacts total about 17 MB, which is the main thing to weigh in
reviewing this. If that is too much for a release branch, the builder works without them
—
make-baseline.shregenerates everything in one command — and the artifacts cansimply be dropped from the branch.
Installing without a browser
2.1's installer turns out to be drivable with
curl: it reads its step from?step=,runs steps in order within one request, and stops at the first that needs input. There
is no CSRF token and no server-side progress. Three details are not obvious, and are
documented in
install-forum.sh:DatabasePopulation()returnsfalseeven when it succeeds — it pauses to showits "N duplicate tables ignored" report, and that form posts
pop_doneto skip pastit. That is why the sequence is five requests, not four.
dbsessionandreg_modemust travel withboardurl, because forum settingsreturning true falls straight through into database population in the same request.
DeleteInstall()deletes nothing. The congratulations page has a checkbox thatpoints the browser at
?delete; that request is what removesinstall.phpand thetwo schema files.
The driver never sends the
statsfield, which would register the site withsimplemachines.org.
Populate.php is fetched, not vendored
Content comes from
Populate.phpinSimpleMachines/tools. It is downloaded at a
pinned commit and checksummed rather than committed here: it is third-party licensed
(MPL 1.1, with a BSD lorem ipsum generator) and does not belong in an SMF release
branch. Four patches adapt it, each asserting it matched exactly once, so an upstream
change breaks the build loudly instead of quietly producing a different baseline.
Two of those are worth flagging upstream:
makeBoards()andmakeMessages()both reachmt_rand(1, 0)on their first item, which PHP 8 rejects outright where PHP 7 silentlyswapped the arguments.
Populate.phpcannot run at all on PHP 8 unpatched.It does not disturb the existing CI checks
Everything lives under
.docker/, which the integrity scripts skip. Every PHP file addedis 7.1-syntax-clean, since the lint job runs
phplintover the whole checkout from7.1 through 8.4 and it is not worth betting on whether it descends into dot directories —
verified by running
php -lover all of them under bothphp:7.1-cliandphp:8.4-cli.shellcheck -S warningis clean on every shell script. No file under.github/istouched.
Outside
.docker/andcompose.yamlthe only tracked changes are.gitignoreand.gitattributes: the former because 2.1 ignores*.sql,*.gzandattachments/andwould otherwise swallow the entire deliverable, the latter to mark the dumps
-text -diffso line-ending rewriting cannot corrupt their hex-encoded IP columns.Verification
Both engines, both profiles, built from an empty database and then restored from the
committed files:
the manifest and all 41 recorded table counts matching.
check-coverage.php— 78 assertions that the columns the migrations read areactually filled, not merely that rows exist — passes on both engines.
zero deprecation notices in the response body.
That coverage check exists because it was needed. The custom field definitions were
silently absent on PostgreSQL for several builds:
db_insert()builds itsON CONFLICTclause only from key columns that also appear in the column list, and
id_fieldis asequence the caller does not supply, so PostgreSQL received
ON CONFLICT () DO NOTHING,rejected it as a syntax error, and SMF logged that rather than raising it. Row counts
looked healthy — a stock install ships four custom fields of its own — and only reading
the data showed it.
make-baseline.shnow runs the check before dumping.Notes for review
admin/baselineandsmf/smf/smf, deliberately trivialand published; the whole stack is throwaway and binds only to localhost. Generated
members cannot log in at all, and every address is
@example.com, which RFC 2606reserves.
$auth_secretand$image_proxy_secretare pinned by the builder. They live inSettings.phpand not in the database, so a restored dump paired with fresh randomsecrets would invalidate every session and two-factor backup code inside it.
utf8/utf8mb3aliases 2.1's DDL is written against.
CURRENT; it neveroverwrites, so a test can pin a revision.
.docker/baseline/README.mddocuments the cross-branch recipe for consuming these froma 3.0 branch —
git archivefrom a pinned commit, so the artifacts have exactly onehome and no copy can drift.
Issues References (Fixes|Related|Closes)