start.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import os
  2. import sys
  3. import subprocess
  4. import socket
  5. from pathlib import Path, PurePosixPath
  6. from dataclasses import dataclass
  7. from typing import Any
  8. from enum import StrEnum
  9. is_windows = os.name == "nt"
  10. def read_data_sources(hostname: str, login: str) -> list[Path]:
  11. file = Path(f"./data_sources_{hostname}_{login}")
  12. with open(file) as f:
  13. paths = f.readlines()
  14. return [Path(p_str.strip()).expanduser() for p_str in paths]
  15. @dataclass
  16. class KeePass:
  17. path: Path
  18. bin: str | Path
  19. def read_entry_attribute(self, key, attribute):
  20. return self._exec(["show", "-a", attribute, self.path, key]).strip()
  21. def _exec(self, args: list[Any]):
  22. return subprocess.check_output([self.bin] + args, text=True)
  23. @classmethod
  24. def new(cls, path: Path):
  25. binary = Path("C:\\") / "Program Files" / "KeePassXC" / "keepassxc-cli.exe" if is_windows else "keepassxc-cli"
  26. return cls(path=path, bin=binary)
  27. class SecretType(StrEnum):
  28. File="file"
  29. KeepassAttribute="keepass-attribute"
  30. @dataclass
  31. class Secret:
  32. name: str
  33. mode: int
  34. type: SecretType
  35. host_path: Path | None = None
  36. key: str | None = None
  37. attribute: str | None = None
  38. def create(self, keepass: KeePass):
  39. match self.type:
  40. case SecretType.File:
  41. args = ["podman", "secret", "create", "--replace", self.name, self.host_path]
  42. print(args)
  43. subprocess.run(args)
  44. case SecretType.KeepassAttribute:
  45. value = keepass.read_entry_attribute(self.key, self.attribute)
  46. args = ["podman", "secret", "create", "--replace", self.name, "-"]
  47. print(args)
  48. subprocess.run(args, input=value.encode())
  49. @classmethod
  50. def from_line(cls, line: str):
  51. type_, *args = line.split(",")
  52. match (SecretType(type_), *args):
  53. case (SecretType.File, path):
  54. path = Path(path).expanduser()
  55. return cls(host_path=path, name=path.name, mode=0o0400, type=SecretType.File)
  56. case (SecretType.KeepassAttribute, key, attribute):
  57. return cls(name=key, key=key, mode=0o0400, type=SecretType.KeepassAttribute, attribute=attribute)
  58. @classmethod
  59. def read_sources(cls, hostname: str, login: str) -> list["Secret"]:
  60. file = Path(f"./secret_sources_{hostname}_{login}")
  61. with open(file) as f:
  62. lines = f.readlines()
  63. return [cls.from_line(l.strip()) for l in lines]
  64. def to_source_path(path: Path):
  65. mount_base = PurePosixPath("/mnt") / "source"
  66. inner_path = PurePosixPath(path)
  67. with_drive = PurePosixPath(inner_path.parts[0].replace(":", "")).joinpath(*inner_path.parts[1:])
  68. return mount_base / with_drive.relative_to(with_drive.anchor)
  69. def start_borgmatic_container(hostname: str, login: str, secret_sources: list[Secret]):
  70. data_sources = read_data_sources(hostname, login)
  71. container_name = f"borgmatic_{login}"
  72. ssh_auth_sock = os.getenv("SSH_AUTH_SOCK")
  73. data_path = Path.cwd() / "data"
  74. config_d_path = data_path / "borgmatic.d"
  75. config_path = data_path / "borgmatic"
  76. history_file = data_path / ".bash_history"
  77. history_file.touch()
  78. ssh_config_path = Path.home() / ".ssh"
  79. volumes = [
  80. f"{config_d_path}:/etc/borgmatic.d/",
  81. f"{config_path}:/etc/borgmatic/",
  82. f"{ssh_config_path}:/root/.ssh",
  83. f"{history_file}:/root/.bash_history",
  84. "borg_config:/root/.config/borg",
  85. "borg_cache:/root/.cache/borg",
  86. "borgmatic_state:/root/.local/state/borgmatic",
  87. ]
  88. if ssh_auth_sock:
  89. volumes += [f"{ssh_auth_sock}:{ssh_auth_sock}:Z"]
  90. volumes += [
  91. f"{vol}:{to_source_path(vol)}:ro" for vol in data_sources
  92. ]
  93. volume_args = [a for vol in volumes for a in ["-v", vol]]
  94. secrets_args = [a for s in secret_sources for a in ["--secret", f"{s.name},mode=0{s.mode:o}"]]
  95. image_name = "ghcr.io/borgmatic-collective/borgmatic"
  96. args = [
  97. "podman",
  98. "run",
  99. "-h",
  100. hostname,
  101. "--detach",
  102. "--name",
  103. container_name,
  104. "-e",
  105. "SSH_AUTH_SOCK",
  106. "-e",
  107. "TZ=Europe/Paris",
  108. "-e",
  109. "SSH_KEY_NAME",
  110. "-e",
  111. f"HOST_LOGIN={login}",
  112. "--security-opt=label=disable"
  113. ] + volume_args + secrets_args + [image_name]
  114. print(args)
  115. subprocess.run(args)
  116. def main():
  117. login = os.getlogin()
  118. hostname = socket.gethostname()
  119. secret_sources = Secret.read_sources(hostname, login)
  120. if not secret_sources:
  121. print("no secret required ?")
  122. try:
  123. if sys.argv[1] == "create_secrets":
  124. keepass_path = Path(sys.argv[2])
  125. keepass = KeePass.new(keepass_path)
  126. for s in secret_sources:
  127. s.create(keepass)
  128. elif sys.argv[1] == "start":
  129. start_borgmatic_container(hostname, login, secret_sources)
  130. elif sys.argv[1] == "rm":
  131. subprocess.run(["podman", "rm", "-f", f"borgmatic_{login}"])
  132. elif sys.argv[1] == "bash":
  133. subprocess.run(["podman", "exec", "-ti", f"borgmatic_{login}", "bash"])
  134. except IndexError:
  135. print("You should provide an argument")
  136. exit(1)
  137. if __name__ == "__main__":
  138. main()