From c6a45eae37bb2a5c78eb5fc1189606ca39d97f27 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Fri, 31 Jul 2026 12:14:13 -0700 Subject: [PATCH] contrib: add cln-plugin-bounce Stopping a dynamic plugin requires its exact registered name, which for versioned installs includes the version string. cln-plugin-bounce looks each name up from plugin list, stops the named plugins in the order given, and starts them again in reverse order, so the list order encodes any shutdown dependency between them. Restarts prefer the unversioned sibling path (usually a symlink maintained by the install script) so a repointed symlink brings up the new version. Plugin names are the arguments without a leading dash; everything else passes through to lightning-cli, so names and options may appear in any order. A script works under sudo where a shell alias does not. --- contrib/README.md | 15 +++++ contrib/cln-plugin-bounce | 134 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100755 contrib/cln-plugin-bounce diff --git a/contrib/README.md b/contrib/README.md index b8b039c..fa9d4b2 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -60,6 +60,8 @@ cd contrib/ ./clboss-askrene-layer-summary +./cln-plugin-bounce ... + The `clboss-routing-stats` and `clboss-forwarding-stats` scripts now accept `--days` to limit how many days of earnings history are considered when ranking channels. @@ -83,6 +85,19 @@ how many days of earnings history are considered when ranking channels. with `--top N` resolving the busiest directions to node aliases. Works on any persistent layer (`clboss` by default) and can replay a captured `askrene-listlayers` dump offline via `--input`. +- **`cln-plugin-bounce`** stops and restarts running plugins without + restarting `lightningd`. `plugin stop` needs a plugin's exact + registered name, which for versioned installs includes the version + string; the script looks each one up from `plugin list`, stops the + named plugins in the order given, and starts them again in reverse + order, so the list order encodes any shutdown dependency between + them. Restarts use the unversioned sibling path when one exists + (usually a symlink maintained by the install script), so a repointed + symlink brings up the new version. Plugin names are the arguments + not starting with `-`; every other argument is passed to + `lightning-cli` (e.g. `--signet --lightning-dir=...`), so names and + options may appear in any order. Plain POSIX sh plus `jq`, so unlike + a shell alias it also works under `sudo`. - **`fee-log-parser`** is a parser that streams DEBUG-level logging and writes a sqlite database containing fee algorithm information. CLBOSS now records the same schema in its internal database (`data.clboss`, tables diff --git a/contrib/cln-plugin-bounce b/contrib/cln-plugin-bounce new file mode 100755 index 0000000..d5c8a43 --- /dev/null +++ b/contrib/cln-plugin-bounce @@ -0,0 +1,134 @@ +#!/bin/sh +# cln-plugin-bounce - stop and restart running CLN plugins without +# restarting lightningd. +# +# usage: cln-plugin-bounce [lightning-cli options...] ... +# +# "plugin stop" requires a plugin's exact registered name, which for +# versioned installs includes the version string (e.g. +# /usr/local/bin/xrebalance-v0.4.1). This script looks each exact +# name up from "plugin list", stops the plugins in the order given, +# then starts them again in reverse order, so the list order encodes +# any shutdown dependency between them. When an unversioned sibling +# path exists (usually a symlink maintained by the install script), +# the restart uses that, so a repointed symlink brings up the new +# version. +# +# Plugin names are the arguments that do not start with "-"; each is +# a short name ("clboss", "xrebalance"), or a full versioned basename +# if the short name matches more than one running plugin. All other +# arguments are passed to every lightning-cli call, so names and +# options may appear in any order: +# cln-plugin-bounce --signet --lightning-dir="$CLNDIR" xrebalance clboss +# Set $LIGHTNING_CLI to override the lightning-cli binary. + +set -eu + +me=${0##*/} + +names= +rebuilt= +for arg do + [ -n "$rebuilt" ] || { set --; rebuilt=1; } + if [ "${arg#-}" = "$arg" ]; then + names="${names}${arg} +" + else + set -- "$@" "$arg" + fi +done + +if [ -z "$names" ]; then + echo "usage: $me [lightning-cli options...] ..." >&2 + exit 2 +fi + +lcli=${LIGHTNING_CLI:-lightning-cli} + +if ! command -v jq >/dev/null; then + echo "$me: jq is required" >&2 + exit 1 +fi + +# Registered paths whose basename is or -. +match='.plugins[].name + | select((sub(".*/"; "")) as $b + | $b == $n or ($b | startswith($n + "-")))' + +# Resolve every name before touching anything, from one snapshot. +plugins=$("$lcli" "$@" plugin list) + +resolved= +while IFS= read -r n; do + [ -n "$n" ] || continue + running=$(printf '%s\n' "$plugins" | jq -r --arg n "$n" "$match") + if [ -z "$running" ]; then + echo "$me: no running plugin matches '$n'" >&2 + exit 1 + fi + if [ "$(printf '%s\n' "$running" | wc -l)" -gt 1 ]; then + echo "$me: '$n' matches more than one running plugin:" >&2 + printf '%s\n' "$running" >&2 + exit 1 + fi + resolved="${resolved}${n} ${running} +" +done </dev/null; then + echo "$me: stop failed for $path" >&2 + if [ -n "$stopped_names" ]; then + echo "$me: already stopped, not restarted:$stopped_names" >&2 + fi + exit 1 + fi + stopped_names="$stopped_names $n" + tostart="${n} ${path} +${tostart}" +done </dev/null; then + ok= + if [ "$start" != "$path" ]; then + echo "$me: start failed; retrying with $path" >&2 + if "$lcli" "$@" plugin start "$path" >/dev/null; then + ok=1 + fi + fi + if [ -z "$ok" ]; then + echo "$me: failed to start $n" >&2 + failed=1 + fi + fi +done <