Skip to content
Open
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
23 changes: 9 additions & 14 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@ and on top of that:
default eases management (especially with tools such as the turnkey script
wrappers) but also has potential security implications.

**Security note**: Updates to Drupal may require supervision so
**Security note**: Updates to Drupal require supervision so
they **ARE NOT** configured to install automatically. See below for
updating Drupal. And/or see `Drupal 8 documentation`_
updating Drupal. See the `Drupal update documentation`_ for details.

- Includes drush_, drupal-console_ and composer_ for command line
administration, configuration and development. Also includes relevant
wrapper scripts; turnkey-drush_, turnkey-drupal_ & turnkey-composer_ (run
the relevant command, but as www-data user).
- Includes drush_ and composer_ for command line administration,
configuration and development. The turnkey-drush_ and turnkey-composer_
wrappers run these tools as the www-data user.

- Drupal security update alerts delivered to your inbox - requires
`Security Alerts`_ ('secalerts') be enabled on firstboot with a valid
Expand All @@ -35,7 +34,7 @@ and on top of that:
swap - a temporary swap file is acceptable).

- Bundled Drupal 10 modules and dependencies (installed to
/var/www/drupal10/web/modules - new default for Drupal10):
/var/www/drupal10/web/modules/contrib):

- `Field group`_: Allows fields to be grouped together.
- `Google analytics`_: Adds Google Analytics js tracking code to all
Expand Down Expand Up @@ -69,9 +68,7 @@ details, please consult the `Drupal Upgrade docs`_, we recommend using
`Composer to update from the commandline`_ (via turnkey-composer_)::

cd /var/www/drupal10
# update composer - not strictly necessary
composer self-update
turnkey-composer update drupal/core --with-dependencies
turnkey-composer update "drupal/core-*" --with-all-dependencies
turnkey-drush updatedb -y
turnkey-drush cr

Expand All @@ -97,12 +94,10 @@ Credentials *(passwords set at first boot)*
.. _TurnKey Core: https://www.turnkeylinux.org/core
.. _composer: https://getcomposer.org/
.. _drush: https://www.drush.org/
.. _drupal-console: https://drupalconsole.com/
.. _turnkey-drush: https://github.com/turnkeylinux-apps/drupal8/blob/master/overlay/usr/local/bin/turnkey-drush
.. _turnkey-drupal: https://github.com/turnkeylinux-apps/drupal8/blob/master/overlay/usr/local/bin/turnkey-drupal
.. _turnkey-drush: https://github.com/turnkeylinux-apps/drupal10/blob/master/overlay/usr/local/bin/turnkey-drush
.. _turnkey-composer: https://github.com/turnkeylinux/common/blob/master/overlays/composer/usr/local/bin/turnkey-composer
.. _Security Alerts: https://www.turnkeylinux.org/docs/automatic-security-alerts
.. _Drupal 8 documentation: https://www.drupal.org/docs/8/update
.. _Drupal update documentation: https://www.drupal.org/docs/updating-drupal
.. _Field group: https://www.drupal.org/project/field_group
.. _Google analytics: https://www.drupal.org/project/google_analytics
.. _Honeypot: https://www.drupal.org/project/honeypot
Expand Down
17 changes: 17 additions & 0 deletions changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
turnkey-drupal10-19.0 (1) turnkey; urgency=low

* Upgrade the base distribution to Debian 13/Trixie and PHP 8.4.

* Update Drupal to 10.6.15 with Drush 13.7.6 and compatible pinned versions
of the bundled contributed modules.

* Use Drupal core-recommended's Guzzle 7.15.3 dependency, which includes the
upstream fixes for CVE-2026-69245 and CVE-2026-69246.

* Create the welcome page through Drupal's entity API and update the
security advisory check for current Drush.

* See the TurnKey Core 19.0 changelog for changes common to all appliances.

-- TurnKey Linux release engineering <release@turnkeylinux.org> Tue, 25 Aug 2026 00:00:00 +0000

