Skip to content

Commit 4ccdd72

Browse files
authored
Merge pull request #17 from frostyard/nbc
feat: install with nbc
2 parents 9740c06 + f2428e3 commit 4ccdd72

File tree

3 files changed

+191
-199
lines changed

3 files changed

+191
-199
lines changed

snow_first_setup/core/backend.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,3 +279,76 @@ def set_dry_run(dry: bool):
279279
def subscribe_errors(callback):
280280
global _error_subscribers
281281
_error_subscribers.append(callback)
282+
283+
def run_script_streaming(name: str, args: list[str], root: bool = False, line_callback=None) -> bool:
284+
"""Execute a script and stream its output line-by-line to a callback.
285+
286+
This is designed for scripts that output JSON Lines (one JSON object per line)
287+
for real-time progress monitoring.
288+
289+
Args:
290+
name: Script name to execute
291+
args: Arguments to pass to the script
292+
root: Whether to run with pkexec for root privileges
293+
line_callback: Function called with each line of output (str)
294+
295+
Returns:
296+
bool: True if script succeeded, False otherwise
297+
"""
298+
if dry_run:
299+
print("dry-run (streaming)", name, args)
300+
# Simulate some progress events for dry-run testing
301+
import json
302+
import time
303+
fake_events = [
304+
{"type": "message", "message": "Dry run: Checking prerequisites..."},
305+
{"type": "step", "step": 1, "total_steps": 4, "step_name": "Creating partitions"},
306+
{"type": "step", "step": 2, "total_steps": 4, "step_name": "Formatting partitions"},
307+
{"type": "step", "step": 3, "total_steps": 4, "step_name": "Extracting filesystem"},
308+
{"type": "progress", "percent": 25, "message": "Layer 1/4"},
309+
{"type": "progress", "percent": 50, "message": "Layer 2/4"},
310+
{"type": "progress", "percent": 75, "message": "Layer 3/4"},
311+
{"type": "progress", "percent": 100, "message": "Layer 4/4"},
312+
{"type": "step", "step": 4, "total_steps": 4, "step_name": "Installing bootloader"},
313+
{"type": "complete", "message": "Installation complete (dry run)"},
314+
]
315+
for event in fake_events:
316+
if line_callback:
317+
line_callback(json.dumps(event))
318+
time.sleep(0.3)
319+
return True
320+
321+
if script_base_path is None:
322+
print("Could not run operation", name, args, "due to missing script base path")
323+
return False
324+
325+
script_path = os.path.join(script_base_path, name)
326+
command = [script_path] + args
327+
if root:
328+
command = ["pkexec"] + command
329+
330+
logger.info(f"Executing streaming command: {command}")
331+
332+
process = subprocess.Popen(
333+
command,
334+
stdout=subprocess.PIPE,
335+
stderr=subprocess.STDOUT,
336+
text=True,
337+
bufsize=1 # Line buffered
338+
)
339+
340+
# Read output line by line and pass to callback
341+
for line in process.stdout:
342+
line = line.strip()
343+
if line and line_callback:
344+
line_callback(line)
345+
logger.debug(f"Stream line from {name}: {line}")
346+
347+
process.wait()
348+
349+
if process.returncode != 0:
350+
report_error(name, command, f"Script exited with code {process.returncode}")
351+
print(name, args, "returned an error (exit code", process.returncode, ")")
352+
return False
353+
354+
return True
Lines changed: 26 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -1,223 +1,55 @@
11
#!/bin/bash
22
set -euo pipefail
33

4-
# Cleanup function to ensure mounted filesystems are unmounted
5-
cleanup() {
6-
local exit_code=$?
7-
if [ -n "${MOUNTPOINT:-}" ] && [ -d "$MOUNTPOINT" ]; then
8-
if mountpoint -q "$MOUNTPOINT" 2>/dev/null; then
9-
echo "Unmounting $MOUNTPOINT..."
10-
umount "$MOUNTPOINT" 2>/dev/null || true
11-
fi
12-
rmdir "$MOUNTPOINT" 2>/dev/null || true
13-
fi
14-
if [ $exit_code -ne 0 ]; then
15-
echo "Script failed with exit code $exit_code" >&2
16-
fi
17-
exit $exit_code
18-
}
19-
20-
# Set up trap to call cleanup on exit
21-
trap cleanup EXIT INT TERM
4+
# Install script using nbc (SNOW bootc installer)
5+
# Outputs JSON Lines for streaming progress to the installer GUI
226

237
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
24-
echo "usage:"
25-
echo "install-to-disk <image> <filesystem> <device> [fde]"
8+
echo '{"type":"error","message":"Missing arguments. Usage: install-to-disk <image> <filesystem> <device> [fde]"}'
269
exit 5
2710
fi
2811

