From 12853260f4fb823bdf10e6560864bc30c354b830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <323649642+oc-tmueller@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:06:02 +0200 Subject: [PATCH 1/2] docs: sync config.sample.php parameters for 10.15 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The same staleness just fixed for 10.16 in #138: core added 'web-updater.enabled' to config/config.sample.php in owncloud/core#41385, which is present on the 10.15 branch too, and the generated page was never regenerated. Regenerated with config-to-docs from core's 10.15 branch. The diff is the one missing section, byte-identical to the block #138 added for 10.16, and the hand-written header above the "header end" marker is untouched. With this, all three published server versions regenerate to an empty diff against their core branch, which is the precondition for checking that automatically rather than discovering drift years later. The same caveat as #138 applies: the pre-existing 'upgrade.disable-web' comment a few lines above still claims the web updater is enabled by default, which owncloud/core#41385 invalidated. That wants fixing in core, not here. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Thomas Müller <323649642+oc-tmueller@users.noreply.github.com> --- .../server/config_sample_php_parameters.adoc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/content/server/10.15/modules/admin_manual/pages/configuration/server/config_sample_php_parameters.adoc b/content/server/10.15/modules/admin_manual/pages/configuration/server/config_sample_php_parameters.adoc index a307140..1abdb1d 100644 --- a/content/server/10.15/modules/admin_manual/pages/configuration/server/config_sample_php_parameters.adoc +++ b/content/server/10.15/modules/admin_manual/pages/configuration/server/config_sample_php_parameters.adoc @@ -2310,6 +2310,16 @@ The web based updater is enabled by default. 'upgrade.disable-web' => false, .... +=== Explicitly enable the web updater - used by /updater/ +By default, it is disabled. + +==== Code Sample + +[source,php] +.... +'web-updater.enabled' => false, +.... + === Define whether to enable automatic update of market apps Set to `false` to disable. From 801e1c2389ba9a7f8d525466e417a700fce15ca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <323649642+oc-tmueller@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:20:57 +0200 Subject: [PATCH 2/2] ci: fail when the generated config pages drift from core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two config parameter pages per server version are generated by config-to-docs from core's config/config.sample.php and config.apps.sample.php. Everything below the "header end do not delete or edit this line" marker is machine output. Nothing enforced that, so a hand edit there looked correct, rendered correctly, passed CI, and would have been destroyed the next time anyone regenerated - and drift in the other direction went unnoticed for two releases, which is what #138 and #142 just repaired. This regenerates every version from its core branch and fails on any difference, so the pages are provably generator output. Nothing here infers success from an exit status, because convert.php exits 0 on both of its own failure paths - unreadable input and unwritable output. Every file is checked before use and the pages actually regenerated are counted and asserted. Without that, a sample renamed in core would leave the pages untouched, the comparison would find nothing, and the job would pass having checked nothing at all - which is precisely the failure this workflow exists to catch, one level up. The comparison snapshots each page before regenerating and then plain-diffs the two, rather than asking git. The pages are just files, and git inside this container cannot see the workspace repository even though actions/checkout populated it with git - which is worth knowing, but not worth depending on. Other choices worth stating: The loop is driven by the content/server/* directories themselves rather than a version list, so a new server version cannot be added and silently left unchecked. The core branch is resolved by name, falling back to master only for the version still in development - and that fallback is verified against master's own OC_VersionString, so a directory that matches no core branch fails loudly instead of being regenerated from the wrong source. git ls-remote's "no such branch" (exit 2) is distinguished from every other failure. Conflating them would let a transient network error resolve 10.16 to master and then fail the comparison for a baffling reason. It is deliberately not path-filtered: a required check that is skipped reports as perpetually pending and blocks merges, the trap already documented at length in lint-pr-title.yml. The cost is that drift caused by a core commit surfaces on an unrelated author's PR, so there is also a nightly run to find it first. PHP comes from owncloudci/php:7.4 as a job container rather than a third-party setup action, per the Actions policy in AGENTS.md. Inside a container the default shell is sh, so bash is requested explicitly. The converter is installed with `composer install`, not `update`: its comment parsing lives in phpdocumentor/reflection-docblock, accepted as ^4.3.0 but pinned in the lock, and floating it can change the generated output. The converter checkout itself is deliberately unpinned - the question being asked is whether the pages match what a maintainer running ctd.sh today would produce. On failure the explanation and the full diff go to the job summary, and the regenerated pages are uploaded as an artifact, so the fix can be downloaded rather than copy-pasted out of a log. Verified by running the job's own script in that container against four cases: an in-sync tree passes and reports 6 pages across 3 versions; a hand edit below the marker fails with the diff; an edit above the marker still passes, because the generator preserves that region by design; and a version directory with no corresponding core branch fails naming the mismatch. Stacked on #142 - the check only goes green once 10.15 is in sync. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Thomas Müller <323649642+oc-tmueller@users.noreply.github.com> --- .github/workflows/config-docs-in-sync.yml | 227 ++++++++++++++++++++++ .gitignore | 5 + 2 files changed, 232 insertions(+) create mode 100644 .github/workflows/config-docs-in-sync.yml diff --git a/.github/workflows/config-docs-in-sync.yml b/.github/workflows/config-docs-in-sync.yml new file mode 100644 index 0000000..89a3796 --- /dev/null +++ b/.github/workflows/config-docs-in-sync.yml @@ -0,0 +1,227 @@ +name: Config Docs In Sync + +# The two config parameter pages per server version are generated by +# owncloud/config-to-docs from owncloud/core's config/config.sample.php and +# config/config.apps.sample.php. Everything below the "header end" marker in +# those pages is machine output, and a hand edit there is silently destroyed the +# next time anyone regenerates. This job regenerates and fails on any +# difference, so the published pages are provably generator output and an edit +# in the wrong place is caught here instead of years later. +# +# Deliberately NOT path-filtered. A required check that is skipped reports as +# perpetually pending and blocks merges - the same trap documented at length in +# lint-pr-title.yml. The cost of that choice is that drift introduced by a *core* +# commit surfaces on the next docs PR, whoever opens it; the nightly run below +# exists so it is usually found before then. + +on: + push: + branches: [main] + pull_request: + schedule: + # Core can drift this repo without touching it, so look once a day. + - cron: '17 4 * * *' + workflow_dispatch: + +permissions: + contents: read + +env: + CORE_REPO: https://github.com/owncloud/core.git + +jobs: + config-docs-in-sync: + runs-on: ubuntu-latest + # owncloud-owned image, so no third-party action is needed to provide PHP. + container: owncloudci/php:7.4 + # Inside a container the default shell is sh, not bash. + defaults: + run: + shell: bash + concurrency: + group: config-docs-in-sync-${{ github.ref }} + cancel-in-progress: true + steps: + - name: Checkout docs + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Checkout config-to-docs + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: owncloud/config-to-docs + path: .config-to-docs + # Deliberately unpinned. This check asks "do the committed pages match + # what a maintainer running ctd.sh today would produce", so it has to + # track the current generator. The composer deps below are pinned + # because the lock file is that generator's own declared contract. + + - name: Install the converter + working-directory: .config-to-docs + # install, not update: the comment parsing lives in + # phpdocumentor/reflection-docblock, which composer.json accepts as + # ^4.3.0 while the lock pins a known-good version. Floating it can + # change the generated output. + run: composer install --no-interaction --no-progress --no-dev --no-scripts + + - name: Regenerate every version from its core branch + run: | + set -eu + + # convert.php exits 0 on both of its own failure paths - unreadable + # input, unwritable output - so nothing here may infer success from an + # exit status. Every file is checked before use, and the pages actually + # regenerated are counted and asserted at the end. Without that, a + # renamed sample in core would leave the pages untouched, the diff + # below would find nothing, and this job would pass having checked + # nothing at all. + versions=0 + generated=0 + + # Driven by the content directories themselves, so a new server + # version cannot be added and silently left unchecked. + for dir in content/server/*/; do + version=$(basename "$dir") + pages="${dir%/}/modules/admin_manual/pages/configuration/server" + + if [ ! -d "$pages" ]; then + echo "::notice::$version has no configuration/server directory, nothing to check" + continue + fi + + # git ls-remote exits 2 for "no such branch" and non-zero otherwise. + # Treating those alike would let a transient network failure resolve + # 10.16 to master and then fail the diff for a baffling reason. + rc=0 + git ls-remote --exit-code --heads "$CORE_REPO" "$version" >/dev/null 2>&1 || rc=$? + case "$rc" in + 0) ref="$version" ;; + 2) ref=master ;; + *) echo "::error::cannot reach owncloud/core to resolve a branch for $version (git ls-remote exit $rc)"; exit 1 ;; + esac + + echo "::group::$version (core $ref)" + rm -rf ".core-$version" + # blob:none matters: a plain --depth 1 --sparse clone still transfers + # the whole tree's blobs (~30 MB), this is ~1.5 MB. + git clone --quiet --depth 1 --branch "$ref" --filter=blob:none --sparse \ + "$CORE_REPO" ".core-$version" + # --no-cone with explicit paths, because cone mode cannot select a + # single root file and version.php is needed for the check below. + git -C ".core-$version" sparse-checkout set --no-cone /config/ /version.php + + # Only the version still in development legitimately has no branch of + # its own. Anything else falling back to master would be regenerated + # from the wrong source, so say so instead. + if [ "$ref" = master ]; then + core_version=$(sed -n 's/^$OC_VersionString = .\([0-9]*\.[0-9]*\).*/\1/p' ".core-$version/version.php") + if [ "$core_version" != "$version" ]; then + echo "::error::core has no $version branch and master is $core_version, so there is no source for content/server/$version" + exit 1 + fi + fi + + for pair in \ + "config.sample.php:config_sample_php_parameters.adoc" \ + "config.apps.sample.php:config_apps_sample_php_parameters.adoc"; do + sample=".core-$version/config/${pair%%:*}" + page="$pages/${pair#*:}" + + if [ ! -r "$sample" ]; then + echo "::error::$sample is missing in core $ref - the converter would report success and change nothing" + exit 1 + fi + # A page that exists in the other versions but not this one means + # either a deletion or a half-finished new version. If a version + # ever legitimately has only one, skip it explicitly rather than + # letting it pass unnoticed. + if [ ! -f "$page" ]; then + echo "::error::$page does not exist, so it cannot be verified" + exit 1 + fi + + # Keep the committed copy to compare against. The comparison + # deliberately does not use git: the pages are just files, and + # inside this container git cannot see the workspace repository. + mkdir -p "$RUNNER_TEMP/committed/$(dirname "$page")" + cp "$page" "$RUNNER_TEMP/committed/$page" + + php .config-to-docs/convert.php config:convert-adoc \ + --input-file="$sample" --output-file="$page" + generated=$((generated + 1)) + done + + rm -rf ".core-$version" + versions=$((versions + 1)) + echo "::endgroup::" + done + + if [ "$versions" -eq 0 ]; then + echo "::error::no server version was checked - has content/server/ moved?" + exit 1 + fi + expected=$((versions * 2)) + if [ "$generated" -ne "$expected" ]; then + echo "::error::regenerated $generated pages for $versions versions, expected $expected" + exit 1 + fi + echo "Regenerated $generated pages across $versions versions." + + - name: Fail if the committed pages differ from the generated output + run: | + set -eu + + # diff, not git diff: see the note in the previous step. Each snapshot + # taken before regeneration is compared with the page as it now stands, + # so the diff reads committed -> generated, i.e. what needs applying. + : > "$RUNNER_TEMP/drift.diff" + drift=0 + compared=0 + while IFS= read -r snapshot; do + page="${snapshot#"$RUNNER_TEMP"/committed/}" + compared=$((compared + 1)) + diff -u "$snapshot" "$page" \ + --label "a/$page" --label "b/$page" >> "$RUNNER_TEMP/drift.diff" || drift=1 + done < <(find "$RUNNER_TEMP/committed" -type f -name '*_sample_php_parameters.adoc' | sort) + + if [ "$compared" -eq 0 ]; then + echo "::error::no snapshots to compare - the previous step cannot have run" + exit 1 + fi + + if [ "$drift" -eq 0 ]; then + echo "All $compared config parameter pages match the generator output." + exit 0 + fi + + { + echo "### Config parameter pages are out of sync" + echo + echo "These pages are generated by [config-to-docs](https://github.com/owncloud/config-to-docs)" + echo "from \`config/config.sample.php\` and \`config/config.apps.sample.php\` in" + echo "[owncloud/core](https://github.com/owncloud/core). Everything below the" + echo "\`header end\` marker is machine output." + echo + echo "* **To document a config key**, change its comment in core, then regenerate." + echo "* **To change a page's introduction**, edit *above* the marker - that part is kept." + echo "* **If core changed and the page is merely stale**, commit the regenerated page." + echo + echo "To regenerate locally: clone core and config-to-docs, check out the core branch" + echo "matching the version, and run \`./ctd.sh\` in config-to-docs." + echo + echo '```diff' + cat "$RUNNER_TEMP/drift.diff" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + echo "::error::Committed config parameter pages differ from config-to-docs output - see the job summary for the diff and how to fix it." + grep -E '^(---|\+\+\+|Only in)' "$RUNNER_TEMP/drift.diff" || true + exit 1 + + - name: Upload the regenerated pages + # So the fix can be downloaded rather than copy-pasted out of a log. + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: regenerated-config-pages + path: content/server/*/modules/admin_manual/pages/configuration/server/config_*sample_php_parameters.adoc + if-no-files-found: ignore diff --git a/.gitignore b/.gitignore index e934dac..bac09ac 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,11 @@ docs/superpowers/ # (see the preantora/preantora-local npm scripts) ui/supplemental/js/vendor/ +# scratch checkouts made by the config-docs-in-sync workflow, and by anyone +# reproducing it locally: the converter and one core clone per server version +.config-to-docs/ +.core-*/ + # macOS and IDEs .DS_Store .vscode/