{"id":49058145,"url":"https://github.com/soakes/blackhole-threats","last_synced_at":"2026-05-14T21:01:33.473Z","repository":{"id":352481056,"uuid":"1215023855","full_name":"soakes/blackhole-threats","owner":"soakes","description":"Go-based RTBH daemon that turns threat feeds into controlled BGP blackhole announcements.","archived":false,"fork":false,"pushed_at":"2026-05-07T19:34:32.000Z","size":30030,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-07T21:31:58.514Z","etag":null,"topics":["bgp","gobgp","golang","network-security","rtbh","threat-intelligence"],"latest_commit_sha":null,"homepage":"https://soakes.github.io/blackhole-threats/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/soakes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-19T11:29:46.000Z","updated_at":"2026-05-07T19:30:51.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/soakes/blackhole-threats","commit_stats":null,"previous_names":["soakes/blackhole-threats"],"tags_count":31,"template":false,"template_full_name":null,"purl":"pkg:github/soakes/blackhole-threats","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soakes%2Fblackhole-threats","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soakes%2Fblackhole-threats/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soakes%2Fblackhole-threats/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soakes%2Fblackhole-threats/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/soakes","download_url":"https://codeload.github.com/soakes/blackhole-threats/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soakes%2Fblackhole-threats/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33043249,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-13T13:14:54.681Z","status":"online","status_checked_at":"2026-05-14T02:00:06.663Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bgp","gobgp","golang","network-security","rtbh","threat-intelligence"],"created_at":"2026-04-20T00:12:18.187Z","updated_at":"2026-05-14T21:01:33.459Z","avatar_url":"https://github.com/soakes.png","language":"Go","funding_links":["https://buymeacoffee.com/soakes"],"categories":[],"sub_categories":[],"readme":"# 🛡️ Blackhole Threats\n\n\u003e A Go-based RTBH route server that turns threat feeds into controlled BGP blackhole announcements.\n\n[![Validate](https://img.shields.io/github/actions/workflow/status/soakes/blackhole-threats/build-and-validate.yml?branch=main\u0026style=flat-square\u0026label=validate)](https://github.com/soakes/blackhole-threats/actions/workflows/build-and-validate.yml)\n[![Container](https://img.shields.io/github/actions/workflow/status/soakes/blackhole-threats/container-image.yml?branch=main\u0026style=flat-square\u0026label=container)](https://github.com/soakes/blackhole-threats/actions/workflows/container-image.yml)\n[![Release](https://img.shields.io/github/v/release/soakes/blackhole-threats?sort=semver\u0026style=flat-square)](https://github.com/soakes/blackhole-threats/releases)\n[![APT Repository](https://img.shields.io/badge/APT-signed%20repo-A81D33?style=flat-square\u0026logo=debian\u0026logoColor=white)](https://soakes.github.io/blackhole-threats/)\n[![GHCR](https://img.shields.io/badge/GHCR-published-2088FF?style=flat-square\u0026logo=github)](https://ghcr.io/soakes/blackhole-threats)\n[![Go](https://img.shields.io/badge/Go-1.24%2B-00ADD8.svg?style=flat-square\u0026logo=go\u0026logoColor=white)](https://go.dev/)\n[![License](https://img.shields.io/badge/License-MIT-2EA043.svg?style=flat-square)](LICENSE)\n[![Buy Me a Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-support-FFDD00?style=flat-square\u0026logo=buymeacoffee\u0026logoColor=000000)](https://buymeacoffee.com/soakes)\n\nBuilt for operators who want one service, one config format, and one release path\nthey can actually deploy: source builds, container images, Debian packages,\nand a signed APT repository.\n\n**Quick links:** [📦 Releases](https://github.com/soakes/blackhole-threats/releases) · [🐳 GHCR](https://ghcr.io/soakes/blackhole-threats) · [🔐 APT Repository](https://soakes.github.io/blackhole-threats/) · [📚 Documentation](docs/README.md) · [🏗️ Architecture](docs/architecture.md)\n\n## 🧭 Table of Contents\n\n- [📖 Overview](#overview)\n- [✨ Capabilities](#capabilities)\n- [🔄 How It Works](#how-it-works)\n- [✅ Prerequisites](#prerequisites)\n- [🚀 Installation](#installation)\n- [⚙️ Configuration](#configuration)\n- [🌐 Feed Sources and Formats](#feed-sources-and-formats)\n- [🧪 Usage](#usage)\n- [🐳 Container](#container)\n- [📦 Debian Package](#debian-package)\n- [🔐 APT Repository](#apt-repository)\n- [🤖 CI/CD and Release Automation](#cicd-and-release-automation)\n- [🗂️ Project Structure](#project-structure)\n- [🩺 Troubleshooting](#troubleshooting)\n- [🤝 Contributing](#contributing)\n- [📄 License](#license)\n\n---\n\n## 📖 Overview\n\n`blackhole-threats` is a Go RTBH route server. It reads local or remote threat\nfeeds, extracts IPv4 and IPv6 networks, summarises them, and advertises the\nresulting routes over BGP so downstream routers can apply blackhole policy.\n\nIn normal operation the service does four things:\n\n- loads GoBGP and feed configuration from YAML\n- fetches and parses the configured feeds\n- computes the route delta against the current advertised set\n- announces new routes and withdraws stale ones on a refresh loop\n\nThis repository packages that workflow as a single service with first-party\nsource builds, container images, Debian packages, and a signed APT repository.\n\n### Quick Start\n\nPick the path that matches how you deploy:\n\n- **Container**: pull `ghcr.io/soakes/blackhole-threats:latest` for the latest tagged release\n- **Debian**: use the signed APT repository and install `blackhole-threats`\n- **Source**: run `make build` and execute `dist/blackhole-threats`\n- **Validation**: use `-check-config` or `-once` before putting the daemon into service\n\nLonger-form guides for operations, configuration, release flow, and deployment\nexamples live under [`docs/`](docs/README.md).\n\n### First Deployment Checklist\n\n1. Copy [`examples/blackhole-threats.yaml`](examples/blackhole-threats.yaml) and replace the local ASN, router ID, peers, and feed communities for your network.\n2. Validate the file with `./dist/blackhole-threats -conf /path/to/blackhole-threats.yaml -check-config`.\n3. Use `-once` with an unprivileged local BGP port for a first smoke test before binding to production port `179`.\n4. Start the daemon and inspect the startup logs for `tag_version`, `local_as`, `router_id`, `peer_count`, and the first `Refresh completed` line.\n5. Only enable automatic service startup after the one-shot validation path looks correct.\n\n### Motivation and Lineage\n\nThe original `blackhole-threats` project by Eric Barkie established the basic\nGoBGP-based pattern for advertising threat-feed routes. That repository was\narchived on March 31, 2026. This repository continues the same operational\nmodel with current Go toolchains, Debian packaging, container publishing,\nGitHub Actions workflows, and automated pin refreshes.\n\nCredit for the original project and idea belongs to Eric Barkie:\n\n- Original repository: \u003chttps://github.com/ebarkie/blackhole-threats\u003e\n- Original project title: `Blackhole threats (with GoBGP)`\n\n---\n\n## ✨ Capabilities\n\n- **Feed ingestion**: reads local files plus `http://` and `https://` sources\n- **Format handling**: parses plain text, JSON, JSONL, and NDJSON feeds\n- **Prefix support**: extracts and summarises both IPv4 and IPv6 networks\n- **Community control**: supports per-feed BGP communities and defaults to `\u003clocal ASN\u003e:666`\n- **Runtime control**: supports periodic refresh, `SIGUSR1`, `-check-config`, and `-once`\n- **Failure handling**: keeps the last good community state when a feed refresh fails\n- **Logging**: emits structured logfmt-style output for Docker, journald, and syslog pipelines\n- **Distribution**: publishes release binaries, multi-arch container images, Debian packages, and a signed APT repository\n- **Automation**: includes validation, release publishing, and pinned dependency refresh workflows\n\n---\n\n## 🔄 How It Works\n\nAt runtime, `blackhole-threats` follows a simple route lifecycle:\n\n```mermaid\nflowchart TD\n    A[Load YAML configuration] --\u003e B[Merge configured feeds and extra -feed arguments]\n    B --\u003e C[Start embedded GoBGP server]\n    C --\u003e D[Fetch configured feeds concurrently]\n    D --\u003e E[Parse IPv4 and IPv6 prefixes]\n    E --\u003e F[Summarise overlapping prefixes]\n    F --\u003e G[Group routes by BGP community]\n    G --\u003e H[Diff against currently advertised routes]\n    H --\u003e I[Announce new routes]\n    H --\u003e J[Withdraw stale routes]\n    I --\u003e K[Wait for refresh interval or SIGUSR1]\n    J --\u003e K\n    K --\u003e D\n```\n\nIf a feed refresh fails for a community, the daemon keeps the last good routes\nfor that community and retries on the next refresh instead of withdrawing on\npartial input.\n\n1. Load YAML configuration from `blackhole-threats.yaml` or the configured path.\n2. Merge configured feeds with any additional `-feed` CLI arguments.\n3. Start the embedded GoBGP server.\n4. Fetch all configured feeds concurrently.\n5. Parse IPv4 and IPv6 prefixes from each source.\n6. Summarise overlapping prefixes to reduce route churn.\n7. Group routes by BGP community.\n8. Diff the new route set against the current route set.\n9. Announce new routes and withdraw stale ones.\n10. Repeat on the configured refresh interval, or immediately when sent `SIGUSR1`.\n\nFor the package layout and component boundaries, see\n[docs/architecture.md](docs/architecture.md).\n\n---\n\n## ✅ Prerequisites\n\n- A BGP-speaking environment where downstream routers can peer with this service\n- A valid GoBGP-compatible configuration for your local ASN, router ID, and peers\n- One or more threat feeds reachable as:\n  - local files\n  - `http://` URLs\n  - `https://` URLs\n- Go `1.24+` if building from source\n- Docker if running the container image\n- Debian packaging tools if building `.deb` packages locally\n\n---\n\n## 🚀 Installation\n\n### Build From Source\n\n```bash\ngit clone https://github.com/soakes/blackhole-threats.git\ncd blackhole-threats\nmake build\n```\n\nThis produces:\n\n```text\ndist/blackhole-threats\n```\n\nThe minimum supported Go version stays aligned with Debian trixie packaging.\nGitHub Actions and container builds track the current stable Go release\nseparately.\n\n### Run the Published Container\n\n```bash\ndocker pull ghcr.io/soakes/blackhole-threats:latest\n```\n\n`latest` tracks stable tagged releases, and release candidates publish to the\n`ghcr.io/soakes/blackhole-threats:rc` channel plus their full `v*-rc.*` tag.\n\n### Build a Debian Package\n\n```bash\nmake package\n```\n\nThis expects Debian packaging tools such as `debhelper`, `golang-any`, and\n`devscripts` to be available.\n\n---\n\n## ⚙️ Configuration\n\nThe service uses a YAML file with two main sections:\n\n- `gobgp`: the GoBGP configuration set\n- `feeds`: the threat intelligence sources to ingest\n\nA ready-to-edit reference file lives at\n[`examples/blackhole-threats.yaml`](examples/blackhole-threats.yaml).\nThat file is a full reference example with multiple peers, mixed IPv4/IPv6\nneighbors, and a broader feed set. The shorter example below is the README\nversion to keep the page readable.\n\n### Example Configuration\n\n```yaml\ngobgp:\n  global:\n    config:\n      as: 64520\n      routerid: \"198.51.100.10\"\n  neighbors:\n    - config:\n        neighboraddress: \"198.51.100.1\"\n        peeras: 64520\n    - config:\n        neighboraddress: \"203.0.113.1\"\n        peeras: 64520\n    - config:\n        neighboraddress: \"2001:db8:10::1\"\n        peeras: 64520\n    - config:\n        neighboraddress: \"2001:db8:20::1\"\n        peeras: 64520\n\nfeeds:\n  - url: http://localhost:41412/security/blocklist\n    community: 64520:1100\n  - url: https://team-cymru.org/Services/Bogons/fullbogons-ipv4.txt\n    community: 64520:1101\n  - url: https://team-cymru.org/Services/Bogons/fullbogons-ipv6.txt\n    community: 64520:1101\n  - url: https://www.spamhaus.org/drop/drop_v4.json\n    community: 64520:1102\n  - url: https://www.spamhaus.org/drop/drop_v6.json\n    community: 64520:1103\n  - url: https://rules.emergingthreats.net/fwrules/emerging-Block-IPs.txt\n    community: 64520:1107\n  - url: https://sslbl.abuse.ch/blacklist/sslipblacklist.txt\n    community: 64520:1108\n```\n\n### Configuration Notes\n\n- If `community` is omitted, the service defaults it to `\u003clocal ASN\u003e:666`\n- Communities must be written as `\u003cas\u003e:\u003caction\u003e`\n- Each community component must fit in the range `0-65535`\n- Local file paths are supported as feed URLs\n- The refresh timer defaults to `2h`\n- BGP port settings are optional; GoBGP uses standard port `179` when they are\n  omitted\n- `gobgp.global.config.port` optionally changes the local BGP listen port\n- `gobgp.neighbors[].config.port` optionally changes the remote TCP port for a\n  peer\n- The sample config uses RFC 5737 IPv4 and RFC 3849 IPv6 documentation\n  addresses; replace them with your real router ID and peer addresses\n\n### RouterOS Examples\n\nRouterOS filter examples are included below as starting points. Adjust ASN,\naddresses, and policy to match your network.\n\n#### RouterOS v6\n\n```text\n/routing bgp instance\nset default as=64512\n/routing bgp peer\nadd address-families=ip,ipv6 allow-as-in=2 in-filter=threats-in name=threats remote-address=\\\n    192.168.1.2 ttl=default\n/routing filter\nadd action=accept address-family=ip bgp-communities=64512:666 chain=threats-in comment=\\\n    \"Blackhole IPv4 C\u0026C and don't route or peer addresses\" protocol=bgp set-type=blackhole\nadd address-family=ipv6 bgp-communities=64512:666 chain=threats-in comment=\\\n    \"Unreachable IPv6 C\u0026C and don't route or peer addresses\" protocol=bgp set-type=unreachable\n```\n\n#### RouterOS v7\n\n```text\n/routing bgp template\nset default as=64512 disabled=no routing-table=main\n/routing bgp connection\nadd address-families=ip,ipv6 as=64512 disabled=no input.allow-as=2 .filter=threats-in local.role=ibgp \\\n    name=threats remote.address=192.168.1.2 routing-table=main templates=default\n/routing filter rule\nadd chain=threats-in comment=\"Blackhole C\u0026C and don't route or peer addresses\" disabled=no rule=\\\n    \"if (bgp-communities equal 64512:666) {set blackhole yes; accept}\"\n```\n\n---\n\n## 🌐 Feed Sources and Formats\n\n`blackhole-threats` can ingest feeds from both disk and the network.\n\n### Supported Sources\n\n- Local files with no URI scheme\n- `http://` endpoints\n- `https://` endpoints\n\n### Supported Formats\n\n- Plain text prefix lists\n- JSON\n- JSONL\n- NDJSON\n\n### Text Feed Parsing\n\nFor plain text feeds, the parser:\n\n- ignores empty lines\n- ignores comment lines starting with `#`, `;`, or `//`\n- extracts prefixes or individual IPs from mixed-content lines\n- accepts both IPv4 and IPv6 entries\n\n### JSON Feed Parsing\n\nFor JSON-derived feeds, the parser looks for common fields such as:\n\n- `cidr`\n- `prefix`\n- `ip`\n- `address`\n\nIndividual IP addresses are converted to host prefixes automatically.\n\nTop-level JSON arrays and line-delimited JSON streams are both supported.\n\n### Example Public Feeds\n\n- [AbuseIPDB export by tmiland](https://abuseipdb.tmiland.com/abuseipdb.txt)\n- [abuse.ch Botnet C2 IP Blacklist](https://sslbl.abuse.ch/blacklist/sslipblacklist.txt)\n- [blocklist.de fail2ban reporting service](https://lists.blocklist.de/lists/all.txt)\n- [Dan.me.uk Tor exit list](https://www.dan.me.uk/torlist/?full)\n- [Emerging Threats fwip rules](https://rules.emergingthreats.net/fwrules/emerging-Block-IPs.txt)\n- [Peter Hansteen trap list](https://home.nuug.no/~peter/bsdly.net.traplist)\n- [Spamhaus DROP](https://www.spamhaus.org/drop/drop.txt)\n- [Spamhaus EDROP](https://www.spamhaus.org/drop/edrop.txt)\n- [Talos IP Blacklist](https://www.talosintelligence.com/documents/ip-blacklist)\n- [Team Cymru fullbogons IPv4](https://team-cymru.org/Services/Bogons/fullbogons-ipv4.txt)\n- [Team Cymru fullbogons IPv6](https://team-cymru.org/Services/Bogons/fullbogons-ipv6.txt)\n\n---\n\n## 🧪 Usage\n\n### Basic Run\n\n```bash\n./dist/blackhole-threats -conf examples/blackhole-threats.yaml -debug\n```\n\n### Command-Line Flags\n\n```text\n-conf string\n    Configuration file (default \"blackhole-threats.yaml\")\n-check-config\n    Validate configuration and exit\n-debug\n    Enable debug logging\n-log-format string\n    Log format (default \"logfmt\")\n-log-level string\n    Log level (default \"info\")\n-feed value\n    Threat intelligence feed (use multiple times)\n-once\n    Run a single refresh cycle and exit\n-refresh-rate duration\n    Refresh timer (default 2h0m0s)\n-version\n    Print version information and exit\n```\n\n### Examples\n\nRun with the default config path:\n\n```bash\n./dist/blackhole-threats\n```\n\nRun with a custom config file:\n\n```bash\n./dist/blackhole-threats -conf /etc/blackhole-threats.yaml\n```\n\nValidate configuration and exit without starting BGP:\n\n```bash\n./dist/blackhole-threats -conf examples/blackhole-threats.yaml -check-config\n```\n\nRun a single refresh cycle and exit:\n\n```bash\n./dist/blackhole-threats -conf examples/blackhole-threats.yaml -once\n```\n\nFor unprivileged smoke tests, use a temporary config with\n`gobgp.global.config.port` set to a high local listen port such as `1179`.\nPeer endpoint ports are optional; omit `gobgp.neighbors[].config.port` unless\nthat peer also listens on a non-standard port.\n\nAdd an extra feed without editing the YAML file:\n\n```bash\n./dist/blackhole-threats \\\n  -conf examples/blackhole-threats.yaml \\\n  -feed https://www.spamhaus.org/drop/drop.txt\n```\n\nUse a faster refresh interval while testing:\n\n```bash\n./dist/blackhole-threats \\\n  -conf examples/blackhole-threats.yaml \\\n  -refresh-rate 15m \\\n  -log-format logfmt \\\n  -log-level debug\n```\n\nTrigger an immediate refresh on a running process:\n\n```bash\nkill -USR1 \u003cpid\u003e\n```\n\nOn packaged installations, the same refresh can be triggered with:\n\n```bash\nsudo systemctl reload blackhole-threats\n```\n\nPrint build metadata:\n\n```bash\n./dist/blackhole-threats -version\n```\n\n### Maintainer Commands\n\n```bash\nmake fmt\nmake fmt-check\nmake vet\nmake test\nmake build\nmake docker-build\nmake package\n```\n\n### Logging\n\nThe daemon writes structured logfmt-style lines to stdout. That keeps output\neasy to read locally while still working cleanly with `docker logs`,\n`journalctl`, and common syslog or log shipping pipelines.\n\nUse `-log-format logfmt` for the default operator-oriented output, or\n`-log-format json` when you want newline-delimited JSON logs for a log\ncollector.\n\nUse `-log-level` for normal operator control. The older `-debug` flag remains\navailable as a shorthand for `-log-level debug`.\n\nStartup logs include build and runtime metadata such as:\n\n- tag version, commit, and build date\n- Go runtime version and platform\n- config path, run mode, refresh interval, and feed counts\n- local ASN, router ID, default community, and configured peer count\n- on non-tagged branch builds, `tag_version` may be `unknown`\n\nExample startup line:\n\n```text\ntime=2026-04-20T00:00:28Z level=info msg=\"Starting blackhole-threats\" build_date=2026-04-20T00:00:09Z commit=1a2b3c4 go_arch=arm64 go_os=linux go_version=go1.26.2 pid=76 tag_version=v1.0.0\n```\n\nEquivalent JSON startup line:\n\n```json\n{\"build_date\":\"2026-04-20T00:00:09Z\",\"commit\":\"1a2b3c4\",\"go_arch\":\"arm64\",\"go_os\":\"linux\",\"go_version\":\"go1.26.2\",\"level\":\"info\",\"msg\":\"Starting blackhole-threats\",\"pid\":76,\"tag_version\":\"v1.0.0\",\"time\":\"2026-04-20T00:00:28Z\"}\n```\n\n---\n\n## 🐳 Container\n\nThe container image is published to GitHub Container Registry as:\n\n```text\nghcr.io/soakes/blackhole-threats\n```\n\n### Container Notes\n\n- Debian Trixie runtime base\n- Current stable Go build stage on Debian Trixie\n- S6 Overlay for supervision and lifecycle handling\n- Pinned S6 Overlay release with checksum verification during image build\n- Runtime configuration stored under `/config`\n- Automatic first-boot creation of `/config/blackhole-threats.yaml`\n- Structured stdout logs that are suitable for Docker log collection and parsing\n- Optional container overrides:\n  - `BLACKHOLE_THREATS_CONF` to point at a different config path\n  - `BLACKHOLE_THREATS_EXTRA_OPTS` to append daemon flags such as `-log-format json -log-level info`\n- Multi-architecture images for `linux/amd64` and `linux/arm64`\n\n### Example Usage\n\n```bash\ndocker pull ghcr.io/soakes/blackhole-threats:latest\ndocker run -d \\\n  -p 179:179 \\\n  -v \"$PWD/config:/config\" \\\n  -e BLACKHOLE_THREATS_EXTRA_OPTS=\"-log-level debug -refresh-rate 15m\" \\\n  --name blackhole-threats \\\n  ghcr.io/soakes/blackhole-threats:latest\n```\n\nIf you want the current release candidate stream, use\n`ghcr.io/soakes/blackhole-threats:rc`.\n\nInspect the live service logs with:\n\n```bash\ndocker logs -f blackhole-threats\n```\n\n---\n\n## 📦 Debian Package\n\nThe Debian package installs the service into standard Debian locations.\n\n### Installed Paths\n\n- Binary: `/usr/sbin/blackhole-threats`\n- Default config: `/etc/blackhole-threats.yaml`\n- Service defaults: `/etc/default/blackhole-threats`\n- Manual page: `/usr/share/man/man8/blackhole-threats.8.gz`\n- Sample config: `/usr/share/doc/blackhole-threats/examples/blackhole-threats.yaml`\n- Additional packaged docs: `/usr/share/doc/blackhole-threats/`\n\n### Service Integration\n\nThe package ships a systemd unit and an `/etc/default/blackhole-threats`\nenvironment file for native service management on Debian-family systems.\nRuntime logs are written to journald with the `blackhole-threats` syslog\nidentifier.\n\nCommand reference is available locally after installation with:\n\n```bash\nman blackhole-threats\n```\n\nTrigger an immediate feed refresh with:\n\n```bash\nsudo systemctl reload blackhole-threats\n```\n\nFollow the service logs with:\n\n```bash\nsudo journalctl -u blackhole-threats -f\n```\n\n---\n\n## 🔐 APT Repository\n\nAutomated `v*` releases from `main` publish a signed APT repository through\nGitHub Pages.\nThe Pages root also serves as the public product landing page, while the APT\nindexes, package pool, and signing files stay available under the same base\nURL for machine consumption.\n\nRepository base URL:\n\n```text\nhttps://soakes.github.io/blackhole-threats/\n```\n\nArchive key:\n\n```text\nhttps://soakes.github.io/blackhole-threats/blackhole-threats-archive-keyring.gpg\n```\n\nArchive key fingerprint:\n\n```text\nhttps://soakes.github.io/blackhole-threats/blackhole-threats-archive-keyring.fingerprint.txt\n```\n\n### Add the Repository\n\n```bash\nsudo install -d -m 0755 /etc/apt/keyrings\ncurl -fsSL https://soakes.github.io/blackhole-threats/blackhole-threats-archive-keyring.gpg \\\n  | sudo tee /etc/apt/keyrings/blackhole-threats-archive-keyring.gpg \u003e/dev/null\n\ncurl -fsSL https://soakes.github.io/blackhole-threats/blackhole-threats-archive-keyring.fingerprint.txt\n\n# Verify the fingerprint matches the expected archive key before proceeding\n\nsudo tee /etc/apt/sources.list.d/blackhole-threats.sources \u003e/dev/null \u003c\u003c'EOF'\nTypes: deb deb-src\nURIs: https://soakes.github.io/blackhole-threats/\nSuites: stable\nComponents: main\nSigned-By: /etc/apt/keyrings/blackhole-threats-archive-keyring.gpg\nEOF\n\nsudo apt update\nsudo apt install blackhole-threats\n```\n\n### Operator Notes\n\n- Binary indexes are published under `dists/stable/main/binary-*`\n- Source indexes are published under `dists/stable/main/source`\n- Repository metadata is signed and published as `InRelease` and `Release.gpg`\n- The public archive key is published alongside the repository for `signed-by`\n- The full archive key fingerprint is published alongside the repository for\n  out-of-band verification\n- Packages are hosted through GitHub Pages rather than the GitHub Releases asset listing\n- The landing page at the repository root is built from\n  `.github/assets/website/` and deployed together with the signed package\n  indexes\n- Website-only changes can refresh the Pages landing site from `main` without\n  cutting a new release; that deploy path reuses the latest published\n  repository snapshot so the signed APT content remains intact\n- Tagged GitHub Releases attach the installable binaries, runtime Debian\n  packages, `sha256sums.txt`, and the Debian source package trio for offline\n  retrieval and inspection; maintainer-only artifacts such as `-dbgsym`,\n  `.buildinfo`, and `.changes` stay out of the release page\n- GitHub Pages must be enabled for this repository with GitHub Actions as the\n  publishing source\n- The signing workflow should use protected environment secrets on the\n  `apt-repository` environment:\n  - `APT_GPG_PRIVATE_KEY`\n  - `APT_GPG_KEY_ID` if the imported secret contains more than one signing key\n  - `APT_GPG_PASSPHRASE` if the signing key is passphrase protected\n\n---\n\n## 🤖 CI/CD and Release Automation\n\nGitHub Actions covers validation, packaging, publishing, and scheduled pin\nrefresh for this repository.\n\n### Workflows\n\n- `Build and Validate`\n  - runs formatting, vetting, tests, native build checks, binary smoke tests, Debian package validation, signed APT repository validation with an ephemeral archive key, and cross-build validation\n- `Container Image`\n  - validates the container build, smoke-tests bootstrap and config override behavior, validates published platforms on pull requests, release tags, and manual dispatches, and only publishes `rc`, `v*-rc.*`, stable semver tags, and `latest` for release tags or explicit recovery dispatches\n- `Automated Release Candidate`\n  - runs after `Build and Validate` succeeds for a push to `main`, calculates the next semantic stable target from conventional commit history, creates a `v*-rc.*` tag, waits for the prerelease asset and container publish jobs to pass, and leaves stable publication to explicit promotion\n- `Automation Auto Merge`\n  - runs after `Build and Validate` succeeds for Dependabot pull requests and the scheduled build/runtime pin refresh PR, attempts an approval when repository policy allows it, squash-merges green automation PRs when GitHub can merge them immediately, enables GitHub auto-merge when a PR only needs later branch-protection satisfaction, preserves the pull request conventional-commit title on merge, and includes scheduled or manual backstops for any missed pull requests\n- `Promote Release Candidate`\n  - promotes a specific `v*-rc.*` tag to a stable `v*` tag when a maintainer is ready to publish stable assets, containers, and the signed APT repository, while honoring the optional `RELEASE_AUTOMATION_TOKEN` escape hatch for workflow-touching release commits\n- `Release Drafter`\n  - keeps a curated draft release updated on `main` when there is at least one release-bearing change queued, removes stale automated drafts when there are no release-bearing changes left, applies release-note labels to pull requests, and groups merged work into cleaner operator-facing sections\n- `Release Assets`\n  - builds tagged release binaries plus Debian binary and source packages, publishes a curated GitHub Release asset set for operators, generates checksums, publishes GitHub Releases as prereleases for `v*-rc.*` tags and stable releases for `v*`, uses an existing draft body when one already exists for the tag, cleans up stale orphan `untagged-*` drafts before publishing, and supports recovery dispatches from the current default branch with `release_ref=\u003ctag\u003e`\n- `Publish Signed Debian Repository`\n  - builds Debian binary and source packages, generates APT metadata, smoke-tests the signed repository with APT, builds the Astro-based landing site, signs the repository, deploys both to GitHub Pages for stable `v*` tags, and supports recovery dispatches from the current default branch with `release_ref=\u003cstable-tag\u003e`\n- `Deploy Pages Site`\n  - rebuilds the Astro landing site on `main`, overlays it onto the latest published Pages snapshot, republishes the combined result after stable APT publication or website-only changes, and does not require a new release tag for landing-page-only refreshes\n- `Refresh Build and Runtime Pins`\n  - refreshes the pinned Docker Go build image version and updates the pinned Docker `s6-overlay` version and checksum pins\n\n### Automated Updates\n\n- Dependabot checks GitHub Actions, Go modules, and Docker base images daily\n- Dependabot pull requests and the scheduled build/runtime pin refresh PR can\n  be merged automatically after they pass `Build and Validate`\n- all semver version updates, including major bumps, stay eligible for\n  Dependabot auto-merge; the repo keeps ecosystem PRs separate and raises the\n  open-PR limit so a broken major update does not block newer updates from\n  being proposed\n- The scheduled refresh workflow updates the pinned Docker Go build image\n  version and the pinned Docker `s6-overlay` release metadata, then dispatches\n  validation for the generated pull request\n\n### Public Repository Hardening\n\n- Pull requests run validation jobs only; they do not run the signed repository\n  publisher or release publisher\n- `main` is treated as release-candidate-ready: each merge to `main` can become\n  the next automated `v*-rc.*` tag, but stable `latest` publication only\n  happens after a maintainer promotes an RC\n- `main` pushes also refresh the public GitHub Pages landing site, but they do\n  so by overlaying the website onto the latest published repository snapshot\n  rather than by rebuilding or resigning the APT repo\n- automated version bumps use `feat` for minor releases, `fix`/`perf`/`revert`/\n  `container`/`build`/`deps`/`packaging` for patch releases, and\n  `BREAKING CHANGE:` or `type!:` for major releases\n- the automation merge workflow preserves the pull request title as the squash\n  commit subject, so release-bearing Dependabot updates such as `deps:` and\n  `container:` continue into the automated release flow after merge, while\n  `ci:` and `chore:` updates stay non-release-bearing by design\n- Dependabot semver labels such as `major`, `minor`, and `patch` are kept in\n  the repository label catalog so failing update PRs are easier to triage\n- the automation merge workflow uses the repo-scoped `GITHUB_TOKEN` by default\n  and only needs an optional `AUTOMATION_AUTOMERGE_TOKEN` when GitHub policy\n  blocks workflow-file pull requests from being merged by the default token\n- the release workflows use the repo-scoped `GITHUB_TOKEN` by default and only\n  need an optional `RELEASE_AUTOMATION_TOKEN` when a release-bearing commit\n  updates files under `.github/workflows/` and GitHub blocks the tag push\n  without extra workflow permission\n- docs-only, README-only, website-only, and Pages-only changes are\n  non-release-bearing by default; they can refresh the public site from `main`,\n  but they should not create or advance a release draft, RC tag, or stable tag\n  unless they also change shipped runtime, packaging, container, or release\n  behavior\n- pull request labels drive the grouped GitHub release notes; the Release\n  Drafter workflow applies most labels automatically, but the correct label\n  should still be set before merge for unusual changes\n- The signed Debian repository workflow only runs on stable `v*` tags that\n  point to commits already contained in `main`\n- The GitHub release workflow only runs on trusted `v*` or `v*-rc.*` tags that\n  point to commits already contained in `main`\n- The GHCR publish job only runs on `main` pushes or trusted `v*` / `v*-rc.*`\n  tags that point to commits already contained in `main`\n- Write permissions are scoped to the specific publish jobs that need them\n- The scheduled dependency refresh job only runs from the default branch\n- The archive signing key should be stored as a protected environment secret,\n  not as a broadly scoped repository secret\n\n---\n\n## 🗂️ Project Structure\n\n```text\nblackhole-threats/\n├── .github/\n│   ├── assets/\n│   │   └── website/\n│   ├── dependabot.yml\n│   ├── release-drafter.yml\n│   ├── repository-labels.json\n│   └── workflows/\n│       ├── automated-release.yml\n│       ├── build-and-validate.yml\n│       ├── container-image.yml\n│       ├── automation-auto-merge.yml\n│       ├── deploy-pages-site.yml\n│       ├── promote-release.yml\n│       ├── publish-apt-repository.yml\n│       ├── refresh-toolchain-and-runtime.yml\n│       ├── release-drafter.yml\n│       └── release-assets.yml\n├── cmd/\n│   └── blackhole-threats/\n│       └── main.go\n├── debian/\n├── docs/\n│   ├── README.md\n│   ├── architecture.md\n│   ├── config-reference.md\n│   ├── deployment-examples.md\n│   ├── feed-behavior.md\n│   ├── operations.md\n│   ├── release-and-publishing.md\n│   └── troubleshooting.md\n├── examples/\n│   └── blackhole-threats.yaml\n├── internal/\n│   ├── bgp/\n│   ├── buildinfo/\n│   ├── config/\n│   └── feed/\n├── packaging/\n│   └── container/\n├── scripts/\n│   └── build-apt-repository.sh\n├── Dockerfile\n├── Makefile\n├── go.mod\n└── README.md\n```\n\n### Key Directories\n\n- `cmd/blackhole-threats`\n  - CLI entrypoint, flag parsing, signal wiring, and process startup\n- `internal/config`\n  - YAML loading, feed definitions, and community parsing\n- `internal/feed`\n  - feed retrieval, parsing, and prefix summarisation\n- `internal/bgp`\n  - GoBGP lifecycle and route announce/withdraw logic\n- `packaging/container`\n  - container rootfs and S6 service definitions\n- `debian`\n  - Debian packaging metadata\n- `docs`\n  - longer-form operator and maintainer guides, including operations, config\n    reference, release flow, deployment examples, and troubleshooting\n- `.github/assets/website`\n  - Vite source for the GitHub Pages landing site that fronts the signed APT repository\n\n---\n\n## 🩺 Troubleshooting\n\n### Configuration File Not Found\n\nSymptom:\n\n```text\nFailed to load configuration\n```\n\nChecks:\n\n- confirm the `-conf` path exists\n- verify the service user can read it\n- make sure the container bind mount is present if using Docker\n\n### Feed Parse Errors\n\nSymptom:\n\n```text\nFailed to read threat feed\n```\n\nChecks:\n\n- verify the URL is reachable\n- confirm local file paths are correct\n- check for malformed JSON in JSON-derived feeds\n- confirm the remote endpoint returns `200 OK`\n\n### Routes Not Appearing\n\nChecks:\n\n- confirm the BGP session is established\n- verify the configured router ID is correct\n- check whether the selected community matches your router-side policy\n- increase logging with `-debug`\n- trigger a manual refresh with `SIGUSR1`\n- use `systemctl reload blackhole-threats` on packaged installations\n\n### Container Starts But No Config Appears\n\nChecks:\n\n- verify the `/config` directory is writable\n- confirm the volume mount is present\n- inspect container logs with `docker logs blackhole-threats`\n\n### Debian Service Does Not Start\n\nChecks:\n\n- review `/etc/default/blackhole-threats`\n- confirm `/etc/blackhole-threats.yaml` exists and is readable\n- inspect `systemctl status blackhole-threats`\n- inspect `journalctl -u blackhole-threats`\n- verify the startup log fields for `config_path`, `local_as`, `router_id`, and `peer_count`\n\n---\n\n## 🤝 Contributing\n\nContributions should preserve the operator-facing behavior of the project:\n\n- keep runtime behavior easy to understand\n- preserve packaging and release reproducibility\n- avoid undocumented surprises in routing behavior\n- keep `main` in a releasable state\n\n### Local Validation\n\n```bash\nmake fmt\nmake fmt-check\nmake vet\nmake test\nmake build\n```\n\nIf you change container, packaging, or release logic, also check:\n\n```bash\nmake docker-build\nmake package\n```\n\n### Pull Request Expectations\n\n- use conventional commit subjects for merge commits and squashed PRs\n- make sure the pull request carries the correct release-note label before\n  merge; Release Drafter uses labels for grouped release sections and applies\n  most obvious ones automatically\n- include a meaningful commit body when the change affects operators,\n  packaging, release flow, or security posture\n- assume merged `main` commits are eligible for automated RC tagging and\n  prerelease artifact publication\n- use a release-bearing type when the change should publish artifacts;\n  `docs`, `ci`, `chore`, `test`, and `refactor` do not cut a release by\n  default\n- use the non-release-bearing path for README, docs, and website-only work\n  unless the same change intentionally modifies shipped artifacts or release\n  automation\n- explain the operational reason for the change\n- keep README, packaging, and workflow docs in sync with behavior\n- include test coverage or a verification note when code paths change\n- call out any routing, packaging, or upgrade impact explicitly\n- do not broaden workflow permissions or secret exposure without a clear\n  operational reason\n- keep release and publishing workflows safe for a public repository\n\n### Commit Message Guidance\n\nRelease notes are drafted from pull requests through Release Drafter, with a\ncommit-history fallback for direct-to-`main` changes. Commit messages should\nstill be written as release inputs rather than throwaway local notes.\n\nPreferred shape:\n\n```text\nfix(logging): standardize startup logs across Docker and systemd\n\nEmit structured logfmt-style startup lines with the release tag version.\nKeep Docker logs, journald, and syslog collectors aligned on the same fields.\nDocument the operator-visible logging change in the README and man page.\n```\n\nPractical rules:\n\n- keep the subject in conventional commit form\n- make the subject describe the visible outcome, not just the file touched\n- write the subject as release-ready prose; it will appear as the change title in\n  the fallback published release notes and still drives automated version bumps\n- if the pull request label is wrong, fix the label before merge instead of\n  relying on the fallback path\n- use the body to explain operator impact, packaging changes, or security\n  implications\n- keep body paragraphs factual and operator-facing; the fallback release-note\n  generator lifts the first useful detail from the body when there is no pull\n  request metadata to draft from\n- split distinct impacts into separate paragraphs so the generated notes read\n  cleanly\n- use `feat`, `fix`, `perf`, `revert`, `container`, `build`, `deps`, or\n  `packaging` when the change should trigger an automated release\n- use `BREAKING CHANGE:` in the body when the release should force a major\n  version bump\n- do not merge work to `main` unless you are happy for automation to tag and\n  publish it as a release candidate\n\n### Useful Contribution Areas\n\n- additional operational documentation\n- more feed examples and validation coverage\n- router interoperability notes\n- packaging and release polish\n- dependency and supply-chain hardening\n\n---\n\n## 📄 License\n\nThis project is licensed under the MIT License. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoakes%2Fblackhole-threats","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsoakes%2Fblackhole-threats","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoakes%2Fblackhole-threats/lists"}