controlEmergencyRun outs the ciúrrent timestamp into $cloudInitControl * * @return int 0 to trigger a skip run * @return int 1 to trigger an emergency run (set again all cloudinit settings) * @return int 3 to trigger a reset to factory defaults run (reset all cloudinit & pfSense changes) */ function controlEmergencyRun() { global $cloudInitLocalPath, $cloudInitControl, $cloudInitResetControl; if ((file_get_contents( "$cloudInitLocalPath/$cloudInitControl")) == "init") { file_put_contents( "$cloudInitLocalPath/$cloudInitControl", time()); return 0; } if ((file_get_contents( "$cloudInitLocalPath/$cloudInitControl") + 300) > time()) { if (file_exists( "$cloudInitLocalPath/$cloudInitResetControl")) { if ((file_get_contents( "$cloudInitLocalPath/$cloudInitResetControl") + 300) > time()) { unlink( "$cloudInitLocalPath/$cloudInitResetControl"); return 2; } else { unlink( "$cloudInitLocalPath/$cloudInitResetControl"); return 1; } } else { file_put_contents( "$cloudInitLocalPath/$cloudInitResetControl", time()); return 1; } } else { unlink( "$cloudInitLocalPath/$cloudInitResetControl"); file_put_contents( "$cloudInitLocalPath/$cloudInitControl", time()); return 0; } } /** * updateCloudInitFiles compares the cloudinit files * * conpares the cloudinit drive by a local copy and * update the local copy in case of changes * or if the local copy does not exist * * @return bool true in case of updates, false in case of all is up to date */ function updateCloudInitFiles() { global $cloudInitMountPoint, $cloudInitLocalPath, $cloudInitFiles; $cloudInitFileDiff = false; // 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,"cloudinit failed: $sys_err[type] $sys_err[message]"); exit(1); } else { $cloudInitFileDiff = true; } } } return $cloudInitFileDiff; } /** * checkCloudInitFiles probes existence of all necessary cloudinit files * * @return bool true in case of all files are in place or false if someone missing */ function checkCloudInitFiles() { global $cloudInitMountPoint, $cloudInitFiles; foreach($cloudInitFiles as $cloudInitFile) { if (!file_exists("$cloudInitMountPoint/$cloudInitFile")) { return false; } } return true; } /** * checkCloudInitDevice search for a cloudinit drive * * probes any attached cd device for existing cloudinit files * mounts the drive to /mnt/cloudinit and probes that all neccessary cloudinit files exist * * @return bool true in case of success, fals in case of no cloudinit drive could found */ function checkCloudInitDevice() { global $cloudInitMountPoint, $cloudInitFiles; // 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,"cloudinit: no cloudinit drive found"); exit(1); } // check cloudinit iso is mounted or try to mount the cloudinit medium foreach($cdDeviceList[0] as $cdDevice) { // is a cd mounted on /mnt/cloudinit ? $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( $cloudInitMountPoint, $cloudInitFiles)) { syslog(LOG_INFO,"cloudinit: 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,"cloudinit: mounted a wrong device $cdDevice but not able to umount because $sys_msg"); return false; } } } } else { //already mounted (but not by us) if (checkCloudInitFiles( $cloudInitMountPoint, $cloudInitFiles)) { syslog(LOG_INFO,"cloudinit: found cloud init drive on $cdDevice at $cloudInitMountPoint/"); return true; } else { syslog(LOG_ERR,"cloudinit: expected files on cloud init drive not found"); return false; } } } return false; } /** * searchIfDevice 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; } // create mountpoint for cloudinit drive if not exist createDir( $cloudInitMountPoint); // create local folder for config & control files createDir( $cloudInitLocalPath); // search and mount the cloudinit image or exit 1 if (!checkCloudInitDevice()) { syslog(LOG_ERR,"cloudinit: no cloud init drive available, skipping...\n"); exit(1); } // probe for special run modes if (!updateCloudInitFiles()) { switch (controlEmergencyRun()) { case 0: syslog(LOG_INFO,"cloudinit: cloud init files up to date, skipping...\n"); exit(0); break; case 1: syslog(LOG_INFO,"cloudinit: cloud init files up to date, but emergency run triggered...\n"); break; case 2: syslog(LOG_INFO,"cloudinit: reset run triggered, restore cloudinit default configuration!\n"); restoreConfiguration(); break; } } // 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,"cloudinit: 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'] = calcCIDR( $netData['config'][0][0]['netmask']); $config['interfaces']['wan']['gateway'] = $netData['config'][0][0]['gateway']; } elseif ($netData['config'][0][0]['type'] == 'dhcp4') { $config['interfaces']['wan']['ipaddr'] = 'dhcp'; unset( $config['interfaces']['wan']['subnet']); unset( $config['interfaces']['wan']['gateway']); } // configure primary LAN device $lanDevice = searchIfDevice( $netData['config'][1]['mac_address']); if (!$lanDevice) { syslog(LOG_ERR,"cloudinit: 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'] = calcCIDR( $netData['config'][1][0]['netmask']); $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,"cloudinit: 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'] = calcCIDR( $netData['config'][$ifNr][0]['netmask']); $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]['name'] = $userData['user']; $config['system']['user'][0]['bcrypt-hash'] = $userData['password']; // write the configuration write_config(); // update runmode control file file_put_contents( "$cloudInitLocalPath/$cloudInitControl", "init"); // skip pfSense wizard unlink("/conf/trigger_initial_wizard"); // finally reboot the system system_reboot_sync();