linux-image-manager 2.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
lim/runner.py ADDED
@@ -0,0 +1,134 @@
1
+ """Thin wrapper around subprocess for all external commands.
2
+
3
+ Every module calls external tools through this module so tests can
4
+ replace ``run``/``output``/``succeeds``/``pipeline`` with fakes.
5
+
6
+ Missing binaries never surface as tracebacks: actions (``check=True``,
7
+ ``pipeline``) abort with a clear LimError, tolerated calls
8
+ (``check=False``, ``succeeds``) degrade to a warning plus shell-style
9
+ exit code 127.
10
+ """
11
+
12
+ import os
13
+ import shlex
14
+ import subprocess
15
+
16
+ from lim import ui
17
+ from lim.errors import LimError
18
+
19
+ COMMAND_NOT_FOUND = 127
20
+
21
+
22
+ def _with_sudo(cmd: list[str], *, sudo: bool) -> list[str]:
23
+ parts = [str(part) for part in cmd]
24
+ if sudo and os.geteuid() != 0:
25
+ return ["sudo", *parts]
26
+ return parts
27
+
28
+
29
+ def _not_found_message(cmd: list[str]) -> str:
30
+ return f"Command not found: {cmd[0]} โ€” please install it."
31
+
32
+
33
+ def run(
34
+ cmd: list[str],
35
+ *,
36
+ sudo: bool = False,
37
+ input_text: str | None = None,
38
+ check: bool = True,
39
+ error_msg: str | None = None,
40
+ ) -> subprocess.CompletedProcess:
41
+ """Run a command inheriting stdio so progress output stays visible."""
42
+ cmd = _with_sudo(cmd, sudo=sudo)
43
+ ui.info(f"Running: {shlex.join(cmd)}")
44
+ try:
45
+ result = subprocess.run(cmd, input=input_text, text=True, check=False)
46
+ except FileNotFoundError:
47
+ if check:
48
+ raise LimError(error_msg or _not_found_message(cmd)) from None
49
+ ui.warning(_not_found_message(cmd))
50
+ return subprocess.CompletedProcess(cmd, COMMAND_NOT_FOUND)
51
+ if check and result.returncode != 0:
52
+ raise LimError(
53
+ error_msg
54
+ or f"Command failed with code {result.returncode}: {shlex.join(cmd)}"
55
+ )
56
+ return result
57
+
58
+
59
+ def output(
60
+ cmd: list[str],
61
+ *,
62
+ sudo: bool = False,
63
+ check: bool = True,
64
+ error_msg: str | None = None,
65
+ ) -> str:
66
+ """Run a command and return its stripped stdout."""
67
+ cmd = _with_sudo(cmd, sudo=sudo)
68
+ try:
69
+ result = subprocess.run(cmd, text=True, capture_output=True, check=False)
70
+ except FileNotFoundError:
71
+ if check:
72
+ raise LimError(error_msg or _not_found_message(cmd)) from None
73
+ ui.warning(_not_found_message(cmd))
74
+ return ""
75
+ if check and result.returncode != 0:
76
+ raise LimError(
77
+ error_msg
78
+ or f"Command failed with code {result.returncode}: {shlex.join(cmd)}"
79
+ + (f"\n{result.stderr.strip()}" if result.stderr else "")
80
+ )
81
+ return result.stdout.strip()
82
+
83
+
84
+ def succeeds(cmd: list[str], *, sudo: bool = False) -> bool:
85
+ """Run a command silently, report whether it exited with 0."""
86
+ cmd = _with_sudo(cmd, sudo=sudo)
87
+ try:
88
+ return subprocess.run(cmd, capture_output=True, check=False).returncode == 0
89
+ except FileNotFoundError:
90
+ ui.warning(_not_found_message(cmd))
91
+ return False
92
+
93
+
94
+ def pipeline(
95
+ *cmds: list[str],
96
+ sudo_last: bool = False,
97
+ error_msg: str | None = None,
98
+ ) -> None:
99
+ """Run commands as a shell-style pipeline (cmd1 | cmd2 | ...)."""
100
+ prepared = [
101
+ _with_sudo(cmd, sudo=sudo_last and index == len(cmds) - 1)
102
+ for index, cmd in enumerate(cmds)
103
+ ]
104
+ pretty = " | ".join(shlex.join(cmd) for cmd in prepared)
105
+ ui.info(f"Running: {pretty}")
106
+ processes: list[subprocess.Popen] = []
107
+ previous_stdout = None
108
+ try:
109
+ for index, cmd in enumerate(prepared):
110
+ last = index == len(prepared) - 1
111
+ process = subprocess.Popen(
112
+ cmd,
113
+ stdin=previous_stdout,
114
+ stdout=None if last else subprocess.PIPE,
115
+ )
116
+ if previous_stdout is not None:
117
+ previous_stdout.close()
118
+ previous_stdout = process.stdout
119
+ processes.append(process)
120
+ except FileNotFoundError as exc:
121
+ if previous_stdout is not None:
122
+ previous_stdout.close()
123
+ for process in processes:
124
+ process.kill()
125
+ process.wait()
126
+ raise LimError(f"Command not found: {exc.filename} โ€” please install it.") from None
127
+ for process in processes:
128
+ process.wait()
129
+ if any(process.returncode != 0 for process in processes):
130
+ raise LimError(error_msg or f"Pipeline failed: {pretty}")
131
+
132
+
133
+ def sync_disks() -> None:
134
+ run(["sync"])
File without changes
lim/storage/common.py ADDED
@@ -0,0 +1,38 @@
1
+ """Shared path derivation for encrypted storage setups."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from lim import device as device_module
6
+ from lim import ui
7
+ from lim.device import Device
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class StorageTarget:
12
+ """An encrypted drive plus all derived mapper/mount/partition paths."""
13
+
14
+ device: Device
15
+
16
+ @property
17
+ def mapper_name(self) -> str:
18
+ return f"encrypteddrive-{self.device.name}"
19
+
20
+ @property
21
+ def mapper_path(self) -> str:
22
+ return f"/dev/mapper/{self.mapper_name}"
23
+
24
+ @property
25
+ def mount_path(self) -> str:
26
+ return f"/media/{self.mapper_name}"
27
+
28
+ @property
29
+ def partition_path(self) -> str:
30
+ return self.device.partition(1)
31
+
32
+
33
+ def select_storage_target() -> StorageTarget:
34
+ target = StorageTarget(device_module.select_device())
35
+ ui.info(f"mapper name set to : {target.mapper_name}")
36
+ ui.info(f"mapper path set to : {target.mapper_path}")
37
+ ui.info(f"mount path set to : {target.mount_path}")
38
+ return target
lim/storage/raid1.py ADDED
@@ -0,0 +1,68 @@
1
+ """Encrypted Btrfs RAID1 across two drives.
2
+
3
+ See https://balaskas.gr/btrfs/raid1.html and
4
+ https://mutschler.eu/linux/install-guides/ubuntu-btrfs-raid1/
5
+ """
6
+
7
+ from lim import luks, runner, ui
8
+ from lim.storage.common import StorageTarget, select_storage_target
9
+
10
+
11
+ def _select_pair() -> tuple[StorageTarget, StorageTarget]:
12
+ ui.info("RAID1 partition 1...")
13
+ first = select_storage_target()
14
+ ui.info("RAID1 partition 2...")
15
+ second = select_storage_target()
16
+ return first, second
17
+
18
+
19
+ def setup() -> None:
20
+ first, second = _select_pair()
21
+
22
+ ui.info(f"Encrypting {first.device.path}...")
23
+ runner.run(["cryptsetup", "luksFormat", first.device.path], sudo=True)
24
+ ui.info(f"Encrypting {second.device.path}...")
25
+ runner.run(["cryptsetup", "luksFormat", second.device.path], sudo=True)
26
+
27
+ runner.run(["cryptsetup", "luksOpen", first.device.path, first.mapper_name], sudo=True)
28
+ runner.run(
29
+ ["cryptsetup", "luksOpen", second.device.path, second.mapper_name], sudo=True
30
+ )
31
+ runner.run(["cryptsetup", "status", first.mapper_path], sudo=True)
32
+ runner.run(["cryptsetup", "status", second.mapper_path], sudo=True)
33
+
34
+ runner.run(
35
+ [
36
+ "mkfs.btrfs",
37
+ "-m",
38
+ "raid1",
39
+ "-d",
40
+ "raid1",
41
+ first.mapper_path,
42
+ second.mapper_path,
43
+ ],
44
+ sudo=True,
45
+ )
46
+
47
+ ui.success("Encryption successfull :)")
48
+
49
+
50
+ def _show_luks_devices() -> None:
51
+ for name in runner.output(["lsblk", "-dno", "NAME"], check=False).split():
52
+ if runner.succeeds(["cryptsetup", "isLuks", f"/dev/{name}"], sudo=True):
53
+ ui.info(f"/dev/{name} is a LUKS encrypted storage device.")
54
+
55
+
56
+ def mount_on_boot() -> None:
57
+ ui.info("Activate Automount raid1 encrypted storages...")
58
+ _show_luks_devices()
59
+
60
+ first, second = _select_pair()
61
+
62
+ luks.create_luks_key_and_update_crypttab(first.mapper_name, first.device.path)
63
+ ui.info(f'Creating mount folder under "{first.mount_path}"...')
64
+ runner.run(["mkdir", "-vp", first.mount_path], sudo=True)
65
+ luks.create_luks_key_and_update_crypttab(second.mapper_name, second.device.path)
66
+ luks.update_fstab(first.mapper_path, first.mount_path)
67
+
68
+ ui.success("Installation finished. Please restart :)")
@@ -0,0 +1,88 @@
1
+ """Single-drive LUKS + Btrfs storage: setup, mount, umount, mount-on-boot."""
2
+
3
+ from lim import device as device_module
4
+ from lim import luks, runner, system, ui
5
+ from lim.storage.common import select_storage_target
6
+
7
+ # fdisk answer sequences; empty lines accept the defaults.
8
+ CREATE_GPT_TABLE_INPUT = "g\nw\n"
9
+ CREATE_PARTITION_INPUT = "n\n\n\n\np\nw\n"
10
+
11
+
12
+ def setup() -> None:
13
+ print("Setups disk encryption")
14
+ target = select_storage_target()
15
+
16
+ device_module.overwrite_device(target.device)
17
+
18
+ ui.info("Creating new GPT partition table...")
19
+ runner.run(
20
+ ["fdisk", "--wipe", "always", target.device.path],
21
+ input_text=CREATE_GPT_TABLE_INPUT,
22
+ sudo=True,
23
+ )
24
+ ui.info("Creating partition table...")
25
+ runner.run(
26
+ ["fdisk", "--wipe", "always", target.device.path],
27
+ input_text=CREATE_PARTITION_INPUT,
28
+ sudo=True,
29
+ )
30
+
31
+ ui.info(f"Encrypt {target.device.path}...")
32
+ runner.run(["cryptsetup", "-v", "-y", "luksFormat", target.partition_path], sudo=True)
33
+
34
+ ui.info("Unlock partition...")
35
+ runner.run(
36
+ ["cryptsetup", "luksOpen", target.partition_path, target.mapper_name], sudo=True
37
+ )
38
+
39
+ ui.info("Create btrfs file system...")
40
+ runner.run(["mkfs.btrfs", target.mapper_path], sudo=True)
41
+
42
+ ui.info(f'Creating mount folder under "{target.mount_path}"...')
43
+ runner.run(["mkdir", "-p", target.mount_path], sudo=True)
44
+
45
+ ui.info("Mount partition...")
46
+ runner.run(["mount", target.mapper_path, target.mount_path], sudo=True)
47
+
48
+ user = system.real_user()
49
+ ui.info("Own partition by user...")
50
+ runner.run(["chown", "-R", f"{user}:{user}", target.mount_path], sudo=True)
51
+
52
+ ui.success("Encryption successfull :)")
53
+
54
+
55
+ def mount() -> None:
56
+ print("Mounts encrypted storages")
57
+ target = select_storage_target()
58
+
59
+ ui.info("Unlock partition...")
60
+ runner.run(
61
+ ["cryptsetup", "luksOpen", target.partition_path, target.mapper_name], sudo=True
62
+ )
63
+
64
+ ui.info("Mount partition...")
65
+ runner.run(["mount", target.mapper_path, target.mount_path], sudo=True)
66
+
67
+ ui.success("Mounting successfull :)")
68
+
69
+
70
+ def umount() -> None:
71
+ print("Unmount encrypted storages")
72
+ target = select_storage_target()
73
+
74
+ ui.info(f"Unmount {target.mapper_path}...")
75
+ runner.run(["umount", target.mapper_path], sudo=True)
76
+ runner.run(["cryptsetup", "luksClose", target.mapper_name], sudo=True)
77
+
78
+ ui.success("Successfull :)")
79
+
80
+
81
+ def mount_on_boot() -> None:
82
+ print("Automount encrypted storages")
83
+ target = select_storage_target()
84
+
85
+ luks.create_luks_key_and_update_crypttab(target.mapper_name, target.partition_path)
86
+ luks.update_fstab(target.mapper_path, target.mount_path)
87
+
88
+ ui.success("Installation finished. Please restart :)")
lim/system.py ADDED
@@ -0,0 +1,35 @@
1
+ """Process-level helpers: privilege handling and user resolution."""
2
+
3
+ import getpass
4
+ import os
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ from lim import ui
9
+
10
+
11
+ def is_root() -> bool:
12
+ return os.geteuid() == 0
13
+
14
+
15
+ def ensure_root() -> None:
16
+ """Re-execute the current command with sudo when not running as root."""
17
+ if is_root():
18
+ return
19
+ ui.info("Root privileges required. Re-executing with sudo...")
20
+ script = str(Path(sys.argv[0]).resolve())
21
+ # Deliberate privilege escalation: replace this process with sudo.
22
+ os.execvp("sudo", ["sudo", sys.executable, script, *sys.argv[1:]]) # noqa: S606
23
+
24
+
25
+ def real_user() -> str:
26
+ """Return the invoking user, even when running under sudo."""
27
+ return os.environ.get("SUDO_USER") or getpass.getuser()
28
+
29
+
30
+ def real_home() -> Path:
31
+ """Home directory of the invoking user, even when running under sudo."""
32
+ sudo_user = os.environ.get("SUDO_USER")
33
+ if sudo_user:
34
+ return Path("/home") / sudo_user
35
+ return Path.home()
lim/ui.py ADDED
@@ -0,0 +1,62 @@
1
+ """Colored console messages and interactive prompts."""
2
+
3
+ import sys
4
+
5
+ _USE_COLOR = sys.stdout.isatty()
6
+
7
+
8
+ def _color(code: str) -> str:
9
+ return f"\033[{code}m" if _USE_COLOR else ""
10
+
11
+
12
+ COLOR_RED = _color("31")
13
+ COLOR_GREEN = _color("32")
14
+ COLOR_YELLOW = _color("33")
15
+ COLOR_BLUE = _color("34")
16
+ COLOR_MAGENTA = _color("35")
17
+ COLOR_CYAN = _color("36")
18
+ COLOR_WHITE = _color("37")
19
+ COLOR_RESET = _color("0")
20
+
21
+
22
+ def message(color: str, tag: str, text: str) -> None:
23
+ print(f"{color}[{tag}]:{COLOR_RESET} {text}")
24
+
25
+
26
+ def question(text: str) -> None:
27
+ message(COLOR_MAGENTA, "QUESTION", text)
28
+
29
+
30
+ def info(text: str) -> None:
31
+ message(COLOR_BLUE, "INFO", text)
32
+
33
+
34
+ def warning(text: str) -> None:
35
+ message(COLOR_YELLOW, "WARNING", text)
36
+
37
+
38
+ def success(text: str) -> None:
39
+ message(COLOR_GREEN, "SUCCESS", text)
40
+
41
+
42
+ def error(text: str) -> None:
43
+ message(COLOR_RED, "ERROR", text)
44
+
45
+
46
+ def ask(prompt: str) -> str:
47
+ question(prompt)
48
+ return input().strip()
49
+
50
+
51
+ def confirm(prompt: str) -> bool:
52
+ return ask(f"{prompt}(y/N)") == "y"
53
+
54
+
55
+ def header() -> None:
56
+ print(
57
+ f"\n{COLOR_YELLOW}The\n"
58
+ "LINUX IMAGE MANAGER\n"
59
+ "is an administration tool designed from and for Kevin Veen-Birkenbach.\n\n"
60
+ "Licensed under GNU GENERAL PUBLIC LICENSE Version 3"
61
+ f"{COLOR_RESET}\n"
62
+ )
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.4
2
+ Name: linux-image-manager
3
+ Version: 2.1.0
4
+ Summary: CLI (`lim`) for downloading, verifying and flashing Linux images, LUKS/Btrfs encrypted storage (single drive and RAID1), image backups and chroot maintenance
5
+ Author-email: Kevin Veen-Birkenbach <kevin@veen.world>
6
+ Maintainer-email: Kevin Veen-Birkenbach <kevin@veen.world>
7
+ License-Expression: GPL-3.0-only
8
+ Project-URL: Homepage, https://veen.world
9
+ Project-URL: Repository, https://github.com/kevinveenbirkenbach/linux-image-manager
10
+ Classifier: Environment :: Console
11
+ Classifier: Operating System :: POSIX :: Linux
12
+ Classifier: Programming Language :: Python :: 3
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE.txt
16
+ Requires-Dist: PyYAML>=6
17
+ Dynamic: license-file
18
+
19
+ # Linux Image Manager ๐Ÿ–ฅ๏ธ๐Ÿ› ๏ธ
20
+
21
+ [![GitHub Sponsors](https://img.shields.io/badge/Sponsor-GitHub%20Sponsors-blue?logo=github)](https://github.com/sponsors/kevinveenbirkenbach) [![Patreon](https://img.shields.io/badge/Support-Patreon-orange?logo=patreon)](https://www.patreon.com/c/kevinveenbirkenbach) [![Buy Me a Coffee](https://img.shields.io/badge/Buy%20me%20a%20Coffee-Funding-yellow?logo=buymeacoffee)](https://buymeacoffee.com/kevinveenbirkenbach) [![PayPal](https://img.shields.io/badge/Donate-PayPal-blue?logo=paypal)](https://s.veen.world/paypaldonate)
22
+
23
+ [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](./LICENSE.txt) [![GitHub stars](https://img.shields.io/github/stars/kevinveenbirkenbach/linux-image-manager.svg?style=social)](https://github.com/kevinveenbirkenbach/linux-image-manager/stargazers)
24
+
25
+ Linux Image Manager (lim) is a Python tool for downloading, configuring, and managing Linux images. Whether you're setting up encrypted storage, configuring a virtual Btrfs RAID1, performing backups, or chrooting into an image, this tool makes Linux image administration simple and efficient. ๐Ÿš€
26
+
27
+ ## Features โœจ
28
+
29
+ - **Image Download & Setup:** Automatically download, verify (checksum + GPG signature) and prepare Linux distributions.
30
+ - **Encrypted Storage:** Configure LUKS encryption for secure image management.
31
+ - **Virtual RAID1:** Easily set up virtual Btrfs RAID1 for data redundancy.
32
+ - **Backup & Restore:** Create image backups from devices using dd.
33
+ - **Chroot Environment:** Easily enter a chroot shell to maintain or modify Linux images.
34
+ - **Data Import/Export:** Sync personal data into an encfs-encrypted store and back.
35
+ - **Automated Procedures:** Simplify partitioning, formatting, mounting, and more.
36
+
37
+ ## Installation ๐Ÿ“ฆ
38
+
39
+ Install Linux Image Manager quickly using [Kevin's Package Manager](https://github.com/kevinveenbirkenbach/package-manager) under the alias `lim`. Just run:
40
+
41
+ ```bash
42
+ package-manager install lim
43
+ ```
44
+
45
+ This command makes Linux Image Manager globally available as `lim` in your terminal. The `lim` alias points to the **main.py** entry point.
46
+
47
+ There are no Python dependencies beyond the standard library. The commands call the usual system tools (`cryptsetup`, `fdisk`, `dd`, `rsync`, `wget`, `gpg`, `encfs`, `pv`, `bsdtar`, ...), so those need to be installed for the command you use.
48
+
49
+ ## Usage โš™๏ธ
50
+
51
+ `lim` provides a unified interface for all image and storage operations. Commands that need root privileges re-execute themselves with `sudo` automatically. The built-in `--help` option displays detailed usage information.
52
+
53
+ ### Available Command Types
54
+
55
+ | `--type` | Description |
56
+ |---------------|-----------------------------------------------------------------------------|
57
+ | `image` | Download, verify and transfer a Linux image to a device, incl. optional LUKS encryption and Raspberry Pi configuration. |
58
+ | `single` | Set up LUKS encryption with Btrfs on a single drive. |
59
+ | `raid1` | Set up an encrypted virtual Btrfs RAID1 across two drives. |
60
+ | `backup` | Create an image backup from a memory device using dd. |
61
+ | `chroot` | Mount an image and open a shell inside it. |
62
+ | `mount` | Unlock and mount an encrypted drive. |
63
+ | `umount` | Unmount an encrypted drive and close the mapper. |
64
+ | `single-boot` | Register a single encrypted drive for automount on boot (keyfile, crypttab, fstab). |
65
+ | `raid1-boot` | Register an encrypted RAID1 for automount on boot. |
66
+ | `unlock` | Decrypt the encfs data store. |
67
+ | `lock` | Lock the encfs data store. |
68
+ | `import` | Import personal data from the system into the encrypted store. |
69
+ | `export` | Export personal data from the encrypted store back to the system. |
70
+
71
+ ### Command-Line Options
72
+
73
+ - **`--type`** *(required)*: Choose the command to execute (see table above).
74
+ - **`--auto-confirm`** *(optional)*: Bypass the confirmation prompt before execution.
75
+ - **`--help`** *(optional)*: Display detailed help information.
76
+
77
+ ### Example Commands
78
+
79
+ ```bash
80
+ # Display help
81
+ lim --help
82
+
83
+ # Execute the Linux image setup
84
+ lim --type image
85
+
86
+ # Run the single drive encryption setup without a confirmation prompt
87
+ lim --type single --auto-confirm
88
+
89
+ # Set up an encrypted RAID1
90
+ lim --type raid1
91
+
92
+ # Back up a memory device to an image file
93
+ lim --type backup
94
+
95
+ # Enter a chroot environment for a Linux image
96
+ lim --type chroot
97
+ ```
98
+
99
+ ## Project Structure ๐Ÿ—‚๏ธ
100
+
101
+ ```
102
+ main.py # entry point (the `lim` alias)
103
+ lim/
104
+ cli.py # argument parsing and command dispatch
105
+ catalog.py # read-only access to distributions.yml
106
+ device.py # block device selection, dd, blkid helpers
107
+ luks.py # LUKS keyfiles, crypttab/fstab bookkeeping
108
+ runner.py # subprocess wrapper used by all modules
109
+ storage/ # single drive and RAID1 encryption setups
110
+ image/ # image setup, backup, chroot, verification
111
+ data/ # encfs lock/unlock and data import/export
112
+ distributions.yml # single point of truth for the image catalog
113
+ configuration/ # package collections used during image setup
114
+ tests/unit/ # unit tests (all external commands mocked)
115
+ tests/lint/ # architecture guards (e.g. max file length)
116
+ ```
117
+
118
+ ## Configuration & Customization ๐Ÿ”ง
119
+
120
+ Customize your environment in the `lim/configuration/` folder:
121
+ - **General Packages:** Contains common packages for all setup scripts.
122
+ - **Server LUKS Packages:** Contains packages needed for setting up LUKS encryption on servers.
123
+
124
+ ## Development & Tests ๐Ÿงช
125
+
126
+ The test suite mocks all external commands, so it runs safely on any machine:
127
+
128
+ ```bash
129
+ pytest
130
+ ```
131
+
132
+ ## License ๐Ÿ“œ
133
+
134
+ This project is licensed under the GNU General Public License Version 3. See the [LICENSE.txt](./LICENSE.txt) file for details.
135
+
136
+ ## Contact & Support ๐Ÿ’ฌ
137
+
138
+ - **Author:** Kevin Veen-Birkenbach
139
+ - **Email:** [kevin@veen.world](mailto:kevin@veen.world)
140
+ - **Website:** [https://www.veen.world/](https://www.veen.world/)
141
+
142
+ Feel free to contribute, report issues, or get in touch. Happy Linux managing! ๐Ÿ˜Š
@@ -0,0 +1,39 @@
1
+ lim/__init__.py,sha256=C8ZE1GmhxFQzIa6WP8EWS89H52g-8yIDMfPaQngPVkE,113
2
+ lim/catalog.py,sha256=DASr3KdjN_Xy7dlsobGnmLBGd8Ppqv1oaSn6Abhn3LY,681
3
+ lim/cli.py,sha256=e92mJeKLsO314fSscg3kioMtyccTLn1GZIQZfGkBPjQ,5131
4
+ lim/config.py,sha256=eEhYGRHNw5ttpiVn7966rLdAm4dwpcu9XF5R__IUikE,630
5
+ lim/device.py,sha256=vzjWocxMCbJl5bpU_tSUw55X46ZhrF5JnqT_X7lfksI,4380
6
+ lim/distributions.yml,sha256=_yL050W7YNFbpaaZ_9rIrGyc1eLqbnxDzMoOH6lMvDI,2178
7
+ lim/errors.py,sha256=0TuxNae3m-Y7VOjb714MrbtsBeiwKtp6Cpdoj6i-XNQ,86
8
+ lim/fsutil.py,sha256=2VT4tA5pj6GM4B_bZbCgZVTblERNDo9MrgGcKR55uRQ,1058
9
+ lim/luks.py,sha256=wpG7vwIW_T0UgHbV_5RTHuxEn7ONjer1LaTpDIPQA1Y,2699
10
+ lim/packages.py,sha256=DlsRCFgf6qifgHFTQpeCrVJW0Ly5sJpVr8V9yylTSO0,636
11
+ lim/runner.py,sha256=KByLjepsYMTRE92DQngsPOuJgORitLxWBcWYPUHKLC8,4278
12
+ lim/system.py,sha256=DAMrv47KGombVp5R73nSPZw7EbKA7bBteZ5LdVPQKns,995
13
+ lim/ui.py,sha256=4uqIOupwT7PdApqnWAPhEAC9OZBkKE2Fa6iRI3NI4rg,1312
14
+ lim/configuration/packages/general.txt,sha256=gdU29G5IKcPA-3XS82J5hRNPZwiRRAHbjcB5ASp2xhg,74
15
+ lim/configuration/packages/server/luks.txt,sha256=i-fkCLHO4XfA5PgeTMwcY1xsm49V0Lq75tKsOXZlrjE,132
16
+ lim/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ lim/data/crypt.py,sha256=hZltw-tktAllFPYF0UW_yec9NCrSfZ0mSTBlZEw0d5g,881
18
+ lim/data/sync.py,sha256=xLbHCceOY4caUlvdqWGN6TQ2HSrBihM9Rg07nG6aeA8,4648
19
+ lim/image/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ lim/image/backup.py,sha256=t5ImNWS9lBOCSomeNe_CaB1uztcGlakK4rnBCS06TVM,996
21
+ lim/image/choosers.py,sha256=bjscrLQ0Fyy6AvpMpYtM4UDRkVEMzyL9_-UQP5wqgcI,4036
22
+ lim/image/chroot.py,sha256=evqagFlKW1h5DzjegKTbjM4kNGbIYbHDRzdbbrb-Eb0,730
23
+ lim/image/encryption.py,sha256=M5p3POygI-YV69VdUXPLM0iHUHU7QwBhla2C8MZqKtU,4903
24
+ lim/image/plan.py,sha256=yKtIuz2g5njMswXu4DriAhYqi1NGUx2w3XTQH5Qb26Q,908
25
+ lim/image/raspberry.py,sha256=GszoYkzFv0C6deNENbjPtte5-1CvJwxQblX5CGc2nZQ,9024
26
+ lim/image/session.py,sha256=CdS5E3aJlWNzCDrfoIhEpd8Iejy8wXxHuS5Qvs6fNZs,7671
27
+ lim/image/setup.py,sha256=zW7y8ngGT41T2fOR8ubuMC7cT8xm7lcV7R1i6S0x8gk,3296
28
+ lim/image/transfer.py,sha256=sSsWbVvgUhfuu5glBfFLp8VGYf0ephQzADmKMDWCFzM,5089
29
+ lim/image/verify.py,sha256=6nyDFeLDv5Em8F2ebBs44XJn9ShIXBTvg3ua7CFCvPo,3802
30
+ lim/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
+ lim/storage/common.py,sha256=Z-zODKO2TT-_XUuk4J3tj02_o4p0RhHj1auyirjmv64,1006
32
+ lim/storage/raid1.py,sha256=LJWXxuNquKfwdg-4NYapsc9K4C7G6Kh0hKqg4bnR8Vg,2259
33
+ lim/storage/single_drive.py,sha256=FdT2h_uhbcm8i9G6901p-dkhp1u5sWZ26EXCq1F8fwo,2747
34
+ linux_image_manager-2.1.0.dist-info/licenses/LICENSE.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
35
+ linux_image_manager-2.1.0.dist-info/METADATA,sha256=eY69GFQWLCbNQ8XaZwJ1gxuVVexP3tSq9I8307remdU,7245
36
+ linux_image_manager-2.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
37
+ linux_image_manager-2.1.0.dist-info/entry_points.txt,sha256=26af-MdIDdAPbJpU_efWahw6dNRoYilAIhZheY6GCNQ,37
38
+ linux_image_manager-2.1.0.dist-info/top_level.txt,sha256=3pzdvJsQP8C5_0PC98XH_QvY5J4rMGXg6UtVWvjKrzc,4
39
+ linux_image_manager-2.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ lim = lim.cli:main