ci: rework Discord lifecycle + migration workflows

- Merge defer-discord-thread.yml and delete-discord-thread.yml into
  create-ready-for-testing-message.yml (ready/lock/close jobs)
- Derive script slug from the "Name of the Script" issue field
  (lowercase + spaces->dashes), use issue-title case for display
- Fix slug mismatch (tr -d ' ' vs dashes) that broke delete_new_script
- move-to-main: PR title/body taken from the issue

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michel Roegl-Brunner
2026-06-02 11:26:31 +02:00
parent a942dd7282
commit 4385451b37
5 changed files with 215 additions and 154 deletions

View File

@@ -1,27 +1,47 @@
name: Create discord thread and comment on GitHub issue when script is ready for testing
name: Discord testing-thread lifecycle (ready / defer / close)
on:
issues:
types:
- labeled
- unlabeled
- closed
permissions:
issues: write
actions: write
jobs:
post_to_discord:
# ---------------------------------------------------------------------------
# Ready For Testing added -> create (or re-open) the Discord thread + comment
# ---------------------------------------------------------------------------
ready:
runs-on: ubuntu-latest
if: contains(github.event.issue.labels.*.name, 'Ready For Testing') && github.repository == 'community-scripts/ProxmoxVED'
if: |
github.repository == 'community-scripts/ProxmoxVED'
&& github.event.action == 'labeled'
&& github.event.label.name == 'Ready For Testing'
steps:
- name: Extract Issue Title and Script Type
- name: Extract Script Name and Type
id: extract_info
env:
ISSUE_BODY: ${{ github.event.issue.body }}
ISSUE_TITLE: ${{ github.event.issue.title }}
run: |
# Extract title (lowercase, spaces to dashes)
TITLE=$(echo '${{ github.event.issue.title }}' | tr '[:upper:]' '[:lower:]' | sed 's/ /-/g')
echo "TITLE=$TITLE" >> $GITHUB_ENV
# DISPLAY name: original issue-title capitalization (prose + Discord thread name)
echo "DISPLAY=$ISSUE_TITLE" >> $GITHUB_ENV
# SLUG: from the "Name of the Script" field in the issue body, normalized to the
# repo's lowercase + spaces->dashes convention (e.g. "Alpine Cinny" -> "alpine-cinny").
NAME_RAW=$(printf '%s\n' "$ISSUE_BODY" \
| sed -n '/^###[[:space:]]*Name of the Script/,/^###/p' \
| sed '1d;/^###/d;/^[[:space:]]*$/d' | head -n1)
if [ -z "$NAME_RAW" ]; then
# Fallback for issues not created from the template: use the title.
NAME_RAW="$ISSUE_TITLE"
fi
SLUG=$(echo "$NAME_RAW" | tr -d '\r' | tr '[:upper:]' '[:lower:]' | sed 's/[[:space:]]\+/-/g')
echo "SLUG=$SLUG" >> $GITHUB_ENV
# Extract script type from issue body (using env var to handle special chars)
if echo "$ISSUE_BODY" | grep -qi "CT (LXC Container)"; then
@@ -34,7 +54,7 @@ jobs:
SCRIPT_TYPE="pve"
else
# Fallback: detect by filename pattern or default to ct
if [[ "$TITLE" == *"-vm"* ]]; then
if [[ "$SLUG" == *"-vm"* ]]; then
SCRIPT_TYPE="vm"
else
SCRIPT_TYPE="ct"
@@ -42,7 +62,7 @@ jobs:
fi
echo "SCRIPT_TYPE=$SCRIPT_TYPE" >> $GITHUB_ENV
echo "Detected script type: $SCRIPT_TYPE for title: $TITLE"
echo "Detected script type: $SCRIPT_TYPE for slug: $SLUG (display: $DISPLAY)"
- name: Check if Files Exist in community-scripts/ProxmoxVED
id: check_files
@@ -51,33 +71,33 @@ jobs:
run: |
REPO="community-scripts/ProxmoxVED"
API_URL="https://api.github.com/repos/$REPO/contents"
TITLE="${{ env.TITLE }}"
SLUG="${{ env.SLUG }}"
SCRIPT_TYPE="${{ env.SCRIPT_TYPE }}"
# Define files based on script type
case "$SCRIPT_TYPE" in
ct)
FILES=(
"ct/${TITLE}.sh"
"install/${TITLE}-install.sh"
"json/${TITLE}.json"
"ct/${SLUG}.sh"
"install/${SLUG}-install.sh"
"json/${SLUG}.json"
)
;;
vm)
FILES=(
"vm/${TITLE}.sh"
"json/${TITLE}.json"
"vm/${SLUG}.sh"
"json/${SLUG}.json"
)
;;
addon)
FILES=(
"tools/addon/${TITLE}.sh"
"json/${TITLE}.json"
"tools/addon/${SLUG}.sh"
"json/${SLUG}.json"
)
;;
pve)
FILES=(
"tools/pve/${TITLE}.sh"
"tools/pve/${SLUG}.sh"
)
;;
esac
@@ -98,24 +118,25 @@ jobs:
- name: Create message to send
id: create_message
run: |
TITLE="${{ env.TITLE }}"
SLUG="${{ env.SLUG }}"
DISPLAY="${{ env.DISPLAY }}"
SCRIPT_TYPE="${{ env.SCRIPT_TYPE }}"
VAR="The ${TITLE} script is ready for testing:\n"
VAR="The ${DISPLAY} script is ready for testing:\n"
# Generate correct command based on script type
case "$SCRIPT_TYPE" in
ct)
VAR+="\`\`\`bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/ct/${TITLE}.sh)\"\`\`\`\n"
VAR+="\`\`\`bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/ct/${SLUG}.sh)\"\`\`\`\n"
;;
vm)
VAR+="\`\`\`bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/vm/${TITLE}.sh)\"\`\`\`\n"
VAR+="\`\`\`bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/vm/${SLUG}.sh)\"\`\`\`\n"
;;
addon)
VAR+="\`\`\`bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/tools/addon/${TITLE}.sh)\"\`\`\`\n"
VAR+="\`\`\`bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/tools/addon/${SLUG}.sh)\"\`\`\`\n"
;;
pve)
VAR+="\`\`\`bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/tools/pve/${TITLE}.sh)\"\`\`\`\n"
VAR+="\`\`\`bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/tools/pve/${SLUG}.sh)\"\`\`\`\n"
;;
esac
@@ -123,7 +144,7 @@ jobs:
JSON_FILE=""
case "$SCRIPT_TYPE" in
ct|vm|addon)
JSON_FILE="json/${TITLE}.json"
JSON_FILE="json/${SLUG}.json"
;;
esac
@@ -236,4 +257,124 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh workflow run push_json_to_pocketbase.yml --repo ${{ github.repository }} -f script_slug=${{ env.TITLE }}
gh workflow run push_json_to_pocketbase.yml --repo ${{ github.repository }} -f script_slug=${{ env.SLUG }}
# ---------------------------------------------------------------------------
# deferred added OR Ready For Testing removed -> lock the Discord thread.
# On the 'deferred' label only: also post a GitHub note and lock the issue.
# ---------------------------------------------------------------------------
lock:
runs-on: ubuntu-latest
if: |
github.repository == 'community-scripts/ProxmoxVED' && (
(github.event.action == 'labeled' && github.event.label.name == 'deferred') ||
(github.event.action == 'unlabeled' && github.event.label.name == 'Ready For Testing')
)
steps:
- name: Find Discord thread
id: find_thread
run: |
ISSUE_TITLE="${{ github.event.issue.title }}"
THREAD_ID=$(curl -s "https://discord.com/api/v10/guilds/${{ secrets.DISCORD_GUILD_ID }}/threads/active" \
-H "Authorization: Bot ${{ secrets.DISCORD_BOT_TOKEN }}" \
-H "Content-Type: application/json" | \
jq -r --arg TITLE "$ISSUE_TITLE" --arg PARENT_ID "${{ secrets.DISCORD_CHANNEL_ID }}" \
'.threads[] | select(.parent_id == $PARENT_ID and .name == ("Wanted Tester for " + $TITLE)) | .id')
if [ -n "$THREAD_ID" ]; then
echo "Found thread: $THREAD_ID"
echo "thread_id=$THREAD_ID" >> "$GITHUB_OUTPUT"
else
echo "No Discord thread found for: $ISSUE_TITLE"
fi
- name: Get last GitHub issue comment
if: steps.find_thread.outputs.thread_id != ''
id: last_comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
LAST=$(gh issue view ${{ github.event.issue.number }} \
--repo ${{ github.repository }} \
--comments \
--json comments \
--jq '.comments[-1].body // empty')
if [[ -z "$LAST" ]]; then
LAST=$(gh issue view ${{ github.event.issue.number }} \
--repo ${{ github.repository }} \
--json body \
--jq '.body')
fi
{
echo "last_comment<<EOF"
echo "$LAST"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Post deferral notice to Discord thread
if: steps.find_thread.outputs.thread_id != ''
env:
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}
THREAD_ID: ${{ steps.find_thread.outputs.thread_id }}
LAST_COMMENT: ${{ steps.last_comment.outputs.last_comment }}
ISSUE_URL: ${{ github.event.issue.html_url }}
run: |
MESSAGE="$(printf '⏸️ **This script has been deferred.**\n\nLast update from the issue:\n\n%s\n\n🔗 %s' "$LAST_COMMENT" "$ISSUE_URL")"
JSON_PAYLOAD=$(jq -n --arg content "$MESSAGE" '{content: $content}')
curl -s -X POST "https://discord.com/api/v10/channels/$THREAD_ID/messages" \
-H "Authorization: Bot $DISCORD_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD"
- name: Lock Discord thread
if: steps.find_thread.outputs.thread_id != ''
run: |
curl -s -X PATCH "https://discord.com/api/v10/channels/${{ steps.find_thread.outputs.thread_id }}" \
-H "Authorization: Bot ${{ secrets.DISCORD_BOT_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"locked": true}'
- name: Add deferral note to GitHub issue
if: github.event.action == 'labeled' && github.event.label.name == 'deferred'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh issue comment ${{ github.event.issue.number }} \
--repo ${{ github.repository }} \
--body "⏸️ This script has been **deferred**. The Discord testing thread has been locked. Re-add the \`Ready For Testing\` label to resume testing."
- name: Lock GitHub issue
if: github.event.action == 'labeled' && github.event.label.name == 'deferred'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh api --method PUT /repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/lock \
-f lock_reason=resolved
# ---------------------------------------------------------------------------
# Issue closed -> delete the Discord thread
# ---------------------------------------------------------------------------
close:
runs-on: ubuntu-latest
if: github.repository == 'community-scripts/ProxmoxVED' && github.event.action == 'closed'
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
steps:
- name: Get thread-ID and delete thread
run: |
THREAD_ID=$(curl -s -X GET "https://discord.com/api/v10/guilds/${{ secrets.DISCORD_GUILD_ID }}/threads/active" \
-H "Authorization: Bot ${{ secrets.DISCORD_BOT_TOKEN }}" \
-H "Content-Type: application/json" | \
jq -r --arg TITLE "$ISSUE_TITLE" --arg PARENT_ID "${{ secrets.DISCORD_CHANNEL_ID }}" \
'.threads[] | select(.parent_id == $PARENT_ID and .name == ("Wanted Tester for " + $TITLE)) | .id')
if [ -n "$THREAD_ID" ]; then
echo "Thread found: $THREAD_ID. Deleting..."
curl -X DELETE "https://discord.com/api/v10/channels/$THREAD_ID" \
-H "Authorization: Bot ${{ secrets.DISCORD_BOT_TOKEN }}"
else
echo "No thread found for issue: $ISSUE_TITLE"
fi

