Compare commits

...

67 Commits

Author SHA1 Message Date
a7ac0986a1 Add support for post arguments 2024-10-19 20:54:20 +02:00
57edfd6d7b Add support for post arguments 2024-10-19 20:54:04 +02:00
3a5d5f07ef Fix for Docker rebuild loop 2024-10-19 19:52:48 +02:00
13413b5d1f share/docker/tailscale_container_hook aktualisiert 2024-10-18 18:46:39 +02:00
47a04e10c0 Add error handling to package installation 2024-10-18 14:04:08 +02:00
44e09d534c Change JSON download routine and audjust paths 2024-10-18 06:38:27 +02:00
1b65b07110 emhttp/plugins/dynamix.docker.manager/include/DockerClient.php aktualisiert 2024-10-17 20:26:10 +02:00
da01e24ff8 Add version comparison 2024-10-17 18:23:50 +02:00
a080ec364d Initial Tailscale Docker integration
- Remove exclusion from share directory from .gitignore
- Add Unraid specific container hook script
- Add Tailscale icon
- Add helptexts for Tailscale

This integration allows users to easily make use of Tailscale in their Docker containers by just clicking a switch on the Docker page.
The Tailscale plugin itself is not needed for this integration but for the best user experience it is strongly recommended to install the Tailscale plugin from Community Applications.

How this works:
1. Configure Tailscale in the Docker template in Unraid and click Apply
2. Unraid will extract the default Entrypoint and CMD from the container
3. The hook script will be mounted in the container to /opt/unraid/tailscale-hook and the Entrypoint from the container will be modified to /opt/unraid/tailscale-hook
4. The original Entrypoint and CMD from the container, alongside with the other necessary variables for Tailscale will be passed over to the container
5. When the container starts the hook script will be executed, install dependencies (currently Alpine, Arch and Debian based containers are supported), download the newest version from Tailscale and run it
6. After the first start with Tailscale the container will halt and wait for the user to click on the link which is presented in the log from the container to add the container to your Tailnet
(alternatively one could also open up a Console from the container and issue `tailscale status` which will also present the link to authenticate the container to your Tailnet)
7. The hook script will pass over the default Entrypoint and CMD which was extracted in step 2 and the container will start as usual

These steps will be repeated after Container update, force update from the Docker page and if changes in the template are made.
If the container is only Started/Restarted the hook script will detect that Tailscale is installed and only start it, if one wants to update Tailscale inside the container simply hit `force update` on the Docker page in Unraid (with Advanced View Enabled)

The integration will show a Tailscale icon on the Docker page for each Tailscale enabled Container and show some basic information from the container (Installed Tailscale Version, Online Status, Hostname, Main Relay, IPs, Exit Node, Auth Expiry,...)
When Serve or Funnel is enabled it will also generate `Tailscale WebUI` in the drop down for the container which you can open up if Tailscale is installed from the device you are accessing Unraid.
2024-10-16 22:55:53 +02:00
Tom Mortensen
95c6913c62 small chang in how rc.cgroup2unaid is invoked 2024-10-11 02:24:43 -07:00
Tom Mortensen
b783d4b207 more killall and pgrep updates. 2024-10-11 02:24:43 -07:00
Tom Mortensen
a866de833a Revert "stop ntpd from complaining about multiple IP addresses" 2024-10-11 02:24:43 -07:00
tom mortensen
ee31e35849 Merge pull request #1882 from desertwitch/fix-rsyslogd
(bugfix) /etc/rc.d/rc.rsyslogd: OS-native PID for decisions
2024-10-10 17:13:35 -07:00
tom mortensen
50e7389c8a Merge pull request #1889 from SimonFair/ntp-fix
killall and pgrep updates.
2024-10-10 17:02:59 -07:00
tom mortensen
d536ef285b Merge pull request #1890 from dlandon/delete_DS_Store_files_when_deleting_user_share
Delete all '*.DS_Store' files when deleting share.
2024-10-10 16:57:08 -07:00
dlandon
2dc82b61de Delete all '*.DS_Store' files when deleting share. 2024-10-10 16:16:09 -05:00
SimonFair
57ec7909e5 Update rc.sshd 2024-10-10 21:54:16 +01:00
SimonFair
01c6f64b52 Revert sshd loops. 2024-10-10 21:03:02 +01:00
SimonFair
80d567dfde killall and pgrep updates.
Set --ns $$ on commands.
2024-10-10 19:18:54 +01:00
tom mortensen
99d60fa08a Merge pull request #1888 from SimonFair/ntp-fix
Samba update and to use smbcontrol for reload
2024-10-10 10:37:49 -07:00
tom mortensen
8bbf176b8b Merge pull request #1887 from unraid/fix-device-list
Fix PHP error in device_list
2024-10-10 10:36:50 -07:00
tom mortensen
ca51a3799b Merge pull request #1885 from dlandon/remove_ps.txt_file_from_diagnostics
Remove ps.txt from diagnostics.
2024-10-10 10:36:28 -07:00
tom mortensen
e4bb758b05 Merge pull request #1884 from dlandon/show_delete_on_shares_settings_page_at_all_times
Delete share is always visible.
2024-10-10 10:35:59 -07:00
SimonFair
3c007fa1d0 Samba update and to use smbcontrol for reload 2024-10-10 17:17:17 +01:00
ljm42
c062e4dd9c Fix PHP error in device_list
Was preventing the main page from populating with data in some cases
2024-10-10 09:09:39 -07:00
desertwitch
06b1c9a20f /etc/rc.d/rc.rsyslogd: check status before reload 2024-10-10 07:03:48 +02:00
desertwitch
bf6d5982be /etc/rc.d/rc.rsyslogd: use pgrep, killall with PID namespace 2024-10-10 06:51:05 +02:00
dlandon
d9bd5b56c8 Remove ps.txt from diagnostics. 2024-10-09 13:08:55 -05:00
dlandon
bc7c66fec9 Delete share is always visible and enabled only when it is safe to delete the share. 2024-10-09 12:06:48 -05:00
tom mortensen
dc50e7d2c2 Merge pull request #1881 from SimonFair/ntp-fix
Set NTP to use interfaces.
2024-10-09 08:56:07 -07:00
desertwitch
0061c66dfe /etc/rc.d/rc.rsyslogd: OS-native PID for decisions
bugfix: multiple running rsyslogd processes (e.g. spawned by Docker containers) caused the rc.d script to think that the OS-native process was already running, resulting in startup failure of the OSes daemon.
2024-10-09 14:52:44 +02:00
SimonFair
91caf869f5 Set NTP to use inferfaces. 2024-10-08 21:57:48 +01:00
tom mortensen
97c3a4621b Merge pull request #1877 from dlandon/cannot_change_share_settings_if_pools_missing
Fix a situation where the secondary pool device is missing.
2024-10-08 12:18:48 -07:00
tom mortensen
1ffb22bddf Merge pull request #1880 from ich777/ich777-docker-directory-fix
Fix for docker directory
2024-10-08 12:16:44 -07:00
tom mortensen
1d9e14f07c Merge pull request #1878 from SimonFair/VM-ZFS-Fixes
Fix virtiofsd pre processor + add virtiogpu option to gui.
2024-10-08 12:14:26 -07:00
6f7b97e37a Fix for docker directory
- store path outside from function to not shorten path if users switches multiple times between `image` and `folder`
- make sure overlay2 is selected by default
2024-10-07 14:54:08 +02:00
SimonFair
ed7219d9c7 Add virtio gpu option. 2024-10-06 11:29:57 +01:00
SimonFair
082d7d842b Revert "Add virtio gu(2d) as an option of template."
This reverts commit 9a502776a1.
2024-10-06 11:26:34 +01:00
SimonFair
9a502776a1 Add virtio gu(2d) as an option of template. 2024-10-06 11:17:19 +01:00
SimonFair
03346f4709 Update virtiofsd.php 2024-10-05 07:57:19 +01:00
dlandon
1cc84832ee Fix a situation where secondary pool device is missing and the Array is chosen for the primary device.. 2024-10-03 13:57:21 -05:00
Tom Mortensen
959df7e46c fx: Agent notifications don't work if there's a problem with email notifications 2024-10-02 09:55:44 -07:00
Tom Mortensen
3fb6c2147b Small change of var name CUSTOMFA to NGINX_CUSTOMFA. 2024-10-02 09:55:44 -07:00
Tom Mortensen
3db6fa9a1d update rc.bind (but not used) 2024-10-02 09:55:44 -07:00
tom mortensen
0c5987fab0 Merge pull request #1811 from unraid/kill-samba
Forcibly kill samba if needed
2024-10-02 09:52:46 -07:00
tom mortensen
fcbc8f700e Merge pull request #1876 from dkaser/check-container-networks
fix: prevent deleting containers that are assigned as a network
2024-10-01 14:00:08 -07:00
Derek Kaser
6fd88575b2 fix: prevent deleting containers that are assigned as a network 2024-10-01 04:21:51 +00:00
tom mortensen
bff0238f88 Merge pull request #1875 from dkaser/sanitize-tsnet
feat: sanitize ts.net domain names from nginx config
2024-09-30 15:45:50 -07:00
Derek Kaser
ee7f1f4a5b feat: sanitize ts.net domain names from nginx config 2024-09-30 02:29:37 +00:00
tom mortensen
c3dd62f1d5 Merge pull request #1874 from dlandon/container_device_not_showing
Container device not showing in Dockerman.
2024-09-27 09:19:24 -07:00
tom mortensen
7c0fb18e3c Merge pull request #1873 from unraid/add-csp-frame-ancestors
Improved "Content-Security-Policy frame-ancestors" support
2024-09-27 09:15:29 -07:00
ljm42
fe2e2ff897 define CUSTOMFA in /etc/defaults/nginx 2024-09-26 16:03:36 -07:00
ljm42
74530129ae Merge branch 'master' into add-csp-frame-ancestors 2024-09-26 15:52:44 -07:00
tom mortensen
968e3b1d72 Merge pull request #1870 from dkaser/tailscale-routes
feat: show routes from all routing tables, not just default
2024-09-26 12:25:30 -07:00
tom mortensen
6bdcb38c47 Merge pull request #1869 from ich777/ich777-rc.sshd-fix
Fix for rc.sshd
2024-09-26 12:25:09 -07:00
tom mortensen
8c7cdca4aa Merge pull request #1868 from unraid/ts-redirect
redirect http TS url to https TS url
2024-09-26 12:24:42 -07:00
tom mortensen
08024a0464 Merge pull request #1867 from ich777/ich777-docker-fix
Fix for 3rd party containers "not available" colour
2024-09-26 12:24:05 -07:00
dlandon
828cd7b747 Container device not showing in Dockerman. 2024-09-26 09:13:42 -05:00
ljm42
0ce3960de6 Improved "Content-Security-Policy frame-ancestors" support 2024-09-25 23:16:52 -07:00
tom mortensen
8b91d22796 Merge pull request #1872 from ich777/ich777-rc.docker-patch
Update rc.docker
2024-09-25 10:36:52 -07:00
b716920800 Update rc.docker
- Remove ID since not necessary
2024-09-24 16:55:21 +02:00
6d749a8b1a Update rc.docker
- Further improvement to stop containers only managed by Unraid or the Compose plugin
- Small fix for the notification
2024-09-24 16:46:02 +02:00
69b95ae27d Update rc.docker
- Only stop Unraid managed containers
- Don't kill containers since Docker will kill them if they won't stop after the set timeout when the daemon is stopping
- Increase timeout for daemon to die to 30 seconds (seems a bit short if 3rd party containers are installed)
- Rephrase message for daemon to die and display it only once
2024-09-24 15:51:46 +02:00
Derek Kaser
20e29ab5af feat: show routes from all routing tables, not just default 2024-09-23 21:32:13 +00:00
a75bc3d4d7 Fix for rc.sshd
- Remove trailing slash to be sure to grab the correct PIDs
2024-09-23 22:16:02 +02:00
020ed9a07f Small fix for 3rd party containers
- Fix orange text "not available" to use the default text colour.
2024-09-23 21:19:04 +02:00
ljm42
33a73b2fb5 Forcibly kill samba if needed 2024-08-02 10:33:34 -07:00
50 changed files with 1664 additions and 183 deletions

View File

@@ -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>&nbsp;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>&nbsp;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>

View File

