From 2f6d0b72d8e88cfb7839883002b7ec4c16708242 Mon Sep 17 00:00:00 2001
From: Liraz Siri
Date: Tue, 25 Aug 2026 20:49:16 +0000
Subject: [PATCH 1/9] Port XOOPS appliance to Trixie
Install the maintained official XOOPS Core 2.7.3 release at an exact commit and verified archive digest, update the LAMP integration for current PHP and MariaDB, and keep generated database and administrator credentials out of process arguments and build traces.
Add an authenticated HTTPS acceptance probe for firstboot login, custom-block create and public read, database persistence, and the official stable update channel. The updater resolves official release tags to commits, preserves generated state, creates backups, and stages the upstream browser-supervised upgrader.
Verified with Bash syntax checks, Python compilation, diff checks, source-tree secret-argument and documentation punctuation scans, updater usage-boundary behavior, and required test-environment failure behavior. Exact appliance acceptance remains pending because the shared Wave 2 runner is missing its cleanup dependency.
---
README.rst | 9 +-
changelog | 16 +-
conf.d/downloads | 23 +-
conf.d/main | 210 ++++++++++--------
docs/v19.0-testing.md | 68 ++++++
.../etc/apache2/sites-available/xoops.conf | 4 +-
overlay/usr/lib/inithooks/bin/xoops.py | 35 ++-
.../firstboot.d/20regen-xoops-secrets | 10 +-
overlay/usr/lib/inithooks/firstboot.d/40xoops | 6 +-
overlay/usr/local/bin/turnkey-xoops-update | 186 ++++++++++++++++
plan/main | 4 +-
tests/v19.sh | 161 ++++++++++++++
12 files changed, 610 insertions(+), 122 deletions(-)
create mode 100644 docs/v19.0-testing.md
create mode 100755 overlay/usr/local/bin/turnkey-xoops-update
create mode 100755 tests/v19.sh
diff --git a/README.rst b/README.rst
index 696e252..645e45b 100644
--- a/README.rst
+++ b/README.rst
@@ -11,13 +11,16 @@ support.
This appliance includes all the standard features in `TurnKey Core`_,
and on top of that:
-- XOOPS configurations:
+- XOOPS 2.7 configuration:
- Installed from upstream source code to /var/www/xoops
+ - Pinned to the official stable release commit and archive digest.
+ - ``turnkey-xoops-update`` checks the official XOOPS 2.7 stable channel
+ and prepares supervised upgrades without replacing site data.
**Security note**: Updates to XOOPS may require supervision so
they **ARE NOT** configured to install automatically. See "Upgrading
- from previous versions" section of the `Xoops release notes`_.
+ from previous versions" section of the `XOOPS release notes`_.
- SSL support out of the box.
- `Adminer`_ administration frontend for MySQL (listening on port
@@ -36,5 +39,5 @@ Credentials *(passwords set at first boot)*
.. _XOOPS: https://xoops.org/
.. _TurnKey Core: https://www.turnkeylinux.org/core
-.. _XOOPS release notes: https://github.com/XOOPS/XoopsCore25/blob/master/release_notes.txt
+.. _XOOPS release notes: https://github.com/XOOPS/XoopsCore27/blob/master/release_notes.txt
.. _Adminer: https://www.adminer.org
diff --git a/changelog b/changelog
index 44fa0ac..67af415 100644
--- a/changelog
+++ b/changelog
@@ -1,3 +1,18 @@
+turnkey-xoops-19.0 (1) turnkey; urgency=low
+
+ * Rebase the appliance on Debian 13/Trixie.
+
+ * Install XOOPS 2.7.3 from its pinned official release commit with archive
+ integrity verification.
+
+ * Preserve the existing firstboot administrator and database credential
+ contract while storing the administrator password with PHP's current
+ password hash.
+
+ * Add a supervised updater for the official XOOPS 2.7 stable channel.
+
+ -- TurnKey Linux Developers Tue, 25 Aug 2026 00:00:00 +0000
+
turnkey-xoops-18.0 (1) turnkey; urgency=low
* Upgraded to latest upstream version of XOOPS - 2.5.11.
@@ -270,4 +285,3 @@ turnkey-xoops-12.0 (1) turnkey; urgency=low
appliances. Here we only describe changes specific to this appliance.
-- Alon Swartz Wed, 01 Aug 2012 08:00:00 +0200
-
diff --git a/conf.d/downloads b/conf.d/downloads
index b9fabc7..1f06352 100755
--- a/conf.d/downloads
+++ b/conf.d/downloads
@@ -1,13 +1,16 @@
-#!/bin/bash -ex
+#!/bin/bash -e
-dl() {
- [ "$FAB_HTTP_PROXY" ] && PROXY="--proxy $FAB_HTTP_PROXY"
- cd $2; curl -L -f -O $PROXY $1; cd -
-}
+set -x
+VERSION=2.7.3
+COMMIT=00fff34c11fd4bee606e734d3bccf52d36fe0fda
+SHA256=b49d935116352434a60e5dae93efce356d9e5f360ad92bc72dcc69d7ab82b8cb
+URL="https://github.com/XOOPS/XoopsCore27/archive/$COMMIT.tar.gz"
+ARCHIVE="/usr/local/src/$COMMIT.tar.gz"
-VERSION="v2.5.11"
-URL="https://github.com/XOOPS/XoopsCore25/archive/$VERSION.tar.gz"
-
-dl $URL /usr/local/src
-
+curl_args=(-fsSL)
+if [ -n "${FAB_HTTP_PROXY:-}" ]; then
+ curl_args+=(--proxy "$FAB_HTTP_PROXY")
+fi
+curl "${curl_args[@]}" "$URL" -o "$ARCHIVE"
+echo "$SHA256 $ARCHIVE" | sha256sum --check --status
diff --git a/conf.d/main b/conf.d/main
index 7faa68b..f9f70f2 100755
--- a/conf.d/main
+++ b/conf.d/main
@@ -1,118 +1,154 @@
-#!/bin/sh -ex
+#!/bin/bash -e
+
+set -x
DB_NAME=xoops
DB_USER=xoops
DB_PREFIX=xoops
-DB_PASS=$(mcookie)
ADMIN_NAME=admin
-ADMIN_PASS=turnkey
ADMIN_MAIL=admin@example.com
SRC=/usr/local/src
WEBROOT=/var/www/xoops
XOOPS=/var/local/lib/xoops
-VERSION=v2.5.11
+VERSION=2.7.3
+COMMIT=00fff34c11fd4bee606e734d3bccf52d36fe0fda
+ARCHIVE_SHA256=b49d935116352434a60e5dae93efce356d9e5f360ad92bc72dcc69d7ab82b8cb
+SOURCE_DIR="$SRC/XoopsCore27-$COMMIT"
-# unpack and configure
-tar -zxf $SRC/$VERSION.tar.gz -C $SRC
-cp -a $SRC/XoopsCore*/htdocs $WEBROOT
-cp $SRC/XoopsCore*/extras/mainfile.php $WEBROOT
-rm -rf $SRC/XoopsCore*
+# Unpack and configure the official XOOPS release.
+tar -zxf "$SRC/$COMMIT.tar.gz" -C "$SRC"
+install -d "$WEBROOT"
+cp -a "$SOURCE_DIR/htdocs/." "$WEBROOT/"
+cp "$WEBROOT/mainfile.dist.php" "$WEBROOT/mainfile.php"
-mkdir -p $XOOPS
-mv $WEBROOT/xoops_lib $XOOPS/lib
-mv $WEBROOT/xoops_data $XOOPS/data
+install -d "$XOOPS"
+mv "$WEBROOT/xoops_lib" "$XOOPS/lib"
+mv "$WEBROOT/xoops_data" "$XOOPS/data"
+rm -rf "$SOURCE_DIR"
-chown -R www-data:www-data $WEBROOT $XOOPS
+chown -R www-data:www-data "$WEBROOT" "$XOOPS"
-# configure apache
+# Configure Apache and start the services required by the web installer.
a2dissite 000-default
a2ensite xoops
a2enmod rewrite
-
-# start services
service mysql start
service apache2 start
-# setup the database
-MYSQL_BATCH="mysql --user=root --password=$MYSQL_PASS --batch"
-MYSQL_ADMIN="mysqladmin --user=root --password=$MYSQL_PASS"
-
-$MYSQL_ADMIN create $DB_NAME
-$MYSQL_BATCH --execute "grant all privileges on $DB_NAME.* to $DB_USER@localhost identified by '$DB_PASS'; flush privileges;"
-
-# curl based install
-XMAIL=$(echo $ADMIN_MAIL | sed s/@/%40/)
-XROOT=$(echo $WEBROOT | sed "s/\//%2F/g")
-XDATA=$(echo $XOOPS/data | sed "s/\//%2F/g")
-XLIB=$(echo $XOOPS/lib | sed "s/\//%2F/g")
-
-URL="http://127.0.0.1/install"
-CURL="curl -c /tmp/cookie -b /tmp/cookie"
-
-$CURL ${URL}/index.php -H 'Cookie: xo_install_lang=english' --data 'lang=english'
-$CURL ${URL}/page_start.php
-$CURL ${URL}/page_modcheck.php
-$CURL ${URL}/page_pathsettings.php --data "root=$XROOT&data=$XDATA&lib=$XLIB&URL=http%3A%2F%2F127.0.0.1&COOKIE_DOMAIN"
-$CURL ${URL}/page_dbconnection.php --data "DB_TYPE=mysql&DB_HOST=localhost&DB_USER=$DB_USER&DB_PASS=$DB_PASS&DB_PCONNECT=1"
-$CURL ${URL}/page_dbsettings.php --data "DB_NAME=$DB_NAME&DB_PREFIX=$DB_PREFIX&DB_CHARSET=utf8&DB_COLLATION=utf8_general_ci"
-$CURL ${URL}/page_configsave.php
+# Keep generated credentials and installer requests out of the build trace.
+# Errexit remains active throughout this section.
+set +x
+DB_PASS=$(mcookie)
+ADMIN_PASS=turnkey
+MYSQL_BATCH=(mysql --user=root "--password=${MYSQL_PASS:-}" --batch)
+MYSQL_ADMIN=(mysqladmin --user=root "--password=${MYSQL_PASS:-}")
+
+"${MYSQL_ADMIN[@]}" create "$DB_NAME"
+"${MYSQL_BATCH[@]}" <Lets get you started...
- Log in as admin and visit the administration dashboard to customize your site.
- When ready, delete this welcome block and create some new ones.
For more information, check out the release notes and the online documentation
.'
-
-$MYSQL_BATCH --database=$DB_NAME --execute "INSERT INTO ${DB_PREFIX}_newblocks VALUES ('$BLOCK_ID', '0', '0', '', 'Custom Block \(HTML\)', 'Welcome to TurnKey Xoops', '$WELCOME', '5', '0', '1', 'C', 'H', '1', '', '', '', '', '', '0', '$DATE');"
-
-$MYSQL_BATCH --database=$DB_NAME --execute "INSERT INTO ${DB_PREFIX}_block_module_link VALUES ('$BLOCK_ID', '-1')"
-
-$MYSQL_BATCH --database=$DB_NAME --execute "INSERT INTO ${DB_PREFIX}_group_permission VALUES (NULL, '1', '$BLOCK_ID', '1', 'block_read');"
-$MYSQL_BATCH --database=$DB_NAME --execute "INSERT INTO ${DB_PREFIX}_group_permission VALUES (NULL, '2', '$BLOCK_ID', '1', 'block_read');"
-$MYSQL_BATCH --database=$DB_NAME --execute "INSERT INTO ${DB_PREFIX}_group_permission VALUES (NULL, '3', '$BLOCK_ID', '1', 'block_read');"
-
-# remove hardcoded IP
-$MYSQL_BATCH --database=$DB_NAME --execute "UPDATE ${DB_PREFIX}_banner SET imageurl=\"/images/banners/xoops_banner_2.gif\" WHERE bid=\"1\";"
-$MYSQL_BATCH --database=$DB_NAME --execute "UPDATE ${DB_PREFIX}_banner SET imageurl=\"/images/banners/xoopsifyIt.gif\" WHERE bid=\"2\";"
-# remove broken flash banners
-$MYSQL_BATCH --database=$DB_NAME --execute "DELETE FROM ${DB_PREFIX}_banner WHERE bid=\"3\";"
-$MYSQL_BATCH --database=$DB_NAME --execute "DELETE FROM ${DB_PREFIX}_banner WHERE bid=\"4\";"
-
-sed -i "s|XOOPS_URL.*|XOOPS_URL', '');|g" $WEBROOT/mainfile.php
-
-# secure sensitive files
-chown -R root:root $WEBROOT $XOOPS
-chown -R www-data:www-data $WEBROOT/uploads
-chown www-data:www-data $WEBROOT/mainfile.php
-chown www-data:www-data $WEBROOT/include/license.php
-chown -R www-data:www-data $XOOPS/data/caches
-chown -R www-data:www-data $XOOPS/data/data
-chown -R www-data:www-data $XOOPS/lib/modules/protector/configs/
-
-chown -R www-data:www-data $XOOPS/data/protector/
-
-chmod 440 $WEBROOT/mainfile.php
-chmod 440 $XOOPS/data/data/secure.php
+"${CURL[@]}" "$URL/page_moduleinstaller.php"
+"${CURL[@]}" "$URL/page_end.php"
+
+rm -f "$COOKIE"
+rm -rf "$WEBROOT/install"
+
+# Add one visible custom block that gives a new administrator a starting point.
+WELCOME='Lets get you started...
- Log in as admin and visit the administration dashboard to customize your site.
- When ready, delete this welcome block and create some new ones.
For more information, visit the XOOPS project.
'
+"${MYSQL_BATCH[@]}" --database="$DB_NAME" < /usr/local/share/turnkey-xoops/source <
Options +FollowSymLinks
AllowOverride All
- order allow,deny
- allow from all
+ Require all granted
-
diff --git a/overlay/usr/lib/inithooks/bin/xoops.py b/overlay/usr/lib/inithooks/bin/xoops.py
index ff9bc60..b15aa8c 100755
--- a/overlay/usr/lib/inithooks/bin/xoops.py
+++ b/overlay/usr/lib/inithooks/bin/xoops.py
@@ -2,7 +2,7 @@
"""Set Xoops admin password and email
Option:
- --pass= unless provided, will ask interactively
+ --pass-stdin read the password from standard input
--email= unless provided, will ask interactively
"""
@@ -10,7 +10,6 @@
import sys
import getopt
import subprocess
-import hashlib
from libinithooks import inithooks_cache
from libinithooks.dialog_wrapper import Dialog
@@ -26,21 +25,31 @@ def usage(s=None):
def main():
try:
opts, args = getopt.gnu_getopt(sys.argv[1:], "h",
- ['help', 'pass=', 'email='])
+ ['help', 'pass=', 'pass-stdin', 'email='])
except getopt.GetoptError as e:
usage(e)
password = ""
+ password_stdin = False
email = ""
for opt, val in opts:
if opt in ('-h', '--help'):
usage()
elif opt == '--pass':
password = val
+ elif opt == '--pass-stdin':
+ password_stdin = True
elif opt == '--email':
email = val
- if not password:
+ if password and password_stdin:
+ usage('--pass and --pass-stdin are mutually exclusive')
+
+ if password_stdin:
+ password = sys.stdin.read()
+ if not password:
+ usage('standard input did not contain a password')
+ elif not password:
d = Dialog('TurnKey Linux - First boot configuration')
password = d.get_password(
"Xoops Password",
@@ -57,14 +66,26 @@ def main():
inithooks_cache.write('APP_EMAIL', email)
- hash = hashlib.md5(password.encode('utf8')).hexdigest()
+ password_hash = subprocess.run(
+ [
+ '/usr/bin/php',
+ '-r',
+ 'echo password_hash(stream_get_contents(STDIN), PASSWORD_DEFAULT);',
+ ],
+ input=password,
+ text=True,
+ capture_output=True,
+ check=True,
+ ).stdout
+
+ if not password_hash.startswith('$'):
+ raise RuntimeError('PHP did not return a modern password hash')
m = MySQL()
- m.execute('UPDATE xoops.xoops_users SET pass=%s WHERE uname=\"admin\";', (hash,))
+ m.execute('UPDATE xoops.xoops_users SET pass=%s WHERE uname=\"admin\";', (password_hash,))
m.execute('UPDATE xoops.xoops_users SET email=%s WHERE uname=\"admin\";', (email,))
m.execute('UPDATE xoops.xoops_config SET conf_value=%s WHERE conf_name=\"adminmail\";', (email,))
if __name__ == "__main__":
main()
-
diff --git a/overlay/usr/lib/inithooks/firstboot.d/20regen-xoops-secrets b/overlay/usr/lib/inithooks/firstboot.d/20regen-xoops-secrets
index a834cc2..b4a86e7 100755
--- a/overlay/usr/lib/inithooks/firstboot.d/20regen-xoops-secrets
+++ b/overlay/usr/lib/inithooks/firstboot.d/20regen-xoops-secrets
@@ -1,11 +1,11 @@
#!/bin/bash -e
# regenerate xoops mysql password
-. /etc/default/inithooks
-
CONF=/var/local/lib/xoops/data/data/secure.php
PASSWORD=$(mcookie)
-sed -i "s|XOOPS_DB_PASS.*|XOOPS_DB_PASS', '$PASSWORD');|" $CONF
-$INITHOOKS_PATH/bin/mysqlconf.py --user=xoops --pass="$PASSWORD"
-
+sed -i "s|XOOPS_DB_PASS.*|XOOPS_DB_PASS', '$PASSWORD');|" "$CONF"
+mysql --batch <&2
+ exit 2
+}
+
+source_value() {
+ local key=$1
+ sed -n "s/^${key}=//p" "$SOURCE_FILE" | head -n 1
+}
+
+[[ $# -ge 1 && $# -le 2 ]] || usage
+mode=$1
+dry_run=false
+case "$mode" in
+ --check)
+ [[ $# -eq 1 ]] || usage
+ ;;
+ --apply)
+ if [[ $# -eq 2 ]]; then
+ [[ $2 = --dry-run ]] || usage
+ dry_run=true
+ fi
+ ;;
+ *)
+ usage
+ ;;
+esac
+
+[[ -r $SOURCE_FILE && -r $APP_ROOT/include/version.php ]] || {
+ echo "XOOPS installation provenance is unavailable" >&2
+ exit 1
+}
+
+installed=$(source_value version)
+installed_commit=$(source_value commit)
+runtime=$(php -r 'require $argv[1]; echo XOOPS_VERSION;' \
+ "$APP_ROOT/include/version.php")
+[[ $installed =~ ^2\.7\.[0-9]+$ ]] || {
+ echo "Installed XOOPS source version is invalid" >&2
+ exit 1
+}
+[[ $installed_commit =~ ^[0-9a-f]{40}$ ]] || {
+ echo "Installed XOOPS source commit is invalid" >&2
+ exit 1
+}
+[[ $runtime == "XOOPS $installed"* ]] || {
+ echo "XOOPS runtime version differs from its source record" >&2
+ exit 1
+}
+
+release_json=$(curl -fsSL "$RELEASE_API")
+tag=$(php -r '
+ $release = json_decode(stream_get_contents(STDIN), true, 512, JSON_THROW_ON_ERROR);
+ if (($release["draft"] ?? true) || ($release["prerelease"] ?? true)) {
+ exit(1);
+ }
+ $tag = $release["tag_name"] ?? "";
+ if (!preg_match("/^v2\\.7\\.[0-9]+$/", $tag)) {
+ exit(1);
+ }
+ echo $tag;
+' <<<"$release_json")
+latest=${tag#v}
+
+candidate=$(git ls-remote "$UPSTREAM_REPO" "refs/tags/$tag^{}" \
+ | awk 'NR == 1 { print $1 }')
+if [[ -z $candidate ]]; then
+ candidate=$(git ls-remote "$UPSTREAM_REPO" "refs/tags/$tag" \
+ | awk 'NR == 1 { print $1 }')
+fi
+[[ $candidate =~ ^[0-9a-f]{40}$ ]] || {
+ echo "The official XOOPS release tag could not be resolved" >&2
+ exit 1
+}
+
+status=update-available
+if [[ $installed = "$latest" && $installed_commit = "$candidate" ]]; then
+ status=up-to-date
+fi
+
+if [[ $mode = --check ]]; then
+ printf 'installed=%s\ninstalled_commit=%s\nruntime=%s\n' \
+ "$installed" "$installed_commit" "$runtime"
+ printf 'latest=%s\ncandidate=%s\ntag=%s\n' "$latest" "$candidate" "$tag"
+ printf 'channel=official-xoops-core27-stable\nstatus=%s\n' "$status"
+ exit 0
+fi
+
+if $dry_run; then
+ printf 'mode=apply-dry-run\ntarget=%s\ncandidate=%s\ntag=%s\n' \
+ "$latest" "$candidate" "$tag"
+ printf 'verified=official-xoops-release-tag\nstatus=%s\n' "$status"
+ printf 'upgrade_path=/upgrade/\n'
+ exit 0
+fi
+
+[[ $EUID -eq 0 ]] || {
+ echo "--apply must run as root" >&2
+ exit 1
+}
+
+if [[ $status = up-to-date ]]; then
+ echo "XOOPS $installed is already current"
+ exit 0
+fi
+
+stage=$(mktemp -d /var/tmp/turnkey-xoops-update.XXXXXX)
+cleanup() {
+ rm -rf "$stage"
+}
+trap cleanup EXIT
+
+archive="$stage/$candidate.tar.gz"
+curl -fsSL "https://github.com/XOOPS/XoopsCore27/archive/$candidate.tar.gz" \
+ -o "$archive"
+archive_sha256=$(sha256sum "$archive" | awk '{ print $1 }')
+tar -xzf "$archive" -C "$stage"
+release_root="$stage/XoopsCore27-$candidate"
+[[ -d $release_root/htdocs && -d $release_root/upgrade ]] || {
+ echo "The official XOOPS archive has an unexpected layout" >&2
+ exit 1
+}
+grep -Fq "XOOPS $latest" "$release_root/htdocs/include/version.php" || {
+ echo "The official XOOPS archive version differs from its release tag" >&2
+ exit 1
+}
+
+backup_dir=/var/backups/turnkey-xoops
+backup_id=$(date -u +%Y%m%dT%H%M%SZ)
+install -d -m 0700 "$backup_dir"
+mysqldump --single-transaction "$DB_NAME" \
+ | gzip -c > "$backup_dir/xoops-$backup_id.sql.gz"
+tar -C / -czf "$backup_dir/xoops-$backup_id-files.tar.gz" \
+ var/www/xoops var/local/lib/xoops
+
+tar -C "$release_root/htdocs" \
+ --exclude=./mainfile.php \
+ --exclude=./uploads \
+ --exclude=./xoops_lib \
+ --exclude=./xoops_data \
+ -cf - . | tar -C "$APP_ROOT" -xf -
+tar -C "$release_root/htdocs/xoops_lib" -cf - . \
+ | tar -C "$DATA_ROOT/lib" -xf -
+tar -C "$release_root/htdocs/xoops_data" \
+ --exclude=./caches \
+ --exclude=./data/secure.php \
+ --exclude=./logs \
+ --exclude=./protector \
+ -cf - . | tar -C "$DATA_ROOT/data" -xf -
+cp -a "$release_root/upgrade" "$APP_ROOT/upgrade"
+
+chown -R root:root "$APP_ROOT" "$DATA_ROOT"
+chown -R www-data:www-data "$APP_ROOT/uploads"
+chown -R www-data:www-data "$DATA_ROOT/data/caches"
+chown -R www-data:www-data "$DATA_ROOT/data/data"
+chown -R www-data:www-data "$DATA_ROOT/data/logs"
+chown -R www-data:www-data "$DATA_ROOT/data/protector"
+chown -R www-data:www-data "$DATA_ROOT/lib/modules/protector/configs"
+chown root:www-data "$APP_ROOT/mainfile.php" "$DATA_ROOT/data/data/secure.php"
+chmod 0440 "$APP_ROOT/mainfile.php" "$DATA_ROOT/data/data/secure.php"
+
+updated_runtime=$(php -r 'require $argv[1]; echo XOOPS_VERSION;' \
+ "$APP_ROOT/include/version.php")
+cat > "$SOURCE_FILE" <
#include
+git
php-gd
php-cli
php-curl
php-intl
php-mbstring
php-xml
-
-unzip
-
diff --git a/tests/v19.sh b/tests/v19.sh
new file mode 100755
index 0000000..fa48d17
--- /dev/null
+++ b/tests/v19.sh
@@ -0,0 +1,161 @@
+#!/bin/bash
+set -euo pipefail
+
+: "${TKL_TEST_RESULT:?TKL_TEST_RESULT must name the result file}"
+: "${TKL_TEST_APP_PASS:?TKL_TEST_APP_PASS must contain the firstboot XOOPS password}"
+
+APP_ROOT=/var/www/xoops
+SOURCE_FILE=/usr/local/share/turnkey-xoops/source
+BASE_URL=https://127.0.0.1
+
+fail() {
+ echo "FAIL: $*" >&2
+ exit 1
+}
+
+require_contains() {
+ local text=$1
+ local expected=$2
+ local context=$3
+ [[ $text == *"$expected"* ]] || fail "$context did not contain: $expected"
+}
+
+source_value() {
+ local key=$1
+ sed -n "s/^${key}=//p" "$SOURCE_FILE" | head -n 1
+}
+
+[[ -r $SOURCE_FILE ]] || fail "XOOPS source provenance is missing"
+[[ -x /usr/local/bin/turnkey-xoops-update ]] || fail "XOOPS updater is not executable"
+
+version=$(source_value version)
+commit=$(source_value commit)
+archive_sha256=$(source_value archive_sha256)
+channel=$(source_value channel)
+recorded_runtime=$(source_value runtime_version)
+
+[[ $version = 2.7.3 ]] || fail "unexpected XOOPS source version"
+[[ $commit = 00fff34c11fd4bee606e734d3bccf52d36fe0fda ]] \
+ || fail "unexpected XOOPS source commit"
+[[ $archive_sha256 = b49d935116352434a60e5dae93efce356d9e5f360ad92bc72dcc69d7ab82b8cb ]] \
+ || fail "unexpected XOOPS archive digest"
+[[ $channel = official-xoops-core27-stable ]] || fail "unexpected XOOPS channel"
+
+runtime=$(php -r 'require $argv[1]; echo XOOPS_VERSION;' \
+ "$APP_ROOT/include/version.php")
+[[ $runtime = "$recorded_runtime" ]] || fail "runtime version differs from provenance"
+[[ $runtime == "XOOPS $version"* ]] || fail "runtime version differs from release tag"
+
+[[ $(stat -c '%U:%G:%a' "$APP_ROOT/mainfile.php") = root:www-data:440 ]] \
+ || fail "XOOPS mainfile permissions are incorrect"
+[[ $(stat -c '%U:%G:%a' /var/local/lib/xoops/data/data/secure.php) = root:www-data:440 ]] \
+ || fail "XOOPS database credential permissions are incorrect"
+
+systemctl is-active --quiet apache2 || fail "Apache is not active"
+systemctl is-active --quiet mariadb || fail "MariaDB is not active"
+apache2ctl configtest 2>&1 | grep -q 'Syntax OK' || fail "Apache configuration is invalid"
+
+home_page=$(curl --insecure -fsSL --max-time 30 "$BASE_URL/")
+require_contains "$home_page" "TurnKey XOOPS" "XOOPS HTTPS home page"
+require_contains "$home_page" "Welcome to TurnKey XOOPS" "XOOPS welcome block"
+
+adminer_page=$(curl --insecure -fsSL --max-time 30 https://127.0.0.1:12322/)
+require_contains "$adminer_page" "Adminer" "Adminer HTTPS endpoint"
+
+cookie=$(mktemp)
+pass_file=$(mktemp)
+trap 'rm -f "$cookie" "$pass_file"' EXIT
+chmod 0600 "$cookie" "$pass_file"
+printf '%s' "$TKL_TEST_APP_PASS" > "$pass_file"
+curl_args=(--insecure -fsS --max-time 30 -c "$cookie" -b "$cookie")
+
+curl "${curl_args[@]}" "$BASE_URL/user.php" -o /dev/null
+curl "${curl_args[@]}" "$BASE_URL/user.php" \
+ --data-urlencode 'uname=admin' \
+ --data-urlencode "pass@$pass_file" \
+ --data-urlencode 'op=login' \
+ -o /dev/null
+
+admin_page=$(curl "${curl_args[@]}" "$BASE_URL/admin.php")
+require_contains "$admin_page" "Control Panel" "XOOPS administrator login"
+
+block_form=$(curl "${curl_args[@]}" \
+ "$BASE_URL/modules/system/admin.php?fct=blocksadmin&op=add")
+require_contains "$block_form" "Blocks Administration" "XOOPS block administration"
+token=$(sed -n \
+ "s/.*name=['\"]XOOPS_TOKEN_REQUEST['\"][^>]*value=['\"]\([^'\"]*\)['\"].*/\1/p" \
+ <<<"$block_form" | head -n 1)
+[[ -n $token ]] || fail "XOOPS block form did not contain a request token"
+
+fixture_title='TurnKey XOOPS acceptance block'
+fixture_content='XOOPS content create and read verified'
+curl "${curl_args[@]}" "$BASE_URL/modules/system/admin.php" \
+ --data-urlencode "XOOPS_TOKEN_REQUEST=$token" \
+ --data-urlencode 'fct=blocksadmin' \
+ --data-urlencode 'op=save' \
+ --data-urlencode 'bid=0' \
+ --data-urlencode 'block_type=C' \
+ --data-urlencode 'mid=0' \
+ --data-urlencode 'func_num=0' \
+ --data-urlencode 'func_file=' \
+ --data-urlencode 'show_func=' \
+ --data-urlencode 'edit_func=' \
+ --data-urlencode 'template=' \
+ --data-urlencode 'dirname=' \
+ --data-urlencode 'name=' \
+ --data-urlencode 'side=5' \
+ --data-urlencode 'weight=0' \
+ --data-urlencode 'visible=1' \
+ --data-urlencode 'modules[]=-1' \
+ --data-urlencode 'groups[]=1' \
+ --data-urlencode 'groups[]=2' \
+ --data-urlencode 'groups[]=3' \
+ --data-urlencode "title=$fixture_title" \
+ --data-urlencode "content_block=$fixture_content" \
+ --data-urlencode 'c_type=H' \
+ --data-urlencode 'bcachetime=0' \
+ -o /dev/null
+
+public_fixture=$(curl --insecure -fsSL --max-time 30 "$BASE_URL/?v19-acceptance=1")
+require_contains "$public_fixture" "$fixture_title" "public XOOPS content"
+require_contains "$public_fixture" "$fixture_content" "public XOOPS content"
+
+db_fixture=$(mysql --batch --skip-column-names "$DB_NAME" --execute="
+ SELECT CONCAT(title, '|', content)
+ FROM xoops_newblocks
+ WHERE title='$fixture_title'
+ ORDER BY bid DESC LIMIT 1;")
+[[ $db_fixture = "$fixture_title|$fixture_content" ]] \
+ || fail "XOOPS database did not retain the created content"
+
+password_scheme=$(mysql --batch --skip-column-names "$DB_NAME" --execute="
+ SELECT LEFT(pass, 1) FROM xoops_users WHERE uname='admin';")
+[[ $password_scheme = '$' ]] || fail "firstboot did not store a modern admin password hash"
+
+check_output=$(turnkey-xoops-update --check)
+latest=$(awk -F= '$1 == "latest" { print $2 }' <<<"$check_output")
+candidate=$(awk -F= '$1 == "candidate" { print $2 }' <<<"$check_output")
+tag=$(awk -F= '$1 == "tag" { print $2 }' <<<"$check_output")
+[[ $latest =~ ^2\.7\.[0-9]+$ ]] || fail "updater returned an invalid stable version"
+[[ $candidate =~ ^[0-9a-f]{40}$ ]] || fail "updater returned an invalid commit"
+[[ $tag = "v$latest" ]] || fail "updater tag and version differ"
+require_contains "$check_output" "channel=official-xoops-core27-stable" "XOOPS updater"
+
+apply_plan=$(turnkey-xoops-update --apply --dry-run)
+require_contains "$apply_plan" "mode=apply-dry-run" "XOOPS updater plan"
+require_contains "$apply_plan" "target=$latest" "XOOPS updater plan"
+require_contains "$apply_plan" "candidate=$candidate" "XOOPS updater plan"
+require_contains "$apply_plan" "verified=official-xoops-release-tag" "XOOPS updater plan"
+require_contains "$apply_plan" "upgrade_path=/upgrade/" "XOOPS updater plan"
+
+echo "PASS: XOOPS HTTPS admin login, custom block create/read, MariaDB, and updater"
+echo "version=$version runtime=$runtime commit=$commit"
+cat > "$TKL_TEST_RESULT" <
Date: Wed, 26 Aug 2026 05:12:38 +0000
Subject: [PATCH 2/9] Let the XOOPS installer create mainfile
Do not install the unconfigured mainfile template before invoking the upstream wizard. Including that template on the first request leaves XOOPS_VAR_PATH empty, prevents the installer from establishing its session, and stops schema creation.
Keep mainfile creation within the official page_configsave flow so the configured external data and library paths are written together.
---
conf.d/main | 1 -
1 file changed, 1 deletion(-)
diff --git a/conf.d/main b/conf.d/main
index f9f70f2..4b95a69 100755
--- a/conf.d/main
+++ b/conf.d/main
@@ -22,7 +22,6 @@ SOURCE_DIR="$SRC/XoopsCore27-$COMMIT"
tar -zxf "$SRC/$COMMIT.tar.gz" -C "$SRC"
install -d "$WEBROOT"
cp -a "$SOURCE_DIR/htdocs/." "$WEBROOT/"
-cp "$WEBROOT/mainfile.dist.php" "$WEBROOT/mainfile.php"
install -d "$XOOPS"
mv "$WEBROOT/xoops_lib" "$XOOPS/lib"
From 3e70a28fccafef8261fce025047573b08ea75333 Mon Sep 17 00:00:00 2001
From: Liraz Siri
Date: Wed, 26 Aug 2026 05:46:33 +0000
Subject: [PATCH 3/9] Bootstrap XOOPS version probes
Define the application root before loading XOOPS's guarded version file from CLI contexts. Without that bootstrap, build provenance, acceptance, and updater checks read the guard message instead of the runtime version.
Keep all three identity boundaries on the same probe so recorded, tested, and updated source identities remain comparable.
---
conf.d/main | 2 +-
overlay/usr/local/bin/turnkey-xoops-update | 4 ++--
tests/v19.sh | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/conf.d/main b/conf.d/main
index 4b95a69..37ee7a8 100755
--- a/conf.d/main
+++ b/conf.d/main
@@ -124,7 +124,7 @@ unset DB_PASS ADMIN_PASS MYSQL_BATCH MYSQL_ADMIN
set -x
# Record the exact upstream identity installed in the appliance.
-RUNTIME_VERSION=$(php -r 'require $argv[1]; echo XOOPS_VERSION;' \
+RUNTIME_VERSION=$(php -r 'define("XOOPS_ROOT_PATH", dirname($argv[1], 2)); require $argv[1]; echo XOOPS_VERSION;' \
"$WEBROOT/include/version.php")
install -d /usr/local/share/turnkey-xoops
cat > /usr/local/share/turnkey-xoops/source <&2
@@ -169,7 +169,7 @@ chown -R www-data:www-data "$DATA_ROOT/lib/modules/protector/configs"
chown root:www-data "$APP_ROOT/mainfile.php" "$DATA_ROOT/data/data/secure.php"
chmod 0440 "$APP_ROOT/mainfile.php" "$DATA_ROOT/data/data/secure.php"
-updated_runtime=$(php -r 'require $argv[1]; echo XOOPS_VERSION;' \
+updated_runtime=$(php -r 'define("XOOPS_ROOT_PATH", dirname($argv[1], 2)); require $argv[1]; echo XOOPS_VERSION;' \
"$APP_ROOT/include/version.php")
cat > "$SOURCE_FILE" <
Date: Wed, 26 Aug 2026 06:23:10 +0000
Subject: [PATCH 4/9] Place XOOPS acceptance blocks in a rendered zone
Use the left block zone for the seeded welcome content and the authenticated create/read fixture. XOOPS maps side 0 to the left collection, which the pinned xbootstrap5 theme renders; its prior top-center side 5 is omitted by that theme.
This keeps the appliance and acceptance flow aligned without carrying a theme override.
---
conf.d/main | 2 +-
tests/v19.sh | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/conf.d/main b/conf.d/main
index 37ee7a8..fe0275a 100755
--- a/conf.d/main
+++ b/conf.d/main
@@ -102,7 +102,7 @@ INSERT INTO ${DB_PREFIX}_newblocks
template, bcachetime, last_modified)
VALUES
(0, 0, '', 'Custom Block (HTML)', 'Welcome to TurnKey XOOPS', '$WELCOME',
- 5, 0, 1, 'C', 'H', 1, '', '', '', '', '', 0, UNIX_TIMESTAMP());
+ 0, 0, 1, 'C', 'H', 1, '', '', '', '', '', 0, UNIX_TIMESTAMP());
SET @block_id = LAST_INSERT_ID();
INSERT INTO ${DB_PREFIX}_block_module_link (block_id, module_id)
VALUES (@block_id, -1);
diff --git a/tests/v19.sh b/tests/v19.sh
index 60447e9..4e3f3ac 100755
--- a/tests/v19.sh
+++ b/tests/v19.sh
@@ -103,7 +103,7 @@ curl "${curl_args[@]}" "$BASE_URL/modules/system/admin.php" \
--data-urlencode 'template=' \
--data-urlencode 'dirname=' \
--data-urlencode 'name=' \
- --data-urlencode 'side=5' \
+ --data-urlencode 'side=0' \
--data-urlencode 'weight=0' \
--data-urlencode 'visible=1' \
--data-urlencode 'modules[]=-1' \
From 3f8a764548f370e54a4b978668e98afaa2688588 Mon Sep 17 00:00:00 2001
From: Liraz Siri
Date: Wed, 26 Aug 2026 06:31:58 +0000
Subject: [PATCH 5/9] Declare the XOOPS acceptance database
Bind direct persistence assertions to the appliance's XOOPS database instead of relying on an undeclared environment variable. This lets the already-passing web round trip proceed to its SQL and updater checks under nounset.
The correction changes only test execution and does not alter appliance behavior.
---
tests/v19.sh | 1 +
1 file changed, 1 insertion(+)
diff --git a/tests/v19.sh b/tests/v19.sh
index 4e3f3ac..b288ec9 100755
--- a/tests/v19.sh
+++ b/tests/v19.sh
@@ -7,6 +7,7 @@ set -euo pipefail
APP_ROOT=/var/www/xoops
SOURCE_FILE=/usr/local/share/turnkey-xoops/source
BASE_URL=https://127.0.0.1
+DB_NAME=xoops
fail() {
echo "FAIL: $*" >&2
From ae080fc406bc174c6287bca7ad4abfaee93bb420 Mon Sep 17 00:00:00 2001
From: Liraz Siri
Date: Wed, 26 Aug 2026 06:43:33 +0000
Subject: [PATCH 6/9] Recover Apache after firstboot certificate rotation
Restart the XOOPS web service after the shared certificate hook and database credential rotation. Apache can enter active state while its certificate files are being replaced, then exit before the shared hook's active-service guard decides whether to restart it.
This lane-local recovery keeps the normal first boot serving HTTPS without changing shared infrastructure.
---
overlay/usr/lib/inithooks/firstboot.d/20regen-xoops-secrets | 3 +++
1 file changed, 3 insertions(+)
diff --git a/overlay/usr/lib/inithooks/firstboot.d/20regen-xoops-secrets b/overlay/usr/lib/inithooks/firstboot.d/20regen-xoops-secrets
index b4a86e7..420375c 100755
--- a/overlay/usr/lib/inithooks/firstboot.d/20regen-xoops-secrets
+++ b/overlay/usr/lib/inithooks/firstboot.d/20regen-xoops-secrets
@@ -9,3 +9,6 @@ mysql --batch <
Date: Wed, 26 Aug 2026 06:53:58 +0000
Subject: [PATCH 7/9] Bind XOOPS migration evidence to the passing run
Record the exact clean behavioral commit, source and harness hashes, configured-root boundary, runtime checks, updater result, cleanup, and retained report from the passing Wave 2 acceptance.
Map each README identity claim to its observed result and preserve the two upstream-supervision limitations for independent review.
---
docs/v19.0-testing.md | 63 +++++++++++++++++++++++++++++++++----------
1 file changed, 49 insertions(+), 14 deletions(-)
diff --git a/docs/v19.0-testing.md b/docs/v19.0-testing.md
index 1747eb3..919238d 100644
--- a/docs/v19.0-testing.md
+++ b/docs/v19.0-testing.md
@@ -4,14 +4,14 @@
| README or group criterion | Implementation boundary | Command and expected result | Actual and retained evidence |
| --- | --- | --- | --- |
-| Trixie appliance | The v19 LAMP base supplies Debian PHP, Apache, MariaDB, Adminer, Postfix, and Webmin. No active Bookworm source remains. | The exact Wave 2 build must report `RELEASE=debian/trixie`, install `turnkey-xoops-19.0`, and complete normal init. | Pending the exact acceptance run. Unchanged Core services map to the retained Core 19 baseline. |
-| Official maintained XOOPS | Debian Trixie has no XOOPS package. XOOPS Core 2.7.3 is fetched from the official repository by exact release commit and archive SHA256. The official security policy supports the latest release and provides a private reporting address. | `tests/v19.sh` compares the installed source record, runtime version, release commit, archive digest, and stable channel. | Pending the exact acceptance run. |
-| XOOPS administrator credentials | Firstboot sets the documented `admin` password and email. PHP creates the current `PASSWORD_DEFAULT` hash without exposing the password in the process arguments. | Log in over HTTPS with `TKL_TEST_APP_PASS`, open `/admin.php`, require `Control Panel`, and verify the stored password uses a modern hash. | Pending the exact acceptance run. |
-| Content create and read | The XOOPS block administration flow creates a visible custom HTML block. | Authenticate, fetch a live CSRF token, POST the block form, read the title and body on the public HTTPS home page, and verify the same row in MariaDB. | Pending the exact acceptance run. |
-| Database | XOOPS uses a generated MariaDB account whose password is rotated at firstboot and retained only in the protected external data configuration. | The authenticated web flow must read and write through XOOPS, the direct SQL check must read the created block, and `secure.php` must be `root:www-data` mode `0440`. | Pending the exact acceptance run. |
-| HTTPS and Adminer | The appliance Apache virtual host serves XOOPS on ports 80 and 443. The inherited LAMP Adminer endpoint remains on HTTPS port 12322. | Request the XOOPS home and admin flows at `https://127.0.0.1` and require `Adminer` at `https://127.0.0.1:12322/`. | Pending the exact acceptance run. |
-| Supervised updater | `turnkey-xoops-update` reads the official non-prerelease 2.7 release, resolves its official tag commit, backs up application data on apply, preserves configuration and uploads, and stages the upstream web upgrader. | Run `turnkey-xoops-update --check` and `--apply --dry-run`; require the same eligible version, tag, commit, official channel, and `/upgrade/` path. | Pending the exact acceptance run. |
-| Postfix, Webmin, SSH, and root credentials | These README claims are inherited unchanged from the v19 Core and LAMP baselines. XOOPS adds no overlay or firstboot behavior for them. | Cite the retained Core 19 baseline and require the appliance runtime health gate. | Pending the exact acceptance run. |
+| Trixie appliance | The v19 LAMP base supplies Debian PHP, Apache, MariaDB, Adminer, Postfix, and Webmin. No active Bookworm source remains. | The exact Wave 2 build must report `RELEASE=debian/trixie`, install `turnkey-xoops-19.0`, and complete normal init. | Run `20260826t064349z-6297-24697` built the Trixie root, installed the release package, reached multi-user with inithooks complete, and passed cleanup. Unchanged Core services map to the retained Core 19 baseline. |
+| Official maintained XOOPS | Debian Trixie has no XOOPS package. XOOPS Core 2.7.3 is fetched from the official repository by exact release commit and archive SHA256. The official security policy supports the latest release and provides a private reporting address. | `tests/v19.sh` compares the installed source record, runtime version, release commit, archive digest, and stable channel. | PASS: release 2.7.3, runtime `XOOPS 2.7.3-RC1`, commit `00fff34c11fd4bee606e734d3bccf52d36fe0fda`, archive SHA256 `b49d935116352434a60e5dae93efce356d9e5f360ad92bc72dcc69d7ab82b8cb`, and the official stable channel agreed. |
+| XOOPS administrator credentials | Firstboot sets the documented `admin` password and email. PHP creates the current `PASSWORD_DEFAULT` hash without exposing the password in the process arguments. | Log in over HTTPS with `TKL_TEST_APP_PASS`, open `/admin.php`, require `Control Panel`, and verify the stored password uses a modern hash. | PASS: exact-run login reached the control panel and the MariaDB record contained a current dollar-prefixed PHP password hash. |
+| Content create and read | The XOOPS block administration flow creates a visible custom HTML block. | Authenticate, fetch a live CSRF token, POST the block form, read the title and body on the public HTTPS home page, and verify the same row in MariaDB. | PASS: the authenticated form created the fixture, the public home page rendered its title and body, and direct SQL returned the same values. |
+| Database | XOOPS uses a generated MariaDB account whose password is rotated at firstboot and retained only in the protected external data configuration. | The authenticated web flow must read and write through XOOPS, the direct SQL check must read the created block, and `secure.php` must be `root:www-data` mode `0440`. | PASS: firstboot rotation completed, the web and direct SQL persistence checks passed, and the protected credential file had the required owner and mode. |
+| HTTPS and Adminer | The appliance Apache virtual host serves XOOPS on ports 80 and 443. The inherited LAMP Adminer endpoint remains on HTTPS port 12322. | Request the XOOPS home and admin flows at `https://127.0.0.1` and require `Adminer` at `https://127.0.0.1:12322/`. | PASS: Apache remained active after firstboot certificate rotation, all XOOPS flows used HTTPS, and port 12322 returned Adminer. |
+| Supervised updater | `turnkey-xoops-update` reads the official non-prerelease 2.7 release, resolves its official tag commit, backs up application data on apply, preserves configuration and uploads, and stages the upstream web upgrader. | Run `turnkey-xoops-update --check` and `--apply --dry-run`; require the same eligible version, tag, commit, official channel, and `/upgrade/` path. | PASS: both commands selected official tag `v2.7.3` at commit `00fff34c11fd4bee606e734d3bccf52d36fe0fda`; the dry run verified the tag and reported `/upgrade/`. |
+| Postfix, Webmin, SSH, and root credentials | These README claims are inherited unchanged from the v19 Core and LAMP baselines. XOOPS adds no overlay or firstboot behavior for them. | Cite the retained Core 19 baseline and require the appliance runtime health gate. | Run `20260826t064349z-6297-24697` reached the shared runtime health gate. The service behavior remains covered by the retained Core 19 baseline. |
## Source decision
@@ -29,13 +29,40 @@ security policy, and `security@xoops.org` for vulnerability reports.
TKLDEV_CONTAINER=tkldev19-wave2 \
TKL_HARNESS_STATE_DIR=/home/agent/.local/state/turnkey-v19-harness-wave2 \
TKL_HARNESS_LOCK_FILE=/home/agent/.local/state/turnkey-v19-harness-wave2/build.lock \
+TKL_HARNESS_LOCK_TIMEOUT=3600 \
+TKL_HARNESS_DOCKER_LIMIT_BYTES=42949672960 \
/sandboxed-git/turnkey/tools/test-v19-appliance xoops \
--source /home/agent/.local/worktrees/turnkey-apps/xoops/wish-xoops-v19-trixie
```
## Acceptance result
-Pending the first exact Wave 2 run on a clean behavioral commit.
+PASS on behavioral commit `ae080fc406bc174c6287bca7ad4abfaee93bb420`
+in run `20260826t064349z-6297-24697`.
+
+- Source commit archive SHA256 and transported archive SHA256:
+ `445201bfd8129c0f674ad102dd8fe064f07857449c74f218d4703a847186a6b2`
+- Input tree SHA256:
+ `76d6fa85dfd9edbb83ad637e8b5ac286a2228412e31726b07448a2b19202cb4e`
+- Build tree SHA256:
+ `346429b9542777e0101af3d5e8e26440080cc27830d5bd51e4fc42990aa7238e`
+- Harness commit: `54ca2cff6b98a034089d186b249835b58fc467da`
+- Harness tree SHA256:
+ `6b39d295251eaa5b17dcd0fe1b691b31a987b861e2d883c25c1ec1759d66a982`
+- Configured root: `root.patched-overlayfs-fallback`. The complete configured
+ root passed the required postconditions; the immediate `root.sandbox`
+ attempt failed only with the accepted overlayfs `EINVAL` boundary.
+- Runtime: firstboot and multi-user health passed, followed by HTTPS admin
+ login, custom-block create and public read, MariaDB persistence, Adminer,
+ credential, and updater checks.
+- Cleanup: PASS. The run removed its disposable build root, image, and
+ container.
+- Retained evidence:
+ `/home/agent/.local/state/turnkey-v19-harness-wave2/runs/xoops/20260826t064349z-6297-24697/`.
+
+The Docker runtime does not exercise installer, kernel, or hardware behavior.
+The Docker kernel rejects the appliance hostname firstboot call and
+`systemd-sysctl`; the acceptance script checks application services directly.
## Update contract
@@ -61,8 +88,16 @@ steps in the browser, as required by XOOPS release documentation.
## Loop accounting
-Product loops begin at zero. Only a reproduced main-flow appliance defect or a
-HIGH issue reachable in a normal supported deployment consumes the three-loop
-ceiling. Source checks, test corrections, builder queue failures, retained
-evidence updates, and the known shared cleanup dependency failure consume zero
-product loops.
+The lane used all three product loops:
+
+1. Removed the premature unconfigured `mainfile.php` that prevented the
+ official installer from creating the schema.
+2. Placed the seeded and acceptance blocks in the left zone rendered by the
+ pinned theme.
+3. Recovered Apache after firstboot certificate rotation overlapped its
+ initial startup.
+
+The guarded version-probe bootstrap and missing test database declaration were
+assertion corrections and consumed no product loop. Source identity checks,
+builder and infrastructure boundaries, unchanged diagnostics, and this
+evidence-only update also consumed no product loop.
From 0e1801f327b13ceeffeffa153859fd2e872b02d0 Mon Sep 17 00:00:00 2001
From: Liraz Siri
Date: Wed, 26 Aug 2026 07:04:42 +0000
Subject: [PATCH 8/9] Suppress XOOPS installer response bodies
The web installer echoed generated database configuration into retained build logs even though shell tracing was disabled around credential creation. Send installer response bodies to /dev/null while retaining curl's fail-on-HTTP behavior and shell errexit propagation.
Verified with bash syntax checking, diff validation, and focused installer-call counts before the exact Wave 2 rerun.
---
conf.d/main | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/conf.d/main b/conf.d/main
index fe0275a..5aded27 100755
--- a/conf.d/main
+++ b/conf.d/main
@@ -59,7 +59,7 @@ XLIB=${XOOPS//\//%2F}%2Flib
URL=http://127.0.0.1/install
COOKIE=/tmp/xoops-install-cookie
-CURL=(curl -fsS -c "$COOKIE" -b "$COOKIE")
+CURL=(curl -fsS -o /dev/null -c "$COOKIE" -b "$COOKIE")
"${CURL[@]}" "$URL/index.php" -H 'Cookie: xo_install_lang=english' --data 'lang=english'
"${CURL[@]}" "$URL/page_start.php"
From 058927cecadd8323f6e35b730c10c76918223586 Mon Sep 17 00:00:00 2001
From: Liraz Siri
Date: Wed, 26 Aug 2026 07:14:16 +0000
Subject: [PATCH 9/9] Bind XOOPS docs to sanitized evidence
Replace the superseded credential-bearing run references with the exact passing rerun at the executable correction commit. Record the retained checksums and count-only disclosure scan so reviewers can verify that installer responses no longer expose bootstrap database credentials.
Verified the documented run ID and source, input, and build hashes against report.txt; all retained checksums pass.
---
docs/v19.0-testing.md | 20 ++++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/docs/v19.0-testing.md b/docs/v19.0-testing.md
index 919238d..2aaa81b 100644
--- a/docs/v19.0-testing.md
+++ b/docs/v19.0-testing.md
@@ -4,14 +4,14 @@
| README or group criterion | Implementation boundary | Command and expected result | Actual and retained evidence |
| --- | --- | --- | --- |
-| Trixie appliance | The v19 LAMP base supplies Debian PHP, Apache, MariaDB, Adminer, Postfix, and Webmin. No active Bookworm source remains. | The exact Wave 2 build must report `RELEASE=debian/trixie`, install `turnkey-xoops-19.0`, and complete normal init. | Run `20260826t064349z-6297-24697` built the Trixie root, installed the release package, reached multi-user with inithooks complete, and passed cleanup. Unchanged Core services map to the retained Core 19 baseline. |
+| Trixie appliance | The v19 LAMP base supplies Debian PHP, Apache, MariaDB, Adminer, Postfix, and Webmin. No active Bookworm source remains. | The exact Wave 2 build must report `RELEASE=debian/trixie`, install `turnkey-xoops-19.0`, and complete normal init. | Run `20260826t070502z-6411-28610` built the Trixie root, installed the release package, reached multi-user with inithooks complete, and passed cleanup. Unchanged Core services map to the retained Core 19 baseline. |
| Official maintained XOOPS | Debian Trixie has no XOOPS package. XOOPS Core 2.7.3 is fetched from the official repository by exact release commit and archive SHA256. The official security policy supports the latest release and provides a private reporting address. | `tests/v19.sh` compares the installed source record, runtime version, release commit, archive digest, and stable channel. | PASS: release 2.7.3, runtime `XOOPS 2.7.3-RC1`, commit `00fff34c11fd4bee606e734d3bccf52d36fe0fda`, archive SHA256 `b49d935116352434a60e5dae93efce356d9e5f360ad92bc72dcc69d7ab82b8cb`, and the official stable channel agreed. |
| XOOPS administrator credentials | Firstboot sets the documented `admin` password and email. PHP creates the current `PASSWORD_DEFAULT` hash without exposing the password in the process arguments. | Log in over HTTPS with `TKL_TEST_APP_PASS`, open `/admin.php`, require `Control Panel`, and verify the stored password uses a modern hash. | PASS: exact-run login reached the control panel and the MariaDB record contained a current dollar-prefixed PHP password hash. |
| Content create and read | The XOOPS block administration flow creates a visible custom HTML block. | Authenticate, fetch a live CSRF token, POST the block form, read the title and body on the public HTTPS home page, and verify the same row in MariaDB. | PASS: the authenticated form created the fixture, the public home page rendered its title and body, and direct SQL returned the same values. |
| Database | XOOPS uses a generated MariaDB account whose password is rotated at firstboot and retained only in the protected external data configuration. | The authenticated web flow must read and write through XOOPS, the direct SQL check must read the created block, and `secure.php` must be `root:www-data` mode `0440`. | PASS: firstboot rotation completed, the web and direct SQL persistence checks passed, and the protected credential file had the required owner and mode. |
| HTTPS and Adminer | The appliance Apache virtual host serves XOOPS on ports 80 and 443. The inherited LAMP Adminer endpoint remains on HTTPS port 12322. | Request the XOOPS home and admin flows at `https://127.0.0.1` and require `Adminer` at `https://127.0.0.1:12322/`. | PASS: Apache remained active after firstboot certificate rotation, all XOOPS flows used HTTPS, and port 12322 returned Adminer. |
| Supervised updater | `turnkey-xoops-update` reads the official non-prerelease 2.7 release, resolves its official tag commit, backs up application data on apply, preserves configuration and uploads, and stages the upstream web upgrader. | Run `turnkey-xoops-update --check` and `--apply --dry-run`; require the same eligible version, tag, commit, official channel, and `/upgrade/` path. | PASS: both commands selected official tag `v2.7.3` at commit `00fff34c11fd4bee606e734d3bccf52d36fe0fda`; the dry run verified the tag and reported `/upgrade/`. |
-| Postfix, Webmin, SSH, and root credentials | These README claims are inherited unchanged from the v19 Core and LAMP baselines. XOOPS adds no overlay or firstboot behavior for them. | Cite the retained Core 19 baseline and require the appliance runtime health gate. | Run `20260826t064349z-6297-24697` reached the shared runtime health gate. The service behavior remains covered by the retained Core 19 baseline. |
+| Postfix, Webmin, SSH, and root credentials | These README claims are inherited unchanged from the v19 Core and LAMP baselines. XOOPS adds no overlay or firstboot behavior for them. | Cite the retained Core 19 baseline and require the appliance runtime health gate. | Run `20260826t070502z-6411-28610` reached the shared runtime health gate. The service behavior remains covered by the retained Core 19 baseline. |
## Source decision
@@ -37,15 +37,15 @@ TKL_HARNESS_DOCKER_LIMIT_BYTES=42949672960 \
## Acceptance result
-PASS on behavioral commit `ae080fc406bc174c6287bca7ad4abfaee93bb420`
-in run `20260826t064349z-6297-24697`.
+PASS on behavioral commit `0e1801f327b13ceeffeffa153859fd2e872b02d0`
+in run `20260826t070502z-6411-28610`.
- Source commit archive SHA256 and transported archive SHA256:
- `445201bfd8129c0f674ad102dd8fe064f07857449c74f218d4703a847186a6b2`
+ `539925bed5bac3d086b375d3a12004aa95bac74dfd4a9a45ddd3bc5920b3a62a`
- Input tree SHA256:
- `76d6fa85dfd9edbb83ad637e8b5ac286a2228412e31726b07448a2b19202cb4e`
+ `1504d53e5f6fe19c64f22770b4acbb1f709fadfa603958b67224048a96d20be1`
- Build tree SHA256:
- `346429b9542777e0101af3d5e8e26440080cc27830d5bd51e4fc42990aa7238e`
+ `ad3b0f5f2ccef61b2302a05956b0ab555f0c6b3296b2588cb8b7f8f248f0806c`
- Harness commit: `54ca2cff6b98a034089d186b249835b58fc467da`
- Harness tree SHA256:
`6b39d295251eaa5b17dcd0fe1b691b31a987b861e2d883c25c1ec1759d66a982`
@@ -57,8 +57,12 @@ in run `20260826t064349z-6297-24697`.
credential, and updater checks.
- Cleanup: PASS. The run removed its disposable build root, image, and
container.
+- Evidence sanitation: all retained checksums passed. A count-only scan of
+ the retained text and compressed build logs found zero raw
+ `XOOPS_DB_PASS` values, bootstrap database-password assignments, installer
+ password fields, installer response HTML, or installer curl xtrace lines.
- Retained evidence:
- `/home/agent/.local/state/turnkey-v19-harness-wave2/runs/xoops/20260826t064349z-6297-24697/`.
+ `/home/agent/.local/state/turnkey-v19-harness-wave2/runs/xoops/20260826t070502z-6411-28610/`.
The Docker runtime does not exercise installer, kernel, or hardware behavior.
The Docker kernel rejects the appliance hostname firstboot call and