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
|