2025-12-12 09:36:23 +01:00
|
|
|
#!/usr/bin/env -S uv run --script
|
|
|
|
|
|
|
|
|
|
# /// script
|
2026-05-12 22:19:12 +02:00
|
|
|
# requires-python = ">=3.10"
|
2025-12-12 09:36:23 +01:00
|
|
|
# dependencies = [
|
2026-05-12 22:19:12 +02:00
|
|
|
# "click>=8.3.3",
|
|
|
|
|
# "requests[socks]>=2.34.0",
|
2025-12-12 09:36:23 +01:00
|
|
|
# ]
|
|
|
|
|
# ///
|
|
|
|
|
|
2020-12-30 21:58:39 +01:00
|
|
|
from backends import get_backend
|
|
|
|
|
from backend import Change
|
2021-02-15 16:02:53 +01:00
|
|
|
from server import SocketServer, setup_server_logging
|
2020-12-30 21:58:39 +01:00
|
|
|
|
2020-04-04 14:25:33 +02:00
|
|
|
import os
|
|
|
|
|
import click
|
|
|
|
|
import json
|
2020-12-30 21:58:39 +01:00
|
|
|
import logging
|
2020-04-04 17:05:20 +02:00
|
|
|
import sqlite3
|
|
|
|
|
import sys
|
2020-04-04 14:25:33 +02:00
|
|
|
|
2020-12-30 21:58:39 +01:00
|
|
|
root = logging.getLogger()
|
|
|
|
|
root.setLevel(logging.INFO)
|
|
|
|
|
|
|
|
|
|
handler = logging.StreamHandler(sys.stdout)
|
|
|
|
|
handler.setLevel(logging.DEBUG)
|
2025-12-12 09:37:31 +01:00
|
|
|
formatter = logging.Formatter("%(message)s")
|
2020-12-30 21:58:39 +01:00
|
|
|
handler.setFormatter(formatter)
|
|
|
|
|
root.addHandler(handler)
|
2020-04-04 14:25:33 +02:00
|
|
|
|
2025-12-12 09:37:31 +01:00
|
|
|
|
2020-04-04 14:25:33 +02:00
|
|
|
@click.command()
|
2020-04-04 16:20:46 +02:00
|
|
|
@click.argument("backend-url")
|
2025-12-12 09:37:31 +01:00
|
|
|
@click.option(
|
|
|
|
|
"--lightning-dir",
|
|
|
|
|
type=click.Path(exists=True),
|
|
|
|
|
default=None,
|
|
|
|
|
help="Use an existing lightning directory (default: initialize an empty backup).",
|
|
|
|
|
)
|
2020-04-04 16:20:46 +02:00
|
|
|
def init(lightning_dir, backend_url):
|
|
|
|
|
destination = backend_url
|
2020-04-05 15:42:25 +02:00
|
|
|
backend = get_backend(destination, create=True)
|
2020-12-30 21:58:39 +01:00
|
|
|
|
|
|
|
|
if lightning_dir is not None:
|
|
|
|
|
lock_file = os.path.join(lightning_dir, "backup.lock")
|
|
|
|
|
db_file = os.path.join(lightning_dir, "lightningd.sqlite3")
|
|
|
|
|
|
|
|
|
|
with open(lock_file, "w") as f:
|
2025-12-12 09:37:31 +01:00
|
|
|
f.write(
|
|
|
|
|
json.dumps(
|
|
|
|
|
{
|
|
|
|
|
"backend_url": destination,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
)
|
2020-12-30 21:58:39 +01:00
|
|
|
|
|
|
|
|
data_version = 0
|
|
|
|
|
if os.path.exists(db_file):
|
2025-12-12 09:37:31 +01:00
|
|
|
print(
|
|
|
|
|
"Found an existing database at {db_file}, initializing the backup with a snapshot".format(
|
|
|
|
|
db_file=db_file
|
|
|
|
|
)
|
|
|
|
|
)
|
2020-12-30 21:58:39 +01:00
|
|
|
# Peek into the DB to see if we have
|
|
|
|
|
db = sqlite3.connect(db_file)
|
|
|
|
|
cur = db.cursor()
|
|
|
|
|
rows = cur.execute("SELECT intval FROM vars WHERE name = 'data_version'")
|
|
|
|
|
data_version = rows.fetchone()[0]
|
|
|
|
|
|
|
|
|
|
snapshot = Change(
|
|
|
|
|
version=data_version,
|
2025-12-12 09:37:31 +01:00
|
|
|
snapshot=open(db_file, "rb").read(),
|
|
|
|
|
transaction=None,
|
2020-12-30 21:58:39 +01:00
|
|
|
)
|
|
|
|
|
if not backend.add_change(snapshot):
|
|
|
|
|
print("Could not write snapshot to backend")
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
else:
|
2025-12-12 09:37:31 +01:00
|
|
|
print(
|
|
|
|
|
"Successfully written initial snapshot to {destination}".format(
|
|
|
|
|
destination=destination
|
|
|
|
|
)
|
|
|
|
|
)
|
2020-04-04 17:05:20 +02:00
|
|
|
else:
|
2020-12-30 21:58:39 +01:00
|
|
|
print("Database does not exist yet, created an empty backup file")
|
2020-04-04 17:05:20 +02:00
|
|
|
|
2025-12-12 09:37:31 +01:00
|
|
|
print(
|
|
|
|
|
"Initialized backup backend {destination}, you can now start Core-Lightning".format(
|
|
|
|
|
destination=destination,
|
|
|
|
|
)
|
|
|
|
|
)
|
2020-04-04 14:25:33 +02:00
|
|
|
|
|
|
|
|
|
2020-04-04 20:12:18 +02:00
|
|
|
@click.command()
|
|
|
|
|
@click.argument("backend-url")
|
|
|
|
|
@click.argument("restore-destination")
|
|
|
|
|
def restore(backend_url, restore_destination):
|
|
|
|
|
destination = backend_url
|
|
|
|
|
backend = get_backend(destination)
|
|
|
|
|
backend.restore(restore_destination)
|
|
|
|
|
|
|
|
|
|
|
2020-12-30 21:58:39 +01:00
|
|
|
@click.command()
|
|
|
|
|
@click.argument("backend-url")
|
|
|
|
|
@click.argument("addr")
|
2025-12-12 09:37:31 +01:00
|
|
|
@click.option(
|
|
|
|
|
"--log-mode",
|
|
|
|
|
type=click.Choice(["plain", "systemd"], case_sensitive=False),
|
|
|
|
|
default="plain",
|
|
|
|
|
help="Debug log mode, defaults to plain",
|
|
|
|
|
)
|
|
|
|
|
@click.option(
|
|
|
|
|
"--log-level",
|
|
|
|
|
type=click.Choice(
|
|
|
|
|
["debug", "info", "notice", "warning", "error", "critical"],
|
|
|
|
|
case_sensitive=False,
|
|
|
|
|
),
|
|
|
|
|
default="info",
|
|
|
|
|
help="Debug log level, defaults to info",
|
|
|
|
|
)
|
2021-02-15 16:02:53 +01:00
|
|
|
def server(backend_url, addr, log_mode, log_level):
|
2020-12-30 21:58:39 +01:00
|
|
|
backend = get_backend(backend_url)
|
2025-12-12 09:37:31 +01:00
|
|
|
addr, port = addr.split(":")
|
2020-12-30 21:58:39 +01:00
|
|
|
port = int(port)
|
2021-02-15 16:02:53 +01:00
|
|
|
|
|
|
|
|
setup_server_logging(log_mode, log_level)
|
|
|
|
|
|
2020-12-30 21:58:39 +01:00
|
|
|
server = SocketServer((addr, port), backend)
|
|
|
|
|
server.run()
|
|
|
|
|
|
|
|
|
|
|
2020-04-04 14:25:33 +02:00
|
|
|
@click.group()
|
|
|
|
|
def cli():
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cli.add_command(init)
|
2020-04-04 20:12:18 +02:00
|
|
|
cli.add_command(restore)
|
2020-12-30 21:58:39 +01:00
|
|
|
cli.add_command(server)
|
2020-04-04 14:25:33 +02:00
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
cli()
|