cli (backup): add backup commands:

Add the `li backup` commands with `export`, `import` and `verify`:

- data types selection (`-i`/`-e`) and profile selection (`-p`), `--no-db` on export, `--use-db` on
  import
- GPG symmetric encryption of the archive (`--encrypt`)
- progression is shown, with ETA
- the commands can run without a backend connection (import from DB MUST be run without
  backend)
- cli (base): `BRIDGELESS_COMMANDS` holds the name of the few commands which can be run
  without backend.

rel 480
This commit is contained in:
Goffi 2026-09-15 11:35:55 +02:00
commit f22af42ece

View file

@ -128,6 +128,9 @@ class LiberviaCli:
"""
# commands which can be run without a backend connection
BRIDGELESS_COMMANDS = ("backup",)
def __init__(self):
"""
@ -830,33 +833,44 @@ class LiberviaCli:
if self.args.max is None:
self.args.max = C.NO_LIMIT
def _is_bridgeless_command(self, args):
"""Check if the command on command line can be run without a backend"""
argv = args if args is not None else sys.argv[1:]
return bool(argv) and argv[0] in self.BRIDGELESS_COMMANDS
async def main(self, args, namespace):
try:
self.bridge, self.bridge_settings = await dynamic_import.load_and_init_bridge(
self.settings, verbose=False
)
except exceptions.BridgeExceptionNoService as e:
self.disp(
_(
"Can't connect to Libervia backend, are you sure that it's "
"launched ?"
),
error=True,
)
self.quit(C.EXIT_BACKEND_NOT_FOUND, raise_exc=False)
if not self._is_bridgeless_command(args):
self.disp(
_(
"Can't connect to Libervia backend, are you sure that it's "
"launched ?"
),
error=True,
)
self.quit(C.EXIT_BACKEND_NOT_FOUND, raise_exc=False)
return
self.bridge = None
self.version = "unknown (no backend connection)"
except exceptions.BridgeInitError as e:
self.disp(_("Can't init bridge"), error=True)
self.quit(C.EXIT_BRIDGE_ERROR, raise_exc=False)
return
except Exception as e:
raise exceptions.InternalError(
"Something went wrong while initialising bridge."
) from e
self.loop.install_hooks(self.bridge_settings.type)
if self.bridge is not None:
self.loop.install_hooks(self.bridge_settings.type)
# we wait on init_pre_script instead of ready_get, so the CLI frontend can be used
# in init script.
await self.bridge.init_pre_script()
self.version = await self.bridge.version_get()
# we wait on init_pre_script instead of ready_get, so the CLI frontend can be
# used in init script.
await self.bridge.init_pre_script()
self.version = await self.bridge.version_get()
self._bridge_connected()
self.import_plugins()
try:
@ -905,7 +919,12 @@ class LiberviaCli:
loop = asyncio.get_running_loop()
stdin_fut = loop.create_future()
loop.add_reader(sys.stdin, self._read_stdin, stdin_fut)
return await stdin_fut
try:
return await stdin_fut
finally:
# remove the reader once the input is done, so the callback does not fire
# again on a resolved future (e.g. stdin reaching EOF on exit)
loop.remove_reader(sys.stdin)
async def confirm(self, message):
"""Request user to confirm action, return answer as boolean"""