Merge remote-tracking branch 'upstream/master'

This commit is contained in:
bergware
2023-12-24 15:58:27 +01:00
15 changed files with 977 additions and 1 deletions

View File

@@ -2474,3 +2474,32 @@ If set to 'Yes' the bash history will persist reboots, set to 'No' to disable.
**Note:** Disabling and Enabling will remove the entire bash history.
:end
:WOL_enable_help:
If set to yes Unraidwold daemon is set to run.
:end
:WOL_docker_help:
If set to yes when wake on lan packets are received checks are carrried out for dockers otherwise dockers will be ignored.
:end
:WOL_run_VM_help:
If set to yes when wake on lan packets are received checks are carrried out for virtual machines otherwise virtual machines will be ignored.
:end
:WOL_run_LXC_help:
If set to yes when wake on lan packets are received checks are carrried out for LXC otherwise LXC will be ignored. The LXC plugin needs to be installed for LXC to be processed.
:end
:WOL_interface_help:
Specify the interface to the daemon to bind to. Some interfaces may not be able to recieve etherframe packets. Recommend to bind to a physical interface.
:end
:WOL_promiscuous_mode_help:
Enable to set the NIC not to filer packets.
:end
:WOL_log_file_help:
Default is to log to syslog but if you want a different log location set the file name.
:end

View File

@@ -50,7 +50,7 @@
'uuid' => $lv->domain_generate_uuid(),
'clock' => 'localtime',
'arch' => 'x86_64',
'machine' => 'pc',
'machine' => 'pc-i440fx',
'mem' => 1024 * 1024,
'maxmem' => 1024 * 1024,
'password' => '',

View File

@@ -0,0 +1,121 @@
Menu="UNRAID-OS"
Title="Wake On LAN"
Icon="fa-bell"
Tag="server"
---
<?PHP
/* Copyright 2005-2023, Lime Technology
* Copyright 2012-2023, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
if (count($_POST)) {
$cfg = NULL ;
if ($_POST['#apply'] == "_(Save)_") {
foreach($_POST as $postkey=>$data) {
if ($postkey=="#apply") continue;
$keys = explode(";",$postkey);
if (count($keys) >1) $update_file[$keys[1]][$keys[2]][$keys[0]]=$data;
}
foreach($update_file as $type => $types) {
foreach($types as $name => $details) {
if ($details['user_mac'] == "") $details['user_mac'] = "None Defined";
if ($details['user_mac'] == "None Defined" && $details['enable'] == "enable") unset($update_file[$type][$name]) ;
}
}
}
unset($_POST) ;
file_put_contents("/boot/config/wol.json",json_encode($update_file));
#echo '<meta http-equiv="refresh" content="0;url=/Tools">';
echo '<meta http-equiv="refresh" content="0">';
#unset($_SESSION['csrf_token']);
}
?>
<script src="/webGui/javascript/jquery.tablesorter.widgets.js"></script>
<script>
function showWOL(options, init = false) {
option = options;
if (init) {
$('#wolsearch').prop('disabled', true);
$.post('/webGui/include/WOL.php',{table:'t1load',option:"all"},function(data){
clearTimeout(timers.refresh);
filter = [];
$("#t1").trigger("destroy");
$('#t1').html(data.html);
$('#t1').tablesorter({
sortList: [[0,0]],
sortAppend: [[0,0]],
widgets: ['stickyHeaders','filter','zebra'],
widgetOptions: {
// on black and white, offset is height of #menu
// on azure and gray, offset is height of #header
stickyHeaders_offset: ($('#menu').height() < 50) ? $('#menu').height() : $('#header').height(),
filter_columnFilters: false,
zebra: ["normal-row","alt-row"]
}
});
$('div.spinner.fixed').hide('slow');
$('#wolsearch').prop('disabled', false);
$('#select').prop('disabled', false);
$('#rebuild').prop('disabled', data.init);
},"json");
} else {
filter = [];
filterWOL();
}
}
function filterWOL() {
var totalColumns = $('#t1')[0].config.columns;
var filter = [];
filter[totalColumns] = $('#wolsearch').val(); // this searches all columns
$('#t1').trigger('search', [filter]);
}
function showWOLupdate() {
$('#rebuild').prop('disabled', true);
$('#t1').html("");
$('#wolsearch').prop('disabled', true);
$('#select').prop('disabled', true);
$('div.spinner.fixed').show('slow');
$.post('/webGui/include/WOL.php',{table:'t1create',option:"all"},function(data){
$('#rebuild').prop('disabled', false);
showWOL("all",true);
$('div.spinner.fixed').hide('slow');
});
}
function maccreate(name) {
$.getJSON("/plugins/dynamix.vm.manager/include/VMajax.php?action=generate-mac", function( data ) {
if (data.mac) {
$('#'+name).val(data.mac);
}
});
}
showWOL("all",true);
</script>
:WOL_intro_help:
<form autocomplete="off" onsubmit="return false;"><span><input class="t1 search" id="wolsearch" type="search" placeholder="Search..." onchange="filterWOL();"></span></form>
<pre><form name="WOL" id="WOL" method="POST" class="js-confirm-leave" >
<table name="t1"id='t1' class="t1 unraid tablesorter" >
<tr><td><div class="spinner"></div></td></tr></table></pre><br>
<input type="button" value="_(Done)_" onclick="done()">
<input type="submit" name="#apply" id='#apply' value="_(Save)_" >
</form>

View File

@@ -0,0 +1,94 @@
Menu="OtherSettings"
Type="xmenu"
Title="Wake on Lan Settings"
Icon="fa-bell"
Tag="share-alt"
---
<?PHP
/* Copyright 2005-2023, Lime Technology
* Copyright 2012-2023, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
?>
<?
exec("ls --indicator-style=none /sys/class/net|grep -P '^eth[1-9][0-9]*$'",$ports);
$disabled = _var($var,'fsState')!='Stopped' ? 'disabled' : '';
$width = [166,300];
$file = '/boot/config/wol.cfg';
$current = parse_ini_file($file);
if (!isset($current['LOGFILE'])) $current['LOGFILE'] = "syslog"
?>
<script>
showStatus('pid','unraidwold');
</script>
<form markdown="1" name="WOLsettings" method="POST" action="/update.php" target="progressFrame" >
<input type="hidden" name="#file" value="<?=$file;?>">
<input type="hidden" name="#command" value="/webGui/scripts/WOL_action">
<input type="hidden" name="#arg[1]" value="load">
_(Enable Wake on Lan)_:
: <select name="WOLENABLED" >
<?=mk_option($current['WOLENABLED'], "no", _('No'))?>
<?=mk_option($current['WOLENABLED'], "yes", _('Yes'))?>
</select>
:WOL_enable_help:
_(Enable Docker actions)_:
: <select name="RUNDOCKER" >
<?=mk_option($current['RUNDOCKER'], "y", _('Yes'))?>
<?=mk_option($current['RUNDOCKER'], "n", _('No'))?>
</select>
:WOL_run_docker_help:
_(Enable LXC actions)_:
: <select name="RUNLXC" >
<?=mk_option($current['RUNLXC'], "y", _('Yes'))?>
<?=mk_option($current['RUNLXC'], "n", _('No'))?>
</select>
:WOL_run_LXC_help:
_(Enable VM actions)_:
: <select name="RUNVM" >
<?=mk_option($current['RUNVM'], "y", _('Yes'))?>
<?=mk_option($current['RUNVM'], "n", _('No'))?>
</select>
:WOL_run_VM_help:
_(Interface to listern on)_:
: <select id="INTERFACE" name="INTERFACE" >
<?=mk_option(_var($eth0,'INTERFACE'),'eth0','eth0','selected')?>
<?foreach ($ports as $port):?>
<?if (!locked('eth0',$port)) echo mk_option_check($current['INTERFACE'],$port,$port)?>
<?endforeach;?>
</select>
:WOL_interface_help:
_(Interface promiscuous mode)_:
: <select id="IFMODE" name="IFMODE" >
<?=mk_option($current['IFMODE'], "n", _('No'))?>
<?=mk_option($current['IFMODE'], "y", _('Yes'))?>
</select>
:WOL_promiscuous_mode_help:
_(Log file)_:
: <input name="LOGFILE" class="narrow"type="text" value=<?=$current['LOGFILE']?>>
:WOL_log_file_help:
&nbsp;
: <input type="submit" value="_(Apply)_" disabled><input type="button" value="_(Done)_" onclick="done()">
</form>

View File

@@ -0,0 +1,3 @@
#!/bin/bash
/usr/local/emhttp/plugins/dynamix/include/script/WOL_action load &

View File

@@ -0,0 +1,3 @@
#!/bin/bash
/usr/local/emhttp/plugins/dynamix/include/script/WOL_action stop &

View File

@@ -0,0 +1,112 @@
<?PHP
/* Copyright 2005-2023, Lime Technology
* Copyright 2012-2023, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
?>
<?
global $lv;
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Helpers.php";
require_once "$docroot/webGui/include/SysDriversHelpers.php";
require_once "$docroot/plugins/dynamix.plugin.manager/include/PluginHelpers.php";
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
// add translations
$_SERVER['REQUEST_URI'] = 'tools';
require_once "$docroot/webGui/include/Translations.php";
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
require_once "/usr/local/emhttp/plugins/dynamix.vm.manager/include/libvirt.php";
$vms = $lv->get_domains();
sort($vms,SORT_NATURAL);
foreach($vms as $vm){
$arrEntries['VM'][$vm]['interfaces'] = $lv->get_nic_info($vm);
$arrEntries['VM'][$vm]['name'] = $vm;
}
$DockerClient = new DockerClient();
$containers = $DockerClient->getDockerJSON("/containers/json?all=1");
foreach($containers as $ct)
$arrEntries['Docker'][substr($ct["Names"][0],1)] = [
'interfaces' => ['0 '=> ['mac' => isset($ct["NetworkSettings"]["Networks"]["bridge"]["MacAddress"]) ? $ct["NetworkSettings"]["Networks"]["bridge"]["MacAddress"] : ""]],
'name' => substr($ct["Names"][0],1),
];
$lxc = explode("\n",shell_exec("lxc-ls -1")) ;
$lxcpath = trim(shell_exec("lxc-config lxc.lxcpath"));
foreach ($lxc as $lxcname) {
if ($lxcname == "") continue;
$value = explode("=",shell_exec("cat $lxcpath/$lxcname/config | grep 'hwaddr'"));
$arrEntries['LXC'][$lxcname]['interfaces'][0]['mac'] = trim($value[1]);
$arrEntries['LXC'][$lxcname]['name'] = $lxcname;
}
if (is_file("/boot/config/wol.json")) $user_mac = json_decode(file_get_contents("/boot/config/wol.json"),true); else $user_mac = [];
foreach($arrEntries as $key => $data) {
$type=$key;
foreach($data as $data2){
$name=$data2['name'];
if (isset($user_mac[$type][$name])) {
$name=$name;
#var_dump($name);
$arrEntries[$type][$name]['enable'] = $user_mac[$type][$name]['enable'];
$arrEntries[$type][$name]['user_mac'] = strtolower($user_mac[$type][$name]['user_mac']);
} else {
$arrEntries[$type][$name]['enable'] = 'enable';
$arrEntries[$type][$name]['user_mac'] = 'None Defined';
}
}
}
switch ($_POST['table']) {
case 't1load':
$arrMacs = $arrEntries;
$html = "<thead><tr><th>"._('Service')."</th><th>"._('Name')."</th><th>"._('Mac Address')."</th><th>"._('Enabled')."</th><th>"._('User Mac Address')."</th></tr></thead>";
$html .= "<tbody>";
ksort($arrMacs);
foreach($arrMacs as $systype => $m) {
foreach($m as $macaddr) {
if ($systype == "") continue;
$html .= "<tr id='row$systype'>";
$macs = "";
foreach($macaddr['interfaces'] as $intdetail)
{
$macs .= " {$intdetail['mac']}" ;
}
$html .= "<td>$systype</td>";
$selecttypename="enable;".$systype.";".$macaddr['name'];
$mactypename=htmlspecialchars("user_mac;".$systype.";".$macaddr['name']);
$mactypeid=htmlspecialchars("user_mac".$systype."".$macaddr['name']);
$user_mac_str = '<input type="text" name="'.$mactypename.'" id="'.$mactypeid.'" class="narrow" value="'.htmlspecialchars($macaddr['user_mac']).'" title="'._("random mac, you can supply your own").'" /><a><i onclick="maccreate(\''.$mactypeid.'\')" class="fa fa-refresh mac_generate" title="re-generate random mac address"></i></a>';
$html .= "<td>{$macaddr['name']}</td><td id=\"status$systype\">$macs</td><td>";
$html .="<select name='$selecttypename' class='audio narrow'>";
$html .= mk_option($macaddr["enable"] , "disable", _("Disabled"));
$html .= mk_option($macaddr["enable"] , "enable", _("Enabled"));
$html .= "</select></td><td>".$user_mac_str."</td></tr>";
$text = "";
}
}
$html .= "</tbody>";
$rtn = array();
$rtn['html'] = $html;
echo json_encode($rtn);
break;
case "macaddress":
$seed = 1;
$prefix = '52:54:AA';
$prefix.':'.$lv->macbyte(($seed * rand()) % 256).':'.$lv->macbyte(($seed * rand()) % 256).':'.$lv->macbyte(($seed * rand()) % 256);
echo json_encode(['mac' => $prefix]);
break;
}
?>

View File

@@ -0,0 +1,174 @@
#!/usr/bin/php
<?php
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
require_once "$docroot/webGui/include/Helpers.php";
require_once "$docroot/webGui/include/Custom.php";
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt.php";
require_once "$docroot/plugins/dynamix.plugin.manager/include/PluginHelpers.php";
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
function getContainerStats($container, $option) {
exec("lxc-info " . $container, $content);
foreach($content as $index => $string) {
if (strpos($string, $option) !== FALSE)
return trim(explode(':', $string)[1]);
}
}
$mac = $argv[1];
$libvirtd_running = is_file('/var/run/libvirt/libvirtd.pid') ;
$dockerd_running = is_file('/var/run/dockerd.pid');
$lxc_ls_exist = is_file('/usr/bin/lxc-ls');
#$RUNDOCKER = $RUNLXC = $RUNVM = true;
extract(parse_ini_file("/boot/config/wol.cfg")) ;
if (!isset($RUNLXC)) $RUNLXC = "y";
if (!isset($RUNVM)) $RUNVM = "y";
if (!isset($RUNDocker)) $RUNDocker = "y";
$arrEntries = [] ;
if ($libvirtd_running && $RUNVM == "y") {
$vms = $lv->get_domains();
sort($vms,SORT_NATURAL);
foreach($vms as $vm){
$arrEntries['VM'][$vm]['interfaces'] = $lv->get_nic_info($vm);
$arrEntries['VM'][$vm]['name'] = $vm;
}
}
if ($dockerd_running && $RUNDOCKER == "y") {
$DockerClient = new DockerClient();
$containers = $DockerClient->getDockerJSON("/containers/json?all=1");
foreach($containers as $container)
$arrEntries['Docker'][ substr($container["Names"][0],1) ] = [
'interfaces' => ['0' => ['mac' => isset($container["NetworkSettings"]["Networks"]["bridge"]["MacAddress"]) ? $container["NetworkSettings"]["Networks"]["bridge"]["MacAddress"]:""]],
'name' => substr($container["Names"][0],1),
'state' => $container["State"],
];
}
if ($lxc_ls_exist && $RUNLXC == "y") {
$lxc = explode("\n",shell_exec("lxc-ls -1")) ;
$lxcpath = trim(shell_exec("lxc-config lxc.lxcpath"));
foreach ($lxc as $lxcname) {
if ($lxcname == "") continue;
$values = explode("=",shell_exec("cat $lxcpath/$lxcname/config | grep 'hwaddr'"));
$arrEntries['LXC'][$lxcname]['interfaces'][0]['mac'] = trim($values[1]);
$arrEntries['LXC'][$lxcname]['name'] = $lxcname;
}
}
if (is_file("/boot/config/wol.json")) $user_mac = json_decode(file_get_contents("/boot/config/wol.json"),true); else $user_mac = [];
foreach($arrEntries as $typekey => $typedata)
{
foreach($typedata as $typeEntry){
$name=$typeEntry['name'];
if (isset($user_mac[$typekey][$name])) {
$name=$name;
$arrEntries[$typekey][$name]['enable'] = $user_mac[$typekey][$name]['enable'];
$arrEntries[$typekey][$name]['user_mac'] = strtolower($user_mac[$typekey][$name]['user_mac']);
} else {
$arrEntries[$typekey][$name]['enable'] = "enable";
$arrEntries[$typekey][$name]['user_mac'] = 'None Defined';
}
}
}
$mac_list=[];
foreach($arrEntries as $type => $detail)
{
foreach($detail as $name => $entryDetail)
{
foreach($entryDetail['interfaces'] as $interfaces)
{
if($interfaces['mac'] == "" && $entryDetail['user_mac'] == "None Defined") continue;
if (isset($entryDetail['state'])) $state = $entryDetail['state']; else $state = "";
if (isset($entryDetail['enable']) && !$entryDetail['enable'] ) $enable = false; else $enable = true;
if ($entryDetail['user_mac'] != "None Defined") {
$mac_list[$entryDetail['user_mac']] = [
'type' => $type,
'name' => $name,
'state' => $state,
'enable' => $entryDetail['enable'],
];
}
if ($interfaces['mac'] != "") {
$mac_list[$interfaces['mac']] = [
'type' => $type,
'name' => $name,
'state' => $state,
'enable' => $entryDetail['enable'],
];
}
}
}
}
$found = array_key_exists($mac,$mac_list);
if ($found && $mac_list[$mac]['enable'] == "enable") {
echo _("Found "). $mac . " ".$mac_list[$mac]['type']." ".$mac_list[$mac]['name'];
switch ($mac_list[$mac]['type']) {
case "VM":
if ($libvirtd_running && $RUNVM == "y") {
$res = $lv->get_domain_by_name($mac_list[$mac]['name']);
$dom = $lv->domain_get_info($res);
$state = $lv->domain_state_translate($dom['state']);
switch ($state) {
case 'running':
break;
case 'paused':
case 'pmsuspended':
$lv->domain_resume("{$mac_list[$mac]['name']}");
break;
default:
$lv->domain_start("{$mac_list[$mac]['name']}");
}
}
break;
case "LXC":
if ($lxc_ls_exist && $RUNLXC == "y") {
$state = getContainerStats($mac_list[$mac]['name'], "State");
switch ($state) {
case 'RUNNING':
break;
case 'FROZEN':
shell_exec("lxc-unfreeze {$mac_list[$mac]['name']}");
break;
default:
shell_exec("lxc-start {$mac_list[$mac]['name']}");
}
}
break;
case "Docker":
if ($dockerd_running && $RUNDOCKER == "y") {
switch ($mac_list[$mac]['state']) {
case "exited":
case "created":
shell_exec("docker start {$mac_list[$mac]['name']}");
break;
case "paused":
shell_exec("docker unpause {$mac_list[$mac]['name']}");
}
}
break;
}
} else {
if ($mac_list[$mac]['enable'] == "disable") echo $mac . " " . _(" has not been actioned as set to disabled");
else echo _("Not Found ")." ". $mac . " "._(" ignoring or Maybe actions disabled for type(Docker/VM/LXC)");
}
?>

View File

@@ -0,0 +1,93 @@
#!/bin/bash
#
# script: WOL_action
#
# Startup script for unraidwold
#
# Simon Fairweather - Initial Script October 2023
DAEMON="unraidwold"
BINARY="/usr/local/sbin/unraidwold"
# run & log functions
. /etc/rc.d/rc.runlog
. /boot/config/wol.cfg
unraidwold_running(){
sleep 0.1
ps axc | grep -q ' unraidwold'
}
unraidwold_start(){
log "Starting $DAEMON..."
local REPLY
if unraidwold_running; then
REPLY="Already started"
else
nohup $BINARY $1 $2 $3 $4 $5 $6 > /dev/null &
fi
log "$DAEMON... $REPLY."
}
unraidwold_stop(){
log "Stopping $DAEMON..."
local REPLY
if ! unraidwold_running; then
REPLY="Already stopped"
else
killall -TERM $DAEMON
if ! unraidwold_running; then REPLY="Stopped"; else REPLY="Failed"; fi
fi
log "$DAEMON... $REPLY."
}
unraidwold_status(){
if unraidwold_running; then
echo "$DAEMON is currently running."
else
echo "$DAEMON is not running."
exit 1
fi
}
unraidwold_load()
{
if unraidwold_running; then
unraidwold_stop
fi
sleep 1
if [ "$WOLENABLED" == "yes" ]; then
interfacemode=""
logoptions=""
if [ "$IFMODE" == "y" ]; then
interfacemode="--promiscuous"
fi
if [ "$LOGFILE" != "syslog" ]; then
logoptions="--log $LOGFILE"
fi
unraidwold_start --interface $INTERFACE $interfacemode $logoptions
fi
}
case "$1" in
'start')
unraidwold_start $2 $3 $4 $5 $6
;;
'stop')
unraidwold_stop
;;
'status')
unraidwold_status
;;
'load')
unraidwold_load
;;
*)
echo "Usage: $BDAEMON start|stop|restart|status"
exit 1
esac
exit 0

View File

@@ -0,0 +1,11 @@
table#t1{margin-top:0;font-family:clear-sans}
table#t1 thead tr th{font-weight:bold}
table#t1 tbody tr td{padding:4px 20px 4px 0;margin:0;text-align:left;white-space:normal}
table#t1 tbody tr td:nth-child(1){width:10%}
table#t1 tbody tr td:nth-child(2){width:30%}
table#t1 tbody tr td:nth-child(3){width:15%}
table#t1 tbody tr td:nth-child(4){width:20%}
table#t1 tbody tr td:nth-child(5){width:25%;padding-right:0}
table.t1.tablesorter .filtered{display:none}
.tablesorter-header-inner{font-family:clear-sans;font-weight:bold}
#macform .mac_generate{cursor:pointer;margin-left:-5px;color:#08C;font-size:1.3rem;transform:translate(0px, 2px)}

26
src/unraidwold/LICENSE Normal file
View File

@@ -0,0 +1,26 @@
BSD 2-Clause License
Copyright (c) 2021, Scott Ellis
All rights reserved.
Copyright (c) 2023, Limetech,Simon Fairweather
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

1
src/unraidwold/go.mod Normal file
View File

@@ -0,0 +1 @@
module github.com/SimonFair/unraidwold/v2

63
src/unraidwold/go.sum Normal file
View File

@@ -0,0 +1,63 @@
github.com/antchfx/xmlquery v1.3.15 h1:aJConNMi1sMha5G8YJoAIF5P+H+qG1L73bSItWHo8Tw=
github.com/antchfx/xmlquery v1.3.15/go.mod h1:zMDv5tIGjOxY/JCNNinnle7V/EwthZ5IT8eeCGJKRWA=
github.com/antchfx/xpath v1.2.3 h1:CCZWOzv5bAqjVv0offZ2LVgVYFbeldKQVuLNbViZdes=
github.com/antchfx/xpath v1.2.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/digitalocean/go-libvirt v0.0.0-20221205150000-2939327a8519 h1:OpkN/n40cmKenDQS+IOAeW9DLhYy4DADSeZnouCEV/E=
github.com/digitalocean/go-libvirt v0.0.0-20221205150000-2939327a8519/go.mod h1:WyJJyfmJ0gWJvjV+ZH4DOgtOYZc1KOvYyBXWCLKxsUU=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,236 @@
// Copyright (c) 2021, Scott Ellis
// All rights reserved.
// Copyright (c) 2023 Limetech, Simon Fairweather.
//
// Unraid Wake-on-LAN(V1.0.0)
//
// Listens for a WOL magic packet (UDP) and ether frame type 0x0842
// If a matching VM/Docker or LXC is found, it is started (if not already running) and resumed if paused
//
// Filters on ether proto 0x0842 or udp port 9
package main
import (
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"log/syslog"
"os"
"os/exec"
"os/signal"
"syscall"
"github.com/google/gopacket"
"github.com/google/gopacket/pcap"
"github.com/google/gopacket/layers"
)
var logger *log.Logger
func main() {
var logOutput io.Writer
var (
appVersion bool
interfaceName string
logFile string
promiscuous bool
)
flag.BoolVar(&appVersion, "version", false, "Print the version and copyright information")
flag.StringVar(&interfaceName, "interface", "", "Network interface name (required)")
flag.StringVar(&logFile, "log", "", "Log file path")
flag.BoolVar(&promiscuous, "promiscuous", false, "Enable promiscuous mode")
flag.Parse()
versionInfo := "Unraid Wake-on-LAN (V1.0.0)\nCopyright (c) 2021, Scott Ellis\nAll rights reserved.\nCopyright (c) 2023 Limetech, Simon Fairweather.\n"
// Check if the version flag is set
if appVersion {
fmt.Println(versionInfo)
return
}
// Check if the required --interface flag is provided
if interfaceName == "" {
fmt.Println("Error: The --interface flag is required")
flag.PrintDefaults()
os.Exit(1)
}
deviceError := deviceExists(interfaceName)
if (! deviceError) {
fmt.Println("Error: The --interface network address is not valid")
flag.PrintDefaults()
os.Exit(1)
}
// Set up logging
if logFile != "" {
// If a log file is specified, create or append to the file
file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
defer file.Close()
if err != nil {
logger.Fatal(err)
}
//defer file.Close()
logOutput = io.MultiWriter(file, os.Stdout) // Log to both file and stdout
} else {
// If no log file is specified, log to syslog
syslogWriter, err := syslog.New(syslog.LOG_INFO|syslog.LOG_DAEMON, "Unraidwold")
if err != nil {
logger.Fatal(err)
}
logOutput = syslogWriter
}
// Create a logger that writes to the specified output
logger = log.New(logOutput, "", log.LstdFlags)
var filter = "ether proto 0x0842 or udp port 9"
// Create a PID file
pidFile := "/var/run/unraidwold.pid" // Change the path as needed
err := writePIDFile(pidFile)
if err != nil {
logger.Fatal(err)
}
logger.Println("Processing WOL Requests.")
// Check if promiscuous mode is enabled
if promiscuous {
logger.Println("Promiscuous mode is enabled")
}
handle, err := pcap.OpenLive(interfaceName, 1600, promiscuous, pcap.BlockForever)
if err != nil {
logger.Fatal(err)
}
if err := handle.SetBPFFilter(filter); err != nil {
log.Fatalf("Something in the BPF went wrong!: %v", err)
}
defer handle.Close()
signalChan := make(chan os.Signal, 1)
doneChan := make(chan bool, 1)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
go processPackets(handle, signalChan, doneChan)
// Wait for a signal to exit
<-doneChan
//fmt.Println("Exiting...")
removePIDFile(pidFile)
logger.Println("Stopping WOL Daemon.")
// Close down.
os.Exit(1)
//return
}
func writePIDFile(pidFile string) error {
pid := os.Getpid()
pidStr := fmt.Sprintf("%d\n", pid)
return ioutil.WriteFile(pidFile, []byte(pidStr), 0644)
}
func removePIDFile(pidFile string) {
err := os.Remove(pidFile)
if err != nil {
logger.Printf("Error removing PID file: %v\n", err)
}
}
func processPackets(handle *pcap.Handle, signalChan chan os.Signal, doneChan chan bool) error {
var mac string
source := gopacket.NewPacketSource(handle, handle.LinkType())
for {
select {
case packet := <-source.Packets():
ethLayer := packet.Layer(layers.LayerTypeEthernet)
udpLayer := packet.Layer(layers.LayerTypeUDP)
if ethLayer != nil {
ethernetPacket, _ := ethLayer.(*layers.Ethernet)
if ethernetPacket.EthernetType == 0x0842 {
payload := ethernetPacket.Payload
mac = fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", payload[6], payload[7], payload[8], payload[9], payload[10], payload[11])
}
}
if udpLayer != nil {
udpPacket, _ := udpLayer.(*layers.UDP)
if udpPacket.DstPort == layers.UDPPort(9) {
appPacket := packet.ApplicationLayer()
if appPacket != nil {
payload := appPacket.Payload()
mac = fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", payload[12], payload[13], payload[14], payload[15], payload[16], payload[17])
}
}
}
go runcmd(mac)
case sig := <-signalChan:
fmt.Printf("Received signal: %v\n", sig)
doneChan <- true
return nil
}
}
}
func runcmd(mac string) bool {
app := "/usr/local/emhttp/plugins/dynamix/include/WOLrun.php"
arg := mac
cmd := exec.Command(app, arg)
stdout, err := cmd.Output()
if err != nil {
fmt.Println(err.Error())
return false
}
// Print the output
logger.Println(string(stdout))
return true
}
// Return the first MAC address seen in the UDP WOL packet
func GrabMACAddrUDP(packet gopacket.Packet) (string, error) {
app := packet.ApplicationLayer()
if app != nil {
payload := app.Payload()
mac := fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", payload[12], payload[13], payload[14], payload[15], payload[16], payload[17])
//fmt.Printf("found MAC: %s\n", mac)
return mac, nil
}
return "", errors.New("no MAC found in packet")
}
// Check if the network device exists
func deviceExists(interfacename string) bool {
if interfacename == "" {
fmt.Printf("No valid interface to listen on specified\n\n")
return false
}
devices, err := pcap.FindAllDevs()
if err != nil {
log.Panic(err)
}
for _, device := range devices {
if device.Name == interfacename {
return true
}
}
return false
}

10
src/unraidwold/wolbuild Normal file
View File

@@ -0,0 +1,10 @@
mkdir -p unraidwol
cd unraidwol/
DATA_DIR=$(pwd)
cd ${DATA_DIR}
git clone https://github.com/SimonFair/unraidwol
cd ${DATA_DIR}/unraidwol
git checkout main
PATH="$PATH:/usr/local/go/bin"
go mod tidy
go build