From 9ab1e4b1195f5cfa60a52d2d8a757d30a881a7ad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?John=20Molakvo=C3=A6?=
<14975046+skjnldsv@users.noreply.github.com>
Date: Wed, 28 Oct 2026 20:04:19 +0100
Subject: [PATCH 1/3] fix(go.php): fix admin-sso and admin-theming-icons
targets
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
admin-sso pointed to configuration_server/sso_configuration.html, which
is not part of the docs. Point it to the OpenID Connect page, the only
SSO page we have.
The theming icons heading anchor is #icon-theming, not
#theming-of-icons.
Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: John Molakvoæ <14975046+skjnldsv@users.noreply.github.com>
---
go.php/index.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/go.php/index.html b/go.php/index.html
index 19c07750140..4dcf26f5928 100644
--- a/go.php/index.html
+++ b/go.php/index.html
@@ -55,10 +55,10 @@
Redirecting to documentation...
'admin-sharing-federated': '/admin_manual/configuration_files/federated_cloud_sharing_configuration.html',
'admin-source_install': '/admin_manual/installation/source_installation.html',
'admin-sse-legacy-format': '/admin_manual/configuration_files/encryption_migration.html',
- 'admin-sso': '/admin_manual/configuration_server/sso_configuration.html',
+ 'admin-sso': '/admin_manual/configuration_user/user_auth_oidc.html',
'admin-system-requirements': '/admin_manual/installation/system_requirements.html',
'admin-theming': '/admin_manual/configuration_server/theming.html',
- 'admin-theming-icons': '/admin_manual/configuration_server/theming.html#theming-of-icons',
+ 'admin-theming-icons': '/admin_manual/configuration_server/theming.html#icon-theming',
'admin-transactional-locking': '/admin_manual/configuration_files/files_locking_transactional.html',
'admin-trusted-domains': '/admin_manual/installation/installation_wizard.html#trusted-domains',
'admin-update': '/admin_manual/maintenance/update.html',
From 0b1286b1b98cf5110d5b666cf17e44316da47fbf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?John=20Molakvo=C3=A6?=
<14975046+skjnldsv@users.noreply.github.com>
Date: Wed, 28 Oct 2026 20:04:20 +0100
Subject: [PATCH 2/3] ci: check go.php redirect targets against the master
build
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Every target must start with / (go.php appends it to the version path),
the page must exist in the build and its anchor must be an id on the
page. Runs in the link-check job, on master builds only, since go.php is
only deployed from master.
Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: John Molakvoæ <14975046+skjnldsv@users.noreply.github.com>
---
.github/workflows/sphinxbuild.yml | 19 ++++++++
build/check-go-php-redirects.sh | 43 +++++++++++++++++
build/tests/test-check-go-php-redirects.sh | 55 ++++++++++++++++++++++
3 files changed, 117 insertions(+)
create mode 100755 build/check-go-php-redirects.sh
create mode 100644 build/tests/test-check-go-php-redirects.sh
diff --git a/.github/workflows/sphinxbuild.yml b/.github/workflows/sphinxbuild.yml
index c5f29012599..50d8680cdc4 100644
--- a/.github/workflows/sphinxbuild.yml
+++ b/.github/workflows/sphinxbuild.yml
@@ -46,6 +46,25 @@ jobs:
name: ${{ needs.build.outputs.artifact_name }}
path: stage/
+ # go.php is only deployed from master, so only check it against the master build
+ - name: Checkout go.php and its check
+ if: needs.build.outputs.branch_name == 'latest'
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ persist-credentials: false
+ path: repo
+ sparse-checkout: |
+ go.php/index.html
+ build/check-go-php-redirects.sh
+ build/tests/test-check-go-php-redirects.sh
+ sparse-checkout-cone-mode: false
+
+ - name: Check go.php redirect targets
+ if: needs.build.outputs.branch_name == 'latest'
+ run: |
+ bash repo/build/tests/test-check-go-php-redirects.sh
+ repo/build/check-go-php-redirects.sh repo/go.php/index.html "stage/${{ needs.build.outputs.branch_name }}"
+
- name: Strip canonical links from validation HTML
run: |
find "stage/${{ needs.build.outputs.branch_name }}" -name '*.html' -print0 | while IFS= read -r -d '' f; do
diff --git a/build/check-go-php-redirects.sh b/build/check-go-php-redirects.sh
new file mode 100755
index 00000000000..8428500aae3
--- /dev/null
+++ b/build/check-go-php-redirects.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+#
+# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
+#
+# Checks every go.php redirect target against a built documentation folder:
+# the target must start with "/" (go.php appends it to the version path), the
+# page must exist, and its #anchor, if any, must be an id on that page.
+#
+# Usage: build/check-go-php-redirects.sh go.php/index.html stage/latest
+
+set -euo pipefail
+
+go_php="$1"
+build_dir="$2"
+failed=0
+
+fail() {
+ echo "::error file=${go_php}::${1}"
+ failed=1
+}
+
+while read -r key target; do
+ if [[ "$target" != /* ]]; then
+ fail "'${key}': '${target}' must start with /"
+ continue
+ fi
+
+ path="${build_dir}${target%%#*}"
+ if [[ -d "$path" ]]; then
+ path="${path%/}/index.html"
+ fi
+ if [[ ! -f "$path" ]]; then
+ fail "'${key}': ${target%%#*} does not exist in the build"
+ continue
+ fi
+
+ if [[ "$target" == *#* ]] && ! grep -qF "id=\"${target#*#}\"" "$path"; then
+ fail "'${key}': anchor #${target#*#} not found in ${target%%#*}"
+ fi
+done < <(sed -nE "s/^[[:space:]]*'([a-z0-9-]+)': '([^']*)',?[[:space:]]*$/\1 \2/p" "$go_php")
+
+exit "$failed"
diff --git a/build/tests/test-check-go-php-redirects.sh b/build/tests/test-check-go-php-redirects.sh
new file mode 100644
index 00000000000..ebad304017a
--- /dev/null
+++ b/build/tests/test-check-go-php-redirects.sh
@@ -0,0 +1,55 @@
+#!/usr/bin/env bash
+# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
+#
+# Tests for build/check-go-php-redirects.sh. Run: bash build/tests/test-check-go-php-redirects.sh
+
+set -euo pipefail
+
+check="$(dirname "${BASH_SOURCE[0]}")/../check-go-php-redirects.sh"
+tmp=$(mktemp -d)
+trap 'rm -rf "$tmp"' EXIT
+
+failures=0
+
+mkdir -p "$tmp/build/admin_manual/ai"
+echo '' > "$tmp/build/admin_manual/index.html"
+echo '' > "$tmp/build/admin_manual/ai/overview.html"
+
+# Runs the check on a go.php holding the given mapping lines, prints exit code and errors
+run_check() {
+ printf ' const mapping = {\n' > "$tmp/go.html"
+ printf ' %s\n' "$@" >> "$tmp/go.html"
+ printf ' }\n' >> "$tmp/go.html"
+ local output status=0
+ output=$(bash "$check" "$tmp/go.html" "$tmp/build") || status=$?
+ echo "$status ${output//"$tmp/go.html"/go.html}"
+}
+
+assert_eq() {
+ if [ "$2" = "$3" ]; then
+ echo "ok - $1"
+ else
+ echo "not ok - $1"
+ printf ' expected: %s\n actual: %s\n' "$2" "$3"
+ failures=$((failures + 1))
+ fi
+}
+
+assert_eq "valid page, directory and anchor targets pass" "0 " "$(run_check \
+ "'admin': '/admin_manual'," \
+ "'admin-ai-pickup-speed': '/admin_manual/ai/overview.html#improve-ai-task-pickup-speed'")"
+
+assert_eq "target without leading slash fails" \
+ "1 ::error file=go.html::'admin-ai-pickup-speed': 'admin_manual/ai/overview.html' must start with /" \
+ "$(run_check "'admin-ai-pickup-speed': 'admin_manual/ai/overview.html',")"
+
+assert_eq "missing page fails" \
+ "1 ::error file=go.html::'admin-sso': /admin_manual/sso.html does not exist in the build" \
+ "$(run_check "'admin-sso': '/admin_manual/sso.html',")"
+
+assert_eq "missing anchor fails" \
+ "1 ::error file=go.html::'admin-ai': anchor #gone not found in /admin_manual/ai/overview.html" \
+ "$(run_check "'admin-ai': '/admin_manual/ai/overview.html#gone',")"
+
+exit $((failures > 0))
From 6a5471e74c7f3b95f5a4be74111cdf73bfdbdc2f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?John=20Molakvo=C3=A6?=
<14975046+skjnldsv@users.noreply.github.com>
Date: Thu, 29 Oct 2026 02:39:18 +0100
Subject: [PATCH 3/3] ci: check user manual go.php targets in English
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
go.php replaces the second user_manual segment with the reader's
language, and the staged build has no user_manual/index.html (the
deploy adds it). Check those targets the same way, against en.
Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: John Molakvoæ <14975046+skjnldsv@users.noreply.github.com>
---
build/check-go-php-redirects.sh | 12 +++++++++---
build/tests/test-check-go-php-redirects.sh | 12 +++++++++++-
2 files changed, 20 insertions(+), 4 deletions(-)
diff --git a/build/check-go-php-redirects.sh b/build/check-go-php-redirects.sh
index 8428500aae3..d99a386d02e 100755
--- a/build/check-go-php-redirects.sh
+++ b/build/check-go-php-redirects.sh
@@ -26,17 +26,23 @@ while read -r key target; do
continue
fi
- path="${build_dir}${target%%#*}"
+ page="${target%%#*}"
+ # Like go.php, put the language in the second user_manual segment, English here
+ if [[ "$page" =~ ^/user_manual(/[^/]*)?(/.*)?$ ]]; then
+ page="/user_manual/en${BASH_REMATCH[2]}"
+ fi
+
+ path="${build_dir}${page}"
if [[ -d "$path" ]]; then
path="${path%/}/index.html"
fi
if [[ ! -f "$path" ]]; then
- fail "'${key}': ${target%%#*} does not exist in the build"
+ fail "'${key}': ${page} does not exist in the build"
continue
fi
if [[ "$target" == *#* ]] && ! grep -qF "id=\"${target#*#}\"" "$path"; then
- fail "'${key}': anchor #${target#*#} not found in ${target%%#*}"
+ fail "'${key}': anchor #${target#*#} not found in ${page}"
fi
done < <(sed -nE "s/^[[:space:]]*'([a-z0-9-]+)': '([^']*)',?[[:space:]]*$/\1 \2/p" "$go_php")
diff --git a/build/tests/test-check-go-php-redirects.sh b/build/tests/test-check-go-php-redirects.sh
index ebad304017a..ec92aeab666 100644
--- a/build/tests/test-check-go-php-redirects.sh
+++ b/build/tests/test-check-go-php-redirects.sh
@@ -12,8 +12,10 @@ trap 'rm -rf "$tmp"' EXIT
failures=0
-mkdir -p "$tmp/build/admin_manual/ai"
+mkdir -p "$tmp/build/admin_manual/ai" "$tmp/build/user_manual/en/files"
echo '' > "$tmp/build/admin_manual/index.html"
+echo '' > "$tmp/build/user_manual/en/index.html"
+echo '' > "$tmp/build/user_manual/en/files/access_webdav.html"
echo '' > "$tmp/build/admin_manual/ai/overview.html"
# Runs the check on a go.php holding the given mapping lines, prints exit code and errors
@@ -40,6 +42,14 @@ assert_eq "valid page, directory and anchor targets pass" "0 " "$(run_check \
"'admin': '/admin_manual'," \
"'admin-ai-pickup-speed': '/admin_manual/ai/overview.html#improve-ai-task-pickup-speed'")"
+assert_eq "user manual targets are checked in English, like go.php redirects them" "0 " "$(run_check \
+ "'user': '/user_manual'," \
+ "'user-webdav': '/user_manual/de/files/access_webdav.html',")"
+
+assert_eq "user manual target without a language segment fails" \
+ "1 ::error file=go.html::'user-webdav': /user_manual/en/access_webdav.html does not exist in the build" \
+ "$(run_check "'user-webdav': '/user_manual/files/access_webdav.html',")"
+
assert_eq "target without leading slash fails" \
"1 ::error file=go.html::'admin-ai-pickup-speed': 'admin_manual/ai/overview.html' must start with /" \
"$(run_check "'admin-ai-pickup-speed': 'admin_manual/ai/overview.html',")"