Establish clean homelab infrastructure baseline
Reorganize the brownfield repository, remove retired and generated artifacts, harden ignore rules, and record the GitOps/IaC redesign.
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
# Installing Windows 9x (95 / 98 / ME) over the network
|
||||
|
||||
9x is DOS-based, not NT — none of the NT5/NT6 methods apply. There are two routes; the second
|
||||
is much better for netboot.
|
||||
|
||||
## Route A — the "real DOS way" (not recommended)
|
||||
|
||||
PXE → `memdisk` boots a DOS floppy/ISO (FreeDOS or MS-DOS) → load a **real-mode NIC packet
|
||||
driver** + network redirector → copy the `WIN98` CABs from a share → run `setup.exe`. It works
|
||||
(netboot.xyz already has a FreeDOS entry to bootstrap from), but real-mode DOS packet drivers
|
||||
are a per-NIC nightmare and the CAB copy is slow. Only worth it for authenticity.
|
||||
|
||||
## Route B — win98-quickinstall (recommended)
|
||||
|
||||
<https://github.com/oerg866/win98-quickinstall>
|
||||
|
||||
The important thing: **its installer is Linux-based** (a minimal Linux env + a writer called
|
||||
`lunmercy`). It does **not** run DOS-era Setup — it streams a pre-made image ("MercyPak",
|
||||
designed to be read once, sequentially) onto the disk, then you reboot into a working Win98.
|
||||
It bundles driver libraries (NIC/sound/video/storage incl. USB/NVMe) that can be injected at
|
||||
install time — so it also solves 9x's driver problem.
|
||||
|
||||
Because the installer is Linux, **it netboots exactly like any Linux distro** — which iPXE does
|
||||
natively — with no DOS, no packet drivers, and no mid-install media dependency (it's a
|
||||
single-pass writer, so unlike XP it survives the process fine).
|
||||
|
||||
```
|
||||
iPXE → kernel vmlinuz + initrd (HTTP) → lunmercy writes the image → reboot into Win98
|
||||
```
|
||||
|
||||
### Building the image (on Linux)
|
||||
|
||||
QuickInstall images are **derived from an already-set-up Win98 install**, so you either:
|
||||
- grab a **prebuilt release ISO** from the repo (fastest way to test the pipeline), or
|
||||
- `git clone` + `./build.sh` against your **Win98 SE ISO** to produce a custom image + boot
|
||||
media (see the repo's `BUILDING.md`).
|
||||
|
||||
Output includes bootable **ISO / USB / floppy** images and the underlying **kernel + initrd**.
|
||||
|
||||
### Wiring it into netboot — two ways
|
||||
|
||||
Host the files under the appliance's assets dir so they're on `:8080`:
|
||||
`/home/panxiao81/services/apps/netboot/assets/win98qi/` → `http://192.168.10.127:8080/win98qi/`
|
||||
|
||||
**B1. Quick path — `sanboot` the ISO** (good first test). Works here *because* it's a
|
||||
one-shot Linux writer (the reboot-mid-install problem that kills XP sanboot doesn't apply):
|
||||
|
||||
```ipxe
|
||||
#!ipxe
|
||||
sanboot http://192.168.10.127:8080/win98qi/win98-quickinstall.iso
|
||||
```
|
||||
|
||||
**B2. Proper path — `kernel`/`initrd` over HTTP.** Extract `vmlinuz` + `initrd` from the ISO
|
||||
and boot them directly:
|
||||
|
||||
```ipxe
|
||||
#!ipxe
|
||||
kernel http://192.168.10.127:8080/win98qi/vmlinuz
|
||||
initrd http://192.168.10.127:8080/win98qi/initrd.gz
|
||||
imgargs vmlinuz <any source/args lunmercy needs>
|
||||
boot
|
||||
```
|
||||
|
||||
> **Verify first:** whether `lunmercy` can read the MercyPak image from the **network**
|
||||
> (HTTP/NFS) via a kernel-cmdline source. If yes → serve the image over HTTP (its sequential
|
||||
> read design is ideal). If it only reads from the boot medium → either bake the image into
|
||||
> the `initrd`, or just use the `sanboot`-ISO path (B1), which keeps the image on the virtual CD.
|
||||
|
||||
### Adding it to the netboot.xyz menu
|
||||
|
||||
netboot.xyz supports a **custom menu**: set `custom_url` in `config/menus/local-vars.ipxe` to a
|
||||
dir that serves a `custom.ipxe`, and the menu gains a custom entry that chains it.
|
||||
|
||||
```ipxe
|
||||
# in local-vars.ipxe
|
||||
set custom_url http://192.168.10.127:8080
|
||||
```
|
||||
|
||||
Then host `assets/custom.ipxe` (→ `:8080/custom.ipxe`) containing a menu that chains the B1 or
|
||||
B2 snippet above. For a one-off test you can also just drop to the **iPXE shell** (menu →
|
||||
"iPXE shell") and paste the `sanboot`/`kernel` lines directly.
|
||||
|
||||
## Test in a VM
|
||||
|
||||
Use the `drive-vm` skill (BIOS, IDE disk — 9x is BIOS-only and wants IDE):
|
||||
|
||||
```bash
|
||||
V=~/.claude/skills/drive-vm/scripts/vmctl.sh
|
||||
# either boot the ISO directly to validate the installer itself:
|
||||
$V start --name w98 --mem 512 --disk /path/blank.qcow2 --iso /path/win98qi.iso --boot d
|
||||
# or netboot it (bridge) once the menu/custom entry is wired:
|
||||
$V start --name w98 --mem 512 --disk /path/blank.qcow2 --bridge br0 --netboot
|
||||
```
|
||||
|
||||
Then screenshot/keydrive through it. 9x is happy with **512 MB RAM** (more can upset it),
|
||||
a **BIOS** machine, and an **IDE** disk.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **BIOS/CSM only** — no UEFI. Legacy boot for both the installer and the target.
|
||||
- **9x RAM ceiling** — >512 MB–1 GB can cause "insufficient memory" errors; cap the VM/target.
|
||||
- **Disk geometry** — FAT32; keep the system partition sane (<127 GB is safest for 9x).
|
||||
- Drivers are handled by quickinstall's driver libs at install time — no F6/packet-driver pain.
|
||||
- The MercyPak-over-network question (above) is the one thing to confirm before committing to
|
||||
the pure `kernel`/`initrd`+HTTP path; `sanboot`-ISO always works as a fallback.
|
||||
@@ -0,0 +1,151 @@
|
||||
# Installing NT5 (Windows 2000 / XP / Server 2003) over the network
|
||||
|
||||
NT5 predates WinPE/`install.wim`/`setup.exe`, so the modern pipeline in `WINDOWS.md` does
|
||||
**not** apply. It also can't be `sanboot`ed (setup reboots mid-install and loses the virtual
|
||||
CD). The workable network method that reuses our infra (SMB share + picker) is:
|
||||
|
||||
**boot an x86 WinPE → mount the share → run `winnt32.exe /makelocalsource` from the NT5 source.**
|
||||
|
||||
`/makelocalsource` copies the whole `i386` tree to the target disk first, so the share isn't
|
||||
needed after the reboot into the real setup.
|
||||
|
||||
```
|
||||
iPXE (Windows menu) → x86 WinPE (HTTP/wimboot)
|
||||
└─ startnet.cmd: drvload NIC → net use Z: \\192.168.10.127\win
|
||||
└─ winnt32.exe /syspart /tempdrive /makelocalsource /unattend:winnt.sif /noreboot
|
||||
└─ reboot → NT5 text-mode setup → GUI setup (all from local disk)
|
||||
```
|
||||
|
||||
## Hard prerequisite: an x86 WinPE
|
||||
|
||||
`winnt32.exe` is **32-bit**. Our built WinPE is **x64**, which has no 32-bit support unless
|
||||
`WinPE-WoW64` is added (an ADK/Windows step). So you need one of:
|
||||
|
||||
- **An x86 WinPE** — built the same way as the x64 one (see `WINDOWS.md` step 1), but from a
|
||||
**32-bit `boot.wim`**, i.e. a **Win10 x86** (or Win7 x86) ISO. Place it at
|
||||
`assets/WinPE/x86/` (the netboot.xyz Windows menu's arch toggle switches `${win_arch}` to
|
||||
`x86`). *No x86 Windows source is on this host yet — this is the missing ingredient.*
|
||||
- **or** x64 WinPE + `dism /add-package WinPE-WoW64.cab` (Windows box).
|
||||
|
||||
Everything else below is editable on Linux (the source tree + answer file live on the share).
|
||||
|
||||
## 1. Put the NT5 source on the share
|
||||
|
||||
One folder per version under `/mnt/pool/win`, containing the extracted `i386` tree:
|
||||
|
||||
```
|
||||
/mnt/pool/win/win2k/
|
||||
├── i386/ ← extracted from the Win2000/XP/2003 ISO
|
||||
├── $OEM$/ ← driver integration (see §3)
|
||||
│ ├── Textmode/ ← mass-storage F6 driver(s) + txtsetup.oem
|
||||
│ └── $1/Drivers/ ← PnP drivers (NIC/GPU/chipset) → copied to C:\Drivers
|
||||
└── winnt.sif ← unattended answer file (§2)
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo mount -o loop,ro "Windows 2000 ....iso" /mnt/iso
|
||||
mkdir -p /mnt/pool/win/win2k
|
||||
cp -a /mnt/iso/I386 /mnt/pool/win/win2k/i386 # case as the ISO presents it
|
||||
sudo umount /mnt/iso
|
||||
```
|
||||
|
||||
The picker (`menu.cmd`) lists folders with a `setup.exe`; NT5 has no `setup.exe`, so either
|
||||
add a tiny launcher or just run winnt32 by hand (below).
|
||||
|
||||
## 2. `winnt.sif` answer file (driver integration + unattend)
|
||||
|
||||
Skeleton — drop it in the version folder. Fill in the `[MassStorageDrivers]` /
|
||||
`[OEMBootFiles]` from your controller's F6 package's `txtsetup.oem`:
|
||||
|
||||
```ini
|
||||
[Data]
|
||||
AutoPartition = 0
|
||||
MsDosInitiated = 0
|
||||
UnattendedInstall = Yes
|
||||
|
||||
[Unattended]
|
||||
UnattendMode = FullUnattended
|
||||
OemPreinstall = Yes ; required for $OEM$ processing
|
||||
OemSkipEula = Yes
|
||||
FileSystem = LeaveAlone ; or ConvertNTFS
|
||||
OemPnPDriversPath = Drivers\NIC;Drivers\Chipset ; under C:\Drivers (from $OEM$\$1\Drivers)
|
||||
|
||||
[MassStorageDrivers]
|
||||
"Intel(R) SATA AHCI Controller" = "OEM" ; the exact string from txtsetup.oem
|
||||
"IDE CD-ROM (ATAPI 1.2)/PCI IDE Controller" = "RETAIL" ; keep inbox IDE too
|
||||
|
||||
[OEMBootFiles]
|
||||
txtsetup.oem
|
||||
iaahci.inf
|
||||
iaahci.sys
|
||||
iaahci.cat
|
||||
|
||||
[GuiUnattended]
|
||||
AdminPassword = *
|
||||
TimeZone = 210 ; 210 = China Standard Time
|
||||
OEMSkipRegional = 1
|
||||
OemSkipWelcome = 1
|
||||
|
||||
[UserData]
|
||||
ProductKey = XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
|
||||
FullName = "user"
|
||||
OrgName = "home"
|
||||
ComputerName = *
|
||||
|
||||
[Identification]
|
||||
JoinWorkgroup = WORKGROUP
|
||||
|
||||
[Networking]
|
||||
InstallDefaultComponents = Yes
|
||||
```
|
||||
|
||||
## 3. Drivers — two separate problems
|
||||
|
||||
**a) NIC for WinPE** (so the PE can reach the share): bake it into the x86 `boot.wim` and
|
||||
`drvload` it — no Windows tooling:
|
||||
|
||||
```bash
|
||||
wimlib-imagex update assets/WinPE/x86/sources/boot.wim 1 --command="add /path/to/nicdrv /Drivers/nic"
|
||||
# startnet.cmd, BEFORE net use: drvload X:\Drivers\nic\<driver>.inf
|
||||
```
|
||||
|
||||
Use a **PE-compatible** driver (a Win10/7 *x86* NIC driver for an x86 PE) — *not* the XP one.
|
||||
|
||||
**b) Storage controller for the TARGET** (the `0x7B` BSOD): NT5 text-mode setup has no inbox
|
||||
AHCI/NVMe/RAID. Put the controller's **F6 package** in `$OEM$\Textmode\` and reference it from
|
||||
`[MassStorageDrivers]`/`[OEMBootFiles]` above. All editable on the Linux-hosted share.
|
||||
|
||||
**Easiest dodge (recommended where possible):**
|
||||
- **VM → IDE disk** (`vmctl … --disk` uses `if=ide`) → inbox driver, **skip §3b entirely**.
|
||||
- **Retro physical → BIOS SATA = IDE/Legacy/Compatibility** → inbox driver. (NT5 is BIOS-only;
|
||||
targets are old hardware that usually offers this.)
|
||||
|
||||
**c) Installed-OS drivers** (NIC/GPU/chipset for the running XP, distinct from the PE's NIC):
|
||||
put XP-era drivers in `$OEM$\$1\Drivers\…` and list them in `OemPnPDriversPath` (§2). They get
|
||||
copied to `C:\Drivers` and PnP-installed during GUI setup.
|
||||
|
||||
## 4. Run it (inside the x86 WinPE)
|
||||
|
||||
The target partition must exist, be **formatted (FAT32/NTFS)** and marked **active**
|
||||
(`diskpart`: `create partition primary` → `format fs=ntfs quick` → `active`). Then:
|
||||
|
||||
```bat
|
||||
net use Z: \\192.168.10.127\win
|
||||
Z:\win2k\i386\winnt32 /s:Z:\win2k\i386 /unattend:Z:\win2k\winnt.sif ^
|
||||
/syspart:C: /tempdrive:C: /makelocalsource /noreboot
|
||||
```
|
||||
|
||||
`/syspart` requires `/tempdrive`. On `/noreboot` completion, reboot the target off its **local
|
||||
disk** — text-mode setup runs from the copied `$WIN_NT$.~BT`/`~LS`, then GUI setup, no network.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **XP/2000/2003 are BIOS/CSM only** — no UEFI. Boot the target (and the PE) in legacy mode.
|
||||
- winnt32 under WinPE wants `/makelocalsource`; without it, it expects the source to remain
|
||||
reachable across the reboot (it won't be).
|
||||
- Chinese media (like the Win2000 ISO here) installs fine; the answer file drives it in that
|
||||
language. TimeZone 210 = CST.
|
||||
- **Activation:** volume/retail keys as appropriate; NT5 activation servers are long gone —
|
||||
use a VL edition/key for hands-off installs.
|
||||
- If you'd rather not deal with any of this on real hardware: **install NT5 once, capture the
|
||||
disk, and PXE-boot Clonezilla** (already in the netboot menu) to clone it to targets.
|
||||
@@ -0,0 +1,109 @@
|
||||
# netboot
|
||||
|
||||
Self-hosted [netboot.xyz](https://netboot.xyz) PXE boot server for the LAN, using the
|
||||
official appliance container plus a `dnsmasq` proxyDHCP so it coexists with the existing
|
||||
DHCP server (the **NEC IX router**, which is left untouched).
|
||||
|
||||
## Architecture
|
||||
|
||||
The NEC IX router keeps leasing IPs. `dnsmasq` runs in **proxyDHCP** mode and only answers
|
||||
the PXE/boot part of the conversation, pointing clients at this host (`192.168.10.127`).
|
||||
|
||||
Client type (announced via DHCP option 60/93) → what it gets:
|
||||
|
||||
| Client | Transport | File |
|
||||
|------------------------------------------|-----------|----------------------------------------------|
|
||||
| Legacy BIOS (`arch 0`) | TFTP | `netboot.xyz.kpxe` |
|
||||
| UEFI, normal PXE (`arch 7/9`) | TFTP | `netboot.xyz.efi` |
|
||||
| UEFI with HTTP Boot (`vendor HTTPClient`)| HTTP | `http://192.168.10.127:8080/menus/netboot.xyz.efi` |
|
||||
|
||||
Fallback is automatic: a UEFI box only announces `HTTPClient` when HTTP Boot is actually
|
||||
enabled/supported; otherwise it does normal PXE and lands on the TFTP `.efi` branch.
|
||||
|
||||
This is a **two-stage** chain, which matters for the dnsmasq config:
|
||||
|
||||
1. **Firmware → netboot.xyz iPXE.** Raw firmware (not iPXE) gets the binary above. proxyDHCP
|
||||
*requires* `pxe-service` here — plain `dhcp-boot` produces no boot offer in proxy mode.
|
||||
2. **netboot.xyz iPXE → menu.** The loaded `.efi`/`.kpxe` re-does DHCP (announcing itself via
|
||||
option 175) and dnsmasq answers with `dhcp-boot=tag:ipxe,netboot.xyz.efi,,192.168.10.127`.
|
||||
Two details matter, both dictated by the bootstrap **embedded in the netboot.xyz binary**:
|
||||
|
||||
- **The bootfile must be a *recognised binary name*** (`netboot.xyz.efi`), not `menu.ipxe`.
|
||||
The embedded bootstrap only chains the menu **locally** (its `:tftpmenu` branch) when the
|
||||
bootfile matches one of its own binary names; any other name skips that branch and boots
|
||||
the **public** `boot.netboot.xyz` menu instead.
|
||||
- **The router's DHCP `next-server` must point at `192.168.10.127`** (see below). Under
|
||||
proxyDHCP the bootstrap fetches its `local-vars.ipxe` from `${next-server}` — the value
|
||||
from the *real* DHCP server (the NEC IX router), **not** from dnsmasq's
|
||||
`${proxydhcp/next-server}`. `local-vars.ipxe` is what sets `use_proxydhcp_settings true`
|
||||
(the no-keypress switch), so if it can't be fetched the UEFI client stalls fetching from
|
||||
the router, then prompts for a `p` keypress or falls back to the public menu.
|
||||
|
||||
The boot binaries then chain the menu **locally** over TFTP from `192.168.10.127`, so clients
|
||||
boot *this* host's menu, not the public site. Only the version check and distro mirrors reach
|
||||
the internet.
|
||||
|
||||
### Required NEC IX router setting
|
||||
|
||||
The router keeps leasing IPs as before, but its DHCP scope for the LAN must advertise
|
||||
**`next-server 192.168.10.127`** (a.k.a. the `siaddr` / BOOTP server field) on the LAN DHCP
|
||||
pool. This is the one piece of PXE config the router *does* need — it does not otherwise
|
||||
PXE-boot anything, and regular (non-PXE) DHCP clients ignore `next-server`. Set it via the
|
||||
DHCP-server/boot-server (`siaddr`) option of the IX DHCP profile serving the `192.168.10.0/24`
|
||||
scope; leave the bootfile name unset (dnsmasq's proxyDHCP still supplies it).
|
||||
|
||||
## Services (all on host `192.168.10.127`)
|
||||
|
||||
| Port | Service | Provided by | Purpose |
|
||||
|-------------|-----------|------------------------|------------------------------------------|
|
||||
| `67/udp` | proxyDHCP | `dnsmasq` (host net) | PXE boot offers (no IP leasing) |
|
||||
| `69/udp` | TFTP | `netbootxyz` | serves `/config/menus` (binaries + menu) |
|
||||
| `8080` | HTTP | `netbootxyz` nginx | `/` = `/assets` mirror; `/menus/` = binaries (UEFI HTTP Boot) |
|
||||
| `3000` | Web UI | `netbootxyz` | manage menus / download assets |
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
docker compose up -d # start
|
||||
docker compose logs -f dnsmasq # watch DHCP offers during a client boot
|
||||
docker compose down # stop
|
||||
```
|
||||
|
||||
Config manager (add/update distros, edit menus): <http://192.168.10.127:3000>
|
||||
|
||||
## Verified
|
||||
|
||||
Both PXE paths were tested end-to-end with QEMU VMs bridged onto `br0` (real SeaBIOS and
|
||||
OVMF/UEFI firmware), booting all the way to the local netboot.xyz menu:
|
||||
|
||||
- **Legacy BIOS** → proxyDHCP offered `netboot.xyz.kpxe` → TFTP → local menu rendered ✅
|
||||
- **UEFI x64** → proxyDHCP offered `netboot.xyz.efi` → TFTP → local menu rendered ✅
|
||||
- **UEFI HTTP Boot** → configured, not yet VM-tested (hard to trigger in QEMU).
|
||||
|
||||
To re-test: create a tap on `br0`, run a diskless QEMU VM with `-boot n`, and watch
|
||||
`docker compose logs -f dnsmasq` + `docker logs -f netbootxyz`.
|
||||
|
||||
## Notes / gotchas
|
||||
|
||||
- **Host networking is required** for `netbootxyz`: TFTP renegotiates to an ephemeral
|
||||
port that Docker's bridge NAT mangles (clients get `TID mismatch`). Host net serves TFTP
|
||||
straight off the LAN interface.
|
||||
- `NGINX_PORT` is ignored by the image; the nginx listen port is pinned to `8080` in
|
||||
`config/nginx/site-confs/default`, which also adds a `/menus/` location so UEFI HTTP Boot
|
||||
can fetch the first-stage `.efi`. (If the appliance ever regenerates that file on upgrade,
|
||||
re-add the `listen 8080` and `/menus/` bits.)
|
||||
- `dnsmasq` binds **only `br0`** (`interface=br0` + `bind-interfaces`) so it doesn't clash
|
||||
with libvirt's own dnsmasq on `virbr0`/`virbr1`.
|
||||
- `config/menus/local-vars.ipxe` sets `use_proxydhcp_settings true` so proxyDHCP clients
|
||||
boot without a keypress prompt.
|
||||
- The NEC IX router needs **no PXE configuration** — proxyDHCP handles everything.
|
||||
- Editing `dnsmasq.conf` requires `docker restart netboot-dnsmasq` (compose won't
|
||||
auto-recreate on a bind-mounted file content change).
|
||||
|
||||
## Files
|
||||
|
||||
- `compose.yaml` — the two services (`netbootxyz` + `dnsmasq`)
|
||||
- `dnsmasq.conf` — proxyDHCP + arch detection
|
||||
- `config/` — netbootxyz appliance state (menus, nginx conf); managed by the container
|
||||
- `assets/` — optional locally-mirrored distro images
|
||||
- `buildout/` — leftover from an earlier manual build; **no longer used** (safe to delete)
|
||||
@@ -0,0 +1,333 @@
|
||||
# Installing Windows via netboot.xyz
|
||||
|
||||
Works for KVM VMs and physical machines. Windows Setup needs a real filesystem for the
|
||||
~4 GB `install.wim`, so the flow is: **wimboot → WinPE (HTTP) → SMB media → setup.exe**.
|
||||
|
||||
> This covers **NT6+ (Vista/7/8/10/11, Server 2008–2025)**. Older Windows works completely
|
||||
> differently — see **`NT5.md`** for Windows 2000/XP/Server 2003, and **`9x.md`** for
|
||||
> Windows 95/98/ME (via win98-quickinstall's Linux installer).
|
||||
|
||||
```
|
||||
iPXE Windows menu
|
||||
└─ wimboot loads WinPE (boot.wim) over HTTP from this host (assets/WinPE/x64/)
|
||||
└─ WinPE boots to a cmd prompt; wpeinit brings up the NIC
|
||||
└─ net use → mount the SMB share with the extracted ISO
|
||||
└─ setup.exe → installs Windows to the local disk
|
||||
```
|
||||
|
||||
## Already set up on the server (done)
|
||||
|
||||
- **SMB share** `\\192.168.10.127\win` — **guests get read-only, passwordless** (WinPE mounts
|
||||
it this way); the AD user **`panxiao81` has read-write** (`write list = DDUPAN\panxiao81`) for
|
||||
staging images/media. Backed by ZFS dataset `data/win` → `/mnt/pool/win`. Holds `menu.cmd`
|
||||
(the version picker) and the `winpe-build/` driver kit. Managed by the `samba_member` Ansible
|
||||
role (`samba-ad/`), not a hand-edited `smb.conf`.
|
||||
- **`win_base_url`** = `http://192.168.10.127:8080/WinPE` — set in both
|
||||
`config/menus/local-vars.ipxe` and `config/menus/boot.cfg` (the latter covers clients whose
|
||||
firmware is already iPXE and skips `local-vars`).
|
||||
- **WinPE** built into `assets/WinPE/x64/` (base PE extracted from a Server 2025 ISO; NIC/
|
||||
storage drivers injected with **DISM** — see step 1 / Driver notes), with a `startnet.cmd`
|
||||
that brings up networking, auto-mounts the share, and launches the picker.
|
||||
- Only remaining step for a real install: drop a version folder onto the share (step 2).
|
||||
|
||||
## Verified (PXE-tested)
|
||||
|
||||
Driven end-to-end in a KVM VM on `br0`: iPXE Windows menu → wimboot loaded the WinPE over
|
||||
HTTP → WinPE booted → `startnet.cmd` ran `wpeinit`, mounted `\\192.168.10.127\win`, and
|
||||
launched `menu.cmd`, which showed the (empty) picker. So the whole path works; adding a
|
||||
version folder makes it installable. Note: give the target **≥4 GB RAM** (2 GB bugchecks the
|
||||
RAM-loaded WinPE and reboots).
|
||||
|
||||
## What you do
|
||||
|
||||
### 1. Build WinPE
|
||||
|
||||
The WinPE at `assets/WinPE/x64/` is **already built and PXE-tested** (see "Verified" below).
|
||||
Two ways to (re)build it:
|
||||
|
||||
**A. On Linux, no Windows box needed (how it was built here).** A Windows installation ISO's
|
||||
`sources/boot.wim` *is* a modern WinPE. Extract its bare-PE image with `wimlib-imagex` and
|
||||
inject a startup script that auto-mounts the share and runs the picker:
|
||||
|
||||
```bash
|
||||
ISO=~/zh-cn_windows_server_2025_..._x64_dvd.iso # any modern Windows/Server ISO
|
||||
OUT=/home/panxiao81/services/apps/netboot/assets/WinPE/x64
|
||||
sudo mount -o loop,ro "$ISO" /mnt/winiso
|
||||
mkdir -p "$OUT/boot" "$OUT/sources"
|
||||
cp /mnt/winiso/bootmgr "$OUT/bootmgr"
|
||||
cp /mnt/winiso/bootmgr.efi "$OUT/bootmgr.efi"
|
||||
cp /mnt/winiso/boot/bcd "$OUT/boot/bcd"
|
||||
cp /mnt/winiso/boot/boot.sdi "$OUT/boot/boot.sdi"
|
||||
# export image 1 ("Windows PE") as a single bootable wim
|
||||
wimlib-imagex export /mnt/winiso/sources/boot.wim 1 "$OUT/sources/boot.wim" --boot
|
||||
# auto-run our startup: wpeinit + mount \\host\win + launch menu.cmd (startnet.cmd is CRLF)
|
||||
wimlib-imagex update "$OUT/sources/boot.wim" 1 --command="delete --force /Windows/System32/startnet.cmd"
|
||||
wimlib-imagex update "$OUT/sources/boot.wim" 1 --command="add /path/to/startnet.cmd /Windows/System32/startnet.cmd"
|
||||
sudo umount /mnt/winiso
|
||||
```
|
||||
|
||||
Use the **newest** Windows/Server ISO you have — a WinPE installs any OS at or below its
|
||||
version. Drivers are injected separately with **DISM** (see **Driver notes**), so the baked-in
|
||||
`startnet.cmd` no longer needs `drvload` — it just brings up networking (with a DHCP retry
|
||||
loop, since a freshly-loaded NIC can be a few seconds behind the first DISCOVER) and launches
|
||||
the picker:
|
||||
|
||||
```bat
|
||||
wpeinit REM PnP auto-loads the DISM-injected NIC driver
|
||||
reg add HKLM\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters /v AllowInsecureGuestAuth /t REG_DWORD /d 1 /f
|
||||
:netwait REM retry until a real 192.168.10.x lease appears
|
||||
wpeutil InitializeNetwork
|
||||
ipconfig | find "192.168.10." >nul && goto neton
|
||||
ipconfig /renew >nul & ping 127.0.0.1 -n 4 >nul & goto netwait
|
||||
:neton
|
||||
net use Z: \\192.168.10.127\win
|
||||
if exist Z:\menu.cmd call Z:\menu.cmd
|
||||
cmd
|
||||
```
|
||||
|
||||
**B. On a Windows box (Windows ADK).** `copype amd64 C:\winpe` → `MakeWinPEMedia /ISO ...`,
|
||||
then copy the ISO/media contents into `assets/WinPE/x64/`. Edit `boot.wim`'s
|
||||
`Windows\System32\startnet.cmd` to the same script as above. Use this if you want ADK's
|
||||
optional components or a custom PE. (This same ADK box is where drivers get DISM-injected —
|
||||
see Driver notes.)
|
||||
|
||||
Either way the tree must be:
|
||||
|
||||
```
|
||||
assets/WinPE/x64/
|
||||
├── bootmgr
|
||||
├── bootmgr.efi
|
||||
├── boot/bcd (BCD store — the menu also tries Boot/BCD)
|
||||
├── boot/boot.sdi
|
||||
└── sources/boot.wim (your WinPE image, single bootable index)
|
||||
```
|
||||
|
||||
netboot.xyz loads exactly those five files from `${win_base_url}/x64/`. (`wimboot` itself is
|
||||
fetched from public `boot.netboot.xyz` — fine as long as the host has internet.) Give WinPE
|
||||
**≥4 GB RAM** on the target — the wim is RAM-loaded and 2 GB bugchecks → reboot.
|
||||
|
||||
### 2. Populate the SMB share with install media
|
||||
|
||||
Put each Windows version in **its own subfolder** under `/mnt/pool/win` — extract the ISO
|
||||
*files* (not the .iso). One WinPE installs all of them; you do NOT need a WinPE per version.
|
||||
|
||||
```bash
|
||||
sudo mount -o loop Win11_24H2.iso /mnt/iso
|
||||
mkdir -p /mnt/pool/win/win11-24h2
|
||||
cp -a /mnt/iso/. /mnt/pool/win/win11-24h2/
|
||||
sudo umount /mnt/iso
|
||||
# repeat for win10-22h2/, server2022/, server2025/, ...
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```
|
||||
/mnt/pool/win/
|
||||
├── menu.cmd ← version picker (already installed)
|
||||
├── win11-24h2/ ← setup.exe, sources/install.wim, ...
|
||||
├── win10-22h2/
|
||||
└── server2025/
|
||||
```
|
||||
|
||||
Editions (Home/Pro/Enterprise) usually live inside one ISO's `install.wim`; `setup.exe`
|
||||
lets you pick, so they don't need separate folders. See "Multiple versions" below.
|
||||
|
||||
### 3. Boot a target → install
|
||||
|
||||
1. PXE boot → **Windows** → **Load Microsoft Windows Installer** (uses `win_base_url`).
|
||||
On real hardware confirm WinPE actually got a `192.168.10.x` (`ipconfig`); if the onboard
|
||||
NIC won't network, use a **USB Ethernet dongle** — see **Driver notes**.
|
||||
2. At the WinPE `cmd` prompt (startnet usually does this for you):
|
||||
```bat
|
||||
wpeinit
|
||||
net use Z: \\192.168.10.127\win
|
||||
Z:\menu.cmd REM pick a version; launches <folder>\sources\setup.exe
|
||||
```
|
||||
3. Pick a version → click through Setup → install to the local disk.
|
||||
4. **After Setup's first reboot, boot the LOCAL DISK, not PXE** — otherwise it loops back into
|
||||
netboot and the install looks like it "restarted." (One-time boot menu, or move the disk
|
||||
above the network in the BIOS boot order.)
|
||||
|
||||
> **Win11 24H2/25H2 gotcha — launch `sources\setup.exe`, not the media-root `setup.exe`.** In
|
||||
> 24H2+ the root `setup.exe` is the new "modern setup" front-end, meant for booting from real
|
||||
> USB/DVD media or upgrading from within Windows; started from a bare WinPE prompt it **exits
|
||||
> partway** ("quits in half"). The classic engine at `<folder>\sources\setup.exe` is PE-friendly.
|
||||
> `menu.cmd` already prefers `sources\setup.exe` (falling back to the root one for older media).
|
||||
|
||||
## Multiple Windows versions
|
||||
|
||||
One x64 WinPE handles every x64 Windows (10/11, Server 2019–2025, all editions) — as long
|
||||
as the WinPE is at least as new as the newest OS you install. Manage versions purely as the
|
||||
folder library on the share; `menu.cmd` auto-lists every subfolder that contains a
|
||||
`setup.exe` and launches the one you choose.
|
||||
|
||||
**Make it hands-off** by baking the mount + picker into WinPE so every boot lands on the
|
||||
menu. When building WinPE, edit `mount\Windows\System32\startnet.cmd` (in the mounted
|
||||
`boot.wim`) to:
|
||||
|
||||
```bat
|
||||
wpeinit
|
||||
rem allow passwordless (guest) SMB from WinPE
|
||||
reg add HKLM\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters /v AllowInsecureGuestAuth /t REG_DWORD /d 1 /f
|
||||
net use Z: \\192.168.10.127\win
|
||||
Z:\menu.cmd
|
||||
```
|
||||
|
||||
**Unattended per version:** drop an `autounattend.xml` in a version folder and launch it with
|
||||
`setup.exe /unattend:%~dp0autounattend.xml` (you can add per-folder entries to `menu.cmd`).
|
||||
Each version can have its own answer file (edition index, product key, partitioning).
|
||||
|
||||
**x86 / ARM64:** only these need a second WinPE — place it in `assets/WinPE/x86/` (the Windows
|
||||
menu's arch toggle switches `${win_arch}`). Rarely needed.
|
||||
|
||||
**Advanced — per-version entries in the iPXE menu** (choose the version *before* WinPE, e.g.
|
||||
for fully automated imaging): pass a config into WinPE via extra `initrd` lines in
|
||||
`windows.ipxe` so WinPE auto-installs a specific folder. See
|
||||
[netbootxyz discussion #757](https://github.com/netbootxyz/netboot.xyz/discussions/757).
|
||||
For interactive use, the `menu.cmd` picker is simpler and needs no iPXE changes.
|
||||
|
||||
## Driver notes (mainly physical machines)
|
||||
|
||||
WinPE must have the target's **NIC driver** (to reach the share) and Setup must have the
|
||||
**storage driver** (to see the disk). VMs rarely need this; real hardware often does.
|
||||
|
||||
The build's reduced WinPE driver set is missing most modern **Intel** desktop NICs: it ships
|
||||
`e1i`/`e1e`/`e1g` (I350/82575/8257x-era) but **not** `e1d` (I217/I218/**I219**) or `e2f`
|
||||
(**I225/I226** 2.5G). Symptom: WinPE boots but `net use` fails because there is no link —
|
||||
no NIC was ever loaded. Realtek onboard NICs (RTL8111/8168/8125) are likewise absent, and so
|
||||
is **virtio-net** (needed for KVM installs with a virtio NIC).
|
||||
|
||||
### How the current image gets its drivers: DISM injection on `winadmin`
|
||||
|
||||
Drivers are injected into `boot.wim`'s driver store with **DISM** on the Windows ADK box
|
||||
(`winadmin`, `192.168.10.6`). This is the proper method: they become real PnP drivers that load
|
||||
automatically at boot — no `drvload`. A ready-to-run **build kit** lives on the share at
|
||||
`\\192.168.10.127\win\winpe-build\`:
|
||||
|
||||
```
|
||||
winpe-build/
|
||||
├── boot.wim ← image to service (copy of the live one)
|
||||
├── drivers/
|
||||
│ ├── Intel-1G/ e1dn (I219 — but see ⚠ box), e1r (I210/211/350), v1q (82575/6/80)
|
||||
│ ├── Intel-2.5G/ e2f (I225/I226) NDIS68
|
||||
│ ├── virtio-NetKVM/ netkvm (virtio-net) + netkvmp.exe/netkvmco.exe
|
||||
│ ├── virtio-viostor/ viostor (virtio-blk)
|
||||
│ └── virtio-vioscsi/ vioscsi (virtio-scsi)
|
||||
├── startnet.cmd ← no-drvload version (PnP loads drivers; DHCP retry loop)
|
||||
├── build-winpe.cmd ← one-click DISM script
|
||||
└── READ-ME-FIRST.txt
|
||||
```
|
||||
|
||||
Rebuild on `winadmin` (the DISM mount dir must be **local**, not the share):
|
||||
|
||||
```bat
|
||||
robocopy \\192.168.10.127\win\winpe-build C:\winpe-build /E
|
||||
:: Start menu -> "Deployment and Imaging Tools Environment" -> Run as administrator
|
||||
cd /d C:\winpe-build
|
||||
build-winpe.cmd :: mounts boot.wim, drops any old \Drivers tree, DISM /add-driver, commits
|
||||
copy /y C:\winpe-build\boot.wim \\192.168.10.127\win\winpe-build\boot.new.wim
|
||||
```
|
||||
|
||||
Then on this host, back up the live image and swap it in (netboot serves it statically — no
|
||||
restart, and a size change is fine, the BCD loads `boot.wim` by name):
|
||||
|
||||
```bash
|
||||
cd assets/WinPE/x64/sources
|
||||
cp -a boot.wim boot.wim.prev
|
||||
cp /mnt/pool/win/winpe-build/boot.new.wim boot.wim
|
||||
```
|
||||
|
||||
`build-winpe.cmd` is essentially:
|
||||
|
||||
```bat
|
||||
dism /Mount-Image /ImageFile:.\boot.wim /Index:1 /MountDir:.\mount
|
||||
rmdir /s /q .\mount\Drivers :: drop any old drvload tree
|
||||
copy /y .\startnet.cmd .\mount\Windows\System32\startnet.cmd
|
||||
dism /Image:.\mount /Add-Driver /Driver:.\drivers /Recurse /ForceUnsigned
|
||||
dism /Image:.\mount /Get-Drivers :: confirm the NIC driver is listed
|
||||
dism /Unmount-Image /MountDir:.\mount /Commit
|
||||
```
|
||||
|
||||
Use the **current Win11 24H2 / 10.1.26100 ADK** — servicing a 26100 `boot.wim` with an older
|
||||
DISM fails (*"image version is higher than the DISM version"*).
|
||||
|
||||
> **⚠ Verdict — the onboard Intel I219 does NOT work in this (build-26100) WinPE with any
|
||||
> driver. Use a USB Ethernet dongle.** On a real I219 (`DEV_550B`, recent Lenovo board) all
|
||||
> three Intel drivers failed to move a single frame in *either* direction (no DHCP; a static-IP
|
||||
> ping gets no ARP reply — confirmed with `tcpdump` on the host, which saw nothing from the NIC's
|
||||
> MAC):
|
||||
> - `e1dn` v20.0.3.24 **and** the Lenovo-OEM `e1dn` v20.0.2.19 → link shows "connected", **no traffic**.
|
||||
> - `e1d` v12.19.2.65 → **no traffic**, and `netsh …set interface admin=disabled` **bugchecks with
|
||||
> `PNP_WATCHDOG`** (the driver can't even cleanly stop the device).
|
||||
>
|
||||
> PXE firmware works on the same port (its own minimal driver), so it's specifically the I219
|
||||
> datapath under build-26100 WinPE — a known regression on newer PE builds. **Fix: a USB GbE
|
||||
> dongle** (Realtek RTL8153/8156 is inbox in WinPE 26100 — ours worked with nothing injected).
|
||||
> The onboard I219 is fine once *real* Windows is installed. An older WinPE base (Win10 22H2 /
|
||||
> Server 2022, build ≤20348) *might* also work but is untested.
|
||||
|
||||
Intel driver sources: **Intel Wired driver 31.2**
|
||||
(`downloadmirror.intel.com/921523/Wired_driver_31.2_x64.zip`), `PRO1000\Winx64\NDIS68` +
|
||||
`PRO2500\Winx64\NDIS68` subfolders; the I219 `e1dn` in the current kit is the Lenovo OEM package
|
||||
(`e1dn` 20.0.2.19). virtio drivers from `virtio-win-0.1.285.iso` (`~/virtio-win-0.1.285.iso`),
|
||||
`<driver>/w11/amd64` folders — **keep `netkvmp.exe`**, `netkvm.inf`'s `[CopyFiles]` requires it.
|
||||
|
||||
**State:** the live `boot.wim` is a DISM build whose driver store (`oem*.inf`) holds `e1dn`
|
||||
(OEM I219 — moot, see box), `e1r`/`v1q` (I210/211/350), `e2f` (I225/226) and
|
||||
`netkvm`/`viostor`/`vioscsi` (virtio) — so it still covers other Intel NICs and KVM VMs, and the
|
||||
`e1d` that `PNP_WATCHDOG`'d is deliberately excluded. The I219 machine installs via a **USB
|
||||
dongle**, and Setup ran once the picker used `sources\setup.exe`. (An earlier `drvload` build was
|
||||
VM-verified with a virtio-net NIC reaching the share.)
|
||||
|
||||
### Alternative: `drvload` at runtime (Linux build, no Windows box)
|
||||
|
||||
The image can also be built entirely on Linux with `wimlib-imagex`: stage the driver
|
||||
`.inf`/`.sys`/`.cat` files inside `boot.wim` under `\Drivers` and `drvload` them from
|
||||
`startnet.cmd` before networking. This is how it was *first* built — it's the fallback, since
|
||||
`drvload` only loads into the running PE (no persistent driver store) and can load several
|
||||
matching drivers at once, leaving PnP to bind whichever it ranks highest (which is how the flaky
|
||||
`e1dn` got picked early on):
|
||||
|
||||
```bat
|
||||
for /r X:\Drivers %%i in (*.inf) do drvload "%%i" REM filename-agnostic; non-matching INFs fail harmlessly
|
||||
wpeinit
|
||||
wpeutil InitializeNetwork
|
||||
```
|
||||
```bash
|
||||
WIM=assets/WinPE/x64/sources/boot.wim
|
||||
printf '%s\n' \
|
||||
"add /path/to/stage/Drivers /Drivers" \
|
||||
"delete --force /Windows/System32/startnet.cmd" \
|
||||
"add /path/to/startnet.cmd /Windows/System32/startnet.cmd" \
|
||||
| wimlib-imagex update "$WIM" 1
|
||||
```
|
||||
|
||||
> **Gotcha `drvload` fails with `0x80070002` (FILE_NOT_FOUND):** the INF's `[CopyFiles]`
|
||||
> references a file you trimmed (e.g. NetKVM's `netkvmp.exe`). Keep the whole driver folder —
|
||||
> strip only `*.pdb`/readme.
|
||||
|
||||
### Storage drivers
|
||||
|
||||
- Storage/RAID (Intel VMD/RST) drivers usually also need loading in Setup (or slipstream into
|
||||
`install.wim`). NVMe/AHCI are typically inbox.
|
||||
- **KVM:** using virtio disk/NIC → the virtio drivers above are baked in; or give the VM a
|
||||
**SATA disk + e1000 NIC** (both inbox) to skip driver work entirely.
|
||||
|
||||
## Gotcha: guest SMB from WinPE
|
||||
|
||||
Modern Windows blocks "insecure guest" logons by policy. WinPE usually allows it, but if
|
||||
`net use` fails with **system error 1272 / 5**, enable it in the running WinPE:
|
||||
|
||||
```bat
|
||||
reg add HKLM\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters /v AllowInsecureGuestAuth /t REG_DWORD /d 1 /f
|
||||
```
|
||||
|
||||
(To make it permanent, set the same key offline in the mounted `boot.wim`.) Alternatively,
|
||||
switch the share to a real user + password — but do it in the `samba_member` role
|
||||
(`samba-ad/`), **not** `/etc/samba/smb.conf` directly (Ansible regenerates that file). The
|
||||
`[win]` share already grants the AD user `panxiao81` read-write via `write list`.
|
||||
|
||||
## Later: unattended installs
|
||||
|
||||
Drop an `autounattend.xml` at the root of the SMB media (or bake into WinPE via
|
||||
`startnet.cmd` running `wpeinit` + `net use` + `setup.exe /unattend:...`) for zero-touch.
|
||||
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/bin/bash
|
||||
|
||||
cat << EOF
|
||||
|
||||
#########################################################################################################
|
||||
# Create PXE bootable Proxmox image including ISO #
|
||||
# #
|
||||
# Author: mrballcb @ Proxmox Forum (06-12-2012) #
|
||||
# Thread: http://forum.proxmox.com/threads/8484-Proxmox-installation-via-PXE-solution?p=55985#post55985 #
|
||||
# Modified: morph027 @ Proxmox Forum (23-02-2015) to work with 3.4 #
|
||||
#########################################################################################################
|
||||
|
||||
EOF
|
||||
|
||||
if [ ! $# -eq 1 ]; then
|
||||
echo -ne "Usage: bash pve-iso-2-pxe.sh /path/to/pve.iso\n\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BASEDIR="$(dirname "$(readlink -f "$1")")"
|
||||
pushd "$BASEDIR" >/dev/null || exit 1
|
||||
|
||||
[ -L "proxmox.iso" ] && rm proxmox.iso &>/dev/null
|
||||
|
||||
for ISO in *.iso; do
|
||||
if [ "$ISO" = "*.iso" ]; then continue; fi
|
||||
if [ "$ISO" = "proxmox.iso" ]; then continue; fi
|
||||
echo "Using ${ISO}..."
|
||||
ln -s "$ISO" proxmox.iso
|
||||
done
|
||||
|
||||
if [ ! -f "proxmox.iso" ]; then
|
||||
echo "Couldn't find a proxmox iso, aborting."
|
||||
echo "Add /path/to/iso_dir to the commandline."
|
||||
exit 2
|
||||
fi
|
||||
|
||||
rm -rf pxeboot
|
||||
[ -d pxeboot ] || mkdir pxeboot
|
||||
|
||||
pushd pxeboot >/dev/null || exit 1
|
||||
echo "extracting kernel..."
|
||||
if [ -x $(which isoinfo) ] ; then
|
||||
isoinfo -i ../../infrastructure/proxmox.iso -R -x /boot/linux26 > linux26 || exit 3
|
||||
else
|
||||
7z x ../../infrastructure/proxmox.iso boot/linux26 -o/tmp || exit 3
|
||||
mv /tmp/boot/linux26 /tmp/
|
||||
fi
|
||||
echo "extracting initrd..."
|
||||
if [ -x $(which isoinfo) ] ; then
|
||||
isoinfo -i ../../infrastructure/proxmox.iso -R -x /boot/initrd.img > /tmp/initrd.img
|
||||
else
|
||||
7z x ../../infrastructure/proxmox.iso boot/initrd.img -o/tmp
|
||||
mv /tmp/boot/initrd.img /tmp/
|
||||
fi
|
||||
|
||||
mimetype="$(file --mime-type --brief /tmp/initrd.img)"
|
||||
case "${mimetype##*/}" in
|
||||
"zstd"|"x-zstd")
|
||||
decompress="zstd -d /tmp/initrd.img -c"
|
||||
;;
|
||||
"gzip"|"x-gzip")
|
||||
decompress="gzip -S img -d /tmp/initrd.img -c"
|
||||
;;
|
||||
*)
|
||||
echo "unable to detect initrd compression method, exiting"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
$decompress > initrd || exit 4
|
||||
echo "adding iso file ..."
|
||||
if [ -x $(which cpio) ] ; then
|
||||
echo "../../infrastructure/proxmox.iso" | cpio -L -H newc -o >> initrd || exit 5
|
||||
else
|
||||
7z x "../../infrastructure/proxmox.iso" >> initrd || exit 5
|
||||
fi
|
||||
popd >/dev/null 2>&1 || exit 1
|
||||
|
||||
echo "Finished! pxeboot files can be found in ${PWD}."
|
||||
popd >/dev/null 2>&1 || true # don't care if these pops fail
|
||||
popd >/dev/null 2>&1 || true
|
||||
@@ -0,0 +1,33 @@
|
||||
#!ipxe
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Proxmox VE 9.2 install — pve1 (Intel NUC) → 192.168.10.4, target sdb
|
||||
#
|
||||
# The NUC's UEFI CANNOT unpack a ~1.8GB initramfs — it dies with
|
||||
# "initramfs unpacking failed: write error", leaving a TRUNCATED /proxmox.iso,
|
||||
# which then fails to loop-mount → "no device with valid ISO found".
|
||||
# (The ISO itself is fine and the HTTP transfer completes — verified in the
|
||||
# nginx log. RAM is fine too: 15881 MB. It's a firmware/early-boot limit.)
|
||||
#
|
||||
# So we DON'T ship the ISO in the initrd here. We boot only the kernel + a
|
||||
# lean initrd augmented with e1000e (the stock installer initrd has NO network
|
||||
# drivers at all). The ISO search fails, init drops to a debug shell, and you
|
||||
# run ONE command:
|
||||
#
|
||||
# sh /netfetch.sh
|
||||
#
|
||||
# which brings up the NIC, streams the ISO onto /dev/sda (the 1TB HDD), and
|
||||
# re-execs init — which then finds it via the normal block-device path and
|
||||
# auto-installs to sdb. Nothing oversized ever goes through initramfs.
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
echo
|
||||
echo Proxmox VE 9.2 — pve1 (NUC) network-staged install
|
||||
echo Loading kernel + net-enabled initrd (no ISO in initramfs)...
|
||||
echo
|
||||
echo ">>> At the debug shell that appears, type: sh /netfetch.sh"
|
||||
echo
|
||||
kernel http://192.168.10.127:8080/proxmox/linux26 ro ramdisk_size=16777216 rw quiet splash=silent proxmox-start-auto-installer
|
||||
initrd http://192.168.10.127:8080/proxmox/initrd-net.img
|
||||
boot || goto failed
|
||||
:failed
|
||||
echo Proxmox netboot failed. Dropping to shell.
|
||||
shell
|
||||
@@ -0,0 +1,16 @@
|
||||
#!ipxe
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Proxmox VE 9.2 AUTOMATED install — pve1 → 192.168.10.4 (target: sdb, the SATA SSD)
|
||||
# Same --pxe-style method: lean kernel + gzip initrd, then the answer-embedded
|
||||
# ISO as a second initrd (proxmox.iso). WIPES sdb (HDD sda untouched).
|
||||
# NOTE: pve1 is the SSH jump host / build box — reinstall it LAST.
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
echo
|
||||
echo Proxmox VE 9.2 AUTOMATED install: pve1 -> 192.168.10.4 (wipes sdb)
|
||||
kernel http://192.168.10.127:8080/proxmox/linux26 ro ramdisk_size=16777216 rw quiet splash=silent proxmox-start-auto-installer
|
||||
initrd http://192.168.10.127:8080/proxmox/initrd.img
|
||||
initrd http://192.168.10.127:8080/proxmox/prepared-pve1.iso proxmox.iso
|
||||
boot || goto failed
|
||||
:failed
|
||||
echo Proxmox netboot failed. Dropping to shell.
|
||||
shell
|
||||
@@ -0,0 +1,16 @@
|
||||
#!ipxe
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Proxmox VE 9.2 AUTOMATED install — pve2 → 192.168.10.7 (target: nvme0n1)
|
||||
# Same --pxe-style method as pve3: lean kernel + gzip initrd, then the
|
||||
# answer-embedded ISO as a second initrd (proxmox.iso). WIPES nvme0n1.
|
||||
# Reinstalling pve2 also vacates the contested 192.168.10.5.
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
echo
|
||||
echo Proxmox VE 9.2 AUTOMATED install: pve2 -> 192.168.10.7 (wipes nvme0n1)
|
||||
kernel http://192.168.10.127:8080/proxmox/linux26 ro ramdisk_size=16777216 rw quiet splash=silent proxmox-start-auto-installer
|
||||
initrd http://192.168.10.127:8080/proxmox/initrd.img
|
||||
initrd http://192.168.10.127:8080/proxmox/prepared-pve2.iso proxmox.iso
|
||||
boot || goto failed
|
||||
:failed
|
||||
echo Proxmox netboot failed. Dropping to shell.
|
||||
shell
|
||||
@@ -0,0 +1,19 @@
|
||||
#!ipxe
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Proxmox VE 9.2 AUTOMATED install — pve3 → 192.168.10.9 (target: nvme0n1)
|
||||
# Replicates the (unreleased) `proxmox-auto-install-assistant --pxe` method:
|
||||
# lean kernel + lean gzip initrd, then the answer-embedded ISO loaded as a
|
||||
# SECOND initrd named proxmox.iso (iPXE's native multi-initrd = correct cpio).
|
||||
# The installer finds /proxmox.iso, loop-mounts it, reads the embedded answer,
|
||||
# and installs unattended (proxmox-start-auto-installer). WIPES nvme0n1.
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
echo
|
||||
echo Proxmox VE 9.2 AUTOMATED install: pve3 -> 192.168.10.9 (wipes nvme0n1)
|
||||
echo Loading kernel + lean initrd + ISO (as proxmox.iso)...
|
||||
kernel http://192.168.10.127:8080/proxmox/linux26 ro ramdisk_size=16777216 rw quiet splash=silent proxmox-start-auto-installer
|
||||
initrd http://192.168.10.127:8080/proxmox/initrd.img
|
||||
initrd http://192.168.10.127:8080/proxmox/prepared-pve3.iso proxmox.iso
|
||||
boot || goto failed
|
||||
:failed
|
||||
echo Proxmox netboot failed. Dropping to shell.
|
||||
shell
|
||||
@@ -0,0 +1,32 @@
|
||||
services:
|
||||
# Official netboot.xyz appliance: TFTP (:69/udp), assets nginx (:8080), web UI (:3000).
|
||||
# HOST networking is required — TFTP renegotiates to an ephemeral port that Docker's
|
||||
# bridge NAT mangles (clients get "TID mismatch"). Host net serves TFTP straight off
|
||||
# the LAN interface. Does NOT provide DHCP (see the dnsmasq service below).
|
||||
netbootxyz:
|
||||
image: ghcr.io/netbootxyz/netbootxyz
|
||||
container_name: netbootxyz
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
environment:
|
||||
TZ: Asia/Tokyo
|
||||
NGINX_PORT: "8080" # assets mirror (host port, host net)
|
||||
WEB_APP_PORT: "3000" # web configuration UI
|
||||
volumes:
|
||||
- "./config:/config"
|
||||
- "./assets:/assets"
|
||||
|
||||
# proxyDHCP: runs ALONGSIDE the NEC IX router's DHCP. The router leases IPs; dnsmasq
|
||||
# only answers the PXE/boot part and points clients at this host (192.168.10.127).
|
||||
dnsmasq:
|
||||
image: 4km3/dnsmasq:2.90-r3
|
||||
container_name: netboot-dnsmasq
|
||||
restart: unless-stopped
|
||||
network_mode: host # must see LAN DHCP broadcasts (scoped to br0 in the conf)
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
command: ["-k", "--conf-file=/etc/dnsmasq.conf"]
|
||||
volumes:
|
||||
- "./dnsmasq.conf:/etc/dnsmasq.conf:ro"
|
||||
depends_on:
|
||||
- netbootxyz
|
||||
@@ -0,0 +1,2 @@
|
||||
429: Too Many Requests
|
||||
For more on scraping GitHub and how it may affect your rights, please review our Terms of Service (https://docs.github.com/en/site-policy/github-terms/github-terms-of-service).
|
||||
@@ -0,0 +1,219 @@
|
||||
#!ipxe
|
||||
|
||||
:global_vars
|
||||
# set site name
|
||||
set site_name netboot.xyz
|
||||
|
||||
# set boot domain
|
||||
set boot_domain boot.netboot.xyz/3.0.2
|
||||
|
||||
# set location of memdisk
|
||||
set memdisk http://${boot_domain}/memdisk
|
||||
|
||||
# set location of custom netboot.xyz live assets, override in local-vars.ipxe
|
||||
isset ${live_endpoint} || set live_endpoint https://github.com/netbootxyz
|
||||
|
||||
# default Windows install source (local WinPE); local-vars.ipxe may override
|
||||
isset ${win_base_url} || set win_base_url http://192.168.10.127:8080/WinPE
|
||||
|
||||
# signature check enabled?
|
||||
set sigs_enabled false
|
||||
|
||||
# disable signature checks if SecureBoot is active, as imgverify is not
|
||||
# available when booting via iPXE upstream's official Secure Boot image
|
||||
iseq ${efi/SecureBoot} 01 && set sigs_enabled false ||
|
||||
|
||||
# set location of signatures for sources
|
||||
set sigs http://${boot_domain}/sigs/
|
||||
|
||||
# set location of latest iPXE
|
||||
iseq ${platform} efi && set ipxe_disk netboot.xyz-snponly.efi || set ipxe_disk netboot.xyz-undionly.kpxe
|
||||
|
||||
# set default boot timeout
|
||||
isset ${boot_timeout} || set boot_timeout 300000
|
||||
|
||||
##################
|
||||
# official mirrors
|
||||
##################
|
||||
:mirrors
|
||||
### AlmaLinux
|
||||
set almalinux_mirror http://repo.almalinux.org
|
||||
set almalinux_base_dir almalinux
|
||||
|
||||
### Alpine Linux
|
||||
set alpinelinux_mirror http://dl-cdn.alpinelinux.org
|
||||
set alpinelinux_base_dir alpine
|
||||
|
||||
### Arch Linux
|
||||
set archlinux_mirror mirrors.kernel.org
|
||||
set archlinux_base_dir archlinux
|
||||
|
||||
### CentOS Stream
|
||||
set centos_mirror https://mirror.stream.centos.org
|
||||
set centos_base_dir
|
||||
|
||||
### CentOS Stream CoreOS
|
||||
set scos_mirror https://cloud.centos.org
|
||||
set scos_base_dir centos/scos
|
||||
|
||||
### Debian
|
||||
set debian_mirror http://deb.debian.org
|
||||
set debian_base_dir debian
|
||||
|
||||
### Devuan
|
||||
set devuan_mirror http://deb.devuan.org
|
||||
set devuan_base_dir devuan
|
||||
|
||||
### Fedora
|
||||
set fedora_mirror http://mirrors.kernel.org
|
||||
set fedora_base_dir fedora
|
||||
|
||||
### Fedora CoreOS
|
||||
set coreos_mirror https://builds.coreos.fedoraproject.org
|
||||
set coreos_base_dir prod/streams
|
||||
|
||||
### FreeDOS
|
||||
set freedos_mirror http://www.ibiblio.org
|
||||
set freedos_base_dir pub/micro/pc-stuff/freedos/files/distributions/1.4
|
||||
|
||||
### IPFire
|
||||
set ipfire_mirror https://downloads.ipfire.org
|
||||
set ipfire_base_dir releases/ipfire-2.x
|
||||
|
||||
### Kali Linux
|
||||
set kali_mirror http://http.kali.org
|
||||
set kali_base_dir kali
|
||||
|
||||
### Mageia
|
||||
set mageia_mirror http://mirrors.kernel.org
|
||||
set mageia_base_dir mageia
|
||||
|
||||
### OpenBSD
|
||||
set openbsd_mirror http://cdn.openbsd.org
|
||||
set openbsd_base_dir pub/OpenBSD
|
||||
|
||||
### openEuler
|
||||
set openEuler_mirror http://repo.openeuler.org
|
||||
set openEuler_base_dir
|
||||
|
||||
### openSUSE
|
||||
set opensuse_mirror http://download.opensuse.org
|
||||
set opensuse_base_dir distribution/leap
|
||||
|
||||
### Red Hat Enterprise Linux CoreOS
|
||||
set rhcos_mirror https://mirror.openshift.com
|
||||
set rhcos_base_dir pub/openshift-v
|
||||
|
||||
### Rocky Linux
|
||||
set rockylinux_mirror http://download.rockylinux.org
|
||||
set rockylinux_base_dir pub/rocky
|
||||
|
||||
### Slackware
|
||||
set slackware_mirror http://mirrors.kernel.org
|
||||
set slackware_base_dir slackware
|
||||
|
||||
### SmartOS
|
||||
set smartos_mirror https://netboot.smartos.org/os/
|
||||
set smartos_base_dir /platform/i86pc/
|
||||
|
||||
### Ubuntu
|
||||
set ubuntu_mirror http://archive.ubuntu.com
|
||||
set ubuntu_base_dir ubuntu
|
||||
|
||||
#################################################
|
||||
# determine architectures and enable menu options
|
||||
#################################################
|
||||
:architectures
|
||||
set menu_linux 1
|
||||
set menu_bsd 1
|
||||
set menu_unix 1
|
||||
set menu_freedos 1
|
||||
set menu_live 1
|
||||
set menu_pci 1
|
||||
set menu_windows 1
|
||||
set menu_utils 1
|
||||
iseq ${arch} i386 && goto i386 ||
|
||||
iseq ${arch} x86_64 && goto x86_64 ||
|
||||
iseq ${arch} arm64 && goto arm64 ||
|
||||
goto architectures_end
|
||||
:x86_64
|
||||
set menu_linux_i386 0
|
||||
iseq ${platform} efi && goto efi ||
|
||||
goto architectures_end
|
||||
:i386
|
||||
set menu_linux 0
|
||||
set menu_linux_i386 1
|
||||
set menu_bsd 1
|
||||
set menu_unix 0
|
||||
set menu_freedos 1
|
||||
set menu_live 0
|
||||
set menu_windows 0
|
||||
set menu_utils 1
|
||||
iseq ${platform} efi && goto efi ||
|
||||
goto architectures_end
|
||||
:arm64
|
||||
set menu_linux 0
|
||||
set menu_linux_arm 1
|
||||
set menu_unix 0
|
||||
set menu_freedos 0
|
||||
set menu_live 0
|
||||
set menu_live_arm 1
|
||||
set menu_windows 0
|
||||
set menu_utils 0
|
||||
set menu_utils_arm 1
|
||||
set menu_pci 0
|
||||
iseq ${platform} efi && goto efi ||
|
||||
goto architectures_end
|
||||
:efi
|
||||
set menu_bsd 1
|
||||
set menu_freedos 0
|
||||
set menu_unix 0
|
||||
set menu_pci 0
|
||||
goto architectures_end
|
||||
:architectures_end
|
||||
goto clouds
|
||||
|
||||
###################################
|
||||
# set iPXE cloud provider specifics
|
||||
###################################
|
||||
:clouds
|
||||
iseq ${ipxe_cloud_config} gce && goto gce ||
|
||||
iseq ${ipxe_cloud_config} metal && goto metal ||
|
||||
iseq ${ipxe_cloud_config} packet && goto metal ||
|
||||
goto clouds_end
|
||||
|
||||
:gce
|
||||
set cmdline console=ttyS0,115200n8
|
||||
goto clouds_end
|
||||
|
||||
:metal
|
||||
iseq ${arch} i386 && goto metal_x86_64 ||
|
||||
iseq ${arch} x86_64 && goto metal_x86_64 ||
|
||||
iseq ${arch} arm64 && goto metal_arm64 ||
|
||||
goto clouds_end
|
||||
|
||||
:metal_x86_64
|
||||
set cmdline console=ttyS1,115200n8
|
||||
iseq ${platform} efi && set ipxe_disk netboot.xyz-metal-snp.efi || set ipxe_disk netboot.xyz-metal.kpxe
|
||||
set menu_linux_i386 0
|
||||
set menu_freedos 0
|
||||
set menu_windows 0
|
||||
iseq ${platform} efi && set menu_pci 0 ||
|
||||
goto clouds_end
|
||||
|
||||
:metal_arm64
|
||||
set cmdline console=ttyAMA0,115200
|
||||
set ipxe_disk netboot.xyz-metal-arm64-snp.efi
|
||||
set menu_bsd 1
|
||||
set menu_freedos 0
|
||||
set menu_live 0
|
||||
set menu_windows 0
|
||||
set menu_utils 0
|
||||
set menu_pci 0
|
||||
goto clouds_end
|
||||
|
||||
:clouds_end
|
||||
goto end
|
||||
|
||||
:end
|
||||
exit
|
||||
@@ -0,0 +1,22 @@
|
||||
#!ipxe
|
||||
### local overrides for this self-hosted netboot.xyz instance
|
||||
|
||||
# Use the proxyDHCP-provided TFTP server (192.168.10.127) without prompting for a keypress
|
||||
set use_proxydhcp_settings true
|
||||
|
||||
# Windows: where wimboot fetches WinPE from (files live in assets/WinPE/x64/,
|
||||
# served by the appliance nginx on :8080). The Windows menu appends /x64/...
|
||||
set win_base_url http://192.168.10.127:8080/WinPE
|
||||
|
||||
# ─── Proxmox VE 9.2 reinstall auto-boot (per-node MAC-match) ───
|
||||
# Re-add ONE line at a time, only for the node you're actively reinstalling, then
|
||||
# remove it once that node is up — this prevents a network-boot reinstall loop.
|
||||
# Runs after DHCP so ${mac} is set; non-matching machines fall through (||) to the menu.
|
||||
# iseq ${mac} 6c:4b:90:c9:f8:0a && chain --replace http://192.168.10.127:8080/proxmox/pve3.ipxe || # pve3 -> .9
|
||||
# iseq ${mac} 6c:4b:90:c9:f8:53 && chain --replace http://192.168.10.127:8080/proxmox/pve2.ipxe || # pve2 -> .7
|
||||
# pve1 (NUC) needs pve1-net.ipxe, NOT pve1.ipxe: its UEFI cannot unpack a
|
||||
# ~1.8GB initramfs ("initramfs unpacking failed: write error"), so the ISO
|
||||
# must be staged to a local disk over the network instead of riding along in
|
||||
# the initrd. See services/netboot/assets/proxmox/pve1-net.ipxe for details.
|
||||
# iseq ${mac} b8:ae:ed:ea:0f:30 && chain --replace http://192.168.10.127:8080/proxmox/pve1-net.ipxe || # pve1 -> .4
|
||||
# ALL THREE NODES REINSTALLED 2026-07-25 — every line above is disarmed on purpose.
|
||||
@@ -0,0 +1 @@
|
||||
3.0.2
|
||||
@@ -0,0 +1,26 @@
|
||||
user nbxyz;
|
||||
worker_processes 4;
|
||||
pid /run/nginx.pid;
|
||||
include /etc/nginx/modules/*.conf;
|
||||
|
||||
events {
|
||||
worker_connections 768;
|
||||
}
|
||||
|
||||
http {
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
client_max_body_size 0;
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
access_log /config/log/nginx/access.log;
|
||||
error_log /config/log/nginx/error.log;
|
||||
gzip on;
|
||||
gzip_disable "msie6";
|
||||
include /config/nginx/site-confs/*;
|
||||
|
||||
}
|
||||
daemon off;
|
||||
@@ -0,0 +1,12 @@
|
||||
server {
|
||||
listen 8080;
|
||||
location / {
|
||||
root /assets;
|
||||
autoindex on;
|
||||
}
|
||||
# menus/binaries over HTTP — for UEFI HTTP Boot (serves the first-stage .efi)
|
||||
location /menus/ {
|
||||
alias /config/menus/;
|
||||
autoindex on;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
# ─── netboot proxyDHCP ───────────────────────────────────────────────────
|
||||
# Runs ALONGSIDE the NEC IX router's DHCP. The router leases IPs; dnsmasq only
|
||||
# answers the PXE/boot half and hands out NO addresses. TFTP + HTTP + the local
|
||||
# menu are all served by the netbootxyz container on this host (192.168.10.127).
|
||||
|
||||
port=0 # disable dnsmasq's DNS server entirely
|
||||
log-dhcp # verbose boot logging (comment out once stable)
|
||||
log-facility=- # send logs to stderr so `docker logs` shows them
|
||||
|
||||
interface=br0 # LAN bridge ONLY
|
||||
bind-interfaces # don't bind virbr0/virbr1 (libvirt's dnsmasq owns :67 there)
|
||||
|
||||
dhcp-range=192.168.10.0,proxy # proxyDHCP on the LAN subnet — assigns no leases
|
||||
|
||||
# ─── client classification ───────────────────────────────────────────────
|
||||
dhcp-match=set:ipxe,175 # our 2nd-stage iPXE sets option 175
|
||||
dhcp-match=set:httpboot,option:vendor-class,HTTPClient # UEFI HTTP Boot firmware
|
||||
dhcp-option=tag:httpboot,60,HTTPClient # echo the vendor class back
|
||||
# architecture (DHCP option 93) → EFI vs legacy BIOS, so stage 2 hands an iPXE
|
||||
# client a binary it can actually execute (a BIOS iPXE cannot run a UEFI .efi).
|
||||
dhcp-match=set:efi,option:client-arch,6 # UEFI ia32
|
||||
dhcp-match=set:efi,option:client-arch,7 # UEFI x86-64
|
||||
dhcp-match=set:efi,option:client-arch,9 # UEFI x86-64 (alt)
|
||||
dhcp-match=set:efi,option:client-arch,11 # UEFI arm64
|
||||
|
||||
# ─── stage 1: raw firmware (NOT iPXE) → hand it the netboot.xyz binary ────
|
||||
# proxyDHCP REQUIRES pxe-service (not dhcp-boot) to emit a boot offer. The TFTP
|
||||
# server defaults to this dnsmasq host (192.168.10.127) = the container's TFTP.
|
||||
pxe-prompt="Booting netboot.xyz...",0 # 0s timeout — no keypress
|
||||
pxe-service=tag:!ipxe,x86PC,"netboot.xyz (BIOS)",netboot.xyz.kpxe
|
||||
# NOTE: use the SNPONLY EFI build (SNP/UNDI = firmware NIC driver) instead of the
|
||||
# all-drivers netboot.xyz.efi. iPXE's NATIVE driver hangs on some NICs (Intel I219
|
||||
# in the NUC froze right after "autoexec.ipxe not found"); snponly reuses the
|
||||
# firmware's own driver and boots reliably. Realtek ThinkCentres work either way.
|
||||
pxe-service=tag:!ipxe,BC_EFI,"netboot.xyz (UEFI)",netboot.xyz-snponly.efi
|
||||
pxe-service=tag:!ipxe,X86-64_EFI,"netboot.xyz (UEFI)",netboot.xyz-snponly.efi
|
||||
# UEFI HTTP Boot firmware → fetch the first-stage .efi over HTTP
|
||||
dhcp-boot=tag:httpboot,tag:!ipxe,http://192.168.10.127:8080/menus/netboot.xyz-snponly.efi
|
||||
|
||||
# ─── stage 2: our netboot.xyz iPXE re-requests → give it OUR next-server ──
|
||||
# The .efi's embedded bootstrap fetches local-vars.ipxe from ${next-server} (the
|
||||
# REAL DHCP server = the NEC IX router), NOT from ${proxydhcp/next-server}. So the
|
||||
# router's DHCP next-server MUST be set to 192.168.10.127 (see README) — otherwise
|
||||
# local-vars is fetched from the router (no TFTP → fails) and, lacking
|
||||
# use_proxydhcp_settings, the bootstrap prompts for a 'p' keypress / falls back to
|
||||
# the public menu.
|
||||
#
|
||||
# The bootfile MUST be a name the bootstrap recognises (netboot.xyz.efi / .kpxe) so
|
||||
# it reaches its :tftpmenu branch and chains menu.ipxe LOCALLY. A non-binary name
|
||||
# like menu.ipxe skips :tftpmenu and boots the PUBLIC boot.netboot.xyz menu instead.
|
||||
# The server field (192.168.10.127) sets ${proxydhcp/next-server}.
|
||||
#
|
||||
# Split by arch: the netboot.xyz bootstrap only string-matches these names (never
|
||||
# execs them), but an ALREADY-iPXE client (e.g. a firmware iPXE ROM) autoboots the
|
||||
# bootfile, so it must be executable on that arch — .efi for UEFI, .kpxe for BIOS.
|
||||
# Both binaries re-run the bootstrap, which then reaches :tftpmenu → local menu.
|
||||
dhcp-boot=tag:ipxe,tag:efi,netboot.xyz-snponly.efi,,192.168.10.127
|
||||
dhcp-boot=tag:ipxe,tag:!efi,netboot.xyz.kpxe,,192.168.10.127
|
||||
Reference in New Issue
Block a user