Name update

This commit is contained in:
Miles Ward
2026-05-06 14:13:38 -04:00
parent e1b7154b51
commit 20c585e3ed
56 changed files with 755 additions and 802 deletions
+19 -19
View File
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.7
#
# PXEForge — multi-stage build.
# OpenPXE — multi-stage build.
#
# Design:
# - stage `fetch`: runs scripts/fetch-ipxe.sh to pull official iPXE binaries
@@ -8,7 +8,7 @@
# - stage `build`: compiles the workspace with cargo in release mode.
# - stage `runtime`: Debian slim image with setcap for NET_BIND_SERVICE,
# running as a non-root UID. No shell in PATH for the service user;
# attacker surface is just the pxeforge binary + libc.
# attacker surface is just the openpxe binary + libc.
#
# Why not distroless? We want setcap support and easy debug (`oc rsh`).
# Debian slim at ~75 MB + binary ~25 MB is fine for a PXE server that
@@ -24,7 +24,7 @@ WORKDIR /src
COPY scripts/fetch-ipxe.sh scripts/fetch-ipxe.sh
RUN mkdir -p assets/ipxe && bash scripts/fetch-ipxe.sh
########## build pxeforge ##########
########## build openpxe ##########
FROM rust:${RUST_VERSION}-bookworm AS build
WORKDIR /src
@@ -43,9 +43,9 @@ COPY --from=fetch /src/assets/ipxe /src/assets/ipxe
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/src/target,sharing=locked \
find crates -name '*.rs' -exec touch {} + && \
cargo build --release --bin pxeforge && \
cp target/release/pxeforge /pxeforge && \
ls -l /pxeforge
cargo build --release --bin openpxe && \
cp target/release/openpxe /openpxe && \
ls -l /openpxe
########## runtime ##########
FROM debian:12-slim AS runtime
@@ -54,13 +54,13 @@ RUN apt-get update \
ca-certificates libcap2-bin tini gosu iproute2 \
wimtools samba nfs-common \
&& rm -rf /var/lib/apt/lists/* \
&& useradd --system --uid 10001 --home-dir /var/lib/pxeforge --shell /usr/sbin/nologin pxeforge \
&& mkdir -p /var/lib/pxeforge/isos /var/lib/pxeforge/work /var/lib/pxeforge/smb \
&& chown -R pxeforge:pxeforge /var/lib/pxeforge
&& useradd --system --uid 10001 --home-dir /var/lib/openpxe --shell /usr/sbin/nologin openpxe \
&& mkdir -p /var/lib/openpxe/isos /var/lib/openpxe/work /var/lib/openpxe/smb \
&& chown -R openpxe:openpxe /var/lib/openpxe
# Runtime deps explained:
# wimtools - provides `wimlib-imagex`, used to inject startnet.cmd into boot.wim.
# samba - `smbd` serves extracted Windows install media on :445 for WinPE
# to `net use`. Guest read-only, scoped to /var/lib/pxeforge/smb.
# to `net use`. Guest read-only, scoped to /var/lib/openpxe/smb.
# nfs-common - provides `mount.nfs` / `mount.nfs4` for the Storage tab's
# NFS share manager. Mount also requires the container to run
# with CAP_SYS_ADMIN — without it, mount(2) returns EPERM and
@@ -74,23 +74,23 @@ RUN apt-get update \
# bind-mount ownership (common OpenShift/Docker UX issue).
# Windows-specific tools only activate when the WebUI toggle is on.
COPY --from=build /pxeforge /usr/local/bin/pxeforge
COPY --from=build /openpxe /usr/local/bin/openpxe
COPY deploy/docker/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
# Grant the binary the ability to bind <1024 ports as a non-root user.
# This is the only capability PXEForge needs for proxy-mode DHCP + TFTP + HTTP.
RUN setcap cap_net_bind_service=+ep /usr/local/bin/pxeforge
# This is the only capability OpenPXE needs for proxy-mode DHCP + TFTP + HTTP.
RUN setcap cap_net_bind_service=+ep /usr/local/bin/openpxe
# IMPORTANT: we do NOT `USER pxeforge` here. The entrypoint runs as root,
# chowns the mounted data dirs, then execs the binary via gosu as pxeforge.
# IMPORTANT: we do NOT `USER openpxe` here. The entrypoint runs as root,
# chowns the mounted data dirs, then execs the binary via gosu as openpxe.
# OpenShift ignores USER directives anyway (it injects its own uid), and
# there entrypoint.sh's non-root branch just execs directly.
WORKDIR /var/lib/pxeforge
WORKDIR /var/lib/openpxe
ENV PXEFORGE_ISO_DIR=/var/lib/pxeforge/isos \
PXEFORGE_WORK_DIR=/var/lib/pxeforge/work \
PXEFORGE_LOG=info,pxeforge=info
ENV OPENPXE_ISO_DIR=/var/lib/openpxe/isos \
OPENPXE_WORK_DIR=/var/lib/openpxe/work \
OPENPXE_LOG=info,openpxe=info
EXPOSE 67/udp 69/udp 4011/udp 80/tcp 445/tcp
+9 -9
View File
@@ -2,9 +2,9 @@
# Container entrypoint that handles the common bind-mount-as-root case.
#
# When volumes are bind-mounted into the container (e.g. `-v ./data/isos:...`),
# they come up owned by the host uid:gid — often root:root. The pxeforge
# they come up owned by the host uid:gid — often root:root. The openpxe
# binary runs as uid 10001 and can't write there. This script, when started
# as root, chowns the two state dirs to the pxeforge user, then drops
# as root, chowns the two state dirs to the openpxe user, then drops
# privileges via gosu before execing the binary.
#
# If the container is already running as non-root (OpenShift does this via
@@ -13,20 +13,20 @@
# or the operator is on their own for permissions.
set -e
PXEFORGE_UID=${PXEFORGE_UID:-10001}
PXEFORGE_GID=${PXEFORGE_GID:-10001}
DATA_DIRS="/var/lib/pxeforge/isos /var/lib/pxeforge/work /var/lib/pxeforge/smb"
OPENPXE_UID=${OPENPXE_UID:-10001}
OPENPXE_GID=${OPENPXE_GID:-10001}
DATA_DIRS="/var/lib/openpxe/isos /var/lib/openpxe/work /var/lib/openpxe/smb"
if [ "$(id -u)" = "0" ]; then
for d in $DATA_DIRS; do
if [ -d "$d" ]; then
chown -R "${PXEFORGE_UID}:${PXEFORGE_GID}" "$d" 2>/dev/null || true
chown -R "${OPENPXE_UID}:${OPENPXE_GID}" "$d" 2>/dev/null || true
fi
done
# Re-exec ourselves under the pxeforge user so the binary inherits a
# Re-exec ourselves under the openpxe user so the binary inherits a
# clean process environment and a predictable umask.
exec gosu "${PXEFORGE_UID}:${PXEFORGE_GID}" /usr/local/bin/pxeforge "$@"
exec gosu "${OPENPXE_UID}:${OPENPXE_GID}" /usr/local/bin/openpxe "$@"
fi
# Non-root: straight exec, no chown attempt.
exec /usr/local/bin/pxeforge "$@"
exec /usr/local/bin/openpxe "$@"
+1 -1
View File
@@ -1,7 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
name: pxeforge
name: openpxe
labels:
# Allow privileged pods (host-network) in this namespace only. The pod
# itself still runs non-root with only NET_BIND_SERVICE — privileged
+11 -11
View File
@@ -1,5 +1,5 @@
---
# Custom SCC for PXEForge.
# Custom SCC for OpenPXE.
#
# The default `restricted-v2` SCC blocks host network and all capabilities,
# which PXE cannot tolerate: DHCPDISCOVER is an L2 broadcast that CNI overlays
@@ -19,10 +19,10 @@
apiVersion: security.openshift.io/v1
kind: SecurityContextConstraints
metadata:
name: pxeforge-scc
name: openpxe-scc
annotations:
kubernetes.io/description: >-
Minimal SCC for PXEForge: host network + NET_BIND_SERVICE only, no raw
Minimal SCC for OpenPXE: host network + NET_BIND_SERVICE only, no raw
sockets, no privileged mode.
allowPrivilegedContainer: false
allowPrivilegeEscalation: false
@@ -54,27 +54,27 @@ volumes:
users: []
groups: []
---
# Bind the SCC to the pxeforge service account.
# Bind the SCC to the openpxe service account.
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: pxeforge-scc-use
name: openpxe-scc-use
rules:
- apiGroups: ["security.openshift.io"]
resources: ["securitycontextconstraints"]
resourceNames: ["pxeforge-scc"]
resourceNames: ["openpxe-scc"]
verbs: ["use"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: pxeforge-scc-use
namespace: pxeforge
name: openpxe-scc-use
namespace: openpxe
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: pxeforge-scc-use
name: openpxe-scc-use
subjects:
- kind: ServiceAccount
name: pxeforge
namespace: pxeforge
name: openpxe
namespace: openpxe
+9 -9
View File
@@ -2,29 +2,29 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: pxeforge
namespace: pxeforge
name: openpxe
namespace: openpxe
---
apiVersion: v1
kind: ConfigMap
metadata:
name: pxeforge-config
namespace: pxeforge
name: openpxe-config
namespace: openpxe
data:
# Toggle DHCP proxy on or off. "proxy" = answer PXE clients alongside an
# existing DHCP server. "disabled" = require operator to point an external
# DHCP at us via next-server/filename.
PXEFORGE_DHCP_MODE: "proxy"
OPENPXE_DHCP_MODE: "proxy"
# Override if auto-detection picks the wrong NIC in multi-homed pods.
# Leave unset to auto-detect from the node's primary IPv4.
# PXEFORGE_PUBLIC_IP: "10.0.0.5"
PXEFORGE_LOG: "info,pxeforge=info"
# OPENPXE_PUBLIC_IP: "10.0.0.5"
OPENPXE_LOG: "info,openpxe=info"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pxeforge-isos
namespace: pxeforge
name: openpxe-isos
namespace: openpxe
spec:
# ReadWriteOnce is fine — we deploy as a single replica since DHCP proxy
# coordination across replicas is not useful (clients hit whichever node
+12 -12
View File
@@ -2,10 +2,10 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: pxeforge
namespace: pxeforge
name: openpxe
namespace: openpxe
labels:
app.kubernetes.io/name: pxeforge
app.kubernetes.io/name: openpxe
spec:
# Single replica by design (see PVC comment). If HA is needed later, split
# the HTTP/web plane (scalable, stateless) from the DHCP-proxy/TFTP plane
@@ -15,13 +15,13 @@ spec:
type: Recreate
selector:
matchLabels:
app.kubernetes.io/name: pxeforge
app.kubernetes.io/name: openpxe
template:
metadata:
labels:
app.kubernetes.io/name: pxeforge
app.kubernetes.io/name: openpxe
spec:
serviceAccountName: pxeforge
serviceAccountName: openpxe
# L2 broadcast (DHCPDISCOVER) does not cross most CNI overlays into
# pod netns. Host network is the working path.
hostNetwork: true
@@ -33,8 +33,8 @@ spec:
runAsUser: 10001
fsGroup: 10001
containers:
- name: pxeforge
image: ghcr.io/casperadmin/pxeforge:0.1.0
- name: openpxe
image: ghcr.io/casperadmin/openpxe:0.1.0
imagePullPolicy: IfNotPresent
ports:
- name: dhcp
@@ -59,7 +59,7 @@ spec:
protocol: TCP
envFrom:
- configMapRef:
name: pxeforge-config
name: openpxe-config
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
@@ -70,9 +70,9 @@ spec:
add: ["NET_BIND_SERVICE"]
volumeMounts:
- name: isos
mountPath: /var/lib/pxeforge/isos
mountPath: /var/lib/openpxe/isos
- name: work
mountPath: /var/lib/pxeforge/work
mountPath: /var/lib/openpxe/work
- name: tmp
mountPath: /tmp
readinessProbe:
@@ -97,7 +97,7 @@ spec:
volumes:
- name: isos
persistentVolumeClaim:
claimName: pxeforge-isos
claimName: openpxe-isos
- name: work
emptyDir: {}
- name: tmp
+7 -7
View File
@@ -5,14 +5,14 @@
apiVersion: v1
kind: Service
metadata:
name: pxeforge
namespace: pxeforge
name: openpxe
namespace: openpxe
labels:
app.kubernetes.io/name: pxeforge
app.kubernetes.io/name: openpxe
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: pxeforge
app.kubernetes.io/name: openpxe
ports:
- name: http
port: 80
@@ -29,12 +29,12 @@ spec:
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: pxeforge
namespace: pxeforge
name: openpxe
namespace: openpxe
spec:
to:
kind: Service
name: pxeforge
name: openpxe
weight: 100
port:
targetPort: http
+23 -23
View File
@@ -1,12 +1,12 @@
# PXEForge on Unraid
# OpenPXE 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.
boot from OpenPXE". 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
`gitea.milesward.dev/mward4/openpxe:0.1.0` (or your equivalent) and
every Unraid template / docker-compose just references the tag.
Pre-flight:
@@ -24,12 +24,12 @@ Run on the Unraid host (Settings → Terminal, or `ssh root@unraid`):
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
http://localhost:3000/mward4/OpenPXE/raw/branch/main/scripts/build-and-publish-unraid.sh \
-o /tmp/openpxe-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
chmod +x /tmp/openpxe-publish.sh
GITEA_TOKEN=$GITEA_TOKEN /tmp/openpxe-publish.sh
```
What it does:
@@ -47,17 +47,17 @@ After it finishes, in Unraid → Docker → Add Container, set:
| Field | Value |
|------------|-------------------------------------------------|
| Repository | `gitea.milesward.dev/mward4/pxeforge:0.1.0` |
| Repository | `gitea.milesward.dev/mward4/openpxe: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`
- **`/var/lib/openpxe/isos`** ↔ `/mnt/user/appdata/openpxe/isos`
- **`/var/lib/openpxe/work`** ↔ `/mnt/user/appdata/openpxe/work`
- **`/var/lib/openpxe/smb`** ↔ `/mnt/user/appdata/openpxe/smb`
Or skip the manual UI by dropping `pxeforge.xml` (in this directory)
Or skip the manual UI by dropping `openpxe.xml` (in this directory)
into `/boot/config/plugins/dockerMan/templates-user/` and Unraid will
list it as a one-click template.
@@ -70,15 +70,15 @@ Skip the registry entirely. Useful for "hack on it locally" iterations.
```bash
ssh root@unraid
cd /mnt/user/appdata
git clone http://localhost:3000/mward4/PXEForge.git pxeforge-src
cd pxeforge-src
git clone http://localhost:3000/mward4/OpenPXE.git openpxe-src
cd openpxe-src
bash scripts/fetch-ipxe.sh
docker compose -f docker-compose.yml up -d --build pxeforge
docker compose -f docker-compose.yml up -d --build openpxe
```
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/`.
paths if you want them under `/mnt/user/appdata/openpxe/`.
## Path C — `docker load` from a tarball I built off-box
@@ -88,14 +88,14 @@ then:
```bash
# On the build host
docker save pxeforge:0.1.0 | gzip > pxeforge-0.1.0.tar.gz
docker save openpxe:0.1.0 | gzip > openpxe-0.1.0.tar.gz
# Transfer (rsync / scp / SMB / ZFS-replicate / sneakernet)
scp pxeforge-0.1.0.tar.gz root@unraid:/tmp/
scp openpxe-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
gunzip -c /tmp/openpxe-0.1.0.tar.gz | docker load
docker tag openpxe:0.1.0 gitea.milesward.dev/mward4/openpxe:0.1.0
```
If you want it pullable by tag from other Unraid templates, push to
@@ -119,16 +119,16 @@ 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
domain. OpenPXE 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
- **Permissions on `/mnt/user/appdata/openpxe`.** 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.
root, fixes perms, then drops to openpxe 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
@@ -1,12 +1,12 @@
<?xml version="1.0"?>
<!--
Unraid Docker template for PXEForge.
Unraid Docker template for OpenPXE.
Drop this file into /boot/config/plugins/dockerMan/templates-user/
on your Unraid box (or import via the Docker tab → "Add Container" →
"Template Repositories" if you publish it on a Gitea raw URL).
IMPORTANT: PXEForge needs host networking for DHCP/TFTP raw broadcasts.
IMPORTANT: OpenPXE needs host networking for DHCP/TFTP raw broadcasts.
Bridge mode will NOT work — clients can't see broadcast DHCP from a
bridged container. The template forces NetworkType=host below.
@@ -21,20 +21,20 @@
Web UI: http://<unraid-ip>/ (port 80)
-->
<Container version="2">
<Name>PXEForge</Name>
<Repository>gitea.milesward.dev/mward4/pxeforge:latest</Repository>
<Registry>https://gitea.milesward.dev/mward4/-/packages/container/pxeforge</Registry>
<Name>OpenPXE</Name>
<Repository>gitea.milesward.dev/mward4/openpxe:latest</Repository>
<Registry>https://gitea.milesward.dev/mward4/-/packages/container/openpxe</Registry>
<Network>host</Network>
<MyIP/>
<Shell>sh</Shell>
<Privileged>false</Privileged>
<Support>https://gitea.milesward.dev/mward4/PXEForge/issues</Support>
<Project>https://gitea.milesward.dev/mward4/PXEForge</Project>
<Support>https://gitea.milesward.dev/mward4/OpenPXE/issues</Support>
<Project>https://gitea.milesward.dev/mward4/OpenPXE</Project>
<Overview>
Air-gapped network PXE boot server. Container-native Rust
implementation — DHCP proxy + TFTP + iPXE chainload + HTTP ISO
streaming, all in one process. Web UI for ISO upload, NFS share
mounting, and Gated Deployment ("horse-race" simultaneous launch
mounting, and Queued Deployment ("horse-race" simultaneous launch
of one ISO across many waiting clients).
NEVER touches the client OS trust store: no test-signed drivers,
@@ -44,7 +44,7 @@
<Category>Network:Other Network:Management</Category>
<WebUI>http://[IP]/</WebUI>
<TemplateURL/>
<Icon>https://gitea.milesward.dev/mward4/PXEForge/raw/branch/main/crates/webui/src/logo.svg</Icon>
<Icon>https://gitea.milesward.dev/mward4/OpenPXE/raw/branch/main/crates/webui/src/logo.svg</Icon>
<ExtraParams>--cap-add=NET_BIND_SERVICE</ExtraParams>
<PostArgs/>
<CPUset/>
@@ -53,23 +53,23 @@
<DonateLink/>
<Requires>
Host networking. Unraid&#39;s built-in DHCP server (if any) must
not collide with a network that already has DHCP — PXEForge runs
not collide with a network that already has DHCP — OpenPXE runs
in proxy mode and coexists, but only one DHCP _proxy_ should reply
per broadcast domain.
</Requires>
<Config Name="ISOs" Target="/var/lib/pxeforge/isos" Default="/mnt/user/appdata/pxeforge/isos"
<Config Name="ISOs" Target="/var/lib/openpxe/isos" Default="/mnt/user/appdata/openpxe/isos"
Mode="rw" Description="Where uploaded and seeded .iso files live."
Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/pxeforge/isos</Config>
<Config Name="Work dir" Target="/var/lib/pxeforge/work" Default="/mnt/user/appdata/pxeforge/work"
Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/openpxe/isos</Config>
<Config Name="Work dir" Target="/var/lib/openpxe/work" Default="/mnt/user/appdata/openpxe/work"
Mode="rw" Description="Settings, NFS state, and runtime scratch. Persisted across restarts."
Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/pxeforge/work</Config>
<Config Name="SMB share root" Target="/var/lib/pxeforge/smb" Default="/mnt/user/appdata/pxeforge/smb"
Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/openpxe/work</Config>
<Config Name="SMB share root" Target="/var/lib/openpxe/smb" Default="/mnt/user/appdata/openpxe/smb"
Mode="rw" Description="Where extracted Windows install media lives. Only used when Windows toggle is on."
Type="Path" Display="advanced" Required="false" Mask="false">/mnt/user/appdata/pxeforge/smb</Config>
<Config Name="Public IP" Target="PXEFORGE_PUBLIC_IP" Default=""
Type="Path" Display="advanced" Required="false" Mask="false">/mnt/user/appdata/openpxe/smb</Config>
<Config Name="Public IP" Target="OPENPXE_PUBLIC_IP" Default=""
Mode="" Description="IP advertised to PXE clients. Leave blank to auto-detect; set explicitly on multi-homed Unraid hosts."
Type="Variable" Display="always" Required="false" Mask="false"></Config>
<Config Name="Log filter" Target="PXEFORGE_LOG" Default="info,pxeforge=debug"
<Config Name="Log filter" Target="OPENPXE_LOG" Default="info,openpxe=debug"
Mode="" Description="tracing-subscriber EnvFilter expression."
Type="Variable" Display="advanced" Required="false" Mask="false">info,pxeforge=debug</Config>
Type="Variable" Display="advanced" Required="false" Mask="false">info,openpxe=debug</Config>
</Container>