indexer/README.md
Ricardo (XenGi) Band 63c6f4b761
Some checks failed
CI pipeline for ffd-indexer / checks (go test + golangci) (push) Failing after 26s
CI pipeline for ffd-indexer / build binaries + containers (push) Failing after 3s
CI pipeline for ffd-indexer / format (gofmt + nixfmt) (push) Failing after 3s
CI pipeline for ffd-indexer / integration (live scrape + Typesense) (push) Failing after 20s
🔥
2026-10-01 00:37:44 +02:00

169 lines
6 KiB
Markdown

[![CI Build Status](https://git.xengi.de/ffd/indexer/badges/workflows/ci.yaml/badge.svg?style=flat-square)](https://git.xengi.de/ffd/indexer/actions?workflow=ci.yaml)
[![Container Build Status](https://git.xengi.de/ffd/indexer/badges/workflows/container.yaml/badge.svg?style=flat-square)](https://git.xengi.de/ffd/indexer/actions?workflow=container.yaml)
# ffd-indexer
Federated Forge Discovery Indexer.
Builds and serves an index of public open-source software projects, discovered
from self-hosted forges you configure. The indexer scrapes each forge's public
API, enriches every repository with languages, topics, licenses and READMEs,
stores the result in SQLite, and exposes the state as Prometheus metrics and a
Typesense search index.
> **Why this exists** — public code is scattered across many small forges, and
> there is no federated view of it. This project fills that gap: point it at a
> list of forges, and it continuously aggregates and indexes what is publicly
> hosted there, so you can search and measure across all of them at once.
**Status:** working. Both binaries build from a Nix flake, ship as systemd
services and containers, and are verified end-to-end against live forges.
License: **AGPL-3.0** (see below).
Two binaries ship together:
- **`ffd-indexer`** — the scraper. Runs once per schedule, scrapes every
configured forge, stores the index, and syncs it to Typesense.
- **`ffd-metrics`** — a long-running daemon. Serves the stored index state as
Prometheus metrics at `/metrics`; it never scrapes forges itself.
---
## Quick start
The fastest way to try it is the flake's container image (it runs once and
exits, bundling CA certificates so HTTPS to forges work out of the box):
```sh
nix build .#container
docker load < result
docker run --rm \
-v "$PWD/config.yaml:/etc/ffd-indexer/config.yaml:ro" \
ffd-indexer
```
A minimal `config.yaml`:
```yaml
db_path: /data/index.db
forges:
- url: git.example.org
type: forgejo
```
Mount a volume for the DB so the index persists between runs:
```sh
docker run --rm \
-v "$PWD/config.yaml:/etc/ffd-indexer/config.yaml:ro" \
-v "$PWD/data:/data" \
ffd-indexer
```
Or run from the flake directly (local dev):
```sh
nix run .#default -- -config config.yaml
```
**The next step** — point it at a second forge and check the output. See
[Configuration](#configuration) for the `type` field, and [DOCS.md](DOCS.md)
for metrics, architecture, and the NixOS module.
---
## Configuration
The indexer reads a single YAML file (default `config.yaml`, override with
`-config <path>`). Both binaries share the same file.
```yaml
db_path: "./index.db" # SQLite database (index + per-forge status)
typesense: # optional search index
host: "http://localhost:8108"
api_key: "dev-key" # inline key...
# api_key_file: /run/secrets/ts # ...or a secret file (takes precedence)
metrics:
listen: "127.0.0.1:9090" # ffd-metrics binds here and serves /metrics
forges:
- url: git.example.org
type: forgejo
- url: git.example.net
```
- **`url`** — a bare hostname (FQDN). The indexer connects over `https://` by
default and adds it internally. You may prefix `http://` or `https://` to
force the connection protocol. The stored identity and all metrics labels
always use the bare hostname.
- **`type`** — the forge's software. Set it by hand (`forgejo` or `gitea`) for
the recommended, deterministic setup. The default `auto` re-detects the type
on every run; it is **experimental** (see [DOCS.md](DOCS.md)). A forge that
disallows `/api/` in its robots.txt is skipped either way.
- **`typesense.api_key` / `typesense.api_key_file`** — the Typesense API key,
either inlined or read from a secret file (e.g. a [sops](https://github.com/getsops/sops)/
[agenix](https://github.com/ryantm/agenix) secret). If both are set, the
**file takes precedence**, so the key never needs to appear in the config.
---
## What it does
For each configured forge the indexer:
1. **Respects robots.txt** — skips the instance if `/api/` or a sub-API is
disallowed.
2. **Lists public source repositories** — paginated search; forks and mirrors
are excluded.
3. **Enriches each repository** — fetches topics, languages, latest commit,
latest release, the README, and derives SPDX licenses from the repo's files.
The indexer runs once and exits (systemd timer / container); it never stays
resident. On each run it records per-forge status (online, type, version,
last error) into SQLite, upserts every project, and pushes the index to
Typesense.
Everything else — request volume, the HTTP transport (HTTP/2, optional
HTTP/3), error handling, and the full detail — is in [DOCS.md](DOCS.md).
---
## Verify it's working
The companion daemon `ffd-metrics` serves the index's state as Prometheus
metrics — it never scrapes forges itself; it renders the SQLite state on each
scrape:
```sh
ffd-metrics -config config.yaml
curl -s http://127.0.0.1:9090/metrics
```
You should see one series per configured forge: `ffd_forge_info` (type +
software version) and `ffd_forge_online` (0/1). The full metric reference and
ready-made PromQL queries are in [DOCS.md](DOCS.md).
For a production setup, the NixOS module wires the one-shot indexer timer, the
`ffd-metrics` daemon, and the config file — see [DOCS.md](DOCS.md).
---
## Contributing
Contributions are welcome. Please:
1. Run `go test ./...` and `go vet ./...` from the dev shell
(`nix develop`), and run `nix flake check` (lint + tests) before opening a
PR.
2. Keep the diff small and focused, and update the README/DOCS when behavior
changes.
3. Open a pull request; issues and PRs are triaged.
---
## License
This project is licensed under the **GNU Affero General Public License v3.0**
(AGPL-3.0) — see [LICENSE](LICENSE). It is a strong-copyleft license selected
for network server software: if you modify it and provide the resulting
service to others over a network, those modifications must be made available
under the same license.