Skip to content

Commit edb4e05

Browse files
committed
feat(install): G1 GNOME desktop profile + G2 clean-disk installer
G1: profiles/desktop-gnome — declarative GNOME (gdm + GNOME + PipeWire + NetworkManager + sourceos user), NO syncd/katello/sops/mesh, boots with zero enrollment. Exposed as flake nixosModules.desktop-gnome. Full system evaluates to a buildable derivation, warning-free on 26.11. G2: scripts/install-image.sh — safe blank-disk installer (explicit target, refuses live medium, typed confirm, GPT ESP+root) that composes a per-machine flake from hardware-configuration.nix + the desktop-gnome module, then nixos-install. Wired into the ISO 'sourceos-install' command. Closes the two blocking gaps: the public ISO now installs a usable GNOME desktop to a fresh disk.
1 parent be63df2 commit edb4e05

4 files changed

Lines changed: 190 additions & 4 deletions

File tree

flake.nix

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@
138138

139139
nixosModules = {
140140
sourceos-syncd = import ./modules/nixos/sourceos-syncd/default.nix;
141+
# Reusable GNOME desktop profile for end-user installs. The public
142+
# installer (scripts/install-image.sh) composes this with a freshly
143+
# generated hardware-configuration.nix on the target machine, so no
144+
# per-machine hardware config is committed here.
145+
desktop-gnome = import ./profiles/desktop-gnome/default.nix;
141146
};
142147

143148
nixosConfigurations = {

images/installer-common.nix

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ in
2929
# ── Bundle the SourceOS installer into the live image ────────────────────────
3030
# The install scripts ship read-only at /etc/sourceos-installer; a wrapper
3131
# `sourceos-install` is on PATH so the first thing a user sees works.
32+
environment.etc."sourceos-installer/install-image.sh".source = ../scripts/install-image.sh;
3233
environment.etc."sourceos-installer/install-on-device.sh".source = ../scripts/install-on-device.sh;
3334
environment.etc."sourceos-installer/preflight.sh".source = ../scripts/preflight.sh;
3435

@@ -37,10 +38,9 @@ in
3738
rsync vim gnused gawk pciutils usbutils
3839
]) ++ [
3940
(pkgs.writeShellScriptBin "sourceos-install" ''
40-
set -euo pipefail
41-
echo "SourceOS ${release} installer"
42-
echo "Target disk auto-detection runs next; you'll confirm before any format."
43-
exec sudo bash /etc/sourceos-installer/install-on-device.sh "$@"
41+
echo "SourceOS ${release} installer — clean-disk GNOME install."
42+
echo "You will pick a target disk and confirm before anything is erased."
43+
exec sudo bash /etc/sourceos-installer/install-image.sh "$@"
4444
'')
4545
];
4646

profiles/desktop-gnome/default.nix

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# SourceOS GNOME desktop profile — the clean end-user target installed from the
2+
# public ISO. Deliberately does NOT pull in the server machinery (sourceos-syncd,
3+
# Katello, sops, mesh): a fresh install must boot with zero enrollment.
4+
#
5+
# The imperative GNOME "polish" layer (profiles/linux-dev/workstation-v0) can be
6+
# applied on top after first boot via its apply.sh; it is not required to boot.
7+
{ lib, pkgs, ... }:
8+
let
9+
release = "26.11";
10+
in
11+
{
12+
# ── Desktop ───────────────────────────────────────────────────────────────
13+
services.xserver.enable = true; # X/Xwayland + keymap plumbing
14+
services.displayManager.gdm.enable = true;
15+
services.desktopManager.gnome.enable = true;
16+
services.xserver.xkb.layout = "us";
17+
18+
# Audio (PipeWire) + printing — expected on a desktop.
19+
services.pulseaudio.enable = false;
20+
security.rtkit.enable = true;
21+
services.pipewire = {
22+
enable = true;
23+
alsa.enable = true;
24+
pulse.enable = true;
25+
};
26+
services.printing.enable = true;
27+
28+
# ── Networking ──────────────────────────────────────────────────────────────
29+
networking.networkmanager.enable = true;
30+
31+
# ── Default user ──────────────────────────────────────────────────────────────
32+
# Password is set interactively during install (install-image.sh runs passwd),
33+
# so no password is baked into the image.
34+
users.users.sourceos = {
35+
isNormalUser = true;
36+
description = "SourceOS";
37+
extraGroups = [ "wheel" "networkmanager" "video" "audio" ];
38+
};
39+
# Wheel may sudo; lock down later if desired.
40+
security.sudo.wheelNeedsPassword = lib.mkDefault true;
41+
42+
# ── Base apps + Nix UX ─────────────────────────────────────────────────────
43+
nix.settings.experimental-features = [ "nix-command" "flakes" ];
44+
environment.systemPackages = with pkgs; [
45+
firefox git curl vim gnome-tweaks
46+
];
47+
48+
# GNOME pulls a lot of default apps; trim the most universally unwanted.
49+
environment.gnome.excludePackages = with pkgs; [ gnome-tour epiphany geary ];
50+
51+
# ── Boot (generic UEFI; Apple Silicon uses its own module, not this profile) ──
52+
boot.loader.systemd-boot.enable = lib.mkDefault true;
53+
boot.loader.efi.canTouchEfiVariables = lib.mkDefault true;
54+
55+
time.timeZone = lib.mkDefault "UTC";
56+
i18n.defaultLocale = lib.mkDefault "en_US.UTF-8";
57+
system.stateVersion = release;
58+
}

