Skip to content

Commit e7bf2ed

Browse files
authored
Merge pull request #78 from desultory/plymouth
Add plymouth support, improve dependency resolution
2 parents 36e845b + d798e16 commit e7bf2ed

File tree

9 files changed

+162
-65
lines changed

9 files changed

+162
-65
lines changed

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.24.2"
7+
version = "1.25.0"
88
authors = [
99
{ name="Desultory", email="[email protected]" },
1010
]

src/ugrd/base/base.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
__author__ = 'desultory'
2-
__version__ = '4.9.1'
2+
__version__ = '5.1.0'
33

44
from importlib.metadata import version
55
from pathlib import Path
@@ -117,7 +117,14 @@ def rd_fail(self) -> list[str]:
117117
r'eerror "Mounts:\n$(mount)"',
118118
'if [ "$(readvar recovery)" == "1" ]; then',
119119
' einfo "Entering recovery shell"',
120-
' bash -l',
120+
' if check_var plymouth; then',
121+
' plymouth display-message --text="Entering recovery shell"',
122+
' plymouth hide-splash',
123+
' bash -l',
124+
' plymouth show-splash',
125+
' else',
126+
' bash -l',
127+
' fi',
121128
'fi',
122129
'prompt_user "Press enter to restart init."',
123130
'rd_restart']
@@ -163,9 +170,15 @@ def prompt_user(self) -> str:
163170
Returns a bash function that pauses until the user presses enter.
164171
The first argument is the prompt message.
165172
The second argument is the timeout in seconds.
173+
174+
if plymouth is running, run 'plymouth display-message --text="$prompt" instead of echo.
166175
"""
167176
return ['prompt=${1:-"Press enter to continue."}',
168-
r'echo -e "\e[1;35m *\e[0m $prompt"',
177+
'if check_var plymouth; then',
178+
' plymouth display-message --text="$prompt"',
179+
'else',
180+
r' echo -e "\e[1;35m *\e[0m $prompt"',
181+
'fi',
169182
'if [ -n "$2" ]; then',
170183
' read -t "$2" -rs',
171184
'else',
@@ -210,29 +223,35 @@ def edebug(self) -> str:
210223
'if [ "$(readvar debug)" != "1" ]; then',
211224
' return',
212225
'fi',
213-
r'echo -e "\e[1;34m *\e[0m ${*}"'
214-
]
226+
r'echo -e "\e[1;34m *\e[0m ${*}"']
215227

216228

217229
def einfo(self) -> str:
218230
""" Returns a bash function like einfo. """
219231
return ['if check_var quiet; then',
220232
' return',
221233
'fi',
222-
r'echo -e "\e[1;32m *\e[0m ${*}"'
223-
]
234+
r'echo -e "\e[1;32m *\e[0m ${*}"']
224235

225236

226237
def ewarn(self) -> str:
227238
""" Returns a bash function like ewarn. """
228-
return ['if check_var quiet; then',
239+
return ['if check_var plymouth; then',
240+
' plymouth display-message --text="Warning: ${*}"',
241+
' return',
242+
'fi',
243+
'if check_var quiet; then',
229244
' return',
230245
'fi',
231246
r'echo -e "\e[1;33m *\e[0m ${*}"']
232247

233248

234249
def eerror(self) -> str:
235250
""" Returns a bash function like eerror. """
236-
return r'echo -e "\e[1;31m *\e[0m ${*}"'
251+
return ['if check_var plymouth; then',
252+
' plymouth display-message --text="Error: ${*}"',
253+
' return',
254+
'fi',
255+
r'echo -e "\e[1;31m *\e[0m ${*}"']
237256

238257

src/ugrd/base/core.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
__author__ = 'desultory'
2-
__version__ = '3.8.0'
2+
__version__ = '3.9.1'
33

44
from pathlib import Path
55
from typing import Union
@@ -263,9 +263,15 @@ def _process_dependencies_multi(self, dependency: Union[Path, str]) -> None:
263263
"""
264264
dependency = _validate_dependency(self, dependency)
265265

