Ver código fonte

Dateien hochladen nach ''

Gogs 5 anos atrás
pai
commit
32ad9e79b8
1 arquivos alterados com 230 adições e 0 exclusões
  1. 230 0
      proxmox_cloud-init.php

+ 230 - 0
proxmox_cloud-init.php

@@ -0,0 +1,230 @@
+<?php
+/**
+ * cloud-init script for pfSense on Proxmox VE
+ * 
+ * The script does a pfSense configuration in that manner:
+ * 1. looks for an available cloud-init drive and mount it to /etc/cloud-init
+ * 2. compares the cloud-init files on the cloud-init drive by a local copy placed in /etc/cloud-init
+ *    and ends without changing anything if the files on both location are similar
+ * 3. parses the cloud-init YAML files and prepare and set the given values to pfSense config array
+ * 4. write the pfSense configuration and reboot the instance
+ * HowTo install:
+ * 1. get a copy of the YAML parse from https://github.com/mustangostang/spyc/ and place it to /usr/local/sbin
+ * 2. place a copy of proxmox_cloud-init.php also to /usr/local/sbin
+ * HowTo use:
+ * 1. attach a cloud-init drive to the pfSense VM
+ * 2. create a startupscript
+ * 
+ * @version 0.9
+ * @author Andre Genrich andre.genrich@thurdata.ch
+ */
+require_once('Spyc.php');                                                      // yaml parser
+require_once('config.inc');                                                    // pfSense configuration structures
+require_once('system.inc');                                                    // pfSense system utilities
+$cloudInitFiles = array('meta-data', 'network-config', 'user-data');           // provided by Proxmox
+$cloudInitLocalPath = '/etc/cloud-init';
+$cloudInitMountPoint = '/mnt/cloud-init';
+/**
+ * compares the cloud-init files
+ * 
+ * conpares the cloud-init drive by a local copy and
+ * update the local copy in case of changes
+ * or if the local copy does not exist
+ * 
+ * @param array $cloudInitFiles an array of filenames created by Proxmox
+ * @param string $cloudInitLocalPath the local path to place the copy of cloud-init files
+ * @param string $cloudInitMountPoint the location where the cloud-init image is mounted
+ * @return bool true in case of updates, false in case of all is up to date 
+ */
+function updateCloudInitFiles( $cloudInitFiles, $cloudInitLocalPath, $cloudInitMountPoint) {
+	$cloudInitFileDiff = false;
+	// create /etc/cloud-init if not exist
+	if (!is_dir("$cloudInitLocalPath")) {
+		if (!mkdir ( "$cloudInitLocalPath", 0770, TRUE)) {
+			$sys_err= error_get_last();
+			echo "cloud-init failed: $sys_err[type] $sys_err[message]\n";
+			syslog(LOG_ERR,"cloud-init failed: $sys_err[type] $sys_err[message]");
+			exit(1);
+		}
+	}
+	// check for updated config files and update the local copy in case of differs
+	foreach ( $cloudInitFiles as $cloudInitFile ) {
+		if (!((sha1_file("$cloudInitLocalPath/$cloudInitFile")) == (sha1_file("$cloudInitMountPoint/$cloudInitFile")))) {
+			if (!copy("$cloudInitMountPoint/$cloudInitFile", "$cloudInitLocalPath/$cloudInitFile")) {
+				$sys_err = error_get_last();
+				syslog(LOG_ERR,"cloud-init failed: $sys_err[type] $sys_err[message]");
+				exit(1);
+			} else {
+				$cloudInitFileDiff = true;
+			}
+		}
+	}
+	return $cloudInitFileDiff;
+}
+/**
+ * probes existence of all necessary cloud-init files
+ * 
+ * @param array $cloudInitFiles an array of filenames created by Proxmox
+ * @param string $cloudInitPath the location where the cloud-init files should be
+ * @return bool true in case of all files are in place or false if something missing
+ */
+function checkCloudInitFiles( $cloudInitFiles, $cloudInitPath) {
+	foreach($cloudInitFiles as $cloudInitFile) {
+		if (!file_exists("$cloudInitPath/$cloudInitFile")) {
+			return false;
+		}
+	}
+	return true;
+}
+/**
+ * search for a cloud-init drive
+ * 
+ * probes any attached cd device for existing cloud-init files
+ * mounts the drive to /mnt/cloud-init and probes that all neccessary cloud-init files exist
+ * 
+ * @param array $cloudInitFiles an array of filenames created by Proxmox
+ * @param string $cloudInitMountPoint the mountpoint for cloud-init drive
+ * @return bool true in case of success, fals in case of no cloud-init drive could found
+ */
+function checkCloudInitDevice( $cloudInitFiles, $cloudInitMountPoint) {
+	// get attached cd devices
+	preg_match_all( "/.*cd[0-9] /", file_get_contents('/var/run/dmesg.boot'), $cdDeviceList);
+	if (empty($cdDeviceList[0])) {
+		syslog(LOG_ERR,"cloud-init failed: No cloud-init drive found");
+		exit(1);
+	}
+	if(!is_dir($cloudInitMountPoint)) {
+		if(!mkdir($cloudInitMountPoint)) {
+			syslog(LOG_ERR,"cloud-init failed: Cloud not create mountpoint $cloudInitMountPoint");
+			exit(1);
+		}
+	}
+	// check cloud-init iso is mounted or try to mount the cloud-init medium
+	foreach($cdDeviceList[0] as $cdDevice) {
+		// is a cd mounted on /mnt/cloud-init ?
+		$sys_err_msg = exec("df $cloudInitMountPoint | grep -q /dev/$cdDevice 2>&1", $sys_msg, $sys_err_no);
+		if ($sys_err_no) { // not mounted
+			// try to mount the cd
+			$mount_err = exec("mount_cd9660 /dev/$cdDevice $cloudInitMountPoint", $sys_msg, $sys_err_no);
+			if (!$sys_err_no) {
+				if (checkCloudInitFiles( $cloudInitFiles, $cloudInitMountPoint)) {
+					syslog(LOG_INFO,"cloud-init: found cloud init drive on $cdDevice mounted at $cloudInitMountPoint/");
+					return true;
+				} else {
+					$umount_err = exec("umount $cloudInitMountPoint", $sys_msg, $sys_err_no);
+					if ($sys_err_no) {
+						syslog(LOG_ERR,"cloud-init: mounted a wrong device $cdDevice but not able to umount because $sys_msg");
+						return false;
+					}
+				}
+			}
+		} else { //already mounted (but not by us)
+			if (checkCloudInitFiles( $cloudInitFiles, $cloudInitMountPoint)) {
+				syslog(LOG_INFO,"cloud-init: found cloud init drive on $cdDevice at $cloudInitMountPoint/");
+				return true;
+			} else {
+				syslog(LOG_ERR,"cloud-init: expected files on cloud init drive not found");
+				return false;
+			}
+		}
+	}
+	return false;
+}
+/**
+ * does a case insensitive search for a network device by given hardware address
+ * 
+ * @param string $mac hardware address
+ * @return string $if[1] device name or false if no device could be found
+ */
+function searchIfDevice( $mac) {
+	exec("ifconfig -a | awk '/^[a-z]/ { gsub(/\:/,\"\", $1); iface=$1; next } /hwaddr/ { mac=$2; print mac, iface}'", $ifMacList, $sys_err_no);
+	foreach($ifMacList as $ifMac) {
+		$if = explode(" ",$ifMac);
+		if (strcasecmp("$if[0]", "$mac") == 0) {                               // case insensitive
+			return $if[1];
+		}
+	}
+	return false;
+}
+// search and mount the cloud-init image or exit 1
+if (!checkCloudInitDevice( $cloudInitFiles, $cloudInitMountPoint)) {
+	syslog(LOG_ERR,"cloud-init: no cloud init drive available, skipping...\n");
+	exit(1);
+}
+// update the local copy of cloud-init files if there are any changes or exit 0
+if (!(updateCloudInitFiles( $cloudInitFiles, $cloudInitLocalPath, $cloudInitMountPoint))) {
+	syslog(LOG_INFO,"cloud-init: cloud init files up to date, skipping...\n");
+	exit(0);
+}
+// parse cloud init configurations
+// $metaData = Spyc::YAMLLoad("$cloudInitLocalPath/$cloudInitFiles[0]");       // meta-data (actually not in use)
+$netData = Spyc::YAMLLoad("$cloudInitLocalPath/$cloudInitFiles[1]");           // network-config
+$userData = Spyc::YAMLLoad("$cloudInitLocalPath/$cloudInitFiles[2]");          // user-data
+// configure nameserver if set
+$ifLastNr=(count($netData['config'])-1);                                       //  the YAML parser reurns a crappy array like this
+if (reset($netData['config'][$ifLastNr]) == 'nameserver') {                    //  (
+	next($netData['config'][$ifLastNr]);                                       //    [type] => nameserver
+	$dnsServerCount = 0;                                                       //    [address] =>
+	while($nameserverIP=next($netData['config'][$ifLastNr])) {                 //    [0] => 1.2.3.4
+		$config['system']['dnsserver'][$dnsServerCount] = $nameserverIP;       //    [1] => 4.3.2.1
+		$dnsServerCount++;                                                     //    [search] =>
+	}                                                                          //    [2] => mydomain.local
+	$config['system']['domain'] = next($netData['config'][$ifLastNr]);         //  )
+}
+// configure WAN interface
+$wanDevice = searchIfDevice( $netData['config'][0]['mac_address']);
+if (!$wanDevice) {
+	syslog(LOG_ERR,"cloud-init: no WAN device found");
+	exit(1);
+} else {
+	$config['interfaces']['wan']['if'] = $wanDevice;
+}
+if ($netData['config'][0][0]['type'] == 'static') {
+	$config['interfaces']['wan']['ipaddr'] = $netData['config'][0][0]['address'];
+	$config['interfaces']['wan']['subnet'] = 32 - log((ip2long($netData['config'][0][0]['netmask']) ^ ip2long('255.255.255.255')) + 1 ,2);
+	$config['interfaces']['wan']['gateway'] = $netData['config'][0][0]['gateway'];
+}
+// configure primary LAN device
+$lanDevice = searchIfDevice( $netData['config'][1]['mac_address']);
+if (!$lanDevice) {
+	syslog(LOG_ERR,"cloud-init: no LAN device found");
+	exit(1);
+} else {
+	$config['interfaces']['lan']['if'] = $lanDevice;
+}
+if ($netData['config'][1][0]['type'] == 'static') {
+	$config['interfaces']['lan']['ipaddr'] = $netData['config'][1][0]['address'];
+	$config['interfaces']['lan']['subnet'] = 32 - log((ip2long($netData['config'][1][0]['netmask']) ^ ip2long('255.255.255.255')) + 1 ,2);
+	$config['interfaces']['lan']['gateway'] = $netData['config'][1][0]['gateway'];
+}
+// configure additional network devices
+if ($ifLastNr > 2) {
+	for ($ifNr=2;$ifNr<$ifLastNr;$ifNr++) {
+		$optDeviceName = "opt" . strval($ifNr-1);
+		$optDevice = searchIfDevice( $netData['config'][$ifNr]['mac_address']);
+		if (!$optDevice) {
+			syslog(LOG_WARN,"cloud-init: given network device {$netData['config'][$ifNr]['mac_address']} not found");
+			break;
+		} else {
+			$config['interfaces'][$optDeviceName]['if'] = $optDevice;
+		}
+		if ($netData['config'][$ifNr][0]['type'] == 'static') {
+			$config['interfaces'][$optDeviceName]['ipaddr'] = $netData['config'][$ifNr][0]['address'];
+			$config['interfaces'][$optDeviceName]['subnet'] = 32 - log((ip2long($netData['config'][$ifNr][0]['netmask']) ^ ip2long('255.255.255.255')) + 1 ,2);
+			$config['interfaces'][$optDeviceName]['gateway'] = $netData['config'][$ifNr][0]['gateway'];
+		}
+	}
+}
+// add ssh keys
+if (isset($userData['ssh_authorized_keys'])) {
+	foreach ($userData[ssh_authorized_keys] as $sshKey) {
+		$sshKeys .= "$sshKey\n";
+	}
+	$config['system']['user'][0]['authorizedkeys'] = base64_encode("$sshKeys");
+}
+$config['system']['hostname'] = $userData['hostname'];
+$config['system']['user'][0]['bcrypt-hash'] = $userData['password'];
+// write the configuration
+write_config();
+// finally reboot the system
+system_reboot_sync();