nixos/limine: init module (#386368)

This commit is contained in:
Pol Dellaiera 2025-03-11 14:09:50 +01:00 committed by GitHub
commit c39e50acb0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 863 additions and 0 deletions

View file

@ -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

View file

@ -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";

View file

@ -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}

View file

@ -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

View 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()

View 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;
};
};
})
];
}

View file

@ -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 {};

View 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')
'';
}

View file

@ -0,0 +1,8 @@
{
runTest,
...
}:
{
checksum = runTest ./checksum.nix;
uefi = runTest ./uefi.nix;
}

View 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')
'';
}