Compare commits

...

186 Commits

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

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

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

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

The integration will show a Tailscale icon on the Docker page for each Tailscale enabled Container and show some basic information from the container (Installed Tailscale Version, Online Status, Hostname, Main Relay, IPs, Exit Node, Auth Expiry,...)
When Serve or Funnel is enabled it will also generate `Tailscale WebUI` in the drop down for the container which you can open up if Tailscale is installed from the device you are accessing Unraid.
2024-10-16 22:55:53 +02:00
Tom Mortensen
95c6913c62 small chang in how rc.cgroup2unaid is invoked 2024-10-11 02:24:43 -07:00
Tom Mortensen
b783d4b207 more killall and pgrep updates. 2024-10-11 02:24:43 -07:00
Tom Mortensen
a866de833a Revert "stop ntpd from complaining about multiple IP addresses" 2024-10-11 02:24:43 -07:00
tom mortensen
ee31e35849 Merge pull request #1882 from desertwitch/fix-rsyslogd
(bugfix) /etc/rc.d/rc.rsyslogd: OS-native PID for decisions
2024-10-10 17:13:35 -07:00
tom mortensen
50e7389c8a Merge pull request #1889 from SimonFair/ntp-fix
killall and pgrep updates.
2024-10-10 17:02:59 -07:00
tom mortensen
d536ef285b Merge pull request #1890 from dlandon/delete_DS_Store_files_when_deleting_user_share
Delete all '*.DS_Store' files when deleting share.
2024-10-10 16:57:08 -07:00
dlandon
2dc82b61de Delete all '*.DS_Store' files when deleting share. 2024-10-10 16:16:09 -05:00
SimonFair
57ec7909e5 Update rc.sshd 2024-10-10 21:54:16 +01:00
SimonFair
01c6f64b52 Revert sshd loops. 2024-10-10 21:03:02 +01:00
SimonFair
80d567dfde killall and pgrep updates.
Set --ns $$ on commands.
2024-10-10 19:18:54 +01:00
tom mortensen
99d60fa08a Merge pull request #1888 from SimonFair/ntp-fix
Samba update and to use smbcontrol for reload
2024-10-10 10:37:49 -07:00
tom mortensen
8bbf176b8b Merge pull request #1887 from unraid/fix-device-list
Fix PHP error in device_list
2024-10-10 10:36:50 -07:00
tom mortensen
ca51a3799b Merge pull request #1885 from dlandon/remove_ps.txt_file_from_diagnostics
Remove ps.txt from diagnostics.
2024-10-10 10:36:28 -07:00
tom mortensen
e4bb758b05 Merge pull request #1884 from dlandon/show_delete_on_shares_settings_page_at_all_times
Delete share is always visible.
2024-10-10 10:35:59 -07:00
SimonFair
3c007fa1d0 Samba update and to use smbcontrol for reload 2024-10-10 17:17:17 +01:00
ljm42
c062e4dd9c Fix PHP error in device_list
Was preventing the main page from populating with data in some cases
2024-10-10 09:09:39 -07:00
desertwitch
06b1c9a20f /etc/rc.d/rc.rsyslogd: check status before reload 2024-10-10 07:03:48 +02:00
desertwitch
bf6d5982be /etc/rc.d/rc.rsyslogd: use pgrep, killall with PID namespace 2024-10-10 06:51:05 +02:00
dlandon
d9bd5b56c8 Remove ps.txt from diagnostics. 2024-10-09 13:08:55 -05:00
dlandon
bc7c66fec9 Delete share is always visible and enabled only when it is safe to delete the share. 2024-10-09 12:06:48 -05:00
tom mortensen
dc50e7d2c2 Merge pull request #1881 from SimonFair/ntp-fix
Set NTP to use interfaces.
2024-10-09 08:56:07 -07:00
desertwitch
0061c66dfe /etc/rc.d/rc.rsyslogd: OS-native PID for decisions
bugfix: multiple running rsyslogd processes (e.g. spawned by Docker containers) caused the rc.d script to think that the OS-native process was already running, resulting in startup failure of the OSes daemon.
2024-10-09 14:52:44 +02:00
SimonFair
91caf869f5 Set NTP to use inferfaces. 2024-10-08 21:57:48 +01:00
tom mortensen
97c3a4621b Merge pull request #1877 from dlandon/cannot_change_share_settings_if_pools_missing
Fix a situation where the secondary pool device is missing.
2024-10-08 12:18:48 -07:00
tom mortensen
1ffb22bddf Merge pull request #1880 from ich777/ich777-docker-directory-fix
Fix for docker directory
2024-10-08 12:16:44 -07:00
tom mortensen
1d9e14f07c Merge pull request #1878 from SimonFair/VM-ZFS-Fixes
Fix virtiofsd pre processor + add virtiogpu option to gui.
2024-10-08 12:14:26 -07:00
6f7b97e37a Fix for docker directory
- store path outside from function to not shorten path if users switches multiple times between `image` and `folder`
- make sure overlay2 is selected by default
2024-10-07 14:54:08 +02:00
SimonFair
ed7219d9c7 Add virtio gpu option. 2024-10-06 11:29:57 +01:00
SimonFair
082d7d842b Revert "Add virtio gu(2d) as an option of template."
This reverts commit 9a502776a1.
2024-10-06 11:26:34 +01:00
SimonFair
9a502776a1 Add virtio gu(2d) as an option of template. 2024-10-06 11:17:19 +01:00
SimonFair
03346f4709 Update virtiofsd.php 2024-10-05 07:57:19 +01:00
dlandon
1cc84832ee Fix a situation where secondary pool device is missing and the Array is chosen for the primary device.. 2024-10-03 13:57:21 -05:00
Tom Mortensen
959df7e46c fx: Agent notifications don't work if there's a problem with email notifications 2024-10-02 09:55:44 -07:00
Tom Mortensen
3fb6c2147b Small change of var name CUSTOMFA to NGINX_CUSTOMFA. 2024-10-02 09:55:44 -07:00
Tom Mortensen
3db6fa9a1d update rc.bind (but not used) 2024-10-02 09:55:44 -07:00
tom mortensen
0c5987fab0 Merge pull request #1811 from unraid/kill-samba
Forcibly kill samba if needed
2024-10-02 09:52:46 -07:00
tom mortensen
fcbc8f700e Merge pull request #1876 from dkaser/check-container-networks
fix: prevent deleting containers that are assigned as a network
2024-10-01 14:00:08 -07:00
Derek Kaser
6fd88575b2 fix: prevent deleting containers that are assigned as a network 2024-10-01 04:21:51 +00:00
tom mortensen
bff0238f88 Merge pull request #1875 from dkaser/sanitize-tsnet
feat: sanitize ts.net domain names from nginx config
2024-09-30 15:45:50 -07:00
Derek Kaser
ee7f1f4a5b feat: sanitize ts.net domain names from nginx config 2024-09-30 02:29:37 +00:00
tom mortensen
c3dd62f1d5 Merge pull request #1874 from dlandon/container_device_not_showing
Container device not showing in Dockerman.
2024-09-27 09:19:24 -07:00
tom mortensen
7c0fb18e3c Merge pull request #1873 from unraid/add-csp-frame-ancestors
Improved "Content-Security-Policy frame-ancestors" support
2024-09-27 09:15:29 -07:00
ljm42
fe2e2ff897 define CUSTOMFA in /etc/defaults/nginx 2024-09-26 16:03:36 -07:00
ljm42
74530129ae Merge branch 'master' into add-csp-frame-ancestors 2024-09-26 15:52:44 -07:00
tom mortensen
968e3b1d72 Merge pull request #1870 from dkaser/tailscale-routes
feat: show routes from all routing tables, not just default
2024-09-26 12:25:30 -07:00
tom mortensen
6bdcb38c47 Merge pull request #1869 from ich777/ich777-rc.sshd-fix
Fix for rc.sshd
2024-09-26 12:25:09 -07:00
tom mortensen
8c7cdca4aa Merge pull request #1868 from unraid/ts-redirect
redirect http TS url to https TS url
2024-09-26 12:24:42 -07:00
tom mortensen
08024a0464 Merge pull request #1867 from ich777/ich777-docker-fix
Fix for 3rd party containers "not available" colour
2024-09-26 12:24:05 -07:00
dlandon
828cd7b747 Container device not showing in Dockerman. 2024-09-26 09:13:42 -05:00
ljm42
0ce3960de6 Improved "Content-Security-Policy frame-ancestors" support 2024-09-25 23:16:52 -07:00
tom mortensen
8b91d22796 Merge pull request #1872 from ich777/ich777-rc.docker-patch
Update rc.docker
2024-09-25 10:36:52 -07:00
b716920800 Update rc.docker
- Remove ID since not necessary
2024-09-24 16:55:21 +02:00
6d749a8b1a Update rc.docker
- Further improvement to stop containers only managed by Unraid or the Compose plugin
- Small fix for the notification
2024-09-24 16:46:02 +02:00
69b95ae27d Update rc.docker
- Only stop Unraid managed containers
- Don't kill containers since Docker will kill them if they won't stop after the set timeout when the daemon is stopping
- Increase timeout for daemon to die to 30 seconds (seems a bit short if 3rd party containers are installed)
- Rephrase message for daemon to die and display it only once
2024-09-24 15:51:46 +02:00
Derek Kaser
20e29ab5af feat: show routes from all routing tables, not just default 2024-09-23 21:32:13 +00:00
a75bc3d4d7 Fix for rc.sshd
- Remove trailing slash to be sure to grab the correct PIDs
2024-09-23 22:16:02 +02:00
ljm42
e8e5ccdf18 redirect http TS url to https TS url 2024-09-23 12:59:05 -07:00
020ed9a07f Small fix for 3rd party containers
- Fix orange text "not available" to use the default text colour.
2024-09-23 21:19:04 +02:00
Tom Mortensen
8f656e87b1 establish sensible zfs pool defaults 2024-09-23 10:57:13 -07:00
Tom Mortensen
54b1e81b38 unconditinally set directories to mode 0777 2024-09-23 10:54:57 -07:00
Tom Mortensen
4c6be23467 Clean up empty cgroups 2024-09-20 09:20:44 -07:00
Tom Mortensen
4a4983f7c5 rc.S: reboot should not invoke shutdown 2024-09-20 09:20:44 -07:00
tom mortensen
dcfaa1afa0 Merge pull request #1866 from SimonFair/VM-ZFS-Fixes
Fix VM usage stats value if timer > 1 sec.
2024-09-20 09:19:46 -07:00
SimonFair
e52813b626 Divide net and disk values by timer 2024-09-19 22:16:00 +01:00
tom mortensen
760aac71df Merge pull request #1863 from dlandon/default_array_file_system_message
Change default file system text.
2024-09-16 14:58:28 -07:00
tom mortensen
20ef176665 Merge pull request #1862 from ich777/patch-4
Fix for rc.docker for interfaces with higher index than 0
2024-09-16 14:57:48 -07:00
tom mortensen
66d7193dab Merge pull request #1858 from ich777/rc.sshd_fix
Only kill sshd from host
2024-09-16 14:56:54 -07:00
tom mortensen
9c9c79b1b3 Merge pull request #1857 from ich777/ich777-docker-container-network
Allow users to select Container networks in the WebUI
2024-09-16 14:55:54 -07:00
tom mortensen
8aac4ee119 Merge pull request #1856 from desertwitch/patch-1
en_US/helptext.txt: Fix broken link in GUI
2024-09-16 14:55:00 -07:00
tom mortensen
8120959c2f Merge pull request #1854 from desertwitch/fix-php-warning
dynamix/include/SysDevs.php: fix PHP warnings
2024-09-16 14:54:20 -07:00
tom mortensen
72abe50721 Merge pull request #1853 from Commifreak/libvirt-log-timeout-info
Add a log line noting how long we wait currently for a VM action.
2024-09-16 14:52:55 -07:00
tom mortensen
18e37ed045 Merge pull request #1852 from dkaser/tailscale-nginx
Add fastcgi_path_info to default nginx configuration
2024-09-16 14:51:36 -07:00
tom mortensen
6845c007a7 Merge pull request #1851 from ich777/page-builder-fix
Fix issue with plugin icons
2024-09-16 14:50:50 -07:00
tom mortensen
b844f941d0 Merge pull request #1850 from unraid/add-ts-certs
Add support for Tailscale certs in the webgui
2024-09-16 14:49:59 -07:00
tom mortensen
5883e767aa Merge pull request #1849 from dlandon/parity-check-shows-incorrect-results
Parity check wrong again.
2024-09-16 14:46:15 -07:00
tom mortensen
efc4fa2673 Merge pull request #1848 from ich777/ich777-not-dockerman-managed
Correctly identify/show non dockerman Managed containers
2024-09-16 14:45:11 -07:00
tom mortensen
0810fc5bd8 Merge pull request #1847 from SimonFair/VM-ZFS-Fixes
VM ZFS  dataset removal processing additional fixes
2024-09-16 14:44:14 -07:00
tom mortensen
897365a5de Merge pull request #1845 from Squidly271/patch-42
Adjust URL for CA installation
2024-09-16 14:42:52 -07:00
dlandon
8d628aad4f Change default file system text. 2024-09-13 11:47:57 -05:00
e3c4ff280d Small fix 2024-09-12 23:33:27 +02:00
19de7c1979 Fix for rc.docker for interfaces with higher index than 0
- Fix error in first if condition which always returned `false`
- Change name from variables so that it a bit easier to read
- Make sure that custom interfaces with a higher index then 0 are properly rebuilt
2024-09-12 23:12:03 +02:00
5ec695921a Fix sshd_update logic 2024-09-11 07:39:34 +02:00
171a77feec Change function
- return `1` if daemon is running and return `0` if it is not running
2024-09-11 07:38:15 +02:00
3e29f0b8b8 Only kill sshd from host 2024-09-10 22:32:07 +02:00
b62c8f5a12 Update Helpers.php 2024-09-09 17:09:15 +02:00
e9faee0d27 Update CreateDocker.php 2024-09-09 17:07:43 +02:00
Rysz
16823d07b1 en_US/helptext.txt: Fix broken link 2024-09-09 06:56:35 +02:00
desertwitch
73705b71fa dynamix/include/SysDevs.php: fix PHP warnings
test for exec-returned variable existence before usage
2024-09-08 20:21:18 +02:00
Robin Kluth
6b31532688 Add a log line noting how long we wait currently for a VM action. 2024-09-06 15:46:06 +02:00
Derek Kaser
039c798b43 Add fastcgi_path_info to default nginx configuration 2024-09-05 22:00:20 +00:00
1e43abc785 Fix issue with plugin icons
- make sure the maximum icon size for a plugin is 18x18px
2024-09-05 10:16:51 +02:00
ljm42
9b1081d2e1 Add support for Tailscale certs in the webgui 2024-09-04 21:37:08 -07:00
SimonFair
c27e018fdb Reduce Multifunction starting bus. 2024-09-04 21:11:44 +01:00
dlandon
ef5067584b Change file_put_contents_atomic() back to file_put_contents() since it is liable to change. 2024-09-04 12:34:23 -05:00
SimonFair
4ea425411a Add disable rename if snapshots found. 2024-09-03 21:45:50 +01:00
dlandon
24bdc5169d Parity check wrong again. 2024-09-03 15:36:12 -05:00
SimonFair
753d87c690 Additional VM ZFS delete fixes 2024-09-02 18:03:10 +01:00
07d02f579f Add files via upload 2024-09-02 17:48:25 +02:00
SimonFair
ceb97ab392 Disable dataset processing. 2024-09-01 21:17:05 +01:00
Squidly271
6a15afa2a8 Update Apps.page 2024-09-01 15:57:02 -04:00
tom mortensen
175d24afd2 Merge pull request #1842 from dlandon/rework-share-list
Fix condition where useCache is 'no' when there is no array.
2024-08-30 13:00:58 -07:00
Tom Mortensen
12828eec63 stop ntpd from complaining about multiple IP addresses 2024-08-30 09:04:48 -07:00
dlandon
156599031a Fix condition where useCache is 'no' when there is no array. 2024-08-30 07:25:33 -05:00
tom mortensen
15f4138c87 Merge pull request #1839 from SimonFair/VM-ZFS-Fixes
ZFS fixes
2024-08-29 11:58:12 -07:00
tom mortensen
fd6e4f1ba1 Merge pull request #1840 from dlandon/rework-share-list
Fix share list.
2024-08-29 11:57:17 -07:00
tom mortensen
72a47035ef Merge pull request #1841 from dlandon/rename-pool-message
Add warning message to rename pool dialog.
2024-08-29 11:55:57 -07:00
dlandon
d57bf205fa Add warning message to rename pool dialog. 2024-08-29 13:23:38 -05:00
dlandon
16a8e7092d Fix share list not showing invalid storage configurations and some code cleanup. 2024-08-29 13:00:06 -05:00
SimonFair
00b1f77742 ZFS fixes 2024-08-29 17:45:00 +01:00
Tom Mortensen
506270e413 Fixes zfs subpool creation issue.
Fixes DeviceInfo not populating for array devices when Started.
2024-08-29 02:04:38 -07:00
Tom Mortensen
303c76d7da rc.nfsd: the 'update' method only causes re-read of /etc/exports 2024-08-29 02:03:51 -07:00
Tom Mortensen
73ea1bb7b3 remove deprecated check 2024-08-29 02:02:55 -07:00
tom mortensen
9d4ca6a2c9 Merge pull request #1837 from ich777/docker_fix
Make sure to not show internal IP from stopped containers
2024-08-29 01:25:29 -07:00
49b82d0eb8 Make sure to not show internal IP from stopped containers 2024-08-28 20:33:25 +02:00
tom mortensen
106f155ecc Merge pull request #1835 from donbuehl/feature/1832
Feature Request: Use user's actual shell in OpenTerminal.php #1832
2024-08-27 21:14:52 -07:00
donbuehl
53704b58aa Final minimal viable solution 2024-08-27 21:43:46 +02:00
tom mortensen
da9add3637 Merge pull request #1836 from desertwitch/feature-default-file
update.php: add two optional methods to define defaults
2024-08-27 12:18:16 -07:00
donbuehl
db77c13552 MVP solution for zsh plugin
Implemented a minimal viable solution for the zsh plugin:
- Checks if the shell is /bin/zsh
- Falls back to bash if not