scripts/install-image.sh

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env bash
2+
# install-image.sh — SourceOS clean-disk installer for the public ISO (PC / ARM).
3+
#
4+
# This is the BLANK-DISK path: it creates a fresh GPT layout (ESP + root) on a
5+
# target disk and installs the SourceOS GNOME desktop. For Apple Silicon use the
6+
# Asahi path (get-sourceos.sh); for a disk that is already partitioned by the
7+
# Asahi flow use install-on-device.sh.
8+
#
9+
# SAFETY: this ERASES the target disk. It requires an explicit disk argument,
10+
# refuses the disk the live system is running from, shows the plan, and waits
11+
# for you to type the disk name to confirm.
12+
#
13+
# Usage (run as root in the SourceOS live installer):
14+
# sudo install-image.sh # interactive: pick from a list
15+
# sudo install-image.sh /dev/nvme0n1 # non-interactive target
16+
#
17+
# Env overrides: FLAKE_REF (default github:SourceOS-Linux/source-os),
18+
# MODULE (default desktop-gnome), HOSTNAME (default sourceos).
19+
set -euo pipefail
20+
21+
FLAKE_REF="${FLAKE_REF:-github:SourceOS-Linux/source-os}"
22+
MODULE="${MODULE:-desktop-gnome}"
23+
TARGET_HOSTNAME="${HOSTNAME:-sourceos}"
24+
MNT=/mnt
25+
26+
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
27+
ok() { printf " ${GREEN}${NC} %s\n" "$*"; }
28+
info() { printf " ${CYAN}·${NC} %s\n" "$*"; }
29+
warn() { printf " ${YELLOW}!${NC} %s\n" "$*"; }
30+
die() { printf " ${RED}✗ ERROR:${NC} %s\n" "$*" >&2; exit 1; }
31+
32+
[[ $(id -u) -eq 0 ]] || die "Run as root (sudo install-image.sh)"
33+
34+
# ── Identify the disk the live system runs from, so we never offer/erase it ──
35+
LIVE_SRC="$(findmnt -no SOURCE / 2>/dev/null || true)"
36+
LIVE_DISK=""
37+
[[ -n "$LIVE_SRC" ]] && LIVE_DISK="/dev/$(lsblk -no PKNAME "$LIVE_SRC" 2>/dev/null | head -1)"
38+
39+
list_disks() { lsblk -dno NAME,SIZE,MODEL,TYPE | awk '$NF=="disk"{print "/dev/"$1" "$2" "substr($0, index($0,$3))}'; }
40+
41+
# ── Choose target ─────────────────────────────────────────────────────────────
42+
TARGET="${1:-}"
43+
if [[ -z "$TARGET" ]]; then
44+
echo; info "Available disks (the live USB/ISO disk is excluded):"; echo
45+
list_disks | grep -v -- "${LIVE_DISK:-__none__}" | sed 's/^/ /'
46+
echo
47+
read -rp " Target disk (e.g. /dev/nvme0n1 or /dev/sda): " TARGET
48+
fi
49+
[[ -b "$TARGET" ]] || die "Not a block device: $TARGET"
50+
[[ "$TARGET" != "$LIVE_DISK" ]] || die "Refusing to erase the live medium ($TARGET)."
51+
52+
# ── Confirm (typed) ───────────────────────────────────────────────────────────
53+
SIZE="$(lsblk -dno SIZE "$TARGET")"
54+
echo
55+
warn "About to ERASE ${TARGET} (${SIZE}) and install SourceOS:"
56+
echo " 1. New GPT label"
57+
echo " 2. ESP 512 MiB FAT32 → /boot"
58+
echo " 3. Root rest ext4 → / (GNOME desktop)"
59+
echo
60+
read -rp " Type the disk name ('${TARGET}') to confirm: " CONFIRM
61+
[[ "$CONFIRM" == "$TARGET" ]] || die "Confirmation did not match. Aborted — nothing changed."
62+
63+
# Partition suffix: nvme0n1 -> nvme0n1p1 ; sda -> sda1
64+
part() { case "$TARGET" in *[0-9]) echo "${TARGET}p$1" ;; *) echo "${TARGET}$1" ;; esac; }
65+
ESP="$(part 1)"; ROOT="$(part 2)"
66+
67+
# ── Partition + format ────────────────────────────────────────────────────────
68+
info "Partitioning ${TARGET}..."
69+
wipefs -a "$TARGET" >/dev/null 2>&1 || true
70+
sgdisk --zap-all "$TARGET" >/dev/null
71+
sgdisk -n1:0:+512M -t1:ef00 -c1:EFI "$TARGET" >/dev/null
72+
sgdisk -n2:0:0 -t2:8300 -c2:nixos "$TARGET" >/dev/null
73+
partprobe "$TARGET" 2>/dev/null || true; udevadm settle 2>/dev/null || true; sleep 1
74+
75+
info "Formatting..."
76+
mkfs.fat -F32 -n EFI "$ESP" >/dev/null
77+
mkfs.ext4 -F -L nixos "$ROOT" >/dev/null
78+
ok "ESP=$ESP ROOT=$ROOT"
79+
80+
# ── Mount ─────────────────────────────────────────────────────────────────────
81+
mount "$ROOT" "$MNT"
82+
mkdir -p "$MNT/boot"
83+
mount "$ESP" "$MNT/boot"
84+
85+
# ── Compose a per-machine flake: hardware-config + the SourceOS GNOME module ──
86+
info "Generating hardware configuration..."
87+
nixos-generate-config --root "$MNT" --no-filesystems >/dev/null 2>&1 || nixos-generate-config --root "$MNT"
88+
# Keep the generated hardware-configuration.nix; replace configuration with a
89+
# flake that pulls SourceOS and applies the desktop-gnome module.
90+
NIXDIR="$MNT/etc/nixos"
91+
mkdir -p "$NIXDIR"
92+
cat > "$NIXDIR/flake.nix" <<EOF
93+
{
94+
description = "SourceOS machine";
95+
inputs.sourceos.url = "${FLAKE_REF}";
96+
inputs.nixpkgs.follows = "sourceos/nixpkgs";
97+
outputs = { self, nixpkgs, sourceos }: {
98+
nixosConfigurations.${TARGET_HOSTNAME} = nixpkgs.lib.nixosSystem {
99+
modules = [
100+
./hardware-configuration.nix
101+
sourceos.nixosModules.${MODULE}
102+
{ networking.hostName = "${TARGET_HOSTNAME}"; }
103+
];
104+
};
105+
};
106+
}
107+
EOF
108+
ok "Wrote $NIXDIR/flake.nix (module: ${MODULE})"
109+
110+
# ── Install ───────────────────────────────────────────────────────────────────
111+
info "Running nixos-install (this builds the system; grab a coffee)..."
112+
nixos-install --root "$MNT" --flake "$NIXDIR#${TARGET_HOSTNAME}" --no-channel-copy
113+
114+
# ── Password ──────────────────────────────────────────────────────────────────
115+
echo; info "Set a password for the 'sourceos' user:"
116+
nixos-enter --root "$MNT" -c 'passwd sourceos'
117+
118+
echo
119+
ok "SourceOS installed to ${TARGET}."
120+
info "Remove the USB and reboot. After first boot you can apply the GNOME polish layer:"
121+
info " bash <(curl -fsSL https://raw.githubusercontent.com/SourceOS-Linux/source-os/main/profiles/linux-dev/workstation-v0/gnome/apply.sh)"
122+
echo
123+
read -rp " Reboot now? [y/N] " R; [[ "${R:-N}" =~ ^[Yy]$ ]] && { umount -R "$MNT"; reboot; }

0 commit comments

Comments
 (0)