View File

@@ -1,92 +0,0 @@
name: Lock Discord Thread and GitHub Issue on Deferral
on:
issues:
types:
- labeled
- unlabeled
permissions:
issues: write
jobs:
defer_discord_thread:
runs-on: ubuntu-latest
if: |
github.repository == 'community-scripts/ProxmoxVED' && (
(github.event.action == 'labeled' && github.event.label.name == 'deferred') ||
(github.event.action == 'unlabeled' && github.event.label.name == 'Ready For Testing')
)
steps:
- name: Find Discord thread
id: find_thread
run: |
ISSUE_TITLE="${{ github.event.issue.title }}"
THREAD_ID=$(curl -s "https://discord.com/api/v10/guilds/${{ secrets.DISCORD_GUILD_ID }}/threads/active" \
-H "Authorization: Bot ${{ secrets.DISCORD_BOT_TOKEN }}" \
-H "Content-Type: application/json" | \
jq -r --arg TITLE "$ISSUE_TITLE" --arg PARENT_ID "${{ secrets.DISCORD_CHANNEL_ID }}" \
'.threads[] | select(.parent_id == $PARENT_ID and .name == ("Wanted Tester for " + $TITLE)) | .id')
if [ -n "$THREAD_ID" ]; then
echo "Found thread: $THREAD_ID"
echo "thread_id=$THREAD_ID" >> "$GITHUB_OUTPUT"
else
echo "No Discord thread found for: $ISSUE_TITLE"
fi
- name: Get last GitHub issue comment
if: steps.find_thread.outputs.thread_id != ''
id: last_comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
LAST=$(gh issue view ${{ github.event.issue.number }} \
--repo ${{ github.repository }} \
--comments \
--json comments \
--jq '.comments[-1].body // empty')
if [[ -z "$LAST" ]]; then
LAST=$(gh issue view ${{ github.event.issue.number }} \
--repo ${{ github.repository }} \
--json body \
--jq '.body')
fi
{
echo "last_comment<<EOF"
echo "$LAST"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Post deferral notice to Discord thread
if: steps.find_thread.outputs.thread_id != ''
env:
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}
THREAD_ID: ${{ steps.find_thread.outputs.thread_id }}
LAST_COMMENT: ${{ steps.last_comment.outputs.last_comment }}
ISSUE_URL: ${{ github.event.issue.html_url }}
run: |
MESSAGE="$(printf '⏸️ **This script has been deferred.**\n\nLast update from the issue:\n\n%s\n\n🔗 %s' "$LAST_COMMENT" "$ISSUE_URL")"
JSON_PAYLOAD=$(jq -n --arg content "$MESSAGE" '{content: $content}')
curl -s -X POST "https://discord.com/api/v10/channels/$THREAD_ID/messages" \
-H "Authorization: Bot $DISCORD_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD"
- name: Lock Discord thread
if: steps.find_thread.outputs.thread_id != ''
run: |
curl -s -X PATCH "https://discord.com/api/v10/channels/${{ steps.find_thread.outputs.thread_id }}" \
-H "Authorization: Bot ${{ secrets.DISCORD_BOT_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"locked": true}'
- name: Lock GitHub issue
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh api --method PUT /repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/lock \
-f lock_reason=resolved

