TLS certificate extraction and distribution https://nrtn.dev/catalyst/tls-leech/
  • Go 71.7%
  • Python 15.4%
  • Just 4.1%
  • Makefile 3.8%
  • Shell 1.9%
  • Other 3.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Oleksii Khilkevych 8344e04197
All checks were successful
Release / Binaries, image, release (push) Successful in 2m39s
CI / Test & Build (push) Successful in 2m47s
CI / Dev image (push) Has been skipped
Route --help to the binary when a subcommand is present
2026-10-26 00:16:36 +01:00
.claude M2: Go scaffold renamed to tls-leech, docs in place 2026-10-20 02:06:52 +02:00
.forgejo/workflows Self-hosted leaf bootstrap off the server's own dist tree 2026-10-25 23:24:30 +01:00
.github M2: Go scaffold renamed to tls-leech, docs in place 2026-10-20 02:06:52 +02:00
attic/tls-leech-v0.1.2 M1: pin kubernetes<37 for attic tree 2026-10-20 01:52:18 +02:00
cmd/tls-leech M2: Go scaffold renamed to tls-leech, docs in place 2026-10-20 02:06:52 +02:00
contrib M2: Go scaffold renamed to tls-leech, docs in place 2026-10-20 02:06:52 +02:00
docs Cut the chown: stored files belong to whoever runs the server 2026-10-25 23:47:42 +01:00
internal Route --help to the binary when a subcommand is present 2026-10-26 00:16:36 +01:00
leech Route --help to the binary when a subcommand is present 2026-10-26 00:16:36 +01:00
LICENSES M2: Go scaffold renamed to tls-leech, docs in place 2026-10-20 02:06:52 +02:00
observability M2: Go scaffold renamed to tls-leech, docs in place 2026-10-20 02:06:52 +02:00
scripts licenses.sh: admit ISC, prune test-only passengers via BFS 2026-10-20 04:01:30 +02:00
site M2: Go scaffold renamed to tls-leech, docs in place 2026-10-20 02:06:52 +02:00
test/e2e Self-hosted leaf bootstrap off the server's own dist tree 2026-10-25 23:24:30 +01:00
.dockerignore Self-hosted leaf bootstrap off the server's own dist tree 2026-10-25 23:24:30 +01:00
.gitignore LEECH_PY doc, drop goals, ignore .agents 2026-10-25 19:53:36 +01:00
.golangci.yml Borrow kpr sealed() lint exclusion for future proofs 2026-10-25 20:31:53 +01:00
AGENTS.md Docs point at the index; history stays historical 2026-10-25 20:54:23 +01:00
CODE_OF_CONDUCT.md M2: Go scaffold renamed to tls-leech, docs in place 2026-10-20 02:06:52 +02:00
codecov.yml M2: Go scaffold renamed to tls-leech, docs in place 2026-10-20 02:06:52 +02:00
CONTRIBUTING.md M2: Go scaffold renamed to tls-leech, docs in place 2026-10-20 02:06:52 +02:00
docker-compose.observability.yml M2: Go scaffold renamed to tls-leech, docs in place 2026-10-20 02:06:52 +02:00
docker-compose.yml Self-hosted leaf bootstrap off the server's own dist tree 2026-10-25 23:24:30 +01:00
docker-entrypoint.sh Self-hosted leaf bootstrap off the server's own dist tree 2026-10-25 23:24:30 +01:00
Dockerfile Bake release repo into leech.py from build-stamped config 2026-10-25 18:41:41 +01:00
Dockerfile.package Self-hosted leaf bootstrap off the server's own dist tree 2026-10-25 23:24:30 +01:00
go.mod Basic e2e via testcontainers, kpr-structured 2026-10-25 11:49:13 +01:00
go.sum Basic e2e via testcontainers, kpr-structured 2026-10-25 11:49:13 +01:00
justfile Self-hosted leaf bootstrap off the server's own dist tree 2026-10-25 23:24:30 +01:00
LICENSE M2: Go scaffold renamed to tls-leech, docs in place 2026-10-20 02:06:52 +02:00
license.go M2: Go scaffold renamed to tls-leech, docs in place 2026-10-20 02:06:52 +02:00
license_test.go M2: Go scaffold renamed to tls-leech, docs in place 2026-10-20 02:06:52 +02:00
Makefile Self-hosted leaf bootstrap off the server's own dist tree 2026-10-25 23:24:30 +01:00
README.md Self-hosted leaf bootstrap off the server's own dist tree 2026-10-25 23:24:30 +01:00
SECURITY.md M2: Go scaffold renamed to tls-leech, docs in place 2026-10-20 02:06:52 +02:00

tls-leech

TLS certificate extraction and distribution tool, in Go: accept uploads over HTTP, store them on disk, run hook scripts — and export certificates from certbot, Kubernetes secrets, and Traefik acme.json.

