mirror of
https://github.com/aljazceru/plugins.git
synced 2025-12-20 06:34:20 +01:00
backup: Add backup-cli tool and use it to initialize the backups
This commit is contained in:
39
backup/backup-cli
Executable file
39
backup/backup-cli
Executable file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from backup import FileBackend
|
||||||
|
import os
|
||||||
|
import click
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.argument("lightning-dir", type=click.Path(exists=True))
|
||||||
|
@click.argument("backup-dir", type=click.Path(exists=True))
|
||||||
|
def init(lightning_dir, backup_dir):
|
||||||
|
destination = 'file://' + os.path.join(backup_dir, 'backup.dbak')
|
||||||
|
backend = FileBackend(destination)
|
||||||
|
backend.version, backend.prev_version = 0, 0
|
||||||
|
backend.offsets = [512, 0]
|
||||||
|
backend.version_count = 0
|
||||||
|
backend.write_metadata()
|
||||||
|
|
||||||
|
lock_file = os.path.join(lightning_dir, "backup.lock")
|
||||||
|
|
||||||
|
with open(lock_file, "w") as f:
|
||||||
|
f.write(json.dumps({
|
||||||
|
'backend_url': destination,
|
||||||
|
}))
|
||||||
|
|
||||||
|
print("Initialized backup backend {destination}, you can now start c-lightning".format(
|
||||||
|
destination=destination,
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
def cli():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
cli.add_command(init)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
cli()
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from pyln.client import Plugin
|
|
||||||
from pprint import pprint
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from pyln.client import Plugin
|
||||||
|
from typing import Mapping, Type
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
import struct
|
import json
|
||||||
import os
|
|
||||||
from typing import Mapping, Type, Optional
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
import sys
|
import sys
|
||||||
from binascii import hexlify
|
|
||||||
|
|
||||||
|
|
||||||
plugin = Plugin()
|
plugin = Plugin()
|
||||||
@@ -27,6 +26,7 @@ root.addHandler(handler)
|
|||||||
# applied.
|
# applied.
|
||||||
Change = namedtuple('Change', ['version', 'transaction'])
|
Change = namedtuple('Change', ['version', 'transaction'])
|
||||||
|
|
||||||
|
|
||||||
class Backend(object):
|
class Backend(object):
|
||||||
def __init__(self, destination: str):
|
def __init__(self, destination: str):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@@ -40,6 +40,7 @@ class Backend(object):
|
|||||||
def initialize(self) -> bool:
|
def initialize(self) -> bool:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class FileBackend(Backend):
|
class FileBackend(Backend):
|
||||||
def __init__(self, destination: str):
|
def __init__(self, destination: str):
|
||||||
self.version = None
|
self.version = None
|
||||||
@@ -116,9 +117,15 @@ class FileBackend(Backend):
|
|||||||
self.prev_version, self.offsets[1] = 0, 0
|
self.prev_version, self.offsets[1] = 0, 0
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_backend_class(backend_url):
|
||||||
backend_map: Mapping[str, Type[Backend]] = {
|
backend_map: Mapping[str, Type[Backend]] = {
|
||||||
'file': FileBackend,
|
'file': FileBackend,
|
||||||
}
|
}
|
||||||
|
p = urlparse(backend_url)
|
||||||
|
backend_cl = backend_map.get(p.scheme, None)
|
||||||
|
return backend_cl
|
||||||
|
|
||||||
|
|
||||||
def abort(reason: str) -> None:
|
def abort(reason: str) -> None:
|
||||||
plugin.log(reason)
|
plugin.log(reason)
|
||||||
@@ -165,7 +172,7 @@ def on_db_write(writes, data_version, plugin, **kwargs):
|
|||||||
change = Change(data_version, writes)
|
change = Change(data_version, writes)
|
||||||
if not hasattr(plugin, 'backend'):
|
if not hasattr(plugin, 'backend'):
|
||||||
plugin.early_writes.append(change)
|
plugin.early_writes.append(change)
|
||||||
return True
|
return {"result": "continue"}
|
||||||
else:
|
else:
|
||||||
return apply_write(plugin, change)
|
return apply_write(plugin, change)
|
||||||
|
|
||||||
@@ -175,7 +182,8 @@ def apply_write(plugin, change):
|
|||||||
assert(check_first_write(plugin, change.version))
|
assert(check_first_write(plugin, change.version))
|
||||||
plugin.initialized = True
|
plugin.initialized = True
|
||||||
|
|
||||||
return plugin.backend.add_entry(change)
|
if plugin.backend.add_change(change):
|
||||||
|
return {"result": "continue"}
|
||||||
|
|
||||||
|
|
||||||
@plugin.init()
|
@plugin.init()
|
||||||
@@ -185,6 +193,17 @@ def on_init(options: Mapping[str, str], plugin: Plugin, **kwargs):
|
|||||||
plugin.db_path = configs['wallet']
|
plugin.db_path = configs['wallet']
|
||||||
destination = options['backup-destination']
|
destination = options['backup-destination']
|
||||||
|
|
||||||
|
# Ensure that we don't inadventently switch the destination
|
||||||
|
if os.path.exists("backup.lock"):
|
||||||
|
d = json.load(open("backup.lock", 'r'))
|
||||||
|
if destination is None or destination == 'null':
|
||||||
|
destination = d['backend_url']
|
||||||
|
elif destination != d['backend_url']:
|
||||||
|
abort(
|
||||||
|
"The destination specified as option does not match the one "
|
||||||
|
"specified in backup.lock. Please check your settings"
|
||||||
|
)
|
||||||
|
|
||||||
if not plugin.db_path.startswith('sqlite3'):
|
if not plugin.db_path.startswith('sqlite3'):
|
||||||
abort("The backup plugin only works with the sqlite3 database.")
|
abort("The backup plugin only works with the sqlite3 database.")
|
||||||
|
|
||||||
@@ -192,8 +211,7 @@ def on_init(options: Mapping[str, str], plugin: Plugin, **kwargs):
|
|||||||
abort("You must specify a backup destination, possibly on a secondary disk.")
|
abort("You must specify a backup destination, possibly on a secondary disk.")
|
||||||
|
|
||||||
# Let's initialize the backed. First we need to figure out which backend to use.
|
# Let's initialize the backed. First we need to figure out which backend to use.
|
||||||
p = urlparse(destination)
|
backend_cl = resolve_backend_class(destination)
|
||||||
backend_cl = backend_map.get(p.scheme, None)
|
|
||||||
if backend_cl is None:
|
if backend_cl is None:
|
||||||
abort("Could not find a backend for scheme {p.scheme}".format(p=p))
|
abort("Could not find a backend for scheme {p.scheme}".format(p=p))
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -3,20 +3,23 @@ from pyln.client import RpcError
|
|||||||
from pyln.testing.fixtures import *
|
from pyln.testing.fixtures import *
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
plugin_dir = os.path.dirname(__file__)
|
plugin_dir = os.path.dirname(__file__)
|
||||||
plugin_path = os.path.join(plugin_dir, "backup.py")
|
plugin_path = os.path.join(plugin_dir, "backup.py")
|
||||||
|
cli_path = os.path.join(os.path.dirname(__file__), "backup-cli")
|
||||||
|
|
||||||
|
|
||||||
def test_start(node_factory, directory):
|
def test_start(node_factory, directory):
|
||||||
|
bdest = os.path.join(directory, 'backup.dbak')
|
||||||
opts = {
|
opts = {
|
||||||
'plugin': plugin_path,
|
'plugin': plugin_path,
|
||||||
'backup-destination': 'file://' + os.path.join(directory, 'backup.dbak')
|
'backup-destination': 'file://' + bdest,
|
||||||
}
|
}
|
||||||
shutil.copyfile(
|
|
||||||
os.path.join(plugin_dir, 'fixtures', "backup.dbak"),
|
subprocess.check_call([cli_path, "init", directory, directory])
|
||||||
os.path.join(directory, "backup.dbak")
|
|
||||||
)
|
|
||||||
l1 = node_factory.get_node(options=opts)
|
l1 = node_factory.get_node(options=opts)
|
||||||
|
|
||||||
l1.daemon.wait_for_log(r'backup.py')
|
l1.daemon.wait_for_log(r'backup.py')
|
||||||
@@ -37,14 +40,12 @@ def test_tx_abort(node_factory, directory):
|
|||||||
inbetween the hook call and the DB transaction.
|
inbetween the hook call and the DB transaction.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
bdest = os.path.join(directory, 'backup.dbak')
|
||||||
opts = {
|
opts = {
|
||||||
'plugin': plugin_path,
|
'plugin': plugin_path,
|
||||||
'backup-destination': 'file://' + os.path.join(directory, 'backup.dbak')
|
'backup-destination': 'file://' + bdest,
|
||||||
}
|
}
|
||||||
shutil.copyfile(
|
subprocess.check_call([cli_path, "init", directory, directory])
|
||||||
os.path.join(plugin_dir, 'fixtures', "backup.dbak"),
|
|
||||||
os.path.join(directory, "backup.dbak")
|
|
||||||
)
|
|
||||||
l1 = node_factory.get_node(options=opts)
|
l1 = node_factory.get_node(options=opts)
|
||||||
l1.stop()
|
l1.stop()
|
||||||
|
|
||||||
@@ -71,10 +72,7 @@ def test_failing_restore(nf, directory):
|
|||||||
'plugin': plugin_path,
|
'plugin': plugin_path,
|
||||||
'backup-destination': 'file://' + os.path.join(directory, 'backup.dbak')
|
'backup-destination': 'file://' + os.path.join(directory, 'backup.dbak')
|
||||||
}
|
}
|
||||||
shutil.copyfile(
|
subprocess.check_call([cli_path, "init", directory, directory])
|
||||||
os.path.join(plugin_dir, 'fixtures', "backup.dbak"),
|
|
||||||
os.path.join(directory, "backup.dbak")
|
|
||||||
)
|
|
||||||
l1 = node_factory.get_node(options=opts)
|
l1 = node_factory.get_node(options=opts)
|
||||||
l1.stop()
|
l1.stop()
|
||||||
|
|
||||||
@@ -97,10 +95,7 @@ def test_intermittent_backup(node_factory, directory):
|
|||||||
'plugin': plugin_path,
|
'plugin': plugin_path,
|
||||||
'backup-destination': 'file://' + os.path.join(directory, 'backup.dbak')
|
'backup-destination': 'file://' + os.path.join(directory, 'backup.dbak')
|
||||||
}
|
}
|
||||||
shutil.copyfile(
|
subprocess.check_call([cli_path, "init", directory, directory])
|
||||||
os.path.join(plugin_dir, 'fixtures', "backup.dbak"),
|
|
||||||
os.path.join(directory, "backup.dbak")
|
|
||||||
)
|
|
||||||
l1 = node_factory.get_node(options=opts)
|
l1 = node_factory.get_node(options=opts)
|
||||||
|
|
||||||
# Now start without the plugin. This should work fine.
|
# Now start without the plugin. This should work fine.
|
||||||
|
|||||||
Reference in New Issue
Block a user