This approach prioritizes simplicity and reliability over a more general solution to avoid potential errors.
2024-08-26 14:29:24 +02:00
donbuehl
be22c0e1f8 Simplify shell detection for openterminal 2024-08-25 15:44:28 +02:00
donbuehl
cd9d20eaf3 Simplify shell detection for openterminal 2024-08-25 15:19:53 +02:00
Rysz
c4afbba9bc update.php: do not silence parse errors 2024-08-25 13:16:36 +02:00
Rysz
16089cd927 update.php: shorten var name and improve documentation 2024-08-25 13:13:26 +02:00
Rysz
1ede8e621b update.php: remove unneeded spaces 2024-08-25 08:43:54 +02:00
Rysz
64ead9a127 update.php: additional safeguards 2024-08-25 08:28:40 +02:00
Rysz
ff80906d11 update.php: add #defaultvalues parameter 2024-08-25 07:36:23 +02:00
tom mortensen
8981e8bb15 Merge pull request #1834 from ich777/grub_cpuisolation_fix
GRUB compatibility changes
2024-08-24 09:18:04 -07:00
tom mortensen
83675005d2 Merge pull request #1833 from SimonFair/VM-Unmap-support
Fix vmusage stopping/Unmap Defaults and Fix Multifunction.
2024-08-24 09:17:10 -07:00
SimonFair
4b7f2bfcee Default new disk to unmap. 2024-08-23 07:30:11 +01:00
SimonFair
72ff3c52c0 Unmap Default and Multifuncton
Set default for new VMs to discard="unmap"
Fix BSOD for VMs and Multifunction.
2024-08-22 23:24:18 +01:00
Tom Mortensen
69e11713e5 Dashboard: Soften messages during parity sync/check 2024-08-21 23:28:44 -07:00
desertwitch
67cf2db493 update.php: clarify paths in documentation 2024-08-21 12:38:40 +02:00
desertwitch
631479d27d update.php: follow #file behavior for more versatility 2024-08-21 12:36:56 +02:00
desertwitch
28b3d2ae71 update.php: clarify documentation comment 2024-08-21 12:02:09 +02:00
desertwitch
ec1689dc68 update.php: null coalescing to elvis operator 2024-08-21 11:11:47 +02:00
desertwitch
3615992dc4 update.php: add #defaultfile parameter 2024-08-21 10:51:37 +02:00
d9f83cc76b Fix diagnostics to support GRUB 2024-08-21 07:54:31 +02:00
donbuehl
7e6ad9512d Wrap getUserShell() call with escapeshellarg() for additional security 2024-08-20 09:08:31 +02:00
donbuehl
d7b4dfd44b Fixed missing $ in variable 2024-08-19 22:14:46 +02:00
donbuehl
c62ef28fc3 Refactor getUserShell() for improved clarity and maintainability
- Introduce  variable for better code readability
- Simplify return logic using the default shell variable
- Maintain comprehensive error handling with Throwable
2024-08-19 22:06:41 +02:00
donbuehl
07fa790411 Improve getUserShell() function for accuracy and consistency
- Refine username matching to prevent partial matches
- Use English for syslog error message
2024-08-19 21:49:31 +02:00
donbuehl
fb19a99ad4 Added function getUserShell() to OpenTerminal.php and use it for the execution 2024-08-19 18:16:38 +02:00
d0dcf6c314 Fix for GRUB bootloader 2024-08-19 16:58:31 +02:00
0d925a2471 Fix for GRUB bootloader 2024-08-19 16:54:19 +02:00
SimonFair
2b4eb1abad Fix vmusage stopping when you on a new page on a tab. 2024-08-19 13:16:50 +01:00
tom mortensen
251881d850 Merge pull request #1831 from SimonFair/VM-Unmap-support
Add Unmap Support
2024-08-18 10:34:35 -07:00
tom mortensen
24fce7582c Merge pull request #1828 from mtongnz/network-display-improvements
Network info display improvements (part 2)
2024-08-17 08:28:45 -07:00
SimonFair
b56f3e529c Add Discard option. 2024-08-17 00:34:52 +01:00
tom mortensen
130e3fcd44 Merge pull request #1829 from desertwitch/fix-php-settings
fix inconsistent PHP error reporting defaults (PHPSettings.page)
2024-08-16 09:15:35 -07:00
mtongnz
c1b2bb7435 fix: better display of container LAN IP for ipvlan & macvlan 2024-08-16 18:20:10 +12:00
mtongnz
840e19d322 fix: some networks not showing 2024-08-16 17:35:22 +12:00
SimonFair
2ae85fdd31 Add unmap on VM change. 2024-08-15 22:36:15 +01:00
SimonFair
0db0032648 Add Unmap Support 2024-08-15 19:29:20 +01:00
desertwitch
8d9e2a04c0 PHP constants for better read-/maintainability 2024-08-15 16:49:32 +02:00
desertwitch
31f81349a8 sanitize PHP setting constants for update.php
update.php encapsulates the PHP constants used by the OS default PHP error reporting level with double quotes, which breaks functionality as they are then treated as text. by turning the PHP constants into strings first this problem is avoided.
2024-08-15 16:29:45 +02:00
desertwitch
3de8e05432 fix inconsistent PHP error reporting defaults 2024-08-15 14:08:58 +02:00
Matt
1c019c8f08 fix: display LAN:IP for multiple external networks 2024-08-15 18:50:44 +12:00
Matt
8d76d6f1cc fix: display multiple networks for all types
This commit ensures containers with multiple networks are always displayed.
Previously, networks connected to containers (docker network connect...) don't show
2024-08-15 15:55:01 +12:00
tom mortensen
3e17f35e19 Merge pull request #1826 from zackspear/refactor/registration-page-transfer-ineligible-copy
refactor: registration transfer check ineligible copy
2024-08-14 11:15:46 -07:00
Zack Spear
6f51589547 refactor: registration page transfer check ineligible copy 2024-08-13 17:28:29 -07:00
Zack Spear
b18e734381 chore: update .gitignore for prettier usage 2024-08-13 17:27:26 -07:00
tom mortensen
b5a8223ffe Merge pull request #1820 from Garbee/ssh-key-regex
feat(user-edit): update allowed ssh key encryptions
2024-08-13 12:46:39 -07:00
tom mortensen
936adea879 Merge pull request #1824 from unraid/fix-typo
Fix typo in help text
2024-08-13 12:44:31 -07:00
tom mortensen
1572378824 Merge pull request #1822 from SimonFair/VM-Manager-PHP-Fixes
Add ZFS Checking
2024-08-13 12:39:12 -07:00
Tom Mortensen
0cf3585a0d update error messsage 2024-08-13 12:37:57 -07:00
Tom Mortensen
3f103f2089 DeviceInfo page improvements:
* Present confirmation dialog for Delete Pool operation
* Fix expansion of single device pool to multi-device zfs mirror or btrfs raid-1
2024-08-13 12:28:28 -07:00
Tom Mortensen
ed308c3a69 Display warning along side replacement and expansion devivces on Main if they will be initialized 2024-08-13 12:28:28 -07:00
Tom Mortensen
985d077af5 Samba smb.conf: set "nmbd bind explicit broadcast = no" if NetBIOS enabled 2024-08-13 12:28:28 -07:00
Tom Mortensen
86b8b170d1 Startup improvements in rc.S script:
* Automatically repair boot sector backup
* Detect bad root value in syslinux.cfg
* Explicitly unmount all file systems if cannot continue boot
2024-08-13 12:28:28 -07:00
Tom Mortensen
d351e51f58 remove wol source 2024-08-13 12:28:28 -07:00
tom mortensen
53c1788580 Merge pull request #1821 from Garbee/remove-document-write
fix: replace document write usages
2024-08-13 12:27:42 -07:00
tom mortensen
0870461731 Merge pull request #1818 from ich777/grub_vm_settings_fix
Grub VM settings fix
2024-08-13 10:27:49 -07:00
SimonFair
32bb9bb6d9 Add ZFS Checking
This will check VM name does not include characters that are not valid for ZFS.
Existing VMs are not modified but will throw error and disable update if invalid characters are found.
2024-08-12 17:18:28 +01:00
Jonathan Garbee
2ff8b77c9d fix: replace document write usages 2024-08-10 15:35:49 +00:00
Jonathan Garbee
654db74167 feat(user-edit): update allowed ssh key encryptions 2024-08-10 14:51:06 +00:00
fb2b66b5b0 Make VM Settings work with grub 2024-08-09 12:38:18 +02:00
663665a61b Make VM Settings work when using GRUB 2024-08-09 12:37:00 +02:00
ljm42
33a73b2fb5 Forcibly kill samba if needed 2024-08-02 10:33:34 -07:00
85 changed files with 3141 additions and 1121 deletions

