linux-image-manager 2.1.0__tar.gz → 2.2.0__tar.gz
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.
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/PKG-INFO +74 -3
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/README.md +73 -2
- linux_image_manager-2.2.0/lim/configuration/initcpio/tor_hook +47 -0
- linux_image_manager-2.2.0/lim/configuration/initcpio/tor_install +37 -0
- linux_image_manager-2.2.0/lim/configuration/initcpio/torrc +7 -0
- linux_image_manager-2.2.0/lim/configuration/packages/server/tor.txt +3 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/image/encryption.py +14 -2
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/image/plan.py +1 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/image/setup.py +4 -0
- linux_image_manager-2.2.0/lim/image/tor.py +79 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/linux_image_manager.egg-info/PKG-INFO +74 -3
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/linux_image_manager.egg-info/SOURCES.txt +5 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/pyproject.toml +6 -2
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/LICENSE.txt +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/__init__.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/catalog.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/cli.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/config.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/configuration/packages/general.txt +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/configuration/packages/server/luks.txt +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/data/__init__.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/data/crypt.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/data/sync.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/device.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/distributions.yml +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/errors.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/fsutil.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/image/__init__.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/image/backup.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/image/choosers.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/image/chroot.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/image/raspberry.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/image/session.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/image/transfer.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/image/verify.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/luks.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/packages.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/runner.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/storage/__init__.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/storage/common.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/storage/raid1.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/storage/single_drive.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/system.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/ui.py +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/linux_image_manager.egg-info/dependency_links.txt +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/linux_image_manager.egg-info/entry_points.txt +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/linux_image_manager.egg-info/requires.txt +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/linux_image_manager.egg-info/top_level.txt +0 -0
- {linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: linux-image-manager
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
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
5
|
Author-email: Kevin Veen-Birkenbach <kevin@veen.world>
|
|
6
6
|
Maintainer-email: Kevin Veen-Birkenbach <kevin@veen.world>
|
|
@@ -28,6 +28,7 @@ Linux Image Manager (lim) is a Python tool for downloading, configuring, and man
|
|
|
28
28
|
|
|
29
29
|
- **Image Download & Setup:** Automatically download, verify (checksum + GPG signature) and prepare Linux distributions.
|
|
30
30
|
- **Encrypted Storage:** Configure LUKS encryption for secure image management.
|
|
31
|
+
- **Tor Onion Unlock:** Optionally bake a Tor onion service into the initramfs so the dropbear LUKS unlock shell stays reachable behind NAT or dynamic IPs.
|
|
31
32
|
- **Virtual RAID1:** Easily set up virtual Btrfs RAID1 for data redundancy.
|
|
32
33
|
- **Backup & Restore:** Create image backups from devices using dd.
|
|
33
34
|
- **Chroot Environment:** Easily enter a chroot shell to maintain or modify Linux images.
|
|
@@ -44,7 +45,7 @@ package-manager install lim
|
|
|
44
45
|
|
|
45
46
|
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
|
|
|
47
|
-
|
|
48
|
+
The only Python dependency is **PyYAML** (`>=6`), used to read the image catalog. The pip/wheel install pulls it in automatically; on the `package-manager`/symlink install path make sure it is present (`python-yaml` on Arch). The commands also 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
|
|
|
49
50
|
## Usage ⚙️
|
|
50
51
|
|
|
@@ -112,6 +113,8 @@ lim/
|
|
|
112
113
|
distributions.yml # single point of truth for the image catalog
|
|
113
114
|
configuration/ # package collections used during image setup
|
|
114
115
|
tests/unit/ # unit tests (all external commands mocked)
|
|
116
|
+
tests/e2e/ # Tor onion unlock end-to-end tests (rootless + QEMU, opt-in)
|
|
117
|
+
tests/e2e/qemu/ # full virtualized build+boot+unlock harness
|
|
115
118
|
tests/lint/ # architecture guards (e.g. max file length)
|
|
116
119
|
```
|
|
117
120
|
|
|
@@ -120,15 +123,83 @@ tests/lint/ # architecture guards (e.g. max file length)
|
|
|
120
123
|
Customize your environment in the `lim/configuration/` folder:
|
|
121
124
|
- **General Packages:** Contains common packages for all setup scripts.
|
|
122
125
|
- **Server LUKS Packages:** Contains packages needed for setting up LUKS encryption on servers.
|
|
126
|
+
- **initcpio Hooks:** The mkinitcpio install/runtime hooks and torrc baked into images for the Tor onion unlock.
|
|
127
|
+
|
|
128
|
+
## Remote LUKS Unlock via Tor 🧅
|
|
129
|
+
|
|
130
|
+
When you answer yes to *"Should the system be remotely unlockable via a Tor
|
|
131
|
+
onion service?"* during an encrypted `lim --type image` setup, the image gets:
|
|
132
|
+
|
|
133
|
+
- `tor` and `busybox` installed, plus a custom `tor` mkinitcpio hook ordered
|
|
134
|
+
between `netconf` and `dropbear`.
|
|
135
|
+
- A v3 onion service key generated **offline** inside the image chroot; the
|
|
136
|
+
resulting `.onion` address is printed at the end of the setup and stays
|
|
137
|
+
stable across reboots and IP changes.
|
|
138
|
+
- An NTP clock sync in early userspace (boards like the Raspberry Pi have no
|
|
139
|
+
RTC and would otherwise boot with a clock Tor rejects). Override the server
|
|
140
|
+
with the kernel parameter `tor_ntp=<host>`.
|
|
141
|
+
|
|
142
|
+
After booting the device, unlock it from any machine with a running Tor
|
|
143
|
+
client:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
torsocks ssh root@<onion-address>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Typing the LUKS passphrase into that shell resumes the boot. The direct
|
|
150
|
+
`ssh root@<ip>` unlock keeps working as before; Tor is an additional path,
|
|
151
|
+
useful behind NAT/CGNAT where no port forwarding is possible.
|
|
152
|
+
|
|
153
|
+
**Security note:** the onion private key is stored in the initramfs on the
|
|
154
|
+
unencrypted boot partition. Anyone with physical access to the SD card can
|
|
155
|
+
read it and impersonate the onion service — treat the address as
|
|
156
|
+
non-secret. Access control remains the SSH public key
|
|
157
|
+
(`/etc/dropbear/root_key`), exactly as with the direct unlock.
|
|
123
158
|
|
|
124
159
|
## Development & Tests 🧪
|
|
125
160
|
|
|
126
|
-
The
|
|
161
|
+
The unit suite mocks all external commands, so it runs safely on any machine:
|
|
127
162
|
|
|
128
163
|
```bash
|
|
129
164
|
pytest
|
|
130
165
|
```
|
|
131
166
|
|
|
167
|
+
### Tor unlock end-to-end test
|
|
168
|
+
|
|
169
|
+
`tests/e2e/test_tor_unlock_e2e.py` reproduces the operator-visible half of the
|
|
170
|
+
Tor decryption process **rootless** — no block devices, no `cryptsetup`, no
|
|
171
|
+
`chroot`. It generates the v3 onion keys offline (exactly as `lim.image.tor`
|
|
172
|
+
does inside the image), starts a real Tor onion service from a torrc mirroring
|
|
173
|
+
the baked-in one, then connects to the `.onion` address through Tor and
|
|
174
|
+
delivers a passphrase to a dropbear stand-in, asserting it arrives and the
|
|
175
|
+
endpoint "unlocks".
|
|
176
|
+
|
|
177
|
+
It needs the real `tor` binary and live Tor network access (onion bootstrap is
|
|
178
|
+
slow), so it is opt-in and skipped otherwise:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
LIM_E2E_TOR=1 pytest tests/e2e/test_tor_unlock_e2e.py -v
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The offline-only checks in that file (onion keygen, production-flag guard) run
|
|
185
|
+
without network. The physical flashing half (`dd`, `cryptsetup luksFormat`,
|
|
186
|
+
`mount`, `chroot`, `mkinitcpio`) requires root and a matching CPU/qemu setup and
|
|
187
|
+
is not covered by this rootless test.
|
|
188
|
+
|
|
189
|
+
### Full virtualized unlock (QEMU)
|
|
190
|
+
|
|
191
|
+
For the *whole* process — build an encrypted image, boot it, run the real
|
|
192
|
+
initramfs Tor+dropbear chain, and unlock it — `tests/e2e/qemu/` drives a QEMU
|
|
193
|
+
virtual machine end to end and asserts the real system booted only after the
|
|
194
|
+
passphrase was delivered over Tor. It is opt-in (`LIM_E2E_QEMU=1`) and needs
|
|
195
|
+
QEMU, `arch-install-scripts`, `cryptsetup`, `tor` and `ncat`; the build stage
|
|
196
|
+
needs root (ideally inside a throwaway VM to keep the host rootless). A private,
|
|
197
|
+
offline Tor network via [chutney](https://gitlab.torproject.org/tpo/core/chutney)
|
|
198
|
+
is supported. See [tests/e2e/qemu/README.md](tests/e2e/qemu/README.md).
|
|
199
|
+
|
|
200
|
+
The harness's pure logic and drift guards run in the normal suite
|
|
201
|
+
(`test_qemu_harness_unit.py`) — no QEMU, root, or network needed.
|
|
202
|
+
|
|
132
203
|
## License 📜
|
|
133
204
|
|
|
134
205
|
This project is licensed under the GNU General Public License Version 3. See the [LICENSE.txt](./LICENSE.txt) file for details.
|
|
@@ -10,6 +10,7 @@ Linux Image Manager (lim) is a Python tool for downloading, configuring, and man
|
|
|
10
10
|
|
|
11
11
|
- **Image Download & Setup:** Automatically download, verify (checksum + GPG signature) and prepare Linux distributions.
|
|
12
12
|
- **Encrypted Storage:** Configure LUKS encryption for secure image management.
|
|
13
|
+
- **Tor Onion Unlock:** Optionally bake a Tor onion service into the initramfs so the dropbear LUKS unlock shell stays reachable behind NAT or dynamic IPs.
|
|
13
14
|
- **Virtual RAID1:** Easily set up virtual Btrfs RAID1 for data redundancy.
|
|
14
15
|
- **Backup & Restore:** Create image backups from devices using dd.
|
|
15
16
|
- **Chroot Environment:** Easily enter a chroot shell to maintain or modify Linux images.
|
|
@@ -26,7 +27,7 @@ package-manager install lim
|
|
|
26
27
|
|
|
27
28
|
This command makes Linux Image Manager globally available as `lim` in your terminal. The `lim` alias points to the **main.py** entry point.
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
The only Python dependency is **PyYAML** (`>=6`), used to read the image catalog. The pip/wheel install pulls it in automatically; on the `package-manager`/symlink install path make sure it is present (`python-yaml` on Arch). The commands also 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.
|
|
30
31
|
|
|
31
32
|
## Usage ⚙️
|
|
32
33
|
|
|
@@ -94,6 +95,8 @@ lim/
|
|
|
94
95
|
distributions.yml # single point of truth for the image catalog
|
|
95
96
|
configuration/ # package collections used during image setup
|
|
96
97
|
tests/unit/ # unit tests (all external commands mocked)
|
|
98
|
+
tests/e2e/ # Tor onion unlock end-to-end tests (rootless + QEMU, opt-in)
|
|
99
|
+
tests/e2e/qemu/ # full virtualized build+boot+unlock harness
|
|
97
100
|
tests/lint/ # architecture guards (e.g. max file length)
|
|
98
101
|
```
|
|
99
102
|
|
|
@@ -102,15 +105,83 @@ tests/lint/ # architecture guards (e.g. max file length)
|
|
|
102
105
|
Customize your environment in the `lim/configuration/` folder:
|
|
103
106
|
- **General Packages:** Contains common packages for all setup scripts.
|
|
104
107
|
- **Server LUKS Packages:** Contains packages needed for setting up LUKS encryption on servers.
|
|
108
|
+
- **initcpio Hooks:** The mkinitcpio install/runtime hooks and torrc baked into images for the Tor onion unlock.
|
|
109
|
+
|
|
110
|
+
## Remote LUKS Unlock via Tor 🧅
|
|
111
|
+
|
|
112
|
+
When you answer yes to *"Should the system be remotely unlockable via a Tor
|
|
113
|
+
onion service?"* during an encrypted `lim --type image` setup, the image gets:
|
|
114
|
+
|
|
115
|
+
- `tor` and `busybox` installed, plus a custom `tor` mkinitcpio hook ordered
|
|
116
|
+
between `netconf` and `dropbear`.
|
|
117
|
+
- A v3 onion service key generated **offline** inside the image chroot; the
|
|
118
|
+
resulting `.onion` address is printed at the end of the setup and stays
|
|
119
|
+
stable across reboots and IP changes.
|
|
120
|
+
- An NTP clock sync in early userspace (boards like the Raspberry Pi have no
|
|
121
|
+
RTC and would otherwise boot with a clock Tor rejects). Override the server
|
|
122
|
+
with the kernel parameter `tor_ntp=<host>`.
|
|
123
|
+
|
|
124
|
+
After booting the device, unlock it from any machine with a running Tor
|
|
125
|
+
client:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
torsocks ssh root@<onion-address>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Typing the LUKS passphrase into that shell resumes the boot. The direct
|
|
132
|
+
`ssh root@<ip>` unlock keeps working as before; Tor is an additional path,
|
|
133
|
+
useful behind NAT/CGNAT where no port forwarding is possible.
|
|
134
|
+
|
|
135
|
+
**Security note:** the onion private key is stored in the initramfs on the
|
|
136
|
+
unencrypted boot partition. Anyone with physical access to the SD card can
|
|
137
|
+
read it and impersonate the onion service — treat the address as
|
|
138
|
+
non-secret. Access control remains the SSH public key
|
|
139
|
+
(`/etc/dropbear/root_key`), exactly as with the direct unlock.
|
|
105
140
|
|
|
106
141
|
## Development & Tests 🧪
|
|
107
142
|
|
|
108
|
-
The
|
|
143
|
+
The unit suite mocks all external commands, so it runs safely on any machine:
|
|
109
144
|
|
|
110
145
|
```bash
|
|
111
146
|
pytest
|
|
112
147
|
```
|
|
113
148
|
|
|
149
|
+
### Tor unlock end-to-end test
|
|
150
|
+
|
|
151
|
+
`tests/e2e/test_tor_unlock_e2e.py` reproduces the operator-visible half of the
|
|
152
|
+
Tor decryption process **rootless** — no block devices, no `cryptsetup`, no
|
|
153
|
+
`chroot`. It generates the v3 onion keys offline (exactly as `lim.image.tor`
|
|
154
|
+
does inside the image), starts a real Tor onion service from a torrc mirroring
|
|
155
|
+
the baked-in one, then connects to the `.onion` address through Tor and
|
|
156
|
+
delivers a passphrase to a dropbear stand-in, asserting it arrives and the
|
|
157
|
+
endpoint "unlocks".
|
|
158
|
+
|
|
159
|
+
It needs the real `tor` binary and live Tor network access (onion bootstrap is
|
|
160
|
+
slow), so it is opt-in and skipped otherwise:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
LIM_E2E_TOR=1 pytest tests/e2e/test_tor_unlock_e2e.py -v
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The offline-only checks in that file (onion keygen, production-flag guard) run
|
|
167
|
+
without network. The physical flashing half (`dd`, `cryptsetup luksFormat`,
|
|
168
|
+
`mount`, `chroot`, `mkinitcpio`) requires root and a matching CPU/qemu setup and
|
|
169
|
+
is not covered by this rootless test.
|
|
170
|
+
|
|
171
|
+
### Full virtualized unlock (QEMU)
|
|
172
|
+
|
|
173
|
+
For the *whole* process — build an encrypted image, boot it, run the real
|
|
174
|
+
initramfs Tor+dropbear chain, and unlock it — `tests/e2e/qemu/` drives a QEMU
|
|
175
|
+
virtual machine end to end and asserts the real system booted only after the
|
|
176
|
+
passphrase was delivered over Tor. It is opt-in (`LIM_E2E_QEMU=1`) and needs
|
|
177
|
+
QEMU, `arch-install-scripts`, `cryptsetup`, `tor` and `ncat`; the build stage
|
|
178
|
+
needs root (ideally inside a throwaway VM to keep the host rootless). A private,
|
|
179
|
+
offline Tor network via [chutney](https://gitlab.torproject.org/tpo/core/chutney)
|
|
180
|
+
is supported. See [tests/e2e/qemu/README.md](tests/e2e/qemu/README.md).
|
|
181
|
+
|
|
182
|
+
The harness's pure logic and drift guards run in the normal suite
|
|
183
|
+
(`test_qemu_harness_unit.py`) — no QEMU, root, or network needed.
|
|
184
|
+
|
|
114
185
|
## License 📜
|
|
115
186
|
|
|
116
187
|
This project is licensed under the GNU General Public License Version 3. See the [LICENSE.txt](./LICENSE.txt) file for details.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/ash
|
|
2
|
+
# mkinitcpio runtime hook: start Tor so the dropbear unlock shell is
|
|
3
|
+
# reachable as an onion service while encryptssh waits for the passphrase.
|
|
4
|
+
# Installed to /etc/initcpio/hooks/tor by linux-image-manager.
|
|
5
|
+
|
|
6
|
+
# netconf's ipconfig drops its DHCP lease data (incl. DNS) into
|
|
7
|
+
# /tmp/net-*.conf; busybox's resolver only reads /etc/resolv.conf.
|
|
8
|
+
_tor_write_resolv_conf() {
|
|
9
|
+
[ -s /etc/resolv.conf ] && return 0
|
|
10
|
+
local conf dns
|
|
11
|
+
for conf in /tmp/net-*.conf; do
|
|
12
|
+
[ -f "$conf" ] || continue
|
|
13
|
+
# Extract ONLY the DNS fields with sed; never source these files —
|
|
14
|
+
# they hold attacker-controllable DHCP option strings (hostname,
|
|
15
|
+
# domain, rootpath), and sourcing would run them as root pre-boot.
|
|
16
|
+
for dns in $(sed -n 's/^IPV4DNS[01]=//p' "$conf"); do
|
|
17
|
+
[ -n "$dns" ] && [ "$dns" != "0.0.0.0" ] \
|
|
18
|
+
&& echo "nameserver $dns" >> /etc/resolv.conf
|
|
19
|
+
done
|
|
20
|
+
done
|
|
21
|
+
[ -s /etc/resolv.conf ]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
run_hook() {
|
|
25
|
+
# Tor rejects consensus documents when the clock is far off; boards
|
|
26
|
+
# without an RTC boot in 1970, so sync before starting Tor. Bounded:
|
|
27
|
+
# a failed sync must never block the boot.
|
|
28
|
+
msg "tor: syncing clock via NTP..."
|
|
29
|
+
# Best-effort DNS; a tor_ntp IP literal needs none, so never gate on it.
|
|
30
|
+
_tor_write_resolv_conf || msg "tor: no DNS from DHCP (fine if tor_ntp is an IP)"
|
|
31
|
+
/usr/local/bin/busybox timeout 30 \
|
|
32
|
+
/usr/local/bin/busybox ntpd -n -q -p "${tor_ntp:-pool.ntp.org}" \
|
|
33
|
+
|| msg "tor: NTP sync failed, keeping current clock"
|
|
34
|
+
|
|
35
|
+
msg "tor: starting onion service for remote unlock..."
|
|
36
|
+
mkdir -p /var/lib/tor
|
|
37
|
+
chmod 0700 /var/lib/tor /etc/tor/onion
|
|
38
|
+
chmod 0600 /etc/tor/onion/hs_ed25519_secret_key
|
|
39
|
+
tor -f /etc/tor/torrc --RunAsDaemon 1 --Log "notice file /tmp/tor.log" \
|
|
40
|
+
|| msg "tor: failed to start, unlock stays reachable via direct IP"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
run_cleanuphook() {
|
|
44
|
+
# Nothing from the initramfs may keep running after the pivot.
|
|
45
|
+
/usr/local/bin/busybox killall tor 2>/dev/null
|
|
46
|
+
return 0
|
|
47
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# mkinitcpio install hook: Tor onion service for remote LUKS unlock.
|
|
3
|
+
# Installed to /etc/initcpio/install/tor by linux-image-manager.
|
|
4
|
+
|
|
5
|
+
build() {
|
|
6
|
+
add_binary /usr/bin/tor
|
|
7
|
+
# Full busybox for the ntpd applet: boards without an RTC (e.g. most
|
|
8
|
+
# Raspberry Pis) wake up in 1970, which Tor's consensus checks reject.
|
|
9
|
+
# A different target path keeps mkinitcpio's own busybox untouched.
|
|
10
|
+
add_binary /usr/bin/busybox /usr/local/bin/busybox
|
|
11
|
+
# glibc resolves the NTP server hostname via getaddrinfo(), which dlopen()s
|
|
12
|
+
# these NSS modules at runtime — add_binary only follows NEEDED libs, so
|
|
13
|
+
# without them DNS silently fails, ntpd never syncs, and the clock stays
|
|
14
|
+
# at 1970. glibc's built-in default (no nsswitch.conf) is "dns ... files".
|
|
15
|
+
add_binary /usr/lib/libnss_dns.so.2
|
|
16
|
+
add_binary /usr/lib/libnss_files.so.2
|
|
17
|
+
add_file /etc/tor/initramfs-torrc /etc/tor/torrc
|
|
18
|
+
add_file /etc/tor/initramfs-onion/hostname /etc/tor/onion/hostname
|
|
19
|
+
add_file /etc/tor/initramfs-onion/hs_ed25519_public_key /etc/tor/onion/hs_ed25519_public_key
|
|
20
|
+
add_file /etc/tor/initramfs-onion/hs_ed25519_secret_key /etc/tor/onion/hs_ed25519_secret_key
|
|
21
|
+
add_runscript
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
help() {
|
|
25
|
+
cat <<HELPEOF
|
|
26
|
+
Starts a Tor onion service in early userspace so the dropbear unlock
|
|
27
|
+
shell stays reachable under the .onion address baked into the image,
|
|
28
|
+
even behind NAT or a dynamic IP.
|
|
29
|
+
|
|
30
|
+
Place it between netconf and dropbear:
|
|
31
|
+
HOOKS=(... netconf tor dropbear encryptssh ...)
|
|
32
|
+
|
|
33
|
+
Optional kernel parameter:
|
|
34
|
+
tor_ntp=<server> NTP server used to set the clock before Tor starts
|
|
35
|
+
(default: pool.ntp.org)
|
|
36
|
+
HELPEOF
|
|
37
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Tor configuration for the initramfs onion unlock service.
|
|
2
|
+
# Baked into the initramfs as /etc/tor/torrc by the "tor" mkinitcpio hook;
|
|
3
|
+
# the onion keys come from /etc/tor/initramfs-onion on the system.
|
|
4
|
+
DataDirectory /var/lib/tor
|
|
5
|
+
HiddenServiceDir /etc/tor/onion
|
|
6
|
+
HiddenServicePort 22 127.0.0.1:22
|
|
7
|
+
SocksPort 0
|
|
@@ -9,6 +9,7 @@ from pathlib import Path
|
|
|
9
9
|
from lim import catalog, fsutil, packages, runner, ui
|
|
10
10
|
from lim.image.plan import ImagePlan
|
|
11
11
|
from lim.image.session import ImageSession, chroot_bash, install_packages
|
|
12
|
+
from lim.image.tor import configure_tor_unlock
|
|
12
13
|
|
|
13
14
|
MKINITCPIO_HOOKS_PREFIX = (
|
|
14
15
|
"base udev autodetect microcode modconf kms keyboard keymap consolefont block"
|
|
@@ -32,9 +33,10 @@ def _configure_mkinitcpio(plan: ImagePlan, root: Path) -> None:
|
|
|
32
33
|
fsutil.replace_in_file(
|
|
33
34
|
"BINARIES=()", "BINARIES=(/usr/lib/libgcc_s.so.1)", mkinitcpio_path
|
|
34
35
|
)
|
|
36
|
+
tor_hook = "tor " if plan.tor_unlock else ""
|
|
35
37
|
fsutil.replace_in_file(
|
|
36
38
|
f"HOOKS=({MKINITCPIO_HOOKS_PREFIX} {MKINITCPIO_HOOKS_SUFFIX})",
|
|
37
|
-
f"HOOKS=({MKINITCPIO_HOOKS_PREFIX} sleep netconf dropbear encryptssh "
|
|
39
|
+
f"HOOKS=({MKINITCPIO_HOOKS_PREFIX} sleep netconf {tor_hook}dropbear encryptssh "
|
|
38
40
|
f"{MKINITCPIO_HOOKS_SUFFIX})",
|
|
39
41
|
mkinitcpio_path,
|
|
40
42
|
)
|
|
@@ -90,9 +92,15 @@ def _configure_bootloader(plan: ImagePlan, session: ImageSession, root: Path) ->
|
|
|
90
92
|
else:
|
|
91
93
|
cmdline_txt_path = session.boot_mount_path / "cmdline.txt"
|
|
92
94
|
ui.info(f"Configuring {cmdline_txt_path}...")
|
|
95
|
+
# Firmware-boot boards (e.g. RPi4 cmdline.txt) need the same early
|
|
96
|
+
# networking as the boot.txt branch, or the initramfs netconf hook
|
|
97
|
+
# brings up no interface and remote unlock (dropbear + tor) is
|
|
98
|
+
# unreachable — the onion never even publishes.
|
|
99
|
+
hostname = (root / "etc/hostname").read_text().strip()
|
|
93
100
|
fsutil.replace_in_file(
|
|
94
101
|
"root=/dev/mmcblk0p2",
|
|
95
|
-
f"{cryptdevice} rootfstype={plan.root_filesystem}"
|
|
102
|
+
f"{cryptdevice} rootfstype={plan.root_filesystem} "
|
|
103
|
+
f"ip=::::{hostname}:eth0:dhcp net.ifnames=0 biosdevname=0",
|
|
96
104
|
cmdline_txt_path,
|
|
97
105
|
)
|
|
98
106
|
ui.info(f"Content of {cmdline_txt_path}:{cmdline_txt_path.read_text()}")
|
|
@@ -112,6 +120,10 @@ def configure_encryption(
|
|
|
112
120
|
ui.info(f"Adding {authorized_keys} to dropbear...")
|
|
113
121
|
runner.run(["cp", "-v", str(authorized_keys), str(dropbear_root_key_path)], sudo=True)
|
|
114
122
|
|
|
123
|
+
if plan.tor_unlock:
|
|
124
|
+
# Hook files and onion keys must exist before mkinitcpio bakes the image.
|
|
125
|
+
configure_tor_unlock(plan, root)
|
|
126
|
+
|
|
115
127
|
_configure_mkinitcpio(plan, root)
|
|
116
128
|
_register_encrypted_root(plan, session, root)
|
|
117
129
|
_configure_bootloader(plan, session, root)
|
|
@@ -49,6 +49,10 @@ def _choose_and_verify_image(plan: ImagePlan) -> None:
|
|
|
49
49
|
if plan.operation_system == "linux":
|
|
50
50
|
choosers.choose_linux_image(plan)
|
|
51
51
|
plan.encrypt_system = ui.confirm("Should the system be encrypted?")
|
|
52
|
+
if plan.encrypt_system:
|
|
53
|
+
plan.tor_unlock = ui.confirm(
|
|
54
|
+
"Should the system be remotely unlockable via a Tor onion service?"
|
|
55
|
+
)
|
|
52
56
|
ui.info("Generating os-image...")
|
|
53
57
|
transfer.download_image(plan)
|
|
54
58
|
else:
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Tor onion service in the initramfs for remote LUKS unlock.
|
|
2
|
+
|
|
3
|
+
The onion keys are generated offline inside the image chroot
|
|
4
|
+
(``tor --DisableNetwork 1`` writes them without touching the network)
|
|
5
|
+
and baked into the initramfs by the "tor" mkinitcpio hook, so the
|
|
6
|
+
dropbear unlock shell stays reachable under a stable .onion address
|
|
7
|
+
even behind NAT or a dynamic IP.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from lim import config, packages, runner, ui
|
|
13
|
+
from lim.errors import LimError
|
|
14
|
+
from lim.image.plan import ImagePlan
|
|
15
|
+
from lim.image.session import chroot_bash, install_packages
|
|
16
|
+
|
|
17
|
+
# Paths inside the image (relative to the mounted root).
|
|
18
|
+
ONION_DIR = "etc/tor/initramfs-onion"
|
|
19
|
+
TORRC_PATH = "etc/tor/initramfs-torrc"
|
|
20
|
+
|
|
21
|
+
# An empty -f torrc keeps the image's /etc/tor/torrc (User tor, ...) out of
|
|
22
|
+
# the keygen run; with DisableNetwork the keys appear within a second, the
|
|
23
|
+
# loop only cushions slow qemu-emulated chroots.
|
|
24
|
+
_KEYGEN_SCRIPT = f"""
|
|
25
|
+
mkdir -p /{ONION_DIR}
|
|
26
|
+
chmod 0700 /{ONION_DIR}
|
|
27
|
+
: > /tmp/tor-keygen-torrc
|
|
28
|
+
tor -f /tmp/tor-keygen-torrc --DisableNetwork 1 \\
|
|
29
|
+
--DataDirectory /tmp/tor-keygen-data \\
|
|
30
|
+
--HiddenServiceDir /{ONION_DIR} \\
|
|
31
|
+
--HiddenServicePort "22 127.0.0.1:22" \\
|
|
32
|
+
--SocksPort 0 --RunAsDaemon 0 --Log "notice stderr" &
|
|
33
|
+
tor_pid=$!
|
|
34
|
+
for _ in $(seq 1 30); do
|
|
35
|
+
[ -s /{ONION_DIR}/hostname ] && break
|
|
36
|
+
sleep 1
|
|
37
|
+
done
|
|
38
|
+
kill "$tor_pid" 2>/dev/null || true
|
|
39
|
+
rm -rf /tmp/tor-keygen-data /tmp/tor-keygen-torrc
|
|
40
|
+
[ -s /{ONION_DIR}/hostname ]
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _install_initcpio_files(root: Path) -> None:
|
|
45
|
+
source_dir = config.CONFIGURATION_PATH / "initcpio"
|
|
46
|
+
for source, target in (
|
|
47
|
+
(source_dir / "tor_install", root / "etc/initcpio/install/tor"),
|
|
48
|
+
(source_dir / "tor_hook", root / "etc/initcpio/hooks/tor"),
|
|
49
|
+
(source_dir / "torrc", root / TORRC_PATH),
|
|
50
|
+
):
|
|
51
|
+
ui.info(f"Installing {target}...")
|
|
52
|
+
runner.run(
|
|
53
|
+
["install", "-D", "-m", "0644", str(source), str(target)], sudo=True
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _generate_onion_keys(root: Path) -> str:
|
|
58
|
+
hostname_path = root / ONION_DIR / "hostname"
|
|
59
|
+
if hostname_path.is_file():
|
|
60
|
+
ui.info("Onion keys already exist, keeping the existing address.")
|
|
61
|
+
else:
|
|
62
|
+
ui.info("Generating onion service keys (offline, inside the chroot)...")
|
|
63
|
+
chroot_bash(root, _KEYGEN_SCRIPT, error_msg="Onion key generation failed.")
|
|
64
|
+
if not hostname_path.is_file():
|
|
65
|
+
raise LimError(f"Onion key generation produced no {hostname_path}.")
|
|
66
|
+
return hostname_path.read_text().strip()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def configure_tor_unlock(plan: ImagePlan, root: Path) -> str:
|
|
70
|
+
"""Install everything the "tor" mkinitcpio hook bakes in; return the onion address."""
|
|
71
|
+
ui.info("Setting up remote unlock via Tor onion service...")
|
|
72
|
+
install_packages(
|
|
73
|
+
plan.distribution, root, " ".join(packages.get_packages("server/tor"))
|
|
74
|
+
)
|
|
75
|
+
_install_initcpio_files(root)
|
|
76
|
+
onion_address = _generate_onion_keys(root)
|
|
77
|
+
ui.success(f"Onion unlock address: {onion_address}")
|
|
78
|
+
ui.info(f"Unlock later with: torsocks ssh root@{onion_address}")
|
|
79
|
+
return onion_address
|
{linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/linux_image_manager.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: linux-image-manager
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
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
5
|
Author-email: Kevin Veen-Birkenbach <kevin@veen.world>
|
|
6
6
|
Maintainer-email: Kevin Veen-Birkenbach <kevin@veen.world>
|
|
@@ -28,6 +28,7 @@ Linux Image Manager (lim) is a Python tool for downloading, configuring, and man
|
|
|
28
28
|
|
|
29
29
|
- **Image Download & Setup:** Automatically download, verify (checksum + GPG signature) and prepare Linux distributions.
|
|
30
30
|
- **Encrypted Storage:** Configure LUKS encryption for secure image management.
|
|
31
|
+
- **Tor Onion Unlock:** Optionally bake a Tor onion service into the initramfs so the dropbear LUKS unlock shell stays reachable behind NAT or dynamic IPs.
|
|
31
32
|
- **Virtual RAID1:** Easily set up virtual Btrfs RAID1 for data redundancy.
|
|
32
33
|
- **Backup & Restore:** Create image backups from devices using dd.
|
|
33
34
|
- **Chroot Environment:** Easily enter a chroot shell to maintain or modify Linux images.
|
|
@@ -44,7 +45,7 @@ package-manager install lim
|
|
|
44
45
|
|
|
45
46
|
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
|
|
|
47
|
-
|
|
48
|
+
The only Python dependency is **PyYAML** (`>=6`), used to read the image catalog. The pip/wheel install pulls it in automatically; on the `package-manager`/symlink install path make sure it is present (`python-yaml` on Arch). The commands also 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
|
|
|
49
50
|
## Usage ⚙️
|
|
50
51
|
|
|
@@ -112,6 +113,8 @@ lim/
|
|
|
112
113
|
distributions.yml # single point of truth for the image catalog
|
|
113
114
|
configuration/ # package collections used during image setup
|
|
114
115
|
tests/unit/ # unit tests (all external commands mocked)
|
|
116
|
+
tests/e2e/ # Tor onion unlock end-to-end tests (rootless + QEMU, opt-in)
|
|
117
|
+
tests/e2e/qemu/ # full virtualized build+boot+unlock harness
|
|
115
118
|
tests/lint/ # architecture guards (e.g. max file length)
|
|
116
119
|
```
|
|
117
120
|
|
|
@@ -120,15 +123,83 @@ tests/lint/ # architecture guards (e.g. max file length)
|
|
|
120
123
|
Customize your environment in the `lim/configuration/` folder:
|
|
121
124
|
- **General Packages:** Contains common packages for all setup scripts.
|
|
122
125
|
- **Server LUKS Packages:** Contains packages needed for setting up LUKS encryption on servers.
|
|
126
|
+
- **initcpio Hooks:** The mkinitcpio install/runtime hooks and torrc baked into images for the Tor onion unlock.
|
|
127
|
+
|
|
128
|
+
## Remote LUKS Unlock via Tor 🧅
|
|
129
|
+
|
|
130
|
+
When you answer yes to *"Should the system be remotely unlockable via a Tor
|
|
131
|
+
onion service?"* during an encrypted `lim --type image` setup, the image gets:
|
|
132
|
+
|
|
133
|
+
- `tor` and `busybox` installed, plus a custom `tor` mkinitcpio hook ordered
|
|
134
|
+
between `netconf` and `dropbear`.
|
|
135
|
+
- A v3 onion service key generated **offline** inside the image chroot; the
|
|
136
|
+
resulting `.onion` address is printed at the end of the setup and stays
|
|
137
|
+
stable across reboots and IP changes.
|
|
138
|
+
- An NTP clock sync in early userspace (boards like the Raspberry Pi have no
|
|
139
|
+
RTC and would otherwise boot with a clock Tor rejects). Override the server
|
|
140
|
+
with the kernel parameter `tor_ntp=<host>`.
|
|
141
|
+
|
|
142
|
+
After booting the device, unlock it from any machine with a running Tor
|
|
143
|
+
client:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
torsocks ssh root@<onion-address>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Typing the LUKS passphrase into that shell resumes the boot. The direct
|
|
150
|
+
`ssh root@<ip>` unlock keeps working as before; Tor is an additional path,
|
|
151
|
+
useful behind NAT/CGNAT where no port forwarding is possible.
|
|
152
|
+
|
|
153
|
+
**Security note:** the onion private key is stored in the initramfs on the
|
|
154
|
+
unencrypted boot partition. Anyone with physical access to the SD card can
|
|
155
|
+
read it and impersonate the onion service — treat the address as
|
|
156
|
+
non-secret. Access control remains the SSH public key
|
|
157
|
+
(`/etc/dropbear/root_key`), exactly as with the direct unlock.
|
|
123
158
|
|
|
124
159
|
## Development & Tests 🧪
|
|
125
160
|
|
|
126
|
-
The
|
|
161
|
+
The unit suite mocks all external commands, so it runs safely on any machine:
|
|
127
162
|
|
|
128
163
|
```bash
|
|
129
164
|
pytest
|
|
130
165
|
```
|
|
131
166
|
|
|
167
|
+
### Tor unlock end-to-end test
|
|
168
|
+
|
|
169
|
+
`tests/e2e/test_tor_unlock_e2e.py` reproduces the operator-visible half of the
|
|
170
|
+
Tor decryption process **rootless** — no block devices, no `cryptsetup`, no
|
|
171
|
+
`chroot`. It generates the v3 onion keys offline (exactly as `lim.image.tor`
|
|
172
|
+
does inside the image), starts a real Tor onion service from a torrc mirroring
|
|
173
|
+
the baked-in one, then connects to the `.onion` address through Tor and
|
|
174
|
+
delivers a passphrase to a dropbear stand-in, asserting it arrives and the
|
|
175
|
+
endpoint "unlocks".
|
|
176
|
+
|
|
177
|
+
It needs the real `tor` binary and live Tor network access (onion bootstrap is
|
|
178
|
+
slow), so it is opt-in and skipped otherwise:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
LIM_E2E_TOR=1 pytest tests/e2e/test_tor_unlock_e2e.py -v
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The offline-only checks in that file (onion keygen, production-flag guard) run
|
|
185
|
+
without network. The physical flashing half (`dd`, `cryptsetup luksFormat`,
|
|
186
|
+
`mount`, `chroot`, `mkinitcpio`) requires root and a matching CPU/qemu setup and
|
|
187
|
+
is not covered by this rootless test.
|
|
188
|
+
|
|
189
|
+
### Full virtualized unlock (QEMU)
|
|
190
|
+
|
|
191
|
+
For the *whole* process — build an encrypted image, boot it, run the real
|
|
192
|
+
initramfs Tor+dropbear chain, and unlock it — `tests/e2e/qemu/` drives a QEMU
|
|
193
|
+
virtual machine end to end and asserts the real system booted only after the
|
|
194
|
+
passphrase was delivered over Tor. It is opt-in (`LIM_E2E_QEMU=1`) and needs
|
|
195
|
+
QEMU, `arch-install-scripts`, `cryptsetup`, `tor` and `ncat`; the build stage
|
|
196
|
+
needs root (ideally inside a throwaway VM to keep the host rootless). A private,
|
|
197
|
+
offline Tor network via [chutney](https://gitlab.torproject.org/tpo/core/chutney)
|
|
198
|
+
is supported. See [tests/e2e/qemu/README.md](tests/e2e/qemu/README.md).
|
|
199
|
+
|
|
200
|
+
The harness's pure logic and drift guards run in the normal suite
|
|
201
|
+
(`test_qemu_harness_unit.py`) — no QEMU, root, or network needed.
|
|
202
|
+
|
|
132
203
|
## License 📜
|
|
133
204
|
|
|
134
205
|
This project is licensed under the GNU General Public License Version 3. See the [LICENSE.txt](./LICENSE.txt) file for details.
|
{linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/linux_image_manager.egg-info/SOURCES.txt
RENAMED
|
@@ -14,8 +14,12 @@ lim/packages.py
|
|
|
14
14
|
lim/runner.py
|
|
15
15
|
lim/system.py
|
|
16
16
|
lim/ui.py
|
|
17
|
+
lim/configuration/initcpio/tor_hook
|
|
18
|
+
lim/configuration/initcpio/tor_install
|
|
19
|
+
lim/configuration/initcpio/torrc
|
|
17
20
|
lim/configuration/packages/general.txt
|
|
18
21
|
lim/configuration/packages/server/luks.txt
|
|
22
|
+
lim/configuration/packages/server/tor.txt
|
|
19
23
|
lim/data/__init__.py
|
|
20
24
|
lim/data/crypt.py
|
|
21
25
|
lim/data/sync.py
|
|
@@ -28,6 +32,7 @@ lim/image/plan.py
|
|
|
28
32
|
lim/image/raspberry.py
|
|
29
33
|
lim/image/session.py
|
|
30
34
|
lim/image/setup.py
|
|
35
|
+
lim/image/tor.py
|
|
31
36
|
lim/image/transfer.py
|
|
32
37
|
lim/image/verify.py
|
|
33
38
|
lim/storage/__init__.py
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "linux-image-manager"
|
|
8
|
-
version = "2.
|
|
8
|
+
version = "2.2.0"
|
|
9
9
|
description = "CLI (`lim`) for downloading, verifying and flashing Linux images, LUKS/Btrfs encrypted storage (single drive and RAID1), image backups and chroot maintenance"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.10"
|
|
@@ -28,7 +28,11 @@ lim = "lim.cli:main"
|
|
|
28
28
|
include = ["lim*"]
|
|
29
29
|
|
|
30
30
|
[tool.setuptools.package-data]
|
|
31
|
-
lim = [
|
|
31
|
+
lim = [
|
|
32
|
+
"distributions.yml",
|
|
33
|
+
"configuration/packages/**/*.txt",
|
|
34
|
+
"configuration/initcpio/*",
|
|
35
|
+
]
|
|
32
36
|
|
|
33
37
|
[tool.pytest.ini_options]
|
|
34
38
|
testpaths = ["tests"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/configuration/packages/general.txt
RENAMED
|
File without changes
|
{linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/lim/configuration/packages/server/luks.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/linux_image_manager.egg-info/requires.txt
RENAMED
|
File without changes
|
{linux_image_manager-2.1.0 → linux_image_manager-2.2.0}/linux_image_manager.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|