mirror of
https://github.com/tiennm99/ghglance.git
synced 2026-10-11 03:13:20 +00:00
The web UI now runs every job on an OAuth token from "Sign in with GitHub". The ticked options decide the requested scopes (read:user, plus repo for private repos, plus read:org for org repos); a grant wider than requested is refused. The token lives only on the job and is revoked when the job ends, on every path including shutdown. The server no longer holds a GitHub token of its own, and the pasted-token field and /generate are gone. -serve requires -oauth-client-id, -oauth-client-secret and -public-url (GHGLANCE_OAUTH_* and GHGLANCE_PUBLIC_URL), and compose.yml requires them too. The CLI and the Action keep -token unchanged.
192 lines
8.3 KiB
Markdown
192 lines
8.3 KiB
Markdown
# Deployment Guide
|
||
|
||
Three consumption paths: **GitHub Action**, **prebuilt binaries**, **go install**.
|
||
The same binary also runs as a self-hosted **web UI** (section 4).
|
||
|
||
## 1. GitHub Action (recommended for README auto-updates)
|
||
|
||
### Workflow template
|
||
|
||
File: `.github/workflows/ghglance.yml` in your profile repo.
|
||
|
||
```yaml
|
||
name: ghglance
|
||
|
||
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/ghglance@v1
|
||
with:
|
||
user: ${{ github.repository_owner }}
|
||
token: ${{ secrets.GHGLANCE_TOKEN }}
|
||
themes: dracula,github_dark,tokyonight
|
||
tz: Asia/Saigon
|
||
# start_of_week: monday # optional; default sunday — rotates heatmap rows + weekday bars
|
||
include_forks: "false"
|
||
include_private: "false"
|
||
commit_changes: "true"
|
||
```
|
||
|
||
### Required secrets
|
||
|
||
`GHGLANCE_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 `GHGLANCE_TOKEN`.
|
||
|
||
### Embedding in README
|
||
|
||
```md
|
||

|
||

|
||

|
||

|
||

|
||

|
||

|
||

|
||

|
||

|
||

|
||

|
||

|
||

|
||

|
||
```
|
||
|
||
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/ghglance/releases/latest/download/ghglance_linux_amd64.tar.gz \
|
||
| tar xz
|
||
./ghglance -user YOUR_USERNAME
|
||
```
|
||
|
||
## 3. go install
|
||
|
||
```sh
|
||
go install github.com/tiennm99/ghglance@latest
|
||
```
|
||
|
||
Requires Go 1.26+. Puts the binary in `$(go env GOPATH)/bin`.
|
||
|
||
## 4. Web UI (Docker Compose / Coolify)
|
||
|
||
`compose.yml` at the repo root builds the `Dockerfile`, overrides its
|
||
entrypoint to run `ghglance -serve :8080 -data-dir /data`, stores cards in
|
||
the `ghglance-data` volume, and health-checks `/healthz` with busybox
|
||
`wget`. It publishes no host port; Coolify routes the domain generated for
|
||
`SERVICE_FQDN_GHGLANCE_8080` to container port 8080.
|
||
|
||
| Variable | Needed for |
|
||
| --- | --- |
|
||
| `GHGLANCE_OAUTH_CLIENT_ID` | Required. Client ID of the GitHub OAuth App behind "Sign in with GitHub" (`-oauth-client-id`). |
|
||
| `GHGLANCE_OAUTH_CLIENT_SECRET` | Required. That OAuth App's client secret (`-oauth-client-secret`). Never logged or printed. |
|
||
| `GHGLANCE_PUBLIC_URL` | Required. The site's external origin, e.g. `https://ghglance.sg.miti99.com` (`-public-url`). The OAuth App's callback URL must be exactly `<public-url>/auth/callback`. |
|
||
|
||
The web UI is sign-in only: the server holds no GitHub token, and every
|
||
generation runs on the visitor's OAuth token, revoked when the job ends.
|
||
`compose.yml` refuses to start while any of the three variables is empty,
|
||
and `-serve` exits with an error naming the missing settings.
|
||
|
||
Coolify: register an OAuth App (GitHub **Settings > Developer settings >
|
||
OAuth Apps > New OAuth App**; homepage URL = the domain, authorization
|
||
callback URL = `<domain>/auth/callback`) and generate a client secret.
|
||
Then create a Docker Compose resource from this repo, compose file
|
||
`/compose.yml`, set the three variables, assign the domain, and deploy;
|
||
the startup log prints the callback URL it uses. Server flags
|
||
(`-cooldown`, `-retention`, `-workers`, `-timeout`) are changed by editing `command:` in
|
||
`compose.yml`. Steps for a plain Docker host and the request-handling rules
|
||
are in the README's "Run the web UI" section.
|
||
|
||
Rollback: redeploy the previous commit. Card sets on the volume are
|
||
format-stable, so no data migration is involved.
|
||
|
||
## Docker image
|
||
|
||
Published to `ghcr.io/tiennm99/ghglance:<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/ghglance: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/ghglance@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:** the action is listed on the GitHub Marketplace as
|
||
[`ghglance`](https://github.com/marketplace/actions/ghglance); the listing
|
||
name comes from `name:` in `action.yml`. Publishing is a web-UI step —
|
||
edit the GitHub release and tick "Publish this Action to the GitHub
|
||
Marketplace" — since `action-gh-release` cannot publish it.
|
||
|
||
## 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 | 15–40 | Well under 5000 pts/hr |
|
||
| Active user (8 years, 30+ seed repos) | 40–80 | Still comfortable |
|
||
| `-include-private=true` with 100+ work repos | 80–200 | 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 |
|