5
.gitignore vendored
View File

@@ -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

View File

@@ -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
@@ -1283,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/).
@@ -2295,6 +2303,136 @@ Generally speaking, it is recommended to leave this setting to its default value
IMPORTANT NOTE: If adjusting port mappings, do not modify the settings for the Container port as only the Host port can be adjusted.
:end
:docker_container_network_help:
This allows your container to utilize the network configuration of another container. Select the appropriate container from the list.<br/>This setup can be particularly beneficial if you wish to route your container's traffic through a VPN.
:end
:docker_tailscale_help:
Enable Tailscale to add this container as a machine on your Tailnet.
:end
:docker_tailscale_hostname_help:
Provide the hostname for this container. It does not need to match the container name, but it must be unique on your Tailnet. Note that an HTTPS certificate will be generated for this hostname, which means it will be placed in a public ledger, so use a name that you don't mind being public.
For more information see <a href="https://tailscale.com/kb/1153/enabling-https" target="_blank">enabling https</a>.
:end
:docker_tailscale_be_exitnode_help:
Enable this if other machines on your Tailnet should route their Internet traffic through this container, this is most useful for containers that connect to commercial VPN services.
Be sure to authorize this Exit Node in your <a href="https://login.tailscale.com/admin/machines" target="_blank">Tailscale Machines Admin Panel</a>.
For more details, see the Tailscale documentation on <a href="https://tailscale.com/kb/1103/exit-nodes" target="_blank">Exit Nodes</a>.
:end
:docker_tailscale_exitnode_ip_help:
Optionally route this container's outgoing Internet traffic through an Exit Node on your Tailnet. Choose the Exit Node or input its Tailscale IP address.
For more details, see <a href="https://tailscale.com/kb/1103/exit-nodes" target="_blank">Exit Nodes</a>.
:end
:docker_tailscale_lanaccess_help:
Only applies when this container is using an Exit Node. Enable this to allow the container to access the local network.
<b>WARNING:</b>&nbsp;Even with this feature enabled, systems on your LAN may not be able to access the container unless they have Tailscale installed.
:end
:docker_tailscale_userspace_networking_help:
When enabled, this container will operate in a restricted environment. Tailscale DNS will not work, and the container will not be able to initiate connections to other Tailscale machines. However, other machines on your Tailnet will still be able to communicate with this container.
When disabled, this container will have full access to your Tailnet. Tailscale DNS will work, and the container can fully communicate with other machines on the Tailnet.
However, systems on your LAN may not be able to access the container unless they have Tailscale installed.
:end
:docker_tailscale_ssh_help:
Tailscale SSH is similar to the Docker "Console" option in the Unraid webgui, except you connect with an SSH client and authenticate via Tailscale.
For more details, see the <a href="https://tailscale.com/kb/1193/tailscale-ssh" target="_blank">Tailscale SSH</a> documentation..
:end
:docker_tailscale_serve_mode_help:
Enabling <b>Serve</b> will automatically reverse proxy the primary web service from this container and make it available on your Tailnet using https with a valid certificate!
Note that when accessing the <b>Tailscale WebUI</b> url, no additional authentication layer is added beyond restricting it to your Tailnet - the container is still responsible for managing usernames/passwords that are allowed to access it. Depending on your configuration, direct access to the container may still be possible as well.
For more details, see the <a href="https://tailscale.com/kb/1312/serve" target="_blank">Tailscale Serve</a> documentation.
If the documentation recommends additional settings for a more complex use case, enable "Tailscale Show Advanced Settings". Support for these advanced settings is not available beyond confirming the commands are passed to Tailscale correctly.
<b>Funnel</b> is similar to <b>Serve</b>, except that the web service is made available on the open Internet. Use with care as the service will likely be attacked. As with <b>Serve</b>, the container itself is responsible for handling any authentication.
We recommend reading the <a href="https://tailscale.com/kb/1223/funnel" target="_blank">Tailscale Funnel</a> documentation. before enabling this feature.
<b>Note:</b>&nbsp;Enabling <b>Serve</b> or <b>Funnel</b> publishes the Tailscale hostname to a public ledger.
For more details, see the Tailscale Documentation: <a href="https://tailscale.com/kb/1153/enabling-https" target="_blank">Enabling HTTPS</a>.
:end
:docker_tailscale_serve_port_help:
This field should specify the port for the primary web service this container offers. Note: it should specify the port in the container, not a port that was remapped on the host.
The system attempted to determine the correct port automatically. If it used the wrong value then there is likely an issue with the "Web UI" field for this container, visible by switching from "Basic View" to "Advanced View" in the upper right corner of this page.
In most cases this port is all you will need to specify in order to Serve the website in this container, although additional options are available below for more complex containers.
This value is passed to the `<serve_port>` portion of this command which starts serve or funnel:<br>
`tailscale [serve|funnel] --bg --<protocol><protocol_port><path> http://localhost:`**`<serve_port>`**`<local_path>`<br>
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
:end
:docker_tailscale_show_advanced_help:
Here there be dragons!
:end
:docker_tailscale_serve_local_path_help:
When not specified, this value defaults to an empty string. It is passed to the `<local_path>` portion of this command which starts serve or funnel:<br>
`tailscale [serve|funnel] --bg --<protocol><protocol_port><path> http://localhost:<serve_port>`**`<local_path>`**<br>
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
:end
:docker_tailscale_serve_protocol_help:
When not specified, this value defaults to "https". It is passed to the `<protocol>` portion of this command which starts serve or funnel:<br>
`tailscale [serve|funnel] --bg --`**`<protocol>`**`=<protocol_port><path> http://localhost:<serve_port><local_path>`<br>
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
:end
:docker_tailscale_serve_protocol_port_help:
When not specified, this value defaults to "=443". It is passed to the `<protocol_port>` portion of this command which starts serve or funnel:<br>
`tailscale [serve|funnel] --bg --<protocol>`**`<protocol_port>`**`<path> http://localhost:<serve_port><local_path>`<br>
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
:end
:docker_tailscale_serve_path_help:
When not specified, this value defaults to an empty string. It is passed to the `<path>` portion of this command which starts serve or funnel:<br>
`tailscale [serve|funnel] --bg --<protocol><protocol_port>`**`<path>`** `http://localhost:<serve_port><local_path>`<br>
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
:end
:docker_tailscale_serve_webui_help:
If <b>Serve</b> is enabled this will be an https url with a proper domain name that is accessible over your Tailnet, no port needed!
If <b>Funnel</b> is enabled the same url will be available on the Internet.
If they are disabled then the url will be generated from the container's main "Web UI" field, but modified to use the Tailscale IP. If the wrong port is specified here then switch from "Basic View" to "Advanced View" and review the "Web UI" field for this container.
:end
:docker_tailscale_advertise_routes_help:
If desired, specify any routes that should be passed to the **`--advertise-routes=`** parameter when running **`tailscale up`**.
For more details see the <a href="https://tailscale.com/kb/1019/subnets#connect-to-tailscale-as-a-subnet-router" target="_blank">Subnet routers</a> documentation.
:end
:docker_tailscale_daemon_extra_params_help:
Specify any extra parameters to pass when starting **`tailscaled`**.
For more details see the <a href="https://tailscale.com/kb/1278/tailscaled" target="_blank">tailscaled</a> documentation.
:end
:docker_tailscale_extra_param_help:
Specify any extra parameters to pass when running **`tailscale up`**.
For more details see the <a href="https://tailscale.com/kb/1080/cli#up" target="_blank">Tailscale CLI</a> documentation.
:end
:docker_tailscale_statedir_help:
If state directory detection fails on startup, you can specify a persistent directory in the container to override automatic detection.
:end
:docker_tailscale_troubleshooting_packages_help:
Enable this to install `ping`, `nslookup`, and `curl` into the container to help troubleshoot networking issues. Once the issues are resolved we recommend disabling this to reduce the size of the container.
:end
:docker_privileged_help:
For containers that require the use of host-device access directly or need full exposure to host capabilities, this option will need to be selected.
For more information, see this link: <a href="https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities" target="_blank">https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities</a>

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

