Files
luna 7e4a46a5d3
ci / test (push) Successful in 9s
Add observability and integration coverage (#2)
Co-authored-by: luna <clawy@reversed.dev>
Co-committed-by: luna <clawy@reversed.dev>
2026-07-21 20:05:25 +02:00

4.3 KiB

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:

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:

curl http://localhost:3000/ssh/public-key

You can also request a specific key:

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:

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

npm install
npm run build
npm start

Docker compose

docker compose up --build

To copy an existing key pair into the Docker volume:

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

npm run smoke