- Go 71.7%
- Python 15.4%
- Just 4.1%
- Makefile 3.8%
- Shell 1.9%
- Other 3.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .claude | ||
| .forgejo/workflows | ||
| .github | ||
| attic/tls-leech-v0.1.2 | ||
| cmd/tls-leech | ||
| contrib | ||
| docs | ||
| internal | ||
| leech | ||
| LICENSES | ||
| observability | ||
| scripts | ||
| site | ||
| test/e2e | ||
| .dockerignore | ||
| .gitignore | ||
| .golangci.yml | ||
| AGENTS.md | ||
| CODE_OF_CONDUCT.md | ||
| codecov.yml | ||
| CONTRIBUTING.md | ||
| docker-compose.observability.yml | ||
| docker-compose.yml | ||
| docker-entrypoint.sh | ||
| Dockerfile | ||
| Dockerfile.package | ||
| go.mod | ||
| go.sum | ||
| justfile | ||
| LICENSE | ||
| license.go | ||
| license_test.go | ||
| Makefile | ||
| README.md | ||
| SECURITY.md | ||
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.