Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dockerfiles/arch
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ RUN pacman -Sy --noconfirm \
btrfs-progs \
cryptsetup \
e2fsprogs \
git \
make \
python \
python-pipx \
Expand Down
2 changes: 1 addition & 1 deletion dockerfiles/debian
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ FROM debian:trixie-slim
# Provide excecutables
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update \
&& apt install -y btrfs-progs cryptsetup e2fsprogs make python3 pipx restic rsync sudo \
&& apt install -y btrfs-progs cryptsetup e2fsprogs git make python3 pipx restic rsync sudo \
&& apt autoclean

ENV PATH="/root/.local/bin:$PATH"
Expand Down
6 changes: 3 additions & 3 deletions dockerfiles/python3.11
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ FROM python:3.11

# Provide excecutables
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update && \
apt install -y btrfs-progs cryptsetup e2fsprogs make restic rsync sudo && \
apt autoclean
RUN apt update \
&& apt install -y btrfs-progs cryptsetup e2fsprogs git make python3 pipx restic rsync sudo \
&& apt autoclean

# Setup uv
ENV UV_VERSION=0.10.12
Expand Down
6 changes: 3 additions & 3 deletions dockerfiles/python3.12
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ FROM python:3.12

# Provide excecutables
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update && \
apt install -y btrfs-progs cryptsetup e2fsprogs make restic rsync sudo && \
apt autoclean
RUN apt update \
&& apt install -y btrfs-progs cryptsetup e2fsprogs git make python3 pipx restic rsync sudo \
&& apt autoclean

# Setup uv
ENV UV_VERSION=0.10.12
Expand Down
6 changes: 3 additions & 3 deletions dockerfiles/python3.13
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ FROM python:3.13

# Provide excecutables
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update && \
apt install -y btrfs-progs cryptsetup e2fsprogs make restic rsync sudo && \
apt autoclean
RUN apt update \
&& apt install -y btrfs-progs cryptsetup e2fsprogs git make python3 pipx restic rsync sudo \
&& apt autoclean

# Setup uv
ENV UV_VERSION=0.10.12
Expand Down
6 changes: 3 additions & 3 deletions dockerfiles/python3.14
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ FROM python:3.14

# Provide excecutables
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update && \
apt install -y btrfs-progs cryptsetup e2fsprogs make restic rsync sudo && \
apt autoclean
RUN apt update \
&& apt install -y btrfs-progs cryptsetup e2fsprogs git make python3 pipx restic rsync sudo \
&& apt autoclean

# Setup uv
ENV UV_VERSION=0.10.12
Expand Down
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ dependencies = [
"pydantic>=2.13.1",
"pyyaml>=6.0.3",
"shell-interface>=1.0.2",
"storage-device-managers>=1.0.2",
"storage-device-managers",
"typer>=0.24.1",
]

