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
12 changes: 6 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ and on top of that:

window.location = "/cp/"

- All components installed from package management.
- Using OpenJDK Java runtime.
- Tomcat 10.1 and all components installed from package management.
- Using the OpenJDK 21 Java runtime from Debian.
- Deployed web applications in /var/lib/tomcat10/webapps.
- TurnKey web control panel in /var/lib/tomcat10/webapps/cp.
- JSP console output sent to syslog (/var/log/syslog).
- JSP console output available through the systemd journal.
- Created Tomcat admin/manager roles and admin user.
- Use Apache2 Jk loadbalancer connector (performance).
- JkMounts for admin, manager, host-manager applications
Expand All @@ -36,16 +36,16 @@ and on top of that:
- Removed Tomcat HTTP connector listener (security).
- Set system wide Tomcat and Java environment variables.

- Includes MySQL.
- Includes MariaDB, a MySQL-compatible database server.
- SSL support out of the box.
- Includes Webmin module for configuring Apache2 and MySQL.
- Includes Webmin modules for configuring Apache2 and MariaDB.

See the `Tomcat on Apache docs`_ for further details.

Credentials *(passwords set at first boot)*
-------------------------------------------

- Webmin, SSH, MySQL: username **root**
- Webmin, SSH, MariaDB: username **root**
- Tomcat administration applications: username **admin**


Expand Down
13 changes: 13 additions & 0 deletions changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
turnkey-tomcat-apache-19.0 (1) turnkey; urgency=low

* Update the appliance for Debian 13 Trixie packages, including Tomcat
10.1, Apache 2.4 and OpenJDK 21.

* Preserve the localhost AJP connector, Apache mod_jk integration, firstboot
manager credentials and documented control panel deployment.

* Add focused runtime acceptance for Apache-to-Tomcat requests, manager
authentication, JSP deployment, MariaDB, Webmin and APT updates.

-- TurnKey Linux Developers <admin@turnkeylinux.org> Mon, 24 Aug 2026 00:00:00 +0000

turnkey-tomcat-apache-18.0 (1) turnkey; urgency=low

* Updated all relevant Debian packages to Bookworm/12 versions; including
Expand Down
1 change: 1 addition & 0 deletions conf.d/main
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ done

sed -i "s|HOSTNAME_DESC|Tomcat Apache|g" $WEBROOT/index.jsp
sed -i "s|HOSTNAME|tomcat-apache|g" $WEBROOT/index.jsp
sed -i "s|tomcat-9.0-doc|tomcat-10.1-doc|g" $WEBROOT/index.jsp
72 changes: 72 additions & 0 deletions docs/v19.0-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Tomcat-Apache 19.0 testing

## Scope and source decision

Tomcat-Apache 19.0 uses Debian 13 Trixie packages for Tomcat 10.1,
Apache 2.4, mod_jk, OpenJDK 21 and MariaDB. Trixie supplies the complete
application stack, so no upstream repository or downloaded release artifact
is needed. Webmin modules remain maintained through the signed TurnKey Trixie
APT repository inherited from Core.

The appliance preserves the established integration: Tomcat listens only on
the localhost AJP connector, Apache serves ports 80 and 443, and mod_jk maps
the control panel, documentation and manager applications to Tomcat.

## Acceptance command

```sh
/sandboxed-git/turnkey/tools/test-v19-appliance tomcat-apache \
--source /home/agent/.local/worktrees/turnkey-apps/tomcat-apache/wish-tomcat-apache-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`. The accepted
run identity and retained report will be recorded after exact acceptance.

## README crosswalk