2912
if ! [ "$UID" == "0" ]; then
30-
echo "this script must be run with super user privileges"
13+
echo '{"type":"error","message":"This script must be run with super user privileges"}'
3114
exit 6
3215
fi
3316

34-
3517
if ! [ -b "$3" ]; then
36-
echo "device '$3' is not a valid block device"
18+
echo '{"type":"error","message":"Device '"'$3'"' is not a valid block device"}'
3719
exit 9
3820
fi
3921

40-
# Parse FDE parameters
22+
IMAGE="$1"
23+
FILESYSTEM="$2"
24+
DEVICE="$3"
4125
FDE="${4:-false}"
4226

4327
# Validate FDE parameter
4428
if [ "$FDE" != "true" ] && [ "$FDE" != "false" ]; then
45-
echo "fde parameter must be 'true' or 'false', got: $FDE"
29+
echo '{"type":"error","message":"FDE parameter must be '"'true'"' or '"'false'"', got: '"$FDE"'"}'
4630
exit 7
4731
fi
4832

49-
BLOCKSETUP="direct"
50-
if [ "$FDE" == "true" ]; then
51-
BLOCKSETUP="tpm2-luks"
52-
fi
53-
54-
# if FDE is requested, verify that there is a tpm2 device available
55-
if [ "$FDE" == "true" ]; then
56-
if ! [ -e /dev/tpm0 ] && ! [ -e /dev/tpmrm0 ]; then
57-
echo "FDE with tpm2-luks requested, but no TPM2 device found"
58-
exit 8
59-
fi
60-
fi
61-
62-
# Check that bootc is available
63-
if ! command -v bootc &> /dev/null; then
64-
echo "bootc command not found, cannot proceed with installation"
33+
# Check that nbc is available
34+
if ! command -v nbc &> /dev/null; then
35+
echo '{"type":"error","message":"nbc command not found, cannot proceed with installation"}'
6536
exit 14
6637
fi
6738

68-
echo "Starting bootc installation to $3..."
69-
if ! RUST_LOG=debug bootc \
70-
install \
71-
to-disk \
72-
--composefs-backend \
73-
--block-setup "$BLOCKSETUP" \
74-
--filesystem "$2" \
75-
--source-imgref docker://"$1" \
76-
--target-imgref "$1" \
77-
--wipe \
78-
--bootloader systemd \
79-
--karg "quiet" \
80-
--karg "splash" \
81-
"$3"; then
82-
echo "bootc installation failed"
83-
exit 15
84-
fi
85-
echo "bootc installation completed successfully"
86-
87-
# HACK: fix secure boot in bootc
88-
# now that the install is done, we can fix the efi binaries
89-
# to support secure boot.
90-
# This is a workaround for the fact that the bootc installs
91-
# systemd-boot only. What we require is the signed-shim as
92-
# the first efi binary, which then loads systemd-boot.
93-
# shim is hard-coded to load grub, so we'll "fix" that by copying the
94-
# systemd-boot binaries into the right place with grub's name.
95-
# HACK ALERT! We're copying the efi binaries from the installer image
96-
# to the target image. This is not ideal, but it works for now.
97-
98-
# Mount the EFI partition from the target device ($3)
99-
# EFI partition is the second partition, so we use partprobe
100-
# to ensure the kernel sees it
101-
echo "Probing partitions on $3..."
102-
if ! partprobe "$3"; then
103-
echo "Failed to probe partitions on $3"
104-
exit 16
105-
fi
106-
107-
# Give the kernel a moment to recognize the new partitions
108-
sleep 2
109-
110-
DEVICE="$3"
111-
112-
# adjust partition names for devices that require 'p' before partition number
113-
if [[ "$DEVICE" == *"nvme"* || "$DEVICE" == *"mmcblk"* || "$DEVICE" == *"loop"* ]]; then
114-
DEVICE="${DEVICE}p"
115-
fi
116-
117-
EFI_PARTITION="${DEVICE}2"
118-
119-
# Verify the EFI partition exists
120-
if ! [ -b "$EFI_PARTITION" ]; then
121-
echo "EFI partition $EFI_PARTITION does not exist or is not a block device"
122-
exit 17
123-
fi
124-
125-
echo "Creating temporary mount point..."
126-
MOUNTPOINT=$(mktemp -d)
127-
if [ ! -d "$MOUNTPOINT" ]; then
128-
echo "Failed to create temporary mount point"
129-
exit 18
130-
fi
39+
# Build nbc install command with --json for streaming output
40+
NBC_ARGS=(
41+
"install"
42+
"--image" "$IMAGE"
43+
"--device" "$DEVICE"
44+
"--filesystem" "$FILESYSTEM"
45+
"--json"
46+
)
13147