266-
if dependency.is_symlink():
266+
if dependency.is_dir():
267+
self.logger.debug("Dependency is a directory, adding to paths: %s" % dependency)
268+
self['paths'] = dependency
269+
return
270+
271+
while dependency.is_symlink():
267272
if self['symlinks'].get(f'_auto_{dependency.name}'):
268273
self.logger.log(5, "Dependency is a symlink which is already in the symlinks list, skipping: %s" % dependency)
274+
break
269275
else:
270276
resolved_path = dependency.resolve()
271277
self.logger.debug("Dependency is a symlink, adding to symlinks: %s -> %s" % (dependency, resolved_path))
@@ -328,7 +334,7 @@ def _process_copies_multi(self, name: str, parameters: dict) -> None:
328334
if 'source' not in parameters:
329335
raise ValueError("[%s] No source specified" % name)
330336
if 'destination' not in parameters:
331-
raise ValueError("[%s] No target specified" % name)
337+
raise ValueError("[%s] No destination specified" % name)
332338

333339
self.logger.debug("[%s] Adding copies: %s" % (name, parameters))
334340
self['copies'][name] = parameters

src/ugrd/base/plymouth.py

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,67 @@
1+
from zenlib.util import unset
2+
from configparser import ConfigParser
3+
from pathlib import Path
14

5+
PLYMOUTH_CONFIG_FILES = ['/etc/plymouth/plymouthd.conf', '/usr/share/plymouth/plymouthd.defaults']
26

3-
def populate_initrd(self):
4-
"""
5-
Runs /usr/libexec/plymouth/plymouth-populate-initrd
6-
"""
7-
self._run(['/usr/libexec/plymouth/plymouth-populate-initrd', '-t', self.build_dir])
7+
8+
@unset('plymouth_config')
9+
def find_plymouth_config(self):
10+
""" Adds the plymouth config files to the build directory """
11+
self.logger.info("Finding plymouthd.conf")
12+
for file in PLYMOUTH_CONFIG_FILES:
13+
plymouth_config = ConfigParser()
14+
plymouth_config.read(file)
15+
if plymouth_config.has_section('Daemon') and plymouth_config.has_option('Daemon', 'Theme'):
16+
self['plymouth_config'] = file
17+
break
18+
self.logger.debug("Plymouth config file missing theme option: %s" % file)
19+
else:
20+
raise FileNotFoundError('Failed to find plymouthd.conf')
21+
22+
23+
def _process_plymouth_config(self, file):
24+
""" Checks that the config file is valid """
25+
self.logger.info("Processing plymouthd.conf: %s" % file)
26+
plymouth_config = ConfigParser()
27+
plymouth_config.read(file)
28+
self['plymouth_theme'] = plymouth_config['Daemon']['Theme']
29+
self.data['plymouth_config'] = file
30+
self['copies'] = {'plymouth_config_file': {'source': file, 'destination': '/etc/plymouth/plymouthd.conf'}}
31+
32+
33+
def _process_plymouth_theme(self, theme):
34+
""" Checks that the theme is valid """
35+
theme_dir = Path('/usr/share/plymouth/themes') / theme
36+
if not theme_dir.exists():
37+
raise FileNotFoundError('Theme directory not found: %s' % theme_dir)
38+
39+
self.data['plymouth_theme'] = theme
40+
41+
42+
def pull_plymouth(self):
43+
""" Adds plymouth files to dependencies """
44+
dir_list = [Path('/usr/lib64/plymouth'), Path('/usr/share/plymouth/themes/') / self["plymouth_theme"]]
45+
self.logger.info("Adding plymouth files to dependencies.")
46+
for directory in dir_list:
47+
for file in directory.rglob('*'):
48+
self['dependencies'] = file
49+
50+
self['dependencies'] = ['/usr/share/plymouth/themes/text/text.plymouth',
51+
'/usr/share/plymouth/themes/details/details.plymouth']
52+
53+
54+
def make_devpts(self):
55+
""" Creates /dev/pts and mounts the fstab entry """
56+
return ['mkdir -m755 -p /dev/pts',
57+
'mount /dev/pts']
858

