iPXE Server
This commit is contained in:
246
Containerfile
Normal file
246
Containerfile
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
# ------------------------------------------------------------------
|
||||||
|
# ██╗██████╗ ██╗ ██╗███████╗ Lightweight HTTP server
|
||||||
|
# ██║██╔══██╗╚██╗██╔╝██╔════╝ for network booting.
|
||||||
|
# ██║██████╔╝ ╚███╔╝ █████╗
|
||||||
|
# ██║██╔═══╝ ██╔██╗ ██╔══╝ Serves boot menus, kernels, and images
|
||||||
|
# ██║██║ ██╔╝ ██╗███████╗
|
||||||
|
# ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝ Version 1.0.0
|
||||||
|
# ███████╗███████╗██████╗ ██╗ ██╗███████╗██████╗
|
||||||
|
# ██╔════╝██╔════╝██╔══██╗██║ ██║██╔════╝██╔══██╗
|
||||||
|
# ███████╗█████╗ ██████╔╝██║ ██║█████╗ ██████╔╝
|
||||||
|
# ╚════██║██╔══╝ ██╔══██╗╚██╗ ██╔╝██╔══╝ ██╔══██╗
|
||||||
|
# ███████║███████╗██║ ██║ ╚████╔╝ ███████╗██║ ██║
|
||||||
|
# ╚══════╝╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝
|
||||||
|
# Author: Kevin [+ Claude]
|
||||||
|
# Created: 2025-02-02
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
FROM docker.io/library/alpine:3.21 AS base
|
||||||
|
|
||||||
|
# Install runtime dependencies
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
nginx \
|
||||||
|
curl \
|
||||||
|
tzdata \
|
||||||
|
tini \
|
||||||
|
&& rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
# Create ipxe user
|
||||||
|
RUN addgroup -g 1000 ipxe && \
|
||||||
|
adduser -D -s /sbin/nologin -u 1000 -G ipxe ipxe
|
||||||
|
|
||||||
|
# Create directory structure
|
||||||
|
RUN mkdir -p \
|
||||||
|
/srv/boot \
|
||||||
|
/srv/menus \
|
||||||
|
/srv/images \
|
||||||
|
/srv/scripts \
|
||||||
|
/etc/ipxe \
|
||||||
|
/var/log/nginx \
|
||||||
|
/var/lib/nginx \
|
||||||
|
/run/nginx && \
|
||||||
|
chown -R ipxe:ipxe /srv /var/log/nginx /var/lib/nginx /run/nginx
|
||||||
|
|
||||||
|
# nginx configuration for iPXE
|
||||||
|
RUN cat > /etc/nginx/nginx.conf << 'EOF'
|
||||||
|
worker_processes auto;
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
pid /run/nginx/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 256;
|
||||||
|
use epoll;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
# Custom MIME types for iPXE
|
||||||
|
types {
|
||||||
|
application/octet-stream ipxe efi;
|
||||||
|
text/plain ipxe menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_format ipxe '$remote_addr - [$time_local] "$request" $status $body_bytes_sent '
|
||||||
|
'"$http_user_agent" rt=$request_time';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log ipxe;
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
tcp_nopush on;
|
||||||
|
tcp_nodelay on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
# Disable buffering for faster boot file delivery
|
||||||
|
proxy_buffering off;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /srv;
|
||||||
|
|
||||||
|
# Boot files (kernels, initramfs, iPXE binaries)
|
||||||
|
location /boot/ {
|
||||||
|
alias /srv/boot/;
|
||||||
|
autoindex on;
|
||||||
|
autoindex_exact_size off;
|
||||||
|
autoindex_localtime on;
|
||||||
|
}
|
||||||
|
|
||||||
|
# iPXE menus and scripts
|
||||||
|
location /menus/ {
|
||||||
|
alias /srv/menus/;
|
||||||
|
autoindex on;
|
||||||
|
default_type text/plain;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ISO/squashfs images
|
||||||
|
location /images/ {
|
||||||
|
alias /srv/images/;
|
||||||
|
autoindex on;
|
||||||
|
autoindex_exact_size on;
|
||||||
|
|
||||||
|
# Support range requests for large files
|
||||||
|
add_header Accept-Ranges bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Dynamic menu endpoint (can be extended with scripts)
|
||||||
|
location /menu {
|
||||||
|
alias /srv/menus/boot.ipxe;
|
||||||
|
default_type text/plain;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Health check endpoint
|
||||||
|
location /health {
|
||||||
|
access_log off;
|
||||||
|
return 200 'OK\n';
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Status page
|
||||||
|
location /status {
|
||||||
|
stub_status on;
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Health check script
|
||||||
|
RUN cat > /usr/local/bin/healthcheck.sh << 'EOF'
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
curl -sf http://localhost:8080/health > /dev/null || exit 1
|
||||||
|
echo "iPXE server healthy"
|
||||||
|
EOF
|
||||||
|
RUN chmod +x /usr/local/bin/healthcheck.sh
|
||||||
|
|
||||||
|
# Entrypoint script
|
||||||
|
RUN cat > /usr/local/bin/entrypoint.sh << 'EOF'
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== iPXE Boot Server ==="
|
||||||
|
echo "Boot files: /srv/boot"
|
||||||
|
echo "Menus: /srv/menus"
|
||||||
|
echo "Images: /srv/images"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# List available boot assets
|
||||||
|
echo "Available boot files:"
|
||||||
|
ls -la /srv/boot/ 2>/dev/null || echo " (none)"
|
||||||
|
echo ""
|
||||||
|
echo "Available menus:"
|
||||||
|
ls -la /srv/menus/ 2>/dev/null || echo " (none)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Validate nginx config
|
||||||
|
nginx -t
|
||||||
|
|
||||||
|
# Start nginx in foreground
|
||||||
|
exec nginx -g 'daemon off;'
|
||||||
|
EOF
|
||||||
|
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||||
|
|
||||||
|
# Default boot menu template
|
||||||
|
RUN cat > /srv/menus/boot.ipxe << 'EOF'
|
||||||
|
#!ipxe
|
||||||
|
# iPXE Boot Menu - NUC Home Server
|
||||||
|
# Served via Caddy reverse proxy
|
||||||
|
|
||||||
|
set menu-timeout 30000
|
||||||
|
set menu-default local
|
||||||
|
|
||||||
|
:start
|
||||||
|
menu iPXE Boot Menu - nuc.lan
|
||||||
|
item --gap -- -------- Boot Options --------
|
||||||
|
item local Boot from local disk
|
||||||
|
item --gap -- -------- Network Boot --------
|
||||||
|
item fedora-live Fedora 42 Live (Minimal)
|
||||||
|
item fedora-kiosk Fedora 42 Kiosk/PoS
|
||||||
|
item fedora-rescue Fedora 42 Rescue
|
||||||
|
item --gap -- -------- Utilities --------
|
||||||
|
item memtest Memtest86+
|
||||||
|
item shell iPXE Shell
|
||||||
|
item reboot Reboot
|
||||||
|
item exit Exit to BIOS
|
||||||
|
choose --timeout ${menu-timeout} --default ${menu-default} selected || goto cancel
|
||||||
|
goto ${selected}
|
||||||
|
|
||||||
|
:cancel
|
||||||
|
echo Boot cancelled
|
||||||
|
goto start
|
||||||
|
|
||||||
|
:local
|
||||||
|
sanboot --no-describe --drive 0x80 || goto start
|
||||||
|
|
||||||
|
:fedora-live
|
||||||
|
echo Booting Fedora 42 Live (Minimal)...
|
||||||
|
set base-url http://ipxe.nuc.lan/images/fedora-42
|
||||||
|
kernel ${base-url}/vmlinuz initrd=initrd.img root=live:${base-url}/squashfs.img rd.live.image rd.live.overlay.overlayfs=1 quiet
|
||||||
|
initrd ${base-url}/initrd.img
|
||||||
|
boot || goto start
|
||||||
|
|
||||||
|
:fedora-kiosk
|
||||||
|
echo Booting Fedora 42 Kiosk...
|
||||||
|
set base-url http://ipxe.nuc.lan/images/fedora-42-kiosk
|
||||||
|
kernel ${base-url}/vmlinuz initrd=initrd.img root=live:${base-url}/squashfs.img rd.live.image quiet
|
||||||
|
initrd ${base-url}/initrd.img
|
||||||
|
boot || goto start
|
||||||
|
|
||||||
|
:fedora-rescue
|
||||||
|
echo Booting Fedora 42 Rescue...
|
||||||
|
set base-url http://ipxe.nuc.lan/images/fedora-42
|
||||||
|
kernel ${base-url}/vmlinuz initrd=initrd.img rescue quiet
|
||||||
|
initrd ${base-url}/initrd.img
|
||||||
|
boot || goto start
|
||||||
|
|
||||||
|
:memtest
|
||||||
|
echo Loading Memtest86+...
|
||||||
|
kernel http://ipxe.nuc.lan/boot/memtest86+.bin
|
||||||
|
boot || goto start
|
||||||
|
|
||||||
|
:shell
|
||||||
|
echo Dropping to iPXE shell...
|
||||||
|
shell
|
||||||
|
|
||||||
|
:reboot
|
||||||
|
reboot
|
||||||
|
|
||||||
|
:exit
|
||||||
|
exit
|
||||||
|
EOF
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
USER ipxe
|
||||||
|
WORKDIR /srv
|
||||||
|
|
||||||
|
ENTRYPOINT ["/sbin/tini", "--"]
|
||||||
|
CMD ["/usr/local/bin/entrypoint.sh"]
|
||||||
|
|
||||||
|
LABEL maintainer="Kevin" \
|
||||||
|
version="1.0.0" \
|
||||||
|
description="iPXE boot server for network booting"
|
||||||
48
README.md
Normal file
48
README.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# IPXE Boot server
|
||||||
|
|
||||||
|
Boot from ipxe.nuc.lan:8080
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|-------------------------|-------------------------------------------------|
|
||||||
|
| `ipxe-server.container` | Quadlet unit joining `internal_caddy` network |
|
||||||
|
| `Containerfile` | Alpine + nginx serving boot assets on port 8080 |
|
||||||
|
| `snippets/ipxe` | Caddy snippet with reusable proxy directives |
|
||||||
|
| `caddy/ipxe.caddyfile` | Site block for `ipxe.nuc.lan` (HTTP + HTTPS) |
|
||||||
|
| `setup.sh` | Automated deployment script |
|
||||||
|
|
||||||
|
**Integration with existing Caddyfile:**
|
||||||
|
|
||||||
|
1. Copy `snippets/ipxe` to your snippets directory
|
||||||
|
2. Add to top of your Caddyfile:
|
||||||
|
|
||||||
|
```
|
||||||
|
import snippets/ipxe
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Add the `ipxe.nuc.lan` block (or paste the content from `ipxe.caddyfile`)
|
||||||
|
|
||||||
|
|
||||||
|
**Or** since you have `*.nuc.lan` already, add this matcher to your wildcard block:
|
||||||
|
|
||||||
|
```caddy
|
||||||
|
@ipxe host ipxe.nuc.lan
|
||||||
|
handle @ipxe {
|
||||||
|
reverse_proxy ipxe-server:8080
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Quick deploy:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./setup.sh install # Creates dirs, builds image, installs Quadlet
|
||||||
|
./setup.sh start # Starts via systemd
|
||||||
|
|
||||||
|
# Add boot files
|
||||||
|
cp vmlinuz initrd.img ~/ipxe/boot/
|
||||||
|
cp squashfs.img ~/ipxe/images/fedora-42/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** HTTP is intentionally kept open for `ipxe.nuc.lan:80` because most PXE ROMs chainload via HTTP before the full iPXE stack with HTTPS support is loaded. The local network restriction handles security.
|
||||||
|
|
||||||
114
ipxe.caddyfile
Normal file
114
ipxe.caddyfile
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# === iPXE BOOT SERVER ===
|
||||||
|
# Add this to your main Caddyfile or include via import
|
||||||
|
# Requires: import snippets/ipxe at the top of Caddyfile
|
||||||
|
|
||||||
|
# HTTPS endpoint for iPXE (local network)
|
||||||
|
ipxe.nuc.lan {
|
||||||
|
tls /etc/caddy/tls/wildcard.nuc.lan.crt /etc/caddy/tls/wildcard.nuc.lan.key {
|
||||||
|
protocols tls1.2 tls1.3
|
||||||
|
}
|
||||||
|
|
||||||
|
# Only allow local network access
|
||||||
|
@local_network {
|
||||||
|
remote_ip 192.168.1.0/24 10.89.0.0/24 10.10.5.0/24
|
||||||
|
}
|
||||||
|
|
||||||
|
# Boot menus - no caching
|
||||||
|
@menus {
|
||||||
|
path /menus/* /menu
|
||||||
|
}
|
||||||
|
handle @menus {
|
||||||
|
import ipxe-headers
|
||||||
|
import ipxe-upstream
|
||||||
|
}
|
||||||
|
|
||||||
|
# Boot files (kernels, initramfs) - cache aggressively
|
||||||
|
@boot {
|
||||||
|
path /boot/*
|
||||||
|
}
|
||||||
|
handle @boot {
|
||||||
|
import ipxe-boot-headers
|
||||||
|
import ipxe-upstream
|
||||||
|
}
|
||||||
|
|
||||||
|
# Images (ISO, squashfs) - moderate caching, range support
|
||||||
|
@images {
|
||||||
|
path /images/*
|
||||||
|
}
|
||||||
|
handle @images {
|
||||||
|
import ipxe-image-headers
|
||||||
|
import ipxe-upstream
|
||||||
|
}
|
||||||
|
|
||||||
|
# Health/status endpoints
|
||||||
|
@health {
|
||||||
|
path /health /status
|
||||||
|
}
|
||||||
|
handle @health {
|
||||||
|
import ipxe-upstream
|
||||||
|
}
|
||||||
|
|
||||||
|
# Default handler for local network
|
||||||
|
handle @local_network {
|
||||||
|
import ipxe-upstream
|
||||||
|
}
|
||||||
|
|
||||||
|
# Block external access
|
||||||
|
handle {
|
||||||
|
respond "Access denied" 403
|
||||||
|
}
|
||||||
|
|
||||||
|
log {
|
||||||
|
output file /var/log/caddy/ipxe.log {
|
||||||
|
roll_size 10MB
|
||||||
|
roll_keep 3
|
||||||
|
}
|
||||||
|
format json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTTP endpoint for iPXE (required for legacy/chainloading)
|
||||||
|
# iPXE firmware often starts with HTTP before HTTPS
|
||||||
|
ipxe.nuc.lan:80 {
|
||||||
|
# Allow HTTP for initial iPXE chainload (most PXE ROMs don't support HTTPS)
|
||||||
|
@local_network {
|
||||||
|
remote_ip 192.168.1.0/24 10.89.0.0/24 10.10.5.0/24
|
||||||
|
}
|
||||||
|
|
||||||
|
# Menus via HTTP (for initial boot)
|
||||||
|
@menus {
|
||||||
|
path /menus/* /menu
|
||||||
|
}
|
||||||
|
handle @menus {
|
||||||
|
import ipxe-headers
|
||||||
|
import ipxe-upstream
|
||||||
|
}
|
||||||
|
|
||||||
|
# Boot files via HTTP
|
||||||
|
@boot {
|
||||||
|
path /boot/*
|
||||||
|
}
|
||||||
|
handle @boot {
|
||||||
|
import ipxe-boot-headers
|
||||||
|
import ipxe-upstream
|
||||||
|
}
|
||||||
|
|
||||||
|
# Images via HTTP (for clients that don't support HTTPS)
|
||||||
|
@images {
|
||||||
|
path /images/*
|
||||||
|
}
|
||||||
|
handle @images {
|
||||||
|
import ipxe-image-headers
|
||||||
|
import ipxe-upstream
|
||||||
|
}
|
||||||
|
|
||||||
|
# Health endpoint
|
||||||
|
handle @local_network {
|
||||||
|
import ipxe-upstream
|
||||||
|
}
|
||||||
|
|
||||||
|
handle {
|
||||||
|
respond "Access denied" 403
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
51
ipxe.snippet
Normal file
51
ipxe.snippet
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# snippets/ipxe
|
||||||
|
# iPXE Boot Server reverse proxy configuration
|
||||||
|
# Import in Caddyfile with: import snippets/ipxe
|
||||||
|
|
||||||
|
(ipxe-headers) {
|
||||||
|
header {
|
||||||
|
# Disable caching for menus (dynamic content)
|
||||||
|
Cache-Control "no-cache, no-store, must-revalidate"
|
||||||
|
Pragma "no-cache"
|
||||||
|
-Server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(ipxe-boot-headers) {
|
||||||
|
header {
|
||||||
|
# Cache boot files aggressively (kernels, initramfs rarely change)
|
||||||
|
Cache-Control "public, max-age=86400"
|
||||||
|
-Server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(ipxe-image-headers) {
|
||||||
|
header {
|
||||||
|
# Moderate caching for images
|
||||||
|
Cache-Control "public, max-age=3600"
|
||||||
|
# Support range requests for large files
|
||||||
|
Accept-Ranges "bytes"
|
||||||
|
-Server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(ipxe-upstream) {
|
||||||
|
# Reverse proxy to iPXE container
|
||||||
|
reverse_proxy ipxe-server:8080 {
|
||||||
|
# Timeouts for large file transfers
|
||||||
|
transport http {
|
||||||
|
response_header_timeout 30s
|
||||||
|
read_timeout 300s
|
||||||
|
write_timeout 300s
|
||||||
|
}
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
health_uri /health
|
||||||
|
health_interval 30s
|
||||||
|
health_timeout 5s
|
||||||
|
|
||||||
|
# Fail fast if unhealthy
|
||||||
|
fail_duration 30s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
50
quadlet
Normal file
50
quadlet
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# ipxe-server.container
|
||||||
|
# Quadlet unit for iPXE boot server
|
||||||
|
# Place in: ~/.config/containers/systemd/ (rootless) or /etc/containers/systemd/ (root)
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=iPXE Boot Server
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
Requires=caddy.service
|
||||||
|
After=caddy.service
|
||||||
|
|
||||||
|
[Container]
|
||||||
|
Image=localhost/ipxe-server:latest
|
||||||
|
ContainerName=ipxe-server
|
||||||
|
|
||||||
|
# Join caddy network for reverse proxy
|
||||||
|
Network=internal_caddy
|
||||||
|
|
||||||
|
# Mount points for boot assets and configuration
|
||||||
|
Volume=%h/ipxe/boot:/srv/boot:ro,Z
|
||||||
|
Volume=%h/ipxe/menus:/srv/menus:ro,Z
|
||||||
|
Volume=%h/ipxe/config:/etc/ipxe:ro,Z
|
||||||
|
|
||||||
|
# Optional: mount ISO storage for serving live images
|
||||||
|
Volume=%h/ipxe/images:/srv/images:ro,Z
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
Environment=IPXE_LOG_LEVEL=info
|
||||||
|
Environment=TZ=Europe/Oslo
|
||||||
|
|
||||||
|
# Resource limits
|
||||||
|
PodmanArgs=--memory=256m --cpus=0.5
|
||||||
|
|
||||||
|
# Labels for Caddy/Traefik discovery (optional)
|
||||||
|
Label=ipxe.server=true
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HealthCmd=/usr/local/bin/healthcheck.sh
|
||||||
|
HealthInterval=30s
|
||||||
|
HealthTimeout=5s
|
||||||
|
HealthRetries=3
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
TimeoutStartSec=90
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
|
||||||
234
setup.sh
Executable file
234
setup.sh
Executable file
@@ -0,0 +1,234 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Setup script for iPXE boot server
|
||||||
|
# Creates directory structure and deploys Quadlet
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
YELLOW='\033[0;33m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
log_info() { printf "${BLUE}[INFO]${NC} %s\n" "$1"; }
|
||||||
|
log_ok() { printf "${GREEN}[OK]${NC} %s\n" "$1"; }
|
||||||
|
log_warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$1"; }
|
||||||
|
log_err() { printf "${RED}[ERROR]${NC} %s\n" "$1"; }
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
IPXE_BASE="${IPXE_BASE:-$HOME/ipxe}"
|
||||||
|
QUADLET_DIR="${QUADLET_DIR:-$HOME/.config/containers/systemd}"
|
||||||
|
CADDY_SNIPPETS="${CADDY_SNIPPETS:-$HOME/Caddy/snippets}"
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat << EOF
|
||||||
|
iPXE Boot Server Setup
|
||||||
|
|
||||||
|
Usage: $0 [command]
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
install Install Quadlet and create directories
|
||||||
|
build Build the container image
|
||||||
|
start Start the iPXE server
|
||||||
|
stop Stop the iPXE server
|
||||||
|
status Show server status
|
||||||
|
logs Show server logs
|
||||||
|
uninstall Remove Quadlet unit
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--rootless Install for rootless Podman (default)
|
||||||
|
--root Install system-wide (requires sudo)
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
create_directories() {
|
||||||
|
log_info "Creating directory structure..."
|
||||||
|
|
||||||
|
mkdir -p "$IPXE_BASE"/{boot,menus,images,config}
|
||||||
|
mkdir -p "$QUADLET_DIR"
|
||||||
|
mkdir -p "$CADDY_SNIPPETS"
|
||||||
|
|
||||||
|
log_ok "Directories created at $IPXE_BASE"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_quadlet() {
|
||||||
|
log_info "Installing Quadlet unit..."
|
||||||
|
|
||||||
|
# Copy Quadlet file
|
||||||
|
cp "$SCRIPT_DIR/ipxe-server.container" "$QUADLET_DIR/"
|
||||||
|
|
||||||
|
# Update paths in Quadlet to use actual home directory
|
||||||
|
sed -i "s|%h/ipxe|$IPXE_BASE|g" "$QUADLET_DIR/ipxe-server.container"
|
||||||
|
|
||||||
|
log_ok "Quadlet installed to $QUADLET_DIR/ipxe-server.container"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_caddy_snippet() {
|
||||||
|
log_info "Installing Caddy snippet..."
|
||||||
|
|
||||||
|
cp "$SCRIPT_DIR/snippets/ipxe" "$CADDY_SNIPPETS/"
|
||||||
|
|
||||||
|
log_ok "Caddy snippet installed to $CADDY_SNIPPETS/ipxe"
|
||||||
|
log_warn "Don't forget to:"
|
||||||
|
log_warn " 1. Add 'import snippets/ipxe' to your Caddyfile"
|
||||||
|
log_warn " 2. Add the ipxe.nuc.lan site block from caddy/ipxe.caddyfile"
|
||||||
|
log_warn " 3. Reload Caddy: podman exec caddy caddy reload --config /etc/caddy/Caddyfile"
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_default_menu() {
|
||||||
|
log_info "Copying default boot menu..."
|
||||||
|
|
||||||
|
if [[ ! -f "$IPXE_BASE/menus/boot.ipxe" ]]; then
|
||||||
|
# Extract default menu from Containerfile or use embedded one
|
||||||
|
cat > "$IPXE_BASE/menus/boot.ipxe" << 'EOF'
|
||||||
|
#!ipxe
|
||||||
|
# iPXE Boot Menu - NUC Home Server
|
||||||
|
|
||||||
|
set menu-timeout 30000
|
||||||
|
set menu-default local
|
||||||
|
|
||||||
|
:start
|
||||||
|
menu iPXE Boot Menu - nuc.lan
|
||||||
|
item --gap -- -------- Boot Options --------
|
||||||
|
item local Boot from local disk
|
||||||
|
item --gap -- -------- Network Boot --------
|
||||||
|
item fedora-live Fedora 42 Live (Minimal)
|
||||||
|
item fedora-kiosk Fedora 42 Kiosk/PoS
|
||||||
|
item --gap -- -------- Utilities --------
|
||||||
|
item shell iPXE Shell
|
||||||
|
item reboot Reboot
|
||||||
|
choose --timeout ${menu-timeout} --default ${menu-default} selected || goto cancel
|
||||||
|
goto ${selected}
|
||||||
|
|
||||||
|
:cancel
|
||||||
|
echo Boot cancelled
|
||||||
|
goto start
|
||||||
|
|
||||||
|
:local
|
||||||
|
sanboot --no-describe --drive 0x80 || goto start
|
||||||
|
|
||||||
|
:fedora-live
|
||||||
|
echo Booting Fedora 42 Live...
|
||||||
|
set base-url http://ipxe.nuc.lan/images/fedora-42
|
||||||
|
kernel ${base-url}/vmlinuz initrd=initrd.img root=live:${base-url}/squashfs.img rd.live.image quiet
|
||||||
|
initrd ${base-url}/initrd.img
|
||||||
|
boot || goto start
|
||||||
|
|
||||||
|
:fedora-kiosk
|
||||||
|
echo Booting Fedora 42 Kiosk...
|
||||||
|
set base-url http://ipxe.nuc.lan/images/fedora-42-kiosk
|
||||||
|
kernel ${base-url}/vmlinuz initrd=initrd.img root=live:${base-url}/squashfs.img rd.live.image quiet
|
||||||
|
initrd ${base-url}/initrd.img
|
||||||
|
boot || goto start
|
||||||
|
|
||||||
|
:shell
|
||||||
|
shell
|
||||||
|
|
||||||
|
:reboot
|
||||||
|
reboot
|
||||||
|
EOF
|
||||||
|
log_ok "Default boot menu created"
|
||||||
|
else
|
||||||
|
log_warn "Boot menu already exists, skipping"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
build_image() {
|
||||||
|
log_info "Building iPXE server container image..."
|
||||||
|
|
||||||
|
podman build -t localhost/ipxe-server:latest -f "$SCRIPT_DIR/Containerfile" "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
log_ok "Image built: localhost/ipxe-server:latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
reload_systemd() {
|
||||||
|
log_info "Reloading systemd user units..."
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
log_ok "Systemd reloaded"
|
||||||
|
}
|
||||||
|
|
||||||
|
start_server() {
|
||||||
|
log_info "Starting iPXE server..."
|
||||||
|
systemctl --user start ipxe-server
|
||||||
|
log_ok "iPXE server started"
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_server() {
|
||||||
|
log_info "Stopping iPXE server..."
|
||||||
|
systemctl --user stop ipxe-server
|
||||||
|
log_ok "iPXE server stopped"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_status() {
|
||||||
|
systemctl --user status ipxe-server
|
||||||
|
}
|
||||||
|
|
||||||
|
show_logs() {
|
||||||
|
journalctl --user -u ipxe-server -f
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstall() {
|
||||||
|
log_info "Uninstalling iPXE server..."
|
||||||
|
|
||||||
|
systemctl --user stop ipxe-server 2>/dev/null || true
|
||||||
|
rm -f "$QUADLET_DIR/ipxe-server.container"
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
|
||||||
|
log_ok "Quadlet removed"
|
||||||
|
log_warn "Data in $IPXE_BASE preserved. Remove manually if needed."
|
||||||
|
}
|
||||||
|
|
||||||
|
do_install() {
|
||||||
|
create_directories
|
||||||
|
install_quadlet
|
||||||
|
install_caddy_snippet
|
||||||
|
copy_default_menu
|
||||||
|
build_image
|
||||||
|
reload_systemd
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
log_ok "Installation complete!"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Add boot files to: $IPXE_BASE/boot/"
|
||||||
|
echo " 2. Add images to: $IPXE_BASE/images/"
|
||||||
|
echo " 3. Edit boot menu: $IPXE_BASE/menus/boot.ipxe"
|
||||||
|
echo " 4. Update Caddy config (see caddy/ipxe.caddyfile)"
|
||||||
|
echo " 5. Start server: $0 start"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main
|
||||||
|
case "${1:-}" in
|
||||||
|
install)
|
||||||
|
do_install
|
||||||
|
;;
|
||||||
|
build)
|
||||||
|
build_image
|
||||||
|
;;
|
||||||
|
start)
|
||||||
|
start_server
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
stop_server
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
show_status
|
||||||
|
;;
|
||||||
|
logs)
|
||||||
|
show_logs
|
||||||
|
;;
|
||||||
|
uninstall)
|
||||||
|
uninstall
|
||||||
|
;;
|
||||||
|
-h|--help|help|"")
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_err "Unknown command: $1"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Reference in New Issue
Block a user