turnkey-drupal10-18.1 (1) turnkey; urgency=low

* v18.1 rebuild - includes latest Debian & TurnKey packages.
Expand Down
65 changes: 24 additions & 41 deletions conf.d/main
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ DB_PASS=$(mcookie)
ADMIN_NAME=admin
ADMIN_PASS=turnkey

SRC=/usr/local/src
WEBROOT=/var/www/drupal10

PHP_V=8.2
USER=www-data
PHP_V=8.4
WEB_USER=www-data

# set proxy settings
[ "$FAB_HTTP_PROXY" ] && export HTTP_PROXY=$FAB_HTTP_PROXY
Expand All @@ -27,43 +26,33 @@ export COMPOSER_MEMORY_LIMIT=-1 # temporarily disable composer memory limits dur

# install drupal10
mkdir -p $WEBROOT
chown $USER:$USER $WEBROOT
turnkey-composer create-project drupal/recommended-project:10.x $WEBROOT --no-dev --no-interaction
chown $WEB_USER:$WEB_USER $WEBROOT
turnkey-composer create-project drupal/recommended-project:10.6.15 $WEBROOT --no-dev --no-interaction

cd $WEBROOT

# install drush
cd $WEBROOT
turnkey-composer require drush/drush --no-interaction

CONF=$WEBROOT/web/sites/default/settings.php
cp $WEBROOT/web/sites/default/default.settings.php $CONF
LIBRARIES_PATH=$WEBROOT/libraries
mkdir -p $LIBRARIES_PATH/
chown -R $USER:$USER $WEBROOT
chown -R $WEB_USER:$WEB_USER $WEBROOT

service mysql start
mysqladmin create $DB_NAME
mysql --batch --execute "grant all privileges on $DB_NAME.* to $DB_USER@localhost identified by '$DB_PASS'; flush privileges;"
mysql --batch --execute "CREATE DATABASE $DB_NAME CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql --batch --execute "CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';"
mysql --batch --execute "GRANT ALL PRIVILEGES ON $DB_NAME.* TO '$DB_USER'@'localhost';"
mysql --batch --execute "set global transaction isolation level read committed;"

# download and install modules
MODULES="drupal/ctools \
drupal/field_group \
drupal/google_analytics \
drupal/imce \
drupal/pathauto \
drupal/token"

# drupal/honeypot
#
# currently doesn't work on drupal10, if you want this re-included upstream
# would appreciate help, read the thread
# https://www.drupal.org/project/honeypot/issues/2943526

for module in $MODULES; do
turnkey-composer require $module --no-interaction
done
# Install the maintained command-line tools and bundled modules at the versions
# validated for the v19 appliance.
turnkey-composer require --no-interaction --update-no-dev \
drush/drush:13.7.6 \
drupal/ctools:4.1.1 \
drupal/field_group:4.0.0 \
drupal/google_analytics:4.0.3 \
drupal/imce:3.1.5 \
drupal/pathauto:1.15.0 \
drupal/token:1.17.0

turnkey-drush site-install standard -y \
--account-name=$ADMIN_NAME \
Expand All @@ -83,8 +72,8 @@ turnkey-drush cache-rebuild
# configure apache
a2dissite 000-default
a2ensite drupal10
a2enmod rewrite
a2enmod rewrite

# insert "www.example.com" as default trusted_host (inithook will adjust)
echo "\$settings['trusted_host_patterns'] = ['^www\.example\.com\$'];" >> $CONF

Expand Down Expand Up @@ -120,17 +109,11 @@ DRUSH="/usr/local/bin/turnkey-drush"
EOF
chmod +x $CRON_DRUPAL

# create welcome page
MYSQL_BATCH="mysql --batch --execute"