View File

@@ -141,11 +141,24 @@ if (isset($_POST['contName'])) {
@unlink("$userTmplDir/my-$existing.xml");
}
}
// Extract real Entrypoint and Cmd from container for Tailscale
if (isset($_POST['contTailscale']) && $_POST['contTailscale'] == 'on') {
// Create preliminary base container but don't run it
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker create --name '" . escapeshellarg($Name) . "' '" . escapeshellarg($Repository) . "'");
// Get Entrypoint and Cmd from docker inspect
$containerInfo = $DockerClient->getContainerDetails($Name);
$ts_env = isset($containerInfo['Config']['Entrypoint']) ? '-e ORG_ENTRYPOINT="' . implode(' ', $containerInfo['Config']['Entrypoint']) . '" ' : '';
$ts_env .= isset($containerInfo['Config']['Cmd']) ? '-e ORG_CMD="' . implode(' ', $containerInfo['Config']['Cmd']) . '" ' : '';
// Insert Entrypoint and Cmd to docker command
$cmd = str_replace('-l net.unraid.docker.managed=dockerman', $ts_env . '-l net.unraid.docker.managed=dockerman' , $cmd);
// Remove preliminary container
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker rm '" . escapeshellarg($Name) . "'");
}
if ($startContainer) $cmd = str_replace('/docker create ', '/docker run -d ', $cmd);
execCommand($cmd);
if ($startContainer) addRoute($Name); // add route for remote WireGuard access
echo '<div style="text-align:center"><button type="button" onclick="done()">'._('Done').'</button></div><br>';
echo '<div style="text-align:center"><button type="button" onclick="openTerminal(\'docker\',\''.addslashes($Name).'\',\'.log\')">'._('View Container Log').'</button> <button type="button" onclick="done()">'._('Done').'</button></div><br>';
goto END;
}
@@ -169,6 +182,9 @@ if (isset($_GET['updateContainer'])){
$xml = file_get_contents($tmpl);
[$cmd, $Name, $Repository] = xmlToCommand($tmpl);
$Registry = getXmlVal($xml, "Registry");
$ExtraParams = getXmlVal($xml, "ExtraParams");
$Network = getXmlVal($xml, "Network");
$TS_Enabled = getXmlVal($xml, "TailscaleEnabled");
$oldImageID = $DockerClient->getImageID($Repository);
// pull image
if ($echo && !pullImage($Name, $Repository)) continue;
@@ -182,8 +198,39 @@ if (isset($_GET['updateContainer'])){
// attempt graceful stop of container first
stopContainer($Name, false, $echo);
}
// check if network from another container is specified in xml (Network & ExtraParams)
if (preg_match('/^container:(.*)/', $Network)) {
$Net_Container = str_replace("container:", "", $Network);
} else {
preg_match("/--(net|network)=container:[^\s]+/", $ExtraParams, $NetworkParam);
if (!empty($NetworkParam[0])) {
$Net_Container = explode(':', $NetworkParam[0])[1];
$Net_Container = str_replace(['"', "'"], '', $Net_Container);
}
}
// check if the container still exists from which the network should be used, if it doesn't exist any more recreate container with network none and don't start it
if (!empty($Net_Container)) {
$Net_Container_ID = $DockerClient->getContainerID($Net_Container);
if (empty($Net_Container_ID)) {
$cmd = str_replace('/docker run -d ', '/docker create ', $cmd);
$cmd = preg_replace("/--(net|network)=(['\"]?)container:[^'\"]+\\2/", "--network=none ", $cmd);
}
}
// force kill container if still running after time-out
if (empty($_GET['communityApplications'])) removeContainer($Name, $echo);
// Extract real Entrypoint and Cmd from container for Tailscale
if ($TS_Enabled == 'true') {
// Create preliminary base container but don't run it
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker create --name '" . escapeshellarg($Name) . "' '" . escapeshellarg($Repository) . "'");
// Get Entrypoint and Cmd from docker inspect
$containerInfo = $DockerClient->getContainerDetails($Name);
$ts_env = isset($containerInfo['Config']['Entrypoint']) ? '-e ORG_ENTRYPOINT="' . implode(' ', $containerInfo['Config']['Entrypoint']) . '" ' : '';
$ts_env .= isset($containerInfo['Config']['Cmd']) ? '-e ORG_CMD="' . implode(' ', $containerInfo['Config']['Cmd']) . '" ' : '';
// Insert Entrypoint and Cmd to docker command
$cmd = str_replace('-l net.unraid.docker.managed=dockerman', $ts_env . '-l net.unraid.docker.managed=dockerman' , $cmd);
// Remove preliminary container
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker rm '" . escapeshellarg($Name) . "'");
}
execCommand($cmd, $echo);
if ($startContainer) addRoute($Name); // add route for remote WireGuard access
$DockerClient->flushCaches();
@@ -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);

View File

@@ -292,6 +292,16 @@ class DockerTemplates {
return $WebUI;
}
private function getTailscaleJson($name) {
$TS_raw = [];
exec("docker exec -i ".$name." /bin/sh -c \"tailscale status --peers=false --json\" 2>/dev/null", $TS_raw);
if (!empty($TS_raw)) {
$TS_raw = implode("\n", $TS_raw);
return json_decode($TS_raw, true);
}
return '';
}
public function getAllInfo($reload=false,$com=true,$communityApplications=false) {
global $driver, $dockerManPaths, $host;
$DockerClient = new DockerClient();
@@ -299,6 +309,7 @@ class DockerTemplates {
//$DockerUpdate->verbose = $this->verbose;
$info = DockerUtil::loadJSON($dockerManPaths['webui-info']);
$autoStart = array_map('var_split', @file($dockerManPaths['autostart-file'],FILE_IGNORE_NEW_LINES) ?: []);
//$TS_dns = $this->getTailscaleDNS();
foreach ($DockerClient->getDockerContainers() as $ct) {
$name = $ct['Name'];
$image = $ct['Image'];
@@ -334,6 +345,39 @@ class DockerTemplates {
if (strpos($ct['NetworkMode'], 'container:') === 0)
$tmp['url'] = '';
}
// Check if webui & ct TSurl is set, if set construct WebUI URL on Docker page
$tmp['TSurl'] = '';
if (!empty($webui) && !empty($ct['TSUrl'])) {
$TS_no_peers = $this->getTailscaleJson($name);
if (!empty($TS_no_peers) && (!empty($TS_no_peers['CurrentTailnet']['MagicDNSEnabled']) || $TS_no_peers['CurrentTailnet']['MagicDNSEnabled'])) {
$TS_container = $TS_no_peers['Self'];
$TS_DNSName = _var($TS_container,'DNSName','');
$TS_HostNameActual = substr($TS_DNSName, 0, strpos($TS_DNSName, '.'));
// Check if serve or funnel are enabled by checking for [hostname] and replace string with TS_DNSName
if (strpos($ct['TSUrl'], '[hostname]') !== false && isset($TS_DNSName)) {
$tmp['TSurl'] = str_replace("[hostname][magicdns]", rtrim($TS_DNSName, '.'), $ct['TSUrl']);
// Check if serve is disabled, construct url with port, path and query if present and replace [noserve] with url
} elseif (strpos($ct['TSUrl'], '[noserve]') !== false && isset($TS_container['TailscaleIPs'])) {
$ipv4 = '';
foreach ($TS_container['TailscaleIPs'] as $ip) {
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$ipv4 = $ip;
break;
}
}
if (!empty($ipv4)) {
$webui_url = isset($webui) ? parse_url($webui) : '';
$webui_port = (preg_match('/\[PORT:(\d+)\]/', $webui, $matches)) ? ':' . $matches[1] : '';
$webui_path = $webui_url['path'] ?? '';
$webui_query = isset($webui_url['query']) ? '?' . $webui_url['query'] : '';
$tmp['TSurl'] = 'http://' . $ipv4 . $webui_port . $webui_path . $webui_query;
}
// Check if TailscaleWebUI in the xml is custom and display instead
} elseif (strpos($ct['TSUrl'], '[hostname]') === false && strpos($ct['TSUrl'], '[noserve]') === false) {
$tmp['TSurl'] = $ct['TSUrl'];
}
}
}
if ( ($tmp['shell'] ?? false) == false )
$tmp['shell'] = $this->getTemplateValue($image, 'Shell');
}
@@ -929,13 +973,18 @@ 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['Manager'] = $info['Config']['Labels']['net.unraid.docker.managed'] ?? false;
$c['TSUrl'] = $info['Config']['Labels']['net.unraid.docker.tailscale.webui'] ?? false;
$c['TSHostname'] = $info['Config']['Labels']['net.unraid.docker.tailscale.hostname'] ?? false;
$c['Shell'] = $info['Config']['Labels']['net.unraid.docker.shell'] ?? false;
$c['Manager'] = $info['Config']['Labels']['net.unraid.docker.managed'] ?? false;
$c['Ports'] = [];
$c['Networks'] = [];
if ($id) $c['NetworkMode'] = $net.str_replace('/',':',DockerUtil::ctMap($id)?:'/???');
@@ -945,16 +994,21 @@ class DockerClient {
} elseif ($driver[$c['NetworkMode']]=='host') {
$c['Ports']['host'] = ['host' => ''];
} elseif ($driver[$c['NetworkMode']]=='ipvlan' || $driver[$c['NetworkMode']]=='macvlan') {
$c['Ports']['vlan'] = ['vlan' => ''];
$i = $ct['NetworkSettings']['Networks'][$c['NetworkMode']]['IPAddress'];
$c['Ports']['vlan'] = ["$i" => $i];
} else {
$ports = &$info['Config']['ExposedPorts'];
}
} 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 ];
}
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;