View File

@@ -1,29 +0,0 @@
name: Delete Discord Thread on GitHub Issue Closure
on:
issues:
types:
- closed
jobs:
close_discord_thread:
if: github.repository == 'community-scripts/ProxmoxVED'
runs-on: ubuntu-latest
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
steps:
- name: Get thread-ID op and close thread
run: |
THREAD_ID=$(curl -s -X GET "https://discord.com/api/v10/guilds/${{ secrets.DISCORD_GUILD_ID }}/threads/active" \
-H "Authorization: Bot ${{ secrets.DISCORD_BOT_TOKEN }}" \
-H "Content-Type: application/json" | \
jq -r --arg TITLE "$ISSUE_TITLE" '.threads[] | select(.parent_id == "${{ secrets.DISCORD_CHANNEL_ID }}" and .name == ("Wanted Tester for " + $TITLE)) | .id')
if [ -n "$THREAD_ID" ]; then
echo "Thread found: $THREAD_ID. Archiving..."
curl -X DELETE "https://discord.com/api/v10/channels/$THREAD_ID" \
-H "Authorization: Bot ${{ secrets.DISCORD_BOT_TOKEN }}"
else
echo "No thread found for issue: $ISSUE_TITLE"
fi

View File

@@ -25,9 +25,23 @@ jobs:
- name: Extract Issue Info and Script Type
id: extract_info
env:
ISSUE_BODY: ${{ github.event.issue.body }}
ISSUE_TITLE: ${{ github.event.issue.title }}
run: |
TITLE=$(echo '${{ github.event.issue.title }}' | tr '[:upper:]' '[:lower:]' | tr -d ' ')
BODY='${{ github.event.issue.body }}'
BODY="$ISSUE_BODY"
# TITLE (slug): from the "Name of the Script" field in the issue body, normalized to
# the repo's lowercase + spaces->dashes convention (e.g. "Alpine Cinny" -> "alpine-cinny").
# This must match the slug move-to-main-repo.yaml used to create the files, otherwise the
# delete branch would not find them. Falls back to the title if the field is absent.
NAME_RAW=$(printf '%s\n' "$BODY" \
| sed -n '/^###[[:space:]]*Name of the Script/,/^###/p' \
| sed '1d;/^###/d;/^[[:space:]]*$/d' | head -n1)
if [ -z "$NAME_RAW" ]; then
NAME_RAW="$ISSUE_TITLE"
fi
TITLE=$(echo "$NAME_RAW" | tr -d '\r' | tr '[:upper:]' '[:lower:]' | sed 's/[[:space:]]\+/-/g')
echo "TITLE=$TITLE" >> $GITHUB_ENV

