Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
22 changes: 22 additions & 0 deletions .github/workflows/pr-preview-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Build CA PR preview

on:
pull_request:
types: [opened, synchronize, reopened]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Build installable preview
run: ./scripts/build-pr-preview.sh "${{ github.event.pull_request.number }}" "${{ github.event.pull_request.head.sha }}"
- uses: actions/upload-artifact@v7
with:
name: ca-pr-preview
path: dist/pr-preview/
if-no-files-found: error
retention-days: 7
30 changes: 30 additions & 0 deletions .github/workflows/pr-preview-cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Remove CA PR preview

on:
pull_request_target:
types: [closed]

permissions:
contents: write

concurrency:
group: ca-pr-preview-publish

jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
ref: pr-previews
- name: Remove closed PR artifacts
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
test -d "pr/$PR_NUMBER" || exit 0
rm -rf "pr/$PR_NUMBER"
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
git add -A "pr/$PR_NUMBER"
git commit -m "preview: remove closed PR #$PR_NUMBER"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
git push origin pr-previews
94 changes: 94 additions & 0 deletions .github/workflows/pr-preview-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
name: Publish CA PR preview

on:
workflow_run:
workflows: [Build CA PR preview]
types: [completed]

permissions:
actions: read
contents: write
issues: write

concurrency:
group: ca-pr-preview-publish

jobs:
publish:
if: >-
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
ref: pr-previews
- uses: actions/download-artifact@v8
with:
name: ca-pr-preview
path: artifact
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Publish stable PR installer URL
id: publish
env:
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
PR_NUMBER="$(jq -r '.pr' artifact/preview.json)"
ARTIFACT_SHA="$(jq -r '.sha' artifact/preview.json)"
[[ "$PR_NUMBER" =~ ^[0-9]+$ ]]
test "$ARTIFACT_SHA" = "$HEAD_SHA"
echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
rm -rf "pr/$PR_NUMBER"
mkdir -p "pr/$PR_NUMBER"
cp artifact/* "pr/$PR_NUMBER/"
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
git add "pr/$PR_NUMBER"
git diff --cached --quiet && exit 0
git commit -m "preview: publish PR #$PR_NUMBER at ${HEAD_SHA:0:7}"
git push origin pr-previews
Comment thread
elibosley marked this conversation as resolved.
- name: Add installer link to pull request
uses: actions/github-script@v9
env:
PR_NUMBER: ${{ steps.publish.outputs.pr_number }}
with:
script: |
const issueNumber = Number(process.env.PR_NUMBER)
if (!Number.isInteger(issueNumber) || issueNumber < 1) {
throw new Error(`Invalid pull request number: ${process.env.PR_NUMBER}`)
}

const marker = '<!-- ca-pr-preview -->'
const installerUrl = `https://raw.githubusercontent.com/${context.repo.owner}/${context.repo.repo}/pr-previews/pr/${issueNumber}/community.applications.plg`
const body = `${marker}
### Community Applications preview

Install this PR build on a test server:

\`${installerUrl}\`

This is an unreleased test build. The same URL updates when new commits are pushed to this pull request.`

const comments = await github.paginate(github.rest.issues.listComments, {
...context.repo,
issue_number: issueNumber,
per_page: 100
})
const existing = comments.find(comment =>
comment.user?.type === 'Bot' && comment.body?.includes(marker)
)

if (existing) {
await github.rest.issues.updateComment({
...context.repo,
comment_id: existing.id,
body
})
} else {
await github.rest.issues.createComment({
...context.repo,
issue_number: issueNumber,
body
})
}
86 changes: 86 additions & 0 deletions scripts/build-pr-preview.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/env bash
# Build an installable Community Applications preview for a pull request.
set -euo pipefail

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
PR_NUMBER="${1:?usage: $0 <pr-number> <git-sha> [output-directory]}"
GIT_SHA="${2:?usage: $0 <pr-number> <git-sha> [output-directory]}"
OUTPUT_DIR="${3:-$ROOT/dist/pr-preview}"
SOURCE_DIR="$ROOT/source/community.applications"
PLUGIN_TEMPLATE="$ROOT/plugins/community.applications.plg"
SHORT_SHA="${GIT_SHA:0:7}"
VERSION="$(date -u +%Y.%m.%d)-pr${PR_NUMBER}-${SHORT_SHA}"
PACKAGE="community.applications-${VERSION}-x86_64-1.txz"
BASE_URL="https://raw.githubusercontent.com/unraid/community.applications/pr-previews/pr/${PR_NUMBER}"

if [[ ! "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
echo "PR number must be numeric: $PR_NUMBER" >&2
exit 1
fi
if [[ ! -d "$SOURCE_DIR" || ! -f "$PLUGIN_TEMPLATE" ]]; then
echo "Run this script from a complete community.applications checkout." >&2
exit 1
fi

rm -rf "$OUTPUT_DIR"
mkdir -p "$OUTPUT_DIR"
STAGING="$(mktemp -d -t ca-pr-preview.XXXXXX)"
trap 'rm -rf "$STAGING"' EXIT

COPYFILE_DISABLE=1 cp -R "$SOURCE_DIR/" "$STAGING/"
find "$STAGING" \( -name '.DS_Store' -o -name '._*' -o -name 'sftp-config.json' \) -delete
find "$STAGING" -name '.claude' -type d -prune -exec rm -rf {} + 2>/dev/null || true
chmod -R 0755 "$STAGING"

if tar --version 2>/dev/null | grep -q 'GNU tar'; then
tar -C "$STAGING" --owner=0 --group=0 --numeric-owner -cJf "$OUTPUT_DIR/$PACKAGE" .
else
COPYFILE_DISABLE=1 tar -C "$STAGING" --uid 0 --gid 0 --uname root --gname root -cJf "$OUTPUT_DIR/$PACKAGE" .
fi

if command -v md5sum >/dev/null 2>&1; then
MD5="$(md5sum "$OUTPUT_DIR/$PACKAGE" | awk '{print $1}')"
else
MD5="$(md5 -q "$OUTPUT_DIR/$PACKAGE")"
fi

python3 - "$PLUGIN_TEMPLATE" "$OUTPUT_DIR/community.applications.plg" "$VERSION" "$MD5" "$BASE_URL/$PACKAGE" "$BASE_URL/community.applications.plg" <<'PY'
from pathlib import Path
import re
import sys

source, destination, version, md5, package_url, plugin_url = sys.argv[1:]
text = Path(source).read_text()
replacements = {
"version": version,
"md5": md5,
"pluginURL": plugin_url,
}
for entity, value in replacements.items():
text, count = re.subn(
rf'(<!ENTITY\s+{entity}\s+")[^"]*(">)',
rf'\g<1>{value}\g<2>',
text,
count=1,
)
if count != 1:
raise SystemExit(f"could not replace {entity} entity")

text, count = re.subn(
r'(<FILE Name="/boot/config/plugins/&name;/&name;-&version;-x86_64-1\.txz" Run="upgradepkg --install-new --reinstall">\s*<URL>)[^<]*(</URL>)',
rf'\g<1>{package_url}\g<2>',
text,
count=1,
)
if count != 1:
raise SystemExit("could not replace package URL")

Path(destination).write_text(text)
PY

cat > "$OUTPUT_DIR/preview.json" <<EOF
{"pr":${PR_NUMBER},"sha":"${GIT_SHA}","version":"${VERSION}","package":"${PACKAGE}"}
EOF

echo "Built $OUTPUT_DIR/community.applications.plg"
echo "Installer: $BASE_URL/community.applications.plg"