Repository navigation
Expand file tree
/
Copy pathbuild.sh
More file actions
executable file
·605 lines (537 loc) · 24.3 KB
/
Copy pathbuild.sh
File metadata and controls
executable file
·605 lines (537 loc) · 24.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
#!/bin/sh
# build.sh — assemble a FreeBSD live ISO using the init_chroot architecture
# with an in-kernel unionfs + tmpfs writable overlay:
# cd9660 = kernel root; vnode-mounted rootfs.uzip is the read-only lower;
# tmpfs is the writable upper; in-kernel unionfs combines them; pivot
# via init_chroot kenv (no preload, no mfsroot, no reboot -r).
# Runs on FreeBSD (host or vmactions VM). Produces out/livecd.iso.
set -eu
: "${FREEBSD_VERSION:=15.0}"
: "${COMPRESS:=zstd}"
: "${LABEL:=LIVECD}"
ARCH=${ARCH:-amd64}
# Note: no LIVE_HEADROOM in this variant. The lower UFS is sized exactly
# to content; writable headroom comes from the tmpfs upper at boot, which
# is page-allocated on demand and bounded by host RAM + swap rather than
# by a build-time constant.
ROOT=$(cd "$(dirname "$0")" && pwd)
WORK=$ROOT/work
OUT=$ROOT/out
DIST=$ROOT/distfiles
REPOS=$ROOT/repos
MIRROR="https://download.freebsd.org/ftp/releases/${ARCH}/${FREEBSD_VERSION}-RELEASE"
mkdir -p "$WORK" "$OUT" "$DIST" "$REPOS"
# Clean any prior partial build (but keep distfiles + repos cached)
rm -rf "$WORK"/* "$OUT"/*
echo "==> build: FreeBSD $FREEBSD_VERSION ($ARCH), compress=$COMPRESS"
#
# 0. host-side: clone or update the system-domain upstreams into repos/.
# Order is significant per plan §8.2: tools-make writes the GNUstep.conf
# that libobjc2 reads, libobjc2 needs BlocksRuntime from libdispatch,
# libs-base/libs-corebase install through gnustep-make. Tracking
# upstream HEAD; no pinning. Chroot stays git-free — these clones live
# on the host and get rsynced in below.
#
echo "==> cloning/updating system-domain upstreams"
UPSTREAMS="
https://github.com/apple/swift-corelibs-libdispatch.git
https://github.com/gnustep/tools-make.git
https://github.com/gnustep/libobjc2.git
https://github.com/Tessil/robin-map.git
https://github.com/gnustep/libs-base.git
https://github.com/gnustep/libs-corebase.git
"
for repo in $UPSTREAMS; do
name=$(basename "$repo" .git)
if [ -d "$REPOS/$name/.git" ]; then
echo " updating $name"
( cd "$REPOS/$name" && git fetch --all --tags && git pull --ff-only )
else
echo " cloning $name"
git clone "$repo" "$REPOS/$name"
fi
done
#
# 1. fetch base.txz + kernel.txz + src.txz
#
# src.txz gives us /usr/src/release/amd64/mkisoimages.sh — the FreeBSD
# release engineering script that builds the hybrid (BIOS + UEFI El
# Torito + GPT-overlaid for USB-stick dd) cd9660. Replaces the
# hand-rolled makefs invocation we used to do; the script's hybrid-GPT
# step at the end of mkisoimages.sh is what makes the same .iso file
# bootable from both optical media and a dd'd USB stick.
#
for f in base.txz kernel.txz src.txz; do
if [ ! -f "$DIST/$f" ]; then
echo "==> downloading $f"
fetch -o "$DIST/$f" "$MIRROR/$f"
fi
done
#
# 2. extract into rootfs staging dir
#
echo "==> extracting base+kernel"
mkdir -p "$WORK/rootfs"
tar -xJf "$DIST/base.txz" -C "$WORK/rootfs"
tar -xJf "$DIST/kernel.txz" -C "$WORK/rootfs"
# base.txz ships /etc/login.conf but not the compiled /etc/login.conf.db.
# Without the .db, login_getclass() can't find any class and logs a noisy
# warning at boot ("login_getclass: unknown class 'daemon'"). The FreeBSD
# installer rebuilds it via cap_mkdb during install; we have to do the
# same since we skip bsdinstall.
cap_mkdb "$WORK/rootfs/etc/login.conf"
# Same idea for the password databases. base.txz may or may not ship the
# *.db files depending on version; rebuild them to be safe so getpwnam()
# and friends work without warnings.
pwd_mkdb -p -d "$WORK/rootfs/etc" "$WORK/rootfs/etc/master.passwd"
#
# 3. chroot: runtime pkgs (pkglist.txt) + build pkgs (buildpkgs.txt) +
# system-domain build (libdispatch + GNUstep stack) + buildpkgs purge.
# Single chroot session for all of it; build pkgs go in and out before
# the slim pass so they don't ship in the ISO.
#
RUNTIME_PKGS=$(grep -v '^[[:space:]]*#' "$ROOT/pkglist.txt" 2>/dev/null | grep -v '^[[:space:]]*$' || true)
BUILD_PKGS=$( grep -v '^[[:space:]]*#' "$ROOT/buildpkgs.txt" 2>/dev/null | grep -v '^[[:space:]]*$' || true)
if [ -n "$RUNTIME_PKGS" ] || [ -n "$BUILD_PKGS" ]; then
cp /etc/resolv.conf "$WORK/rootfs/etc/resolv.conf"
mount -t devfs devfs "$WORK/rootfs/dev"
cleanup_chroot() {
umount -f "$WORK/rootfs/dev" 2>/dev/null || true
rm -f "$WORK/rootfs/etc/resolv.conf"
rm -rf "$WORK/rootfs/tmp/repos"
}
trap cleanup_chroot EXIT INT TERM
chroot "$WORK/rootfs" env ASSUME_ALWAYS_YES=yes IGNORE_OSVERSION=yes pkg bootstrap -f
if [ -n "$RUNTIME_PKGS" ]; then
echo "==> installing runtime packages:"
echo "$RUNTIME_PKGS" | sed 's/^/ /'
# shellcheck disable=SC2086
# LICENSES_ACCEPTED=NVIDIA: nvidia-drm-latest-kmod is a
# restricted-distribution blob and pkg refuses to install it
# without explicit license acceptance.
chroot "$WORK/rootfs" env \
ASSUME_ALWAYS_YES=yes \
IGNORE_OSVERSION=yes \
LICENSES_ACCEPTED=NVIDIA \
pkg install -y $RUNTIME_PKGS
# ---- dhcpcd: silence DHCPv6 retry spam ----
# The dhcpcd port ships /usr/local/etc/dhcpcd.conf as @sample;
# pkg copies it to dhcpcd.conf at install. Append nodhcp6 so
# dhcpcd doesn't attempt DHCPv6 on networks where the router
# advertises the M-flag (M=1, "use DHCPv6 for addresses") but
# the DHCPv6 server returns "No Addresses Available" — common
# on consumer routers with half-configured IPv6, generates
# endless retry-spam in /var/log/messages with no functional
# gain. SLAAC + RA processing stay enabled, so global IPv6
# still works on networks that advertise a prefix correctly.
# Verified empirically on a Lenovo laptop in May 2026.
if [ -f "$WORK/rootfs/usr/local/etc/dhcpcd.conf" ]; then
cat >> "$WORK/rootfs/usr/local/etc/dhcpcd.conf" <<'EOF'
# Live ISO overrides (see build.sh comment).
# nodhcp6: don't try DHCPv6 stateful — common consumer routers
# advertise the M-flag but have a broken DHCPv6 server returning
# "No Addresses Available" on every Solicit, generating endless
# retry spam in /var/log/messages. SLAAC + RA processing remain
# enabled, so global IPv6 still works on networks that advertise a
# prefix in the RA.
# quiet: drop notice-level logging to reduce "part of a Router
# Advertisement expired" spam on networks where RA components
# have short lifetimes (also common consumer-router behavior).
# We keep warning/error level; just suppress the routine
# informational notices that look alarming but aren't.
nodhcp6
quiet
EOF
fi
else
echo "==> pkglist.txt empty; skipping runtime pkg install"
fi
if [ -n "$BUILD_PKGS" ]; then
echo "==> installing build packages:"
echo "$BUILD_PKGS" | sed 's/^/ /'
# shellcheck disable=SC2086
chroot "$WORK/rootfs" env ASSUME_ALWAYS_YES=yes IGNORE_OSVERSION=yes \
pkg install -y $BUILD_PKGS
# Host-cloned upstreams into chroot; chroot stays git-free.
echo "==> rsyncing repos/ -> chroot:/tmp/repos/"
mkdir -p "$WORK/rootfs/tmp/repos"
rsync -a --delete "$REPOS/" "$WORK/rootfs/tmp/repos/"
# libobjc2's CMakeLists FetchContent_Declare(robinmap) calls git
# at configure-time — would fail in our git-free chroot. Rewrite
# the Declare to point SOURCE_DIR at the sibling robin-map clone
# we just rsynced. Patches the chroot copy only; the host clone
# stays clean so future git pulls keep working. Idempotent (the
# source pattern disappears after first run).
echo "==> patching libobjc2 FetchContent(robinmap) -> SOURCE_DIR"
sed -i '' \
-e 's|GIT_REPOSITORY https://github.com/Tessil/robin-map/|SOURCE_DIR /tmp/repos/robin-map)|' \
-e '/GIT_TAG[[:space:]]*v1\.4\.0)/d' \
"$WORK/rootfs/tmp/repos/libobjc2/CMakeLists.txt"
# §8.4 verbatim build invocations. Order matters; see §8.2.
echo "==> building system-domain libraries in chroot"
chroot "$WORK/rootfs" /bin/sh -ex <<'CHROOT_BUILD'
MAKE_CMD=gmake
CPUS=$(sysctl -n hw.ncpu)
REPOS_DIR=/tmp/repos
# libdispatch
mkdir -p "$REPOS_DIR/swift-corelibs-libdispatch/Build"
cd "$REPOS_DIR/swift-corelibs-libdispatch/Build"
cmake .. \
-DCMAKE_INSTALL_PREFIX=/System/Library \
-DCMAKE_INSTALL_LIBDIR=Libraries \
-DINSTALL_DISPATCH_HEADERS_DIR=/System/Library/Headers/dispatch \
-DINSTALL_BLOCK_HEADERS_DIR=/System/Library/Headers \
-DINSTALL_OS_HEADERS_DIR=/System/Library/Headers/os \
-DINSTALL_PRIVATE_HEADERS=ON \
-DCMAKE_INSTALL_MANDIR=Documentation/man \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++
"$MAKE_CMD" -j"$CPUS"
"$MAKE_CMD" install
# tools-make
cd "$REPOS_DIR/tools-make"
$MAKE_CMD distclean 2>/dev/null || true
./configure \
--with-config-file=/System/Library/Preferences/GNUstep.conf \
--with-layout=gershwin \
--with-library-combo=ng-gnu-gnu \
--with-objc-lib-flag=" " \
LDFLAGS="-L/System/Library/Libraries" \
CPPFLAGS="-I/System/Library/Headers" \
libobjc_LIBS=" "
$MAKE_CMD
$MAKE_CMD install
# Source the GNUstep environment now that tools-make has installed
# /System/Library/Makefiles/GNUstep.sh. Sets GNUSTEP_HEADERS,
# GNUSTEP_LIBRARY, GNUSTEP_MAKEFILES — required by libobjc2's cmake
# (else it installs to /usr/local/lib + /objc/), and required by
# libs-base/libs-corebase configure (AC_CONFIG_AUX_DIR uses
# GNUSTEP_MAKEFILES).
. /System/Library/Makefiles/GNUstep.sh
# libobjc2
rm -rf "$REPOS_DIR/libobjc2/Build"
mkdir -p "$REPOS_DIR/libobjc2/Build"
cd "$REPOS_DIR/libobjc2/Build"
cmake .. \
-DGNUSTEP_INSTALL_TYPE=SYSTEM \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DEMBEDDED_BLOCKS_RUNTIME=OFF \
-DBlocksRuntime_INCLUDE_DIR=/System/Library/Headers \
-DBlocksRuntime_LIBRARIES=/System/Library/Libraries/libBlocksRuntime.so
"$MAKE_CMD" -j"$CPUS"
"$MAKE_CMD" install
# libs-base (Foundation)
export GNUSTEP_INSTALLATION_DOMAIN="SYSTEM"
cd "$REPOS_DIR/libs-base"
# --disable-tls per plan §8.7 — launchd doesn't speak TLS, and pulling
# in libgnutls/openssl would bloat the ISO for NSStream/NSURLConnection
# functionality we don't use.
./configure \
--with-dispatch-include=/System/Library/Headers \
--with-dispatch-library=/System/Library/Libraries \
--disable-tls
$MAKE_CMD -j"$CPUS"
$MAKE_CMD install
$MAKE_CMD clean
# libs-corebase (CoreFoundation)
cd "$REPOS_DIR/libs-corebase"
./configure \
CPPFLAGS="-I/System/Library/Headers" \
LDFLAGS="-L/System/Library/Libraries"
$MAKE_CMD -j"$CPUS"
$MAKE_CMD install
$MAKE_CMD clean
CHROOT_BUILD
rm -rf "$WORK/rootfs/tmp/repos"
# ---- launchd build (phase 3b) ----
# Stage src/ + make-launchd.sh together at /tmp/launchd in the
# chroot. make-launchd.sh expects src/ as a sibling so a clone
# of the repo can run it standalone — same layout works here.
echo "==> staging src/ + make-launchd.sh -> chroot:/tmp/launchd/"
mkdir -p "$WORK/rootfs/tmp/launchd"
rsync -a --delete "$ROOT/src/" "$WORK/rootfs/tmp/launchd/src/"
cp "$ROOT/make-launchd.sh" "$WORK/rootfs/tmp/launchd/make-launchd.sh"
chmod +x "$WORK/rootfs/tmp/launchd/make-launchd.sh"
echo "==> building + installing launchd in chroot"
chroot "$WORK/rootfs" /tmp/launchd/make-launchd.sh
rm -rf "$WORK/rootfs/tmp/launchd"
# ---- netconfigd build (configd Phase 1) ----
# Same staging shape as launchd. configd/ + make-configd.sh land
# under /tmp/configd/ in the chroot; make-configd.sh drives
# configd/Makefile to compile the Phase-1 daemon and install to
# /usr/libexec/netconfigd.
echo "==> staging configd/ + make-configd.sh -> chroot:/tmp/configd/"
mkdir -p "$WORK/rootfs/tmp/configd"
rsync -a --delete "$ROOT/configd/" "$WORK/rootfs/tmp/configd/configd/"
cp "$ROOT/make-configd.sh" "$WORK/rootfs/tmp/configd/make-configd.sh"
chmod +x "$WORK/rootfs/tmp/configd/make-configd.sh"
echo "==> building + installing netconfigd in chroot"
chroot "$WORK/rootfs" /tmp/configd/make-configd.sh
rm -rf "$WORK/rootfs/tmp/configd"
# ---- kmodloader build (Phase 1) ----
# kmodloader's own Makefile handles compile + install in one
# `gmake install` call. No wrapper script.
echo "==> staging kmodloader/ -> chroot:/tmp/kmodloader/"
mkdir -p "$WORK/rootfs/tmp/kmodloader"
rsync -a --delete "$ROOT/kmodloader/" "$WORK/rootfs/tmp/kmodloader/"
echo "==> building + installing kmodloader in chroot"
chroot "$WORK/rootfs" /usr/local/bin/gmake -C /tmp/kmodloader install
rm -rf "$WORK/rootfs/tmp/kmodloader"
# ---- ldconfig hint for /System/Library/Libraries ----
# FreeBSD's /etc/rc.d/ldconfig at boot reads $ldconfig_local_dirs
# (default /usr/local/libdata/ldconfig) and adds each listed
# directory to the runtime linker hints. NOT /etc/ld-elf.so.conf.d
# — that's a Linux glibc convention FreeBSD does not honor. The
# `ldconfig -m` here primes the hints DB at build time so the
# ISO boots with /System/Library/Libraries/* immediately
# discoverable.
echo "==> writing ldconfig hint for /System/Library/Libraries"
mkdir -p "$WORK/rootfs/usr/local/libdata/ldconfig"
echo "/System/Library/Libraries" \
> "$WORK/rootfs/usr/local/libdata/ldconfig/freebsd-launchd"
chroot "$WORK/rootfs" ldconfig -m /usr/local/lib /System/Library/Libraries
echo "==> purging build packages"
# shellcheck disable=SC2086
chroot "$WORK/rootfs" env ASSUME_ALWAYS_YES=yes \
pkg delete -y $BUILD_PKGS
chroot "$WORK/rootfs" env ASSUME_ALWAYS_YES=yes \
pkg autoremove -y || true
fi
cleanup_chroot
trap - EXIT INT TERM
fi
#
# 4. trim rootfs of things not needed at runtime
#
echo "==> slimming rootfs"
rm -rf \
"$WORK/rootfs/usr/share/man" \
"$WORK/rootfs/usr/share/doc" \
"$WORK/rootfs/usr/share/info" \
"$WORK/rootfs/usr/share/locale" \
"$WORK/rootfs/usr/share/games" \
"$WORK/rootfs/usr/share/examples" \
"$WORK/rootfs/usr/share/openssl" \
"$WORK/rootfs/usr/share/dict" \
"$WORK/rootfs/usr/share/calendar" \
"$WORK/rootfs/usr/include" \
"$WORK/rootfs/usr/tests" \
"$WORK/rootfs/usr/lib/debug" \
"$WORK/rootfs/usr/libdata/lint" \
"$WORK/rootfs/var/db/etcupdate"
find "$WORK/rootfs/boot/kernel" -name '*.symbols' -delete 2>/dev/null || true
#
# 5. apply local overlays (etc/rc.conf, etc/rc.local, ...)
#
if [ -d "$ROOT/overlays" ]; then
echo "==> applying overlays"
cp -aR "$ROOT/overlays/." "$WORK/rootfs/"
fi
# rc.local needs to be executable
[ -f "$WORK/rootfs/etc/rc.local" ] && chmod +x "$WORK/rootfs/etc/rc.local"
#
# 6. minimal /etc/fstab; root mounted by unionfs at boot, no entries needed
#
cat > "$WORK/rootfs/etc/fstab" <<'EOF'
# Live system: root is the unionfs merged view (read-only UFS lower +
# tmpfs upper, layered in the ramdisk-style init phase and then exposed
# as / via init_chroot).
EOF
#
# 7. makefs UFS without an explicit -s. The writable upper is tmpfs at
# boot, so the lower UFS doesn't need user-visible headroom -- only
# enough room for UFS internal overhead (cylinder groups, inode
# tables, default ~8% minfree). makefs auto-computes that when -s
# is omitted; passing a tight -s trips its bsize rounding logic.
# mkuzip then compresses; output goes into the cdroot.
#
CONTENT_BYTES=$(du -sk "$WORK/rootfs" | awk '{print $1*1024}')
echo "==> rootfs content = $CONTENT_BYTES bytes ($((CONTENT_BYTES / 1024 / 1024)) MiB)"
echo "==> makefs ffs (auto-sized)"
makefs -t ffs -o version=2,label=ROOTFS \
"$WORK/rootfs.ufs" "$WORK/rootfs"
ls -lh "$WORK/rootfs.ufs"
mkdir -p "$WORK/cdroot"
case "$COMPRESS" in
zstd) MKUZIP_FLAGS="-A zstd -C 19 -d -s 262144" ;;
zlib) MKUZIP_FLAGS="-d -s 65536" ;;
*) echo "ERROR: unknown COMPRESS=$COMPRESS"; exit 1 ;;
esac
echo "==> mkuzip $MKUZIP_FLAGS"
mkuzip $MKUZIP_FLAGS -j "$(sysctl -n hw.ncpu)" \
-o "$WORK/cdroot/rootfs.uzip" "$WORK/rootfs.ufs"
# Stage the init environment on the cd9660 root. The kernel mounts cd9660
# as / and runs /sbin/init from there, which then runs /init.sh which uses
# tools from /rescue. unionfs is in-kernel and rescue's mount_unionfs is
# statically linked — no dynamic /sbin/geom + libs needed (unlike the
# gunion variant).
echo "==> staging init environment on cd9660"
mkdir -p "$WORK/cdroot/sbin" "$WORK/cdroot/rescue" "$WORK/cdroot/sysroot" \
"$WORK/cdroot/upper" "$WORK/cdroot/dev" "$WORK/cdroot/etc"
# /rescue: statically-linked busybox-equivalent. Provides sh, mdconfig,
# mount, mount_unionfs, kldload, kenv, sleep, echo, cat, halt, etc.
# Self-contained.
#
# CRITICAL: /rescue uses hardlinks aggressively -- every tool name is a
# hardlink to the same crunchgen binary, so the real disk footprint is
# ~14 MB even though there are ~200 entries. FreeBSD's `cp -a` does NOT
# preserve hardlinks (unlike GNU cp), so a naive cp turns every hardlink
# into a full file copy -> 2.8 GB explosion. Use a tar pipe which does
# preserve hardlinks.
( cd "$WORK/rootfs" && tar cf - rescue ) | ( cd "$WORK/cdroot" && tar xf - )
# No /sbin/init symlink — loader.conf's init_path is explicit
# ("/init.sh:/rescue/init") so the kernel goes straight to /init.sh
# (Option D) or falls back to /rescue/init. The default /sbin/init
# search slot is never consulted.
# Ship /etc/login.conf (+ compiled .db) on the cd9660 root.
# Without this, login_getclass() called early in boot -- before the
# init_chroot pivot fully takes effect for the calling process -- sees
# cd9660's empty /etc and logs a noisy warning:
# init - - login_getclass: unknown class 'daemon'
# GhostBSD's livecd hits the same issue and ships login.conf in their
# ramdisk for the same reason. lib/libutil/login_cap.c:349 emits the
# warning; it goes away as soon as login.conf is reachable.
# mkisoimages.sh runs `makefs -D -N $cdroot/etc -t cd9660 ...`. The -N
# flag tells makefs to read user/group databases from $cdroot/etc when
# resolving uname=/gname= in the metalog. So we need passwd, master.passwd,
# group, and the compiled .db forms here too. Build-time only — after
# /init.sh chroots into /sysroot, the running system reads /etc from the
# unionfs (rootfs.uzip + tmpfs), not from the cdroot.
for f in passwd master.passwd group pwd.db spwd.db; do
if [ -f "$WORK/rootfs/etc/$f" ]; then
cp "$WORK/rootfs/etc/$f" "$WORK/cdroot/etc/$f"
fi
done
cp "$WORK/rootfs/etc/login.conf" "$WORK/cdroot/etc/login.conf"
[ -f "$WORK/rootfs/etc/login.conf.db" ] && \
cp "$WORK/rootfs/etc/login.conf.db" "$WORK/cdroot/etc/login.conf.db"
# pivot script
cp "$ROOT/ramdisk/init.sh" "$WORK/cdroot/init.sh"
chmod +x "$WORK/cdroot/init.sh"
ls -lh "$WORK/cdroot/rootfs.uzip"
#
# 8. stage /boot on the cd9660 carrier — but ONLY the loader-needed bits.
# Linux livecds ship just the kernel + initramfs on iso9660 (~60 MB
# total) and put all kernel modules inside the squashfs. We do the
# same: copy the kernel binary (gzipped) plus the few modules the
# loader will preload + a handful of likely-auto-loaded ones. The
# other ~80 modules stay only in rootfs.uzip; the running system
# kldloads them from there post-chroot.
#
echo "==> staging minimal /boot on cd9660"
mkdir -p "$WORK/cdroot/boot/kernel"
# Bootloader pieces (whichever exist; vary by FreeBSD release/arch)
for f in cdboot loader loader.efi loader_lua loader_lua.efi \
loader_simp loader_simp.efi pmbr isoboot boot1.efi \
gptboot defaults device.hints lua fonts; do
if [ -e "$WORK/rootfs/boot/$f" ]; then
cp -aR "$WORK/rootfs/boot/$f" "$WORK/cdroot/boot/"
fi
done
# Kernel binary, gzipped so the loader unpacks it on read. Save as
# kernel.gz (with .gz extension) -- the loader's gzipfs layer detects
# the extension and decompresses transparently. Same pattern mfsBSD uses.
gzip -9c "$WORK/rootfs/boot/kernel/kernel" > "$WORK/cdroot/boot/kernel/kernel.gz"
ls -lh "$WORK/cdroot/boot/kernel/kernel.gz" \
"$WORK/rootfs/boot/kernel/kernel"
# Modules: only what we need at boot.
# * geom_uzip / unionfs — explicitly loaded via loader.conf for the pivot
# * acpi, ahci, virtio_blk, virtio_pci, ahci/scsi_da/cd — typically
# auto-loaded by the kernel for storage in qemu/real hardware. Some
# are built into GENERIC, but ship them anyway in case the user
# boots a kernel without them.
BOOT_MODULES="geom_uzip.ko unionfs.ko \
acpi.ko \
virtio.ko virtio_pci.ko virtio_blk.ko virtio_scsi.ko \
ahci.ko mfi.ko"
for m in $BOOT_MODULES; do
if [ -f "$WORK/rootfs/boot/kernel/$m" ]; then
cp "$WORK/rootfs/boot/kernel/$m" "$WORK/cdroot/boot/kernel/"
fi
done
cp "$ROOT/boot/loader.conf" "$WORK/cdroot/boot/loader.conf"
# /boot/firmware on the cd9660 → symlink to /sysroot/boot/firmware
# (where the unionfs mounts the rootfs.uzip layer at boot, with all
# pkg-installed firmware files inside).
#
# Why: kernel-context vn_open in subr_firmware.c's try_binary_file()
# does namei against the kernel's root namespace, which is the cd9660
# mount — NOT the chroot the userspace processes see. So files visible
# at /boot/firmware/ in chroot view are invisible to kernel firmware
# loading. Result: iwlwifi/i915kms attach but firmware-load fails with
# "File size way too small!" → no wlan0, no DRM acceleration.
#
# The symlink turns kernel-namei's lookup of /boot/firmware/foo.ucode
# into a follow-through-mount-point chain:
# 1. cd9660:/boot/firmware → symlink → /sysroot/boot/firmware
# 2. cd9660:/sysroot is the unionfs mount point
# 3. namei traverses into the unionfs view
# 4. Finds the file in the rootfs.uzip layer
# Costs ~0 bytes on the cd9660 (just a symlink). Avoids copying ~1GB
# of firmware blobs into the cdroot.
#
# Rock Ridge extension on cd9660 (already enabled via mkisoimages.sh)
# preserves symlinks correctly. The symlink target uses the standard
# init.sh mount point /sysroot.
ln -sf /sysroot/boot/firmware "$WORK/cdroot/boot/firmware"
echo "==> /boot on cd9660:"
du -sh "$WORK/cdroot/boot" "$WORK/cdroot/boot/kernel" || true
ls -la "$WORK/cdroot/boot/kernel/" || true
ls -la "$WORK/cdroot/boot/firmware" || true
#
# 9. extract src.txz to expose FreeBSD's release scripts.
#
# We don't build from source — we just need /usr/src/release/amd64/
# mkisoimages.sh and its sister scripts (tools.subr, install-boot.sh)
# for the next step.
#
echo "==> extracting src.txz for FreeBSD release scripts"
mkdir -p "$WORK/freebsd-src"
tar -xJf "$DIST/src.txz" -C "$WORK/freebsd-src"
#
# 10. stage cd9660-direct UEFI fallback.
#
# /EFI/BOOT/BOOTX64.EFI in the cd9660 root is a fallback path for
# firmware that mounts the ISO as a filesystem rather than walking the
# El Torito boot catalog. mkisoimages.sh handles the El Torito ESP
# itself (via make_esp_file in tools/boot/install-boot.sh), so we only
# stage this fallback copy here.
#
mkdir -p "$WORK/cdroot/EFI/BOOT"
if [ -f "$WORK/rootfs/boot/loader_lua.efi" ]; then
cp "$WORK/rootfs/boot/loader_lua.efi" "$WORK/cdroot/EFI/BOOT/BOOTX64.EFI"
elif [ -f "$WORK/rootfs/boot/loader.efi" ]; then
cp "$WORK/rootfs/boot/loader.efi" "$WORK/cdroot/EFI/BOOT/BOOTX64.EFI"
else
echo "ERROR: no loader.efi found in base.txz boot/"
exit 1
fi
#
# 11. build hybrid ISO via FreeBSD's own mkisoimages.sh.
#
# Same script releng uses for disc1.iso. Produces a cd9660 with:
# - El Torito BIOS boot (via boot/cdboot)
# - El Torito UEFI boot (via an ESP image built from boot/loader.efi)
# - GPT/PMBR overlay in the System Area so the same blob is also a
# valid GPT-partitioned disk image: dd it to a USB stick and the
# stick boots on both BIOS (via PMBR + freebsd-boot/boot/isoboot)
# and UEFI (via the EFI partition aliased to the ESP).
# Reads boot files from $WORK/cdroot which already has cdboot,
# loader.efi, pmbr, isoboot in place from §8's copy loop.
#
echo "==> building hybrid ISO via mkisoimages.sh"
MKISO="$WORK/freebsd-src/usr/src/release/amd64/mkisoimages.sh"
[ -f "$MKISO" ] || { echo "ERROR: $MKISO not found in src.txz" >&2; exit 1; }
chmod +x "$MKISO"
"$MKISO" -b "$LABEL" "$OUT/livecd.iso" "$WORK/cdroot"
ls -lh "$OUT/livecd.iso"
sha256 "$OUT/livecd.iso" 2>/dev/null || sha256sum "$OUT/livecd.iso"
echo
echo "==> cdroot size breakdown:"
du -sh "$WORK/cdroot"/* 2>/dev/null | sort -h
echo
echo "==> ISO total: $(ls -lh "$OUT/livecd.iso" | awk '{print $5}')"
echo "==> DONE"