959

1060
def start_plymouth(self):
1161
"""
1262
Runs plymouthd
1363
"""
14-
return ['plymouthd --attach-to-session --pid-file /run/plymouth/pid --mode=boot', 'plymouth show-splash']
64+
return ['mkdir -p /run/plymouth',
65+
'plymouthd --mode boot --pid-file /run/plymouth/plymouth.pid --attach-to-session',
66+
'setvar plymouth 1',
67+
'plymouth show-splash']

src/ugrd/base/plymouth.toml

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
1+
binaries = ['plymouthd', 'plymouth', 'plymouth-set-default-theme']
12

2-
binaries = ['plymouthd', 'plymouth']
3-
4-
paths = ['/run/plymouth']
3+
[imports.config_processing]
4+
"ugrd.base.plymouth" = [ "_process_plymouth_config", "_process_plymouth_theme" ]
55

66
[mounts.devpts]
77
type = "devpts"
88
destination = "/dev/pts"
9-
source = "/dev/pts"
9+
options = ['noauto', 'nosuid', 'noexec', 'mode=0620', 'gid=5']
10+
path = "/dev/pts"
11+
no_validate = true
1012

1113
[imports.build_pre]
12-
"ugrd.base.plymouth" = [ "populate_initrd" ]
14+
"ugrd.base.plymouth" = [ "find_plymouth_config" ]
15+
16+
[imports.build_tasks]
17+
"ugrd.base.plymouth" = [ "pull_plymouth" ]
18+
19+
[imports.init_early]
20+
"ugrd.base.plymouth" = [ "make_devpts" ]
1321

1422
[imports.init_main]
1523
"ugrd.base.plymouth" = [ "start_plymouth" ]
24+
25+
[custom_parameters]
26+
plymouth_config = "Path" # Path to the plymouth configuration file
27+
plymouth_theme = "str" # Name of the plymouth theme to use

src/ugrd/crypto/cryptsetup.py

Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
__author__ = 'desultory'
2-
__version__ = '2.9.0'
2+
__version__ = '3.2.0'
33

44
from zenlib.util import contains
55

66
from pathlib import Path
77

88
_module_name = 'ugrd.crypto.cryptsetup'
99

10-
CRYPTSETUP_PARAMETERS = ['key_type', 'partuuid', 'uuid', 'path',
11-
'key_file', 'header_file', 'retries',
12-
'key_command', 'reset_command', 'try_nokey',
13-
'include_key', 'validate_key', 'validate']
10+
11+
CRYPTSETUP_KEY_PARAMETERS = ['key_command', 'plymouth_key_command', 'reset_command']
12+
CRYPTSETUP_PARAMETERS = ['key_type', 'partuuid', 'uuid', 'path', 'key_file', 'header_file', 'retries',
13+
*CRYPTSETUP_KEY_PARAMETERS, 'try_nokey', 'include_key', 'validate_key']
14+
1415

1516