View File

@@ -48,6 +48,55 @@ $null = '0.0.0.0';
$autostart = (array)@file($autostart_file,FILE_IGNORE_NEW_LINES);
$names = array_map('var_split',$autostart);
// Grab Tailscale json from container
function tailscale_stats($name) {
exec("docker exec -i ".$name." /bin/sh -c \"tailscale status --json | jq '{Self: .Self, ExitNodeStatus: .ExitNodeStatus, Version: .Version}'\" 2>/dev/null", $TS_stats);
if (!empty($TS_stats)) {
$TS_stats = implode("\n", $TS_stats);
return json_decode($TS_stats, true);
}
return '';
}
// Download Tailscal JSON and return Array, refresh file if older than 24 hours
function tailscale_json_dl($file, $url) {
$dl_status = 0;
if (!is_dir('/tmp/tailscale')) {
mkdir('/tmp/tailscale', 0777, true);
}
if (!file_exists($file)) {
exec("wget -T 3 -q -O " . $file . " " . $url, $output, $dl_status);
} else {
$fileage = time() - filemtime($file);
if ($fileage > 86400) {
unlink($file);
exec("wget -T 3 -q -O " . $file . " " . $url, $output, $dl_status);
}
}
if ($dl_status === 0) {
return json_decode(@file_get_contents($file), true);
} elseif ($dl_status === 0 && is_file($file)) {
return json_decode(@file_get_contents($file), true);
} else {
unlink($file);
return '';
}
}
// Grab Tailscale DERP map JSON
$TS_derp_url = 'https://login.tailscale.com/derpmap/default';
$TS_derp_file = '/tmp/tailscale/tailscale-derpmap.json';
$TS_derp_list = tailscale_json_dl($TS_derp_file, $TS_derp_url);
// Grab Tailscale version JSON
$TS_version_url = 'https://pkgs.tailscale.com/stable/?mode=json';
$TS_version_file = '/tmp/tailscale/tailscale-latest-version.json';
// Extract tarbal version string
$TS_latest_version = tailscale_json_dl($TS_version_file, $TS_version_url);
if (!empty($TS_latest_version)) {
$TS_latest_version = $TS_latest_version["TarballsVersion"];
}
function my_lang_time($text) {
[$number, $text] = my_explode(' ',$text,2);
return sprintf(_("%s $text"),$number);
@@ -69,49 +118,50 @@ 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);
$networks = [];
$network_ips = [];
foreach($ct['Networks'] as $netName => $netVals) {
$networks[] = $netName;
$network_ips[] = $netVals['IPAddress'];
}
$ports_internal = [];
$ports_external = [];
foreach ($ct['Ports'] as $port) {
if (strpos($ct['NetworkMode'], 'container:') === 0)
break;
if (_var($port,'PublicPort') && _var($port,'Driver') == 'bridge')
$ports_external[] = sprintf('%s:%s', $host, strtoupper(_var($port,'PublicPort')));
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[] = sprintf('%s', 'all');
break;
}
if (isset($ct['Ports']['vlan'])) {
$ports_external[] = sprintf('%s', $netVals['IPAddress']);
$ports_internal[] = sprintf('%s', 'all');
break;
}
if ((!isset($ct['Networks']['host'])) || (!isset($ct['Networks']['vlan']))) {
$ports_internal[] = sprintf('%s:%s', _var($port,'PrivatePort'), strtoupper(_var($port,'Type')));
$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 = [];
@@ -121,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').": ";
@@ -139,27 +189,135 @@ 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&#10060;\nPlease check the logs!";
} else {
$TS_version = explode('-', $TSstats["Version"])[0];
if (!empty($TS_version)) {
if (!empty($TS_latest_version)) {
if ($TS_version !== $TS_latest_version) {
$TSinfo .= "Version:\t\t" . $TS_version . " &#10132; " . $TS_latest_version . " available!\n";
} else {
$TSinfo .= "Version:\t\t" . $TS_version . "\n";
}
} else {
$TSinfo .= "Version:\t\t" . $TS_version . "\n";
}
}
$TSinfo .= "Online:\t\t&#9989;\n";
$TS_DNSName = $TSstats["Self"]["DNSName"];
$TS_HostNameActual = substr($TS_DNSName, 0, strpos($TS_DNSName, '.'));
if (strcasecmp($TS_HostNameActual, $TShostname) !== 0 && !empty($TS_DNSName)) {
$TSinfo .= "Hostname:\tReal Hostname &#10132; " . $TS_HostNameActual . "\n";
} else {
$TSinfo .= "Hostname:\t" . $TShostname . "\n";
}
// Map region relay code to cleartext region if TS_derp_list is available
if (!empty($TS_derp_list)) {
foreach ($TS_derp_list['Regions'] as $region) {
if ($region['RegionCode'] === $TSstats["Self"]["Relay"]) {
$TSregion = $region['RegionName'];
break;
}
}
if (!empty($TSregion)) {
$TSinfo .= "Main Relay:\t" . $TSregion . "\n";
} else {
$TSinfo .= "Main Relay:\t" . $TSstats["Self"]["Relay"] . "\n";
}
} else {
$TSinfo .= "Main Relay:\t" . $TSstats["Self"]["Relay"] . "\n";
}
if (!empty($TSstats["Self"]["TailscaleIPs"])) {
$TSinfo .= "Addresses:\t" . implode("\n\t\t\t", $TSstats["Self"]["TailscaleIPs"]) . "\n";
}
if (!empty($TSstats["Self"]["PrimaryRoutes"])) {
$TSinfo .= "Routes:\t\t" . implode("\n\t\t\t", $TSstats["Self"]["PrimaryRoutes"]) . "\n";
}
if ($TSstats["Self"]["ExitNodeOption"]) {
$TSinfo .= "Is Exit Node:\t&#9989;\n";
} else {
if (!empty($TSstats["ExitNodeStatus"])) {
$TS_exit_node_status = ($TSstats["ExitNodeStatus"]["Online"]) ? "&#9989;" : "&#10060;";
$TSinfo .= "Exit Node:\t" . strstr($TSstats["ExitNodeStatus"]["TailscaleIPs"][0], '/', true) . " | Status: " . $TS_exit_node_status ."\n";
} else {
$TSinfo .= "Is Exit Node:\t&#10060;\n";
}
}
if (!empty($TSwebGui)) {
$TSinfo .= "URL:\t\t" . $TSwebGui . "\n";
}
if (!empty($TSstats["Self"]["KeyExpiry"])) {
$TS_expiry = new DateTime($TSstats["Self"]["KeyExpiry"]);
$current_Date = new DateTime();
$TS_expiry_formatted = $TS_expiry->format('Y-m-d');
$TS_expiry_diff = $current_Date->diff($TS_expiry);
if ($TS_expiry_diff->invert) {
$TSinfo .= "Key Expiry:\t&#10060; Expired! Renew/Disable key expiry!\n";
} else {
$TSinfo .= "Key Expiry:\t" . $TS_expiry_formatted . " (" . $TS_expiry_diff->days . " days)\n";
}
}
}
// Display message to refresh page if Tailscale in the container wasn't maybe ready to get the data
} else {
echo "<div title='Error gathering Tailscale information from container.\nPlease check the logs and refresh the page.'><img src='/plugins/dynamix.docker.manager/images/tailscale.png' style='height: 16px;'> Tailscale</div></td>";
}
// Display TSinfo if data was fetched correctly
echo "<div title='" . $TSinfo . "'><img src='/plugins/dynamix.docker.manager/images/tailscale.png' style='height: 16px;'> Tailscale</div>";
// Display message that container isn't running
} else {
echo "<div title='Container not runnig'><img src='/plugins/dynamix.docker.manager/images/tailscale.png' style='height: 16px;'> Tailscale</div></td>";
}
}
echo "<div class='advanced'><i class='fa fa-info-circle fa-fw'></i> ".compress(_($version),12,0)."</div></td>";
echo "<td style='white-space:nowrap'><span class='docker_readmore'> ".implode('<br>',$networks)."</span></td>";
@@ -169,7 +327,15 @@ foreach ($containers as $ct) {
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>";
}
@@ -184,5 +350,4 @@ foreach ($images as $image) {
echo "</td><td>"._('Created')." ".htmlspecialchars(_($image['Created'],0))."</td></tr>";
}
echo "\0".implode($docker)."\0".(pgrep('rc.docker')!==false ? 1:0);
?>
?>

View File

