summaryrefslogtreecommitdiffstats
path: root/scripts/mksystem.sh
blob: 053fb3bf8f18d3085f61ae5e7e1ed423a172580c (plain) (blame)
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
#!/usr/bin/env bash
#
# scripts/mksystem.sh — assemble the single dd-able rs-dos system image
#
# One FAT32 image holds EVERYTHING (boot medium and data medium are the
# same disk, mounted by the kernel at /disk/<UID>/):
#
#   \EFI\BOOT\BOOTX64.EFI    the kernel — the firmware boots it directly
#   \bzImage                 kernel copy (rsbl's expected path, real hw)
#   \initramfs.cpio.gz       empty initramfs (rsbl requires the path)
#   \bin\cli\*.efi           terminal binaries  (invoked by the shell)
#   \bin\gui\*.efi           windowed binaries  (launched with no shell)
#   \etc\motd.txt            seed config
#   \*.txt                   seed files (seed/)
#
# Freshness: an existing image is only rebuilt when a kernel/binary/seed
# file is newer than it, so files written at runtime survive across
# boots.  --force always re-creates (wipes runtime data).
#
# Usage:
#   ./scripts/mksystem.sh [options] [output.img]   default: $SYSTEM_IMG
#
# Options:
#   --force        rebuild even if the image is up to date
#   --size NM      image size in MB (default 64)
#   --label NAME   FAT volume label (default RSDOS)
#   --gpt          wrap the FAT volume in a GPT partition table (for
#                  dd-ing to real USB sticks whose firmware refuses
#                  superfloppies)
#   --help         show this help
#
# Needs: dosfstools (mkfs.fat), mtools (mcopy/mmd), cargo (kernel+bins
# built via scripts/build.sh when missing).
#
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# Absolute self path: lib.sh cd's to the project root, so a relative $0
# would no longer resolve by the time --help prints the header.
SELF="$SCRIPT_DIR/$(basename "$0")"

# shellcheck source=lib.sh
source "$SCRIPT_DIR/lib.sh"

IMG="$SYSTEM_IMG"
SIZE_MB=64
FORCE=0
GPT=0
LABEL="RSDOS"

while [ $# -gt 0 ]; do
	case "$1" in
	--force | --fresh)
		FORCE=1
		;;
	--size)
		shift
		SIZE_MB="$1"
		;;
	--label)
		shift
		LABEL="$1"
		;;
	--gpt)
		GPT=1
		;;
	--help | -h)
		awk 'NR > 1 && /^#/ { sub(/^# ?/, ""); print; next } NR > 1 { exit }' "$SELF"
		exit 0
		;;
	-*)
		echo "mksystem: unknown option: $1" >&2
		exit 1
		;;
	*)
		IMG="$1"
		;;
	esac
	shift
done

SEED_DIR="$PROJECT_DIR/seed"

# ---- checks ---------------------------------------------------------------
for cmd in mkfs.fat mcopy mmd; do
	if ! command -v "$cmd" >/dev/null 2>&1; then
		echo "mksystem: $cmd not found (install dosfstools + mtools)" >&2
		exit 1
	fi
done
if [ "$GPT" -eq 1 ]; then
	for cmd in sfdisk truncate dd; do
		if ! command -v "$cmd" >/dev/null 2>&1; then
			echo "mksystem: --gpt needs $cmd" >&2
			exit 1
		fi
	done
fi

