name: Discord testing-thread lifecycle (ready / defer / close) on: issues: types: - labeled - unlabeled - closed permissions: issues: write actions: write jobs: # --------------------------------------------------------------------------- # Ready For Testing added -> create (or re-open) the Discord thread + comment # --------------------------------------------------------------------------- ready: runs-on: ubuntu-latest if: | github.repository == 'community-scripts/ProxmoxVED' && github.event.action == 'labeled' && github.event.label.name == 'Ready For Testing' steps: - name: Extract Script Name and Type id: extract_info env: ISSUE_BODY: ${{ github.event.issue.body }} ISSUE_TITLE: ${{ github.event.issue.title }} run: | # 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 SCRIPT_TYPE="ct" elif echo "$ISSUE_BODY" | grep -qi "VM (Virtual Machine)"; then SCRIPT_TYPE="vm" elif echo "$ISSUE_BODY" | grep -qi "Addon (tools/addon)"; then SCRIPT_TYPE="addon" elif echo "$ISSUE_BODY" | grep -qi "PVE Tool (tools/pve)"; then SCRIPT_TYPE="pve" else # Fallback: detect by filename pattern or default to ct if [[ "$SLUG" == *"-vm"* ]]; then SCRIPT_TYPE="vm" else SCRIPT_TYPE="ct" fi fi echo "SCRIPT_TYPE=$SCRIPT_TYPE" >> $GITHUB_ENV echo "Detected script type: $SCRIPT_TYPE for slug: $SLUG (display: $DISPLAY)" - name: Check if Files Exist in community-scripts/ProxmoxVED id: check_files env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | REPO="community-scripts/ProxmoxVED" API_URL="https://api.github.com/repos/$REPO/contents" SLUG="${{ env.SLUG }}" SCRIPT_TYPE="${{ env.SCRIPT_TYPE }}" # Define files based on script type case "$SCRIPT_TYPE" in ct) FILES=( "ct/${SLUG}.sh" "install/${SLUG}-install.sh" "json/${SLUG}.json" ) ;; vm) FILES=( "vm/${SLUG}.sh" "json/${SLUG}.json" ) ;; addon) FILES=( "tools/addon/${SLUG}.sh" "json/${SLUG}.json" ) ;; pve) FILES=( "tools/pve/${SLUG}.sh" ) ;; esac EXISTING_FILES=() for FILE in "${FILES[@]}"; do STATUS=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token $GH_TOKEN" "$API_URL/$FILE") if [ "$STATUS" -eq 200 ]; then EXISTING_FILES+=("$FILE") echo "$FILE exists in $REPO" else echo "$FILE does NOT exist in $REPO" fi done echo "EXISTING_FILES=${EXISTING_FILES[*]}" >> $GITHUB_ENV - name: Create message to send id: create_message run: | SLUG="${{ env.SLUG }}" DISPLAY="${{ env.DISPLAY }}" SCRIPT_TYPE="${{ env.SCRIPT_TYPE }}" 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/${SLUG}.sh)\"\`\`\`\n" ;; vm) 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/${SLUG}.sh)\"\`\`\`\n" ;; pve) VAR+="\`\`\`bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/tools/pve/${SLUG}.sh)\"\`\`\`\n" ;; esac # Try to get JSON info (may not exist for all types) JSON_FILE="" case "$SCRIPT_TYPE" in ct|vm|addon) JSON_FILE="json/${SLUG}.json" ;; esac if [[ -n "$JSON_FILE" ]]; then JSON=$(curl -fsSL "https://github.com/community-scripts/ProxmoxVED/raw/main/${JSON_FILE}" 2>/dev/null || echo "{}") if [[ "$JSON" != "{}" && "$JSON" != "" ]]; then username=$(echo "$JSON" | jq -r '.default_credentials.username // empty') password=$(echo "$JSON" | jq -r '.default_credentials.password // empty') if [[ -n "$username" || -n "$password" ]]; then VAR+="Default credentials:\n" [[ -n "$username" ]] && VAR+="Username: $username\n" [[ -n "$password" ]] && VAR+="Password: $password\n" VAR+="\n" fi # Get notes mapfile -t notes_array < <(echo "$JSON" | jq -r '.notes[]?.text // empty' 2>/dev/null) if [ ${#notes_array[@]} -gt 0 ]; then for note in "${notes_array[@]}"; do [[ -n "$note" ]] && VAR+="$note\n" done VAR+="\n" fi fi fi VAR+="Note: This is not in the official repo yet—it's just a dev version! After merging into ProxmoxVE, it will need to be recreated.\n\n" VAR+="Discussion & issue tracking:\n" VAR+="${{ github.event.issue.html_url }}" echo "message=$VAR" >> $GITHUB_ENV - name: Check if Discord thread exists id: check_thread run: | ISSUE_TITLE="${{ github.event.issue.title }}" 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_exists=true" >> "$GITHUB_OUTPUT" echo "thread_id=$THREAD_ID" >> "$GITHUB_OUTPUT" else echo "thread_exists=false" >> "$GITHUB_OUTPUT" fi - name: Unlock existing Discord thread (re-labeled after deferral) if: steps.check_thread.outputs.thread_exists == 'true' run: | curl -s -X PATCH "https://discord.com/api/v10/channels/${{ steps.check_thread.outputs.thread_id }}" \ -H "Authorization: Bot ${{ secrets.DISCORD_BOT_TOKEN }}" \ -H "Content-Type: application/json" \ -d '{"locked": false}' - name: Unlock GitHub issue (re-labeled after deferral) if: steps.check_thread.outputs.thread_exists == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh api --method DELETE /repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/lock || true - name: Create a forumpost in Discord if: steps.check_thread.outputs.thread_exists != 'true' id: post_to_discord env: DISCORD_CHANNEL_ID: ${{ secrets.DISCORD_CHANNEL_ID }} DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }} TITLE: ${{ github.event.issue.title }} MESSAGE: ${{ env.message }} run: | JSON_PAYLOAD=$(jq -n --arg name "Wanted Tester for $TITLE" --arg content "$MESSAGE" '{name: $name, message: {content: $content | gsub("\\\\n"; "\n")}, applied_tags: []}') echo "JSON Payload: $JSON_PAYLOAD" RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "https://discord.com/api/v10/channels/$DISCORD_CHANNEL_ID/threads" \ -H "Authorization: Bot $DISCORD_BOT_TOKEN" \ -H "Content-Type: application/json" \ -d "$JSON_PAYLOAD") HTTP_BODY=$(echo "$RESPONSE" | head -n -1) HTTP_CODE=$(echo "$RESPONSE" | tail -n1) THREAD_ID=$(echo "$HTTP_BODY" | jq -r '.id') STATUS_CODE=$(echo "$RESPONSE" | tail -n 1) if [[ "$HTTP_CODE" == "201" && -n "$THREAD_ID" ]]; then echo "Discord post created successfully!" else echo "Response: $RESPONSE" echo "Failed to create Discord post! Status code: $STATUS_CODE" exit 1 fi - name: Comment on Issue if: steps.check_thread.outputs.thread_exists != 'true' id: comment_on_issue env: MESSAGE: ${{ env.message }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | echo -e "$MESSAGE" > comment.txt sed -i '/Discussion & issue tracking:/,$d' comment.txt gh issue comment ${{ github.event.issue.number }} --repo ${{ github.repository }} --body-file comment.txt - name: Push script JSON to PocketBase if: env.SCRIPT_TYPE == 'ct' || env.SCRIPT_TYPE == 'vm' || env.SCRIPT_TYPE == 'addon' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | 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<> "$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