container.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import os
  2. import subprocess
  3. from pathlib import Path, PurePosixPath
  4. from dataclasses import dataclass
  5. from pc_backup.secret import Secret
  6. from pc_backup.env import is_windows
  7. def read_data_sources(file: Path) -> list[Path]:
  8. with open(file) as f:
  9. paths = f.readlines()
  10. return [Path(p_str.strip()).expanduser() for p_str in paths]
  11. @dataclass
  12. class Configuration:
  13. secret_sources: list[Secret]
  14. data_sources: list[Path]
  15. borgmatic_d_path: Path
  16. borgmatic_path: Path
  17. history_file: Path
  18. ssh_auth_sock: Path | None
  19. @staticmethod
  20. def get_config_dir() -> Path:
  21. if is_windows():
  22. program_data = Path(os.getenv("ProgramData"))
  23. return program_data / "pc_backup"
  24. else:
  25. return Path.home() / ".config" / "pc_backup"
  26. @classmethod
  27. def read(cls, hostname: str, login: str, config_dir: Path):
  28. secret_sources_file = config_dir / f"secret_sources_{hostname}_{login}"
  29. data_sources_file = config_dir / f"data_sources_{hostname}_{login}"
  30. ssh_auth_sock = os.getenv("SSH_AUTH_SOCK")
  31. return cls(
  32. secret_sources=Secret.read_sources(secret_sources_file),
  33. data_sources=read_data_sources(data_sources_file),
  34. borgmatic_d_path=config_dir / "borgmatic.d",
  35. borgmatic_path=config_dir / "borgmatic",
  36. history_file=config_dir / f".bash_history_{login}",
  37. ssh_auth_sock=Path(ssh_auth_sock) if ssh_auth_sock else None,
  38. )
  39. @dataclass
  40. class BorgmaticContainer:
  41. hostname: str
  42. login: str
  43. name: str
  44. image: str = "ghcr.io/borgmatic-collective/borgmatic"
  45. def run(self, config: Configuration):
  46. container_name = f"borgmatic_{self.login}"
  47. config.history_file.touch()
  48. volumes = [
  49. f"{config.borgmatic_d_path}:/etc/borgmatic.d/",
  50. f"{config.borgmatic_path}:/etc/borgmatic/",
  51. f"{config.history_file}:/root/.bash_history",
  52. "borg_ssh_dir:/root/.ssh",
  53. "borg_config:/root/.config/borg",
  54. "borg_cache:/root/.cache/borg",
  55. "borgmatic_state:/root/.local/state/borgmatic",
  56. "borgmatic_log:/root/.local/share/borgmatic",
  57. ]
  58. if config.ssh_auth_sock:
  59. volumes += [f"{config.ssh_auth_sock}:{config.ssh_auth_sock}:Z"]
  60. volumes += [
  61. f"{vol}:{self.to_source_path(vol)}:ro" for vol in config.data_sources
  62. ]
  63. volume_args = [a for vol in volumes for a in ["-v", vol]]
  64. secrets_args = [
  65. a
  66. for s in config.secret_sources
  67. for a in ["--secret", f"{s.name},mode=0{s.mode:o}"]
  68. ]
  69. args = (
  70. [
  71. "podman",
  72. "run",
  73. "-h",
  74. self.hostname,
  75. "--detach",
  76. "--name",
  77. container_name,
  78. "-e",
  79. "SSH_AUTH_SOCK",
  80. "-e",
  81. "TZ=Europe/Paris",
  82. "-e",
  83. "SSH_KEY_NAME",
  84. "-e",
  85. f"HOST_LOGIN={self.login}",
  86. "--security-opt=label=disable",
  87. ]
  88. + volume_args
  89. + secrets_args
  90. + [self.image]
  91. )
  92. print(args)
  93. subprocess.run(args)
  94. def rm(self):
  95. subprocess.run(["podman", "rm", "-f", self.name])
  96. def exec(self, cmd: list[str], env_vars: list[str] = []):
  97. args = ["podman", "exec", "-ti"]
  98. args += [a for var in env_vars for a in ["-e", var]]
  99. subprocess.run(args + [self.name] + cmd)
  100. @staticmethod
  101. def to_source_path(path: Path):
  102. mount_base = PurePosixPath("/mnt") / "source"
  103. inner_path = PurePosixPath(path)
  104. with_drive = PurePosixPath(inner_path.parts[0].replace(":", "")).joinpath(
  105. *inner_path.parts[1:]
  106. )
  107. return mount_base / with_drive.relative_to(with_drive.anchor)
  108. @classmethod
  109. def new(cls, hostname: str, login: str):
  110. return cls(hostname, login, f"borgmatic_{login}")