Skip to content
Merged
Show file tree
Hide file tree
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
138 changes: 138 additions & 0 deletions .github/workflows/generate-runners-status.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
name: "Generate runners status"

on:
push:
schedule:
- cron: '0 * * * *' # Run every hour
workflow_dispatch: # Manually triggered via GitHub Actions UI

#concurrency:
# group: redirector
# cancel-in-progress: false

jobs:

Check:

name: "Check permissions"
runs-on: "ubuntu-22.04"
steps:

- name: "Check permissions"
uses: armbian/actions/team-check@main
with:
ORG_MEMBERS: ${{ secrets.ORG_MEMBERS }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TEAM: "Release manager"

Comment on lines +15 to +27
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Scheduled runs will always fail the Check job

On schedule, the actor is github-actions[bot], not a human team member, so team-check fails and blocks build. Gate Check to non-scheduled events (or invert needs on build).

 Check:
+  if: github.event_name != 'schedule'
   name: "Check permissions"
   runs-on: "ubuntu-22.04"
   steps:
     - name: "Check permissions"
-      uses: armbian/actions/team-check@main
+      uses: armbian/actions/team-check@main
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Check:
name: "Check permissions"
runs-on: "ubuntu-22.04"
steps:
- name: "Check permissions"
uses: armbian/actions/team-check@main
with:
ORG_MEMBERS: ${{ secrets.ORG_MEMBERS }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TEAM: "Release manager"
Check:
if: github.event_name != 'schedule'
name: "Check permissions"
runs-on: "ubuntu-22.04"
steps:
- name: "Check permissions"
uses: armbian/actions/team-check@main
with:
ORG_MEMBERS: ${{ secrets.ORG_MEMBERS }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TEAM: "Release manager"
🤖 Prompt for AI Agents
.github/workflows/generate-runners-status.yml around lines 15-27: the scheduled
workflow runs as github-actions[bot] so the team-check step (which verifies a
human team membership) will always fail on schedule; to fix, gate the Check
job/step so it does not run for scheduled events by adding a condition like if:
${{ github.event_name != 'schedule' }} (apply to the job or the specific step),
or alternatively adjust job dependencies so the build does not need the Check
job for schedule-triggered runs.

build:
name: "Get self hosted runners status"
runs-on: ubuntu-24.04
needs: Check
steps:

- name: "Install dependencies: jq"
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: jq
version: 1.0

Comment on lines +34 to +39
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid @latest; either pin by SHA or use apt-get directly

Pin the action or switch to native apt for simplicity.

-      - name: "Install dependencies: jq"
-        uses: awalsh128/cache-apt-pkgs-action@latest
-        with:
-          packages: jq
-          version: 1.0
+      - name: "Install jq"
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y jq
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: "Install dependencies: jq"
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: jq
version: 1.0
- name: "Install jq"
run: |
sudo apt-get update
sudo apt-get install -y jq
🤖 Prompt for AI Agents
.github/workflows/generate-runners-status.yml around lines 34-39: the workflow
currently uses the third-party action with @latest which is not pinned; either
pin the action to a specific commit SHA (e.g. replace @latest with
@<commit-sha>) to ensure reproducible builds, or remove the action and install
jq directly using native apt in a run step (e.g. run: sudo apt-get update &&
sudo apt-get install -y jq) and remove the uses/with block.

- name: "Get runners from ORG"
env:
GH_TOKEN: ${{ secrets.RUNNERS }}
run: |

GH_TOKEN=${{ secrets.RUNNERS }}
NETBOX_TOKEN=${{ secrets.NETBOX_TOKEN }}
NETBOX_API=${{ secrets.NETBOX_API }}

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

runners_nd="$tmp/runners.ndjson"
out_html="$tmp/runners_summary.html"
: > "$runners_nd"

Comment on lines +49 to +55
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Harden script execution

Enable strict mode; drop unused out_html var.

           tmp="$(mktemp -d)"
           trap 'rm -rf "$tmp"' EXIT
 
           runners_nd="$tmp/runners.ndjson"
-          out_html="$tmp/runners_summary.html"
           : > "$runners_nd"
+          set -Eeuo pipefail
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
runners_nd="$tmp/runners.ndjson"
out_html="$tmp/runners_summary.html"
: > "$runners_nd"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
runners_nd="$tmp/runners.ndjson"
: > "$runners_nd"
set -Eeuo pipefail
🤖 Prompt for AI Agents
.github/workflows/generate-runners-status.yml around lines 49 to 55: enable
strict shell mode and remove the unused out_html variable; add a safe
shebang/session options by inserting set -euo pipefail and IFS=$'\n\t' near the
top of the script (or before this block) so the script fails fast on
errors/unset variables and handles word splitting safely, and delete the
out_html="$tmp/runners_summary.html" assignment since out_html is unused; keep
tmp="$(mktemp -d)" and the trap as-is and ensure the rest of the script
references "$runners_nd" only.

# Fetch ALL GitHub org runners (prefer gh; fallback to curl)
if command -v gh >/dev/null 2>&1; then
gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
--paginate /orgs/armbian/actions/runners \
-q '.runners[]' > "$runners_nd"
else
curl -fsSL \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/orgs/armbian/actions/runners?per_page=100" \
| jq -c '.runners[]' > "$runners_nd"
fi

Comment on lines +56 to +71
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Curl fallback fetches only first page (misses >100 runners)

Paginate until empty or Link header exhausted.

         else
-          curl -fsSL \
-            -H "Accept: application/vnd.github+json" \
-            -H "Authorization: Bearer ${GH_TOKEN}" \
-            -H "X-GitHub-Api-Version: 2022-11-28" \
-            "https://api.github.com/orgs/armbian/actions/runners?per_page=100" \
-            | jq -c '.runners[]' > "$runners_nd"
+          page=1
+          while :; do
+            resp="$(curl -fsSL \
+              -H "Accept: application/vnd.github+json" \
+              -H "Authorization: Bearer ${GH_TOKEN}" \
+              -H "X-GitHub-Api-Version: 2022-11-28" \
+              "https://api.github.com/orgs/armbian/actions/runners?per_page=100&page=${page}")"
+            count="$(jq '.runners | length' <<<"$resp")"
+            [[ "${count}" -eq 0 ]] && break
+            jq -c '.runners[]' <<<"$resp" >> "$runners_nd"
+            page=$((page+1))
+          done
         fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Fetch ALL GitHub org runners (prefer gh; fallback to curl)
if command -v gh >/dev/null 2>&1; then
gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
--paginate /orgs/armbian/actions/runners \
-q '.runners[]' > "$runners_nd"
else
curl -fsSL \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/orgs/armbian/actions/runners?per_page=100" \
| jq -c '.runners[]' > "$runners_nd"
fi
# Fetch ALL GitHub org runners (prefer gh; fallback to curl)
if command -v gh >/dev/null 2>&1; then
gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
--paginate /orgs/armbian/actions/runners \
-q '.runners[]' > "$runners_nd"
else
page=1
while :; do
resp="$(curl -fsSL \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/orgs/armbian/actions/runners?per_page=100&page=${page}")"
count="$(jq '.runners | length' <<<"$resp")"
[[ "${count}" -eq 0 ]] && break
jq -c '.runners[]' <<<"$resp" >> "$runners_nd"
page=$((page+1))
done
fi
🤖 Prompt for AI Agents
.github/workflows/generate-runners-status.yml around lines 56 to 71: the curl
fallback currently fetches only the first page (up to 100 runners) and will miss
additional pages; change the fallback to loop and paginate until no more pages
(either by following the Link header or incrementing a page counter with
?per_page=100&page=N) and append each page's runners to the output file,
preserving the same headers/authorization and using jq to extract '.runners[]'
for each page; ensure the loop breaks when the response has no runners or when
the Link header indicates no "next" link.

# Build lookup maps
declare -A RUNNER_STATUS RUNNER_BUSY
while IFS=$'\t' read -r name status busy; do
RUNNER_STATUS["$name"]="$status"
RUNNER_BUSY["$name"]="$busy"
done < <(jq -r '[.name, .status, (.busy|tostring)] | @tsv' "$runners_nd")

# Fetch NetBox VMs
nb_json="$tmp/netbox.json"

curl -s \
-H "Authorization: Token ${NETBOX_TOKEN}" \
-H "Accept: application/json; indent=4" \
"${NETBOX_API}/virtualization/virtual-machines/?limit=500&name__empty=false&tag=github-runner&status=active" \
> "$nb_json"

# Render to stdout and to HTML file
echo "<table border=0>" >> $GITHUB_STEP_SUMMARY
echo "<tr><th align=left>Server</th><th align=left>Runner label</th><th align=right>CPU cores</th><th align=right>Memory GB</th><th align=right>Storage GB</th><th align=right>Runners</th></tr>" >> $GITHUB_STEP_SUMMARY

# init totals
total_cpu=0
total_mem_gb=0
total_storage_gb=0
total_runners=0

while IFS=$'\t' read -r NAME CPU MEM_MB DISK_GB RUNNERS LABELS ID; do
CALC_MEM=$(( (MEM_MB + 512) / 1024 ))
printf "<tr><td>%s</td><td>%s</td><td align=right>%d</td><td align=right>%d</td><td align=right>%d</td><td align=right>%d</td></tr>\n" \
"$NAME" "$LABELS" "$CPU" "$CALC_MEM" "$DISK_GB" "$RUNNERS" >> $GITHUB_STEP_SUMMARY
Comment on lines +88 to +101
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Escape HTML and quote file path

Names/labels from NetBox may contain special chars; escape to avoid broken markup. Also quote $GITHUB_STEP_SUMMARY.

-          echo "<table border=0>"  >> $GITHUB_STEP_SUMMARY
-          echo "<tr><th align=left>Server</th><th align=left>Runner label</th><th align=right>CPU cores</th><th align=right>Memory GB</th><th align=right>Storage GB</th><th align=right>Runners</th></tr>"  >> $GITHUB_STEP_SUMMARY
+          echo "<table border=0>"  >> "$GITHUB_STEP_SUMMARY"
+          echo "<tr><th align=left>Server</th><th align=left>Runner label</th><th align=right>CPU cores</th><th align=right>Memory GB</th><th align=right>Storage GB</th><th align=right>Runners</th></tr>"  >> "$GITHUB_STEP_SUMMARY"
@@
-            printf "<tr><td>%s</td><td>%s</td><td align=right>%d</td><td align=right>%d</td><td align=right>%d</td><td align=right>%d</td></tr>\n" \
-              "$NAME" "$LABELS" "$CPU" "$CALC_MEM" "$DISK_GB" "$RUNNERS" >> $GITHUB_STEP_SUMMARY
+            esc_name=$(jq -Rr @html <<<"$NAME")
+            esc_labels=$(jq -Rr @html <<<"$LABELS")
+            printf "<tr><td>%s</td><td>%s</td><td align=right>%d</td><td align=right>%d</td><td align=right>%d</td><td align=right>%d</td></tr>\n" \
+              "$esc_name" "$esc_labels" "$CPU" "$CALC_MEM" "$DISK_GB" "$RUNNERS" >> "$GITHUB_STEP_SUMMARY"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Render to stdout and to HTML file
echo "<table border=0>" >> $GITHUB_STEP_SUMMARY
echo "<tr><th align=left>Server</th><th align=left>Runner label</th><th align=right>CPU cores</th><th align=right>Memory GB</th><th align=right>Storage GB</th><th align=right>Runners</th></tr>" >> $GITHUB_STEP_SUMMARY
# init totals
total_cpu=0
total_mem_gb=0
total_storage_gb=0
total_runners=0
while IFS=$'\t' read -r NAME CPU MEM_MB DISK_GB RUNNERS LABELS ID; do
CALC_MEM=$(( (MEM_MB + 512) / 1024 ))
printf "<tr><td>%s</td><td>%s</td><td align=right>%d</td><td align=right>%d</td><td align=right>%d</td><td align=right>%d</td></tr>\n" \
"$NAME" "$LABELS" "$CPU" "$CALC_MEM" "$DISK_GB" "$RUNNERS" >> $GITHUB_STEP_SUMMARY
# Render to stdout and to HTML file
echo "<table border=0>" >> "$GITHUB_STEP_SUMMARY"
echo "<tr><th align=left>Server</th><th align=left>Runner label</th><th align=right>CPU cores</th><th align=right>Memory GB</th><th align=right>Storage GB</th><th align=right>Runners</th></tr>" >> "$GITHUB_STEP_SUMMARY"
# init totals
total_cpu=0
total_mem_gb=0
total_storage_gb=0
total_runners=0
while IFS=$'\t' read -r NAME CPU MEM_MB DISK_GB RUNNERS LABELS ID; do
CALC_MEM=$(( (MEM_MB + 512) / 1024 ))
esc_name=$(jq -Rr @html <<<"$NAME")
esc_labels=$(jq -Rr @html <<<"$LABELS")
printf "<tr><td>%s</td><td>%s</td><td align=right>%d</td><td align=right>%d</td><td align=right>%d</td><td align=right>%d</td></tr>\n" \
"$esc_name" "$esc_labels" "$CPU" "$CALC_MEM" "$DISK_GB" "$RUNNERS" >> "$GITHUB_STEP_SUMMARY"
🤖 Prompt for AI Agents
In .github/workflows/generate-runners-status.yml around lines 88-101, the NAME
and LABELS values from NetBox are not HTML-escaped and the redirection target is
unquoted; update the script to HTML-escape special characters (&, <, >, ", ') in
NAME and LABELS (e.g., create a small function that replaces those characters
with &amp;, &lt;, &gt;, &quot;, &#39; and call it for NAME and LABELS before
printing) and change all redirections to quote the summary file variable (use
"$GITHUB_STEP_SUMMARY" instead of $GITHUB_STEP_SUMMARY) so markup cannot be
broken and the filename with spaces is handled safely.


echo "<tr><td colspan=6>" >> $GITHUB_STEP_SUMMARY
if [[ "$RUNNERS" -gt 0 ]]; then
for i in $(seq -f "%02g" 1 "$RUNNERS"); do
rn="${LABELS}-${i}"
if [[ -n "${RUNNER_STATUS[$rn]:-}" || "$LABELS" == "github" ]]; then
printf "🟢" >> $GITHUB_STEP_SUMMARY
else
printf "🔴 <small>(%s)</small> " "$i" >> $GITHUB_STEP_SUMMARY
fi
done
fi
echo "</td></tr>" >> $GITHUB_STEP_SUMMARY
Comment on lines +103 to +114
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Don’t mark offline runners as green; use real status (+busy) and quote path

Presence in the API isn’t “online”. Render online/idle/busy vs offline.

-            echo "<tr><td colspan=6>" >> $GITHUB_STEP_SUMMARY
+            echo "<tr><td colspan=6>" >> "$GITHUB_STEP_SUMMARY"
             if [[ "$RUNNERS" -gt 0 ]]; then
               for i in $(seq -f "%02g" 1 "$RUNNERS"); do
                 rn="${LABELS}-${i}"
-                if [[ -n "${RUNNER_STATUS[$rn]:-}" || "$LABELS" == "github" ]]; then
-                  printf "🟢" >> $GITHUB_STEP_SUMMARY
-                else
-                  printf "🔴 <small>(%s)</small> " "$i" >> $GITHUB_STEP_SUMMARY
-                fi
+                status="${RUNNER_STATUS[$rn]:-}"
+                busy="${RUNNER_BUSY[$rn]:-false}"
+                if [[ "$LABELS" == "github" ]]; then
+                  icon="🟢"
+                elif [[ "$status" == "online" ]]; then
+                  icon="🟢"
+                elif [[ -n "$status" ]]; then
+                  icon="🟡"
+                else
+                  icon="🔴"
+                fi
+                [[ "$busy" == "true" ]] && icon="${icon}🔥"
+                printf "%s <small>(%s)</small> " "$icon" "$i" >> "$GITHUB_STEP_SUMMARY"
               done
             fi
-            echo "</td></tr>" >> $GITHUB_STEP_SUMMARY
+            echo "</td></tr>" >> "$GITHUB_STEP_SUMMARY"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
echo "<tr><td colspan=6>" >> $GITHUB_STEP_SUMMARY
if [[ "$RUNNERS" -gt 0 ]]; then
for i in $(seq -f "%02g" 1 "$RUNNERS"); do
rn="${LABELS}-${i}"
if [[ -n "${RUNNER_STATUS[$rn]:-}" || "$LABELS" == "github" ]]; then
printf "🟢" >> $GITHUB_STEP_SUMMARY
else
printf "🔴 <small>(%s)</small> " "$i" >> $GITHUB_STEP_SUMMARY
fi
done
fi
echo "</td></tr>" >> $GITHUB_STEP_SUMMARY
echo "<tr><td colspan=6>" >> "$GITHUB_STEP_SUMMARY"
if [[ "$RUNNERS" -gt 0 ]]; then
for i in $(seq -f "%02g" 1 "$RUNNERS"); do
rn="${LABELS}-${i}"
status="${RUNNER_STATUS[$rn]:-}"
busy="${RUNNER_BUSY[$rn]:-false}"
if [[ "$LABELS" == "github" ]]; then
icon="🟢"
elif [[ "$status" == "online" ]]; then
icon="🟢"
elif [[ -n "$status" ]]; then
icon="🟡"
else
icon="🔴"
fi
[[ "$busy" == "true" ]] && icon="${icon}🔥"
printf "%s <small>(%s)</small> " "$icon" "$i" >> "$GITHUB_STEP_SUMMARY"
done
fi
echo "</td></tr>" >> "$GITHUB_STEP_SUMMARY"


# accumulate totals (note: no $ needed for CPU/RUNNERS inside arithmetic)
total_cpu=$(( total_cpu + CPU ))
total_mem_gb=$(( total_mem_gb + CALC_MEM ))
total_storage_gb=$(( total_storage_gb + DISK_GB ))
total_runners=$(( total_runners + RUNNERS ))
done < <(jq -r '
.results[] |
[
.name,
((.vcpus // 0) | floor),
((.memory // 0) | floor),
((.disk // 0) | floor),
(.custom_fields["runners"] // 0),
(.custom_fields["labels"] // "null"),
.id
] | @tsv
' "$nb_json")

# summary row
printf "<tr><th colspan=2 align=right>Totals:</th><th align=right>%d</th><th align=right>%d</th><th align=right>%d</th><th align=right>%d</th></tr>\n" \
"$total_cpu" "$total_mem_gb" "$total_storage_gb" "$total_runners" >> $GITHUB_STEP_SUMMARY

echo "</table>" >> $GITHUB_STEP_SUMMARY
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ It also produces [data exchange files](https://github.armbian.com/) used for aut
<a href=https://github.com/armbian/armbian.github.io/actions/workflows/invite-contributors.yml><img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/armbian/armbian.github.io/invite-contributors.yml?logo=githubactions&label=Status&style=for-the-badge&branch=main&logoColor=white"></a>
Automatically invites external contributors to join the [Armbian GitHub organization](https://github.com/orgs/armbian/people).

- **Generate Self Hosted Runners Status page**
<a href=https://github.com/armbian/armbian.github.io/actions/workflows/generate-runners-status.yml><img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/armbian/armbian.github.io/generate-runners-status.yml?logo=githubactions&label=Status&style=for-the-badge&branch=main&logoColor=white"></a>
Generates a table of Self Hosted runners with CPU, memory, storage, runner status, and totals.


### Documentation

- **Generate Documentation**
Expand Down