# Create the welcome page through Drupal so entity revisions and field storage
# remain consistent with the installed schema.
WELCOME='<p>Please follow these steps to set up and start using your website:</p><ol><li><strong>Configure your website:</strong> To get started, log in as <b>admin</b> and visit the <a href=\"/admin\">administration section</a>, where you can customize and configure all aspects of your website.</li><li><strong>Start posting content:</strong> When ready, delete this welcome and <a href=\"/node/add\">create content</a> for your website.</li></ol><p>For more information, please refer to the <a href=\"https://www.turnkeylinux.org/drupal10\">TurnKey Drupal10 release notes</a>, <a href=\"/admin/help\">help section</a>, or the <a href=\"https://drupal.org/handbooks\">online Drupal handbooks</a>. You may also post at the <a href=\"https://drupal.org/forum\">Drupal forum</a>, or view the wide range of <a href=\"https://drupal.org/support\">other support options</a> available.</p>'

$MYSQL_BATCH "INSERT INTO $DB_NAME.node (nid, vid, type, uuid, langcode) VALUES (1,1,'page','dbab9403-2ebd-4fb9-8a00-e764ce980ecd','en');"
$MYSQL_BATCH "INSERT INTO $DB_NAME.node__body (bundle, deleted, entity_id, revision_id, langcode, delta, body_value, body_summary, body_format) VALUES ('page',0,1,1,'en',0,'$WELCOME','$WELCOME','basic_html');"
$MYSQL_BATCH "INSERT INTO $DB_NAME.node_field_data (nid, vid, type, langcode, title, uid, status, created, changed, promote, sticky, revision_translation_affected, default_langcode) VALUES (1,1,'page','en','Welcome to TurnKey Drupal',1,1,1435831165,1435831165,1,0,1,1);"
$MYSQL_BATCH "INSERT INTO $DB_NAME.node_field_revision (nid, vid, langcode, title, uid, status, created, changed, promote, sticky, revision_translation_affected, default_langcode) VALUES (1,1,'en','Welcome to TurnKey Drupal',1,1,1435831165,1435831165,1,0,1,1);"
$MYSQL_BATCH "INSERT INTO $DB_NAME.node_revision (nid, vid, langcode, revision_timestamp, revision_uid, revision_log) VALUES (1,1,'en',1435831535,1,'');"
$MYSQL_BATCH "INSERT INTO $DB_NAME.node_revision__body (bundle, deleted, entity_id, revision_id, langcode, delta, body_value, body_summary, body_format) VALUES ('page',0,1,1,'en',0,'$WELCOME','$WELCOME','basic_html');"
WELCOME_B64=$(printf '%s' "$WELCOME" | base64 -w0)
turnkey-drush php:eval "\$node = \\Drupal\\node\\Entity\\Node::create(['type' => 'page', 'title' => 'Welcome to TurnKey Drupal', 'body' => ['value' => base64_decode('$WELCOME_B64'), 'format' => 'basic_html'], 'status' => 1, 'promote' => 1]); \$node->save();"

service mysql stop

Expand Down
95 changes: 95 additions & 0 deletions docs/v19.0-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Drupal 10 19.0 testing

## Scope and source decision

Debian 13 Trixie does not package the Drupal application. Drupal 10.6.15 and
the contributed modules therefore come from Drupal's official Composer
repository. Drush and transitive PHP dependencies come from Packagist. The
build requests exact application and module versions, and Composer records the
resolved distributions in `composer.lock`.
Drupal 10.6.15's `core-recommended` lock selects Guzzle 7.15.3, which includes
the fixes for CVE-2026-69245 and CVE-2026-69246 without overriding Drupal's
tested dependency set.

PHP 8.4, Apache, MariaDB, Postfix and Composer come from signed Debian Trixie
repositories. Drupal 10.6 supports PHP 8.4. The supported update path is a
supervised Composer update followed by Drupal database updates and a cache
rebuild, as documented in the README.

## Acceptance command

```sh
/sandboxed-git/turnkey/tools/test-v19-appliance drupal10 \
--source /home/agent/.local/worktrees/turnkey-apps/drupal10/wish-drupal10-v19-trixie
```

