From 6bcad54d1620cce5fd1dbc011c83cf9eb69cb40d Mon Sep 17 00:00:00 2001 From: Liraz Siri Date: Mon, 24 Aug 2026 09:56:20 +0000 Subject: [PATCH 1/5] Port Redis to Debian Trixie Move Redis Server, its tools, Node.js, npm and Nginx to maintained Debian 13 packages. Install Redis Commander from its official npm registry channel so the shipped lock records integrity metadata and administrators retain a supported update command. Repair the firstboot all-interface selection and reload Redis Commander through a real node-account process instead of the unsupported subprocess user argument. Add focused normal-boot, authentication, persistence, management-interface and updater tests plus a README crosswalk. Verified shell and Python syntax, executable test modes, documentation character policy and git diff whitespace. A disposable Trixie fixture upgraded Redis Commander 0.8.0 to registry candidate 0.9.0 while preserving authenticated Redis data and management access. --- README.rst | 62 ++++++------ changelog | 19 ++++ conf.d/main | 35 +++++-- docs/v19.0-testing.md | 88 ++++++++++++++++ overlay/usr/lib/inithooks/bin/redis.py | 39 +++---- plan/main | 3 + tests/v19-upgrade.sh | 81 +++++++++++++++ tests/v19.sh | 134 +++++++++++++++++++++++++ 8 files changed, 407 insertions(+), 54 deletions(-) create mode 100644 docs/v19.0-testing.md create mode 100755 tests/v19-upgrade.sh create mode 100755 tests/v19.sh diff --git a/README.rst b/README.rst index 02c6db3..acdcdcd 100644 --- a/README.rst +++ b/README.rst @@ -1,51 +1,55 @@ Redis - Open Source, In-memory Data Structure Store =================================================== -`Redis`_ can be used as a database, cache or message broker. It supports data -structures such as strings, hashes, lists, sets, sorted sets -with range queries, bitmaps, hyperloglogs and geospatial indexes -with radius queries. Redis has built-in replication, Lua scripting, -LRU eviction, transactions and different levels of on-disk persistence, -and provides high availability via `Redis Sentinel`_ (requires install of -Debian `redis-sentinel`_ package) and automatic partitioning with -`Redis Cluster`_. +`Redis`_ is an in-memory database, cache and message broker. It supports +strings, hashes, lists, sets, sorted sets, streams and other data structures. +Redis also provides replication, scripting, transactions, on-disk persistence, +high availability through `Redis Sentinel`_ and partitioning through +`Redis Cluster`_. Sentinel requires the optional Debian `redis-sentinel`_ +package. This appliance includes all the standard features in `TurnKey Core`_, and on top of that: -- Redis configurations: +- Redis configuration: - - Installed from debian package repository (auto security updates). - - Includes web based management tool `Redis Commander`_. - - Complex Redis system password auto-generated on firstboot (security). - - Confconsole plugin provided to view Redis system password (convenience). + - Redis Server and its command-line tools come from Debian Trixie and use + normal APT security updates. + - First boot generates a strong Redis password. View it from Confconsole or + run ``turnkey-redis-pw get`` as root. + - First boot lets you keep Redis on localhost, bind all interfaces or enter + a local address. Remote deployments should restrict TCP port 6379 to + trusted clients with the firewall. + - Debian's default RDB persistence remains enabled. - **Security note**: Updates to `Redis Commander`_ may require supervision so - they **ARE NOT** configured to install automatically. See `Plone - documentation`_ for upgrading. + - Includes the `Redis Commander`_ browser interface at + ``https:///redis-commander/``. -- SSL support out of the box. +- HTTPS protects the landing page and Redis Commander management interface. - Postfix MTA (bound to localhost) to allow sending of email from web - applications (e.g., password recovery). + applications. -Supervised Manual Redis Commander Update ----------------------------------------- +Supervised Redis Commander update +--------------------------------- Always ensure that you have a current and tested backup before performing an -upgrade. Ideally also do a test upgrade proceedure on a development server, -before updating your production server.:: +upgrade. Test the update on a development server before updating production.:: - su - node -c "cd /opt/tklweb-cp && npm update" + su - node -c "cd /opt/tklweb-cp && npm install redis-commander@latest" + systemctl restart pm2-node + +The npm lock file records registry integrity hashes. Redis Server itself is +updated through APT. Credentials *(passwords set at first boot)* ------------------------------------------- - Webmin, SSH: username **root** -- Redis-commander: username **admin** +- Redis Commander: username **admin** -.. _Redis: https://redis.io/ -.. _Redis Sentinel: https://redis.io/topics/sentinel -.. _redis-sentinel: https://packages.debian.org/stretch/redis-sentinel -.. _Redis Cluster: https://redis.io/topics/cluster-tutorial +.. _Redis: https://redis.io/docs/latest/ +.. _Redis Sentinel: https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel/ +.. _redis-sentinel: https://packages.debian.org/trixie/redis-sentinel +.. _Redis Cluster: https://redis.io/docs/latest/operate/oss_and_stack/management/scaling/ .. _TurnKey Core: https://www.turnkeylinux.org/core -.. _Redis Commander: https://joeferner.github.io/redis-commander/ +.. _Redis Commander: https://github.com/joeferner/redis-commander diff --git a/changelog b/changelog index e420a5d..62a8663 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,22 @@ +turnkey-redis-19.0 (1) turnkey; urgency=low + + * Upgrade the base distribution to Debian 13/Trixie. + + * Update Redis to Debian's 8.0 series. + + * Use Debian's Node.js and npm packages for the management application + runtime. + + * Install Redis Commander from its maintained npm registry release with + dependency integrity metadata and a supported npm update path. + + * Preserve the selected Redis network binding and reliably reload the + first-boot Redis Commander credentials. + + * See the TurnKey Core 19.0 changelog for changes common to all appliances. + + -- TurnKey Linux release engineering Mon, 24 Aug 2026 00:00:00 +0000 + turnkey-redis-18.0 (1) turnkey; urgency=low * Update Redis-Commander (via NPM) to latest upstream release: v0.8.1. diff --git a/conf.d/main b/conf.d/main index c8e6255..ced7d35 100755 --- a/conf.d/main +++ b/conf.d/main @@ -1,15 +1,38 @@ #!/bin/bash -ex NODEUSER=node +NODEAPP=/opt/tklweb-cp -REPO="joeferner/redis-commander" -VERSION=$(gh_releases $REPO | sort -V | tail -1) -URL="git+https://github.com/$REPO.git" +# Keep npm registry access on the system-managed trust path. +grep -qxF 'cafile=/etc/ssl/certs/ca-certificates.crt' /home/$NODEUSER/.npmrc || \ + echo 'cafile=/etc/ssl/certs/ca-certificates.crt' >> /home/$NODEUSER/.npmrc +chown $NODEUSER:$NODEUSER /home/$NODEUSER/.npmrc -su -lc "[[ "$FAB_HTTP_PROXY" ]] && export http_proxy=$FAB_HTTP_PROXY; cd /opt/tklweb-cp && npm i $URL#$VERSION" $NODEUSER -su -lc "[[ "$FAB_HTTP_PROXY" ]] && export http_proxy=$FAB_HTTP_PROXY; cd /opt/tklweb-cp && pm2 start ecosystem.config.js && sleep 20 && pm2 save && pm2 kill" $NODEUSER +# Redis Commander is not packaged by Debian. Install the current release from +# its maintained npm channel and retain npm's integrity-bearing lock file. +VERSION=$(su -lc "npm view redis-commander version" $NODEUSER) +su -lc "[[ -n \"$FAB_HTTP_PROXY\" ]] && export http_proxy=\"$FAB_HTTP_PROXY\"; cd $NODEAPP && npm install --save-exact redis-commander@$VERSION" $NODEUSER +su -lc "cd $NODEAPP && pm2 start ecosystem.config.js && sleep 20 && pm2 save && pm2 kill" $NODEUSER -echo {} > /opt/tklweb-cp/node_modules/redis-commander/config/local-production.json +echo {} > $NODEAPP/node_modules/redis-commander/config/local-production.json rm /etc/nginx/sites-enabled/tkl-default ln -s /etc/nginx/sites-available/redis /etc/nginx/sites-enabled/redis + +# The inherited build profile uses n temporarily. Ship Debian's maintained +# Trixie Node.js and npm commands while retaining the npm-managed PM2 service. +rm -f /usr/local/bin/{n,node,npm,npx,corepack} +rm -rf /usr/local/n \ + /usr/local/src/n \ + /usr/local/include/node \ + /usr/local/lib/node_modules/npm \ + /usr/local/lib/node_modules/corepack \ + /usr/local/share/doc/node \ + /usr/local/share/man/man1/node.1 +rm -f /usr/local/etc/npmrc + +hash -r +dpkg-query -S "$(readlink -f "$(command -v node)")" | grep -q '^nodejs:' +dpkg-query -S "$(readlink -f "$(command -v npm)")" | grep -q '^npm:' + +rm -rf /tmp/npm* diff --git a/docs/v19.0-testing.md b/docs/v19.0-testing.md new file mode 100644 index 0000000..a90ac3d --- /dev/null +++ b/docs/v19.0-testing.md @@ -0,0 +1,88 @@ +# Redis 19.0 testing + +## Scope and source decision + +Redis 19.0 uses Debian 13 Trixie packages for Redis Server, Redis command-line +tools, Node.js, npm and Nginx. Redis Commander is not packaged by Debian, so it +comes from the project's maintained npm registry channel. npm records registry +integrity hashes in `/opt/tklweb-cp/package-lock.json` and provides the +documented `npm install redis-commander@latest` update path. + +The inherited Node.js build profile temporarily uses the `n` version manager +while assembling the management application. Product configuration removes +that runtime and version manager so the shipped `node` and `npm` commands are +owned by Debian packages. The `node` account uses the system CA bundle for npm +registry access. + +## Acceptance command + +```sh +/sandboxed-git/turnkey/tools/test-v19-appliance redis \ + --source /home/agent/.local/worktrees/turnkey-apps/redis/wish-redis-v19-trixie +``` + +The command builds a Trixie root filesystem, imports it into Docker, boots the +normal systemd and firstboot path, then runs `tests/v19.sh`. The retained +harness report records the exact source and harness revisions, archive and tree +hashes, installed versions, configured-root mode, runtime evidence, updater +evidence, cleanup result and verdict. + +## README crosswalk + +| README contract | Focused check | Required result | Evidence | +| --- | --- | --- | --- | +| Redis Server and tools come from Debian Trixie | Resolve dpkg versions, refresh APT and inspect candidates | Installed packages retain eligible signed Trixie candidates | `tests/v19.sh`; harness updater fields | +| First boot generates an authenticated Redis service | Retrieve the generated password, reject an unauthenticated PING and perform an authenticated PING | Unauthenticated access is denied and authenticated access returns `PONG` | `tests/v19.sh`; harness runtime fields | +| The selected all-interface binding and protected mode are applied | Inspect the live firstboot-generated Redis configuration | `bind 0.0.0.0`, `protected-mode yes` and the generated password are present | `tests/v19.sh` | +| Redis stores, retrieves, persists and deletes data | Set a disposable key, run synchronous `SAVE`, restart Redis, retrieve the value, delete it and prove it is absent | The complete database round trip passes across a service restart | `tests/v19.sh`; harness runtime fields | +| Redis Commander is available through authenticated HTTPS | Request the interface without credentials and with the firstboot administrator credential | Anonymous access returns 401 and the authenticated page identifies Redis Commander | `tests/v19.sh` | +| The landing page links Redis Commander and Webmin | Request the HTTPS landing page | Redis GUI and Webmin links are present | `tests/v19.sh` | +| PM2 supervises Redis Commander and the TurnKey control panel | Inspect PM2 JSON state after normal boot | Both processes are online with live PIDs | `tests/v19.sh` | +| Redis Commander has integrity and a maintained update path | Inspect the npm lock, query the registry candidate and exercise an older-to-current update in a disposable Trixie fixture | Registry integrity hashes are present and the interface works before and after update | `tests/v19.sh`; `tests/v19-upgrade.sh` | +| Postfix, Webmin and root SSH are inherited Core behavior | Cite the unchanged Core layer | Core 19 baseline passes | Core run `20260824t010251z-1634-32241`, source `24c82ee3540ce545422742b0e28ba6b687c53ec2`, verdict `PASS` | + +## Updater checks + +`tests/v19.sh` runs `apt-get update`, confirms eligible Trixie candidates for +`redis-server`, `redis-tools`, `nodejs`, `npm` and `nginx`, and verifies the +installed versions remain unchanged. It queries the current Redis Commander +candidate from npm without changing the accepted appliance. The documented +supervised update command is: + +```sh +su - node -c "cd /opt/tklweb-cp && npm install redis-commander@latest" +systemctl restart pm2-node +``` + +`tests/v19-upgrade.sh` is a disposable Trixie fixture. It installs Redis +Commander 0.8.0, verifies authenticated access to Redis and the management +page, updates through the official npm channel, restarts Commander, verifies +the page again and proves the Redis value survived. It also checks the current +version and lock-file integrity fields. + +The fixture was run with the application source and local Docker inspection CA +mounted read-only: + +```sh +docker run --rm --name tkl-redis-v19-upgrade \ + --mount type=bind,src=/home/agent/.local/worktrees/turnkey-apps/redis/wish-redis-v19-trixie,dst=/src,readonly \ + --mount type=bind,src=/usr/local/share/ca-certificates/microsandbox-ca.crt,dst=/usr/local/share/ca-certificates/microsandbox-ca.crt,readonly \ + debian:trixie-slim /src/tests/v19-upgrade.sh +``` + +It upgraded Redis Commander from 0.8.0 to 0.9.0, matched the 0.9.0 registry +candidate, served the authenticated interface before and after the update and +preserved the fixture Redis value. The initial run without the local Docker CA +failed at registry TLS verification before installation. A second assumption +check found that v0.8.1 was released only as a Git tag, not an npm version, so +the fixture uses the published 0.8.0 registry release as its baseline. + +## Known limitation + +Docker runtime validation does not exercise the installer, kernel, bootloader +or physical hardware. Redis adds no appliance-specific behavior at those +boundaries, so the validated Core 19 baseline supplies inherited evidence. + +## Deferred issues + +No minor appliance issues are recorded before the functional acceptance run. diff --git a/overlay/usr/lib/inithooks/bin/redis.py b/overlay/usr/lib/inithooks/bin/redis.py index 1f79412..ae52ea6 100755 --- a/overlay/usr/lib/inithooks/bin/redis.py +++ b/overlay/usr/lib/inithooks/bin/redis.py @@ -70,10 +70,10 @@ def main(): ("local", "Enter custom range"))) if bind == "all": bind_ip = "0.0.0.0" - if bind == "local": + elif bind == "local": localaddr = InterfaceInfo(get_ifnames()[0]).address d = Dialog('TurnKey Linux - First boot configuration') - bind_ip = d.get_input("Bind IP Range", "Enter bind ip range", localaddr) + bind_ip = d.get_input("Bind IP Range", "Enter bind ip range", localaddr) else: bind_ip = "127.0.0.1" @@ -102,26 +102,27 @@ def main(): redis_commander_conf]) # restart redis and redis commander if running so change takes effect - try: - subprocess.run(["systemctl", "is-active", - "--quiet", "redis-server.service"]) - subprocess.run(["service", "redis-server", "restart"]) - except ExecError: - pass + if subprocess.run(["systemctl", "is-active", "--quiet", + "redis-server.service"]).returncode == 0: + subprocess.run(["service", "redis-server", "restart"], check=True) # reload and restart pm2 so changes take affect # and save them to /home/node/.pm2/dump.pm2 - try: - subprocess.run(["systemctl", "is-active", - "--quiet", "pm2-node.service"]) - subprocess.run(["systemctl", "reload", - "pm2-node.service"]) -# subprocess.run(["rm", "/home/node/.pm2/dump.pm2"]) - subprocess.run(["pm2", "reload", "/opt/tklweb-cp/ecosystem.config.js"],env={"PM2_HOME": "/home/node/.pm2", "PATH": "/usr/local/bin"}, check=True, user="node") - subprocess.run(["pm2", "save"],env={"PM2_HOME": "/home/node/.pm2", "PATH": "/usr/local/bin"}, check=True, user="node") - subprocess.run(["service", "pm2-node", "restart"]) - except ExecError: - pass + if subprocess.run(["systemctl", "is-active", "--quiet", + "pm2-node.service"]).returncode == 0: + environment = [ + "env", "PM2_HOME=/home/node/.pm2", + "PATH=/usr/local/bin:/usr/bin:/bin", + ] + subprocess.run([ + "runuser", "--user", "node", "--", *environment, + "pm2", "reload", "/opt/tklweb-cp/ecosystem.config.js", + ], check=True) + subprocess.run([ + "runuser", "--user", "node", "--", *environment, + "pm2", "save", + ], check=True) + subprocess.run(["service", "pm2-node", "restart"], check=True) if __name__ == "__main__": diff --git a/plan/main b/plan/main index dec6021..1475d07 100644 --- a/plan/main +++ b/plan/main @@ -2,3 +2,6 @@ #include redis-server +redis-tools +nodejs # Debian Node.js runtime +npm # Debian Node.js package manager diff --git a/tests/v19-upgrade.sh b/tests/v19-upgrade.sh new file mode 100755 index 0000000..d597573 --- /dev/null +++ b/tests/v19-upgrade.sh @@ -0,0 +1,81 @@ +#!/bin/bash +set -Eeuo pipefail +umask 077 + +workdir=$(mktemp -d) +commander_pid= + +cleanup() { + if [[ -n "$commander_pid" ]]; then + kill "$commander_pid" 2>/dev/null || true + wait "$commander_pid" 2>/dev/null || true + fi + rm -rf -- "$workdir" +} +trap cleanup EXIT + +export DEBIAN_FRONTEND=noninteractive +apt-get update >/dev/null +apt-get install -y ca-certificates curl nodejs npm redis-server redis-tools \ + >/dev/null + +redis_password=turnkey-v19-upgrade-fixture +redis-server --daemonize yes --bind 127.0.0.1 --protected-mode yes \ + --requirepass "$redis_password" + +cd "$workdir" +npm init -y >/dev/null +npm install --save-exact redis-commander@0.8.0 >/dev/null + +start_commander() { + HTTP_USER=admin HTTP_PASSWORD=fixture \ + node node_modules/redis-commander/bin/redis-commander.js \ + --address 127.0.0.1 --port 8082 \ + --redis-user default --redis-password "$redis_password" \ + >commander.log 2>&1 & + commander_pid=$! + for _ in {1..30}; do + if curl --fail --silent --user admin:fixture \ + http://127.0.0.1:8082/ | grep -qi 'redis commander'; then + return + fi + sleep 1 + done + cat commander.log >&2 + return 1 +} + +stop_commander() { + kill "$commander_pid" + wait "$commander_pid" 2>/dev/null || true + commander_pid= +} + +test "$(redis-cli --no-auth-warning -a "$redis_password" \ + set turnkey:v19:upgrade preserved)" = OK +old_version=$(node -p "require('redis-commander/package.json').version") +start_commander +stop_commander + +npm install --save-exact redis-commander@latest >/dev/null +new_version=$(node -p "require('redis-commander/package.json').version") +candidate=$(npm view redis-commander version) +test "$old_version" != "$new_version" +test "$new_version" = "$candidate" +node - <<'NODE' +const lock = require('./package-lock.json'); +const commander = lock.packages['node_modules/redis-commander']; +if (!commander || !commander.integrity) process.exit(1); +const dependencies = Object.entries(lock.packages || {}) + .filter(([path]) => path.startsWith('node_modules/')); +if (!dependencies.length || dependencies.some(([, pkg]) => !pkg.integrity)) { + process.exit(1); +} +NODE + +start_commander +test "$(redis-cli --no-auth-warning -a "$redis_password" \ + get turnkey:v19:upgrade)" = preserved + +printf 'old_version=%s\nnew_version=%s\nregistry_candidate=%s\n' \ + "$old_version" "$new_version" "$candidate" diff --git a/tests/v19.sh b/tests/v19.sh new file mode 100755 index 0000000..6380827 --- /dev/null +++ b/tests/v19.sh @@ -0,0 +1,134 @@ +#!/bin/bash +set -Eeuo pipefail +umask 077 + +result=${TKL_TEST_RESULT:?TKL_TEST_RESULT is required} +admin_password=${TKL_TEST_APP_PASS:?TKL_TEST_APP_PASS is required} +response=/tmp/tkl-redis-response.$$ +pm2_state=/tmp/tkl-redis-pm2.$$ +policy=/tmp/tkl-redis-policy.$$ + +cleanup() { + rm -f -- "$response" "$pm2_state" "$policy" +} +trap cleanup EXIT + +systemctl --quiet is-active redis-server.service nginx.service \ + pm2-node.service multi-user.target +systemctl --quiet is-enabled redis-server.service pm2-node.service +nginx -t + +redis_version=$(dpkg-query -W -f='${Version}' redis-server) +redis_tools_version=$(dpkg-query -W -f='${Version}' redis-tools) +node_version=$(dpkg-query -W -f='${Version}' nodejs) +npm_version=$(dpkg-query -W -f='${Version}' npm) +nginx_version=$(dpkg-query -W -f='${Version}' nginx) + +node_path=$(readlink -f "$(command -v node)") +npm_path=$(readlink -f "$(command -v npm)") +dpkg-query -S "$node_path" | grep -q '^nodejs:' +dpkg-query -S "$npm_path" | grep -q '^npm:' +[[ $(node --version) == v20.* ]] +test "$(su node -s /bin/bash -c 'npm config get cafile')" = \ + /etc/ssl/certs/ca-certificates.crt + +grep -Fxq 'bind 0.0.0.0' /etc/redis/redis.conf +grep -Fxq 'protected-mode yes' /etc/redis/redis.conf +redis_password=$(turnkey-redis-pw get) +test -n "$redis_password" +grep -Fxq "requirepass $redis_password" /etc/redis/redis.conf + +redis-cli ping >"$response" 2>&1 || true +grep -q 'NOAUTH' "$response" +test "$(redis-cli --no-auth-warning -a "$redis_password" ping)" = PONG + +key="turnkey:v19:persistence:$$" +value="redis-v19-main-flow-$$" +test "$(redis-cli --no-auth-warning -a "$redis_password" set "$key" "$value")" = OK +test "$(redis-cli --no-auth-warning -a "$redis_password" save)" = OK +systemctl restart redis-server.service +for _ in {1..20}; do + if redis-cli --no-auth-warning -a "$redis_password" ping \ + 2>/dev/null | grep -qx PONG; then + break + fi + sleep 1 +done +test "$(redis-cli --no-auth-warning -a "$redis_password" get "$key")" = \ + "$value" +test "$(redis-cli --no-auth-warning -a "$redis_password" del "$key")" = 1 +test "$(redis-cli --no-auth-warning -a "$redis_password" exists "$key")" = 0 + +unauth_status=$(curl --insecure --silent --output /dev/null \ + --write-out '%{http_code}' https://127.0.0.1/redis-commander/) +test "$unauth_status" = 401 +curl --insecure --fail --silent --show-error \ + --user "admin:$admin_password" \ + https://127.0.0.1/redis-commander/ >"$response" +grep -qi 'redis commander' "$response" +curl --insecure --fail --silent --show-error https://127.0.0.1/ \ + >"$response" +grep -q 'Redis GUI' "$response" +grep -q ':12321' "$response" + +su node -s /bin/bash -c \ + 'PM2_HOME=/home/node/.pm2 /usr/local/lib/node_modules/pm2/bin/pm2 jlist' \ + >"$pm2_state" +python3 - "$pm2_state" <<'PYTHON' +import json +import sys + +processes = json.load(open(sys.argv[1])) +names = { + process.get("name") + for process in processes + if process.get("pid", 0) > 0 + and process.get("pm2_env", {}).get("status") == "online" +} +assert "Redis-commander" in names +assert "TurnKey Linux CP" in names +PYTHON + +commander_version=$(node -p \ + "require('/opt/tklweb-cp/node_modules/redis-commander/package.json').version") +commander_candidate=$(su node -s /bin/bash -c \ + 'cd /opt/tklweb-cp && npm view redis-commander version') +test -n "$commander_candidate" +test -f /opt/tklweb-cp/package-lock.json +node - "$commander_version" <<'NODE' +const lock = require('/opt/tklweb-cp/package-lock.json'); +const expected = process.argv[2]; +const commander = lock.packages['node_modules/redis-commander']; +if (!commander || commander.version !== expected || !commander.integrity) { + process.exit(1); +} +const dependencies = Object.entries(lock.packages || {}) + .filter(([path]) => path.startsWith('node_modules/')); +if (!dependencies.length || dependencies.some(([, pkg]) => !pkg.integrity)) { + process.exit(1); +} +NODE + +before="$redis_version|$redis_tools_version|$node_version|$npm_version|$nginx_version" +apt-get update >/dev/null +for package in redis-server redis-tools nodejs npm nginx; do + apt-cache policy "$package" >"$policy" + candidate=$(awk '/Candidate:/ {print $2}' "$policy") + test -n "$candidate" + test "$candidate" != '(none)' + grep -Eq 'https?://(deb|security)\.debian\.org/.*trixie' "$policy" +done +after="$(dpkg-query -W -f='${Version}' redis-server)|$(dpkg-query -W -f='${Version}' redis-tools)|$(dpkg-query -W -f='${Version}' nodejs)|$(dpkg-query -W -f='${Version}' npm)|$(dpkg-query -W -f='${Version}' nginx)" +test "$after" = "$before" +grep -Rqs '^Suites: trixie' /etc/apt/sources.list.d +! grep -Rqi bookworm /etc/apt/sources.list.d + +cat >"$result" < Date: Mon, 24 Aug 2026 10:10:30 +0000 Subject: [PATCH 2/5] Apply npm trust before inherited application setup The inherited Node.js control-panel configuration contacts the npm registry before the product configuration phase. Without an early npm CA setting, the TKLDev inspection proxy caused every registry request to fail with SELF_SIGNED_CERT_IN_CHAIN. Register the existing product overlay in the common-overlay phase and provide npm with the system CA bundle, matching the completed Node.js v19 pattern. The final product configuration still removes the temporary global npm configuration after copying the managed CA setting to the node account. Verified against retained build run 20260824t095632z-4268-9544 plus shell syntax, npmrc content and git diff whitespace checks. --- Makefile | 2 ++ overlay/usr/local/etc/npmrc | 1 + 2 files changed, 3 insertions(+) create mode 100644 overlay/usr/local/etc/npmrc diff --git a/Makefile b/Makefile index 329d3e5..ee2efc5 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,4 @@ +COMMON_OVERLAYS += $(CURDIR)/overlay + include $(FAB_PATH)/common/mk/turnkey/nodejs-tklwebcp.mk include $(FAB_PATH)/common/mk/turnkey.mk diff --git a/overlay/usr/local/etc/npmrc b/overlay/usr/local/etc/npmrc new file mode 100644 index 0000000..bf3eec7 --- /dev/null +++ b/overlay/usr/local/etc/npmrc @@ -0,0 +1 @@ +cafile=/etc/ssl/certs/ca-certificates.crt From 7c720d29053931a8431c4dc9f9db3a909a48c219 Mon Sep 17 00:00:00 2001 From: Liraz Siri Date: Mon, 24 Aug 2026 10:47:49 +0000 Subject: [PATCH 3/5] Start Nginx after firstboot certificate generation The imported appliance can start Nginx while the common certificate hook is replacing the default certificate and key. If that initial start sees the pair between writes, Nginx fails, and the common hook skips it because it only restarts active services. This leaves the documented Redis Commander HTTPS interface unavailable on an otherwise successful boot. Restart Nginx from the later Redis firstboot hook after certificate generation has completed. This keeps the correction in the application startup path and makes firstboot fail visibly if the final Nginx configuration is unusable. Diagnosed from retained acceptance run 20260824t101037z-1960-16911: Nginx reported a private-key mismatch, remained failed after certificate regeneration, and the first HTTPS request exited with status 7. Shell syntax and diff whitespace checks pass. --- overlay/usr/lib/inithooks/firstboot.d/40redis | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/overlay/usr/lib/inithooks/firstboot.d/40redis b/overlay/usr/lib/inithooks/firstboot.d/40redis index f1a99de..c7299b3 100755 --- a/overlay/usr/lib/inithooks/firstboot.d/40redis +++ b/overlay/usr/lib/inithooks/firstboot.d/40redis @@ -17,3 +17,7 @@ fi $INITHOOKS_PATH/bin/redis.py --pass="$APP_PASS" \ --bind="$APP_IP_BIND" --protected_mode="$APP_PROTECTED" + +# The TLS certificate hook only restarts services that were already active. +# Ensure Nginx starts with the completed certificate and key pair. +systemctl restart --quiet nginx.service From aec0174d7f59a124757989bb5871af11f0ce4d08 Mon Sep 17 00:00:00 2001 From: Liraz Siri Date: Mon, 24 Aug 2026 11:23:22 +0000 Subject: [PATCH 4/5] Test the Redis Commander 0.9 login flow Redis Commander 0.9 replaced the legacy HTTP Basic challenge with a public login page and bearer-protected APIs. The initial v19 test carried forward the v18 root-page 401 assumption, so it failed after Nginx and the underlying Redis flow were already healthy. Wait briefly for the PM2 application to accept connections, verify anonymous denial on the protected connections API, sign in with the firstboot administrator credential, and query the configured Redis connection with the returned bearer token. Update the disposable updater fixture to prove both the 0.8 legacy login and the 0.9 token flow, and record the observed upstream dependency notices for release review. Verified shell syntax and documentation policy. A disposable Debian Trixie fixture upgraded Redis Commander 0.8.0 to registry candidate 0.9.0, authenticated through both generations, queried the protected current API, retained lock integrity, and preserved Redis data. --- changelog | 3 +++ docs/v19.0-testing.md | 18 +++++++++++------ tests/v19-upgrade.sh | 37 ++++++++++++++++++++++++++++++----- tests/v19.sh | 45 ++++++++++++++++++++++++++++++++++++++----- 4 files changed, 87 insertions(+), 16 deletions(-) diff --git a/changelog b/changelog index 62a8663..5e30dda 100644 --- a/changelog +++ b/changelog @@ -13,6 +13,9 @@ turnkey-redis-19.0 (1) turnkey; urgency=low * Preserve the selected Redis network binding and reliably reload the first-boot Redis Commander credentials. + * Start Nginx after first-boot TLS certificate generation so Redis Commander + is available on the initial HTTPS session. + * See the TurnKey Core 19.0 changelog for changes common to all appliances. -- TurnKey Linux release engineering Mon, 24 Aug 2026 00:00:00 +0000 diff --git a/docs/v19.0-testing.md b/docs/v19.0-testing.md index a90ac3d..521a0c3 100644 --- a/docs/v19.0-testing.md +++ b/docs/v19.0-testing.md @@ -35,7 +35,7 @@ evidence, cleanup result and verdict. | First boot generates an authenticated Redis service | Retrieve the generated password, reject an unauthenticated PING and perform an authenticated PING | Unauthenticated access is denied and authenticated access returns `PONG` | `tests/v19.sh`; harness runtime fields | | The selected all-interface binding and protected mode are applied | Inspect the live firstboot-generated Redis configuration | `bind 0.0.0.0`, `protected-mode yes` and the generated password are present | `tests/v19.sh` | | Redis stores, retrieves, persists and deletes data | Set a disposable key, run synchronous `SAVE`, restart Redis, retrieve the value, delete it and prove it is absent | The complete database round trip passes across a service restart | `tests/v19.sh`; harness runtime fields | -| Redis Commander is available through authenticated HTTPS | Request the interface without credentials and with the firstboot administrator credential | Anonymous access returns 401 and the authenticated page identifies Redis Commander | `tests/v19.sh` | +| Redis Commander is available through authenticated HTTPS | Load the login page, prove the connections API rejects an anonymous request, sign in with the firstboot administrator credential and query that API with its bearer token | The login page loads, anonymous API access returns 401 and the authenticated API reports the configured Redis connection | `tests/v19.sh` | | The landing page links Redis Commander and Webmin | Request the HTTPS landing page | Redis GUI and Webmin links are present | `tests/v19.sh` | | PM2 supervises Redis Commander and the TurnKey control panel | Inspect PM2 JSON state after normal boot | Both processes are online with live PIDs | `tests/v19.sh` | | Redis Commander has integrity and a maintained update path | Inspect the npm lock, query the registry candidate and exercise an older-to-current update in a disposable Trixie fixture | Registry integrity hashes are present and the interface works before and after update | `tests/v19.sh`; `tests/v19-upgrade.sh` | @@ -55,10 +55,11 @@ systemctl restart pm2-node ``` `tests/v19-upgrade.sh` is a disposable Trixie fixture. It installs Redis -Commander 0.8.0, verifies authenticated access to Redis and the management -page, updates through the official npm channel, restarts Commander, verifies -the page again and proves the Redis value survived. It also checks the current -version and lock-file integrity fields. +Commander 0.8.0, verifies its legacy authenticated page, updates through the +official npm channel, restarts Commander, signs in through the current form +and bearer-token flow, queries the protected connections API and proves the +Redis value survived. It also checks the current version and lock-file +integrity fields. The fixture was run with the application source and local Docker inspection CA mounted read-only: @@ -85,4 +86,9 @@ boundaries, so the validated Core 19 baseline supplies inherited evidence. ## Deferred issues -No minor appliance issues are recorded before the functional acceptance run. +The current official Redis Commander dependency tree emits deprecation notices +and an npm audit summary of one low, two moderate, three high and one critical +finding. These are upstream transitive-dependency findings rather than a +failure in the documented flow. Force-updating transitive packages outside the +maintained Redis Commander release is deferred for release review because it +could break the supported update path. diff --git a/tests/v19-upgrade.sh b/tests/v19-upgrade.sh index d597573..081f9ae 100755 --- a/tests/v19-upgrade.sh +++ b/tests/v19-upgrade.sh @@ -35,14 +35,41 @@ start_commander() { >commander.log 2>&1 & commander_pid=$! for _ in {1..30}; do - if curl --fail --silent --user admin:fixture \ - http://127.0.0.1:8082/ | grep -qi 'redis commander'; then - return + status=$(curl --silent --output /dev/null --write-out '%{http_code}' \ + http://127.0.0.1:8082/ || true) + if [[ $status == 200 || $status == 401 ]]; then + break fi sleep 1 done - cat commander.log >&2 - return 1 + version=$(node -p "require('redis-commander/package.json').version") + if [[ $version == 0.8.* ]]; then + curl --fail --silent --user admin:fixture \ + http://127.0.0.1:8082/ | grep -qi 'redis commander' + return + fi + curl --fail --silent \ + --data-urlencode username=admin --data-urlencode password=fixture \ + http://127.0.0.1:8082/signin >"$workdir/signin.json" + bearer_token=$(python3 - "$workdir/signin.json" <<'PYTHON' +import json +import sys + +result = json.load(open(sys.argv[1])) +assert result.get("ok") is True +print(result["bearerToken"]) +PYTHON +) + curl --fail --silent --header "Authorization: Bearer $bearer_token" \ + http://127.0.0.1:8082/connections >"$workdir/connections.json" + python3 - "$workdir/connections.json" <<'PYTHON' +import json +import sys + +result = json.load(open(sys.argv[1])) +assert result.get("ok") is True +assert result.get("connections") +PYTHON } stop_commander() { diff --git a/tests/v19.sh b/tests/v19.sh index 6380827..5a1d594 100755 --- a/tests/v19.sh +++ b/tests/v19.sh @@ -59,13 +59,48 @@ test "$(redis-cli --no-auth-warning -a "$redis_password" get "$key")" = \ test "$(redis-cli --no-auth-warning -a "$redis_password" del "$key")" = 1 test "$(redis-cli --no-auth-warning -a "$redis_password" exists "$key")" = 0 -unauth_status=$(curl --insecure --silent --output /dev/null \ - --write-out '%{http_code}' https://127.0.0.1/redis-commander/) -test "$unauth_status" = 401 +commander_status= +for _ in {1..30}; do + commander_status=$(curl --insecure --silent --output /dev/null \ + --write-out '%{http_code}' \ + https://127.0.0.1/redis-commander/ || true) + [[ $commander_status == 200 ]] && break + sleep 1 +done +test "$commander_status" = 200 curl --insecure --fail --silent --show-error \ - --user "admin:$admin_password" \ https://127.0.0.1/redis-commander/ >"$response" grep -qi 'redis commander' "$response" + +unauth_status=$(curl --insecure --silent --output /dev/null \ + --write-out '%{http_code}' \ + https://127.0.0.1/redis-commander/connections) +test "$unauth_status" = 401 +curl --insecure --fail --silent --show-error \ + --data-urlencode username=admin \ + --data-urlencode "password=$admin_password" \ + https://127.0.0.1/redis-commander/signin >"$response" +bearer_token=$(python3 - "$response" <<'PYTHON' +import json +import sys + +result = json.load(open(sys.argv[1])) +assert result.get("ok") is True +print(result["bearerToken"]) +PYTHON +) +curl --insecure --fail --silent --show-error \ + --header "Authorization: Bearer $bearer_token" \ + https://127.0.0.1/redis-commander/connections >"$response" +python3 - "$response" <<'PYTHON' +import json +import sys + +result = json.load(open(sys.argv[1])) +assert result.get("ok") is True +assert result.get("connections") +PYTHON + curl --insecure --fail --silent --show-error https://127.0.0.1/ \ >"$response" grep -q 'Redis GUI' "$response" @@ -126,7 +161,7 @@ grep -Rqs '^Suites: trixie' /etc/apt/sources.list.d cat >"$result" < Date: Mon, 24 Aug 2026 11:43:22 +0000 Subject: [PATCH 5/5] Record Redis 19 acceptance evidence Capture the exact passing source and harness revisions, archive and tree hashes, installed versions, retained report location, and artifact checksum result. Align the documented disposable updater command with the rerun that exercised Redis Commander 0.8 legacy authentication and the current 0.9 form and token flow. This is an evidence-only update. Product behavior remains the exact source accepted by run 20260824t112332z-4695-11523. --- docs/v19.0-testing.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/v19.0-testing.md b/docs/v19.0-testing.md index 521a0c3..23d0c42 100644 --- a/docs/v19.0-testing.md +++ b/docs/v19.0-testing.md @@ -27,6 +27,23 @@ harness report records the exact source and harness revisions, archive and tree hashes, installed versions, configured-root mode, runtime evidence, updater evidence, cleanup result and verdict. +Acceptance passed in run `20260824t112332z-4695-11523` with source +`aec0174d7f59a124757989bb5871af11f0ce4d08` and harness +`b6f8b8c2f3e8f00fd5cf36869e645fc08f01f87e`. The report is retained at +`/home/agent/.local/state/turnkey-v19-harness/runs/redis/20260824t112332z-4695-11523/report.txt`. +Its source archive SHA-256 is +`3f29578766f012c27c8d767ee2d118e1192e49398114924fc38029e6f12c567e`, +input tree SHA-256 is +`55b2f18dbdb5a9c5680926ebcb97513c306ecf7be2bd54c4d8f33b6043c44537` +and build tree SHA-256 is +`b6fd2e7d6f4696e81cf6ebd23f4fe14104784899d2c687dc982993a7327ac061`. +Build, import, normal boot, product tests and cleanup passed. The command +`sha256sum -c SHA256SUMS` passed for all eight retained report artifacts. + +The accepted image contained Redis Server and tools +`5:8.0.2-3+deb13u2`, Node.js `20.19.2+dfsg-1+deb13u2`, npm +`9.2.0~ds1-3`, Nginx `1.26.3-3+deb13u7` and Redis Commander `0.9.0`. + ## README crosswalk | README contract | Focused check | Required result | Evidence | @@ -65,7 +82,7 @@ The fixture was run with the application source and local Docker inspection CA mounted read-only: ```sh -docker run --rm --name tkl-redis-v19-upgrade \ +docker run --rm --name tkl-redis-v19-upgrade-loop3 \ --mount type=bind,src=/home/agent/.local/worktrees/turnkey-apps/redis/wish-redis-v19-trixie,dst=/src,readonly \ --mount type=bind,src=/usr/local/share/ca-certificates/microsandbox-ca.crt,dst=/usr/local/share/ca-certificates/microsandbox-ca.crt,readonly \ debian:trixie-slim /src/tests/v19-upgrade.sh