lnd-recovery-tools-overlay¶
Overlays
A tiny, self-contained Nixpkgs overlay that packages two Lightning/Bitcoin operator CLIs that are not in nixpkgs, so you have them ready before the day you need them — which, for channel recovery, is always a bad day.
chantools— Lightning Labs' channel rescue toolkit. The last-resort kit for getting funds out of LND channels when the node itself won't cooperate: Static Channel Backup (SCB) recovery, force-close sweeps, key derivation, on-chain address sweeping,channel.dbsurgery.bbolt-cli— thebboltCLI from etcd-io. LND stores its channel state inchannel.db, a BoltDB / bbolt file. When that database is what's wedged, you need to look inside it — dump buckets, check pages, inspect keys. This is that tool.
The problem it solves¶
Both of these are the kind of thing you reach for exactly once, in an
emergency, and discover they aren't packaged. Building a Go CLI by hand under
stress — chasing the right vendorHash, guessing the sub-package path — is not
what you want to be doing while funds are stuck. This overlay makes them
first-class pkgs.chantools / pkgs.bbolt-cli derivations so they're already
in your system closure (or one nix shell away).
Usage¶
Register the overlay:
or against a bare nixpkgs import:
Then use the packages anywhere:
or ad-hoc, without installing anything:
$ nix shell --impure --expr \
'import <nixpkgs> { overlays = [ (import ./lnd-recovery-tools-overlay) ]; }' \
-c chantools --version
Packaging notes / traps¶
-
Two hashes per package. Each derivation pins both the source
hash(the fetched Git tag tarball) and thevendorHash(the whole vendored Go module tree). When you bumpversion, both change. A stalevendorHashdoes not silently reuse old deps — it fails the build with a hash mismatch. The reliable loop: set the new version, set both hashes tolib.fakeHash, build once, copy the twogot:hashes the error prints. -
subPackagespicks the binary — and getting it wrong builds nothing, silently. Neither repo has itsmainat the module root:chantoolskeeps it incmd/chantools, andbbolt's repo is primarily the library with its CLI incmd/bbolt. Both therefore usesubPackages = [ "cmd/<name>" ]. This overlay previously pointedchantoolsat[ "." ]; because the root directory contains no Go files at all, the build succeeded and installed an empty$out— nobin/, no binary,mainProgramdangling. Nothing warns you. After changingsubPackages, check the output actually has the binary (ls $(nix build --no-link --print-out-paths …)/bin), don't just check that the build went green. -
Upstream tests may not pass in a clean checkout.
chantoolsshipsTestCompactDBAndDumpChannels, which compares against a golden dump that no longer matches; it is skipped viacheckFlags. The rest of thecmd/chantoolssuite runs and passes, so the skip stays narrow and anchored (-skip=^…$) rather than turningdoCheckoff wholesale. -
-Xversion stamping does not work here — verify before you add it. An earlier revision of this overlay carried-X github.com/lightningnetwork/chantools.Version=…/…Commit=…. Those flags were dead weight, for two independent reasons, and both are worth internalising because a bad-Xfails silently: the Go linker just drops a flag whose target it cannot find, so the build stays green and you only notice when--versionprints nothing useful. -
Wrong import path. The fetched repo is
lightninglabs/chantools, andgo.modsaysmodule github.com/lightninglabs/chantools— notlightningnetwork.-Xtakes<importpath>.<symbol>, so a typo'd org is a no-op. The module'smainpackage is undercmd/chantoolsbesides, not at the root. - The symbols are
const, notvar.cmd/chantools/root.godeclaresversionandCommitinside aconst (…)block.-Xcan only patch string variables; constants are baked in at compile time and are untouchable. Upstream's ownMakefilehas the same dead-X main.Commit=$(git describe --tags).
The practical upshot: chantools --version already prints the correct
v<version> (it comes from the constant) and an empty commit, with or
without any linker flags. Check go.mod and whether the target is a var
before reaching for -X in any Go package.
bbolt-cliis renamed to avoid a clash. The package ispname = "bbolt-cli"but the installed binary isbbolt(mainProgram). Thepkgsattribute isbbolt-cliso it doesn't collide with any future library-onlybboltattribute that might land in nixpkgs.
Caveats¶
- Versions and hashes here are a point-in-time snapshot; bump them for your own deploy (see the two-hashes note above).
- These are operator / rescue tools.
chantoolsin particular manipulates keys and can broadcast on-chain transactions — read its docs and work against backups.bboltcan write to a database file; inspect read-only unless you mean it, and always on a copy.
Source¶
overlays/lnd-recovery-tools-overlay/default.nix
# lnd-recovery-tools-overlay
#
# A self-contained Nixpkgs overlay that packages two Lightning/Bitcoin operator
# CLIs that are (as of writing) absent from nixpkgs:
#
# * chantools — Lightning Labs' last-resort toolkit for rescuing funds from
# LND channels (SCB recovery, force-close sweeps, key
# derivation, on-chain address sweeping).
# * bbolt-cli — the etcd-io/bbolt CLI, for inspecting and surgically editing
# BoltDB / bbolt database files — the embedded k/v store LND
# keeps its `channel.db` in, and that many Go services use.
#
# Both are plain `buildGoModule` derivations wired into an overlay via
# `callPackage`, so `pkgs.chantools` / `pkgs.bbolt-cli` become available fleet-
# wide once the overlay is registered.
#
# Usage — register the overlay:
#
# nixpkgs.overlays = [ (import ./lnd-recovery-tools-overlay) ];
#
# or on a bare nixpkgs import:
#
# import <nixpkgs> { overlays = [ (import ./lnd-recovery-tools-overlay) ]; };
#
# then reference `pkgs.chantools` and `pkgs.bbolt-cli` in `environment.systemPackages`,
# a `nix shell`, or a devShell.
#
# --- Bumping versions -------------------------------------------------------
# When you change `version`, both the source `hash` AND the `vendorHash` will
# change. The reliable loop: set the new version, set both hashes to
# `lib.fakeHash`, build once, and copy the two "got:" hashes the error prints.
# `vendorHash` covers the whole vendored Go module tree — a stale value fails
# the build with a hash mismatch, it does not silently use old deps.
final: prev:
let
# chantools — Lightning channel rescue toolkit (Lightning Labs).
#
# No `-X` version stamping here, and that is deliberate: for this release
# `chantools --version` is fed by `version` and `Commit`, both declared in a
# `const` block in `cmd/chantools/root.go`. The Go linker's `-X` can only
# patch string *variables*, never constants, so any `-X …Version=…` /
# `-X …Commit=…` pair is silently discarded — including upstream's own
# `-X main.Commit=…` in their Makefile. The binary already reports the right
# version because the constant carries it; `commit` just stays empty.
#
# (An earlier revision of this overlay stamped
# `github.com/lightningnetwork/chantools.{Version,Commit}`, which was wrong
# twice over: the module path is `github.com/lightninglabs/chantools` — see
# `go.mod` — and the symbols live in package `main` under `cmd/chantools`,
# not at the module root.)
chantools = prev.callPackage (
{
lib,
buildGoModule,
fetchFromGitHub,
}:
buildGoModule rec {
pname = "chantools";
version = "0.14.2";
src = fetchFromGitHub {
owner = "lightninglabs";
repo = "chantools";
rev = "v${version}";
hash = "sha256-pHcTBoipN1mYdGPswgAUVs/A3k1HKD5LXmCxwduStOw=";
};
vendorHash = "sha256-+jOrR8jhNdMvICwwLPAuYTGjlkXh7y4tZceioi9EJQI=";
# The module root holds no Go files at all — `main` lives in
# `cmd/chantools`. `subPackages = [ "." ]` builds cleanly and installs
# NOTHING, leaving an empty derivation and a `mainProgram` that does not
# exist; the failure only shows up when you try to run the tool.
subPackages = [ "cmd/chantools" ];
ldflags = [
"-s"
"-w"
];
# One upstream unit test compares against a stale golden dump and fails
# in a clean checkout. The rest of `cmd/chantools`' tests run and pass.
checkFlags = [ "-skip=^TestCompactDBAndDumpChannels$" ];
meta = {
description = "Tools for rescuing funds from Lightning Network channels";
homepage = "https://github.com/lightninglabs/chantools";
license = lib.licenses.mit;
mainProgram = "chantools";
};
}
) { };
# bbolt-cli — the `bbolt` CLI from etcd-io/bbolt.
# The upstream repo is the bbolt library; the CLI lives in `cmd/bbolt`, so
# `subPackages = [ "cmd/bbolt" ]` builds just that one binary (named `bbolt`).
bbolt-cli = prev.callPackage (
{
lib,
buildGoModule,
fetchFromGitHub,
}:
buildGoModule rec {
pname = "bbolt-cli";
version = "1.4.3";
src = fetchFromGitHub {
owner = "etcd-io";
repo = "bbolt";
rev = "v${version}";
hash = "sha256-awBkr2ObRxPQkMlfVFZxEbQ9JQJsFrJvSBHtqP4Hb3I=";
};
vendorHash = "sha256-TzVmAMrNrNkFE9jQ+SILJXvbhBK1WenNPqA0FfuDU+M=";
subPackages = [ "cmd/bbolt" ];
ldflags = [
"-s"
"-w"
];
meta = {
description = "BoltDB CLI tool for inspecting and manipulating bbolt databases";
homepage = "https://github.com/etcd-io/bbolt";
license = lib.licenses.mit;
mainProgram = "bbolt";
};
}
) { };
in
{
inherit chantools bbolt-cli;
}