-
-
Notifications
You must be signed in to change notification settings - Fork 821
update pull #1798
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
update pull #1798
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| # Copy this file to .env and edit the values | ||
|
|
||
| # Timezone (see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) | ||
| TZ=America/New_York | ||
|
|
||
| # User/Group ID for file permissions (run `id` on Linux to find yours; leave 1000 on Windows) | ||
| PUID=1000 | ||
| PGID=1000 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # Docker named volume data (managed by Docker, not tracked in git) | ||
| data/ | ||
|
|
||
| # Local ISO files (can be large — track only the readme placeholder) | ||
| isos/*.iso | ||
|
|
||
| # Environment overrides | ||
| .env | ||
|
|
||
| # OS / editor | ||
| .DS_Store | ||
| Thumbs.db | ||
| *.swp |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| services: | ||
| netbootxyz: | ||
| image: lscr.io/linuxserver/netbootxyz:latest | ||
| container_name: netbootxyz | ||
| environment: | ||
| - PUID=${PUID:-1000} | ||
| - PGID=${PGID:-1000} | ||
| - TZ=${TZ:-Etc/UTC} | ||
| volumes: | ||
| # Named volume for persistent config/state — mirrors the data:data pattern | ||
| - data:/config | ||
| # Custom iPXE menu files — written by the iso-watcher container | ||
| - ./menus:/config/menus | ||
| # Local ISOs — drop files here; the watcher auto-updates the menu | ||
| # To map to another drive, replace ./isos with an absolute path: | ||
| # Windows: - D:\ISOs:/assets/isos | ||
| # Linux: - /mnt/storage/isos:/assets/isos | ||
| - ./isos:/assets/isos | ||
| ports: | ||
| - 3000:3000 # Web UI | ||
| - 69:69/udp # TFTP (PXE boot entry point) | ||
| - 8080:80 # HTTP (serves bootloaders + ISOs) | ||
| restart: unless-stopped | ||
|
|
||
| iso-watcher: | ||
| build: | ||
| context: . | ||
| dockerfile: docker/Dockerfile.watcher | ||
| container_name: netbootxyz-iso-watcher | ||
| volumes: | ||
| # Read ISOs from the same folder (read-only — watcher never modifies ISOs) | ||
| - ./isos:/isos:ro | ||
| # Write the generated menu into the menus folder | ||
| - ./menus:/menus | ||
| restart: unless-stopped | ||
|
|
||
| volumes: | ||
| data: # Named volume — survives container restarts/upgrades |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| FROM alpine:latest | ||
| RUN apk add --no-cache inotify-tools | ||
| COPY docker/watcher.sh /watcher.sh | ||
| RUN chmod +x /watcher.sh | ||
| ENTRYPOINT ["/watcher.sh"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| #!/bin/sh | ||
| # Watches /isos for changes and auto-regenerates /menus/local-isos.ipxe | ||
| # Runs inside the iso-watcher container — nothing needs to be done manually. | ||
|
|
||
| ISOS_DIR="/isos" | ||
| OUTPUT="/menus/local-isos.ipxe" | ||
| TMPLIST="/tmp/iso_list.txt" | ||
| CHANGED="/tmp/iso_changed" | ||
|
|
||
| generate_menu() { | ||
| mkdir -p /menus | ||
|
|
||
| # Collect all ISOs recursively, sorted by path | ||
| find "${ISOS_DIR}" -name "*.iso" 2>/dev/null | sort > "${TMPLIST}" | ||
| ISO_COUNT="$(wc -l < "${TMPLIST}" | tr -d ' ')" | ||
|
|
||
| if [ "${ISO_COUNT}" = "0" ]; then | ||
| printf '#!ipxe\necho No ISOs found. Drop .iso files into the isos/ folder.\necho.\nprompt Press any key to return...\nexit 1\n' \ | ||
| > "${OUTPUT}" | ||
| echo "$(date '+%Y-%m-%d %H:%M:%S') [iso-watcher] No ISOs found — menu cleared" | ||
| return | ||
| fi | ||
|
|
||
| # ── Header ────────────────────────────────────────────────────────────────── | ||
| cat > "${OUTPUT}" <<'HEADER' | ||
| #!ipxe | ||
| ### | ||
| ### Local ISO Boot Menu (auto-generated — managed by iso-watcher container) | ||
| ### | ||
|
|
||
| :local_iso_menu | ||
| clear iso_choice | ||
| menu Local ISO Boot Menu | ||
| HEADER | ||
|
|
||
| # ── Menu items, grouped by subfolder ──────────────────────────────────────── | ||
| last_dir="" | ||
| idx=0 | ||
| while IFS= read -r isofile; do | ||
| [ -z "${isofile}" ] && continue | ||
| rel="${isofile#${ISOS_DIR}/}" | ||
| dir="$(dirname "${rel}")" | ||
| filename="$(basename "${rel}")" | ||
| label="${filename%.iso}" | ||
|
|
||
| # Emit a category gap whenever the directory changes | ||
| if [ "${dir}" != "${last_dir}" ]; then | ||
| if [ "${dir}" = "." ]; then | ||
| printf 'item --gap -- === General ===\n' >> "${OUTPUT}" | ||
| else | ||
| printf 'item --gap -- === %s ===\n' "${dir}" >> "${OUTPUT}" | ||
| fi | ||
| last_dir="${dir}" | ||
| fi | ||
|
|
||
| printf 'item iso_%d %s\n' "${idx}" "${label}" >> "${OUTPUT}" | ||
| idx=$((idx + 1)) | ||
| done < "${TMPLIST}" | ||
|
|
||
| # ── Navigation footer ──────────────────────────────────────────────────────── | ||
| printf 'item --gap --\nitem iso_back Go back\nchoose iso_choice || goto iso_back\ngoto ${iso_choice}\n\n' \ | ||
| >> "${OUTPUT}" | ||
|
|
||
| # ── Boot entries ───────────────────────────────────────────────────────────── | ||
| # ${boot_domain} is an iPXE variable — the $ is intentionally literal here. | ||
| # ISOs in subfolders are served at http://${boot_domain}/isos/subfolder/name.iso | ||
| idx=0 | ||
| while IFS= read -r isofile; do | ||
| [ -z "${isofile}" ] && continue | ||
| rel="${isofile#${ISOS_DIR}/}" | ||
| filename="$(basename "${rel}")" | ||
| printf ':iso_%d\necho Booting %s ...\nkernel http://${boot_domain}/memdisk raw iso\ninitrd http://${boot_domain}/isos/%s\nboot || goto local_iso_menu\n\n' \ | ||
| "${idx}" "${filename}" "${rel}" >> "${OUTPUT}" | ||
| idx=$((idx + 1)) | ||
| done < "${TMPLIST}" | ||
|
|
||
| printf ':iso_back\nexit 1\n' >> "${OUTPUT}" | ||
|
|
||
| echo "$(date '+%Y-%m-%d %H:%M:%S') [iso-watcher] Menu updated — ${ISO_COUNT} ISO(s)" | ||
| } | ||
|
|
||
| echo "[iso-watcher] Starting. Watching ${ISOS_DIR} for changes ..." | ||
| generate_menu | ||
|
|
||
| # ── Background watcher ──────────────────────────────────────────────────────── | ||
| # inotifywait fires on any file event; we touch a flag file as the signal. | ||
| # The main loop checks every 2 s and regenerates only once per burst of events. | ||
| ( | ||
| while true; do | ||
| inotifywait -r -e create,delete,move,close_write -q "${ISOS_DIR}" 2>/dev/null \ | ||
| && touch "${CHANGED}" | ||
| sleep 1 | ||
| done | ||
| ) & | ||
|
|
||
| while true; do | ||
| sleep 2 | ||
| if [ -f "${CHANGED}" ]; then | ||
| rm -f "${CHANGED}" | ||
| generate_menu | ||
| fi | ||
| done |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| #!/usr/bin/env bash | ||
| # MANUAL FALLBACK — normally the iso-watcher container handles this automatically. | ||
| # Only run this if the watcher container is not running (e.g. during development). | ||
| # | ||
| # Scans the isos/ folder (including subfolders) and writes menus/local-isos.ipxe. | ||
| # Subfolders become category headers in the boot menu. | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| ISOS_DIR="${SCRIPT_DIR}/isos" | ||
| OUTPUT_FILE="${SCRIPT_DIR}/menus/local-isos.ipxe" | ||
|
|
||
| mkdir -p "${SCRIPT_DIR}/menus" | ||
|
|
||
| # Collect ALL .iso files recursively, sorted by path (groups subfolders together) | ||
| mapfile -d '' ALL_ISOS < <(find "${ISOS_DIR}" -name "*.iso" -print0 2>/dev/null | sort -z) | ||
|
|
||
| ISO_COUNT=${#ALL_ISOS[@]} | ||
|
|
||
| if [ "${ISO_COUNT}" -eq 0 ]; then | ||
| echo "No .iso files found in ${ISOS_DIR}/ (or subfolders)." | ||
| echo "Drop .iso files there — subfolders like isos/linux/ or isos/windows/ are supported." | ||
| cat > "${OUTPUT_FILE}" <<'PLACEHOLDER' | ||
| #!ipxe | ||
| echo No local ISOs found. | ||
| echo Drop .iso files into the isos/ folder (subfolders are supported). | ||
| echo Then run ./generate-iso-menu.sh on the host. | ||
| echo. | ||
| prompt Press any key to return... | ||
| exit 1 | ||
| PLACEHOLDER | ||
| exit 0 | ||
| fi | ||
|
|
||
| # ── Build the menu header ──────────────────────────────────────────────────── | ||
| cat > "${OUTPUT_FILE}" <<'HEADER' | ||
| #!ipxe | ||
| ### | ||
| ### Local ISO Boot Menu (auto-generated — do not edit by hand) | ||
| ### Re-run ./generate-iso-menu.sh to refresh after adding/moving ISOs. | ||
| ### | ||
|
|
||
| :local_iso_menu | ||
| clear iso_choice | ||
| menu Local ISO Boot Menu | ||
| HEADER | ||
|
|
||
| # ── Menu items, grouped by subfolder ───────────────────────────────────────── | ||
| # When the parent folder changes, emit a --gap separator with the folder name. | ||
| # ISOs directly in isos/ appear under "General". | ||
| declare -a ITEM_ISOS=() | ||
| last_dir="" | ||
| idx=0 | ||
|
|
||
| for isofile in "${ALL_ISOS[@]}"; do | ||
| rel="${isofile#"${ISOS_DIR}/"}" # e.g. linux/ubuntu.iso or ubuntu.iso | ||
| dir="$(dirname "${rel}")" # e.g. linux or . | ||
| filename="$(basename "${rel}")" | ||
| label="${filename%.iso}" # strip .iso for display | ||
|
|
||
| # Emit a category gap whenever the directory changes | ||
| if [ "${dir}" != "${last_dir}" ]; then | ||
| if [ "${dir}" = "." ]; then | ||
| echo 'item --gap -- === General ===' >> "${OUTPUT_FILE}" | ||
| else | ||
| echo "item --gap -- === ${dir} ===" >> "${OUTPUT_FILE}" | ||
| fi | ||
| last_dir="${dir}" | ||
| fi | ||
|
|
||
| printf 'item iso_%d %s\n' "${idx}" "${label}" >> "${OUTPUT_FILE}" | ||
| ITEM_ISOS+=("${rel}") | ||
| idx=$((idx + 1)) | ||
| done | ||
|
|
||
| # ── Footer of menu items ───────────────────────────────────────────────────── | ||
| cat >> "${OUTPUT_FILE}" <<'MID' | ||
| item --gap -- | ||
| item iso_back Go back | ||
| choose iso_choice || goto iso_back | ||
| goto ${iso_choice} | ||
|
|
||
| MID | ||
|
|
||
| # ── Boot entries ───────────────────────────────────────────────────────────── | ||
| # ISOs are served by the container's HTTP server: | ||
| # container port 80 → host port 8080 (docker-compose.yml) | ||
| # URL: http://${boot_domain}/isos/<relative-path> | ||
| # ${boot_domain} is resolved by iPXE to the IP that delivered the boot file. | ||
| # Subfolders are preserved in the URL path automatically. | ||
| for i in "${!ITEM_ISOS[@]}"; do | ||
| rel="${ITEM_ISOS[$i]}" | ||
| filename="$(basename "${rel}")" | ||
| urlpath="isos/${rel}" # e.g. isos/linux/ubuntu.iso | ||
| cat >> "${OUTPUT_FILE}" <<ENTRY | ||
| :iso_${i} | ||
| echo Booting ${filename} ... | ||
| kernel http://\${boot_domain}/memdisk raw iso | ||
| initrd http://\${boot_domain}/${urlpath} | ||
| boot || goto local_iso_menu | ||
|
|
||
| ENTRY | ||
| done | ||
|
|
||
| # ── Final label ────────────────────────────────────────────────────────────── | ||
| cat >> "${OUTPUT_FILE}" <<'FOOTER' | ||
| :iso_back | ||
| exit 1 | ||
| FOOTER | ||
|
|
||
| echo "Generated: ${OUTPUT_FILE}" | ||
| echo "Found ${ISO_COUNT} ISO file(s):" | ||
| for f in "${ALL_ISOS[@]}"; do | ||
| rel="${f#"${ISOS_DIR}/"}" | ||
| echo " - ${rel}" | ||
| done | ||
| echo | ||
| echo "ISOs are served at: http://YOUR_SERVER_IP:8080/isos/" | ||
| echo "Refresh the page in the netboot.xyz web UI if the menu doesn't update." |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| DROP .ISO FILES IN THIS FOLDER | ||
| ================================ | ||
|
|
||
| Just drop any .iso file here — the menu updates automatically. | ||
| No scripts, no restarts, nothing else to do. | ||
|
|
||
| You can also use subfolders to keep things organised: | ||
|
|
||
| isos/ | ||
| linux/ | ||
| ubuntu-24.04.iso | ||
| debian-12.iso | ||
| windows/ | ||
| windows11.iso | ||
| tools/ | ||
| proxmox-8.iso | ||
| rescue.iso | ||
|
|
||
| Subfolders show up as category labels in the boot menu. | ||
| ISOs in the root of this folder appear under "General". | ||
|
|
||
| TO MAP THIS FOLDER TO ANOTHER DRIVE | ||
| ------------------------------------- | ||
| Edit the docker-compose.yml line: | ||
|
|
||
| - ./isos:/assets/isos | ||
|
|
||
| Replace ./isos with the full path to your drive: | ||
|
|
||
| Windows: - D:\MyISOs:/assets/isos | ||
| Linux: - /mnt/nas/isos:/assets/isos | ||
|
|
||
| Then restart the stack in Portainer. | ||
|
|
||
| HOW TO BOOT | ||
| ------------ | ||
| In the netboot.xyz menu, go to: | ||
| Custom... → Custom Boot Menu → Boot a Local ISO | ||
|
|
||
| You can also boot any ISO by URL: | ||
| Custom... → Custom Boot Menu → Boot from URL |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| #!ipxe | ||
| ### | ||
| ### Custom Boot Menu | ||
| ### Includes local ISOs (from isos/ folder) and URL boot option | ||
| ### | ||
|
|
||
| :custom_menu | ||
| clear custom_choice | ||
| menu Custom Boot Menu | ||
| item local_isos Boot a Local ISO (from isos/ folder) | ||
| item url_boot Boot from URL (type any ISO link) | ||
| item --gap -- | ||
| item return Back to main menu | ||
| choose custom_choice || goto custom_menu_exit | ||
| goto ${custom_choice} | ||
| goto custom_menu_exit | ||
|
|
||
| :local_isos | ||
| chain local-isos.ipxe || goto no_isos | ||
|
|
||
| :no_isos | ||
| echo No local ISOs found. | ||
| echo Run ./generate-iso-menu.sh on the host to build the menu, | ||
| echo then drop .iso files into the isos/ folder. | ||
| echo. | ||
| prompt Press any key to return... | ||
| goto custom_menu | ||
|
|
||
| :url_boot | ||
| clear boot_url | ||
| echo. | ||
| echo Enter the full URL to an ISO or image file: | ||
| echo (e.g. http://192.168.1.10:8080/isos/ubuntu.iso) | ||
| echo. | ||
| read boot_url | ||
| iseq ${boot_url} && goto url_boot || | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CRITICAL: Logic error in conditional This line uses The correct iPXE pattern for "retry if empty" is: (The trailing |
||
| echo. | ||
| echo Booting ${boot_url} ... | ||
| kernel http://${boot_domain}/memdisk raw iso | ||
| initrd ${boot_url} | ||
| boot || goto url_boot_fail | ||
|
|
||
| :url_boot_fail | ||
| echo. | ||
| echo Boot failed. Check the URL and that the server is reachable. | ||
| prompt Press any key to go back... | ||
| goto custom_menu | ||
|
|
||
| :custom_menu_exit | ||
| exit 1 | ||
Uh oh!
There was an error while loading. Please reload this page.