Skip to content

Mine GitHub user stats + deploy to Cloudflare #9

Mine GitHub user stats + deploy to Cloudflare

Mine GitHub user stats + deploy to Cloudflare #9

name: Mine GitHub user stats + deploy to Cloudflare
on:
schedule:
- cron: "0 6 * * *"
workflow_dispatch:
inputs:
user:
description: "Single GitHub login to mine (skips the full users.txt loop)."
required: false
push:
branches: [main]
paths:
- "generate_stats.py"
- "stats_template.html"
- "cloudflare/**"
- ".github/workflows/mine-and-deploy.yml"
concurrency:
group: mine-and-deploy
cancel-in-progress: false
jobs:
mine-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: write # for appending new users to users.txt
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GH_MINING_TOKEN || github.token }}
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install gh CLI
run: |
type -p gh >/dev/null || (
sudo apt-get update -qq && sudo apt-get install -y gh
)
- name: Authenticate gh
env:
GH_TOKEN: ${{ secrets.GH_MINING_TOKEN }}
run: echo "$GH_TOKEN" | gh auth login --with-token
- name: Determine target users
id: targets
working-directory: .
env:
INPUT_USER: ${{ inputs.user }}
run: |
set -e
mkdir -p cloudflare/public
# Build the list of users we'll mine THIS run.
if [ -n "$INPUT_USER" ]; then
echo "Single-user mine: $INPUT_USER"
# Persist new users into users.txt so future scheduled runs
# include them.
if ! grep -qiE "^${INPUT_USER}$" cloudflare/users.txt; then
echo "$INPUT_USER" >> cloudflare/users.txt
echo "added=true" >> $GITHUB_OUTPUT
fi
echo "$INPUT_USER" > /tmp/targets.txt
else
echo "Full mine of users.txt"
grep -vE '^\s*(#|$)' cloudflare/users.txt > /tmp/targets.txt
fi
# Pre-stage pirate's enhanced version
if [ -f stats.html ]; then
cp stats.html cloudflare/public/pirate.html
fi
cat /tmp/targets.txt
- name: Mine each user (with live in-progress deploys)
working-directory: .
env:
NO_COLOR: "1"
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
set -e
deploy_now() {
(cd cloudflare && npx --yes wrangler@latest deploy --minify 2>&1 |
tail -2) || echo "::warning::interim deploy failed"
}
watch_and_deploy() {
# Watches stats_<user>.html every 30s while $1 (PID) is alive.
# Copies any updated file into the deploy dir and re-deploys so
# the live page shows partial data as mining progresses.
local pid="$1" user="$2" src="stats_${user}.html" \
dst="cloudflare/public/${user}.html" last_mtime=0
while kill -0 "$pid" 2>/dev/null; do
sleep 30
if [ -f "$src" ]; then
local mtime
mtime=$(stat -c %Y "$src" 2>/dev/null \
|| stat -f %m "$src" 2>/dev/null || echo 0)
if [ "$mtime" -gt "$last_mtime" ]; then
cp "$src" "$dst"
echo "::group::Interim deploy of @$user (live)"
deploy_now
echo "::endgroup::"
last_mtime="$mtime"
fi
fi
done
}
while IFS= read -r user || [ -n "$user" ]; do
user="${user%%#*}"
user="${user//[[:space:]]/}"
[ -z "$user" ] && continue
[ "$user" = "pirate" ] && continue
echo "::group::Mining @$user"
# Run mining in the background; watch loop deploys partials.
python3 generate_stats.py --user "$user" \
--no-search-commits \
--max-api-fetches 1500 &
MINE_PID=$!
watch_and_deploy "$MINE_PID" "$user" &
WATCH_PID=$!
wait "$MINE_PID" || echo "::warning::mining @$user exited non-zero"
# Stop the watcher and do a final deploy with the final HTML.
kill "$WATCH_PID" 2>/dev/null || true
wait "$WATCH_PID" 2>/dev/null || true
if [ -f "stats_$user.html" ]; then
cp "stats_$user.html" "cloudflare/public/$user.html"
echo "::group::Final deploy of @$user"
deploy_now
echo "::endgroup::"
fi
echo "::endgroup::"
done < /tmp/targets.txt
- name: Commit added users.txt entries
if: steps.targets.outputs.added == 'true'
working-directory: cloudflare
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add users.txt
git diff --staged --quiet || git commit -m "Add ${{ inputs.user }} to users.txt [skip ci]"
git push || echo "::warning::push failed (no commit permission?)"
- name: Final deploy
working-directory: cloudflare
run: npx --yes wrangler@latest deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}