| README contract | Focused check | Required result | Evidence |
| --- | --- | --- | --- |
| Tomcat 10.1, Apache, mod_jk and OpenJDK 21 come from package management | Query installed versions, binary ownership and APT candidates | Debian-owned binaries retain eligible signed Trixie candidates | `tests/v19.sh` |
| Apache fronts Tomcat through the localhost AJP connector | Validate both services, mod_jk, worker and site configuration, Tomcat XML and sockets, then request the control panel over Apache HTTPS | Apache serves the Tomcat-generated control panel; AJP listens on `127.0.0.1:8009`; no Tomcat HTTP connector listens on 8080 | `tests/v19.sh` |
| The control panel is deployed under `/var/lib/tomcat10/webapps/cp` | Inspect the deployed context, request it, create a temporary JSP in that documented deployment path and request it through Apache | The branded panel and live JSP response are returned through mod_jk | `tests/v19.sh` |
| Firstboot creates the `admin` manager account | Reject anonymous manager access, compare the configured password and roles, then use the HTML and text manager endpoints | Anonymous access receives 401; authenticated manager requests succeed | `tests/v19.sh` |
| MariaDB is included | Verify service supervision and execute a create, insert, select and drop roundtrip | The selected value is returned from the temporary database | `tests/v19.sh` |
| Webmin provides documented Apache and MariaDB administration | Verify both Webmin packages and request Webmin HTTPS | Both modules are installed and Webmin responds | `tests/v19.sh` |
| SSH and common system administration are inherited from Core | Cite the accepted Core 19 baseline | Core supplies the inherited root, SSH and base management behavior | Core run `20260824t010251z-1634-32241`, source `24c82ee3540ce545422742b0e28ba6b687c53ec2`, verdict `PASS` |
| The stack updates through package management | Refresh metadata and inspect candidates without changing installed versions | Signed Trixie metadata is accepted and no Bookworm source remains | `tests/v19.sh` |

## Updater check

`tests/v19.sh` runs `apt-get update`, confirms eligible Trixie candidates for
Tomcat, Apache, mod_jk, OpenJDK, MariaDB and the Webmin modules, and verifies
the installed runtime versions remain unchanged. Normal supervised updates
use APT, followed by service restarts when the package transaction requires
them:

```sh
apt-get update
apt-get install --only-upgrade tomcat10 apache2 libapache2-mod-jk \
openjdk-21-jre-headless mariadb-server webmin-apache webmin-mysql
systemctl restart tomcat10 apache2
```

## Known limitations

Docker runtime validation does not exercise the installer, kernel, bootloader
or physical hardware. Tomcat-Apache adds no appliance-specific behavior at
those boundaries, so the accepted Core 19 baseline supplies inherited
evidence.

The practical deployment check writes a temporary JSP into the documented
control-panel context and requests it through Apache and mod_jk. It avoids an
extra VM or second application server while exercising the appliance-specific
deployment and request path.

## Deferred issues

- The shared Tomcat environment still exports the legacy `MaxPermSize` JVM
option. The packaged systemd service does not consume that environment
value, and normal service startup is covered by acceptance. Removing the
shared option belongs in the common Tomcat integration rather than this
appliance lane.
4 changes: 2 additions & 2 deletions plan/main
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

ant

openjdk-17-jdk-headless
openjdk-17-jre-headless
openjdk-21-jdk-headless
openjdk-21-jre-headless

default-mysql-server
libmariadb-java
Expand Down
191 changes: 191 additions & 0 deletions tests/v19.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
#!/bin/bash
set -Eeuo pipefail
umask 077

result=${TKL_TEST_RESULT:?TKL_TEST_RESULT is required}
password=${TKL_TEST_APP_PASS:?TKL_TEST_APP_PASS is required}
db_password=${TKL_TEST_DB_PASS:?TKL_TEST_DB_PASS is required}
response=/tmp/tkl-tomcat-apache-response.$$
headers=/tmp/tkl-tomcat-apache-headers.$$
apache_modules=/tmp/tkl-tomcat-apache-modules.$$
policy=/tmp/tkl-tomcat-apache-policy.$$
probe=/var/lib/tomcat10/webapps/cp/tkl-v19-probe.jsp
database=tkl_tomcat_apache_v19_acceptance
database_created=false

report_error() {
printf 'test_failure line=%s status=%s command=%q\n' "$1" "$2" "$3" >&2
exit "$2"
}
trap 'report_error "$LINENO" "$?" "$BASH_COMMAND"' ERR

cleanup() {
rm -f -- "$response" "$headers" "$apache_modules" "$policy" "$probe"
if $database_created; then
mariadb --user=root --password="$db_password" \
--execute "DROP DATABASE IF EXISTS $database" || true
fi
}
trap cleanup EXIT

systemctl --quiet is-active apache2.service tomcat10.service mariadb.service \
multi-user.target
systemctl --quiet is-enabled apache2.service tomcat10.service mariadb.service
test "$(systemctl show --property=User --value tomcat10.service)" = tomcat
apache2ctl configtest
apache2ctl -M >"$apache_modules"
grep -Fq ' jk_module ' "$apache_modules"
test -L /etc/apache2/sites-enabled/jktomcat.conf
test ! -e /etc/apache2/sites-enabled/000-default.conf