@@ -32,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,7 +654,7 @@ function setXmlVal(&$xml, $value, $el, $attr=null, $pos=0) {
function getAllocations() {
global $DockerClient, $host;
$ports = [];
foreach ($DockerClient->getDockerContainers() as $ct) {
$list = $port = [];

View File

@@ -1,10 +1,11 @@
var eventURL = '/plugins/dynamix.docker.manager/include/Events.php';
function addDockerContainerContext(container, image, template, started, paused, update, autostart, webui, shell, id, Support, Project, Registry, donateLink, ReadMe) {
function addDockerContainerContext(container, image, template, started, paused, update, autostart, webui, tswebui, shell, id, Support, Project, Registry, donateLink, ReadMe) {
var opts = [];
context.settings({right:false,above:false});
if (started && !paused) {
if (webui !== '' && webui != '#') opts.push({text:_('WebUI'), icon:'fa-globe', href:webui, target:'_blank'});
if (tswebui !== '' && tswebui != '#') opts.push({text:_('Tailscale WebUI'), icon:'fa-globe', href:tswebui, target:'_blank'});
opts.push({text:_('Console'), icon:'fa-terminal', action:function(e){e.preventDefault(); openTerminal('docker',container,shell);}});
opts.push({divider:true});
}

View File

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

View File

@@ -170,6 +170,7 @@ foreach (explode('*',rawurldecode($argv[1])) as $value) {
$xml = file_get_contents($tmpl);
[$cmd, $Name, $Repository] = xmlToCommand($tmpl);
$Registry = getXmlVal($xml, "Registry");
$TS_Enabled = getXmlVal($xml, "TailscaleEnabled");
$oldImageID = $DockerClient->getImageID($Repository);
// pull image
if (!pullImage_nchan($Name, $Repository)) continue;
@@ -182,14 +183,25 @@ foreach (explode('*',rawurldecode($argv[1])) as $value) {
// attempt graceful stop of container first
stopContainer_nchan($Name);
}
if ( ($argv[2]??null) == "ca_docker_run_override" )
if ( ($argv[2]??null) == "ca_docker_run_override" )
$startContainer = true;
if ( $startContainer )
$cmd = str_replace('/docker create ', '/docker run -d ', $cmd);
// force kill container if still running after 10 seconds
if (empty($_GET['communityApplications'])) removeContainer_nchan($Name);
// Extract real Entrypoint and Cmd from container for Tailscale
if ($TS_Enabled == 'true') {
// Create preliminary base container but don't run it
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker create --name '" . escapeshellarg($Name) . "' '" . escapeshellarg($Repository) . "'");
// Get Entrypoint and Cmd from docker inspect
$containerInfo = $DockerClient->getContainerDetails($Name);
$ts_env = isset($containerInfo['Config']['Entrypoint']) ? '-e ORG_ENTRYPOINT="' . implode(' ', $containerInfo['Config']['Entrypoint']) . '" ' : '';
$ts_env .= isset($containerInfo['Config']['Cmd']) ? '-e ORG_CMD="' . implode(' ', $containerInfo['Config']['Cmd']) . '" ' : '';
// Insert Entrypoint and Cmd to docker command
$cmd = str_replace('-l net.unraid.docker.managed=dockerman', $ts_env . '-l net.unraid.docker.managed=dockerman' , $cmd);
// Remove preliminary container
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker rm '" . escapeshellarg($Name) . "'");
}
execCommand_nchan($cmd);
if ($startContainer) addRoute($Name); // add route for remote WireGuard access
$DockerClient->flushCaches();

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

@@ -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>

View File

@@ -30,30 +30,68 @@ if (!$hardware) {
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;?>

View File

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

View File

@@ -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 ;
}
@@ -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."' />" ;
}
}
@@ -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,8 +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)) my_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;

View File

@@ -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']);
@@ -2823,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;
}
?>

View File

@@ -80,8 +80,8 @@ while (true) {
$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'],$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 .= _("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 ;
}

View File

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

View File

@@ -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>
@@ -1880,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;
@@ -1991,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 ;

View File

@@ -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">
&nbsp;
: <input type="button" value="_(Install)_" onclick="installPlugin(this.form.file.value)">

View File

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

View File

@@ -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,113 +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 (($('#diskFsType').val()||'').indexOf('zfs') == -1) {
var label = (num_slots == 1) ? "device" : "devices";
$('#diskFsWidthZFS').append($('<option>', {
value: num_slots,
text: _(sprintf('%s '+label,num_slots))
}));
} 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').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').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);
}
$('#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');
}
$('#compression').show(t);
<?if (diskType('Cache')):?>
$('#autotrim').show(t);
} else if (($('#diskFsType').val()||'').indexOf('xfs') != -1) {
$('#autotrim').show(t);
<?endif;?>
}
selectDiskFsWidth();
$('#diskFsGroups').val(0);
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 = [];
@@ -539,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)?>",
@@ -560,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();
});
@@ -631,90 +731,35 @@ _(Spin down delay)_:
</select><span id="smart_selftest" class='orange-text'></span>
<?endif;?>
<?if (diskType('Data') || isPool($tag)):?>
<?$fsTypeImmutable = _var($var,'fsState')=="Started" || (isPool($tag) && _var($disk,'state')!="NEW_ARRAY")?>
<?if (diskType('Data') || (!isSubpool($name) && _var($disk,'slots',0)==1)):?>
<?if (diskType('Data') || (isPool($tag) && !isSubpool($tag))):?>
_(File system status)_:
: <?=_(_var($disk,'fsStatus'))?>&nbsp;
_(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>&nbsp;_(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'))?>&nbsp;
_(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)?>>
<input id="diskFsGroups" name="diskFsGroups.<?=_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'))?>
@@ -723,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'))?>
@@ -733,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'))?>
@@ -749,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')?>">
@@ -759,22 +805,21 @@ _(Critical disk utilization threshold)_ (%):
:info_critical_utilization_help:
<?endif;?>
<?endif;?>
&nbsp;
: <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 Pool)_" onclick="eraseDisk('<?=$name?>')"<?=$erasable?'':' disabled'?>>
<?endif;?>
<?if (_var($var,'fsState')=="Stopped" && isPool($name)):?>
<input type="button" value="_(Delete Pool)_" onclick="deletePool()"<?=isSubpool($name)?' disabled':''?>>
<?if (_var($var,'fsState')=="Stopped"): $removeable=true; endif;?>
<input type="button" id="removeButton" value="_(Remove Pool)_" onclick="removePool('<?=$name?>')"<?=$removeable?'':' disabled'?>>
<?endif;?>
</form>
@@ -1372,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):?>

View File

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

View File

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

View File

@@ -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>&nbsp;</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>&nbsp;</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)_:
&nbsp;
: <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;?>

View File

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

View File

@@ -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)_:
&nbsp;
: <input type="submit" name="cmdEditShare" value="_(Add Share)_" onclick="this.value='Add Share'"><input type="button" value="_(Done)_" onclick="done()">
<?else:?>
<div markdown="1" class="empty">
_(Delete)_<input type="checkbox" name="confirmDelete" onchange="chkDelete(this.form, document.getElementById('cmdEditShare'));">
<div markdown="1">
<label id="deleteLabel" title="">_(Delete)_</label><input type="checkbox" name="confirmDelete" onchange="chkDelete(this.form, document.getElementById('cmdEditShare'));" title="" disabled>
: <input type="submit" id="cmdEditShare" name="cmdEditShare" value="_(Apply)_" onclick="if (this.value=='_(Delete)_') this.value='Delete'; else this.value='Apply'; return handleDeleteClick(this)" disabled><input type="button" value="_(Done)_" onclick="done()">
</div>
<div markdown="1" class="full">
&nbsp;
: <input type="submit" name="cmdEditShare" value="_(Apply)_" onclick="this.value='Apply'" disabled><input type="button" value="_(Done)_" onclick="done()">
</div>
<?endif;?>
</form>
@@ -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;?>

View File

@@ -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";

View File

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

View File

@@ -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>

View File

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

View File

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

View File

@@ -48,12 +48,13 @@ if ($_POST['docker']) {
$template = $info['template'];
$shell = $info['shell'];
$webGui = html_entity_decode($info['url']);
$TSwebGui = html_entity_decode($info['TSurl']);
$support = html_entity_decode($info['Support']);
$project = html_entity_decode($info['Project']);
$registry = html_entity_decode($info['registry']);
$donateLink = html_entity_decode($info['DonateLink']);
$readme = html_entity_decode($info['ReadMe']);
$menu = sprintf("onclick=\"addDockerContainerContext('%s','%s','%s',%s,%s,%s,%s,'%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($name), addslashes($ct['ImageId']), addslashes($template), $running, $paused, $updateStatus, $is_autostart, addslashes($webGui), $shell, $id, addslashes($support), addslashes($project), addslashes($registry), addslashes($donateLink), addslashes($readme));
$menu = sprintf("onclick=\"addDockerContainerContext('%s','%s','%s',%s,%s,%s,%s,'%s','%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($name), addslashes($ct['ImageId']), addslashes($template), $running, $paused, $updateStatus, $is_autostart, addslashes($webGui), addslashes($TSwebGui), $shell, $id, addslashes($support), addslashes($project), addslashes($registry), addslashes($donateLink), addslashes($readme));
$shape = $running ? ($paused ? 'pause' : 'play') : 'square';
$status = $running ? ($paused ? 'paused' : 'started') : 'stopped';
$color = $status=='started' ? 'green-text' : ($status=='paused' ? 'orange-text' : 'red-text');

View File

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

View File

@@ -263,7 +263,7 @@ function urlencode_path($path) {
return str_replace("%2F", "/", urlencode($path));
}
function pgrep($process_name, $escape_arg=true) {
$pid = exec("pgrep ".($escape_arg?escapeshellarg($process_name):$process_name), $output, $retval);
$pid = exec('pgrep --ns $$ '.($escape_arg?escapeshellarg($process_name):$process_name), $output, $retval);
return $retval==0 ? $pid : false;
}
function is_block($path) {
@@ -345,26 +345,46 @@ function my_mkdir($dirname,$permissions = 0777,$recursive = false,$own = "nobody
return($rtncode);
}
function my_rmdir($dirname) {
if (!is_dir($dirname)) return(false);
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);
$dirname = str_replace('/mnt/user/', "/mnt/$realdisk/", "$dirname");
}
}
$fstype = trim(shell_exec(" stat -f -c '%T' $dirname"));
$fstype = trim(shell_exec(" stat -f -c '%T' ".escapeshellarg($dirname)));
$rtncode = false;
switch ($fstype) {
case "zfs":
$zfsdataset = trim(shell_exec("zfs list -H -o name $dirname")) ;
$rtncode=exec("zfs destroy \"$zfsdataset\"");
$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($rtncode);
return($return);
}
function get_realvolume($path) {
if (strpos($path,"/mnt/user/",0) === 0)

View File

@@ -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

View 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";
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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);
@@ -362,8 +363,8 @@ function array_slots() {
}
function cache_slots($off,$pool,$min,$slots) {
global $var, $disks;
$off = $off && $min ? ' disabled' : '';
$off = '';
// $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'>";
@@ -371,10 +372,10 @@ $off = '';
$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>";
}
// 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++) {
$selected = ($n==$slots) ? ' selected' : '';
$echo[] = "<option value='$n'{$selected}>$n</option>";

View File

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

View File

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

View File

@@ -78,8 +78,8 @@ while (true) {
$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'] .="&nbsp&nbsp</span>";
else $echo[$vmencode ]['mem'] .= " / ".my_scale($vmdata['maxmem']*1024,$unit)."$unit&nbsp&nbsp</span>";
$echo[$vmencode ]['disk'] = "<span>Disk: "._("Rd").": ".my_scale($vmdata['rdrate'],$unit)."$unit/s "._("Wr").": ".my_scale($vmdata['wrrate'],$unit)."$unit/s&nbsp&nbsp</span>";
$echo[$vmencode ]['net'] = "<span>Net: "._("RX").": ".my_scale($vmdata['rxrate'],$unit)."$unit/s "._("TX").": ".my_scale($vmdata['txrate'],$unit)."$unit/s&nbsp&nbsp</span>";
$echo[$vmencode ]['disk'] = "<span>Disk: "._("Rd").": ".my_scale($vmdata['rdrate']/$timer,$unit)."$unit/s "._("Wr").": ".my_scale($vmdata['wrrate']/$timer,$unit)."$unit/s&nbsp&nbsp</span>";
$echo[$vmencode ]['net'] = "<span>Net: "._("RX").": ".my_scale($vmdata['rxrate']/$timer,$unit)."$unit/s "._("TX").": ".my_scale($vmdata['txrate']/$timer,$unit)."$unit/s&nbsp&nbsp</span>";
}
}

View File

@@ -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";
@@ -434,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"));
@@ -717,6 +722,7 @@ newline("/$diag/system/sshd.txt");
copy("/etc/nginx/conf.d/servers.conf", "/$diag/system/servers.conf.txt");
maskIP("/$diag/system/servers.conf.txt");
run("sed -Ei 's/[01234567890abcdef]+\.((my)?unraid\.net)/hash.\\1/gm;t' ".escapeshellarg("/$diag/system/servers.conf.txt")." 2>/dev/null");
run("sed -Ei 's/\.[^\.]*\.ts\.net/\.magicdns\.ts\.net/gm' ".escapeshellarg("/$diag/system/servers.conf.txt")." 2>/dev/null");
newline("/$diag/system/servers.conf.txt");
// BEGIN - third party plugins diagnostics

View File

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

View File

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

View File

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

View File

@@ -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) ?: []) : [];

View File

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

View File

@@ -76,12 +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..."
if /bin/mountpoint -q /boot; then
/bin/umount -v /boot
fi
/bin/echo
/sbin/reboot
/sbin/reboot -fd
}
find_device() {
@@ -106,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
@@ -164,7 +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 boot - you must remove 'root=$UNRAIDROOT' from syslinux.cfg"
[[ ! -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

View File

@@ -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."

View File

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

View File

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

75
etc/rc.d/rc.cgroup2unraid Executable file
View 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

View File

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

View File

@@ -127,7 +127,7 @@ all_containers(){
# Running containers
running_containers(){
docker ps --format='{{.Names}}' 2>/dev/null
docker ps --format='{{.Names}} {{.Labels}}' 2>/dev/null | grep 'net.unraid.docker.managed=' | awk '{print $1}'
}
# Network driver
@@ -264,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
@@ -527,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(){
@@ -564,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
@@ -579,8 +583,6 @@ docker_service_stop(){
# signal successful stop
TIMER=-1
else
# show waiting message
echo "$DAEMON... Waiting to die."
((TIMER--))
fi
done

View File

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

View File

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

View File

@@ -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

View File

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

View File

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

View File

@@ -104,11 +104,11 @@ nfsd_stop(){
if ! nfsd_running; then
REPLY="Already stopped"
else
killall rpc.mountd 2>/dev/null
killall nfsd 2>/dev/null
killall --ns $$ rpc.mountd 2>/dev/null
killall --ns $$ nfsd 2>/dev/null
sleep 1
killall -9 nfsd 2>/dev/null
killall rpc.rquotad 2>/dev/null
killall --ns $$ -9 nfsd 2>/dev/null
killall --ns $$ rpc.rquotad 2>/dev/null
run $EXPORTFS -au
if ! nfsd_running; then REPLY="Stopped"; else REPLY="Failed"; fi
fi
@@ -123,8 +123,8 @@ nfsd_restart(){
}
nfsd_reload(){
# restart without info
nfsd_restart &>/dev/null
# reload without info
$EXPORTFS -r &>/dev/null
}
nfsd_update(){

View File

@@ -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

View File

@@ -35,7 +35,7 @@ ntpd_build(){
[[ $IPV6 == no ]] && echo "interface ignore ipv6" >>$CONF
# add listen interfaces
for NET in $BIND; do
echo "interface listen $NET # $(show $NET)" >>$CONF
echo "interface listen $(show $NET) # $NET" >>$CONF
done
fi
# add configured NTP servers
@@ -76,7 +76,7 @@ ntpd_stop(){
kill -HUP $(cat /var/run/ntpd.pid)
rm -f /var/run/ntpd.pid
else
killall -HUP -q ntpd
killall --ns $$ -HUP -q ntpd
fi
if ! ntpd_running; then REPLY="Stopped"; else REPLY="Failed"; fi
fi
@@ -93,7 +93,7 @@ ntpd_restart(){
}
ntpd_reload(){
killall -HUP -q ntpd
killall --ns $$ -HUP -q ntpd
. <(fromdos <$IDENT)
ntpd_build
$NTPD $OPTIONS 2>/dev/null

View File

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

View File

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

View File

@@ -34,7 +34,14 @@ PRIVATE="/var/lib/samba/private"
samba_running(){
sleep 0.1
[[ $(pgrep -cf $SMBD) -gt 0 ]]
[[ $(pgrep --ns $$ -cf $SMBD) -gt 0 ]]
}
samba_waitfor_shutdown(){
for i in {1..5}; do
if ! samba_running; then break; fi
sleep 1
done
}
samba_settings(){
@@ -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(){

View File

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

View File

@@ -23,7 +23,8 @@ SSH_ETC="/etc/ssh"
sshd_running(){
sleep 0.1
[[ $(pgrep -cf $SSHD) -gt 0 ]]
# get all pids from sshd
[[ $(pgrep --ns $$ -cf $SSHD) -gt 0 ]]
}
sshd_build(){
@@ -67,7 +68,7 @@ sshd_stop(){
REPLY="Already stopped"
else
log "Stopping $DAEMON..."
killall sshd
killall --ns $$ sshd
if ! sshd_running; then REPLY="Stopped"; else REPLY="Failed"; fi
fi
log "$DAEMON... $REPLY."

View File

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

View File

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

View File

@@ -23,7 +23,6 @@
# operation continues on to the next file.
PIDFILE="/var/run/mover.pid"
CFGFILE="/boot/config/share.cfg"
DEBUGGING=""
move() {
@@ -38,14 +37,6 @@ start() {
fi
fi
if [ -f $CFGFILE ]; then
# Only start if shfs includes pools
if ! grep -qs 'shareCacheEnabled="yes"' $CFGFILE ; then
echo "mover: cache not enabled"
exit 2
fi
fi
echo $$ >/var/run/mover.pid
echo "mover: started"
@@ -126,7 +117,7 @@ empty() {
killtree() {
local pid=$1 child
for child in $(pgrep -P $pid); do
for child in $(pgrep --ns $$ -P $pid); do
killtree $child
done
[ $pid -ne $$ ] && kill -TERM $pid

View File

@@ -0,0 +1,313 @@
#!/bin/sh
# Copyright 2024, Lime Technology
# Copyright 2024, Christoph Hummer
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 2,
# as published by the Free Software Foundation.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
exec_entrypoint() {
echo "Starting container..."
echo
echo "======================="
echo
eval "exec ${ORG_ENTRYPOINT} ${ORG_CMD} ${ORG_POSTARGS}"
}
error_handler() {
echo "ERROR: Unraid Docker Hook script throw an error!"
echo " Starting container without Tailscale!"
echo
exec_entrypoint
}
echo "======================="
echo
echo "Executing Unraid Docker Hook for Tailscale"
echo
if [ ! -f /usr/bin/tailscale ] || [ ! -f /usr/bin/tailscaled ]; then
if [ ! -z "${TAILSCALE_EXIT_NODE_IP}" ]; then
if [ ! -c /dev/net/tun ]; then
echo "ERROR: Device /dev/net/tun not found!"
echo " Make sure to pass through /dev/net/tun to the container."
error_handler
fi
INSTALL_IPTABLES="iptables "
fi
echo "Detecting Package Manager..."
if which apt-get >/dev/null 2>&1; then
echo "Detected Advanced Package Tool!"
PACKAGES_UPDATE="apt-get update"
PACKAGES_INSTALL="apt-get -y install --no-install-recommends"
elif which apk >/dev/null 2>&1; then
echo "Detected Alpine Package Keeper!"
PACKAGES_UPDATE="apk update"
PACKAGES_INSTALL="apk add"
elif which pacman >/dev/null 2>&1; then
echo "Detected pacman Package Manager!"
PACKAGES_INSTALL="pacman -Syu --noconfirm"
else
echo "ERROR: Detection from Package Manager failed!"
error_handler
fi
if [ "${TAILSCALE_TROUBLESHOOTING}" = "true" ]; then
if which apt-get >/dev/null 2>&1; then
PACKAGES_TROUBLESHOOTING="curl dnsutils iputils-ping "
elif which apk >/dev/null 2>&1; then
PACKAGES_TROUBLESHOOTING="curl bind-tools iputils-ping "
elif which pacman >/dev/null 2>&1; then
PACKAGES_TROUBLESHOOTING="curl dnsutils iputils "
fi
echo "Tailscale Troubleshooting enabled!"
echo "Installing additional packages: $(echo "${PACKAGES_TROUBLESHOOTING}" | sed 's/[[:blank:]]*$//' | sed 's/ /, /g')"
fi
echo "Installing packages..."
echo "Please wait..."
if [ ! -z "${PACKAGES_UPDATE}" ]; then
UPDATE_LOG=$(${PACKAGES_UPDATE} 2>&1)
fi
INSTALL_LOG=$(${PACKAGES_INSTALL} jq wget ${INSTALL_IPTABLES}${PACKAGES_TROUBLESHOOTING} 2>&1)
INSTALL_RESULT=$?
if [ "${INSTALL_RESULT}" -eq 0 ]; then
echo "Packages installed!"
unset INSTALL_LOG
else
echo "ERROR: Installing packages!"
echo "${UPDATE_LOG}"
echo "${INSTALL_LOG}"
error_handler
fi
if [ "${INSTALL_IPTABLES}" = "iptables " ]; then
if ! iptables -L >/dev/null 2>&1; then
echo "ERROR: Cap: NET_ADMIN not available!"
echo " Make sure to add --cap-add=NET_ADMIN to the Extra Parameters"
error_handler
fi
fi
echo "Tailscale not found, downloading..."
echo "Please wait..."
TAILSCALE_VERSION=$(wget -qO- 'https://pkgs.tailscale.com/stable/?mode=json' | jq -r '.TarballsVersion')
if [ -z "${TAILSCALE_VERSION}" ]; then
echo "ERROR: Can't get Tailscale JSON"
error_handler
fi
if [ ! -d /tmp/tailscale ]; then
mkdir -p /tmp/tailscale
fi
if wget -q -nc --show-progress --progress=bar:force:noscroll -O /tmp/tailscale/tailscale.tgz "https://pkgs.tailscale.com/stable/tailscale_${TAILSCALE_VERSION}_amd64.tgz" ; then
echo "Download from Tailscale version ${TAILSCALE_VERSION} successful!"
else
echo "ERROR: Download from Tailscale version ${TAILSCALE_VERSION} failed!"
rm -rf /tmp/tailscale
error_handler
fi
tar -C /tmp/tailscale -xf /tmp/tailscale/tailscale.tgz
cp /tmp/tailscale/tailscale_${TAILSCALE_VERSION}_amd64/tailscale /usr/bin/tailscale
cp /tmp/tailscale/tailscale_${TAILSCALE_VERSION}_amd64/tailscaled /usr/bin/tailscaled
rm -rf /tmp/tailscale
echo "Installation Done!"
else
echo "Tailscale found, continuing..."
fi
unset TSD_PARAMS
unset TS_PARAMS
if [ ! -z "${SERVER_DIR}" ]; then
TSD_STATE_DIR="${SERVER_DIR}/.tailscale_state"
echo "Settings Tailscale state dir to: ${TSD_STATE_DIR}"
elif [ ! -z "${DATA_DIR}" ]; then
TSD_STATE_DIR="${DATA_DIR}/.tailscale_state"
echo "Settings Tailscale state dir to: ${TSD_STATE_DIR}"
else
if [ -z "${TAILSCALE_STATE_DIR}" ]; then
TAILSCALE_STATE_DIR="/config/.tailscale_state"
fi
TSD_STATE_DIR="${TAILSCALE_STATE_DIR}"
echo "Settings Tailscale state dir to: ${TSD_STATE_DIR}"
fi
if [ ! -d "${TSD_STATE_DIR}" ]; then
mkdir -p ${TSD_STATE_DIR}
fi
if [ ! -z "${TAILSCALE_EXIT_NODE_IP}" ]; then
echo "Disabling userspace networking! Tailscale DNS available"
echo "Using ${TAILSCALE_EXIT_NODE_IP} as Exit Node! See https://tailscale.com/kb/1103/exit-nodes"
TS_PARAMS=" --exit-node=${TAILSCALE_EXIT_NODE_IP}"
if [ "${TAILSCALE_ALLOW_LAN_ACCESS}" = "true" ]; then
echo "Enabling local LAN Access to the container!"
TS_PARAMS="${TS_PARAMS} --exit-node-allow-lan-access"
fi
else
if [ -z "${TAILSCALE_USERSPACE_NETWORKING}" ] || [ "${TAILSCALE_USERSPACE_NETWORKING}" = "true" ]; then
echo "Enabling userspace networking! Tailscale DNS not available"
TSD_PARAMS="-tun=userspace-networking "
else
if [ ! -c /dev/net/tun ]; then
echo "ERROR: Device /dev/net/tun not found!"
echo " Make sure to pass through /dev/net/tun to the container and add the"
echo " parameter --cap-add=NET_ADMIN to the Extra Parameters!"
error_handler
fi
fi
fi
if [ ! -z "${TAILSCALE_ADVERTISE_ROUTES}" ]; then
TAILSCALE_ADVERTISE_ROUTES="$(echo ${TAILSCALE_ADVERTISE_ROUTES} | sed 's/ //g')"
echo "Advertising custom routes! See https://tailscale.com/kb/1019/subnets#advertise-subnet-routes"
TS_PARAMS="${TS_PARAMS} --advertise-routes=${TAILSCALE_ADVERTISE_ROUTES}"
fi
if [ "${TAILSCALE_USE_SSH}" = "true" ]; then
echo "Enabling SSH! See https://tailscale.com/kb/1193/tailscale-ssh"
TS_PARAMS="${TS_PARAMS} --ssh"
fi
if [ "${TAILSCALE_LOG}" != "false" ]; then
TSD_PARAMS="${TSD_PARAMS} >>/var/log/tailscaled 2>&1 "
TSD_MSG=" with log file location: /var/log/tailscaled"
else
TSD_PARAMS="${TSD_PARAMS} >/dev/null 2>&1 "
TSD_MSG=" with logging disabled"
fi
if [ ! -z "${TAILSCALE_HOSTNAME}" ]; then
echo "Setting host name to \"${TAILSCALE_HOSTNAME}\""
TAILSCALE_HOSTNAME="$(echo "$TAILSCALE_HOSTNAME" | tr -d ' ')"
TS_PARAMS="${TS_PARAMS} --hostname=${TAILSCALE_HOSTNAME}"
fi
if [ "${TAILSCALE_EXIT_NODE}" = "true" ]; then
echo "Configuring container as Exit Node! See https://tailscale.com/kb/1103/exit-nodes"
TS_PARAMS="${TS_PARAMS} --advertise-exit-node"
fi
if [ ! -z "${TAILSCALED_PARAMS}" ]; then
TSD_PARAMS="${TAILSCALED_PARAMS} ${TSD_PARAMS}"
fi
if [ ! -z "${TAILSCALE_PARAMS}" ]; then
TS_PARAMS="${TAILSCALE_PARAMS}${TS_PARAMS}"
fi
echo "Starting tailscaled${TSD_MSG}"
eval tailscaled -statedir=${TSD_STATE_DIR} ${TSD_PARAMS}&
echo "Starting tailscale"
eval tailscale up ${TS_PARAMS} --reset
EXIT_STATUS="$?"
if [ "${EXIT_STATUS}" != "0" ]; then
echo "ERROR: Connecting to Tailscale not successful!"
if [ -f /var/log/tailscaled ]; then
echo "Please check the logs:"
tail -20 /var/log/tailscaled
fi
error_handler
fi
unset EXIT_STATUS
if [ ! -z "${TAILSCALE_SERVE_PORT}" ] && [ "$(tailscale status --json | jq -r '.CurrentTailnet.MagicDNSEnabled')" = "false" ] ; then
echo "ERROR: Enable HTTPS on your Tailscale account to use Tailscale Serve/Funnel."
echo "See: https://tailscale.com/kb/1153/enabling-https"
error_handler
fi
if [ "${TAILSCALE_EXIT_NODE}" = "true" ]; then
if [ "$(tailscale status --json | jq -r '.Self.ExitNodeOption')" = "false" ]; then
TSIP=$(tailscale status --json | jq -r '.Self.TailscaleIPs[0]')
echo "WARNING: Exit Node not yet approved."
echo " Navigate to https://login.tailscale.com/admin/machines/${TSIP} and approve it."
fi
fi
KEY_EXPIRY=$(tailscale status --json | jq -r '.Self.KeyExpiry')
if [ "${KEY_EXPIRY}" != "null" ]; then
EXPIRY_EPOCH=$(date -d "${KEY_EXPIRY}" +"%s" 2>/dev/null)
CUR_EPOCH=$(date -u +%s)
DIFF_EPOCH=$((EXPIRY_EPOCH - CUR_EPOCH))
DIFF_DAYS=$((DIFF_EPOCH / 86400))
HOST=$(tailscale status --json | jq -r '.Self.HostName')
if [ -n "${DIFF_DAYS}" ] && echo "${DIFF_DAYS}" | grep -Eq '^[0-9]+$'; then
echo "WARNING: Tailscale Key will expire in ${DIFF_DAYS} days."
echo " Navigate to https://login.tailscale.com/admin/machines and 'Disable Key Expiry' for ${HOST}"
else
echo "ERROR: Tailscale Key expired!"
echo " Navigate to https://login.tailscale.com/admin/machines and Renew/Disable Key Expiry for ${HOST}"
fi
echo "See: https://tailscale.com/kb/1028/key-expiry"
fi
if [ ! -z "${TAILSCALE_ADVERTISE_ROUTES}" ]; then
APPROVED_ROUTES="$(tailscale status --json | jq -r '.Self.PrimaryRoutes')"
IFS=','
set -- ${TAILSCALE_ADVERTISE_ROUTES}
ROUTES="$@"
for route in ${ROUTES}; do
if ! echo "${APPROVED_ROUTES}" | grep -q "${route}"; then
NOT_APPROVED="$NOT_APPROVED ${route}"
fi
done
if [ ! -z "${NOT_APPROVED}" ]; then
TSIP="$(tailscale status --json | jq -r '.Self.TailscaleIPs[0]')"
echo "WARNING: The following route(s) are not approved:${NOT_APPROVED}"
echo " Navigate to https://login.tailscale.com/admin/machines/${TSIP} and approve it."
fi
fi
if [ ! -z "${TAILSCALE_SERVE_PORT}" ]; then
if [ ! -z "${TAILSCALE_SERVE_PATH}" ]; then
TAILSCALE_SERVE_PATH="=${TAILSCALE_SERVE_PATH}"
fi
if [ -z "${TAILSCALE_SERVE_PROTOCOL}" ]; then
TAILSCALE_SERVE_PROTOCOL="https"
fi
if [ -z "${TAILSCALE_SERVE_PROTOCOL_PORT}" ]; then
TAILSCALE_SERVE_PROTOCOL_PORT="=443"
fi
if [ "${TAILSCALE_FUNNEL}" = "true" ]; then
echo "Enabling Funnel! See https://tailscale.com/kb/1223/funnel"
eval tailscale funnel --bg --"${TAILSCALE_SERVE_PROTOCOL}"${TAILSCALE_SERVE_PROTOCOL_PORT}${TAILSCALE_SERVE_PATH} http://localhost:"${TAILSCALE_SERVE_PORT}${TAILSCALE_SERVE_LOCALPATH}" | grep -v "To disable the proxy"
else
echo "Enabling Serve! See https://tailscale.com/kb/1312/serve"
eval tailscale serve --bg --"${TAILSCALE_SERVE_PROTOCOL}"${TAILSCALE_SERVE_PROTOCOL_PORT}${TAILSCALE_SERVE_PATH} http://localhost:"${TAILSCALE_SERVE_PORT}${TAILSCALE_SERVE_LOCALPATH}" | grep -v "To disable the proxy"
fi
if [ "${TAILSCALE_SERVE_PROTOCOL}" = "https" ]; then
TS_DNSNAME="$(tailscale status --json | jq -r '.Self.DNSName' | sed 's/\.$//')"
if [ ! -f "${TSD_STATE_DIR}/certs/${TS_DNSNAME}.crt" ] || [ ! -f "${TSD_STATE_DIR}/certs/${TS_DNSNAME}.key" ]; then
if [ ! -d "${TSD_STATE_DIR}/certs" ]; then
mkdir -p "${TSD_STATE_DIR}/certs"
fi
echo "Generating Tailscale certs! This can take some time, please wait..."
timeout 30 tailscale cert --cert-file="${TSD_STATE_DIR}/certs/${TS_DNSNAME}.crt" --key-file="${TSD_STATE_DIR}/certs/${TS_DNSNAME}.key" "${TS_DNSNAME}" >/dev/null 2>&1
EXIT_STATUS="$?"
if [ "${EXIT_STATUS}" != "0" ]; then
echo "ERROR: Can't generate certificates!"
echo "Please check the logs:"
tail -10 /var/log/tailscaled
else
echo "Done!"
fi
unset EXIT_STATUS
fi
fi
fi
exec_entrypoint

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +0,0 @@
cd /tmp
rm -rf unraidwol
git clone https://github.com/SimonFair/unraidwol
cd unraidwol/
DATA_DIR=$(pwd)
git checkout main
PATH="$PATH:/usr/local/go/bin"
go mod tidy
go build
cp /tmp/unraidwol/unraidwold /usr/libexec/unraid