Update Profile README (uptime + repos + stats) #3543
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Update Profile README (uptime + repos + stats) | |
| on: | |
| schedule: | |
| - cron: "0 * * * *" # alle 1 Stunden | |
| workflow_dispatch: | |
| permissions: | |
| contents: write # für git push | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y jq curl | |
| - name: Debug paths (optional) | |
| run: | | |
| pwd | |
| ls -lah | |
| ls -lah profile || true | |
| # ---------- UPTIME (mit dynamischen Commits) ---------- | |
| - name: Update uptime block | |
| env: | |
| FILE: profile/README.md | |
| GH_USER: mzcydev | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| test -f "$FILE" || { echo "::error file=$FILE::File not found"; exit 1; } | |
| START_DATE="2017-01-01" # ab wann commits gezählt werden | |
| # Commits via Search API (erfordert Accept-Header 'cloak-preview' für commit search) | |
| COMMITS=$(curl -s -L \ | |
| -H "Authorization: Bearer $GITHUB_TOKEN" \ | |
| -H "User-Agent: gh-actions" \ | |
| -H "Accept: application/vnd.github.cloak-preview" \ | |
| "https://api.github.com/search/commits?q=author:${GH_USER}+author-date:>=${START_DATE}" \ | |
| | jq -r '.total_count // 0') | |
| LOAD="0.42, 0.33, 0.12" | |
| NOW=$(date -u +"%s") | |
| START=$(date -u -d "$START_DATE" +"%s") || START=0 | |
| DAYS=$(( (NOW - START) / 86400 )) | |
| LINE=" $(date +%H:%M:%S) up ${DAYS} days, ${COMMITS} commits, load average: ${LOAD}" | |
| sed -i "/<!-- UPTIME-START -->/,/<!-- UPTIME-END -->/c\<!-- UPTIME-START -->\n\`\`\`bash\nroot@${GH_USER}:~# uptime\n$LINE\n\`\`\`\n<!-- UPTIME-END -->" "$FILE" | |
| # ---------- REPOS (Fake ls -lah mit Top-5 zuletzt gepushten Repos) ---------- | |
| - name: Update repos block | |
| env: | |
| FILE: profile/README.md | |
| GH_USER: mzcydev | |
| run: | | |
| test -f "$FILE" || { echo "::error file=$FILE::File not found"; exit 1; } | |
| JSON=$(curl -s "https://api.github.com/users/${GH_USER}/repos?per_page=100&sort=pushed") | |
| NAMES=$(echo "$JSON" | jq -r '.[].name' | head -n 5) | |
| DATE=$(date +"%b %d") | |
| OUTPUT="" | |
| while IFS= read -r R; do | |
| [ -z "$R" ] && continue | |
| OUTPUT="${OUTPUT}\ndrwxr-xr-x 2 ${GH_USER} dev 4.0K ${DATE} ${R}" | |
| done <<< "$NAMES" | |
| [ -z "$OUTPUT" ] && OUTPUT="\ndrwxr-xr-x 2 ${GH_USER} dev 4.0K ${DATE} README.md" | |
| sed -i "/<!-- REPOS-START -->/,/<!-- REPOS-END -->/c\<!-- REPOS-START -->\n\`\`\`bash\nroot@${GH_USER}:~# ls -lah ~/repos/\n$OUTPUT\n\`\`\`\n<!-- REPOS-END -->" "$FILE" | |
| # ---------- STATS (Script erzeugen, ausführen, Block ersetzen) ---------- | |
| - name: Generate and run github-stats.sh | |
| env: | |
| GH_USER: mzcydev | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| mkdir -p scripts | |
| cat > scripts/github-stats.sh <<'EOF' | |
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| USER_ARG="${1:-}" | |
| GH_USER="${USER_ARG:-${GH_USER:-}}" | |
| GITHUB_TOKEN="${GITHUB_TOKEN:-}" | |
| if [[ -z "${GH_USER}" ]]; then | |
| if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then | |
| ORIGIN_URL="$(git config --get remote.origin.url || true)" | |
| GH_USER="$(sed -E 's#(git@github\.com:|https://github\.com/)([^/]+).*#\2#' <<<"$ORIGIN_URL" 2>/dev/null || echo "")" | |
| fi | |
| fi | |
| [[ -z "${GH_USER}" ]] && { echo "ERROR: set GH_USER"; exit 2; } | |
| headers=( "-sS" "-L" "-H" "User-Agent: gh-stats-script" "-H" "Accept: application/vnd.github+json" ) | |
| [[ -n "${GITHUB_TOKEN}" ]] && headers+=( "-H" "Authorization: Bearer ${GITHUB_TOKEN}" ) | |
| api() { curl "${headers[@]}" "$1"; } | |
| # collect repos (paginate) | |
| collect_repos() { | |
| local page=1 per=100 all="[]" | |
| while :; do | |
| local chunk; chunk="$(api "https://api.github.com/users/${GH_USER}/repos?per_page=${per}&page=${page}")" | |
| local cnt; cnt="$(jq 'length' <<<"$chunk")" | |
| all="$(jq -s 'add' <(echo "$all") <(echo "$chunk"))" | |
| [[ "$cnt" -lt "$per" ]] && break | |
| page=$((page+1)); [[ "$page" -gt 10 ]] && break | |
| done | |
| echo "$all" | |
| } | |
| filter_repos() { | |
| jq '[ .[] | { name, stargazers_count, forks_count, language, archived, fork, pushed_at } ]' | |
| } | |
| search_commits_total() { | |
| local start_date="${1:-2017-01-01}" | |
| local out | |
| if [[ -n "${GITHUB_TOKEN}" ]]; then | |
| out="$(curl -sS -L \ | |
| -H "User-Agent: gh-stats-script" \ | |
| -H "Authorization: Bearer ${GITHUB_TOKEN}" \ | |
| -H "Accept: application/vnd.github.cloak-preview" \ | |
| "https://api.github.com/search/commits?q=author:${GH_USER}+author-date:>=${start_date}" \ | |
| | jq -r '.total_count // empty' || true)" | |
| else | |
| out="$(curl -sS -L \ | |
| -H "User-Agent: gh-stats-script" \ | |
| -H "Accept: application/vnd.github.cloak-preview" \ | |
| "https://api.github.com/search/commits?q=author:${GH_USER}+author-date:>=${start_date}" \ | |
| | jq -r '.total_count // empty' || true)" | |
| fi | |
| [[ -z "$out" ]] && out="$(api "https://api.github.com/search/commits?q=author:${GH_USER}+author-date:>=${start_date}" | jq -r '.total_count // 0' || echo 0)" | |
| echo "${out:-0}" | |
| } | |
| USER_JSON="$(api "https://api.github.com/users/${GH_USER}")" | |
| REPOS_JSON="$(collect_repos | filter_repos)" | |
| NAME="$(jq -r '.name // empty' <<<"$USER_JSON")" | |
| FOLLOWERS="$(jq -r '.followers // 0' <<<"$USER_JSON")" | |
| PUBLIC_REPOS="$(jq -r '.public_repos // 0' <<<"$USER_JSON")" | |
| TOTAL_STARS="$(jq '[.[].stargazers_count] | add // 0' <<<"$REPOS_JSON")" | |
| TOTAL_FORKS="$(jq '[.[].forks_count] | add // 0' <<<"$REPOS_JSON")" | |
| TOP_REPOS="$(jq -r 'sort_by(.stargazers_count) | reverse | .[:5] | | |
| map("⭐ " + .name + " (" + (.stargazers_count|tostring) + ")") | .[]' <<<"$REPOS_JSON")" | |
| TOP_LANGS="$(jq -r ' | |
| map(.language) | del(.[] | select(.==null)) | | |
| group_by(.) | map({lang: .[0], count: length}) | | |
| sort_by(.count) | reverse | .[:5] | | |
| map("• " + .lang + " (" + (.count|tostring) + ")") | .[] | |
| ' <<<"$REPOS_JSON")" | |
| START_DATE="${GH_START_DATE:-2017-01-01}" | |
| COMMITS_TOTAL="$(search_commits_total "${START_DATE}")" | |
| printf "root@%s:~# ./github-stats.sh\n" "${GH_USER}" | |
| if [[ -n "$NAME" ]]; then | |
| printf "User: %s (%s)\n" "$NAME" "$GH_USER" | |
| else | |
| printf "User: %s\n" "$GH_USER" | |
| fi | |
| printf "Followers: %s\n" "$FOLLOWERS" | |
| printf "Public Repos: %s\n" "$PUBLIC_REPOS" | |
| printf "Stars (total): %s Forks (total): %s\n" "$TOTAL_STARS" "$TOTAL_FORKS" | |
| printf "Commits since %s: %s\n" "$START_DATE" "$COMMITS_TOTAL" | |
| printf "\nTop repos by stars:\n" | |
| if [[ -n "$TOP_REPOS" ]]; then | |
| echo "$TOP_REPOS" | |
| else | |
| echo "• (no repositories found)" | |
| fi | |
| printf "\nTop languages (by primary tag):\n" | |
| if [[ -n "$TOP_LANGS" ]]; then | |
| echo "$TOP_LANGS" | |
| else | |
| echo "• (no languages detected)" | |
| fi | |
| printf "\nDone.\n" | |
| EOF | |
| chmod +x scripts/github-stats.sh | |
| - name: Render github-stats into README (robust; no sed) | |
| env: | |
| FILE: profile/README.md | |
| GH_USER: mzcydev | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| test -f "$FILE" || { echo "::error file=$FILE::File not found"; exit 1; } | |
| OUT="$(GH_USER="$GH_USER" GITHUB_TOKEN="$GITHUB_TOKEN" bash scripts/github-stats.sh)" | |
| # AWK ersetzt den Inhalt zwischen <!-- STATS-START --> und <!-- STATS-END --> | |
| # und fügt die Codefence ```bash automatisch mit ein. | |
| awk -v payload="$OUT" ' | |
| BEGIN{inblk=0} | |
| { | |
| if ($0 ~ /<!-- STATS-START -->/) { | |
| print $0 | |
| print "```bash" | |
| print payload | |
| print "```" | |
| inblk=1 | |
| next | |
| } | |
| if (inblk && $0 ~ /<!-- STATS-END -->/) { | |
| print $0 | |
| inblk=0 | |
| next | |
| } | |
| if (inblk) next | |
| }' "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE" | |
| - name: Commit & push | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add profile/README.md scripts/github-stats.sh | |
| git diff --cached --quiet && echo "No changes to commit" || git commit -m "chore: auto-update uptime, repos & stats" | |
| git push |