Compare commits

...

73 Commits

Author SHA1 Message Date
Kroese f0b3967927 fix: Unbound USB variable (#1860) 2026-07-15 19:39:32 +02:00
Kroese 3b2a26e303 fix: Backwards compatibility for USB state (#1859) 2026-07-15 18:40:29 +02:00
Kroese 463935baf2 fix: Make changing ownership non-fatal (#1858) 2026-07-15 18:27:08 +02:00
Kroese 50d5f4fc09 fix: Remove deprecated Samba socket address (#1857) 2026-07-15 17:03:33 +02:00
Kroese 9421d31b91 fix: Preserve installation ISO when marker creation fails (#1856) 2026-07-15 16:25:48 +02:00
Kroese 7642a17b57 feat: Improve curl error reporting (#1855) 2026-07-15 11:34:26 +02:00
Kroese b1ff5da11a feat: Improve wget error reporting (#1854) 2026-07-15 10:29:15 +02:00
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
16 changed files with 1468 additions and 849 deletions
+4
View File
@@ -4,7 +4,11 @@ updates:
directory: / directory: /
schedule: schedule:
interval: weekly interval: weekly
cooldown:
default-days: 7
- package-ecosystem: github-actions - package-ecosystem: github-actions
directory: / directory: /
schedule: schedule:
interval: weekly 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

+1 -1
View File
@@ -22,7 +22,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v7
with: with:
fetch-depth: 0 fetch-depth: 0
- -
+4 -3
View File
@@ -7,9 +7,10 @@ jobs:
name: shellcheck name: shellcheck
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v7
- parallel:
- -
name: Run ShellCheck name: Run ShellCheck
uses: ludeeus/action-shellcheck@master uses: ludeeus/action-shellcheck@master
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
steps: steps:
- -
name: Checkout repo name: Checkout repo
uses: actions/checkout@v6 uses: actions/checkout@v7
- -
name: Docker Hub Description name: Docker Hub Description
uses: peter-evans/dockerhub-description@v5 uses: peter-evans/dockerhub-description@v5
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v7
with: with:
fetch-depth: 0 fetch-depth: 0
- -
+4 -5
View File
@@ -13,16 +13,16 @@ jobs:
name: review name: review
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v7
- parallel:
- -
name: Spelling name: Spelling
uses: reviewdog/action-misspell@v1 uses: reviewdog/action-misspell@v1
with: with:
locale: "US" locale: "US"
level: warning level: warning
fail_level: error
pattern: | pattern: |
*.md *.md
*.sh *.sh
@@ -42,7 +42,6 @@ jobs:
uses: reviewdog/action-yamllint@v1 uses: reviewdog/action-yamllint@v1
with: with:
level: warning level: warning
fail_level: error
reporter: github-pr-review reporter: github-pr-review
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
- -
+22 -16
View File
@@ -3,29 +3,35 @@
ARG VERSION_ARG="latest" ARG VERSION_ARG="latest"
FROM scratch AS build-amd64 FROM scratch AS build-amd64
COPY --from=qemux/qemu:7.32 / / COPY --from=qemux/qemu:7.36 / /
ARG TARGETARCH ARG TARGETARCH
ARG VERSION_WSDD="1.24" ARG VERSION_WSDD="1.26"
ARG VERSION_VIRTIO="1.9.57" ARG VERSION_VIRTIO="1.9.58"
ARG DEBCONF_NOWARNINGS="yes" ARG DEBCONF_NOWARNINGS="yes"
ARG DEBIAN_FRONTEND="noninteractive" ARG DEBIAN_FRONTEND="noninteractive"
ARG DEBCONF_NONINTERACTIVE_SEEN="true" ARG DEBCONF_NONINTERACTIVE_SEEN="true"
RUN set -eu && \ RUN <<EOF
apt-get update && \ set -eu
apt-get --no-install-recommends -y install \
samba \ apt-get update
wimtools \ apt-get --no-install-recommends -y install \
dos2unix \ samba \
cabextract \ wimtools \
libxml2-utils \ dos2unix \
libarchive-tools && \ cabextract \
wget "https://github.com/gershnik/wsdd-native/releases/download/v${VERSION_WSDD}/wsddn_${VERSION_WSDD}_${TARGETARCH}.deb" -O /tmp/wsddn.deb -q --timeout=10 && \ libxml2-utils \
dpkg -i /tmp/wsddn.deb && \ libarchive-tools
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* # 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 ./src /run/
COPY --chmod=755 ./assets /run/assets COPY --chmod=755 ./assets /run/assets
+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. |
+90 -46
View File
@@ -1,6 +1,6 @@
<h1 align="center">Windows<br /> <h1 align="center">Windows<br />
<div align="center"> <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>
<div align="center"> <div align="center">
@@ -16,17 +16,22 @@ Windows inside a Docker container.
## Features ✨ ## Features ✨
- ISO downloader - Runs Windows inside a Docker container
- KVM acceleration - Automatic download and hands-free installation
- Web-based viewer - 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 📺 ## Video 📺
[![Youtube](https://img.youtube.com/vi/xhGYobuG508/maxresdefault.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 🐳 ## Usage 🐳
##### Via Docker Compose: ##### Docker Compose:
```yaml ```yaml
services: services:
@@ -50,26 +55,36 @@ services:
stop_grace_period: 2m stop_grace_period: 2m
``` ```
##### Via Docker CLI: ##### Docker CLI:
```bash ```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 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 ```shell
kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/master/kubernetes.yml 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) [![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) [![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 💬 ## FAQ 💬
### How do I use it? ### 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. - 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. - Once you see the desktop, your Windows installation is ready for use.
@@ -90,7 +105,7 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
```yaml ```yaml
environment: environment:
VERSION: "11" VERSION: "10"
``` ```
Select from the values below: Select from the values below:
@@ -143,7 +158,7 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
``` ```
> [!TIP] > [!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) afterwards, 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? ### 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" 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? ### How do I configure the username and password?
By default, a user called `Docker` is created and its password is `admin`. 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 ```yaml
environment: environment:
@@ -197,7 +231,7 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
### How do I select the keyboard layout? ### 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 ```yaml
environment: environment:
@@ -218,7 +252,7 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
```yaml ```yaml
volumes: 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. 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 - ./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? ### 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. 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 ```yaml
environment: environment:
MANUAL: "Y" 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? ### 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. 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 - ./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 ```yaml
devices: 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). 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 ```yaml
environment: environment:
@@ -344,35 +370,48 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
``` ```
> [!WARNING] > [!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. > 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** | 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.
|---|---|---|---|---|
| Docker CLI | ✅ | ✅ | ❌ | ❌ |
| Docker Desktop | ❌ | ✅ | ❌ | ❌ |
| Podman CLI | ✅ | ✅ | ❌ | ❌ |
| Podman Desktop | ✅ | ✅ | ❌ | ❌ |
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 ```bash
sudo apt install cpu-checker sudo apt install cpu-checker
sudo kvm-ok 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? ### How do I run macOS in a container?
@@ -384,7 +423,12 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
### Is this project legal? ### 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 ⚖️ ## Disclaimer ⚖️
+40 -17
View File
@@ -16,17 +16,21 @@ set -Eeuo pipefail
: "${USERNAME:=""}" : "${USERNAME:=""}"
: "${PASSWORD:=""}" : "${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 MIRRORS=4
parseVersion() { parseVersion() {
if [[ "${VERSION}" == \"*\" || "${VERSION}" == \'*\' ]]; then VERSION=$(strip "$VERSION")
VERSION="${VERSION:1:-1}"
fi
VERSION="${VERSION#"${VERSION%%[! ]*}"}"
VERSION="${VERSION%"${VERSION##*[! ]}"}"
[ -z "$VERSION" ] && VERSION="win11" [ -z "$VERSION" ] && VERSION="win11"
case "${VERSION,,}" in case "${VERSION,,}" in
@@ -149,7 +153,7 @@ parseVersion() {
VERSION="tiny11" VERSION="tiny11"
[ -z "$DETECTED" ] && DETECTED="win11x64" [ -z "$DETECTED" ] && DETECTED="win11x64"
;; ;;
"tiny10" | "tiny 10" ) "tiny10" | "tiny 10" )
VERSION="tiny10" VERSION="tiny10"
[ -z "$DETECTED" ] && DETECTED="win10x64-ltsc" [ -z "$DETECTED" ] && DETECTED="win10x64-ltsc"
;; ;;
@@ -786,8 +790,8 @@ getLink1() {
case "${id,,}" in case "${id,,}" in
"win11x64" | "win11x64-enterprise" | "win11x64-enterprise-eval" ) "win11x64" | "win11x64-enterprise" | "win11x64-enterprise-eval" )
size=6898546688 size=6927149056
sum="2618a56931b645a6f097082431994bd85ae80862518de389e382f35ebfd455be" sum="f5ffe9313eebc6299fba9e6eeb2971007264e6c6be013073a89b5ae9bd85bfb3"
url="11/en-us_windows_11_25h2_x64.iso" url="11/en-us_windows_11_25h2_x64.iso"
;; ;;
"win11x64-iot" | "win11x64-enterprise-iot" | "win11x64-enterprise-iot-eval" ) "win11x64-iot" | "win11x64-enterprise-iot" | "win11x64-enterprise-iot-eval" )
@@ -801,8 +805,8 @@ getLink1() {
url="11/X23-81951_26100.1742.240906-0331.ge_release_svc_refresh_CLIENT_ENTERPRISES_OEM_x64FRE_en-us.iso" 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" ) "win10x64" | "win10x64-enterprise" | "win10x64-enterprise-eval" )
size=5767888896 size=5723299840
sum="9dce12d73168debc697919a6bc4d8c6624b2175bbed01a2ca97edb7d93627319" sum="316f718f21fc9b386d81dadd62dc60268a1cfd65b184ac6a052875a454c3431b"
url="10/en-us_windows_10_22h2_x64.iso" url="10/en-us_windows_10_22h2_x64.iso"
;; ;;
"win10x64-iot" | "win10x64-enterprise-iot" | "win10x64-enterprise-iot-eval" ) "win10x64-iot" | "win10x64-enterprise-iot" | "win10x64-enterprise-iot-eval" )
@@ -1287,7 +1291,7 @@ isMido() {
local lang="$2" local lang="$2"
local sum local sum
[[ "${MIDO:-}" == [Nn]* ]] && return 1 disabled "${MIDO:-}" && return 1
sum=$(getMido "$id" "en" "sum") sum=$(getMido "$id" "en" "sum")
[ -n "$sum" ] && return 0 [ -n "$sum" ] && return 0
@@ -1300,7 +1304,7 @@ isESD() {
local id="$1" local id="$1"
local lang="$2" local lang="$2"
[[ "${ESD:-}" == [Nn]* ]] && return 1 disabled "${ESD:-}" && return 1
case "${id,,}" in case "${id,,}" in
"win11${PLATFORM,,}" | "win10${PLATFORM,,}" ) "win11${PLATFORM,,}" | "win10${PLATFORM,,}" )
@@ -1355,7 +1359,13 @@ addFolder() {
local file local file
file=$(find "$dest" -maxdepth 1 -type f -iname install.bat -print -quit) 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 return 0
} }
@@ -1772,8 +1782,21 @@ prepareLegacy() {
rm -f "$dir/$ETFS" rm -f "$dir/$ETFS"
local len offset 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 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 error "Failed to extract boot image from $desc ISO!" && return 1
+3 -2
View File
@@ -17,6 +17,7 @@ cd /run
. install.sh # Run installation . install.sh # Run installation
. disk.sh # Initialize disks . disk.sh # Initialize disks
. display.sh # Initialize graphics . display.sh # Initialize graphics
. audio.sh # Initialize audio
. network.sh # Initialize network . network.sh # Initialize network
. samba.sh # Configure samba . samba.sh # Configure samba
. boot.sh # Configure boot . boot.sh # Configure boot
@@ -31,7 +32,7 @@ trap - ERR
cmd=(qemu-system-x86_64) cmd=(qemu-system-x86_64)
version=$("${cmd[@]}" --version | awk 'NR==1 { print $4 }') version=$("${cmd[@]}" --version | awk 'NR==1 { print $4 }')
info "Booting ${APP}${BOOT_DESC} using QEMU v$version..." info "Booting ${APP}${BOOT_DESC} using QEMU v$version..." && echo
pipe="$QEMU_DIR/qemu.pipe" pipe="$QEMU_DIR/qemu.pipe"
rm -f "$pipe" && mkfifo "$pipe" rm -f "$pipe" && mkfifo "$pipe"
@@ -46,7 +47,7 @@ sed -u \
-e 's/failed to load Boot/skipped Boot/g' \ -e 's/failed to load Boot/skipped Boot/g' \
-e 's/0): Not Found/0)/g' & -e 's/0): Not Found/0)/g' &
if [[ "$SHUTDOWN" != [Yy1]* ]]; then if ! enabled "$SHUTDOWN"; then
exec "${cmd[@]}" ${ARGS:+ $ARGS} >"$pipe" exec "${cmd[@]}" ${ARGS:+ $ARGS} >"$pipe"
fi fi
+261 -254
View File
@@ -11,16 +11,10 @@ backup () {
local iso="$1" local iso="$1"
local name="unknown" local name="unknown"
local root="$STORAGE/backups" local root="$STORAGE/backups"
local previous="$STORAGE/windows.base" local previous
if [ -f "$previous" ]; then previous=$(readState "base") || return 1
[ -n "$previous" ] && name="${previous%.*}"
previous=$(<"$previous")
previous="${previous//[![:print:]]/}"
[ -n "$previous" ] && name="${previous%.*}"
fi
if ! makeDir "$root"; then if ! makeDir "$root"; then
error "Failed to create directory \"$root\" !" error "Failed to create directory \"$root\" !"
@@ -59,47 +53,42 @@ skipInstall() {
local method="" local method=""
local magic byte local magic byte
local boot="$STORAGE/windows.boot" local boot="$STORAGE/windows.boot"
local previous="$STORAGE/windows.base" local previous
if [ -f "$previous" ]; then previous=$(readState "base") || return 1
previous=$(<"$previous") if [ -n "$previous" ]; then
previous="${previous//[![:print:]]/}" if [[ "${STORAGE,,}/${previous,,}" != "${iso,,}" ]]; then
if [ -n "$previous" ]; then if ! hasDisk; then
if [[ "${STORAGE,,}/${previous,,}" != "${iso,,}" ]]; then
if ! hasDisk; then
rm -f "$STORAGE/$previous"
return 1
fi
if [[ "${iso,,}" == "${STORAGE,,}/windows."* ]]; then
method="your custom .iso file was changed"
else
if [[ "${previous,,}" != "windows."* ]]; then
method="the VERSION variable was changed"
else
method="your custom .iso file was removed"
if [ -f "$boot" ]; then
info "Detected that $method, will be ignored."
return 0
fi
fi
fi
info "Detected that $method, a backup of your previous installation will be saved..."
! backup "$STORAGE/$previous" && error "Backup failed!"
rm -f "$STORAGE/$previous"
return 1 return 1
fi fi
fi
if [[ "${iso,,}" == "${STORAGE,,}/windows."* ]]; then
method="your custom .iso file was changed"
else
if [[ "${previous,,}" != "windows."* ]]; then
method="the VERSION variable was changed"
else
method="your custom .iso file was removed"
if [ -f "$boot" ]; then
info "Detected that $method, will be ignored."
return 0
fi
fi
fi
info "Detected that $method, a backup of your previous installation will be saved..."
! backup "$STORAGE/$previous" && error "Backup failed!"
return 1
fi
fi fi
[ -f "$boot" ] && hasDisk && return 0 [ -f "$boot" ] && hasDisk && return 0
@@ -110,7 +99,7 @@ skipInstall() {
# Check if the ISO was already processed by our script # Check if the ISO was already processed by our script
magic=$(dd if="$iso" 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')" 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 if [[ "$magic" != "$byte" ]]; then
@@ -185,15 +174,19 @@ startInstall() {
return 0 return 0
} }
writeFile() { checkFreeSpace() {
local txt="$1" local dir="$1"
local path="$2" 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 if (( size > space )); then
error "Failed to set the owner for \"$path\" !" error "Not enough free space in $STORAGE, have $space_gb available but need at least $size_gb."
return 1
fi fi
return 0 return 0
@@ -210,79 +203,74 @@ finishInstall() {
fi fi
if [[ "$iso" == "$STORAGE/"* ]]; then if [[ "$iso" == "$STORAGE/"* ]]; then
! setOwner "$iso" && error "Failed to set the owner for \"$iso\" !" if ! setOwner "$iso"; then
warn "failed to set the owner for \"$iso\" !"
fi
fi fi
if [[ "$aborted" != [Yy1]* ]]; then if [[ "$aborted" != [Yy1]* ]]; then
# Mark ISO as prepared via magic byte # 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 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" warn "failed to set magic byte in ISO file: $iso"
fi fi
fi fi
local file="$STORAGE/windows.ver" local file="$STORAGE/windows.ver"
cp -f /etc/version "$file" cp -f /etc/version "$file" || return 1
! setOwner "$file" && error "Failed to set the owner for \"$file\" !"
if ! setOwner "$file"; then
warn "Failed to set the owner for \"$file\" !"
fi
if [[ "$iso" == "$STORAGE/"* ]]; then if [[ "$iso" == "$STORAGE/"* ]]; then
if [[ "$aborted" != [Yy1]* ]] || [ -z "$CUSTOM" ]; then if [[ "$aborted" != [Yy1]* ]] || [ -z "$CUSTOM" ]; then
base=$(basename "$iso") base=$(basename "$iso")
file="$STORAGE/windows.base" writeState "base" "$base" || return 1
writeFile "$base" "$file"
fi fi
fi fi
if [[ "${PLATFORM,,}" == "x64" ]]; then if [[ "${PLATFORM,,}" == "x64" ]]; then
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
file="$STORAGE/windows.mode" writeState "mode" "$BOOT_MODE" || return 1
writeFile "$BOOT_MODE" "$file"
if [[ "${MACHINE,,}" != "q35" ]]; then if [[ "${MACHINE,,}" != "q35" ]]; then
file="$STORAGE/windows.old" writeState "old" "$MACHINE" || return 1
writeFile "$MACHINE" "$file"
fi fi
else else
# Enable secure boot + TPM on manual installs as Win11 requires # 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 if [[ "${DETECTED,,}" == "win11"* ]]; then
BOOT_MODE="windows_secure" BOOT_MODE="windows_secure"
file="$STORAGE/windows.mode" writeState "mode" "$BOOT_MODE" || return 1
writeFile "$BOOT_MODE" "$file"
fi fi
fi fi
# Enable secure boot on multi-socket systems to workaround freeze # Enable secure boot on multi-socket systems to workaround freeze
if [ -n "$SOCKETS" ] && [[ "$SOCKETS" != "1" ]]; then if [ -n "$SOCKETS" ] && [[ "$SOCKETS" != "1" ]]; then
BOOT_MODE="windows_secure" BOOT_MODE="windows_secure"
file="$STORAGE/windows.mode" writeState "mode" "$BOOT_MODE" || return 1
writeFile "$BOOT_MODE" "$file"
fi fi
fi fi
fi fi
if [ -n "${ARGS:-}" ]; then if [ -n "${ARGS:-}" ]; then
ARGUMENTS="$ARGS ${ARGUMENTS:-}" ARGUMENTS="$ARGS ${ARGUMENTS:-}"
file="$STORAGE/windows.args" writeState "args" "$ARGS" || return 1
writeFile "$ARGS" "$file"
fi fi
if [ -n "${VGA:-}" ] && [[ "${VGA:-}" != "virtio"* ]]; then if [ -n "${VGA:-}" ] && [[ "${VGA:-}" != "virtio"* ]]; then
file="$STORAGE/windows.vga" writeState "vga" "$VGA" || return 1
writeFile "$VGA" "$file"
fi fi
if [ -n "${USB:-}" ] && [[ "${USB:-}" != "qemu-xhci"* ]]; then if [ -n "${USB:-}" ] && [[ "${USB:-}" != "qemu-xhci"* ]]; then
file="$STORAGE/windows.usb" writeState "usb" "$USB" || return 1
writeFile "$USB" "$file"
fi fi
if [ -n "${DISK_TYPE:-}" ] && [[ "${DISK_TYPE:-}" != "scsi" ]]; then if [ -n "${DISK_TYPE:-}" ] && [[ "${DISK_TYPE:-}" != "scsi" ]]; then
file="$STORAGE/windows.type" writeState "type" "$DISK_TYPE" || return 1
writeFile "$DISK_TYPE" "$file"
fi fi
if [ -n "${ADAPTER:-}" ] && [[ "${ADAPTER:-}" != "virtio-net-pci" ]]; then if [ -n "${ADAPTER:-}" ] && [[ "${ADAPTER:-}" != "virtio-net-pci" ]]; then
file="$STORAGE/windows.net" writeState "net" "$ADAPTER" || return 1
writeFile "$ADAPTER" "$file"
fi fi
rm -rf "$TMP" rm -rf "$TMP"
@@ -296,7 +284,7 @@ abortInstall() {
local efi local efi
[[ "${iso,,}" == *".esd" ]] && exit 60 [[ "${iso,,}" == *".esd" ]] && exit 60
[[ "${UNPACK:-}" == [Yy1]* ]] && exit 60 enabled "${UNPACK:-}" && exit 60
efi=$(find "$dir" -maxdepth 1 -type d -iname efi -print -quit) efi=$(find "$dir" -maxdepth 1 -type d -iname efi -print -quit)
@@ -370,95 +358,135 @@ detectCustom() {
return 0 return 0
} }
getEsdField() {
local list="$1"
local index="$2"
sed -n "${index}p" <<< "$list" | tr -cd '0-9'
return 0
}
extractESD() { extractESD() {
local iso="$1" local iso="$1"
local dir="$2" local dir="$2"
local version="$3" local version="$3"
local desc="$4" local desc="$4"
local size size_gb sizes space space_gb
local retVal 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..." info "$msg..." && html "$msg..."
if [ "$(stat -c%s "$iso")" -lt 100000000 ]; then if [ "$(stat -c%s "$iso")" -lt "$minSize" ]; then
error "Invalid ESD file: Size is smaller than 100 MB" && return 1 error "Invalid ESD file: Size is smaller than 100 MB"
return 1
fi fi
rm -rf "$dir" rm -rf "$dir"
if ! makeDir "$dir"; then if ! makeDir "$dir"; then
error "Failed to create directory \"$dir\" !" && return 1 error "Failed to create directory \"$dir\" !"
return 1
fi fi
size=9606127360 checkFreeSpace "$dir" "$freeSpace" || return 1
size_gb=$(formatBytes "$size")
space=$(df --output=avail -B 1 "$dir" | tail -n 1)
space_gb=$(formatBytes "$space")
if (( size > space )); then info=$(wimlib-imagex info "$iso") || {
error "Not enough free space in $STORAGE, have $space_gb available but need at least $size_gb." && return 1 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 fi
local esdImageCount if (( count < 3 )); then
esdImageCount=$(wimlib-imagex info "$iso" | awk '/Image Count:/ {print $3}') error "Invalid ESD file: expected at least 3 images, found $count."
return 1
if [ -z "$esdImageCount" ]; then
error "Cannot read the image count in ESD file!" && return 1
fi fi
sizes=$(wimlib-imagex info "$iso" | grep "Total Bytes:") totals=$(grep "Total Bytes:" <<< "$info" || true)
links=$(wimlib-imagex info "$iso" | grep "Hard Link Bytes:") links=$(grep "Hard Link Bytes:" <<< "$info" || true)
total1=$(awk "NR==1{ print; }" <<< "$sizes" | cut -d':' -f2 | sed 's/^ *//') bootTotal=$(getEsdField "$totals" 1)
links1=$(awk "NR==1{ print; }" <<< "$links" | cut -d':' -f2 | sed 's/^ *//') bootLinks=$(getEsdField "$links" 1)
total=$(( total1 - links1 ))
total3=$(awk "NR==3{ print; }" <<< "$sizes" | cut -d':' -f2 | sed 's/^ *//') if [[ ! "$bootTotal" =~ ^[0-9]+$ ]] || [[ ! "$bootLinks" =~ ^[0-9]+$ ]]; then
links3=$(awk "NR==3{ print; }" <<< "$links" | cut -d':' -f2 | sed 's/^ *//') error "Cannot read bootdisk size from ESD file!"
total3=$(( total3 - links3 )) return 1
total3=$(( total3 + 60000000 )) fi
/run/progress.sh "$dir" "$total" "$msg ([P])..." & bootSize=$(( bootTotal - bootLinks ))
imageIndex="1" wimTotal=$(getEsdField "$totals" 3)
wimlib-imagex apply "$iso" "$imageIndex" "$dir" --quiet 2>/dev/null || { wimLinks=$(getEsdField "$links" 3)
retVal=$?
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" fKill "progress.sh"
error "Extracting $desc bootdisk failed ($retVal)" && return 1 error "Extracting $desc bootdisk failed ($ret)"
return 1
} }
fKill "progress.sh" fKill "progress.sh"
local bootWimFile="$dir/sources/boot.wim" bootWim="$dir/sources/boot.wim"
local installWimFile="$dir/sources/install.wim" installWim="$dir/sources/install.wim"
local msg="Extracting $desc environment" msg="Extracting $desc environment"
info "$msg..." && html "$msg..." info "$msg..." && html "$msg..."
imageIndex="2" index="2"
/run/progress.sh "$bootWimFile" "$total3" "$msg ([P])..." & /run/progress.sh "$bootWim" "$wimSize" "$msg ([P])..." &
wimlib-imagex export "$iso" "$imageIndex" "$bootWimFile" --compress=none --quiet || { wimlib-imagex export "$iso" "$index" "$bootWim" --compress=none --quiet || {
retVal=$? ret=$?
fKill "progress.sh" fKill "progress.sh"
error "Adding WinPE failed ($retVal)" && return 1 error "Adding WinPE failed ($ret)"
return 1
} }
fKill "progress.sh" fKill "progress.sh"
local msg="Extracting $desc setup" msg="Extracting $desc setup"
info "$msg..." info "$msg..."
imageIndex="3" index="3"
/run/progress.sh "$bootWimFile" "$total3" "$msg ([P])..." & /run/progress.sh "$bootWim" "$wimSize" "$msg ([P])..." &
wimlib-imagex export "$iso" "$imageIndex" "$bootWimFile" --compress=none --boot --quiet || { wimlib-imagex export "$iso" "$index" "$bootWim" --compress=none --boot --quiet || {
retVal=$? ret=$?
fKill "progress.sh" fKill "progress.sh"
error "Adding Windows Setup failed ($retVal)" && return 1 error "Adding Windows Setup failed ($ret)"
return 1
} }
fKill "progress.sh" fKill "progress.sh"
@@ -469,30 +497,31 @@ extractESD() {
LABEL="CPBA_A64FRE_EN-US_DV9" LABEL="CPBA_A64FRE_EN-US_DV9"
fi fi
local msg="Extracting $desc image" msg="Extracting $desc image"
info "$msg..." && html "$msg..." info "$msg..." && html "$msg..."
local edition imageEdition
edition=$(getCatalog "$version" "name") edition=$(getCatalog "$version" "name")
if [ -z "$edition" ]; then 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 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]*//') imgEdition=$(wimlib-imagex info "$iso" "$index" | grep '^Description:' | sed 's/Description:[ \t]*//')
[[ "${imageEdition,,}" != "${edition,,}" ]] && continue [[ "${imgEdition,,}" != "${edition,,}" ]] && continue
total4=$(du -sb "$iso" | cut -f1) installSize=$(stat -c%s "$iso")
total4=$(( total4 + 3000000 )) 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 || { wimlib-imagex export "$iso" "$index" "$installWim" --compress=LZMS --chunk-size 128K --quiet || {
retVal=$? ret=$?
fKill "progress.sh" 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" fKill "progress.sh"
@@ -501,7 +530,8 @@ extractESD() {
done done
fKill "progress.sh" 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() { extractImage() {
@@ -510,7 +540,7 @@ extractImage() {
local dir="$2" local dir="$2"
local version="$3" local version="$3"
local desc="local ISO" local desc="local ISO"
local file size size_gb space space_gb local file size
if [ -z "$CUSTOM" ]; then if [ -z "$CUSTOM" ]; then
desc="downloaded ISO" desc="downloaded ISO"
@@ -534,17 +564,12 @@ extractImage() {
fi fi
size=$(stat -c%s "$iso") 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 if (( size < 100000000 )); then
error "Invalid ISO file: Size is smaller than 100 MB" && return 1 error "Invalid ISO file: Size is smaller than 100 MB" && return 1
fi fi
if (( size > space )); then checkFreeSpace "$dir" "$size" || return 1
error "Not enough free space in $STORAGE, have $space_gb available but need at least $size_gb." && return 1
fi
rm -rf "$dir" rm -rf "$dir"
/run/progress.sh "$dir" "$size" "$msg ([P])..." & /run/progress.sh "$dir" "$size" "$msg ([P])..." &
@@ -556,9 +581,9 @@ extractImage() {
fKill "progress.sh" 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 else
@@ -572,8 +597,8 @@ extractImage() {
error "Failed to extract archive!" && return 1 error "Failed to extract archive!" && return 1
fi fi
LABEL=$(isoinfo -d -i "$file" | sed -n 's/Volume id: //p') LABEL=$(isoinfo -d -i "$file" | sed -n 's/Volume id: //p') || LABEL=""
rm -f "$file" rm -f "$file" || warn "Failed to remove temporary ISO file: $file"
fi fi
@@ -727,7 +752,7 @@ detectImage() {
local dir="$1" local dir="$1"
local version="$2" local version="$2"
local desc msg find language local desc msg language
XML="" XML=""
@@ -739,7 +764,7 @@ detectImage() {
skipVersion "${DETECTED,,}" && return 0 skipVersion "${DETECTED,,}" && return 0
if ! setXML "" && [[ "$MANUAL" != [Yy1]* ]]; then if ! setXML "" && ! enabled "$MANUAL"; then
MANUAL="Y" MANUAL="Y"
desc=$(printEdition "$DETECTED" "this version") desc=$(printEdition "$DETECTED" "this version")
warn "the answer file for $desc was not found ($DETECTED.xml), $FB." warn "the answer file for $desc was not found ($DETECTED.xml), $FB."
@@ -769,14 +794,18 @@ detectImage() {
warn "failed to locate 'install.wim' or 'install.esd' in ISO image, $FB" && return 1 warn "failed to locate 'install.wim' or 'install.esd' in ISO image, $FB" && return 1
fi 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 checkPlatform "$info" || exit 67
DETECTED=$(detectVersion "$info") DETECTED=$(detectVersion "$info")
if [ -z "$DETECTED" ]; then if [ -z "$DETECTED" ]; then
msg="Failed to determine Windows version from image" msg="Failed to determine Windows version from image"
if setXML "" || [[ "$MANUAL" == [Yy1]* ]]; then if setXML "" || enabled "$MANUAL"; then
info "${msg}!" info "${msg}!"
else else
MANUAL="Y" MANUAL="Y"
@@ -803,8 +832,8 @@ detectImage() {
msg="the answer file for $desc was not found ($DETECTED.xml)" msg="the answer file for $desc was not found ($DETECTED.xml)"
local fallback="/run/assets/${DETECTED%%-*}.xml" local fallback="/run/assets/${DETECTED%%-*}.xml"
if setXML "$fallback" || [[ "$MANUAL" == [Yy1]* ]]; then if setXML "$fallback" || enabled "$MANUAL"; then
[[ "$MANUAL" != [Yy1]* ]] && warn "${msg}." ! enabled "$MANUAL" && warn "${msg}."
else else
MANUAL="Y" MANUAL="Y"
warn "${msg}, $FB." warn "${msg}, $FB."
@@ -845,7 +874,7 @@ updateXML() {
local asset="$1" local asset="$1"
local language="$2" local language="$2"
local culture region user admin pass keyboard local culture region user admin pass pw keyboard
[ -z "$HEIGHT" ] && HEIGHT="720" [ -z "$HEIGHT" ] && HEIGHT="720"
[ -z "$WIDTH" ] && WIDTH="1280" [ -z "$WIDTH" ] && WIDTH="1280"
@@ -996,22 +1025,12 @@ addDrivers() {
wimlib-imagex update "$file" "$index" --command "delete --force --recursive /$target" >/dev/null || true wimlib-imagex update "$file" "$index" --command "delete --force --recursive /$target" >/dev/null || true
addDriver "$version" "$drivers" "$target" "qxl" || return 1 local driver
addDriver "$version" "$drivers" "$target" "viofs" || return 1 local driver_list=( qxl viofs sriov smbus qxldod viorng viostor viomem NetKVM Balloon vioscsi pvpanic vioinput viogpudo vioserial qemupciserial )
addDriver "$version" "$drivers" "$target" "sriov" || return 1
addDriver "$version" "$drivers" "$target" "smbus" || return 1 for driver in "${driver_list[@]}"; do
addDriver "$version" "$drivers" "$target" "qxldod" || return 1 addDriver "$version" "$drivers" "$target" "$driver" || return 1
addDriver "$version" "$drivers" "$target" "viorng" || return 1 done
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 dst="$src/\$OEM\$/\$\$/Drivers" local dst="$src/\$OEM\$/\$\$/Drivers"
mkdir -p "$dst" || return 1 mkdir -p "$dst" || return 1
@@ -1038,16 +1057,16 @@ updateImage() {
local asset="$2" local asset="$2"
local language="$3" local language="$3"
local tmp="/tmp/install" local tmp="/tmp/install"
local file="autounattend.xml" local xml="autounattend.xml"
local org="${file//.xml/.org}" local bak="${xml//.xml/.org}"
local dat="${file//.xml/.dat}" local dat="${xml//.xml/.dat}"
local desc path src wim xml index result local desc path src wim name info idx
skipVersion "${DETECTED,,}" && return 0 skipVersion "${DETECTED,,}" && return 0
if [ ! -s "$asset" ] || [ ! -f "$asset" ]; then if [ ! -s "$asset" ] || [ ! -f "$asset" ]; then
asset="" asset=""
if [[ "$MANUAL" != [Yy1]* ]]; then if ! enabled "$MANUAL"; then
MANUAL="Y" MANUAL="Y"
warn "no answer file provided, $FB." warn "no answer file provided, $FB."
fi fi
@@ -1059,23 +1078,30 @@ updateImage() {
src=$(find "$dir" -maxdepth 1 -type d -iname sources -print -quit) src=$(find "$dir" -maxdepth 1 -type d -iname sources -print -quit)
if [ ! -d "$src" ]; then 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 fi
wim=$(find "$src" -maxdepth 1 -type f \( -iname boot.wim -or -iname boot.esd \) -print -quit) wim=$(find "$src" -maxdepth 1 -type f \( -iname boot.wim -or -iname boot.esd \) -print -quit)
if [ ! -f "$wim" ]; then 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 fi
index="1" idx="1"
result=$(wimlib-imagex info -xml "$wim" | iconv -f UTF-16LE -t UTF-8)
if [[ "${result^^}" == *"<IMAGE INDEX=\"2\">"* ]]; then if ! info=$(wimlib-imagex info -xml "$wim" | iconv -f UTF-16LE -t UTF-8); then
index="2" warn "failed to read boot image information, $FB"
MANUAL="Y"
info=""
fi 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!" error "Failed to add drivers to image!"
fi fi
@@ -1083,55 +1109,69 @@ updateImage() {
error "Failed to add OEM folder to image!" error "Failed to add OEM folder to image!"
fi fi
if wimlib-imagex extract "$wim" "$index" "/$file" "--dest-dir=$tmp" >/dev/null 2>&1; then if wimlib-imagex extract "$wim" "$idx" "/$xml" "--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" "$idx" "/$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 extract "$wim" "$idx" "/$bak" "--dest-dir=$tmp" >/dev/null 2>&1; then
if ! wimlib-imagex update "$wim" "$index" --command "rename /$file /$org" > /dev/null; then if ! wimlib-imagex update "$wim" "$idx" --command "rename /$xml /$bak" > /dev/null; then
warn "failed to backup original answer file ($file)." warn "failed to backup original answer file ($xml)."
fi fi
fi fi
fi fi
fi fi
if [[ "$MANUAL" != [Yy1]* ]]; then if ! enabled "$MANUAL"; then
xml=$(basename "$asset") name=$(basename "$asset")
info "Adding $xml for automatic installation..." local answer="$tmp/$name"
local answer="$tmp/$xml" info "Adding $name for automatic installation..."
cp "$asset" "$answer"
updateXML "$answer" "$language"
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" 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 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
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 extract "$wim" "$idx" "/$bak" "--dest-dir=$tmp" >/dev/null 2>&1; then
if ! wimlib-imagex update "$wim" "$index" --command "add $tmp/$org /$file" > /dev/null; then if ! wimlib-imagex update "$wim" "$idx" --command "add $tmp/$bak /$xml" > /dev/null; then
warn "failed to restore original answer file ($org)." warn "failed to restore original answer file ($bak)."
fi fi
fi fi
fi fi
local find="$file" name="$xml"
[[ "$MANUAL" == [Yy1]* ]] && find="$org" enabled "$MANUAL" && name="$bak"
path=$(find "$dir" -maxdepth 1 -type f -iname "$find" -print -quit) path=$(find "$dir" -maxdepth 1 -type f -iname "$name" -print -quit)
if [ -f "$path" ]; then if [ -f "$path" ]; then
if [[ "$MANUAL" != [Yy1]* ]]; then if ! enabled "$MANUAL"; then
mv -f "$path" "${path%.*}.org" if ! mv -f "$path" "${path%.*}.org"; then
error "Failed to rename answer file: $path"
return 1
fi
else 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
fi fi
@@ -1157,7 +1197,7 @@ buildImage() {
local failed="" local failed=""
local cat="BOOT.CAT" local cat="BOOT.CAT"
local log="/run/shm/iso.log" local log="/run/shm/iso.log"
local base size size_gb space space_gb desc local base size desc
if [ -f "$BOOT" ]; then if [ -f "$BOOT" ]; then
error "File $BOOT does already exist?!" && return 1 error "File $BOOT does already exist?!" && return 1
@@ -1179,13 +1219,7 @@ buildImage() {
fi fi
size=$(du -b --max-depth=0 "$dir" | cut -f1) size=$(du -b --max-depth=0 "$dir" | cut -f1)
size_gb=$(formatBytes "$size") checkFreeSpace "$TMP" "$size" || return 1
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
/run/progress.sh "$out" "$size" "$msg ([P])..." & /run/progress.sh "$out" "$size" "$msg ([P])..." &
@@ -1223,57 +1257,30 @@ buildImage() {
[[ "$err" != "$hide" ]] && echo "$err" [[ "$err" != "$hide" ]] && echo "$err"
mv -f "$out" "$BOOT" || return 1 mv -f "$out" "$BOOT" || return 1
! setOwner "$BOOT" && error "Failed to set the owner for \"$BOOT\" !"
if ! setOwner "$BOOT"; then
warn "Failed to set the owner for \"$BOOT\" !"
fi
return 0 return 0
} }
bootWindows() { bootWindows() {
if [ -f "$STORAGE/windows.args" ]; then ARGS=$(readState "$STORAGE/windows.args") || return 1
ARGS=$(<"$STORAGE/windows.args")
ARGS="${ARGS//[![:print:]]/}" if [ -n "$ARGS" ]; then
ARGUMENTS="$ARGS ${ARGUMENTS:-}" ARGUMENTS="$ARGS ${ARGUMENTS:-}"
fi fi
if [ -s "$STORAGE/windows.vga" ] && [ -f "$STORAGE/windows.vga" ]; then restoreState "VGA" "$STORAGE/windows.vga" || return 1
if [ -z "${VGA:-}" ]; then restoreState "USB" "$STORAGE/windows.usb" || return 1
VGA=$(<"$STORAGE/windows.vga") restoreState "ADAPTER" "$STORAGE/windows.net" || return 1
VGA="${VGA//[![:print:]]/}" restoreState "DISK_TYPE" "$STORAGE/windows.type" || return 1
fi restoreState "BOOT_MODE" "$STORAGE/windows.mode" "Y" || return 1
fi
if [ -s "$STORAGE/windows.usb" ] && [ -f "$STORAGE/windows.usb" ]; then if [[ "${PLATFORM,,}" == "x64" ]]; then
if [ -z "${USB:-}" ]; then restoreState "MACHINE" "$STORAGE/windows.old" "Y" || return 1
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
fi fi
return 0 return 0
+417 -262
View File
File diff suppressed because it is too large Load Diff
+227 -100
View File
@@ -10,6 +10,7 @@ QEMU_PTY="$QEMU_DIR/qemu.pty"
QEMU_END="$QEMU_DIR/qemu.end" QEMU_END="$QEMU_DIR/qemu.end"
_trap() { _trap() {
local func="$1"; shift local func="$1"; shift
local sig local sig
TRAP_PID=$BASHPID TRAP_PID=$BASHPID
@@ -17,10 +18,63 @@ _trap() {
for sig; do for sig; do
trap "$func $sig" "$sig" trap "$func $sig" "$sig"
done done
return 0
} }
app() { signalCode() {
echo "$APP" && return 0
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() { boot() {
@@ -29,12 +83,7 @@ boot() {
if [ -s "$QEMU_PTY" ]; then if [ -s "$QEMU_PTY" ]; then
if [ "$(stat -c%s "$QEMU_PTY")" -gt 7 ]; then if [ "$(stat -c%s "$QEMU_PTY")" -gt 7 ]; then
local fail="" if ! bootFailed; then
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 "$(app) started successfully, visit http://127.0.0.1:8006/ to view the screen..." info "$(app) started successfully, visit http://127.0.0.1:8006/ to view the screen..."
return 0 return 0
fi fi
@@ -47,19 +96,27 @@ boot() {
return 0 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
}
ready() { ready() {
[ -f "$STORAGE/windows.boot" ] && return 0 [ -f "$STORAGE/windows.boot" ] && return 0
[ ! -s "$QEMU_PTY" ] && return 1 [ ! -s "$QEMU_PTY" ] && return 1
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
local last legacyBootReady && return 0
local bios="Booting from Hard" return 1
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
fi fi
local line="\"Windows Boot Manager\"" local line="\"Windows Boot Manager\""
@@ -68,56 +125,173 @@ ready() {
return 1 return 1
} }
finish() { forceKillQemu() {
local i=0 local reason="$1"
local pid="" local pid=""
local reason=$1 local display
local pids=( "${SMB_PID:-}" "${NMB_PID:-}" "${DDN_PID:-}" "${TPM_PID:-}" "${WSD_PID:-}" \
"${WEB_PID:-}" "${PASST_PID:-}" "${DNSMASQ_PID:-}" "${BALLOONING_PID:-}" )
touch "$QEMU_END" ! readQemuPid pid && return 0
! isAlive "$pid" && return 0
display=$(displayReason "$reason")
error "Forcefully terminating $(app), reason: $display..."
{ disown "$pid" || :; kill -9 -- "$pid" || :; } 2>/dev/null
if [ -s "$QEMU_PID" ]; then return 0
if read -r pid <"$QEMU_PID"; then }
if [ -n "$pid" ] && isAlive "$pid"; then
local display="$reason" markWindowsBooted() {
case "$reason" in
129 ) display="SIGHUP" ;; local file="$STORAGE/windows.boot"
130 ) display="SIGINT" ;;
131 ) display="SIGQUIT" ;; if [ -f "$file" ] || [ ! -f "$BOOT" ]; then
134 ) display="SIGABRT" ;; return 0
143 ) display="SIGTERM" ;;
esac
error "Forcefully terminating $(app), reason: $display..."
{ disown "$pid" || :; kill -9 -- "$pid" || :; } 2>/dev/null
fi
fi
fi fi
if [ ! -f "$STORAGE/windows.boot" ] && [ -f "$BOOT" ]; then # Remove CD-ROM ISO after install
# Remove CD-ROM ISO after install ! ready && return 0
if ready; then
local file="$STORAGE/windows.boot" if ! touch "$file"; then
touch "$file" warn "failed to create Windows installation marker!"
! setOwner "$file" && error "Failed to set the owner for \"$file\" !" return 0
if [[ "$REMOVE" != [Nn]* ]]; then
rm -f "$BOOT" 2>/dev/null || true
fi
fi
fi fi
if ! setOwner "$file"; then
rm -f "$file"
warn "failed to set the owner for \"$file\" !"
return 0
fi
if ! disabled "$REMOVE"; then
rm -f "$BOOT" 2>/dev/null || true
fi
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[@]}" mKill "${pids[@]}"
closeNetwork closeNetwork
return 0
}
finish() {
local reason=$1
touch "$QEMU_END"
forceKillQemu "$reason"
if [ ! -f "$STORAGE/windows.boot" ]; then
markWindowsBooted
fi
cleanupHelpers
if ! waitPidFile "$QEMU_PID" 10; then if ! waitPidFile "$QEMU_PID" 10; then
warn "Timed out while waiting for $(app) to exit!" warn "Timed out while waiting for $(app) to exit!"
fi fi
(( reason != 1 )) && echo && echo " Shutdown completed!" (( reason != 1 )) && echo && echo " Shutdown completed!"
exit "$reason" exit "$reason"
} }
normalizeTimeout() {
local term_grace=3 # seconds before loop ends to send SIGTERM
local cleanup_grace=3 # seconds reserved after the loop for cleanup
local min
TIMEOUT=$(strip "$TIMEOUT")
if [[ ! "$TIMEOUT" =~ ^[0-9]+$ ]]; then
TIMEOUT=115
fi
if (( TIMEOUT >= 30 )); then
term_grace=5
cleanup_grace=5
elif (( TIMEOUT >= 15 )); then
term_grace=4
cleanup_grace=4
fi
min=$((term_grace + cleanup_grace + 1))
(( TIMEOUT < min )) && (( TIMEOUT = min ))
wait_until=$((TIMEOUT - cleanup_grace))
sigterm_at=$((wait_until - term_grace))
return 0
}
sendAcpiShutdown() {
[ ! -S "$QEMU_DIR/monitor.sock" ] && return 0
# Send ACPI shutdown signal
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
local pid="$1"
local name="$APP"
local slp
while (( cnt <= wait_until )); do
sleep 1 &
slp=$!
# Stop waiting if the process has exited
! isAlive "$pid" && break
# Workaround for stale/zombie QEMU pid file
[ ! -s "$QEMU_PID" ] && break
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
sendAcpiShutdown
wait "$slp"
(( cnt++ ))
done
return 0
}
graceful_shutdown() { graceful_shutdown() {
local sig="$1" local sig="$1"
@@ -126,13 +300,7 @@ graceful_shutdown() {
[[ $BASHPID != "$TRAP_PID" ]] && return [[ $BASHPID != "$TRAP_PID" ]] && return
case "$sig" in code=$(signalCode "$sig")
SIGHUP) code=129 ;;
SIGINT) code=130 ;;
SIGQUIT) code=131 ;;
SIGABRT) code=134 ;;
SIGTERM) code=143 ;;
esac
if [ -f "$QEMU_END" ]; then if [ -f "$QEMU_END" ]; then
echo && info "Received $1 signal while already shutting down..." echo && info "Received $1 signal while already shutting down..."
@@ -143,68 +311,27 @@ graceful_shutdown() {
touch "$QEMU_END" touch "$QEMU_END"
echo && info "Received $1 signal, sending ACPI shutdown signal..." echo && info "Received $1 signal, sending ACPI shutdown signal..."
if [ ! -s "$QEMU_PID" ] || ! read -r pid <"$QEMU_PID"; then if ! readQemuPid pid; then
warn "QEMU PID file ($QEMU_PID) does not exist?" warn "QEMU PID file ($QEMU_PID) does not exist?"
finish "$code" finish "$code"
fi fi
if [ -z "$pid" ] || ! isAlive "$pid"; then if ! isAlive "$pid"; then
warn "QEMU process with PID $pid does not exist?" warn "QEMU process with PID $pid does not exist?"
finish "$code" finish "$code"
fi fi
if ! ready; then if ! ready; then
info "Cannot send ACPI signal during $(app) setup, aborting..." abortDuringSetup "$code"
sKill "$QEMU_PID"
if ! waitPidFile "$QEMU_PID" 5; then
warn "Timed out while waiting for $(app) to exit!"
fi
finish "$code"
fi fi
local cnt=0 abort=0 factor=3 offset=3 min max name normalizeTimeout
waitForShutdown "$pid"
[[ "$TIMEOUT" =~ ^[0-9]+$ ]] || TIMEOUT=115
[ "$TIMEOUT" -ge 15 ] && factor=4 && offset=4
[ "$TIMEOUT" -ge 30 ] && factor=5 && offset=5
min=$((factor + offset + 1))
[ "$TIMEOUT" -lt "$min" ] && TIMEOUT="$min"
max=$(( TIMEOUT - offset ))
abort=$(( max - factor ))
name="$(app)"
while [ "$cnt" -le "$max" ]; do
sleep 1 &
local slp=$!
! isAlive "$pid" && break
# Workaround for zombie pid
[ ! -s "$QEMU_PID" ] && break
if [ "$cnt" -ne "$abort" ]; then
if [ "$cnt" -gt 0 ]; then
info "Waiting for $name to shut down... ($cnt/$max)"
fi
else
info "${name^} is still running, sending SIGTERM... ($cnt/$max)"
{ kill -15 -- "$pid" || :; } 2>/dev/null
fi
# Send ACPI shutdown signal
if [ -S "$QEMU_DIR/monitor.sock" ]; then
nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || :
fi
wait $slp
(( cnt++ ))
done
finish "$code" finish "$code"
} }
[[ "$SHUTDOWN" != [Yy1]* ]] && return 0 ! enabled "$SHUTDOWN" && return 0
[ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT" [ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT"
_trap graceful_shutdown SIGTERM SIGHUP SIGABRT SIGQUIT _trap graceful_shutdown SIGTERM SIGHUP SIGABRT SIGQUIT
+230 -141
View File
@@ -3,49 +3,86 @@ set -Eeuo pipefail
: "${SAMBA:="Y"}" # Enable Samba : "${SAMBA:="Y"}" # Enable Samba
: "${SAMBA_DEBUG:="N"}" # Disable debug : "${SAMBA_DEBUG:="N"}" # Disable debug
: "${SAMBA_CONFIG:="/etc/samba/smb.conf"}"
tmp="/tmp/smb"
rm -rf "$tmp"
DDN_PID="/var/run/wsdd.pid" DDN_PID="/var/run/wsdd.pid"
NMB_PID="/var/run/samba/nmbd.pid" NMB_PID="/var/run/samba/nmbd.pid"
SMB_PID="/var/run/samba/smbd.pid" SMB_PID="/var/run/samba/smbd.pid"
rm -f "$SMB_PID" "$NMB_PID" "$DDN_PID" if ! rm -f "$SMB_PID" "$NMB_PID" "$DDN_PID"; then
error "Failed to clean Samba PID files!"
return 0
fi
[[ "$SAMBA" == [Nn]* ]] && return 0 disabled "$SAMBA" && return 0
[[ "$NETWORK" == [Nn]* ]] && return 0 disabled "$NETWORK" && return 0
configureNetwork() {
if enabled "$DHCP"; then
hostname="$UPLINK"
interfaces="$DEV"
return 0
fi
if [[ "$DHCP" == [Yy1]* ]]; then
socket="$IP"
hostname="$IP"
interfaces="$VM_NET_DEV"
else
hostname="host.lan" hostname="host.lan"
case "${NETWORK,,}" in
"passt" | "slirp" ) if isUserMode; then
interfaces="lo" interfaces="lo"
socket="127.0.0.1" ;; else
*) interfaces="$BRIDGE"
socket="$VM_NET_IP" fi
interfaces="$VM_NET_BRIDGE" ;;
esac
if [ -n "${SAMBA_INTERFACE:-}" ]; then if [ -n "${SAMBA_INTERFACE:-}" ]; then
interfaces+=",$SAMBA_INTERFACE" interfaces+=",$SAMBA_INTERFACE"
fi fi
fi
html "Initializing shared folder..." return 0
SAMBA_CONFIG="/etc/samba/smb.conf" }
[[ "$DEBUG" == [Yy1]* ]] && echo "Starting Samba daemon..."
writeReadme() {
local dir="$1"
local ref="$2"
if ! {
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"; then
error "Failed to write shared folder readme!"
return 1
fi
return 0
}
addShare() { addShare() {
local dir="$1" local dir="$1"
local ref="$2" local ref="$2"
local name="$3" local name="$3"
local comment="$4" local comment="$4"
local cfg="$5" local cfg="$5"
local owner="" local owner=""
local tmp="/tmp/smb"
if [ ! -d "$dir" ]; then if [ ! -d "$dir" ]; then
if ! mkdir -p "$dir"; then if ! mkdir -p "$dir"; then
@@ -54,171 +91,223 @@ addShare() {
fi fi
if ! ls -A "$dir" >/dev/null 2>&1; then if ! ls -A "$dir" >/dev/null 2>&1; then
msg="No permission to access shared folder ($dir)." local msg="No permission to access shared folder ($dir)."
msg+=" If SELinux is active, you need to add the \":Z\" flag to the bind mount." msg+=" If SELinux is active, you need to add the \":Z\" flag to the bind mount."
error "$msg" && return 1 error "$msg" && return 1
fi fi
if [ ! -w "$dir" ]; then if [ ! -w "$dir" ]; then
msg="shared folder ($dir) is not writeable!" local msg="shared folder ($dir) is not writeable!"
warn "$msg" warn "$msg"
fi fi
if [ -z "$(ls -A "$dir")" ]; then if [ -z "$(ls -A "$dir")" ]; then
if ! chmod 2777 "$dir"; then if ! chmod 2777 "$dir"; then
error "Failed to set permissions for directory $dir" && return 1 error "Failed to set permissions for directory $dir" && return 1
fi 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 [[ "$owner" == "0" ]]; then
if ! chown "1000:1000" "$dir"; then if ! chown "1000:1000" "$dir"; then
error "Failed to set ownership for directory $dir" && return 1 error "Failed to set ownership for directory $dir" && return 1
fi fi
fi fi
fi fi
if [[ "$dir" == "$tmp" ]]; then if [[ "$dir" == "$tmp" ]]; then
writeReadme "$dir" "$ref" || return 1
{ 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"
fi fi
{ echo "" if ! {
echo "[$name]" echo ""
echo " path = $dir" echo "[$name]"
echo " comment = $comment" echo " path = $dir"
echo " writable = yes" echo " comment = $comment"
echo " guest ok = yes" echo " writable = yes"
echo " guest only = yes" echo " guest ok = yes"
} >> "$cfg" echo " guest only = yes"
} >> "$cfg"; then
error "Failed to update Samba config \"$cfg\" !"
return 1
fi
return 0 return 0
} }
{ echo "[global]" writeConfig() {
echo " server string = Dockur"
echo " netbios name = $hostname"
echo " workgroup = WORKGROUP"
echo " interfaces = $interfaces"
echo " bind interfaces only = yes"
echo " socket address = $socket"
echo " security = user"
echo " guest account = nobody"
echo " map to guest = Bad User"
echo " server min protocol = NT1"
echo " follow symlinks = yes"
echo " wide links = yes"
echo " unix extensions = no"
echo " inherit owner = yes"
echo " create mask = 0666"
echo " directory mask = 02777"
echo " force user = root"
echo " force group = root"
echo " force create mode = 0666"
echo " force directory mode = 02777"
echo ""
echo " # Disable printing services"
echo " load printers = no"
echo " printing = bsd"
echo " printcap name = /dev/null"
echo " disable spoolss = yes"
} > "$SAMBA_CONFIG"
# Add shared folders if ! {
share="/shared" echo "[global]"
[ ! -d "$share" ] && [ -d "$STORAGE/shared" ] && share="$STORAGE/shared" echo " server string = Dockur"
[ ! -d "$share" ] && [ -d "/data" ] && share="/data" echo " netbios name = $hostname"
[ ! -d "$share" ] && [ -d "$STORAGE/data" ] && share="$STORAGE/data" echo " workgroup = WORKGROUP"
[ ! -d "$share" ] && share="$tmp" echo " interfaces = $interfaces"
echo " bind interfaces only = yes"
echo " security = user"
echo " guest account = nobody"
echo " map to guest = Bad User"
echo " server min protocol = NT1"
echo " follow symlinks = yes"
echo " wide links = yes"
echo " unix extensions = no"
echo " inherit owner = yes"
echo " create mask = 0666"
echo " directory mask = 02777"
echo " force user = root"
echo " force group = root"
echo " force create mode = 0666"
echo " force directory mode = 02777"
echo ""
echo " # Disable printing services"
echo " load printers = no"
echo " printing = bsd"
echo " printcap name = /dev/null"
echo " disable spoolss = yes"
} > "$SAMBA_CONFIG"; then
error "Failed to write Samba config \"$SAMBA_CONFIG\" !"
return 1
fi
! addShare "$share" "/shared" "Data" "Shared" "$SAMBA_CONFIG" && return 0 return 0
}
if [ -d "/shared2" ]; then selectPrimaryShare() {
addShare "/shared2" "/shared2" "Data2" "Shared" "$SAMBA_CONFIG" || :
elif [ -d "/data2" ]; then
addShare "/data2" "/shared2" "Data2" "Shared" "$SAMBA_CONFIG" || :
fi
if [ -d "/shared3" ]; then local tmp="/tmp/smb"
addShare "/shared3" "/shared3" "Data3" "Shared" "$SAMBA_CONFIG" || :
elif [ -d "/data3" ]; then
addShare "/data3" "/shared3" "Data3" "Shared" "$SAMBA_CONFIG" || :
fi
# Create directories if missing if ! rm -rf "$tmp"; then
mkdir -p /var/lib/samba/sysvol error "Failed to clean temporary Samba folder!"
mkdir -p /var/lib/samba/private return 1
mkdir -p /var/lib/samba/bind-dns fi
# Try to repair Samba permissions share="/shared"
[ -d /run/samba/msg.lock ] && chmod -R 0755 /run/samba/msg.lock 2>/dev/null || : [ ! -d "$share" ] && [ -d "$STORAGE/shared" ] && share="$STORAGE/shared"
[ -d /var/log/samba/cores ] && chmod -R 0700 /var/log/samba/cores 2>/dev/null || : [ ! -d "$share" ] && [ -d "/data" ] && share="/data"
[ -d /var/cache/samba/msg.lock ] && chmod -R 0755 /var/cache/samba/msg.lock 2>/dev/null || : [ ! -d "$share" ] && [ -d "$STORAGE/data" ] && share="$STORAGE/data"
[ ! -d "$share" ] && share="$tmp"
rm -f /var/log/samba/log.smbd return 0
}
if ! smbd -l /var/log/samba; then addOptionalShare() {
SAMBA_DEBUG="Y"
error "Failed to start Samba daemon!"
fi
if [[ "$SAMBA_DEBUG" == [Yy1]* ]]; then local index="$1"
tail -fn +0 /var/log/samba/log.smbd --pid=$$ & local ref="/shared$index"
fi local name="Data$index"
case "${NETWORK,,}" in if [ -d "$ref" ]; then
"passt" | "slirp" ) addShare "$ref" "$ref" "$name" "Shared" "$SAMBA_CONFIG" || :
return 0 ;; elif [ -d "/data$index" ]; then
esac addShare "/data$index" "$ref" "$name" "Shared" "$SAMBA_CONFIG" || :
fi
if [[ "${BOOT_MODE:-}" == "windows_legacy" ]]; then 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
return 0
}
startDaemon() {
local name="$1"
local log="$2"
shift 2
rm -f "$log" || :
if ! "$@"; then
SAMBA_DEBUG="Y"
error "Failed to start $name daemon!"
fi
debugLog "$log"
return 0
}
startSamba() {
startDaemon "Samba" "/var/log/samba/log.smbd" \
smbd -l /var/log/samba
return 0
}
startNetbios() {
# Enable NetBIOS on Windows 7 and lower # 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 return 0
SAMBA_DEBUG="Y" }
error "Failed to start NetBIOS daemon!"
fi
if [[ "$SAMBA_DEBUG" == [Yy1]* ]]; then startWsddn() {
tail -fn +0 /var/log/samba/log.nmbd --pid=$$ &
fi
else
# Enable Web Service Discovery on Vista and up # 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
if ! wsddn -i "${interfaces%%,*}" -H "$hostname" --unixd --log-file=/var/log/wsddn.log --pid-file="$DDN_PID"; then startDaemon "wsddn" "/var/log/wsddn.log" \
SAMBA_DEBUG="Y" wsddn -i "${interfaces%%,*}" -H "$hostname" \
error "Failed to start wsddn daemon!" --unixd --log-file=/var/log/wsddn.log --pid-file="$DDN_PID"
fi
if [[ "$SAMBA_DEBUG" == [Yy1]* ]]; then return 0
tail -fn +0 /var/log/wsddn.log --pid=$$ & }
fi
configureNetwork || return 0
html "Initializing shared folder..."
enabled "$DEBUG" && echo "Starting Samba daemon..."
writeConfig || return 0
# Add shared folders
selectPrimaryShare || return 0
addShare "$share" "/shared" "Data" "Shared" "$SAMBA_CONFIG" || return 0
addOptionalShare "2" || :
addOptionalShare "3" || :
prepareSambaDirs || return 0
startSamba || return 0
isUserMode && return 0
if [[ "${BOOT_MODE:-}" == "windows_legacy" ]]; then
startNetbios || :
else
startWsddn || :
fi fi
return 0 return 0