6.1 KiB
GiteaCustomApi
Gitea repository metadata cache + query API for git.hangman-lab.top.
Overview
A lightweight Go service that:
- Caches repository metadata (id, name, owner, visibility, url) from Gitea MySQL database
- Refreshes every 5 hours via full sync
- Listens for Gitea webhook events (repo create/delete) for real-time cache updates
- Exposes a
/list?username=xxxendpoint that queries cached repos + live collaborator permissions from MySQL
Architecture
┌─────────────────────────────────────────────────────────────┐
│ VPS.git │
│ ┌──────────────┐ ┌─────────────┐ ┌──────────────────┐ │
│ │ Gitea DB │ │ go-server │ │ Nginx │ │
│ │ (MySQL) │◄──│ (cache+api) │ │ /c-api -> :8080 │ │
│ └──────────────┘ └─────────────┘ └──────────────────┘ │
│ ▲ │
│ ┌────────┴────────┐ │
│ │ Gitea Webhook │ │
│ │ (create/delete)│ │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Tech Stack
- Language: Go
- Source DB: Gitea MySQL (read-only, same docker network)
- HTTP: Standard library
net/http - Deployment: Docker + Docker Compose on vps.git
- API Key: Rotates every 10 minutes, stored in Docker volume at
/data/api-key - Cache: In-memory map (repo_id → Repo), refreshed every 5h + webhook updates
Authentication
All /list and /webhook/gitea endpoints require Authorization: Bearer <api-key> header.
API Key Rotation
- API key is generated every 10 minutes by the c-api service itself
- Stored in the Docker volume at
/data/api-key - Clients must fetch the latest key before each request
Client-side key fetch
Before calling any c-api endpoint, fetch the current key:
API_KEY=$(ssh root@vps.git "cat /path/to/api-key")
curl -H "Authorization: Bearer $API_KEY" "https://git.hangman-lab.top/c-api/list?username=xxx"
Note
: Script-side clients (e.g.
list-projs) should perform this key fetch as part of their request flow. The key changes every 10 minutes so it must be re-fetched each time.
API
GET /list?username={username}
Requires:
Authorization: Bearer <api-key>header
Returns all repositories visible to the given Gitea user.
Response (JSON):
[
{
"name": "ClawSkills",
"owner": "lyn",
"url": "https://git.hangman-lab.top/lyn/ClawSkills.git",
"is_private": false,
"can_write": true
}
]
can_write is determined at query time by checking:
- User is the repo owner, OR
- User has explicit access via
accesstable, OR - User is a member of a team that has access to the repo
POST /webhook/gitea
Receives Gitea webhook events (create/delete only).
Supported events:
repository.create— insert new repo into cacherepository.delete— remove repo from cache
GET /health
Returns {"status": "ok"}.
Data Model
In-memory cache: map[int64]*Repo
type Repo struct {
ID int64
Name string
Owner string
IsPrivate bool
URL string
}
Cached in a sync.RWMutex-protected map keyed by Gitea repo ID.
Refreshed on startup, every 5 hours, and on webhook events.
Configuration
Environment variables:
| Variable | Default | Description |
|---|---|---|
DB_HOST |
mysql |
MySQL container hostname |
DB_USER |
root |
MySQL username |
DB_PASS |
— | MySQL password |
DB_NAME |
giteadb |
MySQL database name |
WEBHOOK_SECRET |
— | Gitea webhook secret token |
PORT |
8080 |
HTTP listen port |
Docker
Dockerfile
FROM golang:1.22-alpine AS build
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY *.go ./
RUN CGO_ENABLED=0 GOOS=linux go build -o server .
CMD ["./server"]
docker-compose.yml (fragment to merge into vps.git compose)
services:
gitea-custom-api:
build: ./gitea-custom-api
container_name: gitea-custom-api
restart: unless-stopped
environment:
DB_HOST: mysql
DB_USER: root
DB_PASS: ${MYSQL_ROOT_PASSWORD}
DB_NAME: giteadb
WEBHOOK_SECRET: ${GITEA_WEBHOOK_SECRET}
PORT: 8080
volumes:
- ./gitea-custom-api/api-key:/data/api-key
networks:
- git-network
networks:
git-network:
external: true
Nginx location (merge into vps.git nginx config)
location /c-api/ {
proxy_pass http://localhost:8080/;
}
Gitea Webhook Setup
In Gitea admin panel → Webhooks → Add webhook:
- URL:
https://git.hangman-lab.top/c-api/webhook/gitea - HTTP Method: POST
- Content Type:
application/json - Secret: set a strong token, pass via
WEBHOOK_SECRET - Events: Repository: Create, Repository: Delete
Refresh Strategy
- Startup: full sync of all non-archived repos from
repositorytable - Every 5 hours: full sync (upsert based on repo id)
- Webhook: incremental create/delete for real-time updates
- On cache miss during query: fall back to live MySQL query
Out of Scope
- Authentication on the API endpoint (internal service, only accessible via nginx on vps.git)
- Caching collaborator permissions (queried live per request)
- Handling repo rename/transfer (caught by 5h refresh)