style: apply ruff format across the tree; refresh moved-module doc refs
Bring 14 files that predated the ruff-format run into line with the configured formatter (line-length 100); provably formatting-only (ruff format of HEAD == working tree, and ruff format is semantics-preserving). Also refresh two lim.image.tor._KEYGEN_SCRIPT doc references in tor_harness.py to lim.image.initramfs.keygen after the module moved. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
18
lim/cli.py
18
lim/cli.py
@@ -59,14 +59,12 @@ COMMANDS: dict[str, Command] = {
|
|||||||
),
|
),
|
||||||
"mount": Command(
|
"mount": Command(
|
||||||
single_drive.mount,
|
single_drive.mount,
|
||||||
"Mount Encrypted Storage:\n"
|
"Mount Encrypted Storage:\n - Unlocks a LUKS partition and mounts it.",
|
||||||
" - Unlocks a LUKS partition and mounts it.",
|
|
||||||
needs_root=True,
|
needs_root=True,
|
||||||
),
|
),
|
||||||
"umount": Command(
|
"umount": Command(
|
||||||
single_drive.umount,
|
single_drive.umount,
|
||||||
"Unmount Encrypted Storage:\n"
|
"Unmount Encrypted Storage:\n - Unmounts a LUKS partition and closes the mapper.",
|
||||||
" - Unmounts a LUKS partition and closes the mapper.",
|
|
||||||
needs_root=True,
|
needs_root=True,
|
||||||
),
|
),
|
||||||
"single-boot": Command(
|
"single-boot": Command(
|
||||||
@@ -83,26 +81,22 @@ COMMANDS: dict[str, Command] = {
|
|||||||
),
|
),
|
||||||
"unlock": Command(
|
"unlock": Command(
|
||||||
crypt.unlock,
|
crypt.unlock,
|
||||||
"Unlock Data:\n"
|
"Unlock Data:\n - Decrypts the encfs data store into the decrypted folder.",
|
||||||
" - Decrypts the encfs data store into the decrypted folder.",
|
|
||||||
needs_root=False,
|
needs_root=False,
|
||||||
),
|
),
|
||||||
"lock": Command(
|
"lock": Command(
|
||||||
crypt.lock,
|
crypt.lock,
|
||||||
"Lock Data:\n"
|
"Lock Data:\n - Unmounts the decrypted encfs folder.",
|
||||||
" - Unmounts the decrypted encfs folder.",
|
|
||||||
needs_root=False,
|
needs_root=False,
|
||||||
),
|
),
|
||||||
"import": Command(
|
"import": Command(
|
||||||
sync.import_from_system,
|
sync.import_from_system,
|
||||||
"Import Data:\n"
|
"Import Data:\n - Copies personal data from the system into the encrypted store.",
|
||||||
" - Copies personal data from the system into the encrypted store.",
|
|
||||||
needs_root=False,
|
needs_root=False,
|
||||||
),
|
),
|
||||||
"export": Command(
|
"export": Command(
|
||||||
sync.export_to_system,
|
sync.export_to_system,
|
||||||
"Export Data:\n"
|
"Export Data:\n - Copies personal data from the encrypted store back to the system.",
|
||||||
" - Copies personal data from the encrypted store back to the system.",
|
|
||||||
needs_root=False,
|
needs_root=False,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,31 +90,22 @@ def execute_sync_plan(operations: list[SyncOperation]) -> None:
|
|||||||
if not operation.is_directory and Path(operation.destination).is_file():
|
if not operation.is_directory and Path(operation.destination).is_file():
|
||||||
ui.info("The destination file already exists!")
|
ui.info("The destination file already exists!")
|
||||||
ui.info("Difference:")
|
ui.info("Difference:")
|
||||||
runner.run(
|
runner.run(["diff", operation.destination, operation.source], check=False)
|
||||||
["diff", operation.destination, operation.source], check=False
|
|
||||||
)
|
|
||||||
runner.run(rsync_command(operation))
|
runner.run(rsync_command(operation))
|
||||||
|
|
||||||
|
|
||||||
def ensure_unlocked() -> None:
|
def ensure_unlocked() -> None:
|
||||||
mounts = runner.output(["mount"], check=False)
|
mounts = runner.output(["mount"], check=False)
|
||||||
if str(config.DECRYPTED_PATH) not in mounts:
|
if str(config.DECRYPTED_PATH) not in mounts:
|
||||||
ui.info(
|
ui.info(f"The decrypted folder {config.DECRYPTED_PATH} is locked. You need to unlock it!")
|
||||||
f"The decrypted folder {config.DECRYPTED_PATH} is locked. "
|
|
||||||
"You need to unlock it!"
|
|
||||||
)
|
|
||||||
crypt.unlock()
|
crypt.unlock()
|
||||||
|
|
||||||
|
|
||||||
def import_from_system(mode: str = "import") -> None:
|
def import_from_system(mode: str = "import") -> None:
|
||||||
ensure_unlocked()
|
ensure_unlocked()
|
||||||
backup_folder = (
|
backup_folder = config.BACKUP_PATH / mode / time.strftime("%Y%m%d%H%M%S")
|
||||||
config.BACKUP_PATH / mode / time.strftime("%Y%m%d%H%M%S")
|
|
||||||
)
|
|
||||||
backup_folder.mkdir(parents=True, exist_ok=True)
|
backup_folder.mkdir(parents=True, exist_ok=True)
|
||||||
operations = build_sync_plan(
|
operations = build_sync_plan(mode, system.real_home(), config.DATA_PATH, backup_folder)
|
||||||
mode, system.real_home(), config.DATA_PATH, backup_folder
|
|
||||||
)
|
|
||||||
execute_sync_plan(operations)
|
execute_sync_plan(operations)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -119,9 +119,7 @@ def overwrite_device(device: Device) -> None:
|
|||||||
|
|
||||||
def blkid_value(path: str, tag: str) -> str:
|
def blkid_value(path: str, tag: str) -> str:
|
||||||
"""Return a blkid tag value ("TYPE", "UUID", ...) or "" when unavailable."""
|
"""Return a blkid tag value ("TYPE", "UUID", ...) or "" when unavailable."""
|
||||||
return runner.output(
|
return runner.output(["blkid", path, "-s", tag, "-o", "value"], sudo=True, check=False)
|
||||||
["blkid", path, "-s", tag, "-o", "value"], sudo=True, check=False
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def is_mounted(path_fragment: str) -> bool:
|
def is_mounted(path_fragment: str) -> bool:
|
||||||
|
|||||||
@@ -44,9 +44,7 @@ def choose_manjaro(plan: ImagePlan) -> None:
|
|||||||
def choose_moode(plan: ImagePlan) -> None:
|
def choose_moode(plan: ImagePlan) -> None:
|
||||||
plan.boot_size = "+200M"
|
plan.boot_size = "+200M"
|
||||||
plan.image_checksum = "185cbc9a4994534bb7a4bc2744c78197"
|
plan.image_checksum = "185cbc9a4994534bb7a4bc2744c78197"
|
||||||
plan.base_download_url = (
|
plan.base_download_url = "https://github.com/moode-player/moode/releases/download/r651prod/"
|
||||||
"https://github.com/moode-player/moode/releases/download/r651prod/"
|
|
||||||
)
|
|
||||||
plan.image_name = "moode-r651-iso.zip"
|
plan.image_name = "moode-r651-iso.zip"
|
||||||
|
|
||||||
|
|
||||||
@@ -57,9 +55,7 @@ def choose_retropie(plan: ImagePlan) -> None:
|
|||||||
if entry is None:
|
if entry is None:
|
||||||
raise LimError(f"Version {version} isn't supported.")
|
raise LimError(f"Version {version} isn't supported.")
|
||||||
plan.raspberry_pi_version = version
|
plan.raspberry_pi_version = version
|
||||||
plan.base_download_url = (
|
plan.base_download_url = "https://github.com/RetroPie/RetroPie-Setup/releases/download/4.8/"
|
||||||
"https://github.com/RetroPie/RetroPie-Setup/releases/download/4.8/"
|
|
||||||
)
|
|
||||||
plan.image_checksum = entry["checksum"]
|
plan.image_checksum = entry["checksum"]
|
||||||
plan.image_name = entry["image"]
|
plan.image_name = entry["image"]
|
||||||
|
|
||||||
@@ -67,9 +63,7 @@ def choose_retropie(plan: ImagePlan) -> None:
|
|||||||
def choose_torbox(plan: ImagePlan) -> None:
|
def choose_torbox(plan: ImagePlan) -> None:
|
||||||
plan.base_download_url = "https://www.torbox.ch/data/"
|
plan.base_download_url = "https://www.torbox.ch/data/"
|
||||||
plan.image_name = "torbox-20220102-v050.gz"
|
plan.image_name = "torbox-20220102-v050.gz"
|
||||||
plan.image_checksum = (
|
plan.image_checksum = "0E1BA7FFD14AAAE5F0462C8293D95B62C3BF1D9E726E26977BD04772C55680D3"
|
||||||
"0E1BA7FFD14AAAE5F0462C8293D95B62C3BF1D9E726E26977BD04772C55680D3"
|
|
||||||
)
|
|
||||||
plan.boot_size = "+200M"
|
plan.boot_size = "+200M"
|
||||||
|
|
||||||
|
|
||||||
@@ -78,9 +72,7 @@ def choose_android_x86(plan: ImagePlan) -> None:
|
|||||||
"https://www.fosshub.com/Android-x86.html?dwl=android-x86_64-9.0-r2.iso"
|
"https://www.fosshub.com/Android-x86.html?dwl=android-x86_64-9.0-r2.iso"
|
||||||
)
|
)
|
||||||
plan.image_name = "android-x86_64-9.0-r2.iso"
|
plan.image_name = "android-x86_64-9.0-r2.iso"
|
||||||
plan.image_checksum = (
|
plan.image_checksum = "f7eb8fc56f29ad5432335dc054183acf086c539f3990f0b6e9ff58bd6df4604e"
|
||||||
"f7eb8fc56f29ad5432335dc054183acf086c539f3990f0b6e9ff58bd6df4604e"
|
|
||||||
)
|
|
||||||
plan.boot_size = "+500M"
|
plan.boot_size = "+500M"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -36,8 +36,7 @@ def _select_unmounted_device() -> device_module.Device:
|
|||||||
device = device_module.select_device()
|
device = device_module.select_device()
|
||||||
if device_module.is_mounted(device.path):
|
if device_module.is_mounted(device.path):
|
||||||
raise LimError(
|
raise LimError(
|
||||||
f'Device {device.path} is allready mounted. '
|
f'Device {device.path} is allready mounted. Umount with "umount {device.path}*".'
|
||||||
f'Umount with "umount {device.path}*".'
|
|
||||||
)
|
)
|
||||||
return device
|
return device
|
||||||
|
|
||||||
@@ -83,9 +82,7 @@ def run_setup() -> None:
|
|||||||
session.make_working_folder()
|
session.make_working_folder()
|
||||||
session.make_mount_folders()
|
session.make_mount_folders()
|
||||||
|
|
||||||
plan.root_filesystem = ui.ask(
|
plan.root_filesystem = ui.ask("Which filesystem should be used? E.g.:btrfs,ext4... (none):")
|
||||||
"Which filesystem should be used? E.g.:btrfs,ext4... (none):"
|
|
||||||
)
|
|
||||||
|
|
||||||
if ui.confirm(f"Should the image be transfered to {device.path}?"):
|
if ui.confirm(f"Should the image be transfered to {device.path}?"):
|
||||||
transfer.transfer_image(plan, session)
|
transfer.transfer_image(plan, session)
|
||||||
|
|||||||
@@ -15,10 +15,7 @@ def download_image(plan: ImagePlan) -> None:
|
|||||||
ui.info(f"Removing image {plan.image_path}.")
|
ui.info(f"Removing image {plan.image_path}.")
|
||||||
plan.image_path.unlink()
|
plan.image_path.unlink()
|
||||||
else:
|
else:
|
||||||
ui.info(
|
ui.info(f"Forcing download wasn't neccessary. File {plan.image_path} doesn't exist.")
|
||||||
"Forcing download wasn't neccessary. "
|
|
||||||
f"File {plan.image_path} doesn't exist."
|
|
||||||
)
|
|
||||||
|
|
||||||
ui.info("Start Download procedure...")
|
ui.info("Start Download procedure...")
|
||||||
if plan.image_path.is_file():
|
if plan.image_path.is_file():
|
||||||
@@ -35,13 +32,13 @@ def download_image(plan: ImagePlan) -> None:
|
|||||||
def arch_partition_input(boot_size: str) -> str:
|
def arch_partition_input(boot_size: str) -> str:
|
||||||
"""Fdisk answers: FAT32 boot partition of boot_size plus root partition."""
|
"""Fdisk answers: FAT32 boot partition of boot_size plus root partition."""
|
||||||
return (
|
return (
|
||||||
"o\n" # clear out any partitions on the drive
|
"o\n" # clear out any partitions on the drive
|
||||||
"p\n" # list partitions (should be empty)
|
"p\n" # list partitions (should be empty)
|
||||||
"n\np\n1\n\n" # new primary partition 1, default start sector
|
"n\np\n1\n\n" # new primary partition 1, default start sector
|
||||||
f"{boot_size}\n"
|
f"{boot_size}\n"
|
||||||
"t\nc\n" # set partition 1 to type W95 FAT32 (LBA)
|
"t\nc\n" # set partition 1 to type W95 FAT32 (LBA)
|
||||||
"n\np\n2\n\n\n" # new primary partition 2 over the remaining space
|
"n\np\n2\n\n\n" # new primary partition 2 over the remaining space
|
||||||
"w\n" # write partition table
|
"w\n" # write partition table
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -61,9 +58,18 @@ def decompress_command(image_path: Path) -> list[str]:
|
|||||||
|
|
||||||
def _luks_format_root(plan: ImagePlan, session: ImageSession) -> None:
|
def _luks_format_root(plan: ImagePlan, session: ImageSession) -> None:
|
||||||
luks_format = [
|
luks_format = [
|
||||||
"cryptsetup", "-v", "luksFormat",
|
"cryptsetup",
|
||||||
"-c", "aes-xts-plain64", "-s", "512", "-h", "sha512",
|
"-v",
|
||||||
"--use-random", "-i", "1000",
|
"luksFormat",
|
||||||
|
"-c",
|
||||||
|
"aes-xts-plain64",
|
||||||
|
"-s",
|
||||||
|
"512",
|
||||||
|
"-h",
|
||||||
|
"sha512",
|
||||||
|
"--use-random",
|
||||||
|
"-i",
|
||||||
|
"1000",
|
||||||
]
|
]
|
||||||
if plan.luks_memory_cost:
|
if plan.luks_memory_cost:
|
||||||
ui.info(
|
ui.info(
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ def resolve_checksum(download_url: str) -> str | None:
|
|||||||
for extension in ("sha1", "sha512", "md5"):
|
for extension in ("sha1", "sha512", "md5"):
|
||||||
checksum_url = f"{download_url}.{extension}"
|
checksum_url = f"{download_url}.{extension}"
|
||||||
ui.info(
|
ui.info(
|
||||||
"Image Checksum is not defined. "
|
f"Image Checksum is not defined. Try to download image signature from {checksum_url}."
|
||||||
f"Try to download image signature from {checksum_url}."
|
|
||||||
)
|
)
|
||||||
if url_exists(checksum_url):
|
if url_exists(checksum_url):
|
||||||
content = runner.output(["wget", checksum_url, "-q", "-O", "-"])
|
content = runner.output(["wget", checksum_url, "-q", "-O", "-"])
|
||||||
@@ -62,8 +61,7 @@ def verify_signature(download_url: str, image_path: str | Path, image_folder: Pa
|
|||||||
"""Best-effort GPG signature verification of the downloaded image."""
|
"""Best-effort GPG signature verification of the downloaded image."""
|
||||||
ui.info("Note: Checksums verify integrity but do not confirm authenticity.")
|
ui.info("Note: Checksums verify integrity but do not confirm authenticity.")
|
||||||
ui.info(
|
ui.info(
|
||||||
"Proceeding to signature verification, "
|
"Proceeding to signature verification, which ensures the file comes from a trusted source."
|
||||||
"which ensures the file comes from a trusted source."
|
|
||||||
)
|
)
|
||||||
signature_url = f"{download_url}.sig"
|
signature_url = f"{download_url}.sig"
|
||||||
ui.info(f"Attempting to download the image signature from: {signature_url}")
|
ui.info(f"Attempting to download the image signature from: {signature_url}")
|
||||||
|
|||||||
12
lim/luks.py
12
lim/luks.py
@@ -40,14 +40,10 @@ def create_luks_key_and_update_crypttab(
|
|||||||
runner.sync_disks()
|
runner.sync_disks()
|
||||||
|
|
||||||
ui.info("Opening and closing device to verify that everything works fine...")
|
ui.info("Opening and closing device to verify that everything works fine...")
|
||||||
closed = runner.run(
|
closed = runner.run(["cryptsetup", "-v", "luksClose", mapper_name], sudo=True, check=False)
|
||||||
["cryptsetup", "-v", "luksClose", mapper_name], sudo=True, check=False
|
|
||||||
)
|
|
||||||
if closed.returncode != 0:
|
if closed.returncode != 0:
|
||||||
ui.info(f"No need to luksClose {mapper_name}. Device isn't open.")
|
ui.info(f"No need to luksClose {mapper_name}. Device isn't open.")
|
||||||
runner.run(
|
runner.run(["cryptsetup", "luksAddKey", partition_path, str(secret_key_path)], sudo=True)
|
||||||
["cryptsetup", "luksAddKey", partition_path, str(secret_key_path)], sudo=True
|
|
||||||
)
|
|
||||||
runner.run(
|
runner.run(
|
||||||
[
|
[
|
||||||
"cryptsetup",
|
"cryptsetup",
|
||||||
@@ -70,9 +66,7 @@ def create_luks_key_and_update_crypttab(
|
|||||||
print(crypttab_path.read_text())
|
print(crypttab_path.read_text())
|
||||||
|
|
||||||
|
|
||||||
def update_fstab(
|
def update_fstab(mapper_path: str, mount_path: str, *, fstab_path: Path = FSTAB_PATH) -> None:
|
||||||
mapper_path: str, mount_path: str, *, fstab_path: Path = FSTAB_PATH
|
|
||||||
) -> None:
|
|
||||||
entry = f"{mapper_path} {mount_path} btrfs defaults 0 2"
|
entry = f"{mapper_path} {mount_path} btrfs defaults 0 2"
|
||||||
ui.info("Adding fstab entry...")
|
ui.info("Adding fstab entry...")
|
||||||
fsutil.ensure_line_in_file(entry, fstab_path)
|
fsutil.ensure_line_in_file(entry, fstab_path)
|
||||||
|
|||||||
@@ -50,8 +50,7 @@ def run(
|
|||||||
return subprocess.CompletedProcess(cmd, COMMAND_NOT_FOUND)
|
return subprocess.CompletedProcess(cmd, COMMAND_NOT_FOUND)
|
||||||
if check and result.returncode != 0:
|
if check and result.returncode != 0:
|
||||||
raise LimError(
|
raise LimError(
|
||||||
error_msg
|
error_msg or f"Command failed with code {result.returncode}: {shlex.join(cmd)}"
|
||||||
or f"Command failed with code {result.returncode}: {shlex.join(cmd)}"
|
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -98,8 +97,7 @@ def pipeline(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Run commands as a shell-style pipeline (cmd1 | cmd2 | ...)."""
|
"""Run commands as a shell-style pipeline (cmd1 | cmd2 | ...)."""
|
||||||
prepared = [
|
prepared = [
|
||||||
_with_sudo(cmd, sudo=sudo_last and index == len(cmds) - 1)
|
_with_sudo(cmd, sudo=sudo_last and index == len(cmds) - 1) for index, cmd in enumerate(cmds)
|
||||||
for index, cmd in enumerate(cmds)
|
|
||||||
]
|
]
|
||||||
pretty = " | ".join(shlex.join(cmd) for cmd in prepared)
|
pretty = " | ".join(shlex.join(cmd) for cmd in prepared)
|
||||||
ui.info(f"Running: {pretty}")
|
ui.info(f"Running: {pretty}")
|
||||||
|
|||||||
@@ -25,9 +25,7 @@ def setup() -> None:
|
|||||||
runner.run(["cryptsetup", "luksFormat", second.device.path], sudo=True)
|
runner.run(["cryptsetup", "luksFormat", second.device.path], sudo=True)
|
||||||
|
|
||||||
runner.run(["cryptsetup", "luksOpen", first.device.path, first.mapper_name], sudo=True)
|
runner.run(["cryptsetup", "luksOpen", first.device.path, first.mapper_name], sudo=True)
|
||||||
runner.run(
|
runner.run(["cryptsetup", "luksOpen", second.device.path, second.mapper_name], sudo=True)
|
||||||
["cryptsetup", "luksOpen", second.device.path, second.mapper_name], sudo=True
|
|
||||||
)
|
|
||||||
runner.run(["cryptsetup", "status", first.mapper_path], sudo=True)
|
runner.run(["cryptsetup", "status", first.mapper_path], sudo=True)
|
||||||
runner.run(["cryptsetup", "status", second.mapper_path], sudo=True)
|
runner.run(["cryptsetup", "status", second.mapper_path], sudo=True)
|
||||||
|
|
||||||
|
|||||||
@@ -32,9 +32,7 @@ def setup() -> None:
|
|||||||
runner.run(["cryptsetup", "-v", "-y", "luksFormat", target.partition_path], sudo=True)
|
runner.run(["cryptsetup", "-v", "-y", "luksFormat", target.partition_path], sudo=True)
|
||||||
|
|
||||||
ui.info("Unlock partition...")
|
ui.info("Unlock partition...")
|
||||||
runner.run(
|
runner.run(["cryptsetup", "luksOpen", target.partition_path, target.mapper_name], sudo=True)
|
||||||
["cryptsetup", "luksOpen", target.partition_path, target.mapper_name], sudo=True
|
|
||||||
)
|
|
||||||
|
|
||||||
ui.info("Create btrfs file system...")
|
ui.info("Create btrfs file system...")
|
||||||
runner.run(["mkfs.btrfs", target.mapper_path], sudo=True)
|
runner.run(["mkfs.btrfs", target.mapper_path], sudo=True)
|
||||||
@@ -57,9 +55,7 @@ def mount() -> None:
|
|||||||
target = select_storage_target()
|
target = select_storage_target()
|
||||||
|
|
||||||
ui.info("Unlock partition...")
|
ui.info("Unlock partition...")
|
||||||
runner.run(
|
runner.run(["cryptsetup", "luksOpen", target.partition_path, target.mapper_name], sudo=True)
|
||||||
["cryptsetup", "luksOpen", target.partition_path, target.mapper_name], sudo=True
|
|
||||||
)
|
|
||||||
|
|
||||||
ui.info("Mount partition...")
|
ui.info("Mount partition...")
|
||||||
runner.run(["mount", target.mapper_path, target.mount_path], sudo=True)
|
runner.run(["mount", target.mapper_path, target.mount_path], sudo=True)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Matches lim.image.tor._KEYGEN_SCRIPT: an empty -f torrc keeps the host's
|
# Matches lim.image.initramfs.keygen._KEYGEN_SCRIPT: an empty -f torrc keeps the host's
|
||||||
# /etc/tor/torrc out of the run, --DisableNetwork 1 makes the keys appear
|
# /etc/tor/torrc out of the run, --DisableNetwork 1 makes the keys appear
|
||||||
# without ever touching the network.
|
# without ever touching the network.
|
||||||
KEYGEN_TIMEOUT = 30
|
KEYGEN_TIMEOUT = 30
|
||||||
@@ -30,7 +30,7 @@ def free_port() -> int:
|
|||||||
def generate_onion_keys(onion_dir: Path, data_dir: Path) -> str:
|
def generate_onion_keys(onion_dir: Path, data_dir: Path) -> str:
|
||||||
"""Create v3 onion keys offline; return the .onion hostname.
|
"""Create v3 onion keys offline; return the .onion hostname.
|
||||||
|
|
||||||
Mirrors lim.image.tor._KEYGEN_SCRIPT, run directly on the host instead
|
Mirrors lim.image.initramfs.keygen._KEYGEN_SCRIPT, run directly on the host instead
|
||||||
of inside the image chroot (the tor invocation is identical).
|
of inside the image chroot (the tor invocation is identical).
|
||||||
"""
|
"""
|
||||||
onion_dir.mkdir(parents=True, exist_ok=True)
|
onion_dir.mkdir(parents=True, exist_ok=True)
|
||||||
@@ -39,11 +39,23 @@ def generate_onion_keys(onion_dir: Path, data_dir: Path) -> str:
|
|||||||
empty_torrc.write_text("")
|
empty_torrc.write_text("")
|
||||||
process = subprocess.Popen(
|
process = subprocess.Popen(
|
||||||
[
|
[
|
||||||
"tor", "-f", str(empty_torrc), "--DisableNetwork", "1",
|
"tor",
|
||||||
"--DataDirectory", str(data_dir),
|
"-f",
|
||||||
"--HiddenServiceDir", str(onion_dir),
|
str(empty_torrc),
|
||||||
"--HiddenServicePort", "22 127.0.0.1:22",
|
"--DisableNetwork",
|
||||||
"--SocksPort", "0", "--RunAsDaemon", "0", "--Log", "notice stderr",
|
"1",
|
||||||
|
"--DataDirectory",
|
||||||
|
str(data_dir),
|
||||||
|
"--HiddenServiceDir",
|
||||||
|
str(onion_dir),
|
||||||
|
"--HiddenServicePort",
|
||||||
|
"22 127.0.0.1:22",
|
||||||
|
"--SocksPort",
|
||||||
|
"0",
|
||||||
|
"--RunAsDaemon",
|
||||||
|
"0",
|
||||||
|
"--Log",
|
||||||
|
"notice stderr",
|
||||||
],
|
],
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
@@ -62,9 +74,7 @@ def generate_onion_keys(onion_dir: Path, data_dir: Path) -> str:
|
|||||||
return hostname.read_text().strip()
|
return hostname.read_text().strip()
|
||||||
|
|
||||||
|
|
||||||
def start_onion_service(
|
def start_onion_service(torrc_path: Path, socks_port: int, log_path: Path) -> subprocess.Popen:
|
||||||
torrc_path: Path, socks_port: int, log_path: Path
|
|
||||||
) -> subprocess.Popen:
|
|
||||||
"""Start Tor with the given torrc (service side) plus a SOCKS port.
|
"""Start Tor with the given torrc (service side) plus a SOCKS port.
|
||||||
|
|
||||||
The torrc carries the HiddenServiceDir/HiddenServicePort just like the
|
The torrc carries the HiddenServiceDir/HiddenServicePort just like the
|
||||||
@@ -73,9 +83,13 @@ def start_onion_service(
|
|||||||
"""
|
"""
|
||||||
return subprocess.Popen(
|
return subprocess.Popen(
|
||||||
[
|
[
|
||||||
"tor", "-f", str(torrc_path),
|
"tor",
|
||||||
"--SocksPort", str(socks_port),
|
"-f",
|
||||||
"--Log", f"notice file {log_path}",
|
str(torrc_path),
|
||||||
|
"--SocksPort",
|
||||||
|
str(socks_port),
|
||||||
|
"--Log",
|
||||||
|
f"notice file {log_path}",
|
||||||
],
|
],
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
@@ -98,11 +112,7 @@ def _socks5_handshake(sock: socket.socket, onion_host: str, onion_port: int) ->
|
|||||||
if sock.recv(2) != b"\x05\x00":
|
if sock.recv(2) != b"\x05\x00":
|
||||||
raise RuntimeError("SOCKS5 handshake rejected.")
|
raise RuntimeError("SOCKS5 handshake rejected.")
|
||||||
host = onion_host.encode()
|
host = onion_host.encode()
|
||||||
request = (
|
request = b"\x05\x01\x00\x03" + bytes([len(host)]) + host + struct.pack(">H", onion_port)
|
||||||
b"\x05\x01\x00\x03"
|
|
||||||
+ bytes([len(host)]) + host
|
|
||||||
+ struct.pack(">H", onion_port)
|
|
||||||
)
|
|
||||||
sock.settimeout(ONION_CONNECT_TIMEOUT)
|
sock.settimeout(ONION_CONNECT_TIMEOUT)
|
||||||
sock.sendall(request)
|
sock.sendall(request)
|
||||||
header = sock.recv(4)
|
header = sock.recv(4)
|
||||||
|
|||||||
@@ -24,9 +24,7 @@ def test_every_command_dispatches_to_its_function(monkeypatch):
|
|||||||
def test_needs_root_command_requests_root(monkeypatch):
|
def test_needs_root_command_requests_root(monkeypatch):
|
||||||
escalated = []
|
escalated = []
|
||||||
monkeypatch.setattr(system, "ensure_root", lambda: escalated.append(True))
|
monkeypatch.setattr(system, "ensure_root", lambda: escalated.append(True))
|
||||||
monkeypatch.setitem(
|
monkeypatch.setitem(cli.COMMANDS, "backup", Command(lambda: None, "d", needs_root=True))
|
||||||
cli.COMMANDS, "backup", Command(lambda: None, "d", needs_root=True)
|
|
||||||
)
|
|
||||||
cli.main(["--type", "backup", "--auto-confirm"])
|
cli.main(["--type", "backup", "--auto-confirm"])
|
||||||
assert escalated == [True]
|
assert escalated == [True]
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,7 @@ def test_storage_target_derives_all_paths():
|
|||||||
assert target.partition_path == "/dev/sdb1"
|
assert target.partition_path == "/dev/sdb1"
|
||||||
|
|
||||||
|
|
||||||
def test_single_drive_setup_command_sequence(
|
def test_single_drive_setup_command_sequence(fake_runner, answers, block_devices, monkeypatch):
|
||||||
fake_runner, answers, block_devices, monkeypatch
|
|
||||||
):
|
|
||||||
monkeypatch.setattr("lim.system.real_user", lambda: "kevin")
|
monkeypatch.setattr("lim.system.real_user", lambda: "kevin")
|
||||||
answers("sdb", "N") # device name, skip overwrite
|
answers("sdb", "N") # device name, skip overwrite
|
||||||
|
|
||||||
|
|||||||
@@ -58,10 +58,19 @@ def test_rsync_command_uses_delete_only_for_directories():
|
|||||||
directory_op = sync.SyncOperation("src/", "dst/", "backup", is_directory=True)
|
directory_op = sync.SyncOperation("src/", "dst/", "backup", is_directory=True)
|
||||||
file_op = sync.SyncOperation("src", "dst", "backup", is_directory=False)
|
file_op = sync.SyncOperation("src", "dst", "backup", is_directory=False)
|
||||||
assert sync.rsync_command(directory_op) == [
|
assert sync.rsync_command(directory_op) == [
|
||||||
"rsync", "-abcEPuvW", "--delete", "--backup-dir=backup", "src/", "dst/"
|
"rsync",
|
||||||
|
"-abcEPuvW",
|
||||||
|
"--delete",
|
||||||
|
"--backup-dir=backup",
|
||||||
|
"src/",
|
||||||
|
"dst/",
|
||||||
]
|
]
|
||||||
assert sync.rsync_command(file_op) == [
|
assert sync.rsync_command(file_op) == [
|
||||||
"rsync", "-abcEPuvW", "--backup-dir=backup", "src", "dst"
|
"rsync",
|
||||||
|
"-abcEPuvW",
|
||||||
|
"--backup-dir=backup",
|
||||||
|
"src",
|
||||||
|
"dst",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -95,18 +104,12 @@ def test_export_chowns_real_home_without_sudo(tmp_path, fake_runner, monkeypatch
|
|||||||
|
|
||||||
sync.export_to_system()
|
sync.export_to_system()
|
||||||
|
|
||||||
chown_calls = [
|
chown_calls = [(cmd, sudo) for cmd, sudo in fake_runner.sudo_log if cmd[0] == "chown"]
|
||||||
(cmd, sudo) for cmd, sudo in fake_runner.sudo_log if cmd[0] == "chown"
|
assert chown_calls == [(["chown", "-R", "kevin:kevin", str(real_home)], False)]
|
||||||
]
|
|
||||||
assert chown_calls == [
|
|
||||||
(["chown", "-R", "kevin:kevin", str(real_home)], False)
|
|
||||||
]
|
|
||||||
assert (real_home / ".ssh/id_rsa").stat().st_mode & 0o777 == 0o600
|
assert (real_home / ".ssh/id_rsa").stat().st_mode & 0o777 == 0o600
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sync_plan_creates_folders_and_runs_rsync(
|
def test_execute_sync_plan_creates_folders_and_runs_rsync(tmp_path, fake_runner):
|
||||||
tmp_path, fake_runner
|
|
||||||
):
|
|
||||||
operation = sync.SyncOperation(
|
operation = sync.SyncOperation(
|
||||||
source=str(tmp_path / "src.txt"),
|
source=str(tmp_path / "src.txt"),
|
||||||
destination=str(tmp_path / "deep/nested/dst.txt"),
|
destination=str(tmp_path / "deep/nested/dst.txt"),
|
||||||
|
|||||||
Reference in New Issue
Block a user