5 Incheckningar cf5967074b ... bdb087259b

Upphovsman SHA1 Meddelande Datum
  jherve bdb087259b Use check_output instead of run 1 månad sedan
  jherve 2bc445bee5 Make Podman interface more pythonesque 1 månad sedan
  jherve af721f6515 Create a podman module 1 månad sedan
  jherve c852f8b36d Environment variables are useless with a proper configuration 1 månad sedan
  jherve 4a71e1c72b Add commands to dump configuration 1 månad sedan
5 ändrade filer med 160 tillägg och 58 borttagningar
  1. 2 2
      README.md
  2. 31 8
      pc_backup/cli.py
  3. 32 39
      pc_backup/container.py
  4. 90 0
      pc_backup/podman.py
  5. 5 9
      pc_backup/secret.py

+ 2 - 2
README.md

@@ -5,5 +5,5 @@
 
 Note : This can also be done this way :
 
-1. `BORG_PASSPHRASE_NAME=<passphrase_name> STORAGE_BOX_USER=<user> SSH_KEY_NAME=<name> python3 start.py create_repo`
-1. `BORG_PASSPHRASE_NAME=<passphrase_name> STORAGE_BOX_USER=<user> SSH_KEY_NAME=<name> python3 start.py export_key`
+1. `my-backup create_repo`
+1. `my-backup export_key`

+ 31 - 8
pc_backup/cli.py

@@ -31,6 +31,8 @@ class CliArguments:
 
         for sub in [
             CommandStart,
+            CommandConfig,
+            CommandBorgmaticConfig,
             CommandRm,
             CommandExec,
             CommandBash,
@@ -69,6 +71,32 @@ class CommandStart(Command):
         container.run(config)
 
 
+class CommandConfig(Command):
+    command = "config"
+    help = "dump current config"
+
+    def run(
+        self,
+        *,
+        config: Configuration,
+        **kwargs,
+    ):
+        print(config)
+
+
+class CommandBorgmaticConfig(Command):
+    command = "config_borgmatic"
+    help = "dump current borgmatic config"
+
+    def run(
+        self,
+        *,
+        container: BorgmaticContainer,
+        **kwargs,
+    ):
+        container.exec(["borgmatic", "config", "validate", "-s"])
+
+
 class CommandRm(Command):
     command = "rm"
     help = "remove container"
@@ -80,10 +108,9 @@ class CommandRm(Command):
 class CommandExec(Command):
     command = "exec"
     help = "run command in container"
-    env_vars = ["BORG_PASSPHRASE_NAME", "STORAGE_BOX_USER", "SSH_KEY_NAME"]
 
     def run(self, *, container: BorgmaticContainer, **kwargs):
-        container.exec(self.extra, self.env_vars)
+        container.exec(self.extra)
 
 
 class CommandBash(Command):
@@ -97,21 +124,17 @@ class CommandBash(Command):
 class CommandCreateRepo(Command):
     command = "create_repo"
     help = "create repository"
-    env_vars = ["BORG_PASSPHRASE_NAME", "STORAGE_BOX_USER", "SSH_KEY_NAME"]
 
     def run(self, *, container: BorgmaticContainer, **kwargs):
-        container.exec(
-            ["borgmatic", "repo-create", "--encryption", "repokey"], self.env_vars
-        )
+        container.exec(["borgmatic", "repo-create", "--encryption", "repokey"])
 
 
 class CommandExportKey(Command):
     command = "export_key"
     help = "export the repository key"
-    env_vars = ["BORG_PASSPHRASE_NAME", "STORAGE_BOX_USER", "SSH_KEY_NAME"]
 
     def run(self, *, container: BorgmaticContainer, **kwargs):
-        container.exec(["borgmatic", "export", "key"], self.env_vars)
+        container.exec(["borgmatic", "export", "key"])
 
 
 class CommandCreateSecrets(Command):

+ 32 - 39
pc_backup/container.py

@@ -5,6 +5,7 @@ from dataclasses import dataclass
 
 from pc_backup.secret import Secret
 from pc_backup.env import is_windows
+from pc_backup.podman import Podman
 
 
 def read_data_sources(file: Path) -> list[Path]:
@@ -30,6 +31,26 @@ class Configuration:
         else:
             return Path.home() / ".config" / "pc_backup"
 
+    def __str__(self) -> str:
+        ret = []
+        ret += ["secret_sources:"]
+        for s in self.secret_sources:
+            ret += [f"  {s.name}: {s}"]
+
+        ret += ["data_sources:"]
+        for d in self.data_sources:
+            ret += [f"  {d}"]
+
+        ret += ["borgmatic setups:"]
+        for f in self.borgmatic_d_path.iterdir():
+            ret += [f"  {f.name}"]
+
+        ret += ["paths:"]
+        for p in ["borgmatic_d_path", "borgmatic_path", "history_file"]:
+            ret += [f"  {p}: {getattr(self, p)}"]
+
+        return "\n".join(ret)
+
     @classmethod
     def read(cls, hostname: str, login: str, config_dir: Path):
         # The configuration directory has to be organized like this :
@@ -98,54 +119,26 @@ class BorgmaticContainer:
             "borgmatic_state:/root/.local/state/borgmatic",
             "borgmatic_log:/root/.local/share/borgmatic",
         ]
-        if config.ssh_auth_sock:
-            volumes += [f"{config.ssh_auth_sock}:{config.ssh_auth_sock}:Z"]
 
         volumes += [
             f"{vol}:{self.to_source_path(vol)}:ro" for vol in config.data_sources
         ]
 
