142 lines
4.3 KiB
Markdown
142 lines
4.3 KiB
Markdown
# loc-via-git
|
|
|
|
Tiny API that clones a Git repo and counts its non-empty lines of code.
|
|
|
|
## Features
|
|
|
|
- Plain text and JSON endpoints
|
|
- Optional API key enforcement
|
|
- Per-repo in-memory caching
|
|
- Extension-based language breakdown
|
|
- Generic project metadata files are ignored during counting
|
|
- Basic rate limiting
|
|
- Bounded concurrent scans so the host does not get hammered
|
|
- Host allowlist and scan/file size limits, with a capped temporary filesystem in Docker
|
|
- SSH key file support for private repos
|
|
- Docker Compose deployment
|
|
|
|
## Endpoints
|
|
|
|
- `GET /loc.txt?repo=<git-url>&ssh_key=<optional-key-file>&ref=<optional-ref>&api_key=<optional-api-key>`
|
|
- Returns the line count as plain text.
|
|
- `GET /loc?repo=<git-url>&ssh_key=<optional-key-file>&ref=<optional-ref>&api_key=<optional-api-key>`
|
|
- Returns JSON metadata, including the resolved commit SHA and a language breakdown by files and non-empty lines.
|
|
- `GET /loc/diff?repo=<git-url>&base=<git-ref>&head=<git-ref>&ssh_key=<optional-key-file>`
|
|
- Returns both snapshots and their total/per-language LOC delta.
|
|
- `GET /health`
|
|
- Health plus queue/cache stats.
|
|
- `GET /metrics`
|
|
- Prometheus-compatible request, cache, scan, file, and line counters. Protected by the API key when one is configured.
|
|
|
|
## Auth
|
|
|
|
No auth by default. If `API_KEY` is set, send it as either:
|
|
|
|
- `x-api-key: ...`
|
|
- `Authorization: Bearer ...`
|
|
- `api_key=...` query param
|
|
|
|
The `api_key` query param is unsafe and discouraged because it can leak through logs, browser history, analytics, caches, and referrers.
|
|
Prefer `x-api-key` or `Authorization` whenever possible.
|
|
|
|
## SSH keys
|
|
|
|
SSH keys live inside the Docker volume mounted at `/app/keys`, not in the repo or a host bind mount.
|
|
|
|
You have two options:
|
|
|
|
- Provide a key pair yourself inside the `ssh_keys` Docker volume
|
|
- Let the service generate the default key by setting `GENERATE_SSH_KEY_IF_MISSING=true`
|
|
|
|
Example:
|
|
|
|
```bash
|
|
curl "http://localhost:3000/loc?repo=ssh://git@example.com/org/repo.git&ssh_key=loc_via_git_ed25519"
|
|
```
|
|
|
|
Fetch the public key to add it on the Git host:
|
|
|
|
```bash
|
|
curl http://localhost:3000/ssh/public-key
|
|
```
|
|
|
|
You can also request a specific key:
|
|
|
|
```bash
|
|
curl "http://localhost:3000/ssh/public-key?ssh_key=loc_via_git_ed25519"
|
|
```
|
|
|
|
## Cleanup and caching
|
|
|
|
- Every clone happens in a temporary directory and is deleted in a `finally` block after the scan finishes or fails.
|
|
- The service also sweeps stale temp directories in case a process dies mid-scan.
|
|
- Cache entries live in memory only and expire after `CACHE_TTL_MINUTES`.
|
|
|
|
## Operations
|
|
|
|
- Requests emit structured JSON logs with an opaque request ID, route, status, and duration. Query strings and API keys are never logged.
|
|
- The service handles `SIGINT` and `SIGTERM`: it stops accepting new connections, clears its cleanup timer, and exits after active connections drain (or 10 seconds).
|
|
- `/metrics` is compatible with Prometheus scraping. `/health` remains unauthenticated for container health checks.
|
|
|
|
## Counting rules
|
|
|
|
- Only text files are counted.
|
|
- Empty lines are ignored.
|
|
- Generic project metadata, generated/minified files, and common build/vendor directories are skipped.
|
|
- Scans are rejected when they exceed the configured file, per-file, or total scanned-byte limits.
|
|
|
|
## Repository access
|
|
|
|
`ALLOWED_GIT_HOSTS` is optional. Leave it unset or empty to allow every remote host, or use a comma-separated allowlist such as `gitea.reversed.dev,github.com`. Local paths, `file://` URLs, non-SSH/HTTPS protocols, and HTTPS URLs containing credentials are always rejected.
|
|
|
|
## Configuration
|
|
|
|
Copy `.env.example` to `.env` and adjust:
|
|
|
|
```env
|
|
PORT=3000
|
|
API_KEY=
|
|
ALLOWED_GIT_HOSTS=
|
|
CACHE_TTL_MINUTES=5
|
|
CACHE_SWEEP_INTERVAL_MINUTES=5
|
|
RATE_LIMIT_WINDOW_MINUTES=5
|
|
RATE_LIMIT_MAX=30
|
|
MAX_CONCURRENT_SCANS=4
|
|
MAX_FILES_PER_SCAN=20000
|
|
MAX_FILE_SIZE_MB=5
|
|
MAX_SCAN_SIZE_MB=100
|
|
CLONE_TIMEOUT_SECONDS=45
|
|
DEFAULT_SSH_KEY_NAME=loc_via_git_ed25519
|
|
GENERATE_SSH_KEY_IF_MISSING=false
|
|
TRUST_PROXY=false
|
|
SSH_KEYS_DIR=/app/keys
|
|
TMP_DIR=/tmp/loc-via-git
|
|
```
|
|
|
|
## Local run
|
|
|
|
```bash
|
|
npm install
|
|
npm run build
|
|
npm start
|
|
```
|
|
|
|
## Docker compose
|
|
|
|
```bash
|
|
docker compose up --build
|
|
```
|
|
|
|
To copy an existing key pair into the Docker volume:
|
|
|
|
```bash
|
|
docker cp ./id_ed25519 loc-via-git-api-1:/app/keys/loc_via_git_ed25519
|
|
docker cp ./id_ed25519.pub loc-via-git-api-1:/app/keys/loc_via_git_ed25519.pub
|
|
```
|
|
|
|
## Smoke test
|
|
|
|
```bash
|
|
npm run smoke
|
|
```
|