summaryrefslogtreecommitdiffstats
path: root/modules-available/rebootcontrol/page.inc.php
diff options
context:
space:
mode:
Diffstat (limited to 'modules-available/rebootcontrol/page.inc.php')
-rw-r--r--modules-available/rebootcontrol/page.inc.php371
1 files changed, 255 insertions, 116 deletions
diff --git a/modules-available/rebootcontrol/page.inc.php b/modules-available/rebootcontrol/page.inc.php
index 3a438504..67996f02 100644
--- a/modules-available/rebootcontrol/page.inc.php
+++ b/modules-available/rebootcontrol/page.inc.php
@@ -3,8 +3,6 @@
class Page_RebootControl extends Page
{
- private $action = false;
-
/**
* Called before any page rendering happens - early hook to check parameters etc.
*/
@@ -17,62 +15,131 @@ class Page_RebootControl extends Page
Util::redirect('?do=Main'); // does not return
}
- $this->action = Request::any('action', 'show', 'string');
+ $action = Request::post('action', 'show', 'string');
+ if ($action === 'reboot' || $action === 'shutdown') {
+ $this->execRebootShutdown($action);
+ } elseif ($action === 'savejumphost') {
+ $this->saveJumpHost();
+ } elseif ($action === 'jumphost') {
+ $this->postJumpHostDispatch();
+ }
+ if (Request::isPost()) {
+ Util::redirect('?do=rebootcontrol');
+ }
+ }
- if ($this->action === 'reboot' || $this->action === 'shutdown') {
+ private function execRebootShutdown($action)
+ {
+ $requestedClients = Request::post('clients', false, 'array');
+ if (!is_array($requestedClients) || empty($requestedClients)) {
+ Message::addError('no-clients-selected');
+ return;
+ }
- $requestedClients = Request::post('clients', false, 'array');
- if (!is_array($requestedClients) || empty($requestedClients)) {
- Message::addError('no-clients-selected');
- Util::redirect();
+ $actualClients = RebootQueries::getMachinesByUuid($requestedClients);
+ if (count($actualClients) !== count($requestedClients)) {
+ // We could go ahead an see which ones were not found in DB but this should not happen anyways unless the
+ // user manipulated the request
+ Message::addWarning('some-machine-not-found');
+ }
+ // Filter ones with no permission
+ foreach (array_keys($actualClients) as $idx) {
+ if (!User::hasPermission('action.' . $action, $actualClients[$idx]['locationid'])) {
+ Message::addWarning('locations.no-permission-location', $actualClients[$idx]['locationid']);
+ unset($actualClients[$idx]);
+ } else {
+ $locationId = $actualClients[$idx]['locationid'];
}
+ }
+ // See if anything is left
+ if (!is_array($actualClients) || empty($actualClients)) {
+ Message::addError('no-clients-selected');
+ return;
+ }
+ usort($actualClients, function($a, $b) {
+ $a = ($a['state'] === 'IDLE' || $a['state'] === 'OCCUPIED');
+ $b = ($b['state'] === 'IDLE' || $b['state'] === 'OCCUPIED');
+ if ($a === $b)
+ return 0;
+ return $a ? -1 : 1;
+ });
+ if ($action === 'shutdown') {
+ $mode = 'SHUTDOWN';
+ $minutes = Request::post('s-minutes', 0, 'int');
+ } elseif (Request::any('quick', false, 'string') === 'on') {
+ $mode = 'KEXEC_REBOOT';
+ $minutes = Request::post('r-minutes', 0, 'int');
+ } else {
+ $mode = 'REBOOT';
+ $minutes = Request::post('r-minutes', 0, 'int');
+ }
+ $task = RebootControl::execute($actualClients, $mode, $minutes, $locationId);
+ if (Taskmanager::isTask($task)) {
+ Util::redirect("?do=rebootcontrol&show=task&taskid=" . $task["id"]);
+ }
+ return;
+ }
- $actualClients = RebootQueries::getMachinesByUuid($requestedClients);
- if (count($actualClients) !== count($requestedClients)) {
- // We could go ahead an see which ones were not found in DB but this should not happen anyways unless the
- // user manipulated the request
- Message::addWarning('some-machine-not-found');
- }
- // Filter ones with no permission
- foreach (array_keys($actualClients) as $idx) {
- if (!User::hasPermission('action.' . $this->action, $actualClients[$idx]['locationid'])) {
- Message::addWarning('locations.no-permission-location', $actualClients[$idx]['locationid']);
- unset($actualClients[$idx]);
- } else {
- $locationId = $actualClients[$idx]['locationid'];
+ private function saveJumpHost()
+ {
+ User::assertPermission('jumphost.edit');
+ $id = Request::post('hostid', Request::REQUIRED, 'string');
+ $host = Request::post('host', Request::REQUIRED, 'string');
+ $port = Request::post('port', Request::REQUIRED, 'int');
+ if ($port < 1 || $port > 65535) {
+ Message::addError('invalid-port', $port);
+ return;
+ }
+ $username = Request::post('username', Request::REQUIRED, 'string');
+ $sshkey = Request::post('sshkey', Request::REQUIRED, 'string');
+ $script = preg_replace('/\r\n?/', "\n", Request::post('script', Request::REQUIRED, 'string'));
+ if ($id === 'new') {
+ $ret = Database::exec('INSERT INTO reboot_jumphost (host, port, username, sshkey, script, reachable)
+ VALUE (:host, :port, :username, :sshkey, :script, 0)', compact('host', 'port', 'username', 'sshkey', 'script'));
+ $id = Database::lastInsertId();
+ } else {
+ $ret = Database::exec('UPDATE reboot_jumphost SET
+ host = :host, port = :port, username = :username, sshkey = :sshkey, script = :script, reachable = 0
+ WHERE hostid = :id', compact('host', 'port', 'username', 'sshkey', 'script', 'id'));
+ if ($ret === 0) {
+ $ret = Database::queryFirst('SELECT hostid FROM reboot_jumphost WHERE hostid = :id', ['id' => $id]);
+ if ($ret !== false) {
+ $ret = 1;
}
}
- // See if anything is left
- if (!is_array($actualClients) || empty($actualClients)) {
- Message::addError('no-clients-selected');
- Util::redirect();
- }
- usort($actualClients, function($a, $b) {
- $a = ($a['state'] === 'IDLE' || $a['state'] === 'OCCUPIED');
- $b = ($b['state'] === 'IDLE' || $b['state'] === 'OCCUPIED');
- if ($a === $b)
- return 0;
- return $a ? -1 : 1;
- });
- if ($this->action === 'shutdown') {
- $mode = 'SHUTDOWN';
- $minutes = Request::post('s-minutes', 0, 'int');
- } elseif (Request::any('quick', false, 'string') === 'on') {
- $mode = 'KEXEC_REBOOT';
- $minutes = Request::post('r-minutes', 0, 'int');
- } else {
- $mode = 'REBOOT';
- $minutes = Request::post('r-minutes', 0, 'int');
- }
- $task = RebootControl::execute($actualClients, $mode, $minutes, $locationId);
- if (Taskmanager::isTask($task)) {
- Util::redirect("?do=rebootcontrol&taskid=" . $task["id"]);
- } else {
- Util::redirect("?do=rebootcontrol");
- }
}
+ if ($ret > 0) {
+ Message::addSuccess('jumphost-saved', $id);
+ $this->execCheckConnection($id);
+ } else {
+ Message::addError('no-such-jumphost', $id);
+ }
+ }
+
+ private function postJumpHostDispatch()
+ {
+ $id = Request::post('checkid', false, 'int');
+ if ($id !== false) {
+ // Check connectivity
+ $this->execCheckConnection($id);
+ return;
+ }
+ }
+ private function execCheckConnection($hostid)
+ {
+ $host = $this->getJumpHost($hostid);
+ $script = str_replace(['%IP%', '%MACS%'], ['255.255.255.255', '00:11:22:33:44:55'], $host['script']);
+ $task = RebootControl::runScript([[
+ 'clientip' => $host['host'],
+ 'port' => $host['port'],
+ 'username' => $host['username'],
+ ]], $script, 5, $host['sshkey']);
+ if (!Taskmanager::isTask($task))
+ return;
+ TaskmanagerCallback::addCallback($task, 'rbcConnCheck', $hostid);
+ Util::redirect('?do=rebootcontrol&show=task&type=checkhost&taskid=' . $task['id']);
}
/**
@@ -81,86 +148,150 @@ class Page_RebootControl extends Page
protected function doRender()
{
- if ($this->action === 'show') {
-
- $data = [];
- $task = Request::get("taskid", false, 'string');
- if ($task !== false) {
- $task = Taskmanager::status($task);
- }
+ $show = Request::get('show', 'jumphosts', 'string');
- if (Taskmanager::isTask($task)) {
+ // Always show public key (it's public, isn't it?)
+ $data = ['pubkey' => SSHKey::getPublicKey()];
+ Permission::addGlobalTags($data['perms'], null, ['newkeypair']);
+ Render::addTemplate('header', $data);
- $data['taskId'] = $task['id'];
- $data['locationId'] = $task['data']['locationId'];
- $data['locationName'] = Location::getName($task['data']['locationId']);
- $uuids = array_map(function($entry) {
- return $entry['machineuuid'];
- }, $task['data']['clients']);
- $data['clients'] = RebootQueries::getMachinesByUuid($uuids);
- Render::addTemplate('status', $data);
-
- } else {
-
- //location you want to see, default are "not assigned" clients
- $requestedLocation = Request::get('location', false, 'int');
- $allowedLocs = User::getAllowedLocations("action.*");
- if (empty($allowedLocs)) {
- User::assertPermission('action.*');
- }
+ if ($show === 'task') {
+ $this->showTask();
+ } elseif ($show === 'jumphosts') {
+ $this->showJumpHosts();
+ } elseif ($show === 'jumphost') {
+ $this->showJumpHost();
+ }
+ }
- if ($requestedLocation === false) {
- if (in_array(0, $allowedLocs)) {
- $requestedLocation = 0;
- } else {
- $requestedLocation = reset($allowedLocs);
- }
- }
+ private function showTask()
+ {
+ $taskid = Request::get("taskid", Request::REQUIRED, 'string');
+ $task = Taskmanager::status($taskid);
- $data['locations'] = Location::getLocations($requestedLocation, 0, true);
+ if (!Taskmanager::isTask($task) || !isset($task['data'])) {
+ Message::addError('no-such-task', $taskid);
+ return;
+ }
- // disable each location user has no permission for
- foreach ($data['locations'] as &$loc) {
- if (!in_array($loc["locationid"], $allowedLocs)) {
- $loc["disabled"] = "disabled";
- } elseif ($loc["locationid"] == $requestedLocation) {
- $data['location'] = $loc['locationname'];
- }
- }
- // Always show public key (it's public, isn't it?)
- $data['pubKey'] = SSHKey::getPublicKey();
+ $td =& $task['data'];
+ $type = Request::get('type', false, 'string');
+ if ($type === false) {
+ // Try to guess
+ if (isset($td['locationId']) || isset($td['clients'])) {
+ $type = 'reboot';
+ } elseif (isset($td['result'])) {
+ $type = 'exec';
+ }
+ }
+ if ($type === 'reboot') {
+ $data = [
+ 'taskId' => $task['id'],
+ 'locationId' => $td['locationId'],
+ 'locationName' => Location::getName($td['locationId']),
+ ];
+ $uuids = array_map(function ($entry) {
+ return $entry['machineuuid'];
+ }, $td['clients']);
+ $data['clients'] = RebootQueries::getMachinesByUuid($uuids);
+ Render::addTemplate('status-reboot', $data);
+ } elseif ($type === 'exec') {
+ $data = [
+ 'taskId' => $task['id'],
+ ];
+ Render::addTemplate('status-exec', $data);
+ } elseif ($type === 'checkhost') {
+ $ip = array_key_first($td['result']);
+ $data = [
+ 'taskId' => $task['id'],
+ 'host' => $ip,
+ ];
+ Render::addTemplate('status-checkconnection', $data);
+ } else {
+ Message::addError('unknown-task-type');
+ }
+ }
- // Only enable shutdown/reboot-button if user has permission for the location
- Permission::addGlobalTags($data['perms'], $requestedLocation, ['newkeypair', 'action.shutdown', 'action.reboot']);
+ private function showJumpHosts()
+ {
+ User::assertPermission('jumphost.*');
+ $hosts = [];
+ $res = Database::simpleQuery('SELECT hostid, host, port, Count(jxs.subnetid) AS subnetCount, reachable
+ FROM reboot_jumphost jh
+ LEFT JOIN reboot_jumphost_x_subnet jxs USING (hostid)
+ GROUP BY hostid
+ ORDER BY hostid');
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $hosts[] = $row;
+ }
+ $data = [
+ 'jumpHosts' => $hosts
+ ];
+ Permission::addGlobalTags($data['perms'], null, ['jumphost.edit', 'jumphost.assign-subnet']);
+ Render::addTemplate('jumphost-list', $data);
- Render::addTemplate('header', $data);
+ // Append list of active reboot/shutdown tasks
+ $allowedLocs = User::getAllowedLocations("action.*");
+ $active = RebootControl::getActiveTasks($allowedLocs);
+ if (!empty($active)) {
+ foreach ($active as &$entry) {
+ $entry['locationName'] = Location::getName($entry['locationId']);
+ }
+ unset($entry);
+ Render::addTemplate('task-list', ['list' => $active]);
+ }
- // only fill table if user has at least one permission for the location
- if (!in_array($requestedLocation, $allowedLocs)) {
- Message::addError('locations.no-permission-location', $requestedLocation);
- } else {
- $data['data'] = RebootQueries::getMachineTable($requestedLocation);
- Render::addTemplate('_page', $data);
- }
+ }
- // Append list of active reboot/shutdown tasks
- $active = RebootControl::getActiveTasks($allowedLocs);
- if (!empty($active)) {
- foreach ($active as &$entry) {
- $entry['locationName'] = Location::getName($entry['locationId']);
- }
- unset($entry);
- Render::addTemplate('task-list', ['list' => $active]);
- }
+ private function showJumpHost()
+ {
+ User::assertPermission('jumphost.edit');
+ $id = Request::get('id', Request::REQUIRED, 'string');
+ if ($id === 'new') {
+ $host = ['hostid' => 'new', 'port' => 22, 'script' => "# Assume bash\n"
+ . "MACS='%MACS%'\n"
+ . "IP='%IP'\n"
+ . "EW=false\n"
+ . "WOL=false\n"
+ . "command -v etherwake > /dev/null && ( [ \"\$(id -u)\" = 0 ] || [ -u \"\$(which etherwake)\" ] ) && EW=true\n"
+ . "command -v wakeonlan > /dev/null && WOL=true\n"
+ . "if \$EW && ( ! \$WOL || [ \"\$IP\" = '255.255.255.255' ] ); then\n"
+ . "\tifaces=\"\$(ls -1 /sys/class/net/)\"\n"
+ . "\t[ -z \"\$ifaces\" ] && ifaces=eth0\n"
+ . "\tfor ifc in \$ifaces; do\n"
+ . "\t\t[ \"\$ifc\" = 'lo' ] && continue\n"
+ . "\t\tfor mac in \$MACS; do\n"
+ . "\t\t\tetherwake -i \"\$ifc\" \"\$mac\"\n"
+ . "\t\tdone\n"
+ . "\tdone\n"
+ . "elif \$WOL; then\n"
+ . "\twakeonlan -i \"\$IP\" \$MACS\n"
+ . "else\n"
+ . "\techo 'No suitable WOL tool found' >&2\n"
+ . "\texit 1\n"
+ . "fi\n"];
+ } else {
+ $host = $this->getJumpHost($id);
+ }
+ Render::addTemplate('jumphost-edit', $host);
+ }
- }
+ private function getJumpHost($hostid)
+ {
+ $host = Database::queryFirst('SELECT hostid, host, port, username, sshkey, script
+ FROM reboot_jumphost
+ WHERE hostid = :id', ['id' => $hostid]);
+ if ($host === false) {
+ Message::addError('no-such-jumphost', $hostid);
+ Util::redirect('?do=rebootcontrol');
}
+ return $host;
}
- function doAjax()
+ protected function doAjax()
{
- $this->action = Request::post('action', false, 'string');
- if ($this->action === 'generateNewKeypair') {
+ $action = Request::post('action', false, 'string');
+ if ($action === 'generateNewKeypair') {
User::assertPermission("newkeypair");
Property::set("rebootcontrol-private-key", false);
echo SSHKey::getPublicKey();
@@ -169,6 +300,14 @@ class Page_RebootControl extends Page
}
}
+}
-
+// Remove when we require >= 7.3.0
+if (!function_exists('array_key_first')) {
+ function array_key_first(array $arr) {
+ foreach($arr as $key => $unused) {
+ return $key;
+ }
+ return NULL;
+ }
}