This 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 | Evidence |
| --- | --- | --- | --- |
| Drupal is served over TLS | Request the landing page through Apache | The welcome page is returned over HTTPS | `tests/v19.sh` |
| Firstboot configures the administrator | Log in through Drupal's real web form using the preseeded password | Drupal creates an authenticated administrator session | `tests/v19.sh` |
| Drupal supports normal content management | Create a Basic page through the authenticated web form, read it from MariaDB, restart Apache and MariaDB, then request it again | The page is visible through Drupal and persists across service restarts | `tests/v19.sh` |
| Drush and Composer support command-line administration | Query versions, run cron, validate the Composer lock and inspect the current security command | Drupal 10.6.15 and Drush 13.7.6 run successfully; Composer and cron pass | `tests/v19.sh` |
| The bundled contributed modules are installed | Query enabled modules and the IMCE installation path | CTools, Field Group, Google Analytics, Pathauto and Token are enabled; IMCE is installed | `tests/v19.sh` |
| Drupal security alerts use current tooling | Run the scheduled helper as its configured cron identity, verify the wrappers' effective application user and audit the lock file | Root starts the helper, the wrappers drop to the web user and Composer finds no known advisory | `tests/v19.sh` |
| Webmin, Adminer and local application mail are available | Request both management endpoints and inspect the loopback SMTP listener | Webmin and Adminer answer over HTTPS; Postfix listens locally | `tests/v19.sh` |
| Debian and Drupal components have maintained update paths | Refresh APT metadata and perform a non-mutating Composer core update resolution | Trixie candidates remain eligible; the Composer update resolves without changing the deployment | `tests/v19.sh` |
| Root SSH, backup and other base services are inherited from Core | Cite the unchanged Core layer | Core 19 baseline passes | Core run `20260824t010251z-1634-32241`, source `24c82ee3540ce545422742b0e28ba6b687c53ec2`, verdict `PASS` |

## Cheap compatibility preflight

A disposable `debian:trixie-slim` container installed Drupal 10.6.15 and the
exact selected module set on PHP 8.4.24. Composer's platform, lock validation
and security audit checks passed. The resolved Drupal dependency set retained
Guzzle 7.15.3. The full appliance run is responsible for the installed-site
and firstboot behavior.

The preflight ran these commands after installing Debian's Composer and PHP
packages:

```sh
composer create-project --no-dev --no-interaction \
drupal/recommended-project:10.6.15 /tmp/drupal
composer require --no-interaction --update-no-dev \
drush/drush:13.7.6 drupal/ctools:4.1.1 \
drupal/field_group:4.0.0 drupal/google_analytics:4.0.3 \
drupal/imce:3.1.5 drupal/pathauto:1.15.0 drupal/token:1.17.0
composer check-platform-reqs
composer validate --no-check-publish --no-interaction
composer install --dry-run --no-dev --no-interaction
composer audit --locked --no-interaction
composer update "drupal/core-*" --with-all-dependencies \
--dry-run --no-interaction
```

## Evidence status

The Drupal 10.6.15 candidate at source commit
`3c1fb0daf8267eb730bc59703457af57d45d140e` passed exact Docker run
`20260829t164241z-6730-30442`. It established the complete README crosswalk:
build and import, normal systemd boot, firstboot, administrator login, content
creation with MariaDB readback, service restart persistence, cron, bundled
modules, management endpoints, the scheduled Composer security audit through
its root cron entry and web-user wrapper, and both supported updater channels.
This candidate used three of six product fix loops. The report SHA-256 is
`bc286beada670bb799e2624dfb8efd9e2e2d7ef5cb698516b117de19e048bb72`, the
run log SHA-256 is
`35758cc8d0e74a1e721a59cc5c353df8d5c59294e41fe7543e3369edf3d0299e`, and
the `SHA256SUMS` manifest SHA-256 is
`b9de86de60a8f59002e8bfffab92f57e7157b59c4e386007401f8c4deac2581e`.
The harness reported `verdict=PASS` and verified cleanup.

