From 1f478bccb21382a781ffad1e7ab3f7e47d6c8e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E5=BF=97=E7=BF=94?= Date: Fri, 21 Feb 2025 23:43:57 +0800 Subject: [PATCH 1/2] deploy: improve support for external HTTPS The official `git-scm.com` website is currently built by Hugo with `--baseURL http://git-scm.com/`. This commit allows changing the URL scheme to `https`, in order to avoid unnecessary redirects between HTTPS and unencrypted HTTP. Such redirects occur when one of the following URLs is requested: * URLs in `layouts/alias.html`, e.g. in * Image URLs in * Endpoint URLs in Since these URLs have the `http` scheme, the client is supposed to send the new request unencrypted, only to be told by the server to use HTTPS again. Modern web browsers tend to stick with HTTPS in certain cases, so the downgrade may not always happen, but it's better to eliminate the redirects nevertheless, for security and performance reasons. Instead of trying to use relative URLs everywhere, this commit takes a simpler approach by using the `https` scheme in the base URL. One way to do this is to enable the "Enforce HTTPS" option in the GitHub Pages settings, but it's infeasible because the `git-scm.com` domain points to Cloudflare instead of GitHub. Therefore, this commit introduces a GitHub Actions variable, `EXTERNAL_HTTPS`, which can be set to true if HTTPS is provided by a third party, so that the URL scheme can be safely overridden. This also generalizes the special case of `git-scm.com` for any domain with a similar setup, allowing tests to be run more reliably in a uniform way. See-also: c22a1a5f (deploy(playwright): work around externally-enforced HTTPS, 2024-10-07) See-also: d4f88c1a (deploy: be more careful when auto-upgrading from HTTP -> HTTPS, 2024-10-07) --- .../actions/deploy-to-github-pages/action.yml | 49 +++++++++++++------ .github/workflows/deploy.yml | 1 + ARCHITECTURE.md | 10 ++++ playwright.config.js | 4 ++ 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/.github/actions/deploy-to-github-pages/action.yml b/.github/actions/deploy-to-github-pages/action.yml index d11464ed94..b0ee7c6eb7 100644 --- a/.github/actions/deploy-to-github-pages/action.yml +++ b/.github/actions/deploy-to-github-pages/action.yml @@ -20,6 +20,10 @@ inputs: cloudflare-zone: description: The Cloudflare zone to purge. required: false + external-https: + description: Whether HTTPS is set up externally, e.g. on Cloudflare instead of GitHub. + default: false + required: false outputs: url: description: The URL to which the site was deployed @@ -41,6 +45,15 @@ runs: id: pages uses: actions/configure-pages@v5 + - name: set base URL + shell: bash + run: | + base_url="${{ steps.pages.outputs.base_url }}" + if [ "${{ inputs.external-https }}" = true ] ; then + base_url="$(echo "$base_url" | sed 's/^http:/https:/')" + fi + echo "base_url=$base_url" >>"$GITHUB_ENV" + - name: configure Hugo and Pagefind version shell: bash run: | @@ -59,7 +72,7 @@ runs: env: HUGO_RELATIVEURLS: false shell: bash - run: hugo config && hugo --baseURL "${{ steps.pages.outputs.base_url }}/" + run: hugo config && hugo --baseURL "${{ env.base_url }}/" - name: run Pagefind ${{ env.PAGEFIND_VERSION }} to build the search index shell: bash @@ -110,7 +123,6 @@ runs: id: remap shell: bash run: | - base_url='${{ steps.pages.outputs.base_url }}' echo "result=$(echo "$base_url" | sed 's|^\(.*\)\(/git-scm\.com\)$|(\1)?\2(.*)|') file://$PWD/public\$2" \ >>$GITHUB_OUTPUT @@ -120,6 +132,17 @@ runs: sed -n 's|^\(https\?:\/\/.*\)\(/git-scm\.com\)$|--remap '\''(\1.*) file://../$1'\''|p')" \ >>$GITHUB_OUTPUT + - name: check for downgrades to unencrypted HTTP + if: startsWith(env.base_url, 'https://') + shell: bash + # The `--require-https` option of lychee could come in handy, + # but it also complains about `http://` links to other sites, + # and (more importantly) doesn't work in offline mode. + # A simple `grep` should work without any false positives, + # unless git-scm.com mentions the base URL of one of its forks, + # which is unlikely. + run: '! grep -FInr "http:${base_url#https:}" public' + - name: check for broken links id: lychee uses: lycheeverse/lychee-action@v2 @@ -127,7 +150,7 @@ runs: args: >- --offline --fallback-extensions html - --base '${{ steps.pages.outputs.base_url }}' + --base '${{ env.base_url }}' --remap '${{ steps.remap.outputs.result }}' ${{ steps.remap.outputs.remap-dotdot }} --exclude file:///path/to/repo.git/ @@ -192,23 +215,21 @@ runs: - name: Install @playwright/test shell: bash run: npm install @playwright/test - - name: Edit /etc/hosts to map git-scm.com to GitHub + - name: Edit /etc/hosts to map the deployed domain to GitHub shell: bash - # This side-steps the Cloudflare caches - run: sudo sh -c 'echo "185.199.108.153 git-scm.com" >>/etc/hosts' + # This side-steps any caches that might be configured on the domain, + # and works even when the real server is not reachable from GitHub. + run: | + host="$(echo "$base_url" | sed -E 's|^https?://([^:/]+).*|\1|')" + sudo sh -c "echo '185.199.108.153 $host' >>/etc/hosts" - name: Run Playwright tests shell: bash id: playwright env: - PLAYWRIGHT_TEST_URL: ${{ steps.pages.outputs.base_url }} - run: | + PLAYWRIGHT_TEST_URL: ${{ env.base_url }} # avoid test failures when HTTPS is enforced half-way through - case "$PLAYWRIGHT_TEST_URL" in - https://*|http://git-scm.com) ;; # okay, leave as-is - http://*) PLAYWRIGHT_TEST_URL="https://${PLAYWRIGHT_TEST_URL#http://}";; - esac && - echo "result=$PLAYWRIGHT_TEST_URL" >>$GITHUB_OUTPUT && - npx playwright test --project=chrome + PLAYWRIGHT_EXTERNAL_HTTPS: ${{ inputs.external-https }} + run: npx playwright test --project=chrome - uses: actions/upload-artifact@v4 if: always() && steps.playwright.outputs.result != '' with: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 02bdb826af..de8b94d79b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -27,3 +27,4 @@ jobs: with: cloudflare-token: ${{ secrets.CLOUDFLARE_TOKEN }} cloudflare-zone: ${{ secrets.CLOUDFLARE_ZONE }} + external-https: ${{ vars.EXTERNAL_HTTPS }} diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 884896b320..45ecd6b99d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -34,6 +34,16 @@ With this change, the site can be tested in the fork by pushing to the `gh-pages` branch (which will trigger the `deploy.yml` workflow) and then navigating to https://git-scm..github.io/. +If the site will be deployed to a custom domain that supports HTTPS, +but the "[Enforce HTTPS]" option cannot be enabled in the GitHub Pages settings +(this can happen if the domain points to a third-party gateway), +the [GitHub Actions variable] `EXTERNAL_HTTPS` should be set to `true`, +so that the site can be built with a proper HTTPS base URL. +The official `git-scm.com` domain is an example of such a setup. + +[Enforce HTTPS]: https://docs.github.com/en/pages/getting-started-with-github-pages/securing-your-github-pages-site-with-https#enforcing-https-for-your-github-pages-site +[GitHub Actions variable]: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#defining-configuration-variables-for-multiple-workflows + ## Non-static parts While the site consists mostly of static content, there are a couple of diff --git a/playwright.config.js b/playwright.config.js index 3f67b94ac0..a5da5c5f7f 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -34,6 +34,10 @@ module.exports = defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', + + /* Ignore ERR_CERT_COMMON_NAME_INVALID when tesing against GitHub's server, + if the real certificate is hosted by a third party (e.g. Cloudflare). */ + ignoreHTTPSErrors: process.env.PLAYWRIGHT_EXTERNAL_HTTPS === 'true', }, /* Configure projects for major browsers */ From 9ad730a8fbd55207994fcffa6bdf0f3bea6bb27e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E5=BF=97=E7=BF=94?= Date: Fri, 21 Feb 2025 23:43:58 +0800 Subject: [PATCH 2/2] downloads: update download page URLs Since the download pages now live in `/downloads`, update the existing links to save an extra redirect. --- assets/js/application.js | 16 ++++++++-------- content/downloads/_index.html | 6 +++--- layouts/partials/site-root.html | 2 +- tests/git-scm.spec.js | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/assets/js/application.js b/assets/js/application.js index 0610b18cb5..0b7f0f54e9 100644 --- a/assets/js/application.js +++ b/assets/js/application.js @@ -56,27 +56,27 @@ var DownloadBox = { var os = window.session.browser.os; // Mac, Win, Linux if(os == "Mac") { $(".monitor").addClass("mac"); - $("#download-link").text("Download for Mac").attr("href", `${baseURLPrefix}download/mac`); + $("#download-link").text("Download for Mac").attr("href", `${baseURLPrefix}downloads/mac`); $("#gui-link").removeClass('mac').addClass('gui'); - $("#gui-link").text("Mac GUIs").attr("href", `${baseURLPrefix}download/gui/mac`); + $("#gui-link").text("Mac GUIs").attr("href", `${baseURLPrefix}downloads/guis?os=mac`); $("#gui-os-filter").attr('data-os', 'mac'); $("#gui-os-filter").text("Only show GUIs for my OS (Mac)") } else if (os == "Windows") { $(".monitor").addClass("windows"); - $("#download-link").text("Download for Windows").attr("href", `${baseURLPrefix}download/win`); + $("#download-link").text("Download for Windows").attr("href", `${baseURLPrefix}downloads/win`); $("#gui-link").removeClass('mac').addClass('gui'); - $("#gui-link").text("Windows GUIs").attr("href", `${baseURLPrefix}download/gui/windows`); + $("#gui-link").text("Windows GUIs").attr("href", `${baseURLPrefix}downloads/guis?os=windows`); $("#alt-link").removeClass("windows").addClass("mac"); - $("#alt-link").text("Mac Build").attr("href", `${baseURLPrefix}download/mac`); + $("#alt-link").text("Mac Build").attr("href", `${baseURLPrefix}downloads/mac`); $("#gui-os-filter").attr('data-os', 'windows'); $("#gui-os-filter").text("Only show GUIs for my OS (Windows)") } else if (os == "Linux") { $(".monitor").addClass("linux"); - $("#download-link").text("Download for Linux").attr("href", `${baseURLPrefix}download/linux`); + $("#download-link").text("Download for Linux").attr("href", `${baseURLPrefix}downloads/linux`); $("#gui-link").removeClass('mac').addClass('gui'); - $("#gui-link").text("Linux GUIs").attr("href", `${baseURLPrefix}download/gui/linux`); + $("#gui-link").text("Linux GUIs").attr("href", `${baseURLPrefix}downloads/guis?os=linux`); $("#alt-link").removeClass("windows").addClass("mac"); - $("#alt-link").text("Mac Build").attr("href", `${baseURLPrefix}download/mac`); + $("#alt-link").text("Mac Build").attr("href", `${baseURLPrefix}downloads/mac`); $("#gui-os-filter").attr('data-os', 'linux'); $("#gui-os-filter").text("Only show GUIs for my OS (Linux)") } else { diff --git a/content/downloads/_index.html b/content/downloads/_index.html index ff097fba5c..f683cb51db 100644 --- a/content/downloads/_index.html +++ b/content/downloads/_index.html @@ -14,15 +14,15 @@

