mirror of
https://github.com/dockur/windows.git
synced 2026-08-24 07:39:00 +01:00
1693 lines
34 KiB
Bash
1693 lines
34 KiB
Bash
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
startWindows() {
|
|
|
|
parseVersion || {
|
|
error "Failed to parse the Windows version!"
|
|
exit 58
|
|
}
|
|
|
|
parseLanguage || {
|
|
error "Failed to parse the Windows language!"
|
|
exit 62
|
|
}
|
|
|
|
detectCustom || {
|
|
error "Failed to scan for custom installation media!"
|
|
exit 64
|
|
}
|
|
|
|
html "Starting $APP..."
|
|
|
|
if needsInstall; then
|
|
|
|
startInstall || exit $?
|
|
|
|
else
|
|
|
|
bootWindows || exit $?
|
|
return 0
|
|
|
|
fi
|
|
|
|
if ! hasImage "$ISO"; then
|
|
|
|
if ! downloadImage "$ISO" "$VERSION" "$LANGUAGE"; then
|
|
removeImage "$ISO" || :
|
|
exit 68
|
|
fi
|
|
|
|
fi
|
|
|
|
local boot="$BOOT"
|
|
local dir="$TMP/unpack"
|
|
local handled=0 extracted=0
|
|
|
|
selectWindowsImage "$ISO" "$dir" "$boot" || exit $?
|
|
(( handled )) && return 0
|
|
|
|
configureMachine "$ISO" "$dir" "$boot" || exit $?
|
|
(( handled )) && return 0
|
|
|
|
prepareWindowsImage "$ISO" "$dir" "$boot" || exit $?
|
|
(( handled )) && return 0
|
|
|
|
finishInstall "$BOOT" "N" "$boot" || exit 100
|
|
|
|
return 0
|
|
}
|
|
|
|
needsInstall() {
|
|
|
|
local file boot iso
|
|
|
|
if isCustomImage; then
|
|
iso="$BOOT"
|
|
else
|
|
file=$(getInstallFile) || exit $?
|
|
boot=$(getBootFile "$file") || exit $?
|
|
iso="$STORAGE/$boot"
|
|
fi
|
|
|
|
local previousBase
|
|
previousBase=$(readBase) || exit $?
|
|
|
|
if [ -n "$previousBase" ] && [[ "${STORAGE,,}/${previousBase,,}" != "${iso,,}" ]]; then
|
|
|
|
# Removing custom installation media does not invalidate an installation
|
|
# that has already completed successfully.
|
|
if [[ "${previousBase,,}" == "windows."* ]] &&
|
|
[[ "${iso,,}" != "${STORAGE,,}/windows."* ]] && hasCompletedInstall; then
|
|
info "Detected that your custom .iso file was removed, will be ignored."
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
fi
|
|
|
|
if [ -n "$previousBase" ] && hasSystemImage; then
|
|
return 1
|
|
fi
|
|
|
|
hasData && hasBootMarker && return 1
|
|
|
|
return 0
|
|
}
|
|
|
|
bootWindows() {
|
|
|
|
setBoot || return $?
|
|
cleanupTemp || return $?
|
|
|
|
local previousBase
|
|
previousBase=$(readBase) || return $?
|
|
|
|
cleanupSource "$previousBase" || return $?
|
|
selectBoot "$BOOT" "$previousBase" || return $?
|
|
|
|
if ! restoreMachineState; then
|
|
error "Failed to restore the saved machine state!"
|
|
return 66
|
|
fi
|
|
|
|
if ! restoreBootMode; then
|
|
error "Failed to restore the saved boot mode!"
|
|
return 66
|
|
fi
|
|
|
|
if ! restoreMachine; then
|
|
error "Failed to restore the saved machine type!"
|
|
return 66
|
|
fi
|
|
|
|
if ! reserveSambaPorts; then
|
|
error "Failed to reserve Samba ports!"
|
|
return 66
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
startInstall() {
|
|
|
|
setBoot || return $?
|
|
cleanupTemp || return $?
|
|
|
|
local previousBase
|
|
previousBase=$(readBase) || return $?
|
|
|
|
cleanupSource "$previousBase" || return $?
|
|
cleanupInstall "$BOOT" "$previousBase" || return $?
|
|
|
|
if ! makeDir "$TMP"; then
|
|
error "Failed to create directory \"$TMP\" !"
|
|
return 50
|
|
fi
|
|
|
|
if ! isCustomImage; then
|
|
|
|
if hasImage "$BOOT"; then
|
|
ISO="$TMP/$(basename "$BOOT")"
|
|
else
|
|
local file
|
|
file=$(getInstallFile) || return $?
|
|
ISO="$TMP/$file"
|
|
fi
|
|
|
|
fi
|
|
|
|
cleanupStorage "$BOOT" || return $?
|
|
|
|
if ! isCustomImage && ! isURL "$VERSION"; then
|
|
checkMemory "$VERSION" || return 67
|
|
setDiskMinimum "$VERSION" || return 67
|
|
fi
|
|
|
|
# Work from the temporary directory so the persistent source path can
|
|
# later contain either the preserved ISO or the rebuilt installation image.
|
|
if ! isCustomImage && hasImage "$BOOT"; then
|
|
if ! mv -f -- "$BOOT" "$ISO"; then
|
|
error "Failed to move ISO file from \"$BOOT\" to \"$ISO\" !"
|
|
return 50
|
|
fi
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
selectWindowsImage() {
|
|
|
|
local iso="$1"
|
|
local dir="$2"
|
|
local boot="$3"
|
|
|
|
XML=""
|
|
SYSTEM=""
|
|
FB="falling back to manual installation!"
|
|
|
|
normalizeDetected || :
|
|
|
|
if [ -n "$DETECTED" ]; then
|
|
|
|
if ! setImage; then
|
|
error "Failed to configure the detected Windows image!"
|
|
return 70
|
|
fi
|
|
|
|
if ! needsExtraction "$iso"; then
|
|
|
|
needsPreparation "$DETECTED" || return 0
|
|
|
|
fi
|
|
|
|
if ! extractImage "$iso" "$dir" "$VERSION"; then
|
|
error "Failed to extract the Windows installation image!"
|
|
removeImage "$iso" || :
|
|
return 72
|
|
fi
|
|
|
|
extracted=1
|
|
return 0
|
|
|
|
fi
|
|
|
|
# Inspect unknown bootable media directly before falling back to extraction.
|
|
if ! needsExtraction "$iso"; then
|
|
|
|
detectIsoImage "$iso" && return 0
|
|
|
|
local rc=$?
|
|
if (( rc != 1 )); then
|
|
error "Failed to inspect the Windows installation ISO!"
|
|
return 76
|
|
fi
|
|
|
|
elif [[ "${iso,,}" == *.esd ]]; then
|
|
|
|
detectESDImage "$iso" && return 0
|
|
error "Failed to inspect the Windows installation ESD!"
|
|
return 76
|
|
|
|
fi
|
|
|
|
if ! extractImage "$iso" "$dir" "$VERSION"; then
|
|
error "Failed to extract the Windows installation image!"
|
|
removeImage "$iso" || :
|
|
return 74
|
|
fi
|
|
|
|
extracted=1
|
|
|
|
detectImage "$dir" && return 0
|
|
|
|
local rc=$?
|
|
if (( rc != 1 )); then
|
|
error "Failed to detect the extracted Windows installation image!"
|
|
return 76
|
|
fi
|
|
|
|
skipUnattended "$dir" "$iso" "$boot" || {
|
|
error "Failed to fall back to manual installation!"
|
|
return 76
|
|
}
|
|
|
|
handled=1
|
|
return 0
|
|
}
|
|
|
|
configureMachine() {
|
|
|
|
local iso="$1"
|
|
local dir="$2"
|
|
local boot="$3"
|
|
|
|
local desc
|
|
desc=$(printVariant "$DETECTED" "$DETECTED") || return 78
|
|
|
|
if ! checkMemory "$DETECTED"; then
|
|
|
|
if ! isCustomImage; then
|
|
useOriginalImage "$iso" || {
|
|
error "Failed to preserve the original installation image!"
|
|
return 79
|
|
}
|
|
fi
|
|
|
|
return 79
|
|
fi
|
|
|
|
setDiskMinimum "$DETECTED" || return 79
|
|
|
|
if ! setMachine "$DETECTED" "$iso" "$dir" "$desc"; then
|
|
error "Failed to configure the virtual machine for $desc!"
|
|
return 80
|
|
fi
|
|
|
|
if ! restoreMachineState; then
|
|
error "Failed to restore the saved machine state!"
|
|
return 82
|
|
fi
|
|
|
|
if ! supportsUnattended "$DETECTED"; then
|
|
|
|
skipUnattended "$dir" "$iso" "$boot" "N" || {
|
|
error "Failed to fall back to manual installation!"
|
|
return 83
|
|
}
|
|
|
|
handled=1
|
|
return 0
|
|
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
prepareWindowsImage() {
|
|
|
|
local iso="$1"
|
|
local dir="$2"
|
|
local boot="$3"
|
|
|
|
if [ -n "$SYSTEM" ]; then
|
|
|
|
hasImage "$SYSTEM" || {
|
|
error "Failed to find the generated Windows system image!"
|
|
return 94
|
|
}
|
|
|
|
removeImage "$iso" || {
|
|
error "Failed to remove the source installation image!"
|
|
return 96
|
|
}
|
|
|
|
BOOT="$SYSTEM"
|
|
return 0
|
|
|
|
fi
|
|
|
|
# Keep all run-specific automation on the setup image for XML-capable media.
|
|
if supportsXML "$DETECTED"; then
|
|
|
|
if ! createOverlay "$XML" "$LANGUAGE" "$TMP/setup"; then
|
|
error "Failed to create the Windows setup overlay!"
|
|
return 84
|
|
fi
|
|
|
|
if ! createSetupImage "$TMP/setup" "$STORAGE/setup.img"; then
|
|
error "Failed to create the Windows setup image!"
|
|
return 86
|
|
fi
|
|
|
|
# Bootable ISOs can be reused unchanged with the generated setup image.
|
|
if (( ! extracted )); then
|
|
|
|
useOriginalImage "$iso" || {
|
|
error "Failed to preserve the original installation image!"
|
|
return 88
|
|
}
|
|
|
|
return 0
|
|
fi
|
|
|
|
fi
|
|
|
|
# Extracted modern sources and SIF-based legacy media require a clean rebuild.
|
|
if (( ! extracted )); then
|
|
|
|
if ! extractImage "$iso" "$dir" "$VERSION"; then
|
|
error "Failed to extract the Windows installation image!"
|
|
removeImage "$iso" || :
|
|
return 90
|
|
fi
|
|
|
|
fi
|
|
|
|
if ! prepareImage "$iso" "$dir"; then
|
|
error "Failed to prepare the Windows installation image!"
|
|
return 92
|
|
fi
|
|
|
|
removeImage "$iso" || {
|
|
error "Failed to remove the source installation image!"
|
|
return 96
|
|
}
|
|
|
|
buildImage "$dir" || {
|
|
error "Failed to build the Windows installation image!"
|
|
return 98
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
finishInstall() {
|
|
|
|
local iso="$1"
|
|
local aborted="$2"
|
|
local boot="$3"
|
|
|
|
local base secure=0
|
|
|
|
if ! hasImage "$iso"; then
|
|
error "Failed to find ISO file: $iso" && return 1
|
|
fi
|
|
|
|
if [[ "$iso" == "$STORAGE/"* ]]; then
|
|
if ! setOwner "$iso"; then
|
|
warn "failed to set the owner for \"$iso\" !"
|
|
fi
|
|
fi
|
|
|
|
local file="$STORAGE/windows.ver"
|
|
cp -f /etc/version "$file" || {
|
|
error "Failed to save the Windows installation version!"
|
|
return 1
|
|
}
|
|
|
|
if ! setOwner "$file"; then
|
|
warn "Failed to set the owner for \"$file\" !"
|
|
fi
|
|
|
|
if [[ "$boot" == "$STORAGE/"* ]]; then
|
|
if [[ "$aborted" != [Yy1]* ]] || ! isCustomImage; then
|
|
|
|
base=$(basename "$boot")
|
|
writeState "base" "$base" || {
|
|
error "Failed to save the Windows installation source!"
|
|
return 1
|
|
}
|
|
|
|
fi
|
|
fi
|
|
|
|
if isPlatform "x64"; then
|
|
if isLegacyBoot; then
|
|
|
|
writeState "mode" "$BOOT_MODE" || {
|
|
error "Failed to save the Windows boot mode!"
|
|
return 1
|
|
}
|
|
|
|
else
|
|
|
|
# Aborted Win11 installs boot without any answer file present,
|
|
# so enable Secure Boot and TPM to satisfy its hardware checks.
|
|
if enabled "$aborted" || enabled "$MANUAL"; then
|
|
[[ "${DETECTED,,}" == "win11"* ]] && secure=1
|
|
fi
|
|
|
|
if (( secure )); then
|
|
|
|
BOOT_MODE="windows_secure"
|
|
writeState "mode" "$BOOT_MODE" || {
|
|
error "Failed to save the Windows boot mode!"
|
|
return 1
|
|
}
|
|
|
|
fi
|
|
|
|
fi
|
|
fi
|
|
|
|
reserveSambaPorts || {
|
|
error "Failed to reserve Samba ports!"
|
|
return 1
|
|
}
|
|
|
|
if [ -n "$SYSTEM" ]; then
|
|
|
|
if [[ "$SYSTEM" == "$TMP/"* ]]; then
|
|
|
|
if ! mv -f -- "$SYSTEM" "$STORAGE/windows.img"; then
|
|
error "Failed to finalize the Windows system image!"
|
|
return 1
|
|
fi
|
|
|
|
BOOT="$STORAGE/windows.img"
|
|
|
|
else
|
|
|
|
if ! hasImage "$SYSTEM"; then
|
|
error "Failed to find the Windows system image!"
|
|
return 1
|
|
fi
|
|
|
|
writeState "system" "$SYSTEM" || {
|
|
error "Failed to save the Windows system image!"
|
|
return 1
|
|
}
|
|
|
|
BOOT="$SYSTEM"
|
|
fi
|
|
fi
|
|
|
|
if ! rm -rf -- "$TMP"; then
|
|
error "Failed to remove directory \"$TMP\" !"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
skipUnattended() {
|
|
|
|
local dir="$1"
|
|
local iso="$2"
|
|
local boot="$3"
|
|
local aborted="${4:-Y}"
|
|
|
|
local efi efi32 efi64
|
|
|
|
# Standalone ESD files and nested archives are not directly bootable media,
|
|
# so they cannot use the manual-install fallback.
|
|
if needsExtraction "$iso"; then
|
|
error "Failed to boot \"$iso\" because it is not a directly bootable ISO image!"
|
|
return 1
|
|
fi
|
|
|
|
# When automatic preparation fails, inspect extracted media to determine
|
|
# whether it can still be booted manually using legacy firmware.
|
|
if enabled "$aborted" && isPlatform "x64" && [ -d "$dir" ]; then
|
|
|
|
efi=$(find "$dir" -maxdepth 1 -type d -iname efi -print -quit) || return 1
|
|
efi32=$(find "$dir" -maxdepth 3 -type f -ipath '*/efi/boot/bootia32.efi' -print -quit) || return 1
|
|
efi64=$(find "$dir" -maxdepth 3 -type f -ipath '*/efi/boot/bootx64.efi' -print -quit) || return 1
|
|
|
|
if [ -z "$efi" ] || { [ -n "$efi32" ] && [ -z "$efi64" ]; }; then
|
|
|
|
writeState "mode" "windows_legacy" || return 1
|
|
restoreBootMode || return 1
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
# Preserve custom media in place. Downloaded or reused media must be moved
|
|
# back to persistent storage before the manual fallback is started.
|
|
useOriginalImage "$iso" || return 1
|
|
|
|
finishInstall "$BOOT" "$aborted" "$boot" && return 0
|
|
return 1
|
|
}
|
|
|
|
readBase() {
|
|
|
|
local previousBase
|
|
if ! previousBase=$(readState "base"); then
|
|
error "Failed to read the previous installation state!"
|
|
return 50
|
|
fi
|
|
|
|
# Older releases stored the original download name in windows.base. Current
|
|
# releases store an ISO source identity, so migrate legacy state once.
|
|
if [ -n "$previousBase" ] && [[ "${previousBase,,}" != *.iso ]]; then
|
|
|
|
if isCompressed "$previousBase" || [[ "${previousBase,,}" == *.esd ]]; then
|
|
previousBase="${previousBase%.*}"
|
|
fi
|
|
|
|
[ -n "$previousBase" ] || previousBase="download"
|
|
previousBase+=".iso"
|
|
|
|
if ! writeState "base" "$previousBase"; then
|
|
error "Failed to migrate the previous installation state!"
|
|
return 50
|
|
fi
|
|
|
|
fi
|
|
|
|
printf '%s\n' "$previousBase"
|
|
return 0
|
|
}
|
|
|
|
setBoot() {
|
|
|
|
local file boot
|
|
|
|
if ! isCustomImage; then
|
|
|
|
file=$(getInstallFile) || return $?
|
|
boot=$(getBootFile "$file") || return $?
|
|
|
|
BOOT="$STORAGE/$boot"
|
|
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
selectBoot() {
|
|
|
|
local iso="$1"
|
|
local previousBase="$2"
|
|
|
|
[ -n "$previousBase" ] || return 0
|
|
[[ "${STORAGE,,}/${previousBase,,}" == "${iso,,}" ]] || return 0
|
|
|
|
local system
|
|
if system=$(getSystemImage); then
|
|
BOOT="$system"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
getInstallFile() {
|
|
|
|
local file="${VERSION//\//}.iso"
|
|
|
|
if isURL "$VERSION"; then
|
|
|
|
file=$(basename "${VERSION%%[\?#]*}")
|
|
printf -v file '%b' "${file//%/\\x}"
|
|
file="${file//[!A-Za-z0-9._-]/_}"
|
|
|
|
else
|
|
|
|
local language
|
|
if ! language=$(getLanguage "$LANGUAGE" "culture"); then
|
|
error "Failed to determine the Windows language!"
|
|
return 62
|
|
fi
|
|
|
|
language="${language%%-*}"
|
|
|
|
if [ -n "$language" ] && [[ "${language,,}" != "en" ]]; then
|
|
file="${VERSION//\//}_${language,,}.iso"
|
|
fi
|
|
|
|
fi
|
|
|
|
printf '%s\n' "$file"
|
|
return 0
|
|
}
|
|
|
|
getBootFile() {
|
|
|
|
local file="$1"
|
|
local boot="$file"
|
|
|
|
if isURL "$VERSION"; then
|
|
|
|
if isCompressed "$VERSION" || [[ "${boot,,}" == *.esd ]]; then
|
|
boot="${boot%.*}"
|
|
fi
|
|
|
|
[ -n "$boot" ] || boot="download"
|
|
[[ "${boot,,}" == *.iso ]] || boot+=".iso"
|
|
|
|
case "${boot,,}" in
|
|
"windows."* )
|
|
error "The download filename \"$file\" uses the reserved \"windows.*\" namespace!"
|
|
return 58 ;;
|
|
esac
|
|
|
|
fi
|
|
|
|
printf '%s\n' "$boot"
|
|
return 0
|
|
}
|
|
|
|
cleanupTemp() {
|
|
|
|
TMP="$STORAGE/tmp"
|
|
|
|
if ! rm -rf -- "$TMP"; then
|
|
error "Failed to remove directory \"$TMP\" !"
|
|
return 50
|
|
fi
|
|
|
|
local setup="$STORAGE/setup.img"
|
|
|
|
if ! rm -f -- "$setup" "${setup}.tmp"; then
|
|
error "Failed to remove setup image \"$setup\" !"
|
|
return 50
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
cleanupSource() {
|
|
|
|
local previousBase="$1"
|
|
|
|
[ -n "$previousBase" ] || return 0
|
|
[[ "${previousBase,,}" == "windows."* ]] || return 0
|
|
|
|
hasCompletedInstall || return 0
|
|
|
|
# Older releases may have left a rebuilt custom ISO at its synthetic source
|
|
# identity. A completed installation no longer needs that installation media.
|
|
if ! rm -f -- "$STORAGE/$previousBase"; then
|
|
error "Failed to remove obsolete ISO file \"$STORAGE/$previousBase\" !"
|
|
return 50
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
cleanupStorage() {
|
|
|
|
local boot="$1"
|
|
|
|
# Keep existing media at its persistent path until all storage cleanup has
|
|
# completed successfully, so a later failure cannot strand it under $TMP.
|
|
if isCustomImage || ! hasImage "$boot"; then
|
|
if ! rm -f -- "$boot"; then
|
|
error "Failed to remove obsolete ISO file \"$boot\" !"
|
|
return 50
|
|
fi
|
|
fi
|
|
|
|
if ! find "$STORAGE" -maxdepth 1 -type f -iname 'data.*' -not -iname '*.iso' -delete; then
|
|
error "Failed to remove obsolete disk files from \"$STORAGE\" !"
|
|
return 50
|
|
fi
|
|
|
|
if ! find "$STORAGE" -maxdepth 1 -type f -iname 'windows.*' -not -iname '*.iso' -delete; then
|
|
error "Failed to remove obsolete Windows files from \"$STORAGE\" !"
|
|
return 50
|
|
fi
|
|
|
|
if ! find "$STORAGE" -maxdepth 1 -type f \( -iname '*.rom' -or -iname '*.vars' \) -delete; then
|
|
error "Failed to remove obsolete firmware files from \"$STORAGE\" !"
|
|
return 50
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
cleanupInstall() {
|
|
|
|
local iso="$1"
|
|
local previousBase="$2"
|
|
|
|
if [ -n "$previousBase" ] && [[ "${STORAGE,,}/${previousBase,,}" != "${iso,,}" ]]; then
|
|
|
|
# A changed source invalidates an unfinished installation. Back up an
|
|
# existing installation, but discard stale media when no disk exists yet.
|
|
if ! hasInstalledDisk; then
|
|
|
|
if ! rm -f -- "$STORAGE/$previousBase"; then
|
|
error "Failed to remove ISO file \"$STORAGE/$previousBase\" !"
|
|
return 50
|
|
fi
|
|
|
|
return 0
|
|
|
|
fi
|
|
|
|
local method
|
|
|
|
if [[ "${iso,,}" == "${STORAGE,,}/windows."* ]]; then
|
|
method="your custom .iso file was changed"
|
|
elif [[ "${previousBase,,}" != "windows."* ]]; then
|
|
method="the VERSION variable was changed"
|
|
else
|
|
method="your custom .iso file was removed"
|
|
|
|
if hasCompletedInstall; then
|
|
info "Detected that $method, will be ignored."
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
if ! hasCompletedInstall && ! disabled "${SHUTDOWN:-}"; then
|
|
discardPrevious "$STORAGE/$previousBase" || return 50
|
|
return 0
|
|
fi
|
|
|
|
info "Detected that $method, a backup of your previous installation will be saved..."
|
|
|
|
if ! backupPrevious "$STORAGE/$previousBase"; then
|
|
warn "the backup was incomplete, continuing with installation..."
|
|
fi
|
|
|
|
return 0
|
|
fi
|
|
|
|
if [ -z "$previousBase" ] && hasInstalledDisk; then
|
|
|
|
if ! hasCompletedInstall && ! disabled "${SHUTDOWN:-}"; then
|
|
discardPrevious "" || return 50
|
|
else
|
|
if ! backupPrevious ""; then
|
|
warn "the backup was incomplete, continuing with installation..."
|
|
fi
|
|
fi
|
|
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
discardPrevious() {
|
|
|
|
local iso="$1"
|
|
|
|
if [ -n "$iso" ] && [ -f "$iso" ]; then
|
|
if ! rm -f -- "$iso"; then
|
|
error "Failed to remove ISO file \"$iso\" !"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
if ! find "$STORAGE" -maxdepth 1 -type f \
|
|
\( -iname 'data.*' -or -iname 'windows.*' -or -iname '*.rom' -or -iname '*.vars' \) \
|
|
-not -iname '*.iso' -delete; then
|
|
error "Failed to remove unfinished installation files from \"$STORAGE\" !"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
backupPrevious () {
|
|
|
|
local iso="$1"
|
|
|
|
local count=1
|
|
local name="unknown"
|
|
local root="$STORAGE/backups"
|
|
local failed="" file previous
|
|
|
|
previous=$(readState "base") || return 1
|
|
[ -n "$previous" ] && name="${previous%.*}"
|
|
|
|
if ! makeDir "$root"; then
|
|
error "Failed to create directory \"$root\" !"
|
|
return 1
|
|
fi
|
|
|
|
local folder="$name"
|
|
local dir="$root/$folder"
|
|
|
|
while [ -d "$dir" ]; do
|
|
(( count++ ))
|
|
folder="${name}.${count}"
|
|
dir="$root/$folder"
|
|
done
|
|
|
|
if ! makeDir "$dir"; then
|
|
error "Failed to create directory \"$dir\" !"
|
|
return 1
|
|
fi
|
|
|
|
if [ -f "$iso" ]; then
|
|
if ! mv -f -- "$iso" "$dir/"; then
|
|
error "Failed to move \"$iso\" to \"$dir\"."
|
|
failed="Y"
|
|
fi
|
|
fi
|
|
|
|
while IFS= read -r -d '' file; do
|
|
if ! mv -n -- "$file" "$dir/"; then
|
|
error "Failed to move \"$file\" to \"$dir\"."
|
|
failed="Y"
|
|
fi
|
|
done < <(
|
|
find "$STORAGE" -maxdepth 1 -type f \
|
|
\( -iname 'data.*' -or -iname 'windows.*' -or -iname '*.rom' -or -iname '*.vars' \) \
|
|
-not -iname '*.iso' -print0
|
|
)
|
|
|
|
# Wait for the process-substitution find command so enumeration failures are
|
|
# detected rather than being mistaken for a successful backup.
|
|
local find_pid=$!
|
|
|
|
if ! wait "$find_pid"; then
|
|
error "Failed to enumerate files in \"$STORAGE\"."
|
|
failed="Y"
|
|
fi
|
|
|
|
rmdir "$dir" 2>/dev/null || :
|
|
rmdir "$root" 2>/dev/null || :
|
|
|
|
[ -n "$failed" ] && return 1
|
|
|
|
return 0
|
|
}
|
|
|
|
detectCustom() {
|
|
|
|
CUSTOM=""
|
|
|
|
findFile "custom.iso" || return 1
|
|
|
|
if isCustomImage; then
|
|
DETECTED=""
|
|
return 0
|
|
fi
|
|
|
|
findFile "boot.iso" || return 1
|
|
|
|
if isCustomImage; then
|
|
DETECTED=""
|
|
return 0
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
findFile() {
|
|
|
|
local fname="$1"
|
|
local dir file base
|
|
|
|
dir=$(find / -maxdepth 1 -type d -iname "$fname" -print -quit) || return 1
|
|
|
|
if [ ! -d "$dir" ]; then
|
|
dir=$(find "$STORAGE" -maxdepth 1 -type d -iname "$fname" -print -quit) || return 1
|
|
fi
|
|
|
|
if [ -d "$dir" ]; then
|
|
if ! hasSystemImage && { ! hasDisk || ! hasBootMarker; }; then
|
|
error "The bind $dir maps to a file that does not exist!" && return 1
|
|
fi
|
|
fi
|
|
|
|
file=$(find / -maxdepth 1 -type f -iname "$fname" -print -quit) || return 1
|
|
|
|
if [ ! -s "$file" ]; then
|
|
file=$(find "$STORAGE" -maxdepth 1 -type f -iname "$fname" -print -quit) || return 1
|
|
fi
|
|
|
|
if [ ! -s "$file" ] && ! isURL "$VERSION"; then
|
|
base=$(basename "$VERSION")
|
|
file="$STORAGE/$base"
|
|
fi
|
|
|
|
if ! hasImage "$file"; then
|
|
return 0
|
|
fi
|
|
|
|
local size
|
|
size=$(stat -c%s "$file") || return 1
|
|
|
|
if [ -z "$size" ] || [[ "$size" == "0" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
ISO="$file"
|
|
CUSTOM="$file"
|
|
|
|
# Encode the custom ISO size in a synthetic source identity so replacing a
|
|
# bind-mounted ISO is detected as a different installation source.
|
|
BOOT="$STORAGE/windows.$size.iso"
|
|
|
|
return 0
|
|
}
|
|
|
|
isCustomImage() {
|
|
|
|
[ -n "${CUSTOM:-}" ]
|
|
}
|
|
|
|
isURL() {
|
|
|
|
local value="$1"
|
|
|
|
[[ "${value,,}" == "http"* ]]
|
|
}
|
|
|
|
isPlatform() {
|
|
|
|
local platform="$1"
|
|
|
|
[[ "${PLATFORM,,}" == "${platform,,}" ]]
|
|
}
|
|
|
|
isLegacyBoot() {
|
|
|
|
[[ "${BOOT_MODE,,}" == "windows_legacy" ]]
|
|
}
|
|
|
|
hasBootMarker() {
|
|
|
|
[ -f "$STORAGE/windows.boot" ]
|
|
}
|
|
|
|
hasImage() {
|
|
|
|
local iso="$1"
|
|
|
|
[ -f "$iso" ] && [ -s "$iso" ]
|
|
}
|
|
|
|
getSystemImage() {
|
|
|
|
local image
|
|
|
|
image=$(readState "system") || return 1
|
|
|
|
if [ -n "$image" ]; then
|
|
# Older development builds stored only the basename in windows.system.
|
|
[[ "$image" == /* ]] || image="$STORAGE/$image"
|
|
hasImage "$image" || return 1
|
|
|
|
echo "$image"
|
|
return 0
|
|
fi
|
|
|
|
image="$STORAGE/windows.img"
|
|
hasImage "$image" || return 1
|
|
|
|
echo "$image"
|
|
return 0
|
|
}
|
|
|
|
hasSystemImage() {
|
|
|
|
getSystemImage >/dev/null
|
|
}
|
|
|
|
hasInstalledDisk() {
|
|
|
|
hasData || hasSystemImage
|
|
}
|
|
|
|
hasCompletedInstall() {
|
|
|
|
hasSystemImage && return 0
|
|
hasData && hasBootMarker
|
|
}
|
|
|
|
needsExtraction() {
|
|
|
|
local iso="$1"
|
|
|
|
enabled "${UNPACK:-}" || [[ "${iso,,}" == *.esd ]]
|
|
}
|
|
|
|
needsPreparation() {
|
|
|
|
local id="$1"
|
|
|
|
supportsSIF "$id" && return 0
|
|
|
|
case "${id,,}" in
|
|
"win9"* )
|
|
supportsUnattended "$id" && return 0 ;;
|
|
esac
|
|
|
|
return 1
|
|
}
|
|
|
|
useOriginalImage() {
|
|
|
|
local iso="$1"
|
|
|
|
if isCustomImage; then
|
|
BOOT="$iso"
|
|
elif [[ "$iso" != "$BOOT" ]]; then
|
|
if ! mv -f -- "$iso" "$BOOT"; then
|
|
error "Failed to move ISO file: $iso"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# Keep SYSTEM pointing at the same image when preserving it changes its path.
|
|
if [[ "${SYSTEM:-}" == "$iso" ]]; then
|
|
SYSTEM="$BOOT"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
removeImage() {
|
|
|
|
local iso="$1"
|
|
|
|
isCustomImage && return 0
|
|
|
|
if ! rm -f -- "$iso" 2>/dev/null; then
|
|
warn "failed to remove image \"$iso\"!"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
normalizeDetected() {
|
|
|
|
# Known catalog versions already provide the required image metadata.
|
|
if [ -z "$DETECTED" ] && ! isCustomImage && ! isURL "$VERSION"; then
|
|
DETECTED="$VERSION"
|
|
fi
|
|
|
|
DETECTED="${DETECTED/-enterprise-iot/-iot}"
|
|
DETECTED="${DETECTED/-enterprise-ltsc/-ltsc}"
|
|
|
|
return 0
|
|
}
|
|
|
|
setImage() {
|
|
|
|
local rc=0
|
|
|
|
supportsXML "${DETECTED,,}" || return 0
|
|
|
|
setXML "" || rc=$?
|
|
|
|
if (( rc == 0 )); then
|
|
return 0
|
|
fi
|
|
|
|
enabled "$MANUAL" && return 0
|
|
|
|
# Only a genuinely missing answer file may fall back to manual setup.
|
|
(( rc == 1 )) || return "$rc"
|
|
|
|
MANUAL="Y"
|
|
|
|
local desc
|
|
desc=$(printEdition "$DETECTED" "this version") || return 1
|
|
|
|
warn "the answer file for $desc was not found ($DETECTED.xml), $FB."
|
|
return 0
|
|
}
|
|
|
|
detectImage() {
|
|
|
|
local dir="$1"
|
|
local desc rc
|
|
|
|
info "Detecting version from ISO image..."
|
|
|
|
# Marker-based legacy detection must run before looking for a WIM.
|
|
if detectLegacy "$dir"; then
|
|
|
|
desc=$(printEdition "$DETECTED" "$DETECTED" "Y") || return 2
|
|
|
|
info "Detected: $desc"
|
|
return 0
|
|
|
|
else
|
|
rc=$?
|
|
(( rc == 1 )) || return "$rc"
|
|
fi
|
|
|
|
if detectReactOS "$dir"; then
|
|
|
|
desc=$(printEdition "$DETECTED" "$DETECTED" "Y") || return 2
|
|
|
|
info "Detected: $desc"
|
|
return 0
|
|
|
|
else
|
|
rc=$?
|
|
(( rc == 1 )) || return "$rc"
|
|
fi
|
|
|
|
local wim
|
|
wim=$(findImage "$dir") || return $?
|
|
|
|
local image_info
|
|
image_info=$(readImageInfo "$wim") || return $?
|
|
|
|
detectImageInfo "$image_info" || {
|
|
error "Failed to process the Windows image metadata!"
|
|
return 2
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
prepareImage() {
|
|
|
|
local iso="$1"
|
|
local dir="$2"
|
|
|
|
local desc missing
|
|
desc=$(printVariant "$DETECTED" "$DETECTED")
|
|
|
|
# Use the standard Windows BIOS boot image unless legacy preparation
|
|
# already selected a media-specific El Torito image.
|
|
ETFS="${ETFS:-boot/etfsboot.com}"
|
|
|
|
# Legacy rebuilt media must retain the source ISO's El Torito boot-load size.
|
|
if isLegacyBoot; then
|
|
|
|
getBootLoadSize "$iso" "$dir" "$desc" || return 1
|
|
|
|
fi
|
|
|
|
supportsXML "$DETECTED" || return 0
|
|
|
|
if isLegacyBoot; then
|
|
|
|
extractBootImage "$iso" "$dir" "$desc" && return 0
|
|
|
|
error "Failed to extract boot image from ISO image \"${iso}\"!"
|
|
return 1
|
|
fi
|
|
|
|
EFISYS="efi/microsoft/boot/efisys_noprompt.bin"
|
|
|
|
# A modern rebuilt ISO requires both its BIOS and no-prompt UEFI boot images.
|
|
[ -f "$dir/$ETFS" ] && [ -s "$dir/$ETFS" ] &&
|
|
[ -f "$dir/$EFISYS" ] && [ -s "$dir/$EFISYS" ] && return 0
|
|
|
|
missing=$(basename "$dir/$EFISYS")
|
|
if [ ! -f "$dir/$ETFS" ] || [ ! -s "$dir/$ETFS" ]; then
|
|
missing=$(basename "$dir/$ETFS")
|
|
fi
|
|
|
|
error "Failed to locate file \"${missing,,}\" in ISO image!"
|
|
return 1
|
|
}
|
|
|
|
createOverlay() {
|
|
|
|
local asset="$1"
|
|
local language="$2"
|
|
local stage="$3"
|
|
|
|
local msg="Creating overlay image..."
|
|
info "$msg" && html "$msg"
|
|
|
|
if ! rm -rf -- "$stage"; then
|
|
error "Failed to remove previous image files!"
|
|
return 1
|
|
fi
|
|
|
|
if ! mkdir -p "$stage"; then
|
|
error "Failed to create image staging directory!"
|
|
return 1
|
|
fi
|
|
|
|
if ! addDrivers "$stage" "$DETECTED"; then
|
|
error "Failed to include Windows drivers!"
|
|
return 1
|
|
fi
|
|
|
|
if ! addFolder "$stage" "overlay"; then
|
|
error "Failed to include OEM folder!"
|
|
return 1
|
|
fi
|
|
|
|
addAnswerFile "$asset" "$language" "$stage" || {
|
|
error "Failed to include the Windows answer file!"
|
|
return 1
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
addFolder() {
|
|
|
|
local src="$1"
|
|
local mode="${2:-copy}"
|
|
|
|
local file="" source="" folder
|
|
local dest="$src/\$OEM\$/\$1/OEM"
|
|
local install="$src/.overlay-install.bat"
|
|
|
|
if [ "$mode" = "win9x" ]; then
|
|
dest="$src/OEM"
|
|
fi
|
|
|
|
folder=$(getOemFolder) || return 1
|
|
|
|
[ -z "$folder" ] && [ -z "$COMMAND" ] && return 0
|
|
|
|
local msg="Adding OEM files to image..."
|
|
info "$msg" && html "$msg"
|
|
|
|
# Setup-image mode cannot modify the original ISO, so create a temporary
|
|
# writable copy of install.bat for the overlay image.
|
|
if [ "$mode" = "overlay" ]; then
|
|
|
|
rm -f -- "$install" || return 1
|
|
|
|
if [ -n "$folder" ]; then
|
|
|
|
source=$(find -L "$folder" -maxdepth 1 -type f -iname install.bat -print -quit) || return 1
|
|
|
|
if [ -n "$source" ]; then
|
|
|
|
if ! cp -L -- "$source" "$install"; then
|
|
error "Failed to create a writable copy of $source!"
|
|
return 1
|
|
fi
|
|
|
|
file="$install"
|
|
fi
|
|
|
|
fi
|
|
|
|
else
|
|
|
|
mkdir -p "$dest" || return 1
|
|
|
|
if [ -n "$folder" ]; then
|
|
cp -Lr "$folder/." "$dest" || return 1
|
|
fi
|
|
|
|
file=$(find "$dest" -maxdepth 1 -type f -iname install.bat -print -quit) || return 1
|
|
|
|
fi
|
|
|
|
if [ -s "$file" ]; then
|
|
normalizeBatch "$file" || return 1
|
|
fi
|
|
|
|
if [ -n "$COMMAND" ]; then
|
|
|
|
if [ -z "$file" ]; then
|
|
if [ "$mode" = "overlay" ]; then
|
|
file="$install"
|
|
else
|
|
file="$dest/install.bat"
|
|
fi
|
|
fi
|
|
|
|
if [ -s "$file" ]; then
|
|
printf '\n' >> "$file" || return 1
|
|
fi
|
|
|
|
printf '%s\n' "$COMMAND" >> "$file" || return 1
|
|
|
|
fi
|
|
|
|
if [ -s "$file" ]; then
|
|
|
|
if ! unix2dos -q "$file"; then
|
|
error "Failed to convert $file to DOS format!"
|
|
return 1
|
|
fi
|
|
|
|
checkBatch "$file"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
getOemFolder() {
|
|
|
|
local folder="/oem"
|
|
|
|
[ ! -d "$folder" ] && folder="/OEM"
|
|
[ ! -d "$folder" ] && folder="$STORAGE/oem"
|
|
[ ! -d "$folder" ] && folder="$STORAGE/OEM"
|
|
[ -d "$folder" ] && echo "$folder"
|
|
|
|
return 0
|
|
}
|
|
|
|
addDrivers() {
|
|
|
|
local stage="$1"
|
|
local version="$2"
|
|
|
|
local drivers="$stage/drivers"
|
|
local msg="Windows version and architecture are unknown; cannot select drivers!"
|
|
|
|
rm -rf "$drivers" || return 1
|
|
mkdir -p "$drivers" || return 1
|
|
|
|
info "Adding drivers to image..."
|
|
|
|
if [ -z "$version" ]; then
|
|
|
|
if [ -z "${IMAGE_PLATFORM:-}" ]; then
|
|
error "$msg" && return 1
|
|
fi
|
|
|
|
case "${IMAGE_PLATFORM,,}" in
|
|
"x86" ) version="win7x86" ;;
|
|
"x64" ) version="win11x64" ;;
|
|
"arm64" ) version="win11arm64" ;;
|
|
* ) error "$msg" && return 1 ;;
|
|
esac
|
|
|
|
local desc
|
|
desc=$(printVersion "$version" "") || return 1
|
|
|
|
warn "Windows version unknown, falling back to $desc drivers..."
|
|
fi
|
|
|
|
if ! bsdtar -xf /var/drivers.txz -C "$drivers"; then
|
|
error "Failed to extract drivers from archive!" && return 1
|
|
fi
|
|
|
|
local target="\$WinPEDriver\$"
|
|
local dest="$drivers/$target"
|
|
|
|
mkdir -p "$dest" || return 1
|
|
|
|
selectDrivers "$version" "$drivers" "$target" || return 1
|
|
|
|
local dst="$stage/\$OEM\$/\$\$/Drivers"
|
|
mkdir -p "$dst" || return 1
|
|
cp -Lr "$dest/." "$dst" || return 1
|
|
|
|
# Install the VirtIO display driver explicitly from SetupComplete.cmd so it
|
|
# cannot disrupt Windows Setup by loading through the WinPE driver path.
|
|
if ! isLegacy "$version"; then
|
|
rm -rf "$dest/viogpudo" || return 1
|
|
fi
|
|
|
|
local winpe="$stage/$target"
|
|
rm -rf "$winpe" || return 1
|
|
mkdir -p "$winpe" || return 1
|
|
cp -Lr "$dest/." "$winpe" || return 1
|
|
|
|
rm -rf "$drivers" || return 1
|
|
|
|
return 0
|
|
}
|
|
|
|
selectDrivers() {
|
|
|
|
local version="$1"
|
|
local drivers="$2"
|
|
local target="$3"
|
|
|
|
local driver_list=(
|
|
qxl
|
|
viofs
|
|
sriov
|
|
smbus
|
|
qxldod
|
|
viorng
|
|
viostor
|
|
viomem
|
|
NetKVM
|
|
Balloon
|
|
vioscsi
|
|
pvpanic
|
|
vioinput
|
|
viogpudo
|
|
vioserial
|
|
qemupciserial
|
|
)
|
|
|
|
local driver
|
|
|
|
for driver in "${driver_list[@]}"; do
|
|
addDriver "$version" "$drivers" "$target" "$driver" || return 1
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
addDriver() {
|
|
|
|
local id="$1"
|
|
local path="$2"
|
|
local target="$3"
|
|
local driver="$4"
|
|
|
|
local folder desc
|
|
|
|
if [ -z "$id" ]; then
|
|
warn "no Windows version specified for \"$driver\" driver!"
|
|
return 1
|
|
fi
|
|
|
|
if ! folder=$(getDriverFolder "$id"); then
|
|
folder=""
|
|
fi
|
|
|
|
if [ -z "$folder" ]; then
|
|
|
|
desc=$(printVersion "$id" "$id")
|
|
|
|
if [[ "${id,,}" == *"x86"* ]]; then
|
|
warn "no \"$driver\" driver available for the 32-bit version of \"$desc\" !"
|
|
else
|
|
warn "no \"$driver\" driver available for \"$desc\" !"
|
|
fi
|
|
|
|
return 1
|
|
fi
|
|
|
|
[ -d "$path/$driver/$folder" ] || return 0
|
|
|
|
case "${id,,}" in
|
|
"winvista"* )
|
|
[[ "${driver,,}" == "viorng" ]] && return 0 ;;
|
|
esac
|
|
|
|
local dest="$path/$target/$driver"
|
|
|
|
mkdir -p "$dest" || return 1
|
|
cp -Lr "$path/$driver/$folder/." "$dest" || return 1
|
|
|
|
return 0
|
|
}
|
|
|
|
normalizeBatch() {
|
|
|
|
local file="$1"
|
|
local bom tmp encoding
|
|
|
|
[ ! -s "$file" ] && return 0
|
|
|
|
bom=$(od -An -N2 -tx1 "$file" | tr -d ' \n') || return 1
|
|
|
|
# Convert only BOM-marked UTF-16 files; unmarked ANSI and UTF-8 batch files
|
|
# are deliberately left unchanged.
|
|
case "$bom" in
|
|
"fffe" ) encoding="UTF-16LE" ;;
|
|
"feff" ) encoding="UTF-16BE" ;;
|
|
* ) return 0 ;;
|
|
esac
|
|
|
|
if ! tmp=$(mktemp "${file}.XXXXXX"); then
|
|
error "Failed to create temporary batch file!"
|
|
return 1
|
|
fi
|
|
|
|
if ! tail -c +3 "$file" | iconv -f "$encoding" -t UTF-8 > "$tmp"; then
|
|
rm -f "$tmp"
|
|
error "Failed to convert $file from $encoding to UTF-8!"
|
|
return 1
|
|
fi
|
|
|
|
if ! chmod --reference="$file" "$tmp" || ! mv -f "$tmp" "$file"; then
|
|
rm -f "$tmp"
|
|
error "Failed to replace batch file: $file"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
checkBatch() {
|
|
|
|
local file="$1"
|
|
|
|
local tmp output
|
|
local matches line
|
|
local enabled_rules
|
|
|
|
[ -s "$file" ] || return 0
|
|
|
|
if ! tmp=$(mktemp -d /tmp/blinter.XXXXXX); then
|
|
warn "failed to create temporary Blinter directory."
|
|
return 0
|
|
fi
|
|
|
|
local source="your install.bat file"
|
|
[ -n "${COMMAND:-}" ] && source="your COMMAND variable"
|
|
|
|
# Only check rules that indicate likely execution or behavioural failures.
|
|
enabled_rules="E001,E002,E003,E004,E005,E006,E007,E008"
|
|
enabled_rules+=",E009,E010,E011,E012,E013,E014,E015,E016"
|
|
enabled_rules+=",E017,E018,E019,E020,E021,E022,E023,E024"
|
|
enabled_rules+=",E025,E027,E028,E029,E030,E031,E032,E033,E034"
|
|
enabled_rules+=",W004,W005,W013,W017,W021,W022,W034,W038,W040"
|
|
|
|
if enabled "$DEBUG"; then
|
|
if LC_ALL=C grep -Pq '[^\x09\x0D\x20-\x7E]' "$file"; then
|
|
warn "non-ASCII characters were detected in $source and may not execute correctly in Windows Command Prompt."
|
|
fi
|
|
fi
|
|
|
|
cat > "$tmp/blinter.ini" <<EOC
|
|
[general]
|
|
min_severity = warning
|
|
show_summary = false
|
|
|
|
[rules]
|
|
enabled_rules = $enabled_rules
|
|
EOC
|
|
|
|
output=$(
|
|
cd "$tmp"
|
|
python3 -m blinter "$file" 2>&1 || true
|
|
)
|
|
|
|
# Remove header.
|
|
output=$(
|
|
awk '
|
|
/^DETAILED ISSUES:/ {
|
|
found = 1
|
|
next
|
|
}
|
|
|
|
found && !started {
|
|
if (/^-+$/) {
|
|
started = 1
|
|
}
|
|
next
|
|
}
|
|
|
|
started {
|
|
print
|
|
}
|
|
' <<< "$output"
|
|
)
|
|
|
|
output="${output#"${output%%[!$'\r\n ']*}"}"
|
|
output="${output%"${output##*[!$'\r\n ']}"}"
|
|
|
|
if grep -Eq '^(ERROR|WARNING|SECURITY) LEVEL ISSUES:$' <<< "$output"; then
|
|
|
|
warn "possible issues were detected in $source:"
|
|
printf '\n%s\n\n' "$output" >&2
|
|
fi
|
|
|
|
rm -rf "$tmp" || true
|
|
|
|
reportBatchMatches \
|
|
"$file" \
|
|
"$source" \
|
|
'(?<!\\)\\host[.]lan[\\]' \
|
|
"invalid single-backslash UNC path detected" \
|
|
'Use "\\host.lan\Data\..." instead of "\host.lan\Data\...".'
|
|
|
|
reportBatchMatches \
|
|
"$file" \
|
|
"$source" \
|
|
'(?<![\\[:alnum:]._-])host[.]lan[\\]' \
|
|
"UNC path without leading backslashes detected" \
|
|
'Use "\\host.lan\Data\..." instead of "host.lan\Data\...".'
|
|
|
|
reportBatchMatches \
|
|
"$file" \
|
|
"$source" \
|
|
'//host[.]lan/' \
|
|
"invalid forward-slash UNC path detected" \
|
|
'Use "\\host.lan\Data\..." instead of "//host.lan/Data/...".'
|
|
|
|
reportBatchMatches \
|
|
"$file" \
|
|
"$source" \
|
|
'\\\\host[.]lan\\shared(?:[\\/]|$)' \
|
|
"invalid Samba share name detected" \
|
|
'The "/shared" folder is exposed to Windows as "\\host.lan\Data".'
|
|
|
|
return 0
|
|
}
|
|
|
|
reportBatchMatches() {
|
|
|
|
local file="$1"
|
|
local source="$2"
|
|
local pattern="$3"
|
|
local message="$4"
|
|
local suggestion="$5"
|
|
|
|
local matches line
|
|
matches=$(grep -Pin "$pattern" "$file" || true)
|
|
|
|
[ -n "$matches" ] || return 0
|
|
|
|
warn "$message in $source:"
|
|
|
|
while IFS= read -r line; do
|
|
printf ' %s\n' "$line" >&2
|
|
done <<< "$matches"
|
|
|
|
printf ' %s\n\n' "$suggestion" >&2
|
|
|
|
return 0
|
|
}
|
|
|
|
checkMemory() {
|
|
|
|
local id="$1"
|
|
local required name
|
|
|
|
required=$(getRequiredMemory "$id") || return
|
|
RAM_MINIMUM="$required"
|
|
|
|
name=$(printVersion "$id" "") || return
|
|
checkMemoryRequirement "$name" || return
|
|
|
|
return 0
|
|
}
|
|
|
|
setDiskMinimum() {
|
|
|
|
local id="$1"
|
|
local required
|
|
|
|
required=$(getRequiredDisk "$id") || return
|
|
DISK_MINIMUM="$required"
|
|
|
|
return 0
|
|
}
|
|
|
|
reserveSambaPorts() {
|
|
|
|
disabled "${SAMBA:-Y}" && return 0
|
|
disabled "${NETWORK:-Y}" && return 0
|
|
enabled "${DHCP:-N}" && return 0
|
|
|
|
# NAT can fall back to user-mode networking after this point,
|
|
# so always protect the Samba listeners for non-DHCP networking.
|
|
HOST_PORTS="${HOST_PORTS:+$HOST_PORTS,}139/tcp,445/tcp"
|
|
|
|
return 0
|
|
}
|
|
|
|
startWindows
|
|
|
|
return 0
|