mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-06-12 12:45:27 +03:00
nixos/limine: init module (#386368)
This commit is contained in:
commit
c39e50acb0
10 changed files with 863 additions and 0 deletions
|
@ -129,6 +129,9 @@ nixos/modules/installer/tools/nix-fallback-paths.nix @NixOS/nix-team @raitobeza
|
|||
# Systemd-boot
|
||||
/nixos/modules/system/boot/loader/systemd-boot @JulienMalka
|
||||
|
||||
# Limine
|
||||
/nixos/modules/system/boot/loader/limine @lzcunt
|
||||
|
||||
# Images and installer media
|
||||
/nixos/modules/profiles/installation-device.nix @ElvishJerricco
|
||||
/nixos/modules/installer/cd-dvd/ @ElvishJerricco
|
||||
|
|
|
@ -19070,6 +19070,11 @@
|
|||
githubId = 74465;
|
||||
name = "James Fargher";
|
||||
};
|
||||
programmerlexi = {
|
||||
name = "programmerlexi";
|
||||
github = "programmerlexi";
|
||||
githubId = 60185691;
|
||||
};
|
||||
progrm_jarvis = {
|
||||
email = "mrjarviscraft+nix@gmail.com";
|
||||
github = "JarvisCraft";
|
||||
|
|
|
@ -183,6 +183,8 @@
|
|||
|
||||
- [Rebuilderd](https://github.com/kpcyrd/rebuilderd) an independent verification of binary packages - Reproducible Builds. Available as [services.rebuilderd](#opt-services.rebuilderd.enable).
|
||||
|
||||
- [Limine](https://github.com/limine-bootloader/limine) a modern, advanced, portable, multiprotocol bootloader and boot manager. Available as [boot.loader.limine](#opt-boot.loader.limine.enable)
|
||||
|
||||
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
|
||||
|
||||
## Backward Incompatibilities {#sec-release-25.05-incompatibilities}
|
||||
|
|
|
@ -1719,6 +1719,7 @@
|
|||
./system/boot/loader/grub/memtest.nix
|
||||
./system/boot/loader/external/external.nix
|
||||
./system/boot/loader/init-script/init-script.nix
|
||||
./system/boot/loader/limine/limine.nix
|
||||
./system/boot/loader/loader.nix
|
||||
./system/boot/loader/systemd-boot/systemd-boot.nix
|
||||
./system/boot/luksroot.nix
|
||||
|
|
409
nixos/modules/system/boot/loader/limine/limine-install.py
Normal file
409
nixos/modules/system/boot/loader/limine/limine-install.py
Normal file
|
@ -0,0 +1,409 @@
|
|||
#!@python3@/bin/python3 -B
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import psutil
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import textwrap
|
||||
|
||||
|
||||
limine_dir = None
|
||||
can_use_direct_paths = False
|
||||
install_config = json.load(open('@configPath@', 'r'))
|
||||
|
||||
|
||||
def config(*path: List[str]) -> Optional[Any]:
|
||||
result = install_config
|
||||
for component in path:
|
||||
result = result[component]
|
||||
return result
|
||||
|
||||
|
||||
def get_system_path(profile: str = 'system', gen: Optional[str] = None, spec: Optional[str] = None) -> str:
|
||||
if profile == 'system':
|
||||
result = os.path.join('/nix', 'var', 'nix', 'profiles', 'system')
|
||||
else:
|
||||
result = os.path.join('/nix', 'var', 'nix', 'profiles', 'system-profiles', profile + f'-{gen}-link' if gen is not None else '')
|
||||
|
||||
if spec is not None:
|
||||
result = os.path.join(result, 'specialisation', spec)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_profiles() -> List[str]:
|
||||
profiles_dir = '/nix/var/nix/profiles/system-profiles/'
|
||||
dirs = os.listdir(profiles_dir) if os.path.isdir(profiles_dir) else []
|
||||
|
||||
return [path for path in dirs if not path.endswith('-link')]
|
||||
|
||||
|
||||
def get_gens(profile: str = 'system') -> List[Tuple[int, List[str]]]:
|
||||
nix_env = os.path.join(config('nixPath'), 'bin', 'nix-env')
|
||||
output = subprocess.check_output([
|
||||
nix_env, '--list-generations',
|
||||
'-p', get_system_path(profile),
|
||||
'--option', 'build-users-group', '',
|
||||
], universal_newlines=True)
|
||||
|
||||
gen_lines = output.splitlines()
|
||||
gen_nums = [int(line.split()[0]) for line in gen_lines]
|
||||
|
||||
return [gen for gen in gen_nums][-config('maxGenerations'):]
|
||||
|
||||
|
||||
def is_encrypted(device: str) -> bool:
|
||||
for name, _ in config('luksDevices').items():
|
||||
if os.readlink(os.path.join('/dev/mapper', name)) == os.readlink(device):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def is_fs_type_supported(fs_type: str) -> bool:
|
||||
return fs_type.startswith('vfat')
|
||||
|
||||
|
||||
def get_copied_path_uri(path: str, target: str) -> str:
|
||||
result = ''
|
||||
|
||||
package_id = os.path.basename(os.path.dirname(path))
|
||||
suffix = os.path.basename(path)
|
||||
dest_file = f'{package_id}-{suffix}'
|
||||
dest_path = os.path.join(limine_dir, target, dest_file)
|
||||
|
||||
if not os.path.exists(dest_path):
|
||||
copy_file(path, dest_path)
|
||||
|
||||
path_with_prefix = os.path.join('/limine', target, dest_file)
|
||||
result = f'boot():{path_with_prefix}'
|
||||
|
||||
if config('validateChecksums'):
|
||||
with open(path, 'rb') as file:
|
||||
b2sum = hashlib.blake2b()
|
||||
b2sum.update(file.read())
|
||||
|
||||
result += f'#{b2sum.hexdigest()}'
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_path_uri(path: str) -> str:
|
||||
return get_copied_path_uri(path, "")
|
||||
|
||||
|
||||
def get_file_uri(profile: str, gen: Optional[str], spec: Optional[str], name: str) -> str:
|
||||
gen_path = get_system_path(profile, gen, spec)
|
||||
path_in_store = os.path.realpath(os.path.join(gen_path, name))
|
||||
return get_path_uri(path_in_store)
|
||||
|
||||
|
||||
def get_kernel_uri(kernel_path: str) -> str:
|
||||
return get_copied_path_uri(kernel_path, "kernels")
|
||||
|
||||
|
||||
@dataclass
|
||||
class BootSpec:
|
||||
system: str
|
||||
init: str
|
||||
kernel: str
|
||||
kernelParams: List[str]
|
||||
label: str
|
||||
toplevel: str
|
||||
specialisations: Dict[str, "BootSpec"]
|
||||
initrd: str | None = None
|
||||
initrdSecrets: str | None = None
|
||||
|
||||
|
||||
def bootjson_to_bootspec(bootjson: dict) -> BootSpec:
|
||||
specialisations = bootjson['org.nixos.specialisation.v1']
|
||||
specialisations = {k: bootjson_to_bootspec(v) for k, v in specialisations.items()}
|
||||
return BootSpec(
|
||||
**bootjson['org.nixos.bootspec.v1'],
|
||||
specialisations=specialisations,
|
||||
)
|
||||
|
||||
|
||||
def config_entry(levels: int, bootspec: BootSpec, label: str, time: str) -> str:
|
||||
entry = '/' * levels + label + '\n'
|
||||
entry += 'protocol: linux\n'
|
||||
entry += f'comment: {bootspec.label}, built on {time}\n'
|
||||
entry += 'kernel_path: ' + get_kernel_uri(bootspec.kernel) + '\n'
|
||||
entry += 'cmdline: ' + ' '.join(['init=' + bootspec.init] + bootspec.kernelParams).strip() + '\n'
|
||||
if bootspec.initrd:
|
||||
entry += f'module_path: ' + get_kernel_uri(bootspec.initrd) + '\n'
|
||||
|
||||
if bootspec.initrdSecrets:
|
||||
initrd_secrets_path = limine_dir + '/kernels/' + os.path.basename(toplevel) + '-secrets'
|
||||
os.makedirs(initrd_secrets_path)
|
||||
|
||||
old_umask = os.umask()
|
||||
os.umask(0o137)
|
||||
initrd_secrets_path_temp = tempfile.mktemp(os.path.basename(toplevel) + '-secrets')
|
||||
|
||||
if os.system(bootspec.initrdSecrets + " " + initrd_secrets_path_temp) != 0:
|
||||
print(f'warning: failed to create initrd secrets for "{label}"', file=sys.stderr)
|
||||
print(f'note: if this is an older generation there is nothing to worry about')
|
||||
|
||||
if os.path.exists(initrd_secrets_path_temp):
|
||||
copy_file(initrd_secrets_path_temp, initrd_secrets_path)
|
||||
os.unlink(initrd_secrets_path_temp)
|
||||
entry += 'module_path: ' + get_kernel_uri(initrd_secrets_path) + '\n'
|
||||
|
||||
os.umask(old_umask)
|
||||
return entry
|
||||
|
||||
|
||||
def generate_config_entry(profile: str, gen: str) -> str:
|
||||
time = datetime.datetime.fromtimestamp(os.stat(get_system_path(profile,gen), follow_symlinks=False).st_mtime).strftime("%F %H:%M:%S")
|
||||
boot_json = json.load(open(os.path.join(get_system_path(profile, gen), 'boot.json'), 'r'))
|
||||
boot_spec = bootjson_to_bootspec(boot_json)
|
||||
|
||||
entry = config_entry(2, boot_spec, f'Generation {gen}', time)
|
||||
for spec in boot_spec.specialisations:
|
||||
entry += config_entry(2, boot_spec, f'Generation {gen}, Specialisation {spec}', str(time))
|
||||
return entry
|
||||
|
||||
|
||||
def find_disk_device(part: str) -> str:
|
||||
part = os.path.realpath(part)
|
||||
part = part.removeprefix('/dev/')
|
||||
disk = os.path.realpath(os.path.join('/sys', 'class', 'block', part))
|
||||
disk = os.path.dirname(disk)
|
||||
|
||||
return os.path.join('/dev', os.path.basename(disk))
|
||||
|
||||
|
||||
def find_mounted_device(path: str) -> str:
|
||||
path = os.path.abspath(path)
|
||||
|
||||
while not os.path.ismount(path):
|
||||
path = os.path.dirname(path)
|
||||
|
||||
devices = [x for x in psutil.disk_partitions(all=True) if x.mountpoint == path]
|
||||
|
||||
assert len(devices) == 1
|
||||
return devices[0].device
|
||||
|
||||
|
||||
def copy_file(from_path: str, to_path: str):
|
||||
dirname = os.path.dirname(to_path)
|
||||
|
||||
if not os.path.exists(dirname):
|
||||
os.makedirs(dirname)
|
||||
|
||||
shutil.copyfile(from_path, to_path)
|
||||
|
||||
def option_from_config(name: str, config_path: List[str], conversion: Callable[[str], str] | None = None) -> str:
|
||||
if config(*config_path):
|
||||
return f'{name}: {conversion(config(*config_path)) if conversion else config(*config_path)}\n'
|
||||
return ''
|
||||
|
||||
|
||||
def main():
|
||||
global limine_dir
|
||||
|
||||
boot_fs = None
|
||||
|
||||
for mount_point, fs in config('fileSystems').items():
|
||||
if mount_point == '/boot':
|
||||
boot_fs = fs
|
||||
|
||||
if config('efiSupport'):
|
||||
limine_dir = os.path.join(config('efiMountPoint'), 'limine')
|
||||
elif boot_fs and is_fs_type_supported(boot_fs['fsType']) and not is_encrypted(boot_fs['device']):
|
||||
limine_dir = '/boot/limine'
|
||||
else:
|
||||
possible_causes = []
|
||||
if not boot_fs:
|
||||
possible_causes.append(f'/limine on the boot partition (not present)')
|
||||
else:
|
||||
is_boot_fs_type_ok = is_fs_type_supported(boot_fs['fsType'])
|
||||
is_boot_fs_encrypted = is_encrypted(boot_fs['device'])
|
||||
possible_causes.append(f'/limine on the boot partition ({is_boot_fs_type_ok=} {is_boot_fs_encrypted=})')
|
||||
|
||||
causes_str = textwrap.indent('\n'.join(possible_causes), ' - ')
|
||||
|
||||
raise Exception(textwrap.dedent('''
|
||||
Could not find a valid place for Limine configuration files!'
|
||||
Possible candidates that were ruled out:
|
||||
''') + causes_str + textwrap.dedent('''
|
||||
Limine cannot be installed on a system without an unencrypted
|
||||
partition formatted as FAT.
|
||||
'''))
|
||||
|
||||
if not os.path.exists(limine_dir):
|
||||
os.makedirs(limine_dir)
|
||||
|
||||
if os.path.exists(os.path.join(limine_dir, 'kernels')):
|
||||
print(f'nuking {os.path.join(limine_dir, "kernels")}')
|
||||
shutil.rmtree(os.path.join(limine_dir, 'kernels'))
|
||||
|
||||
os.makedirs(os.path.join(limine_dir, "kernels"))
|
||||
|
||||
profiles = [('system', get_gens())]
|
||||
|
||||
for profile in get_profiles():
|
||||
profiles += (profile, get_gens(profile))
|
||||
|
||||
timeout = config('timeout')
|
||||
editor_enabled = 'yes' if config('enableEditor') else 'no'
|
||||
hash_mismatch_panic = 'yes' if config('panicOnChecksumMismatch') else 'no'
|
||||
|
||||
config_file = config('extraConfig') + '\n'
|
||||
config_file += textwrap.dedent(f'''
|
||||
timeout: {timeout}
|
||||
editor_enabled: {editor_enabled}
|
||||
hash_mismatch_panic: {hash_mismatch_panic}
|
||||
graphics: yes
|
||||
default_entry: 2
|
||||
''')
|
||||
|
||||
if os.path.exists(os.path.join(limine_dir, 'wallpapers')):
|
||||
print(f'nuking {os.path.join(limine_dir, "wallpapers")}')
|
||||
shutil.rmtree(os.path.join(limine_dir, 'wallpapers'))
|
||||
|
||||
if len(config('style', 'wallpapers')) > 0:
|
||||
os.makedirs(os.path.join(limine_dir, 'wallpapers'))
|
||||
|
||||
for wallpaper in config('style', 'wallpapers'):
|
||||
config_file += f'''wallpaper: {get_copied_path_uri(wallpaper, 'wallpapers')}\n'''
|
||||
|
||||
config_file += option_from_config('wallpaper_style', ['style', 'wallpaperStyle'])
|
||||
config_file += option_from_config('backdrop', ['style', 'backdrop'])
|
||||
|
||||
config_file += option_from_config('interface_resolution', ['style', 'interface', 'resolution'])
|
||||
config_file += option_from_config('interface_branding', ['style', 'interface', 'branding'])
|
||||
config_file += option_from_config('interface_branding_colour', ['style', 'interface', 'brandingColor'])
|
||||
config_file += option_from_config('interface_help_hidden', ['style', 'interface', 'helpHidden'])
|
||||
config_file += option_from_config('term_font_scale', ['style', 'graphicalTerminal', 'font', 'scale'])
|
||||
config_file += option_from_config('term_font_spacing', ['style', 'graphicalTerminal', 'font', 'spacing'])
|
||||
config_file += option_from_config('term_palette', ['style', 'graphicalTerminal', 'palette'])
|
||||
config_file += option_from_config('term_palette_bright', ['style', 'graphicalTerminal', 'brightPalette'])
|
||||
config_file += option_from_config('term_foreground', ['style', 'graphicalTerminal', 'foreground'])
|
||||
config_file += option_from_config('term_background', ['style', 'graphicalTerminal', 'background'])
|
||||
config_file += option_from_config('term_foreground_bright', ['style', 'graphicalTerminal', 'brightForeground'])
|
||||
config_file += option_from_config('term_background_bright', ['style', 'graphicalTerminal', 'brightBackground'])
|
||||
config_file += option_from_config('term_margin', ['style', 'graphicalTerminal', 'margin'])
|
||||
config_file += option_from_config('term_margin_gradient', ['style', 'graphicalTerminal', 'marginGradient'])
|
||||
|
||||
config_file += textwrap.dedent('''
|
||||
# NixOS boot entries start here
|
||||
''')
|
||||
|
||||
for (profile, gens) in profiles:
|
||||
group_name = 'default profile' if profile == 'system' else f"profile '{profile}'"
|
||||
config_file += f'/+NixOS {group_name}\n'
|
||||
|
||||
for gen in sorted(gens, key=lambda x: x, reverse=True):
|
||||
config_file += generate_config_entry(profile, gen)
|
||||
|
||||
config_file_path = os.path.join(limine_dir, 'limine.conf')
|
||||
config_file += '\n# NixOS boot entries end here\n\n'
|
||||
|
||||
config_file += config('extraEntries')
|
||||
|
||||
with open(config_file_path, 'w') as file:
|
||||
file.truncate()
|
||||
file.write(config_file.strip())
|
||||
|
||||
for dest_path, source_path in config('additionalFiles').items():
|
||||
dest_path = os.path.join(limine_dir, dest_path)
|
||||
|
||||
copy_file(source_path, dest_path)
|
||||
|
||||
limine_binary = os.path.join(config('liminePath'), 'bin', 'limine')
|
||||
cpu_family = config('hostArchitecture', 'family')
|
||||
if config('efiSupport'):
|
||||
if cpu_family == 'x86':
|
||||
if config('hostArchitecture', 'bits') == 32:
|
||||
boot_file = 'BOOTIA32.EFI'
|
||||
elif config('hostArchitecture', 'bits') == 64:
|
||||
boot_file = 'BOOTX64.EFI'
|
||||
elif cpu_family == 'arm':
|
||||
if config('hostArchitecture', 'arch') == 'armv8-a' and config('hostArchitecture', 'bits') == 64:
|
||||
boot_file = 'BOOTAA64.EFI'
|
||||
else:
|
||||
raise Exception(f'Unsupported CPU arch: {config("hostArchitecture", "arch")}')
|
||||
else:
|
||||
raise Exception(f'Unsupported CPU family: {cpu_family}')
|
||||
|
||||
efi_path = os.path.join(config('liminePath'), 'share', 'limine', boot_file)
|
||||
dest_path = os.path.join(config('efiMountPoint'), 'efi', 'boot' if config('efiRemovable') else 'limine', boot_file)
|
||||
|
||||
copy_file(efi_path, dest_path)
|
||||
|
||||
if config('enrollConfig'):
|
||||
b2sum = hashlib.blake2b()
|
||||
b2sum.update(config_file.strip().encode())
|
||||
try:
|
||||
subprocess.run([limine_binary, 'enroll-config', dest_path, b2sum.hexdigest()])
|
||||
except:
|
||||
print('error: failed to enroll limine config.', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if not config('efiRemovable') and not config('canTouchEfiVariables'):
|
||||
print('warning: boot.loader.efi.canTouchEfiVariables is set to false while boot.loader.limine.efiInstallAsRemovable.\n This may render the system unbootable.')
|
||||
|
||||
if config('canTouchEfiVariables'):
|
||||
if config('efiRemovable'):
|
||||
print('note: boot.loader.limine.efiInstallAsRemovable is true, no need to add EFI entry.')
|
||||
else:
|
||||
efibootmgr = os.path.join(config('efiBootMgrPath'), 'bin', 'efibootmgr')
|
||||
efi_partition = find_mounted_device(config('efiMountPoint'))
|
||||
efi_disk = find_disk_device(efi_partition)
|
||||
efibootmgr_output = subprocess.check_output([
|
||||
efibootmgr,
|
||||
'-c',
|
||||
'-d', efi_disk,
|
||||
'-p', efi_partition.removeprefix(efi_disk).removeprefix('p'),
|
||||
'-l', f'\\efi\\limine\\{boot_file}',
|
||||
'-L', 'Limine',
|
||||
], stderr=subprocess.STDOUT, universal_newlines=True)
|
||||
|
||||
for line in efibootmgr_output.split('\n'):
|
||||
if matches := re.findall(r'Boot([0-9a-fA-F]{4}) has same label Limine', line):
|
||||
subprocess.run(
|
||||
[efibootmgr, '-b', matches[0], '-B'],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
if config('biosSupport'):
|
||||
if cpu_family != 'x86':
|
||||
raise Exception(f'Unsupported CPU family for BIOS install: {cpu_family}')
|
||||
|
||||
limine_sys = os.path.join(config('liminePath'), 'share', 'limine', 'limine-bios.sys')
|
||||
limine_sys_dest = os.path.join(limine_dir, 'limine-bios.sys')
|
||||
|
||||
copy_file(limine_sys, limine_sys_dest)
|
||||
|
||||
device = config('biosDevice')
|
||||
|
||||
if device == 'nodev':
|
||||
print("note: boot.loader.limine.biosSupport is set, but device is set to nodev, only the stage 2 bootloader will be installed.", file=sys.stderr)
|
||||
return
|
||||
else:
|
||||
limine_deploy_args = [limine_binary, 'bios-install', device]
|
||||
|
||||
if config('partitionIndex'):
|
||||
limine_deploy_args.append(config('partitionIndex'))
|
||||
|
||||
if config('forceMbr'):
|
||||
limine_deploy_args += '--force-mbr'
|
||||
try:
|
||||
subprocess.run(limine_deploy_args)
|
||||
except:
|
||||
raise Exception(
|
||||
'Failed to deploy BIOS stage 1 Limine bootloader!\n' +
|
||||
'You might want to try enabling the `boot.loader.limine.forceMbr` option.')
|
||||
|
||||
main()
|
371
nixos/modules/system/boot/loader/limine/limine.nix
Normal file
371
nixos/modules/system/boot/loader/limine/limine.nix
Normal file
|
@ -0,0 +1,371 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.boot.loader.limine;
|
||||
efi = config.boot.loader.efi;
|
||||
limineInstallConfig = pkgs.writeText "limine-install.json" (
|
||||
builtins.toJSON {
|
||||
nixPath = config.nix.package;
|
||||
efiBootMgrPath = pkgs.efibootmgr;
|
||||
liminePath = cfg.package;
|
||||
efiMountPoint = efi.efiSysMountPoint;
|
||||
fileSystems = config.fileSystems;
|
||||
luksDevices = config.boot.initrd.luks.devices;
|
||||
canTouchEfiVariables = efi.canTouchEfiVariables;
|
||||
efiSupport = cfg.efiSupport;
|
||||
efiRemovable = cfg.efiInstallAsRemovable;
|
||||
biosSupport = cfg.biosSupport;
|
||||
biosDevice = cfg.biosDevice;
|
||||
partitionIndex = cfg.partitionIndex;
|
||||
forceMbr = cfg.forceMbr;
|
||||
enrollConfig = cfg.enrollConfig;
|
||||
style = cfg.style;
|
||||
maxGenerations = if cfg.maxGenerations == null then 0 else cfg.maxGenerations;
|
||||
hostArchitecture = pkgs.stdenv.hostPlatform.parsed.cpu;
|
||||
timeout = if config.boot.loader.timeout != null then config.boot.loader.timeout else 10;
|
||||
enableEditor = cfg.enableEditor;
|
||||
extraConfig = cfg.extraConfig;
|
||||
extraEntries = cfg.extraEntries;
|
||||
additionalFiles = cfg.additionalFiles;
|
||||
validateChecksums = cfg.validateChecksums;
|
||||
panicOnChecksumMismatch = cfg.panicOnChecksumMismatch;
|
||||
}
|
||||
);
|
||||
defaultWallpaper = pkgs.nixos-artwork.wallpapers.simple-dark-gray-bootloader.gnomeFilePath;
|
||||
in
|
||||
{
|
||||
meta.maintainers = with lib.maintainers; [
|
||||
lzcunt
|
||||
phip1611
|
||||
programmerlexi
|
||||
];
|
||||
|
||||
options.boot.loader.limine = {
|
||||
enable = lib.mkEnableOption "the Limine Bootloader";
|
||||
|
||||
enableEditor = lib.mkEnableOption null // {
|
||||
description = ''
|
||||
Whether to allow editing the boot entries before booting them.
|
||||
It is recommended to set this to false, as it allows gaining root
|
||||
access by passing `init=/bin/sh` as a kernel parameter.
|
||||
'';
|
||||
};
|
||||
|
||||
maxGenerations = lib.mkOption {
|
||||
default = null;
|
||||
example = 50;
|
||||
type = lib.types.nullOr lib.types.int;
|
||||
description = ''
|
||||
Maximum number of latest generations in the boot menu.
|
||||
Useful to prevent boot partition of running out of disk space.
|
||||
`null` means no limit i.e. all generations that were not
|
||||
garbage collected yet.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = lib.mkOption {
|
||||
default = "";
|
||||
type = lib.types.lines;
|
||||
example = lib.literalExpression ''
|
||||
serial: yes
|
||||
'';
|
||||
description = ''
|
||||
A string which is prepended to limine.conf. The config format can be found [here](https://github.com/limine-bootloader/limine/blob/trunk/CONFIG.md).
|
||||
'';
|
||||
};
|
||||
|
||||
extraEntries = lib.mkOption {
|
||||
default = "";
|
||||
type = lib.types.lines;
|
||||
example = lib.literalExpression ''
|
||||
/memtest86
|
||||
protocol: chainload
|
||||
path: boot():///efi/memtest86/memtest86.efi
|
||||
'';
|
||||
description = ''
|
||||
A string which is appended to the end of limine.conf. The config format can be found [here](https://github.com/limine-bootloader/limine/blob/trunk/CONFIG.md).
|
||||
'';
|
||||
};
|
||||
|
||||
additionalFiles = lib.mkOption {
|
||||
default = { };
|
||||
type = lib.types.attrsOf lib.types.path;
|
||||
example = lib.literalExpression ''
|
||||
{ "efi/memtest86/memtest86.efi" = "''${pkgs.memtest86-efi}/BOOTX64.efi"; }
|
||||
'';
|
||||
description = ''
|
||||
A set of files to be copied to {file}`/boot`. Each attribute name denotes the
|
||||
destination file name in {file}`/boot`, while the corresponding attribute value
|
||||
specifies the source file.
|
||||
'';
|
||||
};
|
||||
|
||||
validateChecksums = lib.mkEnableOption null // {
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to validate file checksums before booting.
|
||||
'';
|
||||
};
|
||||
|
||||
panicOnChecksumMismatch = lib.mkEnableOption null // {
|
||||
description = ''
|
||||
Whether or not checksum validation failure should be a fatal
|
||||
error at boot time.
|
||||
'';
|
||||
};
|
||||
|
||||
package = lib.mkPackageOption pkgs "limine" { };
|
||||
|
||||
efiSupport = lib.mkEnableOption null // {
|
||||
default = pkgs.stdenv.hostPlatform.isEfi;
|
||||
defaultText = lib.literalExpression "pkgs.stdenv.hostPlatform.isEfi";
|
||||
description = ''
|
||||
Whether or not to install the limine EFI files.
|
||||
'';
|
||||
};
|
||||
|
||||
efiInstallAsRemovable = lib.mkEnableOption null // {
|
||||
default = !efi.canTouchEfiVariables;
|
||||
defaultText = lib.literalExpression "!config.boot.loader.efi.canTouchEfiVariables";
|
||||
description = ''
|
||||
Whether or not to install the limine EFI files as removable.
|
||||
|
||||
See {option}`boot.loader.grub.efiInstallAsRemovable`
|
||||
'';
|
||||
};
|
||||
|
||||
biosSupport = lib.mkEnableOption null // {
|
||||
default = !cfg.efiSupport && pkgs.stdenv.hostPlatform.isx86;
|
||||
defaultText = lib.literalExpression "!config.boot.loader.limine.efiSupport && pkgs.stdenv.hostPlatform.isx86";
|
||||
description = ''
|
||||
Whether or not to install limine for BIOS.
|
||||
'';
|
||||
};
|
||||
|
||||
biosDevice = lib.mkOption {
|
||||
default = "nodev";
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
Device to install the BIOS version of limine on.
|
||||
'';
|
||||
};
|
||||
|
||||
partitionIndex = lib.mkOption {
|
||||
default = null;
|
||||
type = lib.types.nullOr lib.types.int;
|
||||
description = ''
|
||||
The 1-based index of the dedicated partition for limine's second stage.
|
||||
'';
|
||||
};
|
||||
|
||||
enrollConfig = lib.mkEnableOption null // {
|
||||
default = cfg.panicOnChecksumMismatch;
|
||||
defaultText = lib.literalExpression "boot.loader.limine.panicOnChecksumMismatch";
|
||||
description = ''
|
||||
Whether or not to enroll the config.
|
||||
Only works on EFI!
|
||||
'';
|
||||
};
|
||||
|
||||
forceMbr = lib.mkEnableOption null // {
|
||||
description = ''
|
||||
Force MBR detection to work even if the safety checks fail, use absolutely only if necessary!
|
||||
'';
|
||||
};
|
||||
|
||||
style = {
|
||||
wallpapers = lib.mkOption {
|
||||
default = [ ];
|
||||
example = lib.literalExpression "[ pkgs.nixos-artwork.wallpapers.simple-dark-gray-bootloader.gnomeFilePath ]";
|
||||
type = lib.types.listOf lib.types.path;
|
||||
description = ''
|
||||
A list of wallpapers.
|
||||
If more than one is specified, a random one will be selected at boot.
|
||||
'';
|
||||
};
|
||||
|
||||
wallpaperStyle = lib.mkOption {
|
||||
default = "streched";
|
||||
type = lib.types.enum [
|
||||
"centered"
|
||||
"streched"
|
||||
"tiled"
|
||||
];
|
||||
description = ''
|
||||
How the wallpaper should be fit to the screen.
|
||||
'';
|
||||
};
|
||||
|
||||
backdrop = lib.mkOption {
|
||||
default = null;
|
||||
example = "7EBAE4";
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
Color to fill the rest of the screen with when wallpaper_style is centered in RRGGBB format.
|
||||
'';
|
||||
};
|
||||
|
||||
interface = {
|
||||
resolution = lib.mkOption {
|
||||
default = null;
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
The resolution of the interface.
|
||||
'';
|
||||
};
|
||||
|
||||
branding = lib.mkOption {
|
||||
default = null;
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
The title at the top of the screen.
|
||||
'';
|
||||
};
|
||||
|
||||
brandingColor = lib.mkOption {
|
||||
default = null;
|
||||
type = lib.types.nullOr lib.types.int;
|
||||
description = ''
|
||||
Color index of the title at the top of the screen in the range of 0-7 (Limine defaults to 6 (cyan)).
|
||||
'';
|
||||
};
|
||||
|
||||
helpHidden = lib.mkEnableOption null // {
|
||||
description = ''
|
||||
Whether or not to hide the keybinds at the top of the screen.
|
||||
'';
|
||||
};
|
||||
};
|
||||
graphicalTerminal = {
|
||||
font = {
|
||||
scale = lib.mkOption {
|
||||
default = null;
|
||||
example = lib.literalExpression "2x2";
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
The scale of the font in the format <width>x<height>.
|
||||
'';
|
||||
};
|
||||
|
||||
spacing = lib.mkOption {
|
||||
default = null;
|
||||
type = lib.types.nullOr lib.types.int;
|
||||
description = ''
|
||||
The horizontal spacing between characters in pixels.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
palette = lib.mkOption {
|
||||
default = null;
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
A ; seperated array of 8 colors in the format RRGGBB:
|
||||
black, red, green, brown, blue, magenta, cyan, and gray.
|
||||
'';
|
||||
};
|
||||
|
||||
brightPalette = lib.mkOption {
|
||||
default = null;
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
A ; seperated array of 8 colors in the format RRGGBB:
|
||||
dark gray, bright red, bright green, yellow, bright blue, bright magenta, bright cyan, and white.
|
||||
'';
|
||||
};
|
||||
|
||||
foreground = lib.mkOption {
|
||||
default = null;
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
Text foreground color (RRGGBB).
|
||||
'';
|
||||
};
|
||||
|
||||
background = lib.mkOption {
|
||||
default = null;
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
Text background color (TTRRGGBB). TT is transparency.
|
||||
'';
|
||||
};
|
||||
|
||||
brightForeground = lib.mkOption {
|
||||
default = null;
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
Text foreground bright color (RRGGBB).
|
||||
'';
|
||||
};
|
||||
|
||||
brightBackground = lib.mkOption {
|
||||
default = null;
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
Text background bright color (RRGGBB).
|
||||
'';
|
||||
};
|
||||
|
||||
margin = lib.mkOption {
|
||||
default = null;
|
||||
type = lib.types.nullOr lib.types.int;
|
||||
description = ''
|
||||
The amount of margin around the terminal.
|
||||
'';
|
||||
};
|
||||
|
||||
marginGradient = lib.mkOption {
|
||||
default = null;
|
||||
type = lib.types.nullOr lib.types.int;
|
||||
description = ''
|
||||
The thickness in pixels for the margin around the terminal.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkMerge [
|
||||
{
|
||||
boot.loader.limine.style.wallpapers = lib.mkDefault [ defaultWallpaper ];
|
||||
}
|
||||
(lib.mkIf (cfg.style.wallpapers == [ defaultWallpaper ]) {
|
||||
boot.loader.limine.style.backdrop = lib.mkDefault "2F302F";
|
||||
boot.loader.limine.style.wallpaperStyle = lib.mkDefault "streched";
|
||||
})
|
||||
(lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion =
|
||||
pkgs.stdenv.hostPlatform.isx86_64
|
||||
|| pkgs.stdenv.hostPlatform.isi686
|
||||
|| pkgs.stdenv.hostPlatform.isAarch64;
|
||||
message = "Limine can only be installed on aarch64 & x86 platforms";
|
||||
}
|
||||
{
|
||||
assertion = cfg.efiSupport || cfg.biosSupport;
|
||||
message = "Both UEFI support and BIOS support for Limine are disabled, this will result in an unbootable system";
|
||||
}
|
||||
];
|
||||
|
||||
boot.loader.grub.enable = lib.mkDefault false;
|
||||
|
||||
boot.loader.supportsInitrdSecrets = true;
|
||||
|
||||
system = {
|
||||
boot.loader.id = "limine";
|
||||
build.installBootLoader = pkgs.substituteAll {
|
||||
src = ./limine-install.py;
|
||||
isExecutable = true;
|
||||
|
||||
python3 = pkgs.python3.withPackages (python-packages: [ python-packages.psutil ]);
|
||||
configPath = limineInstallConfig;
|
||||
};
|
||||
};
|
||||
})
|
||||
];
|
||||
}
|
|
@ -614,6 +614,7 @@ in {
|
|||
lightdm = handleTest ./lightdm.nix {};
|
||||
lighttpd = handleTest ./lighttpd.nix {};
|
||||
limesurvey = handleTest ./limesurvey.nix {};
|
||||
limine = import ./limine { inherit runTest; };
|
||||
listmonk = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./listmonk.nix {};
|
||||
litestream = handleTest ./litestream.nix {};
|
||||
lldap = handleTest ./lldap.nix {};
|
||||
|
|
32
nixos/tests/limine/checksum.nix
Normal file
32
nixos/tests/limine/checksum.nix
Normal file
|
@ -0,0 +1,32 @@
|
|||
{ lib, ... }:
|
||||
{
|
||||
name = "checksum";
|
||||
meta.maintainers = with lib.maintainers; [
|
||||
lzcunt
|
||||
phip1611
|
||||
programmerlexi
|
||||
];
|
||||
meta.platforms = [
|
||||
"aarch64-linux"
|
||||
"i686-linux"
|
||||
"x86_64-linux"
|
||||
];
|
||||
nodes.machine =
|
||||
{ ... }:
|
||||
{
|
||||
virtualisation.useBootLoader = true;
|
||||
virtualisation.useEFIBoot = true;
|
||||
|
||||
boot.loader.efi.canTouchEfiVariables = true;
|
||||
boot.loader.limine.enable = true;
|
||||
boot.loader.limine.efiSupport = true;
|
||||
boot.loader.limine.panicOnChecksumMismatch = true;
|
||||
boot.loader.timeout = 0;
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.start()
|
||||
with subtest('Machine boots correctly'):
|
||||
machine.wait_for_unit('multi-user.target')
|
||||
'';
|
||||
}
|
8
nixos/tests/limine/default.nix
Normal file
8
nixos/tests/limine/default.nix
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
runTest,
|
||||
...
|
||||
}:
|
||||
{
|
||||
checksum = runTest ./checksum.nix;
|
||||
uefi = runTest ./uefi.nix;
|
||||
}
|
31
nixos/tests/limine/uefi.nix
Normal file
31
nixos/tests/limine/uefi.nix
Normal file
|
@ -0,0 +1,31 @@
|
|||
{ lib, ... }:
|
||||
{
|
||||
name = "uefi";
|
||||
meta.maintainers = with lib.maintainers; [
|
||||
lzcunt
|
||||
phip1611
|
||||
programmerlexi
|
||||
];
|
||||
meta.platforms = [
|
||||
"aarch64-linux"
|
||||
"i686-linux"
|
||||
"x86_64-linux"
|
||||
];
|
||||
nodes.machine =
|
||||
{ ... }:
|
||||
{
|
||||
virtualisation.useBootLoader = true;
|
||||
virtualisation.useEFIBoot = true;
|
||||
|
||||
boot.loader.efi.canTouchEfiVariables = true;
|
||||
boot.loader.limine.enable = true;
|
||||
boot.loader.limine.efiSupport = true;
|
||||
boot.loader.timeout = 0;
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.start()
|
||||
with subtest('Machine boots correctly'):
|
||||
machine.wait_for_unit('multi-user.target')
|
||||
'';
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue