Compare commits
9 Commits
master
...
tailscale-
Author | SHA1 | Date | |
---|---|---|---|
a7ac0986a1 | |||
57edfd6d7b | |||
3a5d5f07ef | |||
13413b5d1f | |||
47a04e10c0 | |||
44e09d534c | |||
1b65b07110 | |||
da01e24ff8 | |||
a080ec364d |
@@ -2303,6 +2303,136 @@ Generally speaking, it is recommended to leave this setting to its default value
|
||||
IMPORTANT NOTE: If adjusting port mappings, do not modify the settings for the Container port as only the Host port can be adjusted.
|
||||
:end
|
||||
|
||||
:docker_container_network_help:
|
||||
This allows your container to utilize the network configuration of another container. Select the appropriate container from the list.<br/>This setup can be particularly beneficial if you wish to route your container's traffic through a VPN.
|
||||
:end
|
||||
|
||||
:docker_tailscale_help:
|
||||
Enable Tailscale to add this container as a machine on your Tailnet.
|
||||
:end
|
||||
|
||||
:docker_tailscale_hostname_help:
|
||||
Provide the hostname for this container. It does not need to match the container name, but it must be unique on your Tailnet. Note that an HTTPS certificate will be generated for this hostname, which means it will be placed in a public ledger, so use a name that you don't mind being public.
|
||||
For more information see <a href="https://tailscale.com/kb/1153/enabling-https" target="_blank">enabling https</a>.
|
||||
:end
|
||||
|
||||
:docker_tailscale_be_exitnode_help:
|
||||
Enable this if other machines on your Tailnet should route their Internet traffic through this container, this is most useful for containers that connect to commercial VPN services.
|
||||
Be sure to authorize this Exit Node in your <a href="https://login.tailscale.com/admin/machines" target="_blank">Tailscale Machines Admin Panel</a>.
|
||||
For more details, see the Tailscale documentation on <a href="https://tailscale.com/kb/1103/exit-nodes" target="_blank">Exit Nodes</a>.
|
||||
:end
|
||||
|
||||
:docker_tailscale_exitnode_ip_help:
|
||||
Optionally route this container's outgoing Internet traffic through an Exit Node on your Tailnet. Choose the Exit Node or input its Tailscale IP address.
|
||||
For more details, see <a href="https://tailscale.com/kb/1103/exit-nodes" target="_blank">Exit Nodes</a>.
|
||||
:end
|
||||
|
||||
:docker_tailscale_lanaccess_help:
|
||||
Only applies when this container is using an Exit Node. Enable this to allow the container to access the local network.
|
||||
|
||||
<b>WARNING:</b> Even with this feature enabled, systems on your LAN may not be able to access the container unless they have Tailscale installed.
|
||||
:end
|
||||
|
||||
:docker_tailscale_userspace_networking_help:
|
||||
When enabled, this container will operate in a restricted environment. Tailscale DNS will not work, and the container will not be able to initiate connections to other Tailscale machines. However, other machines on your Tailnet will still be able to communicate with this container.
|
||||
|
||||
When disabled, this container will have full access to your Tailnet. Tailscale DNS will work, and the container can fully communicate with other machines on the Tailnet.
|
||||
However, systems on your LAN may not be able to access the container unless they have Tailscale installed.
|
||||
:end
|
||||
|
||||
:docker_tailscale_ssh_help:
|
||||
Tailscale SSH is similar to the Docker "Console" option in the Unraid webgui, except you connect with an SSH client and authenticate via Tailscale.
|
||||
For more details, see the <a href="https://tailscale.com/kb/1193/tailscale-ssh" target="_blank">Tailscale SSH</a> documentation..
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_mode_help:
|
||||
Enabling <b>Serve</b> will automatically reverse proxy the primary web service from this container and make it available on your Tailnet using https with a valid certificate!
|
||||
|
||||
Note that when accessing the <b>Tailscale WebUI</b> url, no additional authentication layer is added beyond restricting it to your Tailnet - the container is still responsible for managing usernames/passwords that are allowed to access it. Depending on your configuration, direct access to the container may still be possible as well.
|
||||
|
||||
For more details, see the <a href="https://tailscale.com/kb/1312/serve" target="_blank">Tailscale Serve</a> documentation.
|
||||
|
||||
If the documentation recommends additional settings for a more complex use case, enable "Tailscale Show Advanced Settings". Support for these advanced settings is not available beyond confirming the commands are passed to Tailscale correctly.
|
||||
|
||||
<b>Funnel</b> is similar to <b>Serve</b>, except that the web service is made available on the open Internet. Use with care as the service will likely be attacked. As with <b>Serve</b>, the container itself is responsible for handling any authentication.
|
||||
|
||||
We recommend reading the <a href="https://tailscale.com/kb/1223/funnel" target="_blank">Tailscale Funnel</a> documentation. before enabling this feature.
|
||||
|
||||
<b>Note:</b> Enabling <b>Serve</b> or <b>Funnel</b> publishes the Tailscale hostname to a public ledger.
|
||||
For more details, see the Tailscale Documentation: <a href="https://tailscale.com/kb/1153/enabling-https" target="_blank">Enabling HTTPS</a>.
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_port_help:
|
||||
This field should specify the port for the primary web service this container offers. Note: it should specify the port in the container, not a port that was remapped on the host.
|
||||
|
||||
The system attempted to determine the correct port automatically. If it used the wrong value then there is likely an issue with the "Web UI" field for this container, visible by switching from "Basic View" to "Advanced View" in the upper right corner of this page.
|
||||
|
||||
In most cases this port is all you will need to specify in order to Serve the website in this container, although additional options are available below for more complex containers.
|
||||
|
||||
This value is passed to the `<serve_port>` portion of this command which starts serve or funnel:<br>
|
||||
`tailscale [serve|funnel] --bg --<protocol><protocol_port><path> http://localhost:`**`<serve_port>`**`<local_path>`<br>
|
||||
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_show_advanced_help:
|
||||
Here there be dragons!
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_local_path_help:
|
||||
When not specified, this value defaults to an empty string. It is passed to the `<local_path>` portion of this command which starts serve or funnel:<br>
|
||||
`tailscale [serve|funnel] --bg --<protocol><protocol_port><path> http://localhost:<serve_port>`**`<local_path>`**<br>
|
||||
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_protocol_help:
|
||||
When not specified, this value defaults to "https". It is passed to the `<protocol>` portion of this command which starts serve or funnel:<br>
|
||||
`tailscale [serve|funnel] --bg --`**`<protocol>`**`=<protocol_port><path> http://localhost:<serve_port><local_path>`<br>
|
||||
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_protocol_port_help:
|
||||
When not specified, this value defaults to "=443". It is passed to the `<protocol_port>` portion of this command which starts serve or funnel:<br>
|
||||
`tailscale [serve|funnel] --bg --<protocol>`**`<protocol_port>`**`<path> http://localhost:<serve_port><local_path>`<br>
|
||||
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_path_help:
|
||||
When not specified, this value defaults to an empty string. It is passed to the `<path>` portion of this command which starts serve or funnel:<br>
|
||||
`tailscale [serve|funnel] --bg --<protocol><protocol_port>`**`<path>`** `http://localhost:<serve_port><local_path>`<br>
|
||||
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_webui_help:
|
||||
If <b>Serve</b> is enabled this will be an https url with a proper domain name that is accessible over your Tailnet, no port needed!
|
||||
|
||||
If <b>Funnel</b> is enabled the same url will be available on the Internet.
|
||||
|
||||
If they are disabled then the url will be generated from the container's main "Web UI" field, but modified to use the Tailscale IP. If the wrong port is specified here then switch from "Basic View" to "Advanced View" and review the "Web UI" field for this container.
|
||||
:end
|
||||
|
||||
:docker_tailscale_advertise_routes_help:
|
||||
If desired, specify any routes that should be passed to the **`--advertise-routes=`** parameter when running **`tailscale up`**.
|
||||
For more details see the <a href="https://tailscale.com/kb/1019/subnets#connect-to-tailscale-as-a-subnet-router" target="_blank">Subnet routers</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_daemon_extra_params_help:
|
||||
Specify any extra parameters to pass when starting **`tailscaled`**.
|
||||
For more details see the <a href="https://tailscale.com/kb/1278/tailscaled" target="_blank">tailscaled</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_extra_param_help:
|
||||
Specify any extra parameters to pass when running **`tailscale up`**.
|
||||
For more details see the <a href="https://tailscale.com/kb/1080/cli#up" target="_blank">Tailscale CLI</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_statedir_help:
|
||||
If state directory detection fails on startup, you can specify a persistent directory in the container to override automatic detection.
|
||||
:end
|
||||
|
||||
:docker_tailscale_troubleshooting_packages_help:
|
||||
Enable this to install `ping`, `nslookup`, and `curl` into the container to help troubleshoot networking issues. Once the issues are resolved we recommend disabling this to reduce the size of the container.
|
||||
:end
|
||||
|
||||
:docker_privileged_help:
|
||||
For containers that require the use of host-device access directly or need full exposure to host capabilities, this option will need to be selected.
|
||||
For more information, see this link: <a href="https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities" target="_blank">https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities</a>
|
||||
|
BIN
emhttp/plugins/dynamix.docker.manager/images/tailscale.png
Executable file
BIN
emhttp/plugins/dynamix.docker.manager/images/tailscale.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 300 KiB |
@@ -141,11 +141,24 @@ if (isset($_POST['contName'])) {
|
||||
@unlink("$userTmplDir/my-$existing.xml");
|
||||
}
|
||||
}
|
||||
// Extract real Entrypoint and Cmd from container for Tailscale
|
||||
if (isset($_POST['contTailscale']) && $_POST['contTailscale'] == 'on') {
|
||||
// Create preliminary base container but don't run it
|
||||
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker create --name '" . escapeshellarg($Name) . "' '" . escapeshellarg($Repository) . "'");
|
||||
// Get Entrypoint and Cmd from docker inspect
|
||||
$containerInfo = $DockerClient->getContainerDetails($Name);
|
||||
$ts_env = isset($containerInfo['Config']['Entrypoint']) ? '-e ORG_ENTRYPOINT="' . implode(' ', $containerInfo['Config']['Entrypoint']) . '" ' : '';
|
||||
$ts_env .= isset($containerInfo['Config']['Cmd']) ? '-e ORG_CMD="' . implode(' ', $containerInfo['Config']['Cmd']) . '" ' : '';
|
||||
// Insert Entrypoint and Cmd to docker command
|
||||
$cmd = str_replace('-l net.unraid.docker.managed=dockerman', $ts_env . '-l net.unraid.docker.managed=dockerman' , $cmd);
|
||||
// Remove preliminary container
|
||||
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker rm '" . escapeshellarg($Name) . "'");
|
||||
}
|
||||
if ($startContainer) $cmd = str_replace('/docker create ', '/docker run -d ', $cmd);
|
||||
execCommand($cmd);
|
||||
if ($startContainer) addRoute($Name); // add route for remote WireGuard access
|
||||
|
||||
echo '<div style="text-align:center"><button type="button" onclick="done()">'._('Done').'</button></div><br>';
|
||||
echo '<div style="text-align:center"><button type="button" onclick="openTerminal(\'docker\',\''.addslashes($Name).'\',\'.log\')">'._('View Container Log').'</button> <button type="button" onclick="done()">'._('Done').'</button></div><br>';
|
||||
goto END;
|
||||
}
|
||||
|
||||
@@ -169,6 +182,9 @@ if (isset($_GET['updateContainer'])){
|
||||
$xml = file_get_contents($tmpl);
|
||||
[$cmd, $Name, $Repository] = xmlToCommand($tmpl);
|
||||
$Registry = getXmlVal($xml, "Registry");
|
||||
$ExtraParams = getXmlVal($xml, "ExtraParams");
|
||||
$Network = getXmlVal($xml, "Network");
|
||||
$TS_Enabled = getXmlVal($xml, "TailscaleEnabled");
|
||||
$oldImageID = $DockerClient->getImageID($Repository);
|
||||
// pull image
|
||||
if ($echo && !pullImage($Name, $Repository)) continue;
|
||||
@@ -182,8 +198,39 @@ if (isset($_GET['updateContainer'])){
|
||||
// attempt graceful stop of container first
|
||||
stopContainer($Name, false, $echo);
|
||||
}
|
||||
// check if network from another container is specified in xml (Network & ExtraParams)
|
||||
if (preg_match('/^container:(.*)/', $Network)) {
|
||||
$Net_Container = str_replace("container:", "", $Network);
|
||||
} else {
|
||||
preg_match("/--(net|network)=container:[^\s]+/", $ExtraParams, $NetworkParam);
|
||||
if (!empty($NetworkParam[0])) {
|
||||
$Net_Container = explode(':', $NetworkParam[0])[1];
|
||||
$Net_Container = str_replace(['"', "'"], '', $Net_Container);
|
||||
}
|
||||
}
|
||||
// check if the container still exists from which the network should be used, if it doesn't exist any more recreate container with network none and don't start it
|
||||
if (!empty($Net_Container)) {
|
||||
$Net_Container_ID = $DockerClient->getContainerID($Net_Container);
|
||||
if (empty($Net_Container_ID)) {
|
||||
$cmd = str_replace('/docker run -d ', '/docker create ', $cmd);
|
||||
$cmd = preg_replace("/--(net|network)=(['\"]?)container:[^'\"]+\\2/", "--network=none ", $cmd);
|
||||
}
|
||||
}
|
||||
// force kill container if still running after time-out
|
||||
if (empty($_GET['communityApplications'])) removeContainer($Name, $echo);
|
||||
// Extract real Entrypoint and Cmd from container for Tailscale
|
||||
if ($TS_Enabled == 'true') {
|
||||
// Create preliminary base container but don't run it
|
||||
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker create --name '" . escapeshellarg($Name) . "' '" . escapeshellarg($Repository) . "'");
|
||||
// Get Entrypoint and Cmd from docker inspect
|
||||
$containerInfo = $DockerClient->getContainerDetails($Name);
|
||||
$ts_env = isset($containerInfo['Config']['Entrypoint']) ? '-e ORG_ENTRYPOINT="' . implode(' ', $containerInfo['Config']['Entrypoint']) . '" ' : '';
|
||||
$ts_env .= isset($containerInfo['Config']['Cmd']) ? '-e ORG_CMD="' . implode(' ', $containerInfo['Config']['Cmd']) . '" ' : '';
|
||||
// Insert Entrypoint and Cmd to docker command
|
||||
$cmd = str_replace('-l net.unraid.docker.managed=dockerman', $ts_env . '-l net.unraid.docker.managed=dockerman' , $cmd);
|
||||
// Remove preliminary container
|
||||
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker rm '" . escapeshellarg($Name) . "'");
|
||||
}
|
||||
execCommand($cmd, $echo);
|
||||
if ($startContainer) addRoute($Name); // add route for remote WireGuard access
|
||||
$DockerClient->flushCaches();
|
||||
@@ -272,6 +319,153 @@ $authoring = $authoringMode ? 'advanced' : 'noshow';
|
||||
$disableEdit = $authoringMode ? 'false' : 'true';
|
||||
$showAdditionalInfo = '';
|
||||
$bgcolor = strstr('white,azure',$display['theme']) ? '#f2f2f2' : '#1c1c1c';
|
||||
|
||||
# Search for existing TAILSCALE_ entries in the Docker template
|
||||
$TS_existing_vars = false;
|
||||
foreach ($xml["Config"] as $config) {
|
||||
if (isset($config["Target"]) && strpos($config["Target"], "TAILSCALE_") === 0) {
|
||||
$TS_existing_vars = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
# Look for Exit Nodes if Tailscale plugin is installed
|
||||
$ts_exit_nodes = [];
|
||||
$ts_en_check = false;
|
||||
if (file_exists('/usr/local/sbin/tailscale') && exec('pgrep --ns $$ -f "/usr/local/sbin/tailscaled"')) {
|
||||
exec('tailscale exit-node list', $ts_exit_node_list, $retval);
|
||||
if ($retval === 0) {
|
||||
foreach ($ts_exit_node_list as $line) {
|
||||
if (!empty(trim($line))) {
|
||||
if (preg_match('/^(\d+\.\d+\.\d+\.\d+)\s+(.+)$/', trim($line), $matches)) {
|
||||
$parts = preg_split('/\s+/', $matches[2]);
|
||||
$ts_exit_nodes[] = [
|
||||
'ip' => $matches[1],
|
||||
'hostname' => $parts[0],
|
||||
'country' => $parts[1],
|
||||
'city' => $parts[2],
|
||||
'status' => $parts[3]
|
||||
];
|
||||
$ts_en_check = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Try to detect port from WebUI and set webui_url
|
||||
$TSwebuiport = '';
|
||||
$webui_url = '';
|
||||
if (empty($xml['TailscalePort'])) {
|
||||
if (!empty($xml['WebUI'])) {
|
||||
$webui_url = parse_url($xml['WebUI']);
|
||||
preg_match('/:(\d+)\]/', $webui_url['host'], $matches);
|
||||
$TSwebuiport = $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
$TS_raw = [];
|
||||
$TS_container_raw = [];
|
||||
$TS_HostNameWarning = "";
|
||||
$TS_HTTPSDisabledWarning = "";
|
||||
$TS_ExitNodeNeedsApproval = false;
|
||||
$TS_MachinesLink = "https://login.tailscale.com/admin/machines/";
|
||||
$TS_DirectMachineLink = $TS_MachinesLink;
|
||||
$TS_HostNameActual = "";
|
||||
$TS_not_approved = "";
|
||||
// Get Tailscale information and create arrays/variables
|
||||
exec("docker exec -i ".$xml['Name']." /bin/sh -c \"tailscale status --peers=false --json\"", $TS_raw);
|
||||
$TS_no_peers = json_decode(implode('', $TS_raw),true);
|
||||
$TS_container = json_decode(implode('', $TS_raw),true);
|
||||
$TS_container = $TS_container['Self'];
|
||||
if (!empty($TS_no_peers) && !empty($TS_container)) {
|
||||
// define the direct link to this machine on the Tailscale website
|
||||
if (!empty($TS_container['TailscaleIPs']) && !empty($TS_container['TailscaleIPs'][0])) {
|
||||
$TS_DirectMachineLink = $TS_MachinesLink.$TS_container['TailscaleIPs'][0];
|
||||
}
|
||||
// warn if MagicDNS or HTTPS is disabled
|
||||
if (empty($TS_no_peers['CurrentTailnet']['MagicDNSEnabled']) || !$TS_no_peers['CurrentTailnet']['MagicDNSEnabled'] || empty($TS_no_peers['CertDomains']) || empty($TS_no_peers['CertDomains'][0])) {
|
||||
$TS_HTTPSDisabledWarning = "<span><b><a href='https://tailscale.com/kb/1153/enabling-https' target='_blank'>Enable HTTPS</a> on your Tailscale account to use Tailscale Serve/Funnel.</b></span>";
|
||||
}
|
||||
// In $TS_container, 'HostName' is what the user requested, need to parse 'DNSName' to find the actual HostName in use
|
||||
$TS_DNSName = _var($TS_container,'DNSName','');
|
||||
$TS_HostNameActual = substr($TS_DNSName, 0, strpos($TS_DNSName, '.'));
|
||||
// compare the actual HostName in use to the one in the XML file
|
||||
if (strcasecmp($TS_HostNameActual, _var($xml, 'TailscaleHostname')) !== 0 && !empty($TS_DNSName)) {
|
||||
// they are different, show a warning
|
||||
$TS_HostNameWarning = "<span><b>Warning: the actual Tailscale hostname is '".$TS_HostNameActual."'</b></span>";
|
||||
}
|
||||
// If this is an Exit Node, show warning if it still needs approval
|
||||
if (_var($xml,'TailscaleIsExitNode') == 'true' && _var($TS_container, 'ExitNodeOption') === false) {
|
||||
$TS_ExitNodeNeedsApproval = true;
|
||||
}
|
||||
//Check for key expiry
|
||||
if(!empty($TS_container['KeyExpiry'])) {
|
||||
$TS_expiry = new DateTime($TS_container['KeyExpiry']);
|
||||
$current_Date = new DateTime();
|
||||
$TS_expiry_diff = $current_Date->diff($TS_expiry);
|
||||
}
|
||||
// Check for non approved routes
|
||||
if(!empty($xml['TailscaleRoutes'])) {
|
||||
$TS_advertise_routes = str_replace(' ', '', $xml['TailscaleRoutes']);
|
||||
if (empty($TS_container['PrimaryRoutes'])) {
|
||||
$TS_container['PrimaryRoutes'] = [];
|
||||
}
|
||||
$routes = explode(',', $TS_advertise_routes);
|
||||
foreach ($routes as $route) {
|
||||
if (!in_array($route, $TS_container['PrimaryRoutes'])) {
|
||||
$TS_not_approved .= " " . $route;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check for exit nodes if ts_en_check was not already done
|
||||
if (!$ts_en_check) {
|
||||
exec("docker exec -i ".$xml['Name']." /bin/sh -c \"tailscale exit-node list\"", $ts_exit_node_list, $retval);
|
||||
if ($retval === 0) {
|
||||
foreach ($ts_exit_node_list as $line) {
|
||||
if (!empty(trim($line))) {
|
||||
if (preg_match('/^(\d+\.\d+\.\d+\.\d+)\s+(.+)$/', trim($line), $matches)) {
|
||||
$parts = preg_split('/\s+/', $matches[2]);
|
||||
$ts_exit_nodes[] = [
|
||||
'ip' => $matches[1],
|
||||
'hostname' => $parts[0],
|
||||
'country' => $parts[1],
|
||||
'city' => $parts[2],
|
||||
'status' => $parts[3]
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Construct WebUI URL on container template page
|
||||
// Check if webui_url, Tailscale WebUI and MagicDNS are not empty and make sure that MagicDNS is enabled
|
||||
if (!empty($webui_url) && !empty($xml['TailscaleWebUI']) && (!empty($TS_no_peers['CurrentTailnet']['MagicDNSEnabled']) || $TS_no_peers['CurrentTailnet']['MagicDNSEnabled'])) {
|
||||
// Check if serve or funnel are enabled by checking for [hostname] and replace string with TS_DNSName
|
||||
if (!empty($xml['TailscaleWebUI']) && strpos($xml['TailscaleWebUI'], '[hostname]') !== false && isset($TS_DNSName)) {
|
||||
$TS_webui_url = str_replace("[hostname][magicdns]", rtrim($TS_DNSName, '.'), $xml['TailscaleWebUI']);
|
||||
// Check if serve is disabled, construct url with port, path and query if present and replace [noserve] with url
|
||||
} elseif (strpos($xml['TailscaleWebUI'], '[noserve]') !== false && isset($TS_container['TailscaleIPs'])) {
|
||||
$ipv4 = '';
|
||||
foreach ($TS_container['TailscaleIPs'] as $ip) {
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
$ipv4 = $ip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!empty($ipv4)) {
|
||||
$webui_url = isset($xml['WebUI']) ? parse_url($xml['WebUI']) : '';
|
||||
$webui_port = (preg_match('/\[PORT:(\d+)\]/', $xml['WebUI'], $matches)) ? ':' . $matches[1] : '';
|
||||
$webui_path = $webui_url['path'] ?? '';
|
||||
$webui_query = isset($webui_url['query']) ? '?' . $webui_url['query'] : '';
|
||||
$TS_webui_url = 'http://' . $ipv4 . $webui_port . $webui_path . $webui_query;
|
||||
}
|
||||
// Check if TailscaleWebUI in the xml is custom and display instead
|
||||
} elseif (strpos($xml['TailscaleWebUI'], '[hostname]') === false && strpos($xml['TailscaleWebUI'], '[noserve]') === false) {
|
||||
$TS_webui_url = $xml['TailscaleWebUI'];
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/jquery.ui.css")?>">
|
||||
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/jquery.switchbutton.css")?>">
|
||||
@@ -675,6 +869,16 @@ $(function() {
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
foreach ($xml["Config"] as $config) {
|
||||
if (isset($config["Target"]) && strpos($config["Target"], "TAILSCALE_") === 0) {
|
||||
$tailscaleTargetFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<div id="canvas">
|
||||
<form markdown="1" method="POST" autocomplete="off" onsubmit="prepareConfig(this)">
|
||||
<input type="hidden" name="csrf_token" value="<?=$var['csrf_token']?>">
|
||||
@@ -715,7 +919,7 @@ _(Template)_:
|
||||
|
||||
<div markdown="1" class="<?=$showAdditionalInfo?>">
|
||||
_(Name)_:
|
||||
: <input type="text" name="contName" pattern="[a-zA-Z0-9][a-zA-Z0-9_.-]+" required>
|
||||
: <input type="text" name="contName" pattern="[a-zA-Z0-9][a-zA-Z0-9_.\-]+" required>
|
||||
|
||||
:docker_client_name_help:
|
||||
|
||||
@@ -903,9 +1107,263 @@ _(Container Network)_:
|
||||
}
|
||||
}
|
||||
?>
|
||||
:docker_container_network_help:
|
||||
</select>
|
||||
|
||||
:docker_container_network_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSdivider noshow"><hr></div>
|
||||
|
||||
<?if ($TS_existing_vars == 'true'):?>
|
||||
<div markdown="1" class="TSwarning noshow">
|
||||
<b style="color:red;">_(WARNING)_</b>:
|
||||
: <b>_(Existing TAILSCALE variables found, please remove any existing modifications in the Template for Tailscale before using this function!)_</b>
|
||||
</div>
|
||||
<?endif;?>
|
||||
|
||||
<?if (empty($xml['TailscaleEnabled'])):?>
|
||||
<div markdown="1" class="TSdeploy noshow">
|
||||
<b>_(First deployment)_</b>:
|
||||
: <p>_(After deploying the container, open the log and follow the link to register the container to your Tailnet!)_</p>
|
||||
</div>
|
||||
|
||||
<?if (!file_exists('/usr/local/sbin/tailscale')):?>
|
||||
<div markdown="1" class="TSdeploy noshow">
|
||||
<b>_(Recommendation)_</b>:
|
||||
: <p>_(For the best experience with Tailscale, install "Tailscale (Plugin)" from)_ <a href="/Apps" target='_blank'> Community Applications</a>.</p>
|
||||
</div>
|
||||
<?endif;?>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<div markdown="1">
|
||||
_(Use Tailscale)_:
|
||||
: <input type="checkbox" class="switch-on-off" name="contTailscale" id="contTailscale" <?php if (!empty($xml['TailscaleEnabled']) && $xml['TailscaleEnabled'] == 'true') echo 'checked'; ?> onchange="showTailscale(this)">
|
||||
|
||||
:docker_tailscale_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSdivider noshow">
|
||||
<b>_(NOTE)_</b>:
|
||||
: <i>_(This option will install Tailscale and dependencies into the container.)_</i>
|
||||
</div>
|
||||
|
||||
<?if($TS_ExitNodeNeedsApproval):?>
|
||||
<div markdown="1" class="TShostname noshow">
|
||||
<b>Warning:</b>
|
||||
: Exit Node not yet approved. Navigate to the <a href="<?=$TS_DirectMachineLink?>" target='_blank'>Tailscale website</a> and approve it.
|
||||
</div>
|
||||
<?endif;?>
|
||||
|
||||
<?if(!empty($TS_expiry_diff)):?>
|
||||
<div markdown="1" class="TSdivider noshow">
|
||||
<b>_(Warning)_</b>:
|
||||
<?if($TS_expiry_diff->invert):?>
|
||||
: <b>Tailscale Key expired!</b> <a href="<?=$TS_MachinesLink?>" target='_blank'>Renew/Disable key expiry</a> for '<b><?=$TS_HostNameActual?></b>'.
|
||||
<?else:?>
|
||||
: Tailscale Key will expire in <b><?=$TS_expiry_diff->days?> days</b>! <a href="<?=$TS_MachinesLink?>" target='_blank'>Disable Key Expiry</a> for '<b><?=$TS_HostNameActual?></b>'.
|
||||
<?endif;?>
|
||||
<label>See <a href="https://tailscale.com/kb/1028/key-expiry" target='_blank'>key-expiry</a>.</label>
|
||||
</div>
|
||||
<?endif;?>
|
||||
|
||||
<?if(!empty($TS_not_approved)):?>
|
||||
<div markdown="1" class="TSdivider noshow">
|
||||
<b>_(Warning)_</b>:
|
||||
: The following route(s) are not approved: <b><?=trim($TS_not_approved)?></b>
|
||||
</div>
|
||||
<?endif;?>
|
||||
|
||||
<div markdown="1" class="TShostname noshow">
|
||||
_(Tailscale Hostname)_:
|
||||
: <input type="text" pattern="[A-Za-z0-9_\-]*" name="TShostname" <?php if (!empty($xml['TailscaleHostname'])) echo 'value="' . $xml['TailscaleHostname'] . '"'; ?> placeholder="_(Hostname for the container)_"> <?=$TS_HostNameWarning?>
|
||||
|
||||
:docker_tailscale_hostname_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSisexitnode noshow">
|
||||
_(Be a Tailscale Exit Node)_:
|
||||
: <select name="TSisexitnode" id="TSisexitnode" onchange="showTailscale(this)">
|
||||
<?=mk_option(1,'false',_('No'))?>
|
||||
<?=mk_option(1,'true',_('Yes'))?>
|
||||
</select>
|
||||
<span id='TSisexitnode_msg' style='font-style: italic;'></span>
|
||||
|
||||
:docker_tailscale_be_exitnode_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSexitnodeip noshow">
|
||||
_(Use a Tailscale Exit Node)_:
|
||||
<?if($ts_en_check !== true && empty($ts_exit_nodes)):?>
|
||||
: <input type="text" name="TSexitnodeip" <?php if (!empty($xml['TailscaleExitNodeIP'])) echo 'value="' . $xml['TailscaleExitNodeIP'] . '"'; ?> placeholder="_(IP/Hostname from Exit Node)_" onchange="processExitNodeoptions(this)">
|
||||
<?else:?>
|
||||
: <select name="TSexitnodeip" id="TSexitnodeip" onchange="processExitNodeoptions(this)">
|
||||
<?=mk_option(1,'',_('None'))?>
|
||||
<?foreach ($ts_exit_nodes as $ts_exit_node):?>
|
||||
<?=$node_offline = $ts_exit_node['status'] === 'offline' ? ' - OFFLINE' : '';?>
|
||||
<?=mk_option(1,$ts_exit_node['ip'],$ts_exit_node['ip'] . ' - ' . $ts_exit_node['hostname'] . $node_offline)?>
|
||||
<?endforeach;?></select>
|
||||
<?endif;?>
|
||||
</select>
|
||||
<span id='TSexitnodeip_msg' style='font-style: italic;'></span>
|
||||
|
||||
:docker_tailscale_exitnode_ip_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSallowlanaccess noshow">
|
||||
_(Tailscale Allow LAN Access)_:
|
||||
: <select name="TSallowlanaccess" id="TSallowlanaccess">
|
||||
<?=mk_option(1,'false',_('No'))?>
|
||||
<?=mk_option(1,'true',_('Yes'))?>
|
||||
</select>
|
||||
<?=$TS_HTTPSDisabledWarning?>
|
||||
|
||||
:docker_tailscale_lanaccess_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSuserspacenetworking noshow">
|
||||
_(Tailscale Userspace Networking)_:
|
||||
: <select name="TSuserspacenetworking" id="TSuserspacenetworking" onchange="setExitNodeoptions()">
|
||||
<?=mk_option(1,'true',_('Enabled'))?>
|
||||
<?=mk_option(1,'false',_('Disabled'))?>
|
||||
</select>
|
||||
<span id='TSuserspacenetworking_msg' style='font-style: italic;'></span>
|
||||
|
||||
:docker_tailscale_userspace_networking_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSssh noshow">
|
||||
_(Enable Tailscale SSH)_:
|
||||
: <select name="TSssh" id="TSssh">
|
||||
<?=mk_option(1,'false',_('No'))?>
|
||||
<?=mk_option(1,'true',_('Yes'))?>
|
||||
</select>
|
||||
|
||||
:docker_tailscale_ssh_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSserve noshow">
|
||||
_(Tailscale Serve)_:
|
||||
: <select name="TSserve" id="TSserve" onchange="showServe(this.value)">
|
||||
<?=mk_option(1,'no',_('No'))?>
|
||||
<?=mk_option(1,'serve',_('Serve'))?>
|
||||
<?=mk_option(1,'funnel',_('Funnel'))?>
|
||||
</select>
|
||||
<?php if (!empty($TS_webui_url)) echo '<label for="TSserve"><a href="' . $TS_webui_url . '" target="_blank">' . $TS_webui_url . '</a></label>'; ?>
|
||||
|
||||
:docker_tailscale_serve_mode_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSserveport noshow">
|
||||
_(Tailscale Serve Port)_:
|
||||
: <input type="text" name="TSserveport" value="<?php echo !empty($xml['TailscaleServePort']) ? $xml['TailscaleServePort'] : (!empty($TSwebuiport) ? $TSwebuiport : ''); ?>" placeholder="_(Will be detected automatically if possible)_">
|
||||
|
||||
:docker_tailscale_serve_port_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSadvanced noshow">
|
||||
_(Tailscale Show Advanced Settings)_:
|
||||
: <input type="checkbox" name="TSadvanced" class="switch-on-off" onchange="showTSAdvanced(this.checked)">
|
||||
|
||||
:docker_tailscale_show_advanced_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSservelocalpath noshow">
|
||||
_(Tailscale Serve Local Path)_:
|
||||
: <input type="text" name="TSservelocalpath" <?php if (!empty($xml['TailscaleServeLocalPath'])) echo 'value="' . $xml['TailscaleServeLocalPath'] . '"'; ?> placeholder="_(Leave empty if unsure)_">
|
||||
|
||||
:docker_tailscale_serve_local_path_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSserveprotocol noshow">
|
||||
_(Tailscale Serve Protocol)_:
|
||||
: <input type="text" name="TSserveprotocol" <?php if (!empty($xml['TailscaleServeProtocol'])) echo 'value="' . $xml['TailscaleServeProtocol'] . '"'; ?> placeholder="_(Leave empty if unsure, defaults to https)_">
|
||||
|
||||
:docker_tailscale_serve_protocol_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSserveprotocolport noshow">
|
||||
_(Tailscale Serve Protocol Port)_:
|
||||
: <input type="text" name="TSserveprotocolport" <?php if (!empty($xml['TailscaleServeProtocolPort'])) echo 'value="' . $xml['TailscaleServeProtocolPort'] . '"'; ?> placeholder="_(Leave empty if unsure, defaults to =443)_">
|
||||
|
||||
:docker_tailscale_serve_protocol_port_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSservepath noshow">
|
||||
_(Tailscale Serve Path)_:
|
||||
: <input type="text" name="TSservepath" <?php if (!empty($xml['TailscaleServePath'])) echo 'value="' . $xml['TailscaleServePath'] . '"'; ?> placeholder="_(Leave empty if unsure)_">
|
||||
|
||||
:docker_tailscale_serve_path_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSwebui noshow">
|
||||
_(Tailscale WebUI)_:
|
||||
: <input type="text" name="TSwebui" value="<?php echo !empty($TS_webui_url) ? $TS_webui_url : ''; ?>" placeholder="Will be determined automatically if possible" disabled>
|
||||
<input type="hidden" name="TSwebui" <?php if (!empty($xml['TailscaleWebUI'])) echo 'value="' . $xml['TailscaleWebUI'] . '"'; ?>>
|
||||
|
||||
:docker_tailscale_serve_webui_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSroutes noshow">
|
||||
_(Tailscale Advertise Routes)_:
|
||||
: <input type="text" pattern="[0-9:., ]*" name="TSroutes" <?php if (!empty($xml['TailscaleRoutes'])) echo 'value="' . $xml['TailscaleRoutes'] . '"'?> placeholder="_(Leave empty if unsure)_">
|
||||
|
||||
:docker_tailscale_advertise_routes_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSdaemonparams noshow">
|
||||
_(Tailscale Daemon Parameters)_:
|
||||
: <input type="text" name="TSdaemonparams" <?php if (!empty($xml['TailscaleDParams'])) echo 'value="' . $xml['TailscaleDParams'] . '"'; ?> placeholder="_(Leave empty if unsure)_">
|
||||
|
||||
:docker_tailscale_daemon_extra_params_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSextraparams noshow">
|
||||
_(Tailscale Extra Parameters)_:
|
||||
: <input type="text" name="TSextraparams" <?php if (!empty($xml['TailscaleParams'])) echo 'value="' . $xml['TailscaleParams'] . '"'; ?> placeholder="_(Leave empty if unsure)_">
|
||||
|
||||
:docker_tailscale_extra_param_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSstatedir noshow">
|
||||
_(Tailscale State Directory)_:
|
||||
: <input type="text" name="TSstatedir" <?php if (!empty($xml['TailscaleStateDir'])) echo 'value="' . $xml['TailscaleStateDir'] . '"'; ?> placeholder="_(Leave empty if unsure)_">
|
||||
|
||||
:docker_tailscale_statedir_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TStroubleshooting noshow">
|
||||
_(Tailscale Install Troubleshooting Packages)_:
|
||||
: <input type="checkbox" class="switch-on-off" name="TStroubleshooting" <?php if (!empty($xml['TailscaleTroubleshooting']) && $xml['TailscaleTroubleshooting'] == 'true') echo 'checked'; ?>>
|
||||
|
||||
:docker_tailscale_troubleshooting_packages_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSdivider noshow">
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
_(Console shell command)_:
|
||||
: <select name="contShell">
|
||||
<?=mk_option(1,'sh',_('Shell'))?>
|
||||
@@ -1053,6 +1511,210 @@ function showSubnet(bridge) {
|
||||
}
|
||||
}
|
||||
|
||||
function processExitNodeoptions(value) {
|
||||
val = null;
|
||||
if (value.tagName.toLowerCase() === "input") {
|
||||
val = value.value.trim();
|
||||
} else if (value.tagName.toLowerCase() === "select") {
|
||||
val = value.value;
|
||||
}
|
||||
if (val) {
|
||||
$('.TSallowlanaccess').show();
|
||||
} else {
|
||||
$('#TSallowlanaccess').val('false');
|
||||
$('.TSallowlanaccess').hide();
|
||||
}
|
||||
setUserspaceNetworkOptions();
|
||||
setIsExitNodeoptions();
|
||||
}
|
||||
|
||||
function setUserspaceNetworkOptions() {
|
||||
optTrueDisabled = false;
|
||||
optFalseDisabled = false;
|
||||
optMessage = "";
|
||||
value = null;
|
||||
|
||||
var network = $('select[name="contNetwork"]')[0].value;
|
||||
var isExitnode = $('#TSisexitnode').val();
|
||||
if (network == 'host' || isExitnode == 'true') {
|
||||
// in host mode or if this container is an Exit Node
|
||||
// then Userspace Networking MUST be enabled ('true')
|
||||
value = 'true';
|
||||
optTrueDisabled = false;
|
||||
optFalseDisabled = true;
|
||||
optMessage = (isExitnode == 'true') ? "Enabled because this is an Exit Node" : "Enabled due to Docker "+network+" mode";
|
||||
} else {
|
||||
if (document.querySelector('input[name="TSexitnodeip"], select[name="TSexitnodeip"]').value) {
|
||||
// If an Exit Node IP is set, Userspace Networking MUST be disabled ('false')
|
||||
value = 'false';
|
||||
optTrueDisabled = true;
|
||||
optFalseDisabled = false;
|
||||
optMessage = "Disabled due to use of an Exit Node";
|
||||
} else {
|
||||
// Exit Node IP is not set, user can decide whether to enable/disable Userspace Networking
|
||||
optTrueDisabled = false;
|
||||
optFalseDisabled = false;
|
||||
optMessage = "";
|
||||
}
|
||||
}
|
||||
|
||||
$("#TSuserspacenetworking option[value='true']").prop("disabled", optTrueDisabled);
|
||||
$("#TSuserspacenetworking option[value='false']").prop("disabled", optFalseDisabled);
|
||||
if (value != null) $('#TSuserspacenetworking').val(value);
|
||||
$('#TSuserspacenetworking_msg').text(optMessage);
|
||||
setExitNodeoptions();
|
||||
}
|
||||
|
||||
function setIsExitNodeoptions() {
|
||||
optTrueDisabled = false;
|
||||
optFalseDisabled = false;
|
||||
optMessage = "";
|
||||
value = null;
|
||||
|
||||
var network = $('select[name="contNetwork"]')[0].value;
|
||||
if (network == 'host') {
|
||||
// in host mode then this cannot be an Exit Node
|
||||
value = 'false';
|
||||
optTrueDisabled = true;
|
||||
optFalseDisabled = false;
|
||||
optMessage = "Disabled due to Docker "+network+" mode";
|
||||
} else {
|
||||
if (document.querySelector('input[name="TSexitnodeip"], select[name="TSexitnodeip"]').value) {
|
||||
// If an Exit Node IP is set, this cannot be an Exit Node
|
||||
value = 'false';
|
||||
optTrueDisabled = true;
|
||||
optFalseDisabled = false;
|
||||
optMessage = "Disabled due to use of an Exit Node";
|
||||
} else {
|
||||
optTrueDisabled = false;
|
||||
optFalseDisabled = false;
|
||||
}
|
||||
}
|
||||
$("#TSisexitnode option[value='true']").prop("disabled", optTrueDisabled);
|
||||
$("#TSisexitnode option[value='false']").prop("disabled", optFalseDisabled);
|
||||
if (value != null) $('#TSisexitnode').val(value);
|
||||
$('#TSisexitnode_msg').text(optMessage);
|
||||
}
|
||||
|
||||
function setExitNodeoptions() {
|
||||
optMessage = "";
|
||||
var $exitNodeInput = $('input[name="TSexitnodeip"]');
|
||||
var $exitNodeSelect = $('#TSexitnodeip');
|
||||
// In host mode, TSuserspacenetworking is true
|
||||
if ($('#TSuserspacenetworking').val() == 'true') {
|
||||
// if TSuserspacenetworking is true, then TSexitnodeip must be "" and all options are disabled
|
||||
optMessage = "Disabled because Userspace Networking is Enabled.";
|
||||
$exitNodeInput.val('').prop('disabled', true); // Disable the input field
|
||||
$exitNodeSelect.val('').prop('disabled', true).find('option').each(function() {
|
||||
if ($(this).val() === "") {
|
||||
$(this).prop('disabled', false); // Enable the option with value=""
|
||||
} else {
|
||||
$(this).prop('disabled', true); // Disable all other options
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// if TSuserspacenetworking is false, then all TSexitnodeip options can be enabled
|
||||
$exitNodeInput.prop('disabled', false); // Enable the input field
|
||||
$exitNodeSelect.prop('disabled', false).find('option').each(function() {
|
||||
$(this).prop('disabled', false); // Enable all options
|
||||
});
|
||||
}
|
||||
$('#TSexitnodeip_msg').text(optMessage);
|
||||
}
|
||||
|
||||
function showTSAdvanced(checked) {
|
||||
if (!checked) {
|
||||
<?if (!empty($TSwebuiport)):?>
|
||||
$('.TSserveport').hide();
|
||||
<?elseif (empty($contTailscale) || $contTailscale == 'false'):?>
|
||||
$('.TSserveport').hide();
|
||||
<?else:?>
|
||||
$('.TSserveport').show();
|
||||
<?endif;?>
|
||||
$('.TSdaemonparams').hide();
|
||||
$('.TSextraparams').hide();
|
||||
$('.TSstatedir').hide();
|
||||
$('.TSservepath').hide();
|
||||
$('.TSserveprotocol').hide();
|
||||
$('.TSserveprotocolport').hide();
|
||||
$('.TSservelocalpath').hide();
|
||||
$('.TSwebui').hide();
|
||||
$('.TStroubleshooting').hide();
|
||||
$('.TSroutes').hide();
|
||||
} else {
|
||||
$('.TSdaemonparams').show();
|
||||
$('.TSextraparams').show();
|
||||
$('.TSstatedir').show();
|
||||
$('.TSserveport').show();
|
||||
$('.TSservepath').show();
|
||||
$('.TSserveprotocol').show();
|
||||
$('.TSserveprotocolport').show();
|
||||
$('.TSservelocalpath').show();
|
||||
$('.TSwebui').show();
|
||||
$('.TStroubleshooting').show();
|
||||
$('.TSroutes').show();
|
||||
}
|
||||
}
|
||||
|
||||
function showTailscale(source) {
|
||||
if (!$.trim($('#TSallowlanaccess').val())) {
|
||||
$('#TSallowlanaccess').val('false');
|
||||
}
|
||||
if (!$.trim($('#TSserve').val())) {
|
||||
$('#TSserve').val('no');
|
||||
}
|
||||
checked = $('#contTailscale').prop('checked');
|
||||
if (!checked) {
|
||||
$('.TSdivider').hide();
|
||||
$('.TSwarning').hide();
|
||||
$('.TSdeploy').hide();
|
||||
$('.TSisexitnode').hide();
|
||||
$('.TShostname').hide();
|
||||
$('.TSexitnodeip').hide();
|
||||
$('.TSssh').hide();
|
||||
$('.TSallowlanaccess').hide();
|
||||
$('.TSdaemonparams').hide();
|
||||
$('.TSextraparams').hide();
|
||||
$('.TSstatedir').hide();
|
||||
$('.TSserve').hide();
|
||||
$('.TSuserspacenetworking').hide();
|
||||
$('.TSservepath').hide();
|
||||
$('.TSserveprotocol').hide();
|
||||
$('.TSserveprotocolport').hide();
|
||||
$('.TSservelocalpath').hide();
|
||||
$('.TSwebui').hide();
|
||||
$('.TSserveport').hide();
|
||||
$('.TSadvanced').hide();
|
||||
$('.TSroutes').hide();
|
||||
} else {
|
||||
// reset these vals back to what they were in the XML
|
||||
$('#TSssh').val('<?php echo !empty($xml['TailscaleSSH']) ? $xml['TailscaleSSH'] : 'false' ?>');
|
||||
$('#TSallowlanaccess').val('<?php echo $xml['TailscaleLANAccess']; ?>');
|
||||
$('#TSserve').val('<?php echo $xml['TailscaleServe']; ?>');
|
||||
$('#TSexitnodeip').val('<?php echo $xml['TailscaleExitNodeIP']; ?>');
|
||||
$('#TSuserspacenetworking').val('<?php echo !empty($xml['TailscaleUserspaceNetworking']) ? $xml['TailscaleUserspaceNetworking'] : 'false' ?>');
|
||||
<?if (empty($xml['TailscaleServe']) && !empty($TSwebuiport) && empty($xml['TailscaleServePort'])):?>
|
||||
$('#TSserve').val('serve');
|
||||
<?elseif (empty($xml['TailscaleServe']) && empty($TSwebuiport) && empty($xml['TailscaleServePort'])):?>
|
||||
$('#TSserve').val('no');
|
||||
<?endif;?>
|
||||
// don't reset this field if caller was the onchange event for this field
|
||||
if (source.id != 'TSisexitnode') $('#TSisexitnode').val('<?php echo !empty($xml['TailscaleIsExitNode']) ? $xml['TailscaleIsExitNode'] : 'false'; ?>');
|
||||
$('.TSisexitnode').show();
|
||||
$('.TShostname').show();
|
||||
$('.TSssh').show();
|
||||
$('.TSexitnodeip').show();
|
||||
$('.TSallowlanaccess').hide();
|
||||
$('.TSserve').show();
|
||||
$('.TSuserspacenetworking').show();
|
||||
processExitNodeoptions(document.querySelector('input[name="TSexitnodeip"], select[name="TSexitnodeip"]'));
|
||||
$('.TSdivider').show();
|
||||
$('.TSwarning').show();
|
||||
$('.TSdeploy').show();
|
||||
$('.TSadvanced').show();
|
||||
}
|
||||
}
|
||||
|
||||
function reloadTriggers() {
|
||||
$(".basic").toggle(!$(".advancedview").is(":checked"));
|
||||
$(".advanced").toggle($(".advancedview").is(":checked"));
|
||||
|
@@ -292,6 +292,16 @@ class DockerTemplates {
|
||||
return $WebUI;
|
||||
}
|
||||
|
||||
private function getTailscaleJson($name) {
|
||||
$TS_raw = [];
|
||||
exec("docker exec -i ".$name." /bin/sh -c \"tailscale status --peers=false --json\" 2>/dev/null", $TS_raw);
|
||||
if (!empty($TS_raw)) {
|
||||
$TS_raw = implode("\n", $TS_raw);
|
||||
return json_decode($TS_raw, true);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getAllInfo($reload=false,$com=true,$communityApplications=false) {
|
||||
global $driver, $dockerManPaths, $host;
|
||||
$DockerClient = new DockerClient();
|
||||
@@ -299,6 +309,7 @@ class DockerTemplates {
|
||||
//$DockerUpdate->verbose = $this->verbose;
|
||||
$info = DockerUtil::loadJSON($dockerManPaths['webui-info']);
|
||||
$autoStart = array_map('var_split', @file($dockerManPaths['autostart-file'],FILE_IGNORE_NEW_LINES) ?: []);
|
||||
//$TS_dns = $this->getTailscaleDNS();
|
||||
foreach ($DockerClient->getDockerContainers() as $ct) {
|
||||
$name = $ct['Name'];
|
||||
$image = $ct['Image'];
|
||||
@@ -334,6 +345,39 @@ class DockerTemplates {
|
||||
if (strpos($ct['NetworkMode'], 'container:') === 0)
|
||||
$tmp['url'] = '';
|
||||
}
|
||||
// Check if webui & ct TSurl is set, if set construct WebUI URL on Docker page
|
||||
$tmp['TSurl'] = '';
|
||||
if (!empty($webui) && !empty($ct['TSUrl'])) {
|
||||
$TS_no_peers = $this->getTailscaleJson($name);
|
||||
if (!empty($TS_no_peers) && (!empty($TS_no_peers['CurrentTailnet']['MagicDNSEnabled']) || $TS_no_peers['CurrentTailnet']['MagicDNSEnabled'])) {
|
||||
$TS_container = $TS_no_peers['Self'];
|
||||
$TS_DNSName = _var($TS_container,'DNSName','');
|
||||
$TS_HostNameActual = substr($TS_DNSName, 0, strpos($TS_DNSName, '.'));
|
||||
// Check if serve or funnel are enabled by checking for [hostname] and replace string with TS_DNSName
|
||||
if (strpos($ct['TSUrl'], '[hostname]') !== false && isset($TS_DNSName)) {
|
||||
$tmp['TSurl'] = str_replace("[hostname][magicdns]", rtrim($TS_DNSName, '.'), $ct['TSUrl']);
|
||||
// Check if serve is disabled, construct url with port, path and query if present and replace [noserve] with url
|
||||
} elseif (strpos($ct['TSUrl'], '[noserve]') !== false && isset($TS_container['TailscaleIPs'])) {
|
||||
$ipv4 = '';
|
||||
foreach ($TS_container['TailscaleIPs'] as $ip) {
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
$ipv4 = $ip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!empty($ipv4)) {
|
||||
$webui_url = isset($webui) ? parse_url($webui) : '';
|
||||
$webui_port = (preg_match('/\[PORT:(\d+)\]/', $webui, $matches)) ? ':' . $matches[1] : '';
|
||||
$webui_path = $webui_url['path'] ?? '';
|
||||
$webui_query = isset($webui_url['query']) ? '?' . $webui_url['query'] : '';
|
||||
$tmp['TSurl'] = 'http://' . $ipv4 . $webui_port . $webui_path . $webui_query;
|
||||
}
|
||||
// Check if TailscaleWebUI in the xml is custom and display instead
|
||||
} elseif (strpos($ct['TSUrl'], '[hostname]') === false && strpos($ct['TSUrl'], '[noserve]') === false) {
|
||||
$tmp['TSurl'] = $ct['TSUrl'];
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( ($tmp['shell'] ?? false) == false )
|
||||
$tmp['shell'] = $this->getTemplateValue($image, 'Shell');
|
||||
}
|
||||
@@ -824,13 +868,6 @@ class DockerClient {
|
||||
global $docroot, $dockerManPaths;
|
||||
$id = $id ?: $name;
|
||||
$info = DockerUtil::loadJSON($dockerManPaths['webui-info']);
|
||||
|
||||
// Check to see if the container is linked to other containers
|
||||
$networks = array_map(function ($n) { return $n['NetworkMode']; }, $this->getDockerContainers());
|
||||
if (in_array("container:{$name}", $networks)) {
|
||||
return "Container currently assigned as network for another container.";
|
||||
}
|
||||
|
||||
// Attempt to remove container
|
||||
$this->getDockerJSON("/containers/$id?force=1", 'DELETE', $code);
|
||||
if (isset($info[$name])) {
|
||||
@@ -944,8 +981,10 @@ class DockerClient {
|
||||
$c['BaseImage'] = $ct['Labels']['BASEIMAGE'] ?? false;
|
||||
$c['Icon'] = $info['Config']['Labels']['net.unraid.docker.icon'] ?? false;
|
||||
$c['Url'] = $info['Config']['Labels']['net.unraid.docker.webui'] ?? false;
|
||||
$c['Shell'] = $info['Config']['Labels']['net.unraid.docker.shell'] ?? false;
|
||||
$c['Manager'] = $info['Config']['Labels']['net.unraid.docker.managed'] ?? false;
|
||||
$c['TSUrl'] = $info['Config']['Labels']['net.unraid.docker.tailscale.webui'] ?? false;
|
||||
$c['TSHostname'] = $info['Config']['Labels']['net.unraid.docker.tailscale.hostname'] ?? false;
|
||||
$c['Shell'] = $info['Config']['Labels']['net.unraid.docker.shell'] ?? false;
|
||||
$c['Manager'] = $info['Config']['Labels']['net.unraid.docker.managed'] ?? false;
|
||||
$c['Ports'] = [];
|
||||
$c['Networks'] = [];
|
||||
if ($id) $c['NetworkMode'] = $net.str_replace('/',':',DockerUtil::ctMap($id)?:'/???');
|
||||
|
@@ -48,6 +48,55 @@ $null = '0.0.0.0';
|
||||
$autostart = (array)@file($autostart_file,FILE_IGNORE_NEW_LINES);
|
||||
$names = array_map('var_split',$autostart);
|
||||
|
||||
// Grab Tailscale json from container
|
||||
function tailscale_stats($name) {
|
||||
exec("docker exec -i ".$name." /bin/sh -c \"tailscale status --json | jq '{Self: .Self, ExitNodeStatus: .ExitNodeStatus, Version: .Version}'\" 2>/dev/null", $TS_stats);
|
||||
if (!empty($TS_stats)) {
|
||||
$TS_stats = implode("\n", $TS_stats);
|
||||
return json_decode($TS_stats, true);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Download Tailscal JSON and return Array, refresh file if older than 24 hours
|
||||
function tailscale_json_dl($file, $url) {
|
||||
$dl_status = 0;
|
||||
if (!is_dir('/tmp/tailscale')) {
|
||||
mkdir('/tmp/tailscale', 0777, true);
|
||||
}
|
||||
if (!file_exists($file)) {
|
||||
exec("wget -T 3 -q -O " . $file . " " . $url, $output, $dl_status);
|
||||
} else {
|
||||
$fileage = time() - filemtime($file);
|
||||
if ($fileage > 86400) {
|
||||
unlink($file);
|
||||
exec("wget -T 3 -q -O " . $file . " " . $url, $output, $dl_status);
|
||||
}
|
||||
}
|
||||
if ($dl_status === 0) {
|
||||
return json_decode(@file_get_contents($file), true);
|
||||
} elseif ($dl_status === 0 && is_file($file)) {
|
||||
return json_decode(@file_get_contents($file), true);
|
||||
} else {
|
||||
unlink($file);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// Grab Tailscale DERP map JSON
|
||||
$TS_derp_url = 'https://login.tailscale.com/derpmap/default';
|
||||
$TS_derp_file = '/tmp/tailscale/tailscale-derpmap.json';
|
||||
$TS_derp_list = tailscale_json_dl($TS_derp_file, $TS_derp_url);
|
||||
|
||||
// Grab Tailscale version JSON
|
||||
$TS_version_url = 'https://pkgs.tailscale.com/stable/?mode=json';
|
||||
$TS_version_file = '/tmp/tailscale/tailscale-latest-version.json';
|
||||
// Extract tarbal version string
|
||||
$TS_latest_version = tailscale_json_dl($TS_version_file, $TS_version_url);
|
||||
if (!empty($TS_latest_version)) {
|
||||
$TS_latest_version = $TS_latest_version["TarballsVersion"];
|
||||
}
|
||||
|
||||
function my_lang_time($text) {
|
||||
[$number, $text] = my_explode(' ',$text,2);
|
||||
return sprintf(_("%s $text"),$number);
|
||||
@@ -74,12 +123,14 @@ foreach ($containers as $ct) {
|
||||
$template = $info['template']??'';
|
||||
$shell = $info['shell']??'';
|
||||
$webGui = html_entity_decode($info['url']??'');
|
||||
$TShostname = isset($ct['TSHostname']) ? $ct['TSHostname'] : '';
|
||||
$TSwebGui = html_entity_decode($info['TSurl']??'');
|
||||
$support = html_entity_decode($info['Support']??'');
|
||||
$project = html_entity_decode($info['Project']??'');
|
||||
$registry = html_entity_decode($info['registry']??'');
|
||||
$donateLink = html_entity_decode($info['DonateLink']??'');
|
||||
$readme = html_entity_decode($info['ReadMe']??'');
|
||||
$menu = sprintf("onclick=\"addDockerContainerContext('%s','%s','%s',%s,%s,%s,%s,'%s','%s','%s','%s','%s','%s', '%s','%s')\"", addslashes($name), addslashes($ct['ImageId']), addslashes($template), $running, $paused, $updateStatus, $is_autostart, addslashes($webGui), $shell, $id, addslashes($support), addslashes($project),addslashes($registry),addslashes($donateLink),addslashes($readme));
|
||||
$menu = sprintf("onclick=\"addDockerContainerContext('%s','%s','%s',%s,%s,%s,%s,'%s','%s','%s','%s','%s','%s','%s', '%s','%s')\"", addslashes($name), addslashes($ct['ImageId']), addslashes($template), $running, $paused, $updateStatus, $is_autostart, addslashes($webGui), addslashes($TSwebGui), $shell, $id, addslashes($support), addslashes($project),addslashes($registry),addslashes($donateLink),addslashes($readme));
|
||||
$docker[] = "docker.push({name:'$name',id:'$id',state:$running,pause:$paused,update:$updateStatus});";
|
||||
$shape = $running ? ($paused ? 'pause' : 'play') : 'square';
|
||||
$status = $running ? ($paused ? 'paused' : 'started') : 'stopped';
|
||||
@@ -179,6 +230,95 @@ foreach ($containers as $ct) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Check if Tailscale for container is enabled by checking if TShostname is set
|
||||
if (!empty($TShostname)) {
|
||||
if ($running) {
|
||||
// Get stats from container and check if they are not empty
|
||||
$TSstats = tailscale_stats($name);
|
||||
if (!empty($TSstats)) {
|
||||
// Construct TSinfo from TSstats
|
||||
$TSinfo = '';
|
||||
if (!$TSstats["Self"]["Online"]) {
|
||||
$TSinfo .= "Online:\t\t❌\nPlease check the logs!";
|
||||
} else {
|
||||
$TS_version = explode('-', $TSstats["Version"])[0];
|
||||
if (!empty($TS_version)) {
|
||||
if (!empty($TS_latest_version)) {
|
||||
if ($TS_version !== $TS_latest_version) {
|
||||
$TSinfo .= "Version:\t\t" . $TS_version . " ➔ " . $TS_latest_version . " available!\n";
|
||||
} else {
|
||||
$TSinfo .= "Version:\t\t" . $TS_version . "\n";
|
||||
}
|
||||
} else {
|
||||
$TSinfo .= "Version:\t\t" . $TS_version . "\n";
|
||||
}
|
||||
}
|
||||
$TSinfo .= "Online:\t\t✅\n";
|
||||
$TS_DNSName = $TSstats["Self"]["DNSName"];
|
||||
$TS_HostNameActual = substr($TS_DNSName, 0, strpos($TS_DNSName, '.'));
|
||||
if (strcasecmp($TS_HostNameActual, $TShostname) !== 0 && !empty($TS_DNSName)) {
|
||||
$TSinfo .= "Hostname:\tReal Hostname ➔ " . $TS_HostNameActual . "\n";
|
||||
} else {
|
||||
$TSinfo .= "Hostname:\t" . $TShostname . "\n";
|
||||
}
|
||||
// Map region relay code to cleartext region if TS_derp_list is available
|
||||
if (!empty($TS_derp_list)) {
|
||||
foreach ($TS_derp_list['Regions'] as $region) {
|
||||
if ($region['RegionCode'] === $TSstats["Self"]["Relay"]) {
|
||||
$TSregion = $region['RegionName'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!empty($TSregion)) {
|
||||
$TSinfo .= "Main Relay:\t" . $TSregion . "\n";
|
||||
} else {
|
||||
$TSinfo .= "Main Relay:\t" . $TSstats["Self"]["Relay"] . "\n";
|
||||
}
|
||||
} else {
|
||||
$TSinfo .= "Main Relay:\t" . $TSstats["Self"]["Relay"] . "\n";
|
||||
}
|
||||
if (!empty($TSstats["Self"]["TailscaleIPs"])) {
|
||||
$TSinfo .= "Addresses:\t" . implode("\n\t\t\t", $TSstats["Self"]["TailscaleIPs"]) . "\n";
|
||||
}
|
||||
if (!empty($TSstats["Self"]["PrimaryRoutes"])) {
|
||||
$TSinfo .= "Routes:\t\t" . implode("\n\t\t\t", $TSstats["Self"]["PrimaryRoutes"]) . "\n";
|
||||
}
|
||||
if ($TSstats["Self"]["ExitNodeOption"]) {
|
||||
$TSinfo .= "Is Exit Node:\t✅\n";
|
||||
} else {
|
||||
if (!empty($TSstats["ExitNodeStatus"])) {
|
||||
$TS_exit_node_status = ($TSstats["ExitNodeStatus"]["Online"]) ? "✅" : "❌";
|
||||
$TSinfo .= "Exit Node:\t" . strstr($TSstats["ExitNodeStatus"]["TailscaleIPs"][0], '/', true) . " | Status: " . $TS_exit_node_status ."\n";
|
||||
} else {
|
||||
$TSinfo .= "Is Exit Node:\t❌\n";
|
||||
}
|
||||
}
|
||||
if (!empty($TSwebGui)) {
|
||||
$TSinfo .= "URL:\t\t" . $TSwebGui . "\n";
|
||||
}
|
||||
if (!empty($TSstats["Self"]["KeyExpiry"])) {
|
||||
$TS_expiry = new DateTime($TSstats["Self"]["KeyExpiry"]);
|
||||
$current_Date = new DateTime();
|
||||
$TS_expiry_formatted = $TS_expiry->format('Y-m-d');
|
||||
$TS_expiry_diff = $current_Date->diff($TS_expiry);
|
||||
if ($TS_expiry_diff->invert) {
|
||||
$TSinfo .= "Key Expiry:\t❌ Expired! Renew/Disable key expiry!\n";
|
||||
} else {
|
||||
$TSinfo .= "Key Expiry:\t" . $TS_expiry_formatted . " (" . $TS_expiry_diff->days . " days)\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
// Display message to refresh page if Tailscale in the container wasn't maybe ready to get the data
|
||||
} else {
|
||||
echo "<div title='Error gathering Tailscale information from container.\nPlease check the logs and refresh the page.'><img src='/plugins/dynamix.docker.manager/images/tailscale.png' style='height: 16px;'> Tailscale</div></td>";
|
||||
}
|
||||
// Display TSinfo if data was fetched correctly
|
||||
echo "<div title='" . $TSinfo . "'><img src='/plugins/dynamix.docker.manager/images/tailscale.png' style='height: 16px;'> Tailscale</div>";
|
||||
// Display message that container isn't running
|
||||
} else {
|
||||
echo "<div title='Container not runnig'><img src='/plugins/dynamix.docker.manager/images/tailscale.png' style='height: 16px;'> Tailscale</div></td>";
|
||||
}
|
||||
}
|
||||
echo "<div class='advanced'><i class='fa fa-info-circle fa-fw'></i> ".compress(_($version),12,0)."</div></td>";
|
||||
echo "<td style='white-space:nowrap'><span class='docker_readmore'> ".implode('<br>',$networks)."</span></td>";
|
||||
echo "<td style='white-space:nowrap'><span class='docker_readmore'> ".implode('<br>',$network_ips)."</span></td>";
|
||||
@@ -210,5 +350,4 @@ foreach ($images as $image) {
|
||||
echo "</td><td>"._('Created')." ".htmlspecialchars(_($image['Created'],0))."</td></tr>";
|
||||
}
|
||||
echo "\0".implode($docker)."\0".(pgrep('rc.docker')!==false ? 1:0);
|
||||
?>
|
||||
|
||||
?>
|
@@ -32,37 +32,65 @@ function xml_decode($string) {
|
||||
return strval(html_entity_decode($string, ENT_XML1, 'UTF-8'));
|
||||
}
|
||||
|
||||
function generateTSwebui($url, $serve, $webUI) {
|
||||
if (!isset($webUI)) {
|
||||
return '';
|
||||
}
|
||||
$webui_url = isset($webUI) ? parse_url($webUI) : '';
|
||||
$webui_port = (preg_match('/\[PORT:(\d+)\]/', $webUI, $matches)) ? ':' . $matches[1] : '';
|
||||
$webui_path = $webui_url['path'] ?? '';
|
||||
$webui_query = isset($webui_url['query']) ? '?' . $webui_url['query'] : '';
|
||||
if (!empty($url)) {
|
||||
if (strpos($url, '[hostname]') !== false || strpos($url, '[noserve]') !== false) {
|
||||
if ($serve === 'serve' || $serve === 'funnel') {
|
||||
return 'https://[hostname][magicdns]' . $webui_path . $webui_query;
|
||||
} elseif ($serve === 'no') {
|
||||
return 'http://[noserve]' . $webui_port . $webui_path . $webui_query;
|
||||
}
|
||||
}
|
||||
return $url;
|
||||
} else {
|
||||
if (!empty($webUI)) {
|
||||
if ($serve === 'serve' || $serve === 'funnel') {
|
||||
return 'https://[hostname][magicdns]' . $webui_path . $webui_query;
|
||||
} elseif ($serve === 'no') {
|
||||
return 'http://[noserve]' . $webui_port . $webui_path . $webui_query;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function postToXML($post, $setOwnership=false) {
|
||||
$dom = new domDocument;
|
||||
$dom->appendChild($dom->createElement("Container"));
|
||||
$xml = simplexml_import_dom($dom);
|
||||
$xml['version'] = 2;
|
||||
$xml->Name = xml_encode(preg_replace('/\s+/', '', $post['contName']));
|
||||
$xml->Repository = xml_encode(trim($post['contRepository']));
|
||||
$xml->Registry = xml_encode(trim($post['contRegistry']));
|
||||
if (!empty(trim($post['netCONT']))) {
|
||||
$xml->Network = xml_encode($post['contNetwork'].':'.$post['netCONT']);
|
||||
$xml['version'] = 2;
|
||||
$xml->Name = xml_encode(preg_replace('/\s+/', '', $post['contName']));
|
||||
$xml->Repository = xml_encode(trim($post['contRepository']));
|
||||
$xml->Registry = xml_encode(trim($post['contRegistry']));
|
||||
if (isset($post['netCONT']) && !empty(trim($post['netCONT']))) {
|
||||
$xml->Network = xml_encode($post['contNetwork'].':'.$post['netCONT']);
|
||||
} else {
|
||||
$xml->Network = xml_encode($post['contNetwork']);
|
||||
$xml->Network = xml_encode($post['contNetwork']);
|
||||
}
|
||||
$xml->MyIP = xml_encode($post['contMyIP']);
|
||||
$xml->Shell = xml_encode($post['contShell']);
|
||||
$xml->Privileged = strtolower($post['contPrivileged']??'')=='on' ? 'true' : 'false';
|
||||
$xml->Support = xml_encode($post['contSupport']);
|
||||
$xml->Project = xml_encode($post['contProject']);
|
||||
$xml->Overview = xml_encode($post['contOverview']);
|
||||
$xml->Category = xml_encode($post['contCategory']);
|
||||
$xml->WebUI = xml_encode(trim($post['contWebUI']));
|
||||
$xml->TemplateURL = xml_encode($post['contTemplateURL']);
|
||||
$xml->Icon = xml_encode(trim($post['contIcon']));
|
||||
$xml->ExtraParams = xml_encode($post['contExtraParams']);
|
||||
$xml->PostArgs = xml_encode($post['contPostArgs']);
|
||||
$xml->CPUset = xml_encode($post['contCPUset']);
|
||||
$xml->DateInstalled = xml_encode(time());
|
||||
$xml->DonateText = xml_encode($post['contDonateText']);
|
||||
$xml->DonateLink = xml_encode($post['contDonateLink']);
|
||||
$xml->Requires = xml_encode($post['contRequires']);
|
||||
|
||||
$xml->MyIP = xml_encode($post['contMyIP']);
|
||||
$xml->Shell = xml_encode($post['contShell']);
|
||||
$xml->Privileged = strtolower($post['contPrivileged']??'')=='on' ? 'true' : 'false';
|
||||
$xml->Support = xml_encode($post['contSupport']);
|
||||
$xml->Project = xml_encode($post['contProject']);
|
||||
$xml->Overview = xml_encode($post['contOverview']);
|
||||
$xml->Category = xml_encode($post['contCategory']);
|
||||
$xml->WebUI = xml_encode(trim($post['contWebUI']));
|
||||
$xml->TemplateURL = xml_encode($post['contTemplateURL']);
|
||||
$xml->Icon = xml_encode(trim($post['contIcon']));
|
||||
$xml->ExtraParams = xml_encode($post['contExtraParams']);
|
||||
$xml->PostArgs = xml_encode($post['contPostArgs']);
|
||||
$xml->CPUset = xml_encode($post['contCPUset']);
|
||||
$xml->DateInstalled = xml_encode(time());
|
||||
$xml->DonateText = xml_encode($post['contDonateText']);
|
||||
$xml->DonateLink = xml_encode($post['contDonateLink']);
|
||||
$xml->Requires = xml_encode($post['contRequires']);
|
||||
$size = is_array($post['confName']??null) ? count($post['confName']) : 0;
|
||||
for ($i = 0; $i < $size; $i++) {
|
||||
$Type = $post['confType'][$i];
|
||||
@@ -77,6 +105,31 @@ function postToXML($post, $setOwnership=false) {
|
||||
$config['Required'] = xml_encode($post['confRequired'][$i]);
|
||||
$config['Mask'] = xml_encode($post['confMask'][$i]);
|
||||
}
|
||||
if (isset($post['contTailscale']) && strtolower($post['contTailscale']) == 'on') {
|
||||
$xml->TailscaleEnabled = 'true';
|
||||
$xml->TailscaleIsExitNode = xml_encode($post['TSisexitnode']);
|
||||
$xml->TailscaleHostname = xml_encode($post['TShostname']);
|
||||
$xml->TailscaleExitNodeIP = xml_encode($post['TSexitnodeip']);
|
||||
$xml->TailscaleSSH = xml_encode($post['TSssh']);
|
||||
$xml->TailscaleUserspaceNetworking = xml_encode($post['TSuserspacenetworking']);
|
||||
$xml->TailscaleLANAccess = xml_encode($post['TSallowlanaccess']);
|
||||
$xml->TailscaleServe = xml_encode($post['TSserve']);
|
||||
$xml->TailscaleWebUI = xml_encode(generateTSwebui($post['TSwebui'], $post['TSserve'], $post['contWebUI']));
|
||||
if (isset($post['TSserve']) && strtolower($post['TSserve']) !== 'no') {
|
||||
$xml->TailscaleServePort = xml_encode($post['TSserveport']);
|
||||
$xml->TailscaleServeLocalPath = xml_encode($post['TSservelocalpath']);
|
||||
$xml->TailscaleServeProtocol = xml_encode($post['TSserveprotocol']);
|
||||
$xml->TailscaleServeProtocolPort = xml_encode($post['TSserveprotocolport']);
|
||||
$xml->TailscaleServePath = xml_encode($post['TSservepath']);
|
||||
}
|
||||
$xml->TailscaleDParams = xml_encode($post['TSdaemonparams']);
|
||||
$xml->TailscaleParams = xml_encode($post['TSextraparams']);
|
||||
$xml->TailscaleStateDir = xml_encode($post['TSstatedir']);
|
||||
$xml->TailscaleRoutes = xml_encode($post['TSroutes']);;
|
||||
if (isset($post['TStroubleshooting']) && strtolower($post['TStroubleshooting']) === 'on') {
|
||||
$xml->TailscaleTroubleshooting = 'true';
|
||||
}
|
||||
}
|
||||
$dom = new DOMDocument('1.0');
|
||||
$dom->preserveWhiteSpace = false;
|
||||
$dom->formatOutput = true;
|
||||
@@ -86,29 +139,48 @@ function postToXML($post, $setOwnership=false) {
|
||||
|
||||
function xmlToVar($xml) {
|
||||
global $subnet;
|
||||
$xml = is_file($xml) ? simplexml_load_file($xml) : simplexml_load_string($xml);
|
||||
$out = [];
|
||||
$out['Name'] = preg_replace('/\s+/', '', xml_decode($xml->Name));
|
||||
$out['Repository'] = xml_decode($xml->Repository);
|
||||
$out['Registry'] = xml_decode($xml->Registry);
|
||||
$out['Network'] = xml_decode($xml->Network);
|
||||
$out['MyIP'] = xml_decode($xml->MyIP ?? '');
|
||||
$out['Shell'] = xml_decode($xml->Shell ?? 'sh');
|
||||
$out['Privileged'] = xml_decode($xml->Privileged);
|
||||
$out['Support'] = xml_decode($xml->Support);
|
||||
$out['Project'] = xml_decode($xml->Project);
|
||||
$out['Overview'] = stripslashes(xml_decode($xml->Overview));
|
||||
$out['Category'] = xml_decode($xml->Category);
|
||||
$out['WebUI'] = xml_decode($xml->WebUI);
|
||||
$out['TemplateURL'] = xml_decode($xml->TemplateURL);
|
||||
$out['Icon'] = xml_decode($xml->Icon);
|
||||
$out['ExtraParams'] = xml_decode($xml->ExtraParams);
|
||||
$out['PostArgs'] = xml_decode($xml->PostArgs);
|
||||
$out['CPUset'] = xml_decode($xml->CPUset);
|
||||
$out['DonateText'] = xml_decode($xml->DonateText);
|
||||
$out['DonateLink'] = xml_decode($xml->DonateLink);
|
||||
$out['Requires'] = xml_decode($xml->Requires);
|
||||
$out['Config'] = [];
|
||||
$xml = is_file($xml) ? simplexml_load_file($xml) : simplexml_load_string($xml);
|
||||
$out = [];
|
||||
$out['Name'] = preg_replace('/\s+/', '', xml_decode($xml->Name));
|
||||
$out['Repository'] = xml_decode($xml->Repository);
|
||||
$out['Registry'] = xml_decode($xml->Registry);
|
||||
$out['Network'] = xml_decode($xml->Network);
|
||||
$out['MyIP'] = xml_decode($xml->MyIP ?? '');
|
||||
$out['Shell'] = xml_decode($xml->Shell ?? 'sh');
|
||||
$out['Privileged'] = xml_decode($xml->Privileged);
|
||||
$out['Support'] = xml_decode($xml->Support);
|
||||
$out['Project'] = xml_decode($xml->Project);
|
||||
$out['Overview'] = stripslashes(xml_decode($xml->Overview));
|
||||
$out['Category'] = xml_decode($xml->Category);
|
||||
$out['WebUI'] = xml_decode($xml->WebUI);
|
||||
$out['TemplateURL'] = xml_decode($xml->TemplateURL);
|
||||
$out['Icon'] = xml_decode($xml->Icon);
|
||||
$out['ExtraParams'] = xml_decode($xml->ExtraParams);
|
||||
$out['PostArgs'] = xml_decode($xml->PostArgs);
|
||||
$out['CPUset'] = xml_decode($xml->CPUset);
|
||||
$out['DonateText'] = xml_decode($xml->DonateText);
|
||||
$out['DonateLink'] = xml_decode($xml->DonateLink);
|
||||
$out['Requires'] = xml_decode($xml->Requires);
|
||||
$out['TailscaleEnabled'] = xml_decode($xml->TailscaleEnabled ?? '');
|
||||
$out['TailscaleIsExitNode'] = xml_decode($xml->TailscaleIsExitNode ?? '');
|
||||
$out['TailscaleHostname'] = xml_decode($xml->TailscaleHostname ?? '');
|
||||
$out['TailscaleExitNodeIP'] = xml_decode($xml->TailscaleExitNodeIP ?? '');
|
||||
$out['TailscaleSSH'] = xml_decode($xml->TailscaleSSH ?? '');
|
||||
$out['TailscaleLANAccess'] = xml_decode($xml->TailscaleLANAccess ?? '');
|
||||
$out['TailscaleUserspaceNetworking'] = xml_decode($xml->TailscaleUserspaceNetworking ?? '');
|
||||
$out['TailscaleServe'] = xml_decode($xml->TailscaleServe ?? '');
|
||||
$out['TailscaleServePort'] = xml_decode($xml->TailscaleServePort ?? '');
|
||||
$out['TailscaleServeLocalPath'] = xml_decode($xml->TailscaleServeLocalPath ?? '');
|
||||
$out['TailscaleServeProtocol'] = xml_decode($xml->TailscaleServeProtocol ?? '');
|
||||
$out['TailscaleServeProtocolPort'] = xml_decode($xml->TailscaleServeProtocolPort ?? '');
|
||||
$out['TailscaleServePath'] = xml_decode($xml->TailscaleServePath ?? '');
|
||||
$out['TailscaleWebUI'] = xml_decode($xml->TailscaleWebUI ?? '');
|
||||
$out['TailscaleRoutes'] = xml_decode($xml->TailscaleRoutes ?? '');
|
||||
$out['TailscaleDParams'] = xml_decode($xml->TailscaleDParams ?? '');
|
||||
$out['TailscaleParams'] = xml_decode($xml->TailscaleParams ?? '');
|
||||
$out['TailscaleStateDir'] = xml_decode($xml->TailscaleStateDir ?? '');
|
||||
$out['TailscaleTroubleshooting'] = xml_decode($xml->TailscaleTroubleshooting ?? '');
|
||||
$out['Config'] = [];
|
||||
if (isset($xml->Config)) {
|
||||
foreach ($xml->Config as $config) {
|
||||
$c = [];
|
||||
@@ -266,7 +338,7 @@ function xmlToCommand($xml, $create_paths=false) {
|
||||
$Variables[] = 'TZ="'.$var['timeZone'].'"';
|
||||
// Add HOST_OS variable
|
||||
$Variables[] = 'HOST_OS="Unraid"';
|
||||
// Add HOST_HOSTNAME variable
|
||||
// Add HOST_HOSTNAME variable
|
||||
$Variables[] = 'HOST_HOSTNAME="'.$var['NAME'].'"';
|
||||
// Add HOST_CONTAINERNAME variable
|
||||
$Variables[] = 'HOST_CONTAINERNAME="'.$xml['Name'].'"';
|
||||
@@ -275,6 +347,68 @@ function xmlToCommand($xml, $create_paths=false) {
|
||||
if (strlen($xml['WebUI'])) $Labels[] = 'net.unraid.docker.webui='.escapeshellarg($xml['WebUI']);
|
||||
if (strlen($xml['Icon'])) $Labels[] = 'net.unraid.docker.icon='.escapeshellarg($xml['Icon']);
|
||||
|
||||
// Initialize Tailscale variables
|
||||
$TS_entrypoint = '';
|
||||
$TS_hook = '';
|
||||
$TS_hostname = '';
|
||||
$TS_hostname_label = '';
|
||||
$TS_ssh = '';
|
||||
$TS_tundev = '';
|
||||
$TS_cap = '';
|
||||
$TS_exitnode = '';
|
||||
$TS_exitnode_ip = '';
|
||||
$TS_lan_access = '';
|
||||
$TS_userspace_networking = '';
|
||||
$TS_daemon_params = '';
|
||||
$TS_extra_params = '';
|
||||
$TS_state_dir = '';
|
||||
$TS_serve_funnel = '';
|
||||
$TS_serve_port = '';
|
||||
$TS_serve_local_path = '';
|
||||
$TS_serve_protocol = '';
|
||||
$TS_serve_protocol_port = '';
|
||||
$TS_serve_path = '';
|
||||
$TS_web_ui = '';
|
||||
$TS_troubleshooting = '';
|
||||
$TS_routes = '';
|
||||
$TS_postargs = '';
|
||||
// Get all information from xml and create variables for cmd
|
||||
if ($xml['TailscaleEnabled'] == 'true') {
|
||||
$TS_entrypoint = '--entrypoint=\'/opt/unraid/tailscale\'';
|
||||
$TS_hook = '-v \'/usr/local/share/docker/tailscale_container_hook\':\'/opt/unraid/tailscale\'';
|
||||
$TS_hostname = !empty($xml['TailscaleHostname']) ? '-e TAILSCALE_HOSTNAME=' . escapeshellarg($xml['TailscaleHostname']) : '';
|
||||
$TS_hostname_label = !empty($xml['TailscaleHostname']) ? '-l net.unraid.docker.tailscale.hostname=' . escapeshellarg($xml['TailscaleHostname']) : '';
|
||||
$TS_ssh = !empty($xml['TailscaleSSH']) ? '-e TAILSCALE_USE_SSH=' . escapeshellarg($xml['TailscaleSSH']) : '';
|
||||
$TS_daemon_params = !empty($xml['TailscaleDParams']) ? '-e TAILSCALED_PARAMS=' . escapeshellarg($xml['TailscaleDParams']) : '';
|
||||
$TS_extra_params = !empty($xml['TailscaleParams']) ? '-e TAILSCALE_PARAMS=' . escapeshellarg($xml['TailscaleParams']) : '';
|
||||
$TS_state_dir = !empty($xml['TailscaleStateDir']) ? '-e TAILSCALE_STATE_DIR=' . escapeshellarg($xml['TailscaleStateDir']) : '';
|
||||
$TS_userspace_networking = !empty($xml['TailscaleUserspaceNetworking']) ? '-e TAILSCALE_USERSPACE_NETWORKING=' . escapeshellarg($xml['TailscaleUserspaceNetworking']) : '';
|
||||
// Only add tun, cap and specific vairables to containers which are defined as Exit Nodes and Userspace Networking disabled
|
||||
if (_var($xml,'TailscaleIsExitNode') == 'true') {
|
||||
$TS_tundev = preg_match('/--d(evice)?[= ](\'?\/dev\/net\/tun\'?)/', $xml['ExtraParams']) ? "" : "--device='/dev/net/tun'";
|
||||
$TS_cap = preg_match('/--cap\-add=NET_ADMIN/', $xml['ExtraParams']) ? "" : "--cap-add=NET_ADMIN";
|
||||
$TS_exitnode = '-e TAILSCALE_EXIT_NODE=true';
|
||||
} elseif (_var($xml,'TailscaleUserspaceNetworking') == 'false') {
|
||||
$TS_tundev = preg_match('/--d(evice)?[= ](\'?\/dev\/net\/tun\'?)/', $xml['ExtraParams']) ? "" : "--device='/dev/net/tun'";
|
||||
$TS_cap = preg_match('/--cap\-add=NET_ADMIN/', $xml['ExtraParams']) ? "" : "--cap-add=NET_ADMIN";
|
||||
$TS_lan_access = '-e TAILSCALE_ALLOW_LAN_ACCESS=' . escapeshellarg($xml['TailscaleLANAccess']);
|
||||
$TS_exitnode_ip = !empty($xml['TailscaleExitNodeIP']) ? '-e TAILSCALE_EXIT_NODE_IP=' . escapeshellarg($xml['TailscaleExitNodeIP']) : '';
|
||||
}
|
||||
$TS_serve_funnel = ($xml['TailscaleServe'] == 'funnel') ? '-e TAILSCALE_FUNNEL=true' : '';
|
||||
$TS_serve_port = !empty($xml['TailscaleServePort']) ? '-e TAILSCALE_SERVE_PORT=' . escapeshellarg($xml['TailscaleServePort']) : '';
|
||||
$TS_serve_local_path = !empty($xml['TailscaleServeLocalPath']) ? '-e TAILSCALE_SERVE_LOCALPATH=' . escapeshellarg($xml['TailscaleServeLocalPath']) : '';
|
||||
$TS_serve_protocol = !empty($xml['TailscaleServeProtocol']) ? '-e TAILSCALE_SERVE_PROTOCOL=' . escapeshellarg($xml['TailscaleServeProtocol']) : '';
|
||||
$TS_serve_protocol_port = !empty($xml['TailscaleServeProtocolPort']) ? '-e TAILSCALE_SERVE_PROTOCOL_PORT=' . escapeshellarg($xml['TailscaleServeProtocolPort']) : '';
|
||||
$TS_serve_path = !empty($xml['TailscaleServePath']) ? '-e TAILSCALE_SERVE_PATH=' . escapeshellarg($xml['TailscaleServePath']) : '';
|
||||
$TS_web_ui = !empty($xml['TailscaleWebUI']) ? '-l net.unraid.docker.tailscale.webui=' . escapeshellarg($xml['TailscaleWebUI']) : '';
|
||||
$TS_troubleshooting = !empty($xml['TailscaleTroubleshooting']) ? '-e TAILSCALE_TROUBLESHOOTING=' . escapeshellarg($xml['TailscaleTroubleshooting']) : '';
|
||||
$TS_routes = !empty($xml['TailscaleRoutes']) ? '-e TAILSCALE_ADVERTISE_ROUTES=' . escapeshellarg($xml['TailscaleRoutes']) : '';
|
||||
if (!empty($xml['PostArgs'])) {
|
||||
$TS_postargs = '-e ORG_POSTARGS=' . escapeshellarg($xml['PostArgs']);
|
||||
$xml['PostArgs'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($xml['Config'] as $key => $config) {
|
||||
$confType = strtolower(strval($config['Type']));
|
||||
$hostConfig = strlen($config['Value']) ? $config['Value'] : $config['Default'];
|
||||
@@ -332,8 +466,8 @@ function xmlToCommand($xml, $create_paths=false) {
|
||||
$pid_limit = "";
|
||||
}
|
||||
|
||||
$cmd = sprintf($docroot.'/plugins/dynamix.docker.manager/scripts/docker create %s %s %s %s %s %s %s %s %s %s %s %s %s %s',
|
||||
$cmdName, $cmdNetwork, $cmdMyIP, $cmdCPUset, $pid_limit, $cmdPrivileged, implode(' -e ', $Variables), implode(' -l ', $Labels), implode(' -p ', $Ports), implode(' -v ', $Volumes), implode(' --device=', $Devices), $xml['ExtraParams'], escapeshellarg($xml['Repository']), $xml['PostArgs']);
|
||||
$cmd = sprintf($docroot.'/plugins/dynamix.docker.manager/scripts/docker create %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s',
|
||||
$cmdName, $TS_entrypoint, $cmdNetwork, $cmdMyIP, $cmdCPUset, $pid_limit, $cmdPrivileged, implode(' -e ', $Variables), $TS_hostname, $TS_exitnode, $TS_exitnode_ip, $TS_lan_access, $TS_routes, $TS_ssh, $TS_userspace_networking, $TS_serve_funnel, $TS_serve_port, $TS_serve_local_path, $TS_serve_protocol, $TS_serve_protocol_port, $TS_serve_path, $TS_daemon_params, $TS_extra_params, $TS_state_dir, $TS_troubleshooting, $TS_postargs, implode(' -l ', $Labels), $TS_web_ui, $TS_hostname_label, implode(' -p ', $Ports), implode(' -v ', $Volumes), $TS_hook, $TS_cap, $TS_tundev, implode(' --device=', $Devices), $xml['ExtraParams'], escapeshellarg($xml['Repository']), $xml['PostArgs']);
|
||||
return [preg_replace('/\s\s+/', ' ', $cmd), $xml['Name'], $xml['Repository']];
|
||||
}
|
||||
function stopContainer($name, $t=false, $echo=true) {
|
||||
@@ -520,7 +654,7 @@ function setXmlVal(&$xml, $value, $el, $attr=null, $pos=0) {
|
||||
|
||||
function getAllocations() {
|
||||
global $DockerClient, $host;
|
||||
|
||||
|
||||
$ports = [];
|
||||
foreach ($DockerClient->getDockerContainers() as $ct) {
|
||||
$list = $port = [];
|
||||
|
@@ -1,10 +1,11 @@
|
||||
var eventURL = '/plugins/dynamix.docker.manager/include/Events.php';
|
||||
|
||||
function addDockerContainerContext(container, image, template, started, paused, update, autostart, webui, shell, id, Support, Project, Registry, donateLink, ReadMe) {
|
||||
function addDockerContainerContext(container, image, template, started, paused, update, autostart, webui, tswebui, shell, id, Support, Project, Registry, donateLink, ReadMe) {
|
||||
var opts = [];
|
||||
context.settings({right:false,above:false});
|
||||
if (started && !paused) {
|
||||
if (webui !== '' && webui != '#') opts.push({text:_('WebUI'), icon:'fa-globe', href:webui, target:'_blank'});
|
||||
if (tswebui !== '' && tswebui != '#') opts.push({text:_('Tailscale WebUI'), icon:'fa-globe', href:tswebui, target:'_blank'});
|
||||
opts.push({text:_('Console'), icon:'fa-terminal', action:function(e){e.preventDefault(); openTerminal('docker',container,shell);}});
|
||||
opts.push({divider:true});
|
||||
}
|
||||
|
@@ -170,6 +170,7 @@ foreach (explode('*',rawurldecode($argv[1])) as $value) {
|
||||
$xml = file_get_contents($tmpl);
|
||||
[$cmd, $Name, $Repository] = xmlToCommand($tmpl);
|
||||
$Registry = getXmlVal($xml, "Registry");
|
||||
$TS_Enabled = getXmlVal($xml, "TailscaleEnabled");
|
||||
$oldImageID = $DockerClient->getImageID($Repository);
|
||||
// pull image
|
||||
if (!pullImage_nchan($Name, $Repository)) continue;
|
||||
@@ -182,14 +183,25 @@ foreach (explode('*',rawurldecode($argv[1])) as $value) {
|
||||
// attempt graceful stop of container first
|
||||
stopContainer_nchan($Name);
|
||||
}
|
||||
if ( ($argv[2]??null) == "ca_docker_run_override" )
|
||||
if ( ($argv[2]??null) == "ca_docker_run_override" )
|
||||
$startContainer = true;
|
||||
|
||||
if ( $startContainer )
|
||||
$cmd = str_replace('/docker create ', '/docker run -d ', $cmd);
|
||||
|
||||
// force kill container if still running after 10 seconds
|
||||
if (empty($_GET['communityApplications'])) removeContainer_nchan($Name);
|
||||
// Extract real Entrypoint and Cmd from container for Tailscale
|
||||
if ($TS_Enabled == 'true') {
|
||||
// Create preliminary base container but don't run it
|
||||
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker create --name '" . escapeshellarg($Name) . "' '" . escapeshellarg($Repository) . "'");
|
||||
// Get Entrypoint and Cmd from docker inspect
|
||||
$containerInfo = $DockerClient->getContainerDetails($Name);
|
||||
$ts_env = isset($containerInfo['Config']['Entrypoint']) ? '-e ORG_ENTRYPOINT="' . implode(' ', $containerInfo['Config']['Entrypoint']) . '" ' : '';
|
||||
$ts_env .= isset($containerInfo['Config']['Cmd']) ? '-e ORG_CMD="' . implode(' ', $containerInfo['Config']['Cmd']) . '" ' : '';
|
||||
// Insert Entrypoint and Cmd to docker command
|
||||
$cmd = str_replace('-l net.unraid.docker.managed=dockerman', $ts_env . '-l net.unraid.docker.managed=dockerman' , $cmd);
|
||||
// Remove preliminary container
|
||||
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker rm '" . escapeshellarg($Name) . "'");
|
||||
}
|
||||
execCommand_nchan($cmd);
|
||||
if ($startContainer) addRoute($Name); // add route for remote WireGuard access
|
||||
$DockerClient->flushCaches();
|
||||
|
@@ -48,12 +48,13 @@ if ($_POST['docker']) {
|
||||
$template = $info['template'];
|
||||
$shell = $info['shell'];
|
||||
$webGui = html_entity_decode($info['url']);
|
||||
$TSwebGui = html_entity_decode($info['TSurl']);
|
||||
$support = html_entity_decode($info['Support']);
|
||||
$project = html_entity_decode($info['Project']);
|
||||
$registry = html_entity_decode($info['registry']);
|
||||
$donateLink = html_entity_decode($info['DonateLink']);
|
||||
$readme = html_entity_decode($info['ReadMe']);
|
||||
$menu = sprintf("onclick=\"addDockerContainerContext('%s','%s','%s',%s,%s,%s,%s,'%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($name), addslashes($ct['ImageId']), addslashes($template), $running, $paused, $updateStatus, $is_autostart, addslashes($webGui), $shell, $id, addslashes($support), addslashes($project), addslashes($registry), addslashes($donateLink), addslashes($readme));
|
||||
$menu = sprintf("onclick=\"addDockerContainerContext('%s','%s','%s',%s,%s,%s,%s,'%s','%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($name), addslashes($ct['ImageId']), addslashes($template), $running, $paused, $updateStatus, $is_autostart, addslashes($webGui), addslashes($TSwebGui), $shell, $id, addslashes($support), addslashes($project), addslashes($registry), addslashes($donateLink), addslashes($readme));
|
||||
$shape = $running ? ($paused ? 'pause' : 'play') : 'square';
|
||||
$status = $running ? ($paused ? 'paused' : 'started') : 'stopped';
|
||||
$color = $status=='started' ? 'green-text' : ($status=='paused' ? 'orange-text' : 'red-text');
|
||||
|
313
share/docker/tailscale_container_hook
Executable file
313
share/docker/tailscale_container_hook
Executable file
@@ -0,0 +1,313 @@
|
||||
#!/bin/sh
|
||||
# Copyright 2024, Lime Technology
|
||||
# Copyright 2024, Christoph Hummer
|
||||
#
|
||||
# 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_entrypoint() {
|
||||
echo "Starting container..."
|
||||
echo
|
||||
echo "======================="
|
||||
echo
|
||||
eval "exec ${ORG_ENTRYPOINT} ${ORG_CMD} ${ORG_POSTARGS}"
|
||||
}
|
||||
|
||||
error_handler() {
|
||||
echo "ERROR: Unraid Docker Hook script throw an error!"
|
||||
echo " Starting container without Tailscale!"
|
||||
echo
|
||||
exec_entrypoint
|
||||
}
|
||||
|
||||
echo "======================="
|
||||
echo
|
||||
echo "Executing Unraid Docker Hook for Tailscale"
|
||||
echo
|
||||
|
||||
if [ ! -f /usr/bin/tailscale ] || [ ! -f /usr/bin/tailscaled ]; then
|
||||
if [ ! -z "${TAILSCALE_EXIT_NODE_IP}" ]; then
|
||||
if [ ! -c /dev/net/tun ]; then
|
||||
echo "ERROR: Device /dev/net/tun not found!"
|
||||
echo " Make sure to pass through /dev/net/tun to the container."
|
||||
error_handler
|
||||
fi
|
||||
INSTALL_IPTABLES="iptables "
|
||||
fi
|
||||
|
||||
echo "Detecting Package Manager..."
|
||||
if which apt-get >/dev/null 2>&1; then
|
||||
echo "Detected Advanced Package Tool!"
|
||||
PACKAGES_UPDATE="apt-get update"
|
||||
PACKAGES_INSTALL="apt-get -y install --no-install-recommends"
|
||||
elif which apk >/dev/null 2>&1; then
|
||||
echo "Detected Alpine Package Keeper!"
|
||||
PACKAGES_UPDATE="apk update"
|
||||
PACKAGES_INSTALL="apk add"
|
||||
elif which pacman >/dev/null 2>&1; then
|
||||
echo "Detected pacman Package Manager!"
|
||||
PACKAGES_INSTALL="pacman -Syu --noconfirm"
|
||||
else
|
||||
echo "ERROR: Detection from Package Manager failed!"
|
||||
error_handler
|
||||
fi
|
||||
|
||||
if [ "${TAILSCALE_TROUBLESHOOTING}" = "true" ]; then
|
||||
if which apt-get >/dev/null 2>&1; then
|
||||
PACKAGES_TROUBLESHOOTING="curl dnsutils iputils-ping "
|
||||
elif which apk >/dev/null 2>&1; then
|
||||
PACKAGES_TROUBLESHOOTING="curl bind-tools iputils-ping "
|
||||
elif which pacman >/dev/null 2>&1; then
|
||||
PACKAGES_TROUBLESHOOTING="curl dnsutils iputils "
|
||||
fi
|
||||
echo "Tailscale Troubleshooting enabled!"
|
||||
echo "Installing additional packages: $(echo "${PACKAGES_TROUBLESHOOTING}" | sed 's/[[:blank:]]*$//' | sed 's/ /, /g')"
|
||||
fi
|
||||
|
||||
echo "Installing packages..."
|
||||
echo "Please wait..."
|
||||
if [ ! -z "${PACKAGES_UPDATE}" ]; then
|
||||
UPDATE_LOG=$(${PACKAGES_UPDATE} 2>&1)
|
||||
fi
|
||||
INSTALL_LOG=$(${PACKAGES_INSTALL} jq wget ${INSTALL_IPTABLES}${PACKAGES_TROUBLESHOOTING} 2>&1)
|
||||
INSTALL_RESULT=$?
|
||||
|
||||
if [ "${INSTALL_RESULT}" -eq 0 ]; then
|
||||
echo "Packages installed!"
|
||||
unset INSTALL_LOG
|
||||
else
|
||||
echo "ERROR: Installing packages!"
|
||||
echo "${UPDATE_LOG}"
|
||||
echo "${INSTALL_LOG}"
|
||||
error_handler
|
||||
fi
|
||||
|
||||
if [ "${INSTALL_IPTABLES}" = "iptables " ]; then
|
||||
if ! iptables -L >/dev/null 2>&1; then
|
||||
echo "ERROR: Cap: NET_ADMIN not available!"
|
||||
echo " Make sure to add --cap-add=NET_ADMIN to the Extra Parameters"
|
||||
error_handler
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Tailscale not found, downloading..."
|
||||
echo "Please wait..."
|
||||
|
||||
TAILSCALE_VERSION=$(wget -qO- 'https://pkgs.tailscale.com/stable/?mode=json' | jq -r '.TarballsVersion')
|
||||
|
||||
if [ -z "${TAILSCALE_VERSION}" ]; then
|
||||
echo "ERROR: Can't get Tailscale JSON"
|
||||
error_handler
|
||||
fi
|
||||
|
||||
if [ ! -d /tmp/tailscale ]; then
|
||||
mkdir -p /tmp/tailscale
|
||||
fi
|
||||
|
||||
if wget -q -nc --show-progress --progress=bar:force:noscroll -O /tmp/tailscale/tailscale.tgz "https://pkgs.tailscale.com/stable/tailscale_${TAILSCALE_VERSION}_amd64.tgz" ; then
|
||||
echo "Download from Tailscale version ${TAILSCALE_VERSION} successful!"
|
||||
else
|
||||
echo "ERROR: Download from Tailscale version ${TAILSCALE_VERSION} failed!"
|
||||
rm -rf /tmp/tailscale
|
||||
error_handler
|
||||
fi
|
||||
|
||||
tar -C /tmp/tailscale -xf /tmp/tailscale/tailscale.tgz
|
||||
cp /tmp/tailscale/tailscale_${TAILSCALE_VERSION}_amd64/tailscale /usr/bin/tailscale
|
||||
cp /tmp/tailscale/tailscale_${TAILSCALE_VERSION}_amd64/tailscaled /usr/bin/tailscaled
|
||||
rm -rf /tmp/tailscale
|
||||
|
||||
echo "Installation Done!"
|
||||
else
|
||||
echo "Tailscale found, continuing..."
|
||||
fi
|
||||
|
||||
unset TSD_PARAMS
|
||||
unset TS_PARAMS
|
||||
|
||||
if [ ! -z "${SERVER_DIR}" ]; then
|
||||
TSD_STATE_DIR="${SERVER_DIR}/.tailscale_state"
|
||||
echo "Settings Tailscale state dir to: ${TSD_STATE_DIR}"
|
||||
elif [ ! -z "${DATA_DIR}" ]; then
|
||||
TSD_STATE_DIR="${DATA_DIR}/.tailscale_state"
|
||||
echo "Settings Tailscale state dir to: ${TSD_STATE_DIR}"
|
||||
else
|
||||
if [ -z "${TAILSCALE_STATE_DIR}" ]; then
|
||||
TAILSCALE_STATE_DIR="/config/.tailscale_state"
|
||||
fi
|
||||
TSD_STATE_DIR="${TAILSCALE_STATE_DIR}"
|
||||
echo "Settings Tailscale state dir to: ${TSD_STATE_DIR}"
|
||||
fi
|
||||
|
||||
if [ ! -d "${TSD_STATE_DIR}" ]; then
|
||||
mkdir -p ${TSD_STATE_DIR}
|
||||
fi
|
||||
|
||||
if [ ! -z "${TAILSCALE_EXIT_NODE_IP}" ]; then
|
||||
echo "Disabling userspace networking! Tailscale DNS available"
|
||||
echo "Using ${TAILSCALE_EXIT_NODE_IP} as Exit Node! See https://tailscale.com/kb/1103/exit-nodes"
|
||||
TS_PARAMS=" --exit-node=${TAILSCALE_EXIT_NODE_IP}"
|
||||
if [ "${TAILSCALE_ALLOW_LAN_ACCESS}" = "true" ]; then
|
||||
echo "Enabling local LAN Access to the container!"
|
||||
TS_PARAMS="${TS_PARAMS} --exit-node-allow-lan-access"
|
||||
fi
|
||||
else
|
||||
if [ -z "${TAILSCALE_USERSPACE_NETWORKING}" ] || [ "${TAILSCALE_USERSPACE_NETWORKING}" = "true" ]; then
|
||||
echo "Enabling userspace networking! Tailscale DNS not available"
|
||||
TSD_PARAMS="-tun=userspace-networking "
|
||||
else
|
||||
if [ ! -c /dev/net/tun ]; then
|
||||
echo "ERROR: Device /dev/net/tun not found!"
|
||||
echo " Make sure to pass through /dev/net/tun to the container and add the"
|
||||
echo " parameter --cap-add=NET_ADMIN to the Extra Parameters!"
|
||||
error_handler
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -z "${TAILSCALE_ADVERTISE_ROUTES}" ]; then
|
||||
TAILSCALE_ADVERTISE_ROUTES="$(echo ${TAILSCALE_ADVERTISE_ROUTES} | sed 's/ //g')"
|
||||
echo "Advertising custom routes! See https://tailscale.com/kb/1019/subnets#advertise-subnet-routes"
|
||||
TS_PARAMS="${TS_PARAMS} --advertise-routes=${TAILSCALE_ADVERTISE_ROUTES}"
|
||||
fi
|
||||
|
||||
if [ "${TAILSCALE_USE_SSH}" = "true" ]; then
|
||||
echo "Enabling SSH! See https://tailscale.com/kb/1193/tailscale-ssh"
|
||||
TS_PARAMS="${TS_PARAMS} --ssh"
|
||||
fi
|
||||
|
||||
if [ "${TAILSCALE_LOG}" != "false" ]; then
|
||||
TSD_PARAMS="${TSD_PARAMS} >>/var/log/tailscaled 2>&1 "
|
||||
TSD_MSG=" with log file location: /var/log/tailscaled"
|
||||
else
|
||||
TSD_PARAMS="${TSD_PARAMS} >/dev/null 2>&1 "
|
||||
TSD_MSG=" with logging disabled"
|
||||
fi
|
||||
|
||||
if [ ! -z "${TAILSCALE_HOSTNAME}" ]; then
|
||||
echo "Setting host name to \"${TAILSCALE_HOSTNAME}\""
|
||||
TAILSCALE_HOSTNAME="$(echo "$TAILSCALE_HOSTNAME" | tr -d ' ')"
|
||||
TS_PARAMS="${TS_PARAMS} --hostname=${TAILSCALE_HOSTNAME}"
|
||||
fi
|
||||
|
||||
if [ "${TAILSCALE_EXIT_NODE}" = "true" ]; then
|
||||
echo "Configuring container as Exit Node! See https://tailscale.com/kb/1103/exit-nodes"
|
||||
TS_PARAMS="${TS_PARAMS} --advertise-exit-node"
|
||||
fi
|
||||
|
||||
if [ ! -z "${TAILSCALED_PARAMS}" ]; then
|
||||
TSD_PARAMS="${TAILSCALED_PARAMS} ${TSD_PARAMS}"
|
||||
fi
|
||||
|
||||
if [ ! -z "${TAILSCALE_PARAMS}" ]; then
|
||||
TS_PARAMS="${TAILSCALE_PARAMS}${TS_PARAMS}"
|
||||
fi
|
||||
|
||||
echo "Starting tailscaled${TSD_MSG}"
|
||||
eval tailscaled -statedir=${TSD_STATE_DIR} ${TSD_PARAMS}&
|
||||
|
||||
echo "Starting tailscale"
|
||||
eval tailscale up ${TS_PARAMS} --reset
|
||||
EXIT_STATUS="$?"
|
||||
|
||||
if [ "${EXIT_STATUS}" != "0" ]; then
|
||||
echo "ERROR: Connecting to Tailscale not successful!"
|
||||
if [ -f /var/log/tailscaled ]; then
|
||||
echo "Please check the logs:"
|
||||
tail -20 /var/log/tailscaled
|
||||
fi
|
||||
error_handler
|
||||
fi
|
||||
unset EXIT_STATUS
|
||||
|
||||
if [ ! -z "${TAILSCALE_SERVE_PORT}" ] && [ "$(tailscale status --json | jq -r '.CurrentTailnet.MagicDNSEnabled')" = "false" ] ; then
|
||||
echo "ERROR: Enable HTTPS on your Tailscale account to use Tailscale Serve/Funnel."
|
||||
echo "See: https://tailscale.com/kb/1153/enabling-https"
|
||||
error_handler
|
||||
fi
|
||||
|
||||
if [ "${TAILSCALE_EXIT_NODE}" = "true" ]; then
|
||||
if [ "$(tailscale status --json | jq -r '.Self.ExitNodeOption')" = "false" ]; then
|
||||
TSIP=$(tailscale status --json | jq -r '.Self.TailscaleIPs[0]')
|
||||
echo "WARNING: Exit Node not yet approved."
|
||||
echo " Navigate to https://login.tailscale.com/admin/machines/${TSIP} and approve it."
|
||||
fi
|
||||
fi
|
||||
|
||||
KEY_EXPIRY=$(tailscale status --json | jq -r '.Self.KeyExpiry')
|
||||
if [ "${KEY_EXPIRY}" != "null" ]; then
|
||||
EXPIRY_EPOCH=$(date -d "${KEY_EXPIRY}" +"%s" 2>/dev/null)
|
||||
CUR_EPOCH=$(date -u +%s)
|
||||
DIFF_EPOCH=$((EXPIRY_EPOCH - CUR_EPOCH))
|
||||
DIFF_DAYS=$((DIFF_EPOCH / 86400))
|
||||
HOST=$(tailscale status --json | jq -r '.Self.HostName')
|
||||
if [ -n "${DIFF_DAYS}" ] && echo "${DIFF_DAYS}" | grep -Eq '^[0-9]+$'; then
|
||||
echo "WARNING: Tailscale Key will expire in ${DIFF_DAYS} days."
|
||||
echo " Navigate to https://login.tailscale.com/admin/machines and 'Disable Key Expiry' for ${HOST}"
|
||||
else
|
||||
echo "ERROR: Tailscale Key expired!"
|
||||
echo " Navigate to https://login.tailscale.com/admin/machines and Renew/Disable Key Expiry for ${HOST}"
|
||||
fi
|
||||
echo "See: https://tailscale.com/kb/1028/key-expiry"
|
||||
fi
|
||||
|
||||
if [ ! -z "${TAILSCALE_ADVERTISE_ROUTES}" ]; then
|
||||
APPROVED_ROUTES="$(tailscale status --json | jq -r '.Self.PrimaryRoutes')"
|
||||
IFS=','
|
||||
set -- ${TAILSCALE_ADVERTISE_ROUTES}
|
||||
ROUTES="$@"
|
||||
for route in ${ROUTES}; do
|
||||
if ! echo "${APPROVED_ROUTES}" | grep -q "${route}"; then
|
||||
NOT_APPROVED="$NOT_APPROVED ${route}"
|
||||
fi
|
||||
done
|
||||
if [ ! -z "${NOT_APPROVED}" ]; then
|
||||
TSIP="$(tailscale status --json | jq -r '.Self.TailscaleIPs[0]')"
|
||||
echo "WARNING: The following route(s) are not approved:${NOT_APPROVED}"
|
||||
echo " Navigate to https://login.tailscale.com/admin/machines/${TSIP} and approve it."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -z "${TAILSCALE_SERVE_PORT}" ]; then
|
||||
if [ ! -z "${TAILSCALE_SERVE_PATH}" ]; then
|
||||
TAILSCALE_SERVE_PATH="=${TAILSCALE_SERVE_PATH}"
|
||||
fi
|
||||
if [ -z "${TAILSCALE_SERVE_PROTOCOL}" ]; then
|
||||
TAILSCALE_SERVE_PROTOCOL="https"
|
||||
fi
|
||||
if [ -z "${TAILSCALE_SERVE_PROTOCOL_PORT}" ]; then
|
||||
TAILSCALE_SERVE_PROTOCOL_PORT="=443"
|
||||
fi
|
||||
if [ "${TAILSCALE_FUNNEL}" = "true" ]; then
|
||||
echo "Enabling Funnel! See https://tailscale.com/kb/1223/funnel"
|
||||
eval tailscale funnel --bg --"${TAILSCALE_SERVE_PROTOCOL}"${TAILSCALE_SERVE_PROTOCOL_PORT}${TAILSCALE_SERVE_PATH} http://localhost:"${TAILSCALE_SERVE_PORT}${TAILSCALE_SERVE_LOCALPATH}" | grep -v "To disable the proxy"
|
||||
else
|
||||
echo "Enabling Serve! See https://tailscale.com/kb/1312/serve"
|
||||
eval tailscale serve --bg --"${TAILSCALE_SERVE_PROTOCOL}"${TAILSCALE_SERVE_PROTOCOL_PORT}${TAILSCALE_SERVE_PATH} http://localhost:"${TAILSCALE_SERVE_PORT}${TAILSCALE_SERVE_LOCALPATH}" | grep -v "To disable the proxy"
|
||||
fi
|
||||
if [ "${TAILSCALE_SERVE_PROTOCOL}" = "https" ]; then
|
||||
TS_DNSNAME="$(tailscale status --json | jq -r '.Self.DNSName' | sed 's/\.$//')"
|
||||
if [ ! -f "${TSD_STATE_DIR}/certs/${TS_DNSNAME}.crt" ] || [ ! -f "${TSD_STATE_DIR}/certs/${TS_DNSNAME}.key" ]; then
|
||||
if [ ! -d "${TSD_STATE_DIR}/certs" ]; then
|
||||
mkdir -p "${TSD_STATE_DIR}/certs"
|
||||
fi
|
||||
echo "Generating Tailscale certs! This can take some time, please wait..."
|
||||
timeout 30 tailscale cert --cert-file="${TSD_STATE_DIR}/certs/${TS_DNSNAME}.crt" --key-file="${TSD_STATE_DIR}/certs/${TS_DNSNAME}.key" "${TS_DNSNAME}" >/dev/null 2>&1
|
||||
EXIT_STATUS="$?"
|
||||
if [ "${EXIT_STATUS}" != "0" ]; then
|
||||
echo "ERROR: Can't generate certificates!"
|
||||
echo "Please check the logs:"
|
||||
tail -10 /var/log/tailscaled
|
||||
else
|
||||
echo "Done!"
|
||||
fi
|
||||
unset EXIT_STATUS
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec_entrypoint
|
Reference in New Issue
Block a user