Compare commits
278 Commits
7.0.0-beta
...
tailscale-
Author | SHA1 | Date | |
---|---|---|---|
a7ac0986a1 | |||
57edfd6d7b | |||
3a5d5f07ef | |||
13413b5d1f | |||
47a04e10c0 | |||
44e09d534c | |||
1b65b07110 | |||
da01e24ff8 | |||
a080ec364d | |||
|
95c6913c62 | ||
|
b783d4b207 | ||
|
a866de833a | ||
|
ee31e35849 | ||
|
50e7389c8a | ||
|
d536ef285b | ||
|
2dc82b61de | ||
|
57ec7909e5 | ||
|
01c6f64b52 | ||
|
80d567dfde | ||
|
99d60fa08a | ||
|
8bbf176b8b | ||
|
ca51a3799b | ||
|
e4bb758b05 | ||
|
3c007fa1d0 | ||
|
c062e4dd9c | ||
|
06b1c9a20f | ||
|
bf6d5982be | ||
|
d9bd5b56c8 | ||
|
bc7c66fec9 | ||
|
dc50e7d2c2 | ||
|
0061c66dfe | ||
|
91caf869f5 | ||
|
97c3a4621b | ||
|
1ffb22bddf | ||
|
1d9e14f07c | ||
6f7b97e37a | |||
|
ed7219d9c7 | ||
|
082d7d842b | ||
|
9a502776a1 | ||
|
03346f4709 | ||
|
1cc84832ee | ||
|
959df7e46c | ||
|
3fb6c2147b | ||
|
3db6fa9a1d | ||
|
0c5987fab0 | ||
|
fcbc8f700e | ||
|
6fd88575b2 | ||
|
bff0238f88 | ||
|
ee7f1f4a5b | ||
|
c3dd62f1d5 | ||
|
7c0fb18e3c | ||
|
fe2e2ff897 | ||
|
74530129ae | ||
|
968e3b1d72 | ||
|
6bdcb38c47 | ||
|
8c7cdca4aa | ||
|
08024a0464 | ||
|
828cd7b747 | ||
|
0ce3960de6 | ||
|
8b91d22796 | ||
b716920800 | |||
6d749a8b1a | |||
69b95ae27d | |||
|
20e29ab5af | ||
a75bc3d4d7 | |||
|
e8e5ccdf18 | ||
020ed9a07f | |||
|
8f656e87b1 | ||
|
54b1e81b38 | ||
|
4c6be23467 | ||
|
4a4983f7c5 | ||
|
dcfaa1afa0 | ||
|
e52813b626 | ||
|
760aac71df | ||
|
20ef176665 | ||
|
66d7193dab | ||
|
9c9c79b1b3 | ||
|
8aac4ee119 | ||
|
8120959c2f | ||
|
72abe50721 | ||
|
18e37ed045 | ||
|
6845c007a7 | ||
|
b844f941d0 | ||
|
5883e767aa | ||
|
efc4fa2673 | ||
|
0810fc5bd8 | ||
|
897365a5de | ||
|
8d628aad4f | ||
e3c4ff280d | |||
19de7c1979 | |||
5ec695921a | |||
171a77feec | |||
3e29f0b8b8 | |||
b62c8f5a12 | |||
e9faee0d27 | |||
|
16823d07b1 | ||
|
73705b71fa | ||
|
6b31532688 | ||
|
039c798b43 | ||
1e43abc785 | |||
|
9b1081d2e1 | ||
|
c27e018fdb | ||
|
ef5067584b | ||
|
4ea425411a | ||
|
24bdc5169d | ||
|
753d87c690 | ||
07d02f579f | |||
|
ceb97ab392 | ||
|
6a15afa2a8 | ||
|
175d24afd2 | ||
|
12828eec63 | ||
|
156599031a | ||
|
15f4138c87 | ||
|
fd6e4f1ba1 | ||
|
72a47035ef | ||
|
d57bf205fa | ||
|
16a8e7092d | ||
|
00b1f77742 | ||
|
506270e413 | ||
|
303c76d7da | ||
|
73ea1bb7b3 | ||
|
9d4ca6a2c9 | ||
49b82d0eb8 | |||
|
106f155ecc | ||
|
53704b58aa | ||
|
da9add3637 | ||
|
db77c13552 | ||
|
be22c0e1f8 | ||
|
cd9d20eaf3 | ||
|
c4afbba9bc | ||
|
16089cd927 | ||
|
1ede8e621b | ||
|
64ead9a127 | ||
|
ff80906d11 | ||
|
8981e8bb15 | ||
|
83675005d2 | ||
|
4b7f2bfcee | ||
|
72ff3c52c0 | ||
|
69e11713e5 | ||
|
67cf2db493 | ||
|
631479d27d | ||
|
28b3d2ae71 | ||
|
ec1689dc68 | ||
|
3615992dc4 | ||
d9f83cc76b | |||
|
7e6ad9512d | ||
|
d7b4dfd44b | ||
|
c62ef28fc3 | ||
|
07fa790411 | ||
|
fb19a99ad4 | ||
d0dcf6c314 | |||
0d925a2471 | |||
|
2b4eb1abad | ||
|
251881d850 | ||
|
24fce7582c | ||
|
b56f3e529c | ||
|
130e3fcd44 | ||
|
c1b2bb7435 | ||
|
840e19d322 | ||
|
2ae85fdd31 | ||
|
0db0032648 | ||
|
8d9e2a04c0 | ||
|
31f81349a8 | ||
|
3de8e05432 | ||
|
1c019c8f08 | ||
|
8d76d6f1cc | ||
|
3e17f35e19 | ||
|
6f51589547 | ||
|
b18e734381 | ||
|
b5a8223ffe | ||
|
936adea879 | ||
|
1572378824 | ||
|
0cf3585a0d | ||
|
3f103f2089 | ||
|
ed308c3a69 | ||
|
985d077af5 | ||
|
86b8b170d1 | ||
|
d351e51f58 | ||
|
53c1788580 | ||
|
0870461731 | ||
|
043d2baaf7 | ||
|
32bb9bb6d9 | ||
|
2ff8b77c9d | ||
|
654db74167 | ||
fb2b66b5b0 | |||
663665a61b | |||
|
c9333ea955 | ||
|
bad23e7647 | ||
17909f889e | |||
8cabad6f0d | |||
|
566113f86c | ||
|
21e640184e | ||
|
202196f7d3 | ||
9c2aa45751 | |||
|
33a73b2fb5 | ||
|
06735f7a3e | ||
|
e7f7ef43c0 | ||
|
c5b692e805 | ||
|
6a91f270e3 | ||
|
6d3a7a3298 | ||
f81118ffe3 | |||
f9107ebe11 | |||
76f58d2995 | |||
b4722f57aa | |||
3314860d31 | |||
9a6b62ae98 | |||
e0b1612633 | |||
1e41ac637d | |||
858a3aa999 | |||
79c484e2e5 | |||
49793ff602 | |||
342619a567 | |||
|
27047c8832 | ||
|
b850940f19 | ||
|
de3334b0d2 | ||
|
373bb9f2ee | ||
|
833194705c | ||
|
392cc77a20 | ||
|
d272bf78ac | ||
|
9430366eaf | ||
|
71dc414592 | ||
|
2b41f9a8d2 | ||
|
70c01ec454 | ||
|
7840ae6d3c | ||
|
5b009dfb39 | ||
|
5565c02f74 | ||
|
793289bc7f | ||
|
7d92761860 | ||
|
1021adc33b | ||
|
c9374f7911 | ||
|
73a17a0306 | ||
|
0729386af9 | ||
|
4b8ec6e5a3 | ||
|
ea2fa8a8db | ||
|
d7e474257c | ||
|
4b3e8f2d46 | ||
|
54a6e3dd13 | ||
|
e20f37d936 | ||
|
4c1c566e78 | ||
|
a6c50b208a | ||
|
9a23761dc8 | ||
|
359334b85a | ||
|
f5ed6964dd | ||
|
882ee7f911 | ||
|
234a749b7f | ||
|
a9891f557e | ||
|
04640a5708 | ||
|
fac92be4d4 | ||
|
817ed5c1c1 | ||
|
a4abe0fa55 | ||
|
6920542d6c | ||
|
36f9e7402c | ||
|
cfa547972e | ||
|
84b5baf402 | ||
|
1b162e2c21 | ||
|
75f0c2e4f6 | ||
|
67369622fa | ||
|
17fdfb6f50 | ||
|
9e80fb13c7 | ||
|
1d6c3f4375 | ||
58c31f4ef6 | |||
|
84b767ddcd | ||
|
32d3f88b44 | ||
|
6156a2582f | ||
|
8a55228b8e | ||
|
fe7ab1fc43 | ||
30492ed2f2 | |||
da1ef5c0e0 | |||
93054c2091 | |||
|
f2abfaf292 | ||
|
5a42314c60 | ||
|
ffabb996fc | ||
|
4a4444229e | ||
|
083cd984ea | ||
|
3dda97319d | ||
|
064cac1110 | ||
|
3d1b53d0ea | ||
|
61e99a390e |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -68,3 +68,8 @@ emhttp/plugins/dynamix.my.servers/unraid-components/index.html
|
||||
|
||||
# development scripts
|
||||
.dev-scripts/
|
||||
emhttp/plugins/node_modules/
|
||||
emhttp/plugins/.prettierignore
|
||||
emhttp/plugins/.prettierrc
|
||||
emhttp/plugins/package-lock.json
|
||||
emhttp/plugins/package.json
|
@@ -1 +0,0 @@
|
||||
/usr/libexec/unraid/firefox-119.0.r20231019122658-x86_64.AppImage
|
@@ -1 +0,0 @@
|
||||
/usr/libexec/unraid/unraidwold
|
@@ -256,7 +256,7 @@ Unraid OS uses these default options when creating a multiple-device pool:
|
||||
|
||||
`-dconvert=raid1 -mconvert=raid1`
|
||||
|
||||
For more complete documentation, please refer to the btrfs-balance [Manpage](https://btrfs.wiki.kernel.org/index.php/Manpage/btrfs-balance)
|
||||
For more complete documentation, please refer to the btrfs-balance [Manpage](https://man7.org/linux/man-pages/man8/btrfs-balance.8.html)
|
||||
|
||||
*Note: raid5 and raid6 are generally still considered **experimental** by the Linux community*
|
||||
:end
|
||||
@@ -1038,6 +1038,15 @@ You must specify a folder for Docker. The system will automatically create this
|
||||
It is recommended to create this folder under a share which resides on the Cache pool (setting: cache=Only). For best performance SSD devices are preferred.
|
||||
:end
|
||||
|
||||
:docker_storage_driver_help:
|
||||
overlay2 (default): Will use overlay2 as the storage driver for Docker, regardless of the underlying filesystem.
|
||||
|
||||
native: The native storage driver for your underlying filesystem will be used (XFS: overlay2 | ZFS: zfs | BTRFS: btrfs).
|
||||
|
||||
ATTENTION: Changing the storage type from an existing Docker installation is not possible, you have to delete your Docker directory first, change the storage type and then reinstall your containers.
|
||||
It is recommended to take a screenshot from your Docker containers before changing the storage type. After deleting and changing the storage type, reinstall the containers by clicking Add Container on the Docker page and select one by one from the drop down to reinstall them with your previous settings).
|
||||
:end
|
||||
|
||||
:docker_appdata_location_help:
|
||||
You can specify a folder to automatically generate and store subfolders containing configuration files for each Docker app (via the /config mapped volume).
|
||||
|
||||
@@ -1119,6 +1128,10 @@ This is the active Docker version.
|
||||
This is the location of the Docker image.
|
||||
:end
|
||||
|
||||
:docker_storage_driver_active_help:
|
||||
This is the storage driver for Docker.
|
||||
:end
|
||||
|
||||
:docker_appdata_location_active_help:
|
||||
This is the storage location for Docker containers.
|
||||
:end
|
||||
@@ -1270,6 +1283,14 @@ The Local Access URLs shown above are based on your current settings.
|
||||
To adjust URLs or redirects, see the help text for "Use SSL/TLS".
|
||||
:end
|
||||
|
||||
:mgmt_wg_access_urls_help:
|
||||
These URLs will only work when connected via the appropriate WireGuard tunnel as configured on ***Settings > VPN Manager***
|
||||
:end
|
||||
|
||||
:mgmt_tailscale_access_urls_help:
|
||||
These URLs will only work when connected to the appropriate Tailscale Tailnet.
|
||||
:end
|
||||
|
||||
:mgmt_certificate_expiration_help:
|
||||
**Provision** may be used to install a *free* myunraid.net SSL Certificate from
|
||||
[Let's Encrypt](https://letsencrypt.org/).
|
||||
@@ -2282,6 +2303,136 @@ Generally speaking, it is recommended to leave this setting to its default value
|
||||
IMPORTANT NOTE: If adjusting port mappings, do not modify the settings for the Container port as only the Host port can be adjusted.
|
||||
:end
|
||||
|
||||
:docker_container_network_help:
|
||||
This allows your container to utilize the network configuration of another container. Select the appropriate container from the list.<br/>This setup can be particularly beneficial if you wish to route your container's traffic through a VPN.
|
||||
:end
|
||||
|
||||
:docker_tailscale_help:
|
||||
Enable Tailscale to add this container as a machine on your Tailnet.
|
||||
:end
|
||||
|
||||
:docker_tailscale_hostname_help:
|
||||
Provide the hostname for this container. It does not need to match the container name, but it must be unique on your Tailnet. Note that an HTTPS certificate will be generated for this hostname, which means it will be placed in a public ledger, so use a name that you don't mind being public.
|
||||
For more information see <a href="https://tailscale.com/kb/1153/enabling-https" target="_blank">enabling https</a>.
|
||||
:end
|
||||
|
||||
:docker_tailscale_be_exitnode_help:
|
||||
Enable this if other machines on your Tailnet should route their Internet traffic through this container, this is most useful for containers that connect to commercial VPN services.
|
||||
Be sure to authorize this Exit Node in your <a href="https://login.tailscale.com/admin/machines" target="_blank">Tailscale Machines Admin Panel</a>.
|
||||
For more details, see the Tailscale documentation on <a href="https://tailscale.com/kb/1103/exit-nodes" target="_blank">Exit Nodes</a>.
|
||||
:end
|
||||
|
||||
:docker_tailscale_exitnode_ip_help:
|
||||
Optionally route this container's outgoing Internet traffic through an Exit Node on your Tailnet. Choose the Exit Node or input its Tailscale IP address.
|
||||
For more details, see <a href="https://tailscale.com/kb/1103/exit-nodes" target="_blank">Exit Nodes</a>.
|
||||
:end
|
||||
|
||||
:docker_tailscale_lanaccess_help:
|
||||
Only applies when this container is using an Exit Node. Enable this to allow the container to access the local network.
|
||||
|
||||
<b>WARNING:</b> Even with this feature enabled, systems on your LAN may not be able to access the container unless they have Tailscale installed.
|
||||
:end
|
||||
|
||||
:docker_tailscale_userspace_networking_help:
|
||||
When enabled, this container will operate in a restricted environment. Tailscale DNS will not work, and the container will not be able to initiate connections to other Tailscale machines. However, other machines on your Tailnet will still be able to communicate with this container.
|
||||
|
||||
When disabled, this container will have full access to your Tailnet. Tailscale DNS will work, and the container can fully communicate with other machines on the Tailnet.
|
||||
However, systems on your LAN may not be able to access the container unless they have Tailscale installed.
|
||||
:end
|
||||
|
||||
:docker_tailscale_ssh_help:
|
||||
Tailscale SSH is similar to the Docker "Console" option in the Unraid webgui, except you connect with an SSH client and authenticate via Tailscale.
|
||||
For more details, see the <a href="https://tailscale.com/kb/1193/tailscale-ssh" target="_blank">Tailscale SSH</a> documentation..
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_mode_help:
|
||||
Enabling <b>Serve</b> will automatically reverse proxy the primary web service from this container and make it available on your Tailnet using https with a valid certificate!
|
||||
|
||||
Note that when accessing the <b>Tailscale WebUI</b> url, no additional authentication layer is added beyond restricting it to your Tailnet - the container is still responsible for managing usernames/passwords that are allowed to access it. Depending on your configuration, direct access to the container may still be possible as well.
|
||||
|
||||
For more details, see the <a href="https://tailscale.com/kb/1312/serve" target="_blank">Tailscale Serve</a> documentation.
|
||||
|
||||
If the documentation recommends additional settings for a more complex use case, enable "Tailscale Show Advanced Settings". Support for these advanced settings is not available beyond confirming the commands are passed to Tailscale correctly.
|
||||
|
||||
<b>Funnel</b> is similar to <b>Serve</b>, except that the web service is made available on the open Internet. Use with care as the service will likely be attacked. As with <b>Serve</b>, the container itself is responsible for handling any authentication.
|
||||
|
||||
We recommend reading the <a href="https://tailscale.com/kb/1223/funnel" target="_blank">Tailscale Funnel</a> documentation. before enabling this feature.
|
||||
|
||||
<b>Note:</b> Enabling <b>Serve</b> or <b>Funnel</b> publishes the Tailscale hostname to a public ledger.
|
||||
For more details, see the Tailscale Documentation: <a href="https://tailscale.com/kb/1153/enabling-https" target="_blank">Enabling HTTPS</a>.
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_port_help:
|
||||
This field should specify the port for the primary web service this container offers. Note: it should specify the port in the container, not a port that was remapped on the host.
|
||||
|
||||
The system attempted to determine the correct port automatically. If it used the wrong value then there is likely an issue with the "Web UI" field for this container, visible by switching from "Basic View" to "Advanced View" in the upper right corner of this page.
|
||||
|
||||
In most cases this port is all you will need to specify in order to Serve the website in this container, although additional options are available below for more complex containers.
|
||||
|
||||
This value is passed to the `<serve_port>` portion of this command which starts serve or funnel:<br>
|
||||
`tailscale [serve|funnel] --bg --<protocol><protocol_port><path> http://localhost:`**`<serve_port>`**`<local_path>`<br>
|
||||
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_show_advanced_help:
|
||||
Here there be dragons!
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_local_path_help:
|
||||
When not specified, this value defaults to an empty string. It is passed to the `<local_path>` portion of this command which starts serve or funnel:<br>
|
||||
`tailscale [serve|funnel] --bg --<protocol><protocol_port><path> http://localhost:<serve_port>`**`<local_path>`**<br>
|
||||
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_protocol_help:
|
||||
When not specified, this value defaults to "https". It is passed to the `<protocol>` portion of this command which starts serve or funnel:<br>
|
||||
`tailscale [serve|funnel] --bg --`**`<protocol>`**`=<protocol_port><path> http://localhost:<serve_port><local_path>`<br>
|
||||
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_protocol_port_help:
|
||||
When not specified, this value defaults to "=443". It is passed to the `<protocol_port>` portion of this command which starts serve or funnel:<br>
|
||||
`tailscale [serve|funnel] --bg --<protocol>`**`<protocol_port>`**`<path> http://localhost:<serve_port><local_path>`<br>
|
||||
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_path_help:
|
||||
When not specified, this value defaults to an empty string. It is passed to the `<path>` portion of this command which starts serve or funnel:<br>
|
||||
`tailscale [serve|funnel] --bg --<protocol><protocol_port>`**`<path>`** `http://localhost:<serve_port><local_path>`<br>
|
||||
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_webui_help:
|
||||
If <b>Serve</b> is enabled this will be an https url with a proper domain name that is accessible over your Tailnet, no port needed!
|
||||
|
||||
If <b>Funnel</b> is enabled the same url will be available on the Internet.
|
||||
|
||||
If they are disabled then the url will be generated from the container's main "Web UI" field, but modified to use the Tailscale IP. If the wrong port is specified here then switch from "Basic View" to "Advanced View" and review the "Web UI" field for this container.
|
||||
:end
|
||||
|
||||
:docker_tailscale_advertise_routes_help:
|
||||
If desired, specify any routes that should be passed to the **`--advertise-routes=`** parameter when running **`tailscale up`**.
|
||||
For more details see the <a href="https://tailscale.com/kb/1019/subnets#connect-to-tailscale-as-a-subnet-router" target="_blank">Subnet routers</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_daemon_extra_params_help:
|
||||
Specify any extra parameters to pass when starting **`tailscaled`**.
|
||||
For more details see the <a href="https://tailscale.com/kb/1278/tailscaled" target="_blank">tailscaled</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_extra_param_help:
|
||||
Specify any extra parameters to pass when running **`tailscale up`**.
|
||||
For more details see the <a href="https://tailscale.com/kb/1080/cli#up" target="_blank">Tailscale CLI</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_statedir_help:
|
||||
If state directory detection fails on startup, you can specify a persistent directory in the container to override automatic detection.
|
||||
:end
|
||||
|
||||
:docker_tailscale_troubleshooting_packages_help:
|
||||
Enable this to install `ping`, `nslookup`, and `curl` into the container to help troubleshoot networking issues. Once the issues are resolved we recommend disabling this to reduce the size of the container.
|
||||
:end
|
||||
|
||||
:docker_privileged_help:
|
||||
For containers that require the use of host-device access directly or need full exposure to host capabilities, this option will need to be selected.
|
||||
For more information, see this link: <a href="https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities" target="_blank">https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities</a>
|
||||
@@ -2415,7 +2566,7 @@ Note to Cloudflare users: the Cloudflare proxy is designed for http traffic, it
|
||||
|
||||
:wg_local_server_uses_nat_help:
|
||||
When NAT is enabled, the server uses its own LAN address when forwarding traffic from the tunnel to other devices in the LAN network.
|
||||
Use this setting when no router modifications are desired, but this approach doesn't work with Docker containers using custom IP addressess.
|
||||
Use this setting when no router modifications are desired, but this approach doesn't work with Docker containers using custom IP addresses.
|
||||
|
||||
When NAT is disabled, the server uses the WireGuard tunnel address when forwarding traffic.
|
||||
In this case it is required that the default gateway (router) has a static route configured to refer tunnel address back to the server.
|
||||
|
@@ -14,10 +14,15 @@ Tag="battery-3"
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
?>
|
||||
<script>
|
||||
function getUPSstatus() {
|
||||
$.post('/plugins/dynamix.apcupsd/include/UPSstatus.php',{level:<?=$cfg['BATTERYLEVEL']?>,runtime:<?=$cfg['MINUTES']?>},function(data) {
|
||||
var batteryLevel = "<?= _var($cfg,'BATTERYLEVEL',0) ?>";
|
||||
var batteryRuntime = "<?= _var($cfg,'MINUTES',0) ?>";
|
||||
|
||||
$.post('/plugins/dynamix.apcupsd/include/UPSstatus.php',{level:batteryLevel,runtime:batteryRuntime},function(data) {
|
||||
data = data.split('\n');
|
||||
$('#ups_summary').html(data[0]);
|
||||
$('#ups_status').html(data[1]);
|
||||
|
@@ -31,8 +31,8 @@ $cpus = cpu_list();
|
||||
<link type="text/css" rel="stylesheet" href="<?autov("/plugins/dynamix.docker.manager/styles/style-$theme.css")?>">
|
||||
|
||||
<table id="docker_containers" class="tablesorter shift">
|
||||
<thead><tr><th><a id="resetsort" class="nohand" onclick="resetSorting()" title="_(Reset sorting)_"><i class="fa fa-th-list"></i></a>_(Application)_</th><th>_(Version)_</th><th>_(Network)_</th><th>_(Port Mappings)_ <small>(_(App to Host)_)</small></th><th>_(Volume Mappings)_ <small>(_(App to Host)_)</small></th><th class="load advanced">_(CPU & Memory load)_</th><th class="nine">_(Autostart)_</th><th class="five">_(Uptime)_</th></tr></thead>
|
||||
<tbody id="docker_list"><tr><td colspan='8'></td></tr></tbody>
|
||||
<thead><tr><th><a id="resetsort" class="nohand" onclick="resetSorting()" title="_(Reset sorting)_"><i class="fa fa-th-list"></i></a>_(Application)_</th><th>_(Version)_</th><th>_(Network)_</th><th>_(Container IP)_</th><th>_(Container Port)_</th><th>_(LAN IP:Port)_</th><th>_(Volume Mappings)_ <small>(_(App to Host)_)</small></th><th class="load advanced">_(CPU & Memory load)_</th><th class="nine">_(Autostart)_</th><th class="five">_(Uptime)_</th></tr></thead>
|
||||
<tbody id="docker_list"><tr><td colspan='9'></td></tr></tbody>
|
||||
</table>
|
||||
<input type="button" onclick="addContainer()" value="_(Add Container)_" style="display:none">
|
||||
<input type="button" onclick="startAll()" value="_(Start All)_" style="display:none">
|
||||
@@ -183,3 +183,4 @@ window.onunload = function(){
|
||||
dockerload.stop();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@@ -103,6 +103,9 @@ function base_net($route) {
|
||||
return substr(explode('/',$route)[0],0,-2);
|
||||
}
|
||||
$bgcolor = strstr('white,azure',$display['theme']) ? '#f2f2f2' : '#1c1c1c';
|
||||
|
||||
//Check if docker.cfg not exists
|
||||
$no_dockercfg = !is_file('/boot/config/docker.cfg');
|
||||
?>
|
||||
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.filetree.css')?>">
|
||||
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.switchbutton.css')?>">
|
||||
@@ -188,6 +191,23 @@ _(Docker directory)_:
|
||||
:docker_vdisk_directory_help:
|
||||
|
||||
</div>
|
||||
|
||||
<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'), '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>
|
||||
<?elseif (is_dir(_var($dockercfg,'DOCKER_IMAGE_FILE'))):?>
|
||||
<span id="WARNING_BACKINGFS" style="display:none;"><i class="fa fa-warning icon warning"></i>_(Switching the Storage Driver requires to delete and rebuild the Docker directory manually!)_</span>
|
||||
<?endif;?>
|
||||
|
||||
:docker_storage_driver_help:
|
||||
|
||||
</div>
|
||||
|
||||
_(Default appdata storage location)_:
|
||||
: <input type="text" id="DOCKER_APP_CONFIG_PATH" name="DOCKER_APP_CONFIG_PATH" autocomplete="off" spellcheck="false" value="<?=_var($dockercfg,'DOCKER_APP_CONFIG_PATH')?>" placeholder="_(e.g.)_ /mnt/user/appdata/" data-pickfilter="HIDE_FILES_FILTER" data-pickroot="/mnt" data-pickfolders="true" pattern="^[^\\]*/$">
|
||||
<?if ($var['fsState'] != "Started"):?>
|
||||
@@ -418,6 +438,7 @@ _(IPv6 custom network on interface)_ <?=$network?> (_(optional)_):
|
||||
</div>
|
||||
<?else: /* DOCKER STARTED */?>
|
||||
|
||||
|
||||
_(Docker version)_:
|
||||
: <?$arrInfo = $DockerClient->getInfo(); echo $arrInfo['Version']?>
|
||||
|
||||
@@ -433,6 +454,11 @@ _(Docker directory)_:
|
||||
|
||||
:docker_vdisk_location_active_help:
|
||||
|
||||
_(Docker storage driver)_:
|
||||
: <?=_var($dockercfg,'DOCKER_BACKINGFS')?>
|
||||
|
||||
:docker_storage_driver_active_help:
|
||||
|
||||
_(Default appdata storage location)_:
|
||||
: <?=_var($dockercfg,'DOCKER_APP_CONFIG_PATH')?>
|
||||
|
||||
@@ -592,6 +618,9 @@ function prepareDocker(form) {
|
||||
$(form).find('input[name="DOCKER_IMAGE_FILE"]').val($('#DOCKER_IMAGE_TYPE').val()=='folder' ? $("#DOCKER_IMAGE_FILE2").val() : $("#DOCKER_IMAGE_FILE1").val());
|
||||
$("#DOCKER_IMAGE_FILE1").prop('disabled',true);
|
||||
$("#DOCKER_IMAGE_FILE2").prop('disabled',true);
|
||||
<?if ($no_dockercfg):?>
|
||||
$(form).find('input[name="DOCKER_BACKINGFS"]').val($('#DOCKER_IMAGE_TYPE').val()=='folder' ? $("#DOCKER_BACKINGFS").val() : $("#DOCKER_BACKINGFS").val('native'));
|
||||
<?endif;?>
|
||||
<?endif;?>
|
||||
$(form).find('input:hidden[name^="DOCKER_DHCP_"]').each(function(){
|
||||
var id = '#'+$(this).attr('name')+'_';
|
||||
@@ -857,39 +886,54 @@ 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');
|
||||
$('#vdisk_dir').hide('slow');
|
||||
$('#backingfs_type').hide();
|
||||
content1.prop('disabled',false).trigger('change');
|
||||
content2.prop('disabled',true);
|
||||
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('/') + '/docker/');
|
||||
content2.val(path.join('/') + '/');
|
||||
$('#vdisk_file').hide('slow');
|
||||
$('#vdisk_dir').show('slow');
|
||||
$('#backingfs_type').show('slow');
|
||||
content1.prop('disabled',true);
|
||||
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');
|
||||
$('#vdisk_dir').hide('slow');
|
||||
$('#backingfs_type').hide();
|
||||
content1.prop('disabled',false).trigger('change');
|
||||
content2.prop('disabled',true);
|
||||
dropdown.val('native');
|
||||
break;
|
||||
}
|
||||
}
|
||||
function updateBackingFS(val) {
|
||||
var backingfs = "<?= _var($dockercfg,'DOCKER_BACKINGFS') ?>";
|
||||
var warning = document.getElementById("WARNING_BACKINGFS");
|
||||
var checkbox = $(".deleteCheckbox");
|
||||
if (val !== backingfs) {
|
||||
warning.style.display = "inline";
|
||||
} else {
|
||||
warning.style.display = "none";
|
||||
}
|
||||
}
|
||||
function checkbox_state(value) {
|
||||
$.post('/plugins/dynamix.docker.manager/include/UpdateConfig.php',{action:'exist',name:value},function(state){state==0 ? $('.deleteLabel').fadeIn() : $('.deleteLabel').fadeOut();});
|
||||
}
|
||||
@@ -897,6 +941,7 @@ $(function() {
|
||||
<?if ($DockerStopped):?>
|
||||
if ($('#DOCKER_IMAGE_TYPE').val()=='folder') {
|
||||
$('#vdisk_dir').show();
|
||||
$('#backingfs_type').show();
|
||||
checkbox_state($("#DOCKER_IMAGE_FILE2").val());
|
||||
$("#DOCKER_IMAGE_FILE2").prop('disabled',false);
|
||||
} else {
|
||||
|
@@ -9,3 +9,4 @@ DOCKER_ALLOW_ACCESS=""
|
||||
DOCKER_TIMEOUT=10
|
||||
DOCKER_READMORE="yes"
|
||||
DOCKER_PID_LIMIT=""
|
||||
DOCKER_BACKINGFS="overlay2"
|
||||
|
BIN
emhttp/plugins/dynamix.docker.manager/images/tailscale.png
Executable file
BIN
emhttp/plugins/dynamix.docker.manager/images/tailscale.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 300 KiB |
@@ -141,11 +141,24 @@ if (isset($_POST['contName'])) {
|
||||
@unlink("$userTmplDir/my-$existing.xml");
|
||||
}
|
||||
}
|
||||
// Extract real Entrypoint and Cmd from container for Tailscale
|
||||
if (isset($_POST['contTailscale']) && $_POST['contTailscale'] == 'on') {
|
||||
// Create preliminary base container but don't run it
|
||||
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker create --name '" . escapeshellarg($Name) . "' '" . escapeshellarg($Repository) . "'");
|
||||
// Get Entrypoint and Cmd from docker inspect
|
||||
$containerInfo = $DockerClient->getContainerDetails($Name);
|
||||
$ts_env = isset($containerInfo['Config']['Entrypoint']) ? '-e ORG_ENTRYPOINT="' . implode(' ', $containerInfo['Config']['Entrypoint']) . '" ' : '';
|
||||
$ts_env .= isset($containerInfo['Config']['Cmd']) ? '-e ORG_CMD="' . implode(' ', $containerInfo['Config']['Cmd']) . '" ' : '';
|
||||
// Insert Entrypoint and Cmd to docker command
|
||||
$cmd = str_replace('-l net.unraid.docker.managed=dockerman', $ts_env . '-l net.unraid.docker.managed=dockerman' , $cmd);
|
||||
// Remove preliminary container
|
||||
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker rm '" . escapeshellarg($Name) . "'");
|
||||
}
|
||||
if ($startContainer) $cmd = str_replace('/docker create ', '/docker run -d ', $cmd);
|
||||
execCommand($cmd);
|
||||
if ($startContainer) addRoute($Name); // add route for remote WireGuard access
|
||||
|
||||
echo '<div style="text-align:center"><button type="button" onclick="done()">'._('Done').'</button></div><br>';
|
||||
echo '<div style="text-align:center"><button type="button" onclick="openTerminal(\'docker\',\''.addslashes($Name).'\',\'.log\')">'._('View Container Log').'</button> <button type="button" onclick="done()">'._('Done').'</button></div><br>';
|
||||
goto END;
|
||||
}
|
||||
|
||||
@@ -169,6 +182,9 @@ if (isset($_GET['updateContainer'])){
|
||||
$xml = file_get_contents($tmpl);
|
||||
[$cmd, $Name, $Repository] = xmlToCommand($tmpl);
|
||||
$Registry = getXmlVal($xml, "Registry");
|
||||
$ExtraParams = getXmlVal($xml, "ExtraParams");
|
||||
$Network = getXmlVal($xml, "Network");
|
||||
$TS_Enabled = getXmlVal($xml, "TailscaleEnabled");
|
||||
$oldImageID = $DockerClient->getImageID($Repository);
|
||||
// pull image
|
||||
if ($echo && !pullImage($Name, $Repository)) continue;
|
||||
@@ -182,8 +198,39 @@ if (isset($_GET['updateContainer'])){
|
||||
// attempt graceful stop of container first
|
||||
stopContainer($Name, false, $echo);
|
||||
}
|
||||
// check if network from another container is specified in xml (Network & ExtraParams)
|
||||
if (preg_match('/^container:(.*)/', $Network)) {
|
||||
$Net_Container = str_replace("container:", "", $Network);
|
||||
} else {
|
||||
preg_match("/--(net|network)=container:[^\s]+/", $ExtraParams, $NetworkParam);
|
||||
if (!empty($NetworkParam[0])) {
|
||||
$Net_Container = explode(':', $NetworkParam[0])[1];
|
||||
$Net_Container = str_replace(['"', "'"], '', $Net_Container);
|
||||
}
|
||||
}
|
||||
// check if the container still exists from which the network should be used, if it doesn't exist any more recreate container with network none and don't start it
|
||||
if (!empty($Net_Container)) {
|
||||
$Net_Container_ID = $DockerClient->getContainerID($Net_Container);
|
||||
if (empty($Net_Container_ID)) {
|
||||
$cmd = str_replace('/docker run -d ', '/docker create ', $cmd);
|
||||
$cmd = preg_replace("/--(net|network)=(['\"]?)container:[^'\"]+\\2/", "--network=none ", $cmd);
|
||||
}
|
||||
}
|
||||
// force kill container if still running after time-out
|
||||
if (empty($_GET['communityApplications'])) removeContainer($Name, $echo);
|
||||
// Extract real Entrypoint and Cmd from container for Tailscale
|
||||
if ($TS_Enabled == 'true') {
|
||||
// Create preliminary base container but don't run it
|
||||
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker create --name '" . escapeshellarg($Name) . "' '" . escapeshellarg($Repository) . "'");
|
||||
// Get Entrypoint and Cmd from docker inspect
|
||||
$containerInfo = $DockerClient->getContainerDetails($Name);
|
||||
$ts_env = isset($containerInfo['Config']['Entrypoint']) ? '-e ORG_ENTRYPOINT="' . implode(' ', $containerInfo['Config']['Entrypoint']) . '" ' : '';
|
||||
$ts_env .= isset($containerInfo['Config']['Cmd']) ? '-e ORG_CMD="' . implode(' ', $containerInfo['Config']['Cmd']) . '" ' : '';
|
||||
// Insert Entrypoint and Cmd to docker command
|
||||
$cmd = str_replace('-l net.unraid.docker.managed=dockerman', $ts_env . '-l net.unraid.docker.managed=dockerman' , $cmd);
|
||||
// Remove preliminary container
|
||||
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker rm '" . escapeshellarg($Name) . "'");
|
||||
}
|
||||
execCommand($cmd, $echo);
|
||||
if ($startContainer) addRoute($Name); // add route for remote WireGuard access
|
||||
$DockerClient->flushCaches();
|
||||
@@ -213,6 +260,9 @@ if (isset($_GET['xmlTemplate'])) {
|
||||
if (is_file($xmlTemplate)) {
|
||||
$xml = xmlToVar($xmlTemplate);
|
||||
$templateName = $xml['Name'];
|
||||
if (preg_match('/^container:(.*)/', $xml['Network'])) {
|
||||
$xml['Network'] = explode(':', $xml['Network'], 2);
|
||||
}
|
||||
if ($xmlType == 'default') {
|
||||
if (!empty($dockercfg['DOCKER_APP_CONFIG_PATH']) && file_exists($dockercfg['DOCKER_APP_CONFIG_PATH'])) {
|
||||
// override /config
|
||||
@@ -269,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")?>">
|
||||
@@ -423,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();
|
||||
@@ -491,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);
|
||||
@@ -666,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']?>">
|
||||
@@ -706,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:
|
||||
|
||||
@@ -858,6 +1071,7 @@ _(Network Type)_:
|
||||
: <select name="contNetwork" onchange="showSubnet(this.value)">
|
||||
<?=mk_option(1,'bridge',_('Bridge'))?>
|
||||
<?=mk_option(1,'host',_('Host'))?>
|
||||
<?=mk_option(1,'container',_('Container'))?>
|
||||
<?=mk_option(1,'none',_('None'))?>
|
||||
<?foreach ($custom as $network):?>
|
||||
<?$name = $network;
|
||||
@@ -881,6 +1095,275 @@ _(Fixed IP address)_ (_(optional)_):
|
||||
:docker_fixed_ip_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="netCONT noshow">
|
||||
_(Container Network)_:
|
||||
: <select name="netCONT" id="netCONT">
|
||||
<?php
|
||||
foreach ($DockerClient->getDockerContainers() as $ct) {
|
||||
if ($ct['Name'] !== $xml['Name']) {
|
||||
$list[] = $ct['Name'];
|
||||
echo mk_option($ct['Name'], $ct['Name'], $ct['Name']);
|
||||
}
|
||||
}
|
||||
?>
|
||||
</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'))?>
|
||||
@@ -1013,9 +1496,222 @@ function showSubnet(bridge) {
|
||||
if (bridge.match(/^(bridge|host|none)$/i) !== null) {
|
||||
$('.myIP').hide();
|
||||
$('input[name="contMyIP"]').val('');
|
||||
$('.netCONT').hide();
|
||||
$('#netCONT').val('');
|
||||
} else if (bridge.match(/^(container)$/i) !== null) {
|
||||
$('.netCONT').show();
|
||||
$('#netCONT').val('<?php echo $xml['Network'][1]; ?>');
|
||||
$('.myIP').hide();
|
||||
$('input[name="contMyIP"]').val('');
|
||||
} else {
|
||||
$('.myIP').show();
|
||||
$('#myIP').html('Subnet: '+subnet[bridge]);
|
||||
$('.netCONT').hide();
|
||||
$('#netCONT').val('');
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1111,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);
|
||||
|
@@ -49,11 +49,11 @@ $port = file_exists('/sys/class/net/br0') ? 'BR0' : (file_exists('/sys/class/net
|
||||
// Docker configuration file - guaranteed to exist
|
||||
$docker_cfgfile = '/boot/config/docker.cfg';
|
||||
if (file_exists($docker_cfgfile)) {
|
||||
exec("grep -Pom2 '_SUBNET_|_{$port}(_[0-9]+)?=' $docker_cfgfile",$cfg);
|
||||
if (isset($cfg[0]) && $cfg[0]=='_SUBNET_' && empty($cfg[1])) {
|
||||
# interface has changed, update configuration
|
||||
exec("sed -ri 's/_(BR0|BOND0|ETH0)(_[0-9]+)?=/_{$port}\\2=/' $docker_cfgfile");
|
||||
}
|
||||
exec("grep -Pom2 '_SUBNET_|_{$port}(_[0-9]+)?=' $docker_cfgfile",$cfg);
|
||||
if (isset($cfg[0]) && $cfg[0]=='_SUBNET_' && empty($cfg[1])) {
|
||||
# interface has changed, update configuration
|
||||
exec("sed -ri 's/_(BR0|BOND0|ETH0)(_[0-9]+)?=/_{$port}\\2=/' $docker_cfgfile");
|
||||
}
|
||||
}
|
||||
|
||||
$defaults = (array)@parse_ini_file("$docroot/plugins/dynamix.docker.manager/default.cfg");
|
||||
@@ -255,7 +255,7 @@ class DockerTemplates {
|
||||
$doc = new DOMDocument();
|
||||
$doc->load($file['path']);
|
||||
if ($name) {
|
||||
if ($doc->getElementsByTagName('Name')->item(0)->nodeValue !== $name) continue;
|
||||
if (@$doc->getElementsByTagName('Name')->item(0)->nodeValue !== $name) continue;
|
||||
}
|
||||
$TemplateRepository = DockerUtil::ensureImageTag($doc->getElementsByTagName('Repository')->item(0)->nodeValue??'');
|
||||
if ($TemplateRepository && $TemplateRepository==$Repository) {
|
||||
@@ -292,13 +292,24 @@ 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 $dockerManPaths, $host;
|
||||
global $driver, $dockerManPaths, $host;
|
||||
$DockerClient = new DockerClient();
|
||||
$DockerUpdate = new DockerUpdate();
|
||||
//$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'];
|
||||
@@ -307,9 +318,8 @@ class DockerTemplates {
|
||||
$tmp['paused'] = $ct['Paused'];
|
||||
$tmp['autostart'] = in_array($name,$autoStart);
|
||||
$tmp['cpuset'] = $ct['CPUset'];
|
||||
$tmp['url'] = $tmp['url'] ?? '';
|
||||
$tmp['url'] = $ct['Url'] ?? $tmp['url'] ?? '';
|
||||
// read docker label for WebUI & Icon
|
||||
if (isset($ct['Url']) && !$tmp['url']) $tmp['url'] = $ct['Url'];
|
||||
if (isset($ct['Icon'])) $tmp['icon'] = $ct['Icon'];
|
||||
if (isset($ct['Shell'])) $tmp['shell'] = $ct['Shell'];
|
||||
if (!$communityApplications) {
|
||||
@@ -322,8 +332,51 @@ class DockerTemplates {
|
||||
// non-templated webui, user specified
|
||||
$tmp['url'] = $webui;
|
||||
} else {
|
||||
$ip = ($ct['NetworkMode']=='host'||_var($port,'NAT')) ? $host : _var($port,'IP');
|
||||
if ($ct['NetworkMode']=='host') {
|
||||
$ip = $host;
|
||||
} elseif (isset($driver[$ct['NetworkMode']]) && ($driver[$ct['NetworkMode']] == 'ipvlan' || $driver[$ct['NetworkMode']] == 'macvlan')) {
|
||||
$ip = reset($ct['Networks'])['IPAddress'];
|
||||
} elseif (!is_null(_var($port,'PublicPort'))) {
|
||||
$ip = $host;
|
||||
} else {
|
||||
$ip = _var($port,'IP');
|
||||
}
|
||||
$tmp['url'] = $ip ? (strpos($tmp['url'],$ip)!==false ? $tmp['url'] : $this->getControlURL($ct, $ip, $tmp['url'])) : $tmp['url'];
|
||||
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');
|
||||
@@ -338,8 +391,12 @@ class DockerTemplates {
|
||||
$tmp['updated'] = var_export($DockerUpdate->getUpdateStatus($image),true);
|
||||
}
|
||||
if (!$com) $tmp['updated'] = 'undef';
|
||||
if (empty($tmp['template']) || $reload) $tmp['template'] = $this->getUserTemplate($name);
|
||||
if ($reload) $DockerUpdate->updateUserTemplate($name);
|
||||
if ($ct['Manager'] !== 'dockerman')
|
||||
$tmp['template'] = null;
|
||||
else if (empty($tmp['template']) || $reload) {
|
||||
$tmp['template'] = $this->getUserTemplate($name);
|
||||
if ($reload) $DockerUpdate->updateUserTemplate($name);
|
||||
}
|
||||
//$this->debug("\n$name");
|
||||
//foreach ($tmp as $c => $d) $this->debug(sprintf(' %-10s: %s', $c, $d));
|
||||
}
|
||||
@@ -897,7 +954,7 @@ class DockerClient {
|
||||
}
|
||||
|
||||
public function getDockerContainers() {
|
||||
global $driver;
|
||||
global $driver, $host;
|
||||
// Return cached values
|
||||
if (is_array($this::$containersCache)) return $this::$containersCache;
|
||||
$this::$containersCache = [];
|
||||
@@ -916,29 +973,59 @@ class DockerClient {
|
||||
$c['Created'] = $this->humanTiming($ct['Created']);
|
||||
$c['NetworkMode'] = $ct['HostConfig']['NetworkMode'];
|
||||
$c['Manager'] = $info['Config']['Labels']['net.unraid.docker.managed'] ?? false;
|
||||
if ($c['Manager'] == 'composeman') {
|
||||
$c['ComposeProject'] = $info['Config']['Labels']['com.docker.compose.project'];
|
||||
}
|
||||
[$net, $id] = array_pad(explode(':',$c['NetworkMode']),2,'');
|
||||
$c['CPUset'] = $info['HostConfig']['CpusetCpus'];
|
||||
$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['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)?:'/???');
|
||||
if (isset($driver[$c['NetworkMode']])) {
|
||||
if ($driver[$c['NetworkMode']]=='bridge') {
|
||||
$ports = &$info['HostConfig']['PortBindings'];
|
||||
$nat = true;
|
||||
} elseif ($driver[$c['NetworkMode']]=='host') {
|
||||
$c['Ports']['host'] = ['host' => ''];
|
||||
} elseif ($driver[$c['NetworkMode']]=='ipvlan' || $driver[$c['NetworkMode']]=='macvlan') {
|
||||
$i = $ct['NetworkSettings']['Networks'][$c['NetworkMode']]['IPAddress'];
|
||||
$c['Ports']['vlan'] = ["$i" => $i];
|
||||
} else {
|
||||
$ports = &$info['Config']['ExposedPorts'];
|
||||
$nat = false;
|
||||
}
|
||||
$ip = $ct['NetworkSettings']['Networks'][$c['NetworkMode']]['IPAddress'];
|
||||
} else if (!$id) {
|
||||
$c['NetworkMode'] = DockerUtil::ctMap($c['NetworkMode']);
|
||||
$ports = &$info['Config']['ExposedPorts'];
|
||||
}
|
||||
foreach($ct['NetworkSettings']['Networks'] as $netName => $netVals) {
|
||||
$i = $c['NetworkMode']=='host' ? $host : $netVals['IPAddress'];
|
||||
$c['Networks'][$netName] = [ 'IPAddress' => $i ];
|
||||
if ($driver[$netName]=='ipvlan' || $driver[$netName]=='macvlan') {
|
||||
if (!isset($c['Ports']['vlan'])) $c['Ports']['vlan'] = [];
|
||||
$c['Ports']['vlan']["$i"] = $i;
|
||||
}
|
||||
}
|
||||
$ip = $c['NetworkMode']=='host' ? $host : $ct['NetworkSettings']['Networks'][$c['NetworkMode']]['IPAddress'] ?? null;
|
||||
$c['Networks'][$c['NetworkMode']] = [ 'IPAddress' => $ip ];
|
||||
$ports = (isset($ports) && is_array($ports)) ? $ports : [];
|
||||
foreach ($ports as $port => $value) {
|
||||
if (!isset($info['HostConfig']['PortBindings'][$port])) {
|
||||
continue;
|
||||
}
|
||||
[$PrivatePort, $Type] = array_pad(explode('/', $port),2,'');
|
||||
$c['Ports'][] = ['IP' => $ip, 'PrivatePort' => $PrivatePort, 'PublicPort' => $nat ? $value[0]['HostPort'] : $PrivatePort, 'NAT' => $nat, 'Type' => $Type];
|
||||
$PublicPort = $info['HostConfig']['PortBindings']["$port"][0]['HostPort'] ?: null;
|
||||
$nat = ($driver[$c['NetworkMode']]=='bridge');
|
||||
if (array_key_exists($PrivatePort, $c['Ports']) && $Type != $c['Ports'][$PrivatePort]['Type'])
|
||||
$Type = $c['Ports'][$PrivatePort]['Type'] . '/' . $Type;
|
||||
$c['Ports'][$PrivatePort] = ['IP' => $ip, 'PrivatePort' => $PrivatePort, 'PublicPort' => $PublicPort, 'NAT' => $nat, 'Type' => $Type, 'Driver' => $driver[$c['NetworkMode']]];
|
||||
}
|
||||
ksort($c['Ports']);
|
||||
$this::$containersCache[] = $c;
|
||||
}
|
||||
array_multisort(array_column($this::$containersCache,'Name'), SORT_NATURAL|SORT_FLAG_CASE, $this::$containersCache);
|
||||
|
@@ -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);
|
||||
@@ -69,29 +118,51 @@ foreach ($containers as $ct) {
|
||||
$running = $info['running'] ? 1 : 0;
|
||||
$paused = $info['paused'] ? 1 : 0;
|
||||
$is_autostart = $info['autostart'] ? 'true':'false';
|
||||
$updateStatus = substr($ct['NetworkMode'],-4)==':???' ? 2 : ($info['updated']=='true' ? 0 : ($info['updated']=='false' ? 1 : 3));
|
||||
$composestack = isset($ct['ComposeProject']) ? $ct['ComposeProject'] : '';
|
||||
$updateStatus = substr($ct['NetworkMode'], -4) == ':???' ? 2 : ($info['updated'] == 'true' ? 0 : ($info['updated'] == 'false' ? 1 : 3));
|
||||
$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';
|
||||
$color = $status=='started' ? 'green-text' : ($status=='paused' ? 'orange-text' : 'red-text');
|
||||
$update = $updateStatus==1 ? 'blue-text' : '';
|
||||
$update = $updateStatus==1 && !empty($compose) ? 'blue-text' : '';
|
||||
$icon = $info['icon'] ?: '/plugins/dynamix.docker.manager/images/question.png';
|
||||
$image = substr($icon,-4)=='.png' ? "<img src='$icon?".filemtime("$docroot{$info['icon']}")."' class='img' onerror=this.src='/plugins/dynamix.docker.manager/images/question.png';>" : (substr($icon,0,5)=='icon-' ? "<i class='$icon img'></i>" : "<i class='fa fa-$icon img'></i>");
|
||||
$wait = var_split($autostart[array_search($name,$names)]??'',1);
|
||||
$ports = [];
|
||||
foreach ($ct['Ports'] as $port) {
|
||||
$intern = $running ? ($ct['NetworkMode']=='host' ? $host : _var($port,'IP')) : $null;
|
||||
$extern = $running ? (_var($port,'NAT') ? $host : $intern) : $null;
|
||||
$ports[] = sprintf('%s:%s/%s<i class="fa fa-arrows-h" style="margin:0 6px"></i>%s:%s', $intern, _var($port,'PrivatePort'), strtoupper(_var($port,'Type')), $extern, _var($port,'PublicPort'));
|
||||
$networks = [];
|
||||
$network_ips = [];
|
||||
$ports_internal = [];
|
||||
$ports_external = [];
|
||||
if (isset($ct['Ports']['vlan'])) {
|
||||
foreach ($ct['Ports']['vlan'] as $i)
|
||||
$ports_external[] = sprintf('%s', $i);
|
||||
$ports_internal[0] = sprintf('%s', 'all');
|
||||
}
|
||||
foreach($ct['Networks'] as $netName => $netVals) {
|
||||
$networks[] = $netName;
|
||||
$network_ips[] = $running ? $netVals['IPAddress'] : null;
|
||||
|
||||
if (isset($ct['Networks']['host'])) {
|
||||
$ports_external[] = sprintf('%s', $netVals['IPAddress']);
|
||||
$ports_internal[0] = sprintf('%s', 'all');
|
||||
} else if (!isset($ct['Ports']['vlan']) || strpos($ct['NetworkMode'], 'container:') != 0) {
|
||||
foreach ($ct['Ports'] as $port) {
|
||||
if (_var($port,'PublicPort') && _var($port,'Driver') == 'bridge')
|
||||
$ports_external[] = sprintf('%s:%s', $host, strtoupper(_var($port,'PublicPort')));
|
||||
if ((!isset($ct['Networks']['host'])) || (!isset($ct['Networks']['vlan'])))
|
||||
$ports_internal[] = sprintf('%s:%s', _var($port,'PrivatePort'), strtoupper(_var($port,'Type')));
|
||||
}
|
||||
}
|
||||
}
|
||||
$paths = [];
|
||||
$ct['Volumes'] = is_array($ct['Volumes']) ? $ct['Volumes'] : [];
|
||||
@@ -100,12 +171,12 @@ foreach ($containers as $ct) {
|
||||
$paths[] = sprintf('%s<i class="fa fa-%s" style="margin:0 6px"></i>%s', htmlspecialchars($container_path), $access_mode=='ro'?'long-arrow-left':'arrows-h', htmlspecialchars($host_path));
|
||||
}
|
||||
echo "<tr class='sortable'><td class='ct-name' style='width:220px;padding:8px'><i class='fa fa-arrows-v mover orange-text'></i>";
|
||||
if ($template) {
|
||||
if ($template && empty($composestack)) {
|
||||
$appname = "<a class='exec' onclick=\"editContainer('".addslashes(htmlspecialchars($name))."','".addslashes(htmlspecialchars($template))."')\">".htmlspecialchars($name)."</a>";
|
||||
} else {
|
||||
$appname = htmlspecialchars($name);
|
||||
}
|
||||
echo "<span class='outer'><span id='$id' $menu class='hand'>$image</span><span class='inner'><span class='appname $update'>$appname</span><br><i id='load-$id' class='fa fa-$shape $status $color'></i><span class='state'>"._($status)."</span></span></span>";
|
||||
echo "<span class='outer'><span id='$id' $menu class='hand'>$image</span><span class='inner'><span class='appname $update'>$appname</span><br><i id='load-$id' class='fa fa-$shape $status $color'></i><span class='state'>"._($status).(!empty($composestack) ? '<br/>Compose Stack: ' . $composestack : '')."</span></span></span>";
|
||||
echo "<div class='advanced' style='margin-top:8px'>"._('Container ID').": $id<br>";
|
||||
if ($ct['BaseImage']) echo "<i class='fa fa-cubes' style='margin-right:5px'></i>".htmlspecialchars($ct['BaseImage'])."<br>";
|
||||
echo _('By').": ";
|
||||
@@ -118,35 +189,153 @@ foreach ($containers as $ct) {
|
||||
}
|
||||
echo "</div></td><td class='updatecolumn'>";
|
||||
switch ($updateStatus) {
|
||||
case 0:
|
||||
echo "<span class='green-text' style='white-space:nowrap;'><i class='fa fa-check fa-fw'></i> "._('up-to-date')."</span>";
|
||||
if ($ct['Manager'] == "dockerman")
|
||||
echo "<div class='advanced'><a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('force update')."</span></a></div>";
|
||||
break;
|
||||
case 0:
|
||||
if ($ct['Manager'] == "dockerman") {
|
||||
echo "<span class='green-text' style='white-space:nowrap;'><i class='fa fa-check fa-fw'></i> "._('up-to-date')."</span>";
|
||||
echo "<div class='advanced'><a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('force update')."</span></a></div>";
|
||||
} elseif (!empty($composestack)) {
|
||||
echo "<div><span><i class='fa fa-docker fa-fw'/></i> Compose</span></div>";
|
||||
echo "<span tyle='white-space:nowrap;'><i class='fa fa-check fa-fw'></i> "._('up-to-date')."</span>";
|
||||
} else {
|
||||
echo "<div><span><i class='fa fa-docker fa-fw'/></i> 3rd Party</span></div>";
|
||||
echo "<span tyle='white-space:nowrap;'><i class='fa fa-check fa-fw'></i> "._('up-to-date')."</span>";
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
echo "<div class='advanced'><span class='orange-text' style='white-space:nowrap;'><i class='fa fa-flash fa-fw'></i> "._('update ready')."</span></div>";
|
||||
if ($ct['Manager'] == "dockerman")
|
||||
echo "<a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('apply update')."</span></a>";
|
||||
else
|
||||
if ($ct['Manager'] == "dockerman") {
|
||||
echo "<a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('apply update')."</span></a>";
|
||||
} elseif (!empty($composestack)) {
|
||||
echo "<div><span><i class='fa fa-docker fa-fw'/></i> Compose</span></a></div>";
|
||||
echo "<span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('update available')."</span>";
|
||||
} else {
|
||||
echo "<div><span><i class='fa fa-docker fa-fw'/></i> 3rd Party</span></div>";
|
||||
echo "<span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('update available')."</span>";
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
echo "<div class='advanced'><span class='orange-text' style='white-space:nowrap;'><i class='fa fa-flash fa-fw'></i> "._('rebuild ready')."</span></div>";
|
||||
echo "<a class='exec'><span style='white-space:nowrap;'><i class='fa fa-recycle fa-fw'></i> "._('rebuilding')."</span></a>";
|
||||
break;
|
||||
default:
|
||||
echo "<span class='orange-text' style='white-space:nowrap;'><i class='fa fa-unlink'></i> "._('not available')."</span>";
|
||||
if ($ct['Manager'] == "dockerman")
|
||||
if ($ct['Manager'] == "dockerman") {
|
||||
echo "<span class='orange-text' style='white-space:nowrap;'><i class='fa fa-unlink'></i> "._('not available')."</span>";
|
||||
echo "<div class='advanced'><a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('force update')."</span></a></div>";
|
||||
} elseif (!empty($composestack)) {
|
||||
echo "<div><span><i class='fa fa-docker fa-fw'/></i> Compose</span></div>";
|
||||
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 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❌\nPlease check the logs!";
|
||||
} else {
|
||||
$TS_version = explode('-', $TSstats["Version"])[0];
|
||||
if (!empty($TS_version)) {
|
||||
if (!empty($TS_latest_version)) {
|
||||
if ($TS_version !== $TS_latest_version) {
|
||||
$TSinfo .= "Version:\t\t" . $TS_version . " ➔ " . $TS_latest_version . " available!\n";
|
||||
} else {
|
||||
$TSinfo .= "Version:\t\t" . $TS_version . "\n";
|
||||
}
|
||||
} else {
|
||||
$TSinfo .= "Version:\t\t" . $TS_version . "\n";
|
||||
}
|
||||
}
|
||||
$TSinfo .= "Online:\t\t✅\n";
|
||||
$TS_DNSName = $TSstats["Self"]["DNSName"];
|
||||
$TS_HostNameActual = substr($TS_DNSName, 0, strpos($TS_DNSName, '.'));
|
||||
if (strcasecmp($TS_HostNameActual, $TShostname) !== 0 && !empty($TS_DNSName)) {
|
||||
$TSinfo .= "Hostname:\tReal Hostname ➔ " . $TS_HostNameActual . "\n";
|
||||
} else {
|
||||
$TSinfo .= "Hostname:\t" . $TShostname . "\n";
|
||||
}
|
||||
// Map region relay code to cleartext region if TS_derp_list is available
|
||||
if (!empty($TS_derp_list)) {
|
||||
foreach ($TS_derp_list['Regions'] as $region) {
|
||||
if ($region['RegionCode'] === $TSstats["Self"]["Relay"]) {
|
||||
$TSregion = $region['RegionName'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!empty($TSregion)) {
|
||||
$TSinfo .= "Main Relay:\t" . $TSregion . "\n";
|
||||
} else {
|
||||
$TSinfo .= "Main Relay:\t" . $TSstats["Self"]["Relay"] . "\n";
|
||||
}
|
||||
} else {
|
||||
$TSinfo .= "Main Relay:\t" . $TSstats["Self"]["Relay"] . "\n";
|
||||
}
|
||||
if (!empty($TSstats["Self"]["TailscaleIPs"])) {
|
||||
$TSinfo .= "Addresses:\t" . implode("\n\t\t\t", $TSstats["Self"]["TailscaleIPs"]) . "\n";
|
||||
}
|
||||
if (!empty($TSstats["Self"]["PrimaryRoutes"])) {
|
||||
$TSinfo .= "Routes:\t\t" . implode("\n\t\t\t", $TSstats["Self"]["PrimaryRoutes"]) . "\n";
|
||||
}
|
||||
if ($TSstats["Self"]["ExitNodeOption"]) {
|
||||
$TSinfo .= "Is Exit Node:\t✅\n";
|
||||
} else {
|
||||
if (!empty($TSstats["ExitNodeStatus"])) {
|
||||
$TS_exit_node_status = ($TSstats["ExitNodeStatus"]["Online"]) ? "✅" : "❌";
|
||||
$TSinfo .= "Exit Node:\t" . strstr($TSstats["ExitNodeStatus"]["TailscaleIPs"][0], '/', true) . " | Status: " . $TS_exit_node_status ."\n";
|
||||
} else {
|
||||
$TSinfo .= "Is Exit Node:\t❌\n";
|
||||
}
|
||||
}
|
||||
if (!empty($TSwebGui)) {
|
||||
$TSinfo .= "URL:\t\t" . $TSwebGui . "\n";
|
||||
}
|
||||
if (!empty($TSstats["Self"]["KeyExpiry"])) {
|
||||
$TS_expiry = new DateTime($TSstats["Self"]["KeyExpiry"]);
|
||||
$current_Date = new DateTime();
|
||||
$TS_expiry_formatted = $TS_expiry->format('Y-m-d');
|
||||
$TS_expiry_diff = $current_Date->diff($TS_expiry);
|
||||
if ($TS_expiry_diff->invert) {
|
||||
$TSinfo .= "Key Expiry:\t❌ Expired! Renew/Disable key expiry!\n";
|
||||
} else {
|
||||
$TSinfo .= "Key Expiry:\t" . $TS_expiry_formatted . " (" . $TS_expiry_diff->days . " days)\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
// Display message to refresh page if Tailscale in the container wasn't maybe ready to get the data
|
||||
} else {
|
||||
echo "<div title='Error gathering Tailscale information from container.\nPlease check the logs and refresh the page.'><img src='/plugins/dynamix.docker.manager/images/tailscale.png' style='height: 16px;'> Tailscale</div></td>";
|
||||
}
|
||||
// Display TSinfo if data was fetched correctly
|
||||
echo "<div title='" . $TSinfo . "'><img src='/plugins/dynamix.docker.manager/images/tailscale.png' style='height: 16px;'> Tailscale</div>";
|
||||
// Display message that container isn't running
|
||||
} else {
|
||||
echo "<div title='Container not runnig'><img src='/plugins/dynamix.docker.manager/images/tailscale.png' style='height: 16px;'> Tailscale</div></td>";
|
||||
}
|
||||
}
|
||||
echo "<div class='advanced'><i class='fa fa-info-circle fa-fw'></i> ".compress(_($version),12,0)."</div></td>";
|
||||
echo "<td>{$ct['NetworkMode']}</td>";
|
||||
echo "<td style='white-space:nowrap'><span class='docker_readmore'>".implode('<br>',$ports)."</span></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>";
|
||||
echo "<td style='white-space:nowrap'><span class='docker_readmore'>".implode('<br>',$ports_internal)."</span></td>";
|
||||
echo "<td style='white-space:nowrap'><span class='docker_readmore'>".implode('<br>',$ports_external)."</span></td>";
|
||||
echo "<td style='word-break:break-all'><span class='docker_readmore'>".implode('<br>',$paths)."</span></td>";
|
||||
echo "<td class='advanced'><span class='cpu-$id'>0%</span><div class='usage-disk mm'><span id='cpu-$id' style='width:0'></span><span></span></div>";
|
||||
echo "<br><span class='mem-$id'>0 / 0</span></td>";
|
||||
echo "<td><input type='checkbox' id='$id-auto' class='autostart' container='".htmlspecialchars($name)."'".($info['autostart'] ? ' checked':'').">";
|
||||
if (empty($composestack)) {
|
||||
if ($ct['Manager'] == "dockerman") {
|
||||
echo "<td><input type='checkbox' id='$id-auto' class='autostart' container='".htmlspecialchars($name)."'".($info['autostart'] ? ' checked':'').">";
|
||||
} else {
|
||||
echo "<td><i class='fa fa-docker fa-fw'/></i> 3rd Party";
|
||||
}
|
||||
} else {
|
||||
echo "<td><i class='fa fa-docker'/></i> Compose";
|
||||
}
|
||||
echo "<span id='$id-wait' style='float:right;display:none'>"._('wait')."<input class='wait' container='".htmlspecialchars($name)."' type='number' value='$wait' placeholder='0' title=\""._('seconds')."\"></span></td>";
|
||||
echo "<td><div style='white-space:nowrap'>".htmlspecialchars(str_replace('Up',_('Uptime').':',my_lang_log($ct['Status'])))."<div style='margin-top:4px'>"._('Created').": ".htmlspecialchars(my_lang_time($ct['Created']))."</div></div></td></tr>";
|
||||
}
|
||||
@@ -161,4 +350,4 @@ foreach ($images as $image) {
|
||||
echo "</td><td>"._('Created')." ".htmlspecialchars(_($image['Created'],0))."</td></tr>";
|
||||
}
|
||||
echo "\0".implode($docker)."\0".(pgrep('rc.docker')!==false ? 1:0);
|
||||
?>
|
||||
?>
|
@@ -32,33 +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']));
|
||||
$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['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->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];
|
||||
@@ -73,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;
|
||||
@@ -82,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 = [];
|
||||
@@ -132,7 +208,11 @@ function xmlToVar($xml) {
|
||||
$out['Network'] = xml_decode($xml->Networking->Mode);
|
||||
}
|
||||
// check if network exists
|
||||
if (!key_exists($out['Network'],$subnet)) $out['Network'] = 'none';
|
||||
if (preg_match('/^container:(.*)/', $out['Network'])) {
|
||||
$out['Network'] = $out['Network'];
|
||||
} elseif (!key_exists($out['Network'],$subnet)) {
|
||||
$out['Network'] = 'none';
|
||||
}
|
||||
// V1 compatibility
|
||||
if ($xml['version'] != '2') {
|
||||
if (isset($xml->Description)) {
|
||||
@@ -241,7 +321,11 @@ function xmlToCommand($xml, $create_paths=false) {
|
||||
$xml = xmlToVar($xml);
|
||||
$cmdName = strlen($xml['Name']) ? '--name='.escapeshellarg($xml['Name']) : '';
|
||||
$cmdPrivileged = strtolower($xml['Privileged'])=='true' ? '--privileged=true' : '';
|
||||
$cmdNetwork = preg_match('/\-\-net(work)?=/',$xml['ExtraParams']) ? "" : '--net='.escapeshellarg(strtolower($xml['Network']));
|
||||
if (preg_match('/^container:(.*)/', $xml['Network'])) {
|
||||
$cmdNetwork = preg_match('/\-\-net(work)?=/',$xml['ExtraParams']) ? "" : '--net='.escapeshellarg($xml['Network']);
|
||||
} else {
|
||||
$cmdNetwork = preg_match('/\-\-net(work)?=/',$xml['ExtraParams']) ? "" : '--net='.escapeshellarg(strtolower($xml['Network']));
|
||||
}
|
||||
$cmdMyIP = '';
|
||||
foreach (explode(' ',str_replace(',',' ',$xml['MyIP'])) as $myIP) if ($myIP) $cmdMyIP .= (strpos($myIP,':')?'--ip6=':'--ip=').escapeshellarg($myIP).' ';
|
||||
$cmdCPUset = strlen($xml['CPUset']) ? '--cpuset-cpus='.escapeshellarg($xml['CPUset']) : '';
|
||||
@@ -254,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'].'"';
|
||||
@@ -263,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'];
|
||||
@@ -320,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) {
|
||||
@@ -508,16 +654,22 @@ function setXmlVal(&$xml, $value, $el, $attr=null, $pos=0) {
|
||||
|
||||
function getAllocations() {
|
||||
global $DockerClient, $host;
|
||||
|
||||
|
||||
$ports = [];
|
||||
foreach ($DockerClient->getDockerContainers() as $ct) {
|
||||
$list = $port = [];
|
||||
$nat = $ip = false;
|
||||
$list['Name'] = $ct['Name'];
|
||||
foreach ($ct['Ports'] as $tmp) {
|
||||
$nat = $tmp['NAT'];
|
||||
$ip = $tmp['IP'];
|
||||
$port[] = $tmp['PublicPort'];
|
||||
if (isset($tmp['NAT'])) {
|
||||
$nat = $tmp['NAT'];
|
||||
}
|
||||
if (isset($tmp['IP'])) {
|
||||
$ip = $tmp['IP'];
|
||||
}
|
||||
if (isset($tmp['PublicPort'])) {
|
||||
$port[] = $tmp['PublicPort'];
|
||||
}
|
||||
}
|
||||
sort($port);
|
||||
$ip = $ct['NetworkMode']=='host'||$nat ? $host : ($ip ?: DockerUtil::myIP($ct['Name']) ?: '0.0.0.0');
|
||||
|
@@ -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});
|
||||
}
|
||||
|
@@ -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();
|
||||
|
@@ -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();
|
||||
|
28
emhttp/plugins/dynamix.docker.manager/system/Docker
Executable file
28
emhttp/plugins/dynamix.docker.manager/system/Docker
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
# Get active containers
|
||||
ACTIVE_CONTAINERS="$(docker ps -q --no-trunc 2>/dev/null)"
|
||||
|
||||
# Exit if no containers are active and return zero
|
||||
if [ -z "${ACTIVE_CONTAINERS}" ]; then
|
||||
echo "0"
|
||||
exit
|
||||
fi
|
||||
|
||||
# Get all relevant memory entries from containers
|
||||
for container in ${ACTIVE_CONTAINERS} ; do
|
||||
CONT_MEMORY="$(cat /sys/fs/cgroup/docker/${container}/memory.stat 2>/dev/null | grep -Ew "anon|kernel|kernel_stack|pagetables|sec_pagetables|percpu|sock|vmalloc|shmem" | awk '{print $2}')"
|
||||
# Add up memory values
|
||||
for value in ${CONT_MEMORY} ; do
|
||||
if [[ ${value} =~ ^[0-9]+$ ]]; then
|
||||
((MEMORY_USAGE += value))
|
||||
fi
|
||||
done
|
||||
unset CONT_MEMORY
|
||||
done
|
||||
|
||||
# Check if value is a integer and return the value otherwiese return zero
|
||||
if [[ ${MEMORY_USAGE} =~ ^[0-9]+$ ]]; then
|
||||
echo "${MEMORY_USAGE}"
|
||||
else
|
||||
echo "0"
|
||||
fi
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
{
|
||||
".nuxt/nuxt-custom-elements/entries/unraid-components.client.mjs": {
|
||||
"file": "_nuxt/unraid-components.client-CNGeANhD.js",
|
||||
"file": "_nuxt/unraid-components.client-CQOXNcK4.js",
|
||||
"name": "unraid-components.client",
|
||||
"src": ".nuxt/nuxt-custom-elements/entries/unraid-components.client.mjs",
|
||||
"isEntry": true,
|
||||
@@ -8,5 +8,5 @@
|
||||
"_nuxt/unraid-components-p_3YF86n.css"
|
||||
]
|
||||
},
|
||||
"ts": 1716926465
|
||||
"ts": 1723595088
|
||||
}
|
@@ -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;
|
||||
|
@@ -489,6 +489,7 @@ $(function() {
|
||||
<input type="button" onclick="stopAll()" value="_(Stop All)_" style="display:none">
|
||||
|
||||
<div id="dialogWindow"></div>
|
||||
<div id="iframe-popup"></div>
|
||||
|
||||
<div id="templateISO" class="template">
|
||||
<dl>
|
||||
|
@@ -24,36 +24,74 @@ require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
|
||||
$hardware = !empty(shell_exec("/etc/rc.d/rc.libvirt test"));
|
||||
if (!$hardware) {
|
||||
echo "<p class='notice'>"._('Your hardware does not have Intel VT-x or AMD-V capability').". "._('This is required to create VMs in KVM').". "._('Please disable the VM function').". ";
|
||||
echo "<a href='https://docs.unraid.net/unraid-os/manual/vm-management#determining-hvmiommu-hardware-support' target='_blank'> "._('Click here to see the Unraid Wiki for more information')."</a></p>";
|
||||
echo "<a href='https://docs.unraid.net/go/determining-hvmiommu-hardware-support/' target='_blank'> "._('View the Docs for more information')."</a></p>";
|
||||
}
|
||||
|
||||
function scan($area, $text) {
|
||||
return strpos($area,$text)!==false;
|
||||
}
|
||||
function detect(&$syslinux, $key) {
|
||||
$size = count($syslinux);
|
||||
$menu = $i = 0;
|
||||
$value = '';
|
||||
// find the default section
|
||||
while ($i < $size) {
|
||||
if (scan($syslinux[$i],'label ')) {
|
||||
$n = $i + 1;
|
||||
// find the current requested setting
|
||||
while (!scan($syslinux[$n],'label ') && $n < $size) {
|
||||
if (scan($syslinux[$n],'menu default')) $menu = 1;
|
||||
if (scan($syslinux[$n],'append')) foreach (explode(' ',$syslinux[$n]) as $cmd) if (scan($cmd,$key)) {$value = explode('=',$cmd)[1]; break;}
|
||||
$n++;
|
||||
function detect(&$bootcfg, $bootenv, $key) {
|
||||
if ($bootenv === 'syslinux') {
|
||||
$size = count($bootcfg);
|
||||
$menu = $i = 0;
|
||||
$value = '';
|
||||
// find the default section
|
||||
while ($i < $size) {
|
||||
if (scan($bootcfg[$i],'label ')) {
|
||||
$n = $i + 1;
|
||||
// find the current requested setting
|
||||
while (!scan($bootcfg[$n],'label ') && $n < $size) {
|
||||
if (scan($bootcfg[$n],'menu default')) $menu = 1;
|
||||
if (scan($bootcfg[$n],'append')) foreach (explode(' ',$bootcfg[$n]) as $cmd) if (scan($cmd,$key)) {$value = explode('=',$cmd)[1]; break;}
|
||||
$n++;
|
||||
}
|
||||
if ($menu) break; else $i = $n - 1;
|
||||
}
|
||||
if ($menu) break; else $i = $n - 1;
|
||||
$i++;
|
||||
}
|
||||
$i++;
|
||||
} elseif ($bootenv === 'grub') {
|
||||
$menu_entries = [];
|
||||
// find the current boot entry
|
||||
foreach ($bootcfg as $line) {
|
||||
if (preg_match('/set default=(\d+)/', $line, $match)) {
|
||||
$bootentry = (int)$match[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
// split boot entries
|
||||
foreach ($bootcfg as $line) {
|
||||
if (strpos($line, 'menuentry ') === 0) {
|
||||
$in_menuentry = true;
|
||||
$current_entry = $line . "\n";
|
||||
} elseif ($in_menuentry) {
|
||||
$current_entry .= $line . "\n";
|
||||
if (trim($line) === "}") {
|
||||
$menu_entries[] = $current_entry;
|
||||
$in_menuentry = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// search in selected menuentry
|
||||
$menuentry = explode("\n", $menu_entries[$bootentry]);
|
||||
foreach (explode(' ', $menu_entries[$bootentry]) as $cmd) {
|
||||
if (scan($cmd,$key)) {
|
||||
$value = explode('=',$cmd)[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
return trim($value);
|
||||
}
|
||||
if (is_file('/boot/syslinux/syslinux.cfg')) {
|
||||
$bootcfg = file('/boot/syslinux/syslinux.cfg',FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$bootenv = 'syslinux';
|
||||
} elseif (is_file('/boot/grub/grub.cfg')) {
|
||||
$bootcfg = file('/boot/grub/grub.cfg',FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$bootenv = 'grub';
|
||||
}
|
||||
$syslinux = file('/boot/syslinux/syslinux.cfg',FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$arrValidNetworks = getValidNetworks();
|
||||
$pcie_acs_override = detect($syslinux, 'pcie_acs_override');
|
||||
$vfio_allow_unsafe = detect($syslinux, 'allow_unsafe_interrupts');
|
||||
$pcie_acs_override = detect($bootcfg, $bootenv, 'pcie_acs_override');
|
||||
$vfio_allow_unsafe = detect($bootcfg, $bootenv, 'allow_unsafe_interrupts');
|
||||
$bgcolor = strstr('white,azure',$display['theme']) ? '#f2f2f2' : '#1c1c1c';
|
||||
$started = $var['fsState']=='Started';
|
||||
$libvirt_up = $libvirt_running=='yes';
|
||||
@@ -321,7 +359,7 @@ $(function(){
|
||||
<?if ($safemode):?>
|
||||
if (run) $("#settingsForm").submit();
|
||||
<?else:?>
|
||||
if (run) $.post("/plugins/dynamix.vm.manager/include/VMajax.php", {action:'syslinux',pcie:$('#pcie_acs_override').val(),vfio:$('#vfio_allow_unsafe').val()}, function(data){
|
||||
if (run) $.post("/plugins/dynamix.vm.manager/include/VMajax.php", {action:'cmdlineoverride',pcie:$('#pcie_acs_override').val(),vfio:$('#vfio_allow_unsafe').val()}, function(data){
|
||||
$("#settingsForm").submit();
|
||||
});
|
||||
<?endif;?>
|
||||
|
@@ -19,7 +19,7 @@ Cond="exec(\"grep -o '^USAGE=.Y' /boot/config/domain.cfg 2>/dev/null\") && is_fi
|
||||
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
|
||||
?>
|
||||
<table id="vmstats" class="tablesorter four shift">
|
||||
<thead class='child'><tr><th class="th1">_(Name)_</th><th class="th2">_(Guest CPU)_</th><th>_(Host CPU)_</th><th>_(Memory)_</th><th>_(Disk IO)_</th><th>_(Network IO)_</th></tr></thead>
|
||||
<thead class='child'><tr><th class="th1">_(Name)_</th><th class="th2">_(Guest CPU)_</th><th>_(Host CPU)_</th><th>_(Memory inuse/Current/Maximum)_</th><th>_(Disk IO)_</th><th>_(Network IO)_</th></tr></thead>
|
||||
<tbody id ="vmstatsbody" class='child'>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@@ -33,30 +33,60 @@ function scan($line, $text) {
|
||||
return stripos($line,$text)!==false;
|
||||
}
|
||||
|
||||
function embed(&$syslinux, $key, $value) {
|
||||
$size = count($syslinux);
|
||||
$make = false;
|
||||
$new = strlen($value) ? "$key=$value" : false;
|
||||
$i = 0;
|
||||
while ($i < $size) {
|
||||
// find sections and exclude safemode
|
||||
if (scan($syslinux[$i],'label ') && !scan($syslinux[$i],'safe mode') && !scan($syslinux[$i],'safemode')) {
|
||||
$n = $i + 1;
|
||||
// find the current requested setting
|
||||
while (!scan($syslinux[$n],'label ') && $n < $size) {
|
||||
if (scan($syslinux[$n],'append ')) {
|
||||
$cmd = preg_split('/\s+/',trim($syslinux[$n]));
|
||||
// replace the existing setting
|
||||
for ($c = 1; $c < count($cmd); $c++) if (scan($cmd[$c],$key)) {$make |= ($cmd[$c]!=$new); $cmd[$c] = $new; break;}
|
||||
// or insert the new setting
|
||||
if ($c==count($cmd) && $new) {array_splice($cmd,-1,0,$new); $make = true;}
|
||||
$syslinux[$n] = ' '.str_replace(' ',' ',implode(' ',$cmd));
|
||||
function embed(&$bootcfg, $env, $key, $value) {
|
||||
if ($env === 'syslinux') {
|
||||
$size = count($bootcfg);
|
||||
$make = false;
|
||||
$new = strlen($value) ? "$key=$value" : false;
|
||||
$i = 0;
|
||||
while ($i < $size) {
|
||||
// find sections and exclude safemode
|
||||
if (scan($bootcfg[$i],'label ') && !scan($bootcfg[$i],'safe mode') && !scan($bootcfg[$i],'safemode')) {
|
||||
$n = $i + 1;
|
||||
// find the current requested setting
|
||||
while (!scan($bootcfg[$n],'label ') && $n < $size) {
|
||||
if (scan($bootcfg[$n],'append ')) {
|
||||
$cmd = preg_split('/\s+/',trim($bootcfg[$n]));
|
||||
// replace the existing setting
|
||||
for ($c = 1; $c < count($cmd); $c++) if (scan($cmd[$c],$key)) {$make |= ($cmd[$c]!=$new); $cmd[$c] = $new; break;}
|
||||
// or insert the new setting
|
||||
if ($c==count($cmd) && $new) {array_splice($cmd,-1,0,$new); $make = true;}
|
||||
$bootcfg[$n] = ' '.str_replace(' ',' ',implode(' ',$cmd));
|
||||
}
|
||||
$n++;
|
||||
}
|
||||
$n++;
|
||||
$i = $n - 1;
|
||||
}
|
||||
$i = $n - 1;
|
||||
$i++;
|
||||
}
|
||||
} elseif ($env === 'grub') {
|
||||
$size = count($bootcfg);
|
||||
$make = false;
|
||||
$new = strlen($value) ? "$key=$value" : false;
|
||||
$i = 0;
|
||||
while ($i < $size) {
|
||||
// find sections and exclude safemode/memtest
|
||||
if (scan($bootcfg[$i],'menuentry ') && !scan($bootcfg[$i],'safe mode') && !scan($bootcfg[$i],'safemode') && !scan($bootcfg[$i],'memtest')) {
|
||||
$n = $i + 1;
|
||||
// find the current requested setting
|
||||
while (!scan($bootcfg[$n],'menuentry ') && $n < $size) {
|
||||
if (scan($bootcfg[$n],'linux ')) {
|
||||
$cmd = preg_split('/\s+/',trim($bootcfg[$n]));
|
||||
// replace the existing setting
|
||||
for ($c = 1; $c < count($cmd); $c++) if (scan($cmd[$c],$key)) {$make |= ($cmd[$c]!=$new); $cmd[$c] = $new; break;}
|
||||
// or insert the new setting
|
||||
if ($c == count($cmd) && $new) {
|
||||
$cmd[] = $new;
|
||||
$make = true;
|
||||
}
|
||||
$bootcfg[$n] = ' ' . str_replace(' ', ' ', implode(' ', $cmd));
|
||||
}
|
||||
$n++;
|
||||
}
|
||||
$i = $n - 1;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
return $make;
|
||||
}
|
||||
@@ -422,6 +452,61 @@ case 'snap-desc':
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'get_storage_fstype':
|
||||
$fstype = get_storage_fstype(unscript(_var($_REQUEST,'storage')));
|
||||
$arrResponse = ['fstype' => $fstype , 'success' => true] ;
|
||||
break;
|
||||
|
||||
case 'vm-removal':
|
||||
requireLibvirt();
|
||||
$arrResponse = ($data = getvmsnapshots($domName))
|
||||
? ['success' => true]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
$datartn = $disksrtn = "";
|
||||
foreach($data as $snap=>$snapdetail) {
|
||||
$snapshotdatetime = date("Y-m-d H:i:s",$snapdetail["creationtime"]) ;
|
||||
$datartn .= "$snap $snapshotdatetime\n" ;
|
||||
}
|
||||
$disks = $lv->get_disk_stats($domName);
|
||||
|
||||
foreach($disks as $diskid=>$diskdetail) {
|
||||
if ($diskid == 0) $pathinfo = pathinfo($diskdetail['file']);
|
||||
}
|
||||
|
||||
$list = glob($pathinfo['dirname']."/*");
|
||||
$uuid = $lv->domain_get_uuid($domName);
|
||||
|
||||
$list2 = glob("/etc/libvirt/qemu/nvram/*$uuid*");
|
||||
$listnew = array();
|
||||
$list=array_merge($list,$list2);
|
||||
foreach($list as $key => $listent)
|
||||
{
|
||||
$pathinfo = pathinfo($listent);
|
||||
$listnew[] = "{$pathinfo['basename']} ({$pathinfo['dirname']})";
|
||||
}
|
||||
sort($listnew,SORT_NATURAL);
|
||||
$listcount = count($listnew);
|
||||
$snapcount = count($data);
|
||||
$disksrtn=implode("\n",$listnew);
|
||||
|
||||
|
||||
|
||||
if (strpos($dirname,'/mnt/user/')===0) {
|
||||
$realdisk = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($dirname)." 2>/dev/null"));
|
||||
if (!empty($realdisk)) {
|
||||
$dirname = str_replace('/mnt/user/', "/mnt/$realdisk/", $dirname);
|
||||
}
|
||||
}
|
||||
$fstype = trim(shell_exec(" stat -f -c '%T' $dirname"));
|
||||
$html = '<table class="snapshot">
|
||||
<tr><td>'._('VM Being removed').':</td><td><span id="VMBeingRemoved">'.$domName.'</span></td></tr>
|
||||
<tr><td>'._('Remove all files').':</td><td><input type="checkbox" id="All" checked value="" ></td></tr>
|
||||
<tr><td>'._('Files being removed').':</td><td><textarea id="textfiles" class="xml" rows="'.$listcount.'" style="white-space: pre; overflow: auto; width:600px" disabled>'.$disksrtn.'</textarea></td></tr>
|
||||
<tr><td>'._('Snapshots being removed').':</td><td><textarea id="textsnaps" rows="'.$snapsount.'" cols="80" disabled>'.$datartn.'</textarea></td></tr>
|
||||
</table>';
|
||||
$arrResponse = ['html' => $html , 'success' => true] ;
|
||||
break;
|
||||
|
||||
case 'disk-create':
|
||||
$disk = $_REQUEST['disk'];
|
||||
$driver = $_REQUEST['driver'];
|
||||
@@ -536,26 +621,39 @@ case 'hot-detach-usb':
|
||||
//TODO
|
||||
break;
|
||||
|
||||
case 'syslinux':
|
||||
$cfg = '/boot/syslinux/syslinux.cfg';
|
||||
$syslinux = file($cfg, FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$m1 = embed($syslinux, 'pcie_acs_override', $_REQUEST['pcie']);
|
||||
$m2 = embed($syslinux, 'vfio_iommu_type1.allow_unsafe_interrupts', $_REQUEST['vfio']);
|
||||
if ($m1||$m2) file_put_contents($cfg, implode("\n",$syslinux)."\n");
|
||||
case 'cmdlineoverride':
|
||||
if (is_file('/boot/syslinux/syslinux.cfg')) {
|
||||
$cfg = '/boot/syslinux/syslinux.cfg';
|
||||
$env = 'syslinux';
|
||||
$bootcfg = file($cfg, FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
} elseif (is_file('/boot/grub/grub.cfg')) {
|
||||
$cfg = '/boot/grub/grub.cfg';
|
||||
$env = 'grub';
|
||||
$bootcfg = file($cfg, FILE_IGNORE_NEW_LINES);
|
||||
}
|
||||
$m1 = embed($bootcfg, $env, 'pcie_acs_override', $_REQUEST['pcie']);
|
||||
$m2 = embed($bootcfg, $env, 'vfio_iommu_type1.allow_unsafe_interrupts', $_REQUEST['vfio']);
|
||||
if ($m1||$m2) file_put_contents($cfg, implode("\n",$bootcfg)."\n");
|
||||
$arrResponse = ['success' => true, 'modified' => $m1|$m2];
|
||||
break;
|
||||
|
||||
case 'reboot':
|
||||
$cfg = '/boot/syslinux/syslinux.cfg';
|
||||
$syslinux = file($cfg, FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
if (is_file('/boot/syslinux/syslinux.cfg')) {
|
||||
$cfg = '/boot/syslinux/syslinux.cfg';
|
||||
$env = 'syslinux';
|
||||
} elseif (is_file('/boot/grub/grub.cfg')) {
|
||||
$cfg = '/boot/grub/grub.cfg';
|
||||
$env = 'grub';
|
||||
}
|
||||
$bootcfg = file($cfg, FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$cmdline = explode(' ',file_get_contents('/proc/cmdline'));
|
||||
$pcie = $vfio = '';
|
||||
foreach ($cmdline as $cmd) {
|
||||
if (scan($cmd,'pcie_acs_override')) $pcie = explode('=',$cmd)[1];
|
||||
if (scan($cmd,'allow_unsafe_interrupts')) $vfio = explode('=',$cmd)[1];
|
||||
}
|
||||
$m1 = embed($syslinux, 'pcie_acs_override', $pcie);
|
||||
$m2 = embed($syslinux, 'vfio_iommu_type1.allow_unsafe_interrupts', $vfio);
|
||||
$m1 = embed($bootcfg, $env, 'pcie_acs_override', $pcie);
|
||||
$m2 = embed($bootcfg, $env, 'vfio_iommu_type1.allow_unsafe_interrupts', $vfio);
|
||||
$arrResponse = ['success' => true, 'modified' => $m1|$m2];
|
||||
break;
|
||||
|
||||
|
@@ -113,7 +113,7 @@ if (strpos($strSelectedTemplate,"User-") !== false) {
|
||||
<table>
|
||||
<tr>
|
||||
<td>_(Icon)_:</td>
|
||||
<td>
|
||||
<td class="template_img_parent">
|
||||
<input type="hidden" name="template[icon]" id="template_icon" value="<?=htmlspecialchars($arrLoad['icon'])?>" />
|
||||
<img id="template_img" src="<?=htmlspecialchars($strIconURL)?>" width="48" height="48" title="_(Change Icon)_..."/>
|
||||
<div id="template_img_chooser_outer">
|
||||
@@ -189,13 +189,13 @@ $(function() {
|
||||
});
|
||||
|
||||
$('.advancedview').switchButton({
|
||||
labels_placement: "left",
|
||||
labels_placement: "right",
|
||||
on_label: "_(XML View)_",
|
||||
off_label: "_(Form View)_",
|
||||
checked: isVMXMLMode()
|
||||
});
|
||||
$('.inlineview').switchButton({
|
||||
labels_placement: "left",
|
||||
labels_placement: "right",
|
||||
off_label: "_(Hide inline xml)_",
|
||||
on_label: "_(Show Inline XML)_",
|
||||
checked: isinlineXMLMode()
|
||||
|
@@ -267,6 +267,9 @@
|
||||
if (!empty($disk['serial'])) {
|
||||
$arrReturn['serial'] = $disk['serial'];
|
||||
}
|
||||
if (!empty($disk['discard'])) {
|
||||
$arrReturn['discard'] = $disk['discard'];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -699,8 +702,10 @@
|
||||
if ($strDevType == 'file' || $strDevType == 'block') {
|
||||
$strSourceType = ($strDevType == 'file' ? 'file' : 'dev');
|
||||
|
||||
if (isset($disk['discard'])) $strDevUnmap = " discard=\"{$disk['discard']}\" "; else $strDevUnmap = " discard=\"ignore\" ";
|
||||
|
||||
$diskstr .= "<disk type='" . $strDevType . "' device='disk'>
|
||||
<driver name='qemu' type='" . $disk['driver'] . "' cache='writeback'/>
|
||||
<driver name='qemu' type='" . $disk['driver'] . "' cache='writeback'".$strDevUnmap."/>
|
||||
<source " . $strSourceType . "='" . htmlspecialchars($disk['image'], ENT_QUOTES | ENT_XML1) . "'/>
|
||||
<target bus='" . $disk['bus'] . "' dev='" . $disk['dev'] . "' $rotation_rate />
|
||||
$bootorder
|
||||
@@ -905,10 +910,17 @@
|
||||
}
|
||||
|
||||
if ($gpu['multi'] == "on"){
|
||||
$newgpu_bus = dechex(hexdec($gpu_bus) + 0x20) ;
|
||||
$newgpu_bus= 0x07;
|
||||
if (!isset($multibus[$newgpu_bus])) {
|
||||
$multibus[$newgpu_bus] = 0x07;
|
||||
} else {
|
||||
#Get next bus
|
||||
$newgpu_bus = end($multibus) + 0x01;
|
||||
$multibus[$newgpu_bus] = $newgpu_bus;
|
||||
}
|
||||
if ($machine_type == "pc") $newgpu_slot = "0x01" ; else $newgpu_slot = "0x00" ;
|
||||
$strSpecialAddress = "<address type='pci' domain='0x0000' bus='0x$newgpu_bus' slot='$newgpu_slot' function='0x".$gpu_function."' multifunction='on' />" ;
|
||||
$multidevices[$gpu_bus] = "0x$gpu_bus" ;
|
||||
$strSpecialAddress = "<address type='pci' domain='0x0000' bus='$newgpu_bus' slot='$newgpu_slot' function='0x".$gpu_function."' multifunction='on' />" ;
|
||||
$multidevices[$gpu_bus] = $newgpu_bus ;
|
||||
}
|
||||
|
||||
|
||||
@@ -926,9 +938,9 @@
|
||||
}
|
||||
}
|
||||
$audiodevs_used=[];
|
||||
$strSpecialAddressAudio = "" ;
|
||||
if (!empty($audios)) {
|
||||
foreach ($audios as $i => $audio) {
|
||||
$strSpecialAddressAudio = "" ;
|
||||
// Skip duplicate audio devices
|
||||
if (empty($audio['id']) || in_array($audio['id'], $audiodevs_used)) {
|
||||
continue;
|
||||
@@ -937,9 +949,9 @@
|
||||
[$audio_bus, $audio_slot, $audio_function] = my_explode(":", str_replace('.', ':', $audio['id']), 3);
|
||||
if ($audio_function != 0) {
|
||||
if (isset($multidevices[$audio_bus])) {
|
||||
$newaudio_bus = dechex(hexdec($audio_bus) + 0x20) ;
|
||||
$newaudio_bus = $multidevices[$audio_bus] ;
|
||||
if ($machine_type == "pc") $newaudio_slot = "0x01" ; else $newaudio_slot = "0x00" ;
|
||||
$strSpecialAddressAudio = "<address type='pci' domain='0x0000' bus='0x$newaudio_bus' slot='$newaudio_slot' function='0x".$audio_function."' />" ;
|
||||
$strSpecialAddressAudio = "<address type='pci' domain='0x0000' bus='$newaudio_bus' slot='$newaudio_slot' function='0x".$audio_function."' />" ;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -956,9 +968,9 @@
|
||||
}
|
||||
|
||||
$pcidevs_used=[];
|
||||
$strSpecialAddressOther = "" ;
|
||||
if (!empty($pcis)) {
|
||||
foreach ($pcis as $i => $pci_id) {
|
||||
$strSpecialAddressOther = "" ;
|
||||
// Skip duplicate other pci devices
|
||||
if (empty($pci_id) || in_array($pci_id, $pcidevs_used)) {
|
||||
continue;
|
||||
@@ -968,9 +980,9 @@
|
||||
|
||||
if ($pci_function != 0) {
|
||||
if (isset($multidevices[$pci_bus])) {
|
||||
$newpci_bus = dechex(hexdec($pci_bus) + 0x20) ;
|
||||
$newpci_bus = $multidevices[$pci_bus];
|
||||
if ($machine_type == "pc") $newpci_slot = "0x01" ; else $newpci_slot = "0x00" ;
|
||||
$strSpecialAddressOther = "<address type='pci' domain='0x0000' bus='0x$newpci_bus' slot='$newpci_slot' function='0x".$pci_function."' />" ;
|
||||
$strSpecialAddressOther = "<address type='pci' domain='0x0000' bus='$newpci_bus' slot='$newpci_slot' function='0x".$pci_function."' />" ;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1339,6 +1351,7 @@
|
||||
if ($tmp) {
|
||||
$tmp['bus'] = $disk->target->attributes()->bus->__toString();
|
||||
$tmp["boot order"] = $disk->boot->attributes()->order ?? "";
|
||||
$tmp["discard"] = $disk->driver->attributes()->discard ?? "ignore";
|
||||
$tmp["rotation"] = $disk->target->attributes()->rotation_rate ?? "0";
|
||||
$tmp['serial'] = $disk->serial ;
|
||||
|
||||
@@ -1369,7 +1382,8 @@
|
||||
'bus' => $disk->target->attributes()->bus->__toString(),
|
||||
'boot order' => $disk->boot->attributes()->order ,
|
||||
'rotation' => $disk->target->attributes()->rotation_rate ?? "0",
|
||||
'serial' => $disk->serial
|
||||
'serial' => $disk->serial,
|
||||
'discard' => $disk->driver->attributes()->discard ?? "ignore"
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1989,7 +2003,19 @@
|
||||
if (is_file($disk)) unlink($disk);
|
||||
if (is_file($cfg)) unlink($cfg);
|
||||
if (is_file($xml)) unlink($xml);
|
||||
if (is_dir($dir) && $this->is_dir_empty($dir)) rmdir($dir);
|
||||
if (is_dir($dir) && $this->is_dir_empty($dir)) {
|
||||
$result= my_rmdir($dir);
|
||||
if ($result['type'] == "zfs") {
|
||||
qemu_log("$domain","delete empty zfs $dir {$result['rtncode']}");
|
||||
if (isset($result['dataset'])) qemu_log("$domain","dataset {$result['dataset']} ");
|
||||
if (isset($result['cmd'])) qemu_log("$domain","Command {$result['cmd']} ");
|
||||
if (isset($result['output'])) {
|
||||
$outputlogs = implode(" ",$result['output']);
|
||||
qemu_log("$domain","Output $outputlogs end");
|
||||
}
|
||||
}
|
||||
else qemu_log("$domain","delete empty $dir {$result['rtncode']}");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@@ -1091,6 +1091,15 @@ private static $encoding = 'UTF-8';
|
||||
return $arrValidDiskBuses;
|
||||
}
|
||||
|
||||
function getValidDiskDiscard() {
|
||||
$arrValidDiskDiscard = [
|
||||
'ignore' => 'Ignore(No Trim)',
|
||||
'unmap' => 'Unmap(Trim)',
|
||||
];
|
||||
|
||||
return $arrValidDiskDiscard;
|
||||
}
|
||||
|
||||
function getValidCdromBuses() {
|
||||
$arrValidCdromBuses = [
|
||||
'scsi' => 'SCSI',
|
||||
@@ -1106,6 +1115,7 @@ private static $encoding = 'UTF-8';
|
||||
$arrValidVNCModels = [
|
||||
'cirrus' => 'Cirrus',
|
||||
'qxl' => 'QXL (best)',
|
||||
'virtio' => 'Virtio(2d)',
|
||||
'vmvga' => 'vmvga'
|
||||
];
|
||||
|
||||
@@ -1316,6 +1326,7 @@ private static $encoding = 'UTF-8';
|
||||
'driver' => $disk['type'],
|
||||
'dev' => $disk['device'],
|
||||
'bus' => $disk['bus'],
|
||||
'discard' => $disk['discard'],
|
||||
'boot' => $disk['boot order'],
|
||||
'rotation' => $disk['rotation'],
|
||||
'serial' => $disk['serial'],
|
||||
@@ -1330,6 +1341,7 @@ private static $encoding = 'UTF-8';
|
||||
'dev' => 'hda',
|
||||
'select' => '',
|
||||
'bus' => 'virtio',
|
||||
'discard' => 'ignore',
|
||||
'rotation' => "0"
|
||||
];
|
||||
}
|
||||
@@ -1457,7 +1469,12 @@ private static $encoding = 'UTF-8';
|
||||
// preserve existing disk driver settings
|
||||
foreach ($new['devices']['disk'] as $key => $disk) {
|
||||
$source = $disk['source']['@attributes']['file'];
|
||||
foreach ($old['devices']['disk'] as $k => $d) if ($source==$d['source']['@attributes']['file']) $new['devices']['disk'][$key]['driver']['@attributes'] = $d['driver']['@attributes'];
|
||||
if (isset($disk['driver']['@attributes']['discard'])) $discard = $disk['driver']['@attributes']['discard']; else $discard = null;
|
||||
foreach ($old['devices']['disk'] as $k => $d)
|
||||
if ($source==$d['source']['@attributes']['file']) {
|
||||
if (isset($discard)) $d['driver']['@attributes']['discard'] = $discard;
|
||||
$new['devices']['disk'][$key]['driver']['@attributes'] = $d['driver']['@attributes'];
|
||||
}
|
||||
}
|
||||
// settings not in the GUI, but maybe customized
|
||||
unset($old['clock']);
|
||||
@@ -2661,8 +2678,8 @@ function get_vm_usage_stats($collectcpustats = true,$collectdiskstats = true,$co
|
||||
# Memory Metrics
|
||||
if ($state == 1 && $collectmemstats) {
|
||||
$currentmem = $data["balloon.current"];
|
||||
$unusedmem = $data["balloon.unused"];
|
||||
$meminuse = $currentmem - $unusedmem;
|
||||
$maximummem = $data["balloon.maximum"];
|
||||
$meminuse = min($data["balloon.rss"],$data["balloon.current"]);
|
||||
} else $currentmem = $meminuse = 0;
|
||||
|
||||
# Disk
|
||||
@@ -2697,7 +2714,8 @@ function get_vm_usage_stats($collectcpustats = true,$collectdiskstats = true,$co
|
||||
"cpuguest" => $cpuGuestPercent,
|
||||
"timestamp" => $timestamp,
|
||||
"mem" => $meminuse,
|
||||
"maxmem" => $currentmem,
|
||||
"curmem" => $currentmem,
|
||||
"maxmem" => $maximummem,
|
||||
"rxrate" => $rxrate,
|
||||
"rxp" => $rx,
|
||||
"txrate" => $txrate,
|
||||
@@ -2822,4 +2840,26 @@ function get_vm_ip($dom) {
|
||||
return $myIP;
|
||||
}
|
||||
|
||||
function check_zfs_name($zfsname, $storage="default") {
|
||||
global $lv,$domain_cfg;
|
||||
if ($storage == "default") $storage = $domain_cfg['DOMAINDIR']; else $storage = "/mnt/$storage/";
|
||||
$storage=transpose_user_path($storage);
|
||||
$fstype = trim(shell_exec(" stat -f -c '%T' $storage"));
|
||||
#Check if ZFS.
|
||||
$allowed_chars = "/^[A-Za-z0-9][A-Za-z0-9\-_.: ]*$/";
|
||||
if ($fstype == "zfs" && !preg_match($allowed_chars, $zfsname)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function get_storage_fstype($storage="default") {
|
||||
global $domain_cfg;
|
||||
if ($storage == "default") $storage = $domain_cfg['DOMAINDIR']; else $storage = "/mnt/$storage/";
|
||||
$storage=transpose_user_path($storage);
|
||||
$fstype = trim(shell_exec(" stat -f -c '%T' $storage"));
|
||||
return $fstype;
|
||||
}
|
||||
|
||||
?>
|
||||
|
@@ -75,11 +75,13 @@ while (true) {
|
||||
if ($vmdata['state'] == 1) {
|
||||
$running++;
|
||||
$echodata .= "<tr><td>$vm</td>" ;
|
||||
$echodata .= "<td class='advanced'><span class='cpug-".$vm."'>".$vmdata['cpuguest']."%</span><div class='usage-disk mm'><span id='cpug-".$vm."' style='width:".$vmdata['cpuguest']."%;'></span><span></span></div></td>";
|
||||
$echodata .= "<td class='advanced'><span class='cpuh-".$vm."'>".$vmdata['cpuhost']."%</span><div class='usage-disk mm'><span id='cpuh-".$vm."' style='width:".$vmdata['cpuhost']."%;'></span><span></span></div></td><td>";
|
||||
$echodata .= my_scale($vmdata['mem']*1024,$unit)."$unit / ".my_scale($vmdata['maxmem']*1024,$unit)."$unit</td><td>";
|
||||
$echodata .= _("Read").": ".my_scale($vmdata['rdrate'],$unit)."$unit/s<br>"._("Write").": ".my_scale($vmdata['wrrate'],$unit)."$unit/s</td><td>";
|
||||
$echodata .= _("RX").": ".my_scale($vmdata['rxrate'],$unit)."$unit/s<br>"._("TX").": ".my_scale($vmdata['txrate'],$unit)."$unit/s</td></tr>";
|
||||
$echodata .= "<td class='advanced'><span class='cpug-".$vm."'>".$vmdata['cpuguest']."%</span><div class='usage-disk mm'><span id='cpug-".$vm."' style='width:".$vmdata['cpuguest']."%;'> </span><span></span></div></td>";
|
||||
$echodata .= "<td class='advanced'><span class='cpuh-".$vm."'>".$vmdata['cpuhost']."%</span><div class='usage-disk mm'><span id='cpuh-".$vm."' style='width:".$vmdata['cpuhost']."%;'> </span><span></span></div></td><td>";
|
||||
$echodata .= my_scale($vmdata['mem']*1024,$unit)."$unit / ".my_scale($vmdata['curmem']*1024,$unit)."$unit";
|
||||
if ($vmdata['curmem'] === $vmdata['maxmem']) $echodata .= " </td><td>";
|
||||
else $echodata .= " / " .my_scale($vmdata['maxmem']*1024,$unit)."$unit </td><td>";
|
||||
$echodata .= _("Read").": ".my_scale($vmdata['rdrate']/$timer,$unit)."$unit/s<br>"._("Write").": ".my_scale($vmdata['wrrate']/$timer,$unit)."$unit/s</td><td>";
|
||||
$echodata .= _("RX").": ".my_scale($vmdata['rxrate']/$timer,$unit)."$unit/s<br>"._("TX").": ".my_scale($vmdata['txrate']/$timer,$unit)."$unit/s</td></tr>";
|
||||
}
|
||||
$echo = $echodata ;
|
||||
}
|
||||
|
@@ -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"]);
|
||||
|
@@ -23,14 +23,15 @@ span.advancedview_panel{display:none;line-height:16px;margin-top:1px}
|
||||
.basic{display:none}
|
||||
.advanced{/*Empty placeholder*/}
|
||||
.switch-button-label.off{color:inherit}
|
||||
.template_img_parent{position:relative}
|
||||
#template_img{cursor:pointer}
|
||||
#template_img:hover{opacity:0.5}
|
||||
#template_img:hover i{opacity:1.0}
|
||||
.template_img_chooser_inner{display:inline-block;width:80px;margin-bottom:15px;margin-right:10px;text-align:center;}
|
||||
.template_img_chooser_inner img{width:48px;height:48px}
|
||||
.template_img_chooser_inner p{text-align:center;line-height:8px;}
|
||||
#template_img_chooser{width:560px;height:300px;overflow-y:scroll;position:relative}
|
||||
#template_img_chooser div:hover{background-color:#eee;cursor:pointer;}
|
||||
#template_img_chooser{width:560px;height:300px;overflow-y:scroll;position:relative;display:grid;grid-template-columns: repeat(6, minmax(0, 1fr));}
|
||||
#template_img_chooser div:hover{color:#ff8c2f;cursor:pointer;}
|
||||
#form_content{display:none}
|
||||
#vmform .four{overflow:hidden}
|
||||
#vmform .four label{float:left;display:table-cell;width:15%;}
|
||||
|
@@ -23,14 +23,15 @@ span.advancedview_panel{display:none;line-height:16px;margin-top:1px}
|
||||
.basic{display:none}
|
||||
.advanced{/*Empty placeholder*/}
|
||||
.switch-button-label.off{color:inherit}
|
||||
.template_img_parent{position:relative}
|
||||
#template_img{cursor:pointer}
|
||||
#template_img:hover{opacity:0.5}
|
||||
#template_img:hover i{opacity:1.0}
|
||||
.template_img_chooser_inner{display:inline-block;width:80px;margin-bottom:15px;margin-right:10px;text-align:center;}
|
||||
.template_img_chooser_inner img{width:48px;height:48px}
|
||||
.template_img_chooser_inner p{text-align:center;line-height:8px;}
|
||||
#template_img_chooser{width:560px;height:300px;overflow-y:scroll;position:relative}
|
||||
#template_img_chooser div:hover{background-color:#eee;cursor:pointer;}
|
||||
#template_img_chooser{width:560px;height:300px;overflow-y:scroll;position:relative;display:grid;grid-template-columns: repeat(6, minmax(0, 1fr));}
|
||||
#template_img_chooser div:hover{color:#ff8c2f;cursor:pointer;}
|
||||
#form_content{display:none}
|
||||
#vmform .four{overflow:hidden}
|
||||
#vmform .four label{float:left;display:table-cell;width:15%;}
|
||||
|
@@ -31,6 +31,7 @@
|
||||
$arrValidUSBDevices = getValidUSBDevices();
|
||||
$arrValidDiskDrivers = getValidDiskDrivers();
|
||||
$arrValidDiskBuses = getValidDiskBuses();
|
||||
$arrValidDiskDiscard = getValidDiskDiscard();
|
||||
$arrValidCdromBuses = getValidCdromBuses();
|
||||
$arrValidVNCModels = getValidVNCModels();
|
||||
$arrValidProtocols = getValidVMRCProtocols();
|
||||
@@ -88,7 +89,8 @@
|
||||
'select' => $domain_cfg['VMSTORAGEMODE'],
|
||||
'bus' => 'virtio' ,
|
||||
'boot' => 1,
|
||||
'serial' => 'vdisk1'
|
||||
'serial' => 'vdisk1',
|
||||
'discard' => 'unmap'
|
||||
]
|
||||
],
|
||||
'gpu' => [
|
||||
@@ -315,6 +317,18 @@
|
||||
}
|
||||
if ($usertemplate == 1) unset($arrConfig['domain']['uuid']);
|
||||
$xml2 = build_xml_templates($strXML);
|
||||
#disable rename if snapshots exist
|
||||
$snapshots = getvmsnapshots($arrConfig['domain']['name']) ;
|
||||
if ($snapshots != null && count($snapshots) && !$boolNew)
|
||||
{
|
||||
$snaprenamehidden = "";
|
||||
$namedisable = "disabled";
|
||||
$snapcount = count($snapshots);
|
||||
} else {
|
||||
$snaprenamehidden = "hidden";
|
||||
$namedisable = "";
|
||||
$snapcount = "0";
|
||||
};
|
||||
?>
|
||||
|
||||
<link rel="stylesheet" href="<?autov('/plugins/dynamix.vm.manager/scripts/codemirror/lib/codemirror.css')?>">
|
||||
@@ -334,9 +348,12 @@
|
||||
<input type="hidden" name="domain[memoryBacking]" id="domain_memorybacking" value="<?=htmlspecialchars($arrConfig['domain']['memoryBacking'])?>">
|
||||
|
||||
<table>
|
||||
<tr><td></td><td>
|
||||
<span <?=$snaprenamehidden?> id="snap-rename" class="orange-text"><i class="fa fa-warning"></i> _(Rename disabled, <?=$snapcount?> snapshot(s) exists.)_</span>
|
||||
<span hidden id="zfs-name" class="orange-text"><i class="fa fa-warning"></i> _(Name contains invalid characters or does not start with an alphanumberic for a ZFS storage location<br>Only these special characters are valid Underscore (_) Hyphen (-) Colon (:) Period (.))_</span></td></tr>
|
||||
<tr>
|
||||
<td>_(Name)_:</td>
|
||||
<td><input type="text" name="domain[name]" id="domain_name" class="textTemplate" title="_(Name of virtual machine)_" placeholder="_(e.g.)_ _(My Workstation)_" value="<?=htmlspecialchars($arrConfig['domain']['name'])?>" required /></td>
|
||||
<td><input <?=$namedisable?> type="text" name="domain[name]" id="domain_name" oninput="checkName(this.value)" class="textTemplate" title="_(Name of virtual machine)_" placeholder="_(e.g.)_ _(My Workstation)_" value="<?=htmlspecialchars($arrConfig['domain']['name'])?>" required /></td>
|
||||
<td><textarea class="xml" id="xmlname" rows=1 disabled ><?=htmlspecialchars($xml2['name'])."\n".htmlspecialchars($xml2['uuid'])."\n".htmlspecialchars($xml2['metadata'])?></textarea></td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -377,7 +394,7 @@
|
||||
<tr>
|
||||
<?if (!$boolNew) $disablestorage = " disabled "; else $disablestorage = "";?>
|
||||
<td>_(Override Storage Location)_:</td><td>
|
||||
<select <?=$disablestorage?> name="template[storage]" class="disk_select narrow" id="storage_location" title="_(Location of virtual machine files)_">
|
||||
<select <?=$disablestorage?> name="template[storage]" onchange="get_storage_fstype(this)" class="disk_select narrow" id="storage_location" title="_(Location of virtual machine files)_">
|
||||
<?
|
||||
$default_storage=htmlspecialchars($arrConfig['template']['storage']);
|
||||
echo mk_option($default_storage, 'default', _('Default'));
|
||||
@@ -847,6 +864,10 @@
|
||||
</select>
|
||||
_(Boot Order)_:
|
||||
<input type="number" size="5" maxlength="5" id="disk[<?=$i?>][boot]" class="narrow bootorder" style="width: 50px;" name="disk[<?=$i?>][boot]" title="_(Boot order)_" value="<?=$arrDisk['boot']?>" >
|
||||
_(Discard)_:
|
||||
<select name="disk[<?=$i?>][discard]" class="disk_driver narrow" title="_(Set discard option)_">
|
||||
<?mk_dropdown_options($arrValidDiskDiscard, $arrDisk['discard']);?>
|
||||
</select>
|
||||
<? if ($arrDisk['bus'] == "virtio" || $arrDisk['bus'] == "usb") $ssddisabled = "hidden "; else $ssddisabled = " ";?>
|
||||
<span id="disk[<?=$i?>][rotatetext]" <?=$ssddisabled?>>_(SSD)_:</span>
|
||||
<input type="checkbox" id="disk[<?=$i?>][rotation]" class="narrow rotation" onchange="updateSSDCheck(this)"style="width: 50px;" name="disk[<?=$i?>][rotation]" <?=$ssddisabled ?> <?=$arrDisk['rotation'] ? "checked ":"";?> title="_(Set SDD flag)_" value="<?=$arrDisk['rotation']?>" >
|
||||
@@ -897,6 +918,11 @@
|
||||
Specify the order the devices are used for booting.
|
||||
</p>
|
||||
|
||||
<p class="advanced">
|
||||
<b>vDisk Discard</b><br>
|
||||
Specify if unmap(Trim) requests are sent to underlaying filesystem.
|
||||
</p>
|
||||
|
||||
<p class="advanced">
|
||||
<b>vDisk SSD Flag</b><br>
|
||||
Specify the vdisk shows as SSD within the guest, only supported on SCSI, SATA and IDE bus types.
|
||||
@@ -1001,6 +1027,10 @@
|
||||
|
||||
_(Boot Order)_:
|
||||
<input type="number" size="5" maxlength="5" id="disk[{{INDEX}}][boot]" class="narrow bootorder" style="width: 50px;" name="disk[{{INDEX}}][boot]" title="_(Boot order)_" value="" >
|
||||
_(Discard)_:
|
||||
<select name="disk[{{INDEX}}][discard]" class="disk_driver narrow" title="_(Set discard option)_">
|
||||
<?mk_dropdown_options($arrValidDiskDiscard, "unmap");?>
|
||||
</select>
|
||||
<span id="disk[{{INDEX}}][rotatetext]" hidden>_(SSD)_:</span>
|
||||
<input type="checkbox" id="disk[{{INDEX}}][rotation]" class="narrow rotation" onchange="updateSSDCheck(this)"style="width: 50px;" name="disk[{{INDEX}}[rotation]" hidden title="_(Set SSD flag)_" value='0' >
|
||||
</td>
|
||||
@@ -1532,7 +1562,6 @@
|
||||
<table>
|
||||
<tr><td></td>
|
||||
<td>_(Select)_  _(Boot Order)_</td></tr></div>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>_(Other PCI Devices)_:</td>
|
||||
<td>
|
||||
@@ -1557,7 +1586,6 @@
|
||||
<label for="pci<?=$i?>">    <input type="checkbox" name="pci[]" id="pci<?=$i?>" value="<?=htmlspecialchars($arrDev['id'])?>" <?=$extra?>/>  
|
||||
<input type="number" size="5" maxlength="5" id="pciboot<?=$i?>" class="narrow pcibootorder" <?=$bootdisable?> style="width: 50px;" name="pciboot[<?=htmlspecialchars($arrDev['id'])?>]" title="_(Boot order)_" value="<?=$pciboot?>" >
|
||||
<?=htmlspecialchars($arrDev['name'])?> | <?=htmlspecialchars($arrDev['type'])?> (<?=htmlspecialchars($arrDev['id'])?>)</label><br/>
|
||||
<td><textarea class="xml" id="xmlpci<?=$i?>" rows=5 disabled ><?=htmlspecialchars($xml2['devices']['other'][$arrDev['id']])?></textarea></td>
|
||||
<?
|
||||
}
|
||||
}
|
||||
@@ -1567,6 +1595,7 @@
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<td><textarea class="xml" id="xmlpci<?=$i?>" rows=2 disabled ><?=htmlspecialchars($xml2['devices']['other']["allotherpci"])?></textarea></td>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@@ -1881,6 +1910,8 @@
|
||||
<script src="<?autov('/plugins/dynamix.vm.manager/scripts/codemirror/addon/hint/libvirt-schema.js')?>"></script>
|
||||
<script src="<?autov('/plugins/dynamix.vm.manager/scripts/codemirror/mode/xml/xml.js')?>"></script>
|
||||
<script type="text/javascript">
|
||||
var storageType = "<?=get_storage_fstype($arrConfig['template']['storage']);?>";
|
||||
var storageLoc = "<?=$arrConfig['template']['storage']?>";
|
||||
|
||||
function ShareChange(share) {
|
||||
var value = share.value;
|
||||
@@ -1992,6 +2023,36 @@ function SetBootorderfields(usbbootvalue) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Remove characters not allowed in share name. */
|
||||
function checkName(name) {
|
||||
/* Declare variables at the function scope */
|
||||
var isValidName
|
||||
$('#zfs-name').hide();
|
||||
isValidName = /^[A-Za-z0-9][A-Za-z0-9\-_.: ]*$/.test(name);
|
||||
if (isValidName) {
|
||||
$('#btnSubmit').prop("disabled", false);
|
||||
} else {
|
||||
if (storageType == "zfs")
|
||||
{ $('#btnSubmit').prop("disabled", true); $('#zfs-name').show(); }
|
||||
else $('#btnSubmit').prop("disabled", false);
|
||||
}
|
||||
}
|
||||
|
||||
function get_storage_fstype(item) {
|
||||
storageLoc = item.value;
|
||||
$.post("/plugins/dynamix.vm.manager/include/VMajax.php", {action:"get_storage_fstype", storage:item.value}, function( data ) {
|
||||
if (data.success) {
|
||||
if (data.fstype) {
|
||||
storageType=data.fstype;
|
||||
checkName(document.getElementById("domain_name").value);
|
||||
}}
|
||||
|
||||
if (data.error) {
|
||||
}
|
||||
}, "json");
|
||||
}
|
||||
|
||||
function USBBootChange(usbboot) {
|
||||
// Remove all boot orders if changed to Yes
|
||||
var value = usbboot.value ;
|
||||
|
@@ -23,7 +23,7 @@ function installPlugin(file) {
|
||||
<div class="notice">_(Click **Install** to download and install the **Community Applications** plugin)_</div>
|
||||
|
||||
<form markdown="1" name="ca_install" method="POST" target="progressFrame">
|
||||
<input type="hidden" name="file" value="https://raw.githubusercontent.com/Squidly271/community.applications/master/plugins/community.applications.plg">
|
||||
<input type="hidden" name="file" value="https://ca.unraid.net/dl/https://raw.githubusercontent.com/Squidly271/community.applications/master/plugins/community.applications.plg">
|
||||
|
||||
|
||||
: <input type="button" value="_(Install)_" onclick="installPlugin(this.form.file.value)">
|
||||
|
@@ -460,7 +460,7 @@ window.onunload = function(){
|
||||
<? if (_var($var,'fsNumUnmountable',0)>0):?>
|
||||
<tr><td>**<?=_('Unmountable disk'.(_var($var,'fsNumUnmountable',0)==1?'':'s').' present')?>:**<br>
|
||||
<? $cache = [];
|
||||
foreach ($disks as $disk) if (substr(_var($disk,'fsStatus'),0,11)=='Unmountable' || in_array(prefix(_var($disk,'name')),$cache)) {
|
||||
foreach ($disks as $disk) if (substr(_var($disk,'fsStatus'),0,11)=='Unmountable' || in_array(pool_name(_var($disk,'name')),$cache)) {
|
||||
if (strlen(_var($disk,'id'))) echo "<span class='blue-text'>".my_disk(_var($disk,'name'))."</span> • ".my_id(_var($disk,'id'))." ("._var($disk,'device').")<br>";
|
||||
if (in_array(_var($disk,'name'),$pools)) $cache[] = $disk['name'];
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
Menu="Dashboard"
|
||||
Nchan="wg_poller,update_1,update_2,update_3,ups_status:stop,vm_dashusage:stop"
|
||||
Nchan="wg_poller,update_1,update_2,update_3,ups_status:stop,vm_dashusage"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology * Copyright 2012-2023, Bergware International.
|
||||
@@ -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';}));
|
||||
@@ -79,7 +79,7 @@ $domain_cfg = parse_ini_file($domain_cfgfile);
|
||||
if (!isset($domain_cfg['USAGE'])) $vmusage = "N" ; else $vmusage = $domain_cfg['USAGE'];
|
||||
|
||||
// enable/disable graph elements by making hook script executable or not
|
||||
chmod("$docroot/webGui/system/VM_usage",$libvirtd ? 0755 : 0644);
|
||||
chmod("$docroot/webGui/system/VM",$libvirtd ? 0755 : 0644);
|
||||
chmod("$docroot/webGui/system/ZFS_cache",$zfs ? 0755 : 0644);
|
||||
|
||||
foreach ($disks as $disk) {
|
||||
@@ -1049,7 +1049,7 @@ function portSelect(name) {
|
||||
}
|
||||
function moreInfo(data,table) {
|
||||
var info = [];
|
||||
if (data[1]>0) info.push(data[1]+' '+(data[1]==1 ? "_(failed device)_" : "_(failed devices)_"));
|
||||
if (data[1]>0) info.push(data[1]+' '+(data[1]==1 ? "_(device warning)_" : "_(device warnings)_"));
|
||||
if (data[2]>0) info.push(data[2]+' '+(data[2]==1 ? "_(heat warning)_" : "_(heat warnings)_"));
|
||||
if (data[3]>0) info.push(data[3]+' '+(data[3]==1 ? "_(SMART error)_" : "_(SMART errors)_"));
|
||||
if (data[4]>0) info.push(data[4]+' '+(data[4]==1 ? "_(utilization warning)_" : "_(utilization warnings)_"));
|
||||
@@ -1188,6 +1188,7 @@ function SleepNow() {
|
||||
function sortTables() {
|
||||
$('table.dashboard').each(function(){
|
||||
var table = $(this);
|
||||
sanitizeMultiCookie(table.prop('id'), ';', true);
|
||||
var index = $.cookie(table.prop('id'));
|
||||
// sorting list exists
|
||||
if (index != null) {
|
||||
|
@@ -20,13 +20,28 @@ require_once "$docroot/webGui/include/Preselect.php";
|
||||
$unassigned = array_key_exists($name,$devs);
|
||||
$disk = $disks[$name] ?? $devs[$name] ?? [];
|
||||
$dev = _var($disk,'device');
|
||||
$disk['id'] = _var($disk,'id');
|
||||
$events = explode('|',$disk['smEvents'] ?? $var['smEvents'] ?? $numbers);
|
||||
$mode = ['Disabled','Hourly','Daily','Weekly','Monthly'];
|
||||
$days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
|
||||
$sheets = [];
|
||||
$i = $n = 0;
|
||||
|
||||
function hasSubpools($name) {
|
||||
global $disks, $subpools;
|
||||
foreach ($subpools as $subpool) {
|
||||
$index = "$name~$subpool";
|
||||
if (isset($disks[$index])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!isSubpool($name)) {
|
||||
$fsTypeImmutable = !(_var($var,'fsState')=='Stopped' && !hasSubpools($name) && (empty(_var($disk,'uuid')) || _var($disk,'slots',1)==1));
|
||||
$fsProfileImmutable = $fsTypeImmutable;
|
||||
} else {
|
||||
$fsTypeImmutable = true;
|
||||
$fsProfileImmutable = !(_var($var,'fsState')=='Stopped' && empty(_var($disk,'fsGroups','1')));
|
||||
}
|
||||
|
||||
foreach ($disks as $sheet) {
|
||||
if (_var($sheet,'type')=="Flash" || _var($sheet,'color')=="grey-off" || empty($sheet['name'])) continue;
|
||||
$sheets[] = $sheet['name'];
|
||||
@@ -43,7 +58,8 @@ $tag = _var($disk,'name');
|
||||
$end = count($sheets)-1;
|
||||
$prev = $i>0 ? $sheets[$i-1] : $sheets[$end];
|
||||
$next = $i<$end ? $sheets[$i+1] : $sheets[0];
|
||||
$text = isPool($name) ? _('This will ERASE content of ALL devices in the pool') : _('This will ERASE ALL device content');
|
||||
$textErase = isPool($name) ? _('This will ERASE content of ALL devices in the pool') : _('This will ERASE ALL device content');
|
||||
$textDelete = _('This will unassign all devices from the pool but will NOT modify any device contents');
|
||||
|
||||
function disabled_if($condition) {
|
||||
if ($condition !== false) echo ' disabled';
|
||||
@@ -114,12 +130,12 @@ function isPool($name) {
|
||||
<link type="text/css" rel="stylesheet" href="<?autov("/plugins/dynamix.docker.manager/styles/style-$theme.css")?>">
|
||||
|
||||
<script>
|
||||
<?if (empty($disk)):?>
|
||||
done();
|
||||
<?endif;?>
|
||||
|
||||
String.prototype.celsius = function(){return Math.round((parseInt(this)-32)*5/9).toString();}
|
||||
|
||||
if ($.cookie('deletepool')) {
|
||||
$.removeCookie('deletepool');
|
||||
done();
|
||||
}
|
||||
function setFloor() {
|
||||
if ($('#shareFloor').length==0) return;
|
||||
const fsSize = {<?=fsSize()?>};
|
||||
@@ -198,107 +214,172 @@ function prepareZFS(form) {
|
||||
}
|
||||
<?endif;?>
|
||||
|
||||
function selectDiskFsWidth() {
|
||||
var num_slots = <?=_var($disk,'slots',0)?>;
|
||||
var selected_width = <?=_var($disk,'fsWidth',0)?>;
|
||||
$('#diskFsWidthZFS').empty();
|
||||
if (num_slots == 0) {
|
||||
$('#diskFsWidthZFS').prop('disabled',true).append($('<option>', {
|
||||
value: 0,
|
||||
text: "<?=_('no devices')?>"
|
||||
}));
|
||||
} else if ($('#diskFsProfileZFS').val() == '') {
|
||||
var label = (num_slots == 1) ? "device" : "devices";
|
||||
$('#diskFsWidthZFS').append($('<option>', {
|
||||
function setDiskFsWidth(slots) {
|
||||
$('#diskFsWidth').empty();
|
||||
$('#diskFsWidth').append($('<option>', {value: slots, text:''}));
|
||||
$('#diskFsWidth').val(slots);
|
||||
$('#diskFsProfile').off('change');
|
||||
}
|
||||
function selectDiskFsProfileAuto() {
|
||||
$('#diskFsProfile').empty();
|
||||
$('#diskFsProfile').append($('<option>', {value: '', text:''}));
|
||||
$('#diskFsProfile').val('');
|
||||
setDiskFsWidth('');
|
||||
}
|
||||
function selectDiskFsProfileXFS() {
|
||||
$('#diskFsProfile').empty();
|
||||
$('#diskFsProfile').append($('<option>', {value: '', text:''}));
|
||||
$('#diskFsProfile').val('');
|
||||
setDiskFsWidth(1);
|
||||
}
|
||||
function selectDiskFsProfileBTRFS(slots,init) {
|
||||
$('#diskFsProfile').empty();
|
||||
$('#diskFsProfile').append($('<option>', {value: 'single', text:_('single')}));
|
||||
if (slots >= 2) $('#diskFsProfile').append($('<option>', {value: 'raid0', text:_('raid0')}));
|
||||
if (slots >= 2) $('#diskFsProfile').append($('<option>', {value: 'raid1', text:_('raid1')}));
|
||||
if (slots >= 3) $('#diskFsProfile').append($('<option>', {value: 'raid1c3', text:_('raid1c3')}));
|
||||
if (slots >= 4) $('#diskFsProfile').append($('<option>', {value: 'raid1c4', text:_('raid1c4')}));
|
||||
if (slots >= 4) $('#diskFsProfile').append($('<option>', {value: 'raid10', text:_('raid10')}));
|
||||
if (slots >= 3) $('#diskFsProfile').append($('<option>', {value: 'raid5', text:_('raid5')}));
|
||||
if (slots >= 4) $('#diskFsProfile').append($('<option>', {value: 'raid6', text:_('raid6')}));
|
||||
if (init) {
|
||||
$('#diskFsProfile').val("<?=_var($disk,'fsProfile')?>");
|
||||
} else {
|
||||
if (slots == 1) $('#diskFsProfile').val('');
|
||||
if (slots >= 2) $('#diskFsProfile').val('raid1');
|
||||
}
|
||||
setDiskFsWidth(slots);
|
||||
}
|
||||
function selectDiskFsWidthZFS(slots,init) {
|
||||
var selected_width = init ? Number("<?=_var($disk,'fsWidth')?>") : 0;
|
||||
$('#diskFsWidth').empty();
|
||||
if ($('#diskFsProfile').val() == '') {
|
||||
var label = (slots == 1) ? "device" : "devices";
|
||||
$('#diskFsWidth').append($('<option>', {
|
||||
value: 1,
|
||||
text: _(sprintf('%s '+label,num_slots))
|
||||
text: _(sprintf('%s '+label,slots))
|
||||
}));
|
||||
} else if ($('#diskFsProfileZFS').val() == 'mirror') {
|
||||
if (selected_width == 0) selected_width = 1;
|
||||
} else if ($('#diskFsProfile').val() == 'mirror') {
|
||||
var width;
|
||||
for (width=2; width<=Math.min(num_slots,4); width++) {
|
||||
if ((num_slots % width) == 0) {
|
||||
var groups = num_slots / width;
|
||||
var label = (groups == 1) ? "group" : "groups";
|
||||
$('#diskFsWidthZFS').append($('<option>', {
|
||||
for (width=2; width<=Math.min(slots,4); width++) {
|
||||
if ((slots % width) == 0) {
|
||||
var groups = slots / width;
|
||||
var label = (groups == 1) ? "vdev" : "vdevs";
|
||||
$('#diskFsWidth').append($('<option>', {
|
||||
value: width,
|
||||
text: _(sprintf('%s '+label+' of %s devices',groups,width)),
|
||||
selected: (width == selected_width)
|
||||
}));
|
||||
if (selected_width == 0) selected_width = width;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var width, min_width;
|
||||
if ($('#diskFsProfileZFS').val() == 'raidz1') min_width = 3;
|
||||
else if ($('#diskFsProfileZFS').val() == 'raidz2') min_width = 3;
|
||||
else if ($('#diskFsProfileZFS').val() == 'raidz3') min_width = 4;
|
||||
for (width=min_width; width<=num_slots; width++) {
|
||||
if ((num_slots % width) == 0) {
|
||||
var groups = num_slots / width;
|
||||
var label = (groups == 1) ? "group" : "groups";
|
||||
$('#diskFsWidthZFS').append($('<option>', {
|
||||
if ($('#diskFsProfile').val() == 'raidz1') min_width = 3;
|
||||
else if ($('#diskFsProfile').val() == 'raidz2') min_width = 3;
|
||||
else if ($('#diskFsProfile').val() == 'raidz3') min_width = 4;
|
||||
for (width=min_width; width<=slots; width++) {
|
||||
if ((slots % width) == 0) {
|
||||
var groups = slots / width;
|
||||
var label = (groups == 1) ? "vdev" : "vdevs";
|
||||
$('#diskFsWidth').append($('<option>', {
|
||||
value: width,
|
||||
text: _(sprintf('%s '+label+' of %s devices',groups,width)),
|
||||
selected: (width == selected_width)
|
||||
}));
|
||||
if (selected_width == 0) selected_width = width;
|
||||
}
|
||||
}
|
||||
}
|
||||
$('#diskFsWidth').val(selected_width);
|
||||
}
|
||||
function selectDiskFsProfile(init) {
|
||||
var num_slots = <?=_var($disk,'slots',0)?>;
|
||||
var t = init ? null : 'slow';
|
||||
if (($('#diskFsType').val()||'').indexOf('auto') != -1) {
|
||||
$('#diskFsProfileBTRFS').prop('disabled',true).hide();
|
||||
$('#diskFsProfileZFS').prop('disabled',true).hide();
|
||||
$('#diskFsWidthZFS').prop('disabled',true).hide();;
|
||||
$('#compression').hide(t);
|
||||
$('#autotrim').hide(t);
|
||||
} else if (($('#diskFsType').val()||'').indexOf('btrfs') != -1) {
|
||||
if (!init) $('#diskFsProfileBTRFS').prop('disabled',false);
|
||||
$('#diskFsProfileBTRFS').show();
|
||||
$('#diskFsProfileZFS').prop('disabled',true).hide();
|
||||
$('#diskFsWidthZFS').prop('disabled',true).hide();
|
||||
$('#compression').show(t);
|
||||
<?if (diskType('Cache')):?>
|
||||
$('#autotrim').show(t);
|
||||
if (!init) {
|
||||
if (num_slots == 1) $('#diskFsProfileBTRFS').val('');
|
||||
if (num_slots > 1) $('#diskFsProfileBTRFS').val('raid1');
|
||||
function selectDiskFsProfileZFS(slots,init,subpool) {
|
||||
$('#diskFsProfile').empty();
|
||||
if (slots == 1) $('#diskFsProfile').append($('<option>', {value: '', text: _('single')}));
|
||||
if (slots >= 2) $('#diskFsProfile').append($('<option>', {value: '', text: _('stripe')}));
|
||||
if (subpool != 'cache' && subpool != 'spares') {
|
||||
if (slots%2 == 0 || slots%3 == 0 || slots%4 == 0) $('#diskFsProfile').append($('<option>', {value: 'mirror', text: _('mirror')}));
|
||||
if (subpool == '') {
|
||||
if (slots >= 3 && subpool == '') $('#diskFsProfile').append($('<option>', {value: 'raidz1', text: _('raidz1')}));
|
||||
if (slots >= 3 && subpool == '') $('#diskFsProfile').append($('<option>', {value: 'raidz2', text: _('raidz2')}));
|
||||
if (slots >= 4 && subpool == '') $('#diskFsProfile').append($('<option>', {value: 'raidz3', text: _('raidz3')}));
|
||||
}
|
||||
<?endif;?>
|
||||
} else if (($('#diskFsType').val()||'').indexOf('zfs') != -1) {
|
||||
var subpool = "<?=isSubpool(_var($disk,'name'))?>";
|
||||
$('#diskFsProfileBTRFS').prop('disabled',true).hide();
|
||||
if (subpool.length) {
|
||||
$('#diskFsType').prop('disabled',true).hide();
|
||||
}
|
||||
if (!init) {
|
||||
$('#diskFsProfileZFS').prop('disabled',false);
|
||||
$('#diskFsWidthZFS').prop('disabled',false);
|
||||
}
|
||||
$('#diskFsProfileZFS').show();
|
||||
$('#diskFsWidthZFS').show();
|
||||
if (!init) {
|
||||
if (num_slots == 1) $('#diskFsProfileZFS').val('');
|
||||
if (num_slots == 2) $('#diskFsProfileZFS').val('mirror');
|
||||
if (num_slots > 2) $('#diskFsProfileZFS').val('raidz1');
|
||||
}
|
||||
selectDiskFsWidth();
|
||||
$('#compression').show(t);
|
||||
<?if (diskType('Cache')):?>
|
||||
$('#autotrim').show(t);
|
||||
} else if (($('#diskFsType').val()||'').indexOf('xfs') != -1) {
|
||||
$('#autotrim').show(t);
|
||||
<?endif;?>
|
||||
}
|
||||
if (init) {
|
||||
$('#diskFsProfile').val("<?=_var($disk,'fsProfile')?>");
|
||||
} else {
|
||||
if (slots == 1) $('#diskFsProfile').val('');
|
||||
if (slots == 2) $('#diskFsProfile').val('mirror');
|
||||
if (slots >= 3) $('#diskFsProfile').val('raidz1');
|
||||
}
|
||||
selectDiskFsWidthZFS(slots,init);
|
||||
$('#diskFsProfile').on('change', function() {
|
||||
selectDiskFsWidthZFS(slots,false);
|
||||
});
|
||||
}
|
||||
function changeFsType() {
|
||||
var fstype = ($('#diskFsType').val()||'').replace('luks:','');
|
||||
if (['btrfs', 'zfs'].includes(fstype)) $('#compression').show('slow'); else $('#compression').hide('slow');
|
||||
<?if (diskType('Cache')):?>
|
||||
if (['xfs', 'btrfs', 'zfs'].includes(fstype)) $('#autotrim').show('slow'); else $('#autotrim').hide('slow');
|
||||
/* called upon page load (init==true) and when user changes file system type (init==false) */
|
||||
function selectDiskFsProfile(init) {
|
||||
var t = init ? null : 'slow';
|
||||
|
||||
/* for array disks, 'slots', 'fsWidth', and 'fsGroups' is not defined */
|
||||
var slots = Number("<?=_var($disk,'fsWidth',1)?>") * Number("<?=_var($disk,'fsGroups',1)?>");
|
||||
if (slots == 0) slots = <?=_var($disk,'slots',1)?>;
|
||||
|
||||
var subpool = "<?=isSubpool($name) ?: ''?>";
|
||||
var fsType;
|
||||
|
||||
if (subpool == '') {
|
||||
fsType = init ? "<?=_var($disk,'fsType','')?>" : $('#diskFsType').val();
|
||||
} else {
|
||||
fsType = 'zfs';
|
||||
}
|
||||
|
||||
if (slots == 1 || fsType == 'auto') {
|
||||
$('#profile').hide(t);
|
||||
} else {
|
||||
$('#profile').show(t);
|
||||
if (fsType.indexOf('zfs') != -1) {
|
||||
if (subpool != 'cache' && subpool != 'spares') {
|
||||
$('#diskFsProfile').show();
|
||||
} else {
|
||||
$('#diskFsProfile').hide();
|
||||
}
|
||||
$('#diskFsWidth').show();
|
||||
} else {
|
||||
$('#diskFsProfile').show();
|
||||
$('#diskFsWidth').hide()
|
||||
}
|
||||
}
|
||||
|
||||
if (fsType == 'auto') {
|
||||
selectDiskFsProfileAuto();
|
||||
} else if (fsType.indexOf('btrfs') != -1) {
|
||||
selectDiskFsProfileBTRFS(slots,init);
|
||||
} else if (fsType.indexOf('zfs') != -1) {
|
||||
selectDiskFsProfileZFS(slots,init,subpool);
|
||||
} else if (fsType.indexOf('xfs') != -1) {
|
||||
selectDiskFsProfileXFS();
|
||||
}
|
||||
|
||||
if (subpool != '' || fsType == 'auto' || fsType.indexOf('xfs') != -1) {
|
||||
$('#compression').hide(t);
|
||||
$('#diskCompression').prop('disabled',true);
|
||||
} else {
|
||||
$('#compression').show(t);
|
||||
$('#diskCompression').prop('disabled',false);
|
||||
}
|
||||
|
||||
<?if (diskType('Data') || isSubpool($name)):?>
|
||||
$('#autotrim').hide(t);
|
||||
$('#diskAutotrim').prop('disabled',true);
|
||||
<?else:?>
|
||||
if (fsType == 'auto') {
|
||||
$('#autotrim').hide(t);
|
||||
$('#diskAutotrim').prop('disabled',true);
|
||||
} else {
|
||||
$('#autotrim').show(t);
|
||||
$('#diskAutotrim').prop('disabled',false);
|
||||
}
|
||||
<?endif;?>
|
||||
if (fstype=='reiserfs') $('#reiserfs').show(); else $('#reiserfs').hide();
|
||||
}
|
||||
function prepareDeviceInfo(form) {
|
||||
var events = [];
|
||||
@@ -533,14 +614,10 @@ function renamePoolPopup() {
|
||||
});
|
||||
dialogStyle();
|
||||
}
|
||||
function deletePool() {
|
||||
$.cookie('deletepool','deletepool');
|
||||
document.deletepool.submit();
|
||||
}
|
||||
function eraseDisk(name) {
|
||||
swal({
|
||||
title:"_(Erase Device Content)_?",
|
||||
text:"<?=$text?><p style='font-weight:bold;color:red;margin:8px 0'>_(Existing content is permanently lost)_</p>",
|
||||
text:"<?=$textErase?><p style='font-weight:bold;color:red;margin:8px 0'>_(Existing content is permanently lost)_</p>",
|
||||
html:true,
|
||||
type:'input',
|
||||
inputPlaceholder:"<?=sprintf(_('To confirm your action type: %s'),$name)?>",
|
||||
@@ -554,8 +631,37 @@ function eraseDisk(name) {
|
||||
swal.close();
|
||||
$('#doneButton').prop('disabled',true);
|
||||
$('#eraseButton').prop('disabled',true);
|
||||
$('#removeButton').prop('disabled',true);
|
||||
$('div.spinner.fixed').show();
|
||||
$.get("/update.htm",{cmdWipefs:name,csrf_token:"<?=_var($var,'csrf_token')?>"},function(){
|
||||
$.post("/update.htm",{cmdWipefs:name},function(){
|
||||
$('div.spinner.fixed').hide();
|
||||
refresh();
|
||||
});
|
||||
} else {
|
||||
if (confirm.length) swal({title:"_(Incorrect confirmation)_",text:"_(Please try again)_!",type:'error',html:true,confirmButtonText:"_(Ok)_"});
|
||||
}
|
||||
});
|
||||
}
|
||||
function removePool(name) {
|
||||
swal({
|
||||
title:"_(Remove pool)_?",
|
||||
text:"<?=$textDelete?>",
|
||||
html:true,
|
||||
type:'input',
|
||||
inputPlaceholder:"<?=sprintf(_('To confirm your action type: %s'),$name)?>",
|
||||
showCancelButton:true,
|
||||
closeOnConfirm:false,
|
||||
confirmButtonText:"_(Proceed)_",
|
||||
cancelButtonText:"_(Cancel)_"
|
||||
},
|
||||
function(confirm){
|
||||
if (confirm == "<?=$name?>") {
|
||||
swal.close();
|
||||
$('#doneButton').prop('disabled',true);
|
||||
$('#eraseButton').prop('disabled',true);
|
||||
$('#removeButton').prop('disabled',true);
|
||||
$('div.spinner.fixed').show();
|
||||
$.post("/update.htm",{changeSlots:"apply",poolName:name,poolSlots:0},function(){
|
||||
$('div.spinner.fixed').hide();
|
||||
refresh();
|
||||
});
|
||||
@@ -625,89 +731,35 @@ _(Spin down delay)_:
|
||||
</select><span id="smart_selftest" class='orange-text'></span>
|
||||
|
||||
<?endif;?>
|
||||
<?if (diskType('Data') || isPool($tag)):?>
|
||||
<?$fsTypeImmutable = (_var($var,'fsState')=="Stopped" && !empty(_var($disk,'uuid'))) || (_var($var,'fsState')=="Started" && _var($disk,'fsType')!='auto')?>
|
||||
<?if (diskType('Data') || (!isSubpool($name) && _var($disk,'slots',0)==1)):?>
|
||||
<?if (diskType('Data') || (isPool($tag) && !isSubpool($tag))):?>
|
||||
_(File system status)_:
|
||||
: <?=_(_var($disk,'fsStatus'))?>
|
||||
|
||||
_(File system type)_:
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx',0)?>" onchange="changeFsType()" <?=disabled_if($fsTypeImmutable)?>>
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx')?>" onchange="selectDiskFsProfile(false)" <?=disabled_if($fsTypeImmutable)?>>
|
||||
<?=mk_option(_var($disk,'fsType'), "auto", _('auto'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "xfs", _('xfs'))?>
|
||||
<?if (_var($disk,'slots',1) == 1) echo mk_option(_var($disk,'fsType'), "xfs", _('xfs'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "zfs", _('zfs'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "btrfs", _('btrfs'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "reiserfs", _('reiserfs'), "disabled")?>
|
||||
<?=mk_option(_var($disk,'fsType'), "luks:xfs", _('xfs')." - "._('encrypted'))?>
|
||||
<?if (_var($disk,'slots',1) == 1) echo mk_option(_var($disk,'fsType'), "reiserfs", _('reiserfs'), "disabled")?>
|
||||
<?if (_var($disk,'slots',1) == 1) echo mk_option(_var($disk,'fsType'), "luks:xfs", _('xfs')." - "._('encrypted'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "luks:zfs", _('zfs')." - "._('encrypted'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "luks:btrfs", _('btrfs')." - "._('encrypted'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "luks:reiserfs", _('reiserfs')." - "._('encrypted'), "disabled")?>
|
||||
<?if (_var($disk,'slots',1) == 1) echo mk_option(_var($disk,'fsType'), "luks:reiserfs", _('reiserfs')." - "._('encrypted'), "disabled")?>
|
||||
</select><span id="reiserfs" class="warning"<?if (!fsType('reiserfs')):?> style="display:none"<?endif;?>><i class="fa fa-warning"></i> _(ReiserFS is deprecated, please use another file system)_!</span>
|
||||
|
||||
:info_file_system_help:
|
||||
|
||||
<?elseif (!isSubpool($name) && _var($disk,'slots',0)>1):?>
|
||||
_(File system status)_:
|
||||
: <?=_(_var($disk,'fsStatus'))?>
|
||||
|
||||
_(File system type)_:
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsProfile(false)" <?=disabled_if($fsTypeImmutable)?>>
|
||||
<?=mk_option(_var($disk,'fsType'), "auto", _('auto'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "zfs", _('zfs'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "btrfs", _('btrfs'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "luks:zfs", _('zfs')." - "._('encrypted'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "luks:btrfs", _('btrfs')." - "._('encrypted'))?>
|
||||
</select>
|
||||
|
||||
_(Allocation profile)_:
|
||||
: <select id="diskFsProfileBTRFS" name="diskFsProfile.<?=_var($disk,'idx',0)?>" style="display:none" <?=disabled_if($fsTypeImmutable)?>>
|
||||
<?=mk_option(_var($disk,'fsProfile'),"single", _('single'))?>
|
||||
<?if (_var($disk,'slots',0)>=2) echo mk_option(_var($disk,'fsProfile'),"raid0", _('raid0'))?>
|
||||
<?if (_var($disk,'slots',0)>=2) echo mk_option(_var($disk,'fsProfile'),"raid1", _('raid1'))?>
|
||||
<?if (_var($disk,'slots',0)>=3) echo mk_option(_var($disk,'fsProfile'),"raid1c3", _('raid1c3'))?>
|
||||
<?if (_var($disk,'slots',0)>=4) echo mk_option(_var($disk,'fsProfile'),"raid1c4", _('raid1c4'))?>
|
||||
<?if (_var($disk,'slots',0)>=4) echo mk_option(_var($disk,'fsProfile'),"raid10", _('raid10'))?>
|
||||
<?if (_var($disk,'slots',0)>=3) echo mk_option(_var($disk,'fsProfile'),"raid5", _('raid5'))?>
|
||||
<?if (_var($disk,'slots',0)>=4) echo mk_option(_var($disk,'fsProfile'),"raid6", _('raid6'))?>
|
||||
</select>
|
||||
<select id="diskFsProfileZFS" name="diskFsProfile.<?=_var($disk,'idx',0)?>" style="display:none" onchange="selectDiskFsWidth()" <?=disabled_if($fsTypeImmutable)?>>
|
||||
<?if (_var($disk,'slots',0)==1) echo mk_option(_var($disk,'fsProfile'),"", _('single'))?>
|
||||
<?if (_var($disk,'slots',0)>=2) echo mk_option(_var($disk,'fsProfile'),"", _('stripe'))?>
|
||||
<?if ((_var($disk,'slots',0)%2)==0 || (_var($disk,'slots',0)%3)==0 || (_var($disk,'slots',0)%4)==0) echo mk_option(_var($disk,'fsProfile'),"mirror", _('mirror'))?>
|
||||
<?if (_var($disk,'slots',0)>=3) echo mk_option(_var($disk,'fsProfile'),"raidz1", _('raidz'))?>
|
||||
<?if (_var($disk,'slots',0)>=3) echo mk_option(_var($disk,'fsProfile'),"raidz2", _('raidz2'))?>
|
||||
<?if (_var($disk,'slots',0)>=4) echo mk_option(_var($disk,'fsProfile'),"raidz3", _('raidz3'))?>
|
||||
</select>
|
||||
<select id="diskFsWidthZFS" name="diskFsWidth.<?=_var($disk,'idx',0)?>" style="display:none" <?=disabled_if($fsTypeImmutable)?>>
|
||||
</select>
|
||||
<?elseif (isSubpool($name)=="special" || isSubpool($name)=="logs" || isSubpool($name)=="dedup"):?>
|
||||
_(Allocation profile)_:
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx',0)?>" disabled>>
|
||||
<?=mk_option(_var($disk,'fsType'), "zfs", _('zfs'))?>
|
||||
</select>
|
||||
<select id="diskFsProfileZFS" name="diskFsProfile.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsWidth()" <?=disabled_if($fsTypeImmutable)?>>
|
||||
<?if (_var($disk,'slots',0)==1) echo mk_option(_var($disk,'fsProfile'),"", _('single'))?>
|
||||
<?if (_var($disk,'slots',0)>=2) echo mk_option(_var($disk,'fsProfile'),"", _('stripe'))?>
|
||||
<?if ((_var($disk,'slots',0)%2)==0 || (_var($disk,'slots',0)%3)==0 || (_var($disk,'slots',0)%4)==0) echo mk_option(_var($disk,'fsProfile'),"mirror", _('mirror'))?>
|
||||
</select>
|
||||
<select id="diskFsWidthZFS" name="diskFsWidth.<?=_var($disk,'idx',0)?>" style="display:none" <?=disabled_if($fsTypeImmutable)?>>
|
||||
</select>
|
||||
<?elseif (isSubpool($name)=="cache"):?>
|
||||
_(Allocation profile)_:
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx',0)?>" disabled>>
|
||||
<?=mk_option(_var($disk,'fsType'), "zfs", _('zfs'))?>
|
||||
</select>
|
||||
<select id="diskFsProfileZFS" name="diskFsProfile.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsWidth()" <?=disabled_if($fsTypeImmutable)?>>
|
||||
<?if (_var($disk,'slots',0)==1) echo mk_option(_var($disk,'fsProfile'),"", _('single'))?>
|
||||
<?if (_var($disk,'slots',0)>=2) echo mk_option(_var($disk,'fsProfile'),"", _('stripe'))?>
|
||||
</select>
|
||||
<select id="diskFsWidthZFS" name="diskFsWidth.<?=_var($disk,'idx',0)?>" style="display:none" <?=disabled_if($fsTypeImmutable)?>>
|
||||
</select>
|
||||
<?elseif (isSubpool($name)=="spares"):?>
|
||||
<?endif;?>
|
||||
|
||||
<?if (isSubpool($name)===false):?>
|
||||
<div markdown="1" id="compression" style="display:none">
|
||||
<?if (diskType('Data') || isPool($tag)):?>
|
||||
<div markdown="1" id="profile">
|
||||
_(Allocation profile)_:
|
||||
: <select id="diskFsProfile" name="diskFsProfile.<?=_var($disk,'idx')?>" <?=disabled_if($fsProfileImmutable)?>>
|
||||
</select>
|
||||
<select id="diskFsWidth" name="diskFsWidth.<?=_var($disk,'idx')?>" <?=disabled_if($fsProfileImmutable)?>>
|
||||
</select>
|
||||
|
||||
:info_profile_help:
|
||||
</div>
|
||||
<div markdown="1" id="compression">
|
||||
_(Compression)_:
|
||||
: <select id="diskCompression" name="diskCompression.<?=_var($disk,'idx',0)?>" <?=disabled_if(_var($disk,'fsStatus')=='Mounted')?>>
|
||||
<?=mk_option(_var($disk,'compression'), "off", _('Off'))?>
|
||||
@@ -716,8 +768,7 @@ _(Compression)_:
|
||||
|
||||
:info_compression_help:
|
||||
</div>
|
||||
|
||||
<div markdown="1" id="autotrim" style="display:none">
|
||||
<div markdown="1" id="autotrim">
|
||||
_(Autotrim)_:
|
||||
: <select id="diskAutotrim" name="diskAutotrim.<?=_var($disk,'idx',0)?>" <?=disabled_if(_var($disk,'fsStatus')=='Mounted')?>>
|
||||
<?=mk_option(_var($disk,'autotrim'), "on", _('On'))?>
|
||||
@@ -726,7 +777,8 @@ _(Autotrim)_:
|
||||
|
||||
:info_autotrim_help:
|
||||
</div>
|
||||
<?if (isPool($name)):?>
|
||||
<?endif;?>
|
||||
<?if (isPool($tag) && !isSubpool($tag)):?>
|
||||
_(Enable user share assignment)_:
|
||||
: <select id="shareEnabled" name="diskShareEnabled.<?=_var($disk,'idx',0)?>" onchange="freeSpace(this.value)" <?=disabled_if(_var($var,'fsState')!="Stopped")?>>
|
||||
<?=mk_option(_var($disk,'shareEnabled'), "yes", _('Yes'))?>
|
||||
@@ -742,6 +794,7 @@ _(Minimum free space)_:
|
||||
:info_free_space_help:
|
||||
|
||||
<?endif;?>
|
||||
<?if (diskType('Data') || (isPool($tag) && !isSubpool($tag))):?>
|
||||
_(Warning disk utilization threshold)_ (%):
|
||||
: <input type="number" min="0" max="100" name="diskWarning.<?=_var($disk,'idx',0)?>" autocomplete="off" spellcheck="false" class="narrow" value="<?=_var($disk,'warning')?>" placeholder="<?=_var($display,'warning')?>">
|
||||
|
||||
@@ -752,23 +805,21 @@ _(Critical disk utilization threshold)_ (%):
|
||||
|
||||
:info_critical_utilization_help:
|
||||
|
||||
<?endif;?>
|
||||
<?endif;?>
|
||||
|
||||
: <input type="submit" name="changeDisk" value="_(Apply)_" disabled><input type="button" id="doneButton" value="_(Done)_" onclick="done()">
|
||||
<?$erasable=false?>
|
||||
<?$removeable=false?>
|
||||
<?if (diskType('Parity','Data')):?>
|
||||
<?if (_var($var,'fsState')=="Stopped" && diskStatus('_NEW')): $erasable=true; endif;?>
|
||||
<?if (_var($var,'fsState')=="Started" && _var($var,'startMode')!="Normal" && diskType('Data')): $erasable=true; endif;?>
|
||||
<input type="button" id="eraseButton" value="_(Erase)_" onclick="eraseDisk('<?=$name?>')"<?=$erasable?'':' disabled'?>>
|
||||
<?endif;?>
|
||||
<?if (isPool($name) && strpos($name,$_tilde_)===false):?>
|
||||
<?if (isPool($name) && isSubpool($name)===false):?>
|
||||
<?if (_var($var,'fsState')=="Stopped" || (_var($var,'fsState')=="Started" && _var($var,'startMode')!="Normal")): $erasable=true; endif;?>
|
||||
<input type="button" id="eraseButton" value="_(Erase)_" onclick="eraseDisk('<?=$name?>')"<?=$erasable?'':' disabled'?>>
|
||||
<?endif;?>
|
||||
<?if (_var($var,'fsState')=="Stopped" && isPool($name)):?>
|
||||
<?$empty = _var($disk,'devices',0)==0?>
|
||||
<input type="button" value="_(Delete Pool)_" onclick="deletePool()"<?=$empty?'':' disabled'?>><?if (!$empty):?>_(Unassign **ALL** devices to delete this pool)_<?endif;?>
|
||||
<input type="button" id="eraseButton" value="_(Erase Pool)_" onclick="eraseDisk('<?=$name?>')"<?=$erasable?'':' disabled'?>>
|
||||
<?if (_var($var,'fsState')=="Stopped"): $removeable=true; endif;?>
|
||||
<input type="button" id="removeButton" value="_(Remove Pool)_" onclick="removePool('<?=$name?>')"<?=$removeable?'':' disabled'?>>
|
||||
<?endif;?>
|
||||
</form>
|
||||
|
||||
@@ -1366,19 +1417,13 @@ _(SMART attribute notifications)_:
|
||||
<form markdown="1" method="POST" action="/update.htm" target="progressFrame" onsubmit="return validate(this.poolName.value)">
|
||||
<input type="hidden" name="poolNameOrig" value="<?=$name?>">
|
||||
<input type="hidden" name="changeSlots" value="apply">
|
||||
<p>_(Caution)_: _(Renaming the pool will change the share storage allocations)_. _(After renaming the pool, check that your shares are assigned to the proper primary and secondary storage locations)_.</p>
|
||||
_(Name)_:
|
||||
: <input type="text" name="poolName" maxlength="40" value="<?=$name?>">
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<form name="deletepool" method="POST" action="/update.htm" target="progressFrame" style="display:none">
|
||||
<input type="hidden" name="changeSlots" value="apply">
|
||||
<input type="hidden" name="poolName" value="<?=$name?>">
|
||||
<input type="hidden" name="poolSlots" value="0">
|
||||
<input type='hidden' name='csrf_token' value='<?=_var($var,'csrf_token')?>'>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
<?if (count($sheets)>1):?>
|
||||
|
@@ -154,7 +154,7 @@ _(Enable spinup groups)_:
|
||||
|
||||
:disk_spinup_groups_help:
|
||||
|
||||
_(Default file system)_:
|
||||
_(Default file system for Array disks)_:
|
||||
: <select name="defaultFsType">
|
||||
<?=mk_option($var['defaultFsType'], "xfs", _('xfs'));?>
|
||||
<?=mk_option($var['defaultFsType'], "zfs", _('zfs'));?>
|
||||
|
@@ -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)'");
|
||||
|
@@ -117,51 +117,122 @@ if ($cert2Present) {
|
||||
}
|
||||
}
|
||||
|
||||
// Tailscale LE cert
|
||||
$cert3File = "/boot/config/ssl/certs/ts_bundle.pem";
|
||||
$cert3Present = file_exists("$cert3File");
|
||||
if ($cert3Present) {
|
||||
$cert3Subject = exec("/usr/bin/openssl x509 -in $cert3File -noout -subject -nameopt multiline 2>/dev/null|sed -n 's/ *commonName *= //p'");
|
||||
$cert3Issuer = exec("/usr/bin/openssl x509 -in $cert3File -noout -text | sed -n -e 's/^.*Issuer: //p'");
|
||||
$cert3Expires = exec("/usr/bin/openssl x509 -in $cert3File -noout -text | sed -n -e 's/^.*Not After : //p'");
|
||||
}
|
||||
|
||||
// Note: this disables FQDN6 urls since they are not supported by myunraid.net DNS currently
|
||||
if (!empty($nginx['NGINX_LANFQDN6'])) unset($nginx['NGINX_LANFQDN6']);
|
||||
|
||||
$http_port = _var($var,'PORT','80') != '80' ? ":{$var['PORT']}" : '';
|
||||
$https_port = _var($var,'PORTSSL','443') != '443' ? ":{$var['PORTSSL']}" : '';
|
||||
$http_ip_url = "http://"._var($nginx,'NGINX_LANIP')."{$http_port}/";
|
||||
$https_ip_url = "https://"._var($nginx,'NGINX_LANIP')."{$https_port}/";
|
||||
$http_ip6_url = "http://["._var($nginx,'NGINX_LANIP6')."]{$http_port}/";
|
||||
$https_ip6_url = "https://["._var($nginx,'NGINX_LANIP6')."]{$https_port}/";
|
||||
$http_mdns_url = "http://"._var($nginx,'NGINX_LANMDNS')."{$http_port}/";
|
||||
$https_mdns_url = "https://"._var($nginx,'NGINX_LANMDNS')."{$https_port}/";
|
||||
$https_fqdn_url = "https://"._var($nginx,'NGINX_LANFQDN')."{$https_port}/";
|
||||
$https_fqdn6_url = "https://"._var($nginx,'NGINX_LANFQDN6')."{$https_port}/";
|
||||
$http_ip_url = 'http://'._var($nginx,'NGINX_LANIP').$http_port.'/';
|
||||
$https_ip_url = 'https://'._var($nginx,'NGINX_LANIP').$https_port.'/';
|
||||
// bare IPv6 addresses need to be surrounded in brackets
|
||||
$http_ip6_url = 'http://'.'['._var($nginx,'NGINX_LANIP6').']'.$http_port.'/';
|
||||
$https_ip6_url = 'https://'.'['._var($nginx,'NGINX_LANIP6').']'.$https_port.'/';
|
||||
$http_mdns_url = 'http://'._var($nginx,'NGINX_LANMDNS').$http_port.'/';
|
||||
$https_mdns_url = 'https://'._var($nginx,'NGINX_LANMDNS').$https_port.'/';
|
||||
$https_fqdn_url = 'https://'._var($nginx,'NGINX_LANFQDN').$https_port.'/';
|
||||
$https_fqdn6_url = 'https://'._var($nginx,'NGINX_LANFQDN6').$https_port.'/';
|
||||
|
||||
$urls = [];
|
||||
// push an array of four values into the $urls array:
|
||||
// 0 - the url
|
||||
// 1 - the url it redirects to, or null
|
||||
// 2 - the certificate file used, or null
|
||||
// 3 - self-signed certificate, or false
|
||||
// push an array of five values into the $urls array:
|
||||
// 0 - type of url ['LAN','WAN','WG','TAILSCALE']
|
||||
// 1 - the url
|
||||
// 3 - the url it redirects to, or null
|
||||
// 4 - the certificate file used, or null
|
||||
// 5 - self-signed certificate, or false
|
||||
|
||||
// define LAN access urls and redirects that change based on USE_SSL setting
|
||||
switch(_var($var,'USE_SSL','no')) {
|
||||
case 'no':
|
||||
if (!empty($nginx['NGINX_LANIP'])) $urls[] = [$http_ip_url, null, null, false];
|
||||
if (!empty($nginx['NGINX_LANIP6'])) $urls[] = [$http_ip6_url, null, null, false];
|
||||
if (!empty($nginx['NGINX_LANMDNS'])) $urls[] = [$http_mdns_url, null, null, false];
|
||||
if (!empty($nginx['NGINX_LANFQDN'])) $urls[] = [$https_fqdn_url, null, "certificate_bundle.pem", false];
|
||||
if (!empty($nginx['NGINX_LANFQDN6'])) $urls[] = [$https_fqdn6_url, null, "certificate_bundle.pem", false];
|
||||
if (!empty($nginx['NGINX_LANIP'])) $urls[] = ['LAN', $http_ip_url, null, null, false];
|
||||
if (!empty($nginx['NGINX_LANIP6'])) $urls[] = ['LAN', $http_ip6_url, null, null, false];
|
||||
if (!empty($nginx['NGINX_LANMDNS'])) $urls[] = ['LAN', $http_mdns_url, null, null, false];
|
||||
break;
|
||||
case 'yes':
|
||||
if (!empty($nginx['NGINX_LANIP'])) $urls[] = [$http_ip_url, $https_ip_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANIP'])) $urls[] = [$https_ip_url, null, "{$var['NAME']}_unraid_bundle.pem", $cert1SelfSigned];
|
||||
if (!empty($nginx['NGINX_LANIP6'])) $urls[] = [$http_ip6_url, $https_ip6_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANIP6'])) $urls[] = [$https_ip6_url, null, "{$var['NAME']}_unraid_bundle.pem", $cert1SelfSigned];
|
||||
if (!empty($nginx['NGINX_LANMDNS'])) $urls[] = [$http_mdns_url, $https_mdns_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANMDNS'])) $urls[] = [$https_mdns_url, null, "{$var['NAME']}_unraid_bundle.pem", $cert1SelfSigned];
|
||||
if (!empty($nginx['NGINX_LANFQDN'])) $urls[] = [$https_fqdn_url, null, "certificate_bundle.pem", false];
|
||||
if (!empty($nginx['NGINX_LANFQDN6'])) $urls[] = [$https_fqdn6_url, null, "certificate_bundle.pem", false];
|
||||
if (!empty($nginx['NGINX_LANIP'])) $urls[] = ['LAN', $http_ip_url, $https_ip_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANIP'])) $urls[] = ['LAN', $https_ip_url, null, "{$var['NAME']}_unraid_bundle.pem", $cert1SelfSigned];
|
||||
if (!empty($nginx['NGINX_LANIP6'])) $urls[] = ['LAN', $http_ip6_url, $https_ip6_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANIP6'])) $urls[] = ['LAN', $https_ip6_url, null, "{$var['NAME']}_unraid_bundle.pem", $cert1SelfSigned];
|
||||
if (!empty($nginx['NGINX_LANMDNS'])) $urls[] = ['LAN', $http_mdns_url, $https_mdns_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANMDNS'])) $urls[] = ['LAN', $https_mdns_url, null, "{$var['NAME']}_unraid_bundle.pem", $cert1SelfSigned];
|
||||
break;
|
||||
case 'auto': // aka strict
|
||||
if (!empty($nginx['NGINX_LANIP'])) $urls[] = [$http_ip_url, $https_fqdn_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANIP6'])) $urls[] = [$http_ip6_url, $https_fqdn6_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANMDNS'])) $urls[] = [$http_mdns_url, $https_fqdn_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANFQDN'])) $urls[] = [$https_fqdn_url, null, "certificate_bundle.pem", false];
|
||||
if (!empty($nginx['NGINX_LANFQDN6'])) $urls[] = [$https_fqdn6_url, null, "certificate_bundle.pem", false];
|
||||
if (!empty($nginx['NGINX_LANIP']) && !empty($nginx['NGINX_LANFQDN'])) $urls[] = ['LAN', $http_ip_url, $https_fqdn_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANIP6']) && !empty($nginx['NGINX_LANFQDN6'])) $urls[] = ['LAN', $http_ip6_url, $https_fqdn6_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANMDNS']) && !empty($nginx['NGINX_LANFQDN'])) $urls[] = ['LAN', $http_mdns_url, $https_fqdn_url, null, false];
|
||||
break;
|
||||
}
|
||||
|
||||
// define FQDN urls for each interface
|
||||
// when multiple FQDN urls are available for a given interface, make sure they are sorted
|
||||
asort($nginx);
|
||||
foreach ($nginx as $key => $host) {
|
||||
if (!$host) continue;
|
||||
// Only process keys that include 'FQDN'
|
||||
if (strpos($key, 'FQDN') === false) continue;
|
||||
// Extract the interface from the key, e.g., 'NGINX_LANFQDN' -> 'LAN', 'NGINX_WANFQDN' -> 'WAN', NGINX_WG0FQDN -> WG, NGINX_TAILSCALE1FQDN -> TAILSCALE
|
||||
// Note: this specifically excludes FQDN6 urls since they are not supported by myunraid.net DNS currently
|
||||
if (preg_match('/^NGINX_([A-Z]+)(\d*)FQDN$/', $key, $matches)) {
|
||||
$interface = $matches[1]; // Interface type (LAN, WAN, WG, TAILSCALE, etc.)
|
||||
// ignore the WAN interface because we don't have access to the WANPORT here
|
||||
if ($interface == "WAN") continue;
|
||||
$pem = null;
|
||||
if (str_ends_with($host, '.myunraid.net')) $pem = 'certificate_bundle.pem';
|
||||
elseif (str_ends_with($host, '.ts.net')) $pem = 'ts_bundle.pem';
|
||||
$url = 'https://'.$host.$https_port."/";
|
||||
$urls[] = [$interface, $url, null, $pem, false];
|
||||
}
|
||||
}
|
||||
|
||||
// determine whether there are urls for a given interface
|
||||
function has_urls($interface) {
|
||||
global $urls;
|
||||
foreach($urls as $url) {
|
||||
if ($url[0] == $interface) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// show all urls for a given interface
|
||||
function show_urls($interface) {
|
||||
global $urls;
|
||||
// 0 - type of url ['LAN','WAN','WG','TAILSCALE']
|
||||
// 1 - the url
|
||||
// 3 - the url it redirects to, or null
|
||||
// 4 - the certificate file used, or null
|
||||
// 5 - self-signed certificate, or false
|
||||
$output = "";
|
||||
$linestart = "<dt> </dt><dd>";
|
||||
$lineend = "</dd>\n";
|
||||
$first = true;
|
||||
foreach($urls as $url) {
|
||||
if ($url[0] == $interface) {
|
||||
$msg = "<a href='$url[1]'>$url[1]</a>";
|
||||
if ($url[2]) $msg .= " "._("redirects to")." <a href='$url[2]'>$url[2]</a>";
|
||||
if ($url[3]) $msg .= " "._("uses")." ".$url[3];
|
||||
if ($url[4]) $msg .= "<span class='warning'> <i class='fa fa-warning fa-fw'></i> "._("is a self-signed certificate, ignore the browser's warning and proceed to the GUI")."</span>";
|
||||
// 2nd+ urls need leading $linestart
|
||||
$output .= ($first ? "" : $linestart).$msg.$lineend;
|
||||
$first = false;
|
||||
}
|
||||
}
|
||||
if ($first) {
|
||||
$output = "none";
|
||||
} else {
|
||||
// strip final trailing $lineend as it will be added by markdown
|
||||
$output = substr($output, 0, strlen($lineend)*-1);
|
||||
}
|
||||
echo $output;
|
||||
}
|
||||
|
||||
$cert_time_format = $display['date'].($display['date']!='%c' ? ', '.str_replace(['%M','%R'],['%M:%S','%R:%S'],$display['time']):'');
|
||||
$provisionlabel = $isWildcardCert ? _('Renew') : _('Provision');
|
||||
$disabled_provision = $keyfile===false || ($isWildcardCert && $retval_expired===0) || !$addr ? 'disabled' : '';
|
||||
@@ -334,24 +405,34 @@ _(Local TLD)_:
|
||||
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
_(Local access URLs)_:
|
||||
: <?
|
||||
// url[0] = url
|
||||
// url[1] = redirect url or null
|
||||
// url[2] = certificate used or null
|
||||
// url[3] = is certificate self-signed T/F
|
||||
$n = 0;
|
||||
foreach($urls as $url) {
|
||||
$msg = "";
|
||||
if ($url[1]) $msg .= " "._("redirects to")." <a href='$url[1]'>$url[1]</a>";
|
||||
if ($url[2]) $msg .= " "._("uses")." ".$url[2];
|
||||
if ($url[3]) $msg .= "<span class='warning'> <i class='fa fa-warning fa-fw'></i> "._("is a self-signed certificate, ignore the browser's warning and proceed to the GUI")."</span>";
|
||||
echo ($n ? "<dt> </dt><dd>" : ""),"<a href='$url[0]'>$url[0]</a>$msg",($n++ ? "</dd>" : "");
|
||||
}?>
|
||||
: <? show_urls('LAN'); ?>
|
||||
|
||||
:mgmt_local_access_urls_help:
|
||||
|
||||
<?if (has_urls('WG')): ?>
|
||||
|
||||
_(WireGuard URLs)_:
|
||||
: <? show_urls('WG'); ?>
|
||||
|
||||
:mgmt_wg_access_urls_help:
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<?if (has_urls('TAILSCALE')): ?>
|
||||
|
||||
_(Tailscale URLs)_:
|
||||
: <? show_urls('TAILSCALE'); ?>
|
||||
|
||||
:mgmt_tailscale_access_urls_help:
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<?if ($cert1Present):?>
|
||||
<hr>
|
||||
|
||||
_(Self-signed or user-provided certificate)_:
|
||||
: <?=$cert1File?>
|
||||
|
||||
@@ -386,6 +467,9 @@ _(Self-signed certificate file)_:
|
||||
<input type="hidden" name="server_name" value="<?=strtok(_var($_SERVER,'HTTP_HOST'),":")?>">
|
||||
<input type="hidden" name="server_addr" value="<?=_var($_SERVER,'SERVER_ADDR')?>">
|
||||
<?if ($cert2Present):?>
|
||||
|
||||
<hr>
|
||||
|
||||
_(Unraid Let's Encrypt certificate)_:
|
||||
: <?=$cert2File?>
|
||||
|
||||
@@ -415,6 +499,31 @@ _(CA-signed certificate file)_:
|
||||
|
||||
: <button type="submit" name="changePorts" value="Provision" <?=$disabled_provision?>><?=$provisionlabel?></button><button type="submit" name="changePorts" value="Delete" <?=$disabled_delete?> >_(Delete)_</button><?=$disabled_provision_msg?>
|
||||
|
||||
|
||||
<?if ($cert3Present):?>
|
||||
|
||||
<hr>
|
||||
|
||||
_(Tailscale Let's Encrypt certificate)_:
|
||||
: <?=$cert3File?>
|
||||
|
||||
_(Certificate URL)_:
|
||||
: <?="<a href='https://$cert3Subject$https_port'>$cert3Subject</a>"?>
|
||||
|
||||
_(Certificate issuer)_:
|
||||
: <?=$cert3Issuer?>
|
||||
|
||||
_(Certificate expiration)_:
|
||||
: <?=_(my_date($cert_time_format, strtotime($cert3Expires)),0)?>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
:mgmt_certificate_expiration_help:
|
||||
|
||||
</form>
|
||||
|
||||
<?if (has_urls('WG')): ?>
|
||||
|
||||
<small>"WireGuard" and the "WireGuard" logo are registered trademarks of Jason A. Donenfeld</small>
|
||||
|
||||
<?endif;?>
|
||||
|
@@ -88,7 +88,7 @@ _(Select Proxy)_:
|
||||
>
|
||||
> Outgoing connections from the webgui and some system processes will use the specified http proxy. Docker container installs and updates will use the proxy, but the container itself will not, neither will any VMs.
|
||||
>
|
||||
> For a more comprehensive solution you might consider setting up <u><a href='https://docs.unraid.net/unraid-os/manual/security/vpn/#configuring-vpn-tunneled-access-for-system/' target='_blank'>_(VPN tunnel access for System)_</a></u>.
|
||||
> For a more comprehensive solution you might consider setting up <u><a href='https://docs.unraid.net/go/configuring-vpn-tunneled-access-for-system/' target='_blank'>_(VPN tunnel access for System)_</a></u>.
|
||||
:end
|
||||
|
||||
<p><strong>_(Outgoing Proxy)_ 1</strong></p>
|
||||
|
@@ -28,7 +28,7 @@ if (!file_exists($log)) touch($log);
|
||||
:php_settings_plug:
|
||||
This utility is used for development purposes only and allows Plugin Authors to verify their PHP code by enabling different levels of PHP error reporting.
|
||||
|
||||
By default error logging is minimum and any errors are shown on screen. Changing the **Error reporting level** will capture the selected level of errors
|
||||
By default error logging is minimum and errors are not shown on screen. Changing the **Error reporting level** will capture the selected level of errors
|
||||
into a LOG file, which can be opened in a separate window to monitor in real-time the events when visiting various GUI pages or executing background
|
||||
processes on the server.
|
||||
|
||||
@@ -46,12 +46,12 @@ under normal running conditions.
|
||||
<input type="hidden" name="log_errors" value="1">
|
||||
_(Error reporting level)_:
|
||||
: <select name="error_reporting" onchange="toggleScreen(this.selectedIndex)">
|
||||
<?=mk_option(_var($conf,'error_reporting'), "", "_(Default)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), "32767", "_(All Categories)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), "1", "_(Errors Only)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), "2", "_(Warnings Only)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), "8", "_(Notices Only)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), "8192", "_(Deprecated Only)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), strval(E_ALL & ~E_NOTICE & ~E_WARNING & ~E_DEPRECATED), "_(Default)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), strval(E_ALL), "_(All Categories)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), strval(E_ERROR), "_(Errors Only)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), strval(E_WARNING), "_(Warnings Only)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), strval(E_NOTICE), "_(Notices Only)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), strval(E_DEPRECATED), "_(Deprecated Only)_");?>
|
||||
</select>
|
||||
|
||||
|
||||
@@ -147,11 +147,12 @@ function PHPinfo() {
|
||||
}
|
||||
function preset(form) {
|
||||
// reset to default settings
|
||||
// derived from default .ini file installed at boot
|
||||
if (form.error_reporting.selectedIndex==0) {
|
||||
form.error_log.value = "";
|
||||
form.display_startup_errors.value = "";
|
||||
form.display_errors.value = "";
|
||||
form.log_errors.value = "";
|
||||
form.error_log.value = "<?=$log?>";
|
||||
form.display_startup_errors.value = "0";
|
||||
form.display_errors.value = "0";
|
||||
form.log_errors.value = "1";
|
||||
}
|
||||
$.cookie('reload_php',1);
|
||||
}
|
||||
|
@@ -86,11 +86,11 @@ _(Enter route + gateway + metric)_:
|
||||
<input type="text" name="gateway" class="fixed" value="" list="device" placeholder="_(gateway name or address)_" required>
|
||||
<datalist id="device"><?foreach ($list as $port):?><?echo "<option value='$port'>"?><?endforeach;?></datalist>
|
||||
<input type="text" name="metric" min="1" max="9999" value="" class="trim" placeholder="1"><i class="fa fa-sort-numeric-asc"></i> *_(optional metric (lowest is preferred))_*
|
||||
|
||||
<input type="hidden" name="task" value="Add Route">
|
||||
:eth_routing_table_help:
|
||||
|
||||
|
||||
: <input type="submit" name="task" value="_(Add Route)_"><input type="button" value="_(Done)_" class="lock" onclick="done()">
|
||||
: <input type="submit" value="_(Add Route)_"><input type="button" value="_(Done)_" class="lock" onclick="done()">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -19,6 +19,8 @@ Cond="(($var['shareSMBEnabled']!='no') && (isset($name)?array_key_exists($name,$
|
||||
require_once "$docroot/webGui/include/InputSecurity.php";
|
||||
|
||||
$width = [123,300];
|
||||
/* Sort user array by keys in natural order */
|
||||
uksort($users, 'strnatcmp');
|
||||
?>
|
||||
:smb_security_help:
|
||||
|
||||
|
@@ -50,6 +50,12 @@ $pools = array_filter($pools, function($pool) {
|
||||
return !isSubpool($pool);
|
||||
});
|
||||
|
||||
/* Check for cachePool2 only which is a situation where the primary device is the cachePool2 device. */
|
||||
if ((! $share['cachePool']) && ($share['cachePool2'])) {
|
||||
$share['cachePool'] = $share['cachePool2'];
|
||||
$share['cachePool2'] = "";
|
||||
}
|
||||
|
||||
/* Check for non existent pool device. */
|
||||
if ($share['cachePool'] && !in_array($share['cachePool'], $pools)) {
|
||||
$poolDefined = false;
|
||||
@@ -61,16 +67,16 @@ if ($share['cachePool'] && !in_array($share['cachePool'], $pools)) {
|
||||
|
||||
/* Check for pool 2 (or array) being defined. */
|
||||
if ((($share['useCache'] == "yes") || ($share['useCache'] == "prefer")) && ($poolsOnly) && (!$share['cachePool2'])) {
|
||||
$poolDefined2 = false;
|
||||
$share['useCache'] = "yes";
|
||||
$poolDefined2 = true;
|
||||
$share['useCache'] = "only";
|
||||
} else if ($share['cachePool2']) {
|
||||
$poolDefined2 = in_array($share['cachePool2'], $pools);
|
||||
} else {
|
||||
$poolDefined2 = true;
|
||||
}
|
||||
|
||||
$cachePoolCapitalized = ucfirst($share['cachePool']);
|
||||
$cachePoolCapitalized2 = $share['cachePool2'] ? ucfirst($share['cachePool2']) : _("Array");
|
||||
$cachePoolCapitalized = compress(my_disk($share['cachePool'],$display['raw']));
|
||||
$cachePoolCapitalized2 = $share['cachePool2'] ? compress(my_disk($share['cachePool2'],$display['raw'])) : _("Array");
|
||||
|
||||
function globalInclude($name) {
|
||||
global $var;
|
||||
@@ -535,14 +541,10 @@ _(Mover action)_:
|
||||
|
||||
: <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">
|
||||
|
||||
: <input type="submit" name="cmdEditShare" value="_(Apply)_" onclick="this.value='Apply'" disabled><input type="button" value="_(Done)_" onclick="done()">
|
||||
</div>
|
||||
<?endif;?>
|
||||
</form>
|
||||
|
||||
@@ -638,6 +640,7 @@ function updateScreen(cache, slow) {
|
||||
secondaryDropdown.options[i].disabled = true;
|
||||
}
|
||||
secondaryDropdown.selectedIndex = 0;
|
||||
checkRequiredSecondary = false;
|
||||
|
||||
if (poolsOnly) {
|
||||
$('#moverDirection2').hide();
|
||||
@@ -1291,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;?>
|
||||
|
@@ -18,7 +18,7 @@ Cond="_var($var,'fsState')!='Stopped' && _var($var,'shareUser')=='e'"
|
||||
<?
|
||||
/* Function to filter out unwanted disks, check if any valid disks exist, and ignore disks with a blank device. */
|
||||
function checkDisks($disks) {
|
||||
global $pools, $poolsOnly;
|
||||
global $pools;
|
||||
|
||||
$rc = false;
|
||||
|
||||
@@ -35,9 +35,6 @@ function checkDisks($disks) {
|
||||
return $rc;
|
||||
}
|
||||
|
||||
/* If the configuration is pools only, then no array disks are available. */
|
||||
$poolsOnly = ((int)$var['SYS_ARRAY_SLOTS'] <= 2) ? true : false;
|
||||
|
||||
/* Are there any array disks? */
|
||||
$disks = parse_ini_file('state/disks.ini',true) ?? [];
|
||||
$nodisks = checkDisks($disks) ? "" : "disabled";
|
||||
|
@@ -103,7 +103,7 @@ function checkKey(form) {
|
||||
// check syntax of ssh keys
|
||||
var rows = form.text.value.split('\n');
|
||||
for (var i=0,row; row=rows[i]; i++) {
|
||||
if (row.search(/^(ssh-ed25519 AAAAC3NzaC1lZDI1NTE5|sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29t|ssh-rsa AAAAB3NzaC1yc2)[0-9A-Za-z+/]+[=]{0,3}(\s.*)?$/)==-1) {
|
||||
if (row.search(/^(ssh-dss AAAAB3NzaC1kc3|ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNT|sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb2|ssh-ed25519 AAAAC3NzaC1lZDI1NTE5|sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29t|ssh-rsa AAAAB3NzaC1yc2)[0-9A-Za-z+/]+[=]{0,3}(\s.*)?$/)==-1) {
|
||||
swal({title:"_(Invalid Key)_",text:"["+(i+1)+"] "+row.split(' ')[0]+": _(Syntax of the key is incorrect)_!",type:"error",html:true,confirmButtonText:"_(Ok)_"});
|
||||
return false;
|
||||
}
|
||||
|
@@ -1032,14 +1032,13 @@ tstate['wg0'] = "<?=$check_wg0 ? 'active' : 'passive'?>";
|
||||
|
||||
var statistics = new NchanSubscriber('/sub/wireguard',{subscriber:'websocket'});
|
||||
statistics.on('message', function(data) {
|
||||
var list = [];
|
||||
var rows = data.split('\0');
|
||||
var list = [], n = [];
|
||||
var x = 0; var vtun = '';
|
||||
// get all existing tunnels
|
||||
$('div[id^="block-wg"]').each(function(){list.push($(this).prop('id').split('-')[1]);});
|
||||
// update active tunnels
|
||||
for (var i=0,row; row=rows[i]; i++) {
|
||||
var info = row.split(';');
|
||||
var rows = JSON.parse(data);
|
||||
for (var i=0,info; info=rows[i]; i++) {
|
||||
if (info[0] != vtun) {
|
||||
vtun = info[0];
|
||||
// remove tunnel from inactive list
|
||||
@@ -1281,7 +1280,7 @@ _(Local tunnel address IPv6)_:
|
||||
</div>
|
||||
</div>
|
||||
_(Local endpoint)_:
|
||||
: <span class="input"><input type="text" id="endpoint-wg0" name="Endpoint:0" class="subnet" value="<?=$vpn_wg0?'':_var($wg0,'Endpoint:0')?>" onchange="toLC(this);quickValidate(this);" pattern="<?=$validText?>" title="_(IP address or FQDN)_" placeholder="<?=$vpn_wg0?'(_(not used)_)':preg_replace('/^(.+?\.)[0-9a-zA-Z]+(\.(my)?unraid.net)$/','$1<hash>$2',$public)?>">:
|
||||
: <span class="input"><input type="text" class="width:10%;" id="endpoint-wg0" name="Endpoint:0" class="subnet" value="<?=$vpn_wg0?'':_var($wg0,'Endpoint:0')?>" onchange="toLC(this);quickValidate(this);" pattern="<?=$validText?>" title="_(IP address or FQDN)_" placeholder="<?=$vpn_wg0?'(_(not used)_)':preg_replace('/^(.+?\.)[0-9a-zA-Z]+(\.(my)?unraid.net)$/','$1<hash>$2',$public)?>">:
|
||||
<input type="number" name="gui:ListenPort:0" class="port" min="1" max="65535" value="<?=$vpn_wg0?'':_var($wg0,'ListenPort:0')?>" onchange="if(quickValidate(this)) {portRemark($(document.wg0),'wg0',this.value)}" placeholder="<?=$vpn_wg0?'':_var($netport,'wg0')?>"></span>
|
||||
<span class="remark block" style="display:none">_(Remark)_: _(configure your router with port forwarding of port)_ **<span id="my-port-wg0"><?=_var($wg0,'ListenPort:0')?:_var($netport,'wg0')?></span>/_(UDP)_** _(to)_ **<?=$server?>:<?=_var($wg0,'ListenPort:0')?:_var($netport,'wg0')?>**</span><span class="upnp wg0 block"></span>
|
||||
<input type="hidden" name="ListenPort:0" value=""><dl id="endpoint4-wg0" style="display:none"></dl><dl id="endpoint6-wg0" style="display:none"></dl>
|
||||
@@ -1318,7 +1317,7 @@ _(Local tunnel firewall)_:
|
||||
:wg_local_tunnel_firewall_help:
|
||||
|
||||
_(MTU size)_:
|
||||
: <span class="input"><input type="number" name="MTU:0" class="trim" min="68" max="9198" value="<?=_var($wg0,'MTU:0')?>" onchange="quickValidate(this);" placeholder="(_(automatic)_)">_(bytes)_</span>
|
||||
: <span class="input"><input type="number" name="MTU:0" class="trim" min="68" max="9198" value="<?=_var($wg0,'MTU:0')?>" onchange="quickValidate(this);" placeholder="(_(auto)_)">_(bytes)_</span>
|
||||
|
||||
:wg_mtu_size_help:
|
||||
|
||||
@@ -1362,7 +1361,7 @@ _(Peer type of access)_:
|
||||
<?=mk_option(_var($wg0,"TYPE:$i"), "7", _("VPN tunneled access for system"),count($peer_wg0)==1?'':'disabled')?>
|
||||
<?=mk_option(_var($wg0,"TYPE:$i"), "8", _("VPN tunneled access for docker"),count($peer_wg0)==1?'':'disabled')?>
|
||||
</select></span>
|
||||
<span id="access-type-<?=$i?>" class="access-type"></span>
|
||||
<span id="access-type-<?=$i?>"</span>
|
||||
|
||||
<?if ($i==1):?>
|
||||
> ?>)
|
||||
@@ -1426,8 +1425,8 @@ _(Persistent keepalive)_:
|
||||
:wg_persistent_keepalive_help:
|
||||
|
||||
</div>
|
||||
<span class="pin">_(Data received)_: <span class="rx-wg0-<?=$i?>">0 B</span>_(Data sent)_: <span class="tx-wg0-<?=$i?>">0 B</span><br>_(Last handshake)_: <span class="hs-wg0-<?=$i?>">_(unknown)_</span></span>
|
||||
</div>
|
||||
<span class="pin">_(Data received)_: <span class="rx-wg0-<?=$i?>">0 B</span>_(Data sent)_: <span class="tx-wg0-<?=$i?>">0 B</span><br>_(Last handshake)_: <span class="hs-wg0-<?=$i?>">_(unknown)_</span></span>
|
||||
<?endforeach;?>
|
||||
|
||||
|
||||
|
@@ -224,7 +224,7 @@ _(Local tunnel address IPv6)_:
|
||||
</div>
|
||||
</div>
|
||||
_(Local endpoint)_:
|
||||
: <span class="input"><input type="text" id="endpoint-wgX" name="Endpoint:0" class="subnet" value="<?=$vpn_wgX?'':_var($wgX,'Endpoint:0')?>" onchange="toLC(this);quickValidate(this);" pattern="<?=$validText?>" title="_(IP address or FQDN)_" placeholder="<?=$vpn_wgX?'(_(not used)_)':preg_replace('/^(www\.).+(\.unraid.net)$/','$1<hash>$2',$public)?>">:
|
||||
: <span class="input"><input type="text" class="width:10%;" id="endpoint-wgX" name="Endpoint:0" class="subnet" value="<?=$vpn_wgX?'':_var($wgX,'Endpoint:0')?>" onchange="toLC(this);quickValidate(this);" pattern="<?=$validText?>" title="_(IP address or FQDN)_" placeholder="<?=$vpn_wgX?'(_(not used)_)':preg_replace('/^(www\.).+(\.unraid.net)$/','$1<hash>$2',$public)?>">:
|
||||
<input type="number" name="gui:ListenPort:0" class="port" min="1" max="65535" value="<?=$vpn_wgX?'':_var($wgX,'ListenPort:0')?>" onchange="if(quickValidate(this)) {portRemark($(document.wgX),'wgX',this.value)}" placeholder="<?=$vpn_wgX?'':_var($netport,'wgX')?>"></span>
|
||||
<span class="remark block" style="display:none">_(Remark)_: _(configure your router with port forwarding of port)_ **<span id="my-port-wgX"><?=_var($wgX,'ListenPort:0')?:_var($netport,'wgX')?></span>/_(UDP)_** _(to)_ **<?=$server?>:<?=_var($wgX,'ListenPort:0')?:_var($netport,'wgX')?>**</span><span class="upnp wgX block"></span>
|
||||
<input type="hidden" name="ListenPort:0" value=""><dl id="endpoint4-wgX" style="display:none"></dl><dl id="endpoint6-wgX" style="display:none"></dl>
|
||||
@@ -261,7 +261,7 @@ _(Local tunnel firewall)_:
|
||||
:wg_local_tunnel_firewall_help:
|
||||
|
||||
_(MTU size)_:
|
||||
: <span class="input"><input type="number" name="MTU:0" class="trim" min="68" max="9198" value="<?=_var($wgX,'MTU:0')?>" onchange="quickValidate(this);" placeholder="(_(automatic)_)">_(bytes)_</span>
|
||||
: <span class="input"><input type="number" name="MTU:0" class="trim" min="68" max="9198" value="<?=_var($wgX,'MTU:0')?>" onchange="quickValidate(this);" placeholder="(_(auto)_)">_(bytes)_</span>
|
||||
|
||||
:wg_mtu_size_help:
|
||||
|
||||
@@ -305,7 +305,7 @@ _(Peer type of access)_:
|
||||
<?=mk_option(_var($wgX,"TYPE:$i"), "7", _("VPN tunneled access for system"),count($peer_wgX)==1?'':'disabled')?>
|
||||
<?=mk_option(_var($wgX,"TYPE:$i"), "8", _("VPN tunneled access for docker"))?>
|
||||
</select></span>
|
||||
<span id="access-type-<?=$i?>" class="access-type"></span>
|
||||
<span id="access-type-<?=$i?>"></span>
|
||||
|
||||
<?if ($i==1):?>
|
||||
> ?>)
|
||||
@@ -369,8 +369,8 @@ _(Persistent keepalive)_:
|
||||
:wg_persistent_keepalive_help:
|
||||
|
||||
</div>
|
||||
<span class="pin">_(Data received)_: <span class="rx-wgX-<?=$i?>">0 B</span>_(Data sent)_: <span class="tx-wgX-<?=$i?>">0 B</span><br>_(Last handshake)_: <span class="hs-wgX-<?=$i?>">_(unknown)_</span></span>
|
||||
</div>
|
||||
<span class="pin">_(Data received)_: <span class="rx-wgX-<?=$i?>">0 B</span>_(Data sent)_: <span class="tx-wgX-<?=$i?>">0 B</span><br>_(Last handshake)_: <span class="hs-wgX-<?=$i?>">_(unknown)_</span></span>
|
||||
<?endforeach;?>
|
||||
|
||||
|
||||
|
@@ -529,7 +529,12 @@ $theme_dark = in_array($display['theme'], ['black', 'gray']);
|
||||
cookieEnabled = document.cookie.indexOf("cookietest=")!=-1;
|
||||
document.cookie = "cookietest=1; expires=Thu, 01-Jan-1970 00:00:01 GMT";
|
||||
if (!cookieEnabled) {
|
||||
document.write("<p class='error'><?=_('Browser cookie support required for Unraid OS webgui')?></p>");
|
||||
const errorElement = document.createElement('p');
|
||||
errorElement.classList.add('error');
|
||||
errorElement.textContent = "<?=_('Please enable cookies to use the Unraid webGUI')?>";
|
||||
|
||||
document.body.textContent = '';
|
||||
document.body.appendChild(errorElement);
|
||||
}
|
||||
</script>
|
||||
</form>
|
||||
@@ -543,7 +548,7 @@ $theme_dark = in_array($display['theme'], ['black', 'gray']);
|
||||
</div>
|
||||
|
||||
<? if (($twoFactorRequired && !empty($token)) || !$twoFactorRequired) { ?>
|
||||
<p class="js-removeTimeout"><a href="https://docs.unraid.net/unraid-os/manual/troubleshooting#lost-root-password" target="_blank"><?=_('Password recovery')?></a></p>
|
||||
<p class="js-removeTimeout"><a href="https://docs.unraid.net/go/lost-root-password/" target="_blank"><?=_('Password recovery')?></a></p>
|
||||
<? } ?>
|
||||
|
||||
</div>
|
||||
|
@@ -347,7 +347,14 @@ $THEME_DARK = in_array($display['theme'],['black','gray']);
|
||||
document.cookie = "cookietest=1";
|
||||
cookieEnabled = document.cookie.indexOf("cookietest=")!=-1;
|
||||
document.cookie = "cookietest=1; expires=Thu, 01-Jan-1970 00:00:01 GMT";
|
||||
if (!cookieEnabled) document.write("<p class='error'><?=_('Browser cookie support required for Unraid OS webgui')?></p>");
|
||||
if (!cookieEnabled) {
|
||||
const errorElement = document.createElement('p');
|
||||
errorElement.classList.add('error');
|
||||
errorElement.textContent = "<?=_('Please enable cookies to use the Unraid webGUI')?>";
|
||||
|
||||
document.body.textContent = '';
|
||||
document.body.appendChild(errorElement);
|
||||
}
|
||||
// Password toggling
|
||||
const $passToggle = document.querySelector('.js-pass-toggle');
|
||||
const $passToggleHideSvg = $passToggle.querySelector('.js-pass-toggle-hide');
|
||||
|
@@ -98,24 +98,62 @@ case 'ct':
|
||||
echo "\0".implode(';',array_map('urlencode',$cts));
|
||||
break;
|
||||
case 'is':
|
||||
$syslinux = file('/boot/syslinux/syslinux.cfg',FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$size = count($syslinux);
|
||||
$menu = $i = 0;
|
||||
$isol = "";
|
||||
$isolcpus = [];
|
||||
// find the default section
|
||||
while ($i < $size) {
|
||||
if (scan($syslinux[$i],'label ')) {
|
||||
$n = $i + 1;
|
||||
// find the current isolcpus setting
|
||||
while (!scan($syslinux[$n],'label ') && $n < $size) {
|
||||
if (scan($syslinux[$n],'menu default')) $menu = 1;
|
||||
if (scan($syslinux[$n],'append')) foreach (explode(' ',$syslinux[$n]) as $cmd) if (scan($cmd,'isolcpus')) {$isol = explode('=',$cmd)[1]; break;}
|
||||
$n++;
|
||||
if (is_file('/boot/syslinux/syslinux.cfg')) {
|
||||
$menu = $i = 0;
|
||||
$isol = "";
|
||||
$isolcpus = [];
|
||||
$bootcfg = file('/boot/syslinux/syslinux.cfg', FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$size = count($bootcfg);
|
||||
// find the default section
|
||||
while ($i < $size) {
|
||||
if (scan($bootcfg[$i],'label ')) {
|
||||
$n = $i + 1;
|
||||
// find the current isolcpus setting
|
||||
while (!scan($bootcfg[$n],'label ') && $n < $size) {
|
||||
if (scan($bootcfg[$n],'menu default')) $menu = 1;
|
||||
if (scan($bootcfg[$n],'append')) foreach (explode(' ',$bootcfg[$n]) as $cmd) if (scan($cmd,'isolcpus')) {$isol = explode('=',$cmd)[1]; break;}
|
||||
$n++;
|
||||
}
|
||||
if ($menu) break; else $i = $n - 1;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
} elseif (is_file('/boot/grub/grub.cfg')) {
|
||||
$isol = "";
|
||||
$isolcpus = [];
|
||||
$bootcfg = file('/boot/grub/grub.cfg', FILE_IGNORE_NEW_LINES);
|
||||
// find the default section
|
||||
$menu_entries = [];
|
||||
foreach ($bootcfg as $line) {
|
||||
if (preg_match('/set default=(\d+)/', $line, $match)) {
|
||||
$bootentry = (int)$match[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
// split boot entries
|
||||
foreach ($bootcfg as $line) {
|
||||
if (strpos($line, 'menuentry ') === 0) {
|
||||
$in_menuentry = true;
|
||||
$current_entry = $line . "\n";
|
||||
} elseif ($in_menuentry) {
|
||||
$current_entry .= $line . "\n";
|
||||
if (trim($line) === "}") {
|
||||
$menu_entries[] = $current_entry;
|
||||
$in_menuentry = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// search in selected menuentry
|
||||
$menuentry = explode("\n", $menu_entries[$bootentry]);
|
||||
// find the current isolcpus setting
|
||||
if (scan($menu_entries[$bootentry],'linux ')) {
|
||||
foreach ($menuentry as $cmd) {
|
||||
if (scan($cmd,'isolcpus')) {
|
||||
$isol = explode('=',$cmd)[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($menu) break; else $i = $n - 1;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
if ($isol != '') {
|
||||
// convert to individual numbers
|
||||
@@ -133,24 +171,28 @@ case 'is':
|
||||
break;
|
||||
case 'cmd':
|
||||
$isolcpus_now = $isolcpus_new = '';
|
||||
$syslinux = file('/boot/syslinux/syslinux.cfg',FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$cmdline = explode(' ',file_get_contents('/proc/cmdline'));
|
||||
foreach ($cmdline as $cmd) if (scan($cmd,'isolcpus')) {$isolcpus_now = $cmd; break;}
|
||||
$size = count($syslinux);
|
||||
$menu = $i = 0;
|
||||
// find the default section
|
||||
while ($i < $size) {
|
||||
if (scan($syslinux[$i],'label ')) {
|
||||
$n = $i + 1;
|
||||
// find the current isolcpus setting
|
||||
while (!scan($syslinux[$n],'label ') && $n < $size) {
|
||||
if (scan($syslinux[$n],'menu default')) $menu = 1;
|
||||
if (scan($syslinux[$n],'append')) foreach (explode(' ',$syslinux[$n]) as $cmd) if (scan($cmd,'isolcpus')) {$isolcpus_new = $cmd; break;}
|
||||
$n++;
|
||||
if (is_file('/boot/syslinux/syslinux.cfg')) {
|
||||
$bootcfg = file('/boot/syslinux/syslinux.cfg',FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($cmdline as $cmd) if (scan($cmd,'isolcpus')) {$isolcpus_now = $cmd; break;}
|
||||
$size = count($bootcfg);
|
||||
$menu = $i = 0;
|
||||
// find the default section
|
||||
while ($i < $size) {
|
||||
if (scan($bootcfg[$i],'label ')) {
|
||||
$n = $i + 1;
|
||||
// find the current isolcpus setting
|
||||
while (!scan($bootcfg[$n],'label ') && $n < $size) {
|
||||
if (scan($bootcfg[$n],'menu default')) $menu = 1;
|
||||
if (scan($bootcfg[$n],'append')) foreach (explode(' ',$bootcfg[$n]) as $cmd) if (scan($cmd,'isolcpus')) {$isolcpus_new = $cmd; break;}
|
||||
$n++;
|
||||
}
|
||||
if ($menu) break; else $i = $n - 1;
|
||||
}
|
||||
if ($menu) break; else $i = $n - 1;
|
||||
$i++;
|
||||
}
|
||||
$i++;
|
||||
} elseif (is_file('/boot/grub/grub.cfg')) {
|
||||
$bootcfg = file('/boot/grub/grub.cfg', FILE_IGNORE_NEW_LINES);
|
||||
}
|
||||
echo $isolcpus_now==$isolcpus_new ? 0 : 1;
|
||||
break;
|
||||
|
@@ -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');
|
||||
|
@@ -582,7 +582,7 @@ function viewHistory() {
|
||||
}
|
||||
function flashReport() {
|
||||
$.post('/webGui/include/Report.php',{cmd:'config'},function(check){
|
||||
if (check>0) addBannerWarning("<?=_('Your flash drive is corrupted or offline').'. '._('Post your diagnostics in the forum for help').'.'?> <a target='_blank' href='https://docs.unraid.net/unraid-os/manual/changing-the-flash-device'><?=_('See also here')?></a>");
|
||||
if (check>0) addBannerWarning("<?=_('Your flash drive is corrupted or offline').'. '._('Post your diagnostics in the forum for help').'.'?> <a target='_blank' href='https://docs.unraid.net/go/changing-the-flash-device/'><?=_('See also here')?></a>");
|
||||
});
|
||||
}
|
||||
$(function() {
|
||||
@@ -803,7 +803,7 @@ default:
|
||||
}
|
||||
echo "</span></span><span id='countdown'></span><span id='user-notice' class='red-text'></span>";
|
||||
echo "<span id='copyright'>Unraid® webGui ©2024, Lime Technology, Inc.";
|
||||
echo " <a href='https://docs.unraid.net/category/manual' target='_blank' title=\""._('Online manual')."\"><i class='fa fa-book'></i> "._('manual')."</a>";
|
||||
echo " <a href='https://docs.unraid.net/go/manual/' target='_blank' title=\""._('Online manual')."\"><i class='fa fa-book'></i> "._('manual')."</a>";
|
||||
echo "</span></div>";
|
||||
?>
|
||||
<script>
|
||||
|
@@ -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";
|
||||
|
@@ -50,7 +50,7 @@ $match = $_POST['match'];
|
||||
$checkbox = $_POST['multiSelect']=='true' ? "<input type='checkbox'>" : "";
|
||||
|
||||
/* Excluded folders to not show in the dropdown in the '/mnt/' directory only. */
|
||||
$excludedFolders = ["RecycleBin", "addons", "disks", "remotes", "rootshare", "user0"];
|
||||
$excludedFolders = ["RecycleBin", "addons", "remotes", "rootshare", "user0"];
|
||||
|
||||
echo "<ul class='jqueryFileTree'>";
|
||||
if ($_POST['show_parent']=='true' && is_top($rootdir)) echo "<li class='directory collapsed'>$checkbox<a href='#' rel=\"".htmlspecialchars(dirname($rootdir))."\">..</a></li>";
|
||||
|
@@ -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) {
|
||||
@@ -344,6 +344,48 @@ function my_mkdir($dirname,$permissions = 0777,$recursive = false,$own = "nobody
|
||||
chgrp($dirname, $grp);
|
||||
return($rtncode);
|
||||
}
|
||||
function my_rmdir($dirname) {
|
||||
if (!is_dir("$dirname")) {
|
||||
$return = [
|
||||
'rtncode' => "false",
|
||||
'type' => "NoDir",
|
||||
];
|
||||
return($return);
|
||||
}
|
||||
if (strpos($dirname,'/mnt/user/')===0) {
|
||||
$realdisk = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($dirname)." 2>/dev/null"));
|
||||
if (!empty($realdisk)) {
|
||||
$dirname = str_replace('/mnt/user/', "/mnt/$realdisk/", "$dirname");
|
||||
}
|
||||
}
|
||||
$fstype = trim(shell_exec(" stat -f -c '%T' ".escapeshellarg($dirname)));
|
||||
$rtncode = false;
|
||||
switch ($fstype) {
|
||||
case "zfs":
|
||||
$zfsoutput = array();
|
||||
$zfsdataset = trim(shell_exec("zfs list -H -o name ".escapeshellarg($dirname))) ;
|
||||
$cmdstr = "zfs destroy \"$zfsdataset\" 2>&1 ";
|
||||
$error = exec($cmdstr,$zfsoutput,$rtncode);
|
||||
$return = [
|
||||
'rtncode' => $rtncode,
|
||||
'output' => $zfsoutput,
|
||||
'dataset' => $zfsdataset,
|
||||
'type' => $fstype,
|
||||
'cmd' => $cmdstr,
|
||||
'error' => $error,
|
||||
];
|
||||
break;
|
||||
case "btrfs":
|
||||
default:
|
||||
$rtncode = rmdir($dirname);
|
||||
$return = [
|
||||
'rtncode' => $rtncode,
|
||||
'type' => $fstype,
|
||||
];
|
||||
break;
|
||||
}
|
||||
return($return);
|
||||
}
|
||||
function get_realvolume($path) {
|
||||
if (strpos($path,"/mnt/user/",0) === 0)
|
||||
$reallocation = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($path)." 2>/dev/null"));
|
||||
|
@@ -44,14 +44,14 @@ 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]);
|
||||
}
|
||||
if ($retval != 0) exec("ttyd-exec -i '$sock' bash --login");
|
||||
if ($retval != 0) exec("ttyd-exec -i '$sock' '" . posix_getpwuid(0)['shell'] . "' --login");
|
||||
break;
|
||||
case 'syslog':
|
||||
// read syslog file
|
||||
|
@@ -78,7 +78,7 @@ function tab_title($title,$path,$tag) {
|
||||
if (!$tag || substr($tag,-4)=='.png') {
|
||||
$file = "$path/icons/".($tag ?: strtolower(str_replace(' ','',$title)).".png");
|
||||
if (file_exists("$docroot/$file")) {
|
||||
return "<img src='/$file' class='icon'>$title";
|
||||
return "<img src='/$file' class='icon' style='max-width: 18px; max-height: 18px; width: auto; height: auto; object-fit: contain;'>$title";
|
||||
} else {
|
||||
return "<i class='fa fa-th title'></i>$title";
|
||||
}
|
||||
|
@@ -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];
|
||||
|
@@ -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)) {
|
||||
@@ -130,7 +129,7 @@ uksort($shares,'strnatcasecmp');
|
||||
|
||||
/* Function to filter out unwanted disks, check if any valid disks exist, and ignore disks with a blank device. */
|
||||
function checkDisks($disks) {
|
||||
global $pools, $poolsOnly;
|
||||
global $pools;
|
||||
|
||||
$rc = false;
|
||||
|
||||
@@ -149,112 +148,174 @@ function checkDisks($disks) {
|
||||
|
||||
// Display export settings
|
||||
function user_share_settings($protocol,$share) {
|
||||
if (empty($share)) return;
|
||||
if ($protocol!='yes' || $share['export']=='-') return "-";
|
||||
return ($share['export']=='e') ? _(ucfirst($share['security'])) : '<em>'._(ucfirst($share['security'])).'</em>';
|
||||
if (empty($share)) return;
|
||||
if ($protocol!='yes' || $share['export']=='-') return "-";
|
||||
return ($share['export']=='e') ? _(ucfirst($share['security'])) : '<em>'._(ucfirst($share['security'])).'</em>';
|
||||
}
|
||||
function globalInclude($name) {
|
||||
global $var;
|
||||
return substr($name,0,4)!='disk' || !$var['shareUserInclude'] || strpos("{$var['shareUserInclude']},","$name,")!==false;
|
||||
global $var;
|
||||
return substr($name,0,4)!='disk' || !$var['shareUserInclude'] || strpos("{$var['shareUserInclude']},","$name,")!==false;
|
||||
}
|
||||
function shareInclude($name) {
|
||||
global $include;
|
||||
return !$include || substr($name,0,4)!='disk' || strpos("$include,", "$name,")!==false;
|
||||
global $include;
|
||||
return !$include || substr($name,0,4)!='disk' || strpos("$include,", "$name,")!==false;
|
||||
}
|
||||
// Compute user shares & check encryption
|
||||
$crypto = false;
|
||||
foreach ($shares as $name => $share) {
|
||||
if ($all!=0 && (!$compute || $compute==$name)) exec("$docroot/webGui/scripts/share_size ".escapeshellarg($name)." ssz1 ".escapeshellarg($pools));
|
||||
$crypto |= _var($share,'luksStatus',0)>0;
|
||||
if ($all!=0 && (!$compute || $compute==$name)) exec("$docroot/webGui/scripts/share_size ".escapeshellarg($name)." ssz1 ".escapeshellarg($pools));
|
||||
$crypto |= _var($share,'luksStatus',0)>0;
|
||||
}
|
||||
// global shares include/exclude
|
||||
$myDisks = array_filter(array_diff(array_keys($disks), explode(',',$var['shareUserExclude'])), 'globalInclude');
|
||||
|
||||
// Share size per disk
|
||||
$ssz1 = [];
|
||||
if ($all==0)
|
||||
exec("rm -f /var/local/emhttp/*.ssz1");
|
||||
else
|
||||
foreach (glob("state/*.ssz1",GLOB_NOSORT) as $entry) $ssz1[basename($entry,'.ssz1')] = parse_ini_file($entry);
|
||||
if ($all==0) {
|
||||
exec("rm -f /var/local/emhttp/*.ssz1");
|
||||
} else {
|
||||
foreach (glob("state/*.ssz1",GLOB_NOSORT) as $entry) $ssz1[basename($entry,'.ssz1')] = parse_ini_file($entry);
|
||||
}
|
||||
|
||||
/* Define constants for magic strings */
|
||||
define('STATUS_GREEN_ON', 'green-on');
|
||||
define('STATUS_YELLOW_ON', 'yellow-on');
|
||||
define('LUKS_STATUS_UNKNOWN', 0);
|
||||
define('LUKS_STATUS_ENCRYPTED', 1);
|
||||
define('LUKS_STATUS_UNENCRYPTED', 2);
|
||||
|
||||
// Build table
|
||||
$row = 0;
|
||||
foreach ($shares as $name => $share) {
|
||||
/* Check if poolsOnly is true */
|
||||
$array = $share['cachePool2'] ? ucfirst($share['cachePool2']) : "<i class='fa fa-database fa-fw'></i>"._('Array');
|
||||
if ($poolsOnly) {
|
||||
/* If useCache is set to 'yes', change it to 'no'. */
|
||||
if (($share['useCache'] == 'yes') && (!$share['cachePool2'])) {
|
||||
$share['useCache'] = 'no';
|
||||
}
|
||||
/* If useCache is set to 'prefer', change it to 'only'. */
|
||||
if (($share['useCache'] == 'prefer') && (!$share['cachePool2'])) {
|
||||
$share['useCache'] = 'only';
|
||||
/* Is cachePool2 defined? If it is we need to show the cache pool 2 device name instead of 'Array'. */
|
||||
if ($share['cachePool2']) {
|
||||
$array = compress(my_disk($share['cachePool2'],$display['raw']));
|
||||
$indicator = "<i class='fa fa-bullseye fa-fw'></i>";
|
||||
} else {
|
||||
$array = _('Array');
|
||||
$indicator = "<i class='fa fa-database fa-fw'></i>";
|
||||
}
|
||||
|
||||
/* Check that the share storage assignments are valid. */
|
||||
if (($share['cachePool']) && (! in_array($share['cachePool'], $pools_check))) {
|
||||
$array = compress(my_disk($share['cachePool'],$display['raw']));
|
||||
$indicator = "<i class='fa fa-bullseye fa-fw'></i>";
|
||||
$share_valid = false;
|
||||
} else if (($share['cachePool2']) && (! in_array($share['cachePool2'], $pools_check))) {
|
||||
$array = compress(my_disk($share['cachePool2'],$display['raw']));
|
||||
$indicator = "<i class='fa fa-bullseye fa-fw'></i>";
|
||||
$share_valid = false;
|
||||
} else if (($poolsOnly) && (! $share['cachePool']) && (! $share['cachePool2'])) {
|
||||
$share_valid = false;
|
||||
} else {
|
||||
/* Is the share exclusive? */
|
||||
$exclusive = _var($share, 'exclusive') == 'yes' ? "<i class='fa fa-caret-right '></i> " : "";
|
||||
$share_valid = true;
|
||||
}
|
||||
|
||||
/* When there is no array, all pools are treated as 'only' cache. */
|
||||
if (($poolsOnly) && (! $share['cachePool2'])) {
|
||||
$share['useCache'] = 'only';
|
||||
}
|
||||
|
||||
$row++;
|
||||
$color = $share['color'] ?? '';
|
||||
$orb = '';
|
||||
$help = '';
|
||||
|
||||
switch ($color) {
|
||||
case STATUS_GREEN_ON:
|
||||
$orb = 'circle';
|
||||
$color = 'green';
|
||||
$help = _('All files protected');
|
||||
break;
|
||||
case STATUS_YELLOW_ON:
|
||||
$orb = 'warning';
|
||||
$color = 'yellow';
|
||||
$help = _('Some or all files unprotected');
|
||||
break;
|
||||
default:
|
||||
/* Handle unexpected color values */
|
||||
$orb = 'question';
|
||||
$color = 'grey';
|
||||
$help = _('Unknown protection status');
|
||||
break;
|
||||
}
|
||||
|
||||
$luks = '';
|
||||
if ($crypto) {
|
||||
switch ($share['luksStatus'] ?? LUKS_STATUS_UNKNOWN) {
|
||||
case LUKS_STATUS_UNKNOWN:
|
||||
$luks = "<i class='nolock fa fa-lock'></i>";
|
||||
break;
|
||||
case LUKS_STATUS_ENCRYPTED:
|
||||
$luks = "<a class='info' onclick='return false'><i class='padlock fa fa-unlock-alt green-text'></i><span>"._('All files encrypted')."</span></a>";
|
||||
break;
|
||||
case LUKS_STATUS_UNENCRYPTED:
|
||||
$luks = "<a class='info' onclick='return false'><i class='padlock fa fa-unlock-alt orange-text'></i><span>"._('Some or all files unencrypted')."</span></a>";
|
||||
break;
|
||||
default:
|
||||
$luks = "<a class='info' onclick='return false'><i class='padlock fa fa-lock red-text'></i><span>"._('Unknown encryption state')."</span></a>";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$row++;
|
||||
$color = $share['color'];
|
||||
switch ($color) {
|
||||
case 'green-on' : $orb = 'circle'; $color = 'green'; $help = _('All files protected'); break;
|
||||
case 'yellow-on': $orb = 'warning'; $color = 'yellow'; $help = _('Some or all files unprotected'); break;
|
||||
}
|
||||
if ($crypto) switch ($share['luksStatus']) {
|
||||
case 0: $luks = "<i class='nolock fa fa-lock'></i>"; break;
|
||||
case 1: $luks = "<a class='info' onclick='return false'><i class='padlock fa fa-unlock-alt green-text'></i><span>"._('All files encrypted')."</span></a>"; break;
|
||||
case 2: $luks = "<a class='info' onclick='return false'><i class='padlock fa fa-unlock-alt orange-text'></i><span>"._('Some or all files unencrypted')."</span></a>"; break;
|
||||
default: $luks = "<a class='info' onclick='return false'><i class='padlock fa fa-lock red-text'></i><span>"._('Unknown encryption state')."</span></a>"; break;
|
||||
} else $luks = "";
|
||||
echo "<tr><td><a class='view' href=\"/$path/Browse?dir=/mnt/user/",rawurlencode($name),"\"><i class=\"icon-u-tab\" title=\"",_('Browse')," /mnt/user/".rawurlencode($name),"\"></i></a>";
|
||||
echo "<a class='info nohand' onclick='return false'><i class='fa fa-$orb orb $color-orb'></i><span style='left:18px'>$help</span></a>$luks<a href=\"/$path/Share?name=";
|
||||
echo rawurlencode($name),"\" onclick=\"$.cookie('one','tab1')\">$name</a></td>";
|
||||
echo "<td>{$share['comment']}</td>";
|
||||
echo "<td>",user_share_settings($var['shareSMBEnabled'], $sec[$name]),"</td>";
|
||||
echo "<td>",user_share_settings($var['shareNFSEnabled'], $sec_nfs[$name]),"</td>";
|
||||
echo "<tr><td><a class='view' href=\"/$path/Browse?dir=/mnt/user/", rawurlencode($name), "\"><i class=\"icon-u-tab\" title=\"", _('Browse'), " /mnt/user/" . rawurlencode($name), "\"></i></a>";
|
||||
echo "<a class='info nohand' onclick='return false'><i class='fa fa-$orb orb $color-orb'></i><span style='left:18px'>$help</span></a>$luks<a href=\"/$path/Share?name=";
|
||||
echo rawurlencode($name), "\" onclick=\"$.cookie('one','tab1')\">$name</a></td>";
|
||||
echo "<td>{$share['comment']}</td>";
|
||||
echo "<td>", user_share_settings($var['shareSMBEnabled'], $sec[$name]), "</td>";
|
||||
echo "<td>", user_share_settings($var['shareNFSEnabled'], $sec_nfs[$name]), "</td>";
|
||||
|
||||
// Check for non existent pool device
|
||||
if (isset($share['cachePool']) && !in_array($share['cachePool'], $pools_check)) $share['useCache'] = "no";
|
||||
/* If the share pool or array is not valid, indicate that to the user. */
|
||||
if (!$share_valid) {
|
||||
$cache = "<a class='hand info none' onclick='return false'>".$indicator." <i class='fa fa-warning red-orb'></i> ".$array."<span>"._('This share is invalid').'; '. _('It references storage that does not exist')."</span></a>";
|
||||
} else {
|
||||
switch ($share['useCache']) {
|
||||
case 'no':
|
||||
$cache = "<a class='hand info none' onclick='return false'>".$indicator.$exclusive.$array."<span>".sprintf(_('Primary storage %s'), $array)."</span></a>";
|
||||
break;
|
||||
case 'yes':
|
||||
$cache = "<a class='hand info none' onclick='return false'><i class='fa fa-bullseye fa-fw'></i>".compress(my_disk($share['cachePool'], $display['raw']))." <i class='fa fa-long-arrow-right fa-fw'></i>".$indicator.$array."<span>"._('Primary storage to Secondary storage')."</span></a>";
|
||||
break;
|
||||
case 'prefer':
|
||||
$cache = "<a class='hand info none' onclick='return false'><i class='fa fa-bullseye fa-fw'></i>".compress(my_disk($share['cachePool'], $display['raw']))." <i class='fa fa-long-arrow-left fa-fw'></i>".$indicator.$array."<span>"._('Secondary storage to Primary storage')."</span></a>";
|
||||
break;
|
||||
case 'only':
|
||||
$cache = "<a class='hand info none' onclick='return false'><i class='fa fa-bullseye fa-fw'></i>$exclusive".my_disk($share['cachePool'], $display['raw'])."<span>".sprintf(_('Primary storage %s'), $share['cachePool']).($exclusive ? ", "._('Exclusive access') : "")."</span></a>";
|
||||
break;
|
||||
default:
|
||||
/* Handle unexpected useCache values */
|
||||
$cache = "<a class='hand info none' onclick='return false'>". $indicator . $array . "<span>"._('Unknown cache usage')."</span></a>";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($share['useCache']) {
|
||||
case 'no':
|
||||
$cache = "<a class='hand info none' onclick='return false'><i class='fa fa-database fa-fw'></i>"._('Array')."<span>".sprintf(_('Primary storage %s'),_('Array'))."</span></a>";
|
||||
break;
|
||||
case 'yes':
|
||||
$cache = "<a class='hand info none' onclick='return false'><i class='fa fa-bullseye fa-fw'></i>".compress(my_disk($share['cachePool'],$display['raw']))." <i class='fa fa-long-arrow-right fa-fw'></i>".$array."<span>"._('Primary storage to Secondary storage')."</span></a>";
|
||||
break;
|
||||
case 'prefer':
|
||||
$cache = "<a class='hand info none' onclick='return false'><i class='fa fa-bullseye fa-fw'></i>".compress(my_disk($share['cachePool'],$display['raw']))." <i class='fa fa-long-arrow-left fa-fw'></i>".$array."<span>"._('Secondary storage to Primary storage')."</span></a>";
|
||||
break;
|
||||
case 'only':
|
||||
$exclusive = isset($share['exclusive']) && $share['exclusive']=='yes' ? "<i class='fa fa-caret-right '></i> " : "";
|
||||
$cache = "<a class='hand info none' onclick='return false'><i class='fa fa-bullseye fa-fw'></i>$exclusive".my_disk($share['cachePool'],$display['raw'])."<span>".sprintf(_('Primary storage %s'),$share['cachePool']).($exclusive ? ", "._('Exclusive access') : "")."</span></a>";
|
||||
break;
|
||||
}
|
||||
if (array_key_exists($name,$ssz1)) {
|
||||
echo "<td>$cache</td>";
|
||||
echo "<td>",my_scale($ssz1[$name]['disk.total'], $unit)," $unit</td>";
|
||||
echo "<td>",my_scale($share['free']*1024, $unit)," $unit</td>";
|
||||
echo "</tr>";
|
||||
foreach ($ssz1[$name] as $diskname => $disksize) {
|
||||
if ($diskname=='disk.total') continue;
|
||||
$include = $share['include'];
|
||||
$inside = in_array($diskname, array_filter(array_diff($myDisks, explode(',',$share['exclude'])), 'shareInclude'));
|
||||
echo "<tr class='",($inside ? "'>" : "warning'>");
|
||||
echo "<td><a class='view'></a><a href='#' title='",_('Recompute'),"...' onclick=\"computeShare('",rawurlencode($name),"',$(this).parent())\"><i class='fa fa-refresh icon'></i></a> ",_(my_disk($diskname,$display['raw']),3),"</td>";
|
||||
echo "<td>",($inside ? "" : "<em>"._('Share is outside the list of designated disks')."</em>"),"</td>";
|
||||
echo "<td></td>";
|
||||
echo "<td></td>";
|
||||
echo "<td></td>";
|
||||
echo "<td>",my_scale($disksize, $unit)," $unit</td>";
|
||||
echo "<td>",my_scale($disks[$diskname]['fsFree']*1024, $unit)," $unit</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
} else {
|
||||
echo "<td>$cache</td>";
|
||||
echo "<td><a href='#' onclick=\"computeShare('",rawurlencode($name),"',$(this))\">",_('Compute'),"...</a></td>";
|
||||
echo "<td>",my_scale($share['free']*1024, $unit)," $unit</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
if (array_key_exists($name, $ssz1)) {
|
||||
echo "<td>$cache</td>";
|
||||
echo "<td>", my_scale($ssz1[$name]['disk.total'], $unit), " $unit</td>";
|
||||
echo "<td>", my_scale($share['free'] * 1024, $unit), " $unit</td>";
|
||||
echo "</tr>";
|
||||
foreach ($ssz1[$name] as $diskname => $disksize) {
|
||||
if ($diskname == 'disk.total') continue;
|
||||
$include = $share['include'];
|
||||
$inside = in_array($diskname, array_filter(array_diff($myDisks, explode(',', $share['exclude'])), 'shareInclude'));
|
||||
echo "<tr class='", ($inside ? "'>" : "warning'>");
|
||||
echo "<td><a class='view'></a><a href='#' title='", _('Recompute'), "...' onclick=\"computeShare('", rawurlencode($name), "',$(this).parent())\"><i class='fa fa-refresh icon'></i></a> ", _(my_disk($diskname, $display['raw']), 3), "</td>";
|
||||
echo "<td>", ($inside ? "" : "<em>"._('Share is outside the list of designated disks')."</em>"), "</td>";
|
||||
echo "<td></td>";
|
||||
echo "<td></td>";
|
||||
echo "<td></td>";
|
||||
echo "<td>", my_scale($disksize, $unit), " $unit</td>";
|
||||
echo "<td>", my_scale($disks[$diskname]['fsFree'] * 1024, $unit), " $unit</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
} else {
|
||||
echo "<td>$cache</td>";
|
||||
echo "<td><a href='#' onclick=\"computeShare('", rawurlencode($name), "',$(this))\">", _('Compute'), "...</a></td>";
|
||||
echo "<td>", my_scale($share['free'] * 1024, $unit), " $unit</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
}
|
||||
if ($row==0) echo $noshares;
|
||||
?>
|
||||
|
@@ -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) {
|
||||
|
@@ -75,9 +75,11 @@ case 't1':
|
||||
foreach ($devicelist as $line) {
|
||||
if (!empty($line)) {
|
||||
exec('udevadm info --path=$(udevadm info -q path /dev/'.$line.' | cut -d / -f 1-7) --query=path',$linereturn);
|
||||
preg_match_all($DBDF_PARTIAL_REGEX, $linereturn[0], $inuse);
|
||||
foreach ($inuse[0] as $line) {
|
||||
$lines[] = $line;
|
||||
if(isset($linereturn[0])) {
|
||||
preg_match_all($DBDF_PARTIAL_REGEX, $linereturn[0], $inuse);
|
||||
foreach ($inuse[0] as $line) {
|
||||
$lines[] = $line;
|
||||
}
|
||||
}
|
||||
unset($inuse);
|
||||
unset($linereturn);
|
||||
@@ -92,9 +94,11 @@ case 't1':
|
||||
foreach ($nics as $line) {
|
||||
if (!empty($line)) {
|
||||
exec('readlink /sys/class/net/'.$line,$linereturn);
|
||||
preg_match_all($DBDF_PARTIAL_REGEX, $linereturn[0], $inuse);
|
||||
foreach ($inuse[0] as $line) {
|
||||
$lines[] = $line;
|
||||
if(isset($linereturn[0])) {
|
||||
preg_match_all($DBDF_PARTIAL_REGEX, $linereturn[0], $inuse);
|
||||
foreach ($inuse[0] as $line) {
|
||||
$lines[] = $line;
|
||||
}
|
||||
}
|
||||
unset($inuse);
|
||||
unset($linereturn);
|
||||
|
@@ -169,27 +169,16 @@ case 'ct':
|
||||
break;
|
||||
|
||||
case 'is':
|
||||
/* Path to syslinux configuration file */
|
||||
$cfg = '/boot/syslinux/syslinux.cfg';
|
||||
|
||||
/* Read the syslinux configuration file into an array, ignoring empty lines */
|
||||
$syslinux = file($cfg, FILE_IGNORE_NEW_LINES + FILE_SKIP_EMPTY_LINES);
|
||||
$size = count($syslinux);
|
||||
$make = false;
|
||||
|
||||
/* Path to the temporary file containing new isolcpus settings */
|
||||
/* Path to the temporary file containing new isolcpus settings */
|
||||
$file = "/var/tmp/$name.tmp";
|
||||
$isolcpus = file_get_contents($file);
|
||||
|
||||
$isolcpus = file_get_contents($file);
|
||||
if ($isolcpus != '') {
|
||||
/* Convert isolcpus string to an array of numbers and sort them */
|
||||
$numbers = explode(',', $isolcpus);
|
||||
sort($numbers, SORT_NUMERIC);
|
||||
|
||||
/* Initialize variables for range conversion */
|
||||
$isolcpus = $previous = array_shift($numbers);
|
||||
$range = false;
|
||||
|
||||
/* Convert sequential numbers to a range */
|
||||
foreach ($numbers as $number) {
|
||||
if ($number == $previous + 1) {
|
||||
@@ -206,55 +195,94 @@ case 'is':
|
||||
if ($range) {
|
||||
$isolcpus .= '-' . $previous;
|
||||
}
|
||||
|
||||
/* Format isolcpus string for syslinux configuration */
|
||||
/* Format isolcpus string for configuration */
|
||||
$isolcpus = "isolcpus=$isolcpus";
|
||||
}
|
||||
|
||||
/* Remove the temporary file */
|
||||
unlink($file);
|
||||
|
||||
$i = 0;
|
||||
while ($i < $size) {
|
||||
/* Find sections in syslinux config and exclude safemode */
|
||||
if (scan($syslinux[$i], 'label ') && !scan($syslinux[$i], 'safe mode') && !scan($syslinux[$i], 'safemode')) {
|
||||
$n = $i + 1;
|
||||
|
||||
/* Find the current requested setting */
|
||||
while ($n < $size && !scan($syslinux[$n], 'label ')) {
|
||||
if (scan($syslinux[$n], 'append ')) {
|
||||
$cmd = preg_split('/\s+/', trim($syslinux[$n]));
|
||||
|
||||
/* Replace an existing isolcpus setting */
|
||||
for ($c = 1; $c < count($cmd); $c++) {
|
||||
if (scan($cmd[$c], 'isolcpus')) {
|
||||
$make |= ($cmd[$c] != $isolcpus);
|
||||
$cmd[$c] = $isolcpus;
|
||||
break;
|
||||
if (is_file('/boot/syslinux/syslinux.cfg')) {
|
||||
/* Path to syslinux configuration file */
|
||||
$cfg = '/boot/syslinux/syslinux.cfg';
|
||||
/* Read the syslinux configuration file into an array, ignoring empty lines */
|
||||
$bootcfg = file($cfg, FILE_IGNORE_NEW_LINES + FILE_SKIP_EMPTY_LINES);
|
||||
$size = count($bootcfg);
|
||||
$make = false;
|
||||
/* Remove the temporary file */
|
||||
unlink($file);
|
||||
$i = 0;
|
||||
while ($i < $size) {
|
||||
/* Find sections in syslinux config and exclude safemode */
|
||||
if (scan($bootcfg[$i], 'label ') && !scan($bootcfg[$i], 'safe mode') && !scan($bootcfg[$i], 'safemode')) {
|
||||
$n = $i + 1;
|
||||
/* Find the current requested setting */
|
||||
while ($n < $size && !scan($bootcfg[$n], 'label ')) {
|
||||
if (scan($bootcfg[$n], 'append ')) {
|
||||
$cmd = preg_split('/\s+/', trim($bootcfg[$n]));
|
||||
/* Replace an existing isolcpus setting */
|
||||
for ($c = 1; $c < count($cmd); $c++) {
|
||||
if (scan($cmd[$c], 'isolcpus')) {
|
||||
$make |= ($cmd[$c] != $isolcpus);
|
||||
$cmd[$c] = $isolcpus;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Or insert a new isolcpus setting if not found */
|
||||
if ($c == count($cmd) && $isolcpus) {
|
||||
array_splice($cmd, -1, 0, $isolcpus);
|
||||
$make = true;
|
||||
}
|
||||
/* Update the syslinux configuration line */
|
||||
$bootcfg[$n] = ' ' . str_replace(' ', ' ', implode(' ', $cmd));
|
||||
}
|
||||
|
||||
/* Or insert a new isolcpus setting if not found */
|
||||
if ($c == count($cmd) && $isolcpus) {
|
||||
array_splice($cmd, -1, 0, $isolcpus);
|
||||
$make = true;
|
||||
}
|
||||
|
||||
/* Update the syslinux configuration line */
|
||||
$syslinux[$n] = ' ' . str_replace(' ', ' ', implode(' ', $cmd));
|
||||
$n++;
|
||||
}
|
||||
$n++;
|
||||
$i = $n - 1;
|
||||
}
|
||||
$i = $n - 1;
|
||||
$i++;
|
||||
}
|
||||
} elseif (is_file('/boot/grub/grub.cfg')) {
|
||||
/* Path to grub configuration file */
|
||||
$cfg = '/boot/grub/grub.cfg';
|
||||
/* Read the grub configuration file into an array, ignoring empty lines */
|
||||
$bootcfg = file($cfg, FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$size = count($bootcfg);
|
||||
$make = false;
|
||||
/* Remove the temporary file */
|
||||
unlink($file);
|
||||
$i = 0;
|
||||
while ($i < $size) {
|
||||
// find sections and exclude safemode/memtest
|
||||
if (scan($bootcfg[$i],'menuentry ') && !scan($bootcfg[$i],'safe mode') && !scan($bootcfg[$i],'safemode') && !scan($bootcfg[$i],'memtest')) {
|
||||
$n = $i + 1;
|
||||
// find the current requested setting
|
||||
while (!scan($bootcfg[$n],'menuentry ') && $n < $size) {
|
||||
if (scan($bootcfg[$n],'linux ')) {
|
||||
$cmd = preg_split('/\s+/',trim($bootcfg[$n]));
|
||||
/* Replace an existing isolcpus setting */
|
||||
for ($c = 1; $c < count($cmd); $c++) {
|
||||
if (scan($cmd[$c], 'isolcpus')) {
|
||||
$make |= ($cmd[$c] != $isolcpus);
|
||||
$cmd[$c] = $isolcpus;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Or insert a new isolcpus setting if not found */
|
||||
if ($c == count($cmd) && $isolcpus) {
|
||||
$cmd[] = $isolcpus;
|
||||
$make = true;
|
||||
}
|
||||
/* Update the grub configuration line */
|
||||
$bootcfg[$n] = ' ' . str_replace(' ', ' ', implode(' ', $cmd));
|
||||
}
|
||||
$n++;
|
||||
}
|
||||
$i = $n - 1;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
/* Write the updated syslinux configuration back to the file if changes were made */
|
||||
/* Write the updated configuration back to the file if changes were made */
|
||||
if ($make) {
|
||||
file_put_contents_atomic($cfg, implode("\n", $syslinux) . "\n");
|
||||
file_put_contents_atomic($cfg, implode("\n", $bootcfg) . "\n");
|
||||
}
|
||||
|
||||
$reply = ['success' => $name];
|
||||
break;
|
||||
}
|
||||
|
@@ -128,6 +128,9 @@ function no_tilde($name) {
|
||||
function prefix($key) {
|
||||
return preg_replace('/\d+$/','',$key);
|
||||
}
|
||||
function pool_name($key) {
|
||||
return preg_replace('/(\d+$|~.*$)/', '', $key);
|
||||
}
|
||||
function native($name, $full=0) {
|
||||
global $_tilde_, $_arrow_;
|
||||
switch ($full) {
|
||||
|
@@ -151,8 +151,8 @@ function vfs_type(&$disk) {
|
||||
function fs_info(&$disk) {
|
||||
global $display;
|
||||
$echo = [];
|
||||
if (empty($disk['fsStatus']) || $disk['fsStatus']=='-')
|
||||
return "<td>".$disk['fsStatus']."</td><td colspan='3'></td>";
|
||||
if (empty(_var($disk,'fsStatus','')))
|
||||
return "<td colspan='4'></td>";
|
||||
if (_var($disk,'fsStatus')=='Mounted') {
|
||||
$echo[] = "<td>".vfs_type($disk)."</td>";
|
||||
$echo[] = "<td>".my_scale(_var($disk,'fsSize',0)*1024,$unit,-1)." $unit</td>";
|
||||
@@ -183,9 +183,10 @@ function array_offline(&$disk, $pool='') {
|
||||
$status = ['DISK_DSBL','DISK_INVALID','DISK_DSBL_NEW','DISK_WRONG'];
|
||||
$text = "<span class='red-text'><em>"._('All existing data on this device will be OVERWRITTEN when array is Started')."</em></span>";
|
||||
if (_var($disk,'type')=='Cache') {
|
||||
if (!str_contains(_var($diks[$pool],'state'),'ERROR:')) {
|
||||
if (_var($disks[$pool],'state')!='NEW_ARRAY') {
|
||||
if (in_array(_var($disk,'status'),$status)) $warning = $text;
|
||||
if (!str_contains(_var($disks[$pool],'state'),'ERROR:')) {
|
||||
$_pool = (strpos($pool, '~') !== false) ? substr($pool, 0, strpos($pool, '~')) : $pool;
|
||||
if (!empty(_var($disks[$_pool],'uuid'))) {
|
||||
if (in_array(_var($disk,'status'),$status) || _var($disk['status'])=='DISK_NEW') $warning = $text;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -267,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);
|
||||
@@ -361,8 +362,9 @@ function array_slots() {
|
||||
return implode($echo);
|
||||
}
|
||||
function cache_slots($off,$pool,$min,$slots) {
|
||||
global $var;
|
||||
$off = $off && $min ? ' disabled' : '';
|
||||
global $var, $disks;
|
||||
// $off = $off && $min ? ' disabled' : '';
|
||||
$off = _var($disks[$pool],'fsType','auto')!='auto' && empty(_var($disks[$pool],'uuid')) ? ' disabled' : '';
|
||||
$max = _var($var,'MAX_CACHESZ');
|
||||
$echo = [];
|
||||
$echo[] = "<form method='POST' action='/update.htm' target='progressFrame'>";
|
||||
@@ -370,10 +372,13 @@ function cache_slots($off,$pool,$min,$slots) {
|
||||
$echo[] = "<input type='hidden' name='changeSlots' value='apply'>";
|
||||
$echo[] = "<input type='hidden' name='poolName' value='$pool'>";
|
||||
$echo[] = "<select class='narrow' name='poolSlots' onChange='devices.start();this.form.submit()'{$off}>";
|
||||
// if (_var($disks[$pool],'state')=='NEW_ARRAY' || str_contains(_var($diks[$pool],'state'),'NO_DEVICES')) {
|
||||
// $option = _('none');
|
||||
// $echo[] = "<option value='0'>$option</option>";
|
||||
// }
|
||||
for ($n=$min; $n<=$max; $n++) {
|
||||
$option = $n ?: _('none');
|
||||
$selected = ($n==$slots) ? ' selected' : '';
|
||||
$echo[] = "<option value='$n'{$selected}>$option</option>";
|
||||
$echo[] = "<option value='$n'{$selected}>$n</option>";
|
||||
}
|
||||
$echo[] = "</select></form>";
|
||||
return implode($echo);
|
||||
@@ -509,7 +514,6 @@ while (true) {
|
||||
} else {
|
||||
foreach ($Cache as $disk) if (prefix($disk['name'])==$pool) {
|
||||
$fstype = str_replace('luks:','',_var($disk,'fsType'));
|
||||
if (substr(_var($Cache[$pool],'fsStatus'),0,11)=='Unmountable' && empty($disk['fsStatus'])) $disk['fsStatus'] = _var($Cache[$pool],'fsStatus');
|
||||
$echo[$a][] = array_online($disk,$fstype);
|
||||
}
|
||||
if (strcmp($root,$pool)!=0) $Cache[$root]['devices'] += $Cache[$pool]['devices'];
|
||||
|
@@ -18,6 +18,7 @@ $log = '/boot/config/parity-checks.log';
|
||||
$stamps = '/var/tmp/stamps.ini';
|
||||
$resync = '/var/tmp/resync.ini';
|
||||
$md5_old = $spot_old = $fs_old = $proc_old = -1;
|
||||
$remove_resync_files = false;
|
||||
|
||||
require_once "$docroot/webGui/include/Helpers.php";
|
||||
require_once "$docroot/webGui/include/publish.php";
|
||||
@@ -106,10 +107,15 @@ while (true) {
|
||||
/* Save the result in the parity history log. */
|
||||
file_put_contents($log, "$timestamp|$duration|$speed|$status|$error|$action|$size\n", FILE_APPEND);
|
||||
|
||||
delete_file($stamps, $resync);
|
||||
/* Remove the resync files after the history file has been updated. */
|
||||
$remove_resync_files = true;
|
||||
|
||||
/* Parity check is completed. */
|
||||
$echo = "";
|
||||
} elseif ($remove_resync_files) {
|
||||
delete_file($stamps, $resync);
|
||||
|
||||
$remove_resync_files = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +161,6 @@ while (true) {
|
||||
$proc_old = publish('mymonitor', $process) !== false ? $process : -1;
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
sleep(3);
|
||||
}
|
||||
?>
|
||||
|
@@ -59,7 +59,7 @@ while (true) {
|
||||
exec("sensors -uA 2>/dev/null|grep -Po 'fan\d_input: \K\d+'",$fans);
|
||||
[$total,$free] = $memory;
|
||||
$used = $total-$free;
|
||||
$names = [_('Services'),_('Free')];
|
||||
$names = [_('System'),_('Free')];
|
||||
$bytes = $echo = [];
|
||||
$hooks = array_filter(glob("/usr/local/emhttp/plugins/*/system/*",GLOB_NOSORT),function($file){return is_executable($file);});
|
||||
foreach ($hooks as $hook) {
|
||||
|
@@ -142,6 +142,17 @@ function device_name(&$disk) {
|
||||
$fancy = _(my_disk(native($name,1)),3);
|
||||
return "<i class='icon-$type f14'></i> <a href=\"".htmlspecialchars("/Dashboard/Main/Settings/Device?name=$name")."\" title=\"$fancy settings\">$fancy</a>";
|
||||
}
|
||||
function yellow_text($disk) {
|
||||
global $var;
|
||||
if (_var($disk, 'type')=='Parity') {
|
||||
$text = _var($var,'mdResync')==0 ? 'invalid' : 'syncing';
|
||||
} elseif (_var($disk, 'type')=='Data') {
|
||||
$text = _var($var,'mdResync')==0 ? 'emulated' : 'rebuilding';
|
||||
} else {
|
||||
$text = 'invalid';
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
function device_status(&$disk, &$error, &$warning) {
|
||||
global $var;
|
||||
if (_var($disk,'type')!='Extra' && _var($var,'fsState')=='Stopped') {
|
||||
@@ -151,8 +162,8 @@ function device_status(&$disk, &$error, &$warning) {
|
||||
case 'green-blink' : $color = 'grey'; $text = 'standby'; break;
|
||||
case 'blue-on' : $color = 'blue'; $text = 'unassigned'; break;
|
||||
case 'blue-blink' : $color = 'grey'; $text = 'unassigned'; break;
|
||||
case 'yellow-on' : $color = 'yellow'; $text = _var($disk,'type')=='Parity' ? 'invalid' : 'emulated'; $warning++; break;
|
||||
case 'yellow-blink': $color = 'grey'; $text = _var($disk,'type')=='Parity' ? 'invalid' : 'emulated'; $warning++; break;
|
||||
case 'yellow-on' : $color = 'yellow'; $text = yellow_text($disk); $warning++; break;
|
||||
case 'yellow-blink': $color = 'grey'; $text = yellow_text($disk); $warning++; break;
|
||||
case 'red-on' : $color = 'red'; $text = 'disabled'; $error++; break;
|
||||
case 'red-blink' : $color = 'grey'; $text = 'disabled'; $error++; break;
|
||||
case 'red-off' : $color = 'red'; $text = 'faulty'; $error++; break;
|
||||
|
@@ -73,11 +73,13 @@ while (true) {
|
||||
if ($vmdata['state'] == 1) {
|
||||
$vmencode = str_replace(" "," ",$vm);
|
||||
$vmencode = $lv->domain_get_uuid($vm);
|
||||
$echo[$vmencode ]['gcpu'] = "<span class='advanced'>"._("Guest CPU").": <span class='cpug-".$vm."'>".$vmdata['cpuguest']."%</span><div class='usage-disk mm'><span id='cpug-".$vm."' style='width:".$vmdata['cpuguest']."%;'></span><span></span></div></span>";
|
||||
$echo[$vmencode ]['hcpu'] = "<span class='advanced'>"._("Host CPU").": <span class='cpug-".$vm."'>".$vmdata['cpuhost']."%</span><div class='usage-disk mm'><span id='cpug-".$vm."' style='width:".$vmdata['cpuhost']."%;'></span><span></span></div></span>";
|
||||
$echo[$vmencode ]['mem'] = "<span>Mem: ".my_scale($vmdata['mem']*1024,$unit)."$unit / ".my_scale($vmdata['maxmem']*1024,$unit)."$unit</span>";
|
||||
$echo[$vmencode ]['disk'] = "<span>Disk: "._("Rd").": ".my_scale($vmdata['rdrate'],$unit)."$unit/s "._("Wr").": ".my_scale($vmdata['wrrate'],$unit)."$unit/s</span>";
|
||||
$echo[$vmencode ]['net'] = "<span>Net: "._("RX").": ".my_scale($vmdata['rxrate'],$unit)."$unit/s "._("TX").": ".my_scale($vmdata['txrate'],$unit)."$unit/s</span>";
|
||||
$echo[$vmencode ]['gcpu'] = "<span class='advanced'>"._("Guest CPU").": <span class='cpug-".$vm."'>".$vmdata['cpuguest']."%</span><div class='usage-disk mm'><span id='cpug-".$vm."' style='width:".$vmdata['cpuguest']."%;'>  </span><span></span></div></span>";
|
||||
$echo[$vmencode ]['hcpu'] = "<span class='advanced'>"._("Host CPU").": <span class='cpug-".$vm."'>".$vmdata['cpuhost']."%</span><div class='usage-disk mm'><span id='cpug-".$vm."' style='width:".$vmdata['cpuhost']."%;'>  </span><span></span></div></span>";
|
||||
$echo[$vmencode ]['mem'] = "<span>Mem: ".my_scale($vmdata['mem']*1024,$unit)."$unit / ".my_scale($vmdata['curmem']*1024,$unit)."$unit";
|
||||
if ($vmdata['maxmem'] == $vmdata['curmem']) $echo[$vmencode ]['mem'] .="  </span>";
|
||||
else $echo[$vmencode ]['mem'] .= " / ".my_scale($vmdata['maxmem']*1024,$unit)."$unit  </span>";
|
||||
$echo[$vmencode ]['disk'] = "<span>Disk: "._("Rd").": ".my_scale($vmdata['rdrate']/$timer,$unit)."$unit/s "._("Wr").": ".my_scale($vmdata['wrrate']/$timer,$unit)."$unit/s  </span>";
|
||||
$echo[$vmencode ]['net'] = "<span>Net: "._("RX").": ".my_scale($vmdata['rxrate']/$timer,$unit)."$unit/s "._("TX").": ".my_scale($vmdata['txrate']/$timer,$unit)."$unit/s  </span>";
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -28,7 +28,13 @@ $docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
|
||||
require_once "$docroot/webGui/include/Helpers.php";
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
|
||||
$folders = ['/boot','/boot/config','/boot/config/plugins','/boot/syslinux','/var/log','/var/log/plugins','/boot/extra','/var/log/packages','/var/lib/pkgtools/packages','/tmp'];
|
||||
if (is_file('/boot/syslinux/syslinux.cfg')) {
|
||||
$bootenv = '/boot/syslinux';
|
||||
} elseif (is_file('/boot/grub/grub.cfg')) {
|
||||
$bootenv = '/boot/grub';
|
||||
}
|
||||
|
||||
$folders = ['/boot','/boot/config','/boot/config/plugins',$bootenv,'/var/log','/var/log/plugins','/boot/extra','/var/log/packages','/var/lib/pkgtools/packages','/tmp'];
|
||||
|
||||
// global variables
|
||||
$path = "/var/local/emhttp";
|
||||
@@ -113,8 +119,18 @@ function maskIP($file) {
|
||||
// anonymize public IPv4 addresses
|
||||
$rfc1918 = "(127|10|172\.1[6-9]|172\.2[0-9]|172\.3[0-1]|192\.168)((\.[0-9]{1,3}){2,3}([/\" .]|$))";
|
||||
run("sed -ri 's/([\"\[ ]){$rfc1918}/\\1@@@\\2\\3/g; s/([\"\[ ][0-9]{1,3}\.)([0-9]{1,3}\.){2}([0-9]{1,3})([/\" .]|$)/\\1XXX.XXX.\\3\\4/g; s/@@@//g' ".escapeshellarg($file)." 2>/dev/null");
|
||||
|
||||
// anonymize full IPv6 addresses
|
||||
run("sed -ri 's/([\"\[ ]([0-9a-f]{1,4}:){4})(([0-9a-f]{1,4}:){3}|:)([0-9a-f]{1,4})([/\" .]|$)/\\1XXXX:XXXX:XXXX:\\5\\6/g' ".escapeshellarg($file)." 2>/dev/null");
|
||||
$file_escaped = escapeshellarg($file);
|
||||
|
||||
// Anonymize IPv6 addresses without brackets
|
||||
run("sed -ri 's/(([0-9a-f]{1,4}:){4})(([0-9a-f]{1,4}:){3}|:)([0-9a-f]{1,4})([ .:/]|$)/\\1XXXX:XXXX:XXXX:\\5\\6/g' $file_escaped 2>/dev/null");
|
||||
|
||||
// Anonymize IPv6 addresses with brackets
|
||||
run("sed -ri 's/(\[([0-9a-f]{1,4}:){4})(([0-9a-f]{1,4}:){3}|:)([0-9a-f]{1,4})(\])([ .:/]|$)/\\1XXXX:XXXX:XXXX:\\5\\6/g' $file_escaped 2>/dev/null");
|
||||
|
||||
// Handle any remaining edge cases, e.g., addresses with subnet masks
|
||||
run("sed -ri 's/(([0-9a-f]{1,4}:){4})(([0-9a-f]{1,4}:){3}|:)([0-9a-f]{1,4})(\/[0-9]{1,3})([ .:/]|$)/\\1XXXX:XXXX:XXXX:\\5\\7/g' $file_escaped 2>/dev/null");
|
||||
}
|
||||
function download_url($url, $path="", $bg=false, $timeout=15) {
|
||||
$ch = curl_init();
|
||||
@@ -424,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"));
|
||||
@@ -469,7 +484,7 @@ if (!$all) {
|
||||
maskIP("/$diag/config/network.cfg");
|
||||
}
|
||||
// include listening interfaces
|
||||
run("$docroot/webGui/scripts/show_interfaces ip|tr -d ' '|tr '#' ' '|tr ',' '\n' >".escapeshellarg("/$diag/config/listen.txt"));
|
||||
run("$docroot/webGui/scripts/show_interfaces ip|tr ',' '\n' >".escapeshellarg("/$diag/config/listen.txt"));
|
||||
run("$docroot/webGui/scripts/error_interfaces|sed 's/<i.*i>//' >>".escapeshellarg("/$diag/config/listen.txt"));
|
||||
if (!$all) maskIP("/$diag/config/listen.txt");
|
||||
|
||||
@@ -707,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
|
||||
|
@@ -12,11 +12,10 @@
|
||||
|
||||
# With corrections suggested by forum member Stokkes
|
||||
|
||||
# Here's a breakdown of chmod "u-x,go-rwx,go+u,ugo+X"
|
||||
# u-x Clear the 'x' bit in the user permissions (leaves rw as-is)
|
||||
# Here's a breakdown of chmod "go-rwx,u-x,go+u"
|
||||
# go-rwx Clear the 'rwx' bits in both the group and other permissions
|
||||
# u-x Clear the 'x' bit in the user permissions (leaves rw as-is)
|
||||
# go+u Copy the user permissions to group and other
|
||||
# ugo+X Set the 'x' bit for directories in user, group, and other
|
||||
|
||||
$nchan = $argv[$argc-1] == 'nchan'; // console or nchan output
|
||||
if ($nchan) unset($argv[$argc-1]); // remove nchan parameter
|
||||
@@ -45,10 +44,13 @@ function process($path) {
|
||||
$owner = $argv[2] ?? 'nobody';
|
||||
$group = $argv[3] ?? 'users';
|
||||
if (is_dir($path) && preg_match('/^\/mnt\/.+/',$path)) {
|
||||
write("Processing: $path\n", "... chmod -R u-x,go-rwx,go+u,ugo+X $path\n");
|
||||
exec("chmod -R u-x,go-rwx,go+u,ugo+X ".escapeshellarg($path));
|
||||
write("... chown -R $owner:$group $path\n");
|
||||
write("Processing: $path\n");
|
||||
write("... chown -R $owner:$group\n");
|
||||
exec("chown -R $owner:$group ".escapeshellarg($path));
|
||||
write("... chmod -R go-rwx,u-x,go+u\n");
|
||||
exec("chmod -R go-rwx,u-x,go+u ".escapeshellarg($path));
|
||||
write("... find -type d -exec chmod 777 {} \\;\n");
|
||||
exec("find ".escapeshellarg($path)." -type d -exec chmod 777 {} \\;");
|
||||
write("... sync\n");
|
||||
exec("sync");
|
||||
write("\n");
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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"
|
||||
|
@@ -19,7 +19,7 @@ $proxy_ini = '/usr/local/emhttp/state/proxy.ini';
|
||||
$rnd = rand();
|
||||
|
||||
/* Comments to beginning of the proxy_sh file. */
|
||||
$comments = "#!/bin/bash\n"."# Do not edit. This file is autogenerated by /usr/local/sbin/set_proxy.\n";
|
||||
$comments = "#!/bin/bash\n"."# Do not edit. This file is autogenerated by /usr/local/emhttp/plugins/dynamix/scripts/set_proxy.\n";
|
||||
|
||||
/* Set verbose if command line switch is set. */
|
||||
$verbose = false;
|
||||
|
@@ -7,12 +7,12 @@ CALLER="show"
|
||||
|
||||
# include IP addresses?
|
||||
if check && [[ $1 == ip ]]; then
|
||||
ip=()
|
||||
for net in $bind; do
|
||||
ip+=("$net#[$(show -4 dev $net)#$(show -6 dev $net)]")
|
||||
IP=()
|
||||
for NET in $BIND; do
|
||||
IP+=("$NET [$(echo $(show dev $NET)|xargs)]")
|
||||
done
|
||||
bind=${ip[@]}
|
||||
BIND=${IP[@]}
|
||||
fi
|
||||
|
||||
# return list
|
||||
echo ${bind// /, }
|
||||
echo $BIND
|
||||
|
@@ -64,7 +64,7 @@ if (!empty($strLoadedModules)) {
|
||||
// Yah! CPU and motherboard supported and enabled in BIOS
|
||||
$hvm = _('Enabled');
|
||||
} else {
|
||||
$hvm = '<a href="https://docs.unraid.net/unraid-os/manual/vm-management#system-preparation" target="_blank">';
|
||||
$hvm = '<a href="https://docs.unraid.net/go/vm-system-preparation/" target="_blank">';
|
||||
if (strpos($strCPUInfo,'vmx')===false && strpos($strCPUInfo, 'svm')===false) {
|
||||
// CPU doesn't support virtualization
|
||||
$hvm .= _('Not Available');
|
||||
@@ -82,7 +82,7 @@ if (!empty($iommu_groups)) {
|
||||
// Yah! CPU and motherboard supported and enabled in BIOS
|
||||
$iommu = _('Enabled');
|
||||
} else {
|
||||
$iommu = '<a href="https://docs.unraid.net/unraid-os/manual/vm-management#determining-hvmiommu-hardware-support" target="_blank">';
|
||||
$iommu = '<a href="https://docs.unraid.net/go/determining-hvmiommu-hardware-support/" target="_blank">';
|
||||
if (strpos($strCPUInfo,'vmx')===false && strpos($strCPUInfo, 'svm')===false) {
|
||||
// CPU doesn't support virtualization so iommu would be impossible
|
||||
$iommu .= _('Not Available');
|
||||
|
@@ -16,18 +16,32 @@
|
||||
* The $_POST variable contains a list of key/value parameters to be updated in the file.
|
||||
* There are a number of special parameters prefixed with a hash '#' character:
|
||||
*
|
||||
* #file : the pathname of the file to be updated. It does not need to previously exist.
|
||||
* If pathname is relative (no leading '/'), the configuration file will placed
|
||||
* placed under '/boot/config/plugins'.
|
||||
* This parameter may be omitted to perform a command execution only (see #command).
|
||||
* #section : if present, then the ini file consists of a set of named sections, and all of the
|
||||
* configuration parameters apply to this one particular section.
|
||||
* if omitted, then it's just a flat ini file without sections.
|
||||
* #default : if present, then the default values will be restored instead.
|
||||
* #include : specifies name of an include file to read and execute in before saving the file contents
|
||||
* #cleanup : if present then parameters with empty strings are omitted from being written to the file
|
||||
* #command : a shell command to execute after updating the configuration file
|
||||
* #arg : an array of arguments for the shell command
|
||||
* #file : The pathname of the file to be updated. It does not need to previously exist.
|
||||
* If pathname is relative (no leading '/'), the configuration file will placed
|
||||
* placed under '/boot/config/plugins/'.
|
||||
* This parameter may be omitted to perform a command execution only (see #command).
|
||||
*
|
||||
* #section : If present, then the ini file consists of a set of named sections, and all of the
|
||||
* configuration parameters apply to this one particular section.
|
||||
* If omitted, then it's just a flat ini file without sections.
|
||||
*
|
||||
* #default : If present, then the default values will be restored instead (from 'default.cfg').
|
||||
*
|
||||
* #defaultfile : If present in combination with #default, a custom defaults file will be restored
|
||||
* instead of the regular 'default.cfg' file. If pathname is relative (no leading '/'),
|
||||
* the given configuration file will be searched for under '/usr/local/emhttp/plugins/'.
|
||||
*
|
||||
* #defaults : If present in combination with #default, no defaults file but an associative array
|
||||
* passed through POST in format of '#defaults[key]=value' will be restored instead.
|
||||
* e.g. <input type="hidden" name="#defaults[SERVICE]" value="enable">
|
||||
* e.g. <input type="hidden" name="#defaults[INTERVAL]" value="25">
|
||||
* Beware: Browsers generally do not send empty values, if your default values include
|
||||
* any empty strings, you should preferably use a default configuration file instead.
|
||||
*
|
||||
* #include : Specifies name of an include file to read and execute in before saving the file contents.
|
||||
* #cleanup : If present then parameters with empty strings are omitted from being written to the file.
|
||||
* #command : A shell command to execute after updating the configuration file.
|
||||
* #arg : An array of arguments for the shell command.
|
||||
*/
|
||||
function write_log($string) {
|
||||
if (empty($string)) return;
|
||||
@@ -50,7 +64,19 @@ if (isset($_POST['#file'])) {
|
||||
if ($file && $file[0]!='/') $file = "/boot/config/plugins/$file";
|
||||
$section = $_POST['#section'] ?? false;
|
||||
$cleanup = isset($_POST['#cleanup']);
|
||||
$default = ($file && isset($_POST['#default'])) ? @parse_ini_file("$docroot/plugins/".basename(dirname($file))."/default.cfg", $section) : [];
|
||||
|
||||
$default = [];
|
||||
if($file && isset($_POST['#default'])) {
|
||||
if(isset($_POST['#defaultfile'])) {
|
||||
$defaultfile = $_POST['#defaultfile'];
|
||||
if ($defaultfile && $defaultfile[0]!='/') $defaultfile = "$docroot/plugins/$defaultfile";
|
||||
$default = parse_ini_file($defaultfile, $section) ?: [];
|
||||
} elseif(isset($_POST['#defaults'])) {
|
||||
$default = is_array($_POST['#defaults']) ? ($_POST['#defaults'] ?: []) : [];
|
||||
} else {
|
||||
$default = parse_ini_file("$docroot/plugins/".basename(dirname($file))."/default.cfg", $section) ?: [];
|
||||
}
|
||||
}
|
||||
|
||||
// if the file is not a raw file, it can be parsed
|
||||
$keys = (is_file($file) && !$raw_file) ? (parse_ini_file($file, $section) ?: []) : [];
|
||||
@@ -102,6 +128,7 @@ if (isset($_POST['#command'])) {
|
||||
while (!feof($proc)) {
|
||||
write_log(fgets($proc));
|
||||
}
|
||||
@pclose($proc);
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
@@ -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"
|
||||
|
@@ -76,9 +76,10 @@ done
|
||||
# LimeTech - poll for device with $UNRAIDLABEL present, with 30-sec timeout
|
||||
# this serves to synchronize this script with USB subsystem
|
||||
abort() {
|
||||
/bin/umount -a
|
||||
read -p "$1 - press ENTER key to reboot..."
|
||||
/bin/echo
|
||||
/sbin/reboot
|
||||
/sbin/reboot -fd
|
||||
}
|
||||
|
||||
find_device() {
|
||||
@@ -103,7 +104,12 @@ elif /sbin/blkid -s TYPE $DEVICE | /bin/grep -q "xfs" ; then
|
||||
/sbin/mount -v -t xfs -o auto,rw,noatime,nodiratime,discard $DEVICE /boot || abort "cannot mount $DEVICE"
|
||||
else
|
||||
/bin/echo "Checking $DEVICE ..."
|
||||
/sbin/fsck.fat -a -w $DEVICE 2>/dev/null
|
||||
FSCK=$(/sbin/fsck.fat -a -w $DEVICE 2>/dev/null)
|
||||
if [[ "$FSCK" == *"differences between boot sector and its backup"* ]] ; then
|
||||
/sbin/fsck.fat -w $DEVICE 2>/dev/null <<< "1"
|
||||
else
|
||||
echo "$FSCK"
|
||||
fi
|
||||
/sbin/mount -v -t vfat -o auto,rw,flush,noatime,nodiratime,dmask=77,fmask=177,shortname=mixed $DEVICE /boot || abort "cannot mount $DEVICE"
|
||||
fi
|
||||
|
||||
@@ -161,6 +167,7 @@ else
|
||||
/sbin/mount -w -v -n -o remount /
|
||||
RETVAL=$?
|
||||
[[ $RETVAL -gt 0 ]] && abort "failed to remount $UNRAIDROOT r/w with return value $RETVAL"
|
||||
[[ ! -f /etc/rc.d/rc.S.cont ]] && abort "unable to continue - you must remove 'root=$UNRAIDROOT' from syslinux.cfg"
|
||||
fi
|
||||
|
||||
# set permissions for non vfat boot on /boot
|
||||
|
@@ -69,6 +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
|
||||
# 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."
|
||||
|
@@ -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."
|
||||
|
@@ -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
|
||||
}
|
||||
|
75
etc/rc.d/rc.cgroup2unraid
Executable file
75
etc/rc.d/rc.cgroup2unraid
Executable file
@@ -0,0 +1,75 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# script: rc.cgroup2unraid
|
||||
#
|
||||
# start/stop/status/restart/run Unraid cgroup2 cleanup:
|
||||
#
|
||||
# LimeTech - created for Unraid OS
|
||||
# /etc/rc.d/rc.cgroup2unraid
|
||||
|
||||
DAEMON="Unraid cgroup2 cleanup daemon"
|
||||
CGROUP2="/usr/libexec/unraid/cgroup2-unraid"
|
||||
PID="/var/run/cgroup2-unraid.pid"
|
||||
|
||||
# run & log functions
|
||||
. /etc/rc.d/rc.runlog
|
||||
|
||||
|
||||
cgroup2daemon_running(){
|
||||
sleep 0.1
|
||||
[[ $(pgrep -cf $CGROUP2) -gt 0 ]]
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
if cgroup2daemon_running ; then
|
||||
REPLY="Already started"
|
||||
else
|
||||
$CGROUP2 --daemon
|
||||
echo $(pgrep -f $CGROUP2) > $PID
|
||||
if cgroup2daemon_running; then
|
||||
REPLY="Started"
|
||||
else
|
||||
REPLY="Failed"
|
||||
fi
|
||||
fi
|
||||
log "$DAEMON... $REPLY."
|
||||
;;
|
||||
stop)
|
||||
if ! cgroup2daemon_running ; then
|
||||
REPLY="Already stopped"
|
||||
else
|
||||
log "Stopping $DAEMON..."
|
||||
kill $(cat $PID)
|
||||
if cgroup2daemon_running; then
|
||||
REPLY="Failed"
|
||||
else
|
||||
REPLY="Stopped"
|
||||
fi
|
||||
fi
|
||||
log "$DAEMON... $REPLY."
|
||||
;;
|
||||
status)
|
||||
if cgroup2daemon_running ; then
|
||||
echo "$DAEMON running"
|
||||
else
|
||||
echo "$DAEMON not running"
|
||||
if [ -f $PID ]; then
|
||||
rm -f $PID
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
run)
|
||||
echo "Cleaning up cgroups..."
|
||||
$CGROUP2
|
||||
echo "Done!"
|
||||
;;
|
||||
restart)
|
||||
$0 stop
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $BASENAME start|stop|status|restart|run"
|
||||
exit 1
|
||||
esac
|
||||
exit 0
|
@@ -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(){
|
||||
|
@@ -58,14 +58,18 @@ if [[ -f $DOCKER_CFG ]]; then
|
||||
. $DOCKER_CFG
|
||||
fi
|
||||
|
||||
# Set storage driver appropriate for backing filesystem, override user setting
|
||||
BACKINGFS=$(findmnt --output FSTYPE --noheadings $DOCKER_ROOT)
|
||||
if [[ $BACKINGFS == btrfs ]]; then
|
||||
DOCKER_OPTS="$DOCKER_OPTS --storage-driver=btrfs"
|
||||
elif [[ $BACKINGFS == xfs ]]; then
|
||||
# set storage driver to overlay2 if config value is found, otherwise fall back to native FS driver
|
||||
if [[ $(awk -F'"' '/^DOCKER_BACKINGFS=/{print $2}' $DOCKER_CFG 2>/dev/null) == overlay2 ]]; then
|
||||
DOCKER_OPTS="$DOCKER_OPTS --storage-driver=overlay2"
|
||||
elif [[ $BACKINGFS == zfs ]]; then
|
||||
DOCKER_OPTS="$DOCKER_OPTS --storage-driver=zfs"
|
||||
else
|
||||
BACKINGFS=$(findmnt --output FSTYPE --noheadings $DOCKER_ROOT)
|
||||
if [[ $BACKINGFS == btrfs ]]; then
|
||||
DOCKER_OPTS="$DOCKER_OPTS --storage-driver=btrfs"
|
||||
elif [[ $BACKINGFS == xfs ]]; then
|
||||
DOCKER_OPTS="$DOCKER_OPTS --storage-driver=overlay2"
|
||||
elif [[ $BACKINGFS == zfs ]]; then
|
||||
DOCKER_OPTS="$DOCKER_OPTS --storage-driver=zfs"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Less verbose logging by default
|
||||
@@ -86,6 +90,7 @@ MTU=$(ip link show $PORT | grep -Po 'mtu \K\d+')
|
||||
if [[ -n $(ip -6 route show default dev $PORT) ]]; then
|
||||
DOCKER0='fd17::/64'
|
||||
DOCKER_OPTS="--ipv6 --fixed-cidr-v6=$DOCKER0 $DOCKER_OPTS"
|
||||
IPV6_FORWARD=${IPV6_FORWARD:=accept}
|
||||
# create IPv6 NAT rule for docker0
|
||||
[[ -z $(ip6tables -t nat -S | grep -o "$DOCKER0") ]] && run ip6tables -t nat -A POSTROUTING -s $DOCKER0 ! -o docker0 -j MASQUERADE
|
||||
else
|
||||
@@ -122,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
|
||||
@@ -259,15 +264,19 @@ docker_network_start(){
|
||||
USER_NETWORKS=$(docker inspect --format='{{range $key,$value:=.NetworkSettings.Networks}}{{$key}};{{if $value.IPAMConfig}}{{if $value.IPAMConfig.IPv4Address}}{{$value.IPAMConfig.IPv4Address}}{{end}}{{if $value.IPAMConfig.IPv6Address}},{{$value.IPAMConfig.IPv6Address}}{{end}}{{end}} {{end}}' $CONTAINER)
|
||||
for ROW in $USER_NETWORKS; do
|
||||
ROW=(${ROW/;/ })
|
||||
MY_NETWORK=${ROW[0]}
|
||||
MY_IP=${ROW[1]/,/;}
|
||||
if [[ -n $MY_NETWORK && $MY_NETWORK != $MY_NETWORK ]]; then
|
||||
LABEL=${MY_NETWORK//[0-9.]/}
|
||||
if [[ $STOCK =~ $LABEL && $LABEL != ${PORT:0:-1} ]]; then
|
||||
MY_NETWORK=${MY_NETWORK/$LABEL/${PORT:0:-1}}
|
||||
USER_NETWORK=${ROW[0]}
|
||||
USER_IP=${ROW[1]/,/;}
|
||||
if [[ -n $USER_NETWORK && $USER_NETWORK != $MY_NETWORK ]]; then
|
||||
LABEL=${USER_NETWORK//[0-9.]/}
|
||||
IF_NO_PARTS=${USER_NETWORK#"$LABEL"}
|
||||
IF_NO=${IF_NO_PARTS%%.*}
|
||||
if [[ $STOCK =~ $LABEL && $IF_NO -gt 0 ]]; then
|
||||
USER_NETWORK=$USER_NETWORK
|
||||
elif [[ $STOCK =~ $LABEL && $LABEL != ${PORT:0:-1} ]]; then
|
||||
USER_NETWORK=${USER_NETWORK/$LABEL/${PORT:0:-1}}
|
||||
fi
|
||||
log "container $CONTAINER has an additional network that will be restored: $MY_NETWORK"
|
||||
NETRESTORE[$MY_NETWORK]="$CONTAINER,$MY_IP ${NETRESTORE[$MY_NETWORK]}"
|
||||
log "container $CONTAINER has an additional network that will be restored: $USER_NETWORK"
|
||||
NETRESTORE[$USER_NETWORK]="$CONTAINER,$USER_IP ${NETRESTORE[$USER_NETWORK]}"
|
||||
fi
|
||||
done
|
||||
done
|
||||
@@ -453,6 +462,11 @@ docker_network_start(){
|
||||
fi
|
||||
fi
|
||||
done
|
||||
# create IPv6 forward accept rule
|
||||
if [[ $IPV6_FORWARD == accept ]]; then
|
||||
log "creating forward accept rule for IPv6 network"
|
||||
ip6tables -P FORWARD ACCEPT
|
||||
fi
|
||||
log "Network started."
|
||||
}
|
||||
|
||||
@@ -517,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(){
|
||||
@@ -554,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
|
||||
@@ -569,8 +583,6 @@ docker_service_stop(){
|
||||
# signal successful stop
|
||||
TIMER=-1
|
||||
else
|
||||
# show waiting message
|
||||
echo "$DAEMON... Waiting to die."
|
||||
((TIMER--))
|
||||
fi
|
||||
done
|
||||
|
@@ -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(){
|
||||
|
@@ -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."
|
||||
|
@@ -60,6 +60,9 @@ vmlist(){
|
||||
waitfor(){
|
||||
local C=0
|
||||
while [[ $C -lt $TIMEOUT && $(virsh list --state-$1 | awk "NR>2 && /${2:-^.+$}/" | wc -l) -gt 0 ]]; do
|
||||
if [ $C -eq 0 ]; then # echo Timeout info just one time and only if virsh returned something
|
||||
log "Waiting $TIMEOUT seconds for VMs with state: $1"
|
||||
fi
|
||||
((C++))
|
||||
sleep 1
|
||||
done
|
||||
|
@@ -61,6 +61,11 @@ mkdir -p $CONFIG/shares
|
||||
mkdir -p $CONFIG/ssh/root
|
||||
mkdir -p $CONFIG/ssl/certs
|
||||
|
||||
# Set default Docker backingfs type to native for existing installations
|
||||
if [[ -f "/boot/config/docker.cfg" ]]; then
|
||||
grep -q "DOCKER_BACKINGFS=" /boot/config/docker.cfg || echo "DOCKER_BACKINGFS=\"native\"" >> /boot/config/docker.cfg
|
||||
fi
|
||||
|
||||
# upgrade network configuration (if needed) and (re)generates our welcome text
|
||||
if [[ -x /usr/local/sbin/create_network_ini ]]; then
|
||||
/usr/local/sbin/create_network_ini init &>/dev/null &
|
||||
@@ -76,11 +81,6 @@ fi
|
||||
/usr/local/emhttp/webGui/scripts/notify smtp-init
|
||||
/usr/local/emhttp/webGui/scripts/notify cron-init
|
||||
|
||||
# restore favorites
|
||||
if [[ -x /usr/local/emhttp/webGui/scripts/restore_favorites ]]; then
|
||||
/usr/local/emhttp/webGui/scripts/restore_favorites
|
||||
fi
|
||||
|
||||
# start nchan monitoring -> stop all running nchan processes when no subscribers are connected
|
||||
if [[ -x /usr/local/sbin/monitor_nchan ]]; then
|
||||
/usr/local/sbin/monitor_nchan
|
||||
@@ -177,6 +177,11 @@ for LANGUAGE in $CONFIG/plugins/lang-*.xml; do
|
||||
done
|
||||
shopt -u nullglob
|
||||
|
||||
# restore favorites
|
||||
if [[ -x /usr/local/emhttp/webGui/scripts/restore_favorites ]]; then
|
||||
/usr/local/emhttp/webGui/scripts/restore_favorites
|
||||
fi
|
||||
|
||||
# Enable persistent bash history
|
||||
PERSISTENT_BASH_HISTORY=$(grep "persist_bash_history" /boot/config/plugins/dynamix/dynamix.cfg 2>/dev/null | cut -d'=' -f2 | sed 's/"//g')
|
||||
if [[ $PERSISTENT_BASH_HISTORY == 1 ]]; then
|
||||
|
@@ -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
|
||||
|
@@ -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."
|
||||
}
|
||||
|
@@ -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
|
||||
@@ -123,8 +123,8 @@ nfsd_restart(){
|
||||
}
|
||||
|
||||
nfsd_reload(){
|
||||
# restart without info
|
||||
nfsd_restart &>/dev/null
|
||||
# reload without info
|
||||
$EXPORTFS -r &>/dev/null
|
||||
}
|
||||
|
||||
nfsd_update(){
|
||||
|
@@ -18,6 +18,7 @@
|
||||
DAEMON="Nginx server daemon"
|
||||
CALLER="nginx"
|
||||
NGINX="/usr/sbin/nginx"
|
||||
TS="/usr/local/sbin/tailscale"
|
||||
PID="/var/run/nginx.pid"
|
||||
SSL="/boot/config/ssl"
|
||||
CONF="/etc/nginx/nginx.conf"
|
||||
@@ -26,7 +27,13 @@ SERVERS="/etc/nginx/conf.d/servers.conf"
|
||||
LOCATIONS="/etc/nginx/conf.d/locations.conf"
|
||||
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=()
|
||||
@@ -105,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 "}"
|
||||
;;
|
||||
@@ -116,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
|
||||
@@ -154,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
|
||||
@@ -169,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;
|
||||
}
|
||||
@@ -182,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;
|
||||
@@ -227,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;
|
||||
@@ -248,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;
|
||||
@@ -270,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;
|
||||
@@ -285,6 +300,39 @@ build_servers(){
|
||||
EOF
|
||||
fi
|
||||
fi
|
||||
if [[ -n $TSFQDN ]]; then
|
||||
cat <<- EOF >>$SERVERS
|
||||
#
|
||||
# Redirect Tailscale http requests to https
|
||||
# ex: http://tower.magicDNS.ts.net -> https://tower.magicDNS.ts.net
|
||||
#
|
||||
server {
|
||||
$(listen $PORT)
|
||||
server_name $TSFQDN;
|
||||
return 302 https://$TSFQDN$PORTSSL_URL$request_uri;
|
||||
}
|
||||
#
|
||||
# Port settings for https using Tailscale cert
|
||||
# ex: https://tower.magicDNS.ts.net
|
||||
#
|
||||
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;
|
||||
ssl_trusted_certificate $TSCERTPATH;
|
||||
#
|
||||
# OCSP stapling
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
#
|
||||
location ~ /wsproxy/$PORTSSL/ { return 403; }
|
||||
include /etc/nginx/conf.d/locations.conf;
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
|
||||
# build our locations
|
||||
@@ -386,7 +434,9 @@ build_locations(){
|
||||
#
|
||||
# pass PHP scripts to FastCGI server listening on unix:/var/run/php-fpm.sock
|
||||
#
|
||||
location ~ \.php$ {
|
||||
location ~ ^(.+\.php)(.*)$ {
|
||||
fastcgi_split_path_info ^(.+\.php)(.*)$;
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
include fastcgi_params;
|
||||
}
|
||||
#
|
||||
@@ -493,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
|
||||
@@ -506,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
|
||||
@@ -526,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
|
||||
@@ -534,6 +590,23 @@ build_ssl(){
|
||||
[[ -n $(openssl x509 -noout -ocsp_uri -in "$CERTPATH") ]] && CERTSTAPLE=on || CERTSTAPLE=off
|
||||
fi
|
||||
|
||||
# handle TS cert if present
|
||||
if [[ -f "$TSCERTPATH" ]]; then
|
||||
# confirm TS is intalled and running
|
||||
if [[ -x $TS ]] && $TS status &>/dev/null; then
|
||||
# extract common name from cert
|
||||
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 [[ -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
|
||||
|
||||
# build servers configuration file
|
||||
build_servers
|
||||
# build locations configuration file
|
||||
@@ -570,6 +643,8 @@ build_ssl(){
|
||||
echo "NGINX_WANIP6=\"$WANIP6\"" >>$INI
|
||||
echo "NGINX_WANFQDN=\"$WANFQDN\"" >>$INI
|
||||
echo "NGINX_WANFQDN6=\"$WANFQDN6\"" >>$INI
|
||||
# defined if ts_bundle.pem present:
|
||||
echo "NGINX_TAILSCALEFQDN=\"$TSFQDN\"" >>$INI
|
||||
# add included interfaces
|
||||
for NET in ${!NET_FQDN[@]}; do
|
||||
echo "NGINX_${NET^^}FQDN=\"${NET_FQDN[$NET]}\"" >>$INI
|
||||
|
@@ -26,7 +26,7 @@ ntpd_running(){
|
||||
}
|
||||
|
||||
ntpd_build(){
|
||||
cp $CONF- $CONF
|
||||
[[ -f $CONF.orig ]] && cp $CONF.orig $CONF || cp $CONF $CONF.orig
|
||||
echo "# Generated entries follow:" >>$CONF
|
||||
echo "interface ignore wildcard" >>$CONF
|
||||
if check && [[ -n $BIND ]]; then
|
||||
@@ -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
|
||||
|
@@ -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."
|
||||
|
@@ -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."
|
||||
}
|
||||
|
||||
|
@@ -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(){
|
||||
@@ -65,6 +72,7 @@ samba_settings(){
|
||||
else
|
||||
echo "local master = No" >>$CONF
|
||||
fi
|
||||
echo "nmbd bind explicit broadcast = no" >> $CONF
|
||||
else
|
||||
echo "disable netbios = yes" >>$CONF
|
||||
echo "server min protocol = SMB2" >>$CONF
|
||||
@@ -145,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
|
||||
@@ -170,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(){
|
||||
|
@@ -25,7 +25,7 @@ smart_stop() {
|
||||
if [ -r /run/smartd.pid ]; then
|
||||
kill $(cat /run/smartd.pid)
|
||||
else
|
||||
killall smartd
|
||||
killall --ns $$ smartd
|
||||
fi
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user