View File

@@ -60,11 +60,23 @@ jobs:
exit 1
fi
script_name=$(echo "$filtered_issue" | jq -r '.title' | tr '[:upper:]' '[:lower:]' | tr -d ' ')
issue_title=$(echo "$filtered_issue" | jq -r '.title')
issue_nr=$(echo "$filtered_issue" | jq -r '.number')
issue_body=$(echo "$filtered_issue" | jq -r '.body')
# script_name (slug): from the "Name of the Script" field in the issue body,
# normalized to the repo's lowercase + spaces->dashes convention
# (e.g. "Alpine Cinny" -> "alpine-cinny"). Falls back to the title if absent.
name_raw=$(printf '%s\n' "$issue_body" \
| sed -n '/^###[[:space:]]*Name of the Script/,/^###/p' \
| sed '1d;/^###/d;/^[[:space:]]*$/d' | head -n1)
if [ -z "$name_raw" ]; then
name_raw="$issue_title"
fi
script_name=$(echo "$name_raw" | tr -d '\r' | tr '[:upper:]' '[:lower:]' | sed 's/[[:space:]]\+/-/g')
echo "Script Name: $script_name"
echo "Issue Title: $issue_title"
echo "Issue Number: $issue_nr"
# Detect script type from issue body
@@ -90,6 +102,12 @@ jobs:
echo "script_name=$script_name" >> $GITHUB_OUTPUT
echo "issue_nr=$issue_nr" >> $GITHUB_OUTPUT
echo "script_type=$script_type" >> $GITHUB_OUTPUT
echo "issue_title=$issue_title" >> $GITHUB_OUTPUT
{
echo "issue_body<<ISSUE_BODY_EOF"
echo "$issue_body"
echo "ISSUE_BODY_EOF"
} >> $GITHUB_OUTPUT
- name: Check if script files exist
id: check_files
@@ -254,15 +272,24 @@ jobs:
id: create_pull_request
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
ISSUE_TITLE: ${{ steps.list_issues.outputs.issue_title }}
ISSUE_BODY: ${{ steps.list_issues.outputs.issue_body }}
ISSUE_NR: ${{ steps.list_issues.outputs.issue_nr }}
run: |
script_name="${{ steps.list_issues.outputs.script_name }}"
script_type="${{ steps.list_issues.outputs.script_type }}"
# PR body = original issue content + automated-migration footer
printf '%s\n' "$ISSUE_BODY" > pr_body.md
{
echo ""
echo "---"
echo "Automated migration from ProxmoxVED (community-scripts/ProxmoxVED#${ISSUE_NR})."
} >> pr_body.md
gh pr create \
--repo community-scripts/ProxmoxVE \
--head "$branch_name" \
--base main \
--title "${script_name}" \
--body "Automated migration of **${script_name}** (type: ${script_type}) from ProxmoxVED to ProxmoxVE."
--title "$ISSUE_TITLE" \
--body-file pr_body.md
PR_NUMBER=$(gh pr list --repo community-scripts/ProxmoxVE --head "$branch_name" --json number --jq '.[].number')
echo "PR_NUMBER=$PR_NUMBER"
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT