From 585ec9ae1151a973d0c106de0d8e611d7f682aa6 Mon Sep 17 00:00:00 2001 From: Liraz Siri Date: Mon, 24 Aug 2026 11:15:02 +0000 Subject: [PATCH 1/5] Migrate Syncthing appliance to Trixie Use the Debian 13 Syncthing package and signed APT update channel instead of downloading a third-party repository key during the build. Preserve the dedicated service identity, firstboot credentials, localhost GUI binding, and Nginx TLS entry points while correcting device-key regeneration for the current Syncthing state layout. Add functional acceptance coverage for normal boot, authenticated GUI and API access, default-folder indexing, proxy redirects, management services, and the supervised APT update path. Document the README contract crosswalk, Core inheritance, source choice, and deferred runtime tuning. --- README.rst | 7 +- changelog | 14 ++ conf.d/main | 5 - docs/v19.0-testing.md | 69 +++++++ overlay/etc/apt/preferences.d/syncthing.pref | 7 - overlay/etc/apt/sources.list.d/syncthing.list | 1 - overlay/etc/nginx/sites-available/syncthing | 3 +- .../firstboot.d/20regen-syncthing-secrets | 2 +- plan/main | 1 + tests/v19.sh | 189 ++++++++++++++++++ 10 files changed, 278 insertions(+), 20 deletions(-) create mode 100644 docs/v19.0-testing.md delete mode 100644 overlay/etc/apt/preferences.d/syncthing.pref delete mode 100644 overlay/etc/apt/sources.list.d/syncthing.list create mode 100755 tests/v19.sh diff --git a/README.rst b/README.rst index 7951856..820cb84 100644 --- a/README.rst +++ b/README.rst @@ -11,8 +11,7 @@ and on top of that: - Syncthing: - - Installed from the `Stable Release Channel`_ via the official Syncthing - apt package repository. + - Syncthing 1.29 installed from Debian Trixie and maintained through APT. - Pre-configured for remote access, with password set on firstboot. @@ -33,7 +32,8 @@ Supervised Manual Syncthing Update To upgrade to the latest version of Syncthing from the command line:: apt-get update - apt-get install syncthing + apt-get install --only-upgrade syncthing + systemctl restart syncthing@syncthing.service Credentials *(passwords set at first boot)* @@ -45,4 +45,3 @@ Credentials *(passwords set at first boot)* .. _Syncthing: https://syncthing.net/ .. _TurnKey Core: https://www.turnkeylinux.org/core -.. _Stable Release Channel: https://docs.syncthing.net/users/releases.html diff --git a/changelog b/changelog index 4166fbf..d8483bc 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,17 @@ +turnkey-syncthing-19.0 (1) turnkey; urgency=low + + * Upgrade the base distribution to Debian 13/Trixie. + + * Use the Debian Trixie Syncthing package and signed APT update path instead + of the third-party Syncthing repository. + + * Preserve the firstboot-authenticated web interface, Nginx TLS proxy, + default synchronization folder and supervised update workflow. + + * 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-syncthing-18.0 (1) turnkey; urgency=low * Update to latest upstream Syncthing (via upstream apt repo) - v1.27.4. diff --git a/conf.d/main b/conf.d/main index c69abe4..2e675d7 100755 --- a/conf.d/main +++ b/conf.d/main @@ -1,11 +1,6 @@ #!/bin/sh -ex -APT_GPG_URL=https://syncthing.net/release-key.txt -curl -sS $APT_GPG_URL | apt-key --keyring /usr/share/keyrings/syncthing.gpg add - - useradd -r syncthing -m -d /home/syncthing -DEBIAN_FRONTEND=noninteractive apt-get update -y -DEBIAN_FRONTEND=noninteractive apt-get install -y syncthing # syncthing service generates secrets on first run, so is enabled and # started via /usr/lib/inithooks/firstboot.d/20regen-syncthing-secrets diff --git a/docs/v19.0-testing.md b/docs/v19.0-testing.md new file mode 100644 index 0000000..d22b330 --- /dev/null +++ b/docs/v19.0-testing.md @@ -0,0 +1,69 @@ +# Syncthing 19.0 testing + +## Scope and source decision + +Syncthing 19.0 uses Debian 13 Trixie packages for Syncthing 1.29, Nginx and +Python bcrypt. Trixie provides the complete documented appliance stack, so v19 +removes the third-party Syncthing repository, its package preference and its +build-time key download. Signed Debian and TurnKey APT repositories provide the +maintained update path. + +The Debian package supplies the `syncthing@.service` systemd template. The +appliance runs that service as `syncthing`, regenerates its device identity and +GUI credentials at first boot, binds the native GUI to localhost, and exposes +it through the existing Nginx TLS proxy. + +## Acceptance command + +```sh +/sandboxed-git/turnkey/tools/test-v19-appliance syncthing \ + --source /home/agent/.local/worktrees/turnkey-apps/syncthing/wish-syncthing-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 final run +identity and report path will be recorded after exact acceptance. + +## README crosswalk + +| README contract | Focused check | Required result | Evidence | +| --- | --- | --- | --- | +| Syncthing 1.29 comes from Debian Trixie | Query package version and binary ownership, then inspect the APT candidate | The binary is Debian-owned and retains an eligible signed Trixie candidate | `tests/v19.sh` | +| Firstboot configures remote GUI credentials | Submit the real Syncthing password-login request and use its session cookie | The configured `syncthing` user receives an authenticated GUI session | `tests/v19.sh` | +| Syncthing runs as its dedicated service identity | Check the active and enabled template service, effective user, config ownership and listening sockets | Normal boot runs Syncthing as `syncthing`; its config and sync folder are owned by that account | `tests/v19.sh` | +| The default folder provides file synchronization | Read the folder through the authenticated API, create a file, request a scan and read the indexed file metadata | The active send-receive folder is `/home/syncthing/Sync` and the file is indexed with matching name and size | `tests/v19.sh` | +| Nginx provides system TLS and redirects ports 80 and 8384 to 443 | Validate Nginx, request HTTP and HTTPS entry paths, inspect the configured system certificate and test both port 8384 schemes | HTTPS serves Syncthing and both documented redirect paths reach port 443 | `tests/v19.sh` | +| Syncthing API access remains authenticated | Reject an anonymous REST request, extract the generated API key and query status and folder endpoints | Anonymous access is denied and key-authenticated API calls succeed | `tests/v19.sh` | +| Webmin and SSH provide documented administration | Request Webmin HTTPS and cite the unchanged Core layer | Webmin responds; Core 19 supplies inherited root and SSH behavior | `tests/v19.sh`; Core run `20260824t010251z-1634-32241`, source `24c82ee3540ce545422742b0e28ba6b687c53ec2`, verdict `PASS` | +| Syncthing updates are supervised through APT | Refresh metadata and inspect candidates without changing installed versions | Signed Trixie metadata is accepted and the third-party repository is absent | `tests/v19.sh` | + +## Updater check + +`tests/v19.sh` runs `apt-get update`, confirms eligible Trixie candidates for +`syncthing`, `nginx` and `python3-bcrypt`, and verifies the installed versions +remain unchanged. The documented supervised Syncthing update is: + +```sh +apt-get install --only-upgrade syncthing +systemctl restart syncthing@syncthing.service +``` + +## Known limitation + +The one-container acceptance validates the synchronization engine by creating +a file in the default send-receive folder, requesting a scan and reading its +indexed metadata through the authenticated API. It does not transfer that file +to a second Syncthing device because device pairing would add a second runtime +without exercising another appliance-specific integration boundary. + +Docker runtime validation does not exercise the installer, kernel, bootloader +or physical hardware. Syncthing adds no appliance-specific behavior at those +boundaries, so the validated Core 19 baseline supplies inherited evidence. + +## Deferred issues + +- Syncthing 1.29 may request a larger UDP receive buffer than the appliance's + inherited 2.5 MB setting on some kernels. Syncthing continues with the + available buffer and TCP remains available. The runtime acceptance checks + both TCP and UDP listeners; performance tuning is deferred unless normal + deployment evidence shows a material transfer impact. diff --git a/overlay/etc/apt/preferences.d/syncthing.pref b/overlay/etc/apt/preferences.d/syncthing.pref deleted file mode 100644 index 556bfef..0000000 --- a/overlay/etc/apt/preferences.d/syncthing.pref +++ /dev/null @@ -1,7 +0,0 @@ -Package: * -Pin: release apt.syncthing.net -Pin-Priority: 100 - -Package: syncthing -Pin: release apt.syncthing.net -Pin-Priority: 500 diff --git a/overlay/etc/apt/sources.list.d/syncthing.list b/overlay/etc/apt/sources.list.d/syncthing.list deleted file mode 100644 index cb44314..0000000 --- a/overlay/etc/apt/sources.list.d/syncthing.list +++ /dev/null @@ -1 +0,0 @@ -deb [signed-by=/usr/share/keyrings/syncthing.gpg] https://apt.syncthing.net/ syncthing stable diff --git a/overlay/etc/nginx/sites-available/syncthing b/overlay/etc/nginx/sites-available/syncthing index 855c8e0..cfe3e12 100644 --- a/overlay/etc/nginx/sites-available/syncthing +++ b/overlay/etc/nginx/sites-available/syncthing @@ -28,7 +28,7 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_pass http://localhost:8383; + proxy_pass http://127.0.0.1:8383; proxy_read_timeout 600s; proxy_send_timeout 600s; @@ -36,4 +36,3 @@ server { proxy_redirect off; } } - diff --git a/overlay/usr/lib/inithooks/firstboot.d/20regen-syncthing-secrets b/overlay/usr/lib/inithooks/firstboot.d/20regen-syncthing-secrets index 21801d5..24204cd 100755 --- a/overlay/usr/lib/inithooks/firstboot.d/20regen-syncthing-secrets +++ b/overlay/usr/lib/inithooks/firstboot.d/20regen-syncthing-secrets @@ -8,7 +8,7 @@ systemctl enable syncthing@syncthing.service || true systemctl stop syncthing@syncthing.service || true # create backups, just in case... -FILES="config.xml cert.pem cert.key https-cert.pem https-key.pem csrftokens.txt" +FILES="config.xml cert.pem key.pem https-cert.pem https-key.pem csrftokens.txt" for file in ${FILES}; do file=${CONF_DIR}/${file} if [[ -f ${file} ]]; then diff --git a/plan/main b/plan/main index 9f4a622..c6d615d 100644 --- a/plan/main +++ b/plan/main @@ -1,5 +1,6 @@ #include python3-bcrypt +syncthing nginx /* nginx reverse proxy for Syncthing Web UI */ diff --git a/tests/v19.sh b/tests/v19.sh new file mode 100755 index 0000000..af73693 --- /dev/null +++ b/tests/v19.sh @@ -0,0 +1,189 @@ +#!/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} +config=/home/syncthing/.local/state/syncthing/config.xml +sync_root=/home/syncthing/Sync +response=/tmp/tkl-syncthing-response.$$ +headers=/tmp/tkl-syncthing-headers.$$ +cookie=/tmp/tkl-syncthing-cookie.$$ +login_json=/tmp/tkl-syncthing-login.$$ +folders_json=/tmp/tkl-syncthing-folders.$$ +file_json=/tmp/tkl-syncthing-file.$$ +policy=/tmp/tkl-syncthing-policy.$$ +probe_name=turnkey-v19-index-$$.txt +probe_path=$sync_root/$probe_name + +cleanup() { + rm -f -- "$response" "$headers" "$cookie" "$login_json" \ + "$folders_json" "$file_json" "$policy" "$probe_path" +} +trap cleanup EXIT + +systemctl --quiet is-active syncthing@syncthing.service nginx.service \ + multi-user.target +systemctl --quiet is-enabled syncthing@syncthing.service nginx.service +test "$(systemctl show --property=User --value \ + syncthing@syncthing.service)" = syncthing +nginx -t + +syncthing_package=$(dpkg-query -W -f='${Version}' syncthing) +nginx_package=$(dpkg-query -W -f='${Version}' nginx) +bcrypt_package=$(dpkg-query -W -f='${Version}' python3-bcrypt) +syncthing_version=$(syncthing --version) +grep -q '^syncthing v1\.29\.' <<<"$syncthing_version" +dpkg-query -S "$(readlink -f "$(command -v syncthing)")" >/dev/null + +test -s "$config" +test -s /home/syncthing/.local/state/syncthing/cert.pem +test -s /home/syncthing/.local/state/syncthing/key.pem +test "$(stat -c '%U:%G' "$config")" = syncthing:syncthing +grep -Eq '^[[:space:]]*
127\.0\.0\.1:8383
$' "$config" +grep -Eq '^[[:space:]]*syncthing$' "$config" +grep -Eq '^[[:space:]]*\$2' "$config" +grep -Eq '^[[:space:]]*true$' \ + "$config" +test -d "$sync_root" +test "$(stat -c '%U:%G' "$sync_root")" = syncthing:syncthing + +ss -ltn | grep -Eq '127\.0\.0\.1:8383[[:space:]]' +ss -ltn | grep -Eq ':22000[[:space:]]' +ss -lun | grep -Eq ':(21027|22000)[[:space:]]' +nginx -T 2>/dev/null | grep -Fq \ + 'ssl_certificate /etc/ssl/private/cert.pem;' + +curl --insecure --fail --silent --show-error --location \ + http://127.0.0.1/ >"$response" +grep -qi 'Syncthing' "$response" +curl --insecure --fail --silent --show-error \ + https://127.0.0.1/ >"$response" +grep -qi '<title>Syncthing' "$response" + +curl --silent --show-error --dump-header "$headers" --output /dev/null \ + http://127.0.0.1:8384/ +grep -q '^HTTP/.* 302' "$headers" +grep -Fqi 'Location: https://127.0.0.1/' "$headers" +curl --insecure --silent --show-error --dump-header "$headers" \ + --output /dev/null https://127.0.0.1:8384/ +grep -q '^HTTP/.* 302' "$headers" +grep -Fqi 'Location: https://127.0.0.1/' "$headers" + +unauth_status=$(curl --insecure --silent --output /dev/null \ + --write-out '%{http_code}' \ + https://127.0.0.1/rest/system/status) +test "$unauth_status" = 403 + +python3 - "$app_password" >"$login_json" <<'PYTHON' +import json +import sys + +json.dump({ + "username": "syncthing", + "password": sys.argv[1], + "stayLoggedIn": False, +}, sys.stdout) +PYTHON +login_status=$(curl --insecure --silent --show-error \ + --cookie-jar "$cookie" --output "$response" --write-out '%{http_code}' \ + --header 'Content-Type: application/json' \ + --data-binary @"$login_json" \ + https://127.0.0.1/rest/noauth/auth/password) +test "$login_status" = 204 +grep -q 'sessionid-' "$cookie" +curl --insecure --fail --silent --show-error --cookie "$cookie" \ + https://127.0.0.1/meta.js >"$response" +grep -q '"authenticated":true' "$response" + +api_key=$(python3 - "$config" <<'PYTHON' +import sys +import xml.etree.ElementTree as ET + +print(ET.parse(sys.argv[1]).getroot().findtext("gui/apikey")) +PYTHON +) +test -n "$api_key" +curl --insecure --fail --silent --show-error \ + --header "X-API-Key: $api_key" \ + https://127.0.0.1/rest/system/status >"$response" +python3 - "$response" <<'PYTHON' +import json +import sys + +status = json.load(open(sys.argv[1])) +assert status["myID"] +assert status["uptime"] > 0 +PYTHON + +curl --insecure --fail --silent --show-error \ + --header "X-API-Key: $api_key" \ + https://127.0.0.1/rest/config/folders >"$folders_json" +python3 - "$folders_json" <<'PYTHON' +import json +import sys + +folders = json.load(open(sys.argv[1])) +default = next(folder for folder in folders if folder["id"] == "default") +assert default["path"] == "/home/syncthing/Sync" +assert default["type"] == "sendreceive" +assert not default["paused"] +PYTHON + +printf 'syncthing-v19-index-ok\n' >"$probe_path" +chown syncthing:syncthing "$probe_path" +curl --insecure --fail --silent --show-error --request POST \ + --header "X-API-Key: $api_key" \ + 'https://127.0.0.1/rest/db/scan?folder=default' >/dev/null +indexed=false +for _ in {1..30}; do + if curl --insecure --fail --silent --show-error --get \ + --header "X-API-Key: $api_key" \ + --data-urlencode 'folder=default' \ + --data-urlencode "file=$probe_name" \ + https://127.0.0.1/rest/db/file >"$file_json" 2>/dev/null && + python3 - "$file_json" "$probe_name" <<'PYTHON' +import json +import sys + +entry = json.load(open(sys.argv[1]))["local"] +assert entry["name"] == sys.argv[2] +assert entry["size"] == len(b"syncthing-v19-index-ok\n") +assert not entry["deleted"] +PYTHON + then + indexed=true + break + fi + sleep 1 +done +test "$indexed" = true + +grep -Fxq 'Syncthing: https://$ipaddr:8384' \ + /etc/confconsole/services.txt +curl --insecure --fail --silent --show-error --head \ + https://127.0.0.1:12321/ >/dev/null + +before="$syncthing_package|$nginx_package|$bcrypt_package" +apt-get update >/dev/null +for package in syncthing nginx python3-bcrypt; 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}' syncthing)|$(dpkg-query -W -f='${Version}' nginx)|$(dpkg-query -W -f='${Version}' python3-bcrypt)" +test "$after" = "$before" +grep -Rqs '^Suites: trixie' /etc/apt/sources.list.d +! grep -RqiE 'bookworm|apt\.syncthing\.net' /etc/apt/sources.list.d + +cat >"$result" <<EOF +package_source=Debian 13 Trixie APT repositories for Syncthing, Nginx and Python bcrypt +installed_version=syncthing $syncthing_package ($syncthing_version); nginx $nginx_package; python3-bcrypt $bcrypt_package +runtime_checks=normal init; Syncthing and Nginx service supervision; syncthing service identity; localhost GUI binding; HTTP, HTTPS and port 8384 proxy paths; authenticated GUI session; authenticated REST API; default send-receive folder; practical file creation, scan and indexed readback; Webmin management endpoint +updater_command=apt-get update; apt-cache policy syncthing nginx python3-bcrypt; apt-get install --only-upgrade syncthing +updater_result=signed Trixie metadata refreshed; eligible package candidates found; installed versions unchanged +updater_channel=Debian and TurnKey Trixie APT repositories +integrity_evidence=APT accepted signed repository metadata through configured keyrings; the third-party Syncthing repository and Bookworm sources are absent +EOF From 93ad4b25cd56e1a522794241211bc5d371ed4770 Mon Sep 17 00:00:00 2001 From: Liraz Siri <liraz@liraz.org> Date: Mon, 24 Aug 2026 11:31:42 +0000 Subject: [PATCH 2/5] Match inherited Nginx TLS formatting Allow the Syncthing acceptance check to recognize the spacing used by the Core Nginx SSL snippet. The first full Trixie run reached normal boot and healthy application services, then stopped because the test assumed one literal space in the directive. --- tests/v19.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/v19.sh b/tests/v19.sh index af73693..a46d8dc 100755 --- a/tests/v19.sh +++ b/tests/v19.sh @@ -51,8 +51,8 @@ test "$(stat -c '%U:%G' "$sync_root")" = syncthing:syncthing ss -ltn | grep -Eq '127\.0\.0\.1:8383[[:space:]]' ss -ltn | grep -Eq ':22000[[:space:]]' ss -lun | grep -Eq ':(21027|22000)[[:space:]]' -nginx -T 2>/dev/null | grep -Fq \ - 'ssl_certificate /etc/ssl/private/cert.pem;' +nginx -T 2>/dev/null | grep -Eq \ + 'ssl_certificate[[:space:]]+/etc/ssl/private/cert.pem;' curl --insecure --fail --silent --show-error --location \ http://127.0.0.1/ >"$response" From 15ce3ae918c9eb68defca4401788fcb272706fdc Mon Sep 17 00:00:00 2001 From: Liraz Siri <liraz@liraz.org> Date: Mon, 24 Aug 2026 11:59:51 +0000 Subject: [PATCH 3/5] Check active Nginx TLS snippet directly Avoid a quiet grep pipeline that exits nonzero under the acceptance script pipefail contract. Validate the same system-certificate directive in the active inherited SSL snippet without changing appliance behavior or broadening runtime coverage. --- tests/v19.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/v19.sh b/tests/v19.sh index a46d8dc..fa46818 100755 --- a/tests/v19.sh +++ b/tests/v19.sh @@ -51,8 +51,8 @@ test "$(stat -c '%U:%G' "$sync_root")" = syncthing:syncthing ss -ltn | grep -Eq '127\.0\.0\.1:8383[[:space:]]' ss -ltn | grep -Eq ':22000[[:space:]]' ss -lun | grep -Eq ':(21027|22000)[[:space:]]' -nginx -T 2>/dev/null | grep -Eq \ - 'ssl_certificate[[:space:]]+/etc/ssl/private/cert.pem;' +grep -Eq 'ssl_certificate[[:space:]]+/etc/ssl/private/cert.pem;' \ + /etc/nginx/snippets/ssl.conf curl --insecure --fail --silent --show-error --location \ http://127.0.0.1/ >"$response" From ad29d6938160eda134545959137a702b9afdd02a Mon Sep 17 00:00:00 2001 From: Liraz Siri <liraz@liraz.org> Date: Mon, 24 Aug 2026 12:14:26 +0000 Subject: [PATCH 4/5] Recognize the Syncthing 1.29 landing page Match the Angular application marker emitted by the Debian Trixie Syncthing UI. Version 1.29 binds the page title dynamically, so the legacy static title prefix caused the full runtime acceptance to reject an otherwise successful proxied response. --- tests/v19.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/v19.sh b/tests/v19.sh index fa46818..5fbecdf 100755 --- a/tests/v19.sh +++ b/tests/v19.sh @@ -56,10 +56,10 @@ grep -Eq 'ssl_certificate[[:space:]]+/etc/ssl/private/cert.pem;' \ curl --insecure --fail --silent --show-error --location \ http://127.0.0.1/ >"$response" -grep -qi '<title>Syncthing' "$response" +grep -Fq 'ng-app="syncthing"' "$response" curl --insecure --fail --silent --show-error \ https://127.0.0.1/ >"$response" -grep -qi '<title>Syncthing' "$response" +grep -Fq 'ng-app="syncthing"' "$response" curl --silent --show-error --dump-header "$headers" --output /dev/null \ http://127.0.0.1:8384/ From a93ba0b941a2f3fa313904ed3bdcae553b5b7dc6 Mon Sep 17 00:00:00 2001 From: Liraz Siri <liraz@liraz.org> Date: Mon, 24 Aug 2026 12:30:36 +0000 Subject: [PATCH 5/5] Record accepted Syncthing v19 evidence Bind the README crosswalk to the exact passing Docker acceptance run, behavioral source, installed package versions, and retained report hashes. This documentation-only commit preserves the already validated appliance behavior. --- docs/v19.0-testing.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/v19.0-testing.md b/docs/v19.0-testing.md index d22b330..64f11d2 100644 --- a/docs/v19.0-testing.md +++ b/docs/v19.0-testing.md @@ -21,8 +21,21 @@ it through the existing Nginx TLS proxy. ``` The command builds the Trixie root filesystem, imports it into Docker, boots -the normal systemd and firstboot path, then runs `tests/v19.sh`. The final run -identity and report path will be recorded after exact acceptance. +the normal systemd and firstboot path, then runs `tests/v19.sh`. + +Accepted run: + +- Run: `20260824t121432z-6919-1904` +- Behavioral source: `ad29d6938160eda134545959137a702b9afdd02a` +- Report: `/home/agent/.local/state/turnkey-v19-harness/runs/syncthing/20260824t121432z-6919-1904/report.txt` +- Report SHA256: `4696f0686439032ae23fca06585dc5062bbb1691f4736742a5a1ab4bbe790a06` +- `SHA256SUMS` SHA256: `7eb650d7f7e05b4d407a5f4f6ff5279805692892f4a37301d13f5ace342b0f96` +- Verdict: `PASS` + +The accepted image installed Syncthing `1.29.5~ds1-2`, Nginx +`1.26.3-3+deb13u7` and Python bcrypt `4.2.0-2.1+b1`. Normal boot completed, +the firstboot hooks configured the application, and every crosswalk check +below passed. ## README crosswalk