tomcat_package=$(dpkg-query -W -f='${Version}' tomcat10)
tomcat_admin_package=$(dpkg-query -W -f='${Version}' tomcat10-admin)
apache_package=$(dpkg-query -W -f='${Version}' apache2)
jk_package=$(dpkg-query -W -f='${Version}' libapache2-mod-jk)
java_package=$(dpkg-query -W -f='${Version}' openjdk-21-jre-headless)
mariadb_package=$(dpkg-query -W -f='${Version}' mariadb-server)
java_version=$(java -version 2>&1 | head -n 1)
tomcat_version=$(/usr/share/tomcat10/bin/version.sh 2>&1 | \
awk '/^Server number:/ {
sub(/^[^:]+:[[:space:]]*/, "")
print
exit
}')

grep -q '^10\.1\.' <<<"$tomcat_version"
grep -q 'version "21\.' <<<"$java_version"
java_binary=$(readlink -f "$(command -v java)")
dpkg-query -S /usr/share/tomcat10/bin/catalina.sh "$java_binary" \
/usr/lib/apache2/modules/mod_jk.so >/dev/null
dpkg-query -W turnkey-tomcat-apache-19.0 webmin-apache webmin-mysql \
>/dev/null

test -d /var/lib/tomcat10/webapps/cp
test ! -d /var/lib/tomcat10/webapps/ROOT
python3 - <<'PYTHON'
import os
import xml.etree.ElementTree as ET

contexts = {
"manager": "/usr/share/tomcat10-admin/manager",
"host-manager": "/usr/share/tomcat10-admin/host-manager",
"docs": "/usr/share/tomcat10-docs/docs",
}
for name, doc_base in contexts.items():
descriptor = f"/etc/tomcat10/Catalina/localhost/{name}.xml"
assert ET.parse(descriptor).getroot().get("docBase") == doc_base
assert os.path.isdir(doc_base)
PYTHON
grep -q 'CATALINA_HOME="/usr/share/tomcat10"' /etc/environment
grep -q 'JAVA_HOME="/usr/lib/jvm/java-21-openjdk-amd64"' /etc/environment
grep -q '^JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64' \
/etc/default/tomcat10

python3 - /etc/tomcat10/server.xml <<'PYTHON'
import sys
import xml.etree.ElementTree as ET

root = ET.parse(sys.argv[1]).getroot()
connectors = root.findall("./Service/Connector")
assert len(connectors) == 1
connector = connectors[0]
assert connector.get("protocol") == "AJP/1.3"
assert connector.get("address") == "127.0.0.1"
assert connector.get("port") == "8009"
assert connector.get("secretRequired") == "false"
PYTHON

listeners=$(ss -ltnH)
! grep -Eq ':8080[[:space:]]' <<<"$listeners"
grep -Fq 'worker.ajp13_worker.host=127.0.0.1' \
/etc/libapache2-mod-jk/workers.properties
grep -Fq 'worker.ajp13_worker.port=8009' \
/etc/libapache2-mod-jk/workers.properties
grep -Fq 'Include /etc/tomcat10/mod_jk.conf' \
/etc/apache2/sites-available/jktomcat.conf
grep -Fq 'JkMount /cp/*' /etc/tomcat10/mod_jk.conf
grep -Fq 'JkMount /manager/*' /etc/tomcat10/mod_jk.conf

curl --fail --silent --show-error http://127.0.0.1/ >"$response"
grep -Fq 'window.location = "/cp"' "$response"
curl --insecure --fail --silent --show-error --dump-header "$headers" \
https://127.0.0.1/cp/ >"$response"
grep -qi '^Server: Apache' "$headers"
grep -q '<title>TurnKey Tomcat Apache</title>' "$response"
grep -q 'href="/manager/html"' "$response"
grep -q 'href="/host-manager/html"' "$response"