-        volume_args = [a for vol in volumes for a in ["-v", vol]]
-
-        secrets_args = [
-            a
-            for s in config.secret_sources
-            for a in ["--secret", f"{s.name},mode=0{s.mode:o}"]
-        ]
-
-        args = (
-            [
-                "podman",
-                "run",
-                "-h",
-                self.hostname,
-                "--detach",
-                "--name",
-                container_name,
-                "-e",
-                "SSH_AUTH_SOCK",
-                "-e",
-                "TZ=Europe/Paris",
-                "-e",
-                "SSH_KEY_NAME",
-                "-e",
-                f"HOST_LOGIN={self.login}",
-                "--security-opt=label=disable",
-            ]
-            + volume_args
-            + secrets_args
-            + [self.image]
+        Podman.run(
+            self.image,
+            container_name,
+            hostname=self.hostname,
+            env=["TZ=Europe/Paris", f"HOST_LOGIN={self.login}"],
+            ssh_auth_sock=config.ssh_auth_sock,
+            volumes=volumes,
+            secrets=config.secret_sources,
         )
-        print(" ".join(args))
-        subprocess.run(args)
 
     def rm(self):
-        subprocess.run(["podman", "rm", "-f", self.name])
+        Podman.rm(self.name)
 
-    def exec(self, cmd: list[str], env_vars: list[str] = []):
-        args = ["podman", "exec", "-ti"]
-        args += [a for var in env_vars for a in ["-e", var]]
-        subprocess.run(args + [self.name] + cmd)
+    def exec(self, cmd: list[str]):
+        Podman.exec(self.name, *cmd)
 
     @staticmethod
     def to_source_path(path: Path):

+ 90 - 0
pc_backup/podman.py

@@ -0,0 +1,90 @@
+import subprocess
+from pathlib import Path
+
+
+class Podman:
+    @classmethod
+    def run(
+        cls,
+        image: str,
+        name: str,
+        *,
+        hostname: str,
+        env: list,
+        volumes: list[str],
+        # TODO: Actually a list of Secret but creates a circular dependency
+        secrets: list,
+        ssh_auth_sock: Path | None = None,
+        detach=True,
+    ):
+        args = ["run"]
+
+        args += ["-h", hostname]
+        args += ["--name", name]
+
+        for e in env:
+            args += ["-e", e]
+
+        if ssh_auth_sock:
+            args += ["-e", "SSH_AUTH_SOCK"]
+            volumes += [f"{ssh_auth_sock}:{ssh_auth_sock}:Z"]
+
+        args += ["--security-opt=label=disable"]
+
+        if detach:
+            args += ["--detach"]
+
+        args += [a for vol in volumes for a in ["-v", vol]]
+        args += [a for s in secrets for a in ["--secret", f"{s.name},mode=0{s.mode:o}"]]
+
+        args += [image]
+
+        cls._call(args)
+
+    @classmethod
+    def rm(cls, name, *, force=True):
+        args = ["rm"]
+        if force:
+            args += ["-f"]
+        args += [name]
+        cls._call(args)
+
+    @classmethod
+    def exec(cls, name, *cmd):
+        args = ["exec", "-ti", name] + list(cmd)
+        cls._call(args)
+
+    @classmethod
+    def secret_create(
+        cls,
+        name: str,
+        *,
+        replace=True,
+        value: bytes | None = None,
+        host_path: Path | None = None,
+    ):
+        if value and host_path:
+            raise ValueError("both value and host_path can not be set")
+
+        args = ["secret", "create"]
+        kwargs = {}
+
+        if replace:
+            args += ["--replace"]
+
+        args += [name]
+
+        if value is not None:
+            args += ["-"]
+            kwargs["input"] = value
+        elif host_path is not None:
+            args += [host_path]
+
+        cls._call(args, **kwargs)
+
+    @classmethod
+    def _call(cls, args: list, **kwargs):
+        args = ["podman"] + args
+        print(f"Executing `{" ".join(args)}`")
+        out = subprocess.check_output(args, **kwargs)
+        print(out.decode())

+ 5 - 9
pc_backup/secret.py

@@ -3,6 +3,7 @@ from pathlib import Path
 from dataclasses import dataclass
 
 from pc_backup.keepass import KeePass
+from pc_backup.podman import Podman
 
 
 @dataclass
@@ -41,9 +42,7 @@ class SecretKeepassAttachment(Secret):
 
     def create(self, keepass: KeePass):
         value = keepass.read_entry_attachment(self.key, self.attachment)
-        args = ["podman", "secret", "create", "--replace", self.name, "-"]
-        print(args)
-        subprocess.run(args, input=value.encode())
+        Podman.secret_create(self.name, value=value.encode())
 
     @classmethod
     def from_line(cls, name: str, key: str, attachment: str):
@@ -57,9 +56,7 @@ class SecretKeepassAttribute(Secret):
 
     def create(self, keepass: KeePass):
         value = keepass.read_entry_attribute(self.key, self.attribute)
-        args = ["podman", "secret", "create", "--replace", self.name, "-"]
-        print(args)
-        subprocess.run(args, input=value.encode())
+        Podman.secret_create(self.name, value=value.encode())
 
     @classmethod
     def from_line(cls, name: str, key: str, attribute: str):
@@ -71,9 +68,8 @@ class SecretFile(Secret):
     host_path: Path
 
     def create(self, keepass: KeePass):
-        args = ["podman", "secret", "create", "--replace", self.name, self.host_path]
-        print(args)
-        subprocess.run(args)
+        args = ["--replace", self.name, self.host_path]
+        Podman.secret_create(self.name, host_path=self.host_path)
 
     @classmethod
     def from_line(cls, name: str, path: str):