Downloads

- }}" class="icon mac">macOS + }}" class="icon mac">macOS - }}" class="icon windows">Windows + }}" class="icon windows">Windows
- }}" class="icon linux">Linux/Unix + }}" class="icon linux">Linux/Unix
diff --git a/layouts/partials/site-root.html b/layouts/partials/site-root.html index 70f5ded911..01ce7be7bf 100644 --- a/layouts/partials/site-root.html +++ b/layouts/partials/site-root.html @@ -50,7 +50,7 @@

Community

Tarballs - Windows Build + Windows Build Source Code diff --git a/tests/git-scm.spec.js b/tests/git-scm.spec.js index f47f98792b..234bb4bcb1 100644 --- a/tests/git-scm.spec.js +++ b/tests/git-scm.spec.js @@ -59,7 +59,7 @@ test.describe('Windows', () => { await expect(page.getByRole('link', { name: 'Graphical UIs' })).toBeHidden() const windowsGUIs = page.getByRole('link', { name: 'Windows GUIs' }) await expect(windowsGUIs).toBeVisible() - await expect(windowsGUIs).toHaveAttribute('href', /\/download\/gui\/windows$/) + await expect(windowsGUIs).toHaveAttribute('href', /\/downloads\/guis\?os=windows$/) // navigate to Windows GUIs await windowsGUIs.click()