Files
ghstats/docs/deployment-guide.md
T
tiennm99 94e13d2c65 docs: resync with current state across all project docs
- design-guidelines: every dimension was stale — card frame 340x200
  (was 500x220), corner radius 6, title 15px at (20,30), row y0/dy,
  donut centre (250,110) r=55/30, topN=5, legend y0=55 dy=20, bar chart
  area [35,325]x[45,155], area chart [28,312]x[45,150], icon scale 0.75.
- code-standards: FetchContributionsAllTime signature now ctx-first,
  viewbox 500x220 → 340x200.
- codebase-summary: test coverage lists main_test.go +
  TestDonutSingleSlice/Empty; filename convention says plain kebab-case
  (no numeric prefix).
- project-overview-pdr: forks/private defaults now on, not off.
- project-roadmap: add Phase 7 (Marketplace polish — resize, numeric-
  prefix drop, v1 floating tag, rename-rollback). Renumber planned
  phases 8-11. Fix "hard width 500 px" limitation.
- deployment-guide: document update-major-tag job; note
  Marketplace listing name is `ghstats-cards`.
2026-04-19 00:09:00 +07:00

155 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Deployment Guide
Three consumption paths: **GitHub Action**, **prebuilt binaries**, **go install**.
## 1. GitHub Action (recommended for README auto-updates)
### Workflow template
File: `.github/workflows/ghstats.yml` in your profile repo.
```yaml
name: ghstats
on:
schedule:
- cron: "0 0 * * *" # daily at 00:00 UTC
workflow_dispatch:
permissions:
contents: write # needed for commit_changes
jobs:
cards:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: tiennm99/ghstats@v1
with:
user: ${{ github.repository_owner }}
token: ${{ secrets.GHSTATS_TOKEN }}
themes: dracula,github_dark,tokyonight
tz: Asia/Saigon
include_forks: "false"
include_private: "false"
commit_changes: "true"
```
### Required secrets
`GHSTATS_TOKEN`: a **classic** personal access token with at minimum:
| Scope | Needed for |
| --- | --- |
| `read:user` | Basic profile fields, contribution calendar |
| `repo` | Only if `include_private: "true"` |
Fine-grained PATs and the default `${{ github.token }}` lack the introspection scope for contribution calendars in many orgs, so a classic PAT is recommended.
Create one at <https://github.com/settings/tokens> → "Generate new token (classic)" → select `read:user` (+ `repo` if needed) → save as repo secret `GHSTATS_TOKEN`.
### Embedding in README
```md
![profile](./output/dracula/profile-details.svg)
![repos-per-language](./output/dracula/repos-per-language.svg)
![most-commit-language](./output/dracula/most-commit-language.svg)
![stats](./output/dracula/stats.svg)
![productive-time](./output/dracula/productive-time.svg)
![contributions](./output/dracula/contributions.svg)
![most-commit-language-all-time](./output/dracula/most-commit-language-all-time.svg)
![productive-time-all-time](./output/dracula/productive-time-all-time.svg)
![contributions-all-time](./output/dracula/contributions-all-time.svg)
```
The Action commits SVGs to `output/<theme>/` on the default branch. GitHub serves them from the raw URL the README references.
## 2. Prebuilt binaries
Each tag under `v*` publishes:
- Linux `amd64`, `arm64`
- macOS `amd64`, `arm64`
- Windows `amd64`
Released via `.github/workflows/release.yml` which matrixes `GOOS` × `GOARCH`, strips symbols (`-ldflags="-s -w"`), and uploads tar.gz / zip to the GitHub Release.
Install:
```sh
# Linux x86_64 example
curl -L https://github.com/tiennm99/ghstats/releases/latest/download/ghstats_linux_amd64.tar.gz \
| tar xz
./ghstats -user YOUR_USERNAME
```
## 3. go install
```sh
go install github.com/tiennm99/ghstats@latest
```
Requires Go 1.26+. Puts the binary in `$(go env GOPATH)/bin`.
## Docker image
Published to `ghcr.io/tiennm99/ghstats:<tag>` on each `v*` release via `.github/workflows/release.yml` (buildx, multi-tag: exact version, major.minor, major, latest).
The Action itself uses a runner-built image by default (`image: Dockerfile` in `action.yml`). To switch to the pre-built image for faster cold starts, edit `action.yml`:
```yaml
runs:
using: docker
image: docker://ghcr.io/tiennm99/ghstats:v1
```
## Release process
1. Tag: `git tag -a v1.2.0 -m "..." && git push origin v1.2.0`.
2. `release.yml` runs `go vet` + `go test` as a gate before the docker and
binaries jobs. If tests fail, no artifacts ship.
3. On green, GHCR push + cross-platform binary artifacts happen automatically.
4. The `update-major-tag` job force-moves the floating major tag (e.g. `v1`)
to this release's commit after test + docker + binaries all pass.
Consumers pinned to `tiennm99/ghstats@v1` pick up the release on their
next Action run without a workflow edit.
5. Docker base images and third-party actions are SHA-pinned (with version
comments) so mutable-tag changes upstream can't rewrite a released image.
6. **Marketplace publishing (one-time per repo):** GitHub only exposes the
"Publish this Action to the GitHub Marketplace" toggle on the Release
web UI — there is no CLI flag. Open the newly created release at
`https://github.com/tiennm99/ghstats/releases/tag/vX.Y.Z/edit`, tick the
marketplace checkbox, accept the terms, and re-publish. Subsequent
releases inherit marketplace visibility automatically. The Marketplace
listing name is `ghstats-cards` (set in `action.yml`) because the bare
`ghstats` is already taken on the Marketplace.
## Rollback
- Revert the tag: `git push --delete origin v1.2.0`, delete GitHub release, delete GHCR tag.
- Users pinned to `@v1` keep working because the previous patch is still tagged.
## Rate limit considerations
| Scenario | GraphQL calls per run | Notes |
| --- | --- | --- |
| Typical user, defaults | 1540 | Well under 5000 pts/hr |
| Active user (8 years, 30+ seed repos) | 4080 | Still comfortable |
| `-include-private=true` with 100+ work repos | 80200 | Fine for daily cron |
| Adversarial user with 500+ committed repos/year | Capped by `maxRepositories: 100` per year query | Long tail drops silently |
No REST calls today. Future `-accurate-languages` mode will push toward 1000+ REST per run; schedule that mode less frequently (weekly, not daily).
The client auto-handles rate-limit responses: on 429 or 403 with `X-RateLimit-Remaining: 0`, it sleeps up to 5 minutes (honoring `Retry-After` / `X-RateLimit-Reset`) and retries once. A reset window longer than 5 min surfaces as an error so CI can reschedule instead of burning runner time. Use the `-timeout` flag (default 30m) to cap total fetch duration; `SIGINT`/`SIGTERM` cancels in-flight requests cleanly.
## Troubleshooting
| Symptom | Check |
| --- | --- |
| "error: fetch profile: graphql: Could not resolve to a User" | Username typo |
| "http 401" | Token expired or lacks `read:user` |
| "rate limit resets in 42m (>5m0s max wait)" | Client refused to sleep through a long window; reschedule the Action |
| "http 403" on non-rate-limit path | PAT scope too narrow |
| Blank contribution chart | User has 0 contributions in their window; expected |
| Private repo data missing | `-include-private=true` not set, or PAT lacks `repo` |
| Nothing committed by the Action | Check `permissions: contents: write` in the workflow |