2023-01-02 10:00:20 +00:00
{ config , lib , pkgs , utils , . . . }:
# TODO: replace mobile-nixos parts with Disko <https://github.com/nix-community/disko>
2022-06-23 07:24:39 +00:00
2022-06-24 07:10:07 +00:00
with lib ;
2022-06-23 10:39:09 +00:00
let
2022-08-01 07:23:49 +00:00
cfg = config . sane . image ;
2022-06-24 07:10:07 +00:00
in
{
options = {
2022-11-22 03:02:41 +00:00
sane . image . enable = mkOption {
default = true ;
type = types . bool ;
2023-01-02 10:00:20 +00:00
description = " w h e t h e r t o e n a b l e i m a g e t a r g e t s . e v e n s o t h e y w o n ' t b e b u i l t u n l e s s y o u s p e c i f i c a l l y r e f e r e n c e t h e ` s y s t e m . b u i l d . i m g ` t a r g e t . " ;
2022-11-22 03:02:41 +00:00
} ;
2022-08-01 21:01:46 +00:00
# packages whose contents should be copied directly into the /boot partition.
# e.g. EFI loaders, u-boot bootloader, etc.
2022-08-01 07:23:49 +00:00
sane . image . extraBootFiles = mkOption {
2022-06-24 07:10:07 +00:00
default = [ ] ;
type = types . listOf types . package ;
} ;
2022-08-01 21:37:19 +00:00
# extra (empty) directories to create in the rootfs.
# for example, /var/log might be required by the boot process, so ensure it exists.
sane . image . extraDirectories = mkOption {
default = [ ] ;
type = types . listOf types . str ;
} ;
2022-08-01 21:01:46 +00:00
# the GPT header is fixed to Logical Block Address 1,
# but we can actually put the partition entries anywhere.
# this option reserves so many bytes after LBA 1 but *before* the partition entries.
# this is not universally supported, but is an easy hack to claim space near the start
# of the disk for other purposes (e.g. firmware blobs)
2022-08-01 07:23:49 +00:00
sane . image . extraGPTPadding = mkOption {
2022-06-24 07:53:43 +00:00
default = 0 ;
2022-06-29 08:17:53 +00:00
# NB: rpi doesn't like non-zero values for this.
# at the same time, spinning disks REALLY need partitions to be aligned to 4KiB boundaries.
# maybe there's some imageBuilder.fileSystem type which represents empty space?
# default = 2014 * 512; # standard is to start part0 at sector 2048 (versus 34 if no padding)
2022-06-24 07:53:43 +00:00
type = types . int ;
} ;
2022-08-01 21:01:46 +00:00
# optional space (in bytes) to leave unallocated after the GPT structure and before the first partition.
2022-08-01 07:23:49 +00:00
sane . image . firstPartGap = mkOption {
2022-06-29 08:17:53 +00:00
# align the first part to 16 MiB.
# do this by inserting a gap of 16 MiB - gptHeaderSize
# and then multiply by 1MiB and subtract 1 because mobile-nixos
# has a bug which will divide this by 1 MiB (and round up)
default = ( 16 * 1024 * 1024 - 34 * 512 ) * 1024 * 1024 - 1 ;
type = types . nullOr types . int ;
} ;
2022-08-01 07:23:49 +00:00
sane . image . bootPartSize = mkOption {
2022-06-24 09:07:40 +00:00
default = 512 * 1024 * 1024 ;
type = types . int ;
} ;
2023-11-08 16:42:25 +00:00
sane . image . sectorSize = mkOption {
default = 512 ;
type = types . int ;
description = ''
disk sector size . MUST match what the disk firmware believes it to be .
for nvme drives it may be better to use a large sector size like 4096 .
see : < https://wiki.archlinux.org/title/Advanced_Format #Changing_sector_size>.
N . B . : setting this to something other than 5 1 2 B is not well tested .
'' ;
} ;
2022-06-24 07:10:07 +00:00
} ;
config = let
# return true if super starts with sub
startsWith = super : sub : (
( builtins . substring 0 ( builtins . stringLength sub ) super ) == sub
) ;
# return the (string) path to get from `stem` to `path`
2022-08-02 23:03:32 +00:00
# or errors if not a sub-path
2022-06-24 07:10:07 +00:00
relPath = stem : path : (
builtins . head ( builtins . match " ^ ${ stem } ( . + ) " path )
) ;
2022-06-23 11:48:33 +00:00
2022-06-24 07:10:07 +00:00
fileSystems = config . fileSystems ;
bootFs = fileSystems . " / b o o t " ;
nixFs = fileSystems . " / n i x / s t o r e " or fileSystems . " / n i x " or fileSystems . " / " ;
# resolves to e.g. "nix/store", "/store" or ""
storeRelPath = relPath nixFs . mountPoint " / n i x / s t o r e " ;
2022-06-23 11:48:33 +00:00
2022-06-24 07:10:07 +00:00
uuidFromFs = fs : builtins . head ( builtins . match " / d e v / d i s k / b y - u u i d / ( . + ) " fs . device ) ;
vfatUuidFromFs = fs : builtins . replaceStrings [ " - " ] [ " " ] ( uuidFromFs fs ) ;
2022-06-23 22:28:33 +00:00
2022-06-24 07:10:07 +00:00
fsBuilderMapBoot = {
" v f a t " = pkgs . imageBuilder . fileSystem . makeESP ;
} ;
fsBuilderMapNix = {
" e x t 4 " = pkgs . imageBuilder . fileSystem . makeExt4 ;
" b t r f s " = pkgs . imageBuilder . fileSystem . makeBtrfs ;
} ;
2023-01-08 05:37:25 +00:00
in
lib . mkIf cfg . enable
{
2023-01-11 09:08:46 +00:00
system . build . img-without-firmware = with pkgs ; pkgs . imageBuilder . diskImage . makeGPT {
2022-06-24 07:10:07 +00:00
name = " n i x o s " ;
diskID = vfatUuidFromFs bootFs ;
# leave some space for firmware
# TODO: we'd prefer to turn this into a protected firmware partition, rather than reserving space in the GPT header itself
# Tow-Boot manages to do that; not sure how.
2022-06-24 07:53:43 +00:00
headerHole = cfg . extraGPTPadding ;
2022-06-24 07:10:07 +00:00
partitions = [
2022-06-29 08:17:53 +00:00
( pkgs . imageBuilder . gap cfg . firstPartGap )
2022-06-24 07:10:07 +00:00
( fsBuilderMapBoot . " ${ bootFs . fsType } " {
# fs properties
name = " E S P " ;
partitionID = vfatUuidFromFs bootFs ;
# partition properties
partitionLabel = " E F I S y s t e m " ;
partitionUUID = " 4 4 4 4 4 4 4 4 - 4 4 4 4 - 4 4 4 4 - 4 4 4 4 - 4 4 4 4 ${ vfatUuidFromFs bootFs } " ;
2022-06-24 09:07:40 +00:00
size = cfg . bootPartSize ;
2023-11-08 16:42:25 +00:00
inherit ( cfg ) sectorSize ;
blockSize = cfg . sectorSize ; # has to be a multiple of sectorSize
2022-05-22 05:00:38 +00:00
2022-06-24 07:10:07 +00:00
populateCommands = let
extras = builtins . toString ( builtins . map ( d : " c p - R ${ d } / * . / " ) cfg . extraBootFiles ) ;
in ''
echo " r u n n i n g i n s t a l l B o o t L o a d e r "
$ { config . system . build . installBootLoader } $ { config . system . build . toplevel } - d .
echo " r a n i n s t a l l B o o t L o a d e r "
$ { extras }
echo " c o p i e d e x t r a B o o t F i l e s "
'' ;
} )
( fsBuilderMapNix . " ${ nixFs . fsType } " {
# fs properties
name = " N I X O S _ S Y S T E M " ;
partitionID = uuidFromFs nixFs ;
# partition properties
partitionLabel = " L i n u x f i l e s y s t e m " ;
partitionUUID = uuidFromFs nixFs ;
2023-11-08 16:42:25 +00:00
# inherit (cfg) sectorSize; # imageBuilder only supports sectorSize for vfat. btrfs defaults to a 4096B sector size, somehow it abstracts over the drive's sector size?
populateCommands = let
2022-06-24 07:10:07 +00:00
closureInfo = buildPackages . closureInfo { rootPaths = config . system . build . toplevel ; } ;
2022-08-03 00:08:26 +00:00
extraRelPaths = builtins . toString ( builtins . map ( p : " . / " + builtins . toString ( relPath nixFs . mountPoint p ) ) cfg . extraDirectories ) ;
2023-11-08 16:42:25 +00:00
in ''
2022-08-01 21:37:19 +00:00
mkdir - p . / $ { storeRelPath } $ { extraRelPaths }
2022-06-24 07:10:07 +00:00
echo " C o p y i n g s y s t e m c l o s u r e . . . "
while IFS = read - r path ; do
echo " C o p y i n g $ p a t h "
cp - prf " $ p a t h " . / $ { storeRelPath }
done < " ${ closureInfo } / s t o r e - p a t h s "
echo " D o n e c o p y i n g s y s t e m c l o s u r e . . . "
cp - v $ { closureInfo } /registration ./nix-path-registration
'' ;
} )
] ;
} ;
system . build . img = lib . mkDefault config . system . build . img-without-firmware ;
2022-05-22 05:00:38 +00:00
} ;
}