anonymous_status=$(curl --insecure --silent --output /dev/null \
--write-out '%{http_code}' https://127.0.0.1/manager/html)
test "$anonymous_status" = 401
curl --insecure --fail --silent --show-error --user "admin:$password" \
https://127.0.0.1/manager/html >"$response"
grep -q 'Tomcat Web Application Manager' "$response"
curl --insecure --fail --silent --show-error --user "admin:$password" \
https://127.0.0.1/manager/text/serverinfo >"$response"
grep -Fxq 'OK - Server info' "$response"
grep -q '^Tomcat Version: \[Apache Tomcat/10\.1\.' "$response"
curl --insecure --fail --silent --show-error --user "admin:$password" \
https://127.0.0.1/manager/text/list >"$response"
grep -Eq '^/cp:running:' "$response"
grep -Eq '^/docs:running:' "$response"

python3 - /etc/tomcat10/tomcat-users.xml "$password" <<'PYTHON'
import sys
import xml.etree.ElementTree as ET

root = ET.parse(sys.argv[1]).getroot()
admin = next(user for user in root.findall("user")
if user.get("username") == "admin")
assert admin.get("password") == sys.argv[2]
roles = set(admin.get("roles", "").split(","))
assert {"admin-gui", "admin-script", "manager-gui", "manager-script"} <= roles
PYTHON

cat >"$probe" <<'EOF'
<%@ page contentType="text/plain" %>turnkey-tomcat-apache-v19-jsp-ok
EOF
chown tomcat:tomcat "$probe"
curl --retry 5 --retry-delay 1 --insecure --fail --silent --show-error \
https://127.0.0.1/cp/tkl-v19-probe.jsp >"$response"
grep -Fxq 'turnkey-tomcat-apache-v19-jsp-ok' "$response"
rm -f -- "$probe"

curl --insecure --fail --silent --show-error --head \
https://127.0.0.1:12321/ >/dev/null
mariadb --user=root --password="$db_password" \
--execute "CREATE DATABASE $database"
database_created=true
mariadb --user=root --password="$db_password" "$database" --execute \
'CREATE TABLE probe (value VARCHAR(32)); INSERT INTO probe VALUES ("database-ok")'
mariadb --user=root --password="$db_password" --batch --skip-column-names \
"$database" \
--execute 'SELECT value FROM probe' | grep -Fxq 'database-ok'
mariadb --user=root --password="$db_password" \
--execute "DROP DATABASE $database"
database_created=false

before="$tomcat_package|$tomcat_admin_package|$apache_package|$jk_package|$java_package|$mariadb_package"
apt-get update >/dev/null
for package in tomcat10 tomcat10-admin apache2 libapache2-mod-jk \
openjdk-21-jre-headless mariadb-server webmin-apache webmin-mysql; do
apt-cache policy "$package" >"$policy"
candidate=$(awk '/Candidate:/ {print $2}' "$policy")
test -n "$candidate"
test "$candidate" != '(none)'
grep -Eq 'trixie|deb13' "$policy"
done
after="$(dpkg-query -W -f='${Version}' tomcat10)|$(dpkg-query -W -f='${Version}' tomcat10-admin)|$(dpkg-query -W -f='${Version}' apache2)|$(dpkg-query -W -f='${Version}' libapache2-mod-jk)|$(dpkg-query -W -f='${Version}' openjdk-21-jre-headless)|$(dpkg-query -W -f='${Version}' mariadb-server)"
test "$after" = "$before"
grep -Rqs '^Suites: trixie' /etc/apt/sources.list.d
! grep -Rqi bookworm /etc/apt/sources.list.d

cat >"$result" <<EOF
package_source=Debian 13 Trixie APT repositories for Tomcat 10.1, Apache, mod_jk, OpenJDK 21 and MariaDB; TurnKey Trixie APT for Webmin
installed_version=tomcat10 $tomcat_package (Tomcat $tomcat_version); apache2 $apache_package; libapache2-mod-jk $jk_package; openjdk-21-jre-headless $java_package ($java_version); mariadb-server $mariadb_package
runtime_checks=normal init; Apache, Tomcat and MariaDB supervision; localhost-only AJP and disabled HTTP connector; Apache HTTPS to Tomcat control panel; anonymous manager denial; authenticated manager HTML and text API; deployed JSP execution through mod_jk; MariaDB roundtrip; Webmin endpoint
updater_command=apt-get update; apt-cache policy tomcat10 tomcat10-admin apache2 libapache2-mod-jk openjdk-21-jre-headless mariadb-server webmin-apache webmin-mysql
updater_result=signed metadata refreshed; eligible Trixie candidates found; installed versions unchanged
updater_channel=Debian Trixie and TurnKey Trixie APT repositories
integrity_evidence=APT accepted signed repository metadata through configured Deb822 sources and keyrings; no Bookworm source remained
EOF