Files
OpenPXE/deploy/unraid/README.md
T
Miles Ward 083277faae Add Unraid quickstart: build-and-publish script + Docker template
Three paths from "Gitea-on-Unraid + a built repo" to "Unraid pulls
PXEForge by tag":

1. scripts/build-and-publish-unraid.sh — one-shot run on the Unraid
   host. Clones from local Gitea (http://localhost:3000), runs the
   iPXE fetch, docker build, docker login + push to Gitea's container
   registry. Token never lands in the host's ~/.docker/config.json:
   we set DOCKER_CONFIG to a tempdir and rm -rf it on exit. Token
   never lands in `ps`/bash history either: --password-stdin.

2. deploy/unraid/pxeforge.xml — Docker template for the Unraid UI.
   Forces NetworkType=host (PXE needs raw L2 broadcast — bridge mode
   doesn't work, full stop), declares the right cap-add, and surfaces
   PXEFORGE_PUBLIC_IP / PXEFORGE_LOG as configurable variables.

3. deploy/unraid/README.md — three documented paths (registry, compose
   from cloned repo, docker load from tarball) and the gotchas that
   actually bite (DHCP collision, host networking, perms on
   /mnt/user/appdata, NFS-needs-CAP_SYS_ADMIN).

The build host I'm running on can't reach Unraid right now (LAN moved
to a different subnet) and the Cloudflare WAF skip rule on
gitea.milesward.dev doesn't yet cover /v2/* or /git-{upload,receive}-pack
paths, so the publish has to happen from the Unraid host itself for now.
This commit is what makes that one-shot.
2026-04-30 00:02:29 -04:00

5.2 KiB

PXEForge on Unraid

Three paths from "I have an Unraid box with Gitea on it" to "PXE clients boot from PXEForge". Pick the one that matches what you have.

Path A — build on Unraid, push to Gitea registry, pull by tag

Recommended once you've done it once. Image is published to gitea.milesward.dev/mward4/pxeforge:0.1.0 (or your equivalent) and every Unraid template / docker-compose just references the tag.

Pre-flight:

  • Gitea has the Container Registry enabled (Site Admin → Configuration → set [packages] ENABLED = true in app.ini; default-on in Gitea ≥ 1.18).
  • You have a Gitea personal access token with write:package scope (User → Settings → Applications → Generate New Token, check package read+write).

Run on the Unraid host (Settings → Terminal, or ssh root@unraid):

# Pull just the build script straight from Gitea (no clone needed first):
GITEA_TOKEN=<your-token>
curl -fsSL \
  -H "Authorization: token $GITEA_TOKEN" \
  http://localhost:3000/mward4/PXEForge/raw/branch/main/scripts/build-and-publish-unraid.sh \
  -o /tmp/pxeforge-publish.sh

# Run it. ~6 min on Unraid hardware (native amd64, no QEMU).
chmod +x /tmp/pxeforge-publish.sh
GITEA_TOKEN=$GITEA_TOKEN /tmp/pxeforge-publish.sh

What it does:

  1. Shallow-clones the repo from local Gitea (no Cloudflare, no LAN issues).
  2. Runs scripts/fetch-ipxe.sh to grab the iPXE binaries from the official release URLs.
  3. docker build against deploy/docker/Dockerfile.
  4. docker login gitea.milesward.dev:3000 using a temp DOCKER_CONFIG so the credential never lands in your real ~/.docker/config.json.
  5. docker push both :0.1.0 and :latest.
  6. Logout, scrub the temp config, delete the workspace.

After it finishes, in Unraid → Docker → Add Container, set:

Field Value
Repository gitea.milesward.dev/mward4/pxeforge:0.1.0
Network host
Extra args --cap-add=NET_BIND_SERVICE

Volume mounts (paths inside container in bold):

  • /var/lib/pxeforge/isos/mnt/user/appdata/pxeforge/isos
  • /var/lib/pxeforge/work/mnt/user/appdata/pxeforge/work
  • /var/lib/pxeforge/smb/mnt/user/appdata/pxeforge/smb

Or skip the manual UI by dropping pxeforge.xml (in this directory) into /boot/config/plugins/dockerMan/templates-user/ and Unraid will list it as a one-click template.

Web UI: http://<unraid-ip>/.

Path B — docker-compose up directly from cloned repo

Skip the registry entirely. Useful for "hack on it locally" iterations.

ssh root@unraid
cd /mnt/user/appdata
git clone http://localhost:3000/mward4/PXEForge.git pxeforge-src
cd pxeforge-src
bash scripts/fetch-ipxe.sh
docker compose -f docker-compose.yml up -d --build pxeforge

The bundled docker-compose.yml already wires host networking, the right cap_add, and bind-mounts to ./data/. Edit those bind-mount paths if you want them under /mnt/user/appdata/pxeforge/.

Path C — docker load from a tarball I built off-box

For when the build host can reach Unraid but Unraid can't reach the internet. Build the image somewhere with a cargo + buildx environment, then:

# On the build host
docker save pxeforge:0.1.0 | gzip > pxeforge-0.1.0.tar.gz

# Transfer (rsync / scp / SMB / ZFS-replicate / sneakernet)
scp pxeforge-0.1.0.tar.gz root@unraid:/tmp/

# On Unraid
gunzip -c /tmp/pxeforge-0.1.0.tar.gz | docker load
docker tag pxeforge:0.1.0 gitea.milesward.dev/mward4/pxeforge:0.1.0

If you want it pullable by tag from other Unraid templates, push to Gitea afterward (path A's last step).

Verifying it works

Once the container is up, from any LAN host:

# Should return 'ok'
curl -s http://<unraid-ip>/healthz
# Should return 'ready' if iPXE binaries are bundled
curl -s http://<unraid-ip>/readyz

Web UI: http://<unraid-ip>/. The Dashboard's top-bar pill should read ● ready. Upload an ISO, boot a test machine PXE → it should show up in the Dashboard's "Recent connections" table within seconds.

Common gotchas

  • DHCP collision. Don't run two PXE proxies on the same broadcast domain. PXEForge runs in proxy mode and never offers IP leases, so it coexists with whatever DHCP server is already on the network — but two proxies racing each other will whichever-wins at random.
  • Host networking only. Bridge mode containers don't see broadcast DHCP. There's no working bridge-mode config for a PXE server.
  • Permissions on /mnt/user/appdata/pxeforge. The container runs as uid 10001 by default. The entrypoint chowns the bind mounts to 10001 on first start, but only if the container itself has root — --user=root isn't needed; the multi-stage Dockerfile starts as root, fixes perms, then drops to pxeforge via gosu.
  • NFS mounts in the Storage tab. Mounting NFS inside the container needs CAP_SYS_ADMIN. To enable, add --cap-add=SYS_ADMIN to the Unraid template's "Extra args" — but understand that's a meaningful capability, comparable to --privileged. Skip it if you only need uploads.