## Deferred issues

The focused test confirms the local Postfix listener and Drupal mail
configuration but does not deliver mail through an external relay. External
mail delivery depends on site-specific relay credentials and is not required
to establish the appliance's main flow.

The Docker runtime does not repeat installer, kernel or bootloader checks.
Drupal adds no appliance-specific behavior at those boundaries, so the proven
Core 19 baseline supplies that inherited evidence.
2 changes: 1 addition & 1 deletion overlay/etc/cron.d/drush-mail-securityupdates
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0 0 * * * www-data [ -x /usr/local/sbin/drush-mail-securityupdates ] && /usr/local/sbin/drush-mail-securityupdates
0 0 * * * root [ -x /usr/local/sbin/drush-mail-securityupdates ] && /usr/local/sbin/drush-mail-securityupdates
18 changes: 13 additions & 5 deletions overlay/usr/lib/inithooks/bin/drupal10.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,18 @@ def main():
m = MySQL()
m.execute('UPDATE drupal10.users_field_data SET mail=%s WHERE name=\"admin\";', (email,))
m.execute('UPDATE drupal10.users_field_data SET init=%s WHERE name=\"admin\";', (email,))
subprocess.run(['turnkey-drush', '-y', 'config-set', 'contact.form.feedback', 'recipients', email])
subprocess.run(['turnkey-drush', '-y', 'config-set', 'update.settings', 'notification.emails.0', email])
subprocess.run(['turnkey-drush', '-y', 'config-set', 'system.site', 'mail', email])
subprocess.run(['turnkey-drush', 'user-password', 'admin', password])
subprocess.run([
'turnkey-drush', '-y', 'config-set', 'contact.form.feedback',
'recipients', email
], check=True)
subprocess.run([
'turnkey-drush', '-y', 'config-set', 'update.settings',
'notification.emails.0', email
], check=True)
subprocess.run([
'turnkey-drush', '-y', 'config-set', 'system.site', 'mail', email
], check=True)
subprocess.run(['turnkey-drush', 'user-password', 'admin', password], check=True)
conf = '/var/www/drupal10/web/sites/default/settings.php'
conf_tmp = f'{conf}.tmp'
shutil.move(conf, conf_tmp)
Expand All @@ -113,7 +121,7 @@ def main():
os.chown(conf, uid, gid)
os.chmod(conf, 0o444)
print('Data updated; clearing caches')
subprocess.run(['/etc/cron.hourly/drupal10'])
subprocess.run(['/etc/cron.hourly/drupal10'], check=True)

if __name__ == "__main__":
main()
2 changes: 1 addition & 1 deletion overlay/usr/lib/inithooks/firstboot.d/40drupal10
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/bash -e
# set drupal9 admin password and email
# set Drupal 10 admin password, email and domain

. /etc/default/inithooks

Expand Down
26 changes: 20 additions & 6 deletions overlay/usr/local/sbin/drush-mail-securityupdates
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
#!/bin/bash -e
#!/bin/bash
set -e

cd /var/www/drupal10
TMPFILE=$(mktemp)
trap "rm -f $TMPFILE" INT TERM EXIT
/usr/local/bin/drush pm-updatestatus --security-only > $TMPFILE
trap 'rm -f "$TMPFILE"' INT TERM EXIT

if grep -q 'SECURITY UPDATE' $TMPFILE; then
cat $TMPFILE | mail -s 'Drupal security updates available' root
fi
set +e
/usr/local/bin/turnkey-composer audit --locked --no-interaction \
--abandoned=report >"$TMPFILE" 2>&1
status=$?
set -e

case $status in
0)
;;
1)
mail -s 'Drupal security updates available' root <"$TMPFILE"
;;
*)
cat "$TMPFILE" >&2
exit "$status"
;;
esac
Loading