1617
def _merge_cryptsetup(self, mapped_name: str, config: dict) -> None:
@@ -30,7 +31,7 @@ def _process_cryptsetup_key_types_multi(self, key_type: str, config: dict) -> No
3031
"""
3132
self.logger.debug("[%s] Processing cryptsetup key type configuration: %s" % (key_type, config))
3233
for parameter in config:
33-
if parameter not in CRYPTSETUP_PARAMETERS:
34+
if parameter not in CRYPTSETUP_KEY_PARAMETERS:
3435
raise ValueError("Invalid parameter: %s" % parameter)
3536

3637
# Update the key if it already exists, otherwise create a new key type
@@ -115,7 +116,7 @@ def _process_cryptsetup_multi(self, mapped_name: str, config: dict) -> None:
115116
config['key_type'] = key_type
116117

117118
# Inherit from the key type configuration
118-
for parameter in ['key_command', 'reset_command']:
119+
for parameter in CRYPTSETUP_KEY_PARAMETERS:
119120
if value := self['cryptsetup_key_types'][key_type].get(parameter):
120121
config[parameter] = value.format(**config)
121122

@@ -282,51 +283,58 @@ def open_crypt_device(self, name: str, parameters: dict) -> list[str]:
282283
out = [f"prompt_user 'Press enter to unlock device: {name}'"] if self['cryptsetup_prompt'] else []
283284
out += [f"for ((i = 1; i <= {retries}; i++)); do"]
284285

285-
# When there is a key command, read from the named pipe and use that as the key
286+
# Resolve the device source using get_crypt_dev, if no source is returned, run rd_fail
287+
out += [f' crypt_dev="$(get_crypt_dev {name})"',
288+
' if [ -z "$crypt_dev" ]; then',
289+
f' rd_fail "Failed to resolve device source for cryptsetup mount: {name}"',
290+
' fi']
291+
292+
# When there is a key command, evaluate it into $key_data
286293
if 'key_command' in parameters:
287294
self.logger.debug("[%s] Using key command: %s" % (name, parameters['key_command']))
288295
out += [f" einfo 'Attempting to open LUKS key: {parameters['key_file']}'",
289-
f" edebug 'Using key command: {parameters['key_command']}'"]
290-
cryptsetup_command = f'{parameters["key_command"]} | cryptsetup open --key-file -'
291-
elif 'key_file' in parameters:
292-
self.logger.debug("[%s] Using key file: %s" % (name, parameters['key_file']))
293-
cryptsetup_command = f'cryptsetup open --key-file {parameters["key_file"]}'
294-
else:
295-
# Set tries to 1 since it runs in the loop
296-
cryptsetup_command = 'cryptsetup open --tries 1'
296+
f" edebug 'Using key command: {parameters['key_command']}'",
297+
' if check_var plymouth; then',
298+
f' plymouth ask-for-password --prompt "[${{i}} / {retries}] Enter passphrase to unlock key for: {name}" --command "{parameters["plymouth_key_command"]}" --number-of-tries 1 > /run/vars/key_data || continue',
299+
' else',
300+
f' {parameters["key_command"]} > /run/vars/key_data || continue',
301+
' fi']
297302

298-
# Add the header file if it exists
299-
if header_file := parameters.get('header_file'):
303+
cryptsetup_command = 'cryptsetup open --tries 1' # Set tries to 1 since it runs in the loop
304+
cryptsetup_target = f'"$crypt_dev" {name}' # Add a variable for the source device and mapped name
305+
306+
if header_file := parameters.get('header_file'): # Use the header file if it exists
300307
out += [f" einfo 'Using header file: {header_file}'"]
301308
cryptsetup_command += f' --header {header_file}'
302309

303310
if self['cryptsetup_trim']:
304311
cryptsetup_command += ' --allow-discards'
305312
self.logger.warning("Using --allow-discards can be a security risk.")
306313

307-
# Resolve the device source using get_crypt_dev, if no source is returned, run rd_fail
308-
out += [f'crypt_dev="$(get_crypt_dev {name})"',
309-
'if [ -z "$crypt_dev" ]; then',
310-
f' rd_fail "Failed to resolve device source for cryptsetup mount: {name}"',
311-
'fi']
312-
313-
# Add the variable for the source device and mapped name
314-
cryptsetup_command += f' "$crypt_dev" {name}'
315-
316314
# Check if the device was successfully opened
317-
out += [f' if {cryptsetup_command}; then',
318-
f' einfo "Successfully opened device: {name}"',
319-
' break',
320-
' else',
321-
f' ewarn "Failed to open device: {name} ($i / {retries})"']
315+
out += [' einfo "Unlocking device: $crypt_dev"', # Unlock using key data if it exists
316+
' if [ -e /run/vars/key_data ]; then',
317+
f' if {cryptsetup_command} --key-file=/run/vars/key_data {cryptsetup_target}; then',
318+
' rm /run/vars/key_data', # Remove the key data file
319+
' break',
320+
' fi', # Try to open the device using plymouth if it's running
321+
' rm /run/vars/key_data', # Remove the key data file
322+
f' elif check_var plymouth && plymouth ask-for-password --prompt "[${{i}} / {retries}] Enter passphrase to unlock {name}" --command "{cryptsetup_command} {cryptsetup_target}" --number-of-tries 1; then',
323+
' break'] # Break if the device was successfully opened
324+
if 'key_file' in parameters: # try a key file directly if it exists
325+
out += [f' elif {cryptsetup_command} --key-file {parameters["key_file"]} {cryptsetup_target}; then']
326+
else: # Otherwise, open directly
327+
out += [f' elif {cryptsetup_command} {cryptsetup_target}; then']
328+
out += [' break',
329+
' fi',
330+
f' ewarn "Failed to open device: {name} ($i / {retries})"']
322331
# Halt if the autoretry is disabled
323332
if not self['cryptsetup_autoretry']:
324-
out += [' prompt_user "Press enter to retry"']
333+
out += [' prompt_user "Press enter to retry"']
325334
# Add the reset command if it exists
326335
if reset_command := parameters.get('reset_command'):
327-
out += [' einfo "Running key reset command"',
328-
f' {reset_command}']
329-
out += [' fi']
336+
out += [' einfo "Running key reset command"',
337+
f' {reset_command}']
330338
out += ['done\n']
331339

332340
return out

src/ugrd/crypto/cryptsetup.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ cryptsetup_keyfile_validation = true
2222
[imports.functions]
2323
"ugrd.crypto.cryptsetup" = [ "get_crypt_dev" ]
2424

25-
[cryptsetup_key_types.keyfile]
26-
key_command = "cat {key_file} |"
27-
2825
[custom_parameters]
2926
cryptsetup_key_type = "str" # The default key type to use for unlocking devices
3027
cryptsetup_keyfile_validation = "bool" # Whether to validate the key file

src/ugrd/crypto/gpg.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
modules = [ "ugrd.base.console", "ugrd.crypto.cryptsetup" ]
22

3-
binaries = [ "/usr/bin/gpg", "/usr/bin/gpg-agent", "/usr/bin/gpgconf", "/usr/bin/gpg-connect-agent", "/usr/bin/pinentry-tty"]
3+
binaries = [ "/usr/bin/gpg", "/usr/bin/gpg-agent", "/usr/bin/gpgconf", "/usr/bin/gpg-connect-agent", "/usr/bin/pinentry-tty",
4+
"rm" ] # rm needed to remove the decrypted key file
45
opt_dependencies = [ '/usr/libexec/keyboxd' ] # Pull keyboxd in as an optional dependency
56

67

78
[cryptsetup_key_types.gpg]
89
key_command = "gpg --decrypt {key_file}"
10+
plymouth_key_command = "gpg --batch --pinentry-mode loopback --passphrase-fd 0 --decrypt {key_file}"
911

1012
[symlinks.pinentry]
1113
source = "/usr/bin/pinentry-tty"

src/ugrd/fs/mounts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def _process_mount(self, mount_name: str, mount_config, mount_class="mounts") ->
8585
if 'ugrd.fs.btrfs' not in self['modules']:
8686
self.logger.info("Auto-enabling module: btrfs")
8787
self['modules'] = 'ugrd.fs.btrfs'
88-
elif mount_type not in ['proc', 'sysfs', 'devtmpfs', 'tmpfs']:
88+
elif mount_type not in ['proc', 'sysfs', 'devtmpfs', 'tmpfs', 'devpts']:
8989
self.logger.warning("Unknown mount type: %s" % mount_type)
9090

9191
self[mount_class][mount_name] = mount_config

0 commit comments

Comments
 (0)