# ---- build (only when artifacts are missing; build.sh is the only cargo
# invocation, so an already-compiled tree costs nothing) --------------------
if [ ! -f "$KERNEL" ] || ! ls "$OUT_DIR"/*.efi >/dev/null 2>&1; then
	"$SCRIPT_DIR/build.sh"
fi

# ---- freshness stamp ------------------------------------------------------
newest=""
for f in "$OUT_DIR"/*.efi "$SEED_DIR"/*; do
	[ -f "$f" ] || continue
	if [ -z "$newest" ] || [ "$f" -nt "$newest" ]; then
		newest="$f"
	fi
done
if [ "$FORCE" -eq 0 ] && [ -f "$IMG" ] && { [ -z "$newest" ] || [ ! "$newest" -nt "$IMG" ]; }; then
	echo "mksystem: up to date: $IMG (use --force to re-create)"
	exit 0
fi

# ---- create the FAT volume ------------------------------------------------
rm -f "$IMG"
SIZE_BLOCKS=$((SIZE_MB * 1024)) # mkfs.fat counts 1024-byte blocks
echo "mksystem: creating $IMG (${SIZE_MB}M FAT32, label $LABEL)"
mkfs.fat -C -F 32 -n "$LABEL" "$IMG" "$SIZE_BLOCKS" >/dev/null

echo "mksystem: staging system tree"
mmd -i "$IMG" ::/EFI ::/EFI/BOOT ::/etc ::/bin ::/bin/cli ::/bin/gui

# The firmware entry point and rsbl's kernel path are the same file.
mcopy -i "$IMG" "$KERNEL" ::/EFI/BOOT/BOOTX64.EFI
mcopy -i "$IMG" "$KERNEL" ::/bzImage
INITRD="$(mktemp -t rsdos-initrd.XXXXXX)"
trap 'rm -f "$INITRD"' EXIT
printf '' | gzip >"$INITRD"
mcopy -i "$IMG" "$INITRD" ::/initramfs.cpio.gz
echo "  \\EFI\\BOOT\\BOOTX64.EFI  \\bzImage  \\initramfs.cpio.gz"

# External binaries: everything under bins/ was compiled into $OUT_DIR.
# Launch surface decides the directory (ADR 0006): GUI_BINS -> /bin/gui,
# everything else -> /bin/cli.
for src in "$OUT_DIR"/*.efi; do
	[ -f "$src" ] || continue
	name="$(basename "$src" .efi)"
	[ "$name" = "rsdos" ] && continue # the kernel is not a /bin binary
	case " $GUI_BINS " in
	*" $name "*) kind="gui" ;;
	*) kind="cli" ;;
	esac
	mcopy -i "$IMG" "$src" "::/bin/$kind/$name.efi"
	printf '  \\bin\\%s\\%s.efi\n' "$kind" "$name"
done

# Seed files: motd lives in \etc, everything else at the volume root.
if [ -d "$SEED_DIR" ]; then
	for f in "$SEED_DIR"/*; do
		[ -f "$f" ] || continue
		name="$(basename "$f")"
		if [ "$name" = "motd.txt" ]; then
			mcopy -i "$IMG" "$f" ::/etc/"$name"
			printf '  \\etc\\%s\n' "$name"
		else
			mcopy -i "$IMG" "$f" ::/"$name"
			printf '  \\%s\n' "$name"
		fi
	done
fi

# ---- optional GPT wrapper (real-hardware dd target) -----------------------
if [ "$GPT" -eq 1 ]; then
	GPT_START=2048 # 1 MiB offset (standard alignment)
	GPT_IMG="$(mktemp)"
	truncate -s "${SIZE_MB}M" "$GPT_IMG"
	dd if="$IMG" of="$GPT_IMG" bs=512 seek="$GPT_START" conv=notrunc 2>/dev/null
	FINAL="$(mktemp)"
	truncate -s "$((SIZE_MB + 2))M" "$FINAL"
	sfdisk --quiet "$FINAL" <<EOF
label: gpt
type=U, start=$GPT_START, size=
EOF
	dd if="$GPT_IMG" of="$FINAL" bs=512 seek="$GPT_START" conv=notrunc 2>/dev/null
	rm -f "$GPT_IMG"
	mv "$FINAL" "$IMG"
	echo "mksystem: wrapped in GPT (partition at LBA $GPT_START)"
fi

echo ""
echo "=== Done: $IMG ==="
ls -lh "$IMG"
echo "Boot with:  ./scripts/run.sh   (or: make run)"
[ "$GPT" -eq 0 ] && echo "USB dd tip: add --gpt for firmwares that refuse superfloppies"
exit 0