Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions .github/workflows/republish-pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Manually republish the Sparkle appcast to GitHub Pages.
#
# Use this when the Pages custom domain changes or when you need to redeploy
# without cutting a new release. It downloads the appcast.xml from the latest
# GitHub Release and publishes it to Pages with the configured CNAME.

name: Republish Pages

on:
workflow_dispatch:
inputs:
custom_domain:
description: "Custom domain for the CNAME file (e.g. updates.tabbyapp.dev)"
required: true
default: "updates.tabbyapp.dev"
type: string

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: false

jobs:
republish:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deploy.outputs.page_url }}
steps:
- name: Download appcast.xml from latest release
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail

# Find the latest release that has an appcast in its artifacts.
# The release workflow uploads appcast.xml inside the release-artifacts bundle,
# but it also publishes appcast.xml to Pages. We can reconstruct it from the
# release assets or fall back to the repo's existing Pages deployment.

# Try downloading from the latest release artifacts
LATEST_RUN=$(gh run list \
--repo "${{ github.repository }}" \
--workflow release.yml \
--status success \
--limit 1 \
--json databaseId \
--jq '.[0].databaseId')

if [ -n "$LATEST_RUN" ]; then
echo "Downloading appcast from release run $LATEST_RUN"
gh run download "$LATEST_RUN" \
--repo "${{ github.repository }}" \
--name release-artifacts \
--dir artifacts/ || true
fi

if [ -f artifacts/appcast.xml ]; then
echo "Found appcast.xml from release artifacts"
cp artifacts/appcast.xml appcast.xml
else
Comment on lines +62 to +65
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 The path check looks at artifacts/appcast.xml, but actions/upload-artifact@v4 preserves the workspace-relative directory structure when uploading. In release.yml, the file is staged at build/appcast.xml, so when gh run download --dir artifacts/ extracts the artifact, the file lands at artifacts/build/appcast.xml — not artifacts/appcast.xml. The condition will always be false and the workflow will always exit with the "No appcast.xml found" error.

Suggested change
if [ -f artifacts/appcast.xml ]; then
echo "Found appcast.xml from release artifacts"
cp artifacts/appcast.xml appcast.xml
else
if [ -f artifacts/build/appcast.xml ]; then
echo "Found appcast.xml from release artifacts"
cp artifacts/build/appcast.xml appcast.xml
else

Fix in Codex Fix in Claude Code

echo "::error::No appcast.xml found in latest release artifacts. Run a full release first."
exit 1
fi

- name: Prepare Pages artifact
run: |
set -euo pipefail
mkdir -p pages/Cotabby
cp appcast.xml pages/appcast.xml
cp appcast.xml pages/Cotabby/appcast.xml
Comment on lines +73 to +75
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 The subdirectory name Cotabby doesn't match the tabby subdirectory that release.yml writes (build/pages/tabby/appcast.xml). Running this workflow would produce a Pages deployment where https://updates.tabbyapp.dev/tabby/appcast.xml no longer exists and a new https://updates.tabbyapp.dev/Cotabby/appcast.xml appears instead — diverging from every prior release deployment.

Suggested change
mkdir -p pages/Cotabby
cp appcast.xml pages/appcast.xml
cp appcast.xml pages/Cotabby/appcast.xml
mkdir -p pages/tabby
cp appcast.xml pages/appcast.xml
cp appcast.xml pages/tabby/appcast.xml

Fix in Codex Fix in Claude Code

printf '%s\n' "${{ inputs.custom_domain }}" > pages/CNAME

- name: Configure GitHub Pages
uses: actions/configure-pages@v5

- name: Upload GitHub Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: pages

- name: Deploy GitHub Pages
id: deploy
uses: actions/deploy-pages@v4

- name: Summary
run: |
{
echo "### Pages Republished"
echo "| Field | Value |"
echo "|-------|-------|"
echo "| Custom domain | ${{ inputs.custom_domain }} |"
echo "| Appcast URL | https://${{ inputs.custom_domain }}/appcast.xml |"
echo "| Pages URL | ${{ steps.deploy.outputs.page_url }} |"
} >> "${GITHUB_STEP_SUMMARY}"
Comment on lines +90 to +99
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 security ${{ inputs.custom_domain }} is expanded directly into the shell script before Bash parses it. Any shell metacharacters in the input (e.g. a value containing "; rm …; #) would be executed. Pass the value through an environment variable instead so Bash never sees it as code.

Suggested change
- name: Summary
run: |
{
echo "### Pages Republished"
echo "| Field | Value |"
echo "|-------|-------|"
echo "| Custom domain | ${{ inputs.custom_domain }} |"
echo "| Appcast URL | https://${{ inputs.custom_domain }}/appcast.xml |"
echo "| Pages URL | ${{ steps.deploy.outputs.page_url }} |"
} >> "${GITHUB_STEP_SUMMARY}"
- name: Summary
env:
CUSTOM_DOMAIN: ${{ inputs.custom_domain }}
PAGES_URL: ${{ steps.deploy.outputs.page_url }}
run: |
{
echo "### Pages Republished"
echo "| Field | Value |"
echo "|-------|-------|"
echo "| Custom domain | ${CUSTOM_DOMAIN} |"
echo "| Appcast URL | https://${CUSTOM_DOMAIN}/appcast.xml |"
echo "| Pages URL | ${PAGES_URL} |"
} >> "${GITHUB_STEP_SUMMARY}"

Fix in Codex Fix in Claude Code

Loading