mirror of
https://github.com/dockur/windows.git
synced 2025-10-27 11:25:49 +00:00
Merge remote-tracking branch 'bucket_origin/master' into separate_custom_commits_from_base
This commit is contained in:
commit
6da9e41989
23 changed files with 1640 additions and 34 deletions
36
.gitlab-ci.yml
Normal file
36
.gitlab-ci.yml
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
services:
|
||||||
|
- name: docker:dind
|
||||||
|
|
||||||
|
default:
|
||||||
|
image: docker:latest
|
||||||
|
artifacts:
|
||||||
|
expire_in: 1 week
|
||||||
|
interruptible: true
|
||||||
|
retry:
|
||||||
|
max: 2
|
||||||
|
when: runner_system_failure
|
||||||
|
tags:
|
||||||
|
- infra-docker-dind
|
||||||
|
|
||||||
|
variables:
|
||||||
|
DOCKER_DRIVER: overlay2
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- build
|
||||||
|
|
||||||
|
build:
|
||||||
|
stage: build
|
||||||
|
variables:
|
||||||
|
IMAGE_NAME: "registry.digitalarsenal.net/low-level-hacks/third-party-build/dockur_windows"
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_TAG
|
||||||
|
variables:
|
||||||
|
IMAGE_VERSION: "$CI_COMMIT_TAG"
|
||||||
|
- if: $CI_COMMIT_TAG == null
|
||||||
|
variables:
|
||||||
|
IMAGE_VERSION: "$CI_COMMIT_REF_SLUG"
|
||||||
|
before_script:
|
||||||
|
- apk add --no-cache docker-compose bash kmod
|
||||||
|
script:
|
||||||
|
- docker login -u "$CI_REGISTRY_USER" -p "$REGISTRY_PUSH_ACCESS_TOKEN" "$CI_REGISTRY"
|
||||||
|
- ./prepare_image.sh
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
<SystemLocale>en-US</SystemLocale>
|
<SystemLocale>en-US</SystemLocale>
|
||||||
<UILanguage>en-US</UILanguage>
|
<UILanguage>en-US</UILanguage>
|
||||||
<UserLocale>en-US</UserLocale>
|
<UserLocale>en-US</UserLocale>
|
||||||
|
<TimeZone>UTC</TimeZone>
|
||||||
</component>
|
</component>
|
||||||
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||||
<DiskConfiguration>
|
<DiskConfiguration>
|
||||||
|
|
@ -102,6 +103,10 @@
|
||||||
<Order>4</Order>
|
<Order>4</Order>
|
||||||
<Path>reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f</Path>
|
<Path>reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f</Path>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>5</Order>
|
||||||
|
<Path>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\msiserver" /v Start /t REG_DWORD /d 2 /f</Path>
|
||||||
|
</SynchronousCommand>
|
||||||
</RunSynchronous>
|
</RunSynchronous>
|
||||||
</component>
|
</component>
|
||||||
</settings>
|
</settings>
|
||||||
|
|
@ -133,6 +138,7 @@
|
||||||
<SupportURL>https://github.com/dockur/windows/issues</SupportURL>
|
<SupportURL>https://github.com/dockur/windows/issues</SupportURL>
|
||||||
</OEMInformation>
|
</OEMInformation>
|
||||||
<OEMName>Windows for Docker</OEMName>
|
<OEMName>Windows for Docker</OEMName>
|
||||||
|
<TimeZone>UTC</TimeZone>
|
||||||
</component>
|
</component>
|
||||||
<component name="Microsoft-Windows-ErrorReportingCore" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
<component name="Microsoft-Windows-ErrorReportingCore" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||||
<DisableWER>1</DisableWER>
|
<DisableWER>1</DisableWER>
|
||||||
|
|
@ -297,6 +303,7 @@
|
||||||
<TCGSecurityActivationDisabled>1</TCGSecurityActivationDisabled>
|
<TCGSecurityActivationDisabled>1</TCGSecurityActivationDisabled>
|
||||||
</component>
|
</component>
|
||||||
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||||
|
<TimeZone>UTC</TimeZone>
|
||||||
<UserAccounts>
|
<UserAccounts>
|
||||||
<LocalAccounts>
|
<LocalAccounts>
|
||||||
<LocalAccount wcm:action="add">
|
<LocalAccount wcm:action="add">
|
||||||
|
|
@ -458,9 +465,19 @@
|
||||||
</SynchronousCommand>
|
</SynchronousCommand>
|
||||||
<SynchronousCommand wcm:action="add">
|
<SynchronousCommand wcm:action="add">
|
||||||
<Order>24</Order>
|
<Order>24</Order>
|
||||||
<CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine>
|
<CommandLine>cmd /C if exist "C:\OEM\install.bat" cmd /C C:\OEM\install.bat</CommandLine>
|
||||||
<Description>Execute custom script from the OEM folder if exists</Description>
|
<Description>Execute custom script from the OEM folder if exists</Description>
|
||||||
</SynchronousCommand>
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>25</Order>
|
||||||
|
<CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v "IPAutoconfigurationEnabled" /t REG_DWORD /d 0 /f</CommandLine>
|
||||||
|
<Description>Disable ip autoconfiguration for network interfaces</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>26</Order>
|
||||||
|
<CommandLine>cmd /C "type nul > \\host.lan\Data\prepared"</CommandLine>
|
||||||
|
<Description>Let host known that all configuration is done</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
</FirstLogonCommands>
|
</FirstLogonCommands>
|
||||||
</component>
|
</component>
|
||||||
</settings>
|
</settings>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
<SystemLocale>en-US</SystemLocale>
|
<SystemLocale>en-US</SystemLocale>
|
||||||
<UILanguage>en-US</UILanguage>
|
<UILanguage>en-US</UILanguage>
|
||||||
<UserLocale>en-US</UserLocale>
|
<UserLocale>en-US</UserLocale>
|
||||||
|
<TimeZone>UTC</TimeZone>
|
||||||
</component>
|
</component>
|
||||||
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||||
<DiskConfiguration>
|
<DiskConfiguration>
|
||||||
|
|
@ -105,6 +106,10 @@
|
||||||
<Order>4</Order>
|
<Order>4</Order>
|
||||||
<Path>reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f</Path>
|
<Path>reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f</Path>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>5</Order>
|
||||||
|
<Path>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\msiserver" /v Start /t REG_DWORD /d 2 /f</Path>
|
||||||
|
</SynchronousCommand>
|
||||||
</RunSynchronous>
|
</RunSynchronous>
|
||||||
</component>
|
</component>
|
||||||
</settings>
|
</settings>
|
||||||
|
|
@ -136,6 +141,7 @@
|
||||||
<SupportURL>https://github.com/dockur/windows/issues</SupportURL>
|
<SupportURL>https://github.com/dockur/windows/issues</SupportURL>
|
||||||
</OEMInformation>
|
</OEMInformation>
|
||||||
<OEMName>Windows for Docker</OEMName>
|
<OEMName>Windows for Docker</OEMName>
|
||||||
|
<TimeZone>UTC</TimeZone>
|
||||||
</component>
|
</component>
|
||||||
<component name="Microsoft-Windows-ErrorReportingCore" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
<component name="Microsoft-Windows-ErrorReportingCore" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||||
<DisableWER>1</DisableWER>
|
<DisableWER>1</DisableWER>
|
||||||
|
|
@ -300,6 +306,7 @@
|
||||||
<TCGSecurityActivationDisabled>1</TCGSecurityActivationDisabled>
|
<TCGSecurityActivationDisabled>1</TCGSecurityActivationDisabled>
|
||||||
</component>
|
</component>
|
||||||
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||||
|
<TimeZone>UTC</TimeZone>
|
||||||
<UserAccounts>
|
<UserAccounts>
|
||||||
<LocalAccounts>
|
<LocalAccounts>
|
||||||
<LocalAccount wcm:action="add">
|
<LocalAccount wcm:action="add">
|
||||||
|
|
@ -461,9 +468,19 @@
|
||||||
</SynchronousCommand>
|
</SynchronousCommand>
|
||||||
<SynchronousCommand wcm:action="add">
|
<SynchronousCommand wcm:action="add">
|
||||||
<Order>24</Order>
|
<Order>24</Order>
|
||||||
<CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine>
|
<CommandLine>cmd /C if exist "C:\OEM\install.bat" cmd /C C:\OEM\install.bat</CommandLine>
|
||||||
<Description>Execute custom script from the OEM folder if exists</Description>
|
<Description>Execute custom script from the OEM folder if exists</Description>
|
||||||
</SynchronousCommand>
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>25</Order>
|
||||||
|
<CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v "IPAutoconfigurationEnabled" /t REG_DWORD /d 0 /f</CommandLine>
|
||||||
|
<Description>Disable ip autoconfiguration for network interfaces</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>26</Order>
|
||||||
|
<CommandLine>cmd /C "type nul > \\host.lan\Data\prepared"</CommandLine>
|
||||||
|
<Description>Let host known that all configuration is done</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
</FirstLogonCommands>
|
</FirstLogonCommands>
|
||||||
</component>
|
</component>
|
||||||
</settings>
|
</settings>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
<SystemLocale>en-US</SystemLocale>
|
<SystemLocale>en-US</SystemLocale>
|
||||||
<UILanguage>en-US</UILanguage>
|
<UILanguage>en-US</UILanguage>
|
||||||
<UserLocale>en-US</UserLocale>
|
<UserLocale>en-US</UserLocale>
|
||||||
|
<TimeZone>UTC</TimeZone>
|
||||||
</component>
|
</component>
|
||||||
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||||
<DiskConfiguration>
|
<DiskConfiguration>
|
||||||
|
|
@ -105,6 +106,10 @@
|
||||||
<Order>4</Order>
|
<Order>4</Order>
|
||||||
<Path>reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f</Path>
|
<Path>reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f</Path>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>5</Order>
|
||||||
|
<Path>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\msiserver" /v Start /t REG_DWORD /d 2 /f</Path>
|
||||||
|
</SynchronousCommand>
|
||||||
</RunSynchronous>
|
</RunSynchronous>
|
||||||
</component>
|
</component>
|
||||||
</settings>
|
</settings>
|
||||||
|
|
@ -136,6 +141,7 @@
|
||||||
<SupportURL>https://github.com/dockur/windows/issues</SupportURL>
|
<SupportURL>https://github.com/dockur/windows/issues</SupportURL>
|
||||||
</OEMInformation>
|
</OEMInformation>
|
||||||
<OEMName>Windows for Docker</OEMName>
|
<OEMName>Windows for Docker</OEMName>
|
||||||
|
<TimeZone>UTC</TimeZone>
|
||||||
</component>
|
</component>
|
||||||
<component name="Microsoft-Windows-ErrorReportingCore" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
<component name="Microsoft-Windows-ErrorReportingCore" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||||
<DisableWER>1</DisableWER>
|
<DisableWER>1</DisableWER>
|
||||||
|
|
@ -300,6 +306,7 @@
|
||||||
<TCGSecurityActivationDisabled>1</TCGSecurityActivationDisabled>
|
<TCGSecurityActivationDisabled>1</TCGSecurityActivationDisabled>
|
||||||
</component>
|
</component>
|
||||||
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||||
|
<TimeZone>UTC</TimeZone>
|
||||||
<UserAccounts>
|
<UserAccounts>
|
||||||
<LocalAccounts>
|
<LocalAccounts>
|
||||||
<LocalAccount wcm:action="add">
|
<LocalAccount wcm:action="add">
|
||||||
|
|
@ -461,9 +468,19 @@
|
||||||
</SynchronousCommand>
|
</SynchronousCommand>
|
||||||
<SynchronousCommand wcm:action="add">
|
<SynchronousCommand wcm:action="add">
|
||||||
<Order>24</Order>
|
<Order>24</Order>
|
||||||
<CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine>
|
<CommandLine>cmd /C if exist "C:\OEM\install.bat" cmd /C C:\OEM\install.bat</CommandLine>
|
||||||
<Description>Execute custom script from the OEM folder if exists</Description>
|
<Description>Execute custom script from the OEM folder if exists</Description>
|
||||||
</SynchronousCommand>
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>25</Order>
|
||||||
|
<CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v "IPAutoconfigurationEnabled" /t REG_DWORD /d 0 /f</CommandLine>
|
||||||
|
<Description>Disable ip autoconfiguration for network interfaces</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>26</Order>
|
||||||
|
<CommandLine>cmd /C "type nul > \\host.lan\Data\prepared"</CommandLine>
|
||||||
|
<Description>Let host known that all configuration is done</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
</FirstLogonCommands>
|
</FirstLogonCommands>
|
||||||
</component>
|
</component>
|
||||||
</settings>
|
</settings>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
<SystemLocale>en-US</SystemLocale>
|
<SystemLocale>en-US</SystemLocale>
|
||||||
<UILanguage>en-US</UILanguage>
|
<UILanguage>en-US</UILanguage>
|
||||||
<UserLocale>en-US</UserLocale>
|
<UserLocale>en-US</UserLocale>
|
||||||
|
<TimeZone>UTC</TimeZone>
|
||||||
</component>
|
</component>
|
||||||
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||||
<DiskConfiguration>
|
<DiskConfiguration>
|
||||||
|
|
@ -105,6 +106,10 @@
|
||||||
<Order>4</Order>
|
<Order>4</Order>
|
||||||
<Path>reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f</Path>
|
<Path>reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f</Path>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>5</Order>
|
||||||
|
<Path>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\msiserver" /v Start /t REG_DWORD /d 2 /f</Path>
|
||||||
|
</SynchronousCommand>
|
||||||
</RunSynchronous>
|
</RunSynchronous>
|
||||||
</component>
|
</component>
|
||||||
</settings>
|
</settings>
|
||||||
|
|
@ -136,6 +141,7 @@
|
||||||
<SupportURL>https://github.com/dockur/windows/issues</SupportURL>
|
<SupportURL>https://github.com/dockur/windows/issues</SupportURL>
|
||||||
</OEMInformation>
|
</OEMInformation>
|
||||||
<OEMName>Windows for Docker</OEMName>
|
<OEMName>Windows for Docker</OEMName>
|
||||||
|
<TimeZone>UTC</TimeZone>
|
||||||
</component>
|
</component>
|
||||||
<component name="Microsoft-Windows-ErrorReportingCore" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
<component name="Microsoft-Windows-ErrorReportingCore" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||||
<DisableWER>1</DisableWER>
|
<DisableWER>1</DisableWER>
|
||||||
|
|
@ -300,6 +306,7 @@
|
||||||
<TCGSecurityActivationDisabled>1</TCGSecurityActivationDisabled>
|
<TCGSecurityActivationDisabled>1</TCGSecurityActivationDisabled>
|
||||||
</component>
|
</component>
|
||||||
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||||
|
<TimeZone>UTC</TimeZone>
|
||||||
<UserAccounts>
|
<UserAccounts>
|
||||||
<LocalAccounts>
|
<LocalAccounts>
|
||||||
<LocalAccount wcm:action="add">
|
<LocalAccount wcm:action="add">
|
||||||
|
|
@ -461,9 +468,19 @@
|
||||||
</SynchronousCommand>
|
</SynchronousCommand>
|
||||||
<SynchronousCommand wcm:action="add">
|
<SynchronousCommand wcm:action="add">
|
||||||
<Order>24</Order>
|
<Order>24</Order>
|
||||||
<CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine>
|
<CommandLine>cmd /C if exist "C:\OEM\install.bat" cmd /C C:\OEM\install.bat</CommandLine>
|
||||||
<Description>Execute custom script from the OEM folder if exists</Description>
|
<Description>Execute custom script from the OEM folder if exists</Description>
|
||||||
</SynchronousCommand>
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>25</Order>
|
||||||
|
<CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v "IPAutoconfigurationEnabled" /t REG_DWORD /d 0 /f</CommandLine>
|
||||||
|
<Description>Disable ip autoconfiguration for network interfaces</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>26</Order>
|
||||||
|
<CommandLine>cmd /C "type nul > \\host.lan\Data\prepared"</CommandLine>
|
||||||
|
<Description>Let host known that all configuration is done</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
</FirstLogonCommands>
|
</FirstLogonCommands>
|
||||||
</component>
|
</component>
|
||||||
</settings>
|
</settings>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
<SystemLocale>en-US</SystemLocale>
|
<SystemLocale>en-US</SystemLocale>
|
||||||
<UILanguage>en-US</UILanguage>
|
<UILanguage>en-US</UILanguage>
|
||||||
<UserLocale>en-US</UserLocale>
|
<UserLocale>en-US</UserLocale>
|
||||||
|
<TimeZone>UTC</TimeZone>
|
||||||
</component>
|
</component>
|
||||||
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||||
<DiskConfiguration>
|
<DiskConfiguration>
|
||||||
|
|
@ -105,6 +106,10 @@
|
||||||
<Order>4</Order>
|
<Order>4</Order>
|
||||||
<Path>reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f</Path>
|
<Path>reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f</Path>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>5</Order>
|
||||||
|
<Path>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\msiserver" /v Start /t REG_DWORD /d 2 /f</Path>
|
||||||
|
</SynchronousCommand>
|
||||||
</RunSynchronous>
|
</RunSynchronous>
|
||||||
</component>
|
</component>
|
||||||
</settings>
|
</settings>
|
||||||
|
|
@ -136,6 +141,7 @@
|
||||||
<SupportURL>https://github.com/dockur/windows/issues</SupportURL>
|
<SupportURL>https://github.com/dockur/windows/issues</SupportURL>
|
||||||
</OEMInformation>
|
</OEMInformation>
|
||||||
<OEMName>Windows for Docker</OEMName>
|
<OEMName>Windows for Docker</OEMName>
|
||||||
|
<TimeZone>UTC</TimeZone>
|
||||||
</component>
|
</component>
|
||||||
<component name="Microsoft-Windows-ErrorReportingCore" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
<component name="Microsoft-Windows-ErrorReportingCore" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||||
<DisableWER>1</DisableWER>
|
<DisableWER>1</DisableWER>
|
||||||
|
|
@ -300,6 +306,7 @@
|
||||||
<TCGSecurityActivationDisabled>1</TCGSecurityActivationDisabled>
|
<TCGSecurityActivationDisabled>1</TCGSecurityActivationDisabled>
|
||||||
</component>
|
</component>
|
||||||
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||||
|
<TimeZone>UTC</TimeZone>
|
||||||
<UserAccounts>
|
<UserAccounts>
|
||||||
<LocalAccounts>
|
<LocalAccounts>
|
||||||
<LocalAccount wcm:action="add">
|
<LocalAccount wcm:action="add">
|
||||||
|
|
@ -461,9 +468,19 @@
|
||||||
</SynchronousCommand>
|
</SynchronousCommand>
|
||||||
<SynchronousCommand wcm:action="add">
|
<SynchronousCommand wcm:action="add">
|
||||||
<Order>24</Order>
|
<Order>24</Order>
|
||||||
<CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine>
|
<CommandLine>cmd /C if exist "C:\OEM\install.bat" cmd /C C:\OEM\install.bat</CommandLine>
|
||||||
<Description>Execute custom script from the OEM folder if exists</Description>
|
<Description>Execute custom script from the OEM folder if exists</Description>
|
||||||
</SynchronousCommand>
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>25</Order>
|
||||||
|
<CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v "IPAutoconfigurationEnabled" /t REG_DWORD /d 0 /f</CommandLine>
|
||||||
|
<Description>Disable ip autoconfiguration for network interfaces</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>26</Order>
|
||||||
|
<CommandLine>cmd /C "type nul > \\host.lan\Data\prepared"</CommandLine>
|
||||||
|
<Description>Let host known that all configuration is done</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
</FirstLogonCommands>
|
</FirstLogonCommands>
|
||||||
</component>
|
</component>
|
||||||
</settings>
|
</settings>
|
||||||
|
|
|
||||||
50
compose.yml
50
compose.yml
|
|
@ -1,9 +1,51 @@
|
||||||
services:
|
services:
|
||||||
windows:
|
windows-build:
|
||||||
image: dockurr/windows
|
build: .
|
||||||
container_name: windows
|
container_name: windows-build
|
||||||
|
privileged: true
|
||||||
|
healthcheck:
|
||||||
|
test: "[ -f /data/prepared ] || exit 1"
|
||||||
|
interval: 30s
|
||||||
|
retries: 50
|
||||||
|
start_period: 600s
|
||||||
|
timeout: 2s
|
||||||
environment:
|
environment:
|
||||||
VERSION: "11"
|
VERSION: "11"
|
||||||
|
USERNAME: "bill"
|
||||||
|
PASSWORD: "gates"
|
||||||
|
DISK_FMT: "qcow2"
|
||||||
|
NETWORK: "Y"
|
||||||
|
DEBUG: "Y"
|
||||||
|
COMMIT: "Y"
|
||||||
|
devices:
|
||||||
|
- /dev/kvm
|
||||||
|
- /dev/net/tun
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
ports:
|
||||||
|
- 8006:8006
|
||||||
|
- 3389:3389/tcp
|
||||||
|
- 3389:3389/udp
|
||||||
|
restart: always
|
||||||
|
stop_grace_period: 2m
|
||||||
|
volumes:
|
||||||
|
- ./windows:/storage
|
||||||
|
- ./scripts:/oem
|
||||||
|
|
||||||
|
windows-installed:
|
||||||
|
image: $IMAGE_NAME:$IMAGE_VERSION
|
||||||
|
container_name: windows-installed
|
||||||
|
privileged: true
|
||||||
|
healthcheck:
|
||||||
|
test: "[ -f /local/ready ] || exit 1"
|
||||||
|
interval: 30s
|
||||||
|
retries: 20
|
||||||
|
start_period: 60s
|
||||||
|
timeout: 2s
|
||||||
|
environment:
|
||||||
|
VERSION: "11"
|
||||||
|
USERNAME: "bill"
|
||||||
|
PASSWORD: "gates"
|
||||||
devices:
|
devices:
|
||||||
- /dev/kvm
|
- /dev/kvm
|
||||||
- /dev/net/tun
|
- /dev/net/tun
|
||||||
|
|
@ -13,7 +55,5 @@ services:
|
||||||
- 8006:8006
|
- 8006:8006
|
||||||
- 3389:3389/tcp
|
- 3389:3389/tcp
|
||||||
- 3389:3389/udp
|
- 3389:3389/udp
|
||||||
volumes:
|
|
||||||
- ./windows:/storage
|
|
||||||
restart: always
|
restart: always
|
||||||
stop_grace_period: 2m
|
stop_grace_period: 2m
|
||||||
|
|
|
||||||
2
env.sh
Executable file
2
env.sh
Executable file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export IMAGE_NAME=${IMAGE_NAME:-"dockur_windows_installed"}
|
||||||
|
export IMAGE_VERSION=${IMAGE_VERSION:-"latest"}
|
||||||
35
prepare_image.sh
Executable file
35
prepare_image.sh
Executable file
|
|
@ -0,0 +1,35 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
source env.sh
|
||||||
|
|
||||||
|
echo "start to build and install windows"
|
||||||
|
docker compose up windows-build -d --build --force-recreate
|
||||||
|
|
||||||
|
echo "streaming logs..."
|
||||||
|
docker logs -f windows-build | tee windows-build.log &
|
||||||
|
|
||||||
|
echo "waiting for windows-build container to be healthy..."
|
||||||
|
while [[ "$(docker inspect --format='{{.State.Health.Status}}' windows-build 2>/dev/null)" != "healthy" ]]; do
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "windows installed, now stop container"
|
||||||
|
docker stop windows-build
|
||||||
|
|
||||||
|
echo "commit all the changes"
|
||||||
|
docker commit windows-build "$IMAGE_NAME:$IMAGE_VERSION"
|
||||||
|
docker images
|
||||||
|
|
||||||
|
echo "start container with windows installed"
|
||||||
|
docker compose up windows-installed -d
|
||||||
|
|
||||||
|
echo "streaming logs..."
|
||||||
|
docker logs -f windows-installed | tee windows-installed.log &
|
||||||
|
|
||||||
|
echo "waiting for windows-installed container to be healthy..."
|
||||||
|
while [[ "$(docker inspect --format='{{.State.Health.Status}}' windows-installed 2>/dev/null)" != "healthy" ]]; do
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
docker push "$IMAGE_NAME:$IMAGE_VERSION"
|
||||||
166
scripts/dependencies_windows.ps1
Normal file
166
scripts/dependencies_windows.ps1
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
# https://stackoverflow.com/questions/9948517/how-to-stop-a-powershell-script-on-the-first-error
|
||||||
|
function CheckStatus {
|
||||||
|
if (-not $?)
|
||||||
|
{
|
||||||
|
throw "Native Failure"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Validate-FileHash($filePath, $expectedHash, [Parameter(Mandatory=$false)] $algorithm) {
|
||||||
|
if ($algorithm -ne $null) {
|
||||||
|
$computedHash = Get-FileHash $filePath -Algorithm $algorithm
|
||||||
|
} else {
|
||||||
|
$computedHash = Get-FileHash $filePath
|
||||||
|
}
|
||||||
|
if ($computedHash.Hash -ne $expectedHash) {
|
||||||
|
Write-Error "incorrect hash for file: $filePath, actual: $($computedHash.Hash), expected: $expectedHash"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Install-STUN() {
|
||||||
|
$ZipPath = "stunserver_win64_1_2_16.zip"
|
||||||
|
$URL = "http://www.stunprotocol.org/$ZipPath"
|
||||||
|
$Destination = "C:\workspace\stunserver"
|
||||||
|
$Hash = "CDC8C68400E3B9ECE95F900699CEF1535CFCF4E59C34AF9A33F4679638ACA3A1"
|
||||||
|
|
||||||
|
echo "Downloading $URL"
|
||||||
|
curl.exe -L $URL -o $ZipPath
|
||||||
|
CheckStatus
|
||||||
|
|
||||||
|
Validate-FileHash $ZipPath $Hash
|
||||||
|
|
||||||
|
echo "Extracting $ZipPath to $Destination"
|
||||||
|
Expand-Archive $ZipPath -DestinationPath $Destination
|
||||||
|
CheckStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
function Install-iperf() {
|
||||||
|
$ZipPath = "iperf3.17_64.zip"
|
||||||
|
$URL = "https://files.budman.pw/$ZipPath"
|
||||||
|
$Hash = "C1AB63DE610D73779D1003753F8DCD3FAAE0B6AC5BE1EAF31BBF4A1D3D2E3356"
|
||||||
|
$Destination = "C:\workspace\iperf3"
|
||||||
|
$DestinationTmp = "$Destination.tmp"
|
||||||
|
|
||||||
|
echo "Downloading $URL"
|
||||||
|
curl.exe -L $URL -o $ZipPath
|
||||||
|
CheckStatus
|
||||||
|
|
||||||
|
Validate-FileHash $ZipPath $Hash
|
||||||
|
|
||||||
|
echo "Extracting $ZipPath to $DestinationTmp"
|
||||||
|
Expand-Archive $ZipPath -DestinationPath $DestinationTmp
|
||||||
|
CheckStatus
|
||||||
|
|
||||||
|
$firstSubDir = Get-ChildItem -Path $DestinationTmp -Directory | Select-Object -First 1
|
||||||
|
echo "Moving $DestinationTmp\$firstSubDir to $Destination"
|
||||||
|
mv $DestinationTmp\$firstSubDir $Destination
|
||||||
|
Remove-Item $DestinationTmp
|
||||||
|
}
|
||||||
|
|
||||||
|
function Install-Python() {
|
||||||
|
$InstallerPath = "python-3.13.0-amd64.exe"
|
||||||
|
$URL = "https://www.python.org/ftp/python/3.13.0/$InstallerPath"
|
||||||
|
$Hash = "78156AD0CF0EC4123BFB5333B40F078596EBF15F2D062A10144863680AFBDEFC"
|
||||||
|
|
||||||
|
echo "Downloading $URL"
|
||||||
|
curl.exe -L $URL -o $InstallerPath
|
||||||
|
CheckStatus
|
||||||
|
|
||||||
|
Validate-FileHash $InstallerPath $Hash
|
||||||
|
|
||||||
|
echo "Installing python.."
|
||||||
|
Start-Process -NoNewWindow -Wait -FilePath $PWD\$InstallerPath -ArgumentList "/quiet InstallAllUsers=1 PrependPath=1 Include_test=0 Include_doc=0 Include_dev=1 Include_launcher=0 Include_tcltk=0"
|
||||||
|
CheckStatus
|
||||||
|
|
||||||
|
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", [System.EnvironmentVariableTarget]::Machine)
|
||||||
|
|
||||||
|
python.exe -m pip install --upgrade pip
|
||||||
|
}
|
||||||
|
|
||||||
|
function Install-WinDump() {
|
||||||
|
$InstallerPath = "nmap-7.12-setup.exe"
|
||||||
|
$URL = "https://nmap.org/dist/$InstallerPath"
|
||||||
|
$Hash = "56580F1EEBDCCFBC5CE6D75690600225738DDBE8D991A417E56032869B0F43C7"
|
||||||
|
|
||||||
|
echo "Downloading $URL"
|
||||||
|
curl.exe -L $URL -o $InstallerPath
|
||||||
|
CheckStatus
|
||||||
|
|
||||||
|
Validate-FileHash $InstallerPath $Hash
|
||||||
|
|
||||||
|
echo "Installing winpcap.."
|
||||||
|
Start-Process -NoNewWindow -Wait -FilePath $PWD\$InstallerPath -ArgumentList "/S"
|
||||||
|
CheckStatus
|
||||||
|
|
||||||
|
sc.exe config npf start= auto
|
||||||
|
CheckStatus
|
||||||
|
|
||||||
|
$BinaryPath = "WinDump.exe"
|
||||||
|
$URL = "https://www.winpcap.org/windump/install/bin/windump_3_9_5/$BinaryPath"
|
||||||
|
$Hash = "d59bc54721951dec855cbb4bbc000f9a71ea4d95"
|
||||||
|
|
||||||
|
echo "Downloading $URL"
|
||||||
|
curl.exe -L $URL -o $BinaryPath
|
||||||
|
CheckStatus
|
||||||
|
|
||||||
|
Validate-FileHash $BinaryPath $Hash SHA1
|
||||||
|
}
|
||||||
|
|
||||||
|
function Install-QGA() {
|
||||||
|
# Define QEMU Guest Agent installer URL (change version if needed)
|
||||||
|
$QGA_URL = "https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso"
|
||||||
|
$QGA_ISO = "$env:TEMP\virtio-win.iso"
|
||||||
|
|
||||||
|
# Download QEMU Guest Agent ISO
|
||||||
|
Write-Host "Downloading QEMU Guest Agent ISO..."
|
||||||
|
curl.exe -L $QGA_URL -o $QGA_ISO
|
||||||
|
|
||||||
|
# Mount the ISO
|
||||||
|
Write-Host "Mounting ISO..."
|
||||||
|
$mount = Mount-DiskImage -ImagePath $QGA_ISO -PassThru | Get-Volume
|
||||||
|
$QGA_DRIVE = $mount.DriveLetter + ":"
|
||||||
|
|
||||||
|
# Define installer path
|
||||||
|
$QGA_MSI = "$QGA_DRIVE\guest-agent\qemu-ga-x86_64.msi"
|
||||||
|
|
||||||
|
# Install QEMU Guest Agent
|
||||||
|
Write-Host "Installing QEMU Guest Agent..."
|
||||||
|
Start-Process msiexec.exe -ArgumentList "/i `"$QGA_MSI`" /quiet /norestart" -Wait -NoNewWindow
|
||||||
|
|
||||||
|
Get-Service QEMU-GA
|
||||||
|
|
||||||
|
# Unmount the ISO
|
||||||
|
Write-Host "Unmounting ISO..."
|
||||||
|
Dismount-DiskImage -ImagePath $QGA_ISO
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
Remove-Item -Path $QGA_ISO -Force
|
||||||
|
|
||||||
|
Write-Host "QEMU Guest Agent installation complete."
|
||||||
|
}
|
||||||
|
|
||||||
|
[System.IO.Directory]::CreateDirectory("C:\workspace")
|
||||||
|
CheckStatus
|
||||||
|
|
||||||
|
cd C:\workspace
|
||||||
|
setx PATH "%PATH%;C:\workspace\uniffi"
|
||||||
|
|
||||||
|
Install-STUN
|
||||||
|
CheckStatus
|
||||||
|
|
||||||
|
Install-iperf
|
||||||
|
CheckStatus
|
||||||
|
|
||||||
|
Install-Python
|
||||||
|
CheckStatus
|
||||||
|
|
||||||
|
Install-WinDump
|
||||||
|
CheckStatus
|
||||||
|
|
||||||
|
Install-QGA
|
||||||
|
CheckStatus
|
||||||
|
|
||||||
|
pip install Pyro5==5.15
|
||||||
52
scripts/disable_updates.ps1
Normal file
52
scripts/disable_updates.ps1
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Set-RegistryProperty {
|
||||||
|
param (
|
||||||
|
[string]$path,
|
||||||
|
[string]$name,
|
||||||
|
[int]$value
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not (Test-Path $path)) {
|
||||||
|
New-Item -Path $path -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path "$path\$name")) {
|
||||||
|
New-ItemProperty -Path $path -Name $name -Value $value -Force
|
||||||
|
} else {
|
||||||
|
Set-ItemProperty -Path $path -Name $name -Value $value -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "Windows Update settings have been configured to disable automatic updates and notifications."
|
||||||
|
|
||||||
|
$settings = @(
|
||||||
|
@{ Type = "registry"; Name = "NoAutoUpdate"; Value = 1; Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" },
|
||||||
|
@{ Type = "registry"; Name = "AUOptions"; Value = 0; Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" },
|
||||||
|
@{ Type = "registry"; Name = "ExcludeWUDriversInQualityUpdate"; Value = 1; Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" },
|
||||||
|
@{ Type = "registry"; Name = "DisableWindowsUpdateAccess"; Value = 1; Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" },
|
||||||
|
@{ Type = "registry"; Name = "NoAutoRebootWithLoggedOnUsers"; Value = 1; Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" },
|
||||||
|
@{ Type = "registry"; Name = "DisableAutoReboot"; Value = 1; Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" },
|
||||||
|
@{ Type = "registry"; Name = "UseWUServer"; Value = 0; Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" },
|
||||||
|
@{ Type = "registry"; Name = "ExternalManaged"; Value = 1; Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" },
|
||||||
|
@{ Type = "registry"; Name = "DODownloadMode"; Value = 0; Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config" },
|
||||||
|
|
||||||
|
@{ Type = "service"; Name = "wuauserv"; Value = 4; Path = "HKLM:\SYSTEM\CurrentControlSet\Services\wuauserv" },
|
||||||
|
@{ Type = "service"; Name = "BITS"; Value = 4; Path = "HKLM:\SYSTEM\CurrentControlSet\Services\BITS" },
|
||||||
|
@{ Type = "service"; Name = "cryptsvc"; Value = 4; Path = "HKLM:\SYSTEM\CurrentControlSet\Services\cryptsvc" },
|
||||||
|
@{ Type = "service"; Name = "dosvc"; Value = 4; Path = "HKLM:\SYSTEM\CurrentControlSet\Services\dosvc" },
|
||||||
|
@{ Type = "service"; Name = "usosvc"; Value = 4; Path = "HKLM:\SYSTEM\CurrentControlSet\Services\usosvc" },
|
||||||
|
@{ Type = "service"; Name = "msiserver"; Value = 4; Path = "HKLM:\SYSTEM\CurrentControlSet\Services\msiserver" }
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($setting in $settings) {
|
||||||
|
if ($setting.Type -eq "registry") {
|
||||||
|
Set-RegistryProperty -path $setting.Path -name $setting.Name -value $setting.Value
|
||||||
|
Write-Output "Set $($setting.Name) to $($setting.Value) in $($setting.Path)."
|
||||||
|
} elseif ($setting.Type -eq "service") {
|
||||||
|
Set-RegistryProperty -path $setting.Path -name "Start" -value $setting.Value
|
||||||
|
Write-Output "Disabled $($setting.Name) service."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "All specified Windows Update services and group policies have been disabled."
|
||||||
62
scripts/enable_sshd.ps1
Normal file
62
scripts/enable_sshd.ps1
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
# Define variables
|
||||||
|
$OpenSSH_URL = "https://github.com/PowerShell/Win32-OpenSSH/releases/latest/download/OpenSSH-Win64.zip"
|
||||||
|
$OpenSSH_Install_Path = "C:\Program Files\OpenSSH"
|
||||||
|
$OpenSSH_Zip = "$env:TEMP\OpenSSH-Win64.zip"
|
||||||
|
|
||||||
|
# Function to check if running as Administrator
|
||||||
|
function Test-Admin {
|
||||||
|
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||||
|
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal($currentUser)
|
||||||
|
return $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Admin)) {
|
||||||
|
Write-Host "Please run this script as Administrator!" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ensure the install path exists
|
||||||
|
if (!(Test-Path $OpenSSH_Install_Path)) {
|
||||||
|
New-Item -ItemType Directory -Path $OpenSSH_Install_Path -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
# Download OpenSSH if not already present
|
||||||
|
Write-Host "Downloading OpenSSH..." -ForegroundColor Cyan
|
||||||
|
Invoke-WebRequest -Uri $OpenSSH_URL -OutFile $OpenSSH_Zip
|
||||||
|
|
||||||
|
# Extract OpenSSH
|
||||||
|
Write-Host "Extracting OpenSSH..." -ForegroundColor Cyan
|
||||||
|
Expand-Archive -Path $OpenSSH_Zip -DestinationPath $OpenSSH_Install_Path -Force
|
||||||
|
|
||||||
|
# Check if install-sshd.ps1 exists
|
||||||
|
if (!(Test-Path "$OpenSSH_Install_Path\OpenSSH-Win64\install-sshd.ps1")) {
|
||||||
|
Write-Host "❌ Error: install-sshd.ps1 not found in $OpenSSH_Install_Path. Extraction failed!" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Navigate to OpenSSH directory
|
||||||
|
Push-Location -Path $OpenSSH_Install_Path\OpenSSH-Win64
|
||||||
|
|
||||||
|
# Run install script
|
||||||
|
Write-Host "Installing OpenSSH service..." -ForegroundColor Green
|
||||||
|
powershell.exe -ExecutionPolicy Bypass -File install-sshd.ps1
|
||||||
|
|
||||||
|
# Set SSHD service to start automatically
|
||||||
|
Write-Host "Setting SSHD to start automatically..." -ForegroundColor Green
|
||||||
|
if (Get-Service sshd -ErrorAction SilentlyContinue) {
|
||||||
|
Set-Service -Name sshd -StartupType Automatic
|
||||||
|
Start-Service sshd
|
||||||
|
} else {
|
||||||
|
Write-Host "⚠ OpenSSH service was not installed correctly. Try running install-sshd.ps1 manually." -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verify installation
|
||||||
|
$sshdStatus = Get-Service -Name sshd -ErrorAction SilentlyContinue
|
||||||
|
if ($sshdStatus.Status -eq 'Running') {
|
||||||
|
Write-Host "✅ OpenSSH installation successful! You can now connect via SSH." -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "⚠ OpenSSH installation failed. Try restarting your computer and rerun the script." -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
Pop-Location
|
||||||
8
scripts/install.bat
Normal file
8
scripts/install.bat
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
pushd "C:/OEM"
|
||||||
|
|
||||||
|
powershell -ExecutionPolicy Bypass -File "dependencies_windows.ps1"
|
||||||
|
powershell -ExecutionPolicy Bypass -File "optimize.ps1"
|
||||||
|
powershell -ExecutionPolicy Bypass -File "disable_updates.ps1"
|
||||||
|
powershell -ExecutionPolicy Bypass -File "enable_sshd.ps1"
|
||||||
|
|
||||||
|
popd
|
||||||
36
scripts/optimize.ps1
Normal file
36
scripts/optimize.ps1
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
# Set Power Plan to High Performance and disable sleep
|
||||||
|
Write-Output "Configuring Power Plan to High Performance and disabling sleep..."
|
||||||
|
slmgr /rearm
|
||||||
|
powercfg -setactive SCHEME_MIN
|
||||||
|
powercfg /x -hibernate-timeout-ac 0
|
||||||
|
powercfg /x -hibernate-timeout-dc 0
|
||||||
|
powercfg /x -disk-timeout-ac 0
|
||||||
|
powercfg /x -disk-timeout-dc 0
|
||||||
|
powercfg /x -monitor-timeout-ac 0
|
||||||
|
powercfg /x -monitor-timeout-dc 0
|
||||||
|
powercfg /x -standby-timeout-ac 0
|
||||||
|
powercfg /x -standby-timeout-dc 0
|
||||||
|
|
||||||
|
# Disable Windows Search Indexing (optional, for minimal interruption)
|
||||||
|
Write-Output "Disabling Windows Search indexing service..."
|
||||||
|
Stop-Service -Name "WSearch" -Force -ErrorAction SilentlyContinue
|
||||||
|
Set-Service -Name "WSearch" -StartupType Disabled
|
||||||
|
|
||||||
|
# Set Network Adapters to not enter Power Saving mode
|
||||||
|
Write-Output "Disabling Power Saving for Network Adapters..."
|
||||||
|
Get-WmiObject -Namespace root\wmi -Class MSPower_DeviceEnable -Filter "InstanceName LIKE 'PCI\\\\VEN%'" | ForEach-Object {
|
||||||
|
$_.Enable = $false
|
||||||
|
$_.Put()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set Firewall to allow all connections (optional; adjust based on your requirements)
|
||||||
|
Write-Output "Configuring Windows Firewall to allow all connections (if necessary)..."
|
||||||
|
Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False
|
||||||
|
netsh advfirewall set allprofiles state off
|
||||||
|
|
||||||
|
# This can't be done inside provision script, because a restart is needed for changes to take effect.
|
||||||
|
Write-Host "Enable IPv6"
|
||||||
|
reg add hklm\system\currentcontrolset\services\tcpip6\parameters /f /v DisabledComponents /t REG_DWORD /d 0
|
||||||
|
|
||||||
161
src/boot.sh
Executable file
161
src/boot.sh
Executable file
|
|
@ -0,0 +1,161 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# Docker environment variables
|
||||||
|
: "${BIOS:=""}" # BIOS file
|
||||||
|
: "${TPM:="N"}" # Disable TPM
|
||||||
|
: "${SMM:="N"}" # Disable SMM
|
||||||
|
|
||||||
|
BOOT_DESC=""
|
||||||
|
BOOT_OPTS=""
|
||||||
|
|
||||||
|
SECURE="off"
|
||||||
|
[[ "$SMM" == [Yy1]* ]] && SECURE="on"
|
||||||
|
[ -n "$BIOS" ] && BOOT_MODE="custom"
|
||||||
|
|
||||||
|
case "${BOOT_MODE,,}" in
|
||||||
|
"uefi" | "" )
|
||||||
|
BOOT_MODE="uefi"
|
||||||
|
ROM="OVMF_CODE_4M.fd"
|
||||||
|
VARS="OVMF_VARS_4M.fd"
|
||||||
|
;;
|
||||||
|
"secure" )
|
||||||
|
SECURE="on"
|
||||||
|
BOOT_DESC=" securely"
|
||||||
|
ROM="OVMF_CODE_4M.secboot.fd"
|
||||||
|
VARS="OVMF_VARS_4M.secboot.fd"
|
||||||
|
;;
|
||||||
|
"windows" | "windows_plain" )
|
||||||
|
ROM="OVMF_CODE_4M.fd"
|
||||||
|
VARS="OVMF_VARS_4M.fd"
|
||||||
|
;;
|
||||||
|
"windows_secure" )
|
||||||
|
TPM="Y"
|
||||||
|
SECURE="on"
|
||||||
|
BOOT_DESC=" securely"
|
||||||
|
ROM="OVMF_CODE_4M.ms.fd"
|
||||||
|
VARS="OVMF_VARS_4M.ms.fd"
|
||||||
|
;;
|
||||||
|
"windows_legacy" )
|
||||||
|
HV="N"
|
||||||
|
SECURE="on"
|
||||||
|
BOOT_DESC=" (legacy)"
|
||||||
|
[ -z "${USB:-}" ] && USB="usb-ehci,id=ehci"
|
||||||
|
;;
|
||||||
|
"legacy" )
|
||||||
|
BOOT_DESC=" with SeaBIOS"
|
||||||
|
;;
|
||||||
|
"custom" )
|
||||||
|
BOOT_OPTS="-bios $BIOS"
|
||||||
|
BOOT_DESC=" with custom BIOS file"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
error "Unknown BOOT_MODE, value \"${BOOT_MODE}\" is not recognized!"
|
||||||
|
exit 33
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ "${BOOT_MODE,,}" == "windows"* ]]; then
|
||||||
|
BOOT_OPTS+=" -rtc base=utc"
|
||||||
|
BOOT_OPTS+=" -global ICH9-LPC.disable_s3=1"
|
||||||
|
BOOT_OPTS+=" -global ICH9-LPC.disable_s4=1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${BOOT_MODE,,}" in
|
||||||
|
"uefi" | "secure" | "windows" | "windows_plain" | "windows_secure" )
|
||||||
|
|
||||||
|
OVMF="/usr/share/OVMF"
|
||||||
|
DEST="$STORAGE/${BOOT_MODE,,}"
|
||||||
|
|
||||||
|
if [ ! -s "$DEST.rom" ] || [ ! -f "$DEST.rom" ]; then
|
||||||
|
[ ! -s "$OVMF/$ROM" ] || [ ! -f "$OVMF/$ROM" ] && error "UEFI boot file ($OVMF/$ROM) not found!" && exit 44
|
||||||
|
cp "$OVMF/$ROM" "$DEST.rom"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -s "$DEST.vars" ] || [ ! -f "$DEST.vars" ]; then
|
||||||
|
[ ! -s "$OVMF/$VARS" ] || [ ! -f "$OVMF/$VARS" ]&& error "UEFI vars file ($OVMF/$VARS) not found!" && exit 45
|
||||||
|
cp "$OVMF/$VARS" "$DEST.vars"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${BOOT_MODE,,}" == "secure" ]] || [[ "${BOOT_MODE,,}" == "windows_secure" ]]; then
|
||||||
|
BOOT_OPTS+=" -global driver=cfi.pflash01,property=secure,value=on"
|
||||||
|
fi
|
||||||
|
|
||||||
|
BOOT_OPTS+=" -drive file=$DEST.rom,if=pflash,unit=0,format=raw,readonly=on"
|
||||||
|
BOOT_OPTS+=" -drive file=$DEST.vars,if=pflash,unit=1,format=raw"
|
||||||
|
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
MSRS="/sys/module/kvm/parameters/ignore_msrs"
|
||||||
|
if [ -e "$MSRS" ]; then
|
||||||
|
result=$(<"$MSRS")
|
||||||
|
result="${result//[![:print:]]/}"
|
||||||
|
if [[ "$result" == "0" ]] || [[ "${result^^}" == "N" ]]; then
|
||||||
|
echo 1 | tee "$MSRS" > /dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
CLOCKSOURCE="tsc"
|
||||||
|
[[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter"
|
||||||
|
CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource"
|
||||||
|
|
||||||
|
if [ ! -f "$CLOCK" ]; then
|
||||||
|
warn "file \"$CLOCK\" cannot not found?"
|
||||||
|
else
|
||||||
|
result=$(<"$CLOCK")
|
||||||
|
result="${result//[![:print:]]/}"
|
||||||
|
case "${result,,}" in
|
||||||
|
"${CLOCKSOURCE,,}" ) ;;
|
||||||
|
"kvm-clock" ) info "Nested KVM virtualization detected.." ;;
|
||||||
|
"hyperv_clocksource_tsc_page" ) info "Nested Hyper-V virtualization detected.." ;;
|
||||||
|
"hpet" ) warn "unsupported clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
|
||||||
|
*) warn "unexpected clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
SM_BIOS=""
|
||||||
|
PS="/sys/class/dmi/id/product_serial"
|
||||||
|
|
||||||
|
if [ -s "$PS" ] && [ -r "$PS" ]; then
|
||||||
|
|
||||||
|
BIOS_SERIAL=$(<"$PS")
|
||||||
|
BIOS_SERIAL="${BIOS_SERIAL//[![:alnum:]]/}"
|
||||||
|
|
||||||
|
if [ -n "$BIOS_SERIAL" ]; then
|
||||||
|
SM_BIOS="-smbios type=1,serial=$BIOS_SERIAL"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$TPM" == [Yy1]* ]]; then
|
||||||
|
|
||||||
|
rm -f /var/run/tpm.pid
|
||||||
|
|
||||||
|
if ! swtpm socket -t -d --tpmstate "backend-uri=file://$STORAGE/${BOOT_MODE,,}.tpm" --ctrl type=unixio,path=/run/swtpm-sock --pid file=/var/run/tpm.pid --tpm2; then
|
||||||
|
error "Failed to start TPM emulator, reason: $?"
|
||||||
|
else
|
||||||
|
|
||||||
|
for (( i = 1; i < 20; i++ )); do
|
||||||
|
|
||||||
|
[ -S "/run/swtpm-sock" ] && break
|
||||||
|
|
||||||
|
if (( i % 10 == 0 )); then
|
||||||
|
echo "Waiting for TPM emulator to become available..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 0.1
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -S "/run/swtpm-sock" ]; then
|
||||||
|
error "TPM socket not found? Disabling TPM module..."
|
||||||
|
else
|
||||||
|
BOOT_OPTS+=" -chardev socket,id=chrtpm,path=/run/swtpm-sock"
|
||||||
|
BOOT_OPTS+=" -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuox pipefail
|
||||||
|
|
||||||
: "${KEY:=""}"
|
: "${KEY:=""}"
|
||||||
: "${WIDTH:=""}"
|
: "${WIDTH:=""}"
|
||||||
|
|
@ -1412,7 +1412,7 @@ prepareInstall() {
|
||||||
echo " OEMSkipRegional=1"
|
echo " OEMSkipRegional=1"
|
||||||
echo " OemSkipWelcome=1"
|
echo " OemSkipWelcome=1"
|
||||||
echo " AdminPassword=$password"
|
echo " AdminPassword=$password"
|
||||||
echo " TimeZone=0"
|
echo " TimeZone=85"
|
||||||
echo " AutoLogon=Yes"
|
echo " AutoLogon=Yes"
|
||||||
echo " AutoLogonCount=65432"
|
echo " AutoLogonCount=65432"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
|
||||||
56
src/entry.sh
Normal file → Executable file
56
src/entry.sh
Normal file → Executable file
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuox pipefail
|
||||||
|
|
||||||
: "${APP:="Windows"}"
|
: "${APP:="Windows"}"
|
||||||
: "${PLATFORM:="x64"}"
|
: "${PLATFORM:="x64"}"
|
||||||
|
|
@ -27,16 +27,52 @@ trap - ERR
|
||||||
version=$(qemu-system-x86_64 --version | head -n 1 | cut -d '(' -f 1 | awk '{ print $NF }')
|
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..."
|
info "Booting ${APP}${BOOT_DESC} using QEMU v$version..."
|
||||||
|
|
||||||
{ qemu-system-x86_64 ${ARGS:+ $ARGS} >"$QEMU_OUT" 2>"$QEMU_LOG"; rc=$?; } || :
|
{
|
||||||
(( rc != 0 )) && error "$(<"$QEMU_LOG")" && exit 15
|
qemu-system-x86_64 ${ARGS:+ $ARGS} >"$QEMU_OUT" 2>"$QEMU_LOG"
|
||||||
|
rc=$?
|
||||||
|
} || :
|
||||||
|
((rc != 0)) && error "$(<"$QEMU_LOG")" && exit 15
|
||||||
|
|
||||||
terminal
|
terminal
|
||||||
( sleep 30; boot ) &
|
(
|
||||||
tail -fn +0 "$QEMU_LOG" 2>/dev/null &
|
sleep 30
|
||||||
cat "$QEMU_TERM" 2> /dev/null | tee "$QEMU_PTY" | \
|
boot
|
||||||
sed -u -e 's/\x1B\[[=0-9;]*[a-z]//gi' \
|
|
||||||
-e 's/failed to load Boot/skipped Boot/g' \
|
|
||||||
-e 's/0): Not Found/0)/g' & wait $! || :
|
|
||||||
|
|
||||||
sleep 1 & wait $!
|
if ! configure_guest_network_interface; then
|
||||||
|
error "Failed to configure guest network interfaces"
|
||||||
|
exit 666
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${EXTRA_SCRIPT:-}" ]]; then
|
||||||
|
info "Executing extra script: $EXTRA_SCRIPT"
|
||||||
|
if ! "$EXTRA_SCRIPT"; then
|
||||||
|
error "Extra script failed"
|
||||||
|
exit 555
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Windows started successfully, you can now connect using RDP or visit http://localhost:8006/ to view the screen..."
|
||||||
|
touch "$STORAGE/ready"
|
||||||
|
) &
|
||||||
|
bg_pid=$!
|
||||||
|
|
||||||
|
tail -fn +0 "$QEMU_LOG" 2>/dev/null &
|
||||||
|
cat "$QEMU_TERM" 2>/dev/null | tee "$QEMU_PTY" |
|
||||||
|
sed -u -e 's/\x1B\[[=0-9;]*[a-z]//gi' \
|
||||||
|
-e 's/failed to load Boot/skipped Boot/g' \
|
||||||
|
-e 's/0): Not Found/0)/g' &
|
||||||
|
term_pd=$!
|
||||||
|
|
||||||
|
wait $bg_pid
|
||||||
|
exit_code=$?
|
||||||
|
|
||||||
|
if [[ $exit_code -ne 0 ]]; then
|
||||||
|
error "A critical process failed, exiting container..."
|
||||||
|
exit $exit_code
|
||||||
|
fi
|
||||||
|
|
||||||
|
wait $term_pd || :
|
||||||
|
|
||||||
|
sleep 1 &
|
||||||
|
wait $!
|
||||||
[ ! -f "$QEMU_END" ] && finish 0
|
[ ! -f "$QEMU_END" ] && finish 0
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuox pipefail
|
||||||
|
|
||||||
TMP="$STORAGE/tmp"
|
TMP="$STORAGE/tmp"
|
||||||
DIR="$TMP/unpack"
|
DIR="$TMP/unpack"
|
||||||
|
|
@ -1123,13 +1123,17 @@ if ! startInstall; then
|
||||||
exit 68
|
exit 68
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -e /storage/*.qcow2 ]; then
|
||||||
|
html "Windows already installed, skipping image preparation..."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
if [ ! -s "$ISO" ] || [ ! -f "$ISO" ]; then
|
if [ ! -s "$ISO" ] || [ ! -f "$ISO" ]; then
|
||||||
if ! downloadImage "$ISO" "$VERSION" "$LANGUAGE"; then
|
if ! downloadImage "$ISO" "$VERSION" "$LANGUAGE"; then
|
||||||
rm -f "$ISO" 2> /dev/null || true
|
rm -f "$ISO" 2> /dev/null || true
|
||||||
exit 61
|
exit 61
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! extractImage "$ISO" "$DIR" "$VERSION"; then
|
if ! extractImage "$ISO" "$DIR" "$VERSION"; then
|
||||||
rm -f "$ISO" 2> /dev/null || true
|
rm -f "$ISO" 2> /dev/null || true
|
||||||
exit 62
|
exit 62
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuox pipefail
|
||||||
|
|
||||||
handle_curl_error() {
|
handle_curl_error() {
|
||||||
|
|
||||||
|
|
|
||||||
711
src/network.sh
Executable file
711
src/network.sh
Executable file
|
|
@ -0,0 +1,711 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuox pipefail
|
||||||
|
|
||||||
|
# Docker environment variables
|
||||||
|
|
||||||
|
: "${MAC:=""}"
|
||||||
|
: "${DHCP:="N"}"
|
||||||
|
: "${NETWORK:="bridge"}"
|
||||||
|
: "${USER_PORTS:=""}"
|
||||||
|
: "${HOST_PORTS:=""}"
|
||||||
|
: "${ADAPTER:="virtio-net-pci"}"
|
||||||
|
|
||||||
|
: "${VM_NET_DEV:=""}"
|
||||||
|
: "${VM_NET_TAP:="qemu"}"
|
||||||
|
: "${VM_NET_MAC:="$MAC"}"
|
||||||
|
: "${VM_NET_HOST:="QEMU"}"
|
||||||
|
: "${VM_NET_IP:="20.20.20.21"}"
|
||||||
|
|
||||||
|
: "${DNSMASQ_OPTS:=""}"
|
||||||
|
: "${DNSMASQ:="/usr/sbin/dnsmasq"}"
|
||||||
|
: "${DNSMASQ_CONF_DIR:="/etc/dnsmasq.d"}"
|
||||||
|
|
||||||
|
ETH_COUNT=$(ls /sys/class/net | grep -E '^eth[0-9]+$' | wc -l)
|
||||||
|
ADD_ERR="Please add the following setting to your container:"
|
||||||
|
|
||||||
|
# ######################################
|
||||||
|
# Functions
|
||||||
|
# ######################################
|
||||||
|
find_free_ip() {
|
||||||
|
local current_ip="$1"
|
||||||
|
local mask="$2"
|
||||||
|
|
||||||
|
# Get network prefix
|
||||||
|
IFS='.' read -r i1 i2 i3 i4 <<<"$current_ip"
|
||||||
|
IFS='.' read -r m1 m2 m3 m4 <<<"$(ip -o -f inet addr show | awk '/scope global/ {print $4}' | cut -d'/' -f2)"
|
||||||
|
|
||||||
|
network_ip=$((i1 & m1)).$((i2 & m2)).$((i3 & m3)).0
|
||||||
|
base_ip="$i1.$i2.$i3"
|
||||||
|
|
||||||
|
# Iterate over available IPs
|
||||||
|
for i in {2..254}; do
|
||||||
|
new_ip="$base_ip.$i"
|
||||||
|
if [[ "$new_ip" != "$current_ip" ]] && ! ping -c 1 -W 1 "$new_ip" &>/dev/null; then
|
||||||
|
echo "$new_ip"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "No free IP found"
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_guest_network_interface() {
|
||||||
|
if [[ "${NETWORK,,}" == "bridge"* ]]; then
|
||||||
|
for ((i = 0; i < ETH_COUNT; i++)); do
|
||||||
|
HOST_INTERFACE="dockerbridge$i"
|
||||||
|
CURRENT_IP=$(ip addr show $HOST_INTERFACE | grep -oP 'inet \K[\d.]+')
|
||||||
|
MASK="$(ip -4 addr show $HOST_INTERFACE | awk '/inet / {print $2}' | cut -d'/' -f2)"
|
||||||
|
|
||||||
|
if [ -z "$CURRENT_IP" ]; then
|
||||||
|
echo "Error: Unable to retrieve the current IP address of $HOST_INTERFACE."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Current Host IP: $CURRENT_IP"
|
||||||
|
|
||||||
|
IFS='.' read -r -a ip_parts <<<"$CURRENT_IP"
|
||||||
|
NEW_HOST_IP=$(find_free_ip "$CURRENT_IP" "$MASK")
|
||||||
|
GW="${ip_parts[0]}.${ip_parts[1]}.${ip_parts[2]}.1"
|
||||||
|
|
||||||
|
echo "New Host IP: $NEW_HOST_IP"
|
||||||
|
|
||||||
|
ip addr del $CURRENT_IP/$MASK dev $HOST_INTERFACE
|
||||||
|
ip addr add $NEW_HOST_IP/$MASK dev $HOST_INTERFACE
|
||||||
|
|
||||||
|
ip link set $HOST_INTERFACE down
|
||||||
|
ip link set $HOST_INTERFACE up
|
||||||
|
|
||||||
|
route add default gw $GW
|
||||||
|
|
||||||
|
if [ $i -eq 0 ]; then
|
||||||
|
INTERFACE_NAME="Ethernet"
|
||||||
|
else
|
||||||
|
IDX=$((1 + i))
|
||||||
|
INTERFACE_NAME="Ethernet $IDX"
|
||||||
|
fi
|
||||||
|
|
||||||
|
RETRIES=10
|
||||||
|
for j in $(seq 1 $RETRIES); do
|
||||||
|
OUTPUT=$(python3 /run/qga.py powershell -Command "(\$(Get-NetAdapter -Name '$INTERFACE_NAME').Status)")
|
||||||
|
STATUS=$(echo "$OUTPUT" | grep -A1 'STDOUT:' | tail -n1 | tr -d '\r' | xargs)
|
||||||
|
|
||||||
|
echo "Status: '$STATUS'"
|
||||||
|
if [[ "$STATUS" == "Up" ]]; then
|
||||||
|
echo "Interface '$INTERFACE_NAME' is up!"
|
||||||
|
break
|
||||||
|
else
|
||||||
|
echo "Waiting for interface '$INTERFACE_NAME' to be up... ($j/$RETRIES)"
|
||||||
|
sleep 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
exit_code=0
|
||||||
|
python3 /run/qga.py powershell -Command "Set-NetIPInterface -InterfaceAlias '$INTERFACE_NAME' -Dhcp Disabled" || exit_code=$?
|
||||||
|
if [[ $exit_code -ne 0 ]]; then
|
||||||
|
echo "Failed to disable dhcp using qga.py" >&2
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$STORAGE/interfaces_configured" ]]; then
|
||||||
|
python3 /run/qga.py powershell -Command "Remove-NetIPAddress -IPAddress '$CURRENT_IP' -Confirm:\$false" || true
|
||||||
|
python3 /run/qga.py powershell -Command "Remove-NetRoute -InterfaceAlias '$INTERFACE_NAME' -DestinationPrefix '0.0.0.0/0' -Confirm:\$false" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 /run/qga.py powershell -Command "New-NetIPAddress -InterfaceAlias '$INTERFACE_NAME' -IPAddress '$CURRENT_IP' -PrefixLength 24 -DefaultGateway '$GW'" || exit_code=$?
|
||||||
|
if [[ $exit_code -ne 0 ]]; then
|
||||||
|
echo "Failed to set ip address using qga.py" >&2
|
||||||
|
return 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 /run/qga.py powershell -Command "Set-DnsClientServerAddress -InterfaceAlias '$INTERFACE_NAME' -ServerAddresses 1.1.1.1" || exit_code=$?
|
||||||
|
if [[ $exit_code -ne 0 ]]; then
|
||||||
|
echo "Failed to set dns server using qga.py" >&2
|
||||||
|
return 4
|
||||||
|
fi
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
touch "$STORAGE/interfaces_configured"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureDHCP() {
|
||||||
|
|
||||||
|
# Create the necessary file structure for /dev/vhost-net
|
||||||
|
if [ ! -c /dev/vhost-net ]; then
|
||||||
|
if mknod /dev/vhost-net c 10 238; then
|
||||||
|
chmod 660 /dev/vhost-net
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create a macvtap network for the VM guest
|
||||||
|
{
|
||||||
|
msg=$(ip link add link "$VM_NET_DEV" name "$VM_NET_TAP" address "$VM_NET_MAC" type macvtap mode bridge 2>&1)
|
||||||
|
rc=$?
|
||||||
|
} || :
|
||||||
|
|
||||||
|
case "$msg" in
|
||||||
|
"RTNETLINK answers: File exists"*)
|
||||||
|
while ! ip link add link "$VM_NET_DEV" name "$VM_NET_TAP" address "$VM_NET_MAC" type macvtap mode bridge; do
|
||||||
|
info "Waiting for macvtap interface to become available.."
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
"RTNETLINK answers: Invalid argument"*)
|
||||||
|
error "Cannot create macvtap interface. Please make sure that the network type of the container is 'macvlan' and not 'ipvlan'."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
"RTNETLINK answers: Operation not permitted"*)
|
||||||
|
error "No permission to create macvtap interface. Please make sure that your host kernel supports it and that the NET_ADMIN capability is set."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
[ -n "$msg" ] && echo "$msg" >&2
|
||||||
|
if ((rc != 0)); then
|
||||||
|
error "Cannot create macvtap interface."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
while ! ip link set "$VM_NET_TAP" up; do
|
||||||
|
info "Waiting for MAC address $VM_NET_MAC to become available..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
local TAP_NR TAP_PATH MAJOR MINOR
|
||||||
|
TAP_NR=$(</sys/class/net/"$VM_NET_TAP"/ifindex)
|
||||||
|
TAP_PATH="/dev/tap${TAP_NR}"
|
||||||
|
|
||||||
|
# Create dev file (there is no udev in container: need to be done manually)
|
||||||
|
IFS=: read -r MAJOR MINOR < <(cat /sys/devices/virtual/net/"$VM_NET_TAP"/tap*/dev)
|
||||||
|
((MAJOR < 1)) && error "Cannot find: sys/devices/virtual/net/$VM_NET_TAP" && return 1
|
||||||
|
|
||||||
|
[[ ! -e "$TAP_PATH" ]] && [[ -e "/dev0/${TAP_PATH##*/}" ]] && ln -s "/dev0/${TAP_PATH##*/}" "$TAP_PATH"
|
||||||
|
|
||||||
|
if [[ ! -e "$TAP_PATH" ]]; then
|
||||||
|
{
|
||||||
|
mknod "$TAP_PATH" c "$MAJOR" "$MINOR"
|
||||||
|
rc=$?
|
||||||
|
} || :
|
||||||
|
((rc != 0)) && error "Cannot mknod: $TAP_PATH ($rc)" && return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
exec 30>>"$TAP_PATH"
|
||||||
|
rc=$?
|
||||||
|
} 2>/dev/null || :
|
||||||
|
|
||||||
|
if ((rc != 0)); then
|
||||||
|
error "Cannot create TAP interface ($rc). $ADD_ERR --device-cgroup-rule='c *:* rwm'" && return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
exec 40>>/dev/vhost-net
|
||||||
|
rc=$?
|
||||||
|
} 2>/dev/null || :
|
||||||
|
|
||||||
|
if ((rc != 0)); then
|
||||||
|
error "VHOST can not be found ($rc). $ADD_ERR --device=/dev/vhost-net" && return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
NET_OPTS="-netdev tap,id=hostnet0,vhost=on,vhostfd=40,fd=30"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureDNS() {
|
||||||
|
|
||||||
|
# dnsmasq configuration:
|
||||||
|
DNSMASQ_OPTS+=" --dhcp-range=$VM_NET_IP,$VM_NET_IP --dhcp-host=$VM_NET_MAC,,$VM_NET_IP,$VM_NET_HOST,infinite --dhcp-option=option:netmask,255.255.255.0"
|
||||||
|
|
||||||
|
# Create lease file for faster resolve
|
||||||
|
echo "0 $VM_NET_MAC $VM_NET_IP $VM_NET_HOST 01:$VM_NET_MAC" >/var/lib/misc/dnsmasq.leases
|
||||||
|
chmod 644 /var/lib/misc/dnsmasq.leases
|
||||||
|
|
||||||
|
# Set DNS server and gateway
|
||||||
|
DNSMASQ_OPTS+=" --dhcp-option=option:dns-server,${VM_NET_IP%.*}.1 --dhcp-option=option:router,${VM_NET_IP%.*}.1"
|
||||||
|
|
||||||
|
# Add DNS entry for container
|
||||||
|
DNSMASQ_OPTS+=" --address=/host.lan/${VM_NET_IP%.*}.1"
|
||||||
|
|
||||||
|
DNSMASQ_OPTS=$(echo "$DNSMASQ_OPTS" | sed 's/\t/ /g' | tr -s ' ' | sed 's/^ *//')
|
||||||
|
|
||||||
|
if ! $DNSMASQ ${DNSMASQ_OPTS:+ $DNSMASQ_OPTS}; then
|
||||||
|
error "Failed to start dnsmasq, reason: $?" && return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserPorts() {
|
||||||
|
|
||||||
|
local args=""
|
||||||
|
local list=$1
|
||||||
|
local ssh="22"
|
||||||
|
local rdp="3389"
|
||||||
|
|
||||||
|
[ -z "$list" ] && list="$ssh,$rdp" || list+=",$ssh,$rdp"
|
||||||
|
|
||||||
|
list="${list//,/ }"
|
||||||
|
list="${list## }"
|
||||||
|
list="${list%% }"
|
||||||
|
|
||||||
|
for port in $list; do
|
||||||
|
args+="hostfwd=tcp::$port-$VM_NET_IP:$port,"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "${args%?}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
getHostPorts() {
|
||||||
|
|
||||||
|
local list=$1
|
||||||
|
local vnc="5900"
|
||||||
|
local web="8006"
|
||||||
|
|
||||||
|
[ -z "$list" ] && list="$web" || list+=",$web"
|
||||||
|
|
||||||
|
if [[ "${DISPLAY,,}" == "vnc" ]] || [[ "${DISPLAY,,}" == "web" ]]; then
|
||||||
|
[ -z "$list" ] && list="$vnc" || list+=",$vnc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -z "$list" ] && echo "" && return 0
|
||||||
|
|
||||||
|
if [[ "$list" != *","* ]]; then
|
||||||
|
echo " ! --dport $list"
|
||||||
|
else
|
||||||
|
echo " -m multiport ! --dports $list"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureUser() {
|
||||||
|
|
||||||
|
NET_OPTS="-netdev user,id=hostnet0,host=${VM_NET_IP%.*}.1,net=${VM_NET_IP%.*}.0/24,dhcpstart=$VM_NET_IP,hostname=$VM_NET_HOST"
|
||||||
|
|
||||||
|
local forward
|
||||||
|
forward=$(getUserPorts "$USER_PORTS")
|
||||||
|
[ -n "$forward" ] && NET_OPTS+=",$forward"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureNAT() {
|
||||||
|
|
||||||
|
local tuntap="TUN device is missing. $ADD_ERR --device /dev/net/tun"
|
||||||
|
local tables="The 'ip_tables' kernel module is not loaded. Try this command: sudo modprobe ip_tables iptable_nat"
|
||||||
|
|
||||||
|
# Create the necessary file structure for /dev/net/tun
|
||||||
|
if [ ! -c /dev/net/tun ]; then
|
||||||
|
[ ! -d /dev/net ] && mkdir -m 755 /dev/net
|
||||||
|
if mknod /dev/net/tun c 10 200; then
|
||||||
|
chmod 666 /dev/net/tun
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -c /dev/net/tun ]; then
|
||||||
|
error "$tuntap" && return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check port forwarding flag
|
||||||
|
if [[ $(</proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
|
||||||
|
{
|
||||||
|
sysctl -w net.ipv4.ip_forward=1 >/dev/null
|
||||||
|
rc=$?
|
||||||
|
} || :
|
||||||
|
if ((rc != 0)) || [[ $(</proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
|
||||||
|
error "IP forwarding is disabled. $ADD_ERR --sysctl net.ipv4.ip_forward=1" && return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create a bridge with a static IP for the VM guest
|
||||||
|
|
||||||
|
{
|
||||||
|
ip link add dev dockerbridge type bridge
|
||||||
|
rc=$?
|
||||||
|
} || :
|
||||||
|
|
||||||
|
if ((rc != 0)); then
|
||||||
|
error "Failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! ip address add "${VM_NET_IP%.*}.1/24" broadcast "${VM_NET_IP%.*}.255" dev dockerbridge; then
|
||||||
|
error "Failed to add IP address!" && return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while ! ip link set dockerbridge up; do
|
||||||
|
info "Waiting for IP address to become available..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
# QEMU Works with taps, set tap to the bridge created
|
||||||
|
if ! ip tuntap add dev "$VM_NET_TAP" mode tap; then
|
||||||
|
error "$tuntap" && return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while ! ip link set "$VM_NET_TAP" up promisc on; do
|
||||||
|
info "Waiting for TAP to become available..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! ip link set dev "$VM_NET_TAP" master dockerbridge; then
|
||||||
|
error "Failed to set IP link!" && return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add internet connection to the VM
|
||||||
|
update-alternatives --set iptables /usr/sbin/iptables-legacy >/dev/null
|
||||||
|
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy >/dev/null
|
||||||
|
|
||||||
|
exclude=$(getHostPorts "$HOST_PORTS")
|
||||||
|
|
||||||
|
if ! iptables -t nat -A POSTROUTING -o "$VM_NET_DEV" -j MASQUERADE; then
|
||||||
|
error "$tables" && return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
if ! iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p tcp${exclude} -j DNAT --to "$VM_NET_IP"; then
|
||||||
|
error "Failed to configure IP tables!" && return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p udp -j DNAT --to "$VM_NET_IP"; then
|
||||||
|
error "Failed to configure IP tables!" && return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ((KERNEL > 4)); then
|
||||||
|
# Hack for guest VMs complaining about "bad udp checksums in 5 packets"
|
||||||
|
iptables -A POSTROUTING -t mangle -p udp --dport bootpc -j CHECKSUM --checksum-fill >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
NET_OPTS="-netdev tap,id=hostnet0,ifname=$VM_NET_TAP"
|
||||||
|
|
||||||
|
if [ -c /dev/vhost-net ]; then
|
||||||
|
{
|
||||||
|
exec 40>>/dev/vhost-net
|
||||||
|
rc=$?
|
||||||
|
} 2>/dev/null || :
|
||||||
|
((rc == 0)) && NET_OPTS+=",vhost=on,vhostfd=40"
|
||||||
|
fi
|
||||||
|
|
||||||
|
NET_OPTS+=",script=no,downscript=no"
|
||||||
|
|
||||||
|
configureDNS || return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
configureBridge() {
|
||||||
|
|
||||||
|
local tuntap="TUN device is missing. $ADD_ERR --device /dev/net/tun"
|
||||||
|
local tables="The 'ip_tables' kernel module is not loaded. Try this command: sudo modprobe ip_tables iptable_nat"
|
||||||
|
|
||||||
|
# Create the necessary file structure for /dev/net/tun
|
||||||
|
if [ ! -c /dev/net/tun ]; then
|
||||||
|
[ ! -d /dev/net ] && mkdir -m 755 /dev/net
|
||||||
|
if mknod /dev/net/tun c 10 200; then
|
||||||
|
chmod 666 /dev/net/tun
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -c /dev/net/tun ]; then
|
||||||
|
error "$tuntap" && return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check port forwarding flag
|
||||||
|
if [[ $(</proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
|
||||||
|
{
|
||||||
|
sysctl -w net.ipv4.ip_forward=1 >/dev/null
|
||||||
|
rc=$?
|
||||||
|
} || :
|
||||||
|
if ((rc != 0)) || [[ $(</proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then
|
||||||
|
error "IP forwarding is disabled. $ADD_ERR --sysctl net.ipv4.ip_forward=1" && return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
for ((i = 0; i < ETH_COUNT; i++)); do
|
||||||
|
DOCKER_BRIDGE="dockerbridge$i"
|
||||||
|
NET_DEV="eth$i"
|
||||||
|
NET_TAP="qemu$i"
|
||||||
|
{
|
||||||
|
ip link add dev $DOCKER_BRIDGE type bridge
|
||||||
|
rc=$?
|
||||||
|
} || :
|
||||||
|
|
||||||
|
if ((rc != 0)); then
|
||||||
|
error "Failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# We need freshly created bridge to have IP address of the container
|
||||||
|
# For this reason we need to migrate IP from eth0 to dockerbridge.
|
||||||
|
for addr in $(ip --json addr show dev $NET_DEV | jq -c '.[0].addr_info[] | select(.family == "inet")'); do
|
||||||
|
cidr_addr=$(echo $addr | jq -r '[ .local, .prefixlen|tostring] | join("/")')
|
||||||
|
if ! ip addr add dev $DOCKER_BRIDGE $cidr_addr; then
|
||||||
|
error "Failed to add address for $DOCKER_BRIDGE interface"
|
||||||
|
exit 30
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! ip addr flush dev $NET_DEV; then
|
||||||
|
error "Failed to clear $NET_DEV interface addresses"
|
||||||
|
exit 30
|
||||||
|
fi
|
||||||
|
|
||||||
|
while ! ip link set $DOCKER_BRIDGE up; do
|
||||||
|
info "Waiting for IP address to become available..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
# QEMU Works with taps, set tap to the bridge created
|
||||||
|
if ! ip tuntap add dev "$NET_TAP" mode tap; then
|
||||||
|
error "$tuntap" && return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while ! ip link set "$NET_TAP" up promisc on; do
|
||||||
|
info "Waiting for TAP to become available..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! ip link set dev "$NET_TAP" master $DOCKER_BRIDGE; then
|
||||||
|
error "Failed to set IP link!" && return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! ip link set dev "$NET_DEV" master $DOCKER_BRIDGE; then
|
||||||
|
error "Failed to attach docker interface to bridge"
|
||||||
|
fi
|
||||||
|
|
||||||
|
NET_OPTS+=" -netdev tap,id=hostnet$i,ifname=$NET_TAP"
|
||||||
|
if [ -c /dev/vhost-net ]; then
|
||||||
|
fd=$((40 + i))
|
||||||
|
eval "exec $fd>>/dev/vhost-net"
|
||||||
|
rc=$?
|
||||||
|
if ((rc == 0)); then
|
||||||
|
NET_OPTS+=",vhost=on,vhostfd=$fd"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
NET_OPTS+=",script=no,downscript=no "
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
closeNetwork() {
|
||||||
|
|
||||||
|
# Shutdown nginx
|
||||||
|
nginx -s stop 2>/dev/null
|
||||||
|
fWait "nginx"
|
||||||
|
|
||||||
|
[[ "$NETWORK" == [Nn]* ]] && return 0
|
||||||
|
|
||||||
|
exec 30<&- || true
|
||||||
|
for ((i = 0; i < ETH_COUNT; i++)); do
|
||||||
|
fd=$((40 + i))
|
||||||
|
eval "exec $fd<&-" || true
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$DHCP" == [Yy1]* ]]; then
|
||||||
|
|
||||||
|
ip link set "$VM_NET_TAP" down || true
|
||||||
|
ip link delete "$VM_NET_TAP" || true
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
local pid="/var/run/dnsmasq.pid"
|
||||||
|
[ -s "$pid" ] && pKill "$(<"$pid")"
|
||||||
|
|
||||||
|
[[ "${NETWORK,,}" == "user"* ]] && return 0
|
||||||
|
|
||||||
|
if [[ "${NETWORK,,}" == "bridge"* ]]; then
|
||||||
|
for ((i = 0; i < ETH_COUNT; i++)); do
|
||||||
|
ip link set "qemu$i" down promisc off || true
|
||||||
|
ip link delete "qemu$i" || true
|
||||||
|
|
||||||
|
ip link set dockerbridge$i down || true
|
||||||
|
ip link delete dockerbridge$i || true
|
||||||
|
done
|
||||||
|
else
|
||||||
|
ip link set "$VM_NET_TAP" down promisc off || true
|
||||||
|
ip link delete "$VM_NET_TAP" || true
|
||||||
|
|
||||||
|
ip link set dockerbridge down || true
|
||||||
|
ip link delete dockerbridge || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
checkOS() {
|
||||||
|
|
||||||
|
local name
|
||||||
|
local os=""
|
||||||
|
local if="macvlan"
|
||||||
|
name=$(uname -a)
|
||||||
|
|
||||||
|
[[ "${name,,}" == *"darwin"* ]] && os="Docker Desktop for macOS"
|
||||||
|
[[ "${name,,}" == *"microsoft"* ]] && os="Docker Desktop for Windows"
|
||||||
|
|
||||||
|
if [[ "$DHCP" == [Yy1]* ]]; then
|
||||||
|
if="macvtap"
|
||||||
|
[[ "${name,,}" == *"synology"* ]] && os="Synology Container Manager"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$os" ]; then
|
||||||
|
warn "you are using $os which does not support $if, please revert to bridge networking!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
getInfo() {
|
||||||
|
|
||||||
|
if [ -z "$VM_NET_DEV" ]; then
|
||||||
|
# Give Kubernetes priority over the default interface
|
||||||
|
[ -d "/sys/class/net/net0" ] && VM_NET_DEV="net0"
|
||||||
|
[ -d "/sys/class/net/net1" ] && VM_NET_DEV="net1"
|
||||||
|
[ -d "/sys/class/net/net2" ] && VM_NET_DEV="net2"
|
||||||
|
[ -d "/sys/class/net/net3" ] && VM_NET_DEV="net3"
|
||||||
|
# Automaticly detect the default network interface
|
||||||
|
[ -z "$VM_NET_DEV" ] && VM_NET_DEV=$(awk '$2 == 00000000 { print $1 }' /proc/net/route)
|
||||||
|
[ -z "$VM_NET_DEV" ] && VM_NET_DEV="eth0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "/sys/class/net/$VM_NET_DEV" ]; then
|
||||||
|
error "Network interface '$VM_NET_DEV' does not exist inside the container!"
|
||||||
|
error "$ADD_ERR -e \"VM_NET_DEV=NAME\" to specify another interface name." && exit 27
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$MAC" ]; then
|
||||||
|
local file="$STORAGE/$PROCESS.mac"
|
||||||
|
[ -s "$file" ] && MAC=$(<"$file")
|
||||||
|
if [ -z "$MAC" ]; then
|
||||||
|
# Generate MAC address based on Docker container ID in hostname
|
||||||
|
MAC=$(echo "$HOST" | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/')
|
||||||
|
echo "${MAC^^}" >"$file"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
VM_NET_MAC="${MAC^^}"
|
||||||
|
VM_NET_MAC="${VM_NET_MAC//-/:}"
|
||||||
|
|
||||||
|
if [[ ${#VM_NET_MAC} == 12 ]]; then
|
||||||
|
m="$VM_NET_MAC"
|
||||||
|
VM_NET_MAC="${m:0:2}:${m:2:2}:${m:4:2}:${m:6:2}:${m:8:2}:${m:10:2}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#VM_NET_MAC} != 17 ]]; then
|
||||||
|
error "Invalid MAC address: '$VM_NET_MAC', should be 12 or 17 digits long!" && exit 28
|
||||||
|
fi
|
||||||
|
|
||||||
|
GATEWAY=$(ip route list dev "$VM_NET_DEV" | awk ' /^default/ {print $3}')
|
||||||
|
IP=$(ip address show dev "$VM_NET_DEV" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ######################################
|
||||||
|
# Configure Network
|
||||||
|
# ######################################
|
||||||
|
|
||||||
|
if [[ "$NETWORK" == [Nn]* ]]; then
|
||||||
|
NET_OPTS=""
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
getInfo
|
||||||
|
html "Initializing network..."
|
||||||
|
|
||||||
|
if [[ "$DEBUG" == [Yy1]* ]]; then
|
||||||
|
info "Host: $HOST IP: $IP Gateway: $GATEWAY Interface: $VM_NET_DEV MAC: $VM_NET_MAC"
|
||||||
|
[ -f /etc/resolv.conf ] && grep '^nameserver*' /etc/resolv.conf
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$DHCP" == [Yy1]* ]]; then
|
||||||
|
|
||||||
|
checkOS
|
||||||
|
|
||||||
|
if [[ "$IP" == "172."* ]]; then
|
||||||
|
warn "container IP starts with 172.* which is often a sign that you are not on a macvlan network (required for DHCP)!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Configure for macvtap interface
|
||||||
|
configureDHCP || exit 20
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
if [[ "$IP" != "172."* ]] && [[ "$IP" != "10.8"* ]] && [[ "$IP" != "10.9"* ]]; then
|
||||||
|
checkOS
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${NETWORK,,}" == [Yy1]* ]]; then
|
||||||
|
|
||||||
|
# Configure for tap interface
|
||||||
|
if ! configureNAT; then
|
||||||
|
|
||||||
|
NETWORK="user"
|
||||||
|
warn "falling back to usermode networking! Performance will be bad and port mapping will not work."
|
||||||
|
|
||||||
|
ip link set "$VM_NET_TAP" down promisc off &>null || true
|
||||||
|
ip link delete "$VM_NET_TAP" &>null || true
|
||||||
|
|
||||||
|
ip link set dockerbridge down &>null || true
|
||||||
|
ip link delete dockerbridge &>null || true
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${NETWORK,,}" == "user"* ]]; then
|
||||||
|
|
||||||
|
# Configure for usermode networking (slirp)
|
||||||
|
configureUser || exit 24
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${NETWORK,,}" == "bridge"* ]]; then
|
||||||
|
|
||||||
|
# Configure for usermode networking (slirp)
|
||||||
|
# CONFIGURE Bridge
|
||||||
|
html "Configuring bridged network"
|
||||||
|
|
||||||
|
if ! configureBridge; then
|
||||||
|
|
||||||
|
error "Failed to setup bridge networking"
|
||||||
|
for ((i = 0; i < ETH_COUNT; i++)); do
|
||||||
|
ip link set "$VM_NET_TAP$i" down promisc off &>null || true
|
||||||
|
ip link delete "$VM_NET_TAP$i" &>null || true
|
||||||
|
|
||||||
|
ip link set dockerbridge$i down &>null || true
|
||||||
|
ip link delete dockerbridge$i &>null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
exit 25
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
NET_OPTS+=" -device $ADAPTER,romfile=,netdev=hostnet0,mac=$VM_NET_MAC,id=net0"
|
||||||
|
|
||||||
|
if [[ "${NETWORK,,}" == "bridge"* ]]; then
|
||||||
|
for ((i = 1; i < ETH_COUNT; i++)); do
|
||||||
|
MAC=$(printf "52:54:00:%02X:%02X:%02X" $((RANDOM % 256)) $((RANDOM % 256)) $((RANDOM % 256)))
|
||||||
|
NET_OPTS+=" -device $ADAPTER,romfile=,netdev=hostnet$i,mac=$MAC,id=net$i"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
NET_OPTS+=" -device virtio-serial-pci,id=virtserial0,bus=pcie.0,addr=0x6"
|
||||||
|
NET_OPTS+=" -chardev socket,id=qga0,path=/tmp/qga.sock,server=on,wait=off"
|
||||||
|
NET_OPTS+=" -device virtio-serial"
|
||||||
|
NET_OPTS+=" -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0"
|
||||||
|
|
||||||
|
html "Initialized network successfully..."
|
||||||
|
return 0
|
||||||
20
src/power.sh
20
src/power.sh
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuox pipefail
|
||||||
|
|
||||||
# Configure QEMU for graceful shutdown
|
# Configure QEMU for graceful shutdown
|
||||||
|
|
||||||
|
|
@ -17,8 +17,9 @@ rm -f "$QEMU_DIR/qemu.*"
|
||||||
touch "$QEMU_LOG"
|
touch "$QEMU_LOG"
|
||||||
|
|
||||||
_trap() {
|
_trap() {
|
||||||
func="$1" ; shift
|
func="$1"
|
||||||
for sig ; do
|
shift
|
||||||
|
for sig; do
|
||||||
trap "$func $sig" "$sig"
|
trap "$func $sig" "$sig"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
@ -35,7 +36,6 @@ boot() {
|
||||||
grep -Fq "BOOTMGR is missing" "$QEMU_PTY" && fail="y"
|
grep -Fq "BOOTMGR is missing" "$QEMU_PTY" && fail="y"
|
||||||
fi
|
fi
|
||||||
if [ -z "$fail" ]; then
|
if [ -z "$fail" ]; then
|
||||||
info "Windows started succesfully, visit http://127.0.0.1:8006/ to view the screen..."
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
@ -128,7 +128,7 @@ terminal() {
|
||||||
|
|
||||||
if [ -n "$msg" ]; then
|
if [ -n "$msg" ]; then
|
||||||
|
|
||||||
if [[ "${msg,,}" != "char"* || "$msg" != *"serial0)" ]]; then
|
if [[ "${msg,,}" != "char"* || "$msg" != *"serial0)" ]]; then
|
||||||
echo "$msg"
|
echo "$msg"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -161,6 +161,10 @@ _graceful_shutdown() {
|
||||||
|
|
||||||
set +e
|
set +e
|
||||||
|
|
||||||
|
if [ -f "$STORAGE/ready" ]; then
|
||||||
|
rm $STORAGE/ready
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -f "$QEMU_END" ]; then
|
if [ -f "$QEMU_END" ]; then
|
||||||
info "Received $1 while already shutting down..."
|
info "Received $1 while already shutting down..."
|
||||||
return
|
return
|
||||||
|
|
@ -188,13 +192,13 @@ _graceful_shutdown() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Send ACPI shutdown signal
|
# Send ACPI shutdown signal
|
||||||
echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" > /dev/null
|
echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" >/dev/null
|
||||||
|
|
||||||
local cnt=0
|
local cnt=0
|
||||||
while [ "$cnt" -lt "$QEMU_TIMEOUT" ]; do
|
while [ "$cnt" -lt "$QEMU_TIMEOUT" ]; do
|
||||||
|
|
||||||
sleep 1
|
sleep 1
|
||||||
cnt=$((cnt+1))
|
cnt=$((cnt + 1))
|
||||||
|
|
||||||
! isAlive "$pid" && break
|
! isAlive "$pid" && break
|
||||||
# Workaround for zombie pid
|
# Workaround for zombie pid
|
||||||
|
|
@ -203,7 +207,7 @@ _graceful_shutdown() {
|
||||||
info "Waiting for Windows to shutdown... ($cnt/$QEMU_TIMEOUT)"
|
info "Waiting for Windows to shutdown... ($cnt/$QEMU_TIMEOUT)"
|
||||||
|
|
||||||
# Send ACPI shutdown signal
|
# Send ACPI shutdown signal
|
||||||
echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" > /dev/null
|
echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" >/dev/null
|
||||||
|
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
|
||||||
168
src/qga.py
Normal file
168
src/qga.py
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
import argparse
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
QGA_SOCKET = "/tmp/qga.sock" # Adjust if needed
|
||||||
|
|
||||||
|
|
||||||
|
def send_qga_command(sock, command):
|
||||||
|
"""Send a JSON command to the QEMU Guest Agent socket and receive the response."""
|
||||||
|
try:
|
||||||
|
cmd = (json.dumps(command) + "\n").encode()
|
||||||
|
sock.sendall(cmd)
|
||||||
|
response = sock.recv(4096)
|
||||||
|
return json.loads(response.decode())
|
||||||
|
except socket.timeout:
|
||||||
|
print(f"Timeout waiting for response from {QGA_SOCKET}", file=sys.stderr)
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error communicating with socket: {e}", file=sys.stderr)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def decode_output(data):
|
||||||
|
"""Try to decode output as hex or Base64, or return raw."""
|
||||||
|
if not data:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
return bytes.fromhex(data).decode("utf-8", errors="ignore")
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
return base64.b64decode(data).decode("utf-8", errors="ignore")
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def execute_command(sock, command_path, command_args, timeout):
|
||||||
|
"""Execute a command inside the guest VM with specified path and arguments."""
|
||||||
|
exec_request = {
|
||||||
|
"execute": "guest-exec",
|
||||||
|
"arguments": {
|
||||||
|
"path": command_path,
|
||||||
|
"arg": command_args,
|
||||||
|
"capture-output": True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"Executing: {command_path} {' '.join(command_args)}")
|
||||||
|
response = send_qga_command(sock, exec_request)
|
||||||
|
|
||||||
|
if response is None or "return" not in response or "pid" not in response["return"]:
|
||||||
|
print(
|
||||||
|
"Error: Failed to start execution.",
|
||||||
|
json.dumps(response or {}, indent=2),
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
pid = response["return"]["pid"]
|
||||||
|
print(f"Command started with PID {pid}")
|
||||||
|
|
||||||
|
# Step 2: Wait for completion with timeout
|
||||||
|
start_time = time.time()
|
||||||
|
status = {}
|
||||||
|
while True:
|
||||||
|
if time.time() - start_time > timeout:
|
||||||
|
print("Execution timeout reached.", file=sys.stderr)
|
||||||
|
return {"exit_code": -2, "stdout": "", "stderr": "Execution timed out."}
|
||||||
|
|
||||||
|
status_request = {"execute": "guest-exec-status", "arguments": {"pid": pid}}
|
||||||
|
status_response = send_qga_command(sock, status_request)
|
||||||
|
|
||||||
|
if status_response and "return" in status_response:
|
||||||
|
status = status_response["return"]
|
||||||
|
if status.get("exited", False):
|
||||||
|
break
|
||||||
|
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
exit_code = status.get("exitcode", -1)
|
||||||
|
stdout_data = decode_output(status.get("out-data", ""))
|
||||||
|
stderr_data = decode_output(status.get("err-data", ""))
|
||||||
|
|
||||||
|
return {"exit_code": exit_code, "stdout": stdout_data, "stderr": stderr_data}
|
||||||
|
|
||||||
|
|
||||||
|
def create_socket():
|
||||||
|
"""Create and return a reusable socket connection to the QEMU Guest Agent."""
|
||||||
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
sock.settimeout(30)
|
||||||
|
try:
|
||||||
|
sock.connect(QGA_SOCKET)
|
||||||
|
return sock
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error creating socket: {e}", file=sys.stderr)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
"""Parse command-line arguments."""
|
||||||
|
parser = argparse.ArgumentParser(description="Send commands to QEMU Guest Agent.")
|
||||||
|
shell_group = parser.add_mutually_exclusive_group()
|
||||||
|
shell_group.add_argument(
|
||||||
|
"--cmd", action="store_true", help="Run the command through cmd.exe /c"
|
||||||
|
)
|
||||||
|
shell_group.add_argument(
|
||||||
|
"--powershell",
|
||||||
|
action="store_true",
|
||||||
|
help="Run the command with powershell -Command",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--timeout", type=int, default=60, help="Max execution time in seconds"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--json", action="store_true", help="Output result in JSON format"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"command", help="Path to the command to execute inside the guest VM"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"args", nargs=argparse.REMAINDER, help="Arguments to pass to the command"
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
|
if args.cmd:
|
||||||
|
command_path = "cmd.exe"
|
||||||
|
command_args = ["/c", args.command] + args.args
|
||||||
|
elif args.powershell:
|
||||||
|
command_path = "powershell.exe"
|
||||||
|
full_command = " ".join([args.command] + args.args)
|
||||||
|
command_args = ["-Command", full_command]
|
||||||
|
else:
|
||||||
|
command_path = args.command
|
||||||
|
command_args = args.args
|
||||||
|
|
||||||
|
# Create a reusable socket
|
||||||
|
unix_sock = create_socket()
|
||||||
|
if not unix_sock:
|
||||||
|
print("Failed to create socket.", file=sys.stderr)
|
||||||
|
sys.exit(1) # Exit if we can't connect to the socket
|
||||||
|
|
||||||
|
result = execute_command(unix_sock, command_path, command_args, args.timeout)
|
||||||
|
if result:
|
||||||
|
if args.json:
|
||||||
|
print(json.dumps(result, indent=2))
|
||||||
|
else:
|
||||||
|
print(f"Exit Code: {result['exit_code']}")
|
||||||
|
if result["stdout"]:
|
||||||
|
print("STDOUT:\n", result["stdout"])
|
||||||
|
if result["stderr"]:
|
||||||
|
print("STDERR:\n", result["stderr"])
|
||||||
|
|
||||||
|
# Close the socket once all commands are executed
|
||||||
|
unix_sock.close()
|
||||||
|
|
||||||
|
# Exit with the appropriate code based on command execution result
|
||||||
|
sys.exit(result["exit_code"] if result else 2)
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuox pipefail
|
||||||
|
|
||||||
: "${SAMBA:="Y"}"
|
: "${SAMBA:="Y"}"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue