From e7313c2ca307bafa2947956f37f40aa4a12fe481 Mon Sep 17 00:00:00 2001 From: cproudlock Date: Fri, 6 Feb 2026 16:20:50 -0500 Subject: [PATCH] Add multi-boot PXE menu, Clonezilla backup management, and GE Aerospace branding - iPXE boot menu with WinPE, Clonezilla, Blancco Drive Eraser, Memtest86+ - prepare-boot-tools.sh to download/extract boot tool binaries - Clonezilla backup management in webapp (upload, download, delete) - Clonezilla Samba share for network backup/restore - GE Aerospace logo and favicon in webapp - Updated playbook with boot tool directories and webapp env vars Co-Authored-By: Claude Opus 4.6 --- .gitignore | 6 + SETUP.md | 16 +-- build-usb.sh | 10 ++ playbook/pxe_server_setup.yml | 88 ++++++++++++-- prepare-boot-tools.sh | 175 ++++++++++++++++++++++++++++ webapp/app.py | 112 ++++++++++++++++-- webapp/static/favicon.ico | Bin 0 -> 1950 bytes webapp/static/ge-aerospace-logo.svg | 8 ++ webapp/templates/backups.html | 131 +++++++++++++++++++++ webapp/templates/base.html | 22 +++- 10 files changed, 533 insertions(+), 35 deletions(-) create mode 100755 prepare-boot-tools.sh create mode 100644 webapp/static/favicon.ico create mode 100644 webapp/static/ge-aerospace-logo.svg create mode 100644 webapp/templates/backups.html diff --git a/.gitignore b/.gitignore index f1d5314..e15fe74 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,12 @@ WestJeff*/ # Offline packages (built by download-packages.sh) offline-packages/ +# Boot tool binaries (built by prepare-boot-tools.sh) +boot-tools/ + +# Deployment images (imported via webapp or USB) +geastandardpbr/ + # OS files .DS_Store Thumbs.db diff --git a/SETUP.md b/SETUP.md index fb66fe8..0e2c69b 100644 --- a/SETUP.md +++ b/SETUP.md @@ -115,14 +115,14 @@ pxe-server/ ## Image Types -| Image Type | Domain | -|-------------------|-----------------| -| geastandardpbr | geaerospace.com | -| geaengineerpbr | geaerospace.com | -| geashopfloorpbr | geaerospace.com | -| gestandardlegacy | ge.com | -| geengineerlegacy | ge.com | -| geshopfloorlegacy | ge.com | +| Image Type | Domain | Description | +|---------------|-----------------|---------------------| +| gea-standard | geaerospace.com | Standard desktop | +| gea-engineer | geaerospace.com | Engineering desktop | +| gea-shopfloor | geaerospace.com | Shop floor kiosk | +| ge-standard | ge.com | Standard desktop | +| ge-engineer | ge.com | Engineering desktop | +| ge-shopfloor | ge.com | Shop floor kiosk | ## Network Configuration diff --git a/build-usb.sh b/build-usb.sh index 8e6a10b..5806143 100755 --- a/build-usb.sh +++ b/build-usb.sh @@ -185,6 +185,16 @@ if [ -d "$WEBAPP_DIR" ]; then echo " Copied webapp/" fi +# Copy boot tools (Clonezilla, Blancco, Memtest) if prepared +BOOT_TOOLS_DIR="$SCRIPT_DIR/boot-tools" +if [ -d "$BOOT_TOOLS_DIR" ]; then + cp -r "$BOOT_TOOLS_DIR" "$MOUNT_POINT/boot-tools" + TOOLS_SIZE=$(du -sh "$MOUNT_POINT/boot-tools" | cut -f1) + echo " Copied boot-tools/ ($TOOLS_SIZE)" +else + echo " No boot-tools/ found (run prepare-boot-tools.sh first)" +fi + # Optionally copy WinPE deployment images if [ -n "$WINPE_SOURCE" ] && [ -d "$WINPE_SOURCE" ]; then echo " Copying WinPE deployment content from $WINPE_SOURCE..." diff --git a/playbook/pxe_server_setup.yml b/playbook/pxe_server_setup.yml index c7ffeb6..b6fac48 100644 --- a/playbook/pxe_server_setup.yml +++ b/playbook/pxe_server_setup.yml @@ -32,12 +32,13 @@ samba_share: "/srv/samba/winpeapps" usb_mount: "/mnt/usb/playbook" # where your USB is mounted image_types: - - geastandardpbr - - geaengineerpbr - - geashopfloorpbr - - gestandardlegacy - - geengineerlegacy - - geshopfloorlegacy + - gea-standard + - gea-engineer + - gea-shopfloor + - ge-standard + - ge-engineer + - ge-shopfloor-lockdown + - ge-shopfloor-mce deploy_subdirs: - Applications - Control @@ -114,7 +115,17 @@ state: directory mode: '0755' - - name: "Create GetPxeScript.aspx" + - name: "Create boot tool directories" + file: + path: "{{ web_root }}/{{ item }}" + state: directory + mode: '0755' + loop: + - clonezilla + - blancco + - memtest + + - name: "Create GetPxeScript.aspx (iPXE boot menu)" copy: dest: "{{ web_root }}/Altiris/iPXE/GetPxeScript.aspx" backup: yes @@ -123,16 +134,50 @@ set server 10.9.100.1 - kernel http://${server}/win11/wimboot gui + :menu + menu GE Aerospace PXE Boot Menu + item --gap -- ---- Windows Deployment ---- + item winpe Windows PE (Image Deployment) + item --gap -- ---- Utilities ---- + item clonezilla Clonezilla Live (Disk Imaging) + item blancco Blancco Drive Eraser + item memtest Memtest86+ (Memory Diagnostics) + item --gap -- ---- + item reboot Reboot + item exit Exit to BIOS + choose --default winpe --timeout 30000 target && goto ${target} + :winpe + kernel http://${server}/win11/wimboot gui initrd http://${server}/win11/EFI/Microsoft/Boot/boot.stl EFI/Microsoft/Boot/Boot.stl initrd http://${server}/win11/EFI/Microsoft/Boot/BCD EFI/Microsoft/Boot/BCD initrd http://${server}/win11/EFI/Boot/bootx64.efi EFI/Boot/bootx64.efi initrd http://${server}/win11/Boot/boot.sdi Boot/boot.sdi initrd http://${server}/win11/sources/boot.wim sources/boot.wim - boot + :clonezilla + set base http://${server}/clonezilla + kernel ${base}/vmlinuz boot=live username=user union=overlay config components noswap edd=on nomodeset nodmraid locales= keyboard-layouts= ocs_live_run="ocs-live-general" ocs_live_extra_param="" ocs_live_batch=no net.ifnames=0 nosplash noprompt fetch=${base}/filesystem.squashfs + initrd ${base}/initrd.img + boot + + :blancco + set bbase http://${server}/blancco + kernel ${bbase}/vmlinuz-bde-linux archisobasedir=arch archiso_http_srv=http://${server}/blancco/ copytoram=y cow_spacesize=50% memtest=00 vmalloc=400M ip=dhcp quiet nomodeset libata.allow_tpm=1 + initrd ${bbase}/intel-ucode.img ${bbase}/amd-ucode.img ${bbase}/config.img ${bbase}/initramfs-bde-linux.img + boot + + :memtest + kernel http://${server}/memtest/memtest.efi + boot + + :reboot + reboot + + :exit + exit + - name: "Ensure Apache listens on port 4433" lineinfile: path: /etc/apache2/ports.conf @@ -171,7 +216,13 @@ state: directory mode: '0777' - - name: "Configure Samba share" + - name: "Create Clonezilla backup share directory" + file: + path: /srv/samba/clonezilla + state: directory + mode: '0777' + + - name: "Configure Samba shares" blockinfile: path: /etc/samba/smb.conf backup: yes @@ -182,6 +233,13 @@ read only = no guest ok = yes + [clonezilla] + path = /srv/samba/clonezilla + browseable = yes + read only = no + guest ok = yes + comment = Clonezilla backup images + - name: "Create image-type top-level directories" file: path: "{{ samba_share }}/{{ item }}" @@ -219,6 +277,15 @@ loop: - ipxe.efi + - name: "Copy boot tool files from USB (Clonezilla, Blancco, Memtest)" + shell: > + cp -r "{{ usb_mount }}/../boot-tools/{{ item }}/"* "{{ web_root }}/{{ item }}/" 2>/dev/null || + cp -r "{{ usb_mount }}/boot-tools/{{ item }}/"* "{{ web_root }}/{{ item }}/" 2>/dev/null || true + loop: + - clonezilla + - blancco + - memtest + - name: "Check for WinPE deployment content on USB" stat: path: "{{ usb_mount }}/images" @@ -306,6 +373,7 @@ User=root WorkingDirectory=/opt/pxe-webapp Environment=SAMBA_SHARE={{ samba_share }} + Environment=CLONEZILLA_SHARE=/srv/samba/clonezilla ExecStart=/opt/pxe-webapp/venv/bin/python app.py Restart=always RestartSec=5 diff --git a/prepare-boot-tools.sh b/prepare-boot-tools.sh new file mode 100755 index 0000000..9092c8f --- /dev/null +++ b/prepare-boot-tools.sh @@ -0,0 +1,175 @@ +#!/bin/bash +# +# prepare-boot-tools.sh — Download/extract boot files for PXE boot tools +# +# Downloads Clonezilla Live and Memtest86+ for PXE booting, +# and extracts Blancco Drive Eraser from its ISO. +# +# Usage: +# ./prepare-boot-tools.sh [/path/to/blancco.iso] +# +# Output directories: +# boot-tools/clonezilla/ — vmlinuz, initrd.img, filesystem.squashfs +# boot-tools/blancco/ — extracted boot files or ISO for memdisk +# boot-tools/memtest/ — memtest.efi + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +OUT_DIR="$SCRIPT_DIR/boot-tools" +BLANCCO_ISO="${1:-}" + +# Auto-detect Blancco ISO in project directory +if [ -z "$BLANCCO_ISO" ]; then + BLANCCO_ISO=$(find "$SCRIPT_DIR" -maxdepth 1 -name '*DriveEraser*.iso' -o -name '*blancco*.iso' 2>/dev/null | head -1) +fi + +mkdir -p "$OUT_DIR"/{clonezilla,blancco,memtest} + +echo "============================================" +echo "PXE Boot Tools Preparation" +echo "============================================" + +# --- Clonezilla Live --- +echo "" +echo "[1/3] Clonezilla Live" + +CLONEZILLA_VERSION="3.2.1-6" +CLONEZILLA_FILE="clonezilla-live-${CLONEZILLA_VERSION}-amd64.zip" +CLONEZILLA_URL="https://sourceforge.net/projects/clonezilla/files/clonezilla_live_stable/${CLONEZILLA_VERSION}/${CLONEZILLA_FILE}/download" + +if [ -f "$OUT_DIR/clonezilla/vmlinuz" ] && [ -f "$OUT_DIR/clonezilla/filesystem.squashfs" ]; then + echo " Already prepared, skipping. Delete boot-tools/clonezilla/ to re-download." +else + echo " Downloading Clonezilla Live ${CLONEZILLA_VERSION}..." + TMPDIR=$(mktemp -d) + + wget -q --show-progress -O "$TMPDIR/$CLONEZILLA_FILE" "$CLONEZILLA_URL" || { + echo " ERROR: Download failed. Trying alternative URL..." + # Fallback: try OSDN mirror + wget -q --show-progress -O "$TMPDIR/$CLONEZILLA_FILE" \ + "https://free.nchc.org.tw/clonezilla-live/stable/${CLONEZILLA_FILE}" || { + echo " ERROR: Could not download Clonezilla. Download manually and place in boot-tools/clonezilla/" + echo " Need: vmlinuz, initrd.img, filesystem.squashfs from the live ZIP" + } + } + + if [ -f "$TMPDIR/$CLONEZILLA_FILE" ]; then + echo " Extracting PXE boot files..." + unzip -o -j "$TMPDIR/$CLONEZILLA_FILE" "live/vmlinuz" -d "$OUT_DIR/clonezilla/" + unzip -o -j "$TMPDIR/$CLONEZILLA_FILE" "live/initrd.img" -d "$OUT_DIR/clonezilla/" + unzip -o -j "$TMPDIR/$CLONEZILLA_FILE" "live/filesystem.squashfs" -d "$OUT_DIR/clonezilla/" + rm -rf "$TMPDIR" + echo " Done." + fi +fi + +ls -lh "$OUT_DIR/clonezilla/" 2>/dev/null | grep -E 'vmlinuz|initrd|squashfs' | sed 's/^/ /' + +# --- Blancco Drive Eraser --- +echo "" +echo "[2/3] Blancco Drive Eraser" + +if [ -n "$BLANCCO_ISO" ] && [ -f "$BLANCCO_ISO" ]; then + echo " Extracting from: $BLANCCO_ISO" + echo " Using 7z to extract (no root required)..." + + # Blancco is Arch Linux-based. We need: + # arch/boot/x86_64/vmlinuz-bde-linux + # arch/boot/x86_64/initramfs-bde-linux.img + # arch/boot/intel-ucode.img + # arch/boot/amd-ucode.img + # arch/boot/config.img + # arch/x86_64/airootfs.sfs + TMPDIR=$(mktemp -d) + 7z x -o"$TMPDIR" "$BLANCCO_ISO" \ + "arch/boot/x86_64/vmlinuz-bde-linux" \ + "arch/boot/x86_64/initramfs-bde-linux.img" \ + "arch/boot/intel-ucode.img" \ + "arch/boot/amd-ucode.img" \ + "arch/boot/config.img" \ + "arch/x86_64/airootfs.sfs" \ + -r 2>/dev/null || { + echo " 7z extraction failed. Install p7zip-full: apt install p7zip-full" + } + + # Flatten into blancco/ directory for HTTP serving + if [ -f "$TMPDIR/arch/boot/x86_64/vmlinuz-bde-linux" ]; then + cp "$TMPDIR/arch/boot/x86_64/vmlinuz-bde-linux" "$OUT_DIR/blancco/" + cp "$TMPDIR/arch/boot/x86_64/initramfs-bde-linux.img" "$OUT_DIR/blancco/" + cp "$TMPDIR/arch/boot/intel-ucode.img" "$OUT_DIR/blancco/" + cp "$TMPDIR/arch/boot/amd-ucode.img" "$OUT_DIR/blancco/" + cp "$TMPDIR/arch/boot/config.img" "$OUT_DIR/blancco/" + # airootfs.sfs needs to be in arch/x86_64/ path relative to HTTP root + mkdir -p "$OUT_DIR/blancco/arch/x86_64" + cp "$TMPDIR/arch/x86_64/airootfs.sfs" "$OUT_DIR/blancco/arch/x86_64/" + echo " Extracted Blancco boot files." + else + echo " Could not extract boot files from ISO." + fi + rm -rf "$TMPDIR" +else + echo " No Blancco ISO found. Provide path as argument or place in project directory." + echo " Usage: $0 /path/to/DriveEraser.iso" +fi + +ls -lh "$OUT_DIR/blancco/" 2>/dev/null | grep -v '^total' | sed 's/^/ /' + +# --- Memtest86+ --- +echo "" +echo "[3/3] Memtest86+" + +MEMTEST_VERSION="7.20" +MEMTEST_URL="https://memtest.org/download/${MEMTEST_VERSION}/mt86plus_${MEMTEST_VERSION}.binaries.zip" + +if [ -f "$OUT_DIR/memtest/memtest.efi" ]; then + echo " Already prepared, skipping." +else + echo " Downloading Memtest86+ v${MEMTEST_VERSION}..." + TMPDIR=$(mktemp -d) + + wget -q --show-progress -O "$TMPDIR/memtest.zip" "$MEMTEST_URL" || { + echo " ERROR: Download failed. Download manually from https://memtest.org" + TMPDIR="" + } + + if [ -n "$TMPDIR" ] && [ -f "$TMPDIR/memtest.zip" ]; then + echo " Extracting EFI binary..." + unzip -o -j "$TMPDIR/memtest.zip" "memtest64.efi" -d "$OUT_DIR/memtest/" 2>/dev/null || \ + unzip -o -j "$TMPDIR/memtest.zip" "mt86plus_${MEMTEST_VERSION}.x64.efi" -d "$OUT_DIR/memtest/" 2>/dev/null || \ + unzip -o "$TMPDIR/memtest.zip" -d "$TMPDIR/extract/" + + # Find the EFI file regardless of exact name + EFI_FILE=$(find "$TMPDIR" "$OUT_DIR/memtest" -name '*.efi' -name '*64*' 2>/dev/null | head -1) + if [ -n "$EFI_FILE" ] && [ ! -f "$OUT_DIR/memtest/memtest.efi" ]; then + cp "$EFI_FILE" "$OUT_DIR/memtest/memtest.efi" + fi + rm -rf "$TMPDIR" + echo " Done." + fi +fi + +ls -lh "$OUT_DIR/memtest/" 2>/dev/null | grep -v '^total' | sed 's/^/ /' + +# --- Summary --- +echo "" +echo "============================================" +echo "Boot tools prepared in: $OUT_DIR/" +echo "============================================" +echo "" + +for tool in clonezilla blancco memtest; do + COUNT=$(find "$OUT_DIR/$tool" -type f 2>/dev/null | wc -l) + SIZE=$(du -sh "$OUT_DIR/$tool" 2>/dev/null | cut -f1) + printf " %-15s %s (%d files)\n" "$tool" "$SIZE" "$COUNT" +done + +echo "" +echo "These files need to be copied to the PXE server's web root:" +echo " /var/www/html/clonezilla/" +echo " /var/www/html/blancco/" +echo " /var/www/html/memtest/" +echo "" +echo "The build-usb.sh script will include them automatically," +echo "or copy them manually to the server." +echo "" diff --git a/webapp/app.py b/webapp/app.py index 4650977..51e6239 100644 --- a/webapp/app.py +++ b/webapp/app.py @@ -14,34 +14,40 @@ from flask import ( redirect, render_template, request, + send_file, url_for, ) from lxml import etree +from werkzeug.utils import secure_filename app = Flask(__name__) app.secret_key = os.environ.get("FLASK_SECRET_KEY", "pxe-manager-dev-key-change-in-prod") +app.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024 * 1024 # 16 GB max upload # --------------------------------------------------------------------------- # Configuration # --------------------------------------------------------------------------- SAMBA_SHARE = os.environ.get("SAMBA_SHARE", "/srv/samba/winpeapps") +CLONEZILLA_SHARE = os.environ.get("CLONEZILLA_SHARE", "/srv/samba/clonezilla") IMAGE_TYPES = [ - "geastandardpbr", - "geaengineerpbr", - "geashopfloorpbr", - "gestandardlegacy", - "geengineerlegacy", - "geshopfloorlegacy", + "gea-standard", + "gea-engineer", + "gea-shopfloor", + "ge-standard", + "ge-engineer", + "ge-shopfloor-lockdown", + "ge-shopfloor-mce", ] FRIENDLY_NAMES = { - "geastandardpbr": "GEA Standard PBR", - "geaengineerpbr": "GEA Engineer PBR", - "geashopfloorpbr": "GEA Shop Floor PBR", - "gestandardlegacy": "GE Standard Legacy", - "geengineerlegacy": "GE Engineer Legacy", - "geshopfloorlegacy": "GE Shop Floor Legacy", + "gea-standard": "GE Aerospace Standard", + "gea-engineer": "GE Aerospace Engineer", + "gea-shopfloor": "GE Aerospace Shop Floor", + "ge-standard": "GE Legacy Standard", + "ge-engineer": "GE Legacy Engineer", + "ge-shopfloor-lockdown": "GE Legacy Shop Floor Lockdown", + "ge-shopfloor-mce": "GE Legacy Shop Floor MCE", } NS = "urn:schemas-microsoft-com:unattend" @@ -561,6 +567,77 @@ def unattend_editor(image_type): ) +# --------------------------------------------------------------------------- +# Routes — Clonezilla Backups +# --------------------------------------------------------------------------- + +@app.route("/backups") +def clonezilla_backups(): + backups = [] + if os.path.isdir(CLONEZILLA_SHARE): + for f in sorted(os.listdir(CLONEZILLA_SHARE)): + fpath = os.path.join(CLONEZILLA_SHARE, f) + if os.path.isfile(fpath) and f.lower().endswith(".zip"): + stat = os.stat(fpath) + backups.append({ + "filename": f, + "machine": os.path.splitext(f)[0], + "size": stat.st_size, + "modified": stat.st_mtime, + }) + return render_template( + "backups.html", + backups=backups, + image_types=IMAGE_TYPES, + friendly_names=FRIENDLY_NAMES, + ) + + +@app.route("/backups/upload", methods=["POST"]) +def clonezilla_upload(): + if "backup_file" not in request.files: + flash("No file selected.", "danger") + return redirect(url_for("clonezilla_backups")) + + f = request.files["backup_file"] + if not f.filename: + flash("No file selected.", "danger") + return redirect(url_for("clonezilla_backups")) + + filename = secure_filename(f.filename) + if not filename.lower().endswith(".zip"): + flash("Only .zip files are accepted.", "danger") + return redirect(url_for("clonezilla_backups")) + + os.makedirs(CLONEZILLA_SHARE, exist_ok=True) + dest = os.path.join(CLONEZILLA_SHARE, filename) + f.save(dest) + flash(f"Uploaded {filename} successfully.", "success") + return redirect(url_for("clonezilla_backups")) + + +@app.route("/backups/download/") +def clonezilla_download(filename): + filename = secure_filename(filename) + fpath = os.path.join(CLONEZILLA_SHARE, filename) + if not os.path.isfile(fpath): + flash(f"Backup not found: {filename}", "danger") + return redirect(url_for("clonezilla_backups")) + return send_file(fpath, as_attachment=True) + + +@app.route("/backups/delete/", methods=["POST"]) +def clonezilla_delete(filename): + filename = secure_filename(filename) + fpath = os.path.join(CLONEZILLA_SHARE, filename) + if os.path.isfile(fpath): + os.remove(fpath) + flash(f"Deleted {filename}.", "success") + else: + flash(f"Backup not found: {filename}", "danger") + return redirect(url_for("clonezilla_backups")) + + # --------------------------------------------------------------------------- # Routes — API # --------------------------------------------------------------------------- @@ -611,6 +688,17 @@ def api_save_unattend(image_type): return jsonify({"status": "ok", "path": xml_file}) +# --------------------------------------------------------------------------- +# Template filters +# --------------------------------------------------------------------------- + +@app.template_filter("timestamp_fmt") +def timestamp_fmt(ts): + """Format a Unix timestamp to a human-readable date string.""" + from datetime import datetime + return datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M") + + # --------------------------------------------------------------------------- # Context processor — make data available to all templates # --------------------------------------------------------------------------- diff --git a/webapp/static/favicon.ico b/webapp/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..67c7c4e587e6e939c36d505009b840a8792282f1 GIT binary patch literal 1950 zcmd^=`8(8$9>>399b+29kU7Q{#+iyFWT&y5nW;whC6v98Wu)O);#w|6V>wY~h=v?j zuKgH_WJ#tlXG9{)88NnzeGhl~JMQOs-tYJGdOx2ZKhN`dU~7Xr0g;0M0C0kUx3cG_ z)lY#1`7Y7#o5&B)U3;7bXq2guzz3mQSRxhx>axUtcnkAc*%xn51b`TI07y&$fPH?I zI1d2#&I7=r7XTPN1pwLb!dG@C03fhwd)3jJzi>5>#P|P)IXXHzK0ZD?JOqJ2KYsiW z5D}+pu zgTY`9hch)bg+wC7#Kg9?wv?5XM@B}{)6+LMHx(5XH#Rnghldjr6Im?Q=;$a0gIQl+ z|M20%+S=NuPoLD))z6$c!)CKPIy(CL`dV6A3=9n5aClo=o0gW=_wV1^+uOCZwO3bH zt*x!!zkiR#Vl6E#D=I4L>+4rmR*Z~{baZrRG@6EnhMu0Dq@-kKW@c4YRd;uHL_~zS zx%u+)vaGCZWMpJ+ZZ403xzkgIz)WX7os;cU@ zZ{Nnp$I)o?xpU{#)YME&O!W2jzkdD7U@$^MLk9*1dU|@Ao16Xo{M_B$y}iAYl9FO$ zW0_3m<;#~JKYmLh*4oG4F zJJE_#XC<#PCKo_e&Ac5b+&dYI**J^2T^>b7TBvEg0f@Bnincx~3l!h!S|#9U{siyfC?b5R`$ zM}26B8Zo;cZ6x#fT*j}P{a#(z>}ekC)PhJ`la3+8KAK8jp=w)zd{wtS-p6#m(0mvm zz8*)o0hTHp7A7>jN^XrR*mS>^vZKheB0cJbEGYH%I60yalTfc6D`_zi;Su8 zTvA@ejszKooHm8aMVYn`E6P7_jBBY?igmAeqT_;o zDJ<>{SuJdsrcde4)LBUH?&*b(C?Q!58rxVSV*ypuO380t#WrMu}S(@>P6|8yp3s#VgpTc4xzel zRM$|>AGc(YvQg?(kZ(70niDbf;AmZ*ZHAR-38Qn%{`(|(ryS_BoBxHKj&wi#iyTq= z++Ib}v!(fD&4zXByNn=*`gtaZU>B5g#5lEm;JK8>fhbs zgAHS?2n1{*2tMqaUMxh|l^RMA(Ec*=95?EoQpyu}lJi+FuH_Uh9xCfFl=FoqvA`^$ zqK}vVxY3aP=y(4=!h@oGweK=!;B3SDcN7WjQ33TIF9bzLVrY$7NS$Ya#+u{|@zi&J z&BHg2oQXF5A+&2!*RBVd?PYr{s`ncuOa?cE6cB&W`bg5Ngx*0TT;k1@Bsa-aD&2Q? zYtw5mIw0+S=+Zsy;(p~9mo8|UnU&4{$tEdea8l1lN*+27^sSEt3BF+mXEnK`1>dQ; zYQ7gZ$%5BtJQ>q{F@1c!R&6Zc$x2no90}@-nqtfzyCr}cdYEZxDR70KZ;@baV^wY8 GmGnP}`yS5# literal 0 HcmV?d00001 diff --git a/webapp/static/ge-aerospace-logo.svg b/webapp/static/ge-aerospace-logo.svg new file mode 100644 index 0000000..47d2953 --- /dev/null +++ b/webapp/static/ge-aerospace-logo.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/webapp/templates/backups.html b/webapp/templates/backups.html new file mode 100644 index 0000000..2abef12 --- /dev/null +++ b/webapp/templates/backups.html @@ -0,0 +1,131 @@ +{% extends "base.html" %} +{% block title %}Clonezilla Backups - PXE Server Manager{% endblock %} + +{% block content %} +
+

Clonezilla Backups

+ +
+ +
+
+ Machine Backups + {{ backups|length }} +
+
+ {% if backups %} + + + + + + + + + + + + {% for b in backups %} + + + + + + + + {% endfor %} + +
Machine #FilenameSizeLast ModifiedActions
{{ b.machine }}{{ b.filename }}{{ "%.1f"|format(b.size / 1073741824) }} GB{{ b.modified | timestamp_fmt }} + + + + +
+ {% else %} +
+ +

No backups found. Upload a Clonezilla backup .zip to get started.

+
+ {% endif %} +
+
+ +
+
+
Backup Naming Convention
+

+ Name backup files with the machine number (e.g., 6501.zip). + The Samba share \\pxe-server\clonezilla is also available on the network for direct Clonezilla save/restore operations. +

+
+
+ + + + + + +{% endblock %} + +{% block extra_scripts %} + +{% endblock %} diff --git a/webapp/templates/base.html b/webapp/templates/base.html index 7801a51..3ccf728 100644 --- a/webapp/templates/base.html +++ b/webapp/templates/base.html @@ -4,6 +4,7 @@ {% block title %}PXE Server Manager{% endblock %} +