Product home: https://nrtn.dev/catalyst/tls-leech

Details live in docs/; this page is the short version.

Status

Go on master, CI green.

Quick start

Run the server

Needs Go 1.27+:

make build          # or: just build
./dist/tls-leech-$(go env GOOS)-$(go env GOARCH) serve

Upload from leaf nodes

Drop this in as the certbot deploy hook (/etc/letsencrypt/renewal-hooks/deploy/tls-leech.sh) — stdlib-only Python, tracks latest, verifies checksums; no install, no version in the hook:

#!/bin/sh
# Adjust --letsencrypt-dir to your certbot layout, and
# --leech-endpoint if this leaf reports anywhere but here.
# Remove --no-dry-run for a tryout run (previews only).
# TLS_LEECH_API_KEY overrides the default key below.
TLS_LEECH_BIN="uv run https://tls-leech.noroutine.me/leech.py"
TLS_LEECH_API_KEY="${TLS_LEECH_API_KEY:-changeme}"
$TLS_LEECH_BIN upload certbot \
  --api-key "$TLS_LEECH_API_KEY" \
  --no-dry-run \
  --letsencrypt-dir /etc/letsencrypt/live

The endpoint is baked into the served script, so no --leech-endpoint flag — and the binary comes from that server itself first, so the upload always matches the server version (see leech.py). Any other server serves the same script at its own /leech.py; the Forgejo raw copy (https://nrtn.dev/catalyst/tls-leech/raw/branch/master/leech/leech.py) works too and falls back to tls-leech.noroutine.me.

Upload from Traefik

#!/bin/sh
# Adjust --acme-json to your Traefik acme.json path.
# Remove --no-dry-run for a tryout run (previews only).
TLS_LEECH_BIN="uv run https://tls-leech.noroutine.me/leech.py"
TLS_LEECH_API_KEY="${TLS_LEECH_API_KEY:-changeme}"
$TLS_LEECH_BIN upload traefik \
  --api-key "$TLS_LEECH_API_KEY" \
  --no-dry-run \
  --acme-json /etc/traefik/acme.json

Upload from Kubernetes

#!/bin/sh
# --namespace sweeps one namespace; empty sweeps all.
# --kubeconfig follows the same rules as kubectl
# (KUBECONFIG env, in-cluster config, ~/.kube/config).
# Remove --no-dry-run for a tryout run (previews only).
TLS_LEECH_BIN="uv run https://tls-leech.noroutine.me/leech.py"
TLS_LEECH_API_KEY="${TLS_LEECH_API_KEY:-changeme}"
$TLS_LEECH_BIN upload k8s \
  --api-key "$TLS_LEECH_API_KEY" \
  --no-dry-run \
  --kubeconfig ~/.kube/config

Pin the version

The wrapper tracks latest by default; pin with --version. Everything before the -- belongs to the wrapper, everything after rides to the binary. Tagged releases are on the releases page.

#!/bin/sh
TLS_LEECH_BIN="uv run https://tls-leech.noroutine.me/leech.py"
TLS_LEECH_API_KEY="${TLS_LEECH_API_KEY:-changeme}"
$TLS_LEECH_BIN --version v0.1.2.95 -- upload certbot \
  --api-key "$TLS_LEECH_API_KEY" \
  --no-dry-run \
  --letsencrypt-dir /etc/letsencrypt/live
make check          # fmt-check + vet + lint + test
make test           # full suite (gotestsum when installed)
make coverage       # terminal table + coverage.out/html

Run from source

No clone (no -- separator: with an @version query go run drops everything past it):

GOPRIVATE='nrtn.dev/*' go run nrtn.dev/catalyst/tls-leech/cmd/tls-leech@master serve --help

Commands

One file per command in internal/cli (CLI):

Command Does
upload certbot|k8s|traefik export from certbot / K8s secrets / Traefik acme.json (dry-run previews unless --no-dry-run)
serve console + app/upload (POST /api/v1/upload, store, hooks) + events
env resolved TLS_LEECH_*/OTEL_* config
version / license build identity + terms (Website included)
completion generated shell completions

Export one lineage (preview first, --no-dry-run uploads):

tls-leech upload certbot --leech-endpoint http://localhost:8080 --letsencrypt-dir ~/.certbot/live

Docs

Everything else lives in docs/, grouped by what you came here to do.

Docker

docker run -d \
  -p 8080:8080 \
  -v certs:/var/lib/tls-leech/certs \
  -v ./hooks:/etc/tls-leech/hooks:ro \
  -e TLS_LEECH_API_KEYS=secret123 \
  cr.noroutine.me/catalyst/tls-leech:latest

The image runs the upload server as non-root; the entrypoint creates and chowns the cert/hook dirs. Override the command with serve for the console.

License

MIT — see LICENSE. tls-leech license prints the embedded text.