diff --git a/README.rst b/README.rst index 63b4a5b..6720ebb 100644 --- a/README.rst +++ b/README.rst @@ -14,10 +14,13 @@ and on top of that: - Installed from upstream source code to /var/www/e107 - **Security note**: Updates to e107 may require supervision so - they **ARE NOT** configured to install automatically. e107 provide - upgrade scripts as well as full installers. Please see `e107 - documentation`_ + Version 2.3.11 is installed from the official release archive and its + SHA-256 digest is verified during the build. + + **Security note**: e107 updates require administrator supervision and + are not installed automatically. Back up the appliance, then use the + matching v2.x upgrade archive and instructions published in the current + `e107 release notes`_. - SSL support out of the box. - `Adminer`_ administration frontend for MySQL (listening on port @@ -37,5 +40,5 @@ Credentials *(passwords set at first boot)* .. _e107: https://e107.org/ .. _TurnKey Core: https://www.turnkeylinux.org/core -.. _e107 documentation: https://e107.org/get-started +.. _e107 release notes: https://github.com/e107inc/e107/releases/latest .. _Adminer: https://www.adminer.org/ diff --git a/changelog b/changelog index 69ac2df..d4f9311 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,15 @@ +turnkey-e107-19.0 (1) turnkey; urgency=low + + * Install e107 2.3.11 from its official release archive with SHA-256 + verification. + + * Store the firstboot administrator password using PHP's current password + hashing API. + + * Update the appliance for Debian 13 (Trixie), PHP 8.4 and current MariaDB. + + -- TurnKey Linux release engineering Tue, 25 Aug 2026 00:00:00 +0000 + turnkey-e107-18.0 (1) turnkey; urgency=low * Install latest upstream version of e107: 2.3.3. @@ -283,4 +295,3 @@ turnkey-e107-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 fb70eee..ff6de34 100755 --- a/conf.d/downloads +++ b/conf.d/downloads @@ -1,12 +1,21 @@ -#!/bin/bash -ex +#!/bin/bash +set -Eeuxo pipefail dl() { - [ "$FAB_HTTP_PROXY" ] && PROXY="--proxy $FAB_HTTP_PROXY" - cd $2; curl -L -f -O $PROXY $1; cd - -} + local url=$1 + local destination=$2 + local -a proxy=() -VERSION="e107%20v2.3.3/e107_2.3.3_full.tar.gz" -URL="http://sourceforge.net/projects/e107/files/e107/$VERSION" + if [[ -n ${FAB_HTTP_PROXY:-} ]]; then + proxy=(--proxy "$FAB_HTTP_PROXY") + fi + curl --fail --location "${proxy[@]}" "$url" --output "$destination" +} -dl $URL /usr/local/src +VERSION=2.3.11 +ARCHIVE="e107_${VERSION}_full.tar.gz" +URL="https://github.com/e107inc/e107/releases/download/v${VERSION}/${ARCHIVE}" +SHA256=4e60eec29a061ed295a88ce67fcff05b42cc0a3a55b5a8fa84b65476f3e944bc +dl "$URL" "/usr/local/src/$ARCHIVE" +printf '%s %s\n' "$SHA256" "/usr/local/src/$ARCHIVE" | sha256sum --check - diff --git a/conf.d/main b/conf.d/main index dd91778..eb3329d 100755 --- a/conf.d/main +++ b/conf.d/main @@ -1,4 +1,5 @@ -#!/bin/sh -ex +#!/bin/bash +set -Eeuxo pipefail DB_NAME=e107 DB_USER=e107 @@ -12,46 +13,44 @@ SRC=/usr/local/src WEBROOT=/var/www/e107 # unpack and set permissions -mkdir -p $WEBROOT -tar -zxf $SRC/e107_*_full.tar.gz -C $WEBROOT -rm $SRC/e107_*_full.tar.gz -chown -R root:root $WEBROOT -chown -R www-data:www-data $WEBROOT/e107_media -chown -R www-data:www-data $WEBROOT/e107_system -chown -R www-data:www-data $WEBROOT/e107_themes -chown -R www-data:www-data $WEBROOT/e107_images -chown -R www-data:www-data $WEBROOT/e107_plugins -chown www-data:www-data $WEBROOT/e107_config.php +mkdir -p "$WEBROOT" +tar -zxf "$SRC/e107_2.3.11_full.tar.gz" -C "$WEBROOT" +rm "$SRC/e107_2.3.11_full.tar.gz" +chown -R root:root "$WEBROOT" +chown -R www-data:www-data "$WEBROOT/e107_media" +chown -R www-data:www-data "$WEBROOT/e107_system" +chown -R www-data:www-data "$WEBROOT/e107_themes" +chown -R www-data:www-data "$WEBROOT/e107_images" +chown -R www-data:www-data "$WEBROOT/e107_plugins" +chown www-data:www-data "$WEBROOT/e107_config.php" # disable allow_url_fopen with php-curl installed (as recommended in # admin/phpinfo.php) -sed -i "\|^allow_url_fopen|s|=.*|= Off|" /etc/php/?.?/apache2/php.ini +sed -i "\|^allow_url_fopen|s|=.*|= Off|" /etc/php/8.4/apache2/php.ini # configure apache a2dissite 000-default a2ensite e107 a2enmod rewrite +a2enmod ssl # start services -service mysql start +service mariadb start service apache2 start # setup the database -mysqladmin create $DB_NAME -mysql --batch --execute "grant all privileges on $DB_NAME.* to $DB_USER@localhost identified by '$DB_PASS'; flush privileges;" +mariadb --batch --execute "CREATE DATABASE $DB_NAME; CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS'; GRANT ALL PRIVILEGES ON $DB_NAME.* TO '$DB_USER'@'localhost';" # curl based install -EMAIL=$(echo $ADMIN_MAIL | sed s/@/%40/) +EMAIL=${ADMIN_MAIL/@/%40} do_curl() { URL="http://127.0.0.1/install.php" - CURL="curl -c /tmp/cookie -b /tmp/cookie" - #OUT=$($CURL $URL --data "$1") - - $CURL $URL --data "$1" | \ - grep previous_steps | \ - sed -e "s|.*value='\(.*\)'.*|\1|" + curl --fail --silent --show-error \ + --cookie /tmp/cookie --cookie-jar /tmp/cookie \ + "$URL" --data "${1:-}" | \ + sed -n "s|.*name='previous_steps' value='\([^']*\)'.*|\1|p" | head -n1 } PREV=$(do_curl) @@ -64,19 +63,19 @@ PREV=$(do_curl "previous_steps=$PREV&stage=6&u_name=$ADMIN_NAME&d_name=Administr PREV=$(do_curl "previous_steps=$PREV&stage=7&sitename=example&sitetheme=bootstrap3&install_plugins=1&generate_content=1&submit=Continue+%C2%BB") PREV=$(do_curl "previous_steps=$PREV&stage=8&submit=Continue+%C2%BB") -rm -f $WEBROOT/install.php +rm -f "$WEBROOT/install.php" rm -f /tmp/cookie # enable cron.d -chmod +x $WEBROOT/cron.php +chmod +x "$WEBROOT/cron.php" # secure sensitive files -chmod 640 $WEBROOT/e107_config.php +chmod 640 "$WEBROOT/e107_config.php" # enable defaults -mv $WEBROOT/e107.htaccess $WEBROOT/.htaccess -mv $WEBROOT/e107.robots.txt $WEBROOT/robots.txt +mv "$WEBROOT/e107.htaccess" "$WEBROOT/.htaccess" +mv "$WEBROOT/e107.robots.txt" "$WEBROOT/robots.txt" # stop services -service mysql stop +service mariadb stop service apache2 stop diff --git a/docs/v19.0-testing.md b/docs/v19.0-testing.md new file mode 100644 index 0000000..fc755b9 --- /dev/null +++ b/docs/v19.0-testing.md @@ -0,0 +1,57 @@ +# e107 19.0 testing + +## Source decision + +Debian 13 Trixie does not package e107. The appliance installs maintained +upstream release 2.3.11 from the official GitHub release archive. The build +pins and verifies SHA-256 digest +`4e60eec29a061ed295a88ce67fcff05b42cc0a3a55b5a8fa84b65476f3e944bc`. +PHP 8.4, MariaDB, Apache and the required PHP extensions come from signed +Debian Trixie repositories. + +Updates remain an administrator-supervised operation. The supported channel +is the official e107 releases page. Each release publishes full archives and +upgrade archives for existing v2.x installations together with its release +notes and SHA-256 asset digests. + +## Acceptance command + +```sh +/sandboxed-git/turnkey/tools/test-v19-appliance e107 \ + --source /home/agent/.local/worktrees/turnkey-apps/e107/wish-e107-v19-trixie +``` + +The command builds the Trixie root filesystem, imports it into Docker, boots +the normal systemd and firstboot path, then runs `tests/v19.sh`. + +## README crosswalk + +| README contract | Focused check | Required result | +| --- | --- | --- | +| e107 is installed from maintained upstream source | Read the runtime version and query the official latest-release endpoint | Runtime reports 2.3.11 and the official release channel responds | +| Firstboot sets the e107 administrator credentials | Log in through the real web form with the preseeded password and inspect its stored hash | An authenticated administrator page is returned and the password uses PHP's bcrypt format | +| e107 provides a working CMS over TLS | Create a news item through the authenticated, CSRF-protected administrator form, inspect its database row, and request its public detail page | The administrator action persists the expected public article and Apache serves its title and body over HTTPS | +| The scheduled task runs as the web user | Invoke the same PHP command installed in `/etc/cron.d/e107` | The e107 cron entry exits successfully | +| Adminer, Webmin and local mail support are available | Check the database, Webmin modules and HTTPS endpoint, and Postfix loopback listener | MariaDB works, Webmin answers and local SMTP listens | +| Trixie supplies maintained platform updates | Inspect active APT suites and installed PHP and MariaDB packages | Required packages are installed and no Bookworm source remains | +| SSH, backup and other base services are inherited from Core | Cite the unchanged Core layer | Core 19 acceptance remains the inherited evidence: run `20260824t010251z-1634-32241`, source `24c82ee3540ce545422742b0e28ba6b687c53ec2`, report SHA-256 `1a6c2d266b6a2898e98a073421e49212dd0237598861200a3cbd6e81d35a2936`, verdict `PASS` | + +## Evidence and limitations + +Exact run `20260829t162315z-2017-14126` at source commit `85700be` passed the +source preflight, full build, configured-root import, normal systemd boot, +firstboot, and all runtime tests. The tests verified administrator login, +authenticated and CSRF-protected news creation, MariaDB persistence, public +article rendering, bcrypt password storage, the e107 cron command, Webmin, +local Postfix, Trixie package sources, and the official e107 upgrade channel. +The installed versions were e107 2.3.11 and PHP 8.4.24. Product fix-loop usage +remained 1 of 6. + +The retained evidence can be verified with these SHA-256 digests: + +* report: `ce36d01a8e5d8fd0b533649e17ab0fd7abec58b1517389a336e39a3d1f543279` +* run log: `54f9bb36135ca2ffb502a6a6d194891ad29bd77c57140d477e8874a7d3023ab0` +* manifest: `d6141296e3504c13726b917b2cf82da304f432b755d07486e23b81146ffe9d27` + +The Docker runtime does not exercise installer, kernel, or hardware behavior. +Those shared image concerns remain covered by Core 19 release validation. diff --git a/overlay/usr/lib/inithooks/bin/e107.py b/overlay/usr/lib/inithooks/bin/e107.py index 9700243..492cdcf 100755 --- a/overlay/usr/lib/inithooks/bin/e107.py +++ b/overlay/usr/lib/inithooks/bin/e107.py @@ -9,9 +9,8 @@ import sys import getopt -import hashlib -from datetime import datetime -from time import mktime +import subprocess +import time from mysqlconf import MySQL from libinithooks import inithooks_cache @@ -58,14 +57,22 @@ def main(): inithooks_cache.write('APP_EMAIL', email) - hash = hashlib.md5(password.encode('utf8')).hexdigest() - timestamp = int(mktime(datetime.now().timetuple())) + password_hash = subprocess.run( + ["/usr/bin/php", "-r", + "echo password_hash(stream_get_contents(STDIN), PASSWORD_DEFAULT);"], + input=password, + text=True, + stdout=subprocess.PIPE, + check=True, + ).stdout + if not password_hash.startswith("$2y$"): + raise RuntimeError("e107 password hashing failed") + timestamp = int(time.time()) m = MySQL() - m.execute('UPDATE e107.e107_user SET user_password=%s WHERE user_loginname=\"admin\";', (hash,)) + m.execute('UPDATE e107.e107_user SET user_password=%s WHERE user_loginname=\"admin\";', (password_hash,)) m.execute('UPDATE e107.e107_user SET user_email=%s WHERE user_loginname=\"admin\";', (email,)) m.execute('UPDATE e107.e107_user SET user_pwchange=%s WHERE user_loginname=\"admin\";', (timestamp,)) if __name__ == "__main__": main() - diff --git a/tests/v19.sh b/tests/v19.sh new file mode 100755 index 0000000..c17e241 --- /dev/null +++ b/tests/v19.sh @@ -0,0 +1,141 @@ +#!/bin/bash +set -Eeuo pipefail +umask 077 + +result=${TKL_TEST_RESULT:?TKL_TEST_RESULT is required} +app_password=${TKL_TEST_APP_PASS:?TKL_TEST_APP_PASS is required} +cookie=/tmp/tkl-e107-cookie.$$ +page=/tmp/tkl-e107-page.$$ +content_title='TurnKey v19 acceptance article' +content_body='TurnKey e107 content create and read verified' +admin_create_url='https://localhost/e107_admin/newspost.php?mode=main&action=create' + +report_error() { + printf 'test_failure line=%s status=%s command=%q\n' \ + "$1" "$2" "$3" >&2 + exit "$2" +} + +cleanup() { + rm -f -- "$cookie" "$page" +} +trap 'report_error "$LINENO" "$?" "$BASH_COMMAND"' ERR +trap cleanup EXIT + +systemctl --quiet is-active apache2.service mariadb.service postfix.service +systemctl --quiet is-enabled apache2.service mariadb.service postfix.service +apache2ctl -t +apache2ctl -M 2>/dev/null | grep -F ' rewrite_module ' >/dev/null +apache2ctl -M 2>/dev/null | grep -F ' ssl_module ' >/dev/null + +curl --insecure --fail --silent --show-error https://localhost/ >"$page" +grep -Eqi 'e107|Welcome' "$page" + +curl --insecure --location --fail --silent --show-error \ + --cookie "$cookie" --cookie-jar "$cookie" \ + https://localhost/ \ + --data-urlencode 'username=admin' \ + --data-urlencode "userpass=$app_password" \ + --data-urlencode 'userlogin=Login' >"$page" +grep -Eqi 'logout|admin area|administrator' "$page" +test -s "$cookie" + +curl --insecure --location --fail --silent --show-error \ + --cookie "$cookie" --cookie-jar "$cookie" \ + "$admin_create_url" >"$page" +csrf_token=$(sed -n \ + 's/.*"$page" + +content_id=$(mariadb --batch --skip-column-names e107 --execute \ + "SELECT news_id FROM e107_news WHERE news_title='$content_title' ORDER BY news_id DESC LIMIT 1") +test -n "$content_id" +test "$(mariadb --batch --skip-column-names e107 --execute \ + "SELECT news_body FROM e107_news WHERE news_id=$content_id")" = "$content_body" +test "$(mariadb --batch --skip-column-names e107 --execute \ + "SELECT CONCAT(news_author, ':', news_class) FROM e107_news WHERE news_id=$content_id")" = '1:0' + +curl --insecure --fail --silent --show-error \ + "https://localhost/news.php?extend.$content_id" >"$page" +grep -Fq "$content_title" "$page" +grep -Fq "$content_body" "$page" + +test "$(mariadb --batch --skip-column-names e107 --execute \ + "SELECT COUNT(*) FROM e107_user WHERE user_loginname='admin' AND user_admin=1")" = 1 +password_hash=$(mariadb --batch --skip-column-names e107 --execute \ + "SELECT user_password FROM e107_user WHERE user_loginname='admin'") +[[ $password_hash == '$2y$'* ]] +test "$(mariadb --batch --skip-column-names e107 --execute 'SHOW TABLES' | wc -l)" -ge 30 + +test ! -e /var/www/e107/install.php +test "$(stat -c '%a' /var/www/e107/e107_config.php)" = 640 +runuser -u www-data -- /usr/bin/php /var/www/e107/cron.php >/dev/null + +dpkg-query -W php-cli php-curl php-gd php-mbstring php-xml mariadb-server \ + webmin-apache webmin-mysql >/dev/null +curl --insecure --fail --silent --show-error --head \ + https://127.0.0.1:12321/ >/dev/null +ss -ltn | grep -Eq '127\.0\.0\.1:25[[:space:]]' + +installed_version=$(sed -n \ + 's/^$e107info\['"'"'e107_version'"'"'\][[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \ + /var/www/e107/e107_admin/ver.php | head -n1) +test "$installed_version" = 2.3.11 + +curl --fail --silent --show-error \ + https://api.github.com/repos/e107inc/e107/releases/latest >"$page" +latest_tag=$(sed -n \ + 's/.*"tag_name": "\([^"]*\)".*/\1/p' "$page" | head -n1) +test -n "$latest_tag" +upgrade_asset="e107_v2.x_to_${latest_tag#v}_upgrade.tar.gz" +grep -Fq "\"name\": \"$upgrade_asset\"" "$page" +upgrade_digest=$(php -r ' + $release = json_decode(file_get_contents($argv[1]), true, 512, JSON_THROW_ON_ERROR); + foreach ($release["assets"] as $asset) { + if ($asset["name"] === $argv[2]) { + echo $asset["digest"] ?? ""; + exit; + } + } +' "$page" "$upgrade_asset") +[[ $upgrade_digest =~ ^sha256:[[:xdigit:]]{64}$ ]] + +grep -Rqs '^Suites: trixie' /etc/apt/sources.list.d +! grep -Rqi bookworm /etc/apt/sources.list.d + +cat >"$result" <