A Go-based RTBH route server that turns threat feeds into controlled BGP blackhole announcements.
Built for operators who want one service, one config format, and one release path they can actually deploy: source builds, container images, Debian packages, and a signed APT repository.
Quick links: π¦ Releases Β· π³ GHCR Β· π APT Repository Β· π Documentation Β· ποΈ Architecture Β· π¬ Discussions Β· π€ Contributing Β· β Funding
- π Overview
- β¨ Capabilities
- π How It Works
- β Prerequisites
- π Installation
- βοΈ Configuration
- π Feed Sources and Formats
- π§ͺ Usage
- π³ Container
- π¦ Debian Package
- π APT Repository
- π€ CI/CD and Release Automation
- ποΈ Project Structure
- π©Ί Troubleshooting
- π± Open Source and Support
- π€ Contributing
- π License
blackhole-threats is a Go RTBH route server. It reads local or remote threat
feeds, extracts IPv4 and IPv6 networks, summarises them, and advertises the
resulting routes over BGP so downstream routers can apply blackhole policy.
In normal operation the service does four things:
- loads GoBGP and feed configuration from YAML
- fetches and parses the configured feeds
- computes the route delta against the current advertised set
- announces new routes and withdraws stale ones on a refresh loop
This repository packages that workflow as a single service with first-party source builds, container images, Debian packages, and a signed APT repository.
Pick the path that matches how you deploy:
- Container: pull
ghcr.io/soakes/blackhole-threats:latestfor the latest tagged release - Debian: use the signed APT repository and install
blackhole-threats - Source: run
make buildand executedist/blackhole-threats - Validation: use
-check-configor-oncebefore putting the daemon into service
Longer-form guides for operations, configuration, release flow, and deployment
examples live under docs/.
- Copy
examples/blackhole-threats.yamland replace the local ASN, router ID, peers, and feed communities for your network. - Validate the file with
./dist/blackhole-threats -conf /path/to/blackhole-threats.yaml -check-config. - Use
-oncewith an unprivileged local BGP port for a first smoke test before binding to production port179. - Start the daemon and inspect the startup logs for
tag_version,local_as,router_id,peer_count, and the firstRefresh completedline. - Only enable automatic service startup after the one-shot validation path looks correct.
The original blackhole-threats project by Eric Barkie established the basic
GoBGP-based pattern for advertising threat-feed routes. That repository was
archived on March 31, 2026. This repository continues the same operational
model with current Go toolchains, Debian packaging, container publishing,
GitHub Actions workflows, and automated pin refreshes.
Credit for the original project and idea belongs to Eric Barkie:
- Original repository: https://github.com/ebarkie/blackhole-threats
- Original project title:
Blackhole threats (with GoBGP)
- Feed ingestion: reads local files plus
http://andhttps://sources - Format handling: parses plain text, JSON, JSONL, and NDJSON feeds
- Prefix support: extracts and summarises both IPv4 and IPv6 networks
- Community control: supports per-feed BGP communities and defaults to
<local ASN>:666 - Runtime control: supports periodic refresh,
SIGUSR1,-check-config, and-once - Failure handling: keeps the last good community state when a feed refresh fails
- Logging: emits structured logfmt-style output for Docker, journald, and syslog pipelines
- Distribution: publishes release binaries, multi-arch container images, Debian packages, and a signed APT repository
- Automation: includes validation, release publishing, and pinned dependency refresh workflows
At runtime, blackhole-threats follows a simple route lifecycle:
flowchart TD
A[Load YAML configuration] --> B[Merge configured feeds and extra -feed arguments]
B --> C[Start embedded GoBGP server]
C --> D[Fetch configured feeds concurrently]
D --> E[Parse IPv4 and IPv6 prefixes]
E --> F[Summarise overlapping prefixes]
F --> G[Group routes by BGP community]
G --> H[Diff against currently advertised routes]
H --> I[Announce new routes]
H --> J[Withdraw stale routes]
I --> K[Wait for refresh interval or SIGUSR1]
J --> K
K --> D
If a feed refresh fails for a community, the daemon keeps the last good routes for that community and retries on the next refresh instead of withdrawing on partial input.
- Load YAML configuration from
blackhole-threats.yamlor the configured path. - Merge configured feeds with any additional
-feedCLI arguments. - Start the embedded GoBGP server.
- Fetch all configured feeds concurrently.
- Parse IPv4 and IPv6 prefixes from each source.
- Summarise overlapping prefixes to reduce route churn.
- Group routes by BGP community.
- Diff the new route set against the current route set.
- Announce new routes and withdraw stale ones.
- Repeat on the configured refresh interval, or immediately when sent
SIGUSR1.
For the package layout and component boundaries, see docs/architecture.md.
- A BGP-speaking environment where downstream routers can peer with this service
- A valid GoBGP-compatible configuration for your local ASN, router ID, and peers
- One or more threat feeds reachable as:
- local files
http://URLshttps://URLs
- Go
1.24+if building from source - Docker if running the container image
- Debian packaging tools if building
.debpackages locally
git clone https://github.com/soakes/blackhole-threats.git
cd blackhole-threats
make buildThis produces:
dist/blackhole-threats
The minimum supported Go version stays aligned with Debian trixie packaging. GitHub Actions and container builds track the current stable Go release separately.
docker pull ghcr.io/soakes/blackhole-threats:latestlatest tracks stable tagged releases, and release candidates publish to the
ghcr.io/soakes/blackhole-threats:rc channel plus their full v*-rc.* tag.
make packageThis expects Debian packaging tools such as debhelper, golang-any, and
devscripts to be available.
The service uses a YAML file with two main sections:
gobgp: the GoBGP configuration setfeeds: the threat intelligence sources to ingest
A ready-to-edit reference file lives at
examples/blackhole-threats.yaml.
That file is a full reference example with multiple peers, mixed IPv4/IPv6
neighbors, and a broader feed set. The shorter example below is the README
version to keep the page readable.
gobgp:
global:
config:
as: 64520
routerid: "198.51.100.10"
neighbors:
- config:
neighboraddress: "198.51.100.1"
peeras: 64520
- config:
neighboraddress: "203.0.113.1"
peeras: 64520
- config:
neighboraddress: "2001:db8:10::1"
peeras: 64520
- config:
neighboraddress: "2001:db8:20::1"
peeras: 64520
feeds:
- url: http://localhost:41412/security/blocklist
community: 64520:1100
- url: https://team-cymru.org/Services/Bogons/fullbogons-ipv4.txt
community: 64520:1101
- url: https://team-cymru.org/Services/Bogons/fullbogons-ipv6.txt
community: 64520:1101
- url: https://www.spamhaus.org/drop/drop_v4.json
community: 64520:1102
- url: https://www.spamhaus.org/drop/drop_v6.json
community: 64520:1103
- url: https://rules.emergingthreats.net/fwrules/emerging-Block-IPs.txt
community: 64520:1107
- url: https://sslbl.abuse.ch/blacklist/sslipblacklist.txt
community: 64520:1108- If
communityis omitted, the service defaults it to<local ASN>:666 - Communities must be written as
<as>:<action> - Each community component must fit in the range
0-65535 - Local file paths are supported as feed URLs
- The refresh timer defaults to
2h - BGP port settings are optional; GoBGP uses standard port
179when they are omitted gobgp.global.config.portoptionally changes the local BGP listen portgobgp.neighbors[].config.portoptionally changes the remote TCP port for a peer- The sample config uses RFC 5737 IPv4 and RFC 3849 IPv6 documentation addresses; replace them with your real router ID and peer addresses
RouterOS filter examples are included below as starting points. Adjust ASN, addresses, and policy to match your network.
/routing bgp instance
set default as=64512
/routing bgp peer
add address-families=ip,ipv6 allow-as-in=2 in-filter=threats-in name=threats remote-address=\
192.168.1.2 ttl=default
/routing filter
add action=accept address-family=ip bgp-communities=64512:666 chain=threats-in comment=\
"Blackhole IPv4 C&C and don't route or peer addresses" protocol=bgp set-type=blackhole
add address-family=ipv6 bgp-communities=64512:666 chain=threats-in comment=\
"Unreachable IPv6 C&C and don't route or peer addresses" protocol=bgp set-type=unreachable
/routing bgp template
set default as=64512 disabled=no routing-table=main
/routing bgp connection
add address-families=ip,ipv6 as=64512 disabled=no input.allow-as=2 .filter=threats-in local.role=ibgp \
name=threats remote.address=192.168.1.2 routing-table=main templates=default
/routing filter rule
add chain=threats-in comment="Blackhole C&C and don't route or peer addresses" disabled=no rule=\
"if (bgp-communities equal 64512:666) {set blackhole yes; accept}"
blackhole-threats can ingest feeds from both disk and the network.
- Local files with no URI scheme
http://endpointshttps://endpoints
- Plain text prefix lists
- JSON
- JSONL
- NDJSON
For plain text feeds, the parser:
- ignores empty lines
- ignores comment lines starting with
#,;, or// - extracts prefixes or individual IPs from mixed-content lines
- accepts both IPv4 and IPv6 entries
For JSON-derived feeds, the parser looks for common fields such as:
cidrprefixipaddress
Individual IP addresses are converted to host prefixes automatically.
Top-level JSON arrays and line-delimited JSON streams are both supported.
- AbuseIPDB export by tmiland
- abuse.ch Botnet C2 IP Blacklist
- blocklist.de fail2ban reporting service
- Dan.me.uk Tor exit list
- Emerging Threats fwip rules
- Peter Hansteen trap list
- Spamhaus DROP
- Spamhaus EDROP
- Talos IP Blacklist
- Team Cymru fullbogons IPv4
- Team Cymru fullbogons IPv6
./dist/blackhole-threats -conf examples/blackhole-threats.yaml -debug-conf string
Configuration file (default "blackhole-threats.yaml")
-check-config
Validate configuration and exit
-debug
Enable debug logging
-log-format string
Log format (default "logfmt")
-log-level string
Log level (default "info")
-feed value
Threat intelligence feed (use multiple times)
-once
Run a single refresh cycle and exit
-refresh-rate duration
Refresh timer (default 2h0m0s)
-version
Print version information and exit
Run with the default config path:
./dist/blackhole-threatsRun with a custom config file:
./dist/blackhole-threats -conf /etc/blackhole-threats.yamlValidate configuration and exit without starting BGP:
./dist/blackhole-threats -conf examples/blackhole-threats.yaml -check-configRun a single refresh cycle and exit:
./dist/blackhole-threats -conf examples/blackhole-threats.yaml -onceFor unprivileged smoke tests, use a temporary config with
gobgp.global.config.port set to a high local listen port such as 1179.
Peer endpoint ports are optional; omit gobgp.neighbors[].config.port unless
that peer also listens on a non-standard port.
Add an extra feed without editing the YAML file:
./dist/blackhole-threats \
-conf examples/blackhole-threats.yaml \
-feed https://www.spamhaus.org/drop/drop.txtUse a faster refresh interval while testing:
./dist/blackhole-threats \
-conf examples/blackhole-threats.yaml \
-refresh-rate 15m \
-log-format logfmt \
-log-level debugTrigger an immediate refresh on a running process:
kill -USR1 <pid>On packaged installations, the same refresh can be triggered with:
sudo systemctl reload blackhole-threatsPrint build metadata:
./dist/blackhole-threats -versionmake fmt
make fmt-check
make vet
make test
make build
make docker-build
make packageThe daemon writes structured logfmt-style lines to stdout. That keeps output
easy to read locally while still working cleanly with docker logs,
journalctl, and common syslog or log shipping pipelines.
Use -log-format logfmt for the default operator-oriented output, or
-log-format json when you want newline-delimited JSON logs for a log
collector.
Use -log-level for normal operator control. The older -debug flag remains
available as a shorthand for -log-level debug.
Startup logs include build and runtime metadata such as:
- tag version, commit, and build date
- Go runtime version and platform
- config path, run mode, refresh interval, and feed counts
- local ASN, router ID, default community, and configured peer count
- on non-tagged branch builds,
tag_versionmay beunknown
Example startup line:
time=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
Equivalent JSON startup line:
{"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"}The container image is published to GitHub Container Registry as:
ghcr.io/soakes/blackhole-threats
- Debian Trixie runtime base
- Current stable Go build stage on Debian Trixie
- S6 Overlay for supervision and lifecycle handling
- Pinned S6 Overlay release with checksum verification during image build
- Runtime configuration stored under
/config - Automatic first-boot creation of
/config/blackhole-threats.yaml - Structured stdout logs that are suitable for Docker log collection and parsing
- Optional container overrides:
BLACKHOLE_THREATS_CONFto point at a different config pathBLACKHOLE_THREATS_EXTRA_OPTSto append daemon flags such as-log-format json -log-level info
- Multi-architecture images for
linux/amd64andlinux/arm64
docker pull ghcr.io/soakes/blackhole-threats:latest
docker run -d \
-p 179:179 \
-v "$PWD/config:/config" \
-e BLACKHOLE_THREATS_EXTRA_OPTS="-log-level debug -refresh-rate 15m" \
--name blackhole-threats \
ghcr.io/soakes/blackhole-threats:latestIf you want the current release candidate stream, use
ghcr.io/soakes/blackhole-threats:rc.
Inspect the live service logs with:
docker logs -f blackhole-threatsThe Debian package installs the service into standard Debian locations.
- Binary:
/usr/sbin/blackhole-threats - Default config:
/etc/blackhole-threats.yaml - Service defaults:
/etc/default/blackhole-threats - Manual page:
/usr/share/man/man8/blackhole-threats.8.gz - Sample config:
/usr/share/doc/blackhole-threats/examples/blackhole-threats.yaml - Additional packaged docs:
/usr/share/doc/blackhole-threats/
The package ships a systemd unit and an /etc/default/blackhole-threats
environment file for native service management on Debian-family systems.
Runtime logs are written to journald with the blackhole-threats syslog
identifier.
Command reference is available locally after installation with:
man blackhole-threatsTrigger an immediate feed refresh with:
sudo systemctl reload blackhole-threatsFollow the service logs with:
sudo journalctl -u blackhole-threats -fAutomated v* releases from main publish a signed APT repository through
GitHub Pages.
The Pages root also serves as the public product landing page, while the APT
indexes, package pool, and signing files stay available under the same base
URL for machine consumption.
Repository base URL:
https://soakes.github.io/blackhole-threats/
Archive key:
https://soakes.github.io/blackhole-threats/blackhole-threats-archive-keyring.gpg
Archive key fingerprint:
https://soakes.github.io/blackhole-threats/blackhole-threats-archive-keyring.fingerprint.txt
sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://soakes.github.io/blackhole-threats/blackhole-threats-archive-keyring.gpg \
| sudo tee /etc/apt/keyrings/blackhole-threats-archive-keyring.gpg >/dev/null
curl -fsSL https://soakes.github.io/blackhole-threats/blackhole-threats-archive-keyring.fingerprint.txt
# Verify the fingerprint matches the expected archive key before proceeding
sudo tee /etc/apt/sources.list.d/blackhole-threats.sources >/dev/null <<'EOF'
Types: deb deb-src
URIs: https://soakes.github.io/blackhole-threats/
Suites: stable
Components: main
Signed-By: /etc/apt/keyrings/blackhole-threats-archive-keyring.gpg
EOF
sudo apt update
sudo apt install blackhole-threats- Binary indexes are published under
dists/stable/main/binary-* - Source indexes are published under
dists/stable/main/source - Repository metadata is signed and published as
InReleaseandRelease.gpg - The public archive key is published alongside the repository for
signed-by - The full archive key fingerprint is published alongside the repository for out-of-band verification
- Packages are hosted through GitHub Pages rather than the GitHub Releases asset listing
- The landing page at the repository root is built from
.github/assets/website/and deployed together with the signed package indexes - Website-only changes can refresh the Pages landing site from
mainwithout cutting a new release; that deploy path reuses the latest published repository snapshot so the signed APT content remains intact - Tagged GitHub Releases attach the installable binaries, runtime Debian
packages,
sha256sums.txt, and the Debian source package trio for offline retrieval and inspection; maintainer-only artifacts such as-dbgsym,.buildinfo, and.changesstay out of the release page - GitHub Pages must be enabled for this repository with GitHub Actions as the publishing source
- The signing workflow should use protected environment secrets on the
apt-repositoryenvironment:APT_GPG_PRIVATE_KEYAPT_GPG_KEY_IDif the imported secret contains more than one signing keyAPT_GPG_PASSPHRASEif the signing key is passphrase protected
GitHub Actions covers validation, packaging, publishing, and scheduled pin refresh for this repository.
Build and Validate- 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
Container Image- 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, andlatestfor release tags or explicit recovery dispatches
- 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
Automated Release Candidate- runs after
Build and Validatesucceeds for a push tomain, calculates the next semantic stable target from conventional commit history, creates av*-rc.*tag, waits for the prerelease asset and container publish jobs to pass, and leaves stable publication to explicit promotion
- runs after
Automation Auto Merge- runs after
Build and Validatesucceeds 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
- runs after
Promote Release Candidate- promotes a specific
v*-rc.*tag to a stablev*tag when a maintainer is ready to publish stable assets, containers, and the signed APT repository, while honoring the optionalRELEASE_AUTOMATION_TOKENescape hatch for workflow-touching release commits
- promotes a specific
Release Drafter- keeps a curated draft release updated on
mainwhen 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
- keeps a curated draft release updated on
Release Assets- 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 forv*, uses an existing draft body when one already exists for the tag, cleans up stale orphanuntagged-*drafts before publishing, and supports recovery dispatches from the current default branch withrelease_ref=<tag>
- 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
Publish Signed Debian Repository- 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 withrelease_ref=<stable-tag>
- 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
Deploy Pages Site- 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
- rebuilds the Astro landing site on
Refresh Build and Runtime Pins- refreshes the pinned Docker Go build image version and updates the pinned Docker
s6-overlayversion and checksum pins
- refreshes the pinned Docker Go build image version and updates the pinned Docker
- Dependabot checks GitHub Actions, Go modules, and Docker base images daily
- Dependabot pull requests and the scheduled build/runtime pin refresh PR can
be merged automatically after they pass
Build and Validate - all semver version updates, including major bumps, stay eligible for Dependabot auto-merge; the repo keeps ecosystem PRs separate and raises the open-PR limit so a broken major update does not block newer updates from being proposed
- The scheduled refresh workflow updates the pinned Docker Go build image
version and the pinned Docker
s6-overlayrelease metadata, then dispatches validation for the generated pull request
- Pull requests run validation jobs only; they do not run the signed repository publisher or release publisher
mainis treated as release-candidate-ready: each merge tomaincan become the next automatedv*-rc.*tag, but stablelatestpublication only happens after a maintainer promotes an RCmainpushes also refresh the public GitHub Pages landing site, but they do so by overlaying the website onto the latest published repository snapshot rather than by rebuilding or resigning the APT repo- automated version bumps use
featfor minor releases,fix/perf/revert/container/build/deps/packagingfor patch releases, andBREAKING CHANGE:ortype!:for major releases - the automation merge workflow preserves the pull request title as the squash
commit subject, so release-bearing Dependabot updates such as
deps:andcontainer:continue into the automated release flow after merge, whileci:andchore:updates stay non-release-bearing by design - Dependabot semver labels such as
major,minor, andpatchare kept in the repository label catalog so failing update PRs are easier to triage - the automation merge workflow uses the repo-scoped
GITHUB_TOKENby default and only needs an optionalAUTOMATION_AUTOMERGE_TOKENwhen GitHub policy blocks workflow-file pull requests from being merged by the default token - the release workflows use the repo-scoped
GITHUB_TOKENby default and only need an optionalRELEASE_AUTOMATION_TOKENwhen a release-bearing commit updates files under.github/workflows/and GitHub blocks the tag push without extra workflow permission - docs-only, README-only, website-only, and Pages-only changes are
non-release-bearing by default; they can refresh the public site from
main, but they should not create or advance a release draft, RC tag, or stable tag unless they also change shipped runtime, packaging, container, or release behavior - pull request labels drive the grouped GitHub release notes; the Release Drafter workflow applies most labels automatically, but the correct label should still be set before merge for unusual changes
- The signed Debian repository workflow only runs on stable
v*tags that point to commits already contained inmain - The GitHub release workflow only runs on trusted
v*orv*-rc.*tags that point to commits already contained inmain - The GHCR publish job only runs on
mainpushes or trustedv*/v*-rc.*tags that point to commits already contained inmain - Write permissions are scoped to the specific publish jobs that need them
- The scheduled dependency refresh job only runs from the default branch
- The archive signing key should be stored as a protected environment secret, not as a broadly scoped repository secret
blackhole-threats/
βββ .github/
β βββ assets/
β β βββ website/
β βββ dependabot.yml
β βββ release-drafter.yml
β βββ repository-labels.json
β βββ workflows/
β βββ automated-release.yml
β βββ build-and-validate.yml
β βββ container-image.yml
β βββ automation-auto-merge.yml
β βββ deploy-pages-site.yml
β βββ promote-release.yml
β βββ publish-apt-repository.yml
β βββ refresh-toolchain-and-runtime.yml
β βββ release-drafter.yml
β βββ release-assets.yml
βββ cmd/
β βββ blackhole-threats/
β βββ main.go
βββ debian/
βββ docs/
β βββ README.md
β βββ architecture.md
β βββ config-reference.md
β βββ deployment-examples.md
β βββ feed-behavior.md
β βββ operations.md
β βββ release-and-publishing.md
β βββ troubleshooting.md
βββ examples/
β βββ blackhole-threats.yaml
βββ internal/
β βββ bgp/
β βββ buildinfo/
β βββ config/
β βββ feed/
βββ packaging/
β βββ container/
βββ scripts/
β βββ build-apt-repository.sh
βββ Dockerfile
βββ Makefile
βββ go.mod
βββ README.md
cmd/blackhole-threats- CLI entrypoint, flag parsing, signal wiring, and process startup
internal/config- YAML loading, feed definitions, and community parsing
internal/feed- feed retrieval, parsing, and prefix summarisation
internal/bgp- GoBGP lifecycle and route announce/withdraw logic
packaging/container- container rootfs and S6 service definitions
debian- Debian packaging metadata
docs- longer-form operator and maintainer guides, including operations, config reference, release flow, deployment examples, and troubleshooting
.github/assets/website- Vite source for the GitHub Pages landing site that fronts the signed APT repository
Symptom:
Failed to load configuration
Checks:
- confirm the
-confpath exists - verify the service user can read it
- make sure the container bind mount is present if using Docker
Symptom:
Failed to read threat feed
Checks:
- verify the URL is reachable
- confirm local file paths are correct
- check for malformed JSON in JSON-derived feeds
- confirm the remote endpoint returns
200 OK
Checks:
- confirm the BGP session is established
- verify the configured router ID is correct
- check whether the selected community matches your router-side policy
- increase logging with
-debug - trigger a manual refresh with
SIGUSR1 - use
systemctl reload blackhole-threatson packaged installations
Checks:
- verify the
/configdirectory is writable - confirm the volume mount is present
- inspect container logs with
docker logs blackhole-threats
Checks:
- review
/etc/default/blackhole-threats - confirm
/etc/blackhole-threats.yamlexists and is readable - inspect
systemctl status blackhole-threats - inspect
journalctl -u blackhole-threats - verify the startup log fields for
config_path,local_as,router_id, andpeer_count
blackhole-threats is maintained as an MIT-licensed open source project with
public source, packaging, release automation, and documentation.
Project health and support files:
- LICENSE defines the MIT license.
- CONTRIBUTING.md explains development, validation, and pull request expectations.
- CODE_OF_CONDUCT.md covers expected behavior in project spaces.
- SECURITY.md explains private vulnerability reporting and supported versions.
- SUPPORT.md explains where to ask for help and what details to include.
- Discussions are open for operator questions, deployment notes, feed ideas, and roadmap discussion.
- GOVERNANCE.md describes the maintainer-led project model.
- ROADMAP.md lists current direction and non-goals.
- FUNDING.md explains how sponsorship, donated infrastructure, and AI/tooling credits support the public project.
- OPEN_SOURCE_IMPACT.md summarises public benefit, support use, AI/tooling credit use, and maintainer independence.
Current financial support is through Buy Me a Coffee. The GitHub Sponsor button is configured in .github/FUNDING.yml.
The full contribution guide lives in CONTRIBUTING.md. The short version is below.
Contributions should preserve the operator-facing behavior of the project:
- keep runtime behavior easy to understand
- preserve packaging and release reproducibility
- avoid undocumented surprises in routing behavior
- keep
mainin a releasable state
make fmt
make fmt-check
make vet
make test
make buildIf you change container, packaging, or release logic, also check:
make docker-build
make package- use conventional commit subjects for merge commits and squashed PRs
- make sure the pull request carries the correct release-note label before merge; Release Drafter uses labels for grouped release sections and applies most obvious ones automatically
- include a meaningful commit body when the change affects operators, packaging, release flow, or security posture
- assume merged
maincommits are eligible for automated RC tagging and prerelease artifact publication - use a release-bearing type when the change should publish artifacts;
docs,ci,chore,test, andrefactordo not cut a release by default - use the non-release-bearing path for README, docs, and website-only work unless the same change intentionally modifies shipped artifacts or release automation
- explain the operational reason for the change
- keep README, packaging, and workflow docs in sync with behavior
- include test coverage or a verification note when code paths change
- call out any routing, packaging, or upgrade impact explicitly
- do not broaden workflow permissions or secret exposure without a clear operational reason
- keep release and publishing workflows safe for a public repository
Release notes are drafted from pull requests through Release Drafter, with a
commit-history fallback for direct-to-main changes. Commit messages should
still be written as release inputs rather than throwaway local notes.
Preferred shape:
fix(logging): standardize startup logs across Docker and systemd
Emit structured logfmt-style startup lines with the release tag version.
Keep Docker logs, journald, and syslog collectors aligned on the same fields.
Document the operator-visible logging change in the README and man page.
Practical rules:
- keep the subject in conventional commit form
- make the subject describe the visible outcome, not just the file touched
- write the subject as release-ready prose; it will appear as the change title in the fallback published release notes and still drives automated version bumps
- if the pull request label is wrong, fix the label before merge instead of relying on the fallback path
- use the body to explain operator impact, packaging changes, or security implications
- keep body paragraphs factual and operator-facing; the fallback release-note generator lifts the first useful detail from the body when there is no pull request metadata to draft from
- split distinct impacts into separate paragraphs so the generated notes read cleanly
- use
feat,fix,perf,revert,container,build,deps, orpackagingwhen the change should trigger an automated release - use
BREAKING CHANGE:in the body when the release should force a major version bump - do not merge work to
mainunless you are happy for automation to tag and publish it as a release candidate
- additional operational documentation
- more feed examples and validation coverage
- router interoperability notes
- packaging and release polish
- dependency and supply-chain hardening
This project is licensed under the MIT License. See LICENSE.