Expand Down Expand Up @@ -86,6 +86,10 @@ select = ["A", "B", "C", "F", "I", "ISC", "PIE", "PL", "Q", "RUF", "SIM", "TID",
ignore = ["E", "PLC1901", "SIM117"]
mccabe.max-complexity = 6

[tool.uv.sources.storage-device-managers]
git = "https://github.com/MaxG87/storage-device-managers.git"
rev = "bee3454bb8086313a2bd47a741c320d9ee9589b7"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
12 changes: 0 additions & 12 deletions src/butter_backup/backup_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ def do_backup(self, mount_dir: Path) -> None:
files_dest.mkdir(parents=True, exist_ok=True)
for src in self.config.Files:
self.rsync_file(src, files_dest)
self.sync_filesystem_changes(mount_dir)

@staticmethod
def get_source_snapshot(root: Path) -> Path:
Expand Down Expand Up @@ -108,11 +107,6 @@ def rsync_file(src: Path, dest: Path) -> None:
cmd: sh.StrPathList = ["sudo", "rsync", "-ax", "--inplace", src, dest]
sh.run_cmd(cmd=cmd)

@staticmethod
def sync_filesystem_changes(mount_dir: Path) -> None:
sync_cmd: sh.StrPathList = ["sudo", "btrfs", "filesystem", "sync", mount_dir]
sh.run_cmd(cmd=sync_cmd)

@staticmethod
def rsync_folder(
src: Path, dest: Path, maybe_exclude_patterns: Path | None
Expand Down Expand Up @@ -153,11 +147,6 @@ def adapt_ownership(backup_repository: Path) -> None:
)
sdm.chown(backup_repository, user, group, recursive=True)

@staticmethod
def sync_filesystem_changes(mount_dir: Path) -> None:
sync_cmd: sh.StrPathList = ["sudo", "sync", "-f", mount_dir]
sh.run_cmd(cmd=sync_cmd)

def copy_files(self, backup_repository: Path) -> None:
restic_cmd: sh.StrPathList = [
"sudo",
Expand All @@ -171,4 +160,3 @@ def copy_files(self, backup_repository: Path) -> None:
restic_cmd.extend(["--exclude-file", self.config.ExcludePatternsFile])
restic_cmd.extend(list(self.config.FilesAndFolders))
sh.pipe_pass_cmd_to_real_cmd(self.config.RepositoryPassCmd, restic_cmd)
self.sync_filesystem_changes(backup_repository)
28 changes: 15 additions & 13 deletions src/butter_backup/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,7 @@ def _open_device(cfg: cp.Configuration, base_dir: Path) -> None:
mount_dir.mkdir(exist_ok=True)
try:
decrypted = sdm.open_encrypted_device(cfg.device(), cfg.DevicePassCmd)
sdm.mount_btrfs_device(
decrypted, mount_dir=mount_dir, compression=cfg.compression()
)
sdm.mount_device(decrypted, mount_dir=mount_dir, compression=cfg.compression())
except:
typer.echo(
f"Speichermedium {cfg.Name} konnte nicht geöffnet werden. Es wird übersprungen."
Expand Down Expand Up @@ -171,17 +169,21 @@ def close(config: Path = CONFIG_OPTION, verbose: int = VERBOSITY_OPTION) -> None
configurations = cp.parse_configuration(config.read_text())
mounted_devices = sdm.get_mounted_devices()
for cfg in configurations:
mapped_device = str(cfg.map_name())
if cfg.device().exists() and mapped_device in mounted_devices:
mount_dirs = mounted_devices[mapped_device]
if len(mount_dirs) != 1:
# TODO introduce custom exception
raise ValueError(
"Got several possible mount points. Expected exactly 1!"
map_name = cfg.map_name()
map_name_as_str = str(map_name)
if cfg.device().exists() and map_name_as_str in mounted_devices:
mount_dirs = mounted_devices[map_name_as_str]
num_mount_dirs = len(mount_dirs)
if num_mount_dirs != 1:
logger.error(
"Got {num_mount_dirs} mount points for device {device}. Expected"
" exactly 1! Skipping device.",
num_mount_dirs=num_mount_dirs,
device=cfg.Name,
)
mount_dir = next(iter(mount_dirs))
sdm.unmount_device(mount_dir)
sdm.close_decrypted_device(Path(mapped_device))
continue
sdm.unmount_device(map_name)
sdm.close_decrypted_device(map_name)


@app.command()
Expand Down
19 changes: 2 additions & 17 deletions src/butter_backup/device_managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,6 @@
ValidFileSystems = t.Literal["ext4", "btrfs"]


def format_device(device: Path, file_system: ValidFileSystems) -> None:
match file_system:
case "ext4":
mkfs_ext4(device)
case "btrfs":
sdm.mkfs_btrfs(device)
case _:
t.assert_never(file_system)


def mkfs_ext4(device: Path) -> None:
mkfs_cmd: sh.StrPathList = ["sudo", "mkfs.ext4", "-F", device]
sh.run_cmd(cmd=mkfs_cmd)


def prepare_device_for_butterbackend(device: Path) -> cp.BtrFSRsyncConfig:
password_cmd = sdm.generate_passcmd()
backup_repository_folder = "ButterBackupRepository"
Expand All @@ -33,7 +18,7 @@ def prepare_device_for_butterbackend(device: Path) -> cp.BtrFSRsyncConfig:
user = sh.get_user()
group = sh.get_group(user)
with sdm.decrypted_device(device, password_cmd) as decrypted:
format_device(decrypted, "btrfs")
sdm.mkfs(decrypted, "btrfs")
with sdm.mounted_device(decrypted) as mounted:
backup_repository = mounted / backup_repository_folder
mkdir_cmd: sh.StrPathList = ["sudo", "mkdir", backup_repository]
Expand Down Expand Up @@ -75,7 +60,7 @@ def prepare_device_for_resticbackend(
user = sh.get_user()
group = sh.get_group(user)
with sdm.decrypted_device(device, device_passcmd) as decrypted:
format_device(decrypted, file_system)
sdm.mkfs(decrypted, file_system)
with sdm.mounted_device(decrypted) as mounted:
backup_repo = mounted / backup_repository_folder
mkdir_repo: sh.StrPathList = ["sudo", "mkdir", backup_repo]
Expand Down
8 changes: 2 additions & 6 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,12 +339,8 @@ def test_format_device_creates_expected_file_system(
assert len(config_lst) == 1
config = config_lst[0]
with sdm.decrypted_device(big_file, config.DevicePassCmd) as decrypted:
blkid_result = sh.run_cmd(
cmd=["sudo", "blkid", "-o", "value", "-s", "TYPE", str(decrypted)],
capture_output=True,
)
actual_fs = blkid_result.stdout.decode().strip()
assert actual_fs == file_system
result_fs = sdm.get_filesystem(decrypted)
assert result_fs == file_system


@pytest.mark.parametrize("backend", ["restic", "btrfs-rsync"])
Expand Down
23 changes: 5 additions & 18 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading