Skip to content

Commit 22cf54d

Browse files
authored
Merge pull request #89 from desultory/dev
Add resume support, update plymouth support, improve mounts, simplify and expand tests, add basic bcachefs support
2 parents 1926985 + 1862334 commit 22cf54d

24 files changed

+534
-356
lines changed

.github/workflows/ubuntu.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,20 @@ permissions:
1515
jobs:
1616
build:
1717
runs-on: ubuntu-latest
18+
strategy:
19+
matrix:
20+
python-version: ['3.11', '3.12']
21+
1822
steps:
1923
- uses: actions/checkout@v4
2024
- name: Obtain dependency projects
2125
run: |
2226
git clone https://github.com/desultory/zenlib
2327
git clone https://github.com/desultory/pycpio
24-
- name: Set up Python 3.11
28+
- name: Set up Python ${{ matrix.python-version }}
2529
uses: actions/setup-python@v5
2630
with:
27-
python-version: "3.11"
31+
python-version: ${{ matrix.python-version }}
2832
- name: Install system deps
2933
run: |
3034
sudo apt update
@@ -38,5 +42,5 @@ jobs:
3842
venv/bin/pip install .
3943
- name: Test fullauto.toml
4044
run: |
41-
sudo venv/bin/python -m unittest discover tests
4245
sudo venv/bin/ugrd -d
46+
sudo venv/bin/python -m unittest discover tests -v

docs/configuration.md

Lines changed: 71 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ Modules write to a shared config dict that is accessible by other modules.
3232

3333
### base.core
3434

35-
* `build_dir` (/tmp/initramfs) Defines where the build will take place.
36-
* `out_dir` (/tmp/initramfs_out) Defines where packed files will be placed.
35+
* `tmpdir` (/tmp) Sets the temporary directory as the base for the build and out dir.
36+
* `build_dir` (initramfs_build) If relative, it will be placed under `tmpdir`, defines the build directory.
37+
* `out_dir` (initramfs_out) If relative, it will be placed under `tmpdir`, defines the output directory.
3738
* `out_file` Sets the name of the output file, under `out_dir` unless a path is defined.
3839
* `clean` (true) forces the build dir to be cleaned on each run.
3940
* `hostonly` (true) Builds the initramfs for the current host, if disabled, validation is automatically disabled.
@@ -43,6 +44,57 @@ Modules write to a shared config dict that is accessible by other modules.
4344
* `binaries` - A list used to define programs to be pulled into the initrams. `which` is used to find the path of added entries, and `lddtree` is used to resolve dependendies.
4445
* `paths` - A list of directores to create in the `build_dir`. They do not need a leading `/`.
4546

47+
#### Copying files
48+
49+
Using the `dependencies` list will pull files into the initramfs using the same path on the host system.
50+
51+
```
52+
dependencies = [ "/etc/ugrd/testfile" ]
53+
```
54+
55+
#### Copying files to a different destination
56+
57+
To copy files to a different path:
58+
59+
```
60+
[copies.my_key]
61+
source = "/home/larry/.gnupg/pubkey.gpg"
62+
destination = "/etc/ugrd/pub.gpg"
63+
```
64+
65+
#### symlink creation
66+
67+
Symlinks are defined in the `symlinks` dict. Each entry must have a name, `source` and `target`:
68+
69+
```
70+
[symlinks.pinentry]
71+
source = "/usr/bin/pinentry-tty"
72+
target = "/usr/bin/pinentry"
73+
```
74+
75+
##### Device node creation
76+
77+
Device nodes can be created by defining them in the `nodes` dict using the following keys:
78+
79+
* `mode` (0o600) the device node, in octal.
80+
* `path` (/dev/node name) the path to create the node at.
81+
* `major` - Major value.
82+
* `minor` - Minor value.
83+
84+
Example:
85+
86+
```
87+
[nodes.console]
88+
mode = 0o644
89+
major = 5
90+
minor = 1
91+
```
92+
93+
Creates `/dev/console` with permissions `0o644`
94+
95+
> Using `mknod_cpio` from `ugrd.fs.cpio` will not create the device nodes in the build dir, but within the CPIO archive
96+
97+
4698
### base.cmdline
4799

48100
If used, this module will override the `mount_root` function and attempt to mount the root based on the passed cmdline parameters.
@@ -111,15 +163,28 @@ Similarly `ugrd.kmod.novideo` `nonetwork`, and `nosound` exist to ignore video,
111163

112164
### Filesystem modules
113165

166+
`ugrd.fs.mounts` is the core of the filesystem module category and is included by default.
167+
168+
Additional modules include:
169+
170+
`ugrd.fs.btrfs` - Helps with multi-device BTRFS mounts, subvolume selection.
171+
`ugrd.fs.fakeudev` - Makes 'fake' udev entries for DM devices.
172+
`ugrd.fs.cpio` - Packs the build dir into a CPIO archive with PyCPIO.
173+
`ugrd.fs.livecd` - Assists in livecd image creation.
174+
`ugrd.fs.lvm` - Activates LVM mounts.
175+
`ugrd.fs.mdraid` - For MDRAID mounts.
176+
`ugrd.fs.resume` - Handles resume from hibernation.
177+
`ugrd.fs.test_image` - Creates a test rootfs for automated testing.
178+
179+
#### ugrd.fs.mounts
180+
114181
`autodetect_root` (true) Set the root mount parameter based on the current root label or uuid.
115182
`autodetect_root_dm` (true) Attempt to automatically configure virtual block devices such as LUKS/LVM/MDRAID.
116183
`autodetect_root_luks` (true) Attempt to automatically configure LUKS mounts for the root device.
117184
`autodetect_root_lvm` (true) Attempt to automatically configure LVM mounts for the root device.
118185
`autodetect_root_mdraid` (true) Attempt to automatically configure MDRAID mounts for the root device.
119186
`autodetect_init_mount'` (true) Automatically detect the mountpoint for the init binary, and add it to `late_mounts`.
120187

121-
#### ugrd.fs.mounts
122-
123188
`mounts`: A dictionary containing entries for mounts, with their associated config.
124189

125190
`mounts.root` is predefined to have a destination of `/target_rootfs` and defines the root filesystem mount, used by `switch_root`.
@@ -193,55 +258,9 @@ Importing this module will run `btrfs device scan` and pull btrfs modules.
193258
* `root_subvol` - Set the desired root subvolume.
194259
* `_base_mount_path` (/root_base) Sets where the subvolume selector mounts the base filesytem to scan for subvolumes.
195260

196-
#### symlink creation
197-
198-
Symlinks are defined in the `symlinks` dict. Each entry must have a name, `source` and `target`:
199-
200-
```
201-
[symlinks.pinentry]
202-
source = "/usr/bin/pinentry-tty"
203-
target = "/usr/bin/pinentry"
204-
```
261+
#### ugrd.fs.resume
205262

206-
#### Copying files
207-
208-
Using the `dependencies` list will pull files into the initramfs using the same path on the host system.
209-
210-
```
211-
dependencies = [ "/etc/ugrd/testfile" ]
212-
```
213-
214-
#### Copying files to a different destination
215-
216-
To copy files to a different path:
217-
218-
```
219-
[copies.my_key]
220-
source = "/home/larry/.gnupg/pubkey.gpg"
221-
destination = "/etc/ugrd/pub.gpg"
222-
```
223-
224-
##### Device node creation
225-
226-
Device nodes can be created by defining them in the `nodes` dict using the following keys:
227-
228-
* `mode` (0o600) the device node, in octal.
229-
* `path` (/dev/node name) the path to create the node at.
230-
* `major` - Major value.
231-
* `minor` - Minor value.
232-
233-
Example:
234-
235-
```
236-
[nodes.console]
237-
mode = 0o644
238-
major = 5
239-
minor = 1
240-
```
241-
242-
Creates `/dev/console` with permissions `0o644`
243-
244-
> Using `mknod_cpio` from `ugrd.fs.cpio` will not create the device nodes in the build dir, but within the CPIO archive
263+
When enabled, attempts to resume after hibernation if resume= is passed on the kernel command line.
245264

