Compare commits

..

113 Commits

Author SHA1 Message Date
Kroese 85a7529b4e feat: Close audio stream during shutdown (#1853) 2026-07-14 23:56:00 +02:00
Kroese 360d8c11d9 feat: Add support for audio in noVNC (#1852) 2026-07-14 23:41:02 +02:00
Kroese c1b999849a build: Update QEMU base image to v7.36 (#1851) 2026-07-14 23:36:44 +02:00
Kroese 69f35d3aa1 build: Update VirtIO drivers to v1.9.58 (#1850) 2026-07-14 21:38:36 +02:00
Kroese fed8b00b45 docs: Readme (#1849) 2026-07-14 14:54:58 +02:00
Kroese d92c2f276c docs: Readme (#1848) 2026-07-14 13:04:31 +02:00
Kroese 85fedb00d6 docs: Environment variables (#1847) 2026-07-14 12:57:31 +02:00
Kroese f0a9a729c6 docs: Environment variables (#1846) 2026-07-14 11:05:09 +02:00
Kroese 0f331c234c docs: Environment variables (#1845) 2026-07-14 10:43:57 +02:00
Kroese 988f47fb40 docs: Document audio support (#1844) 2026-07-14 02:06:45 +02:00
Kroese ba6d34c362 docs: Readme (#1842) 2026-07-13 00:40:23 +02:00
Kroese a8c38640c0 fix: Skip checksum verification for dynamic URLs (#1841) 2026-07-12 23:05:35 +02:00
Kroese 0b119d589c feat: Deduplicate Samba daemon startup (#1840) 2026-07-12 22:51:48 +02:00
Kroese 3a0b973c0b feat: Use state helper for base image reads (#1839) 2026-07-12 21:06:05 +02:00
Kroese 08f058b758 build: Update QEMU base image to v7.35 (#1838) 2026-07-11 12:04:34 +03:00
Kroese 99c1f10263 feat: Use state helpers from base image (#1837) 2026-07-11 07:22:44 +03:00
Kroese 7a20e4aea8 docs: Network modes (#1834) 2026-07-09 00:38:51 +02:00
Kroese 559033a57d docs: Refer to environment documentation (#1833) 2026-07-08 18:45:38 +02:00
Kroese 936fde9ac0 docs: Document all environment variables (#1832) 2026-07-08 15:53:57 +02:00
Kroese 28571435bb fix: Improve download error message (#1831) 2026-07-08 14:49:03 +02:00
Kroese 633cbefb86 fix: Add extra line after boot (#1829) 2026-07-07 23:18:17 +02:00
Kroese c488bdee93 fix: Get ESD field value (#1828) 2026-07-07 23:07:03 +02:00
Kroese 0d22f9150e fix: Indentation in parseVersion (#1827) 2026-07-07 22:58:23 +02:00
Kroese 23a1028d8c feat: Improve download function (#1826) 2026-07-07 22:49:06 +02:00
Kroese 2436d0e764 feat: Improve ESD extraction (#1825) 2026-07-07 22:29:38 +02:00
Kroese de136699cc build: Remove uuidgen package (#1824) 2026-07-07 22:07:48 +02:00
Kroese ebd4b00a7f feat: Improve Samba error handling (#1823) 2026-07-07 20:58:48 +02:00
Kroese 9aa074f877 feat: Improve legacy .iso parsing (#1822) 2026-07-07 20:49:17 +02:00
Kroese efe9915c24 fix: Verify generated session id (#1821) 2026-07-07 20:33:45 +02:00
Kroese 67f3f23a35 build: Add uuidgen package (#1820) 2026-07-07 20:27:39 +02:00
Kroese 57a766c738 feat: Improve config error handling (#1819) 2026-07-07 20:22:22 +02:00
Kroese 35fe5aab9c fix: Read QEMU pid correctly (#1818) 2026-07-07 11:03:23 +02:00
Kroese 1e12c90226 feat: Improve shutdown logic (#1817) 2026-07-07 03:02:36 +02:00
Kroese 068b3cb966 feat: Improve shutdown logic (#1816) 2026-07-07 02:32:00 +02:00
Kroese b4754f5826 fix: Samba network configuration (#1815) 2026-07-07 02:27:08 +02:00
Kroese 029f59db2e fix: Add missing edQuery variable (#1814) 2026-07-07 01:49:21 +02:00
Kroese 8ad66b62a8 feat: Improve error handling during image detection (#1813) 2026-07-07 01:30:10 +02:00
Kroese 075b6bc97f fix: Improve ESD image parsing (#1812) 2026-07-07 01:16:23 +02:00
Kroese 9c4ac26372 fix: Tolerate missing ESD catalog matches (#1811) 2026-07-07 01:07:30 +02:00
Kroese 6d79a7c9ab build: Update QEMU base image to v7.34 (#1810) 2026-07-06 22:10:25 +02:00
Kroese 8006b7f992 fix: Run cabextract inside subshell (#1808) 2026-07-06 08:41:56 +02:00
Kroese da032a401e feat: Improve installation robustness (#1807) 2026-07-06 07:46:01 +02:00
Kroese cfc44829f8 build: Update wsddn package to v1.26 (#1806) 2026-07-06 06:48:10 +02:00
Kroese 47dcf3732c feat: Add returncode to function (#1805) 2026-07-06 03:46:46 +02:00
Kroese e377c62aaf feat: Refactor shutdown logic (#1804) 2026-07-05 22:04:07 +02:00
Kroese 0bee04a7dd feat: Refactor Samba code (#1803) 2026-07-05 21:46:26 +02:00
Kroese fceb286849 feat: Refactor download code (#1802) 2026-07-05 20:47:44 +02:00
Kroese d443ca377d feat: Add helper functions to installation code (#1801) 2026-07-05 20:39:46 +02:00
Kroese e6b0e530ba docs: Readme (#1800) 2026-07-05 14:56:22 +02:00
Kroese aacf70657a build: Use heredoc for Dockerfile commands (#1795) 2026-07-04 20:36:22 +02:00
OrbisAI Security 7a81862263 fix: Set dependabot cooldown (#1789) 2026-07-04 14:30:02 +02:00
Kroese 0538bd5e32 build: Update QEMU base image to v7.33 (#1787) 2026-07-03 19:42:11 +02:00
Kroese c763015734 fix: Do not strip quotes from password (#1786) 2026-07-03 18:25:53 +02:00
Kroese 700b3b0902 feat: Sanitize environment variables (#1785) 2026-07-03 16:25:39 +02:00
Copilot fc8c850b6d feat: Normalize boolean option handling (#1784) 2026-07-03 15:24:04 +02:00
Kroese 11acdefafb docs: Compress logo (#1783) 2026-07-03 12:47:12 +02:00
Kroese 264d03e871 fix: Improve curl error handling (#1782) 2026-07-02 21:42:58 +02:00
Kroese 38c65b2565 fix: Improve curl error handling (#1781)
Refactor error handling for curl command exit statuses and improve readability.
2026-07-02 21:19:44 +02:00
Kroese c2e6c65248 docs: Readme (#1780) 2026-07-02 20:26:33 +02:00
Kroese 4325600626 feat: Improve shutdown logic (#1779) 2026-07-02 20:06:42 +02:00
Kroese 19d5951aac feat: Update download links (#1778) 2026-07-02 19:15:22 +02:00
Kroese c70c0c304b docs: Readme (#1776) 2026-07-02 12:27:35 +02:00
Kroese b590190cc3 build: Update wsddn package to v1.25 (#1774) 2026-06-28 12:24:24 +02:00
Kroese 57f3a4a723 build: Run review steps in parallel (#1771) 2026-06-25 22:36:15 +02:00
Kroese 9c6c348f5d docs: Update stargazers chart (#1770) 2026-06-25 14:24:32 +02:00
renovate[bot] 67eec73e33 chore(deps): update actions/checkout action to v7 (#1767) 2026-06-19 11:43:05 +02:00
Kroese c768ba894d fix: Display signal names (#1760) 2026-06-05 03:14:50 +02:00
Kroese 0c9472b62c fix: Add fail_level to reviewdog actions (#1759) 2026-06-04 23:21:36 +02:00
Kroese 8d2f27bd67 feat: Improved start and stop logic (#1758) 2026-06-04 22:37:18 +02:00
Kroese bcae867d68 build: Update QEMU base image to v7.32 (#1755) 2026-06-04 12:49:26 +02:00
Kroese 77c164a3fa fix: Provide exitcode on shutdown (#1752) 2026-05-31 00:30:39 +02:00
Kroese f088a60e2c build: Update QEMU base image to v7.31 (#1751) 2026-05-30 15:53:42 +02:00
Kroese 6dc5ce7919 build: Add timeout to wsddn download (#1750) 2026-05-29 18:18:33 +02:00
Kroese ba478700de feat: Update version file path (#1749) 2026-05-29 16:31:47 +02:00
Kroese 6e45deea2f feat: Simplify increment syntax (#1747) 2026-05-29 15:50:53 +02:00
Kroese 0bc2380452 fix: Add CharDevice type to KVM hostPath (#1746) 2026-05-29 15:32:40 +02:00
Kroese e1732e66ec fix: High resolutions produce wrong hex value (#1745) 2026-05-29 14:48:30 +02:00
Kroese 8f70ffa902 build: Disable Shellformat action (#1744) 2026-05-29 13:17:18 +02:00
Kroese 7b4404db6b fix: Improve version string trimming (#1738) 2026-05-17 12:58:03 +02:00
Kroese 2412a3ed18 fix: Local variable declaration (#1736) 2026-05-17 10:51:00 +02:00
Kroese 8c57b119cf fix: Language string replacement syntax (#1735) 2026-05-17 10:22:20 +02:00
Kroese 40867f6e85 feat: Update Windows 11 download links (#1733) 2026-05-17 06:06:54 +02:00
Kroese b5d8c2ad08 feat: Update Windows 11 download link (#1732) 2026-05-16 22:41:45 +02:00
Kroese 603465a687 fix: Update Windows Server 2025 download link (#1731) 2026-05-16 22:18:31 +02:00
Kroese d8a548c3fb fix: Escape illegal characters in base64 output (#1730) 2026-05-16 22:10:56 +02:00
Kroese 60e5925d72 feat: Enhance debug messages (#1729) 2026-05-16 19:34:35 +02:00
Kroese 398344163f fix: Invalid sed command syntax (#1728) 2026-05-16 18:53:26 +02:00
Kroese f2809a8929 fix: Missing equals sign before ConvertNTFS (#1727) 2026-05-16 02:45:58 +02:00
Kroese 7fd7ab3050 fix: Code formatting (#1726) 2026-05-16 02:44:47 +02:00
Kroese fddbebadae fix: Case statements (#1725) 2026-05-16 02:37:05 +02:00
Kroese 34f312ddff build: Update wsddn package to v1.24 (#1724) 2026-05-16 00:17:11 +02:00
Kroese daea34a10f feat: Update download links (#1723) 2026-05-15 22:03:48 +02:00
Kroese c5417e8fca fix: Automated downloads from Microsoft (#1722) 2026-05-13 20:34:30 +02:00
Kroese 603ee5f93b fix: Remove VirtIO Balloon service for x86 (#1721) 2026-05-13 17:49:22 +02:00
Kroese 4926ad0ea9 feat: Always copy drivers to Windows folder (#1720) 2026-05-13 17:10:40 +02:00
Kroese a81c45de30 feat: Update download links (#1719) 2026-05-13 16:50:37 +02:00
Kroese 85bacbec63 fix: Check if VERSION variable is empty (#1718) 2026-05-13 08:28:18 +02:00
Kroese 8f1335d7f8 docs: Improve warning for USB disk drives (#1717) 2026-05-13 04:47:49 +02:00
Kroese 8d50472167 build: Update QEMU base image to v7.30 (#1713) 2026-05-10 16:30:16 +02:00
renovate[bot] 173f35d21b chore(deps): update grantbirki/json-yaml-validate action to v5 (#1712) 2026-05-10 16:27:30 +02:00
dependabot[bot] e21418b228 build(deps): bump docker/metadata-action from 5 to 6 (#1667) 2026-05-06 20:56:39 +02:00
dependabot[bot] 1246667afa build(deps): bump docker/setup-buildx-action from 3 to 4 (#1668) 2026-05-06 20:52:52 +02:00
dependabot[bot] 247e4e0b38 build(deps): bump docker/login-action from 3 to 4 (#1669) 2026-05-06 20:51:21 +02:00
dependabot[bot] c534901402 build(deps): bump docker/build-push-action from 6 to 7 (#1670) 2026-05-06 20:49:05 +02:00
Kroese 49e962e56a fix: Use PID file from base image (#1711) 2026-05-06 20:47:37 +02:00
Kroese 3e37a04d96 build: Update VirtIO drivers to v1.9.57 (#1709) 2026-05-06 08:25:54 +02:00
Luca De Petrillo 0ea63962af feat: Add memory ballooning support (#1688) 2026-05-06 07:57:31 +02:00
Kroese 4d01a81586 build: Use Firefox impersonation while checking links (#1681) 2026-03-23 15:17:26 +01:00
Kroese d8e51d8965 build: Add workflow to validate download links (#1680) 2026-03-23 13:36:57 +01:00
Kroese 8d4788f7c0 build: Update WSDD version to v1.23 (#1679) 2026-03-23 11:31:04 +01:00
Kroese 8ec135d826 build: Update VirtIO drivers to v1.9.53 (#1678) 2026-03-23 11:26:50 +01:00
huulme 0400f5f45d docs: Higher quality video thumbnail (#1671) 2026-03-09 11:50:01 -04:00
Azim Muradov 588969ae5f fix: Path to "Desktop" folder on Windows XP (#1625)
Fix path to "Desktop" folder to work correctly with different locales on Windows XP.
2026-01-13 13:43:48 +01:00
50 changed files with 1749 additions and 970 deletions
+4
View File
@@ -4,7 +4,11 @@ updates:
directory: /
schedule:
interval: weekly
cooldown:
default-days: 7
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
cooldown:
default-days: 7
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 59 KiB

+6 -6
View File
@@ -22,13 +22,13 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0
-
name: Docker metadata
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
context: git
images: |
@@ -43,23 +43,23 @@ jobs:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
-
name: Login into Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Login to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Build Docker image
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: .
push: true
+3 -2
View File
@@ -9,7 +9,8 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
- parallel:
-
name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
@@ -30,6 +31,6 @@ jobs:
file-endings: ".xml"
-
name: Validate JSON and YML files
uses: GrantBirki/json-yaml-validate@v4
uses: GrantBirki/json-yaml-validate@v5.0.0
with:
yaml_exclude_regex: ".*\\kubernetes\\.yml$"
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
steps:
-
name: Checkout repo
uses: actions/checkout@v6
uses: actions/checkout@v7
-
name: Docker Hub Description
uses: peter-evans/dockerhub-description@v5
+67
View File
@@ -0,0 +1,67 @@
name: Links
on:
workflow_dispatch:
concurrency:
group: links
cancel-in-progress: false
jobs:
links:
name: Links
runs-on: ubuntu-latest
permissions:
actions: read
packages: read
contents: read
steps:
-
name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
-
name: Validate Links
run: |
errors=0
count=0
host=""
declare -A seen
wget https://github.com/lwthiker/curl-impersonate/releases/download/v0.6.1/curl-impersonate-v0.6.1.x86_64-linux-gnu.tar.gz
tar -xzf curl-impersonate-v0.6.1.x86_64-linux-gnu.tar.gz
sudo cp curl-impersonate-ff /usr/local/bin/
check() {
local url="$1" http
http=$(curl-impersonate-ff -sSL -o /dev/null -w "%{http_code}" --max-time 10 -I -- "$url" 2>&1) || http="000"
[[ "$http" == 2* ]] && return 0
http=$(curl-impersonate-ff -sSL -o /dev/null -w "%{http_code}" --max-time 10 -r "0-0" -- "$url" 2>&1) || http="000"
[[ "$http" == 2* ]]
}
while IFS= read -r line; do
if [[ "$line" =~ ^[[:space:]]*local[[:space:]]+host=\"(https://[^\"]+)\" ]]; then
host="${BASH_REMATCH[1]%/}"
continue
fi
[[ "$line" =~ ^[[:space:]]*url=\"(.+)\" ]] || continue
val="${BASH_REMATCH[1]#/}"
if [[ "$val" == https://* ]]; then
url="$val"
elif [[ -n "$host" ]]; then
url="$host/$val"
else
continue
fi
[[ -v seen[$url] ]] && continue
seen[$url]=1
count=$((count + 1))
if check "$url"; then
echo " OK: $url"
else
echo "FAIL: $url"
errors=$((errors + 1))
fi
done < "src/define.sh"
echo ""
printf '%d/%d links valid\n' "$(( count - errors ))" "$count"
(( errors == 0 ))
+6 -1
View File
@@ -15,7 +15,8 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
- parallel:
-
name: Spelling
uses: reviewdog/action-misspell@v1
@@ -32,6 +33,7 @@ jobs:
uses: reviewdog/action-hadolint@v1
with:
level: warning
fail_level: error
reporter: github-pr-review
hadolint_ignore: DL3006 DL3008
github_token: ${{ secrets.GITHUB_TOKEN }}
@@ -52,8 +54,10 @@ jobs:
-
name: Shellformat
uses: reviewdog/action-shfmt@v1
if: false
with:
level: warning
fail_on_error: "true"
shfmt_flags: "-i 2 -ci -bn"
github_token: ${{ secrets.GITHUB_TOKEN }}
-
@@ -61,6 +65,7 @@ jobs:
uses: reviewdog/action-shellcheck@v1
with:
level: warning
fail_level: error
reporter: github-pr-review
shellcheck_flags: -x -e SC1091 -e SC2001 -e SC2002 -e SC2034 -e SC2064 -e SC2153 -e SC2317 -e SC2028
github_token: ${{ secrets.GITHUB_TOKEN }}
+18 -9
View File
@@ -3,37 +3,46 @@
ARG VERSION_ARG="latest"
FROM scratch AS build-amd64
COPY --from=qemux/qemu:7.29 / /
COPY --from=qemux/qemu:7.36 / /
ARG TARGETARCH
ARG VERSION_WSDD="1.26"
ARG VERSION_VIRTIO="1.9.58"
ARG DEBCONF_NOWARNINGS="yes"
ARG DEBIAN_FRONTEND="noninteractive"
ARG DEBCONF_NONINTERACTIVE_SEEN="true"
RUN set -eu && \
apt-get update && \
RUN <<EOF
set -eu
apt-get update
apt-get --no-install-recommends -y install \
samba \
wimtools \
dos2unix \
cabextract \
libxml2-utils \
libarchive-tools && \
wget "https://github.com/gershnik/wsdd-native/releases/download/v1.22/wsddn_1.22_${TARGETARCH}.deb" -O /tmp/wsddn.deb -q && \
dpkg -i /tmp/wsddn.deb && \
apt-get clean && \
libarchive-tools
# Install wsdd
wget "https://github.com/gershnik/wsdd-native/releases/download/v${VERSION_WSDD}/wsddn_${VERSION_WSDD}_${TARGETARCH}.deb" -O /tmp/wsddn.deb -q --timeout=10
dpkg -i /tmp/wsddn.deb
apt-get clean
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
EOF
COPY --chmod=755 ./src /run/
COPY --chmod=755 ./assets /run/assets
ADD --chmod=664 https://github.com/qemus/virtiso-whql/releases/download/v1.9.49-0/virtio-win-1.9.49.tar.xz /var/drivers.txz
ADD --chmod=664 https://github.com/qemus/virtiso-whql/releases/download/v${VERSION_VIRTIO}-0/virtio-win-${VERSION_VIRTIO}.tar.xz /var/drivers.txz
FROM dockurr/windows-arm:${VERSION_ARG} AS build-arm64
FROM build-${TARGETARCH}
ARG VERSION_ARG="0.00"
RUN echo "$VERSION_ARG" > /run/version
RUN echo "$VERSION_ARG" > /etc/version
VOLUME /storage
EXPOSE 3389 8006
+5
View File
@@ -246,6 +246,11 @@
<Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path>
<Description>Set Network Location to Home</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>26</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -249,6 +249,11 @@
<Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path>
<Description>Set Network Location to Home</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>26</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -255,6 +255,11 @@
<Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path>
<Description>Set Network Location to Home</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>26</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -252,6 +252,11 @@
<Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path>
<Description>Set Network Location to Home</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>26</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -249,6 +249,11 @@
<Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path>
<Description>Set Network Location to Home</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>26</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -269,6 +269,11 @@
<Path>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</Path>
<Description>Install VirtIO display driver</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>27</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -272,6 +272,11 @@
<Path>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</Path>
<Description>Install VirtIO display driver</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>27</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -272,6 +272,11 @@
<Path>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</Path>
<Description>Install VirtIO display driver</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>27</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -272,6 +272,11 @@
<Path>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</Path>
<Description>Install VirtIO display driver</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>27</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -272,6 +272,11 @@
<Path>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</Path>
<Description>Install VirtIO display driver</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>27</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+9
View File
@@ -154,6 +154,15 @@
</FirewallGroup>
</FirewallGroups>
</component>
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+9
View File
@@ -157,6 +157,15 @@
</FirewallGroup>
</FirewallGroups>
</component>
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -155,6 +155,11 @@
<Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path>
<Description>Set Network Location to Home</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>2</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-ServerManager-SvrMgrNc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -158,6 +158,11 @@
<Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path>
<Description>Set Network Location to Home</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>2</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-ServerManager-SvrMgrNc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -155,6 +155,11 @@
<Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path>
<Description>Set Network Location to Home</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>2</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-ServerManager-SvrMgrNc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -158,6 +158,11 @@
<Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path>
<Description>Set Network Location to Home</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>2</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-ServerManager-SvrMgrNc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -159,6 +159,11 @@
<Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path>
<Description>Set Network Location to Home</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>2</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-ServerManager-SvrMgrNc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -164,6 +164,11 @@
<Path>dism.exe /online /Disable-Feature /FeatureName:Microsoft-Hyper-V /NoRestart</Path>
<Description>Disable Hyper-V role</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>3</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-ServerManager-SvrMgrNc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -162,6 +162,11 @@
<Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path>
<Description>Set Network Location to Home</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>2</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-ServerManager-SvrMgrNc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -159,6 +159,11 @@
<Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path>
<Description>Set Network Location to Home</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>2</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-ServerManager-SvrMgrNc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -162,6 +162,11 @@
<Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path>
<Description>Set Network Location to Home</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>2</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-ServerManager-SvrMgrNc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -164,6 +164,11 @@
<Path>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</Path>
<Description>Install VirtIO display driver</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>4</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-ServerManager-SvrMgrNc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -167,6 +167,11 @@
<Path>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</Path>
<Description>Install VirtIO display driver</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>4</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-ServerManager-SvrMgrNc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+9
View File
@@ -148,6 +148,15 @@
</FirewallGroup>
</FirewallGroups>
</component>
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+9
View File
@@ -152,6 +152,15 @@
</FirewallGroup>
</FirewallGroups>
</component>
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+9
View File
@@ -152,6 +152,15 @@
</FirewallGroup>
</FirewallGroups>
</component>
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+9
View File
@@ -152,6 +152,15 @@
</FirewallGroup>
</FirewallGroups>
</component>
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -146,6 +146,11 @@
<Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path>
<Description>Set Network Location to Home</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>2</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -149,6 +149,11 @@
<Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path>
<Description>Set Network Location to Home</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>2</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+5
View File
@@ -156,6 +156,11 @@
<Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path>
<Description>Set Network Location to Home</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>2</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+9
View File
@@ -90,6 +90,15 @@
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<fDenyTSConnections>false</fDenyTSConnections>
</component>
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+9
View File
@@ -90,6 +90,15 @@
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<fDenyTSConnections>false</fDenyTSConnections>
</component>
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+9
View File
@@ -90,6 +90,15 @@
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<fDenyTSConnections>false</fDenyTSConnections>
</component>
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<Path>C:\Windows\Drivers\Balloon\blnsvr.exe -i</Path>
<Description>Install VirtIO Balloon service</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
+163
View File
@@ -0,0 +1,163 @@
# Environment Variables
This page lists all the environment variables that can be used to configure the container.
An empty default means the variable is unset and its value is determined automatically when applicable.
## 🪟 Windows
| Variable | Default | Description |
|---|---|---|
| `VERSION` | `11` | Windows version to install, such as `10` or `11`. |
| `EDITION` | | Windows edition to install, such as `core` for Windows Server Core. |
| `LANGUAGE` | `en-US` | Windows display language, such as `English`, `en-US`, or `en`. |
| `REGION` | | Windows regional format. Uses `LANGUAGE` when unset. |
| `KEYBOARD` | | Keyboard layout. Uses `LANGUAGE` when unset. |
| `USERNAME` | `Docker` | Name of the Windows user account. |
| `PASSWORD` | `admin` | Password for the Windows account. |
| `KEY` | | Windows product key used to install and activate Windows. |
| `MANUAL` | `N` | Enables manual installation instead of unattended installation. |
| `VERIFY` | `N` | Verifies downloaded images against a predefined list of checksums. |
| `REMOVE` | `Y` | Deletes the downloaded Windows ISO after installation to save space. |
## 🧠 CPU and Memory
| Variable | Default | Description |
|---|---|---|
| `CPU_CORES` | `2` | Number of virtual CPU cores, such as `4`, `half`, or `max`. |
| `CPU_MODEL` | `host` | QEMU CPU model. |
| `CPU_FLAGS` | | Additional QEMU CPU flags. |
| `KVM` | `Y` | Enables KVM hardware acceleration. |
| `VMX` | `N` | Exposes Intel VMX virtualization extensions to the guest. |
| `HV` | `Y` | Enables Hyper-V enlightenments for Windows guests. |
| `RAM_SIZE` | `4G` | Amount of RAM assigned to Windows, such as `8G`, `half`, or `max`. |
| `RAM_CHECK` | `Y` | Checks whether enough host memory is available before starting Windows. |
## 💾 Storage
| Variable | Default | Description |
|---|---|---|
| `DISK_SIZE` | `64G` | Size of the primary disk. |
| `DISK_FMT` | `raw` | Disk image format: `raw` or `qcow2`. |
| `DISK_TYPE` | `scsi` | Disk device type, such as `sata`, `scsi`, `nvme`, or `blk`. |
| `DISK_CACHE` | `none` | Disk cache mode, such as `none` or `writeback`. |
| `DISK_IO` | `native` | Disk I/O mode, such as `native`, `threads`, or `io_uring`. |
| `DISK_DISCARD` | `unmap` | Discard/TRIM mode for the primary disk. |
| `DISK_ROTATION` | `1` | Rotation rate reported to the guest. Use `1` to identify the disk as an SSD. |
| `DISK_FLAGS` | | Additional options used when creating `qcow2` disks. |
| `ALLOCATE` | `N` | Preallocates space for the primary disk. |
| `STORAGE` | `/storage` | Storage directory used for disks, firmware variables, and downloads. |
## 🌐 Networking
| Variable | Default | Description |
|---|---|---|
| `NETWORK` | | Network mode, such as `nat`, `passt`, `slirp`, or `N` to disable networking. |
| `DHCP` | `N` | Enables macvtap networking so Windows receives an address from the external LAN through DHCP. |
| `HOST` | `Windows` | Hostname assigned to Windows. |
| `IP` | | Overrides the automatically selected guest IPv4 address. |
| `MAC` | | Guest network adapter MAC address. |
| `ADAPTER` | `virtio-net-pci` | QEMU network adapter model. |
| `DEV` | `eth0` | Container network interface used as the uplink. |
| `MTU` | | MTU assigned to the guest network interface. |
| `MASK` | `255.255.255.0` | IPv4 netmask. |
| `TAP` | `qemu` | TAP or macvtap interface name. |
| `BRIDGE` | `docker` | Bridge name used for NAT networking. |
| `HOST_PORTS` | | Ports excluded from guest forwarding. |
| `USER_PORTS` | | Additional ports to forward to Windows when using user-mode networking. |
| `DNSMASQ_OPTS` | | Additional options passed to dnsmasq. |
| `DNSMASQ_DEBUG` | `N` | Enables dnsmasq debug output. |
| `DNSMASQ_DISABLE` | `N` | Disables the internal dnsmasq resolver. |
| `PASST_OPTS` | | Additional options passed to passt. |
| `PASST_DEBUG` | `N` | Enables passt debug output. |
## 🖥️ Display
| Variable | Default | Description |
|---|---|---|
| `DISPLAY` | `web` | Display backend, such as `web`, `vnc`, `disabled`, or `none`. |
| `VGA` | `virtio` | QEMU video adapter model. |
| `WIDTH` | `1920` | Display width configured in Windows. |
| `HEIGHT` | `1080` | Display height configured in Windows. |
| `GPU` | `N` | Enables experimental Intel iGPU acceleration. |
| `RENDERNODE` | `/dev/dri/renderD128` | Render node used for GPU acceleration. |
## 🌍 Web UI
| Variable | Default | Description |
|---|---|---|
| `WEB` | `Y` | Enables the web interface. |
| `WEB_PORT` | `8006` | Port for the web interface. |
| `VNC_PORT` | `5900` | Port for the VNC server. |
| `WSS_PORT` | `5700` | WebSocket port used by noVNC. |
| `WSD_PORT` | `8004` | Internal websocketd port used for the display stream. |
| `AUDIO` | `N` | Streams guest audio to the web viewer. |
| `SOUND` | `intel-hda` | QEMU audio device used by the web viewer. |
| `AUX_PORT` | `8003` | Internal WebSocket port used for the audio stream. |
| `PROTECT` | `N` | Enables password protection for the web interface. |
## 📁 File Sharing
| Variable | Default | Description |
|---|---|---|
| `SAMBA` | `Y` | Enables the Samba shared folder. |
| `SAMBA_DEBUG` | `N` | Enables Samba debug output. |
## ⚙️ System
| Variable | Default | Description |
|---|---|---|
| `MACHINE` | `q35` | QEMU machine type. |
| `UUID` | | UUID assigned to Windows. |
| `HPET` | `off` | QEMU HPET timer setting. |
| `VMPORT` | `off` | QEMU VMware port setting. |
| `SM_BIOS` | | Additional arguments passed to QEMUs `-smbios` option. |
| `ARGUMENTS` | | Additional raw arguments appended to the QEMU command line. |
## 🚀 Boot
| Variable | Default | Description |
|---|---|---|
| `BOOT_MODE` | `windows` | Boot configuration, such as `windows`, `windows_secure`, or `windows_legacy`. |
| `BOOT_INDEX` | `9` | Boot priority index for the installation media. |
| `BIOS` | | Custom firmware file. |
| `TPM` | `N` | Enables the TPM emulator, usually set by `BOOT_MODE`. |
| `SMM` | `N` | Enables System Management Mode, usually set by `BOOT_MODE`. |
| `LOGO` | `Y` | Enables the custom boot logo. |
| `CLEAR` | `N` | Resets the NVRAM variables on the next boot. |
| `USB` | `qemu-xhci,id=xhci` | QEMU USB controller configuration. |
## 🎈 Memory Ballooning
Also see [Dynamic memory allocation](https://github.com/qemus/qemu/blob/master/docs/ballooning.md) for usage instructions and important caveats.
| Variable | Default | Description |
|---|---|---|
| `BALLOONING` | `N` | Enables dynamic memory ballooning. |
| `BALLOONING_MIN_MEM` | `33%` | Minimum amount of memory retained by the VM. |
| `BALLOONING_RAM_THRESHOLD` | `80.0` | Host RAM usage percentage at which ballooning begins adjusting memory. |
| `BALLOONING_RAM_THRESHOLD_HARD` | `90.0` | Host RAM usage percentage at which ballooning becomes more aggressive. |
| `BALLOONING_PSI_PRESSURE` | `10.0` | PSI memory pressure level at which ballooning becomes more aggressive. |
| `BALLOONING_PSI_PRESSURE_MAX` | `50.0` | PSI memory pressure level at which ballooning reaches its strongest response. |
| `BALLOONING_HYSTERESIS` | `128M` | Minimum memory change before the balloon target is updated. |
| `BALLOONING_KP` | `0.5` | Proportional gain used by the ballooning controller. |
| `BALLOONING_KI` | `0.05` | Integral gain used by the ballooning controller. |
| `BALLOONING_INTERVAL` | `5` | Polling interval in seconds. |
| `BALLOONING_DEBUG` | `N` | Enables debug output for the ballooning monitor. |
## 🔌 Shutdown
| Variable | Default | Description |
|---|---|---|
| `SHUTDOWN` | `Y` | Enables graceful ACPI shutdown. |
| `TIMEOUT` | `115` | Maximum time, in seconds, to wait before forcing Windows to stop. |
## 🐞 Debugging
| Variable | Default | Description |
|---|---|---|
| `DEBUG` | `N` | Enables verbose debug output. |
| `TRACE` | `N` | Enables shell command tracing. |
| `DETECTED` | | Overrides the automatically detected Windows image identifier. |
| `SERIAL` | `mon:stdio` | QEMU serial device configuration. |
| `MONITOR` | `unix:$QEMU_DIR/monitor.sock,server,wait=off,nodelay` | QEMU monitor configuration. |
+1
View File
@@ -66,6 +66,7 @@ spec:
claimName: windows-pvc
- hostPath:
path: /dev/kvm
type: CharDevice
name: dev-kvm
- hostPath:
path: /dev/net/tun
+93 -48
View File
@@ -1,6 +1,6 @@
<h1 align="center">Windows<br />
<div align="center">
<a href="https://github.com/dockur/windows"><img src="https://github.com/dockur/windows/raw/master/.github/logo.png" title="Logo" style="max-width:100%;" width="128" /></a>
<a href="https://github.com/dockur/windows"><img src="https://github.com/dockur/windows/raw/master/.github/logo.png" title="Logo" style="max-width:100%;" width="96" /></a>
</div>
<div align="center">
@@ -16,17 +16,22 @@ Windows inside a Docker container.
## Features ✨
- ISO downloader
- KVM acceleration
- Web-based viewer
- Runs Windows inside a Docker container
- Automatic download and hands-free installation
- Supports modern and legacy Windows releases
- Near-native performance with KVM acceleration
- Customizable CPU, memory, and storage allocation
- Dynamic memory allocation with memory ballooning
- USB passthrough and host folder sharing
- Supports NAT, user-mode, macvlan, and macvtap networking
## Video 📺
[![Youtube](https://img.youtube.com/vi/xhGYobuG508/0.jpg)](https://www.youtube.com/watch?v=xhGYobuG508)
[![YouTube](https://img.youtube.com/vi/xhGYobuG508/maxresdefault.jpg)](https://www.youtube.com/watch?v=xhGYobuG508)
## Usage 🐳
##### Via Docker Compose:
##### Docker Compose:
```yaml
services:
@@ -50,26 +55,36 @@ services:
stop_grace_period: 2m
```
##### Via Docker CLI:
##### Docker CLI:
```bash
docker run -it --rm --name windows -e "VERSION=11" -p 8006:8006 --device=/dev/kvm --device=/dev/net/tun --cap-add NET_ADMIN -v "${PWD:-.}/windows:/storage" --stop-timeout 120 docker.io/dockurr/windows
```
##### Via Kubernetes:
##### Kubernetes:
```shell
kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/master/kubernetes.yml
```
##### Via Github Codespaces:
##### GitHub Codespaces:
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/dockur/windows)
##### Via a graphical installer:
##### Graphical installer:
[![Download WinBoat](https://github.com/dockur/windows/raw/master/.github/winboat.png)](https://winboat.app)
## Requirements ⚙️
- Docker or Podman on a Linux host with KVM support.
- Docker Desktop or Podman (Desktop) on Windows 11 with nested virtualization enabled.
- At least 4 GB of available RAM.
- At least 64 GB of free disk space.
> [!NOTE]
> Docker Desktop on Linux, macOS, and Windows 10 does not currently provide KVM access to containers and is therefore not supported.
## FAQ 💬
### How do I use it?
@@ -78,7 +93,7 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
- Start the container and connect to [port 8006](http://127.0.0.1:8006/) using your web browser.
- Sit back and relax while the magic happens, the whole installation will be performed fully automatic.
- Sit back and relax while the magic happens, the whole installation will be performed fully automatically.
- Once you see the desktop, your Windows installation is ready for use.
@@ -90,14 +105,14 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
```yaml
environment:
VERSION: "11"
VERSION: "10"
```
Select from the values below:
| **Value** | **Version** | **Size** |
|---|---|---|
| `11` | Windows 11 Pro | 7.2 GB |
| `11` | Windows 11 Pro | 7.9 GB |
| `11l` | Windows 11 LTSC | 4.7 GB |
| `11e` | Windows 11 Enterprise | 6.6 GB |
||||
@@ -111,7 +126,7 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
| `xp` | Windows XP Professional | 0.6 GB |
| `2k` | Windows 2000 Professional | 0.4 GB |
||||
| `2025` | Windows Server 2025 | 6.7 GB |
| `2025` | Windows Server 2025 | 7.6 GB |
| `2022` | Windows Server 2022 | 6.0 GB |
| `2019` | Windows Server 2019 | 5.3 GB |
| `2016` | Windows Server 2016 | 6.5 GB |
@@ -143,7 +158,7 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
```
> [!TIP]
> This can also be used to resize the existing disk to a larger capacity without any data loss. However you will need to [manually extend the disk partition](https://learn.microsoft.com/en-us/windows-server/storage/disk-management/extend-a-basic-volume?tabs=disk-management) since the added disk space will appear as unallocated.
> This can also be used to resize an existing disk to a larger capacity without any data loss. However, you will need to [manually extend the disk partition](https://learn.microsoft.com/en-us/windows-server/storage/disk-management/extend-a-basic-volume?tabs=disk-management) afterwards, since the added disk space will appear as unallocated.
### How do I share files with the host?
@@ -170,11 +185,30 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
CPU_CORES: "4"
```
### How do I enable audio?
Audio is disabled by default unless you are using RDP. To stream it to the browser, add the following environment variable:
```yaml
environment:
AUDIO: "Y"
```
Then enable **Audio** under **Settings → Advanced** in the web viewer. The stream is only active while this option is enabled, so it uses no extra bandwidth otherwise.
### How do I connect using RDP?
The web viewer is mainly intended for use during installation, since it is less responsive than RDP and does not support features such as clipboard sharing.
So for a better experience you can connect using any Microsoft Remote Desktop client to the IP of the container, using the username `Docker` and password `admin`.
There is an RDP client for [Android](https://play.google.com/store/apps/details?id=com.microsoft.rdc.androidx) available from the Play Store and one for [iOS](https://apps.apple.com/nl/app/microsoft-remote-desktop/id714464092?l=en-GB) in the Apple Store. For Linux you can use [FreeRDP](https://www.freerdp.com/) and on Windows just type `mstsc` in the search box.
### How do I configure the username and password?
By default, a user called `Docker` is created and its password is `admin`.
If you want to use different credentials during installation, you can configure them in your compose file:
If you want to set up different credentials during installation, you can configure them in your compose file:
```yaml
environment:
@@ -197,7 +231,7 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
### How do I select the keyboard layout?
If you want to use a keyboard layout or locale that is not the default for your selected language, you can add `KEYBOARD` and `REGION` variables like this:
If you want to set up a keyboard layout or locale that is not the default for your selected language, you can add `KEYBOARD` and `REGION` variables like this:
```yaml
environment:
@@ -218,7 +252,7 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
```yaml
volumes:
- ./example.iso:/boot.iso
- ./example.iso:/custom.iso
```
Replace the example path `./example.iso` with the filename of your desired ISO file. The value of `VERSION` will be ignored in this case.
@@ -234,27 +268,19 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
- ./example:/oem
```
The example folder `./example` will be copied to `C:\OEM` and the containing `install.bat` will be executed during the last step of the automatic installation.
The example folder `./example` will be copied to `C:\OEM` and the `install.bat` file inside that folder will be executed during the last step of the automatic installation.
### How do I perform a manual installation?
It's recommended to stick to the automatic installation, as it adjusts various settings to prevent common issues when running Windows inside a virtual environment.
However, if you insist on performing the installation manually at your own risk, add the following environment variable to your compose file:
However, if you insist on performing the installation manually (at your own risk), add the following environment variable to your compose file:
```yaml
environment:
MANUAL: "Y"
```
### How do I connect using RDP?
The web-viewer is mainly meant to be used during installation, as its picture quality is low, and it has no audio or clipboard for example.
So for a better experience you can connect using any Microsoft Remote Desktop client to the IP of the container, using the username `Docker` and password `admin`.
There is a RDP client for [Android](https://play.google.com/store/apps/details?id=com.microsoft.rdc.androidx) available from the Play Store and one for [iOS](https://apps.apple.com/nl/app/microsoft-remote-desktop/id714464092?l=en-GB) in the Apple Store. For Linux you can use [FreeRDP](https://www.freerdp.com/) and on Windows just type `mstsc` in the search box.
### How do I assign an individual IP address to the container?
By default, the container uses bridge networking, which shares the IP address with the host.
@@ -320,9 +346,9 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
- ./example3:/storage3
```
### How do I pass-through a disk?
### How do I pass through a disk?
It is possible to pass-through disk devices or partitions directly by adding them to your compose file in this way:
You can pass through disk devices or partitions directly by adding them to your compose file in this way:
```yaml
devices:
@@ -332,9 +358,9 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
Use `/disk1` if you want it to become your main drive (which will be formatted during installation), and use `/disk2` and higher to add them as secondary drives (which will stay untouched).
### How do I pass-through a USB device?
### How do I pass through a USB device?
To pass-through a USB device, first lookup its vendor and product id via the `lsusb` command, then add them to your compose file like this:
To pass through a USB device, first look up its vendor and product IDs via the `lsusb` command, then add them to your compose file like this:
```yaml
environment:
@@ -343,35 +369,49 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
- /dev/bus/usb
```
If the device is a USB disk drive, please wait until after the installation is fully completed before connecting it. Otherwise the installation may fail, as the order of the disks can get rearranged.
> [!WARNING]
> Adding a USB mass storage device before Windows Setup has finished may cause it to fail. Or worse: the drive can get formatted as the system disk, and all your data will be lost! So always keep them disconnected when launching the container for the first time.
### How do I verify if my system supports KVM?
### How do I enable dynamic memory allocation?
First check if your software is compatible using this chart:
By default, the VM is allocated the full amount of RAM configured via `RAM_SIZE` for its entire lifetime.
| **Product** | **Linux** | **Win11** | **Win10** | **macOS** |
|---|---|---|---|---|
| Docker CLI | ✅ | ✅ | ❌ | ❌ |
| Docker Desktop | ❌ | ✅ | ❌ | ❌ |
| Podman CLI | ✅ | ✅ | ❌ | ❌ |
| Podman Desktop | ✅ | ✅ | ❌ | ❌ |
However, you can enable [memory ballooning](https://github.com/qemus/qemu/blob/master/docs/ballooning.md) if you want the container to dynamically reclaim unused guest RAM based on host memory pressure.
After that you can run the following commands in Linux to check your system:
### Are these all available options?
No. For a complete overview of all supported settings, see the [environment variables](docs/environment.md) page.
### How do I verify that KVM is available?
First, make sure your platform and container runtime meet the [requirements](#requirements-) listed above.
On a Linux host, install `cpu-checker` and run:
```bash
sudo apt install cpu-checker
sudo kvm-ok
```
If you receive an error from `kvm-ok` indicating that KVM cannot be used, please check whether:
A working configuration should report:
- the virtualization extensions (`Intel VT-x` or `AMD SVM`) are enabled in your BIOS.
```text
KVM acceleration can be used
```
- you enabled "nested virtualization" if you are running the container inside a virtual machine.
You can also verify that the KVM device exists:
- you are not using a cloud provider, as most of them do not allow nested virtualization for their VPS's.
```bash
ls -l /dev/kvm
```
If you did not receive any error from `kvm-ok` but the container still complains about a missing KVM device, it could help to add `privileged: true` to your compose file (or `sudo` to your `docker` command) to rule out any permission issue.
If KVM is unavailable, check whether:
- Hardware virtualization (`Intel VT-x` or `AMD-V`) is enabled in your BIOS or UEFI.
- Nested virtualization is enabled when the host itself is a virtual machine.
- Your VPS or cloud provider supports nested virtualization.
If `kvm-ok` succeeds but the container still reports that KVM is unavailable, you can temporarily add `privileged: true` to your Compose file to rule out a permission or device-access issue.
### How do I run macOS in a container?
@@ -383,7 +423,12 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
### Is this project legal?
Yes, this project contains only open-source code and does not distribute any copyrighted material. Any product keys found in the code are just generic placeholders provided by Microsoft for trial purposes. So under all applicable laws, this project will be considered legal.
Yes, this project contains only open-source code and does not distribute Windows itself. Any product keys found in the code are generic installation keys published by Microsoft for trial purposes and are not valid activation licenses.
You are responsible for ensuring that you have a valid Windows license and that your use complies with Microsoft's licensing terms.
## Stars 🌟
[![Stargazers](https://raw.githubusercontent.com/star-stats/stars/refs/heads/data/charts/dockur-windows.svg)](https://github.com/dockur/windows/stargazers)
## Disclaimer ⚖️
+71 -73
View File
@@ -16,15 +16,21 @@ set -Eeuo pipefail
: "${USERNAME:=""}"
: "${PASSWORD:=""}"
# Sanitize variables
KEY=$(strip "$KEY")
WIDTH=$(strip "$WIDTH")
HEIGHT=$(strip "$HEIGHT")
REGION=$(strip "$REGION")
EDITION=$(strip "$EDITION")
KEYBOARD=$(strip "$KEYBOARD")
LANGUAGE=$(strip "$LANGUAGE")
USERNAME=$(strip "$USERNAME")
MIRRORS=4
parseVersion() {
if [[ "${VERSION}" == \"*\" || "${VERSION}" == \'*\' ]]; then
VERSION="${VERSION:1:-1}"
fi
VERSION=$(expr "$VERSION" : "^\ *\(.*[^ ]\)\ *$")
VERSION=$(strip "$VERSION")
[ -z "$VERSION" ] && VERSION="win11"
case "${VERSION,,}" in
@@ -169,32 +175,26 @@ getLanguage() {
"ar" | "ar-"* )
short="ar"
lang="Arabic"
desc="$lang"
culture="ar-SA" ;;
"bg" | "bg-"* )
short="bg"
lang="Bulgarian"
desc="$lang"
culture="bg-BG" ;;
"cs" | "cs-"* | "cz" | "cz-"* )
short="cs"
lang="Czech"
desc="$lang"
culture="cs-CZ" ;;
"da" | "da-"* | "dk" | "dk-"* )
short="da"
lang="Danish"
desc="$lang"
culture="da-DK" ;;
"de" | "de-"* )
short="de"
lang="German"
desc="$lang"
culture="de-DE" ;;
"el" | "el-"* | "gr" | "gr-"* )
short="el"
lang="Greek"
desc="$lang"
culture="el-GR" ;;
"gb" | "en-gb" )
short="en-gb"
@@ -204,7 +204,6 @@ getLanguage() {
"en" | "en-"* )
short="en"
lang="English"
desc="English"
culture="en-US" ;;
"mx" | "es-mx" )
short="mx"
@@ -214,17 +213,14 @@ getLanguage() {
"es" | "es-"* )
short="es"
lang="Spanish"
desc="$lang"
culture="es-ES" ;;
"et" | "et-"* )
short="et"
lang="Estonian"
desc="$lang"
culture="et-EE" ;;
"fi" | "fi-"* )
short="fi"
lang="Finnish"
desc="$lang"
culture="fi-FI" ;;
"ca" | "fr-ca" )
short="ca"
@@ -234,62 +230,50 @@ getLanguage() {
"fr" | "fr-"* )
short="fr"
lang="French"
desc="$lang"
culture="fr-FR" ;;
"he" | "he-"* | "il" | "il-"* )
short="he"
lang="Hebrew"
desc="$lang"
culture="he-IL" ;;
"hr" | "hr-"* | "cr" | "cr-"* )
short="hr"
lang="Croatian"
desc="$lang"
culture="hr-HR" ;;
"hu" | "hu-"* )
short="hu"
lang="Hungarian"
desc="$lang"
culture="hu-HU" ;;
"it" | "it-"* )
short="it"
lang="Italian"
desc="$lang"
culture="it-IT" ;;
"ja" | "ja-"* | "jp" | "jp-"* )
short="ja"
lang="Japanese"
desc="$lang"
culture="ja-JP" ;;
"ko" | "ko-"* | "kr" | "kr-"* )
short="ko"
lang="Korean"
desc="$lang"
culture="ko-KR" ;;
"lt" | "lt-"* )
short="lt"
lang="Lithuanian"
desc="$lang"
culture="lt-LT" ;;
"lv" | "lv-"* )
short="lv"
lang="Latvian"
desc="$lang"
culture="lv-LV" ;;
"nb" | "nb-"* |"nn" | "nn-"* | "no" | "no-"* )
"nb" | "nb-"* | "nn" | "nn-"* | "no" | "no-"* )
short="no"
lang="Norwegian"
desc="$lang"
culture="nb-NO" ;;
"nl" | "nl-"* )
short="nl"
lang="Dutch"
desc="$lang"
culture="nl-NL" ;;
"pl" | "pl-"* )
short="pl"
lang="Polish"
desc="$lang"
culture="pl-PL" ;;
"br" | "pt-br" )
short="pt"
@@ -299,27 +283,22 @@ getLanguage() {
"pt" | "pt-"* )
short="pp"
lang="Portuguese"
desc="$lang"
culture="pt-BR" ;;
"ro" | "ro-"* )
short="ro"
lang="Romanian"
desc="$lang"
culture="ro-RO" ;;
"ru" | "ru-"* )
short="ru"
lang="Russian"
desc="$lang"
culture="ru-RU" ;;
"sk" | "sk-"* )
short="sk"
lang="Slovak"
desc="$lang"
culture="sk-SK" ;;
"sl" | "sl-"* | "si" | "si-"* )
short="sl"
lang="Slovenian"
desc="$lang"
culture="sl-SI" ;;
"sr" | "sr-"* )
short="sr"
@@ -329,22 +308,18 @@ getLanguage() {
"sv" | "sv-"* | "se" | "se-"* )
short="sv"
lang="Swedish"
desc="$lang"
culture="sv-SE" ;;
"th" | "th-"* )
short="th"
lang="Thai"
desc="$lang"
culture="th-TH" ;;
"tr" | "tr-"* )
short="tr"
lang="Turkish"
desc="$lang"
culture="tr-TR" ;;
"ua" | "ua-"* | "uk" | "uk-"* )
short="uk"
lang="Ukrainian"
desc="$lang"
culture="uk-UA" ;;
"hk" | "zh-hk" | "cn-hk" )
short="hk"
@@ -363,6 +338,8 @@ getLanguage() {
culture="zh-CN" ;;
esac
[ -z "$desc" ] && desc="$lang"
case "${ret,,}" in
"desc" ) echo "$desc" ;;
"name" ) echo "$lang" ;;
@@ -376,9 +353,9 @@ getLanguage() {
parseLanguage() {
REGION="${REGION//_/-/}"
KEYBOARD="${KEYBOARD//_/-/}"
LANGUAGE="${LANGUAGE//_/-/}"
REGION="${REGION//_/-}"
KEYBOARD="${KEYBOARD//_/-}"
LANGUAGE="${LANGUAGE//_/-}"
[ -z "$LANGUAGE" ] && LANGUAGE="en"
@@ -722,8 +699,8 @@ getMido() {
case "${id,,}" in
"win11x64" )
size=7736125440
sum="d141f6030fed50f75e2b03e1eb2e53646c4b21e5386047cb860af5223f102a32"
size=8471603200
sum="768984706b909479417b2368438909440f2967ff05c6a9195ed2667254e465e3"
url="https://software-static.download.prss.microsoft.com/dbazure/888969d5-f34g-4e03-ac9d-1f9786c66749/26200.6584.250915-1905.25h2_ge_release_svc_refresh_CLIENT_CONSUMER_x64FRE_en-us.iso"
;;
"win11x64-enterprise-eval" )
@@ -734,11 +711,7 @@ getMido() {
"win11x64-enterprise-iot-eval" | "win11x64-enterprise-ltsc-eval" )
size=5060020224
sum="2cee70bd183df42b92a2e0da08cc2bb7a2a9ce3a3841955a012c0f77aeb3cb29"
url="https://software-static.download.prss.microsoft.com/dbazure/888969d5-f34g-4e03-ac9d-1f9786c66749/26100.1.240331-1435.ge_release_CLIENT_IOT_LTSC_EVAL_x64FRE_en-us.iso"
;;
"win10x64" )
size=6140975104
sum="a6f470ca6d331eb353b815c043e327a347f594f37ff525f17764738fe812852e"
url="https://software-static.download.prss.microsoft.com/dbazure/998969d5-f34g-4e03-ac9d-1f9786c66749/26100.1742.240906-0331.ge_release_svc_refresh_CLIENT_IOT_LTSC_EVAL_x64FRE_en-us.iso"
;;
"win10x64-enterprise-eval" )
size=5550497792
@@ -756,9 +729,9 @@ getMido() {
url="https://download.microsoft.com/download/B/9/9/B999286E-0A47-406D-8B3D-5B5AD7373A4A/9600.17050.WINBLUE_REFRESH.140317-1640_X64FRE_ENTERPRISE_EVAL_EN-US-IR3_CENA_X64FREE_EN-US_DV9.ISO"
;;
"win2025-eval" )
size=6014152704
sum="d0ef4502e350e3c6c53c15b1b3020d38a5ded011bf04998e950720ac8579b23d"
url="https://software-static.download.prss.microsoft.com/dbazure/888969d5-f34g-4e03-ac9d-1f9786c66749/26100.1742.240906-0331.ge_release_svc_refresh_SERVER_EVAL_x64FRE_en-us.iso"
size=8152356864
sum="7b052573ba7894c9924e3e87ba732ccd354d18cb75a883efa9b900ea125bfd51"
url="https://software-static.download.prss.microsoft.com/dbazure/998969d5-f34g-4e03-ac9d-1f9786c66749/26100.32230.260111-0550.lt_release_svc_refresh_SERVER_EVAL_x64FRE_en-us.iso"
;;
"win2022-eval" )
size=5044094976
@@ -817,9 +790,9 @@ getLink1() {
case "${id,,}" in
"win11x64" | "win11x64-enterprise" | "win11x64-enterprise-eval" )
size=5332989952
sum="aa1ad990f930d907b7a34ea897abbb0dfbe47552ca8acc146f92e40381839e05"
url="11/en-us_windows_11_24h2_x64.iso"
size=6927149056
sum="f5ffe9313eebc6299fba9e6eeb2971007264e6c6be013073a89b5ae9bd85bfb3"
url="11/en-us_windows_11_25h2_x64.iso"
;;
"win11x64-iot" | "win11x64-enterprise-iot" | "win11x64-enterprise-iot-eval" )
size=5144817664
@@ -832,8 +805,8 @@ getLink1() {
url="11/X23-81951_26100.1742.240906-0331.ge_release_svc_refresh_CLIENT_ENTERPRISES_OEM_x64FRE_en-us.iso"
;;
"win10x64" | "win10x64-enterprise" | "win10x64-enterprise-eval" )
size=5535252480
sum="557871965263d0fd0a1ea50b5d0d0d7cb04a279148ca905c1c675c9bc0d5486c"
size=5723299840
sum="316f718f21fc9b386d81dadd62dc60268a1cfd65b184ac6a052875a454c3431b"
url="10/en-us_windows_10_22h2_x64.iso"
;;
"win10x64-iot" | "win10x64-enterprise-iot" | "win10x64-enterprise-iot-eval" )
@@ -1078,6 +1051,7 @@ getLink3() {
size=387424256
sum="08b11c3897eb38d1e6566a17cec5cdf2b3c620444e160e3db200a7e223aabbd8"
url="Windows_2000_SP4.iso"
;;
esac
case "${ret,,}" in
@@ -1167,10 +1141,15 @@ getLink4() {
sum="c3c604c03677504e8905090a8ce5bb1dde76b6fd58e10f32e3a25bef21b2abe1"
url="en_windows_8.1_enterprise_with_update_x64_dvd/en_windows_8.1_enterprise_with_update_x64_dvd_6054382.iso"
;;
"win2025" | "win2025-eval" )
size=6014152704
sum="d0ef4502e350e3c6c53c15b1b3020d38a5ded011bf04998e950720ac8579b23d"
url="26100.1742.240906-0331.ge-release-svc-refresh-server-eval-x-64-fre-en-us/26100.1742.240906-0331.ge_release_svc_refresh_SERVER_EVAL_x64FRE_en-us.iso"
;;
"win2022" | "win2022-eval" )
size=5365624832
sum="c3c57bb2cf723973a7dcfb1a21e97dfa035753a7f111e348ad918bb64b3114db"
url="win-server-2022/2227-January_2024/en-us_windows_server_2022_updated_jan_2024_x64_dvd_2b7a0c9f.iso"
size=5550684160
sum="5a077ee2a95976ef9f3623eb4040e25cdf7f8f01dee3b8165a32a7626f39f025"
url="en-us_windows_server_2022_x64_dvd_620d7eac_202405/en-us_windows_server_2022_x64_dvd_620d7eac.iso"
;;
"win2019" | "win2019-eval" )
size=5575774208
@@ -1215,7 +1194,7 @@ getLink4() {
"winvistax64" | "winvistax64-ultimate" )
size=3861460992
sum="edf9f947c5791469fd7d2d40a5dcce663efa754f91847aa1d28ed7f585675b78"
url="en_windows_vista_sp2_x64_dvd_342267_202010/en_windows_vista_sp2_x64_dvd_342267.iso"
url="ms_windows_vista_sp2/en_windows_vista_sp2_x64_dvd_342267.iso"
;;
"winvistax64-enterprise" )
size=3205953536
@@ -1251,6 +1230,7 @@ getLink4() {
size=386859008
sum="e3816f6e80b66ff686ead03eeafffe9daf020a5e4717b8bd4736b7c51733ba22"
url="MicrosoftWindows2000BuildCollection/5.00.2195.6717_x86fre_client-professional_retail_en-us-ZRMPFPP_EN.iso"
;;
esac
case "${ret,,}" in
@@ -1311,7 +1291,7 @@ isMido() {
local lang="$2"
local sum
[[ "${MIDO:-}" == [Nn]* ]] && return 1
disabled "${MIDO:-}" && return 1
sum=$(getMido "$id" "en" "sum")
[ -n "$sum" ] && return 0
@@ -1324,7 +1304,7 @@ isESD() {
local id="$1"
local lang="$2"
[[ "${ESD:-}" == [Nn]* ]] && return 1
disabled "${ESD:-}" && return 1
case "${id,,}" in
"win11${PLATFORM,,}" | "win10${PLATFORM,,}" )
@@ -1379,7 +1359,13 @@ addFolder() {
local file
file=$(find "$dest" -maxdepth 1 -type f -iname install.bat -print -quit)
[ -f "$file" ] && unix2dos -q "$file"
if [ -f "$file" ]; then
if ! unix2dos -q "$file"; then
error "Failed to convert $file to DOS format!"
return 1
fi
fi
return 0
}
@@ -1572,8 +1558,8 @@ prepareInstall() {
[ -z "$WIDTH" ] && WIDTH="1280"
[ -z "$HEIGHT" ] && HEIGHT="720"
XHEX=$(printf '%x\n' "$WIDTH")
YHEX=$(printf '%x\n' "$HEIGHT")
XHEX=$(printf '%08x\n' "$WIDTH")
YHEX=$(printf '%08x\n' "$HEIGHT")
local username=""
local password=""
@@ -1605,7 +1591,7 @@ prepareInstall() {
echo " OemPnPDriversPath=\"Drivers\viostor;Drivers\NetKVM;Drivers\sata\""
echo " NoWaitAfterTextMode=1"
echo " NoWaitAfterGUIMode=1"
echo " FileSystem-ConvertNTFS"
echo " FileSystem=ConvertNTFS"
echo " ExtendOemPartition=0"
echo " Hibernation=\"No\""
echo ""
@@ -1692,13 +1678,13 @@ prepareInstall() {
echo ""
echo "[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Video\{23A77BF7-ED96-40EC-AF06-9B1F4867732A}\0000]"
echo "\"DefaultSettings.BitsPerPel\"=dword:00000020"
echo "\"DefaultSettings.XResolution\"=dword:00000$XHEX"
echo "\"DefaultSettings.YResolution\"=dword:00000$YHEX"
echo "\"DefaultSettings.XResolution\"=dword:$XHEX"
echo "\"DefaultSettings.YResolution\"=dword:$YHEX"
echo ""
echo "[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Hardware Profiles\Current\System\CurrentControlSet\Control\VIDEO\{23A77BF7-ED96-40EC-AF06-9B1F4867732A}\0000]"
echo "\"DefaultSettings.BitsPerPel\"=dword:00000020"
echo "\"DefaultSettings.XResolution\"=dword:00000$XHEX"
echo "\"DefaultSettings.YResolution\"=dword:00000$YHEX"
echo "\"DefaultSettings.XResolution\"=dword:$XHEX"
echo "\"DefaultSettings.YResolution\"=dword:$YHEX"
echo ""
echo "[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce]"
echo "\"ScreenSaver\"=\"reg add \\\"HKCU\\\\Control Panel\\\\Desktop\\\" /f /v \\\"SCRNSAVE.EXE\\\" /t REG_SZ /d \\\"off\\\"\""
@@ -1766,7 +1752,7 @@ prepareInstall() {
echo ""
echo "Call Domain.MoveHere(LocalAdminADsPath, \"$username\")"
echo ""
echo "Set oLink = WshShell.CreateShortcut(WshShell.ExpandEnvironmentStrings(\"%userprofile%\\Desktop\\Shared.lnk\"))"
echo "Set oLink = WshShell.CreateShortcut(WshShell.SpecialFolders(\"Desktop\") & \"\\Shared.lnk\")"
echo "With oLink"
echo " .TargetPath = \"\\\\host.lan\\Data\""
echo " .Save"
@@ -1796,8 +1782,21 @@ prepareLegacy() {
rm -f "$dir/$ETFS"
local len offset
len=$(isoinfo -d -i "$iso" | grep "Nsect " | grep -o "[^ ]*$")
offset=$(isoinfo -d -i "$iso" | grep "Bootoff " | grep -o "[^ ]*$")
if ! len=$(isoinfo -d -i "$iso" | grep "Nsect " | grep -o "[^ ]*$"); then
error "Failed to determine boot image size from $desc ISO!"
return 1
fi
if ! offset=$(isoinfo -d -i "$iso" | grep "Bootoff " | grep -o "[^ ]*$"); then
error "Failed to determine boot image offset from $desc ISO!"
return 1
fi
if [[ ! "$len" =~ ^[0-9]+$ ]] || [[ ! "$offset" =~ ^[0-9]+$ ]]; then
error "Invalid boot image location found in $desc ISO!"
return 1
fi
if ! dd "if=$iso" "of=$dir/$ETFS" bs=2048 "count=$len" "skip=$offset" status=none; then
error "Failed to extract boot image from $desc ISO!" && return 1
@@ -1857,7 +1856,6 @@ detectLegacy() {
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IB -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51ID -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IL -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IS -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AA -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AD -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AS -print -quit)
+29 -13
View File
@@ -17,32 +17,48 @@ cd /run
. install.sh # Run installation
. disk.sh # Initialize disks
. display.sh # Initialize graphics
. audio.sh # Initialize audio
. network.sh # Initialize network
. samba.sh # Configure samba
. boot.sh # Configure boot
. proc.sh # Initialize processor
. power.sh # Configure shutdown
. memory.sh # Check available memory
. balloon.sh # Initialize ballooning
. config.sh # Configure arguments
. finish.sh # Finish initialization
trap - ERR
version=$(qemu-system-x86_64 --version | head -n 1 | cut -d '(' -f 1 | awk '{ print $NF }')
info "Booting ${APP}${BOOT_DESC} using QEMU v$version..."
cmd=(qemu-system-x86_64)
version=$("${cmd[@]}" --version | awk 'NR==1 { print $4 }')
info "Booting ${APP}${BOOT_DESC} using QEMU v$version..." && echo
{ qemu-system-x86_64 ${ARGS:+ $ARGS} >"$QEMU_OUT" 2>"$QEMU_LOG"; rc=$?; } || :
(( rc != 0 )) && error "$(<"$QEMU_LOG")" && exit 15
pipe="$QEMU_DIR/qemu.pipe"
rm -f "$pipe" && mkfifo "$pipe"
terminal
tee "$QEMU_PTY" <"$pipe" |
sed -u \
-e 's/\x1B\[[=0-9;]*[a-z]//gi' \
-e 's/\x1B\x63//g' \
-e 's/\x1B\[[=?]7l//g' \
-e '/^$/d' \
-e 's/\x44\x53\x73//g' \
-e 's/failed to load Boot/skipped Boot/g' \
-e 's/0): Not Found/0)/g' &
if ! enabled "$SHUTDOWN"; then
exec "${cmd[@]}" ${ARGS:+ $ARGS} >"$pipe"
fi
"${cmd[@]}" ${ARGS:+ $ARGS} >"$pipe" &
pid=$!
( sleep 30; boot ) &
tail -fn +0 "$QEMU_LOG" --pid=$$ 2>/dev/null &
cat "$QEMU_TERM" 2> /dev/null | tee "$QEMU_PTY" | \
sed -u -e 's/\x1B\[[=0-9;]*[a-z]//gi' \
-e 's/\x1B\x63//g' -e 's/\x1B\[[=?]7l//g' \
-e '/^$/d' -e 's/\x44\x53\x73//g' \
-e 's/failed to load Boot/skipped Boot/g' \
-e 's/0): Not Found/0)/g' & wait $! || :
rc=0
wait "$pid" || rc=$?
[ -f "$QEMU_END" ] && exit "$rc"
sleep 1 & wait $!
[ ! -f "$QEMU_END" ] && finish 0
finish "$rc"
+254 -245
View File
@@ -11,17 +11,11 @@ backup () {
local iso="$1"
local name="unknown"
local root="$STORAGE/backups"
local previous="$STORAGE/windows.base"
if [ -f "$previous" ]; then
previous=$(<"$previous")
previous="${previous//[![:print:]]/}"
local previous
previous=$(readState "base") || return 1
[ -n "$previous" ] && name="${previous%.*}"
fi
if ! makeDir "$root"; then
error "Failed to create directory \"$root\" !"
return 1
@@ -32,13 +26,11 @@ backup () {
while [ -d "$dir" ]
do
count=$((count+1))
(( count++ ))
folder="${name}.${count}"
dir="$root/$folder"
done
rm -rf "$dir"
if ! makeDir "$dir"; then
error "Failed to create directory \"$dir\" !"
return 1
@@ -61,12 +53,9 @@ skipInstall() {
local method=""
local magic byte
local boot="$STORAGE/windows.boot"
local previous="$STORAGE/windows.base"
local previous
if [ -f "$previous" ]; then
previous=$(<"$previous")
previous="${previous//[![:print:]]/}"
previous=$(readState "base") || return 1
if [ -n "$previous" ]; then
if [[ "${STORAGE,,}/${previous,,}" != "${iso,,}" ]]; then
@@ -102,17 +91,15 @@ skipInstall() {
fi
fi
fi
[ -f "$boot" ] && hasDisk && return 0
[ ! -f "$iso" ] && return 1
[ ! -s "$iso" ] && return 1
# Check if the ISO was already processed by our script
magic=$(dd if="$iso" seek=0 bs=1 count=1 status=none | tr -d '\000')
magic=$(dd if="$iso" bs=1 count=1 status=none | tr -d '\000')
magic="$(printf '%s' "$magic" | od -A n -t x1 -v | tr -d ' \n')"
byte="16" && [[ "$MANUAL" == [Yy1]* ]] && byte="17"
byte="16" && enabled "$MANUAL" && byte="17"
if [[ "$magic" != "$byte" ]]; then
@@ -164,7 +151,7 @@ startInstall() {
fi
if ! makeDir "$TMP"; then
error "Failed to create directory \"$TMP\" !"
error "Failed to create directory \"$TMP\" !" && exit 50
fi
if [ -z "$CUSTOM" ]; then
@@ -187,15 +174,19 @@ startInstall() {
return 0
}
writeFile() {
checkFreeSpace() {
local txt="$1"
local path="$2"
local dir="$1"
local size="$2"
local size_gb space space_gb
echo "$txt" >"$path"
size_gb=$(formatBytes "$size")
space=$(df --output=avail -B 1 "$dir" | tail -n 1)
space_gb=$(formatBytes "$space")
if ! setOwner "$path"; then
error "Failed to set the owner for \"$path\" !"
if (( size > space )); then
error "Not enough free space in $STORAGE, have $space_gb available but need at least $size_gb."
return 1
fi
return 0
@@ -212,79 +203,76 @@ finishInstall() {
fi
if [[ "$iso" == "$STORAGE/"* ]]; then
! setOwner "$iso" && error "Failed to set the owner for \"$iso\" !"
if ! setOwner "$iso"; then
error "Failed to set the owner for \"$iso\" !"
return 1
fi
fi
if [[ "$aborted" != [Yy1]* ]]; then
# Mark ISO as prepared via magic byte
byte="16" && [[ "$MANUAL" == [Yy1]* ]] && byte="17"
byte="16" && enabled "$MANUAL" && byte="17"
if ! printf '%b' "\x$byte" | dd of="$iso" bs=1 seek=0 count=1 conv=notrunc status=none; then
warn "failed to set magic byte in ISO file: $iso"
fi
fi
local file="$STORAGE/windows.ver"
cp -f /run/version "$file"
! setOwner "$file" && error "Failed to set the owner for \"$file\" !"
cp -f /etc/version "$file" || return 1
if ! setOwner "$file"; then
error "Failed to set the owner for \"$file\" !"
return 1
fi
if [[ "$iso" == "$STORAGE/"* ]]; then
if [[ "$aborted" != [Yy1]* ]] || [ -z "$CUSTOM" ]; then
base=$(basename "$iso")
file="$STORAGE/windows.base"
writeFile "$base" "$file"
writeState "base" "$base" || return 1
fi
fi
if [[ "${PLATFORM,,}" == "x64" ]]; then
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
file="$STORAGE/windows.mode"
writeFile "$BOOT_MODE" "$file"
writeState "mode" "$BOOT_MODE" || return 1
if [[ "${MACHINE,,}" != "q35" ]]; then
file="$STORAGE/windows.old"
writeFile "$MACHINE" "$file"
writeState "old" "$MACHINE" || return 1
fi
else
# Enable secure boot + TPM on manual installs as Win11 requires
if [[ "$MANUAL" == [Yy1]* || "$aborted" == [Yy1]* ]]; then
if enabled "$MANUAL" || [[ "$aborted" == [Yy1]* ]]; then
if [[ "${DETECTED,,}" == "win11"* ]]; then
BOOT_MODE="windows_secure"
file="$STORAGE/windows.mode"
writeFile "$BOOT_MODE" "$file"
writeState "mode" "$BOOT_MODE" || return 1
fi
fi
# Enable secure boot on multi-socket systems to workaround freeze
if [ -n "$SOCKETS" ] && [[ "$SOCKETS" != "1" ]]; then
BOOT_MODE="windows_secure"
file="$STORAGE/windows.mode"
writeFile "$BOOT_MODE" "$file"
writeState "mode" "$BOOT_MODE" || return 1
fi
fi
fi
if [ -n "${ARGS:-}" ]; then
ARGUMENTS="$ARGS ${ARGUMENTS:-}"
file="$STORAGE/windows.args"
writeFile "$ARGS" "$file"
writeState "args" "$ARGS" || return 1
fi
if [ -n "${VGA:-}" ] && [[ "${VGA:-}" != "virtio"* ]]; then
file="$STORAGE/windows.vga"
writeFile "$VGA" "$file"
writeState "vga" "$VGA" || return 1
fi
if [ -n "${USB:-}" ] && [[ "${USB:-}" != "qemu-xhci"* ]]; then
file="$STORAGE/windows.usb"
writeFile "$USB" "$file"
writeState "usb" "$USB" || return 1
fi
if [ -n "${DISK_TYPE:-}" ] && [[ "${DISK_TYPE:-}" != "scsi" ]]; then
file="$STORAGE/windows.type"
writeFile "$DISK_TYPE" "$file"
writeState "type" "$DISK_TYPE" || return 1
fi
if [ -n "${ADAPTER:-}" ] && [[ "${ADAPTER:-}" != "virtio-net-pci" ]]; then
file="$STORAGE/windows.net"
writeFile "$ADAPTER" "$file"
writeState "net" "$ADAPTER" || return 1
fi
rm -rf "$TMP"
@@ -298,7 +286,7 @@ abortInstall() {
local efi
[[ "${iso,,}" == *".esd" ]] && exit 60
[[ "${UNPACK:-}" == [Yy1]* ]] && exit 60
enabled "${UNPACK:-}" && exit 60
efi=$(find "$dir" -maxdepth 1 -type d -iname efi -print -quit)
@@ -372,95 +360,135 @@ detectCustom() {
return 0
}
getEsdField() {
local list="$1"
local index="$2"
sed -n "${index}p" <<< "$list" | tr -cd '0-9'
return 0
}
extractESD() {
local iso="$1"
local dir="$2"
local version="$3"
local desc="$4"
local size size_gb sizes space space_gb
local desc total total1 total2 total3 total4
local imageIndex links links1 links2 links3 links4
local msg="Extracting $desc bootdisk"
local msg ret index
local minSize freeSpace bootPad installPad
local info count totals links
local bootTotal bootLinks bootSize
local wimTotal wimLinks wimSize
local installSize
local bootWim installWim
local edition imgEdition
minSize=100000000
freeSpace=9606127360
bootPad=60000000
installPad=3000000
msg="Extracting $desc bootdisk"
info "$msg..." && html "$msg..."
if [ "$(stat -c%s "$iso")" -lt 100000000 ]; then
error "Invalid ESD file: Size is smaller than 100 MB" && return 1
if [ "$(stat -c%s "$iso")" -lt "$minSize" ]; then
error "Invalid ESD file: Size is smaller than 100 MB"
return 1
fi
rm -rf "$dir"
if ! makeDir "$dir"; then
error "Failed to create directory \"$dir\" !" && return 1
error "Failed to create directory \"$dir\" !"
return 1
fi
size=9606127360
size_gb=$(formatBytes "$size")
space=$(df --output=avail -B 1 "$dir" | tail -n 1)
space_gb=$(formatBytes "$space")
checkFreeSpace "$dir" "$freeSpace" || return 1
if (( size > space )); then
error "Not enough free space in $STORAGE, have $space_gb available but need at least $size_gb." && return 1
info=$(wimlib-imagex info "$iso") || {
error "Cannot read ESD file information!"
return 1
}
count=$(awk '/Image Count:/ {print $3}' <<< "$info")
if [[ ! "$count" =~ ^[0-9]+$ ]]; then
error "Cannot read the image count in ESD file!"
return 1
fi
local esdImageCount
esdImageCount=$(wimlib-imagex info "$iso" | awk '/Image Count:/ {print $3}')
if [ -z "$esdImageCount" ]; then
error "Cannot read the image count in ESD file!" && return 1
if (( count < 3 )); then
error "Invalid ESD file: expected at least 3 images, found $count."
return 1
fi
sizes=$(wimlib-imagex info "$iso" | grep "Total Bytes:")
links=$(wimlib-imagex info "$iso" | grep "Hard Link Bytes:")
totals=$(grep "Total Bytes:" <<< "$info" || true)
links=$(grep "Hard Link Bytes:" <<< "$info" || true)
total1=$(awk "NR==1{ print; }" <<< "$sizes" | cut -d':' -f2 | sed 's/^ *//')
links1=$(awk "NR==1{ print; }" <<< "$links" | cut -d':' -f2 | sed 's/^ *//')
total=$(( total1 - links1 ))
bootTotal=$(getEsdField "$totals" 1)
bootLinks=$(getEsdField "$links" 1)
total3=$(awk "NR==3{ print; }" <<< "$sizes" | cut -d':' -f2 | sed 's/^ *//')
links3=$(awk "NR==3{ print; }" <<< "$links" | cut -d':' -f2 | sed 's/^ *//')
total3=$(( total3 - links3 ))
total3=$(( total3 + 60000000 ))
if [[ ! "$bootTotal" =~ ^[0-9]+$ ]] || [[ ! "$bootLinks" =~ ^[0-9]+$ ]]; then
error "Cannot read bootdisk size from ESD file!"
return 1
fi
/run/progress.sh "$dir" "$total" "$msg ([P])..." &
bootSize=$(( bootTotal - bootLinks ))
imageIndex="1"
wimlib-imagex apply "$iso" "$imageIndex" "$dir" --quiet 2>/dev/null || {
retVal=$?
wimTotal=$(getEsdField "$totals" 3)
wimLinks=$(getEsdField "$links" 3)
if [[ ! "$wimTotal" =~ ^[0-9]+$ ]] || [[ ! "$wimLinks" =~ ^[0-9]+$ ]]; then
error "Cannot read boot.wim size from ESD file!"
return 1
fi
wimSize=$(( wimTotal - wimLinks + bootPad ))
/run/progress.sh "$dir" "$bootSize" "$msg ([P])..." &
index="1"
wimlib-imagex apply "$iso" "$index" "$dir" --quiet 2>/dev/null || {
ret=$?
fKill "progress.sh"
error "Extracting $desc bootdisk failed ($retVal)" && return 1
error "Extracting $desc bootdisk failed ($ret)"
return 1
}
fKill "progress.sh"
local bootWimFile="$dir/sources/boot.wim"
local installWimFile="$dir/sources/install.wim"
bootWim="$dir/sources/boot.wim"
installWim="$dir/sources/install.wim"
local msg="Extracting $desc environment"
msg="Extracting $desc environment"
info "$msg..." && html "$msg..."
imageIndex="2"
/run/progress.sh "$bootWimFile" "$total3" "$msg ([P])..." &
index="2"
/run/progress.sh "$bootWim" "$wimSize" "$msg ([P])..." &
wimlib-imagex export "$iso" "$imageIndex" "$bootWimFile" --compress=none --quiet || {
retVal=$?
wimlib-imagex export "$iso" "$index" "$bootWim" --compress=none --quiet || {
ret=$?
fKill "progress.sh"
error "Adding WinPE failed ($retVal)" && return 1
error "Adding WinPE failed ($ret)"
return 1
}
fKill "progress.sh"
local msg="Extracting $desc setup"
msg="Extracting $desc setup"
info "$msg..."
imageIndex="3"
/run/progress.sh "$bootWimFile" "$total3" "$msg ([P])..." &
index="3"
/run/progress.sh "$bootWim" "$wimSize" "$msg ([P])..." &
wimlib-imagex export "$iso" "$imageIndex" "$bootWimFile" --compress=none --boot --quiet || {
retVal=$?
wimlib-imagex export "$iso" "$index" "$bootWim" --compress=none --boot --quiet || {
ret=$?
fKill "progress.sh"
error "Adding Windows Setup failed ($retVal)" && return 1
error "Adding Windows Setup failed ($ret)"
return 1
}
fKill "progress.sh"
@@ -471,30 +499,31 @@ extractESD() {
LABEL="CPBA_A64FRE_EN-US_DV9"
fi
local msg="Extracting $desc image"
msg="Extracting $desc image"
info "$msg..." && html "$msg..."
local edition imageEdition
edition=$(getCatalog "$version" "name")
if [ -z "$edition" ]; then
error "Invalid VERSION specified, value \"$version\" is not recognized!" && return 1
error "Invalid VERSION specified, value \"$version\" is not recognized!"
return 1
fi
for (( imageIndex=4; imageIndex<=esdImageCount; imageIndex++ )); do
for (( index=4; index<=count; index++ )); do
imageEdition=$(wimlib-imagex info "$iso" "$imageIndex" | grep '^Description:' | sed 's/Description:[ \t]*//')
[[ "${imageEdition,,}" != "${edition,,}" ]] && continue
imgEdition=$(wimlib-imagex info "$iso" "$index" | grep '^Description:' | sed 's/Description:[ \t]*//')
[[ "${imgEdition,,}" != "${edition,,}" ]] && continue
total4=$(du -sb "$iso" | cut -f1)
total4=$(( total4 + 3000000 ))
installSize=$(stat -c%s "$iso")
installSize=$(( installSize + installPad ))
/run/progress.sh "$installWimFile" "$total4" "$msg ([P])..." &
/run/progress.sh "$installWim" "$installSize" "$msg ([P])..." &
wimlib-imagex export "$iso" "$imageIndex" "$installWimFile" --compress=LZMS --chunk-size 128K --quiet || {
retVal=$?
wimlib-imagex export "$iso" "$index" "$installWim" --compress=LZMS --chunk-size 128K --quiet || {
ret=$?
fKill "progress.sh"
error "Addition of $imageIndex to the $desc image failed ($retVal)" && return 1
error "Addition of $index to the $desc image failed ($ret)"
return 1
}
fKill "progress.sh"
@@ -503,7 +532,8 @@ extractESD() {
done
fKill "progress.sh"
error "Failed to find product '$edition' in install.wim!" && return 1
error "Failed to find product '$edition' in install.wim!"
return 1
}
extractImage() {
@@ -512,7 +542,7 @@ extractImage() {
local dir="$2"
local version="$3"
local desc="local ISO"
local file size size_gb space space_gb
local file size
if [ -z "$CUSTOM" ]; then
desc="downloaded ISO"
@@ -536,17 +566,12 @@ extractImage() {
fi
size=$(stat -c%s "$iso")
size_gb=$(formatBytes "$size")
space=$(df --output=avail -B 1 "$dir" | tail -n 1)
space_gb=$(formatBytes "$space")
if (( size < 100000000 )); then
error "Invalid ISO file: Size is smaller than 100 MB" && return 1
fi
if (( size > space )); then
error "Not enough free space in $STORAGE, have $space_gb available but need at least $size_gb." && return 1
fi
checkFreeSpace "$dir" "$size" || return 1
rm -rf "$dir"
/run/progress.sh "$dir" "$size" "$msg ([P])..." &
@@ -558,9 +583,9 @@ extractImage() {
fKill "progress.sh"
if [[ "${UNPACK:-}" != [Yy1]* ]]; then
if ! enabled "${UNPACK:-}"; then
LABEL=$(isoinfo -d -i "$iso" | sed -n 's/Volume id: //p')
LABEL=$(isoinfo -d -i "$iso" | sed -n 's/Volume id: //p') || LABEL=""
else
@@ -574,8 +599,8 @@ extractImage() {
error "Failed to extract archive!" && return 1
fi
LABEL=$(isoinfo -d -i "$file" | sed -n 's/Volume id: //p')
rm -f "$file"
LABEL=$(isoinfo -d -i "$file" | sed -n 's/Volume id: //p') || LABEL=""
rm -f "$file" || warn "Failed to remove temporary ISO file: $file"
fi
@@ -594,7 +619,7 @@ getPlatform() {
case "${arch,,}" in
"0" ) platform="x86" ;;
"9" ) platform="x64" ;;
"12" )platform="arm64" ;;
"12" ) platform="arm64" ;;
esac
echo "$platform"
@@ -729,7 +754,7 @@ detectImage() {
local dir="$1"
local version="$2"
local desc msg find language
local desc msg language
XML=""
@@ -741,7 +766,7 @@ detectImage() {
skipVersion "${DETECTED,,}" && return 0
if ! setXML "" && [[ "$MANUAL" != [Yy1]* ]]; then
if ! setXML "" && ! enabled "$MANUAL"; then
MANUAL="Y"
desc=$(printEdition "$DETECTED" "this version")
warn "the answer file for $desc was not found ($DETECTED.xml), $FB."
@@ -771,14 +796,18 @@ detectImage() {
warn "failed to locate 'install.wim' or 'install.esd' in ISO image, $FB" && return 1
fi
info=$(wimlib-imagex info -xml "$wim" | iconv -f UTF-16LE -t UTF-8)
if ! info=$(wimlib-imagex info -xml "$wim" | iconv -f UTF-16LE -t UTF-8); then
warn "failed to read Windows image information, $FB"
return 1
fi
checkPlatform "$info" || exit 67
DETECTED=$(detectVersion "$info")
if [ -z "$DETECTED" ]; then
msg="Failed to determine Windows version from image"
if setXML "" || [[ "$MANUAL" == [Yy1]* ]]; then
if setXML "" || enabled "$MANUAL"; then
info "${msg}!"
else
MANUAL="Y"
@@ -805,8 +834,8 @@ detectImage() {
msg="the answer file for $desc was not found ($DETECTED.xml)"
local fallback="/run/assets/${DETECTED%%-*}.xml"
if setXML "$fallback" || [[ "$MANUAL" == [Yy1]* ]]; then
[[ "$MANUAL" != [Yy1]* ]] && warn "${msg}."
if setXML "$fallback" || enabled "$MANUAL"; then
! enabled "$MANUAL" && warn "${msg}."
else
MANUAL="Y"
warn "${msg}, $FB."
@@ -847,7 +876,7 @@ updateXML() {
local asset="$1"
local language="$2"
local culture region user admin pass keyboard
local culture region user admin pass pw keyboard
[ -z "$HEIGHT" ] && HEIGHT="720"
[ -z "$WIDTH" ] && WIDTH="1280"
@@ -893,12 +922,12 @@ updateXML() {
pw=$(printf '%s' "${pass}Password" | iconv -f utf-8 -t utf-16le | base64 -w 0)
admin=$(printf '%s' "${pass}AdministratorPassword" | iconv -f utf-8 -t utf-16le | base64 -w 0)
sed -i "s/<Value>password<\/Value>/<Value>$admin<\/Value>/g" "$asset"
sed -i "s/<PlainText>true<\/PlainText>/<PlainText>false<\/PlainText>/g" "$asset"
sed -z "s/<Password>...........<Value \/>/<Password>\n <Value>$pw<\/Value>/g" -i "$asset"
sed -z "s/<Password>...............<Value \/>/<Password>\n <Value>$pw<\/Value>/g" -i "$asset"
sed -z "s/<AdministratorPassword>...........<Value \/>/<AdministratorPassword>\n <Value>$admin<\/Value>/g" -i "$asset"
sed -z "s/<AdministratorPassword>...............<Value \/>/<AdministratorPassword>\n <Value>$admin<\/Value>/g" -i "$asset"
sed -i "s|<Value>password<\/Value>|<Value>$admin<\/Value>|g" "$asset"
sed -i "s|<PlainText>true<\/PlainText>|<PlainText>false<\/PlainText>|g" "$asset"
sed -i -z "s|<Password>...........<Value \/>|<Password>\n <Value>$pw<\/Value>|g" "$asset"
sed -i -z "s|<Password>...............<Value \/>|<Password>\n <Value>$pw<\/Value>|g" "$asset"
sed -i -z "s|<AdministratorPassword>...........<Value \/>|<AdministratorPassword>\n <Value>$admin<\/Value>|g" "$asset"
sed -i -z "s|<AdministratorPassword>...............<Value \/>|<AdministratorPassword>\n <Value>$admin<\/Value>|g" "$asset"
if [ -n "$EDITION" ]; then
[[ "${EDITION^^}" == "CORE" ]] && EDITION="STANDARDCORE"
@@ -998,29 +1027,20 @@ addDrivers() {
wimlib-imagex update "$file" "$index" --command "delete --force --recursive /$target" >/dev/null || true
addDriver "$version" "$drivers" "$target" "qxl" || return 1
addDriver "$version" "$drivers" "$target" "viofs" || return 1
addDriver "$version" "$drivers" "$target" "sriov" || return 1
addDriver "$version" "$drivers" "$target" "smbus" || return 1
addDriver "$version" "$drivers" "$target" "qxldod" || return 1
addDriver "$version" "$drivers" "$target" "viorng" || return 1
addDriver "$version" "$drivers" "$target" "viostor" || return 1
addDriver "$version" "$drivers" "$target" "viomem" || return 1
addDriver "$version" "$drivers" "$target" "NetKVM" || return 1
addDriver "$version" "$drivers" "$target" "Balloon" || return 1
addDriver "$version" "$drivers" "$target" "vioscsi" || return 1
addDriver "$version" "$drivers" "$target" "pvpanic" || return 1
addDriver "$version" "$drivers" "$target" "vioinput" || return 1
addDriver "$version" "$drivers" "$target" "viogpudo" || return 1
addDriver "$version" "$drivers" "$target" "vioserial" || return 1
addDriver "$version" "$drivers" "$target" "qemupciserial" || return 1
local driver
local driver_list=( qxl viofs sriov smbus qxldod viorng viostor viomem NetKVM Balloon vioscsi pvpanic vioinput viogpudo vioserial qemupciserial )
for driver in "${driver_list[@]}"; do
addDriver "$version" "$drivers" "$target" "$driver" || return 1
done
local dst="$src/\$OEM\$/\$\$/Drivers"
mkdir -p "$dst" || return 1
cp -Lr "$dest/." "$dst" || return 1
case "${version,,}" in
"win11x64"* | "win2025"* )
# Workaround Virtio GPU driver bug
local dst="$src/\$OEM\$/\$\$/Drivers"
mkdir -p "$dst" || return 1
cp -Lr "$dest/." "$dst" || return 1
rm -rf "$dest/viogpudo"
;;
esac
@@ -1039,16 +1059,16 @@ updateImage() {
local asset="$2"
local language="$3"
local tmp="/tmp/install"
local file="autounattend.xml"
local org="${file//.xml/.org}"
local dat="${file//.xml/.dat}"
local desc path src wim xml index result
local xml="autounattend.xml"
local bak="${xml//.xml/.org}"
local dat="${xml//.xml/.dat}"
local desc path src wim name info idx
skipVersion "${DETECTED,,}" && return 0
if [ ! -s "$asset" ] || [ ! -f "$asset" ]; then
asset=""
if [[ "$MANUAL" != [Yy1]* ]]; then
if ! enabled "$MANUAL"; then
MANUAL="Y"
warn "no answer file provided, $FB."
fi
@@ -1060,23 +1080,30 @@ updateImage() {
src=$(find "$dir" -maxdepth 1 -type d -iname sources -print -quit)
if [ ! -d "$src" ]; then
error "failed to locate 'sources' folder in ISO image, $FB" && return 1
error "failed to locate 'sources' folder in ISO image, $FB"
return 1
fi
wim=$(find "$src" -maxdepth 1 -type f \( -iname boot.wim -or -iname boot.esd \) -print -quit)
if [ ! -f "$wim" ]; then
error "failed to locate 'boot.wim' or 'boot.esd' in ISO image, $FB" && return 1
error "failed to locate 'boot.wim' or 'boot.esd' in ISO image, $FB"
return 1
fi
index="1"
result=$(wimlib-imagex info -xml "$wim" | iconv -f UTF-16LE -t UTF-8)
idx="1"
if [[ "${result^^}" == *"<IMAGE INDEX=\"2\">"* ]]; then
index="2"
if ! info=$(wimlib-imagex info -xml "$wim" | iconv -f UTF-16LE -t UTF-8); then
warn "failed to read boot image information, $FB"
MANUAL="Y"
info=""
fi
if ! addDrivers "$src" "$tmp" "$wim" "$index" "$DETECTED"; then
if [[ "${info^^}" == *"<IMAGE INDEX=\"2\">"* ]]; then
idx="2"
fi
if ! addDrivers "$src" "$tmp" "$wim" "$idx" "$DETECTED"; then
error "Failed to add drivers to image!"
fi
@@ -1084,55 +1111,69 @@ updateImage() {
error "Failed to add OEM folder to image!"
fi
if wimlib-imagex extract "$wim" "$index" "/$file" "--dest-dir=$tmp" >/dev/null 2>&1; then
if ! wimlib-imagex extract "$wim" "$index" "/$dat" "--dest-dir=$tmp" >/dev/null 2>&1; then
if ! wimlib-imagex extract "$wim" "$index" "/$org" "--dest-dir=$tmp" >/dev/null 2>&1; then
if ! wimlib-imagex update "$wim" "$index" --command "rename /$file /$org" > /dev/null; then
warn "failed to backup original answer file ($file)."
if wimlib-imagex extract "$wim" "$idx" "/$xml" "--dest-dir=$tmp" >/dev/null 2>&1; then
if ! wimlib-imagex extract "$wim" "$idx" "/$dat" "--dest-dir=$tmp" >/dev/null 2>&1; then
if ! wimlib-imagex extract "$wim" "$idx" "/$bak" "--dest-dir=$tmp" >/dev/null 2>&1; then
if ! wimlib-imagex update "$wim" "$idx" --command "rename /$xml /$bak" > /dev/null; then
warn "failed to backup original answer file ($xml)."
fi
fi
fi
fi
if [[ "$MANUAL" != [Yy1]* ]]; then
if ! enabled "$MANUAL"; then
xml=$(basename "$asset")
info "Adding $xml for automatic installation..."
name=$(basename "$asset")
local answer="$tmp/$name"
local answer="$tmp/$xml"
cp "$asset" "$answer"
updateXML "$answer" "$language"
info "Adding $name for automatic installation..."
if ! wimlib-imagex update "$wim" "$index" --command "add $answer /$file" > /dev/null; then
if ! cp "$asset" "$answer"; then
error "Failed to copy answer file to $answer."
return 1
fi
if ! updateXML "$answer" "$language"; then
error "Failed to update answer file: $answer"
return 1
fi
if ! wimlib-imagex update "$wim" "$idx" --command "add $answer /$xml" > /dev/null; then
MANUAL="Y"
warn "failed to add answer file ($xml) to ISO image, $FB"
warn "failed to add answer file ($name) to ISO image, $FB"
else
wimlib-imagex update "$wim" "$index" --command "add $answer /$dat" > /dev/null || true
wimlib-imagex update "$wim" "$idx" --command "add $answer /$dat" > /dev/null || true
fi
fi
if [[ "$MANUAL" == [Yy1]* ]]; then
if enabled "$MANUAL"; then
wimlib-imagex update "$wim" "$index" --command "delete --force /$file" > /dev/null || true
wimlib-imagex update "$wim" "$idx" --command "delete --force /$xml" > /dev/null || true
if wimlib-imagex extract "$wim" "$index" "/$org" "--dest-dir=$tmp" >/dev/null 2>&1; then
if ! wimlib-imagex update "$wim" "$index" --command "add $tmp/$org /$file" > /dev/null; then
warn "failed to restore original answer file ($org)."
if wimlib-imagex extract "$wim" "$idx" "/$bak" "--dest-dir=$tmp" >/dev/null 2>&1; then
if ! wimlib-imagex update "$wim" "$idx" --command "add $tmp/$bak /$xml" > /dev/null; then
warn "failed to restore original answer file ($bak)."
fi
fi
fi
local find="$file"
[[ "$MANUAL" == [Yy1]* ]] && find="$org"
path=$(find "$dir" -maxdepth 1 -type f -iname "$find" -print -quit)
name="$xml"
enabled "$MANUAL" && name="$bak"
path=$(find "$dir" -maxdepth 1 -type f -iname "$name" -print -quit)
if [ -f "$path" ]; then
if [[ "$MANUAL" != [Yy1]* ]]; then
mv -f "$path" "${path%.*}.org"
if ! enabled "$MANUAL"; then
if ! mv -f "$path" "${path%.*}.org"; then
error "Failed to rename answer file: $path"
return 1
fi
else
mv -f "$path" "${path%.*}.xml"
if ! mv -f "$path" "${path%.*}.xml"; then
error "Failed to rename answer file: $path"
return 1
fi
fi
fi
@@ -1158,7 +1199,7 @@ buildImage() {
local failed=""
local cat="BOOT.CAT"
local log="/run/shm/iso.log"
local base size size_gb space space_gb desc
local base size desc
if [ -f "$BOOT" ]; then
error "File $BOOT does already exist?!" && return 1
@@ -1179,14 +1220,8 @@ buildImage() {
error "Failed to locate file \"$ETFS\" in ISO image!" && return 1
fi
size=$(du -h -b --max-depth=0 "$dir" | cut -f1)
size_gb=$(formatBytes "$size")
space=$(df --output=avail -B 1 "$TMP" | tail -n 1)
space_gb=$(formatBytes "$space")
if (( size > space )); then
error "Not enough free space in $STORAGE, have $space_gb available but need at least $size_gb." && return 1
fi
size=$(du -b --max-depth=0 "$dir" | cut -f1)
checkFreeSpace "$TMP" "$size" || return 1
/run/progress.sh "$out" "$size" "$msg ([P])..." &
@@ -1217,64 +1252,38 @@ buildImage() {
error "Failed to build image!" && return 1
fi
local error=""
local err=""
local hide="Warning: creating filesystem that does not conform to ISO-9660."
[ -s "$log" ] && error="$(<"$log")"
[[ "$error" != "$hide" ]] && echo "$error"
[ -s "$log" ] && err="$(<"$log")"
[[ "$err" != "$hide" ]] && echo "$err"
mv -f "$out" "$BOOT" || return 1
! setOwner "$BOOT" && error "Failed to set the owner for \"$BOOT\" !"
if ! setOwner "$BOOT"; then
error "Failed to set the owner for \"$BOOT\" !"
return 1
fi
return 0
}
bootWindows() {
if [ -f "$STORAGE/windows.args" ]; then
ARGS=$(<"$STORAGE/windows.args")
ARGS="${ARGS//[![:print:]]/}"
ARGS=$(readState "$STORAGE/windows.args") || return 1
if [ -n "$ARGS" ]; then
ARGUMENTS="$ARGS ${ARGUMENTS:-}"
fi
if [ -s "$STORAGE/windows.vga" ] && [ -f "$STORAGE/windows.vga" ]; then
if [ -z "${VGA:-}" ]; then
VGA=$(<"$STORAGE/windows.vga")
VGA="${VGA//[![:print:]]/}"
fi
fi
restoreState "VGA" "$STORAGE/windows.vga" || return 1
restoreState "USB" "$STORAGE/windows.usb" || return 1
restoreState "ADAPTER" "$STORAGE/windows.net" || return 1
restoreState "DISK_TYPE" "$STORAGE/windows.type" || return 1
restoreState "BOOT_MODE" "$STORAGE/windows.mode" "Y" || return 1
if [ -s "$STORAGE/windows.usb" ] && [ -f "$STORAGE/windows.usb" ]; then
if [ -z "${USB:-}" ]; then
USB=$(<"$STORAGE/windows.usb")
USB="${USB//[![:print:]]/}"
fi
fi
if [ -s "$STORAGE/windows.net" ] && [ -f "$STORAGE/windows.net" ]; then
if [ -z "${ADAPTER:-}" ]; then
ADAPTER=$(<"$STORAGE/windows.net")
ADAPTER="${ADAPTER//[![:print:]]/}"
fi
fi
if [ -s "$STORAGE/windows.type" ] && [ -f "$STORAGE/windows.type" ]; then
if [ -z "${DISK_TYPE:-}" ]; then
DISK_TYPE=$(<"$STORAGE/windows.type")
DISK_TYPE="${DISK_TYPE//[![:print:]]/}"
fi
fi
if [ -s "$STORAGE/windows.mode" ] && [ -f "$STORAGE/windows.mode" ]; then
BOOT_MODE=$(<"$STORAGE/windows.mode")
BOOT_MODE="${BOOT_MODE//[![:print:]]/}"
fi
if [ -s "$STORAGE/windows.old" ] && [ -f "$STORAGE/windows.old" ]; then
if [[ "${PLATFORM,,}" == "x64" ]]; then
MACHINE=$(<"$STORAGE/windows.old")
MACHINE="${MACHINE//[![:print:]]/}"
fi
restoreState "MACHINE" "$STORAGE/windows.old" "Y" || return 1
fi
return 0
+333 -233
View File
@@ -1,55 +1,53 @@
#!/usr/bin/env bash
set -Eeuo pipefail
handle_curl_error() {
handleCurlError() {
local error_code="$1"
local server_name="$2"
local code="$1"
local server="$2"
case "$error_code" in
case "$code" in
1) error "Unsupported protocol!" ;;
2) error "Failed to initialize curl!" ;;
3) error "The URL format is malformed!" ;;
5) error "Failed to resolve address of proxy host!" ;;
6) error "Failed to resolve $server_name servers! Is there an Internet connection?" ;;
7) error "Failed to contact $server_name servers! Is there an Internet connection or is the server down?" ;;
8) error "$server_name servers returned a malformed HTTP response!" ;;
6) error "Failed to resolve $server servers! Is there an Internet connection?" ;;
7) error "Failed to contact $server servers! Is there an Internet connection or is the server down?" ;;
8) error "$server servers returned a malformed HTTP response!" ;;
16) error "A problem was detected in the HTTP2 framing layer!" ;;
22) error "$server_name servers returned a failing HTTP status code!" ;;
22) error "$server servers returned a failing HTTP status code!" ;;
23) error "Failed at writing Windows media to disk! Out of disk space or permission error?" ;;
26) error "Failed to read Windows media from disk!" ;;
27) error "Ran out of memory during download!" ;;
28) error "Connection timed out to $server_name server!" ;;
35) error "SSL connection error from $server_name server!" ;;
28) error "Connection timed out to $server server!" ;;
35) error "SSL connection error from $server server!" ;;
36) error "Failed to continue earlier download!" ;;
52) error "Received no data from the $server_name server!" ;;
63) error "$server_name servers returned an unexpectedly large response!" ;;
# POSIX defines exit statuses 1-125 as usable by us
# https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_08_02
$((error_code <= 125)))
# Must be some other server or network error (possibly with this specific request/file)
# This is when accounting for all possible errors in the curl manual assuming a correctly formed curl command and an HTTP(S) request, using only the curl features we're using, and a sane build
error "Miscellaneous server or network error, reason: $error_code"
;;
126 | 127 ) error "Curl command not found!" ;;
# Exit statuses are undefined by POSIX beyond this point
52) error "Received no data from the $server server!" ;;
63) error "$server servers returned an unexpectedly large response!" ;;
126) error "Curl command cannot be executed!" ;;
127) error "Curl command not found!" ;;
*)
case "$(kill -l "$error_code")" in
# Signals defined to exist by POSIX:
# https://pubs.opengroup.org/onlinepubs/009695399/basedefs/signal.h.html
if (( code <= 125 )); then
# Must be some other server or network error (possibly with this specific request/file)
# This is when accounting for all possible errors in the curl manual assuming a correctly formed
# curl command and an HTTP(S) request, using only the curl features we're using, and a sane build.
error "Miscellaneous server or network error, reason: $code"
else
case "$(kill -l "$code" 2>/dev/null || true)" in
INT) error "Curl was interrupted!" ;;
# There could be other signals but these are most common
SEGV | ABRT ) error "Curl crashed! Please report any core dumps to curl developers." ;;
*) error "Curl terminated due to fatal signal $error_code !" ;;
SEGV | ABRT) error "Curl crashed! Please report any core dumps to curl developers." ;;
*) error "Curl terminated due to fatal signal $code !" ;;
esac
fi
;;
esac
return 1
}
get_agent() {
getAgent() {
local user_agent
local browser_version
# Determine approximate latest Firefox release
browser_version="$((124 + ($(date +%s) - 1710892800) / 2419200))"
@@ -58,202 +56,249 @@ get_agent() {
return 0
}
download_windows() {
downloadWindows() {
local id="$1"
local lang="$2"
local desc="$3"
local sku_id=""
local sku_url=""
local iso_url=""
local iso_json=""
local language=""
local session_id=""
local user_agent=""
local download_type=""
local windows_version=""
local iso_download_link=""
local download_page_html=""
local product_edition_id=""
local language_skuid_json=""
local rc=0
local ovToken="" ovTicks="" ovTime=""
local skuId="" skuUrl="" skuJson=""
local linkUrl="" linkJson="" link=""
local language="" orgId=""
local instance="" vlsUrl="" ovUrl="" ovData=""
local session="" agent="" type=""
local winVer="" page="" productId=""
local profile="606624d44113"
user_agent=$(get_agent)
agent=$(getAgent)
language=$(getLanguage "$lang" "name")
case "${id,,}" in
"win11x64" ) windows_version="11" && download_type="1" ;;
"win10x64" ) windows_version="10" && download_type="1" ;;
"win11arm64" ) windows_version="11arm64" && download_type="2" ;;
"win11x64" ) winVer="11" && type="1" ;;
"win11arm64" ) winVer="11arm64" && type="2" ;;
* ) error "Invalid VERSION specified, value \"$id\" is not recognized!" && return 1 ;;
esac
local url="https://www.microsoft.com/en-us/software-download/windows$windows_version"
[[ "${id,,}" == "win10"* ]] && url+="ISO"
local url="https://www.microsoft.com/en-us/software-download/windows$winVer"
# uuidgen: For MacOS (installed by default) and other systems (e.g. with no /proc) that don't have a kernel interface for generating random UUIDs
session_id=$(cat /proc/sys/kernel/random/uuid 2> /dev/null || uuidgen --random)
session_id="${session_id//[![:print:]]/}"
if ! session=$(cat /proc/sys/kernel/random/uuid 2> /dev/null || uuidgen --random); then
error "Failed to generate session ID!"
return 1
fi
session="${session//[![:print:]]/}"
if [ -z "$session" ]; then
error "Failed to generate session ID!"
return 1
fi
# Get product edition ID for latest release of given Windows version
# Product edition ID: This specifies both the Windows release (e.g. 22H2) and edition ("multi-edition" is default, either Home/Pro/Edu/etc., we select "Pro" in the answer files) in one number
# This is the *only* request we make that Fido doesn't. Fido manually maintains a list of all the Windows release/edition product edition IDs in its script (see: $WindowsVersions array). This is helpful for downloading older releases (e.g. Windows 10 1909, 21H1, etc.) but we always want to get the newest release which is why we get this value dynamically
# Also, keeping a "$WindowsVersions" array like Fido does would be way too much of a maintenance burden
# Remove "Accept" header that curl sends by default
[[ "$DEBUG" == [Yy1]* ]] && echo "Parsing download page: ${url}"
download_page_html=$(curl --silent --max-time 30 --user-agent "$user_agent" --header "Accept:" --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url") || {
handle_curl_error "$?" "Microsoft"
enabled "$DEBUG" && echo "Parsing download page: ${url}"
page=$(curl --silent --max-time 30 --user-agent "$agent" --header "Accept:" --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url") || {
handleCurlError "$?" "Microsoft"
return $?
}
[[ "$DEBUG" == [Yy1]* ]] && echo -n "Getting Product edition ID: "
product_edition_id=$(echo "$download_page_html" | grep -Eo '<option value="[0-9]+">Windows' | cut -d '"' -f 2 | head -n 1 | tr -cd '0-9' | head -c 16)
[[ "$DEBUG" == [Yy1]* ]] && echo "$product_edition_id"
enabled "$DEBUG" && echo -n "Getting Product edition ID: "
productId=$(echo "$page" | grep -Eo '<option value="[0-9]+">Windows' | cut -d '"' -f 2 | head -n 1 | tr -cd '0-9' | head -c 16)
enabled "$DEBUG" && echo "$productId"
if [ -z "$product_edition_id" ]; then
if [ -z "$productId" ]; then
error "Product edition ID not found!"
return 1
fi
[[ "$DEBUG" == [Yy1]* ]] && echo "Permit Session ID: $session_id"
# Microsoft download "protection" requires the sessionId to be whitelisted through vlscppe.microsoft.com/tags
orgId="y6jn8c31"
vlsUrl="https://vlscppe.microsoft.com/tags?org_id=$orgId&session_id=$session"
enabled "$DEBUG" && echo "Getting Session ID: $session"
# Permit Session ID
curl --silent --max-time 30 --output /dev/null --user-agent "$user_agent" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "https://vlscppe.microsoft.com/tags?org_id=y6jn8c31&session_id=$session_id" || {
curl --silent --max-time 30 --output /dev/null --user-agent "$agent" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$vlsUrl" || {
# This should only happen if there's been some change to how this API works
handle_curl_error "$?" "Microsoft"
handleCurlError "$?" "Microsoft"
return $?
}
[[ "$DEBUG" == [Yy1]* ]] && echo -n "Getting language SKU ID: "
sku_url="https://www.microsoft.com/software-download-connector/api/getskuinformationbyproductedition?profile=$profile&ProductEditionId=$product_edition_id&SKU=undefined&friendlyFileName=undefined&Locale=en-US&sessionID=$session_id"
language_skuid_json=$(curl --silent --max-time 30 --request GET --user-agent "$user_agent" --referer "$url" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$sku_url") || {
handle_curl_error "$?" "Microsoft"
# Microsoft download "protection" also requires an ov-df.microsoft.com request/reply
# 1) Request mdt.js to get w and rticks. InstanceId is (currently) constant.
instance="560dc9f3-1aa5-4a2f-b63c-9e18f8d0e175"
ovUrl="https://ov-df.microsoft.com/mdt.js?instanceId=$instance&PageId=si&session_id=$session"
enabled "$DEBUG" && echo -n "Getting OV data: "
ovData=$(curl --silent --max-time 30 --user-agent "$agent" --header "Accept:" --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$ovUrl") || {
handleCurlError "$?" "Microsoft"
return $?
}
{ sku_id=$(echo "$language_skuid_json" | jq --arg LANG "$language" -r '.Skus[] | select(.Language==$LANG).Id') 2>/dev/null; rc=$?; } || :
if [[ $ovData =~ [\?\&]w=([A-Fa-f0-9]+) ]]; then
ovToken="${BASH_REMATCH[1]}"
fi
if [ -z "$sku_id" ] || [[ "${sku_id,,}" == "null" ]] || (( rc != 0 )); then
if [[ $ovData =~ rticks=\"\+?([0-9]+) ]]; then
ovTicks="${BASH_REMATCH[1]}"
fi
if [[ -z $ovToken || -z $ovTicks ]]; then
error "Could not extract ov-df data from Microsoft server!"
return 1
fi
enabled "$DEBUG" && echo "$ovToken"
sleep 0.2
# 2) Send a reply with session ID, current epoch and previously retrieved w and rticks
ovTime=$(date +%s%3N)
ovUrl="https://ov-df.microsoft.com/?session_id=$session&CustomerId=$instance&PageId=si&w=$ovToken&mdt=$ovTime&rticks=$ovTicks"
enabled "$DEBUG" && echo "Sending OV reply: $instance"
curl --silent --max-time 30 --output /dev/null --user-agent "$agent" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$ovUrl" || {
# This should only happen if there's been some change to how this API works
handleCurlError "$?" "Microsoft"
return $?
}
enabled "$DEBUG" && echo -n "Getting language SKU ID: "
skuUrl="https://www.microsoft.com/software-download-connector/api/getskuinformationbyproductedition?profile=$profile&ProductEditionId=$productId&SKU=undefined&friendlyFileName=undefined&Locale=en-US&sessionID=$session"
skuJson=$(curl --silent --max-time 30 --request GET --user-agent "$agent" --referer "$url" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$skuUrl") || {
handleCurlError "$?" "Microsoft"
return $?
}
{ skuId=$(echo "$skuJson" | jq --arg LANG "$language" -r '.Skus[] | select(.Language==$LANG).Id') 2>/dev/null; rc=$?; } || :
if [ -z "$skuId" ] || [[ "${skuId,,}" == "null" ]] || (( rc != 0 )); then
language=$(getLanguage "$lang" "desc")
error "No download in the $language language available for $desc!"
return 1
fi
[[ "$DEBUG" == [Yy1]* ]] && echo "$sku_id"
[[ "$DEBUG" == [Yy1]* ]] && echo "Getting ISO download link..."
enabled "$DEBUG" && echo "$skuId"
enabled "$DEBUG" && echo "Getting ISO download link..."
# Get ISO download link
# If any request is going to be blocked by Microsoft it's always this last one (the previous requests always seem to succeed)
iso_url="https://www.microsoft.com/software-download-connector/api/GetProductDownloadLinksBySku?profile=$profile&ProductEditionId=undefined&SKU=$sku_id&friendlyFileName=undefined&Locale=en-US&sessionID=$session_id"
iso_json=$(curl --silent --max-time 30 --request GET --user-agent "$user_agent" --referer "$url" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$iso_url")
linkUrl="https://www.microsoft.com/software-download-connector/api/GetProductDownloadLinksBySku?profile=$profile&ProductEditionId=undefined&SKU=$skuId&friendlyFileName=undefined&Locale=en-US&sessionID=$session"
linkJson=$(curl --silent --max-time 30 --request GET --user-agent "$agent" --referer "$url" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$linkUrl") || {
handleCurlError "$?" "Microsoft"
return $?
}
if ! [ "$iso_json" ]; then
if ! [ "$linkJson" ]; then
# This should only happen if there's been some change to how this API works
error "Microsoft servers gave us an empty response to our request for an automated download."
return 1
fi
if echo "$iso_json" | grep -q "Sentinel marked this request as rejected."; then
if echo "$linkJson" | grep -q "Sentinel marked this request as rejected."; then
error "Microsoft blocked the automated download request based on your IP address."
return 1
fi
if echo "$iso_json" | grep -q "We are unable to complete your request at this time."; then
error "Microsoft blocked the automated download request based on your IP address."
if echo "$linkJson" | grep -q "We are unable to complete your request at this time."; then
error "Microsoft blocked the automated download request."
return 1
fi
{ iso_download_link=$(echo "$iso_json" | jq --argjson TYPE "$download_type" -r '.ProductDownloadOptions[] | select(.DownloadType==$TYPE).Uri') 2>/dev/null; rc=$?; } || :
{ link=$(echo "$linkJson" | jq --argjson TYPE "$type" -r '.ProductDownloadOptions[] | select(.DownloadType==$TYPE).Uri') 2>/dev/null; rc=$?; } || :
if [ -z "$iso_download_link" ] || [[ "${iso_download_link,,}" == "null" ]] || (( rc != 0 )); then
error "Microsoft servers gave us no download link to our request for an automated download!"
info "Response: $iso_json"
if [ -z "$link" ] || [[ "${link,,}" == "null" ]] || (( rc != 0 )); then
error "Microsoft server gave us no download link to our request for an automated download!"
info "Response: $linkJson"
return 1
fi
MIDO_URL="$iso_download_link"
MIDO_URL="$link"
return 0
}
download_windows_eval() {
downloadWindowsEval() {
local id="$1"
local lang="$2"
local desc="$3"
local filter=""
local culture=""
local compare=""
local language=""
local user_agent=""
local enterprise_type=""
local windows_version=""
local filter="" culture="" compare="" language=""
local agent="" type="" winVer=""
case "${id,,}" in
"win11${PLATFORM,,}-enterprise-eval" )
enterprise_type="enterprise"
windows_version="windows-11-enterprise" ;;
type="enterprise"
winVer="windows-11-enterprise" ;;
"win11${PLATFORM,,}-enterprise-iot-eval" )
enterprise_type="iot"
windows_version="windows-11-iot-enterprise-ltsc-eval" ;;
type="iot"
winVer="windows-11-iot-enterprise-ltsc-eval" ;;
"win11${PLATFORM,,}-enterprise-ltsc-eval" )
enterprise_type="iot"
windows_version="windows-11-iot-enterprise-ltsc-eval" ;;
"win10${PLATFORM,,}-enterprise-eval" )
enterprise_type="enterprise"
windows_version="windows-10-enterprise" ;;
"win10${PLATFORM,,}-enterprise-ltsc-eval" )
enterprise_type="ltsc"
windows_version="windows-10-enterprise" ;;
type="iot"
winVer="windows-11-iot-enterprise-ltsc-eval" ;;
"win2025-eval" )
enterprise_type="server"
windows_version="windows-server-2025" ;;
type="server"
winVer="windows-server-2025" ;;
"win2022-eval" )
enterprise_type="server"
windows_version="windows-server-2022" ;;
type="server"
winVer="windows-server-2022" ;;
"win2019-hv" )
enterprise_type="server"
windows_version="hyper-v-server-2019" ;;
type="server"
winVer="hyper-v-server-2019" ;;
"win2019-eval" )
enterprise_type="server"
windows_version="windows-server-2019" ;;
type="server"
winVer="windows-server-2019" ;;
"win2016-eval" )
enterprise_type="server"
windows_version="windows-server-2016" ;;
type="server"
winVer="windows-server-2016" ;;
"win2012r2-eval" )
enterprise_type="server"
windows_version="windows-server-2012-r2" ;;
type="server"
winVer="windows-server-2012-r2" ;;
* )
error "Invalid VERSION specified, value \"$id\" is not recognized!" && return 1 ;;
esac
user_agent=$(get_agent)
agent=$(getAgent)
culture=$(getLanguage "$lang" "culture")
local country="${culture#*-}"
local iso_download_page_html=""
local url="https://www.microsoft.com/en-us/evalcenter/download-$windows_version"
local link=""
local links=""
local page=""
local url="https://www.microsoft.com/en-us/evalcenter/download-$winVer"
[[ "$DEBUG" == [Yy1]* ]] && echo "Parsing download page: ${url}"
iso_download_page_html=$(curl --silent --max-time 30 --user-agent "$user_agent" --location --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url") || {
handle_curl_error "$?" "Microsoft"
enabled "$DEBUG" && echo "Parsing download page: ${url}"
page=$(curl --silent --max-time 30 --user-agent "$agent" --location --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url") || {
handleCurlError "$?" "Microsoft"
return $?
}
if ! [ "$iso_download_page_html" ]; then
if ! [ "$page" ]; then
# This should only happen if there's been some change to where this download page is located
error "Windows server download page gave us an empty response"
return 1
fi
[[ "$DEBUG" == [Yy1]* ]] && echo "Getting download link.."
enabled "$DEBUG" && echo "Getting download link.."
filter="https://go.microsoft.com/fwlink/?linkid=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country,,}"
if ! echo "$iso_download_page_html" | grep -io "$filter" > /dev/null; then
if ! echo "$page" | grep -io "$filter" > /dev/null; then
filter="https://go.microsoft.com/fwlink/p/?linkid=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country,,}"
fi
iso_download_links=$(echo "$iso_download_page_html" | grep -io "$filter") || {
links=$(echo "$page" | grep -io "$filter") || {
# This should only happen if there's been some change to the download endpoint web address
if [[ "${lang,,}" == "en" || "${lang,,}" == "en-"* ]]; then
error "Windows server download page gave us no download link!"
@@ -264,74 +309,74 @@ download_windows_eval() {
return 1
}
case "$enterprise_type" in
case "$type" in
"iot" | "ltsc" )
case "${PLATFORM,,}" in
"x64" )
if [[ "$windows_version" != "windows-10"* ]]; then
iso_download_link=$(echo "$iso_download_links" | head -n 1)
if [[ "$winVer" != "windows-10"* ]]; then
link=$(echo "$links" | head -n 1)
else
iso_download_link=$(echo "$iso_download_links" | head -n 4 | tail -n 1)
link=$(echo "$links" | head -n 4 | tail -n 1)
fi ;;
"arm64" )
iso_download_link=$(echo "$iso_download_links" | head -n 2 | tail -n 1) ;;
link=$(echo "$links" | head -n 2 | tail -n 1) ;;
esac ;;
"enterprise" )
case "${PLATFORM,,}" in
"x64" )
if [[ "$windows_version" != "windows-10"* ]]; then
iso_download_link=$(echo "$iso_download_links" | head -n 1)
if [[ "$winVer" != "windows-10"* ]]; then
link=$(echo "$links" | head -n 1)
else
iso_download_link=$(echo "$iso_download_links" | head -n 2 | tail -n 1)
link=$(echo "$links" | head -n 2 | tail -n 1)
fi ;;
"arm64" )
iso_download_link=$(echo "$iso_download_links" | head -n 2 | tail -n 1) ;;
link=$(echo "$links" | head -n 2 | tail -n 1) ;;
esac ;;
"server" )
case "${PLATFORM,,}" in
"x64" )
iso_download_link=$(echo "$iso_download_links" | head -n 1) ;;
link=$(echo "$links" | head -n 1) ;;
esac ;;
* )
error "Invalid type specified, value \"$enterprise_type\" is not recognized!" && return 1 ;;
error "Invalid type specified, value \"$type\" is not recognized!" && return 1 ;;
esac
[ -z "$iso_download_link" ] && error "Could not parse download link from page!" && return 1
[ -z "$link" ] && error "Could not parse download link from page!" && return 1
# Follow redirect so proceeding log message is useful
# This is a request we make that Fido doesn't
iso_download_link=$(curl --silent --max-time 30 --user-agent "$user_agent" --location --output /dev/null --silent --write-out "%{url_effective}" --head --fail --proto =https --tlsv1.2 --http1.1 -- "$iso_download_link") || {
link=$(curl --silent --max-time 30 --user-agent "$agent" --location --output /dev/null --silent --write-out "%{url_effective}" --head --fail --proto =https --tlsv1.2 --http1.1 -- "$link") || {
# This should only happen if the Microsoft servers are down
handle_curl_error "$?" "Microsoft"
handleCurlError "$?" "Microsoft"
return $?
}
case "${PLATFORM,,}" in
"x64" )
if [[ "${iso_download_link,,}" != *"x64"* ]]; then
echo "Found download link: $iso_download_link"
if [[ "${link,,}" != *"x64"* ]]; then
echo "Found download link: $link"
error "Download link is for the wrong platform? Please report this at $SUPPORT/issues"
return 1
fi ;;
"arm64" )
if [[ "${iso_download_link,,}" != *"a64"* && "${iso_download_link,,}" != *"arm64"* ]]; then
if [[ "$DEBUG" == [Yy1]* ]]; then
echo "Found download link: $iso_download_link"
if [[ "${link,,}" != *"a64"* && "${link,,}" != *"arm64"* ]]; then
if enabled "$DEBUG"; then
echo "Found download link: $link"
echo "Link for ARM platform currently not available!"
fi
return 1
fi ;;
esac
if [[ "$DEBUG" == [Yy1]* && "$VERIFY" == [Yy1]* && "${lang,,}" == "en"* ]]; then
if enabled "$DEBUG" && enabled "$VERIFY" && [[ "${lang,,}" == "en"* ]]; then
compare=$(getMido "$id" "$lang" "")
if [[ "${iso_download_link,,}" != "${compare,,}" ]]; then
if [[ "${link,,}" != "${compare,,}" ]]; then
echo "Retrieved link does not match the fixed link: $compare"
fi
fi
MIDO_URL="$iso_download_link"
MIDO_URL="$link"
return 0
}
@@ -349,7 +394,8 @@ getWindows() {
info "$msg" && html "$msg"
case "${version,,}" in
"win2008r2" | "win81${PLATFORM,,}"* | "win11${PLATFORM,,}-enterprise-iot"* | "win11${PLATFORM,,}-enterprise-ltsc"* )
"win2008r2" | "win81${PLATFORM,,}"* | "win10${PLATFORM,,}-enterprise"* | \
"win11${PLATFORM,,}-enterprise-iot"* | "win11${PLATFORM,,}-enterprise-ltsc"* )
if [[ "${lang,,}" != "en" && "${lang,,}" != "en-"* ]]; then
error "No download in the $language language available for $edition!"
MIDO_URL="" && return 1
@@ -367,16 +413,16 @@ getWindows() {
esac
case "${version,,}" in
"win10${PLATFORM,,}" | "win11${PLATFORM,,}" )
download_windows "$version" "$lang" "$edition" && return 0
"win11${PLATFORM,,}" )
downloadWindows "$version" "$lang" "$edition" && return 0
;;
"win11${PLATFORM,,}-enterprise"* | "win10${PLATFORM,,}-enterprise"* )
download_windows_eval "$version" "$lang" "$edition" && return 0
"win11${PLATFORM,,}-enterprise"* )
downloadWindowsEval "$version" "$lang" "$edition" && return 0
;;
"win2025-eval" | "win2022-eval" | "win2019-eval" | "win2019-hv" | "win2016-eval" | "win2012r2-eval" )
download_windows_eval "$version" "$lang" "$edition" && return 0
downloadWindowsEval "$version" "$lang" "$edition" && return 0
;;
"win81${PLATFORM,,}-enterprise"* | "win2008r2" )
"win2008r2" | "win81${PLATFORM,,}"* | "win10${PLATFORM,,}-enterprise"* )
;;
* ) error "Invalid VERSION specified, value \"$version\" is not recognized!" ;;
esac
@@ -461,18 +507,29 @@ getCatalog() {
return 0
}
getXmlTag() {
local tag="$1"
local file="$2"
xmllint --nonet --xpath "//$tag" "$file" 2>/dev/null | sed -E -e "s/<[\/]?$tag>//g" || true
return 0
}
getESD() {
local dir="$1"
local version="$2"
local lang="$3"
local desc="$4"
local file
local result
local culture
local language
local edition
local catalog
local rc=0
local file result culture
local language edition catalog
local xmlFile="products.xml"
local esdFile="esd_edition.xml"
local filterFile="products_filter.xml"
local query
file=$(getCatalog "$version" "file")
catalog=$(getCatalog "$version" "url")
@@ -480,22 +537,20 @@ getESD() {
edition=$(getCatalog "$version" "edition")
if [ -z "$file" ] || [ -z "$catalog" ]; then
error "Invalid VERSION specified, value \"$version\" is not recognized!" && return 1
error "Invalid VERSION specified, value \"$version\" is not recognized!"
return 1
fi
local msg="Downloading catalog..."
local msg="Downloading catalog from the Microsoft servers..."
info "$msg" && html "$msg"
rm -rf "$dir"
if ! makeDir "$dir"; then
error "Failed to create directory \"$dir\" !" && return 1
error "Failed to create directory \"$dir\" !"
return 1
fi
local xFile="products.xml"
local eFile="esd_edition.xml"
local fFile="products_filter.xml"
{ wget "$catalog" -O "$dir/$file" -q --timeout=30 --no-http-keep-alive; rc=$?; } || :
msg="Failed to download $catalog"
@@ -506,75 +561,79 @@ getESD() {
if [[ "$file" == *".xml" ]]; then
mv -f "$dir/$file" "$dir/$xFile"
if ! mv -f "$dir/$file" "$dir/$xmlFile"; then
error "Failed to rename $file to $xmlFile."
return 1
fi
else
cd "$dir"
if ! cabextract "$file" > /dev/null; then
cd /run
error "Failed to extract $file!" && return 1
if ! (
cd "$dir" || exit 1
cabextract "$file" > /dev/null
); then
error "Failed to extract $file!"
return 1
fi
cd /run
fi
if [ ! -s "$dir/$xFile" ]; then
error "Failed to find $xFile in $file!" && return 1
if [ ! -s "$dir/$xmlFile" ]; then
error "Failed to find $xmlFile in $file!"
return 1
fi
local edQuery='//File[Architecture="'${PLATFORM,,}'"]'"${edition}"''
result=$(xmllint --nonet --xpath "${edQuery}" "$dir/$xFile" 2>/dev/null)
query='//File[Architecture="'${PLATFORM,,}'"]'"${edition}"''
result=$(xmllint --nonet --xpath "${query}" "$dir/$xmlFile" 2>/dev/null || true)
if [ -z "$result" ]; then
edQuery='//File[Architecture="'${PLATFORM^^}'"]'"${edition}"''
result=$(xmllint --nonet --xpath "${edQuery}" "$dir/$xFile" 2>/dev/null)
query='//File[Architecture="'${PLATFORM^^}'"]'"${edition}"''
result=$(xmllint --nonet --xpath "${query}" "$dir/$xmlFile" 2>/dev/null || true)
if [ -z "$result" ]; then
desc=$(printEdition "$version" "$desc")
language=$(getLanguage "$lang" "desc")
error "No download link available for $desc!" && return 1
error "No download link available for $desc!"
return 1
fi
fi
echo -e '<Catalog>' > "$dir/$fFile"
echo "$result" >> "$dir/$fFile"
echo -e '</Catalog>'>> "$dir/$fFile"
echo -e '<Catalog>' > "$dir/$filterFile"
echo "$result" >> "$dir/$filterFile"
echo -e '</Catalog>'>> "$dir/$filterFile"
result=$(xmllint --nonet --xpath "//File[LanguageCode=\"${culture,,}\"]" "$dir/$fFile" 2>/dev/null)
result=$(xmllint --nonet --xpath "//File[LanguageCode=\"${culture,,}\"]" "$dir/$filterFile" 2>/dev/null || true)
if [ -z "$result" ]; then
desc=$(printEdition "$version" "$desc")
language=$(getLanguage "$lang" "desc")
error "No download in the $language language available for $desc!" && return 1
error "No download in the $language language available for $desc!"
return 1
fi
echo "$result" > "$dir/$eFile"
echo "$result" > "$dir/$esdFile"
local tag="FilePath"
ESD=$(xmllint --nonet --xpath "//$tag" "$dir/$eFile" | sed -E -e "s/<[\/]?$tag>//g" 2>/dev/null)
ESD=$(getXmlTag "FilePath" "$dir/$esdFile")
if [ -z "$ESD" ]; then
error "Failed to find ESD URL in $eFile!" && return 1
error "Failed to find ESD URL in $esdFile!"
return 1
fi
tag="Sha1"
ESD_SUM=$(xmllint --nonet --xpath "//$tag" "$dir/$eFile" | sed -E -e "s/<[\/]?$tag>//g" 2>/dev/null)
ESD_SUM=$(getXmlTag "Sha1" "$dir/$esdFile")
if [ -z "$ESD_SUM" ]; then
error "Failed to find ESD checksum in $eFile!" && return 1
error "Failed to find ESD checksum in $esdFile!"
return 1
fi
tag="Size"
ESD_SIZE=$(xmllint --nonet --xpath "//$tag" "$dir/$eFile" | sed -E -e "s/<[\/]?$tag>//g" 2>/dev/null)
ESD_SIZE=$(getXmlTag "Size" "$dir/$esdFile")
if [ -z "$ESD_SIZE" ]; then
error "Failed to find ESD filesize in $eFile!" && return 1
error "Failed to find ESD filesize in $esdFile!"
return 1
fi
rm -rf "$dir"
@@ -601,7 +660,7 @@ verifyFile() {
local check="$4"
if [ -n "$size" ] && [[ "$total" != "$size" && "$size" != "0" ]]; then
if [[ "$VERIFY" == [Yy1]* || "$DEBUG" == [Yy1]* ]]; then
if enabled "$VERIFY" || enabled "$DEBUG"; then
warn "The downloaded file has a different size ( $total bytes) than expected ( $size bytes). Please report this at $SUPPORT/issues"
fi
fi
@@ -610,16 +669,22 @@ verifyFile() {
local algo="SHA256"
[ -z "$check" ] && return 0
[[ "$VERIFY" != [Yy1]* ]] && return 0
! enabled "$VERIFY" && return 0
[[ "${#check}" == "40" ]] && algo="SHA1"
local msg="Verifying downloaded ISO..."
info "$msg" && html "$msg"
if [[ "${algo,,}" != "sha256" ]]; then
hash=$(sha1sum "$iso" | cut -f1 -d' ')
if ! hash=$(sha1sum "$iso" | cut -f1 -d' '); then
error "Failed to calculate SHA1 checksum for $iso!"
return 1
fi
else
hash=$(sha256sum "$iso" | cut -f1 -d' ')
if ! hash=$(sha256sum "$iso" | cut -f1 -d' '); then
error "Failed to calculate SHA256 checksum for $iso!"
return 1
fi
fi
if [[ "$hash" == "$check" ]]; then
@@ -641,11 +706,17 @@ downloadFile() {
local msg="Downloading $desc"
local rc total total_gb progress domain dots agent space folder
agent=$(get_agent)
agent=$(getAgent)
if [ -n "$size" ] && [[ "$size" != "0" ]]; then
folder=$(dirname -- "$iso")
space=$(df --output=avail -B 1 "$folder" | tail -n 1)
if ! space=$(df --output=avail -B 1 "$folder" | tail -n 1); then
error "Failed to check free space in $folder!"
return 1
fi
total_gb=$(formatBytes "$space")
(( size > space )) && error "Not enough free space to download file, only $total_gb left!" && return 1
fi
@@ -669,21 +740,32 @@ downloadFile() {
fi
info "$msg..."
[[ "$DEBUG" == [Yy1]* ]] && echo "Downloading: $url"
enabled "$DEBUG" && echo "Downloading: $url"
{ wget "$url" -O "$iso" --continue -q --timeout=30 --no-http-keep-alive --user-agent "$agent" --show-progress "$progress"; rc=$?; } || :
fKill "progress.sh"
if (( rc == 0 )) && [ -f "$iso" ]; then
total=$(stat -c%s "$iso")
total_gb=$(formatBytes "$total")
if [ "$total" -lt 100000000 ]; then
error "Invalid download link: $url (is only $total_gb ?). Please report this at $SUPPORT/issues" && return 1
if ! total=$(stat -c%s "$iso"); then
error "Failed to determine downloaded file size: $iso"
return 1
fi
total_gb=$(formatBytes "$total")
if [ "$total" -lt 100000000 ]; then
error "Invalid download link: $url (is only $total_gb ?). Please report this at $SUPPORT/issues"
return 1
fi
verifyFile "$iso" "$size" "$total" "$sum" || return 1
isCompressed "$url" && UNPACK="Y"
html "Download finished successfully..." && return 0
html "Download finished successfully..."
return 0
fi
msg="Failed to download $url"
@@ -711,6 +793,25 @@ delay() {
return 0
}
tryDownload() {
local iso="$1"
local url="$2"
local sum="$3"
local size="$4"
local lang="$5"
local desc="$6"
local seconds="$7"
rm -f "$iso"
downloadFile "$iso" "$url" "$sum" "$size" "$lang" "$desc" && return 0
delay "$seconds"
downloadFile "$iso" "$url" "$sum" "$size" "$lang" "$desc" && return 0
rm -f "$iso"
return 1
}
downloadImage() {
local iso="$1"
@@ -719,24 +820,20 @@ downloadImage() {
local tried="n"
local success="n"
local seconds="5"
local url sum size base desc language
local url sum size base desc language i
if [[ "${version,,}" == "http"* ]]; then
base=$(basename "$iso")
desc=$(fromFile "$base")
rm -f "$iso"
downloadFile "$iso" "$version" "" "" "" "$desc" && return 0
delay "$seconds"
downloadFile "$iso" "$version" "" "" "" "$desc" && return 0
rm -f "$iso"
tryDownload "$iso" "$version" "" "" "" "$desc" "$seconds" && return 0
return 1
fi
if ! validVersion "$version" "en"; then
error "Invalid VERSION specified, value \"$version\" is not recognized!" && return 1
error "Invalid VERSION specified, value \"$version\" is not recognized!"
return 1
fi
desc=$(printVersion "$version" "")
@@ -745,7 +842,8 @@ downloadImage() {
language=$(getLanguage "$lang" "desc")
if ! validVersion "$version" "$lang"; then
desc=$(printEdition "$version" "$desc")
error "The $language language version of $desc is not available, please switch to English." && return 1
error "The $language language version of $desc is not available, please switch to English."
return 1
fi
desc+=" in $language"
fi
@@ -763,14 +861,21 @@ downloadImage() {
fi
if [[ "$success" == "y" ]]; then
size=$(getMido "$version" "$lang" "size" )
sum=$(getMido "$version" "$lang" "sum")
rm -f "$iso"
downloadFile "$iso" "$MIDO_URL" "$sum" "$size" "$lang" "$desc" && return 0
delay "$seconds"
downloadFile "$iso" "$MIDO_URL" "$sum" "$size" "$lang" "$desc" && return 0
rm -f "$iso"
url=$(getMido "$version" "$lang" "")
sum=""
size=""
if [[ "${MIDO_URL%%\?*}" == "${url%%\?*}" ]]; then
size=$(getMido "$version" "$lang" "size")
sum=$(getMido "$version" "$lang" "sum")
else
enabled "$DEBUG" && echo "Skipping verification because the retrieved URL differs from the static URL."
fi
tryDownload "$iso" "$MIDO_URL" "$sum" "$size" "$lang" "$desc" "$seconds" && return 0
fi
fi
@@ -795,11 +900,10 @@ downloadImage() {
if [[ "$success" == "y" ]]; then
ISO="${ISO%.*}.esd"
rm -f "$ISO"
downloadFile "$ISO" "$ESD" "$ESD_SUM" "$ESD_SIZE" "$lang" "$desc" && return 0
delay "$seconds"
downloadFile "$ISO" "$ESD" "$ESD_SUM" "$ESD_SIZE" "$lang" "$desc" && return 0
rm -f "$ISO"
if tryDownload "$ISO" "$ESD" "$ESD_SUM" "$ESD_SIZE" "$lang" "$desc" "$seconds"; then
return 0
fi
ISO="$iso"
fi
@@ -819,11 +923,7 @@ downloadImage() {
size=$(getSize "$i" "$version" "$lang")
sum=$(getHash "$i" "$version" "$lang")
rm -f "$iso"
downloadFile "$iso" "$url" "$sum" "$size" "$lang" "$desc" && return 0
delay "$seconds"
downloadFile "$iso" "$url" "$sum" "$size" "$lang" "$desc" && return 0
rm -f "$iso"
tryDownload "$iso" "$url" "$sum" "$size" "$lang" "$desc" "$seconds" && return 0
fi
done
+238 -148
View File
@@ -1,26 +1,80 @@
#!/usr/bin/env bash
set -Eeuo pipefail
: "${QEMU_TIMEOUT:="110"}" # QEMU Termination timeout
: "${SHUTDOWN:="Y"}" # Graceful ACPI shutdown
: "${TIMEOUT:="115"}" # QEMU termination timeout
# Configure QEMU for graceful shutdown
QEMU_TERM=""
QEMU_DIR="/run/shm"
QEMU_PID="$QEMU_DIR/qemu.pid"
QEMU_PTY="$QEMU_DIR/qemu.pty"
QEMU_LOG="$QEMU_DIR/qemu.log"
QEMU_OUT="$QEMU_DIR/qemu.out"
QEMU_END="$QEMU_DIR/qemu.end"
rm -f "$QEMU_DIR/qemu.*"
touch "$QEMU_LOG"
_trap() {
func="$1" ; shift
for sig ; do
local func="$1"; shift
local sig
TRAP_PID=$BASHPID
for sig; do
trap "$func $sig" "$sig"
done
return 0
}
signalCode() {
local sig="$1"
case "$sig" in
SIGHUP) echo 129 ;;
SIGINT) echo 130 ;;
SIGQUIT) echo 131 ;;
SIGABRT) echo 134 ;;
SIGTERM) echo 143 ;;
*) echo 0 ;;
esac
return 0
}
displayReason() {
local reason="$1"
case "$reason" in
129 ) echo "SIGHUP" ;;
130 ) echo "SIGINT" ;;
131 ) echo "SIGQUIT" ;;
134 ) echo "SIGABRT" ;;
143 ) echo "SIGTERM" ;;
* ) echo "$reason" ;;
esac
return 0
}
readQemuPid() {
local -n _pid="$1"
if [ ! -s "$QEMU_PID" ] || ! read -r _pid <"$QEMU_PID"; then
return 1
fi
return 0
}
bootFailed() {
local fail=""
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
grep -Fq "No bootable device." "$QEMU_PTY" && fail="y"
grep -Fq "BOOTMGR is missing" "$QEMU_PTY" && fail="y"
fi
[ -n "$fail" ]
}
boot() {
@@ -29,23 +83,28 @@ boot() {
if [ -s "$QEMU_PTY" ]; then
if [ "$(stat -c%s "$QEMU_PTY")" -gt 7 ]; then
local fail=""
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
grep -Fq "No bootable device." "$QEMU_PTY" && fail="y"
grep -Fq "BOOTMGR is missing" "$QEMU_PTY" && fail="y"
fi
if [ -z "$fail" ]; then
info "Windows started successfully, visit http://127.0.0.1:8006/ to view the screen..."
if ! bootFailed; then
info "$(app) started successfully, visit http://127.0.0.1:8006/ to view the screen..."
return 0
fi
fi
fi
error "Timeout while waiting for QEMU to boot the machine!"
error "Timeout while waiting for QEMU to boot the machine, aborting..."
sKill "$QEMU_PID"
local pid
pid=$(<"$QEMU_PID")
{ kill -15 "$pid" || true; } 2>/dev/null
return 0
}
legacyBootReady() {
local last
local bios="Booting from Hard"
last=$(grep "^Booting.*" "$QEMU_PTY" | tail -1)
[[ "${last,,}" != "${bios,,}"* ]] && return 1
grep -Fq "No bootable device." "$QEMU_PTY" && return 1
grep -Fq "BOOTMGR is missing" "$QEMU_PTY" && return 1
return 0
}
@@ -56,13 +115,8 @@ ready() {
[ ! -s "$QEMU_PTY" ] && return 1
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
local last
local bios="Booting from Hard"
last=$(grep "^Booting.*" "$QEMU_PTY" | tail -1)
[[ "${last,,}" != "${bios,,}"* ]] && return 1
grep -Fq "No bootable device." "$QEMU_PTY" && return 1
grep -Fq "BOOTMGR is missing" "$QEMU_PTY" && return 1
return 0
legacyBootReady && return 0
return 1
fi
local line="\"Windows Boot Manager\""
@@ -71,171 +125,207 @@ ready() {
return 1
}
finish() {
forceKillQemu() {
local pid
local cnt=0
local reason=$1
local pids=(
"/var/run/tpm.pid"
"/var/run/wsdd.pid"
"/var/run/samba/nmbd.pid"
"/var/run/samba/smbd.pid"
)
local reason="$1"
local pid=""
local display
touch "$QEMU_END"
! readQemuPid pid && return 0
! isAlive "$pid" && return 0
if [ -s "$QEMU_PID" ]; then
display=$(displayReason "$reason")
error "Forcefully terminating $(app), reason: $display..."
{ disown "$pid" || :; kill -9 -- "$pid" || :; } 2>/dev/null
pid=$(<"$QEMU_PID")
echo && error "Forcefully terminating Windows, reason: $reason..."
{ kill -15 "$pid" || true; } 2>/dev/null
return 0
}
while isAlive "$pid"; do
markWindowsBooted() {
sleep 1
cnt=$((cnt+1))
# Workaround for zombie pid
[ ! -s "$QEMU_PID" ] && break
if [ "$cnt" == "5" ]; then
echo && error "QEMU did not terminate itself, forcefully killing process..."
{ kill -9 "$pid" || true; } 2>/dev/null
fi
done
fi
if [ ! -f "$STORAGE/windows.boot" ] && [ -f "$BOOT" ]; then
# Remove CD-ROM ISO after install
if ready; then
local file="$STORAGE/windows.boot"
if [ -f "$file" ] || [ ! -f "$BOOT" ]; then
return 0
fi
# Remove CD-ROM ISO after install
! ready && return 0
touch "$file"
! setOwner "$file" && error "Failed to set the owner for \"$file\" !"
if [[ "$REMOVE" != [Nn]* ]]; then
if ! disabled "$REMOVE"; then
rm -f "$BOOT" 2>/dev/null || true
fi
fi
fi
for pid in "${pids[@]}"; do
if [[ -s "$pid" ]]; then
pKill "$(cat "$pid")"
fi
rm -f "$pid"
done
return 0
}
cleanupHelpers() {
local pids=( "${SMB_PID:-}" "${NMB_PID:-}" "${DDN_PID:-}" \
"${TPM_PID:-}" "${WSD_PID:-}" "${WEB_PID:-}" \
"${AUX_PID:-}" "${AUDIO_PID:-}" "${PASST_PID:-}" \
"${DNSMASQ_PID:-}" "${BALLOONING_PID:-}" )
mKill "${pids[@]}"
closeNetwork
return 0
}
sleep 0.5
echo " Shutdown completed!"
finish() {
local reason=$1
touch "$QEMU_END"
forceKillQemu "$reason"
if [ ! -f "$STORAGE/windows.boot" ]; then
markWindowsBooted
fi
cleanupHelpers
if ! waitPidFile "$QEMU_PID" 10; then
warn "Timed out while waiting for $(app) to exit!"
fi
(( reason != 1 )) && echo && echo " Shutdown completed!"
exit "$reason"
}
terminal() {
normalizeTimeout() {
local dev=""
local term_grace=3 # seconds before loop ends to send SIGTERM
local cleanup_grace=3 # seconds reserved after the loop for cleanup
local min
if [ -s "$QEMU_OUT" ]; then
local msg
msg=$(<"$QEMU_OUT")
if [ -n "$msg" ]; then
if [[ "${msg,,}" != "char"* || "$msg" != *"serial0)" ]]; then
echo "$msg"
TIMEOUT=$(strip "$TIMEOUT")
if [[ ! "$TIMEOUT" =~ ^[0-9]+$ ]]; then
TIMEOUT=115
fi
dev="${msg#*/dev/p}"
dev="/dev/p${dev%% *}"
fi
if (( TIMEOUT >= 30 )); then
term_grace=5
cleanup_grace=5
elif (( TIMEOUT >= 15 )); then
term_grace=4
cleanup_grace=4
fi
if [ ! -c "$dev" ]; then
dev=$(echo 'info chardev' | nc -q 1 -w 1 localhost "$MON_PORT" | tr -d '\000')
dev="${dev#*serial0}"
dev="${dev#*pty:}"
dev="${dev%%$'\n'*}"
dev="${dev%%$'\r'*}"
fi
min=$((term_grace + cleanup_grace + 1))
(( TIMEOUT < min )) && (( TIMEOUT = min ))
if [ ! -c "$dev" ]; then
error "Device '$dev' not found!"
finish 34 && return 34
fi
wait_until=$((TIMEOUT - cleanup_grace))
sigterm_at=$((wait_until - term_grace))
QEMU_TERM="$dev"
return 0
}
_graceful_shutdown() {
sendAcpiShutdown() {
local code=$?
set +e
if [ -f "$QEMU_END" ]; then
info "Received $1 while already shutting down..."
return
fi
touch "$QEMU_END"
info "Received $1, sending ACPI shutdown signal..."
if [ ! -s "$QEMU_PID" ]; then
error "QEMU PID file does not exist?"
finish "$code" && return "$code"
fi
local pid=""
pid=$(<"$QEMU_PID")
if ! isAlive "$pid"; then
error "QEMU process does not exist?"
finish "$code" && return "$code"
fi
if ! ready; then
info "Cannot send ACPI signal during Windows setup, aborting..."
finish "$code" && return "$code"
fi
[ ! -S "$QEMU_DIR/monitor.sock" ] && return 0
# Send ACPI shutdown signal
echo 'system_powerdown' | nc -q 1 -w 1 localhost "$MON_PORT" > /dev/null
nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || :
return 0
}
abortDuringSetup() {
local code="$1"
info "Cannot send ACPI signal during $(app) setup, aborting..."
sKill "$QEMU_PID"
if ! waitPidFile "$QEMU_PID" 5; then
warn "Timed out while waiting for $(app) to exit!"
fi
finish "$code"
}
waitForShutdown() {
local cnt=0
while [ "$cnt" -lt "$QEMU_TIMEOUT" ]; do
local pid="$1"
local name="$APP"
local slp
sleep 1
cnt=$((cnt+1))
while (( cnt <= wait_until )); do
sleep 1 &
slp=$!
# Stop waiting if the process has exited
! isAlive "$pid" && break
# Workaround for zombie pid
# Workaround for stale/zombie QEMU pid file
[ ! -s "$QEMU_PID" ] && break
info "Waiting for Windows to shutdown... ($cnt/$QEMU_TIMEOUT)"
if (( cnt == sigterm_at )); then
info "${name^} is still running, sending SIGTERM... ($cnt/$wait_until)"
kill -15 -- "$pid" 2>/dev/null || :
elif (( cnt > 0 )); then
info "Waiting for $name to shut down... ($cnt/$wait_until)"
fi
# Send ACPI shutdown signal
echo 'system_powerdown' | nc -q 1 -w 1 localhost "$MON_PORT" > /dev/null
sendAcpiShutdown
wait "$slp"
(( cnt++ ))
done
if [ "$cnt" -ge "$QEMU_TIMEOUT" ]; then
error "Shutdown timeout reached, aborting..."
fi
finish "$code" && return "$code"
return 0
}
SERIAL="pty"
MONITOR="telnet:localhost:$MON_PORT,server,nowait,nodelay"
MONITOR+=" -daemonize -D $QEMU_LOG -pidfile $QEMU_PID"
graceful_shutdown() {
_trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT
local sig="$1"
local pid=""
local code=0
[[ $BASHPID != "$TRAP_PID" ]] && return
code=$(signalCode "$sig")
if [ -f "$QEMU_END" ]; then
echo && info "Received $1 signal while already shutting down..."
return
fi
set +e
touch "$QEMU_END"
echo && info "Received $1 signal, sending ACPI shutdown signal..."
if ! readQemuPid pid; then
warn "QEMU PID file ($QEMU_PID) does not exist?"
finish "$code"
fi
if ! isAlive "$pid"; then
warn "QEMU process with PID $pid does not exist?"
finish "$code"
fi
if ! ready; then
abortDuringSetup "$code"
fi
normalizeTimeout
waitForShutdown "$pid"
finish "$code"
}
! enabled "$SHUTDOWN" && return 0
[ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT"
_trap graceful_shutdown SIGTERM SIGHUP SIGABRT SIGQUIT
return 0
+177 -107
View File
@@ -2,43 +2,75 @@
set -Eeuo pipefail
: "${SAMBA:="Y"}" # Enable Samba
: "${SAMBA_LEVEL:="1"}" # Logging level
: "${SAMBA_DEBUG:="N"}" # Disable debug
tmp="/tmp/smb"
rm -rf "$tmp"
rm -f /var/run/wsdd.pid
rm -f /var/run/samba/nmbd.pid
rm -f /var/run/samba/smbd.pid
DDN_PID="/var/run/wsdd.pid"
NMB_PID="/var/run/samba/nmbd.pid"
SMB_PID="/var/run/samba/smbd.pid"
[[ "$SAMBA" == [Nn]* ]] && return 0
[[ "$NETWORK" == [Nn]* ]] && return 0
rm -f "$SMB_PID" "$NMB_PID" "$DDN_PID"
if [[ "$DHCP" == [Yy1]* ]]; then
socket="$IP"
hostname="$IP"
interfaces="$VM_NET_DEV"
else
disabled "$SAMBA" && return 0
disabled "$NETWORK" && return 0
configureNetwork() {
if enabled "$DHCP"; then
socket="$UPLINK"
hostname="$UPLINK"
interfaces="$DEV"
else
hostname="host.lan"
case "${NETWORK,,}" in
"passt" | "slirp" )
if isUserMode; then
interfaces="lo"
socket="127.0.0.1" ;;
*)
socket="$VM_NET_IP"
interfaces="$VM_NET_BRIDGE" ;;
esac
socket="127.0.0.1"
else
socket="$IP"
interfaces="$BRIDGE"
fi
if [ -n "${SAMBA_INTERFACE:-}" ]; then
interfaces+=",$SAMBA_INTERFACE"
fi
fi
fi
html "Initializing shared folder..."
SAMBA_CONFIG="/etc/samba/smb.conf"
[[ "$DEBUG" == [Yy1]* ]] && echo "Starting Samba daemon..."
return 0
}
writeReadme() {
local dir="$1"
local ref="$2"
{ echo "--------------------------------------------------------"
echo " $APP for $ENGINE v$(</etc/version)..."
echo " For support visit $SUPPORT"
echo "--------------------------------------------------------"
echo ""
echo "Using this folder you can exchange files with the host machine."
echo ""
echo "To select a folder on the host for this purpose, include the following bind mount in your compose file:"
echo ""
echo " volumes:"
echo " - \"./example:${ref}\""
echo ""
echo "Or in your run command:"
echo ""
echo " -v \"\${PWD:-.}/example:${ref}\""
echo ""
echo "Replace the example path ./example with your desired shared folder, which then will become visible here."
echo ""
} | unix2dos > "$dir/readme.txt"
return 0
}
addShare() {
local dir="$1"
local ref="$2"
local name="$3"
@@ -64,54 +96,47 @@ addShare() {
fi
if [ -z "$(ls -A "$dir")" ]; then
if ! chmod 2777 "$dir"; then
error "Failed to set permissions for directory $dir" && return 1
fi
owner=$(stat -c %u "$dir")
if ! owner=$(stat -c %u "$dir"); then
error "Failed to determine ownership for directory $dir"
return 1
fi
if [[ "$owner" == "0" ]]; then
if ! chown "1000:1000" "$dir"; then
error "Failed to set ownership for directory $dir" && return 1
fi
fi
fi
if [[ "$dir" == "$tmp" ]]; then
{ echo "--------------------------------------------------------"
echo " $APP for $ENGINE v$(</run/version)..."
echo " For support visit $SUPPORT"
echo "--------------------------------------------------------"
echo ""
echo "Using this folder you can exchange files with the host machine."
echo ""
echo "To select a folder on the host for this purpose, include the following bind mount in your compose file:"
echo ""
echo " volumes:"
echo " - \"./example:${ref}\""
echo ""
echo "Or in your run command:"
echo ""
echo " -v \"\${PWD:-.}/example:${ref}\""
echo ""
echo "Replace the example path ./example with your desired shared folder, which then will become visible here."
echo ""
} | unix2dos > "$dir/readme.txt"
writeReadme "$dir" "$ref" || return 1
fi
{ echo ""
if ! {
echo ""
echo "[$name]"
echo " path = $dir"
echo " comment = $comment"
echo " writable = yes"
echo " guest ok = yes"
echo " guest only = yes"
} >> "$cfg"
} >> "$cfg"; then
error "Failed to update Samba config \"$cfg\" !"
return 1
fi
return 0
}
{ echo "[global]"
writeConfig() {
{ echo "[global]"
echo " server string = Dockur"
echo " netbios name = $hostname"
echo " workgroup = WORKGROUP"
@@ -138,91 +163,136 @@ addShare() {
echo " printing = bsd"
echo " printcap name = /dev/null"
echo " disable spoolss = yes"
} > "$SAMBA_CONFIG"
} > "$SAMBA_CONFIG"
# Add shared folders
share="/shared"
[ ! -d "$share" ] && [ -d "$STORAGE/shared" ] && share="$STORAGE/shared"
[ ! -d "$share" ] && [ -d "/data" ] && share="/data"
[ ! -d "$share" ] && [ -d "$STORAGE/data" ] && share="$STORAGE/data"
[ ! -d "$share" ] && share="$tmp"
return 0
}
! addShare "$share" "/shared" "Data" "Shared" "$SAMBA_CONFIG" && return 0
selectPrimaryShare() {
if [ -d "/shared2" ]; then
addShare "/shared2" "/shared2" "Data2" "Shared" "$SAMBA_CONFIG" || :
else
if [ -d "/data2" ]; then
addShare "/data2" "/shared2" "Data2" "Shared" "$SAMBA_CONFIG" || :
share="/shared"
[ ! -d "$share" ] && [ -d "$STORAGE/shared" ] && share="$STORAGE/shared"
[ ! -d "$share" ] && [ -d "/data" ] && share="/data"
[ ! -d "$share" ] && [ -d "$STORAGE/data" ] && share="$STORAGE/data"
[ ! -d "$share" ] && share="$tmp"
return 0
}
addOptionalShare() {
local index="$1"
local ref="/shared$index"
local name="Data$index"
if [ -d "$ref" ]; then
addShare "$ref" "$ref" "$name" "Shared" "$SAMBA_CONFIG" || :
elif [ -d "/data$index" ]; then
addShare "/data$index" "$ref" "$name" "Shared" "$SAMBA_CONFIG" || :
fi
fi
if [ -d "/shared3" ]; then
addShare "/shared3" "/shared3" "Data3" "Shared" "$SAMBA_CONFIG" || :
else
if [ -d "/data3" ]; then
addShare "/data3" "/shared3" "Data3" "Shared" "$SAMBA_CONFIG" || :
return 0
}
prepareSambaDirs() {
# Create directories if missing
mkdir -p \
/var/lib/samba/sysvol \
/var/lib/samba/private \
/var/lib/samba/bind-dns || return 1
# Try to repair Samba permissions
[ -d /run/samba/msg.lock ] && chmod -R 0755 /run/samba/msg.lock 2>/dev/null || :
[ -d /var/log/samba/cores ] && chmod -R 0700 /var/log/samba/cores 2>/dev/null || :
[ -d /var/cache/samba/msg.lock ] && chmod -R 0755 /var/cache/samba/msg.lock 2>/dev/null || :
return 0
}
debugLog() {
local file="$1"
if enabled "$SAMBA_DEBUG"; then
tail -fn +0 "$file" --pid=$$ &
fi
fi
# Create directories if missing
mkdir -p /var/lib/samba/sysvol
mkdir -p /var/lib/samba/private
mkdir -p /var/lib/samba/bind-dns
return 0
}
# Try to repair Samba permissions
[ -d /run/samba/msg.lock ] && chmod -R 0755 /run/samba/msg.lock 2>/dev/null || :
[ -d /var/log/samba/cores ] && chmod -R 0700 /var/log/samba/cores 2>/dev/null || :
[ -d /var/cache/samba/msg.lock ] && chmod -R 0755 /var/cache/samba/msg.lock 2>/dev/null || :
startDaemon() {
rm -f /var/log/samba/log.smbd
local name="$1"
local log="$2"
shift 2
if ! smbd -l /var/log/samba; then
rm -f "$log"
if ! "$@"; then
SAMBA_DEBUG="Y"
error "Failed to start Samba daemon!"
fi
error "Failed to start $name daemon!"
fi
if [[ "$SAMBA_DEBUG" == [Yy1]* ]]; then
tail -fn +0 /var/log/samba/log.smbd --pid=$$ &
fi
debugLog "$log"
return 0
}
case "${NETWORK,,}" in
"passt" | "slirp" )
return 0 ;;
esac
startSamba() {
if [[ "${BOOT_MODE:-}" == "windows_legacy" ]]; then
startDaemon "Samba" "/var/log/samba/log.smbd" \
smbd -l /var/log/samba
return 0
}
startNetbios() {
# Enable NetBIOS on Windows 7 and lower
[[ "$DEBUG" == [Yy1]* ]] && echo "Starting NetBIOS daemon..."
enabled "$DEBUG" && echo "Starting NetBIOS daemon..."
rm -f /var/log/samba/log.nmbd
startDaemon "NetBIOS" "/var/log/samba/log.nmbd" \
nmbd -l /var/log/samba
if ! nmbd -l /var/log/samba; then
SAMBA_DEBUG="Y"
error "Failed to start NetBIOS daemon!"
fi
return 0
}
if [[ "$SAMBA_DEBUG" == [Yy1]* ]]; then
tail -fn +0 /var/log/samba/log.nmbd --pid=$$ &
fi
else
startWsddn() {
# Enable Web Service Discovery on Vista and up
[[ "$DEBUG" == [Yy1]* ]] && echo "Starting wsddn daemon..."
enabled "$DEBUG" && echo "Starting wsddn daemon..."
rm -f /var/log/wsddn.log
startDaemon "wsddn" "/var/log/wsddn.log" \
wsddn -i "${interfaces%%,*}" -H "$hostname" \
--unixd --log-file=/var/log/wsddn.log --pid-file="$DDN_PID"
if ! wsddn -i "${interfaces%%,*}" -H "$hostname" --unixd --log-file=/var/log/wsddn.log --pid-file=/var/run/wsdd.pid; then
SAMBA_DEBUG="Y"
error "Failed to start wsddn daemon!"
fi
return 0
}
if [[ "$SAMBA_DEBUG" == [Yy1]* ]]; then
tail -fn +0 /var/log/wsddn.log --pid=$$ &
fi
configureNetwork
html "Initializing shared folder..."
SAMBA_CONFIG="/etc/samba/smb.conf"
enabled "$DEBUG" && echo "Starting Samba daemon..."
writeConfig || return 1
# Add shared folders
selectPrimaryShare
! addShare "$share" "/shared" "Data" "Shared" "$SAMBA_CONFIG" && return 0
addOptionalShare "2"
addOptionalShare "3"
prepareSambaDirs || return 1
startSamba
isUserMode && return 0
if [[ "${BOOT_MODE:-}" == "windows_legacy" ]]; then
startNetbios
else
startWsddn
fi
return 0