132-
echo "Mounting EFI partition $EFI_PARTITION to $MOUNTPOINT..."
133-
if ! mount "$EFI_PARTITION" "$MOUNTPOINT"; then
134-
echo "Failed to mount EFI partition $EFI_PARTITION"
135-
rmdir "$MOUNTPOINT" 2>/dev/null || true
136-
exit 19
137-
fi
138-
139-
140-
if [ ! -d "$MOUNTPOINT/EFI/BOOT" ]; then
141-
echo "Creating $MOUNTPOINT/EFI/BOOT directory..."
142-
if ! mkdir -p "$MOUNTPOINT/EFI/BOOT"; then
143-
echo "Failed to create EFI/BOOT directory"
144-
exit 20
145-
fi
146-
fi
147-
148-
# make sure the source files exists
149-
echo "Verifying source EFI files..."
150-
if [ ! -f /usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed ]; then
151-
echo "systemd-bootx64.efi.signed not found, cannot copy to EFI partition"
152-
exit 10
153-
fi
154-
if [ ! -f /usr/lib/shim/shimx64.efi.signed ]; then
155-
echo "shimx64.efi.signed not found, cannot copy to EFI partition"
156-
exit 11
157-
fi
158-
if [ ! -f /usr/lib/shim/fbx64.efi.signed ]; then
159-
echo "fbx64.efi.signed not found, cannot copy to EFI partition"
160-
exit 12
161-
fi
162-
if [ ! -f /usr/lib/shim/mmx64.efi.signed ]; then
163-
echo "mmx64.efi.signed not found, cannot copy to EFI partition"
164-
exit 13
165-
fi
166-
167-
# replicate a debian secureboot efi setup
168-
echo "Creating EFI/snow directory..."
169-
if ! mkdir -p "$MOUNTPOINT/EFI/snow"; then
170-
echo "Failed to create EFI/snow directory"
171-
exit 21
172-
fi
173-
174-
echo "Copying secure boot EFI binaries..."
175-
if ! cp /usr/lib/shim/shimx64.efi.signed "$MOUNTPOINT/EFI/snow/shimx64.efi"; then
176-
echo "Failed to copy shimx64.efi"
177-
exit 22
178-
fi
179-
if ! cp /usr/lib/shim/fbx64.efi.signed "$MOUNTPOINT/EFI/snow/fbx64.efi"; then
180-
echo "Failed to copy fbx64.efi"
181-
exit 23
182-
fi
183-
if ! cp /usr/lib/shim/mmx64.efi.signed "$MOUNTPOINT/EFI/snow/mmx64.efi"; then
184-
echo "Failed to copy mmx64.efi"
185-
exit 24
186-
fi
187-
if ! cp /usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed "$MOUNTPOINT/EFI/snow/grubx64.efi"; then
188-
echo "Failed to copy systemd-bootx64.efi as grubx64.efi"
189-
exit 25
190-
fi
191-
192-
# create a new boot entry for shim
193-
echo "Creating EFI boot entry..."
194-
if command -v efibootmgr &> /dev/null; then
195-
if ! efibootmgr --create --disk "$3" --part 2 --loader '\EFI\snow\shimx64.efi' --label "Snow Secure Boot"; then
196-
echo "Warning: Failed to create EFI boot entry (continuing anyway)"
197-
fi
198-
else
199-
echo "Warning: efibootmgr not found, skipping boot entry creation"
200-
fi
201-
202-
# finally uncomment the line in loader.conf that sets the timeout
203-
# so that the boot menu appears, allowing the user to edit the kargs
204-
# if needed to unlock the disk
205-
if [ -f "$MOUNTPOINT/loader/loader.conf" ]; then
206-
echo "Configuring bootloader timeout..."
207-
if ! sed -i 's/^#timeout/timeout/' "$MOUNTPOINT/loader/loader.conf"; then
208-
echo "Warning: Failed to update loader.conf (continuing anyway)"
209-
fi
210-
else
211-
echo "Warning: loader.conf not found at $MOUNTPOINT/loader/loader.conf"
212-
fi
213-
214-
# clean up
215-
echo "Unmounting EFI partition..."
216-
if ! umount "$MOUNTPOINT"; then
217-
echo "Warning: Failed to unmount $MOUNTPOINT cleanly"
218-
# Try force unmount as last resort
219-
umount -f "$MOUNTPOINT" 2>/dev/null || true
48+
# Add FDE flag if enabled
49+
if [ "$FDE" == "true" ]; then
50+
NBC_ARGS+=("--fde")
22051
fi
221-
rmdir "$MOUNTPOINT" 2>/dev/null || true
22252

223-
echo "Installation completed successfully!"
53+
# Run nbc install with JSON output streaming directly to stdout
54+
# nbc outputs JSON Lines (one JSON object per line) for real-time progress
55+
exec nbc "${NBC_ARGS[@]}"

0 commit comments

Comments
 (0)