From 2868ddac56fb8321e10c73143e6cb390632b9945 Mon Sep 17 00:00:00 2001 From: Eric Dahl Date: Tue, 6 Oct 2026 15:07:12 -0400 Subject: [PATCH] Report private-repo GitHub-hosted minutes in the weekly email Assisted-by: Claude Code:claude-opus-5-5 Claude-Session: https://claude.ai/code/session_01QetPdWGkSfWsXPCum4FQax --- README.md | 4 +++- host/ci-load-report.sh | 27 ++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index eccbdd8..c911a8f 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,9 @@ runner's busy state to `/var/log/ci-load.tsv` (35 days kept). `host/ci-load-repo summary every Monday at 13:00 UTC, from alerts@ericdahl.dev through the `mail-bridge` container (ger3 blocks outbound SMTP). Its settings are in `/etc/ci-load-report.env` (root only: `MAIL_BRIDGE_SMTP_PASSWORD` from Doppler `ericdahl-dev/prd`, and `REPORT_TO`). Cron: -`/etc/cron.d/ci-load-report`, log `/var/log/ci-load-report.log`. +`/etc/cron.d/ci-load-report`, log `/var/log/ci-load-report.log`. With `GH_BILLING_PAT` in the env file +(fine-grained, org Administration: read, Doppler `GH_BILLING_PAT`) the email also shows private-repo +GitHub-hosted minutes for the month against the 3,000 included. ## Setup facts and workflow rules diff --git a/host/ci-load-report.sh b/host/ci-load-report.sh index 30140bb..3135dea 100755 --- a/host/ci-load-report.sh +++ b/host/ci-load-report.sh @@ -2,7 +2,8 @@ # Mondays: email a 7-day summary of ger3 load and runner use, from the samples # ci-load-sample collects. Mail goes through the mail-bridge on the coolify network # (ger3 blocks outbound SMTP). Needs /etc/ci-load-report.env (root, 600) with -# MAIL_BRIDGE_SMTP_PASSWORD and REPORT_TO. +# MAIL_BRIDGE_SMTP_PASSWORD and REPORT_TO, and optionally GH_BILLING_PAT (fine-grained, +# org Administration: read) to add GitHub-hosted Actions minutes for the month. # Installed as /usr/local/bin/ci-load-report, cron in /etc/cron.d/ci-load-report. set -eu . /etc/ci-load-report.env @@ -30,6 +31,30 @@ summary=$(awk -v s="$since" -v cores="$CORES" ' printf "sits above the core count, revisit runner count or limits.\n" }' "$LOG") +if [ -n "${GH_BILLING_PAT:-}" ]; then + y=$(date -u +%Y); m=$(date -u +%-m) + # Public repos' minutes are free and don't use the included 3,000, so leave them out. + public=$(curl -fsS "https://api.github.com/orgs/ericdahl-dev/repos?type=public&per_page=100" \ + | python3 -c 'import json,sys; print(" ".join(r["name"] for r in json.load(sys.stdin)))') || public="" + minutes=$(curl -fsS -H "Authorization: Bearer $GH_BILLING_PAT" -H "Accept: application/vnd.github+json" \ + "https://api.github.com/organizations/ericdahl-dev/settings/billing/usage?year=$y&month=$m" \ + | PUBLIC="$public" python3 -c ' +import json, os, sys, collections +public = set(os.environ["PUBLIC"].split()) +items = [i for i in json.load(sys.stdin)["usageItems"] + if i["unitType"] == "Minutes" and i["repositoryName"] not in public] +by_repo = collections.Counter(); linux = paid = 0.0 +for i in items: + by_repo[i["repositoryName"]] += i["quantity"] + if i["sku"] == "Actions Linux": linux += i["quantity"] + paid += i["netAmount"] +print(f"Private-repo Linux minutes this month: {linux:.0f} of 3,000 included") +print(f"Billed this month: ${paid:.2f} (macOS/Windows and any overage)") +print("Top private repos: " + ", ".join(f"{r} {q:.0f}" for r, q in by_repo.most_common(5))) +' 2>&1) || minutes="(billing API call failed)" + summary=$(printf '%s\n\nGitHub-hosted Actions (ger3 runs are free and not counted)\n%s\n' "$summary" "$minutes") +fi + body=$(printf 'From: ger3 CI \r\nTo: %s\r\nSubject: ger3 CI load, week ending %s\r\n\r\n%s\r\n' \ "$REPORT_TO" "$(date -u +%Y-%m-%d)" "$summary")