diff --git a/.github/workflows/sphinxbuild.yml b/.github/workflows/sphinxbuild.yml
index 65059bec02c..62d1a21946c 100644
--- a/.github/workflows/sphinxbuild.yml
+++ b/.github/workflows/sphinxbuild.yml
@@ -59,6 +59,50 @@ jobs:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - name: Validate version constants in conf.py
+ if: github.ref == 'refs/heads/master' || github.base_ref == 'master'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ # version_stable: highest stableNN branch whose vN.0.0 release actually exists.
+ # Mirrors the is_version_released() check in build/build-index.php and stage-and-check.
+ # A branch that exists but has no release yet (e.g. RC phase) must be skipped.
+ highest_stable=""
+ for n in $(git ls-remote --heads origin \
+ | sed -n 's?.*refs/heads/stable\([0-9]\{2\}\)$?\1?p' | sort -n -r); do
+ repo=$( [ "$n" -ge 32 ] && echo "nextcloud-releases/server" || echo "nextcloud/server" )
+ status=$(curl -s -o /dev/null -w "%{http_code}" \
+ -H "Authorization: token ${GITHUB_TOKEN}" \
+ "https://api.github.com/repos/${repo}/releases/tags/v${n}.0.0")
+ if [ "$status" = "200" ]; then
+ highest_stable="$n"
+ break
+ fi
+ done
+
+ # version_start: lowest stableNN branch that still exists (branch existence = still documented).
+ lowest_stable=$(git ls-remote --heads origin \
+ | sed -n 's?.*refs/heads/stable\([0-9]\{2\}\)$?\1?p' \
+ | sort -n | head -1)
+
+ conf_stable=$(grep -m1 '^\s*version_stable\s*=' conf.py | grep -o '[0-9]\+')
+ conf_start=$(grep -m1 '^\s*version_start\s*=' conf.py | grep -o '[0-9]\+')
+
+ err=0
+ if [ -z "$highest_stable" ]; then
+ echo "::error::Could not detect any released stable branch."
+ exit 1
+ fi
+ if [ "$highest_stable" != "$conf_stable" ]; then
+ echo "::error::version_stable in conf.py ($conf_stable) != highest released stable ($highest_stable). Update conf.py."
+ err=1
+ fi
+ if [ "$lowest_stable" != "$conf_start" ]; then
+ echo "::error::version_start in conf.py ($conf_start) != lowest existing stable branch ($lowest_stable). Update conf.py."
+ err=1
+ fi
+ exit $err
+
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
@@ -237,12 +281,17 @@ jobs:
- name: Compute PDF release version
id: pdf_version
+ shell: bash
run: |
- branch="${GITHUB_REF#refs/heads/}"
- if [[ "$branch" == stable* ]]; then
- echo "release=${branch#stable}" >> $GITHUB_OUTPUT
+ # For PRs use the target branch; for pushes use the current branch.
+ # This handles both stable33 direct pushes and backport/*/stable33 PRs.
+ branch="${GITHUB_BASE_REF:-${GITHUB_REF#refs/heads/}}"
+ if [[ "$branch" =~ ^stable([0-9]+)$ ]]; then
+ echo "release=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT
else
- echo "release=latest" >> $GITHUB_OUTPUT
+ # master: derive the dev version from conf.py.
+ version_stable=$(grep -m1 '^\s*version_stable\s*=' conf.py | grep -o '[0-9]\+')
+ echo "release=$((version_stable + 1))" >> $GITHUB_OUTPUT
fi
- name: Build pdf documentation
diff --git a/admin_manual/_templates/versions.html b/admin_manual/_templates/versions.html
index 70c5e22dee2..b8c514d113e 100644
--- a/admin_manual/_templates/versions.html
+++ b/admin_manual/_templates/versions.html
@@ -8,14 +8,14 @@
- ☁️ {{ _('Versions') }}
- {% for slug, url in versions|reverse %}
+ {% for slug, url, label in versions|reverse %}
-
- {{ slug }}
+ {{ label }}
{% endfor %}
diff --git a/admin_manual/conf.py b/admin_manual/conf.py
index aa7dab6953b..4cbe4abf438 100644
--- a/admin_manual/conf.py
+++ b/admin_manual/conf.py
@@ -20,7 +20,8 @@
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
-project = u'Nextcloud %s Administration Manual' % (version)
+project = u'Nextcloud %s Administration Manual' % (display_version)
+html_title = project
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
diff --git a/conf.py b/conf.py
index deec64e0ace..7b5962b2ce3 100644
--- a/conf.py
+++ b/conf.py
@@ -56,35 +56,61 @@
# disable including the reST sources in HTML builds (in _sources/) (default is True)
html_copy_source = False
+# building the versions list
+# Update version_start when the lowest stableNN branch is deleted (version goes EoL).
+# Update version_stable when a new NC release ships (highest stableNN branch added).
+version_start = 32 # oldest documented version
+
+ # latest released stable — CHANGING IT MUST RESULT IN A CHANGE OF THE SYMLINK ON THE LIVE SERVER
+version_stable = 34 # mapped to https://docs.nextcloud.com/server/stable/
+
+import re as _re
+# Detect stable branch version for display purposes.
+# For PRs: GITHUB_BASE_REF is the target branch (e.g. 'stable33').
+# For direct pushes: GITHUB_REF is 'refs/heads/stable33'.
+_base = os.environ.get('GITHUB_BASE_REF', '')
+_ref = os.environ.get('GITHUB_REF', '')
+_stable_ver = (
+ _re.match(r'^stable(\d+)$', _base)
+ or _re.match(r'^refs/heads/stable(\d+)$', _ref)
+)
+display_version = (
+ release if release != 'latest' # PDF/ePub builds (DOCS_RELEASE set)
+ else _stable_ver.group(1) if _stable_ver # stableNN branches and PRs targeting them
+ else str(version_stable + 1) # master
+)
+
+# Also search for "TODO ON RELEASE" in the rst files
+
# substitutions go here
rst_epilog = """
.. |version| replace:: %s
-""" % (release)
+""" % (display_version)
# Replace hardcoded /latest/ URLs in all .rst source files with the actual release
def replace_latest(app, docname, source):
if release != 'latest':
source[0] = source[0].replace('/server/latest/', '/server/%s/' % release)
-
+
def setup(app):
app.connect('source-read', replace_latest)
-
-# building the versions list
-version_start = 31 # THIS IS THE OLDEST SUPPORTED VERSION NUMBER
-
- # THIS IS THE VERSION THAT IS MAPPED TO https://docs.nextcloud.com/server/stable/
-version_stable = 32 # CHANGING IT MUST RESULT IN A CHANGE OF THE SYMLINK ON THE LIVE SERVER
-
-# Also search for "TODO ON RELEASE" in the rst files
-
def generateVersionsDocs(current_docs):
versions_doc = []
- for v in range(version_start, version_stable + 1):
+
+ # If viewing an unsupported (older than version_start) branch, prepend it so it
+ # appears last after the template's |reverse — e.g. "26 (unsupported)" at the bottom.
+ if _stable_ver:
+ branch_ver = int(_stable_ver.group(1))
+ if branch_ver < version_start:
+ url = 'https://docs.nextcloud.com/server/%s/%s' % (str(branch_ver), current_docs)
+ versions_doc.append((branch_ver, url, '%s (unsupported)' % branch_ver))
+
+ for v in range(version_start, version_stable):
url = 'https://docs.nextcloud.com/server/%s/%s' % (str(v), current_docs)
- versions_doc.append(tuple((v, url)))
- versions_doc.append(tuple(('stable', 'https://docs.nextcloud.com/server/%s/%s' % ('stable', current_docs))))
- versions_doc.append(tuple(('latest', 'https://docs.nextcloud.com/server/%s/%s' % ('latest', current_docs))))
+ versions_doc.append((v, url, str(v)))
+ versions_doc.append(('stable', 'https://docs.nextcloud.com/server/stable/%s' % current_docs, '%s (stable)' % version_stable))
+ versions_doc.append(('latest', 'https://docs.nextcloud.com/server/latest/%s' % current_docs, '%s (latest)' % str(version_stable + 1)))
return versions_doc
if version.isdigit():
@@ -93,7 +119,8 @@ def generateVersionsDocs(current_docs):
github_branch = 'master'
html_context = {
- 'current_version': version,
+ 'current_version': int(_stable_ver.group(1)) if _stable_ver else version,
+ 'display_version': display_version,
'READTHEDOCS': True,
# force github plugin
diff --git a/developer_manual/_templates/versions.html b/developer_manual/_templates/versions.html
index 70c5e22dee2..b8c514d113e 100644
--- a/developer_manual/_templates/versions.html
+++ b/developer_manual/_templates/versions.html
@@ -8,14 +8,14 @@
- ☁️ {{ _('Versions') }}
- {% for slug, url in versions|reverse %}
+ {% for slug, url, label in versions|reverse %}
-
- {{ slug }}
+ {{ label }}
{% endfor %}
diff --git a/developer_manual/conf.py b/developer_manual/conf.py
index 61d72760015..25071039ccf 100644
--- a/developer_manual/conf.py
+++ b/developer_manual/conf.py
@@ -22,7 +22,8 @@
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
-project = "Nextcloud %s Developer Manual" % (version)
+project = "Nextcloud %s Developer Manual" % (display_version)
+html_title = project
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
diff --git a/user_manual/_templates/versions.html b/user_manual/_templates/versions.html
index 78076e86268..39b7dda82db 100644
--- a/user_manual/_templates/versions.html
+++ b/user_manual/_templates/versions.html
@@ -83,14 +83,14 @@
- ☁️ {{ _('Versions') }}
- {% for slug, url in versions|reverse %}
+ {% for slug, url, label in versions|reverse %}
-
- {{ slug }}
+ {{ label }}
{% endfor %}
diff --git a/user_manual/conf.py b/user_manual/conf.py
index d0b1e0f99b6..9a6382e5d82 100644
--- a/user_manual/conf.py
+++ b/user_manual/conf.py
@@ -20,7 +20,8 @@
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
-project = u'Nextcloud %s User Manual' % (version)
+project = u'Nextcloud %s User Manual' % (display_version)
+html_title = project
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
@@ -55,7 +56,7 @@
## Markup
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-markup
# a substitution that will be included in every source file
-rst_epilog = '.. |version| replace:: %s' % version
+rst_epilog = '.. |version| replace:: %s' % display_version
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output