246265
### Cryptographic modules
247266

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "ugrd"
7-
version = "1.25.0"
7+
version = "1.26.0"
88
authors = [
99
{ name="Desultory", email="[email protected]" },
1010
]

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ The original goal of this project was to create an initramfs suitable for decryp
5656
- Hardlinks are automatically created for files with matching SHA256 hashes
5757
- Automatic xz compression
5858
* ZSH and BASH autocompletion for the `ugrd` command
59+
* Basic hibernation/resume support with `ugrd.fs.resume`
5960
* Similar usage/arguments as Dracut
6061

6162
## Support

src/ugrd/base/base.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
__author__ = "desultory"
2-
__version__ = "5.1.1"
2+
__version__ = "5.1.3"
33

44
from importlib.metadata import version
55
from pathlib import Path
@@ -132,7 +132,7 @@ def rd_fail(self) -> list[str]:
132132
]
133133
if "ugrd.base.plymouth" in self["modules"]:
134134
output += [
135-
" if check_var plymouth; then",
135+
" if plymouth --ping; then",
136136
' plymouth display-message --text="Entering recovery shell"',
137137
" plymouth hide-splash",
138138
" bash -l",
@@ -189,7 +189,7 @@ def prompt_user(self) -> list[str]:
189189
output = ['prompt=${1:-"Press enter to continue."}']
190190
if "ugrd.base.plymouth" in self["modules"]:
191191
output += [
192-
"if check_var plymouth; then",
192+
"if plymouth --ping; then",
193193
' plymouth display-message --text="$prompt"',
194194
"else",
195195
r' echo -e "\e[1;35m *\e[0m $prompt"',
@@ -252,7 +252,18 @@ def edebug(self) -> list[str]:
252252

253253
def einfo(self) -> list[str]:
254254
"""Returns a bash function like einfo."""
255-
return ["if check_var quiet; then", " return", "fi", r'echo -e "\e[1;32m *\e[0m ${*}"']
255+
if "ugrd.base.plymouth" in self["modules"]:
256+
output = [
257+
"if plymouth --ping; then",
258+
' plymouth display-message --text="${*}"',
259+
" return",
260+
"fi",
261+
]
262+
else:
263+
output = []
264+
265+
output += ["if check_var quiet; then", " return", "fi", r'echo -e "\e[1;32m *\e[0m ${*}"']
266+
return output
256267

257268

258269
def ewarn(self) -> list[str]:
@@ -261,7 +272,7 @@ def ewarn(self) -> list[str]:
261272
"""
262273
if "ugrd.base.plymouth" in self["modules"]:
263274
output = [
264-
"if check_var plymouth; then", # Always show the message if plymouth is running
275+
"if plymouth --ping; then", # Always show the message if plymouth is running
265276
' plymouth display-message --text="Warning: ${*}"',
266277
" return", # Return early so echo doesn't leak
267278
"fi",
@@ -282,7 +293,7 @@ def eerror(self) -> str:
282293
"""Returns a bash function like eerror."""
283294
if "ugrd.base.plymouth" in self["modules"]:
284295
return [
285-
"if check_var plymouth; then",
296+
"if plymouth --ping; then",
286297
' plymouth display-message --text="Error: ${*}"',
287298
" return",
288299
"fi",

src/ugrd/base/cmdline.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
__author__ = "desultory"
2-
__version__ = "2.5.0"
2+
__version__ = "2.6.1"
33

44

55
def parse_cmdline_bool(self) -> list[str]:
@@ -61,11 +61,20 @@ def mount_cmdline_root(self) -> list[str]:
6161

6262

6363
def export_exports(self) -> list[str]:
64-
"""Returns a bash script exporting all exports defined in the exports key."""
64+
"""Returns a bash script exporting all exports defined in the exports key.
65+
Sets 'exported' to 1 once done.
66+
If 'exported' is set, returns early.
67+
"""
6568
from importlib.metadata import PackageNotFoundError, version
6669

6770
try:
6871
self["exports"]["VERSION"] = version(__package__.split(".")[0])
6972
except PackageNotFoundError:
7073
self["exports"]["VERSION"] = 9999
71-
return [f'setvar {key} "{value}"' for key, value in self["exports"].items()]
74+
75+
check_lines = ["if check_var exported; then",
76+
' edebug "Exports already set, skipping"',
77+
" return", "fi"]
78+
export_lines = [f'setvar "{key}" "{value}"' for key, value in self["exports"].items()]
79+
80+
return check_lines + export_lines + ["setvar exported 1"]

src/ugrd/base/core.py

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
__author__ = "desultory"
2-
__version__ = "3.9.1"
2+
__version__ = "3.9.2"
33

44
from pathlib import Path
55
from typing import Union
@@ -172,7 +172,8 @@ def find_libgcc(self) -> None:
172172
try:
173173
ldconfig = self._run(["ldconfig", "-p"]).stdout.decode().split("\n")
174174
except RuntimeError:
175-
return self.logger.critical("Unable to run ldconfig -p, if GCC is being used, this is fatal!")
175+
self.logger.critical("Unable to run ldconfig -p, if GCC is being used, this is fatal!")
176+
return self.logger.critical("This check can be disabled by setting `find_libgcc = false` in the configuration.")
176177

177178
libgcc = [lib for lib in ldconfig if "libgcc_s" in lib and "(libc6," in lib][0]
178179
source_path = Path(libgcc.partition("=> ")[-1])
@@ -182,25 +183,37 @@ def find_libgcc(self) -> None:
182183
self["library_paths"] = str(source_path.parent)
183184

184185

185-
def _process_out_file(self, out_file):
186-
"""Processes the out_file configuration option."""
187-
if Path(out_file).is_dir():
188-
self.logger.info("Specified out_file is a directory, setting out_dir: %s" % out_file)
189-
self["out_dir"] = out_file
186+
def _process_out_file(self, out_file: str) -> None:
187+
"""Processes the out_file.
188+
If set to the current directory, resolves and sets the out_dir to the current directory.
189+
If it starts with './', resolves it to the current directory.
190+
If it is a directory, sets the out_dir to the directory.
191+
If it's an absolute path, sets the out_dir to the parent directory.
192+
"""
193+
out_file = str(out_file)
194+
if out_file == "./" or out_file == ".":
195+
current_dir = Path(".").resolve()
196+
self.logger.info("Setting out_dir to current directory: %s" % current_dir)
197+
self["out_dir"] = current_dir
190198
return
191199

192200
if out_file.startswith("./"):
193-
self.logger.debug("Relative out_file path detected: %s" % out_file)
194-
self["out_dir"] = Path(".").resolve()
195-
self.logger.info("Resolved out_dir to: %s" % self["out_dir"])
196-
out_file = Path(out_file[2:])
197-
elif Path(out_file).parent.is_dir() and str(Path(out_file).parent) != ".":
198-
self["out_dir"] = Path(out_file).parent
199-
self.logger.info("Resolved out_dir to: %s" % self["out_dir"])
200-
out_file = Path(out_file).name
201+
cwd = Path(".").resolve()
202+
self.logger.info("Resolved relative out_file path: %s" % cwd)
203+
out_file = cwd / out_file[2:]
201204
else:
202205
out_file = Path(out_file)
203206

207+
if out_file.is_dir():
208+
self.logger.info("Specified out_file is a directory, setting out_dir: %s" % out_file)
209+
self["out_dir"] = out_file
210+
return
211+
212+
if out_file.parent.is_dir() and str(out_file.parent) != ".":
213+
self["out_dir"] = out_file.parent
214+
self.logger.info("Resolved out_dir to: %s" % self["out_dir"])
215+
out_file = out_file.name
216+
204217
self.data["out_file"] = out_file
205218

206219

src/ugrd/base/core.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
tmpdir = "/tmp"
22
build_dir = "initramfs_build"
33
_build_log_level = 10
4-
out_dir = "/tmp/initramfs_out"
4+
out_dir = "initramfs_out"
55
clean = true
66
find_libgcc = true
77
hostonly = true

0 commit comments

Comments
 (0)