diff --git a/README.md b/README.md index 8332aca..b9ac983 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ patch --force --forward --backup -p0 --directory / --input "/absolute/path/to/pa If you want to revert the patch: `patch --force --reverse --backup -p0 --directory / --input "/absolute/path/to/patchfile.pm.patch"` +After you apply the patch, you must restart the PVEdaemon to load the changes. If you do not restart it, Cloud-init will not work properly: +`systemctl restart pvedaemon.service` If you want to apply the patch manually you can follow these steps: [Manual Patching](https://git.geco-it.net/c.soylu/Geco-cloudbase-init/src/branch/master/MANUALPATCH.md) diff --git a/conf/cloudbase-init-unattend.conf b/conf/cloudbase-init-unattend.conf index 8087ae8..7f1330f 100644 --- a/conf/cloudbase-init-unattend.conf +++ b/conf/cloudbase-init-unattend.conf @@ -2,6 +2,7 @@ username=Administrator groups=Administrators inject_user_password=true +first_logon_behaviour=false config_drive_raw_hhd=true config_drive_cdrom=true config_drive_vfat=true diff --git a/conf/cloudbase-init.conf b/conf/cloudbase-init.conf index 5ee67b4..aab14ab 100644 --- a/conf/cloudbase-init.conf +++ b/conf/cloudbase-init.conf @@ -3,11 +3,11 @@ username=Administrator groups=Administrators netbios_host_name_compatibility=true inject_user_password=true -first_logon_behaviour=no +first_logon_behaviour=false config_drive_raw_hhd=true config_drive_cdrom=true config_drive_vfat=true -locations=cdroom +locations=cdrom bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe mtools_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\ metadata_services=cloudbaseinit.metadata.services.configdrive.ConfigDriveService diff --git a/powershell/sysprep.bat b/powershell/sysprep.bat new file mode 100644 index 0000000..bd958e1 --- /dev/null +++ b/powershell/sysprep.bat @@ -0,0 +1,3 @@ +@Echo off +cd "C:\Program Files\Cloudbase Solutions\Cloudbase-Init\conf" +C:\Windows\System32\sysprep\sysprep.exe /generalize /oobe /unattend:Unattend.xml /shutdown diff --git a/qemu-server-8.0.10/Cloudinit.pm.patch b/qemu-server-8.0.10/Cloudinit.pm.patch new file mode 100644 index 0000000..13aad26 --- /dev/null +++ b/qemu-server-8.0.10/Cloudinit.pm.patch @@ -0,0 +1,156 @@ +--- /usr/share/perl5/PVE/QemuServer/Cloudinit.pm.orig 2023-11-25 12:19:52.306038610 +0000 ++++ /usr/share/perl5/PVE/QemuServer/Cloudinit.pm 2023-11-25 12:50:24.263059905 +0000 +@@ -167,13 +167,23 @@ + $content .= "iface lo inet loopback\n\n"; + + my ($searchdomains, $nameservers) = get_dns_conf($conf); ++ ++ ## support windows ++ my $ostype = $conf->{"ostype"}; ++ my $default_dns = ''; ++ my $default_search = ''; ++ ## ++ my $dnsinserted = 0; # insert dns just once for the machine ++ + if ($nameservers && @$nameservers) { + $nameservers = join(' ', @$nameservers); + $content .= " dns_nameservers $nameservers\n"; ++ $default_dns = $nameservers; # Support windows + } + if ($searchdomains && @$searchdomains) { + $searchdomains = join(' ', @$searchdomains); + $content .= " dns_search $searchdomains\n"; ++ $default_search = $searchdomains; # Support Windows + } + + my @ifaces = grep { /^net(\d+)$/ } keys %$conf; +@@ -193,6 +203,13 @@ + $content .= " address $addr\n"; + $content .= " netmask $mask\n"; + $content .= " gateway $net->{gw}\n" if $net->{gw}; ++ ## Support Windows ++ if(PVE::QemuServer::windows_version($ostype) && not($dnsinserted)) { ++ $content .= " dns-nameservers $default_dns\n"; ++ $content .= " dns-search $default_search\n"; ++ $dnsinserted++; ++ } ++ ## + } + } + if ($net->{ip6}) { +@@ -211,19 +228,100 @@ + return $content; + } + +-sub configdrive2_gen_metadata { +- my ($user, $network) = @_; ++# Get mac addresses of dhcp nics from conf file ++sub get_mac_addresses { ++ my ($conf) = @_; ++ ++ my $dhcpstring = undef; ++ my @dhcpmacs = (); ++ my @ifaces = grep { /^net(\d)$/ } keys %$conf; ++ ++ foreach my $iface (sort @ifaces) { ++ (my $id = $iface) =~ s/^net//; ++ my $net = PVE::QemuServer::parse_net($conf->{$iface}); ++ next if !$conf->{"ipconfig$id"}; ++ my $ipconfig = PVE::QemuServer::parse_ipconfig($conf->{"ipconfig$id"}); ++ ++ my $mac = lc $net->{macaddr}; ++ ++ if (($ipconfig->{ip}) and ($ipconfig->{ip} eq 'dhcp')){ ++ push @dhcpmacs, $mac; ++ } ++ } + ++ if (@dhcpmacs){ ++ $dhcpstring = ",\n \"dhcp\":["; ++ foreach my $mac (@dhcpmacs){ ++ if ($mac != $dhcpmacs[-1]){ ++ $dhcpstring .= "\"$mac\","; ++ } ++ else{ ++ $dhcpstring .= "\"$mac\"]"; ++ } ++ } ++ } ++ return ($dhcpstring); ++} ++ ++ ++sub configdrive2_gen_metadata { ++ my ($conf, $vmid, $user, $network) = @_; ++ ++ # Get mac addresses of dhcp nics from conf file ++ my $dhcpmacs = undef; ++ $dhcpmacs = get_mac_addresses($conf); ++ ++ # Get UUID + my $uuid_str = Digest::SHA::sha1_hex($user.$network); +- return configdrive2_metadata($uuid_str); ++ ++ ++ # Get hostname ++ my ($hostname, $fqdn) = get_hostname_fqdn($conf, $vmid); ++ ++ # Get username, default to Administrator if none ++ my $username = undef; ++ if (defined($conf->{ciuser})){ ++ my $name = $conf->{ciuser}; ++ $username = ",\n \"admin_username\": \"$name\"" ++ } ++ ++ # Get user password ++ my $password = $conf->{cipassword}; ++ ++ # Get ssh keys and make a list out of it in json format ++ my $keystring = undef; ++ my $pubkeys = $conf->{sshkeys}; ++ $pubkeys = URI::Escape::uri_unescape($pubkeys); ++ my @pubkeysarray = split "\n", $pubkeys; ++ if (@pubkeysarray) { ++ my $arraylength = @pubkeysarray; ++ my $incrementer = 1; ++ $keystring =",\n \"public_keys\": {\n"; ++ for my $key (@pubkeysarray){ ++ $keystring .= " \"SSH${incrementer}\" : \"${key}\""; ++ if ($arraylength != $incrementer){ ++ $keystring .= ",\n"; ++ }else{ ++ $keystring .= "\n }"; ++ } ++ $incrementer; ++ } ++ } ++ ++ return configdrive2_metadata($password, $uuid_str, $hostname, $username, $keystring, $network, $dhcpmacs); ++ + } + + sub configdrive2_metadata { +- my ($uuid) = @_; ++ my ($password, $uuid, $hostname, $username, $pubkeys, $network, $dhcpmacs) = @_; + return <<"EOF"; + { +- "uuid": "$uuid", +- "network_config": { "content_path": "/content/0000" } ++ "meta":{ ++ "admin_pass": "$password"$username ++ }, ++ "uuid":"$uuid", ++ "hostname":"$hostname", ++ "network_config":{"content_path":"/content/0000"}$pubkeys$dhcpmacs + } + EOF + } +@@ -237,7 +335,7 @@ + $vendor_data = '' if !defined($vendor_data); + + if (!defined($meta_data)) { +- $meta_data = configdrive2_gen_metadata($user_data, $network_data); ++ $meta_data = configdrive2_gen_metadata($conf, $vmid, $user_data, $network_data); + } + + # we always allocate a 4MiB disk for cloudinit and with the overhead of the ISO diff --git a/qemu-server-8.0.10/Qemu.pm.patch b/qemu-server-8.0.10/Qemu.pm.patch new file mode 100644 index 0000000..b2eed9e --- /dev/null +++ b/qemu-server-8.0.10/Qemu.pm.patch @@ -0,0 +1,21 @@ +--- /usr/share/perl5/PVE/API2/Qemu.pm.orig 2023-11-25 12:13:22.223284817 +0000 ++++ /usr/share/perl5/PVE/API2/Qemu.pm 2023-11-25 12:15:29.392184385 +0000 +@@ -1547,10 +1547,16 @@ + + my $skip_cloud_init = extract_param($param, 'skip_cloud_init'); + ++ # WINDOWS CLOUD-INIT MODIFICATION ++ my $conf = PVE::QemuConfig->load_config($vmid); ++ my $ostype = $conf->{ostype}; ++ + if (defined(my $cipassword = $param->{cipassword})) { + # Same logic as in cloud-init (but with the regex fixed...) +- $param->{cipassword} = PVE::Tools::encrypt_pw($cipassword) +- if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/; ++ if (!(PVE::QemuServer::windows_version($ostype))) { ++ $param->{cipassword} = PVE::Tools::encrypt_pw($cipassword) ++ if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/; ++ } + } + + my @paramarr = (); # used for log message diff --git a/qemu-server-8.0.8/Cloudinit.pm.patch b/qemu-server-8.0.8/Cloudinit.pm.patch new file mode 100644 index 0000000..13aad26 --- /dev/null +++ b/qemu-server-8.0.8/Cloudinit.pm.patch @@ -0,0 +1,156 @@ +--- /usr/share/perl5/PVE/QemuServer/Cloudinit.pm.orig 2023-11-25 12:19:52.306038610 +0000 ++++ /usr/share/perl5/PVE/QemuServer/Cloudinit.pm 2023-11-25 12:50:24.263059905 +0000 +@@ -167,13 +167,23 @@ + $content .= "iface lo inet loopback\n\n"; + + my ($searchdomains, $nameservers) = get_dns_conf($conf); ++ ++ ## support windows ++ my $ostype = $conf->{"ostype"}; ++ my $default_dns = ''; ++ my $default_search = ''; ++ ## ++ my $dnsinserted = 0; # insert dns just once for the machine ++ + if ($nameservers && @$nameservers) { + $nameservers = join(' ', @$nameservers); + $content .= " dns_nameservers $nameservers\n"; ++ $default_dns = $nameservers; # Support windows + } + if ($searchdomains && @$searchdomains) { + $searchdomains = join(' ', @$searchdomains); + $content .= " dns_search $searchdomains\n"; ++ $default_search = $searchdomains; # Support Windows + } + + my @ifaces = grep { /^net(\d+)$/ } keys %$conf; +@@ -193,6 +203,13 @@ + $content .= " address $addr\n"; + $content .= " netmask $mask\n"; + $content .= " gateway $net->{gw}\n" if $net->{gw}; ++ ## Support Windows ++ if(PVE::QemuServer::windows_version($ostype) && not($dnsinserted)) { ++ $content .= " dns-nameservers $default_dns\n"; ++ $content .= " dns-search $default_search\n"; ++ $dnsinserted++; ++ } ++ ## + } + } + if ($net->{ip6}) { +@@ -211,19 +228,100 @@ + return $content; + } + +-sub configdrive2_gen_metadata { +- my ($user, $network) = @_; ++# Get mac addresses of dhcp nics from conf file ++sub get_mac_addresses { ++ my ($conf) = @_; ++ ++ my $dhcpstring = undef; ++ my @dhcpmacs = (); ++ my @ifaces = grep { /^net(\d)$/ } keys %$conf; ++ ++ foreach my $iface (sort @ifaces) { ++ (my $id = $iface) =~ s/^net//; ++ my $net = PVE::QemuServer::parse_net($conf->{$iface}); ++ next if !$conf->{"ipconfig$id"}; ++ my $ipconfig = PVE::QemuServer::parse_ipconfig($conf->{"ipconfig$id"}); ++ ++ my $mac = lc $net->{macaddr}; ++ ++ if (($ipconfig->{ip}) and ($ipconfig->{ip} eq 'dhcp')){ ++ push @dhcpmacs, $mac; ++ } ++ } + ++ if (@dhcpmacs){ ++ $dhcpstring = ",\n \"dhcp\":["; ++ foreach my $mac (@dhcpmacs){ ++ if ($mac != $dhcpmacs[-1]){ ++ $dhcpstring .= "\"$mac\","; ++ } ++ else{ ++ $dhcpstring .= "\"$mac\"]"; ++ } ++ } ++ } ++ return ($dhcpstring); ++} ++ ++ ++sub configdrive2_gen_metadata { ++ my ($conf, $vmid, $user, $network) = @_; ++ ++ # Get mac addresses of dhcp nics from conf file ++ my $dhcpmacs = undef; ++ $dhcpmacs = get_mac_addresses($conf); ++ ++ # Get UUID + my $uuid_str = Digest::SHA::sha1_hex($user.$network); +- return configdrive2_metadata($uuid_str); ++ ++ ++ # Get hostname ++ my ($hostname, $fqdn) = get_hostname_fqdn($conf, $vmid); ++ ++ # Get username, default to Administrator if none ++ my $username = undef; ++ if (defined($conf->{ciuser})){ ++ my $name = $conf->{ciuser}; ++ $username = ",\n \"admin_username\": \"$name\"" ++ } ++ ++ # Get user password ++ my $password = $conf->{cipassword}; ++ ++ # Get ssh keys and make a list out of it in json format ++ my $keystring = undef; ++ my $pubkeys = $conf->{sshkeys}; ++ $pubkeys = URI::Escape::uri_unescape($pubkeys); ++ my @pubkeysarray = split "\n", $pubkeys; ++ if (@pubkeysarray) { ++ my $arraylength = @pubkeysarray; ++ my $incrementer = 1; ++ $keystring =",\n \"public_keys\": {\n"; ++ for my $key (@pubkeysarray){ ++ $keystring .= " \"SSH${incrementer}\" : \"${key}\""; ++ if ($arraylength != $incrementer){ ++ $keystring .= ",\n"; ++ }else{ ++ $keystring .= "\n }"; ++ } ++ $incrementer; ++ } ++ } ++ ++ return configdrive2_metadata($password, $uuid_str, $hostname, $username, $keystring, $network, $dhcpmacs); ++ + } + + sub configdrive2_metadata { +- my ($uuid) = @_; ++ my ($password, $uuid, $hostname, $username, $pubkeys, $network, $dhcpmacs) = @_; + return <<"EOF"; + { +- "uuid": "$uuid", +- "network_config": { "content_path": "/content/0000" } ++ "meta":{ ++ "admin_pass": "$password"$username ++ }, ++ "uuid":"$uuid", ++ "hostname":"$hostname", ++ "network_config":{"content_path":"/content/0000"}$pubkeys$dhcpmacs + } + EOF + } +@@ -237,7 +335,7 @@ + $vendor_data = '' if !defined($vendor_data); + + if (!defined($meta_data)) { +- $meta_data = configdrive2_gen_metadata($user_data, $network_data); ++ $meta_data = configdrive2_gen_metadata($conf, $vmid, $user_data, $network_data); + } + + # we always allocate a 4MiB disk for cloudinit and with the overhead of the ISO diff --git a/qemu-server-8.0.8/Qemu.pm.patch b/qemu-server-8.0.8/Qemu.pm.patch new file mode 100644 index 0000000..0900513 --- /dev/null +++ b/qemu-server-8.0.8/Qemu.pm.patch @@ -0,0 +1,21 @@ +--- /usr/share/perl5/PVE/API2/Qemu.pm.orig 2023-11-25 12:13:22.223284817 +0000 ++++ /usr/share/perl5/PVE/API2/Qemu.pm 2023-11-25 12:15:29.392184385 +0000 +@@ -1540,10 +1540,16 @@ + + my $skip_cloud_init = extract_param($param, 'skip_cloud_init'); + ++ # WINDOWS CLOUD-INIT MODIFICATION ++ my $conf = PVE::QemuConfig->load_config($vmid); ++ my $ostype = $conf->{ostype}; ++ + if (defined(my $cipassword = $param->{cipassword})) { + # Same logic as in cloud-init (but with the regex fixed...) +- $param->{cipassword} = PVE::Tools::encrypt_pw($cipassword) +- if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/; ++ if (!(PVE::QemuServer::windows_version($ostype))) { ++ $param->{cipassword} = PVE::Tools::encrypt_pw($cipassword) ++ if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/; ++ } + } + + my @paramarr = (); # used for log message