@@ -195,8 +195,8 @@ _(Docker directory)_:
<div markdown="1" id="backingfs_type" style="display:none">
_(Docker storage driver)_:
: <select id="DOCKER_BACKINGFS" name="DOCKER_BACKINGFS" onchange="updateBackingFS(this.value)">
<?=mk_option(_var($dockercfg,'DOCKER_BACKINGFS'), 'native', _('native'))?>
<?=mk_option(_var($dockercfg,'DOCKER_BACKINGFS'), 'overlay2', _('overlay2'))?>
<?=mk_option(_var($dockercfg,'DOCKER_BACKINGFS'), 'native', _('native'))?>
</select>
<?if ($var['fsState'] != "Started"):?>
<span id="WARNING_BACKINGFS" style="display:none;"><i class="fa fa-warning icon warning"></i>_(Only modify if this is a new installation since this can lead to unwanted behaviour!)_</span>
@@ -886,13 +886,14 @@ function btrfsScrub(path) {
}
});
}
var originalPath = $("#DOCKER_IMAGE_FILE2").val();
function updateLocation(val) {
var content1 = $("#DOCKER_IMAGE_FILE1");
var content2 = $("#DOCKER_IMAGE_FILE2");
var dropdown = $("#DOCKER_BACKINGFS");
var path = originalPath.split('/');
switch (val) {
case 'xfs':
var path = content2.val().split('/');
path.splice(-1,1);
content1.val((path.join('/') + '/docker-xfs.img'));
$('#vdisk_file').show('slow');
@@ -903,9 +904,8 @@ function updateLocation(val) {
dropdown.val('native');
break;
case 'folder':
var path = content2.val().split('/');
if (path[path.length-1]=='') path.splice(-2,2); else path.splice(-1,1);
content2.val(path.join('/'));
content2.val(path.join('/') + '/');
$('#vdisk_file').hide('slow');
$('#vdisk_dir').show('slow');
$('#backingfs_type').show('slow');
@@ -913,7 +913,6 @@ function updateLocation(val) {
content2.prop('disabled',false).trigger('change');
break;
default:
var path = content2.val().split('/');
path.splice(-1,1);
content1.val((path.join('/') + '/docker.img'));
$('#vdisk_file').show('slow');

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

View File

@@ -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")?>">
@@ -426,6 +620,9 @@ function addConfigPopup() {
Opts.Buttons += "<button type='button' onclick='removeConfig("+confNum+")'>_(Remove)_</button>";
}
Opts.Number = confNum;
if (Opts.Type == "Device") {
Opts.Target = Opts.Value;
}
newConf = makeConfig(Opts);
$("#configLocation").append(newConf);
reloadTriggers();
@@ -494,6 +691,9 @@ function editConfigPopup(num,disabled) {
}
Opts.Number = num;
if (Opts.Type == "Device") {
Opts.Target = Opts.Value;
}
newConf = makeConfig(Opts);
if (config.hasClass("config_"+Opts.Display)) {
config.html(newConf);
@@ -669,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']?>">
@@ -709,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:
@@ -897,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'))?>
@@ -1047,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"));
@@ -1139,6 +1807,9 @@ $(function() {
Opts.Buttons += "<button type='button' onclick='removeConfig("+confNum+")'>_(Remove)_</button>";
}
Opts.Number = confNum;
if (Opts.Type == "Device") {
Opts.Target = Opts.Value;
}
newConf = makeConfig(Opts);
if (Opts.Display == 'advanced' || Opts.Display == 'advanced-hide') {
$("#configLocationAdvanced").append(newConf);

View File

@@ -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');
}
@@ -937,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)?:'/???');

View File

@@ -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';
@@ -175,10 +226,99 @@ foreach ($containers as $ct) {
echo "<span style='white-space:nowrap;'><i class='fa fa-unlink'></i> "._('not available')."</span>";
} else {
echo "<div><span><i class='fa fa-docker fa-fw'/></i> 3rd Party</span></div>";
echo "<span class='orange-text' style='white-space:nowrap;'><i class='fa fa-unlink'></i> "._('not available')."</span>";
echo "<span style='white-space:nowrap;'><i class='fa fa-unlink'></i> "._('not available')."</span>";
}
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&#10060;\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 . " &#10132; " . $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&#9989;\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 &#10132; " . $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&#9989;\n";
} else {
if (!empty($TSstats["ExitNodeStatus"])) {
$TS_exit_node_status = ($TSstats["ExitNodeStatus"]["Online"]) ? "&#9989;" : "&#10060;";
$TSinfo .= "Exit Node:\t" . strstr($TSstats["ExitNodeStatus"]["TailscaleIPs"][0], '/', true) . " | Status: " . $TS_exit_node_status ."\n";
} else {
$TSinfo .= "Is Exit Node:\t&#10060;\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&#10060; 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);
?>
?>

View File

@@ -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 = [];

View File

@@ -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});
}

View File

@@ -24,7 +24,7 @@ $_SERVER['REQUEST_URI'] = "scripts";
$login_locale = _var($display,'locale');
require_once "$docroot/webGui/include/Translations.php";
exec("pgrep docker", $pid);
exec('pgrep --ns $$ docker', $pid);
if (count($pid) == 1) exit(0);
$DockerClient = new DockerClient();

View File

@@ -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();

View File

@@ -19,7 +19,7 @@ Code="e944"
?>
<?
// Remove stale /tmp/plugin/*.plg entries (check that script 'plugin' is not running to avoid clashes)
if (!exec("pgrep -f $docroot/plugins/dynamix.plugin.manager/scripts/plugin")) {
if (!exec('pgrep --ns $$ -f '."$docroot/plugins/dynamix.plugin.manager/scripts/plugin")) {
foreach (glob("/tmp/plugins/*.{plg,txt}", GLOB_NOSORT+GLOB_BRACE) as $entry) if (!file_exists("/var/log/plugins/".basename($entry))) @unlink($entry);
}
$check = $notify['version'] ? 0 : 1;

View File

@@ -1115,6 +1115,7 @@ private static $encoding = 'UTF-8';
$arrValidVNCModels = [
'cirrus' => 'Cirrus',
'qxl' => 'QXL (best)',
'virtio' => 'Virtio(2d)',
'vmvga' => 'vmvga'
];

View File

@@ -27,7 +27,9 @@
}
# Check if options file exists. Each option should be on a new line.
if (is_file($file)) $options = explode("\n",file_get_contents($file)) ; else $options = ['--syslog','--inode-file-handles=mandatory','--announce-submounts'];
$options[] = "--fd=".$argoptions['fd'];
if (isset($argoptions['fd'])) {
$options[] = "--fd=".$argoptions['fd'];
}
if (isset($argoptions['o'])) {
$virtiofsoptions = explode(',',$argoptions["o"]);

View File

@@ -69,7 +69,7 @@ $cache_type = $cache_rate = [];
$parity = _var($var,'mdResync');
$mover = file_exists('/var/run/mover.pid');
$btrfs = exec('pgrep -cf /sbin/btrfs');
$btrfs = exec('pgrep --ns $$ -cf /sbin/btrfs');
$vdisk = exec("grep -Pom1 '^DOCKER_IMAGE_TYPE=\"\\K[^\"]+' /boot/config/docker.cfg 2>/dev/null")!='folder' ? _('Docker vdisk') : _('Docker folder');
$dot = _var($display,'number','.,')[0];
$zfs = count(array_filter(array_column($disks,'fsType'),function($fs){return str_replace('luks:','',$fs??'')=='zfs';}));

View File

@@ -60,8 +60,8 @@ foreach ($ports as $ethX) {
}
}
// enable interface only when VMs and Docker are stopped
$service = exec("pgrep libvirt") ? _('VM manager') : '';
$service .= exec("pgrep docker") ? ($service ? ' '._('and').' ' : '')._('Docker service') : '';
$service = exec('pgrep --ns $$ libvirt') ? _('VM manager') : '';
$service .= exec('pgrep --ns $$ docker') ? ($service ? ' '._('and').' ' : '')._('Docker service') : '';
// eth0 port status
$no_eth0 = exec("ip link show eth0|grep -Pom1 '(NO-CARRIER|state DOWN)'");

View File

@@ -541,14 +541,10 @@ _(Mover action)_:
&nbsp;
: <input type="submit" name="cmdEditShare" value="_(Add Share)_" onclick="this.value='Add Share'"><input type="button" value="_(Done)_" onclick="done()">
<?else:?>
<div markdown="1" class="empty">
_(Delete)_<input type="checkbox" name="confirmDelete" onchange="chkDelete(this.form, document.getElementById('cmdEditShare'));">
<div markdown="1">
<label id="deleteLabel" title="">_(Delete)_</label><input type="checkbox" name="confirmDelete" onchange="chkDelete(this.form, document.getElementById('cmdEditShare'));" title="" disabled>
: <input type="submit" id="cmdEditShare" name="cmdEditShare" value="_(Apply)_" onclick="if (this.value=='_(Delete)_') this.value='Delete'; else this.value='Apply'; return handleDeleteClick(this)" disabled><input type="button" value="_(Done)_" onclick="done()">
</div>
<div markdown="1" class="full">
&nbsp;
: <input type="submit" name="cmdEditShare" value="_(Apply)_" onclick="this.value='Apply'" disabled><input type="button" value="_(Done)_" onclick="done()">
</div>
<?endif;?>
</form>
@@ -644,6 +640,7 @@ function updateScreen(cache, slow) {
secondaryDropdown.options[i].disabled = true;
}
secondaryDropdown.selectedIndex = 0;
checkRequiredSecondary = false;
if (poolsOnly) {
$('#moverDirection2').hide();
@@ -1297,13 +1294,24 @@ function handleDeleteClick(button) {
$(function() {
<?if ($name):?>
<?
$tooltip_enabled = _('Share is empty and is safe to delete');
$tooltip_disabled = _('Share must be empty to be deleted');
?>
$.post('/webGui/include/ShareList.php', { scan: "<?=$name?>" }, function(e) {
if (e == 1) {
$('.empty').show();
$('.full').hide();
/* Enable delete checkbox and update tooltip. */
$('input[name="confirmDelete"]').prop('disabled', false).attr('title', '<?= $tooltip_enabled ?>');
$('#deleteLabel').attr('title', '<?= $tooltip_enabled ?>');
} else {
$('.full1').hide();
$('.full2').show();
/* Disable delete checkbox and update tooltip. */
$('input[name="confirmDelete"]').prop('disabled', true).attr('title', '<?= $tooltip_disabled ?>');
$('#deleteLabel').attr('title', '<?= $tooltip_disabled ?>');
}
});
<?endif;?>

View File

@@ -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');

View File

@@ -40,9 +40,9 @@ default:
$file = "/var/lib/$dir/check.status.$id";
if (file_exists($file)) {
switch ($cmd) {
case 'btrfs-check': $pgrep = "pgrep -f '/sbin/btrfs check .*$dev'"; break;
case 'rfs-check': $pgrep = "pgrep -f '/sbin/reiserfsck $dev'"; break;
case 'xfs-check': $pgrep = "pgrep -f '/sbin/xfs_repair.*$dev'"; break;
case 'btrfs-check': $pgrep = 'pgrep --ns $$ -f '."'/sbin/btrfs check .*$dev'"; break;
case 'rfs-check': $pgrep = 'pgrep --ns $$ -f '."'/sbin/reiserfsck $dev'"; break;
case 'xfs-check': $pgrep = 'pgrep --ns $$ -f '."'/sbin/xfs_repair.*$dev'"; break;
}
echo file_get_contents($file);
if (!exec($pgrep)) echo "\0";

View File

@@ -263,7 +263,7 @@ function urlencode_path($path) {
return str_replace("%2F", "/", urlencode($path));
}
function pgrep($process_name, $escape_arg=true) {
$pid = exec("pgrep ".($escape_arg?escapeshellarg($process_name):$process_name), $output, $retval);
$pid = exec('pgrep --ns $$ '.($escape_arg?escapeshellarg($process_name):$process_name), $output, $retval);
return $retval==0 ? $pid : false;
}
function is_block($path) {

View File

@@ -44,10 +44,10 @@ switch ($_GET['tag']) {
case 'ttyd':
// check if ttyd already running
$sock = "/var/run/ttyd.sock";
exec("pgrep -f '$sock'", $ttyd_pid, $retval);
exec('pgrep --ns $$ -f '."'$sock'", $ttyd_pid, $retval);
if ($retval == 0) {
// check if there are any child processes, ie, curently open tty windows
exec("pgrep -P ".$ttyd_pid[0], $output, $retval);
exec('pgrep --ns $$ -P '.$ttyd_pid[0], $output, $retval);
// no child processes, restart ttyd to pick up possible font size change
if ($retval != 0) exec("kill ".$ttyd_pid[0]);
}

View File

@@ -26,8 +26,8 @@ case 'Add Route':
if ($gateway && $route) exec("/etc/rc.d/rc.inet1 ".escapeshellarg("{$gateway}_{$route}_{$metric}_add"));
break;
default:
exec("ip -4 route show|grep -v '^127.0.0.0'",$ipv4);
exec("ip -6 route show|grep -Pv '^([am:]|(f[ef][0-9][0-9])::)|expires'",$ipv6);
exec("ip -4 route show table all|grep -Pv '^(127\\.0\\.0\\.0)|table local'",$ipv4);
exec("ip -6 route show table all|grep -Pv '^([am:]|(f[ef][0-9][0-9])::)|expires|table local'",$ipv6);
foreach ($ipv4 as $info) {
$cell = explode(' ',$info);
$route = $cell[0];

View File

@@ -37,7 +37,7 @@ if (isset($_POST['scan'])) {
/* Iterate over each item in the directory and its subdirectories */
foreach ($iterator as $fileinfo) {
/* Check if the current item is a file and not a .DS_Store file */
if ($fileinfo->isFile() && $fileinfo->getFilename() !== '.DS_Store') {
if ($fileinfo->isFile() && !preg_match('/\.DS_Store$/i', $fileinfo->getFilename())) {
$hasFiles = true;
break;
}
@@ -70,7 +70,7 @@ function removeDSStoreFilesAndEmptyDirs($dir) {
);
foreach ($iterator as $file) {
if ($file->isFile() && $file->getFilename() === '.DS_Store') {
if ($file->isFile() && preg_match('/\.DS_Store$/i', $file->getFilename())) {
unlink($file->getRealPath());
}
}
@@ -87,7 +87,6 @@ if (isset($_POST['cleanup'])) {
$n = 0;
// active shares
$shares = array_map('strtolower',array_keys(parse_ini_file('state/shares.ini',true)));
// stored shares
foreach (glob("/boot/config/shares/*.cfg",GLOB_NOSORT) as $name) {
if (!in_array(strtolower(basename($name,'.cfg')),$shares)) {

View File

@@ -15,7 +15,7 @@ $docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Secure.php";
function pgrep($proc) {
return exec("pgrep -f $proc");
return exec('pgrep --ns $$ -f '."$proc");
}
if (isset($_POST['kill']) && $_POST['kill'] > 1) {

View File

@@ -268,7 +268,7 @@ function array_online(&$disk, $fstype='') {
$sum['count']++;
$sum['temp'] += $disk['temp'];
}
$sum['power'] += _var($disk,'power',0);
$sum['power'] += intval(_var($disk,'power',0));
$sum['numReads'] += _var($disk,'numReads',0);
$sum['numWrites'] += _var($disk,'numWrites',0);
$sum['numErrors'] += _var($disk,'numErrors',0);

View File

@@ -440,7 +440,6 @@ run("lsscsi -vgl 2>/dev/null|todos >".escapeshellarg("/$diag/system/lsscsi.txt")
run("lspci -knn 2>/dev/null|todos >".escapeshellarg("/$diag/system/lspci.txt"));
run("lsusb 2>/dev/null|todos >".escapeshellarg("/$diag/system/lsusb.txt"));
run("free -mth 2>/dev/null|todos >".escapeshellarg("/$diag/system/memory.txt"));
run("ps -auxf --sort=-pcpu 2>/dev/null|todos >".escapeshellarg("/$diag/system/ps.txt"));
run("lsof -Pni 2>/dev/null|todos >".escapeshellarg("/$diag/system/lsof.txt"));
run("lsmod|sort 2>/dev/null|todos >".escapeshellarg("/$diag/system/lsmod.txt"));
run("df -h 2>/dev/null|todos >".escapeshellarg("/$diag/system/df.txt"));
@@ -723,6 +722,7 @@ newline("/$diag/system/sshd.txt");
copy("/etc/nginx/conf.d/servers.conf", "/$diag/system/servers.conf.txt");
maskIP("/$diag/system/servers.conf.txt");
run("sed -Ei 's/[01234567890abcdef]+\.((my)?unraid\.net)/hash.\\1/gm;t' ".escapeshellarg("/$diag/system/servers.conf.txt")." 2>/dev/null");
run("sed -Ei 's/\.[^\.]*\.ts\.net/\.magicdns\.ts\.net/gm' ".escapeshellarg("/$diag/system/servers.conf.txt")." 2>/dev/null");
newline("/$diag/system/servers.conf.txt");
// BEGIN - third party plugins diagnostics

View File

@@ -224,7 +224,7 @@ case 'add':
$entity = $overrule===false ? $notify[$importance] : $overrule;
if (!$mailtest) file_put_contents($archive,"timestamp=$timestamp\nevent=$event\nsubject=$subject\ndescription=$description\nimportance=$importance\n".($message ? "message=".str_replace('\n','<br>',$message)."\n" : ""));
if (($entity & 1)==1 && !$mailtest && !$noBrowser) file_put_contents($unread,"timestamp=$timestamp\nevent=$event\nsubject=$subject\ndescription=$description\nimportance=$importance\nlink=$link\n");
if (($entity & 2)==2 || $mailtest) if (!generate_email($event, clean_subject($subject), str_replace('<br>','. ',$description), $importance, $message, $recipients, $fqdnlink)) exit(1);
if (($entity & 2)==2 || $mailtest) generate_email($event, clean_subject($subject), str_replace('<br>','. ',$description), $importance, $message, $recipients, $fqdnlink);
if (($entity & 4)==4 && !$mailtest) { if (is_array($agents)) {foreach ($agents as $agent) {exec("TIMESTAMP='$timestamp' EVENT=".escapeshellarg($event)." SUBJECT=".escapeshellarg(clean_subject($subject))." DESCRIPTION=".escapeshellarg($description)." IMPORTANCE=".escapeshellarg($importance)." CONTENT=".escapeshellarg($message)." LINK=".escapeshellarg($fqdnlink)." bash ".$agent);};}};
break;

View File

@@ -16,7 +16,7 @@ case "$1" in
else
echo "Not available"
fi;
pgrep -f "/sbin/reiserfsck $2" >/dev/null
pgrep --ns $$ -f "/sbin/reiserfsck $2" >/dev/null
;;
'cancel')
pkill -f "/sbin/reiserfsck $2"

View File

@@ -99,7 +99,7 @@ if [[ -x /etc/rc.d/rc.acpid && -r /var/run/acpid.pid ]]; then # quit
fi
# Kill all processes.
OMITPIDS="$(for P in $(pgrep mdmon); do echo -o $P; done)" # Don't kill mdmon
OMITPIDS="$(for P in $(pgrep --ns $$ mdmon); do echo -o $P; done)" # Don't kill mdmon
log "Sending all processes the SIGHUP signal."
run killall5 -1 $OMITPIDS
log "Waiting for processes to hang up"

View File

@@ -69,7 +69,8 @@ if /bin/grep -wq cgroup /proc/filesystems; then
# See https://docs.kernel.org/admin-guide/cgroup-v2.html (section Mounting)
# Mount cgroup2 filesystem
/sbin/mount -t cgroup2 -o rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot cgroup2 /sys/fs/cgroup
chmod +x /etc/rc.d/rc.cgroup2unraid
# Start cgroup2 cleanup daemon
/etc/rc.d/rc.cgroup2unraid start
else
# Display message if /sys/fs/cgroup does not exist
echo "/sys/fs/cgroup does not exist. cgroup2 cannot be mounted."
@@ -160,11 +161,6 @@ if [[ -x /etc/rc.d/rc.cgconfig && -x /etc/rc.d/rc.cgred && -d /sys/fs/cgroup ]];
/etc/rc.d/rc.cgred start
fi
# Start cgroup2 cleanup daemon
if [[ -x /etc/rc.d/rc.cgroup2unraid && -d /sys/fs/cgroup ]]; then
/etc/rc.d/rc.cgroup2unraid start
fi
# Create /tmp/{.ICE-unix,.X11-unix} if they are not present:
if [[ ! -e /tmp/.ICE-unix ]]; then
/bin/mkdir -p /tmp/.ICE-unix

View File

@@ -40,7 +40,7 @@ acpid_stop(){
REPLY="Already stopped"
else
run kill $(cat /var/run/acpid.pid 2>/dev/null)
run killall acpid
run killall --ns $$ acpid
if ! acpid_running; then REPLY="Stopped"; else REPLY="Failed"; fi
fi
log "$DAEMON... $REPLY."

View File

@@ -42,11 +42,13 @@ bind_start() {
mkdir -p /var/run/named
# Make sure that /var/run/named has correct ownership:
chown -R ${NAMED_USER}:${NAMED_GROUP} /var/run/named
# Make sure that /var/named has correct ownership:
chown -R ${NAMED_USER}:${NAMED_GROUP} /var/named
if [ -r /etc/rndc.key ]; then
# Make sure that /etc/rndc.key has correct ownership:
chown ${NAMED_USER}:${NAMED_GROUP} /etc/rndc.key
if [ "$NAMED_CHOWN" = "YES" ]; then
# Make sure that /var/named has correct ownership:
chown -R ${NAMED_USER}:${NAMED_GROUP} /var/named
if [ -r /etc/rndc.key ]; then
# Make sure that /etc/rndc.key has correct ownership:
chown ${NAMED_USER}:${NAMED_GROUP} /etc/rndc.key
fi
fi
# Start named:
if [ -x /usr/sbin/named ]; then
@@ -94,7 +96,7 @@ bind_stop() {
fi
# Kill named processes if there are any running:
if ps axco command | grep -q -e "^named$"; then
echo "Stopping all named processes in this namespace: /bin/killall -SIGTERM --ns \$\$ named"
echo "Stopping all named processes in this namespace: /bin/killall -SIGTERM --ns $$ named"
/bin/killall -SIGTERM --ns $$ named 2> /dev/null
fi
}

0
etc/rc.d/rc.cgroup2unraid Normal file → Executable file
View File

View File

@@ -15,7 +15,7 @@ PIDFILE="/var/run/dnsmasq.pid"
dnsmasq_running(){
sleep 0.1
pgrep -l -F $PIDFILE 2>/dev/null | grep -q dnsmasq
pgrep --ns $$ -l -F $PIDFILE 2>/dev/null | grep -q dnsmasq
}
dnsmasq_start(){

View File

@@ -127,7 +127,7 @@ all_containers(){
# Running containers
running_containers(){
docker ps --format='{{.Names}}' 2>/dev/null
docker ps --format='{{.Names}} {{.Labels}}' 2>/dev/null | grep 'net.unraid.docker.managed=' | awk '{print $1}'
}
# Network driver
@@ -531,9 +531,7 @@ docker_container_stop(){
log "Stopping containers..."
if ! docker_running; then return 1; fi
[[ -n $(running_containers) ]] && docker stop --time=${DOCKER_TIMEOUT:-10} $(running_containers) >/dev/null
# Kill containers if still running
docker kill $(docker ps -q) 2>/dev/null
log "Containers stopped."
log "Unraid managed containers stopped."
}
docker_service_start(){
@@ -568,7 +566,9 @@ docker_service_stop(){
if [[ -r $DOCKER_PIDFILE ]]; then
# Try to stop dockerd gracefully
kill $(docker_pid) 2>/dev/null
TIMER=15
# show waiting message
echo "Waiting 30 seconds for $DAEMON to die."
TIMER=30
# must ensure daemon has exited
while [[ $TIMER -gt 0 ]]; do
sleep 1
@@ -583,8 +583,6 @@ docker_service_stop(){
# signal successful stop
TIMER=-1
else
# show waiting message
echo "$DAEMON... Waiting to die."
((TIMER--))
fi
done

View File

@@ -29,7 +29,7 @@ PIDFILE="/run/elogind.pid"
elogind_running(){
sleep 0.1
pgrep -l -F $PIDFILE 2>/dev/null | grep -q elogind
pgrep --ns $$ -l -F $PIDFILE 2>/dev/null | grep -q elogind
}
elogind_start(){

View File

@@ -35,7 +35,7 @@ inetd_stop() {
if ! inetd_running; then
REPLY="Already stopped"
else
run killall inetd
run killall --ns $$ inetd
if ! inetd_running; then REPLY="Stopped"; else REPLY="Failed"; fi
fi
log "$DAEMON... $REPLY."

View File

@@ -74,7 +74,7 @@ mcelog_stop(){
if ! mcelog_running; then
REPLY="Already stopped"
else
killall -TERM $MCELOG
killall --ns $$ -TERM $MCELOG
if ! mcelog_running; then REPLY="Stopped"; else REPLY="Failed"; fi
fi
elif [[ $MCELOG_MODE == trigger && -f $TRIGGER ]]; then

View File

@@ -52,7 +52,7 @@ dbus_stop(){
else
run kill $(cat $PIDFILE)
# Just in case:
run killall dbus-daemon
run killall --ns $$ dbus-daemon
rm -f $PIDFILE
if ! dbus_running; then REPLY="Stopped"; else REPLY="Failed"; fi
fi
@@ -65,7 +65,7 @@ dbus_reload(){
pid=$(cat $PIDFILE)
run kill -HUP $pid
else
run killall -HUP dbus-daemon
run killall --ns $$ -HUP dbus-daemon
fi
log "$DAEMON... Reloaded."
}

View File

@@ -104,11 +104,11 @@ nfsd_stop(){
if ! nfsd_running; then
REPLY="Already stopped"
else
killall rpc.mountd 2>/dev/null
killall nfsd 2>/dev/null
killall --ns $$ rpc.mountd 2>/dev/null
killall --ns $$ nfsd 2>/dev/null
sleep 1
killall -9 nfsd 2>/dev/null
killall rpc.rquotad 2>/dev/null
killall --ns $$ -9 nfsd 2>/dev/null
killall --ns $$ rpc.rquotad 2>/dev/null
run $EXPORTFS -au
if ! nfsd_running; then REPLY="Stopped"; else REPLY="Failed"; fi
fi

View File

@@ -29,6 +29,11 @@ INI="/var/local/emhttp/nginx.ini.new"
CERTPATH="$SSL/certs/certificate_bundle.pem"
TSCERTPATH="$SSL/certs/ts_bundle.pem"
MYSERVERS="/boot/config/plugins/dynamix.my.servers/myservers.cfg"
DEFAULTS="/etc/default/nginx"
# Load defaults
# Defines NGINX_CUSTOMFA for custom Content-Security-Policy frame-ancestors url
[[ -r $DEFAULTS ]] && . $DEFAULTS
# hold server names
SERVER_NAMES=()
@@ -107,6 +112,7 @@ redirect(){
[[ $(ipv $ADDR) == 6 ]] && HOST="[$ADDR]"
[[ -n $HOST ]] && echo "${T}listen $HOST:$*; # $(show $ADDR)"
done
echo "${T}add_header Content-Security-Policy \"frame-ancestors 'self' $NGINX_CUSTOMFA\";"
echo "${T}return 302 https://\$host:$PORTSSL\$request_uri;"
echo "}"
;;
@@ -118,6 +124,7 @@ redirect(){
if [[ -n $HOST ]]; then
echo "server {"
echo "${T}listen $HOST:$*; # $(show $ADDR)"
echo "${T}add_header Content-Security-Policy \"frame-ancestors 'self' $NGINX_CUSTOMFA\";"
echo "${T}return 302 https://$(fqdn $ADDR)$PORTSSL_URL\$request_uri;"
echo "}"
fi
@@ -156,6 +163,7 @@ build_servers(){
server {
$(listen lo)
#
add_header Content-Security-Policy "frame-ancestors 'self' $NGINX_CUSTOMFA";
include /etc/nginx/conf.d/locations.conf;
}
EOF
@@ -171,6 +179,7 @@ build_servers(){
server {
$(listen $PORT default_server)
#
add_header Content-Security-Policy "frame-ancestors 'self' $NGINX_CUSTOMFA";
location ~ /wsproxy/$PORT/ { return 403; }
include /etc/nginx/conf.d/locations.conf;
}
@@ -184,6 +193,7 @@ build_servers(){
server {
$(listen $PORTSSL ssl default_server)
http2 on;
add_header Content-Security-Policy "frame-ancestors 'self' $SELFCERTFA $NGINX_CUSTOMFA";
# Ok to use concatenated pem files; nginx will do the right thing.
ssl_certificate $SELFCERTPATH;
ssl_certificate_key $SELFCERTPATH;
@@ -229,6 +239,7 @@ build_servers(){
server {
$(listen $PORTSSL ssl default_server)
http2 on;
add_header Content-Security-Policy "frame-ancestors 'self' $SELFCERTFA $NGINX_CUSTOMFA";
# Ok to use concatenated pem files; nginx will do the right thing.
ssl_certificate $SELFCERTPATH;
ssl_certificate_key $SELFCERTPATH;
@@ -250,6 +261,7 @@ build_servers(){
server {
$(listen $PORTSSL ssl default_server)
http2 on;
add_header Content-Security-Policy "frame-ancestors 'self' $SELFCERTFA $NGINX_CUSTOMFA";
# Ok to use concatenated pem files; nginx will do the right thing.
ssl_certificate $SELFCERTPATH;
ssl_certificate_key $SELFCERTPATH;
@@ -272,6 +284,7 @@ build_servers(){
$(listen $PORTSSL ssl)
http2 on;
server_name ${SERVER_NAMES[@]};
add_header Content-Security-Policy "frame-ancestors 'self' $CERTFA $NGINX_CUSTOMFA";
# Ok to use concatenated pem files; nginx will do the right thing.
ssl_certificate $CERTPATH;
ssl_certificate_key $CERTPATH;
@@ -305,6 +318,7 @@ build_servers(){
server {
$(listen $PORTSSL ssl http2)
server_name $TSFQDN;
add_header Content-Security-Policy "frame-ancestors 'self' $TSFA $NGINX_CUSTOMFA";
# Ok to use concatenated pem files; nginx will do the right thing.
ssl_certificate $TSCERTPATH;
ssl_certificate_key $TSCERTPATH;
@@ -529,11 +543,15 @@ build_ssl(){
fi
# determine if OCSP stapling should be enabled for this cert
[[ -n $(openssl x509 -noout -ocsp_uri -in "$SELFCERTPATH") ]] && SELFCERTSTAPLE=on || SELFCERTSTAPLE=off
# define CSP frame-ancestors for the self-signed cert
[[ -n $LOCAL_TLD ]] && [[ "$LOCAL_TLD" != "local" ]] && SELFCERTFA="https://*.$LOCAL_TLD/"
# handle Certificate Authority signed cert if present
if [[ -f $CERTPATH ]]; then
# extract common name from cert
CERTNAME=$(openssl x509 -noout -subject -nameopt multiline -in $CERTPATH | sed -n 's/ *commonName *= //p')
# define CSP frame-ancestors for cert
CERTFA="https://*.${CERTNAME#*.}/"
# check if Remote Access is enabled and fetch WANIP
if [[ -L /usr/local/sbin/unraid-api ]] && grep -qs 'wanaccess="yes"' $MYSERVERS && ! grep -qs 'username=""' $MYSERVERS; then
WANACCESS=yes
@@ -542,6 +560,8 @@ build_ssl(){
fi
if [[ $CERTNAME == *\.myunraid\.net ]]; then
# wildcard LE certificate
# add Unraid Connect to CSP frame-ancestors for a myunraid.net cert
CERTFA+=" https://connect.myunraid.net/"
[[ -n $LANIP ]] && LANFQDN=$(fqdn $LANIP) SERVER_NAMES+=($LANFQDN)
[[ -n $LANIP6 ]] && LANFQDN6=$(fqdn $LANIP6) SERVER_NAMES+=($LANFQDN6)
# check if remote access enabled
@@ -562,7 +582,7 @@ build_ssl(){
done
fi
else
# custom certificate
# custom certificate, this would be better as SELFCERTPATH
LANFQDN=${CERTNAME/\*/$LANNAME} # support wildcard custom certs
SERVER_NAMES+=($LANFQDN)
fi
@@ -578,8 +598,12 @@ build_ssl(){
TSFQDN1=$(openssl x509 -noout -subject -nameopt multiline -in "$TSCERTPATH" | sed -n 's/ *commonName *= //p')
# get tailscale domain
TSFQDN2=$($TS status -json | jq ' .Self.DNSName' | tr -d '"' | sed 's/.$//')
# if they are equal and not empty, the cert is valid, use it
[[ -n "$TSFQDN1" ]] && [[ "$TSFQDN1" == "$TSFQDN2" ]] && TSFQDN=$TSFQDN1
if [[ -n "$TSFQDN1" ]] && [[ "$TSFQDN1" == "$TSFQDN2" ]]; then
# common name and tailscale domain are equal and not empty, the cert is valid, use it
TSFQDN=$TSFQDN1
# define CSP frame-ancestors for TS cert
TSFA="https://*.${TSFQDN#*.}/"
fi
fi
fi

View File

@@ -35,7 +35,7 @@ ntpd_build(){
[[ $IPV6 == no ]] && echo "interface ignore ipv6" >>$CONF
# add listen interfaces
for NET in $BIND; do
echo "interface listen $NET # $(show $NET)" >>$CONF
echo "interface listen $(show $NET) # $NET" >>$CONF
done
fi
# add configured NTP servers
@@ -76,7 +76,7 @@ ntpd_stop(){
kill -HUP $(cat /var/run/ntpd.pid)
rm -f /var/run/ntpd.pid
else
killall -HUP -q ntpd
killall --ns $$ -HUP -q ntpd
fi
if ! ntpd_running; then REPLY="Stopped"; else REPLY="Failed"; fi
fi
@@ -93,7 +93,7 @@ ntpd_restart(){
}
ntpd_reload(){
killall -HUP -q ntpd
killall --ns $$ -HUP -q ntpd
. <(fromdos <$IDENT)
ntpd_build
$NTPD $OPTIONS 2>/dev/null
@@ -101,11 +101,8 @@ ntpd_reload(){
ntpd_update(){
# 0 = update needed, 1 = no action
# if ! ntpd_running; then exit 1; fi
# if check && [[ "$(this 'interface listen')" == "$BIND" ]]; then exit 1; else exit 0; fi
# upon network change always return 'update needed' - subsequent reload stops ntp complaining
# when multiple interfaces have same IP address (such as shim-br0).
if ntpd_running; then exit 0; else exit 1; fi
if ! ntpd_running; then exit 1; fi
if check && [[ "$(this 'interface listen')" == "$BIND" ]]; then exit 1; else exit 0; fi
}
ntpd_status(){

View File

@@ -80,13 +80,13 @@ rpc_stop(){
if ! rpc_running; then
REPLY="Already stopped"
else
killall rpc.statd 2>/dev/null
killall --ns $$ rpc.statd 2>/dev/null
sleep 1
killall rpcbind 2>/dev/null
killall --ns $$ rpcbind 2>/dev/null
sleep 1
killall -9 rpc.statd 2>/dev/null # make sure :)
killall --ns $$ -9 rpc.statd 2>/dev/null # make sure :)
sleep 1
killall -9 rpcbind 2>/dev/null # make sure :)
killall --ns $$ -9 rpcbind 2>/dev/null # make sure :)
if ! rpc_running; then REPLY="Stopped"; else REPLY="Failed"; fi
fi
log "$DAEMON... $REPLY."

View File

@@ -13,7 +13,7 @@
# Bergware - modified for Unraid OS, October 2023
DAEMON="Syslog server daemon"
PIDFILE=/var/run/rsyslogd.pid # native rsyslogd pid file
PIDFILE="/var/run/rsyslogd.pid" # native rsyslogd pid file
# run & log functions
. /etc/rc.d/rc.runlog
@@ -29,7 +29,14 @@ create_xconsole(){
rsyslogd_running(){
sleep 0.1
ps axc | grep -q ' rsyslogd'
if pgrep --ns $$ -x rsyslogd &>/dev/null; then
# Daemon is alive
return 0
else
# Daemon is dead (remove stale PID file)
[[ -f $PIDFILE ]] && rm -f "$PIDFILE"
return 1
fi
}
rsyslogd_start(){
@@ -38,7 +45,7 @@ rsyslogd_start(){
if rsyslogd_running; then
REPLY="Already started"
else
run /usr/sbin/rsyslogd -i $PIDFILE
run /usr/sbin/rsyslogd -i "$PIDFILE"
if rsyslogd_running; then REPLY="Started"; else REPLY="Failed"; fi
fi
log "$DAEMON... $REPLY."
@@ -50,8 +57,8 @@ rsyslogd_stop(){
if ! rsyslogd_running; then
REPLY="Already stopped"
else
run killall rsyslogd
rm -f $PIDFILE
run killall --ns $$ rsyslogd
sleep 2
if ! rsyslogd_running; then REPLY="Stopped"; else REPLY="Failed"; fi
fi
log "$DAEMON... $REPLY."
@@ -67,8 +74,12 @@ rsyslogd_restart(){
rsyslogd_reload(){
log "Reloading $DAEMON..."
local REPLY
REPLY="Reloaded"
[[ -f $PIDFILE ]] && run kill -HUP $(cat $PIDFILE) || REPLY="Failed"
if ! rsyslogd_running; then
REPLY="Not running"
else
REPLY="Reloaded"
run killall -HUP --ns $$ rsyslogd || REPLY="Failed"
fi
log "$DAEMON... $REPLY."
}

View File

@@ -34,7 +34,14 @@ PRIVATE="/var/lib/samba/private"
samba_running(){
sleep 0.1
[[ $(pgrep -cf $SMBD) -gt 0 ]]
[[ $(pgrep --ns $$ -cf $SMBD) -gt 0 ]]
}
samba_waitfor_shutdown(){
for i in {1..5}; do
if ! samba_running; then break; fi
sleep 1
done
}
samba_settings(){
@@ -146,9 +153,17 @@ samba_stop(){
if ! samba_running; then
REPLY="Already stopped"
else
run killall smbd nmbd wsdd2 winbindd
REPLY="Stopped"
# stop gracefully with SIGTERM
run killall --ns $$ smbd nmbd wsdd2 winbindd
samba_waitfor_shutdown
if samba_running; then
REPLY="Killed"
# stop forcibly with SIGKILL
run killall --ns $$ -SIGKILL smbd nmbd wsdd2 winbindd
samba_waitfor_shutdown
fi
if ! samba_running; then
REPLY="Stopped"
# save samba 'secrets' file if changed
if [[ -e $PRIVATE/secrets.tdb ]]; then
rm -f /tmp/emhttp/secrets.tdb
@@ -171,14 +186,12 @@ samba_restart(){
}
samba_reload(){
killall smbd nmbd wsdd2 winbindd 2>/dev/null
killall --ns $$ wsdd2 2>/dev/null
# update settings
samba_settings
# restart services
$SMBD -D 2>/dev/null
[[ $USE_NETBIOS == yes ]] && $NMBD -D 2>/dev/null
# reload services with smbcontrol
smbcontrol all reload-config 2>/dev/null
[[ $USE_WSD == yes ]] && $WSDD2 -d ${WSD2_OPT## } 2>/dev/null
$WINBINDD -D 2>/dev/null
}
samba_update(){

View File

@@ -25,7 +25,7 @@ smart_stop() {
if [ -r /run/smartd.pid ]; then
kill $(cat /run/smartd.pid)
else
killall smartd
killall --ns $$ smartd
fi
}

View File

@@ -24,13 +24,7 @@ SSH_ETC="/etc/ssh"
sshd_running(){
sleep 0.1
# get all pids from sshd
for pid in $(pgrep -f $SSHD); do
# check if a sshd is running on host system
if ! grep -qE '/docker/|/lxc/' /proc/$pid/cgroup; then
return 0
fi
done
return 1
[[ $(pgrep --ns $$ -cf $SSHD) -gt 0 ]]
}
sshd_build(){
@@ -74,18 +68,8 @@ sshd_stop(){
REPLY="Already stopped"
else
log "Stopping $DAEMON..."
# get all pids from sshd
for pid in $(pgrep -f $SSHD); do
# make sure to kill only sshd from host system
if ! grep -qE '/docker/|/lxc/' /proc/$pid/cgroup; then
kill $pid
fi
done
if ! sshd_running; then
REPLY="Stopped"
else
REPLY="Failed"
fi
killall --ns $$ sshd
if ! sshd_running; then REPLY="Stopped"; else REPLY="Failed"; fi
fi
log "$DAEMON... $REPLY."
}

View File

@@ -191,7 +191,7 @@ case "$1" in
'force-stop')
log "Stopping udevd"
udevadm control --exit
killall udevd 2>/dev/null
killall --ns $$ udevd 2>/dev/null
;;
'force-restart')
log "Restarting udevd"

View File

@@ -58,7 +58,7 @@ wsdd2_stop(){
if ! wsdd2_running; then
REPLY="Already stopped"
else
killall wsdd2
killall --ns $$ wsdd2
if ! wsdd2_running; then REPLY="Stopped"; else REPLY="Failed"; fi
fi
log "$DAEMON... $REPLY."

View File

@@ -117,7 +117,7 @@ empty() {
killtree() {
local pid=$1 child
for child in $(pgrep -P $pid); do
for child in $(pgrep --ns $$ -P $pid); do
killtree $child
done
[ $pid -ne $$ ] && kill -TERM $pid

View 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