211 Commits

Author SHA1 Message Date
MacRimi
fc97499504 New version 2025-07-09 21:55:23 +02:00
MacRimi
681f5622c6 Update install_proxmenux.sh 2025-07-09 20:48:54 +02:00
MacRimi
323812710e Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-07-09 20:48:02 +02:00
MacRimi
d421c0c62c Update install_proxmenux.sh 2025-07-09 20:47:17 +02:00
MacRimi
8cbcd74f1a update menu 2025-07-09 20:46:24 +02:00
ProxMenuxBot
328cdad26e Update helpers_cache.json 2025-07-09 18:19:15 +00:00
MacRimi
25b83cb86e Update menu 2025-07-09 19:50:55 +02:00
MacRimi
d3c5e4c376 Update emergency_repair.sh 2025-07-09 19:36:54 +02:00
MacRimi
bf518f548c Update cache.json 2025-07-09 19:18:12 +02:00
MacRimi
3999416366 Update cache.json 2025-07-09 19:13:34 +02:00
MacRimi
707811cc4a update script coral TPU 2025-07-09 18:59:26 +02:00
MacRimi
05a7f4ba83 Update install_coral_lxc.sh 2025-07-09 18:11:45 +02:00
MacRimi
be339435b8 Update menu_post_install.sh 2025-07-09 14:55:05 +02:00
MacRimi
ea7adb9b5b Update auto_post_install.sh 2025-07-09 14:42:47 +02:00
ProxMenuxBot
3583cb007a Update helpers_cache.json 2025-07-09 12:28:00 +00:00
MacRimi
cc06a4e1b1 Update proxmox_update.sh 2025-07-09 08:44:29 +02:00
MacRimi
6e0c09816b Update proxmox_update.sh 2025-07-09 08:36:00 +02:00
MacRimi
50a4514954 Update network_menu.sh 2025-07-09 08:26:30 +02:00
MacRimi
91bca917c2 update menu network 2025-07-09 08:19:07 +02:00
MacRimi
0550aa9bd2 Update main menu 2025-07-08 23:21:11 +02:00
MacRimi
c9d172d301 update network repair 2025-07-08 22:58:00 +02:00
MacRimi
77c950bc1e Update network_menu.sh 2025-07-08 22:42:12 +02:00
ProxMenuxBot
d0bbfa82d1 Update helpers_cache.json 2025-07-08 01:06:28 +00:00
ProxMenuxBot
53d657ea0e Update helpers_cache.json 2025-07-07 12:27:17 +00:00
ProxMenuxBot
bf8e677b55 Update helpers_cache.json 2025-07-07 06:21:40 +00:00
MacRimi
8deebf83ee Update network_menu.sh 2025-07-06 23:09:27 +02:00
MacRimi
31ec2a0b3a Update network_menu.sh 2025-07-06 17:45:33 +02:00
MacRimi
e6b568d7d6 update menu netwokr 2025-07-06 17:44:01 +02:00
MacRimi
dfbfea5169 update cache 2025-07-06 16:47:50 +02:00
MacRimi
66862c9bd3 Update cache.json 2025-07-06 16:38:25 +02:00
MacRimi
84c217717f Update customizable_post_install.sh 2025-07-06 16:33:23 +02:00
MacRimi
67f309f32e Update post-install 2025-07-06 16:30:16 +02:00
MacRimi
6e7e7cc7fa Update auto_post_install.sh 2025-07-06 14:31:04 +02:00
MacRimi
5f39a1e08c delete scripts 2025-07-06 14:27:46 +02:00
MacRimi
b175ddb0ca Update cache.json 2025-07-06 14:26:03 +02:00
MacRimi
4f36b2eab9 Update cache.json 2025-07-06 14:22:26 +02:00
MacRimi
853c86c40b Update cache.json 2025-07-06 14:17:17 +02:00
MacRimi
242dfa6c9e update post install automated 2025-07-06 14:02:24 +02:00
MacRimi
d98b68ca65 Update menu_post_install.sh 2025-07-06 13:49:52 +02:00
MacRimi
a41df16f14 Update auto_post_install.sh 2025-07-06 13:06:57 +02:00
MacRimi
48b545d731 new script post-install automated 2025-07-06 12:21:29 +02:00
MacRimi
707b6a6f1b Update config_menu.sh 2025-07-05 17:31:18 +02:00
MacRimi
c21b374b49 Update config_menu.sh 2025-07-05 17:26:31 +02:00
MacRimi
e09749c6f2 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-07-05 16:31:26 +02:00
MacRimi
b7876d1774 Update install_proxmenux.sh 2025-07-05 16:31:24 +02:00
MacRimi
d0590600f3 Update cache.json 2025-07-05 16:28:15 +02:00
MacRimi
fe5b30b4c6 Update cache.json 2025-07-05 16:20:49 +02:00
MacRimi
b7ce73d338 Update cache.json 2025-07-05 15:51:09 +02:00
MacRimi
8139eb607c Update utils.sh 2025-07-05 11:55:41 +02:00
MacRimi
96c08e6563 Update utils.sh 2025-07-05 11:52:20 +02:00
MacRimi
38977af9d3 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-07-05 11:48:36 +02:00
MacRimi
0584081c33 Update utils.sh 2025-07-05 11:48:33 +02:00
MacRimi
ea5e471bf8 Update cache.json 2025-07-05 11:28:51 +02:00
MacRimi
230847dace Update install Proxmenux 2025-07-04 22:37:16 +02:00
MacRimi
5a91810e9a Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-07-04 21:44:21 +02:00
MacRimi
49ff63fad4 Update proxmox_update.sh 2025-07-04 21:44:19 +02:00
MacRimi
96cc87cde8 Update cache.json 2025-07-04 21:15:25 +02:00
MacRimi
baddd82ecc Update main_menu.sh 2025-07-04 20:58:43 +02:00
MacRimi
a4b127a8f3 Update main_menu.sh 2025-07-04 20:50:07 +02:00
MacRimi
2851fd20b8 Update main_menu.sh 2025-07-04 20:36:19 +02:00
MacRimi
dd278fe3d3 Update install ProxMenux 2025-07-04 20:30:19 +02:00
MacRimi
196c29da17 Update menu 2025-07-04 19:40:38 +02:00
ProxMenuxBot
a1f7fb57d9 Update helpers_cache.json 2025-07-04 12:26:57 +00:00
ProxMenuxBot
27acd37a2c Update helpers_cache.json 2025-07-03 18:18:48 +00:00
ProxMenuxBot
6fb7202e89 Update helpers_cache.json 2025-07-02 18:19:27 +00:00
MacRimi
dddd12a036 update script iso creator 2025-07-02 18:47:08 +02:00
MacRimi
f4c0211cba Update utilities menu 2025-07-02 18:36:30 +02:00
MacRimi
57d291a13c Update uup_dump_iso_creator.sh 2025-07-02 18:05:38 +02:00
MacRimi
5b2011cf82 Update uup_dump_iso_creator.sh 2025-07-02 18:02:42 +02:00
MacRimi
d14f0e9295 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-07-02 17:57:28 +02:00
MacRimi
f2a15f84a8 Updates menus 2025-07-02 17:57:25 +02:00
ProxMenuxBot
d86831467b Update helpers_cache.json 2025-07-02 01:06:29 +00:00
MacRimi
d12365667f Update proxmox_update.sh 2025-07-01 20:56:57 +02:00
MacRimi
dcc07d4590 Create proxmox_update.sh 2025-07-01 20:49:36 +02:00
MacRimi
c996149476 Update uup_dump_iso_creator.sh 2025-07-01 19:03:35 +02:00
MacRimi
39ff81dee4 Update uup_dump_iso_creator.sh 2025-07-01 18:13:32 +02:00
ProxMenuxBot
22aa3aef96 Update helpers_cache.json 2025-06-30 18:47:59 +00:00
MacRimi
3faff72519 utilities create menu 2025-06-30 18:54:16 +02:00
MacRimi
bac3245eb7 Update customizable_post_install.sh 2025-06-30 17:28:26 +02:00
MacRimi
ec9e8e348f Update customizable_post_install.sh 2025-06-30 15:25:10 +02:00
MacRimi
131da2ec60 Update customizable_post_install.sh 2025-06-30 15:12:31 +02:00
MacRimi
38d44deef6 Update uninstall-tools.sh 2025-06-29 18:20:15 +02:00
MacRimi
71c31b182a Update uninstall-tools.sh 2025-06-29 17:59:45 +02:00
MacRimi
02baeba8b9 Update uninstall-tools.sh 2025-06-29 17:43:48 +02:00
MacRimi
1d6ce959c8 Update customizable_post_install.sh 2025-06-29 16:58:34 +02:00
MacRimi
f599015473 Update customizable_post_install.sh 2025-06-29 16:44:14 +02:00
MacRimi
e98d804902 update menu nas 2025-06-29 15:40:36 +02:00
MacRimi
18f5ac1ead update menus nas and linux 2025-06-29 15:02:13 +02:00
MacRimi
5f2e346bd8 Update select_linux_iso.sh 2025-06-29 14:07:07 +02:00
MacRimi
96c23bf138 Update select_nas_iso.sh 2025-06-29 13:57:23 +02:00
MacRimi
446a724077 update menu linux and nas 2025-06-29 13:53:10 +02:00
MacRimi
6e9afa7f9f Update disk-passthrough_ct.sh 2025-06-28 21:43:48 +02:00
ProxMenuxBot
5c13d124de Update helpers_cache.json 2025-06-28 01:03:36 +00:00
ProxMenuxBot
4f94e21ea8 Update helpers_cache.json 2025-06-27 18:18:47 +00:00
ProxMenuxBot
7498aab76a Update helpers_cache.json 2025-06-26 18:20:04 +00:00
MacRimi
e65ac4ac8b Update uupdump_creator.sh 2025-06-25 20:23:45 +02:00
MacRimi
df2ec69448 Create Iso.sh 2025-06-25 20:23:31 +02:00
ProxMenuxBot
6fe474e494 Update helpers_cache.json 2025-06-25 12:27:42 +00:00
ProxMenuxBot
6408477939 Update helpers_cache.json 2025-06-24 18:18:58 +00:00
ProxMenuxBot
5a20a4260b Update helpers_cache.json 2025-06-24 12:27:48 +00:00
ProxMenuxBot
b604b81f37 Update helpers_cache.json 2025-06-24 01:06:00 +00:00
MacRimi
40e36b3203 Update customizable_post_install.sh 2025-06-21 19:09:10 +02:00
MacRimi
e098b63beb Update customizable_post_install.sh 2025-06-21 09:18:58 +02:00
ProxMenuxBot
56e99bc6ba Update helpers_cache.json 2025-06-19 18:18:49 +00:00
MacRimi
0abc42bf2c Update uupdump_creator.sh 2025-06-19 19:46:33 +02:00
ProxMenuxBot
f648eba8dd Update helpers_cache.json 2025-06-19 12:52:20 +00:00
ProxMenuxBot
20648b479f Update helpers_cache.json 2025-06-19 06:20:00 +00:00
ProxMenuxBot
ef82bac8fc Update helpers_cache.json 2025-06-18 18:19:19 +00:00
MacRimi
28e330520b update menu backup 2025-06-18 18:56:11 +02:00
MacRimi
b481bb08cc Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-06-17 19:52:39 +02:00
MacRimi
506d7fff22 Create backup_host.sh 2025-06-17 19:52:36 +02:00
ProxMenuxBot
da3b42b6ac Update helpers_cache.json 2025-06-17 12:27:56 +00:00
ProxMenuxBot
eea50c23b5 Update helpers_cache.json 2025-06-16 18:19:26 +00:00
ProxMenuxBot
b1f5860335 Update helpers_cache.json 2025-06-16 06:21:42 +00:00
ProxMenuxBot
958c567b6b Update helpers_cache.json 2025-06-16 01:07:19 +00:00
MacRimi
b443f278da Update mount_disk_host_bk.sh 2025-06-15 11:58:17 +02:00
MacRimi
f5ae187012 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-06-15 11:48:40 +02:00
MacRimi
6e48279c8a Update mount_disk_host_bk.sh 2025-06-15 11:48:30 +02:00
MacRimi
61976e8c13 Update mount_disk_host_bk.sh 2025-06-15 11:47:28 +02:00
MacRimi
2009b0ff7e Update mount_disk_host_bk.sh 2025-06-15 11:35:57 +02:00
MacRimi
60dd0d45b9 Update mount to host 2025-06-14 20:17:51 +02:00
MacRimi
a7422e4c1e Update mount_disk_host_bk.sh 2025-06-14 16:19:06 +02:00
MacRimi
ffd79d2404 Update mount_disk_host_bk.sh 2025-06-14 16:17:43 +02:00
MacRimi
7363a461de Update mount_disk_host_bk.sh 2025-06-14 15:58:49 +02:00
MacRimi
f7325f030c Rename Mount_disk_host_bk.sh to mount_disk_host_bk.sh 2025-06-14 11:59:25 +02:00
MacRimi
2668e5d8b2 Create Mount_disk_host_bk.sh 2025-06-14 11:52:50 +02:00
ProxMenuxBot
f09e3ffcdb Update helpers_cache.json 2025-06-13 01:05:40 +00:00
ProxMenuxBot
de4bba2e85 Update helpers_cache.json 2025-06-12 18:18:59 +00:00
ProxMenuxBot
bb50ecc86c Update helpers_cache.json 2025-06-10 18:18:50 +00:00
MacRimi
2191fe4cdd Update customizable_post_install.sh 2025-06-10 19:58:34 +02:00
MacRimi
321e0b2331 Update customizable_post_install.sh 2025-06-10 19:54:31 +02:00
MacRimi
f4611280a7 Update menu_Helper_Scripts.sh 2025-06-10 14:34:30 +02:00
MacRimi
ed3f2415bb Update hw_grafics_menu.sh 2025-06-10 14:29:34 +02:00
MacRimi
495bc24b2f Update install_coral_pve.sh 2025-06-10 14:27:09 +02:00
MacRimi
397c84cacb Update install_coral_lxc.sh 2025-06-10 14:24:30 +02:00
MacRimi
f04fb7e756 Update configure_igpu_lxc.sh 2025-06-10 14:18:05 +02:00
MacRimi
dcbed8b173 Update configure_igpu_lxc.sh 2025-06-10 14:03:58 +02:00
MacRimi
3e5e79ba18 Update config_menu.sh 2025-06-10 13:57:07 +02:00
MacRimi
ddaee77b59 Update config_menu.sh 2025-06-10 13:55:10 +02:00
MacRimi
2d5a08a921 Update create_vm_menu.sh 2025-06-10 13:52:30 +02:00
MacRimi
240a325ef1 Update create_vm_menu_.sh 2025-06-10 13:48:09 +02:00
MacRimi
663a0f15df Update cache.json 2025-06-10 13:30:03 +02:00
MacRimi
cb7afac17b Update main_menu.sh 2025-06-10 13:28:06 +02:00
MacRimi
b04710cf50 Update cache.json 2025-06-10 13:24:49 +02:00
MacRimi
ce3fd894ae Update storage_menu.sh 2025-06-10 12:56:05 +02:00
MacRimi
fd11f4e866 Update create_vm_menu.sh 2025-06-10 12:48:26 +02:00
MacRimi
5422af1e82 Update main_menu.sh 2025-06-10 12:45:17 +02:00
MacRimi
444002b006 Update storage_menu.sh 2025-06-10 12:44:49 +02:00
MacRimi
f01c474536 Actualizar storage_menu.sh 2025-06-10 11:41:07 +02:00
ProxMenuxBot
696b42666f Update helpers_cache.json 2025-06-10 01:06:21 +00:00
ProxMenuxBot
84190e0806 Update helpers_cache.json 2025-06-09 01:08:35 +00:00
ProxMenuxBot
80253426b7 Update helpers_cache.json 2025-06-07 01:03:54 +00:00
MacRimi
26ccc63c96 Actualizar CHANGELOG.md 2025-06-06 19:19:11 +02:00
MacRimi
1124ac41f9 Update CHANGELOG.md 2025-06-06 18:29:10 +02:00
MacRimi
d534d8b25c Update CHANGELOG.md 2025-06-06 18:28:26 +02:00
MacRimi
618afaacd4 add images 2025-06-06 17:54:44 +02:00
MacRimi
53b6ce56bf Update menu_Helper_Scripts.sh 2025-06-06 17:42:52 +02:00
MacRimi
8257c7d7e4 Update menu_Helper_Scripts.sh 2025-06-06 17:40:53 +02:00
MacRimi
769416f474 Update menu_Helper_Scripts.sh 2025-06-06 17:34:51 +02:00
MacRimi
f978e5d261 Update main_menu.sh 2025-06-06 17:30:24 +02:00
MacRimi
9d3660a1e2 update menu 2025-06-06 17:29:06 +02:00
MacRimi
f2637aad46 Update menu 2025-06-06 17:24:56 +02:00
MacRimi
371e8a9570 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-06-06 17:11:59 +02:00
MacRimi
56987fe7a0 Update customizable_post_install.sh 2025-06-06 17:11:57 +02:00
ProxMenuxBot
dfe5138cad Update helpers_cache.json 2025-06-06 12:25:55 +00:00
ProxMenuxBot
90a2d83670 Update helpers_cache.json 2025-06-06 01:04:20 +00:00
MacRimi
8c8981ea9f Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-06-06 00:02:47 +02:00
MacRimi
de49d67361 Update helpers-menu.sh 2025-06-06 00:02:43 +02:00
ProxMenuxBot
5826c383b7 Update helpers_cache.json 2025-06-05 12:27:18 +00:00
ProxMenuxBot
24962f44e1 Update helpers_cache.json 2025-06-04 21:26:29 +00:00
MacRimi
9f2fc40c76 Update generate_helpers_cache.py 2025-06-04 23:25:29 +02:00
MacRimi
b8bdcf4c71 Update helpers-menu.sh 2025-06-04 20:50:01 +02:00
MacRimi
c9c6dc7666 Update helpers-menu.sh 2025-06-04 20:47:11 +02:00
MacRimi
9f686b91a2 Update helpers-menu.sh 2025-06-04 20:45:05 +02:00
MacRimi
9e879d6582 Update helpers-menu.sh 2025-06-04 20:42:23 +02:00
MacRimi
2fc1df729b Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-06-04 20:39:57 +02:00
MacRimi
6586b9746a Create helpers-menu.sh 2025-06-04 20:39:50 +02:00
ProxMenuxBot
37d3ba3bc1 Update helpers_cache.json 2025-06-04 18:19:08 +00:00
ProxMenuxBot
1126860834 Update helpers_cache.json 2025-06-03 18:42:23 +00:00
MacRimi
c26141bc5d Update generate_helpers_cache.py 2025-06-03 20:41:20 +02:00
ProxMenuxBot
3d6cfb44bb Update helpers_cache.json 2025-06-03 17:41:05 +00:00
MacRimi
4b1bdc55f1 Update generate_helpers_cache.py 2025-06-03 19:40:01 +02:00
ProxMenuxBot
c2bdd5f0bb Update helpers_cache.json 2025-06-03 16:44:28 +00:00
MacRimi
c22544672c Update update-helpers-cache.yml 2025-06-03 18:42:14 +02:00
MacRimi
78064cc07c Update generate_helpers_cache.py 2025-06-03 18:41:49 +02:00
MacRimi
84f5897e38 Update generate_helpers_cache.py 2025-06-03 18:37:36 +02:00
ProxMenuxBot
9044f13d2b 🔄 Update helpers_cache.json 2025-06-03 09:13:31 +00:00
MacRimi
cf9ee44970 Actualizar update-helpers-cache.yml 2025-06-03 11:11:24 +02:00
MacRimi
0cf5830671 Actualizar generate_helpers_cache.py 2025-06-03 11:09:29 +02:00
MacRimi
f25d8aec3c Crear update-helpers-cache.yml 2025-06-03 11:00:47 +02:00
MacRimi
4ecb3f9943 Crear generate_helpers_cache.py 2025-06-03 10:58:58 +02:00
MacRimi
3dcb521422 Update import-disk-image.sh 2025-05-30 18:25:10 +02:00
MacRimi
de4db1de9a Update import-disk-image.sh 2025-05-30 15:11:18 +02:00
MacRimi
a6c2b958a2 Update import-disk-image.sh 2025-05-30 14:56:57 +02:00
MacRimi
f721d9d774 Update README.md 2025-05-30 11:12:45 +02:00
MacRimi
3a8c1c3fd9 Update README.md 2025-05-30 11:08:59 +02:00
MacRimi
891c70dd4c Update CODE_OF_CONDUCT.md 2025-05-30 11:07:26 +02:00
MacRimi
f2b99722e3 Create SECURITY.md 2025-05-30 11:03:54 +02:00
MacRimi
32204d3e17 Update vm_configurator.sh 2025-05-29 22:18:52 +02:00
MacRimi
112dfe08b3 Update vm_configurator.sh 2025-05-29 22:07:42 +02:00
MacRimi
4c3736fad7 Update storage_menu.sh 2025-05-29 21:50:48 +02:00
MacRimi
69a5b76e97 Update menu import image to vm 2025-05-29 21:49:21 +02:00
MacRimi
f941207699 Update import-disk-image.sh 2025-05-29 21:43:39 +02:00
MacRimi
084a8956ca Update menu_post_install.sh 2025-05-28 23:33:59 +02:00
MacRimi
571b5270a2 Update menus dialog 2025-05-28 23:26:58 +02:00
MacRimi
dcae6e1cd0 Update RSS page 2025-05-28 17:24:11 +02:00
MacRimi
a7d84d27fd Update page.tsx 2025-05-28 17:16:06 +02:00
MacRimi
9fba81f51d Update CHANGELOG.md 2025-05-27 20:48:54 +02:00
MacRimi
35e399dbaf Update storage_menu.sh 2025-05-27 20:32:30 +02:00
MacRimi
c3fc013002 Update storage_menu.sh 2025-05-27 20:30:17 +02:00
MacRimi
a34fc6eaa4 Update menus 2025-05-27 19:58:03 +02:00
56 changed files with 18811 additions and 1954 deletions

View File

@@ -0,0 +1,76 @@
import requests, json
from pathlib import Path
# GitHub API URL to fetch all .json files describing scripts
API_URL = "https://api.github.com/repos/community-scripts/ProxmoxVE/contents/frontend/public/json"
# Base path to build the full URL for the installable scripts
SCRIPT_BASE = "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
# Output file where the consolidated helper scripts cache will be stored
OUTPUT_FILE = Path("json/helpers_cache.json")
OUTPUT_FILE.parent.mkdir(parents=True, exist_ok=True)
res = requests.get(API_URL)
data = res.json()
cache = []
# Loop over each file in the JSON directory
for item in data:
url = item.get("download_url")
if not url or not url.endswith(".json"):
continue
try:
raw = requests.get(url).json()
if not isinstance(raw, dict):
continue
except:
continue
# Extract fields required to identify a valid helper script
name = raw.get("name", "")
slug = raw.get("slug")
type_ = raw.get("type", "")
script = raw.get("install_methods", [{}])[0].get("script", "")
if not slug or not script:
continue # Skip if it's not a valid script
desc = raw.get("description", "")
categories = raw.get("categories", [])
notes = [note.get("text", "") for note in raw.get("notes", []) if isinstance(note, dict)]
full_script_url = f"{SCRIPT_BASE}/{script}"
credentials = raw.get("default_credentials", {})
cred_username = credentials.get("username")
cred_password = credentials.get("password")
add_credentials = (
(cred_username is not None and str(cred_username).strip() != "") or
(cred_password is not None and str(cred_password).strip() != "")
)
entry = {
"name": name,
"slug": slug,
"desc": desc,
"script": script,
"script_url": full_script_url,
"categories": categories,
"notes": notes,
"type": type_
}
if add_credentials:
entry["default_credentials"] = {
"username": cred_username,
"password": cred_password
}
cache.append(entry)
# Write the JSON cache to disk
with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
json.dump(cache, f, indent=2)
print(f"✅ helpers_cache.json created at {OUTPUT_FILE} with {len(cache)} valid scripts.")

View File

@@ -0,0 +1,38 @@
name: Update Helper Scripts Cache
on:
# Manual trigger from GitHub Actions UI
workflow_dispatch:
# Automatic run every 6 hours
schedule:
- cron: "0 */6 * * *"
jobs:
update-cache:
runs-on: ubuntu-latest
permissions:
contents: write # Required to push changes to the repository
steps:
- name: ⬇️ Checkout the repository
uses: actions/checkout@v3
- name: 🐍 Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.11
- name: 📦 Install Python dependencies
run: pip install requests
- name: ⚙️ Generate json/helpers_cache.json
run: python .github/scripts/generate_helpers_cache.py
- name: 📤 Commit and push if updated
run: |
git config user.name "ProxMenuxBot"
git config user.email "bot@proxmenux.local"
git add json/helpers_cache.json
git diff --cached --quiet || git commit -m "Update helpers_cache.json"
git push

View File

@@ -1,3 +1,169 @@
## 2025-07-01
### New version v1.1.3
![Installer Menu](https://macrimi.github.io/ProxMenux/install/install.png)
- **Dual Installation Modes for ProxMenux**
The installer now offers two distinct modes:
1. **Lite version (no translations):** Only installs two official Debian packages (`dialog`, `jq`) to enable menus and JSON parsing. No files are written beyond the configuration directory.
2. **Full version (with translations):** Uses a virtual environment and allows selecting the interface language during installation.
When updating, if the user switches from full to lite, the old version will be **automatically removed** for a clean transition.
### Added
- **New Script: Automated Post-Installation Setup**
A new minimal post-install script that performs essential setup automatically:
- System upgrade and sync
- Remove enterprise banner
- Optimize APT, journald, logrotate, system limits
- Improve kernel panic handling, memory settings, entropy, network
- Add `.bashrc` tweaks and **Log2RAM auto-install** (if SSD/M.2 is detected)
- **New Function: Log2RAM Configuration**
Now available in both the customizable and automatic post-install scripts.
On systems with SSD/NVMe, Log2RAM is **enabled automatically** to preserve disk life.
- **New Menus:**
- 🧰 **System Utilities Menu**
Lets users select and install useful CLI tools with proper command validation.
- 🌐 **Network Configuration & Repair**
A new interactive menu for analyzing and repairing network interfaces.
### Improved
- **Post-Install Menu Logic**
Options are now grouped more logically for better usability.
- **VM Creation Menu**
Enhanced with improved CPU model support and custom options.
- **UUP Dump ISO Creator Script**
- Added option to **customize the temporary folder location**
- Fixed issue where entire temp folder was deleted instead of just contents
💡 Suggested by [@igrokit](https://github.com/igrokit)
[#17](https://github.com/MacRimi/ProxMenux/issues/17), [#11](https://github.com/MacRimi/ProxMenux/issues/11)
- **Physical Disk to LXC Script**
Now handles **XFS-formatted disks** correctly.
Thanks to [@antroxin](https://github.com/antroxin) for reporting and testing!
- **System Utilities Installer**
Rewritten to **verify command availability** after installation, ensuring tools work as expected.
🐛 Fix for [#18](https://github.com/MacRimi/ProxMenux/issues/18) by [@DST73](https://github.com/DST73)
### Fixed
- **Enable IOMMU on ZFS**
The detection and configuration for enabling IOMMU on ZFS-based systems is now fully functional.
🐛 Fix for [#15](https://github.com/MacRimi/ProxMenux/issues/15) by [@troponaut](https://github.com/troponaut)
### Other
- Performance and code cleanup improvements across several modules.
## 2025-06-06
### Added
- **New Menu: Proxmox PVE Helper Scripts**
Officially introduced the new **Proxmox PVE Helper Scripts** menu, replacing the previous: Esenciales Proxmox.
This new menu includes:
- Script search by name in real time
- Category-based browsing
Its a cleaner, faster, and more functional way to access community scripts in Proxmox.
![Helper Scripts Menu](https://macrimi.github.io/ProxMenux/menu-helpers-script.png)
- **New CPU Models in VM Creation**
The CPU selection menu in VM creation has been greatly expanded to support advanced QEMU and x86-64 CPU profiles.
This allows better compatibility with modern guest systems and fine-tuning performance for specific workloads, including nested virtualization and hardware-assisted features.
![CPU Config](https://macrimi.github.io/ProxMenux/vm/config-cpu.png)
Thanks to **@Nida Légé (Nidouille)** for suggesting this enhancement.
- **Support for `.raw` Disk Images**
The disk import tool for VMs now supports `.raw` files, in addition to `.img`, `.qcow2`, and `.vmdk`.
This improves compatibility when working with disk exports from other hypervisors or backup tools.
💡 Suggested by **@guilloking** in [GitHub Issue #5](https://github.com/MacRimi/ProxMenux/issues/5)
- **Locale Detection in Language Skipping**
The function that disables extra APT languages now includes:
- Automatic locale detection (`LANG`)
- Auto-generation of `en_US.UTF-8` if none is found
- Prevents warnings during script execution due to undefined locale
### Improved
- **APT Language Skipping Logic**
Improved locale handling ensures system compatibility before disabling translations:
```bash
if ! locale -a | grep -qi "^${default_locale//-/_}$"; then
echo "$default_locale UTF-8" >> /etc/locale.gen
locale-gen "$default_locale"
fi
```
- **System Update Speed**
Post-install system upgrades are now faster:
- The upgrade process (`dist-upgrade`) is separated from container template index updates.
- Index refresh is now an optional feature selected in the script.
## 2025-05-27
### Fixed
- **Kali Linux ISO URL Updated**
Fixed the incorrect download URL for Kali Linux ISO in the Linux installer module. The new correct path is:
```
https://cdimage.kali.org/kali-2025.1c/kali-linux-2025.1c-installer-amd64.iso
```
### Improved
- **Faster Dialog Menu Transitions**
Improved UI responsiveness across all interactive menus by replacing `whiptail` with `dialog`, offering faster transitions and smoother navigation.
- **Coral USB Support in LXC**
Improved the logic for configuring Coral USB TPU passthrough into LXC containers:
- Refactored configuration into modular blocks with better structure and inline comments.
- Clear separation of Coral USB (`/dev/coral`) and Coral M.2 (`/dev/apex_0`) logic.
- Maintains backward compatibility with existing LXC configurations.
- Introduced persistent Coral USB passthrough using a udev rule:
```bash
# Create udev rule for Coral USB
SUBSYSTEM=="usb", ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="9302", MODE="0666", TAG+="uaccess", SYMLINK+="coral"
# Map /dev/coral if it exists
if [ -e /dev/coral ]; then
echo "lxc.mount.entry: /dev/coral dev/coral none bind,optional,create=file" >> "$CONFIG_FILE"
fi
```
- Special thanks to **@Blaspt** for validating the persistent Coral USB passthrough and suggesting the use of `/dev/coral` symbolic link.
### Added
- **Persistent Coral USB Passthrough Support**
Added udev rule support for Coral USB devices to persistently map them as `/dev/coral`, enabling consistent passthrough across reboots. This path is automatically detected and mapped in the container configuration.
- **RSS Feed Integration**
Added support for generating an RSS feed for the changelog, allowing users to stay informed of updates through news clients.
- **Release Service Automation**
Implemented a new release management service to automate publishing and tagging of versions, starting with version **v1.1.2**.
## 2025-05-13
### Fixed

View File

@@ -29,6 +29,7 @@ Instead, please report it privately via email:
📧 proxmenux@macrimi.pro
For detailed information on security vulnerabilities and how to report them, please refer to our [Security Policy](./SECURITY.md).
## 🤝 3. Community Guidelines

41
SECURITY.md Normal file
View File

@@ -0,0 +1,41 @@
# 🔒 Security Policy
## 📅 Supported Versions
We actively maintain the latest release of ProxMenux. Only the most recent version receives security updates.
| Version | Supported |
| ------- | --------- |
| Latest | ✅ |
| Older versions | ❌ |
## 📢 Reporting a Vulnerability
If you discover a **security vulnerability**, please help us keep the community safe by reporting it **privately**.
**Do not report vulnerabilities in public GitHub Issues or Discussions.**
### 📬 Contact
To report a vulnerability, email:
**📧 proxmenux@macrimi.pro**
Please include as much detail as possible, including:
- Steps to reproduce the issue
- A description of the impact
- Any known mitigations
We aim to respond as soon as possible, typically within **48 hours**.
## ⚠️ Coordinated Disclosure
We follow responsible disclosure principles. If a vulnerability is confirmed, we will:
1. Work on a fix immediately.
2. Inform you of the resolution status.
---
🔐 Thank you for helping make ProxMenux a safer project for everyone!

View File

@@ -6,8 +6,8 @@
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.2
# Last Updated: 04/04/2025
# Version : 1.3
# Last Updated: 04/07/2025
# ==========================================================
# Description:
# This script installs and configures ProxMenux, a menu-driven
@@ -33,7 +33,6 @@
# the system for running ProxMenux efficiently.
# ==========================================================
# Configuration ============================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
UTILS_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main/scripts/utils.sh"
@@ -42,42 +41,181 @@ BASE_DIR="/usr/local/share/proxmenux"
CONFIG_FILE="$BASE_DIR/config.json"
CACHE_FILE="$BASE_DIR/cache.json"
UTILS_FILE="$BASE_DIR/utils.sh"
#EMERGENCY_FILE="$BASE_DIR/emergency_repair.sh"
LOCAL_VERSION_FILE="$BASE_DIR/version.txt"
MENU_SCRIPT="menu"
VENV_PATH="/opt/googletrans-env"
# Source utils.sh for common functions and styles
if ! source <(curl -sSf "$UTILS_URL"); then
echo "Error: Could not load utils.sh from $UTILS_URL"
exit 1
fi
cleanup_corrupted_files() {
if [ -f "$CONFIG_FILE" ] && ! jq empty "$CONFIG_FILE" >/dev/null 2>&1; then
echo "Cleaning up corrupted configuration file..."
rm -f "$CONFIG_FILE"
fi
if [ -f "$CACHE_FILE" ] && ! jq empty "$CACHE_FILE" >/dev/null 2>&1; then
echo "Cleaning up corrupted cache file..."
rm -f "$CACHE_FILE"
fi
}
# ==========================================================
check_existing_installation() {
local has_venv=false
local has_config=false
local has_language=false
local has_menu=false
if [ -f "$INSTALL_DIR/$MENU_SCRIPT" ]; then
has_menu=true
fi
if [ -d "$VENV_PATH" ] && [ -f "$VENV_PATH/bin/activate" ]; then
has_venv=true
fi
if [ -f "$CONFIG_FILE" ]; then
if jq empty "$CONFIG_FILE" >/dev/null 2>&1; then
has_config=true
local current_language=$(jq -r '.language // empty' "$CONFIG_FILE" 2>/dev/null)
if [[ -n "$current_language" && "$current_language" != "null" && "$current_language" != "empty" ]]; then
has_language=true
fi
else
echo "Warning: Corrupted config file detected, removing..."
rm -f "$CONFIG_FILE"
fi
fi
if [ "$has_venv" = true ] && [ "$has_language" = true ]; then
echo "translation"
elif [ "$has_menu" = true ] && [ "$has_venv" = false ]; then
echo "normal"
elif [ "$has_menu" = true ]; then
echo "unknown"
else
echo "none"
fi
}
uninstall_proxmenu() {
local install_type="$1"
local force_clean="$2"
if [ "$force_clean" != "force" ]; then
if ! whiptail --title "Uninstall ProxMenu" --yesno "Are you sure you want to uninstall ProxMenu?" 10 60; then
return 1
fi
fi
echo "Uninstalling ProxMenu..."
if [ -f "$VENV_PATH/bin/activate" ]; then
echo "Removing googletrans and virtual environment..."
source "$VENV_PATH/bin/activate"
pip uninstall -y googletrans >/dev/null 2>&1
deactivate
rm -rf "$VENV_PATH"
fi
if [ "$install_type" = "translation" ] && [ "$force_clean" != "force" ]; then
DEPS_TO_REMOVE=$(whiptail --title "Remove Translation Dependencies" --checklist \
"Select translation-specific dependencies to remove:" 15 60 3 \
"python3-venv" "Python virtual environment" OFF \
"python3-pip" "Python package installer" OFF \
"python3" "Python interpreter" OFF \
3>&1 1>&2 2>&3)
if [ -n "$DEPS_TO_REMOVE" ]; then
echo "Removing selected dependencies..."
read -r -a DEPS_ARRAY <<< "$(echo "$DEPS_TO_REMOVE" | tr -d '"')"
for dep in "${DEPS_ARRAY[@]}"; do
echo "Removing $dep..."
apt-mark auto "$dep" >/dev/null 2>&1
apt-get -y --purge autoremove "$dep" >/dev/null 2>&1
done
apt-get autoremove -y --purge >/dev/null 2>&1
fi
fi
rm -f "$INSTALL_DIR/$MENU_SCRIPT"
rm -rf "$BASE_DIR"
[ -f /root/.bashrc.bak ] && mv /root/.bashrc.bak /root/.bashrc
if [ -f /etc/motd.bak ]; then
mv /etc/motd.bak /etc/motd
else
sed -i '/This system is optimised by: ProxMenux/d' /etc/motd
fi
echo "ProxMenu has been uninstalled."
return 0
}
handle_installation_change() {
local current_type="$1"
local new_type="$2"
if [ "$current_type" = "$new_type" ]; then
return 0
fi
case "$current_type-$new_type" in
"translation-1"|"translation-normal")
if whiptail --title "Installation Type Change" \
--yesno "Switch from Translation to Normal Version?\n\nThis will remove translation components." 10 60; then
echo "Preparing for installation type change..."
uninstall_proxmenu "translation" "force" >/dev/null 2>&1
return 0
else
return 1
fi
;;
"normal-2"|"normal-translation")
if whiptail --title "Installation Type Change" \
--yesno "Switch from Normal to Translation Version?\n\nThis will add translation components." 10 60; then
return 0
else
return 1
fi
;;
*)
return 0
;;
esac
}
update_config() {
local component="$1"
local status="$2"
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
# List of components we want to track
local tracked_components=("whiptail" "dialog" "curl" "jq" "python3" "python3-venv" "python3-pip" "virtual_environment" "pip" "googletrans")
local tracked_components=("dialog" "curl" "jq" "python3" "python3-venv" "python3-pip" "virtual_environment" "pip" "googletrans")
# Check if the component is in the list of tracked components
if [[ " ${tracked_components[@]} " =~ " ${component} " ]]; then
mkdir -p "$(dirname "$CONFIG_FILE")"
if [ ! -f "$CONFIG_FILE" ]; then
if [ ! -f "$CONFIG_FILE" ] || ! jq empty "$CONFIG_FILE" >/dev/null 2>&1; then
echo '{}' > "$CONFIG_FILE"
fi
tmp=$(mktemp)
local tmp_file=$(mktemp)
if jq --arg comp "$component" --arg stat "$status" --arg time "$timestamp" \
'.[$comp] = {status: $stat, timestamp: $time}' "$CONFIG_FILE" > "$tmp_file" 2>/dev/null; then
mv "$tmp_file" "$CONFIG_FILE"
else
echo '{}' > "$CONFIG_FILE"
jq --arg comp "$component" --arg stat "$status" --arg time "$timestamp" \
'.[$comp] = {status: $stat, timestamp: $time}' "$CONFIG_FILE" > "$tmp" && mv "$tmp" "$CONFIG_FILE"
'.[$comp] = {status: $stat, timestamp: $time}' "$CONFIG_FILE" > "$tmp_file" && mv "$tmp_file" "$CONFIG_FILE"
fi
[ -f "$tmp_file" ] && rm -f "$tmp_file"
fi
}
show_progress() {
local step="$1"
local total="$2"
@@ -88,17 +226,77 @@ show_progress() {
msg_info2 "$message"
}
select_language() {
if [ -f "$CONFIG_FILE" ] && jq empty "$CONFIG_FILE" >/dev/null 2>&1; then
local existing_language=$(jq -r '.language // empty' "$CONFIG_FILE" 2>/dev/null)
if [[ -n "$existing_language" && "$existing_language" != "null" && "$existing_language" != "empty" ]]; then
LANGUAGE="$existing_language"
msg_ok "Using existing language configuration: $LANGUAGE"
return 0
fi
fi
# # Main installation function =============================
LANGUAGE=$(whiptail --title "Select Language" --menu "Choose a language for the menu:" 20 60 12 \
"en" "English (Recommended)" \
"es" "Spanish" \
"fr" "French" \
"de" "German" \
"it" "Italian" \
"pt" "Portuguese" 3>&1 1>&2 2>&3)
install_proxmenu() {
local total_steps=4
if [ -z "$LANGUAGE" ]; then
msg_error "No language selected. Exiting."
exit 1
fi
mkdir -p "$(dirname "$CONFIG_FILE")"
if [ ! -f "$CONFIG_FILE" ] || ! jq empty "$CONFIG_FILE" >/dev/null 2>&1; then
echo '{}' > "$CONFIG_FILE"
fi
local tmp_file=$(mktemp)
if jq --arg lang "$LANGUAGE" '. + {language: $lang}' "$CONFIG_FILE" > "$tmp_file" 2>/dev/null; then
mv "$tmp_file" "$CONFIG_FILE"
else
echo "{\"language\": \"$LANGUAGE\"}" > "$CONFIG_FILE"
fi
[ -f "$tmp_file" ] && rm -f "$tmp_file"
msg_ok "Language set to: $LANGUAGE"
}
# Show installation confirmation for new installations
show_installation_confirmation() {
local install_type="$1"
case "$install_type" in
"1")
if whiptail --title "ProxMenux - Normal Version Installation" \
--yesno "ProxMenux Normal Version will install:\n\n• dialog (interactive menus) - Official Debian package\n• curl (file downloads) - Official Debian package\n• jq (JSON processing) - Official Debian package\n• ProxMenux core files (/usr/local/share/proxmenux)\n\nThis is a lightweight installation with minimal dependencies.\n\nProceed with installation?" 18 70; then
return 0
else
return 1
fi
;;
"2")
if whiptail --title "ProxMenux - Translation Version Installation" \
--yesno "ProxMenux Translation Version will install:\n\n• dialog (interactive menus)\n• curl (file downloads)\n• jq (JSON processing)\n• python3 + python3-venv + python3-pip\n• Google Translate library (googletrans)\n• Virtual environment (/opt/googletrans-env)\n• Translation cache system\n• ProxMenux core files\n\nThis version requires more dependencies for translation support.\n\nProceed with installation?" 18 70; then
return 0
else
return 1
fi
;;
esac
}
####################################################
install_normal_version() {
local total_steps=3
local current_step=1
# Step 1: Check and install system dependencies
show_progress $current_step $total_steps "Checking system dependencies"
show_progress $current_step $total_steps "Installing basic dependencies"
if ! dpkg -l | grep -qw "jq"; then
msg_info "Installing jq..."
@@ -116,8 +314,91 @@ install_proxmenu() {
update_config "jq" "already_installed"
fi
BASIC_DEPS=("dialog" "curl")
for pkg in "${BASIC_DEPS[@]}"; do
if ! dpkg -l | grep -qw "$pkg"; then
msg_info "Installing $pkg..."
if apt-get install -y "$pkg" > /dev/null 2>&1; then
msg_ok "$pkg installed successfully."
update_config "$pkg" "installed"
else
msg_error "Failed to install $pkg. Please install it manually."
update_config "$pkg" "failed"
return 1
fi
else
msg_ok "$pkg is already installed."
update_config "$pkg" "already_installed"
fi
done
DEPS=("whiptail" "dialog" "curl" "python3" "python3-venv" "python3-pip")
((current_step++))
show_progress $current_step $total_steps "Creating directories and configuration"
mkdir -p "$BASE_DIR"
mkdir -p "$INSTALL_DIR"
if [ ! -f "$CONFIG_FILE" ]; then
echo '{}' > "$CONFIG_FILE"
fi
msg_ok "Directories and configuration created."
((current_step++))
show_progress $current_step $total_steps "Downloading necessary files"
FILES=(
"$UTILS_FILE $REPO_URL/scripts/utils.sh"
# "$EMERGENCY_FILE $REPO_URL/scripts/emergency_repair.sh"
"$INSTALL_DIR/$MENU_SCRIPT $REPO_URL/$MENU_SCRIPT"
"$LOCAL_VERSION_FILE $REPO_URL/version.txt"
)
for file in "${FILES[@]}"; do
IFS=" " read -r dest url <<< "$file"
msg_info "Downloading ${dest##*/}..."
sleep 2
if wget -qO "$dest" "$url"; then
msg_ok "${dest##*/} downloaded successfully."
else
msg_error "Failed to download ${dest##*/}. Check your Internet connection."
return 1
fi
done
chmod +x "$INSTALL_DIR/$MENU_SCRIPT"
# chmod +x "$EMERGENCY_FILE"
}
####################################################
install_translation_version() {
local total_steps=4
local current_step=1
show_progress $current_step $total_steps "Language selection"
select_language
((current_step++))
show_progress $current_step $total_steps "Installing system dependencies"
if ! dpkg -l | grep -qw "jq"; then
msg_info "Installing jq..."
apt-get update > /dev/null 2>&1
if apt-get install -y jq > /dev/null 2>&1; then
msg_ok "jq installed successfully."
update_config "jq" "installed"
else
msg_error "Failed to install jq. Please install it manually."
update_config "jq" "failed"
return 1
fi
else
msg_ok "jq is already installed."
update_config "jq" "already_installed"
fi
DEPS=("dialog" "curl" "python3" "python3-venv" "python3-pip")
for pkg in "${DEPS[@]}"; do
if ! dpkg -l | grep -qw "$pkg"; then
msg_info "Installing $pkg..."
@@ -137,14 +418,11 @@ install_proxmenu() {
((current_step++))
show_progress $current_step $total_steps "Setting up translation environment"
# Step 2: Set up virtual environment
show_progress $current_step $total_steps "Setting up virtual environment for translate"
if [ ! -d "$VENV_PATH" ] || [ ! -f "$VENV_PATH/bin/activate" ]; then
msg_info "Creating the virtual environment..."
python3 -m venv --system-site-packages "$VENV_PATH" > /dev/null 2>&1
if [ ! -f "$VENV_PATH/bin/activate" ]; then
msg_error "Failed to create virtual environment. Please check your Python installation."
update_config "virtual_environment" "failed"
@@ -157,12 +435,9 @@ install_proxmenu() {
msg_ok "Virtual environment already exists."
update_config "virtual_environment" "already_exists"
fi
source "$VENV_PATH/bin/activate"
((current_step++))
# Step 3: Install and upgrade pip and googletrans
show_progress $current_step $total_steps "Installing and upgrading pip and googletrans"
msg_info "Upgrading pip..."
if pip install --upgrade pip > /dev/null 2>&1; then
msg_ok "Pip upgraded successfully."
@@ -183,79 +458,118 @@ install_proxmenu() {
deactivate
return 1
fi
deactivate
((current_step++))
# Step 4: Download necessary files
show_progress $current_step $total_steps "Downloading necessary files"
mkdir -p "$BASE_DIR"
mkdir -p "$INSTALL_DIR"
FILES=(
"$CACHE_FILE $REPO_URL/json/cache.json"
"$UTILS_FILE $REPO_URL/scripts/utils.sh"
# "$EMERGENCY_FILE $REPO_URL/scripts/emergency_repair.sh"
"$INSTALL_DIR/$MENU_SCRIPT $REPO_URL/$MENU_SCRIPT"
"$LOCAL_VERSION_FILE $REPO_URL/version.txt"
)
for file in "${FILES[@]}"; do
IFS=" " read -r dest url <<< "$file"
msg_info "Downloading ${dest##*/}..."
sleep 2
if wget -qO "$dest" "$url"; then
msg_ok "${dest##*/} downloaded successfully."
if [[ "$dest" == "$CACHE_FILE" ]]; then
msg_ok "Cache file updated with latest translations."
fi
else
msg_error "Failed to download ${dest##*/}. Check your Internet connection."
return 1
fi
done
((current_step++))
# Final setup
chmod +x "$INSTALL_DIR/$MENU_SCRIPT"
# Installation complete ====================================
echo
#echo -e "${YW}╭─────────────────────────────────────────────────────╮${CL}"
#echo -e "${YW}│${CL} ${GN}🌟 ProxMenux has been installed successfull 🌟 ${CL} ${YW}│${CL}"
#echo -e "${YW}╰─────────────────────────────────────────────────────╯${CL}"
msg_title "ProxMenux has been installed successfull"
echo
echo -ne "${GN}"
type_text "To run ProxMenux, simply execute this command in the console or terminal:"
echo -e "${YWB} menu${CL}"
echo
#chmod +x "$EMERGENCY_FILE"
}
####################################################
show_installation_options() {
local current_install_type
current_install_type=$(check_existing_installation)
local menu_title="ProxMenux Installation"
local menu_text="Choose installation type:"
if [ "$current_install_type" != "none" ]; then
case "$current_install_type" in
"translation")
menu_title="ProxMenux Update - Translation Version Detected"
;;
"normal")
menu_title="ProxMenux Update - Normal Version Detected"
;;
"unknown")
menu_title="ProxMenux Update - Existing Installation Detected"
;;
esac
fi
INSTALL_TYPE=$(whiptail --backtitle "ProxMenux" --title "$menu_title" --menu "\n$menu_text" 14 70 2 \
"1" "Normal Version (English only)" \
"2" "Translation Version (Multi-language support)" 3>&1 1>&2 2>&3)
if [ -z "$INSTALL_TYPE" ]; then
msg_warn "Installation cancelled."
exit 1
fi
# For new installations, show confirmation with details
if [ "$current_install_type" = "none" ]; then
if ! show_installation_confirmation "$INSTALL_TYPE"; then
msg_warn "Installation cancelled."
exit 1
fi
fi
if ! handle_installation_change "$current_install_type" "$INSTALL_TYPE"; then
msg_warn "Installation cancelled."
exit 1
fi
}
install_proxmenu() {
show_installation_options
case "$INSTALL_TYPE" in
"1")
show_proxmenux_logo
msg_title "Installing ProxMenux - Normal Version"
install_normal_version
;;
"2")
show_proxmenux_logo
msg_title "Installing ProxMenux - Translation Version"
install_translation_version
;;
*)
msg_error "Invalid option selected."
exit 1
;;
esac
msg_title "$(translate "ProxMenux has been installed successfully")"
echo -ne "${GN}"
type_text "$(translate "To run ProxMenux, simply execute this command in the console or terminal:")"
echo -e "${YWB} menu${CL}"
echo
}
# Main execution ==========================================
if [ "$(id -u)" -ne 0 ]; then
msg_error "This script must be run as root."
exit 1
fi
clear
show_proxmenux_logo
echo
echo -e "${BOLD}${YW}To function correctly, ProxMenux needs to install the following components:${CL}"
echo -e "${TAB}- whiptail (if not already installed)"
echo -e "${TAB}- curl (if not already installed)"
echo -e "${TAB}- jq (if not already installed)"
echo -e "${TAB}- Python 3 (if not already installed)"
echo -e "${TAB}- Virtual environment for Google Translate"
echo -e "${TAB}- ProxMenux scripts and configuration files"
echo
read -p "Do you want to proceed with the installation? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]
then
install_proxmenu
else
msg_warn "Installation cancelled."
exit 1
fi
cleanup_corrupted_files
install_proxmenu

View File

@@ -1,4 +1,18 @@
{
"ProxMenux has been installed successfully": {
"es": "ProxMenux se ha instalado correctamente.",
"fr": "ProxMenux a été installé avec succès.",
"de": "ProxMenux wurde erfolgreich installiert.",
"it": "ProxMenux è stato installato correttamente.",
"pt": "ProxMenux foi instalado com sucesso."
},
"To run ProxMenux, simply execute this command in the console or terminal:": {
"es": "Para ejecutar ProxMenux, simplemente ejecute este comando en la consola o terminal:",
"fr": "Pour exécuter ProxMenux, lancez simplement cette commande dans la console ou le terminal :",
"de": "Um ProxMenux zu starten, führen Sie diesen Befehl einfach in der Konsole oder im Terminal aus:",
"it": "Per avviare ProxMenux, esegui semplicemente questo comando nel terminale:",
"pt": "Para executar o ProxMenux, basta executar este comando no console ou terminal:"
},
"Language changed to": {
"es": "Idioma cambiado a",
"fr": "Langue changée en",
@@ -34,12 +48,12 @@
"it": "Dischi e archiviazione",
"pt": "Discos e armazenamento"
},
"Network": {
"es": "Red",
"fr": "Réseau",
"de": "Netzwerk",
"it": "Rete",
"pt": "Rede"
"Network Management": {
"es": "Gestión de Red",
"fr": "Gestion de Réseau",
"de": "Netzwerkmanagement",
"it": "Gestione della Rete",
"pt": "Gestão de Rede"
},
"Settings": {
"es": "Configuración",
@@ -223,12 +237,12 @@
"it": "Hardware: GPUs e Coral-TPU",
"pt": "Hardware: GPUs e Coral-TPU"
},
"Essential Proxmox VE Helper-Scripts": {
"es": "Esenciales Proxmox VE Helper-Scripts",
"fr": "Essentiels Proxmox VE Helper-Scripts",
"de": "Essentielle Proxmox VE Helper-Scriptse",
"it": "Essenziali Proxmox VE Helper-Scripts",
"pt": "Essenciais Proxmox VE Helper-Scripts"
"Proxmox VE Helper Scripts": {
"es": "Proxmox VE Helper Scripts",
"fr": "Proxmox VE Helper Scripts",
"de": "Proxmox VE Helper Scriptse",
"it": "Proxmox VE Helper Scripts",
"pt": "Proxmox VE Helper Scripts"
},
"Generating automatic translations...": {
"es": "Generando traducciones...",
@@ -889,7 +903,11 @@
"es": "Comandos de ayuda e información"
},
"Create VM from template or script": {
"es": "Crear VM a partir de plantilla o script"
"es": "Crear VM a partir de plantilla o script",
"fr": "Créer une VM à partir dun modèle ou dun script",
"de": "VM aus Vorlage oder Skript erstellen",
"it": "Crea VM da template o script",
"pt": "Criar VM a partir de modelo ou script"
},
"Disk and Storage Manager": {
"es": "Administrador de disco y almacenamiento"
@@ -1352,5 +1370,544 @@
},
"Manage and inspect VM disk images": {
"es": "Administrar e inspeccionar imágenes de disco de VM"
},
"Utilities and Tools": {
"es": "Utilidades y herramientas",
"fr": "Utilitaires et outils",
"de": "Dienstprogramme und Tools"
},
"Utilities Menu": {
"es": "Menú de utilidades",
"fr": "Menu des utilitaires",
"de": "Dienstprogramm-Menü"
},
"UUp Dump ISO creator Custom": {
"es": "Creador personalizado de ISOs UUP Dump",
"fr": "Créateur personnalisé dISO UUP Dump",
"de": "Benutzerdefinierter UUP Dump ISO-Ersteller"
},
"System Utilities Installer": {
"es": "Instalador de utilidades del sistema",
"fr": "Programme dinstallation des utilitaires système",
"de": "Systemdienstprogramme-Installer"
},
"Proxmox System Update": {
"es": "Actualización del sistema Proxmox",
"fr": "Mise à jour du système Proxmox",
"de": "Proxmox-Systemaktualisierung"
},
"Enter destination path for ISO file": {
"es": "Introduce la ruta de destino para el archivo ISO",
"fr": "Indiquez le chemin de destination du fichier ISO",
"de": "Geben Sie den Zielpfad für die ISO-Datei ein"
},
"Paste the UUP Dump URL here": {
"es": "Pega aquí la URL de UUP Dump",
"fr": "Collez ici lURL de UUP Dump",
"de": "Fügen Sie hier die UUP Dump-URL ein"
},
"Utilities Installation Menu": {
"es": "Menú de instalación de utilidades",
"fr": "Menu dinstallation des utilitaires",
"de": "Installationsmenü für Dienstprogramme"
},
"Select an option": {
"es": "Selecciona una opción",
"fr": "Sélectionnez une option",
"de": "Wählen Sie eine Option"
},
"Custom selection": {
"es": "Selección personalizada",
"fr": "Sélection personnalisée",
"de": "Benutzerdefinierte Auswahl"
},
"Install ALL utilities": {
"es": "Instalar todas las utilidades",
"fr": "Installer tous les utilitaires",
"de": "Alle Dienstprogramme installieren"
},
"Install basic utilities": {
"es": "Instalar utilidades básicas",
"fr": "Installer les utilitaires de base",
"de": "Basis-Dienstprogramme installieren"
},
"Install development tools": {
"es": "Instalar herramientas de desarrollo",
"fr": "Installer les outils de développement",
"de": "Entwicklungstools installieren"
},
"Install compression tools": {
"es": "Instalar herramientas de compresión",
"fr": "Installer les outils de compression",
"de": "Komprimierungstools installieren"
},
"Install terminal multiplexers": {
"es": "Instalar multiplexores de terminal",
"fr": "Installer les multiplexeurs de terminal",
"de": "Terminal-Multiplexer installieren"
},
"Install analysis tools": {
"es": "Instalar herramientas de análisis",
"fr": "Installer les outils danalyse",
"de": "Analysetools installieren"
},
"Install network tools": {
"es": "Instalar herramientas de red",
"fr": "Installer les outils réseau",
"de": "Netzwerktools installieren"
},
"Verify installations": {
"es": "Verificar instalaciones",
"fr": "Vérifier les installations",
"de": "Installationen überprüfen"
},
"Return to main menu": {
"es": "Volver al menú principal",
"fr": "Retour au menu principal",
"de": "Zurück zum Hauptmenü"
},
"Select utilities to install": {
"es": "Selecciona las utilidades a instalar",
"fr": "Sélectionnez les utilitaires à installer",
"de": "Wählen Sie die zu installierenden Dienstprogramme aus"
},
"Use SPACE to select/deselect, ENTER to confirm": {
"es": "Usa ESPACIO para seleccionar/deseleccionar, ENTER para confirmar",
"fr": "Utilisez ESPACE pour sélectionner/désélectionner, ENTRÉE pour confirmer",
"de": "Mit LEERTASTE auswählen/abwählen, mit ENTER bestätigen"
},
"No Selection": {
"es": "Sin selección",
"fr": "Aucune sélection",
"de": "Keine Auswahl"
},
"No utilities were selected": {
"es": "No se seleccionó ninguna utilidad",
"fr": "Aucun utilitaire sélectionné",
"de": "Es wurden keine Dienstprogramme ausgewählt"
},
"This script will update your Proxmox VE system with advanced options:": {
"es": "Este script actualizará su sistema Proxmox VE con opciones avanzadas:",
"fr": "Ce script va mettre à jour votre système Proxmox VE avec des options avancées :",
"de": "Dieses Skript aktualisiert Ihr Proxmox VE-System mit erweiterten Optionen:"
},
"Repairs and optimizes repositories": {
"es": "Repara y optimiza los repositorios",
"fr": "Répare et optimise les dépôts",
"de": "Repariert und optimiert die Paketquellen"
},
"Cleans duplicate or conflicting sources": {
"es": "Limpia fuentes duplicadas o conflictivas",
"fr": "Nettoie les sources dupliquées ou conflictuelles",
"de": "Bereinigt doppelte oder widersprüchliche Quellen"
},
"Switches to the free no-subscription repository": {
"es": "Cambia al repositorio gratuito sin suscripción",
"fr": "Passe au dépôt gratuit sans abonnement",
"de": "Wechselt zum kostenlosen Repository ohne Abonnement"
},
"Updates all Proxmox and Debian packages": {
"es": "Actualiza todos los paquetes de Proxmox y Debian",
"fr": "Met à jour tous les paquets Proxmox et Debian",
"de": "Aktualisiert alle Proxmox- und Debian-Pakete"
},
"Installs essential packages if missing": {
"es": "Instala los paquetes esenciales si faltan",
"fr": "Installe les paquets essentiels sils manquent",
"de": "Installiert fehlende essenzielle Pakete"
},
"Checks for LVM and storage issues": {
"es": "Comprueba problemas de LVM y almacenamiento",
"fr": "Vérifie les problèmes LVM et de stockage",
"de": "Überprüft LVM- und Speicherprobleme"
},
"Performs automatic cleanup after updating": {
"es": "Realiza una limpieza automática tras la actualización",
"fr": "Effectue un nettoyage automatique après la mise à jour",
"de": "Führt eine automatische Bereinigung nach der Aktualisierung durch"
},
"Do you want to proceed and run the Proxmox System Update?": {
"es": "¿Quieres continuar y ejecutar la actualización del sistema Proxmox?",
"fr": "Voulez-vous continuer et lancer la mise à jour du système Proxmox ?",
"de": "Möchten Sie fortfahren und die Proxmox-Systemaktualisierung ausführen?"
},
"Update Proxmox VE Appliance Manager": {
"es": "Actualizar Proxmox VE Appliance Manager",
"fr": "Mettre à jour Proxmox VE Appliance Manager",
"de": "Proxmox VE Appliance Manager aktualisieren",
"it": "Aggiorna Proxmox VE Appliance Manager",
"pt": "Atualizar o Proxmox VE Appliance Manager"
},
"Post-Installation Options": {
"es": "Ajustes post-instalación",
"fr": "Options post-installation",
"de": "Optionen nach der Installation",
"it": "Opzioni post-installazione",
"pt": "Opções pós-instalação"
},
"Automated post-installation script": {
"es": "Script post-instalación automatico",
"fr": "Script post-installation automatisé",
"de": "Automatisches Post-Installationsskript",
"it": "Script post-installazione automatico",
"pt": "Script pós-instalação automatizado"
},
"Customizable post-installation script": {
"es": "Script post-instalación personalizable",
"fr": "Script post-installation personnalisable",
"de": "Anpassbares Post-Installationsskript",
"it": "Script post-installazione personalizzabile",
"pt": "Script pós-instalação personalizável"
},
"Uninstall optimizations": {
"es": "Desinstalar optimizaciones",
"fr": "Désinstaller les optimisations",
"de": "Optimierungen deinstallieren",
"it": "Disinstalla ottimizzazioni",
"pt": "Desinstalar otimizações"
},
"Community Scripts": {
"es": "Scripts comunitarios",
"fr": "Scripts communautaires",
"de": "Community-Skripte",
"it": "Script della comunità",
"pt": "Scripts da comunidade"
},
"Xshok-proxmox Post install": {
"es": "Pos-tinstalación Xshok-Proxmox",
"fr": "Post-installation Xshok-Proxmox",
"de": "Xshok-Proxmox-Post-Installation",
"it": "Post-installazione Xshok-Proxmox",
"pt": "Pós-instalação Xshok-Proxmox"
},
"Post-Installation Scripts": {
"es": "Scripts de post-instalación",
"fr": "Scripts de post-installation",
"de": "Post-Installationsskripte",
"it": "Script di post-installazione",
"pt": "Scripts de pós-instalação"
},
"Select a post-installation script:": {
"es": "Seleccione un script de post-instalación:",
"fr": "Sélectionnez un script de post-installation :",
"de": "Wählen Sie ein Post-Installationsskript aus:",
"it": "Seleziona uno script di post-installazione:",
"pt": "Selecione um script de pós-instalação:"
},
"The automated post-install script performs the following system adjustments": {
"es": "El script automatizado de postinstalación realiza los siguientes ajustes en el sistema",
"fr": "Le script automatisé de post-installation effectue les ajustements système suivants",
"de": "Das automatische Post-Installationsskript führt folgende Systemanpassungen durch",
"it": "Lo script automatico di post-installazione esegue i seguenti aggiustamenti di sistema",
"pt": "O script automatizado de pós-instalação executa os seguintes ajustes no sistema"
},
"Configure": {
"es": "Configurar",
"fr": "Configurer",
"de": "Konfigurieren",
"it": "Configurare",
"pt": "Configurar"
},
"and upgrade system": {
"es": "y actualizar el sistema",
"fr": "et mettre à niveau le système",
"de": "und das System aktualisieren",
"it": "e aggiornare il sistema",
"pt": "e atualizar o sistema"
},
"Remove": {
"es": "Eliminar",
"fr": "Supprimer",
"de": "Entfernen",
"it": "Rimuovere",
"pt": "Remover"
},
"from Proxmox interface": {
"es": "de la interfaz de Proxmox",
"fr": "de l'interface Proxmox",
"de": "aus der Proxmox-Oberfläche",
"it": "dall'interfaccia Proxmox",
"pt": "da interface Proxmox"
},
"Optimize": {
"es": "Optimizar",
"fr": "Optimiser",
"de": "Optimieren",
"it": "Ottimizzare",
"pt": "Otimizar"
},
"and": {
"es": "y",
"fr": "et",
"de": "und",
"it": "e",
"pt": "e"
},
"Enhance": {
"es": "Mejorar",
"fr": "Améliorer",
"de": "Verbessern",
"it": "Migliorare",
"pt": "Melhorar"
},
"performance and": {
"es": "el rendimiento y",
"fr": "les performances et",
"de": "die Leistung und",
"it": "le prestazioni e",
"pt": "o desempenho e"
},
"Install": {
"es": "Instalar",
"fr": "Installer",
"de": "Installieren",
"it": "Installare",
"pt": "Instalar"
},
"for": {
"es": "para",
"fr": "pour",
"de": "für",
"it": "per",
"pt": "para"
},
"Apply": {
"es": "Aplicar",
"fr": "Appliquer",
"de": "Anwenden",
"it": "Applicare",
"pt": "Aplicar"
},
"optimizations": {
"es": "optimizaciones",
"fr": "optimisations",
"de": "Optimierungen",
"it": "ottimizzazioni",
"pt": "otimizações"
},
"Do you want to run this script?": {
"es": "¿Desea ejecutar este script?",
"fr": "Voulez-vous exécuter ce script ?",
"de": "Möchten Sie dieses Skript ausführen?",
"it": "Vuoi eseguire questo script?",
"pt": "Deseja executar este script?"
},
"Automated post-install Script": {
"es": "Script automatizado de post-instalación",
"fr": "Script automatisé de post-installation",
"de": "Automatisches Post-Installationsskript",
"it": "Script automatico di post-installazione",
"pt": "Script automatizado de pós-instalação"
},
"Cancelled by user.": {
"es": "Cancelado por el usuario.",
"fr": "Annulé par l'utilisateur.",
"de": "Vom Benutzer abgebrochen.",
"it": "Annullato dall'utente.",
"pt": "Cancelado pelo usuário."
},
"No optimizations detected to uninstall.": {
"es": "No se detectaron optimizaciones para desinstalar.",
"fr": "Aucune optimisation détectée à désinstaller.",
"de": "Keine zu deinstallierenden Optimierungen erkannt.",
"it": "Nessuna ottimizzazione rilevata da disinstallare.",
"pt": "Nenhuma otimização detectada para desinstalar."
},
"Uninstall Optimizations": {
"es": "Desinstalar optimizaciones",
"fr": "Désinstaller les optimisations",
"de": "Optimierungen deinstallieren",
"it": "Disinstalla ottimizzazioni",
"pt": "Desinstalar otimizações"
},
"Select optimizations to uninstall:": {
"es": "Seleccione optimizaciones para desinstalar:",
"fr": "Sélectionnez les optimisations à désinstaller :",
"de": "Wählen Sie die zu deinstallierenden Optimierungen aus:",
"it": "Seleziona le ottimizzazioni da disinstallare:",
"pt": "Selecione as otimizações para desinstalar:"
},
"Install and configure Log2RAM": {
"es": "Instalar y configurar Log2Ram",
"fr": "Installer et configurer Log2Ram",
"de": "Log2Ram installieren und konfigurieren",
"it": "Installare e configurare Log2Ram",
"pt": "Instalar e configurar o Log2Ram"
},
"Select System Type": {
"es": "Seleccionar tipo de sistema",
"fr": "Sélectionner le type de système",
"de": "Systemtyp auswählen",
"it": "Seleziona il tipo di sistema",
"pt": "Selecionar o tipo de sistema"
},
"Choose the type of virtual system to install:": {
"es": "Elija el tipo de sistema virtual a instalar:",
"fr": "Choisissez le type de système virtuel à installer :",
"de": "Wählen Sie den zu installierenden virtuellen Systemtyp:",
"it": "Scegli il tipo di sistema virtuale da installare:",
"pt": "Escolha o tipo de sistema virtual a instalar:"
},
"Create": {
"es": "Crear",
"fr": "Créer",
"de": "Erstellen",
"it": "Crea",
"pt": "Criar"
},
"Other Prebuilt Linux VMs": {
"es": "Otras máquinas virtuales Linux preinstaladas",
"fr": "Autres machines virtuelles Linux préinstallées",
"de": "Weitere vorgefertigte Linux-VMs",
"it": "Altre VM Linux preconfigurate",
"pt": "Outras VMs Linux pré-construídas"
},
"Select one of the ready-to-run Linux VMs:": {
"es": "Seleccione una de las máquinas virtuales Linux listas para usar:",
"fr": "Sélectionnez l'une des machines virtuelles Linux prêtes à l'emploi :",
"de": "Wählen Sie eine der einsatzbereiten Linux-VMs aus:",
"it": "Seleziona una delle VM Linux pronte all'uso:",
"pt": "Selecione uma das VMs Linux prontas para usar:"
},
"Disk and Storage Manager Menu": {
"es": "Menú de gestión de discos y almacenamiento",
"fr": "Menu du gestionnaire de disques et de stockage",
"de": "Menü für Festplatten- und Speicherverwaltung",
"it": "Menu di gestione disco e storage",
"pt": "Menu de gerenciamento de disco e armazenamento"
},
"Add Disk": {
"es": "Agregar disco",
"fr": "Ajouter un disque",
"de": "Festplatte hinzufügen",
"it": "Aggiungi disco",
"pt": "Adicionar disco"
},
"to a VM": {
"es": "a una VM",
"fr": "à une VM",
"de": "zu einer VM",
"it": "a una VM",
"pt": "a uma VM"
},
"to a LXC": {
"es": "a un LXC",
"fr": "à un LXC",
"de": "zu einem LXC",
"it": "a un LXC",
"pt": "a um LXC"
},
"Installation type:": {
"es": "Tipo de instalación:",
"fr": "Type d'installation :",
"de": "Installationstyp:",
"it": "Tipo di installazione:",
"pt": "Tipo de instalação:"
},
"Translation Version (Multi-language support)": {
"es": "Versión de traducción (soporte multilenguaje)",
"fr": "Version traduction (prise en charge multilingue)",
"de": "Übersetzungsversion (Mehrsprachige Unterstützung)",
"it": "Versione traduzione (supporto multilingue)",
"pt": "Versão de tradução (suporte multilíngue)"
},
"This script will apply the following optimizations and advanced adjustments to your Proxmox VE server": {
"es": "Este script aplicará las siguientes optimizaciones y ajustes avanzados a su servidor Proxmox VE",
"fr": "Ce script appliquera les optimisations et ajustements avancés suivants à votre serveur Proxmox VE",
"de": "Dieses Skript wendet die folgenden Optimierungen und erweiterten Anpassungen auf Ihren Proxmox VE-Server an",
"it": "Questo script applicherà le seguenti ottimizzazioni e regolazioni avanzate al tuo server Proxmox VE",
"pt": "Este script aplicará as seguintes otimizações e ajustes avançados ao seu servidor Proxmox VE"
},
"and upgrade the system (disables the enterprise repo)": {
"es": "y actualiza el sistema (desactiva el repositorio enterprise).",
"fr": "et mettra à niveau le système (désactive le dépôt entreprise)",
"de": "und aktualisiert das System (deaktiviert das Enterprise-Repository)",
"it": "e aggiornerà il sistema (disabilita il repository enterprise)",
"pt": "e atualizará o sistema (desativa o repositório enterprise)"
},
"Optionally remove": {
"es": "Opcionalmente eliminara",
"fr": "Supprimer en option",
"de": "Optional entfernen",
"it": "Rimuovi opzionalmente",
"pt": "Remover opcionalmente"
},
"from Proxmox web interface (you will be asked)": {
"es": "de la interfaz web de Proxmox.",
"fr": "de l'interface web Proxmox.",
"de": "aus der Proxmox-Weboberfläche.",
"it": "dall'interfaccia web di Proxmox.",
"pt": "da interface web do Proxmox."
},
"for better performance and stability": {
"es": "para un mejor rendimiento y estabilidad",
"fr": "pour de meilleures performances et stabilité",
"de": "für bessere Leistung und Stabilität",
"it": "per migliori prestazioni e stabilità",
"pt": "para melhor desempenho e estabilidade"
},
"Install and configure": {
"es": "Instala y configura",
"fr": "Installer et configurer",
"de": "Installieren und konfigurieren",
"it": "Installare e configurare",
"pt": "Instalar e configurar"
},
"(only on SSD/NVMe) to protect your disk": {
"es": "(solo para SSD/NVMe) para proteger su disco",
"fr": "(uniquement sur SSD/NVMe) pour protéger votre disque",
"de": "(nur auf SSD/NVMe) zum Schutz Ihrer Festplatte",
"it": "(solo su SSD/NVMe) per proteggere il tuo disco",
"pt": "(apenas em SSD/NVMe) para proteger o seu disco"
},
"Improve log rotation and limit log size to save space and extend disk life": {
"es": "Mejora la rotación de logs y limita el tamaño para ahorrar espacio y prolongar la vida útil del disco",
"fr": "Améliorer la rotation des logs et limiter leur taille pour économiser de l'espace et prolonger la durée de vie du disque",
"de": "Verbessern Sie die Logrotation und begrenzen Sie die Loggröße, um Speicherplatz zu sparen und die Lebensdauer der Festplatte zu verlängern",
"it": "Migliora la rotazione dei log e limita la dimensione per risparmiare spazio e prolungare la vita del disco",
"pt": "Melhorar a rotação de logs e limitar o tamanho para economizar espaço e prolongar a vida útil do disco"
},
"Increase file and process limits for advanced workloads": {
"es": "Aumenta los límites de archivos y procesos para cargas avanzadas",
"fr": "Augmenter les limites de fichiers et de processus pour les charges de travail avancées",
"de": "Datei- und Prozesslimits für anspruchsvolle Workloads erhöhen",
"it": "Aumentare i limiti di file e processi per carichi di lavoro avanzati",
"pt": "Aumentar os limites de arquivos e processos para cargas de trabalho avançadas"
},
"Set up time synchronization and entropy generation": {
"es": "Configura la sincronización horaria y la generación de entropía",
"fr": "Configurer la synchronisation de l'heure et la génération d'entropie",
"de": "Zeitsynchronisation und Entropieerzeugung einrichten",
"it": "Configurare la sincronizzazione dell'orario e la generazione di entropia",
"pt": "Configurar a sincronização do tempo e a geração de entropia"
},
"Add color prompts and useful aliases to the terminal environment": {
"es": "Añade avisos de color y alias útiles al entorno de terminal",
"fr": "Ajouter des invites colorées et des alias utiles à l'environnement terminal",
"de": "Farbige Prompts und nützliche Aliase zur Terminalumgebung hinzufügen",
"it": "Aggiungere prompt colorati e alias utili all'ambiente terminale",
"pt": "Adicionar prompts coloridos e aliases úteis ao ambiente do terminal"
},
"All changes are reversible using the ProxMenux uninstaller.": {
"es": "Todos los cambios son reversibles usando el desinstalador de ProxMenux.",
"fr": "Toutes les modifications sont réversibles avec le désinstalleur de ProxMenux.",
"de": "Alle Änderungen sind mit dem ProxMenux-Deinstallationsprogramm reversibel.",
"it": "Tutte le modifiche sono reversibili utilizzando il programma di disinstallazione di ProxMenux.",
"pt": "Todas as alterações são reversíveis usando o desinstalador do ProxMenux."
},
"Do you want to apply these optimizations now?": {
"es": "¿Desea aplicar estas optimizaciones ahora?",
"fr": "Voulez-vous appliquer ces optimisations maintenant ?",
"de": "Möchten Sie diese Optimierungen jetzt anwenden?",
"it": "Vuoi applicare queste ottimizzazioni ora?",
"pt": "Deseja aplicar essas otimizações agora?"
},
"Automated Post-Install Script": {
"es": "Script automatizado de post-instalación",
"fr": "Script automatisé de post-installation",
"de": "Automatisiertes Post-Installationsskript",
"it": "Script automatico post-installazione",
"pt": "Script automatizado pós-instalação"
}
}

5113
json/helpers_cache.json Normal file

File diff suppressed because it is too large Load Diff

58
menu
View File

@@ -6,8 +6,8 @@
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 28/01/2025
# Version : 1.1
# Last Updated: 04/07/2025
# ==========================================================
# Description:
# This script serves as the main entry point for ProxMenux,
@@ -29,6 +29,7 @@
# for managing Proxmox VE using ProxMenux.
# ==========================================================
# Configuration ============================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
@@ -41,78 +42,37 @@ VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
# ==========================================================
#show_proxmenux_logo
# Initialize language configuration
initialize_config() {
show_proxmenux_logo
# Check if config file exists and has language field
if [ ! -f "$CONFIG_FILE" ] || [ -z "$(jq -r '.language // empty' "$CONFIG_FILE")" ]; then
LANGUAGE=$(whiptail --title "$(translate "Select Language")" --menu "$(translate "Choose a language for the menu:")" 20 60 12 \
"en" "$(translate "English (Recommended)")" \
"es" "$(translate "Spanish")" \
"fr" "$(translate "French")" \
"de" "$(translate "German")" \
"it" "$(translate "Italian")" \
"pt" "$(translate "Portuguese")" 3>&1 1>&2 2>&3)
if [ -z "$LANGUAGE" ]; then
msg_error "$(translate "No language selected. Exiting.")"
exit 1
fi
if [ -f "$CONFIG_FILE" ]; then
# Update existing config file with new language
tmp=$(mktemp)
jq --arg lang "$LANGUAGE" '. + {language: $lang}' "$CONFIG_FILE" > "$tmp" && mv "$tmp" "$CONFIG_FILE"
else
# Create new config file if it doesn't exist
echo "{\"language\": \"$LANGUAGE\"}" > "$CONFIG_FILE"
fi
msg_ok "$(translate "Initial language set to:") $LANGUAGE"
fi
}
# =========================================================
check_updates() {
local INSTALL_SCRIPT="$BASE_DIR/install_proxmenux.sh"
# Fetch the remote version
local REMOTE_VERSION
REMOTE_VERSION=$(curl -fsSL "$REPO_URL/version.txt" | head -n 1)
# Exit silently if unable to fetch the remote version
if [ -z "$REMOTE_VERSION" ]; then
return 0
fi
# Read the local version
local LOCAL_VERSION
LOCAL_VERSION=$(head -n 1 "$LOCAL_VERSION_FILE")
# If the local version matches the remote version, no update is needed
[ "$LOCAL_VERSION" = "$REMOTE_VERSION" ] && return 0
# Prompt the user for update confirmation
if whiptail --title "$(translate "Update Available")" \
--yesno "$(translate "New version available") ($REMOTE_VERSION)\n\n$(translate "Do you want to update now?")" \
10 60 --defaultno; then
msg_warn "$(translate "Starting ProxMenu update...")"
# Download the installation script
if wget -qO "$INSTALL_SCRIPT" "$REPO_URL/install_proxmenux.sh"; then
chmod +x "$INSTALL_SCRIPT"
# Execute the script directly in the current environment
source "$INSTALL_SCRIPT"
fi
else
msg_warn "$(translate "Update postponed. You can update later from the menu.")"
@@ -126,8 +86,6 @@ main_menu() {
}
# Main flow
initialize_config
load_language
initialize_cache
check_updates

View File

@@ -0,0 +1,854 @@
#!/bin/bash
# ==========================================================
# ProxMenux - Complete Post-Installation Script with Registration
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 06/07/2025
# ==========================================================
# Description:
#
# The script performs system optimizations including:
# - Repository configuration and system upgrades
# - Subscription banner removal and UI enhancements
# - Advanced memory management and kernel optimizations
# - Network stack tuning and security hardening
# - Storage optimizations including log2ram for SSD protection
# - System limits increases and entropy generation improvements
# - Journald and logrotate optimizations for better log management
# - Security enhancements including RPC disabling and time synchronization
# - Bash environment customization and system monitoring setup
#
# Key Features:
# - Zero-interaction automation: Runs completely unattended
# - Intelligent hardware detection: Automatically detects SSD/NVMe for log2ram
# - RAM-aware configurations: Adjusts settings based on available system memory
# - Comprehensive error handling: Robust installation with fallback mechanisms
# - Registration system: Tracks installed optimizations for easy management
# - Reboot management: Intelligently handles reboot requirements
# - Translation support: Multi-language compatible through ProxMenux framework
# - Rollback compatibility: All optimizations can be reversed using the uninstall script
#
# This script is based on the post-install script cutotomizable
# ==========================================================
# Configuration
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
VENV_PATH="/opt/googletrans-env"
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
# Global variables
OS_CODENAME="$(grep "VERSION_CODENAME=" /etc/os-release | cut -d"=" -f 2 | xargs)"
RAM_SIZE_GB=$(( $(vmstat -s | grep -i "total memory" | xargs | cut -d" " -f 1) / 1024 / 1000))
NECESSARY_REBOOT=0
SCRIPT_TITLE="Customizable post-installation optimization script"
# ==========================================================
# Tool registration system
ensure_tools_json() {
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
}
register_tool() {
local tool="$1"
local state="$2"
ensure_tools_json
jq --arg t "$tool" --argjson v "$state" '.[$t]=$v' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
}
# ==========================================================
lvm_repair_check() {
msg_info "$(translate "Checking and repairing old LVM PV headers (if needed)...")"
pvs_output=$(LC_ALL=C pvs -v 2>&1 | grep "old PV header")
if [ -z "$pvs_output" ]; then
msg_ok "$(translate "No PVs with old headers found.")"
register_tool "lvm_repair" true
return
fi
declare -A vg_map
while read -r line; do
pv=$(echo "$line" | grep -o '/dev/[^ ]*')
vg=$(pvs -o vg_name --noheadings "$pv" | awk '{print $1}')
if [ -n "$vg" ]; then
vg_map["$vg"]=1
fi
done <<< "$pvs_output"
for vg in "${!vg_map[@]}"; do
msg_warn "$(translate "Old PV header(s) found in VG $vg. Updating metadata...")"
vgck --updatemetadata "$vg"
vgchange -ay "$vg"
if [ $? -ne 0 ]; then
msg_warn "$(translate "Metadata update failed for VG $vg. Review manually.")"
else
msg_ok "$(translate "Metadata updated successfully for VG $vg")"
fi
done
msg_ok "$(translate "LVM PV headers check completed")"
}
# ==========================================================
cleanup_duplicate_repos() {
local sources_file="/etc/apt/sources.list"
local temp_file=$(mktemp)
local cleaned_count=0
declare -A seen_repos
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ "$line" =~ ^[[:space:]]*# ]] || [[ -z "$line" ]]; then
echo "$line" >> "$temp_file"
continue
fi
if [[ "$line" =~ ^deb ]]; then
read -r _ url dist components <<< "$line"
local key="${url}_${dist}"
if [[ -v "seen_repos[$key]" ]]; then
echo "# $line" >> "$temp_file"
cleaned_count=$((cleaned_count + 1))
else
echo "$line" >> "$temp_file"
seen_repos[$key]="$components"
fi
else
echo "$line" >> "$temp_file"
fi
done < "$sources_file"
mv "$temp_file" "$sources_file"
chmod 644 "$sources_file"
local pve_files=(/etc/apt/sources.list.d/*proxmox*.list /etc/apt/sources.list.d/*pve*.list)
local pve_content="deb http://download.proxmox.com/debian/pve ${OS_CODENAME} pve-no-subscription"
local pve_public_repo="/etc/apt/sources.list.d/pve-public-repo.list"
local pve_public_repo_exists=false
if [ -f "$pve_public_repo" ] && grep -q "^deb.*pve-no-subscription" "$pve_public_repo"; then
pve_public_repo_exists=true
fi
for file in "${pve_files[@]}"; do
if [ -f "$file" ] && grep -q "^deb.*pve-no-subscription" "$file"; then
if ! $pve_public_repo_exists && [[ "$file" == "$pve_public_repo" ]]; then
sed -i 's/^# *deb/deb/' "$file"
pve_public_repo_exists=true
elif [[ "$file" != "$pve_public_repo" ]]; then
sed -i 's/^deb/# deb/' "$file"
cleaned_count=$((cleaned_count + 1))
fi
fi
done
apt update
}
apt_upgrade() {
NECESSARY_REBOOT=1
if [ -f /etc/apt/sources.list.d/pve-enterprise.list ] && grep -q "^deb" /etc/apt/sources.list.d/pve-enterprise.list; then
msg_info "$(translate "Disabling enterprise Proxmox repository...")"
sed -i "s/^deb/#deb/g" /etc/apt/sources.list.d/pve-enterprise.list
msg_ok "$(translate "Enterprise Proxmox repository disabled")"
fi
if [ -f /etc/apt/sources.list.d/ceph.list ] && grep -q "^deb" /etc/apt/sources.list.d/ceph.list; then
msg_info "$(translate "Disabling enterprise Proxmox Ceph repository...")"
sed -i "s/^deb/#deb/g" /etc/apt/sources.list.d/ceph.list
msg_ok "$(translate "Enterprise Proxmox Ceph repository disabled")"
fi
if [ ! -f /etc/apt/sources.list.d/pve-public-repo.list ] || ! grep -q "pve-no-subscription" /etc/apt/sources.list.d/pve-public-repo.list; then
msg_info "$(translate "Enabling free public Proxmox repository...")"
echo "deb http://download.proxmox.com/debian/pve ${OS_CODENAME} pve-no-subscription" > /etc/apt/sources.list.d/pve-public-repo.list
msg_ok "$(translate "Free public Proxmox repository enabled")"
fi
sources_file="/etc/apt/sources.list"
need_update=false
sed -i 's|ftp.es.debian.org|deb.debian.org|g' "$sources_file"
if grep -q "^deb http://security.debian.org ${OS_CODENAME}-security main contrib" "$sources_file"; then
sed -i "s|^deb http://security.debian.org ${OS_CODENAME}-security main contrib|deb http://security.debian.org/debian-security ${OS_CODENAME}-security main contrib non-free non-free-firmware|" "$sources_file"
msg_ok "$(translate "Replaced security repository with full version")"
need_update=true
fi
if ! grep -q "deb http://security.debian.org/debian-security ${OS_CODENAME}-security" "$sources_file"; then
echo "deb http://security.debian.org/debian-security ${OS_CODENAME}-security main contrib non-free non-free-firmware" >> "$sources_file"
need_update=true
fi
if ! grep -q "deb http://deb.debian.org/debian ${OS_CODENAME} " "$sources_file"; then
echo "deb http://deb.debian.org/debian ${OS_CODENAME} main contrib non-free non-free-firmware" >> "$sources_file"
need_update=true
fi
if ! grep -q "deb http://deb.debian.org/debian ${OS_CODENAME}-updates" "$sources_file"; then
echo "deb http://deb.debian.org/debian ${OS_CODENAME}-updates main contrib non-free non-free-firmware" >> "$sources_file"
need_update=true
fi
msg_ok "$(translate "Debian repositories configured correctly")"
# ===================================================
if [ ! -f /etc/apt/apt.conf.d/no-bookworm-firmware.conf ]; then
msg_info "$(translate "Disabling non-free firmware warnings...")"
echo 'APT::Get::Update::SourceListWarnings::NonFreeFirmware "false";' > /etc/apt/apt.conf.d/no-bookworm-firmware.conf
msg_ok "$(translate "Non-free firmware warnings disabled")"
fi
msg_info "$(translate "Updating package lists...")"
if apt-get update > /dev/null 2>&1; then
msg_ok "$(translate "Package lists updated")"
else
msg_error "$(translate "Failed to update package lists")"
return 1
fi
msg_info "$(translate "Removing conflicting utilities...")"
if /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' purge ntp openntpd systemd-timesyncd > /dev/null 2>&1; then
msg_ok "$(translate "Conflicting utilities removed")"
else
msg_error "$(translate "Failed to remove conflicting utilities")"
fi
msg_info "$(translate "Performing packages upgrade...")"
apt-get install pv -y > /dev/null 2>&1
total_packages=$(apt-get -s dist-upgrade | grep "^Inst" | wc -l)
if [ "$total_packages" -eq 0 ]; then
total_packages=1
fi
msg_ok "$(translate "Packages upgrade successfull")"
tput civis
tput sc
(
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' dist-upgrade 2>&1 | \
while IFS= read -r line; do
if [[ "$line" =~ ^(Setting up|Unpacking|Preparing to unpack|Processing triggers for) ]]; then
package_name=$(echo "$line" | sed -E 's/.*(Setting up|Unpacking|Preparing to unpack|Processing triggers for) ([^ ]+).*/\2/')
[ -z "$package_name" ] && package_name="$(translate "Unknown")"
tput rc
tput ed
row=$(( $(tput lines) - 6 ))
tput cup $row 0; echo "$(translate "Installing packages...")"
tput cup $((row + 1)) 0; echo "──────────────────────────────────────────────"
tput cup $((row + 2)) 0; echo "Package: $package_name"
tput cup $((row + 3)) 0; echo "Progress: [ ] 0%"
tput cup $((row + 4)) 0; echo "──────────────────────────────────────────────"
for i in $(seq 1 10); do
progress=$((i * 10))
tput cup $((row + 3)) 9
printf "[%-50s] %3d%%" "$(printf "#%.0s" $(seq 1 $((progress/2))))" "$progress"
done
fi
done
)
if [ $? -eq 0 ]; then
tput rc
tput ed
msg_ok "$(translate "System upgrade completed")"
fi
msg_info "$(translate "Installing additional Proxmox packages...")"
if /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install zfsutils-linux proxmox-backup-restore-image chrony > /dev/null 2>&1; then
msg_ok "$(translate "Additional Proxmox packages installed")"
else
msg_error "$(translate "Failed to install additional Proxmox packages")"
fi
lvm_repair_check
cleanup_duplicate_repos
msg_ok "$(translate "Proxmox update completed")"
}
# ==========================================================
remove_subscription_banner() {
msg_info "$(translate "Removing Proxmox subscription nag banner...")"
local JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
local GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
local APT_HOOK="/etc/apt/apt.conf.d/no-nag-script"
if [[ ! -f "$APT_HOOK" ]]; then
cat <<'EOF' > "$APT_HOOK"
DPkg::Post-Invoke { "dpkg -V proxmox-widget-toolkit | grep -q '/proxmoxlib\.js$'; if [ $? -eq 1 ]; then { echo 'Removing subscription nag from UI...'; sed -i '/.*data\.status.*{/{s/\!//;s/active/NoMoreNagging/;s/Active/NoMoreNagging/}' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js; rm -f /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz; }; fi"; };
EOF
fi
if [[ -f "$JS_FILE" ]]; then
sed -i '/.*data\.status.*{/{s/\!//;s/active/NoMoreNagging/;s/Active/NoMoreNagging/}' "$JS_FILE"
[[ -f "$GZ_FILE" ]] && rm -f "$GZ_FILE"
touch "$JS_FILE"
fi
apt --reinstall install proxmox-widget-toolkit -y > /dev/null 2>&1
msg_ok "$(translate "Subscription nag banner removed successfully")"
register_tool "subscription_banner" true
}
# ==========================================================
configure_time_sync() {
msg_info "$(translate "Configuring system time settings...")"
this_ip=$(dig +short myip.opendns.com @resolver1.opendns.com)
if [ -z "$this_ip" ]; then
msg_warn "$(translate "Failed to obtain public IP address")"
timezone="UTC"
else
timezone=$(curl -s "https://ipapi.co/${this_ip}/timezone")
if [ -z "$timezone" ]; then
msg_warn "$(translate "Failed to determine timezone from IP address")"
timezone="UTC"
else
msg_ok "$(translate "Found timezone $timezone for IP $this_ip")"
fi
fi
msg_info "$(translate "Enabling automatic time synchronization...")"
if timedatectl set-ntp true; then
msg_ok "$(translate "Time settings configured - Timezone:") $timezone"
register_tool "time_sync" true
else
msg_error "$(translate "Failed to enable automatic time synchronization")"
fi
}
# ==========================================================
skip_apt_languages() {
msg_info "$(translate "Configuring APT to skip downloading additional languages...")"
local default_locale=""
if [ -f /etc/default/locale ]; then
default_locale=$(grep '^LANG=' /etc/default/locale | cut -d= -f2 | tr -d '"')
elif [ -f /etc/environment ]; then
default_locale=$(grep '^LANG=' /etc/environment | cut -d= -f2 | tr -d '"')
fi
default_locale="${default_locale:-en_US.UTF-8}"
local normalized_locale=$(echo "$default_locale" | tr 'A-Z' 'a-z' | sed 's/utf-8/utf8/;s/-/_/')
if ! locale -a | grep -qi "^$normalized_locale$"; then
if ! grep -qE "^${default_locale}[[:space:]]+UTF-8" /etc/locale.gen; then
echo "$default_locale UTF-8" >> /etc/locale.gen
fi
locale-gen "$default_locale" > /dev/null 2>&1
fi
echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/99-disable-translations
msg_ok "$(translate "APT configured to skip additional languages")"
register_tool "apt_languages" true
}
# ==========================================================
optimize_journald() {
msg_info "$(translate "Limiting size and optimizing journald...")"
NECESSARY_REBOOT=1
cat <<EOF > /etc/systemd/journald.conf
[Journal]
Storage=persistent
SplitMode=none
RateLimitInterval=0
RateLimitIntervalSec=0
RateLimitBurst=0
ForwardToSyslog=no
ForwardToWall=yes
Seal=no
Compress=yes
SystemMaxUse=64M
RuntimeMaxUse=60M
MaxLevelStore=warning
MaxLevelSyslog=warning
MaxLevelKMsg=warning
MaxLevelConsole=notice
MaxLevelWall=crit
EOF
systemctl restart systemd-journald.service > /dev/null 2>&1
journalctl --vacuum-size=64M --vacuum-time=1d > /dev/null 2>&1
journalctl --rotate > /dev/null 2>&1
msg_ok "$(translate "Journald optimized - Max size: 64M")"
register_tool "journald" true
}
# ==========================================================
optimize_logrotate() {
msg_info "$(translate "Optimizing logrotate configuration...")"
local logrotate_conf="/etc/logrotate.conf"
local backup_conf="${logrotate_conf}.bak"
if ! grep -q "# ProxMenux optimized configuration" "$logrotate_conf"; then
cp "$logrotate_conf" "$backup_conf"
cat <<EOF > "$logrotate_conf"
# ProxMenux optimized configuration
daily
su root adm
rotate 7
create
compress
size=10M
delaycompress
copytruncate
include /etc/logrotate.d
EOF
systemctl restart logrotate > /dev/null 2>&1
fi
msg_ok "$(translate "Logrotate optimization completed")"
register_tool "logrotate" true
}
# ==========================================================
increase_system_limits() {
msg_info "$(translate "Increasing various system limits...")"
NECESSARY_REBOOT=1
cat > /etc/sysctl.d/99-maxwatches.conf << EOF
# ProxMenux configuration
fs.inotify.max_user_watches = 1048576
fs.inotify.max_user_instances = 1048576
fs.inotify.max_queued_events = 1048576
EOF
cat > /etc/security/limits.d/99-limits.conf << EOF
# ProxMenux configuration
* soft nproc 1048576
* hard nproc 1048576
* soft nofile 1048576
* hard nofile 1048576
root soft nproc unlimited
root hard nproc unlimited
root soft nofile unlimited
root hard nofile unlimited
EOF
cat > /etc/sysctl.d/99-maxkeys.conf << EOF
# ProxMenux configuration
kernel.keys.root_maxkeys=1000000
kernel.keys.maxkeys=1000000
EOF
for file in /etc/systemd/system.conf /etc/systemd/user.conf; do
if ! grep -q "^DefaultLimitNOFILE=" "$file"; then
echo "DefaultLimitNOFILE=256000" >> "$file"
fi
done
for file in /etc/pam.d/common-session /etc/pam.d/runuser-l; do
if ! grep -q "^session required pam_limits.so" "$file"; then
echo 'session required pam_limits.so' >> "$file"
fi
done
if ! grep -q "ulimit -n 256000" /root/.profile; then
echo "ulimit -n 256000" >> /root/.profile
fi
cat > /etc/sysctl.d/99-swap.conf << EOF
# ProxMenux configuration
vm.swappiness = 10
vm.vfs_cache_pressure = 100
EOF
cat > /etc/sysctl.d/99-fs.conf << EOF
# ProxMenux configuration
fs.nr_open = 12000000
fs.file-max = 9223372036854775807
fs.aio-max-nr = 1048576
EOF
msg_ok "$(translate "System limits increase completed.")"
register_tool "system_limits" true
}
# ==========================================================
configure_entropy() {
msg_info "$(translate "Configuring entropy generation to prevent slowdowns...")"
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install haveged > /dev/null 2>&1
cat <<EOF > /etc/default/haveged
# -w sets low entropy watermark (in bits)
DAEMON_ARGS="-w 1024"
EOF
systemctl daemon-reload > /dev/null 2>&1
systemctl enable haveged > /dev/null 2>&1
msg_ok "$(translate "Entropy generation configuration completed")"
register_tool "entropy" true
}
# ==========================================================
optimize_memory_settings() {
msg_info "$(translate "Optimizing memory settings...")"
NECESSARY_REBOOT=1
cat <<EOF > /etc/sysctl.d/99-memory.conf
# Balanced Memory Optimization
vm.swappiness = 10
vm.dirty_ratio = 15
vm.dirty_background_ratio = 5
vm.overcommit_memory = 1
vm.max_map_count = 65530
EOF
if [ -f /proc/sys/vm/compaction_proactiveness ]; then
echo "vm.compaction_proactiveness = 20" >> /etc/sysctl.d/99-memory.conf
fi
msg_ok "$(translate "Memory optimization completed.")"
register_tool "memory_settings" true
}
# ==========================================================
configure_kernel_panic() {
msg_info "$(translate "Configuring kernel panic behavior")"
NECESSARY_REBOOT=1
cat <<EOF > /etc/sysctl.d/99-kernelpanic.conf
# Enable restart on kernel panic, kernel oops and hardlockup
kernel.core_pattern = /var/crash/core.%t.%p
kernel.panic = 10
kernel.panic_on_oops = 1
kernel.hardlockup_panic = 1
EOF
msg_ok "$(translate "Kernel panic behavior configuration completed")"
register_tool "kernel_panic" true
}
# ==========================================================
force_apt_ipv4() {
msg_info "$(translate "Configuring APT to use IPv4...")"
echo 'Acquire::ForceIPv4 "true";' > /etc/apt/apt.conf.d/99-force-ipv4
msg_ok "$(translate "APT IPv4 configuration completed")"
register_tool "apt_ipv4" true
}
# ==========================================================
apply_network_optimizations() {
msg_info "$(translate "Optimizing network settings...")"
NECESSARY_REBOOT=1
cat <<EOF > /etc/sysctl.d/99-network.conf
net.core.netdev_max_backlog=8192
net.core.optmem_max=8192
net.core.rmem_max=16777216
net.core.somaxconn=8151
net.core.wmem_max=16777216
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.all.log_martians = 0
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.conf.default.log_martians = 0
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.default.secure_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.ip_local_port_range=1024 65535
net.ipv4.tcp_base_mss = 1024
net.ipv4.tcp_challenge_ack_limit = 999999999
net.ipv4.tcp_fin_timeout=10
net.ipv4.tcp_keepalive_intvl=30
net.ipv4.tcp_keepalive_probes=3
net.ipv4.tcp_keepalive_time=240
net.ipv4.tcp_limit_output_bytes=65536
net.ipv4.tcp_max_syn_backlog=8192
net.ipv4.tcp_max_tw_buckets = 1440000
net.ipv4.tcp_mtu_probing = 1
net.ipv4.tcp_rfc1337=1
net.ipv4.tcp_rmem=8192 87380 16777216
net.ipv4.tcp_sack=1
net.ipv4.tcp_slow_start_after_idle=0
net.ipv4.tcp_syn_retries=3
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_tw_recycle = 0
net.ipv4.tcp_tw_reuse = 0
net.ipv4.tcp_wmem=8192 65536 16777216
net.netfilter.nf_conntrack_generic_timeout = 60
net.netfilter.nf_conntrack_helper=0
net.netfilter.nf_conntrack_max = 524288
net.netfilter.nf_conntrack_tcp_timeout_established = 28800
net.unix.max_dgram_qlen = 4096
EOF
sysctl --system > /dev/null 2>&1
local interfaces_file="/etc/network/interfaces"
if ! grep -q 'source /etc/network/interfaces.d/*' "$interfaces_file"; then
echo "source /etc/network/interfaces.d/*" >> "$interfaces_file"
fi
msg_ok "$(translate "Network optimization completed")"
register_tool "network_optimization" true
}
# ==========================================================
disable_rpc() {
msg_info "$(translate "Disabling portmapper/rpcbind for security...")"
systemctl disable rpcbind > /dev/null 2>&1
systemctl stop rpcbind > /dev/null 2>&1
msg_ok "$(translate "portmapper/rpcbind has been disabled and removed")"
register_tool "disable_rpc" true
}
# ==========================================================
customize_bashrc() {
msg_info "$(translate "Customizing bashrc for root user...")"
local bashrc="/root/.bashrc"
local bash_profile="/root/.bash_profile"
if [ ! -f "${bashrc}.bak" ]; then
cp "$bashrc" "${bashrc}.bak"
fi
cat >> "$bashrc" << 'EOF'
# ProxMenux customizations
export HISTTIMEFORMAT="%d/%m/%y %T "
export PS1="\[\e[31m\][\[\e[m\]\[\e[38;5;172m\]\u\[\e[m\]@\[\e[38;5;153m\]\h\[\e[m\] \[\e[38;5;214m\]\W\[\e[m\]\[\e[31m\]]\[\e[m\]\\$ "
alias l='ls -CF'
alias la='ls -A'
alias ll='ls -alF'
alias ls='ls --color=auto'
alias grep='grep --color=auto'
alias fgrep='fgrep --color=auto'
alias egrep='egrep --color=auto'
source /etc/profile.d/bash_completion.sh
EOF
if ! grep -q "source /root/.bashrc" "$bash_profile"; then
echo "source /root/.bashrc" >> "$bash_profile"
fi
msg_ok "$(translate "Bashrc customization completed")"
register_tool "bashrc_custom" true
}
# ==========================================================
install_log2ram_auto() {
msg_info "$(translate "Checking if system disk is SSD or M.2...")"
ROOT_PART=$(lsblk -no NAME,MOUNTPOINT | grep ' /$' | awk '{print $1}')
SYSTEM_DISK=$(lsblk -no PKNAME /dev/$ROOT_PART 2>/dev/null)
SYSTEM_DISK=${SYSTEM_DISK:-sda}
if [[ "$SYSTEM_DISK" == nvme* || "$(cat /sys/block/$SYSTEM_DISK/queue/rotational 2>/dev/null)" == "0" ]]; then
msg_ok "$(translate "System disk ($SYSTEM_DISK) is SSD or M.2. Proceeding with log2ram setup.")"
else
msg_warn "$(translate "System disk ($SYSTEM_DISK) is not SSD/M.2. Skipping log2ram installation.")"
return 0
fi
# Clean up previous state
rm -rf /tmp/log2ram
rm -f /etc/systemd/system/log2ram*
rm -f /etc/systemd/system/log2ram-daily.*
rm -f /etc/cron.d/log2ram*
rm -f /usr/sbin/log2ram
rm -f /etc/log2ram.conf
rm -f /usr/local/bin/log2ram-check.sh
rm -rf /var/log.hdd
systemctl daemon-reexec >/dev/null 2>&1
systemctl daemon-reload >/dev/null 2>&1
msg_info "$(translate "Installing log2ram from GitHub...")"
git clone https://github.com/azlux/log2ram.git /tmp/log2ram >/dev/null 2>>/tmp/log2ram_install.log
cd /tmp/log2ram || return 1
bash install.sh >>/tmp/log2ram_install.log 2>&1
if [[ -f /etc/log2ram.conf ]] && systemctl list-units --all | grep -q log2ram; then
msg_ok "$(translate "log2ram installed successfully")"
else
msg_error "$(translate "Failed to install log2ram. See /tmp/log2ram_install.log")"
return 1
fi
# Detect RAM
RAM_SIZE_GB=$(free -g | awk '/^Mem:/{print $2}')
[[ -z "$RAM_SIZE_GB" || "$RAM_SIZE_GB" -eq 0 ]] && RAM_SIZE_GB=4
if (( RAM_SIZE_GB <= 8 )); then
LOG2RAM_SIZE="128M"
CRON_HOURS=1
elif (( RAM_SIZE_GB <= 16 )); then
LOG2RAM_SIZE="256M"
CRON_HOURS=3
else
LOG2RAM_SIZE="512M"
CRON_HOURS=6
fi
msg_ok "$(translate "Detected RAM:") $RAM_SIZE_GB GB — $(translate "log2ram size set to:") $LOG2RAM_SIZE"
sed -i "s/^SIZE=.*/SIZE=$LOG2RAM_SIZE/" /etc/log2ram.conf
rm -f /etc/cron.hourly/log2ram
echo "0 */$CRON_HOURS * * * root /usr/sbin/log2ram write" > /etc/cron.d/log2ram
msg_ok "$(translate "log2ram write scheduled every") $CRON_HOURS $(translate "hour(s)")"
cat << 'EOF' > /usr/local/bin/log2ram-check.sh
#!/bin/bash
CONF_FILE="/etc/log2ram.conf"
LIMIT_KB=$(grep '^SIZE=' "$CONF_FILE" | cut -d'=' -f2 | tr -d 'M')000
USED_KB=$(df /var/log --output=used | tail -1)
THRESHOLD=$(( LIMIT_KB * 90 / 100 ))
if (( USED_KB > THRESHOLD )); then
/usr/sbin/log2ram write
fi
EOF
chmod +x /usr/local/bin/log2ram-check.sh
echo "*/5 * * * * root /usr/local/bin/log2ram-check.sh" > /etc/cron.d/log2ram-auto-sync
msg_ok "$(translate "Auto-sync enabled when /var/log exceeds 90% of") $LOG2RAM_SIZE"
register_tool "log2ram" true
}
# ==========================================================
run_complete_optimization() {
clear
show_proxmenux_logo
msg_title "$(translate "ProxMenux Optimization Post-Installation")"
ensure_tools_json
apt_upgrade
remove_subscription_banner
configure_time_sync
skip_apt_languages
optimize_journald
optimize_logrotate
increase_system_limits
configure_entropy
optimize_memory_settings
configure_kernel_panic
force_apt_ipv4
apply_network_optimizations
disable_rpc
customize_bashrc
install_log2ram_auto
echo -e
msg_success "$(translate "Complete post-installation optimization finished!")"
if [[ "$NECESSARY_REBOOT" -eq 1 ]]; then
whiptail --title "Reboot Required" \
--yesno "$(translate "Some changes require a reboot to take effect. Do you want to restart now?")" 10 60
if [[ $? -eq 0 ]]; then
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
apt-get -y autoremove >/dev/null 2>&1
apt-get -y autoclean >/dev/null 2>&1
msg_ok "$(translate "Cleanup finished")"
msg_success "$(translate "Press Enter to continue...")"
read -r
msg_warn "$(translate "Rebooting the system...")"
reboot
else
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
apt-get -y autoremove >/dev/null 2>&1
apt-get -y autoclean >/dev/null 2>&1
msg_ok "$(translate "Cleanup finished")"
msg_info2 "$(translate "You can reboot later manually.")"
msg_success "$(translate "Press Enter to continue...")"
read -r
exit 0
fi
fi
msg_success "$(translate "All changes applied. No reboot required.")"
msg_success "$(translate "Press Enter to return to menu...")"
read -r
clear
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
run_complete_optimization
fi

View File

@@ -0,0 +1,917 @@
#!/usr/bin/env bash
# Configuration ============================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
# ==========================================================
get_external_backup_mount_point() {
local BACKUP_MOUNT_FILE="/usr/local/share/proxmenux/last_backup_mount.txt"
local STORAGE_REPO="$REPO_URL/scripts/backup_restore"
local MOUNT_POINT
if [[ -f "$BACKUP_MOUNT_FILE" ]]; then
MOUNT_POINT=$(head -n1 "$BACKUP_MOUNT_FILE" | tr -d '\r\n' | xargs)
>&2 echo "DEBUG: Valor MOUNT_POINT='$MOUNT_POINT'"
if [[ ! -d "$MOUNT_POINT" ]]; then
msg_error "Mount point does not exist: $MOUNT_POINT"
rm -f "$BACKUP_MOUNT_FILE"
return 1
fi
if ! mountpoint -q "$MOUNT_POINT"; then
msg_error "Mount point is not mounted: $MOUNT_POINT"
rm -f "$BACKUP_MOUNT_FILE"
return 1
fi
echo "$MOUNT_POINT"
return 0
else
source <(curl -s "$STORAGE_REPO/mount_disk_host_bk.sh")
MOUNT_POINT=$(mount_disk_host_bk)
[[ -z "$MOUNT_POINT" ]] && msg_error "$(translate "No disk mounted.")" && return 1
echo "$MOUNT_POINT"
return 0
fi
}
# === Host Backup Main Menu ===
host_backup_menu() {
while true; do
local CHOICE
CHOICE=$(dialog --backtitle "ProxMenux" \
--title "$(translate 'Host Backup')" \
--menu "\n$(translate 'Select backup option:')" 22 70 12 \
"" "$(translate '--- FULL BACKUP ---')" \
1 "$(translate 'Full backup to Proxmox Backup Server (PBS)')" \
2 "$(translate 'Full backup with BorgBackup')" \
3 "$(translate 'Full backup to local .tar.gz')" \
"" "$(translate '--- CUSTOM BACKUP ---')" \
4 "$(translate 'Custom backup to PBS')" \
5 "$(translate 'Custom backup with BorgBackup')" \
6 "$(translate 'Custom backup to local .tar.gz')" \
0 "$(translate 'Return')" \
3>&1 1>&2 2>&3) || return 0
case "$CHOICE" in
1) backup_full_pbs_root ;;
2) backup_with_borg "/boot/efi /etc/pve /etc/network /var/lib/pve-cluster /root /etc/ssh /home /usr/local/bin /etc/cron.d /etc/systemd/system /var/lib/vz" ;;
3) backup_to_local_tar "/boot/efi /etc/pve /etc/network /var/lib/pve-cluster /root /etc/ssh /home /usr/local/bin /etc/cron.d /etc/systemd/system /var/lib/vz" ;;
4) custom_backup_menu backup_to_pbs ;;
5) custom_backup_menu backup_with_borg ;;
6) custom_backup_menu backup_to_local_tar ;;
0) break ;;
esac
done
}
# === Menu checklist for custom backup ===
custom_backup_menu() {
declare -A BACKUP_PATHS=(
[etc-pve]="/etc/pve"
[etc-network]="/etc/network"
[var-lib-pve-cluster]="/var/lib/pve-cluster"
[root-dir]="/root"
[etc-ssh]="/etc/ssh"
[home]="/home"
[local-bin]="/usr/local/bin"
[cron]="/etc/cron.d"
[custom-systemd]="/etc/systemd/system"
[var-lib-vz]="/var/lib/vz"
)
local CHECKLIST_OPTIONS=()
for KEY in "${!BACKUP_PATHS[@]}"; do
DIR="${BACKUP_PATHS[$KEY]}"
CHECKLIST_OPTIONS+=("$KEY" "$DIR" "off")
done
SELECTED_KEYS=$(dialog --separate-output --checklist \
"$(translate 'Select directories to backup:')" 22 70 12 \
"${CHECKLIST_OPTIONS[@]}" \
3>&1 1>&2 2>&3) || return 1
local BACKUP_DIRS=()
for KEY in $SELECTED_KEYS; do
BACKUP_DIRS+=("${BACKUP_PATHS[$KEY]}")
done
# "$1" "${BACKUP_DIRS[*]}"
"$1" "${BACKUP_DIRS[@]}"
}
# === Configure PBS ===
configure_pbs_repository() {
local PBS_REPO_FILE="/usr/local/share/proxmenux/pbs-repo.conf"
local PBS_PASS_FILE="/usr/local/share/proxmenux/pbs-pass.txt"
local PBS_MANUAL_CONFIGS="/usr/local/share/proxmenux/pbs-manual-configs.txt"
[[ ! -f "$PBS_MANUAL_CONFIGS" ]] && touch "$PBS_MANUAL_CONFIGS"
local PBS_CONFIGS=()
local PBS_SOURCES=()
if [[ -f "/etc/pve/storage.cfg" ]]; then
local current_pbs="" server="" datastore="" username=""
while IFS= read -r line; do
if [[ $line =~ ^pbs:\ (.+)$ ]]; then
if [[ -n "$current_pbs" && -n "$server" && -n "$datastore" && -n "$username" ]]; then
PBS_CONFIGS+=("$current_pbs|$username@$server:$datastore")
PBS_SOURCES+=("proxmox|$current_pbs")
fi
current_pbs="${BASH_REMATCH[1]}"
server="" datastore="" username=""
elif [[ -n "$current_pbs" ]]; then
if [[ $line =~ ^[[:space:]]*server[[:space:]]+(.+)$ ]]; then
server="${BASH_REMATCH[1]}"
elif [[ $line =~ ^[[:space:]]*datastore[[:space:]]+(.+)$ ]]; then
datastore="${BASH_REMATCH[1]}"
elif [[ $line =~ ^[[:space:]]*username[[:space:]]+(.+)$ ]]; then
username="${BASH_REMATCH[1]}"
elif [[ $line =~ ^[a-zA-Z]+: ]]; then
if [[ -n "$server" && -n "$datastore" && -n "$username" ]]; then
PBS_CONFIGS+=("$current_pbs|$username@$server:$datastore")
PBS_SOURCES+=("proxmox|$current_pbs")
fi
current_pbs=""
fi
fi
done < "/etc/pve/storage.cfg"
if [[ -n "$current_pbs" && -n "$server" && -n "$datastore" && -n "$username" ]]; then
PBS_CONFIGS+=("$current_pbs|$username@$server:$datastore")
PBS_SOURCES+=("proxmox|$current_pbs")
fi
fi
if [[ -f "$PBS_MANUAL_CONFIGS" ]]; then
while IFS= read -r line; do
if [[ -n "$line" ]]; then
PBS_CONFIGS+=("$line")
local name="${line%%|*}"
PBS_SOURCES+=("manual|$name")
fi
done < "$PBS_MANUAL_CONFIGS"
fi
local menu_options=()
local i=1
for j in "${!PBS_CONFIGS[@]}"; do
local config="${PBS_CONFIGS[$j]}"
local source="${PBS_SOURCES[$j]}"
local name="${config%%|*}"
local repo="${config##*|}"
local source_type="${source%%|*}"
if [[ "$source_type" == "proxmox" ]]; then
menu_options+=("$i" " $name ($repo) [Proxmox]")
else
menu_options+=("$i" " $name ($repo) [Manual]")
fi
((i++))
done
menu_options+=("" "")
menu_options+=("$i" "\Z4\Zb $(translate 'Configure new PBS')\Zn")
local choice
choice=$(dialog --colors --backtitle "ProxMenux" --title "PBS Server Selection" \
--menu "\n$(translate 'Select PBS server for this backup:')" 22 70 12 "${menu_options[@]}" 3>&1 1>&2 2>&3)
local dialog_result=$?
clear
if [[ $dialog_result -ne 0 ]]; then
return 1
fi
if [[ $choice -eq $i ]]; then
configure_pbs_manually
else
local selected_config="${PBS_CONFIGS[$((choice-1))]}"
local selected_source="${PBS_SOURCES[$((choice-1))]}"
local pbs_name="${selected_config%%|*}"
local source_type="${selected_source%%|*}"
PBS_REPO="${selected_config##*|}"
{
mkdir -p "$(dirname "$PBS_REPO_FILE")"
echo "$PBS_REPO" > "$PBS_REPO_FILE"
} >/dev/null 2>&1
local password_found=false
if [[ "$source_type" == "proxmox" ]]; then
local password_file="/etc/pve/priv/storage/${pbs_name}.pw"
if [[ -f "$password_file" ]]; then
{
cp "$password_file" "$PBS_PASS_FILE"
chmod 600 "$PBS_PASS_FILE"
} >/dev/null 2>&1
password_found=true
fi
else
local manual_pass_file="/usr/local/share/proxmenux/pbs-pass-${pbs_name}.txt"
if [[ -f "$manual_pass_file" ]]; then
{
cp "$manual_pass_file" "$PBS_PASS_FILE"
chmod 600 "$PBS_PASS_FILE"
} >/dev/null 2>&1
password_found=true
dialog --backtitle "ProxMenux" --title "PBS Selected" --msgbox "$(translate 'Using manual PBS:') $pbs_name\n\n$(translate 'Repository:') $PBS_REPO\n$(translate 'Password:') $(translate 'Previously saved')" 12 80
fi
fi
if ! $password_found; then
dialog --backtitle "ProxMenux" --title "Password Required" --msgbox "$(translate 'Password not found for:') $pbs_name\n$(translate 'Please enter the password.')" 10 60
get_pbs_password "$pbs_name"
fi
clear
fi
}
configure_pbs_manually() {
local PBS_REPO_FILE="/usr/local/share/proxmenux/pbs-repo.conf"
local PBS_MANUAL_CONFIGS="/usr/local/share/proxmenux/pbs-manual-configs.txt"
local PBS_NAME
PBS_NAME=$(dialog --backtitle "ProxMenux" --title "New PBS Configuration" --inputbox "$(translate 'Enter a name for this PBS configuration:')" 10 60 "PBS-$(date +%m%d)" 3>&1 1>&2 2>&3) || return 1
PBS_USER=$(dialog --backtitle "ProxMenux" --title "New PBS Configuration" --inputbox "$(translate 'Enter PBS username:')" 10 50 "root@pam" 3>&1 1>&2 2>&3) || return 1
PBS_HOST=$(dialog --backtitle "ProxMenux" --title "New PBS Configuration" --inputbox "$(translate 'Enter PBS host or IP:')" 10 50 "" 3>&1 1>&2 2>&3) || return 1
PBS_DATASTORE=$(dialog --backtitle "ProxMenux" --title "New PBS Configuration" --inputbox "$(translate 'Enter PBS datastore name:')" 10 50 "" 3>&1 1>&2 2>&3) || return 1
if [[ -z "$PBS_NAME" || -z "$PBS_USER" || -z "$PBS_HOST" || -z "$PBS_DATASTORE" ]]; then
dialog --backtitle "ProxMenux" --title "Error" --msgbox "$(translate 'All fields are required!')" 8 40
return 1
fi
PBS_REPO="${PBS_USER}@${PBS_HOST}:${PBS_DATASTORE}"
{
mkdir -p "$(dirname "$PBS_REPO_FILE")"
echo "$PBS_REPO" > "$PBS_REPO_FILE"
} >/dev/null 2>&1
local config_line="$PBS_NAME|$PBS_REPO"
if ! grep -Fxq "$config_line" "$PBS_MANUAL_CONFIGS" 2>/dev/null; then
echo "$config_line" >> "$PBS_MANUAL_CONFIGS"
fi
get_pbs_password "$PBS_NAME"
dialog --backtitle "ProxMenux" --title "Success" --msgbox "$(translate 'PBS configuration saved:') $PBS_NAME\n\n$(translate 'Repository:') $PBS_REPO\n\n$(translate 'This configuration will appear in future backups.')" 12 80
}
get_pbs_password() {
local PBS_NAME="$1"
local PBS_PASS_FILE="/usr/local/share/proxmenux/pbs-pass.txt"
local PBS_MANUAL_PASS_FILE="/usr/local/share/proxmenux/pbs-pass-${PBS_NAME}.txt"
while true; do
PBS_REPO_PASS=$(dialog --backtitle "ProxMenux" --title "PBS Password" --insecure --passwordbox "$(translate 'Enter PBS repository password for:') $PBS_NAME" 10 70 "" 3>&1 1>&2 2>&3) || return 1
PBS_REPO_PASS2=$(dialog --backtitle "ProxMenux" --title "PBS Password" --insecure --passwordbox "$(translate 'Confirm PBS repository password:')" 10 60 "" 3>&1 1>&2 2>&3) || return 1
if [[ "$PBS_REPO_PASS" == "$PBS_REPO_PASS2" ]]; then
break
else
dialog --backtitle "ProxMenux" --title "Error" --msgbox "$(translate 'Repository passwords do not match! Please try again.')" 8 50
fi
done
{
echo "$PBS_REPO_PASS" > "$PBS_PASS_FILE"
chmod 600 "$PBS_PASS_FILE"
} >/dev/null 2>&1
{
echo "$PBS_REPO_PASS" > "$PBS_MANUAL_PASS_FILE"
chmod 600 "$PBS_MANUAL_PASS_FILE"
} >/dev/null 2>&1
}
# ===============================
# ========== PBS BACKUP ==========
backup_full_pbs_root() {
local HOSTNAME PBS_REPO PBS_KEY_FILE PBS_PASS_FILE PBS_ENCRYPTION_PASS_FILE ENCRYPT_OPT=""
HOSTNAME=$(hostname)
local PBS_REPO_FILE="/usr/local/share/proxmenux/pbs-repo.conf"
PBS_KEY_FILE="/usr/local/share/proxmenux/pbs-key.conf"
PBS_PASS_FILE="/usr/local/share/proxmenux/pbs-pass.txt"
PBS_ENCRYPTION_PASS_FILE="/usr/local/share/proxmenux/pbs-encryption-pass.txt"
LOGFILE="/tmp/pbs-backup-${HOSTNAME}.log"
configure_pbs_repository
if [[ ! -f "$PBS_REPO_FILE" ]]; then
msg_error "$(translate "Failed to configure PBS connection")"
sleep 3
return 1
fi
PBS_REPO=$(<"$PBS_REPO_FILE")
if [[ ! -f "$PBS_PASS_FILE" ]]; then
msg_error "$(translate "PBS password not configured")"
sleep 3
return 1
fi
dialog --backtitle "ProxMenux" --title "Encryption" --yesno "$(translate 'Do you want to encrypt the backup?')" 8 60
if [[ $? -eq 0 ]]; then
if [[ ! -f "$PBS_ENCRYPTION_PASS_FILE" ]]; then
while true; do
PBS_KEY_PASS=$(dialog --backtitle "ProxMenux" --title "Encryption Password" --insecure --passwordbox "$(translate 'Enter encryption password (different from PBS login):')" 12 70 "" 3>&1 1>&2 2>&3) || return 1
PBS_KEY_PASS2=$(dialog --backtitle "ProxMenux" --title "Encryption Password" --insecure --passwordbox "$(translate 'Confirm encryption password:')" 10 60 "" 3>&1 1>&2 2>&3) || return 1
if [[ "$PBS_KEY_PASS" == "$PBS_KEY_PASS2" ]]; then
break
else
dialog --backtitle "ProxMenux" --title "Error" --msgbox "$(translate 'Passwords do not match! Please try again.')" 8 50
fi
done
{
echo "$PBS_KEY_PASS" > "$PBS_ENCRYPTION_PASS_FILE"
chmod 600 "$PBS_ENCRYPTION_PASS_FILE"
} >/dev/null 2>&1
dialog --backtitle "ProxMenux" --title "Success" --msgbox "$(translate 'Encryption password saved successfully!')" 8 50
fi
if [[ ! -f "$PBS_KEY_FILE" ]]; then
PBS_ENCRYPTION_PASS=$(<"$PBS_ENCRYPTION_PASS_FILE")
dialog --backtitle "ProxMenux" --title "Encryption" --infobox "$(translate 'Creating encryption key...')" 5 50
expect -c "
set timeout 30
spawn proxmox-backup-client key create \"$PBS_KEY_FILE\"
expect {
\"Encryption Key Password:\" {
send \"$PBS_ENCRYPTION_PASS\r\"
exp_continue
}
\"Verify Password:\" {
send \"$PBS_ENCRYPTION_PASS\r\"
exp_continue
}
eof
}
" >/dev/null 2>&1
if [[ ! -f "$PBS_KEY_FILE" ]]; then
dialog --backtitle "ProxMenux" --title "Error" --msgbox "$(translate 'Error creating encryption key.')" 8 40
return 1
fi
dialog --backtitle "ProxMenux" --title "Important" --msgbox "$(translate 'IMPORTANT: Save the key file. Without it you will not be able to restore your backups!')\n\n$(translate 'Key file location:') $PBS_KEY_FILE" 12 70
fi
ENCRYPT_OPT="--keyfile $PBS_KEY_FILE"
else
ENCRYPT_OPT=""
fi
clear
show_proxmenux_logo
echo -e
msg_info2 "$(translate "Starting backup to PBS")"
echo -e
echo -e "${BL}$(translate "PBS Repository:")${WHITE} $PBS_REPO${RESET}"
echo -e "${BL}$(translate "Backup ID:")${WHITE} $HOSTNAME${RESET}"
echo -e "${BL}$(translate "Included:")${WHITE} /boot/efi /etc/pve (all root)${RESET}"
echo -e "${BL}$(translate "Encryption:")${WHITE} $([[ -n "$ENCRYPT_OPT" ]] && echo "Enabled" || echo "Disabled")${RESET}"
echo -e "${BL}$(translate "Log file:")${WHITE} $LOGFILE${RESET}"
echo -e "${BOLD}${NEON_PURPLE_BLUE}-------------------------------${RESET}"
echo ""
PBS_REPO_PASS=$(<"$PBS_PASS_FILE")
if [[ -n "$ENCRYPT_OPT" ]]; then
PBS_ENCRYPTION_PASS=$(<"$PBS_ENCRYPTION_PASS_FILE")
echo "$(translate "Starting encrypted full backup...")"
echo ""
expect -c "
set timeout 3600
log_file $LOGFILE
spawn proxmox-backup-client backup \
--include-dev /boot/efi \
--include-dev /etc/pve \
root-${HOSTNAME}.pxar:/ \
--repository \"$PBS_REPO\" \
$ENCRYPT_OPT \
--backup-type host \
--backup-id \"$HOSTNAME\" \
--backup-time \"$(date +%s)\"
expect {
-re \"Password for .*:\" {
send \"$PBS_REPO_PASS\r\"
exp_continue
}
\"Encryption Key Password:\" {
send \"$PBS_ENCRYPTION_PASS\r\"
exp_continue
}
eof
}
" | tee -a "$LOGFILE"
else
echo "$(translate "Starting unencrypted full backup...")"
echo ""
expect -c "
set timeout 3600
log_file $LOGFILE
spawn proxmox-backup-client backup \
--include-dev /boot/efi \
--include-dev /etc/pve \
root-${HOSTNAME}.pxar:/ \
--repository \"$PBS_REPO\" \
--backup-type host \
--backup-id \"$HOSTNAME\" \
--backup-time \"$(date +%s)\"
expect {
-re \"Password for .*:\" {
send \"$PBS_REPO_PASS\r\"
exp_continue
}
eof
}
" | tee -a "$LOGFILE"
fi
local backup_result=$?
echo -e "${BOLD}${NEON_PURPLE_BLUE}===============================${RESET}\n"
if [[ $backup_result -eq 0 ]]; then
msg_ok "$(translate "Full backup process completed successfully")"
else
msg_error "$(translate "Backup process finished with errors")"
fi
echo ""
msg_success "$(translate "Press Enter to return to the main menu...")"
read -r
}
backup_to_pbs() {
local HOSTNAME TIMESTAMP SNAPSHOT
HOSTNAME=$(hostname)
TIMESTAMP=$(date +%Y-%m-%d_%H-%M)
SNAPSHOT="${HOSTNAME}-${TIMESTAMP}"
local PBS_REPO_FILE="/usr/local/share/proxmenux/pbs-repo.conf"
local PBS_KEY_FILE="/usr/local/share/proxmenux/pbs-key.conf"
local PBS_PASS_FILE="/usr/local/share/proxmenux/pbs-pass.txt"
local PBS_ENCRYPTION_PASS_FILE="/usr/local/share/proxmenux/pbs-encryption-pass.txt"
local PBS_REPO ENCRYPT_OPT USE_ENCRYPTION
local PBS_KEY_PASS PBS_REPO_PASS
configure_pbs_repository
PBS_REPO=$(<"$PBS_REPO_FILE")
USE_ENCRYPTION=false
dialog --backtitle "ProxMenux" --yesno "$(translate 'Do you want to encrypt the backup?')" 8 60
[[ $? -eq 0 ]] && USE_ENCRYPTION=true
if $USE_ENCRYPTION && ! command -v expect >/dev/null 2>&1; then
apt-get update -qq >/dev/null 2>&1
apt-get install -y expect >/dev/null 2>&1
fi
if [[ "$#" -lt 1 ]]; then
clear
show_proxmenux_logo
msg_error "$(translate "No directories specified for backup.")"
sleep 2
return 1
fi
local TOTAL="$#"
local COUNT=1
for dir in "$@"; do
local SAFE_NAME SAFE_ID PXAR_NAME
SAFE_NAME=$(basename "$dir" | tr '.-/' '_')
PXAR_NAME="root-custom-${SAFE_NAME}-${SNAPSHOT}.pxar"
SAFE_ID="custom-${HOSTNAME}-${SAFE_NAME}"
msg_info2 "$(translate "[$COUNT/$TOTAL] Backing up") $dir $(translate "as") $PXAR_NAME"
ENCRYPT_OPT=""
if $USE_ENCRYPTION; then
if [[ -f "$PBS_KEY_FILE" ]]; then
ENCRYPT_OPT="--keyfile $PBS_KEY_FILE"
else
while true; do
PBS_KEY_PASS=$(dialog --backtitle "ProxMenux" --insecure --passwordbox "$(translate 'Enter encryption password (different from PBS login):')" 10 60 "" 3>&1 1>&2 2>&3) || return 1
PBS_KEY_PASS2=$(dialog --backtitle "ProxMenux" --insecure --passwordbox "$(translate 'Confirm encryption password:')" 10 60 "" 3>&1 1>&2 2>&3) || return 1
if [[ "$PBS_KEY_PASS" == "$PBS_KEY_PASS2" ]]; then
break
else
dialog --backtitle "ProxMenux" --msgbox "$(translate 'Passwords do not match! Please try again.')" 8 50
fi
done
{
echo "$PBS_KEY_PASS" > "$PBS_ENCRYPTION_PASS_FILE"
chmod 600 "$PBS_ENCRYPTION_PASS_FILE"
} >/dev/null 2>&1
expect -c "
set timeout 30
spawn proxmox-backup-client key create \"$PBS_KEY_FILE\"
expect {
\"Encryption Key Password:\" {
send \"$PBS_KEY_PASS\r\"
exp_continue
}
\"Verify Password:\" {
send \"$PBS_KEY_PASS\r\"
exp_continue
}
eof
}
" >/dev/null 2>&1
if [[ ! -f "$PBS_KEY_FILE" ]]; then
dialog --backtitle "ProxMenux" --msgbox "$(translate 'Error creating encryption key.')" 8 40
return 1
fi
ENCRYPT_OPT="--keyfile $PBS_KEY_FILE"
dialog --backtitle "ProxMenux" --msgbox "$(translate 'Encryption key generated. Save it in a safe place!')" 10 60
fi
fi
clear
show_proxmenux_logo
echo -e
msg_info2 "$(translate "Starting backup to PBS")"
TOTAL_SIZE=$(du -cb "$@" | awk '/total$/ {print $1}')
TOTAL_SIZE_GB=$(awk "BEGIN {printf \"%.2f\", $TOTAL_SIZE/1024/1024/1024}")
echo -e
echo -e "${BL}$(translate "PBS Repository:")${WHITE} $PBS_REPO${RESET}"
echo -e "${BL}$(translate "Backup ID:")${WHITE} $HOSTNAME${RESET}"
echo -e "${BL}$(translate "Encryption:")${WHITE} $([[ -n "$ENCRYPT_OPT" ]] && echo "Enabled" || echo "Disabled")${RESET}"
echo -e "${BL}$(translate "Included directories:")${WHITE} $*${RESET}"
echo -e "${BL}$(translate "Total size:")${WHITE} ${TOTAL_SIZE_GB} GB${RESET}"
echo -e "${BOLD}${NEON_PURPLE_BLUE}-------------------------------${RESET}"
PBS_REPO_PASS=$(<"$PBS_PASS_FILE")
if $USE_ENCRYPTION && [[ -f "$PBS_ENCRYPTION_PASS_FILE" ]]; then
PBS_KEY_PASS=$(<"$PBS_ENCRYPTION_PASS_FILE")
expect -c "
set timeout 300
spawn proxmox-backup-client backup \"${PXAR_NAME}:$dir\" --repository \"$PBS_REPO\" $ENCRYPT_OPT --backup-type host --backup-id \"$SAFE_ID\" --backup-time \"$(date +%s)\"
expect {
-re \"Password for .*:\" {
send \"$PBS_REPO_PASS\r\"
exp_continue
}
\"Encryption Key Password:\" {
send \"$PBS_KEY_PASS\r\"
exp_continue
}
eof
}
"
else
expect -c "
set timeout 300
spawn proxmox-backup-client backup \"${PXAR_NAME}:$dir\" --repository \"$PBS_REPO\" $ENCRYPT_OPT --backup-type host --backup-id \"$SAFE_ID\" --backup-time \"$(date +%s)\"
expect {
-re \"Password for .*:\" {
send \"$PBS_REPO_PASS\r\"
exp_continue
}
eof
}
"
fi
COUNT=$((COUNT+1))
done
echo -e "${BOLD}${NEON_PURPLE_BLUE}===============================${RESET}\n"
msg_ok "$(translate "Backup process finished.")"
echo ""
msg_success "$(translate "Press Enter to return to the main menu...")"
read -r
}
# ===============================
# ========== BORGBACKUP ==========
backup_with_borg() {
# local SRC="$1"
local BORG_APPIMAGE="/usr/local/share/proxmenux/borg"
local LOGFILE="/tmp/borg-backup.log"
local DEST
local TYPE
local ENCRYPT_OPT=""
local BORG_KEY
if [[ ! -x "$BORG_APPIMAGE" ]]; then
clear
show_proxmenux_logo
msg_info "$(translate "BorgBackup not found. Downloading AppImage...")"
mkdir -p /usr/local/share/proxmenux
wget -qO "$BORG_APPIMAGE" "https://github.com/borgbackup/borg/releases/download/1.2.8/borg-linux64"
chmod +x "$BORG_APPIMAGE"
msg_ok "$(translate "BorgBackup downloaded and ready.")"
fi
TYPE=$(dialog --backtitle "ProxMenux" --menu "$(translate 'Select Borg backup destination:')" 15 60 3 \
"local" "$(translate 'Local directory')" \
"usb" "$(translate 'Internal/External dedicated disk')" \
"remote" "$(translate 'Remote server')" \
3>&1 1>&2 2>&3) || return 1
if [[ "$TYPE" == "local" ]]; then
DEST=$(dialog --backtitle "ProxMenux" --inputbox "$(translate 'Enter local directory for backup:')" 10 60 "/backup/borgbackup" 3>&1 1>&2 2>&3) || return 1
mkdir -p "$DEST"
elif [[ "$TYPE" == "usb" ]]; then
while true; do
BASE_DEST=$(get_external_backup_mount_point)
if [[ -z "$BASE_DEST" ]]; then
dialog --backtitle "ProxMenux" --yesno "$(translate 'No external disk detected or mounted. Would you like to retry?')" 8 60
[[ $? -eq 0 ]] && continue
return 1
fi
DEST="$BASE_DEST/borgbackup"
mkdir -p "$DEST"
DISK_DEV=$(df "$BASE_DEST" | awk 'NR==2{print $1}')
PKNAME=$(lsblk -no PKNAME "$DISK_DEV" 2>/dev/null)
[[ -z "$PKNAME" ]] && PKNAME=$(basename "$DISK_DEV" | sed 's/[0-9]*$//')
if [[ -n "$PKNAME" && -b /dev/$PKNAME ]]; then
DISK_MODEL=$(lsblk -no MODEL "/dev/$PKNAME")
else
DISK_MODEL="(unknown)"
fi
FREE_SPACE=$(df -h "$BASE_DEST" | awk 'NR==2{print $4}')
dialog --backtitle "ProxMenux" \
--title "$(translate "Dedicated Backup Disk")" \
--yesno "\n$(translate "Mount point:") $DEST\n\n\
$(translate "Disk model:") $DISK_MODEL\n\
$(translate "Available space:") $FREE_SPACE\n\n\
$(translate "Use this disk for backup?")" 12 70
if [[ $? -eq 0 ]]; then
break
else
return 1
fi
done
elif [[ "$TYPE" == "remote" ]]; then
REMOTE_USER=$(dialog --backtitle "ProxMenux" --inputbox "$(translate 'Enter SSH user for remote:')" 10 60 "root" 3>&1 1>&2 2>&3) || return 1
REMOTE_HOST=$(dialog --backtitle "ProxMenux" --inputbox "$(translate 'Enter SSH host:')" 10 60 "" 3>&1 1>&2 2>&3) || return 1
REMOTE_PATH=$(dialog --backtitle "ProxMenux" --inputbox "$(translate 'Enter remote path:')" 10 60 "/backup/borgbackup" 3>&1 1>&2 2>&3) || return 1
DEST="ssh://$REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH"
fi
dialog --backtitle "ProxMenux" --yesno "$(translate 'Do you want to encrypt the backup?')" 8 60
if [[ $? -eq 0 ]]; then
BORG_KEY=$(dialog --backtitle "ProxMenux" --inputbox "$(translate 'Enter Borg encryption passphrase (will be saved):')" 10 60 "" 3>&1 1>&2 2>&3) || return 1
ENCRYPT_OPT="--encryption=repokey"
export BORG_PASSPHRASE="$BORG_KEY"
else
ENCRYPT_OPT="--encryption=none"
fi
if [[ "$TYPE" == "local" || "$TYPE" == "usb" ]]; then
if [[ ! -f "$DEST/config" ]]; then
"$BORG_APPIMAGE" init $ENCRYPT_OPT "$DEST"
if [[ $? -ne 0 ]]; then
clear
show_proxmenux_logo
msg_error "$(translate "Failed to initialize Borg repo at") $DEST"
sleep 5
return 1
fi
fi
fi
dialog --backtitle "ProxMenux" --msgbox "$(translate 'Borg backup will start now. This may take a while.')" 8 40
clear
show_proxmenux_logo
msg_info2 "$(translate "Starting backup with BorgBackup...")"
echo -e
TOTAL_SIZE=$(du -cb "$@" | awk '/total$/ {print $1}')
TOTAL_SIZE_GB=$(awk "BEGIN {printf \"%.2f\", $TOTAL_SIZE/1024/1024/1024}")
echo -e "${BL}$(translate "Included directories:")${WHITE} $*${RESET}"
echo -e "${BL}$(translate "Total size:")${WHITE} ${TOTAL_SIZE_GB} GB${RESET}"
# 6. Lanzar el backup y guardar log
# "$BORG_APPIMAGE" create --progress "$DEST"::"root-$(hostname)-$(date +%Y%m%d_%H%M)" $SRC 2>&1 | tee "$LOGFILE"
"$BORG_APPIMAGE" create --progress "$DEST"::"root-$(hostname)-$(date +%Y%m%d_%H%M)" "$@" 2>&1 | tee "$LOGFILE"
echo -e "${BOLD}${NEON_PURPLE_BLUE}===============================${RESET}\n"
msg_ok "$(translate "Backup process finished.")"
echo
msg_success "$(translate "Press Enter to return to the main menu...")"
read -r
}
# ===============================
# ========== LOCAL TAR ==========
backup_to_local_tar() {
# local SRC="$1"
local TYPE
local DEST
local LOGFILE="/tmp/tar-backup.log"
if ! command -v pv &>/dev/null; then
apt-get update -qq && apt-get install -y pv >/dev/null 2>&1
fi
TYPE=$(dialog --backtitle "ProxMenux" --menu "$(translate 'Select backup destination:')" 15 60 2 \
"local" "$(translate 'Local directory')" \
"usb" "$(translate 'Internal/External dedicated disk')" \
3>&1 1>&2 2>&3) || return 1
if [[ "$TYPE" == "local" ]]; then
DEST=$(dialog --backtitle "ProxMenux" --inputbox "$(translate 'Enter directory for backup:')" 10 60 "/backup" 3>&1 1>&2 2>&3) || return 1
mkdir -p "$DEST"
else
while true; do
DEST=$(get_external_backup_mount_point)
if [[ -z "$DEST" ]]; then
dialog --backtitle "ProxMenux" --yesno "No external disk detected or mounted. Would you like to retry?" 8 60
[[ $? -eq 0 ]] && continue
return 1
fi
DISK_DEV=$(df "$DEST" | awk 'NR==2{print $1}')
PKNAME=$(lsblk -no PKNAME "$DISK_DEV" 2>/dev/null)
[[ -z "$PKNAME" ]] && PKNAME=$(basename "$DISK_DEV" | sed 's/[0-9]*$//')
if [[ -n "$PKNAME" && -b /dev/$PKNAME ]]; then
DISK_MODEL=$(lsblk -no MODEL "/dev/$PKNAME")
else
DISK_MODEL="(unknown)"
fi
FREE_SPACE=$(df -h "$DEST" | awk 'NR==2{print $4}')
dialog --backtitle "ProxMenux" \
--title "$(translate "Dedicated Backup Disk")" \
--yesno "\n$(translate "Mount point:") $DEST\n\n\
$(translate "Disk model:") $DISK_MODEL\n\
$(translate "Available space:") $FREE_SPACE\n\n\
$(translate "Use this disk for backup?")" 12 70
if [[ $? -eq 0 ]]; then
mkdir -p "$DEST"
break
else
return 1
fi
done
fi
TAR_INPUT=""
TOTAL_SIZE=0
for src in $SRC; do
sz=$(du -sb "$src" 2>/dev/null | awk '{print $1}')
TOTAL_SIZE=$((TOTAL_SIZE + sz))
TAR_INPUT="$TAR_INPUT $src"
done
local FILENAME="root-$(hostname)-$(date +%Y%m%d_%H%M).tar.gz"
clear
show_proxmenux_logo
msg_info2 "$(translate "Starting backup with tar...")"
echo -e
TOTAL_SIZE=$(du -cb "$@" | awk '/total$/ {print $1}')
TOTAL_SIZE_GB=$(awk "BEGIN {printf \"%.2f\", $TOTAL_SIZE/1024/1024/1024}")
echo -e "${BL}$(translate "Included directories:")${WHITE} $*${RESET}"
echo -e "${BL}$(translate "Total size:")${WHITE} ${TOTAL_SIZE_GB} GB${RESET}"
tar -cf - "$@" 2> >(grep -v "Removing leading \`/'" >&2) \
| pv -s "$TOTAL_SIZE" \
| gzip > "$DEST/$FILENAME"
echo -ne "\033[1A\r\033[K"
echo -e "${BOLD}${NEON_PURPLE_BLUE}===============================${RESET}\n"
msg_ok "$(translate "Backup process finished. Review log above or in /tmp/tar-backup.log")"
echo
msg_success "$(translate "Press Enter to return to the main menu...")"
read -r
}
# ===============================
host_backup_menu

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,433 @@
#!/bin/bash
# ==========================================================
# ProxMenu - Mount disk on Proxmox host for backups
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT
# Version : 1.3-dialog
# Last Updated: 13/12/2024
# ==========================================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
mount_disk_host_bk() {
get_disk_info() {
local disk=$1
MODEL=$(lsblk -dn -o MODEL "$disk" | xargs)
SIZE=$(lsblk -dn -o SIZE "$disk" | xargs)
echo "$MODEL" "$SIZE"
}
is_usb_disk() {
local disk=$1
local disk_name=$(basename "$disk")
if readlink -f "/sys/block/$disk_name/device" 2>/dev/null | grep -q "usb"; then
return 0
fi
if udevadm info --query=property --name="$disk" 2>/dev/null | grep -q "ID_BUS=usb"; then
return 0
fi
return 1
}
is_system_disk() {
local disk=$1
local disk_name=$(basename "$disk")
local system_mounts=$(df -h | grep -E '^\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(/|/boot|/usr|/var|/home)$' | awk '{print $1}')
for mount_dev in $system_mounts; do
local mount_disk=""
if [[ "$mount_dev" =~ ^/dev/mapper/ ]]; then
local vg_name=$(lvs --noheadings -o vg_name "$mount_dev" 2>/dev/null | xargs)
if [[ -n "$vg_name" ]]; then
local pvs_list=$(pvs --noheadings -o pv_name -S vg_name="$vg_name" 2>/dev/null | xargs)
for pv in $pvs_list; do
if [[ -n "$pv" && -e "$pv" ]]; then
mount_disk=$(lsblk -no PKNAME "$pv" 2>/dev/null)
if [[ -n "$mount_disk" && "/dev/$mount_disk" == "$disk" ]]; then
return 0
fi
fi
done
fi
elif [[ "$mount_dev" =~ ^/dev/[hsv]d[a-z][0-9]* || "$mount_dev" =~ ^/dev/nvme[0-9]+n[0-9]+p[0-9]+ ]]; then
mount_disk=$(lsblk -no PKNAME "$mount_dev" 2>/dev/null)
if [[ -n "$mount_disk" && "/dev/$mount_disk" == "$disk" ]]; then
return 0
fi
fi
done
local fs_type=$(lsblk -no FSTYPE "$disk" 2>/dev/null | head -1)
if [[ "$fs_type" == "btrfs" ]]; then
local temp_mount=$(mktemp -d)
if mount -o ro "$disk" "$temp_mount" 2>/dev/null; then
if btrfs subvolume list "$temp_mount" 2>/dev/null | grep -qE '(@|@home|@var|@boot|@root|root)'; then
umount "$temp_mount" 2>/dev/null
rmdir "$temp_mount" 2>/dev/null
return 0
fi
umount "$temp_mount" 2>/dev/null
fi
rmdir "$temp_mount" 2>/dev/null
while read -r part; do
if [[ -n "$part" ]]; then
local part_fs=$(lsblk -no FSTYPE "/dev/$part" 2>/dev/null)
if [[ "$part_fs" == "btrfs" ]]; then
local mount_point=$(lsblk -no MOUNTPOINT "/dev/$part" 2>/dev/null)
if [[ "$mount_point" == "/" || "$mount_point" == "/boot" || "$mount_point" == "/home" || "$mount_point" == "/var" ]]; then
return 0
fi
fi
fi
done < <(lsblk -ln -o NAME "$disk" | tail -n +2)
fi
local disk_uuid=$(blkid -s UUID -o value "$disk" 2>/dev/null)
local part_uuids=()
while read -r part; do
if [[ -n "$part" ]]; then
local uuid=$(blkid -s UUID -o value "/dev/$part" 2>/dev/null)
if [[ -n "$uuid" ]]; then
part_uuids+=("$uuid")
fi
fi
done < <(lsblk -ln -o NAME "$disk" | tail -n +2)
for uuid in "${part_uuids[@]}" "$disk_uuid"; do
if [[ -n "$uuid" ]] && grep -q "UUID=$uuid" /etc/fstab; then
local mount_point=$(grep "UUID=$uuid" /etc/fstab | awk '{print $2}')
if [[ "$mount_point" == "/" || "$mount_point" == "/boot" || "$mount_point" == "/home" || "$mount_point" == "/var" ]]; then
return 0
fi
fi
done
if grep -q "$disk" /etc/fstab; then
local mount_point=$(grep "$disk" /etc/fstab | awk '{print $2}')
if [[ "$mount_point" == "/" || "$mount_point" == "/boot" || "$mount_point" == "/home" || "$mount_point" == "/var" ]]; then
return 0
fi
fi
local disk_count=$(lsblk -dn -e 7,11 -o PATH | wc -l)
if [[ "$disk_count" -eq 1 ]]; then
return 0
fi
return 1
}
USED_DISKS=$(lsblk -n -o PKNAME,TYPE | grep 'lvm' | awk '{print "/dev/" $1}')
MOUNTED_DISKS=$(lsblk -ln -o NAME,MOUNTPOINT | awk '$2!="" {print "/dev/" $1}')
ZFS_DISKS=""
ZFS_RAW=$(zpool list -v -H 2>/dev/null | awk '{print $1}' | grep -v '^NAME$' | grep -v '^-' | grep -v '^mirror')
for entry in $ZFS_RAW; do
path=""
if [[ "$entry" == wwn-* || "$entry" == ata-* ]]; then
if [ -e "/dev/disk/by-id/$entry" ]; then
path=$(readlink -f "/dev/disk/by-id/$entry")
fi
elif [[ "$entry" == /dev/* ]]; then
path="$entry"
fi
if [ -n "$path" ]; then
base_disk=$(lsblk -no PKNAME "$path" 2>/dev/null)
if [ -n "$base_disk" ]; then
ZFS_DISKS+="/dev/$base_disk"$'\n'
fi
fi
done
ZFS_DISKS=$(echo "$ZFS_DISKS" | sort -u)
LVM_DEVICES=$(
pvs --noheadings -o pv_name 2> >(grep -v 'File descriptor .* leaked') |
while read -r dev; do
[[ -n "$dev" && -e "$dev" ]] && readlink -f "$dev"
done | sort -u
)
FREE_DISKS=()
while read -r DISK; do
[[ "$DISK" =~ /dev/zd ]] && continue
INFO=($(get_disk_info "$DISK"))
MODEL="${INFO[@]::${#INFO[@]}-1}"
SIZE="${INFO[-1]}"
LABEL=""
SHOW_DISK=true
IS_MOUNTED=false
IS_RAID=false
IS_ZFS=false
IS_LVM=false
IS_SYSTEM=false
IS_USB=false
if is_system_disk "$DISK"; then
IS_SYSTEM=true
fi
if is_usb_disk "$DISK"; then
IS_USB=true
fi
while read -r part fstype; do
[[ "$fstype" == "zfs_member" ]] && IS_ZFS=true
[[ "$fstype" == "linux_raid_member" ]] && IS_RAID=true
[[ "$fstype" == "LVM2_member" ]] && IS_LVM=true
if grep -q "/dev/$part" <<< "$MOUNTED_DISKS"; then
IS_MOUNTED=true
fi
done < <(lsblk -ln -o NAME,FSTYPE "$DISK" | tail -n +2)
REAL_PATH=""
if [[ -n "$DISK" && -e "$DISK" ]]; then
REAL_PATH=$(readlink -f "$DISK")
fi
if [[ -n "$REAL_PATH" ]] && echo "$LVM_DEVICES" | grep -qFx "$REAL_PATH"; then
IS_MOUNTED=true
fi
USED_BY=""
REAL_PATH=""
if [[ -n "$DISK" && -e "$DISK" ]]; then
REAL_PATH=$(readlink -f "$DISK")
fi
CONFIG_DATA=$(grep -vE '^\s*#' /etc/pve/qemu-server/*.conf /etc/pve/lxc/*.conf 2>/dev/null)
if grep -Fq "$REAL_PATH" <<< "$CONFIG_DATA"; then
USED_BY="$(translate "In use")"
else
for SYMLINK in /dev/disk/by-id/*; do
[[ -e "$SYMLINK" ]] || continue
if [[ "$(readlink -f "$SYMLINK")" == "$REAL_PATH" ]]; then
if grep -Fq "$SYMLINK" <<< "$CONFIG_DATA"; then
USED_BY="$(translate "In use")"
break
fi
fi
done
fi
if $IS_RAID && grep -q "$DISK" <<< "$(cat /proc/mdstat)"; then
if grep -q "active raid" /proc/mdstat; then
SHOW_DISK=false
fi
fi
if $IS_ZFS; then SHOW_DISK=false; fi
if $IS_MOUNTED; then SHOW_DISK=false; fi
if $IS_SYSTEM; then SHOW_DISK=false; fi
if $SHOW_DISK; then
[[ -n "$USED_BY" ]] && LABEL+=" [$USED_BY]"
[[ "$IS_RAID" == true ]] && LABEL+=" ⚠ RAID"
[[ "$IS_LVM" == true ]] && LABEL+=" ⚠ LVM"
[[ "$IS_ZFS" == true ]] && LABEL+=" ⚠ ZFS"
if $IS_USB; then
LABEL+=" USB"
else
LABEL+=" $(translate "Internal")"
fi
DESCRIPTION=$(printf "%-30s %10s%s" "$MODEL" "$SIZE" "$LABEL")
FREE_DISKS+=("$DISK" "$DESCRIPTION" "off")
fi
done < <(lsblk -dn -e 7,11 -o PATH)
if [ "${#FREE_DISKS[@]}" -eq 0 ]; then
dialog --title "$(translate "Error")" --msgbox "$(translate "No available disks found on the host.")" 8 60
exit 1
fi
# Building the array for dialog (format: tag item on/off tag item on/off...)
DLG_LIST=()
for ((i=0; i<${#FREE_DISKS[@]}; i+=3)); do
DLG_LIST+=("${FREE_DISKS[i]}" "${FREE_DISKS[i+1]}" "${FREE_DISKS[i+2]}")
done
SELECTED=$(dialog --clear --backtitle "ProxMenux" --title "$(translate "Select Disk")" \
--radiolist "\n$(translate "Select the disk you want to mount on the host:")" 20 90 10 \
"${DLG_LIST[@]}" 2>&1 >/dev/tty)
if [ -z "$SELECTED" ]; then
dialog --title "$(translate "Error")" --msgbox "$(translate "No disk was selected.")" 8 50
exit 1
fi
# ------------------- Partitions and formatting ------------------------
PARTITION=$(lsblk -rno NAME "$SELECTED" | awk -v disk="$(basename "$SELECTED")" '$1 != disk {print $1; exit}')
SKIP_FORMAT=false
DEFAULT_MOUNT="/mnt/backup"
if [ -n "$PARTITION" ]; then
PARTITION="/dev/$PARTITION"
CURRENT_FS=$(lsblk -no FSTYPE "$PARTITION" | xargs)
if [[ "$CURRENT_FS" == "ext4" || "$CURRENT_FS" == "xfs" || "$CURRENT_FS" == "btrfs" ]]; then
SKIP_FORMAT=true
else
dialog --title "$(translate "Unsupported Filesystem")" --yesno \
"$(translate "The partition") $PARTITION $(translate "has an unsupported filesystem ($CURRENT_FS).\nDo you want to format it?")" 10 70
if [ $? -ne 0 ]; then exit 0; fi
fi
else
CURRENT_FS=$(lsblk -no FSTYPE "$SELECTED" | xargs)
if [[ "$CURRENT_FS" == "ext4" || "$CURRENT_FS" == "xfs" || "$CURRENT_FS" == "btrfs" ]]; then
SKIP_FORMAT=true
PARTITION="$SELECTED"
else
dialog --title "$(translate "No Valid Partitions")" --yesno \
"$(translate "The disk has no partitions and no valid filesystem. Do you want to create a new partition and format it?")" 10 70
if [ $? -ne 0 ]; then exit 0; fi
echo -e "$(translate "Creating partition table and partition...")"
parted -s "$SELECTED" mklabel gpt
parted -s "$SELECTED" mkpart primary 0% 100%
sleep 2
partprobe "$SELECTED"
sleep 2
PARTITION=$(lsblk -rno NAME "$SELECTED" | awk -v disk="$(basename "$SELECTED")" '$1 != disk {print $1; exit}')
if [ -n "$PARTITION" ]; then
PARTITION="/dev/$PARTITION"
else
dialog --title "$(translate "Partition Error")" --msgbox \
"$(translate "Failed to create partition on disk") $SELECTED." 8 70
exit 1
fi
fi
fi
if [ "$SKIP_FORMAT" != true ]; then
FORMAT_TYPE=$(dialog --title "$(translate "Select Format Type")" --menu \
"$(translate "Select the filesystem type for") $PARTITION:" 15 60 5 \
"ext4" "$(translate "Extended Filesystem 4 (recommended)")" \
"xfs" "XFS" \
"btrfs" "Btrfs" 2>&1 >/dev/tty)
if [ -z "$FORMAT_TYPE" ]; then
dialog --title "$(translate "Format Cancelled")" --msgbox \
"$(translate "Format operation cancelled. The disk will not be added.")" 8 60
exit 0
fi
dialog --title "$(translate "WARNING")" --yesno \
"$(translate "WARNING: This operation will FORMAT the disk") $PARTITION $(translate "with") $FORMAT_TYPE.\n\n$(translate "ALL DATA ON THIS DISK WILL BE PERMANENTLY LOST!")\n\n$(translate "Are you sure you want to continue")" 15 70
if [ $? -ne 0 ]; then exit 0; fi
echo -e "$(translate "Formatting partition") $PARTITION $(translate "with") $FORMAT_TYPE..."
case "$FORMAT_TYPE" in
"ext4") mkfs.ext4 -F "$PARTITION" ;;
"xfs") mkfs.xfs -f "$PARTITION" ;;
"btrfs") mkfs.btrfs -f "$PARTITION" ;;
esac
if [ $? -ne 0 ]; then
cleanup
dialog --title "$(translate "Format Failed")" --msgbox \
"$(translate "Failed to format partition") $PARTITION $(translate "with") $FORMAT_TYPE." 12 70
exit 1
else
partprobe "$SELECTED"
sleep 2
fi
fi
# ------------------- Mount point and permissions (modular, non-blocking) -------------------
MOUNT_POINT=$(dialog --clear --title "$(translate "Mount Point")" \
--inputbox "$(translate "Enter the mount point for the disk (e.g., /mnt/backup):")" \
10 60 "$DEFAULT_MOUNT" 2>&1 >/dev/tty)
if [ -z "$MOUNT_POINT" ]; then
>&2 echo "$(translate "No mount point was specified.")"
return 1
fi
mkdir -p "$MOUNT_POINT"
UUID=$(blkid -s UUID -o value "$PARTITION")
FS_TYPE=$(lsblk -no FSTYPE "$PARTITION" | xargs)
FSTAB_ENTRY="UUID=$UUID $MOUNT_POINT $FS_TYPE defaults 0 0"
if grep -q "UUID=$UUID" /etc/fstab; then
sed -i "s|^.*UUID=$UUID.*|$FSTAB_ENTRY|" /etc/fstab
else
echo "$FSTAB_ENTRY" >> /etc/fstab
fi
mount "$MOUNT_POINT" 2> >(grep -v "systemd still uses")
if [ $? -eq 0 ]; then
if ! getent group sharedfiles >/dev/null; then
groupadd sharedfiles
fi
chown root:sharedfiles "$MOUNT_POINT"
chmod 2775 "$MOUNT_POINT"
echo "$MOUNT_POINT" > /usr/local/share/proxmenux/last_backup_mount.txt
MOUNT_POINT=$(echo "$MOUNT_POINT" | head -n1 | tr -d '\r\n\t ')
echo "$MOUNT_POINT"
else
>&2 echo "$(translate "Failed to mount the disk at") $MOUNT_POINT"
return 1
fi
}

View File

@@ -42,7 +42,7 @@ select_container() {
fi
CONTAINER_ID=$(whiptail --title "$(translate 'Select Container')" \
--menu "$(translate 'Select the LXC container:')" 15 60 8 $CONTAINERS 3>&1 1>&2 2>&3)
--menu "$(translate 'Select the LXC container:')" 20 70 10 $CONTAINERS 3>&1 1>&2 2>&3)
if [ -z "$CONTAINER_ID" ]; then
msg_error "$(translate 'No container selected. Exiting.')"
@@ -165,11 +165,13 @@ install_igpu_in_container() {
}
select_container
show_proxmenux_logo
configure_lxc_for_igpu
install_igpu_in_container
msg_success "$(translate 'iGPU configuration completed in container') $CONTAINER_ID."
sleep 2
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
read -r

944
scripts/emergency_repair.sh Normal file
View File

@@ -0,0 +1,944 @@
#!/bin/bash
# ==========================================================
# ProxMenu - Network Management and Repair Tool
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 2.0
# Last Updated: 07/01/2025
# ==========================================================
# Description:
# Advanced network management and troubleshooting tool for Proxmox VE.
# Features include interface detection, bridge management, connectivity testing,
# network diagnostics, configuration backup/restore, and automated repairs.
# Configuration ============================================
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
VENV_PATH="/opt/googletrans-env"
BACKUP_DIR="/var/backups/proxmenux"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
# ==========================================================
# Utility Functions
create_backup_dir() {
[ ! -d "$BACKUP_DIR" ] && mkdir -p "$BACKUP_DIR"
}
backup_network_config() {
create_backup_dir
local timestamp=$(date +"%Y-%m-%d_%H-%M-%S")
local backup_file="$BACKUP_DIR/interfaces_backup_$timestamp"
cp /etc/network/interfaces "$backup_file"
echo "$backup_file"
}
# ==========================================================
# Network Detection Functions
detect_network_method() {
# Detect Netplan
if compgen -G "/etc/netplan/*.yaml" > /dev/null; then
echo "netplan"
return 0
fi
# Detect systemd-networkd
if systemctl is-active --quiet systemd-networkd 2>/dev/null; then
echo "systemd-networkd"
return 0
fi
# Detect NetworkManager
if systemctl is-active --quiet NetworkManager 2>/dev/null; then
echo "networkmanager"
return 0
fi
# Default: Debian/Proxmox classic
echo "classic"
}
detect_physical_interfaces() {
ip -o link show | awk -F': ' '$2 !~ /^(lo|veth|dummy|bond|tap|tun|docker|br-)/ && $2 !~ /vmbr/ {print $2}' | sort
}
detect_bridge_interfaces() {
ip -o link show | awk -F': ' '$2 ~ /^vmbr/ {print $2}' | sort
}
detect_all_interfaces() {
ip -o link show | awk -F': ' '$2 !~ /^(lo|veth|dummy|tap|tun)/ {print $2}' | sort
}
get_interface_info() {
local interface="$1"
local info=""
# Get IP address
local ip=$(ip -4 addr show "$interface" 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}/\d+' | head -1)
[ -z "$ip" ] && ip="$(translate "No IP")"
# Get status
local status=$(ip link show "$interface" 2>/dev/null | grep -o "state [A-Z]*" | cut -d' ' -f2)
[ -z "$status" ] && status="UNKNOWN"
# Get MAC address
local mac=$(ip link show "$interface" 2>/dev/null | grep -o "link/ether [a-f0-9:]*" | cut -d' ' -f2)
[ -z "$mac" ] && mac="$(translate "No MAC")"
echo "$interface|$ip|$status|$mac"
}
# ==========================================================
# Network Testing Functions
test_connectivity() {
local test_results=""
local tests=(
"8.8.8.8|Google DNS"
"1.1.1.1|Cloudflare DNS"
"$(ip route | grep default | awk '{print $3}' | head -1)|Gateway"
)
show_proxmenux_logo
msg_info "$(translate "Test Connectivity")"
test_results+="$(translate "Connectivity Test Results")\n"
test_results+="$(printf '=%.0s' {1..35})\n\n"
for test in "${tests[@]}"; do
IFS='|' read -r target name <<< "$test"
if [ -n "$target" ] && [ "$target" != "" ]; then
if ping -c 2 -W 3 "$target" >/dev/null 2>&1; then
test_results+="$name ($target): $(translate "OK")\n"
else
test_results+="$name ($target): $(translate "FAILED")\n"
fi
fi
done
# DNS Resolution test
if nslookup google.com >/dev/null 2>&1; then
test_results+="$(translate "DNS Resolution"): $(translate "OK")\n"
else
test_results+="$(translate "DNS Resolution"): $(translate "FAILED")\n"
fi
cleanup
dialog --backtitle "ProxMenux" --title "$(translate "Connectivity Test")" \
--msgbox "$test_results" 15 60
}
advanced_network_diagnostics() {
NETWORK_METHOD=$(detect_network_method)
if [[ "$NETWORK_METHOD" != "classic" ]]; then
dialog --title "Unsupported Network Stack" \
--msgbox "WARNING: This script only supports the classic Debian/Proxmox network configuration (/etc/network/interfaces).\n\nDetected: $NETWORK_METHOD.\n\nAborting for safety.\n\nPlease configure your network using your distribution's supported tools." 14 70
exit 1
fi
show_proxmenux_logo
msg_info "$(translate "Advanced Diagnostics")"
sleep 1
local diag_info=""
diag_info+="$(translate "Advanced Network Diagnostics")\n"
diag_info+="$(printf '=%.0s' {1..40})\n\n"
# Network statistics
diag_info+="$(translate "Active Connections"): $(ss -tuln | wc -l)\n"
diag_info+="$(translate "Listening Ports"): $(ss -tln | grep LISTEN | wc -l)\n"
diag_info+="$(translate "Network Interfaces"): $(ip link show | grep -c "^[0-9]")\n\n"
# Check for common issues
diag_info+="$(translate "Common Issues Check"):\n"
# Check if NetworkManager is running (shouldn't be on Proxmox)
if systemctl is-active --quiet NetworkManager 2>/dev/null; then
diag_info+="$(translate "NetworkManager is running (may cause conflicts)")\n"
if dialog --title "$(translate "NetworkManager Detected")" \
--yesno "$(translate "NetworkManager is running, which may conflict with Proxmox.")\n\n$(translate "Do you want to disable and remove it now?")" 10 70; then
dialog --infobox "$(translate "Disabling and removing NetworkManager...")" 6 60
systemctl stop NetworkManager >/dev/null 2>&1
systemctl disable NetworkManager >/dev/null 2>&1
apt-get purge -y network-manager >/dev/null 2>&1
diag_info+="$(translate "NetworkManager has been removed successfully")\n"
else
diag_info+=" $(translate "User chose not to remove NetworkManager")\n"
fi
else
diag_info+="$(translate "NetworkManager not running")\n"
fi
# Check for duplicate IPs
local ips=($(ip -4 addr show | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | sort | uniq -d))
if [ ${#ips[@]} -gt 0 ]; then
diag_info+="$(translate "Duplicate IP addresses found"): ${ips[*]}\n"
else
diag_info+="$(translate "No duplicate IP addresses")\n"
fi
cleanup
dialog --backtitle "ProxMenux" --title "$(translate "Network Diagnostics")" \
--msgbox "$diag_info" 18 70
}
# ==========================================================
# SAFE Network Analysis Functions (NO AUTO-REPAIR)
# ==========================================================
analyze_bridge_configuration() {
NETWORK_METHOD=$(detect_network_method)
if [[ "$NETWORK_METHOD" != "classic" ]]; then
dialog --title "Unsupported Network Stack" \
--msgbox "WARNING: This script only supports the classic Debian/Proxmox network configuration (/etc/network/interfaces).\n\nDetected: $NETWORK_METHOD.\n\nAborting for safety.\n\nPlease configure your network using your distribution's supported tools." 14 70
exit 1
fi
show_proxmenux_logo
msg_info "$(translate "Analyzing Bridge Configuration - READ ONLY MODE")"
sleep 1
local physical_interfaces=($(detect_physical_interfaces))
local bridges=($(detect_bridge_interfaces))
local analysis_report=""
local issues_found=0
local suggestions=""
analysis_report+="🔍 $(translate "BRIDGE CONFIGURATION ANALYSIS")\n"
analysis_report+="$(printf '=%.0s' {1..50})\n\n"
cleanup
if [ ${#bridges[@]} -eq 0 ]; then
analysis_report+=" $(translate "No bridges found in system")\n"
dialog --backtitle "ProxMenux" --title "$(translate "Bridge Analysis")" --msgbox "$analysis_report" 10 60
return
fi
# Analyze each bridge
for bridge in "${bridges[@]}"; do
analysis_report+="🌉 $(translate "Bridge"): $bridge\n"
# Get current configuration
local current_ports=$(grep -A5 "iface $bridge" /etc/network/interfaces 2>/dev/null | grep "bridge-ports" | cut -d' ' -f2-)
local bridge_ip=$(ip -4 addr show "$bridge" 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}/\d+' | head -1)
local bridge_status=$(ip link show "$bridge" 2>/dev/null | grep -o "state [A-Z]*" | cut -d' ' -f2)
analysis_report+=" 📍 $(translate "Status"): ${bridge_status:-UNKNOWN}\n"
analysis_report+=" 🌐 $(translate "IP"): ${bridge_ip:-$(translate "No IP assigned")}\n"
analysis_report+=" 🔌 $(translate "Configured Ports"): ${current_ports:-$(translate "None")}\n"
if [ -n "$current_ports" ]; then
local invalid_ports=""
local valid_ports=""
# Check each configured port
for port in $current_ports; do
if ip link show "$port" >/dev/null 2>&1; then
valid_ports+="$port "
analysis_report+="$(translate "Port") $port: $(translate "EXISTS")\n"
else
invalid_ports+="$port "
analysis_report+="$(translate "Port") $port: $(translate "NOT FOUND")\n"
((issues_found++))
fi
done
# Generate suggestions for invalid ports
if [ -n "$invalid_ports" ]; then
suggestions+="🔧 $(translate "SUGGESTION FOR") $bridge:\n"
if [ ${#physical_interfaces[@]} -gt 0 ]; then
suggestions+=" $(translate "Replace invalid port(s)") '$invalid_ports' $(translate "with"): ${physical_interfaces[0]}\n"
suggestions+=" $(translate "Command"): sed -i 's/bridge-ports.*/bridge-ports ${physical_interfaces[0]}/' /etc/network/interfaces\n"
else
suggestions+=" $(translate "Remove invalid port(s)") '$invalid_ports'\n"
suggestions+=" $(translate "Command"): sed -i 's/bridge-ports.*/bridge-ports none/' /etc/network/interfaces\n"
fi
suggestions+="\n"
fi
else
analysis_report+=" ⚠️ $(translate "No ports configured")\n"
if [ ${#physical_interfaces[@]} -gt 0 ]; then
suggestions+="🔧 $(translate "SUGGESTION FOR") $bridge:\n"
suggestions+=" $(translate "Consider adding physical interface"): ${physical_interfaces[0]}\n"
suggestions+=" $(translate "Command"): sed -i '/iface $bridge/a\\ bridge-ports ${physical_interfaces[0]}' /etc/network/interfaces\n\n"
fi
fi
analysis_report+="\n"
done
# Summary
analysis_report+="📊 $(translate "ANALYSIS SUMMARY")\n"
analysis_report+="$(printf '=%.0s' {1..25})\n"
analysis_report+="$(translate "Bridges analyzed"): ${#bridges[@]}\n"
analysis_report+="$(translate "Issues found"): $issues_found\n"
local auto_only=$(grep "^auto" /etc/network/interfaces | awk '{print $2}' | while read i; do
grep -q "^iface $i" /etc/network/interfaces || echo "$i"
done)
if [ -n "$auto_only" ]; then
analysis_report+="⚠️ $(translate "Interfaces defined with 'auto' but no 'iface' block"): $auto_only\n"
((issues_found++))
fi
analysis_report+="$(translate "Physical interfaces available"): ${#physical_interfaces[@]}\n\n"
if [ $issues_found -gt 0 ]; then
analysis_report+="$suggestions"
analysis_report+="⚠️ $(translate "IMPORTANT"): $(translate "No changes have been made to your system")\n"
analysis_report+="$(translate "Use the Guided Repair option to fix issues safely")\n"
else
analysis_report+="$(translate "No bridge configuration issues found")\n"
fi
# Show analysis in scrollable dialog
local temp_file=$(mktemp)
echo -e "$analysis_report" > "$temp_file"
dialog --backtitle "ProxMenux" --title "$(translate "Bridge Configuration Analysis")" \
--textbox "$temp_file" 25 80
rm -f "$temp_file"
# Offer guided repair if issues found
if [ $issues_found -gt 0 ]; then
if dialog --backtitle "ProxMenux" --title "$(translate "Guided Repair Available")" \
--yesno "$(translate "Issues were found. Would you like to use the Guided Repair Assistant?")" 8 60; then
guided_bridge_repair
fi
fi
}
guided_bridge_repair() {
local step=1
local total_steps=5
local timestamp=$(date +"%Y%m%d_%H%M%S")
local preview_backup_file="$BACKUP_DIR/interfaces_backup_$timestamp"
if ! dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Safety Backup")" \
--yesno "$(translate "Before making any changes, we'll create a safety backup.")\n\n$(translate "Backup location"): $preview_backup_file\n\n$(translate "Continue?")" 12 70; then
return
fi
((step++))
show_proxmenux_logo
local backup_file=$(backup_network_config)
sleep 1
dialog --backtitle "ProxMenux" --title "$(translate "Backup Created")" \
--msgbox "$(translate "Safety backup created"): $backup_file\n\n$(translate "You can restore it anytime with"):\ncp $backup_file /etc/network/interfaces" 10 70
# Step 2: Show current configuration
if ! dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Current Configuration")" \
--yesno "$(translate "Let's review your current network configuration.")\n\n$(translate "Would you like to see the current") /etc/network/interfaces $(translate "file?")" 10 70; then
return
fi
((step++))
# Show current config
local temp_config=$(mktemp)
cat /etc/network/interfaces > "$temp_config"
dialog --backtitle "ProxMenux" --title "$(translate "Current Network Configuration")" \
--textbox "$temp_config" 20 80
rm -f "$temp_config"
# Step 3: Identify specific changes needed
local physical_interfaces=($(detect_physical_interfaces))
local bridges=($(detect_bridge_interfaces))
local changes_needed=""
for bridge in "${bridges[@]}"; do
local current_ports=$(grep -A5 "iface $bridge" /etc/network/interfaces 2>/dev/null | grep "bridge-ports" | cut -d' ' -f2-)
if [ -n "$current_ports" ]; then
for port in $current_ports; do
if ! ip link show "$port" >/dev/null 2>&1; then
if [ ${#physical_interfaces[@]} -gt 0 ]; then
changes_needed+="$(translate "Bridge") $bridge: $(translate "Replace") '$port' $(translate "with") '${physical_interfaces[0]}'\n"
else
changes_needed+="$(translate "Bridge") $bridge: $(translate "Remove invalid port") '$port'\n"
fi
fi
done
fi
done
if [ -z "$changes_needed" ]; then
dialog --backtitle "ProxMenux" --title "$(translate "No Changes Needed")" \
--msgbox "$(translate "After detailed analysis, no changes are needed.")" 8 50
return
fi
if ! dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Proposed Changes")" \
--yesno "$(translate "These are the changes that will be made"):\n\n$changes_needed\n$(translate "Do you want to proceed?")" 15 70; then
return
fi
((step++))
# Step 4: Apply changes with verification
dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Applying Changes")" \
--infobox "$(translate "Applying changes safely...")\n\n$(translate "This may take a few seconds...")" 8 50
# Apply the changes
for bridge in "${bridges[@]}"; do
local current_ports=$(grep -A5 "iface $bridge" /etc/network/interfaces 2>/dev/null | grep "bridge-ports" | cut -d' ' -f2-)
if [ -n "$current_ports" ]; then
local new_ports=""
for port in $current_ports; do
if ip link show "$port" >/dev/null 2>&1; then
new_ports+="$port "
fi
done
# If no valid ports and we have physical interfaces, use the first one
if [ -z "$new_ports" ] && [ ${#physical_interfaces[@]} -gt 0 ]; then
new_ports="${physical_interfaces[0]}"
fi
# Apply the change
if [ "$new_ports" != "$current_ports" ]; then
sed -i "/iface $bridge/,/bridge-ports/ s/bridge-ports.*/bridge-ports $new_ports/" /etc/network/interfaces
fi
fi
done
((step++))
# Step 5: Verification
local verification_report=""
verification_report+="$(translate "CHANGES APPLIED SUCCESSFULLY")\n\n"
verification_report+="$(translate "Verification"):\n"
for bridge in "${bridges[@]}"; do
local new_ports=$(grep -A5 "iface $bridge" /etc/network/interfaces 2>/dev/null | grep "bridge-ports" | cut -d' ' -f2-)
verification_report+="$(translate "Bridge") $bridge: $new_ports\n"
# Verify each port exists
for port in $new_ports; do
if ip link show "$port" >/dev/null 2>&1; then
verification_report+="$port: $(translate "EXISTS")\n"
else
verification_report+="$port: $(translate "NOT FOUND")\n"
fi
done
done
verification_report+="\n$(translate "Backup available at"): $backup_file\n"
verification_report+="$(translate "To restore"): cp $backup_file /etc/network/interfaces"
dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Repair Complete")" \
--msgbox "$verification_report" 18 70
# Ask about network restart
if dialog --backtitle "ProxMenux" --title "$(translate "Network Restart")" \
--yesno "$(translate "Changes have been applied to the configuration file.")\n\n$(translate "Do you want to restart the network service to apply changes?")\n\n$(translate "WARNING: This may cause a brief disconnection.")" 12 70; then
clear
msg_info "$(translate "Restarting network service...")"
if systemctl restart networking; then
msg_ok "$(translate "Network service restarted successfully")"
else
msg_error "$(translate "Failed to restart network service")"
msg_warn "$(translate "You can restore the backup with"): cp $backup_file /etc/network/interfaces"
fi
msg_success "$(translate "Press ENTER to continue...")"
read -r
fi
}
analyze_network_configuration() {
NETWORK_METHOD=$(detect_network_method)
if [[ "$NETWORK_METHOD" != "classic" ]]; then
dialog --title "Unsupported Network Stack" \
--msgbox "WARNING: This script only supports the classic Debian/Proxmox network configuration (/etc/network/interfaces).\n\nDetected: $NETWORK_METHOD.\n\nAborting for safety.\n\nPlease configure your network using your distribution's supported tools." 14 70
exit 1
fi
show_proxmenux_logo
msg_info "$(translate "Analyzing Network Configuration - READ ONLY MODE")"
sleep 1
local configured_interfaces=($(grep "^iface" /etc/network/interfaces | awk '{print $2}' | grep -v "lo"))
local analysis_report=""
local issues_found=0
local suggestions=""
analysis_report+="🔍 $(translate "NETWORK CONFIGURATION ANALYSIS")\n"
analysis_report+="$(printf '=%.0s' {1..50})\n\n"
cleanup
if [ ${#configured_interfaces[@]} -eq 0 ]; then
analysis_report+=" $(translate "No network interfaces configured (besides loopback)")\n"
dialog --title "$(translate "Configuration Analysis")" --msgbox "$analysis_report" 10 60
return
fi
analysis_report+="📋 $(translate "CONFIGURED INTERFACES")\n"
analysis_report+="$(printf '=%.0s' {1..30})\n"
# Analyze each configured interface
for iface in "${configured_interfaces[@]}"; do
analysis_report+="🔌 $(translate "Interface"): $iface\n"
# Check if interface exists physically
if ip link show "$iface" >/dev/null 2>&1; then
local status=$(ip link show "$iface" 2>/dev/null | grep -o "state [A-Z]*" | cut -d' ' -f2)
local ip=$(ip -4 addr show "$iface" 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}/\d+' | head -1)
analysis_report+="$(translate "Status"): $(translate "EXISTS") ($status)\n"
analysis_report+=" 🌐 $(translate "IP"): ${ip:-$(translate "No IP assigned")}\n"
# Check if it's a bridge or bond (these are virtual, so it's normal they exist)
if [[ $iface =~ ^(vmbr|bond) ]]; then
analysis_report+=" $(translate "Type"): $(translate "Virtual interface (normal)")\n"
else
analysis_report+=" $(translate "Type"): $(translate "Physical interface")\n"
fi
else
analysis_report+="$(translate "Status"): $(translate "NOT FOUND")\n"
analysis_report+=" ⚠️ $(translate "Issue"): $(translate "Configured but doesn't exist")\n"
((issues_found++))
# Only suggest removal for non-virtual interfaces
if [[ ! $iface =~ ^(vmbr|bond) ]]; then
suggestions+="🔧 $(translate "SUGGESTION FOR") $iface:\n"
suggestions+=" $(translate "This interface is configured but doesn't exist physically")\n"
suggestions+=" $(translate "Consider removing its configuration")\n"
suggestions+=" $(translate "Command"): sed -i '/iface $iface/,/^$/d' /etc/network/interfaces\n\n"
fi
fi
analysis_report+="\n"
done
# Summary
analysis_report+="📊 $(translate "ANALYSIS SUMMARY")\n"
analysis_report+="$(printf '=%.0s' {1..25})\n"
analysis_report+="$(translate "Interfaces configured"): ${#configured_interfaces[@]}\n"
analysis_report+="$(translate "Issues found"): $issues_found\n\n"
if [ $issues_found -gt 0 ]; then
analysis_report+="$suggestions"
analysis_report+="⚠️ $(translate "IMPORTANT"): $(translate "No changes have been made to your system")\n"
analysis_report+="$(translate "Use the Guided Cleanup option to fix issues safely")\n"
else
analysis_report+="$(translate "No configuration issues found")\n"
fi
# Show analysis in scrollable dialog
local temp_file=$(mktemp)
echo -e "$analysis_report" > "$temp_file"
dialog --backtitle "ProxMenux" --title "$(translate "Network Configuration Analysis")" \
--textbox "$temp_file" 25 80
rm -f "$temp_file"
# Offer guided cleanup if issues found
if [ $issues_found -gt 0 ]; then
if dialog --backtitle "ProxMenux" --title "$(translate "Guided Cleanup Available")" \
--yesno "$(translate "Issues were found. Would you like to use the Guided Cleanup Assistant?")" 8 60; then
guided_configuration_cleanup
fi
fi
}
guided_configuration_cleanup() {
local step=1
local total_steps=5
local timestamp=$(date +"%Y%m%d_%H%M%S")
local preview_backup_file="$BACKUP_DIR/interfaces_backup_$timestamp"
if ! dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Safety Backup")" \
--yesno "$(translate "Before making any changes, we'll create a safety backup.")\n\n$(translate "Backup location"): $preview_backup_file\n\n$(translate "Continue?")" 12 70; then
return
fi
((step++))
show_proxmenux_logo
local backup_file=$(backup_network_config)
sleep 1
dialog --backtitle "ProxMenux" --title "$(translate "Backup Created")" \
--msgbox "$(translate "Safety backup created"): $backup_file\n\n$(translate "You can restore it anytime with"):\ncp $backup_file /etc/network/interfaces" 10 70
# Step 2: Identify interfaces to remove
local configured_interfaces=($(grep "^iface" /etc/network/interfaces | awk '{print $2}' | grep -v "lo"))
local interfaces_to_remove=""
local removal_list=""
for iface in "${configured_interfaces[@]}"; do
if [[ ! $iface =~ ^(vmbr|bond) ]] && ! ip link show "$iface" >/dev/null 2>&1; then
interfaces_to_remove+="$iface "
removal_list+="$iface: $(translate "Configured but doesn't exist")\n"
fi
done
if [ -z "$interfaces_to_remove" ]; then
dialog --backtitle "ProxMenux" --title "$(translate "No Cleanup Needed")" \
--msgbox "$(translate "After detailed analysis, no cleanup is needed.")" 8 50
return
fi
if ! dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Interfaces to Remove")" \
--yesno "$(translate "These interface configurations will be removed"):\n\n$removal_list\n$(translate "Do you want to proceed?")" 15 70; then
return
fi
((step++))
# Step 3: Show what will be removed
local temp_preview=$(mktemp)
echo "$(translate "Configuration sections that will be REMOVED"):" > "$temp_preview"
echo "=================================================" >> "$temp_preview"
echo "" >> "$temp_preview"
for iface in $interfaces_to_remove; do
echo "# Interface: $iface" >> "$temp_preview"
sed -n "/^iface $iface/,/^$/p" /etc/network/interfaces >> "$temp_preview"
echo "" >> "$temp_preview"
done
if ! dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Preview Changes")" \
--yesno "$(translate "Review what will be removed"):\n\n$(translate "Press OK to see the preview, then confirm")" 10 60; then
rm -f "$temp_preview"
return
fi
dialog --backtitle "ProxMenux" --title "$(translate "Configuration to be Removed")" \
--textbox "$temp_preview" 20 80
rm -f "$temp_preview"
if ! dialog --backtitle "ProxMenux" --title "$(translate "Final Confirmation")" \
--yesno "$(translate "Are you sure you want to remove these configurations?")" 8 60; then
return
fi
((step++))
# Step 4: Apply changes
dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Applying Changes")" \
--infobox "$(translate "Removing invalid configurations...")\n\n$(translate "This may take a few seconds...")" 8 50
for iface in $interfaces_to_remove; do
sed -i "/^iface $iface/,/^$/d" /etc/network/interfaces
done
((step++))
# Step 5: Verification
local verification_report=""
verification_report+="$(translate "CLEANUP COMPLETED SUCCESSFULLY")\n\n"
verification_report+="$(translate "Removed configurations for"):\n"
for iface in $interfaces_to_remove; do
verification_report+="$iface\n"
done
verification_report+="\n$(translate "Verification"): $(translate "Checking remaining interfaces")\n"
local remaining_interfaces=($(grep "^iface" /etc/network/interfaces | awk '{print $2}' | grep -v "lo"))
for iface in "${remaining_interfaces[@]}"; do
if ip link show "$iface" >/dev/null 2>&1; then
verification_report+="$iface: $(translate "OK")\n"
else
verification_report+="⚠️ $iface: $(translate "Still has issues")\n"
fi
done
verification_report+="\n$(translate "Backup available at"): $backup_file\n"
verification_report+="$(translate "To restore"): cp $backup_file /etc/network/interfaces"
dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Cleanup Complete")" \
--msgbox "$verification_report" 18 70
}
# ==========================================================
# Configuration Management
show_network_config() {
NETWORK_METHOD=$(detect_network_method)
if [[ "$NETWORK_METHOD" != "classic" ]]; then
dialog --title "Unsupported Network Stack" \
--msgbox "WARNING: This script only supports the classic Debian/Proxmox network configuration (/etc/network/interfaces).\n\nDetected: $NETWORK_METHOD.\n\nAborting for safety.\n\nPlease configure your network using your distribution's supported tools." 14 70
exit 1
fi
local config_content
config_content=$(cat /etc/network/interfaces)
show_proxmenux_logo
echo -e
echo -e
echo "========== $(translate "Network Configuration File") =========="
echo
cat /etc/network/interfaces
echo
msg_success "$(translate "Press Enter to continue...")"
read -r
}
restore_network_backup() {
NETWORK_METHOD=$(detect_network_method)
if [[ "$NETWORK_METHOD" != "classic" ]]; then
dialog --title "Unsupported Network Stack" \
--msgbox "WARNING: This script only supports the classic Debian/Proxmox network configuration (/etc/network/interfaces).\n\nDetected: $NETWORK_METHOD.\n\nAborting for safety.\n\nPlease configure your network using your distribution's supported tools." 14 70
exit 1
fi
local backups=($(ls -1 "$BACKUP_DIR"/interfaces_backup_* 2>/dev/null | sort -r))
if [ ${#backups[@]} -eq 0 ]; then
dialog --backtitle "ProxMenux" --title "$(translate "No Backups")" \
--msgbox "\n$(translate "No network configuration backups found.")" 14 60
return
fi
local menu_items=()
local counter=1
for backup in "${backups[@]}"; do
local filename=$(basename "$backup")
local timestamp=$(basename "$backup" | sed 's/interfaces_backup_//')
menu_items+=("$counter" "$timestamp")
((counter++))
done
local selection=$(dialog --backtitle "ProxMenux" --title "$(translate "Restore Backup")" \
--menu "$(translate "Select backup to restore:"):" 15 60 8 \
"${menu_items[@]}" 3>&1 1>&2 2>&3)
if [ -n "$selection" ] && [ "$selection" -ge 1 ] && [ "$selection" -le ${#backups[@]} ]; then
local selected_backup="${backups[$((selection-1))]}"
if dialog --backtitle "ProxMenux" --title "$(translate "Preview Backup")" \
--yesno "\n$(translate "Do you want to view the selected backup before restoring?")" 8 60; then
dialog --backtitle "ProxMenux" --title "$(translate "Backup Preview")" \
--textbox "$selected_backup" 22 80
fi
if dialog --backtitle "ProxMenux" --title "$(translate "Confirm Restore")" \
--yesno "\n$(translate "Are you sure you want to restore this backup?\nCurrent configuration will be overwritten.")\n\n$(translate "For your safety, a backup of the current configuration will be created automatically before restoring.")" 14 70; then
local pre_restore_backup=$(backup_network_config)
cp "$selected_backup" /etc/network/interfaces
dialog --backtitle "ProxMenux" --title "$(translate "Backup Restored")" \
--msgbox "\n$(translate "Network configuration has been restored from backup.")" 8 60
if dialog --backtitle "ProxMenux" --title "$(translate "Restart Network")" \
--yesno "\n$(translate "Do you want to restart the network service now to apply changes?")" 8 60; then
if systemctl restart networking; then
dialog --backtitle "ProxMenux" --title "$(translate "Network Restarted")" \
--msgbox "\n$(translate "Network service restarted successfully.")" 8 50
fi
fi
fi
fi
}
# ==========================================================
# Emergency System Repair Functions
# ==========================================================
emergency_proxmox_repair() {
clear
show_proxmenux_logo
echo -e
echo "=========================================="
echo " $(translate "EMERGENCY PROXMOX SYSTEM REPAIR")"
echo "=========================================="
echo
msg_warn "$(translate "This will reinstall core Proxmox packages and regenerate certificates")"
echo "$(translate "This operation may take several minutes and requires internet connectivity.")"
echo
echo -n "$(translate "Do you want to continue?") (y/N): "
read -r confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
msg_info2 "$(translate "Operation cancelled by user.")"
return
fi
msg_info2 "$(translate "Starting Proxmox system repair...")"
echo
# Step 1: Update package lists
msg_success "$(translate "Step") 1/3: $(translate "Updating package lists...")"
if apt-get update; then
msg_ok "$(translate "Package lists updated successfully")"
else
msg_error "$(translate "Failed to update package lists")"
echo "$(translate "This might indicate network connectivity issues.")"
echo
echo "$(translate "Press ENTER to continue...")"
read -r
return 1
fi
echo
# Step 2: Reinstall core Proxmox packages
msg_success "$(translate "Step") 2/3: $(translate "Reinstalling core Proxmox packages...")"
echo "$(translate "This may take several minutes...")"
if apt-get install --reinstall proxmox-widget-toolkit pve-manager -y; then
msg_ok "$(translate "Core Proxmox packages reinstalled successfully")"
else
msg_error "$(translate "Failed to reinstall Proxmox packages")"
echo "$(translate "Check the error messages above for details.")"
echo
echo "$(translate "Press ENTER to continue...")"
read -r
return 1
fi
echo
# Step 3: Regenerate certificates and restart services
msg_success "$(translate "Step") 3/3: $(translate "Regenerating certificates and restarting services...")"
# Update certificates
if command -v pvecm >/dev/null 2>&1; then
msg_info "$(translate "Updating cluster certificates...")"
if pvecm updatecerts -f; then
msg_ok "$(translate "Cluster certificates updated")"
else
msg_warn "$(translate "Failed to update cluster certificates (might not be in a cluster)")"
fi
else
msg_warn "$(translate "pvecm command not found (might not be in a cluster)")"
fi
# Restart Proxmox services
msg_success "$(translate "Restarting Proxmox services...")"
local services_restarted=0
local services_failed=0
for service in pveproxy pvedaemon; do
if systemctl restart "$service"; then
msg_ok " $service $(translate "restarted successfully")"
((services_restarted++))
else
msg_error " $(translate "Failed to restart") $service"
((services_failed++))
fi
done
echo
echo "$(translate "REPAIR SUMMARY"):"
echo "==============="
echo " $(translate "Package lists"): $(translate "Updated")"
echo " $(translate "Core packages"): $(translate "Reinstalled")"
echo " $(translate "Services restarted"): $services_restarted"
echo " $(translate "Services failed"): $services_failed"
if [ $services_failed -eq 0 ]; then
msg_ok "$(translate "Proxmox system repair completed successfully!")"
echo
echo "$(translate "You should now be able to access the Proxmox web interface.")"
echo "$(translate "Try accessing"): https://$(hostname -I | awk '{print $1}'):8006"
else
msg_warn "$(translate "Proxmox system repair completed with some issues.")"
echo "$(translate "Check the service status manually if needed.")"
fi
echo
echo "$(translate "Press ENTER to continue...")"
read -r
}
restart_network_service() {
if dialog --title "$(translate "Restart Network")" \
--yesno "$(translate "This will restart the network service and may cause a brief disconnection. Continue?")" 10 60; then
show_proxmenux_logo
msg_info "$(translate "Restarting network service...")"
if systemctl restart networking; then
msg_ok "$(translate "Network service restarted successfully")"
else
msg_error "$(translate "Failed to restart network service")"
msg_warn "$(translate "If you lose connectivity, you can restore from backup using the console.")"
fi
msg_success "$(translate "Press ENTER to continue...")"
read -r
fi
}
# ==========================================================
# Main Menu
show_main_menu() {
while true; do
local selection=$(dialog --clear \
--backtitle "ProxMenux" \
--title "$(translate "Network Management - SAFE MODE")" \
--menu "$(translate "Select an option:"):" 20 70 12 \
"1" "$(translate "Test Connectivity")" \
"2" "$(translate "Advanced Diagnostics")" \
"3" "$(translate "Analyze Bridge Configuration")" \
"4" "$(translate "Analyze Network Configuration")" \
"5" "$(translate "Restart Network Service")" \
"6" "$(translate "Show Network Config File")" \
"7" "$(translate "Emergency Proxmox System Repair")" \
"8" "$(translate "Restore Network Backup")" \
"0" "$(translate "Exit")" \
3>&1 1>&2 2>&3)
case $selection in
1) test_connectivity ;;
2) advanced_network_diagnostics ;;
3) analyze_bridge_configuration ;;
4) analyze_network_configuration ;;
5) restart_network_service ;;
6) show_network_config ;;
7) emergency_proxmox_repair ;;
8) restore_network_backup ;;
0|"") exit ;;
esac
done
}
# ==========================================================
show_main_menu

View File

@@ -6,15 +6,15 @@
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 28/01/2025
# Version : 1.1
# Last Updated: 29/05/2025
# ==========================================================
# Description:
# This script automates the process of importing disk images into Proxmox VE virtual machines (VMs),
# making it easy to attach pre-existing disk files without manual configuration.
#
# Before running the script, ensure that disk images are available in /var/lib/vz/template/images/.
# The script scans this directory for compatible formats (.img, .qcow2, .vmdk) and lists the available files.
# The script scans this directory for compatible formats (.img, .qcow2, .vmdk, .raw) and lists the available files.
#
# Using an interactive menu, you can:
# - Select a VM to attach the imported disk.
@@ -32,201 +32,152 @@ BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
[[ -f "$UTILS_FILE" ]] && source "$UTILS_FILE"
load_language
initialize_cache
show_proxmenux_logo
# ==========================================================
# Path where disk images are stored
IMAGES_DIR="/var/lib/vz/template/images/"
# Configuration ============================================
# Initial setup
if [ ! -d "$IMAGES_DIR" ]; then
msg_info "$(translate 'Creating images directory')"
mkdir -p "$IMAGES_DIR"
chmod 755 "$IMAGES_DIR"
msg_ok "$(translate 'Images directory created:') $IMAGES_DIR"
detect_image_dir() {
for store in $(pvesm status -content images | awk 'NR>1 {print $1}'); do
path=$(pvesm path "${store}:template" 2>/dev/null)
if [[ -d "$path" ]]; then
for ext in raw img qcow2 vmdk; do
if compgen -G "$path/*.$ext" > /dev/null; then
echo "$path"
return 0
fi
done
for sub in images iso; do
dir="$path/$sub"
if [[ -d "$dir" ]]; then
for ext in raw img qcow2 vmdk; do
if compgen -G "$dir/*.$ext" > /dev/null; then
echo "$dir"
return 0
fi
done
fi
done
fi
done
for fallback in /var/lib/vz/template/images /var/lib/vz/template/iso; do
if [[ -d "$fallback" ]]; then
for ext in raw img qcow2 vmdk; do
if compgen -G "$fallback/*.$ext" > /dev/null; then
echo "$fallback"
return 0
fi
done
fi
done
return 1
}
IMAGES_DIR=$(detect_image_dir)
if [[ -z "$IMAGES_DIR" ]]; then
dialog --title "$(translate 'No Images Found')" \
--msgbox "$(translate 'Could not find any directory containing disk images')\n\n$(translate 'Make sure there is at least one file with extension .img, .qcow2, .vmdk or .raw')" 15 60
exit 1
fi
# Check if there are any images in the directory
IMAGES=$(ls -A "$IMAGES_DIR" | grep -E "\.(img|qcow2|vmdk)$")
IMAGES=$(ls -A "$IMAGES_DIR" | grep -E "\.(img|qcow2|vmdk|raw)$")
if [ -z "$IMAGES" ]; then
whiptail --title "$(translate 'No Images Found')" \
--msgbox "$(translate 'No images available for import in:')\n\n$IMAGES_DIR\n\n$(translate 'Supported formats: .img, .qcow2, .vmdk')\n\n$(translate 'Please add some images and try again.')" 15 60
dialog --title "$(translate 'No Disk Images Found')" \
--msgbox "$(translate 'No compatible disk images found in:')\n\n$IMAGES_DIR\n\n$(translate 'Supported formats: .img, .qcow2, .vmdk, .raw')" 15 60
exit 1
fi
# Display initial message
whiptail --title "$(translate 'Import Disk Image')" --msgbox "$(translate 'Make sure the disk images you want to import are located in:')\n\n$IMAGES_DIR\n\n$(translate 'Supported formats: .img, .qcow2, .vmdk.')" 15 60
# 1. Select VM
# === Select VM
msg_info "$(translate 'Getting VM list')"
VM_LIST=$(qm list | awk 'NR>1 {print $1" "$2}')
if [ -z "$VM_LIST" ]; then
msg_error "$(translate 'No VMs available in the system')"
exit 1
fi
[[ -z "$VM_LIST" ]] && { msg_error "$(translate 'No VMs available in the system')"; exit 1; }
msg_ok "$(translate 'VM list obtained')"
VMID=$(whiptail --title "$(translate 'Select VM')" --menu "$(translate 'Select the VM where you want to import the disk image:')" 15 60 8 $VM_LIST 3>&1 1>&2 2>&3)
if [ -z "$VMID" ]; then
# msg_error "$(translate 'No VM selected')"
exit 1
fi
VMID=$(whiptail --title "$(translate 'Select VM')" \
--menu "$(translate 'Select the VM where you want to import the disk image:')" 20 70 10 $VM_LIST 3>&1 1>&2 2>&3)
[[ -z "$VMID" ]] && exit 1
# 2. Select storage volume
# === Select storage
msg_info "$(translate 'Getting storage volumes')"
STORAGE_LIST=$(pvesm status -content images | awk 'NR>1 {print $1}')
if [ -z "$STORAGE_LIST" ]; then
msg_error "$(translate 'No storage volumes available')"
exit 1
fi
[[ -z "$STORAGE_LIST" ]] && { msg_error "$(translate 'No storage volumes available')"; exit 1; }
msg_ok "$(translate 'Storage volumes obtained')"
# Create an array of storage options for whiptail
STORAGE_OPTIONS=()
while read -r storage; do
STORAGE_OPTIONS+=("$storage" "")
done <<< "$STORAGE_LIST"
STORAGE=$(whiptail --title "$(translate 'Select Storage')" --menu "$(translate 'Select the storage volume for disk import:')" 15 60 8 "${STORAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3)
if [ -z "$STORAGE" ]; then
# msg_error "$(translate 'No storage selected')"
exit 1
fi
while read -r storage; do STORAGE_OPTIONS+=("$storage" ""); done <<< "$STORAGE_LIST"
STORAGE=$(whiptail --title "$(translate 'Select Storage')" \
--menu "$(translate 'Select the storage volume for disk import:')" 20 70 10 "${STORAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3)
[[ -z "$STORAGE" ]] && exit 1
# 3. Select disk images
msg_info "$(translate 'Scanning disk images')"
if [ -z "$IMAGES" ]; then
msg_warn "$(translate 'No compatible disk images found in') $IMAGES_DIR"
exit 0
fi
msg_ok "$(translate 'Disk images found')"
# === Select images
IMAGE_OPTIONS=()
while read -r img; do
IMAGE_OPTIONS+=("$img" "" "OFF")
done <<< "$IMAGES"
while read -r img; do IMAGE_OPTIONS+=("$img" "" "OFF"); done <<< "$IMAGES"
SELECTED_IMAGES=$(whiptail --title "$(translate 'Select Disk Images')" \
--checklist "$(translate 'Select the disk images to import:')" 20 70 12 "${IMAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3)
[[ -z "$SELECTED_IMAGES" ]] && exit 1
SELECTED_IMAGES=$(whiptail --title "$(translate 'Select Disk Images')" --checklist "$(translate 'Select the disk images to import:')" 20 60 10 "${IMAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3)
if [ -z "$SELECTED_IMAGES" ]; then
# msg_error "$(translate 'No images selected')"
exit 1
fi
# 4. Import each selected image
# === Import each selected image
for IMAGE in $SELECTED_IMAGES; do
# Remove quotes from selected image
IMAGE=$(echo "$IMAGE" | tr -d '"')
# 5. Select interface type for each image
INTERFACE=$(whiptail --title "$(translate 'Interface Type')" --menu "$(translate 'Select the interface type for the image:') $IMAGE" 15 40 4 \
"sata" "SATA" \
"scsi" "SCSI" \
"virtio" "VirtIO" \
"ide" "IDE" 3>&1 1>&2 2>&3)
if [ -z "$INTERFACE" ]; then
msg_error "$(translate 'No interface type selected for') $IMAGE"
continue
fi
"sata" "SATA" "scsi" "SCSI" "virtio" "VirtIO" "ide" "IDE" 3>&1 1>&2 2>&3)
[[ -z "$INTERFACE" ]] && { msg_error "$(translate 'No interface type selected for') $IMAGE"; continue; }
FULL_PATH="$IMAGES_DIR/$IMAGE"
# Show initial message
msg_info "$(translate 'Importing image:')"
# Temporary file to capture the imported disk
msg_info "$(translate 'Importing image:') $IMAGE"
TEMP_DISK_FILE=$(mktemp)
# Execute the command and process its output in real-time
qm importdisk "$VMID" "$FULL_PATH" "$STORAGE" 2>&1 | while read -r line; do
if [[ "$line" =~ transferred ]]; then
# Extract the progress percentage
PERCENT=$(echo "$line" | grep -oP "\(\d+\.\d+%\)" | tr -d '()%')
# Show progress with custom format without translation
echo -ne "\r${TAB}${YW}-$(translate 'Importing image:') $IMAGE-${CL} ${PERCENT}%"
echo -ne "\r${TAB}${BL}-$(translate 'Importing image:') $IMAGE-${CL} ${PERCENT}%"
elif [[ "$line" =~ successfully\ imported\ disk ]]; then
# Extract the imported disk name and save it to the temporary file
echo "$line" | grep -oP "(?<=successfully imported disk ').*(?=')" > "$TEMP_DISK_FILE"
fi
done
echo -ne "\n"
IMPORT_STATUS=${PIPESTATUS[0]}
IMPORT_STATUS=${PIPESTATUS[0]} # Capture the exit status of the main command
if [ $IMPORT_STATUS -eq 0 ]; then
if [ "$IMPORT_STATUS" -eq 0 ]; then
msg_ok "$(translate 'Image imported successfully')"
# Read the imported disk from the temporary file
IMPORTED_DISK=$(cat "$TEMP_DISK_FILE")
rm -f "$TEMP_DISK_FILE" # Delete the temporary file
rm -f "$TEMP_DISK_FILE"
if [ -n "$IMPORTED_DISK" ]; then
# Find the next available disk slot
EXISTING_DISKS=$(qm config "$VMID" | grep -oP "${INTERFACE}\d+" | sort -n)
if [ -z "$EXISTING_DISKS" ]; then
# If there are no existing disks, start from 0
NEXT_SLOT=0
else
# If there are existing disks, take the last one and add 1
LAST_SLOT=$(echo "$EXISTING_DISKS" | tail -n1 | sed "s/${INTERFACE}//")
NEXT_SLOT=$((LAST_SLOT + 1))
fi
[[ -n "$EXISTING_DISKS" ]] && NEXT_SLOT=$(( $(echo "$EXISTING_DISKS" | tail -n1 | sed "s/${INTERFACE}//") + 1 ))
# Ask if SSD emulation is desired (only for non-VirtIO interfaces)
SSD_OPTION=""
if [ "$INTERFACE" != "virtio" ]; then
if (whiptail --title "$(translate 'SSD Emulation')" --yesno "$(translate 'Do you want to use SSD emulation for this disk?')" 10 60); then
SSD_OPTION=",ssd=1"
else
SSD_OPTION=""
whiptail --yesno "$(translate 'Do you want to use SSD emulation for this disk?')" 10 60 && SSD_OPTION=",ssd=1"
fi
else
SSD_OPTION=""
fi
msg_info "$(translate 'Configuring disk')"
# Configure the disk in the VM
if qm set "$VMID" --${INTERFACE}${NEXT_SLOT} "$IMPORTED_DISK${SSD_OPTION}" &>/dev/null; then
msg_ok "$(translate 'Image') $IMAGE $(translate 'configured as') ${INTERFACE}${NEXT_SLOT}"
# Ask if the disk should be bootable
if (whiptail --title "$(translate 'Make Bootable')" --yesno "$(translate 'Do you want to make this disk bootable?')" 10 60); then
whiptail --yesno "$(translate 'Do you want to make this disk bootable?')" 10 60 && {
msg_info "$(translate 'Configuring disk as bootable')"
if qm set "$VMID" --boot c --bootdisk ${INTERFACE}${NEXT_SLOT} &>/dev/null; then
msg_ok "$(translate 'Disk configured as bootable')"
else
msg_error "$(translate 'Could not configure the disk as bootable')"
fi
fi
}
else
msg_error "$(translate 'Could not configure disk') ${INTERFACE}${NEXT_SLOT} $(translate 'for VM') $VMID"
fi
@@ -239,4 +190,5 @@ for IMAGE in $SELECTED_IMAGES; do
done
msg_ok "$(translate 'All selected images have been processed')"
sleep 2
msg_success "$(translate "Press Enter to return to menu...")"
read -r

View File

@@ -4,10 +4,11 @@
# ProxMenu - A menu-driven script for Proxmox VE management
# ==========================================================
# Author : MacRimi
# Revision : @Blaspt (USB passthrough via udev rule with persistent /dev/coral)
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 28/01/2025
# Version : 1.1
# Last Updated: 16/05/2025
# ==========================================================
# Description:
# This script automates the configuration and installation of
@@ -17,13 +18,10 @@
# - Installs necessary drivers inside the container
# - Manages required system and container restarts
#
# The script aims to simplify the process of enabling
# AI-powered video analysis capabilities in containers
# LXC, leveraging hardware acceleration for
# improved performance.
# Supports Coral USB and Coral M.2 (PCIe) devices.
# Includes USB passthrough enhancement using persistent udev alias (/dev/coral).
# ==========================================================
# Configuration ============================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
@@ -38,10 +36,7 @@ initialize_cache
# ==========================================================
# Select LXC container
select_container() {
CONTAINERS=$(pct list | awk 'NR>1 {print $1, $3}' | xargs -n2)
if [ -z "$CONTAINERS" ]; then
msg_error "$(translate 'No containers available in Proxmox.')"
@@ -49,7 +44,7 @@ select_container() {
fi
CONTAINER_ID=$(whiptail --title "$(translate 'Select Container')" \
--menu "$(translate 'Select the LXC container:')" 15 60 5 $CONTAINERS 3>&1 1>&2 2>&3)
--menu "$(translate 'Select the LXC container:')" 20 70 10 $CONTAINERS 3>&1 1>&2 2>&3)
if [ -z "$CONTAINER_ID" ]; then
msg_error "$(translate 'No container selected. Exiting.')"
@@ -64,15 +59,12 @@ select_container() {
msg_ok "$(translate 'Container selected:') $CONTAINER_ID"
}
# Validate that the selected container is valid
validate_container_id() {
if [ -z "$CONTAINER_ID" ]; then
msg_error "$(translate 'Container ID not defined. Make sure to select a container first.')"
exit 1
fi
# Check if the container is running and stop it before configuration
if pct status "$CONTAINER_ID" | grep -q "running"; then
msg_info "$(translate 'Stopping the container before applying configuration...')"
pct stop "$CONTAINER_ID"
@@ -80,8 +72,33 @@ validate_container_id() {
fi
}
# Añadir regla udev para Coral USB para persistencia de permisos
add_udev_rule_for_coral_usb() {
RULE_FILE="/etc/udev/rules.d/99-coral-usb.rules"
RULE_CONTENT='SUBSYSTEM=="usb", ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="9302", MODE="0666", TAG+="uaccess"'
if [[ ! -f "$RULE_FILE" ]] || ! grep -qF "$RULE_CONTENT" "$RULE_FILE"; then
echo "$RULE_CONTENT" > "$RULE_FILE"
udevadm control --reload-rules && udevadm trigger
msg_ok "$(translate 'Udev rule for Coral USB added and rules reloaded.')"
else
msg_ok "$(translate 'Udev rule for Coral USB already exists.')"
fi
}
add_mount_if_needed() {
local DEVICE="$1"
local DEST="$2"
local CONFIG_FILE="$3"
if [ -e "$DEVICE" ] && ! grep -q "lxc.mount.entry: $DEVICE" "$CONFIG_FILE"; then
echo "lxc.mount.entry: $DEVICE $DEST none bind,optional,create=$( [ -c "$DEVICE" ] && echo file || echo dir )" >> "$CONFIG_FILE"
fi
}
# Configure LXC for Coral TPU and iGPU
configure_lxc_hardware() {
validate_container_id
CONFIG_FILE="/etc/pve/lxc/${CONTAINER_ID}.conf"
@@ -90,6 +107,7 @@ configure_lxc_hardware() {
exit 1
fi
# Privileged container
if grep -q "^unprivileged: 1" "$CONFIG_FILE"; then
msg_info "$(translate 'The container is unprivileged. Changing to privileged...')"
sed -i "s/^unprivileged: 1/unprivileged: 0/" "$CONFIG_FILE"
@@ -103,46 +121,57 @@ configure_lxc_hardware() {
msg_ok "$(translate 'The container is already privileged.')"
fi
# Configure iGPU
sed -i '/^dev[0-9]\+:/d' "$CONFIG_FILE"
# Enable nesting feature
if ! grep -q "features: nesting=1" "$CONFIG_FILE"; then
echo "features: nesting=1" >> "$CONFIG_FILE"
fi
# iGPU support
if ! grep -q "c 226:0 rwm" "$CONFIG_FILE"; then
echo "lxc.cgroup2.devices.allow: c 226:0 rwm # iGPU" >> "$CONFIG_FILE"
echo "lxc.cgroup2.devices.allow: c 226:128 rwm # iGPU" >> "$CONFIG_FILE"
echo "lxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dir" >> "$CONFIG_FILE"
echo "lxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none bind,optional,create=file" >> "$CONFIG_FILE"
fi
add_mount_if_needed "/dev/dri" "dev/dri" "$CONFIG_FILE"
add_mount_if_needed "/dev/dri/renderD128" "dev/dri/renderD128" "$CONFIG_FILE"
add_mount_if_needed "/dev/dri/card0" "dev/dri/card0" "$CONFIG_FILE"
# Framebuffer support
if ! grep -q "c 29:0 rwm # Framebuffer" "$CONFIG_FILE"; then
echo "lxc.cgroup2.devices.allow: c 29:0 rwm # Framebuffer" >> "$CONFIG_FILE"
fi
add_mount_if_needed "/dev/fb0" "dev/fb0" "$CONFIG_FILE"
if ! grep -q "lxc.mount.entry: /dev/fb0" "$CONFIG_FILE"; then
echo "lxc.mount.entry: /dev/fb0 dev/fb0 none bind,optional,create=file" >> "$CONFIG_FILE"
fi
# Configure Coral TPU (USB and M.2)
# ----------------------------------------------------------
# Coral USB passthrough (via udev + /dev/coral)
# ----------------------------------------------------------
add_udev_rule_for_coral_usb
if ! grep -Pq "^lxc.cgroup2.devices.allow: c 189:\* rwm # Coral USB$" "$CONFIG_FILE"; then
echo "lxc.cgroup2.devices.allow: c 189:* rwm # Coral USB" >> "$CONFIG_FILE"
fi
add_mount_if_needed "/dev/coral" "dev/coral" "$CONFIG_FILE"
if ! grep -Pq "^lxc.mount.entry: /dev/bus/usb dev/bus/usb none bind,optional,create=dir$" "$CONFIG_FILE"; then
echo "lxc.mount.entry: /dev/bus/usb dev/bus/usb none bind,optional,create=dir" >> "$CONFIG_FILE"
# ----------------------------------------------------------
# Coral M.2 (PCIe) support
# ----------------------------------------------------------
if lspci | grep -iq "Global Unichip"; then
if ! grep -Pq "^lxc.cgroup2.devices.allow: c 245:0 rwm # Coral M2 Apex$" "$CONFIG_FILE"; then
echo "lxc.cgroup2.devices.allow: c 245:0 rwm # Coral M2 Apex" >> "$CONFIG_FILE"
fi
add_mount_if_needed "/dev/apex_0" "dev/apex_0" "$CONFIG_FILE"
fi
if ! grep -Pq "^lxc.mount.entry: /dev/apex_0 dev/apex_0 none bind,optional,create=file$" "$CONFIG_FILE"; then
echo "lxc.mount.entry: /dev/apex_0 dev/apex_0 none bind,optional,create=file" >> "$CONFIG_FILE"
fi
msg_ok "$(translate 'Coral TPU and iGPU configuration added to container') $CONTAINER_ID."
}
# Install Coral TPU drivers in the container
install_coral_in_container() {
msg_info2 "$(translate 'Installing iGPU and Coral TPU drivers inside the container...')"
tput sc
LOG_FILE=$(mktemp)
@@ -200,13 +229,12 @@ install_coral_in_container() {
fi
}
select_container
show_proxmenux_logo
configure_lxc_hardware
install_coral_in_container
msg_ok "$(translate 'Configuration completed.')"
sleep 2
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
read -r

View File

@@ -68,6 +68,7 @@ verify_and_add_repos() {
# Function to install Coral TPU drivers on the host
install_coral_host() {
show_proxmenux_logo
verify_and_add_repos
apt-get install -y git devscripts dh-dkms dkms pve-headers-$(uname -r) >/dev/null 2>&1
@@ -93,19 +94,23 @@ install_coral_host() {
exit 1
fi
msg_ok "$(translate 'Coral TPU drivers installed successfully on the host.')"
msg_success "$(translate 'Coral TPU drivers installed successfully on the host.')"
echo -e
}
# Prompt for reboot after installation
restart_prompt() {
restart_prompt() {
if whiptail --title "$(translate 'Coral TPU Installation')" --yesno "$(translate 'The installation requires a server restart to apply changes. Do you want to restart now?')" 10 70; then
#echo -ne "\r${TAB}${YW}-$(translate 'Restarting the server...') ${CL}"
msg_warn "$(translate 'Restarting the server...')"
reboot
else
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
read -r
fi
}
# Main logic
pre_install_prompt
install_coral_host
restart_prompt

View File

@@ -1,104 +1,179 @@
#!/bin/bash
# ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 28/01/2025
# Version : 1.1
# Last Updated: 04/07/2025
# ==========================================================
# Configuration ============================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
CONFIG_FILE="$BASE_DIR/config.json"
CACHE_FILE="$BASE_DIR/cache.json"
UTILS_FILE="$BASE_DIR/utils.sh"
LOCAL_VERSION_FILE="$BASE_DIR/version.txt"
INSTALL_DIR="/usr/local/bin"
MENU_SCRIPT="menu"
VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
# ==========================================================
detect_installation_type() {
local has_venv=false
local has_language=false
if [ -d "$VENV_PATH" ] && [ -f "$VENV_PATH/bin/activate" ]; then
has_venv=true
fi
if [ -f "$CONFIG_FILE" ]; then
local current_language=$(jq -r '.language // empty' "$CONFIG_FILE" 2>/dev/null)
if [[ -n "$current_language" && "$current_language" != "null" && "$current_language" != "empty" ]]; then
has_language=true
fi
fi
if [ "$has_venv" = true ] && [ "$has_language" = true ]; then
echo "translation"
else
echo "normal"
fi
}
# ==========================================================
show_config_menu() {
local install_type
install_type=$(detect_installation_type)
while true; do
OPTION=$(whiptail --title "$(translate "Configuration Menu")" --menu "$(translate "Select an option:")" 20 70 8 \
"1" "$(translate "Change Language")" \
"2" "$(translate "Show Version Information")" \
"3" "$(translate "Uninstall ProxMenux")" \
"4" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
local menu_options=()
local option_actions=()
case $OPTION in
1)
if [ "$install_type" = "translation" ]; then
menu_options+=("1" "$(translate "Change Language")")
option_actions[1]="change_language"
menu_options+=("2" "$(translate "Show Version Information")")
option_actions[2]="show_version_info"
menu_options+=("3" "$(translate "Uninstall ProxMenux")")
option_actions[3]="uninstall_proxmenu"
menu_options+=("4" "$(translate "Return to Main Menu")")
option_actions[4]="return_main"
else
menu_options+=("1" "Show Version Information")
option_actions[1]="show_version_info"
menu_options+=("2" "Uninstall ProxMenux")
option_actions[2]="uninstall_proxmenu"
menu_options+=("3" "Return to Main Menu")
option_actions[3]="return_main"
fi
OPTION=$(dialog --clear --backtitle "ProxMenux Configuration" \
--title "$(translate "Configuration Menu")" \
--menu "$(translate "Select an option:")" 20 70 10 \
"${menu_options[@]}" 3>&1 1>&2 2>&3)
case "${option_actions[$OPTION]}" in
"change_language")
change_language
;;
2)
"show_version_info")
show_version_info
;;
3)
"uninstall_proxmenu")
uninstall_proxmenu
;;
4) exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") ;;
*) exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") ;;
"return_main"|"")
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
;;
esac
done
}
# ==========================================================
change_language() {
LANGUAGE=$(whiptail --title "$(translate "Change Language")" --menu "$(translate "Select a new language for the menu:")" 20 70 12 \
"en" "$(translate "English (Recommended)")" \
local new_language
new_language=$(dialog --clear --backtitle "ProxMenux Configuration" \
--title "$(translate "Change Language")" \
--menu "$(translate "Select a new language for the menu:")" 20 60 6 \
"en" "$(translate "English")" \
"es" "$(translate "Spanish")" \
"fr" "$(translate "French")" \
"de" "$(translate "German")" \
"it" "$(translate "Italian")" \
"pt" "$(translate "Portuguese")" 3>&1 1>&2 2>&3)
if [ -z "$LANGUAGE" ]; then
msg_error "$(translate "No language selected.")"
if [ -z "$new_language" ]; then
dialog --clear --backtitle "ProxMenux Configuration" \
--title "$(translate "Language Change")" \
--msgbox "\n\n$(translate "No language selected.")" 10 50
return
fi
# Update only the language field in the config file
if [ -f "$CONFIG_FILE" ]; then
tmp=$(mktemp)
jq --arg lang "$LANGUAGE" '.language = $lang' "$CONFIG_FILE" > "$tmp" && mv "$tmp" "$CONFIG_FILE"
jq --arg lang "$new_language" '.language = $lang' "$CONFIG_FILE" > "$tmp" && mv "$tmp" "$CONFIG_FILE"
else
echo "{\"language\": \"$LANGUAGE\"}" > "$CONFIG_FILE"
echo "{\"language\": \"$new_language\"}" > "$CONFIG_FILE"
fi
msg_ok "$(translate "Language changed to") $LANGUAGE"
dialog --clear --backtitle "ProxMenux Configuration" \
--title "$(translate "Language Change")" \
--msgbox "\n\n$(translate "Language changed to") $new_language" 10 50
# Reload the menu
TMP_FILE=$(mktemp)
curl -s "$REPO_URL/scripts/menus/config_menu.sh" > "$TMP_FILE"
chmod +x "$TMP_FILE"
trap 'rm -f "$TMP_FILE"' EXIT
exec bash "$TMP_FILE"
}
# ==========================================================
show_version_info() {
local version info_message temp_file
local version info_message install_type
install_type=$(detect_installation_type)
if [ -f "$LOCAL_VERSION_FILE" ]; then
version=$(<"$LOCAL_VERSION_FILE")
else
version="Unknown"
fi
info_message+="$(translate "Current ProxMenux version:") $version\n\n"
info_message+="$(translate "Installed components:")\n"
info_message+="$(translate "Installation type:")\n"
if [ "$install_type" = "translation" ]; then
info_message+="$(translate "Translation Version (Multi-language support)")\n"
else
info_message+="$(translate "Normal Version (English only - Lightweight)")\n"
fi
info_message+="\n"
info_message+="$(translate "Installed components:")\n"
if [ -f "$CONFIG_FILE" ]; then
while IFS=': ' read -r component value; do
[ "$component" = "language" ] && continue
@@ -124,11 +199,14 @@ show_version_info() {
info_message+="\n$(translate "ProxMenu files:")\n"
[ -f "$INSTALL_DIR/$MENU_SCRIPT" ] && info_message+="$MENU_SCRIPT$INSTALL_DIR/$MENU_SCRIPT\n" || info_message+="$MENU_SCRIPT\n"
[ -f "$CACHE_FILE" ] && info_message+="✓ cache.json → $CACHE_FILE\n" || info_message+="✗ cache.json\n"
[ -f "$UTILS_FILE" ] && info_message+="✓ utils.sh → $UTILS_FILE\n" || info_message+="✗ utils.sh\n"
[ -f "$CONFIG_FILE" ] && info_message+="✓ config.json → $CONFIG_FILE\n" || info_message+="✗ config.json\n"
[ -f "$LOCAL_VERSION_FILE" ] && info_message+="✓ version.txt → $LOCAL_VERSION_FILE\n" || info_message+="✗ version.txt\n"
if [ "$install_type" = "translation" ]; then
[ -f "$CACHE_FILE" ] && info_message+="✓ cache.json → $CACHE_FILE\n" || info_message+="✗ cache.json\n"
info_message+="\n$(translate "Virtual Environment:")\n"
if [ -d "$VENV_PATH" ] && [ -f "$VENV_PATH/bin/activate" ]; then
info_message+="$(translate "Installed")$VENV_PATH\n"
@@ -140,115 +218,110 @@ show_version_info() {
current_language=$(jq -r '.language // "en"' "$CONFIG_FILE")
info_message+="\n$(translate "Current language:")\n$current_language\n"
else
info_message+="\n$(translate "Language:")\nEnglish (Fixed)\n"
fi
# Mostrar con dialog usando un archivo temporal
if command -v dialog >/dev/null 2>&1; then
tmpfile=$(mktemp)
echo -e "$info_message" > "$tmpfile"
dialog --title "$(translate "ProxMenux Information")" --clear --textbox "$tmpfile" 20 70
dialog --clear --backtitle "ProxMenux Configuration" \
--title "$(translate "ProxMenux Information")" \
--textbox "$tmpfile" 25 80
rm -f "$tmpfile"
fi
#show_proxmenux_logo
}
# ==========================================================
uninstall_proxmenu() {
if ! whiptail --title "Uninstall ProxMenu" --yesno "$(translate "Are you sure you want to uninstall ProxMenu?")" 10 60; then
local install_type
install_type=$(detect_installation_type)
if ! dialog --clear --backtitle "ProxMenux Configuration" \
--title "Uninstall ProxMenu" \
--yesno "\n$(translate "Are you sure you want to uninstall ProxMenu?")" 8 60; then
return
fi
# Show checklist for dependencies
DEPS_TO_REMOVE=$(whiptail --title "Remove Dependencies" --checklist \
"Select dependencies to remove:" 15 60 3 \
local deps_to_remove=""
if [ "$install_type" = "translation" ]; then
deps_to_remove=$(dialog --clear --backtitle "ProxMenux Configuration" \
--title "Remove Dependencies" \
--checklist "Select dependencies to remove:" 15 60 4 \
"python3-venv" "Python virtual environment" OFF \
"python3-pip" "Python package installer" OFF \
"python3" "Python interpreter" OFF \
"jq" "JSON processor" OFF \
3>&1 1>&2 2>&3)
else
deps_to_remove=$(dialog --clear --backtitle "ProxMenux Configuration" \
--title "Remove Dependencies" \
--checklist "Select dependencies to remove:" 12 60 2 \
"dialog" "Interactive dialog boxes" OFF \
"jq" "JSON processor" OFF \
3>&1 1>&2 2>&3)
fi
(
echo "10" ; echo "Removing ProxMenu files..."
sleep 1
echo "Uninstalling ProxMenu..."
# Remove googletrans if virtual environment exists
if [ -f "$VENV_PATH/bin/activate" ]; then
echo "Removing googletrans..."
echo "30" ; echo "Removing googletrans and virtual environment..."
source "$VENV_PATH/bin/activate"
pip uninstall -y googletrans >/dev/null 2>&1
deactivate
fi
# Remove virtual environment
if [ -d "$VENV_PATH" ]; then
echo "Removing virtual environment..."
rm -rf "$VENV_PATH"
fi
# Remove selected dependencies
if [ -n "$DEPS_TO_REMOVE" ]; then
echo "Removing selected dependencies..."
echo "50" ; echo "Removing ProxMenu files..."
rm -f "$INSTALL_DIR/$MENU_SCRIPT"
rm -rf "$BASE_DIR"
# Remove quotes and process each package
read -r -a DEPS_ARRAY <<< "$(echo "$DEPS_TO_REMOVE" | tr -d '"')"
if [ -n "$deps_to_remove" ]; then
echo "70" ; echo "Removing selected dependencies..."
read -r -a DEPS_ARRAY <<< "$(echo "$deps_to_remove" | tr -d '"')"
for dep in "${DEPS_ARRAY[@]}"; do
echo "Removing $dep..."
# Mark package as auto-installed
apt-mark auto "$dep" >/dev/null 2>&1
# Try to remove with apt-get
if ! apt-get -y --purge autoremove "$dep" >/dev/null 2>&1; then
echo "Failed to remove $dep with apt-get. Trying with dpkg..."
if ! dpkg --purge "$dep" >/dev/null 2>&1; then
echo "Failed to remove $dep with dpkg. Trying to force removal..."
dpkg --force-all --purge "$dep" >/dev/null 2>&1
fi
fi
# Verify if the package was actually removed
if dpkg -l "$dep" 2>/dev/null | grep -q '^ii'; then
echo "Warning: Failed to completely remove $dep. You may need to remove it manually."
else
echo "$dep successfully removed."
fi
apt-get -y --purge autoremove "$dep" >/dev/null 2>&1
done
# Run autoremove to clean up any leftover dependencies
echo "Cleaning up unnecessary packages..."
apt-get autoremove -y --purge >/dev/null 2>&1
fi
echo "90" ; echo "Restoring system files..."
# Restore original .bashrc if backup exists
if [ -f /root/.bashrc.bak ]; then
echo "$(translate "Restoring original .bashrc...")"
mv /root/.bashrc.bak /root/.bashrc
fi
# Restore original MOTD if backup exists
[ -f /root/.bashrc.bak ] && mv /root/.bashrc.bak /root/.bashrc
if [ -f /etc/motd.bak ]; then
echo "$(translate "Restoring original MOTD...")"
mv /etc/motd.bak /etc/motd
else
# Remove custom MOTD line if present
sed -i '/This system is optimised by: ProxMenux/d' /etc/motd
fi
echo "100" ; echo "Uninstallation complete!"
sleep 1
# Remove ProxMenu files
rm -f "/usr/local/bin/menu"
rm -rf "$BASE_DIR"
) | dialog --clear --backtitle "ProxMenux Configuration" \
--title "Uninstalling ProxMenux" \
--gauge "Starting uninstallation..." 10 60 0
echo "ProxMenu has been uninstalled."
if [ -n "$DEPS_TO_REMOVE" ]; then
echo "The following dependencies have been removed successfully: $DEPS_TO_REMOVE"
local final_message="ProxMenux has been uninstalled successfully.\n\n"
if [ -n "$deps_to_remove" ]; then
final_message+="The following dependencies were removed:\n$deps_to_remove\n\n"
fi
final_message+="Thank you for using ProxMenux!"
echo
echo "ProxMenux uninstallation complete. Thank you for using it!"
echo
dialog --clear --backtitle "ProxMenux Configuration" \
--title "Uninstallation Complete" \
--msgbox "$final_message" 12 60
clear
exit 0
}
#show_proxmenux_logo
# ==========================================================
# Main execution
show_config_menu

View File

@@ -67,8 +67,8 @@ function header_info() {
# MAIN EXECUTION
# ==========================================================
header_info
echo -e "\n Loading..."
#header_info
#echo -e "\n Loading..."
#sleep 1
@@ -98,7 +98,7 @@ function start_vm_configuration() {
while true; do
OS_TYPE=$(dialog --backtitle "ProxMenux" \
--title "$(translate "Select System Type")" \
--menu "\n$(translate "Choose the type of virtual system to install:")" 18 70 10 \
--menu "\n$(translate "Choose the type of virtual system to install:")" 20 70 10 \
1 "$(translate "Create") VM System NAS" \
2 "$(translate "Create") VM System Windows" \
3 "$(translate "Create") VM System Linux" \

View File

@@ -1,175 +0,0 @@
#!/usr/bin/env bash
# ==========================================================
# ProxMenuX - Virtual Machine Creator Script
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 07/05/2025
# ==========================================================
# Description:
# This script is part of the central ProxMenux VM creation module. It allows users
# to create virtual machines (VMs) in Proxmox VE using either default or advanced
# configurations, streamlining the deployment of Linux, Windows, and other systems.
#
# Key features:
# - Supports both virtual disk creation and physical disk passthrough.
# - Automates CPU, RAM, BIOS, network and storage configuration.
# - Provides a user-friendly menu to select OS type, ISO image and disk interface.
# - Automatically generates a detailed and styled HTML description for each VM.
#
# All operations are designed to simplify and accelerate VM creation in a
# consistent and maintainable way, using ProxMenux standards.
# ==========================================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
VM_REPO="$REPO_URL/scripts/vm"
ISO_REPO="$REPO_URL/scripts/vm"
MENU_REPO="$REPO_URL/scripts/menus"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
VENV_PATH="/opt/googletrans-env"
[[ -f "$UTILS_FILE" ]] && source "$UTILS_FILE"
source <(curl -s "$VM_REPO/vm_configurator.sh")
source <(curl -s "$VM_REPO/disk_selector.sh")
source <(curl -s "$VM_REPO/vm_creator.sh")
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
function header_info() {
clear
show_proxmenux_logo
echo -e "${BL}╔═══════════════════════════════════════════════╗${CL}"
echo -e "${BL}║ ║${CL}"
echo -e "${BL}${YWB} ProxMenux VM Creator ${BL}${CL}"
echo -e "${BL}║ ║${CL}"
echo -e "${BL}╚═══════════════════════════════════════════════╝${CL}"
echo -e
}
# ==========================================================
# MAIN EXECUTION
# ==========================================================
header_info
echo -e "\n Loading..."
sleep 1
function start_vm_configuration() {
if (whiptail --title "ProxMenux" --yesno "$(translate "Use Default Settings?")" --no-button "$(translate "Advanced")" 10 60); then
header_info
load_default_vm_config "$OS_TYPE"
if [[ -z "$HN" ]]; then
HN=$(whiptail --inputbox "$(translate "Enter a name for the new virtual machine:")" 10 60 --title "VM Hostname" 3>&1 1>&2 2>&3)
[[ -z "$HN" ]] && HN="custom-vm"
fi
apply_default_vm_config
else
header_info
echo -e "${CUS}$(translate "Using advanced configuration")${CL}"
configure_vm_advanced "$OS_TYPE"
fi
}
while true; do
OS_TYPE=$(dialog --backtitle "ProxMenux" \
--title "$(translate "Select System Type")" \
--menu "\n$(translate "Choose the type of virtual system to install:")" 18 70 10 \
1 "$(translate "Create") VM System NAS" \
2 "$(translate "Create") VM System Windows" \
3 "$(translate "Create") VM System Linux" \
4 "$(translate "Create") VM System macOS (OSX-PROXMOX)" \
5 "$(translate "Create") VM System Others (based Linux)" \
6 "$(translate "Return to Main Menu")" \
3>&1 1>&2 2>&3)
[[ $? -ne 0 || "$OS_TYPE" == "6" ]] && exec bash <(curl -s "$MENU_REPO/main_menu.sh")
case "$OS_TYPE" in
1)
source <(curl -fsSL "$ISO_REPO/select_nas_iso.sh") && select_nas_iso || continue
;;
2)
source <(curl -fsSL "$ISO_REPO/select_windows_iso.sh") && select_windows_iso || continue
;;
3)
source <(curl -fsSL "$ISO_REPO/select_linux_iso.sh") && select_linux_iso || continue
;;
4)
whiptail --title "OSX-PROXMOX" --yesno "$(translate "This is an external script that creates a macOS VM in Proxmox VE in just a few steps, whether you are using AMD or Intel hardware.")\n\n$(translate "The script clones the osx-proxmox.com repository and once the setup is complete, the server will automatically reboot.")\n\n$(translate "Make sure there are no critical services running as they will be interrupted. Ensure your server can be safely rebooted.")\n\n$(translate "Visit https://osx-proxmox.com for more information.")\n\n$(translate "Do you want to run the script now?")" 20 70
if [[ $? -eq 0 ]]; then
bash -c "$(curl -fsSL https://install.osx-proxmox.com)"
fi
continue
;;
5)
source <(curl -fsSL "$ISO_REPO/select_linux_iso.sh") && select_linux_other_scripts || continue
;;
esac
if ! confirm_vm_creation; then
continue
fi
start_vm_configuration || continue
select_disk_type
if [[ -z "$DISK_TYPE" ]]; then
msg_error "$(translate "Disk type selection failed or cancelled")"
continue
fi
create_vm
break
done
function start_vm_configuration() {
if (whiptail --title "ProxMenux" --yesno "$(translate "Use Default Settings?")" --no-button "$(translate "Advanced")" 10 60); then
header_info
load_default_vm_config "$OS_TYPE"
if [[ -z "$HN" ]]; then
HN=$(whiptail --inputbox "$(translate "Enter a name for the new virtual machine:")" 10 60 --title "VM Hostname" 3>&1 1>&2 2>&3)
[[ -z "$HN" ]] && HN="custom-vm"
fi
apply_default_vm_config
else
header_info
echo -e "${CUS}$(translate "Using advanced configuration")${CL}"
configure_vm_advanced "$OS_TYPE"
fi
}

View File

@@ -25,43 +25,34 @@ initialize_cache
# ==========================================================
while true; do
OPTION=$(whiptail --title "$(translate "GPUs and Coral-TPU Menu")" --menu "$(translate "Select an option:")" 20 70 8 \
OPTION=$(dialog --clear --backtitle "ProxMenux" --title "$(translate "GPUs and Coral-TPU Menu")" \
--menu "\n$(translate "Select an option:")" 20 70 8 \
"1" "$(translate "Add HW iGPU acceleration to an LXC")" \
"2" "$(translate "Add Coral TPU to an LXC")" \
"3" "$(translate "Install/Update Coral TPU on the Host")" \
"4" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
"4" "$(translate "Return to Main Menu")" \
2>&1 >/dev/tty)
case $OPTION in
1)
show_proxmenux_logo
msg_info2 "$(translate "Running script:") $(translate " HW iGPU acceleration LXC")..."
bash <(curl -s "$REPO_URL/scripts/configure_igpu_lxc.sh")
if [ $? -ne 0 ]; then
msg_warn "$(translate "Operation cancelled.")"
sleep 2
return
fi
;;
2)
show_proxmenux_logo
msg_info2 "$(translate "Running script:") Coral TPU LXC..."
bash <(curl -s "$REPO_URL/scripts/install_coral_lxc.sh")
if [ $? -ne 0 ]; then
msg_warn "$(translate "Operation cancelled.")"
sleep 2
return
fi
;;
3)
show_proxmenux_logo
msg_info2 "$(translate "Running script:") $(translate "Install/Update") Coral..."
bash <(curl -s "$REPO_URL/scripts/install_coral_pve.sh")
if [ $? -ne 0 ]; then
msg_warn "$(translate "Operation cancelled.")"
sleep 2
return
fi
;;
4) exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") ;;
*) exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") ;;
esac
done

View File

@@ -37,16 +37,17 @@ show_menu() {
dialog --clear \
--backtitle "ProxMenux" \
--title "$(translate "Main ProxMenux")" \
--menu "$(translate "Select an option:")" 18 70 10 \
--menu "$(translate "Select an option:")" 20 70 10 \
1 "$(translate "Settings post-install Proxmox")" \
2 "$(translate "Help and Info Commands")" \
3 "$(translate "Hardware: GPUs and Coral-TPU")" \
4 "$(translate "Create VM from template or script")" \
5 "$(translate "Disk and Storage Manager")" \
6 "$(translate "Essential Proxmox VE Helper-Scripts")" \
7 "$(translate "Network")" \
8 "$(translate "Settings")" \
9 "$(translate "Exit")" 2>"$TEMP_FILE"
6 "$(translate "Proxmox VE Helper Scripts")" \
7 "$(translate "Network Management")" \
8 "$(translate "Utilities and Tools")" \
9 "$(translate "Settings")" \
0 "$(translate "Exit")" 2>"$TEMP_FILE"
local EXIT_STATUS=$?
@@ -67,9 +68,10 @@ show_menu() {
4) exec bash <(curl -s "$REPO_URL/scripts/menus/create_vm_menu.sh") ;;
5) exec bash <(curl -s "$REPO_URL/scripts/menus/storage_menu.sh") ;;
6) exec bash <(curl -s "$REPO_URL/scripts/menus/menu_Helper_Scripts.sh") ;;
7) exec bash <(curl -s "$REPO_URL/scripts/repair_network.sh") ;;
8) exec bash <(curl -s "$REPO_URL/scripts/menus/config_menu.sh") ;;
9) clear; msg_ok "$(translate "Thank you for using ProxMenu. Goodbye!")"; rm -f "$TEMP_FILE"; exit 0 ;;
7) exec bash <(curl -s "$REPO_URL/scripts/menus/network_menu.sh") ;;
8) exec bash <(curl -s "$REPO_URL/scripts/menus/utilities_menu.sh") ;;
9) exec bash <(curl -s "$REPO_URL/scripts/menus/config_menu.sh") ;;
0) clear; msg_ok "$(translate "Thank you for using ProxMenu. Goodbye!")"; rm -f "$TEMP_FILE"; exit 0 ;;
*) msg_warn "$(translate "Invalid option")"; sleep 2 ;;
esac
done

View File

@@ -1,264 +0,0 @@
#!/bin/bash
# ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 28/01/2025
# ==========================================================
# Configuration
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
UTILS_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main/scripts/utils.sh"
BASE_DIR="/usr/local/share/proxmenux"
CACHE_FILE="$BASE_DIR/cache.json"
CONFIG_FILE="$BASE_DIR/config.json"
VENV_PATH="/opt/googletrans-env"
# Try to load utils.sh from GitHub
if ! source <(curl -sSf "$UTILS_URL"); then
echo "$(translate 'Error: Could not load utils.sh from') $UTILS_URL"
exit 1
fi
# Initialize cache
initialize_cache() {
if [ ! -f "$CACHE_FILE" ]; then
echo "{}" > "$CACHE_FILE"
return
fi
}
# Load language from JSON file
load_language() {
if [ -f "$CONFIG_FILE" ]; then
LANGUAGE=$(jq -r '.language' "$CONFIG_FILE")
fi
}
# Change language
change_language() {
LANGUAGE=$(whiptail --title "$(translate "Change Language")" --menu "$(translate "Select a new language for the menu:")" 20 60 12 \
"en" "$(translate "English (Recommended)")" \
"es" "$(translate "Spanish")" \
"fr" "$(translate "French")" \
"de" "$(translate "German")" \
"it" "$(translate "Italian")" \
"pt" "$(translate "Portuguese")" \
"zh-cn" "$(translate "Simplified Chinese")" \
"ja" "$(translate "Japanese")" 3>&1 1>&2 2>&3)
if [ -z "$LANGUAGE" ]; then
msg_error "$(translate "No language selected.")"
return
fi
echo "{\"language\": \"$LANGUAGE\"}" > "$CONFIG_FILE"
msg_ok "$(translate "Language changed to") $LANGUAGE"
# Descargar el script nuevamente
TMP_FILE=$(mktemp)
curl -s "$REPO_URL/scripts/menus/config_menu.sh" > "$TMP_FILE"
chmod +x "$TMP_FILE"
# Programar la eliminación del archivo cuando termine el proceso
trap 'rm -f "$TMP_FILE"' EXIT
exec bash "$TMP_FILE"
}
# Function to uninstall ProxMenu
uninstall_proxmenu() {
if whiptail --title "$(translate "Uninstall ProxMenu")" --yesno "$(translate "Are you sure you want to uninstall ProxMenu?")" 10 60; then
msg_info "$(translate "Uninstalling ProxMenu...")"
rm -rf "$BASE_DIR"
rm -f "/usr/local/bin/menu.sh"
msg_ok "$(translate "ProxMenu has been completely uninstalled.")"
exit 0
fi
}
# Function to show version information
show_version_info() {
local version=$(cat "$LOCAL_VERSION_FILE" 2>/dev/null || echo "1.0.0")
whiptail --title "$(translate "Version Information")" --msgbox "$(translate "Current ProxMenu version:") $version" 12 60
}
# Show configuration menu
show_config_menu() {
while true; do
OPTION=$(whiptail --title "$(translate "Configuration Menu")" --menu "$(translate "Select an option:")" 15 60 4 \
"1" "$(translate "Change Language")" \
"2" "$(translate "Show Version Information")" \
"3" "$(translate "Uninstall ProxMenu")" \
"4" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
case $OPTION in
1)
change_language
;;
2)
show_version_info
;;
3)
uninstall_proxmenu
;;
4)
return
;;
*)
return
;;
esac
done
}
# Show graphics and video menu
show_graphics_menu() {
while true; do
OPTION=$(whiptail --title "$(translate "HW: GPUs and Coral")" --menu "$(translate "Select an option:")" 15 60 2 \
"1" "IGPU/TPU" \
"2" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
case $OPTION in
1)
msg_info "$(translate "Running script") IGPU/TPU..."
if bash <(curl -s "$REPO_URL/scripts/igpu_tpu.sh"); then
msg_ok "$(translate "Script executed successfully.")"
else
msg_error "$(translate "Error executing script.")"
fi
;;
2)
return
;;
*)
msg_error "$(translate "Invalid option.")"
sleep 2
;;
esac
done
}
# Show storage menu
show_storage_menu() {
while true; do
OPTION=$(whiptail --title "$(translate "Disk and Storage Menu")" --menu "$(translate "Select an option:")" 15 60 3 \
"1" "$(translate "Add Disk Passthrough to a VM")" \
"2" "$(translate "Import Disk Image to a VM")" \
"3" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
case $OPTION in
1)
echo -e "\033[33m[INFO] $(translate "Running script:") $(translate "Disk Passthrough")...\033[0m"
bash <(curl -s "$REPO_URL/scripts/disk-passthrough.sh")
if [ $? -ne 0 ]; then
msg_info "$(translate "Operation cancelled.")"
sleep 2
fi
;;
2)
echo -e "\033[33m[INFO] $(translate "Running script:") $(translate "Import Disk Image")...\033[0m"
bash <(curl -s "$REPO_URL/scripts/import-disk-image.sh")
if [ $? -ne 0 ]; then
msg_info "$(translate "Operation cancelled.")"
sleep 2
fi
;;
3)
return
;;
*)
return
;;
esac
done
}
# Show network menu
show_network_menu() {
while true; do
OPTION=$(whiptail --title "$(translate "Network Menu")" --menu "$(translate "Select an option:")" 15 60 2 \
"1" "$(translate "Repair Network")" \
"2" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
case $OPTION in
1)
msg_info "$(translate "Running network repair...")"
if bash <(curl -s "$REPO_URL/scripts/repair_network.sh"); then
msg_ok "$(translate "Network repair completed.")"
else
msg_error "$(translate "Error in network repair.")"
fi
;;
2)
return
;;
*)
msg_error "$(translate "Invalid option.")"
sleep 2
;;
esac
done
}
# Show main menu
show_menu() {
while true; do
OPTION=$(whiptail --title "$(translate "Main Menu")" --menu "$(translate "Select an option:")" 15 60 5 \
"1" "$(translate "GPUs and Coral-TPU")" \
"2" "$(translate "Hard Drives, Disk Images, and Storage")" \
"3" "$(translate "Network")" \
"4" "$(translate "Settings")" \
"5" "$(translate "Exit")" 3>&1 1>&2 2>&3)
case $OPTION in
1)
show_graphics_menu
;;
2)
show_storage_menu
;;
3)
show_network_menu
;;
4)
show_config_menu
;;
5)
clear
msg_ok "$(translate "Thank you for using ProxMenu. Goodbye!")"
exit 0
;;
*)
msg_error "$(translate "Invalid option.")"
sleep 2
;;
esac
done
}
# Main flow
initialize_cache
load_language
show_menu

View File

@@ -1,125 +0,0 @@
#!/bin/bash
# ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 28/01/2025
# ==========================================================
# Description:
# This script serves as the main entry point for ProxMenux,
# a menu-driven tool designed for Proxmox VE management.
#
# - Displays the ProxMenu logo on startup.
# - Loads necessary configurations and language settings.
# - Checks for available updates and installs them if confirmed.
# - Downloads and executes the latest main menu script.
#
# Key Features:
# - Ensures ProxMenu is always up-to-date by fetching the latest version.
# - Uses whiptail for interactive menus and language selection.
# - Loads utility functions and translation support.
# - Maintains a cache system to improve performance.
# - Executes the ProxMenux main menu dynamically from the repository.
#
# This script ensures a streamlined and automated experience
# for managing Proxmox VE using ProxMenux.
# ==========================================================
# Configuration ============================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
CONFIG_FILE="$BASE_DIR/config.json"
CACHE_FILE="$BASE_DIR/cache.json"
UTILS_FILE="$BASE_DIR/utils.sh"
LOCAL_VERSION_FILE="$BASE_DIR/version.txt"
VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
# ==========================================================
show_proxmenu_logo
# Initialize language configuration
initialize_config() {
if [ ! -f "$CONFIG_FILE" ]; then
LANGUAGE=$(whiptail --title "$(translate "Select Language")" --menu "$(translate "Choose a language for the menu:")" 20 60 12 \
"en" "$(translate "English (Recommended)")" \
"es" "$(translate "Spanish")" \
"fr" "$(translate "French")" \
"de" "$(translate "German")" \
"it" "$(translate "Italian")" \
"pt" "$(translate "Portuguese")" \
"zh-cn" "$(translate "Simplified Chinese")" \
"ja" "$(translate "Japanese")" 3>&1 1>&2 2>&3)
if [ -z "$LANGUAGE" ]; then
msg_error "$(translate "No language selected. Exiting.")"
exit 1
fi
echo "{\"language\": \"$LANGUAGE\"}" > "$CONFIG_FILE"
msg_ok "$(translate "Initial language set to:") $LANGUAGE"
fi
}
check_updates() {
local INSTALL_SCRIPT="$BASE_DIR/install_proxmenux.sh"
# Fetch the remote version
local REMOTE_VERSION
REMOTE_VERSION=$(curl -fsSL "$REPO_URL/version.txt" | head -n 1)
# Exit silently if unable to fetch the remote version
if [ -z "$REMOTE_VERSION" ]; then
return 0
fi
# Read the local version
local LOCAL_VERSION
LOCAL_VERSION=$(head -n 1 "$LOCAL_VERSION_FILE")
# If the local version matches the remote version, no update is needed
[ "$LOCAL_VERSION" = "$REMOTE_VERSION" ] && return 0
# Prompt the user for update confirmation
if whiptail --title "$(translate "Update Available")" \
--yesno "$(translate "New version available") ($REMOTE_VERSION)\n\n$(translate "Do you want to update now?")" \
10 60 --defaultno; then
msg_warn "$(translate "Starting ProxMenu update...")"
# Download the installation script
if wget -qO "$INSTALL_SCRIPT" "$REPO_URL/install_proxmenux.sh"; then
chmod +x "$INSTALL_SCRIPT"
# Execute the script directly in the current environment
source "$INSTALL_SCRIPT"
fi
else
msg_warn "$(translate "Update postponed. You can update later from the menu.")"
fi
}
main_menu() {
exec bash <(curl -fsSL "$REPO_URL/scripts/menus/main_menu.sh")
}
# Main flow
initialize_config
load_language
initialize_cache
check_updates
main_menu

View File

@@ -6,11 +6,11 @@
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 28/01/2025
# Version : 1.1
# Last Updated: 04/06/2025
# ==========================================================
# Description:
# This script provides a simple and efficient way to access and execute essential Proxmox VE scripts
# This script provides a simple and efficient way to access and execute Proxmox VE scripts
# from the Community Scripts project (https://community-scripts.github.io/ProxmoxVE/).
#
# It serves as a convenient tool to run key automation scripts that simplify system management,
@@ -31,18 +31,47 @@ fi
load_language
initialize_cache
#show_proxmenux_logo
# ==========================================================
# Base URL community-scripts
BASE_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc"
BASE_URL2="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
HELPERS_JSON_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/refs/heads/main/json/helpers_cache.json"
METADATA_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/frontend/public/json/metadata.json"
for cmd in curl jq dialog; do
if ! command -v "$cmd" >/dev/null; then
echo "Missing required command: $cmd"
exit 1
fi
done
CACHE_JSON=$(curl -s "$HELPERS_JSON_URL")
META_JSON=$(curl -s "$METADATA_URL")
declare -A CATEGORY_NAMES
while read -r id name; do
CATEGORY_NAMES[$id]="$name"
done < <(echo "$META_JSON" | jq -r '.categories[] | "\(.id)\t\(.name)"')
declare -A CATEGORY_COUNT
for id in $(echo "$CACHE_JSON" | jq -r '.[].categories[]'); do
((CATEGORY_COUNT[$id]++))
done
get_type_label() {
local type="$1"
case "$type" in
ct) echo $'\Z1LXC\Zn' ;;
vm) echo $'\Z4VM\Zn' ;;
pve) echo $'\Z3PVE\Zn' ;;
addon) echo $'\Z2ADDON\Zn' ;;
*) echo $'\Z7GEN\Zn' ;;
esac
}
download_script() {
local url="$1"
local fallback_pve="${url/misc/tools\/pve}"
local fallback_addon="${url/misc/tools\/addon}"
local fallback_copydata="${url/misc/tools\/copy-data}"
local fallback_pve="${url/misc\/tools\/pve}"
local fallback_addon="${url/misc\/tools\/addon}"
local fallback_copydata="${url/misc\/tools\/copy-data}"
if curl --silent --head --fail "$url" >/dev/null; then
bash <(curl -s "$url")
@@ -53,134 +82,246 @@ download_script() {
elif curl --silent --head --fail "$fallback_copydata" >/dev/null; then
bash <(curl -s "$fallback_copydata")
else
msg_error "$(translate 'Error: Failed to download the script.')\033[0m"
msg_error "\n$(translate 'Tried URLs:')\n- $url\n- $fallback_pve\n- $fallback_addons\n- $fallback_copydata\n"
msg_success "$(translate "Press Enter to return to menu...")"
read -r
dialog --title "Helper Scripts" --msgbox "Error: Failed to download the script." 12 70
fi
}
RETURN_TO_MAIN=false
format_credentials() {
local script_info="$1"
local credentials_info=""
local has_credentials
has_credentials=$(echo "$script_info" | base64 --decode | jq -r 'has("default_credentials")')
if [[ "$has_credentials" == "true" ]]; then
local username password
username=$(echo "$script_info" | base64 --decode | jq -r '.default_credentials.username // empty')
password=$(echo "$script_info" | base64 --decode | jq -r '.default_credentials.password // empty')
if [[ -n "$username" && -n "$password" ]]; then
credentials_info="Username: $username | Password: $password"
elif [[ -n "$username" ]]; then
credentials_info="Username: $username"
elif [[ -n "$password" ]]; then
credentials_info="Password: $password"
fi
fi
echo "$credentials_info"
}
run_script_by_slug() {
local slug="$1"
local script_info
script_info=$(echo "$CACHE_JSON" | jq -r --arg slug "$slug" '.[] | select(.slug == $slug) | @base64')
# Array with script names, URLs, categories, and descriptions
scripts=(
"Proxmox VE LXC IP-Tag|Containers|$BASE_URL/add-lxc-iptag.sh|Description:\n\nThis script automatically adds IP address as tags to LXC containers using a Systemd service.\n\nThe service also updates the tags if a LXC IP address is changed. Configuration: nano /opt/lxc-iptag/iptag.conf. iptag.service must be restarted after change.\n\n\The Proxmox Node must contain ipcalc and net-tools. apt-get install -y ipcalc net-tools"
"Add Netbird to LXC|Networking|$BASE_URL/add-netbird-lxc.sh|Description:\n\nNetBird combines a configuration-free peer-to-peer private network and a centralized access control system in a single platform, making it easy to create secure private networks for your organization or home.\n\nAfter the script finishes, reboot the LXC then run netbird up in the LXC console.\n\n\The script only works in Debian/Ubuntu, not in Alpine!"
"Add Tailscale to LXC|Networking|$BASE_URL/add-tailscale-lxc.sh|Description:\n\nTailscale is a software-defined networking solution that enables secure communication between devices over the internet.\n\nIt creates a virtual private network (VPN) that enables devices to communicate with each other as if they were on the same local network.\n\n\After the script finishes, reboot the LXC then run tailscale up in the LXC console."
"Proxmox VE LXC Cleaner|Maintenance|$BASE_URL/clean-lxcs.sh|Description:\n\nThis script provides options to delete logs and cache, and repopulate apt lists for Ubuntu and Debian systems."
"Proxmox VE Host Backup|Security|$BASE_URL/host-backup.sh|Description:\n\nThis script serves as a versatile backup utility, enabling users to specify both the backup path and the directory they want to work in.\n\nThis flexibility empowers users to select the specific files and directories they wish to back up, making it compatible with a wide range of hosts, not limited to Proxmox.\n\nA backup is rendered ineffective when it remains stored on the host"
"Add hardware Acceleration LXC|Containers|$BASE_URL/hw-acceleration.sh|Description:\n\nEnables hardware acceleration IGPU for LXC containers."
"Proxmox Clean Orphaned LVM|Maintenance|$BASE_URL/clean-orphaned-lvm.sh|Description:\n\nThis script helps Proxmox users identify and remove orphaned LVM volumes that are no longer associated with any VM or LXC container.\n\nIt scans all LVM volumes, detects unused ones, and provides an interactive prompt to delete them safely.\n\nSystem-critical volumes like root, swap, and data are excluded to prevent accidental deletion."
"Install Crowdsec|Security|$BASE_URL/crowdsec.sh|Description:\n\nCrowdSec is a free and open-source intrusion prevention system (IPS) designed to provide network security against malicious traffic.\n\nIt is a collaborative IPS that analyzes behaviors and responses to attacks by sharing signals across a community of users."
"Proxmox VE LXC Filesystem Trim|Maintenance|$BASE_URL/fstrim.sh|Description:\n\nThis maintains SSD performance by managing unused blocks.\n\nThin-provisioned storage systems also require management to prevent unnecessary storage use.\n\nVMs automate fstrim, while LXC containers need manual or automated fstrim processes for optimal performance.\n\nThis is designed to work with SSDs on ext4 filesystems only."
"Install Glances|Monitoring|$BASE_URL/glances.sh|Description:\n\nGlances is an open-source system cross-platform monitoring tool.\n\nIt allows real-time monitoring of various aspects of your system such as CPU, memory, disk, network usage etc."
"Proxmox VE Kernel Clean|Maintenance|$BASE_URL/kernel-clean.sh|Description:\n\nCleaning unused kernel images is beneficial for reducing the length of the GRUB menu and freeing up disk space.\n\nBy removing old, unused kernels, the system is able to conserve disk space and streamline the boot process."
"Proxmox VE Kernel Pin|System|$BASE_URL/kernel-pin.sh|Description:\n\nKernel Pin is an essential tool for effortlessly managing kernel pinning and unpinning."
"Container LXC Deletion|Containers|$BASE_URL/lxc-delete.sh|Description:\n\nThis script helps manage and delete LXC containers on a Proxmox VE server.\n\nIt lists all available containers, allowing the user to select one or more for deletion through an interactive menu.\n\nRunning containers are automatically stopped before deletion, and the user is asked to confirm each action.\n\nThe script ensures a controlled and efficient container management process."
"Proxmox VE Processor Microcode|System|$BASE_URL/microcode.sh|Description:\n\nProcessor Microcode is a layer of low-level software that runs on the processor and provides patches or updates to its firmware.\n\nMicrocode updates can fix hardware bugs, improve performance, and enhance security features of the processor."
"Proxmox VE Netdata|Monitoring|$BASE_URL/netdata.sh|Description:\n\nNetdata is an open-source, real-time performance monitoring tool designed to provide insights into the performance and health of systems and applications.\n\nIt is often used by system administrators, DevOps professionals, and developers to monitor and troubleshoot issues on servers and other devices."
"Install Olivetin|Applications|$BASE_URL/olivetin.sh|Description:\n\nOliveTin provides a secure and straightforward way to execute pre-determined shell commands through a web-based interface.\n\nConfiguration Path: /etc/OliveTin/config.yaml"
"Proxmox VE Post Install|System|$BASE_URL/post-pve-install.sh|Description:\n\nThis script provides options for managing Proxmox VE repositories, including disabling the Enterprise Repo, adding or correcting PVE sources, enabling the No-Subscription Repo, adding the test Repo, disabling the subscription nag, updating Proxmox VE, and rebooting the system.\n\nExecute within the Proxmox shell.\n\n\It is recommended to answer yes (y) to all options presented during the process."
"Proxmox VE CPU Scaling Governor|System|$BASE_URL/scaling-governor.sh|Description:\n\nThe CPU scaling governor determines how the CPU frequency is adjusted based on the workload, with the goal of either conserving power or improving performance.\n\nBy scaling the frequency up or down, the operating system can optimize the CPU usage and conserve energy when possible. Generic Scaling Governors."
"Proxmox VE Cron LXC Updater|Maintenance|$BASE_URL/cron-update-lxcs.sh|Description:\n\nThis script will add/remove a crontab schedule that updates all LXCs every Sunday at midnight. To exclude LXCs from updating, edit the crontab using crontab -e and add CTID as shown in the example below:\n\n0 0 * * 0 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/update-lxcs-cron.sh)\" -s 103 111 >>/var/log/update-lxcs-cron.log 2>/dev/null"
"Proxmox VE LXC Updater|Maintenance|$BASE_URL/update-lxcs.sh|Description:\n\nThis script has been created to simplify and speed up the process of updating all LXC containers across various Linux distributions, such as Ubuntu, Debian, Devuan, Alpine Linux, CentOS-Rocky-Alma, Fedora, and ArchLinux.\n\nDesigned to automatically skip templates and specific containers during the update, enhancing its convenience and usability."
"Proxmox Backup Server|Security|$BASE_URL2/ct/proxmox-backup-server.sh|Description:\n\nProxmox Backup Server is an enterprise backup solution, for backing up and restoring VMs, containers, and physical hosts. By supporting incremental, fully deduplicated backups, Proxmox Backup Server significantly reduces network load and saves valuable storage space.\n\n\nSet a root password if using autologin. This will be the PBS password. passwd root"
)
show_menu() {
declare -A category_order
category_order["System"]=1
category_order["Maintenance"]=2
category_order["Containers"]=3
category_order["Applications"]=4
category_order["Monitoring"]=5
category_order["Networking"]=6
category_order["Security"]=7
custom_sort() {
while IFS='|' read -r name category url description; do
category=$(echo "$category" | xargs)
order=${category_order[$category]:-999}
printf "%d|%s|%s|%s|%s\n" "$order" "$name" "$category" "$url" "$description"
done | sort -n | cut -d'|' -f2-
decode() {
echo "$1" | base64 --decode | jq -r "$2"
}
while true; do
IFS=$'\n' sorted_scripts=($(printf "%s\n" "${scripts[@]}" | custom_sort))
unset IFS
HEADER=$(printf " %-57s %-20s" "$(translate "Name")" "$(translate "Category")")
menu_items=()
for script in "${sorted_scripts[@]}"; do
IFS='|' read -r name category url description <<< "$script"
translated_category=$(translate "$category")
padded_name=$(printf "%-57s" "$name")
menu_items+=("$padded_name" "$translated_category")
done
menu_items+=("$(translate "Return to Main Menu")" "")
cleanup
script_selection=$(whiptail --title "$(translate "Essential Proxmox VE Helper-Scripts")" \
--menu "\n$HEADER\n\n$(translate "Select a script to execute")" 25 78 16 \
"${menu_items[@]}" 3>&1 1>&2 2>&3)
if [ -n "$script_selection" ]; then
script_selection=$(echo "$script_selection" | xargs)
if [ "$script_selection" = "$(translate "Return to Main Menu")" ]; then
local name desc script_url notes
name=$(decode "$script_info" ".name")
desc=$(decode "$script_info" ".desc")
script_url=$(decode "$script_info" ".script_url")
notes=$(decode "$script_info" ".notes | join(\"\n\")")
whiptail --title "Proxmox VE Helper-Scripts" \
--msgbox "$(translate "Visit the website to discover more scripts, stay updated with the latest updates, and support the project:\n\nhttps://community-scripts.github.io/ProxmoxVE")" 15 70
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
local notes_dialog=""
if [[ -n "$notes" ]]; then
while IFS= read -r line; do
notes_dialog+="$line\n"
done <<< "$notes"
notes_dialog="${notes_dialog%\\n}"
fi
for script in "${sorted_scripts[@]}"; do
IFS='|' read -r name category url description <<< "$script"
if [ "$name" = "$script_selection" ]; then
selected_url="$url"
selected_description=$(translate "$description")
break
fi
done
if [ -n "$selected_url" ]; then
if whiptail --title "$(translate "Script Information")" \
--yes-button "$(translate "Accept")" \
--no-button "$(translate "Cancel")" \
--yesno "$selected_description" 20 78; then
#msg_info2 "$(translate "Executing script:") $script_selection"
#sleep 2
download_script "$selected_url"
msg_ok "$(translate "Script completed.")"
msg_success "$(translate "Press Enter to return to the main menu...")"
local credentials
credentials=$(format_credentials "$script_info")
local msg="\Zb\Z4Descripción:\Zn\n$desc"
[[ -n "$notes_dialog" ]] && msg+="\n\n\Zb\Z4Notes:\Zn\n$notes_dialog"
[[ -n "$credentials" ]] && msg+="\n\n\Zb\Z4Default Credentials:\Zn\n$credentials"
dialog --clear --colors --backtitle "ProxMenux" --title "$name" --yesno "$msg\n\nExecute this script?" 22 85
if [[ $? -eq 0 ]]; then
download_script "$script_url"
echo
echo
if [[ -n "$desc" || -n "$notes" || -n "$credentials" ]]; then
echo -e "$TAB\e[1;36mScript Information:\e[0m"
if [[ -n "$notes" ]]; then
echo -e "$TAB\e[1;33mNotes:\e[0m"
while IFS= read -r line; do
[[ -z "$line" ]] && continue
echo -e "$TAB$line"
done <<< "$notes"
echo
fi
if [[ -n "$credentials" ]]; then
echo -e "$TAB\e[1;32mDefault Credentials:\e[0m"
echo "$TAB$credentials"
echo
fi
fi
msg_success "Press Enter to return to the main menu..."
read -r
clear
else
msg_info2 "$(translate "Script execution cancelled.")"
sleep 2
RETURN_TO_MAIN=true
fi
else
echo "$(translate "Error: Could not find the selected script URL.")"
read -rp "$(translate "Press Enter to continue...")"
fi
else
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
fi
done
}
if [[ "$LANGUAGE" != "en" ]]; then
show_proxmenux_logo
msg_lang "$(translate "Generating automatic translations...")"
fi
show_menu
search_and_filter_scripts() {
local search_term=""
while true; do
search_term=$(dialog --inputbox "Enter search term (leave empty to show all scripts):" \
8 65 "$search_term" 3>&1 1>&2 2>&3)
[[ $? -ne 0 ]] && return
local filtered_json
if [[ -z "$search_term" ]]; then
filtered_json="$CACHE_JSON"
else
local search_lower
search_lower=$(echo "$search_term" | tr '[:upper:]' '[:lower:]')
filtered_json=$(echo "$CACHE_JSON" | jq --arg term "$search_lower" '
[.[] | select(
(.name | ascii_downcase | contains($term)) or
(.desc | ascii_downcase | contains($term))
)]')
fi
local count
count=$(echo "$filtered_json" | jq length)
if [[ $count -eq 0 ]]; then
dialog --msgbox "No scripts found for: '$search_term'\n\nTry a different search term." 8 50
continue
fi
while true; do
declare -A index_to_slug
local menu_items=()
local i=1
while IFS=$'\t' read -r slug name type; do
index_to_slug[$i]="$slug"
local label
label=$(get_type_label "$type")
local padded_name
padded_name=$(printf "%-42s" "$name")
local entry="$padded_name $label"
menu_items+=("$i" "$entry")
((i++))
done < <(echo "$filtered_json" | jq -r '
sort_by(.name)[] | [.slug, .name, .type] | @tsv')
menu_items+=("" "")
menu_items+=("new_search" "New Search")
menu_items+=("show_all" "Show All Scripts")
local title="Search Results"
if [[ -n "$search_term" ]]; then
title="Search Results for: '$search_term' ($count found)"
else
title="All Available Scripts ($count total)"
fi
local selected
selected=$(dialog --colors --backtitle "ProxMenux" \
--title "$title" \
--menu "Select a script or action:" \
22 75 15 "${menu_items[@]}" 3>&1 1>&2 2>&3)
if [[ $? -ne 0 ]]; then
return
fi
case "$selected" in
"new_search")
break
;;
"show_all")
search_term=""
filtered_json="$CACHE_JSON"
count=$(echo "$filtered_json" | jq length)
continue
;;
"back"|"")
return
;;
*)
if [[ -n "${index_to_slug[$selected]}" ]]; then
run_script_by_slug "${index_to_slug[$selected]}"
[[ "$RETURN_TO_MAIN" == true ]] && { RETURN_TO_MAIN=false; return; }
fi
;;
esac
done
done
}
while true; do
MENU_ITEMS=()
MENU_ITEMS+=("search" "Search/Filter Scripts")
MENU_ITEMS+=("" "")
for id in $(printf "%s\n" "${!CATEGORY_COUNT[@]}" | sort -n); do
name="${CATEGORY_NAMES[$id]:-Category $id}"
count="${CATEGORY_COUNT[$id]}"
padded_name=$(printf "%-35s" "$name")
padded_count=$(printf "(%2d)" "$count")
MENU_ITEMS+=("$id" "$padded_name $padded_count")
done
SELECTED=$(dialog --backtitle "ProxMenux" --title "Proxmox VE Helper-Scripts" --menu \
"Select a category or search for scripts:" 20 70 14 \
"${MENU_ITEMS[@]}" 3>&1 1>&2 2>&3) || {
dialog --clear --title "Proxmox VE Helper-Scripts" \
--msgbox "\n\n$(translate "Visit the website to discover more scripts, stay updated with the latest updates, and support the project:")\n\nhttps://community-scripts.github.io/ProxmoxVE" 15 70
#clear
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
}
if [[ "$SELECTED" == "search" ]]; then
search_and_filter_scripts
continue
fi
while true; do
declare -A INDEX_TO_SLUG
SCRIPTS=()
i=1
while IFS=$'\t' read -r slug name type; do
INDEX_TO_SLUG[$i]="$slug"
label=$(get_type_label "$type")
padded_name=$(printf "%-42s" "$name")
entry="$padded_name $label"
SCRIPTS+=("$i" "$entry")
((i++))
done < <(echo "$CACHE_JSON" | jq -r --argjson id "$SELECTED" \
'[.[] | select(.categories | index($id)) | {slug, name, type}] | sort_by(.name)[] | [.slug, .name, .type] | @tsv')
SCRIPT_INDEX=$(dialog --colors --backtitle "ProxMenux" --title "Scripts in ${CATEGORY_NAMES[$SELECTED]}" --menu \
"Choose a script to execute:" 20 70 14 \
"${SCRIPTS[@]}" 3>&1 1>&2 2>&3) || break
SCRIPT_SELECTED="${INDEX_TO_SLUG[$SCRIPT_INDEX]}"
run_script_by_slug "$SCRIPT_SELECTED"
[[ "$RETURN_TO_MAIN" == true ]] && { RETURN_TO_MAIN=false; break; }
done
done

View File

@@ -1,16 +1,14 @@
#!/bin/bash
# ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 24/02/2025
# Version : 1.2
# Last Updated: 06/07/2025
# ==========================================================
# Configuration ============================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
@@ -19,21 +17,24 @@ VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
#show_proxmenux_logo
# ==========================================================
confirm_and_run() {
local name="$1"
local command="$2"
if whiptail --title "$(translate "Confirmation")" \
--yesno "$(translate "Do you want to run the post-installation script from") $name?" \
10 70; then
dialog --clear --title "$(translate "Confirmation")" \
--yesno "\n\n$(translate "Do you want to run the post-installation script from") $name?" 10 70
response=$?
clear
if [ $response -eq 0 ]; then
eval "$command"
echo ""
echo
msg_success "$(translate 'Press ENTER to continue...')"
read -r _
else
@@ -42,62 +43,149 @@ confirm_and_run() {
fi
}
# ==========================================================
confirm_automated_script() {
local script_info=""
# Define scripts array
scripts=(
"Customizable script post-installation|ProxMenux|bash <(curl -s $REPO_URL/scripts/customizable_post_install.sh)"
"Proxmox VE Post Install|Helper-Scripts|bash -c \"\$(wget -qLO - https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/post-pve-install.sh); msg_success \\\"\$(translate 'Press ENTER to continue...')\\\"; read -r _\""
"xshok-proxmox Post install|fork xshok-proxmox|confirm_and_run \"Xshok\" \"wget https://raw.githubusercontent.com/MacRimi/xshok-proxmox/master/install-post.sh -c -O install-post.sh && bash install-post.sh && rm install-post.sh\""
"Uninstall Tools|ProxMenux|bash <(curl -s $REPO_URL/scripts/uninstall-tools.sh)"
script_info+="$(translate "This script will apply the following optimizations and advanced adjustments to your Proxmox VE server"):\n\n"
script_info+="$(translate "Configure") \Z4free repositories\Z0 $(translate "and upgrade the system (disables the enterprise repo)")\n"
script_info+="$(translate "Optionally remove") \Z4subscription banner\Z0 $(translate "from Proxmox web interface (you will be asked)")\n"
script_info+="$(translate "Optimize") \Z4memory\Z0, \Z4kernel\Z0, $(translate "and") \Z4network\Z0 $(translate "for better performance and stability")\n"
script_info+="$(translate "Install and configure") \Z4Log2RAM\Z0 $(translate "(only on SSD/NVMe) to protect your disk")\n"
script_info+="$(translate "Improve log rotation and limit log size to save space and extend disk life")\n"
script_info+="$(translate "Increase file and process limits for advanced workloads")\n"
script_info+="$(translate "Set up time synchronization and entropy generation")\n"
script_info+="$(translate "Add color prompts and useful aliases to the terminal environment")\n\n"
script_info+="\Zb$(translate "All changes are reversible using the ProxMenux uninstaller.")\Z0\n\n"
script_info+="$(translate "Do you want to apply these optimizations now?")"
dialog --clear --colors \
--backtitle "ProxMenux" \
--title "$(translate "Automated Post-Install Script")" \
--yesno "$script_info" 22 80
local response=$?
clear
if [ $response -eq 0 ]; then
bash <(curl -s $REPO_URL/scripts/post_install/auto_post_install.sh)
else
msg_warn "$(translate "Cancelled by user.")"
sleep 1
fi
}
# ==========================================================
declare -a PROXMENUX_SCRIPTS=(
"Customizable post-installation script|ProxMenux|bash <(curl -s $REPO_URL/scripts/post_install/customizable_post_install.sh)"
"Automated post-installation script|ProxMenux|confirm_automated_script"
"Uninstall optimizations|ProxMenux|bash <(curl -s $REPO_URL/scripts/post_install/uninstall-tools.sh)"
)
show_menu() {
while true; do
HEADER=$(printf " %-52s %-20s" "$(translate "Name")" "$(translate "Repository")")
menu_items=()
for i in "${!scripts[@]}"; do
IFS='|' read -r name repository command <<< "${scripts[$i]}"
number=$((i+1))
padded_option=$(printf "%2d %-50s" "$number" "$(translate "$name")")
menu_items+=("$padded_option" "$repository")
declare -a COMMUNITY_SCRIPTS=(
"Proxmox VE Post Install|Helper-Scripts|bash -c \"\$(wget -qLO - https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/post-pve-install.sh); msg_success \\\"\$(translate 'Press ENTER to continue...')\\\"; read -r _\""
"Xshok-proxmox Post install|fork xshok-proxmox|confirm_and_run \"Xshok\" \"wget https://raw.githubusercontent.com/MacRimi/xshok-proxmox/master/install-post.sh -c -O install-post.sh && bash install-post.sh && rm install-post.sh\""
)
# ==========================================================
format_menu_item() {
local description="$1"
local source="$2"
local total_width=62
local desc_length=${#description}
local source_length=${#source}
local spaces_needed=$((total_width - desc_length - source_length))
[ $spaces_needed -lt 3 ] && spaces_needed=3
local spacing=""
for ((i=0; i<spaces_needed; i++)); do
spacing+=" "
done
menu_items+=("$(printf "%2d %-40s" "$((${#scripts[@]}+1))" "$(translate "Return to Main Menu")")" "")
echo "${description}${spacing}${source}"
}
cleanup
# ==========================================================
show_menu() {
while true; do
local menu_items=()
script_selection=$(whiptail --title "$(translate "Post-Installation Scripts Menu")" \
--menu "\n$HEADER" 20 78 $((${#scripts[@]}+1)) \
"${menu_items[@]}" 3>&1 1>&2 2>&3)
if [ -n "$script_selection" ]; then
selected_number=$(echo "$script_selection" | awk '{print $1}')
declare -A script_commands
local counter=1
if [ "$selected_number" = "$((${#scripts[@]}+1))" ]; then
#show_proxmenux_logo
for script in "${PROXMENUX_SCRIPTS[@]}"; do
IFS='|' read -r name source command <<< "$script"
local translated_name="$(translate "$name")"
local formatted_item
formatted_item=$(format_menu_item "$translated_name" "$source")
menu_items+=("$counter" "$formatted_item")
script_commands["$counter"]="$command"
((counter++))
done
menu_items+=("" "")
menu_items+=("-" "───────────────────── $(translate "Community Scripts") ──────────────────────")
menu_items+=("" "")
for script in "${COMMUNITY_SCRIPTS[@]}"; do
IFS='|' read -r name source command <<< "$script"
local translated_name="$(translate "$name")"
local formatted_item
formatted_item=$(format_menu_item "$translated_name" "$source")
menu_items+=("$counter" "$formatted_item")
script_commands["$counter"]="$command"
((counter++))
done
menu_items+=("" "")
menu_items+=("0" "$(translate "Return to Main Menu")")
exec 3>&1
script_selection=$(dialog --clear \
--backtitle "ProxMenux" \
--title "$(translate "Post-Installation Scripts")" \
--menu "\n$(translate "Select a post-installation script:"):\n" \
22 78 15 \
"${menu_items[@]}" 2>&1 1>&3)
exit_status=$?
exec 3>&-
if [ $exit_status -ne 0 ] || [ "$script_selection" = "0" ]; then
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
fi
index=$((selected_number - 1))
if [ $index -ge 0 ] && [ $index -lt ${#scripts[@]} ]; then
IFS='|' read -r name repository command <<< "${scripts[$index]}"
eval "$command"
if [[ "$script_selection" == "-" || "$script_selection" == "" ]]; then
continue
fi
if [[ -n "${script_commands[$script_selection]}" ]]; then
eval "${script_commands[$script_selection]}"
else
#show_proxmenux_logo
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
msg_error "$(translate "Invalid selection")"
sleep 1
fi
done
}
clear
if [[ "$LANGUAGE" != "en" ]]; then
show_proxmenux_logo
msg_lang "$(translate "Generating automatic translations...")"
fi
# ==========================================================
show_menu

View File

@@ -1,21 +1,25 @@
#!/bin/bash
# ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management
# ProxMenu - Network Management and Repair Tool
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 28/01/2025
# Version : 1.1
# Last Updated: 08/07/2025
# ==========================================================
# Description:
# Advanced network management and troubleshooting tool for Proxmox VE.
# Features include interface detection, bridge management, connectivity testing,
# network diagnostics, configuration backup/restore, and automated repairs.
# Configuration ============================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
VENV_PATH="/opt/googletrans-env"
BACKUP_DIR="/var/backups/proxmenux"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
@@ -23,29 +27,940 @@ fi
load_language
initialize_cache
# ==========================================================
# Utility Functions
create_backup_dir() {
[ ! -d "$BACKUP_DIR" ] && mkdir -p "$BACKUP_DIR"
}
backup_network_config() {
create_backup_dir
local timestamp=$(date +"%Y-%m-%d_%H-%M-%S")
local backup_file="$BACKUP_DIR/interfaces_backup_$timestamp"
cp /etc/network/interfaces "$backup_file"
msg_ok "$(translate "Network configuration backed up")"
echo "$backup_file"
}
# ==========================================================
# Network Detection Functions
detect_network_method() {
# Detect Netplan
if compgen -G "/etc/netplan/*.yaml" > /dev/null; then
echo "netplan"
return 0
fi
# Detect systemd-networkd
if systemctl is-active --quiet systemd-networkd 2>/dev/null; then
echo "systemd-networkd"
return 0
fi
# Detect NetworkManager
if systemctl is-active --quiet NetworkManager 2>/dev/null; then
echo "networkmanager"
return 0
fi
# Default: Debian/Proxmox classic
echo "classic"
}
detect_physical_interfaces() {
ip -o link show | awk -F': ' '$2 !~ /^(lo|veth|dummy|bond|tap|tun|docker|br-)/ && $2 !~ /vmbr/ {print $2}' | sort
}
detect_bridge_interfaces() {
ip -o link show | awk -F': ' '$2 ~ /^vmbr/ {print $2}' | sort
}
detect_all_interfaces() {
ip -o link show | awk -F': ' '$2 !~ /^(lo|veth|dummy|tap|tun)/ {print $2}' | sort
}
get_interface_info() {
local interface="$1"
local info=""
# Get IP address
local ip=$(ip -4 addr show "$interface" 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}/\d+' | head -1)
[ -z "$ip" ] && ip="$(translate "No IP")"
# Get status
local status=$(ip link show "$interface" 2>/dev/null | grep -o "state [A-Z]*" | cut -d' ' -f2)
[ -z "$status" ] && status="UNKNOWN"
# Get MAC address
local mac=$(ip link show "$interface" 2>/dev/null | grep -o "link/ether [a-f0-9:]*" | cut -d' ' -f2)
[ -z "$mac" ] && mac="$(translate "No MAC")"
echo "$interface|$ip|$status|$mac"
}
# ==========================================================
# Network Information Functions
show_interface_details() {
NETWORK_METHOD=$(detect_network_method)
if [[ "$NETWORK_METHOD" != "classic" ]]; then
dialog --title "Unsupported Network Stack" \
--msgbox "WARNING: This script only supports the classic Debian/Proxmox network configuration (/etc/network/interfaces).\n\nDetected: $NETWORK_METHOD.\n\nAborting for safety.\n\nPlease configure your network using your distribution's supported tools." 14 70
exit 1
fi
local interfaces=($(detect_all_interfaces))
local info_text=""
info_text+="$(translate "Network Interface Details")\n"
info_text+="$(printf '=%.0s' {1..50})\n\n"
for interface in "${interfaces[@]}"; do
local details=$(get_interface_info "$interface")
IFS='|' read -r name ip status mac <<< "$details"
info_text+="$(translate "Interface"): $name\n"
info_text+=" $(translate "IP Address"): $ip\n"
info_text+=" $(translate "Status"): $status\n"
info_text+=" $(translate "MAC Address"): $mac\n\n"
done
dialog --backtitle "ProxMenux" --title "$(translate "Interface Details")" \
--msgbox "$info_text" 20 70
}
show_bridge_status() {
NETWORK_METHOD=$(detect_network_method)
if [[ "$NETWORK_METHOD" != "classic" ]]; then
dialog --title "Unsupported Network Stack" \
--msgbox "WARNING: This script only supports the classic Debian/Proxmox network configuration (/etc/network/interfaces).\n\nDetected: $NETWORK_METHOD.\n\nAborting for safety.\n\nPlease configure your network using your distribution's supported tools." 14 70
exit 1
fi
local bridges=($(detect_bridge_interfaces))
local bridge_info=""
bridge_info+="$(translate "Bridge Configuration Status")\n"
bridge_info+="$(printf '=%.0s' {1..40})\n\n"
if [ ${#bridges[@]} -eq 0 ]; then
bridge_info+="$(translate "No bridges found")\n"
else
for bridge in "${bridges[@]}"; do
local details=$(get_interface_info "$bridge")
IFS='|' read -r name ip status mac <<< "$details"
# Get bridge ports
local ports=$(grep -A5 "iface $bridge" /etc/network/interfaces 2>/dev/null | grep "bridge-ports" | cut -d' ' -f2-)
[ -z "$ports" ] && ports="$(translate "None")"
bridge_info+="$(translate "Bridge"): $name\n"
bridge_info+=" $(translate "IP"): $ip\n"
bridge_info+=" $(translate "Status"): $status\n"
bridge_info+=" $(translate "Ports"): $ports\n\n"
done
fi
dialog --backtitle "ProxMenux" --title "$(translate "Bridge Status")" \
--msgbox "$bridge_info" 20 70
}
show_routing_table_() {
local route_info=""
route_info+="$(translate "Routing Table")\n"
route_info+="$(printf '=%.0s' {1..30})\n\n"
route_info+="$(ip route show)\n\n"
route_info+="$(translate "Default Gateway"): $(ip route | grep default | awk '{print $3}' | head -1)\n"
dialog --backtitle "ProxMenux" --title "$(translate "Routing Information")" \
--msgbox "$route_info" 20 80
}
show_routing_table() {
local route_info=""
local default_gw=$(ip route | grep default | awk '{print $3}' | head -1)
local routes=$(ip route show)
local route_count=$(echo "$routes" | wc -l)
route_info+="🗺️ $(translate "Routing Table")\n"
route_info+="$(printf '═%.0s' {1..60})\n\n"
if [ -z "$routes" ]; then
route_info+="⚠️ $(translate "No routing information found.")\n\n"
else
route_info+="$(translate "Total routes"): $route_count\n\n"
while read -r line; do
if [[ "$line" == *"default"* ]]; then
route_info+="➡️ $line\n"
else
route_info+="$line\n"
fi
done <<< "$routes"
route_info+="\n"
route_info+="🌍 $(translate "Default Gateway"): ${default_gw:-$(translate "Not found")}\n"
fi
dialog --backtitle "ProxMenux" --title "$(translate "Routing Information")" \
--msgbox "$route_info" 20 80
}
# ==========================================================
# Network Testing Functions
test_connectivity() {
local test_results=""
local tests=(
"8.8.8.8|Google DNS"
"1.1.1.1|Cloudflare DNS"
"$(ip route | grep default | awk '{print $3}' | head -1)|Gateway"
)
show_proxmenux_logo
msg_info "$(translate "Test Connectivity")"
test_results+="$(translate "Connectivity Test Results")\n"
test_results+="$(printf '=%.0s' {1..35})\n\n"
for test in "${tests[@]}"; do
IFS='|' read -r target name <<< "$test"
if [ -n "$target" ] && [ "$target" != "" ]; then
if ping -c 2 -W 3 "$target" >/dev/null 2>&1; then
test_results+="$name ($target): $(translate "OK")\n"
else
test_results+="$name ($target): $(translate "FAILED")\n"
fi
fi
done
# DNS Resolution test
if nslookup google.com >/dev/null 2>&1; then
test_results+="$(translate "DNS Resolution"): $(translate "OK")\n"
else
test_results+="$(translate "DNS Resolution"): $(translate "FAILED")\n"
fi
cleanup
dialog --backtitle "ProxMenux" --title "$(translate "Connectivity Test")" \
--msgbox "$test_results" 20 70
}
advanced_network_diagnostics() {
NETWORK_METHOD=$(detect_network_method)
if [[ "$NETWORK_METHOD" != "classic" ]]; then
dialog --title "Unsupported Network Stack" \
--msgbox "WARNING: This script only supports the classic Debian/Proxmox network configuration (/etc/network/interfaces).\n\nDetected: $NETWORK_METHOD.\n\nAborting for safety.\n\nPlease configure your network using your distribution's supported tools." 14 70
exit 1
fi
show_proxmenux_logo
msg_info "$(translate "Advanced Diagnostics")"
sleep 1
local diag_info=""
diag_info+="$(translate "Advanced Network Diagnostics")\n"
diag_info+="$(printf '=%.0s' {1..40})\n\n"
# Network statistics
diag_info+="$(translate "Active Connections"): $(ss -tuln | wc -l)\n"
diag_info+="$(translate "Listening Ports"): $(ss -tln | grep LISTEN | wc -l)\n"
diag_info+="$(translate "Network Interfaces"): $(ip link show | grep -c "^[0-9]")\n\n"
# Check for common issues
diag_info+="$(translate "Common Issues Check"):\n"
# Check if NetworkManager is running (shouldn't be on Proxmox)
if systemctl is-active --quiet NetworkManager 2>/dev/null; then
diag_info+="$(translate "NetworkManager is running (may cause conflicts)")\n"
if dialog --title "$(translate "NetworkManager Detected")" \
--yesno "$(translate "NetworkManager is running, which may conflict with Proxmox.")\n\n$(translate "Do you want to disable and remove it now?")" 10 70; then
dialog --infobox "$(translate "Disabling and removing NetworkManager...")" 6 60
systemctl stop NetworkManager >/dev/null 2>&1
systemctl disable NetworkManager >/dev/null 2>&1
apt-get purge -y network-manager >/dev/null 2>&1
diag_info+="$(translate "NetworkManager has been removed successfully")\n"
else
diag_info+=" $(translate "User chose not to remove NetworkManager")\n"
fi
else
diag_info+="$(translate "NetworkManager not running")\n"
fi
# Check for duplicate IPs
local ips=($(ip -4 addr show | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | sort | uniq -d))
if [ ${#ips[@]} -gt 0 ]; then
diag_info+="$(translate "Duplicate IP addresses found"): ${ips[*]}\n"
else
diag_info+="$(translate "No duplicate IP addresses")\n"
fi
cleanup
dialog --backtitle "ProxMenux" --title "$(translate "Network Diagnostics")" \
--msgbox "$diag_info" 20 70
}
# ==========================================================
# SAFE Network Analysis Functions (NO AUTO-REPAIR)
# ==========================================================
analyze_bridge_configuration() {
while true; do
OPTION=$(whiptail --title "$(translate "Network Menu")" --menu "$(translate "Select an option:")" 15 60 2 \
"1" "$(translate "Repair Network")" \
"2" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
NETWORK_METHOD=$(detect_network_method)
case $OPTION in
1)
msg_info2 "$(translate "Running network repair...")"
if bash <(curl -s "$REPO_URL/scripts/repair_network.sh"); then
msg_ok "$(translate "Network repair completed.")"
else
msg_error "$(translate "Error in network repair.")"
if [[ "$NETWORK_METHOD" != "classic" ]]; then
dialog --title "Unsupported Network Stack" \
--msgbox "WARNING: This script only supports the classic Debian/Proxmox network configuration (/etc/network/interfaces).\n\nDetected: $NETWORK_METHOD.\n\nAborting for safety.\n\nPlease configure your network using your distribution's supported tools." 14 70
exit 1
fi
;;
2)
show_proxmenux_logo
msg_info "$(translate "Analyzing Bridge Configuration - READ ONLY MODE")"
sleep 1
local physical_interfaces=($(detect_physical_interfaces))
local bridges=($(detect_bridge_interfaces))
local analysis_report=""
local issues_found=0
local suggestions=""
analysis_report+="🔍 $(translate "BRIDGE CONFIGURATION ANALYSIS")\n"
analysis_report+="$(printf '=%.0s' {1..50})\n\n"
cleanup
if [ ${#bridges[@]} -eq 0 ]; then
analysis_report+=" $(translate "No bridges found in system")\n"
dialog --backtitle "ProxMenux" --title "$(translate "Bridge Analysis")" --msgbox "$analysis_report" 10 60
return
;;
*)
msg_warn "$(translate "Invalid option.")"
sleep 2
;;
fi
# Analyze each bridge
for bridge in "${bridges[@]}"; do
analysis_report+="🌉 $(translate "Bridge"): $bridge\n"
# Get current configuration
local current_ports=$(grep -A5 "iface $bridge" /etc/network/interfaces 2>/dev/null | grep "bridge-ports" | cut -d' ' -f2-)
local bridge_ip=$(ip -4 addr show "$bridge" 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}/\d+' | head -1)
local bridge_status=$(ip link show "$bridge" 2>/dev/null | grep -o "state [A-Z]*" | cut -d' ' -f2)
analysis_report+=" 📍 $(translate "Status"): ${bridge_status:-UNKNOWN}\n"
analysis_report+=" 🌐 $(translate "IP"): ${bridge_ip:-$(translate "No IP assigned")}\n"
analysis_report+=" 🔌 $(translate "Configured Ports"): ${current_ports:-$(translate "None")}\n"
if [ -n "$current_ports" ]; then
local invalid_ports=""
local valid_ports=""
# Check each configured port
for port in $current_ports; do
if ip link show "$port" >/dev/null 2>&1; then
valid_ports+="$port "
analysis_report+="$(translate "Port") $port: $(translate "EXISTS")\n"
else
invalid_ports+="$port "
analysis_report+="$(translate "Port") $port: $(translate "NOT FOUND")\n"
((issues_found++))
fi
done
# Generate suggestions for invalid ports
if [ -n "$invalid_ports" ]; then
suggestions+="🔧 $(translate "SUGGESTION FOR") $bridge:\n"
if [ ${#physical_interfaces[@]} -gt 0 ]; then
suggestions+=" $(translate "Replace invalid port(s)") '$invalid_ports' $(translate "with"): ${physical_interfaces[0]}\n"
suggestions+=" $(translate "Command"): sed -i 's/bridge-ports.*/bridge-ports ${physical_interfaces[0]}/' /etc/network/interfaces\n"
else
suggestions+=" $(translate "Remove invalid port(s)") '$invalid_ports'\n"
suggestions+=" $(translate "Command"): sed -i 's/bridge-ports.*/bridge-ports none/' /etc/network/interfaces\n"
fi
suggestions+="\n"
fi
else
analysis_report+=" ⚠️ $(translate "No ports configured")\n"
if [ ${#physical_interfaces[@]} -gt 0 ]; then
suggestions+="🔧 $(translate "SUGGESTION FOR") $bridge:\n"
suggestions+=" $(translate "Consider adding physical interface"): ${physical_interfaces[0]}\n"
suggestions+=" $(translate "Command"): sed -i '/iface $bridge/a\\ bridge-ports ${physical_interfaces[0]}' /etc/network/interfaces\n\n"
fi
fi
analysis_report+="\n"
done
# Summary
analysis_report+="📊 $(translate "ANALYSIS SUMMARY")\n"
analysis_report+="$(printf '=%.0s' {1..25})\n"
analysis_report+="$(translate "Bridges analyzed"): ${#bridges[@]}\n"
analysis_report+="$(translate "Issues found"): $issues_found\n"
local auto_only=$(grep "^auto" /etc/network/interfaces | awk '{print $2}' | while read i; do
grep -q "^iface $i" /etc/network/interfaces || echo "$i"
done)
if [ -n "$auto_only" ]; then
analysis_report+="⚠️ $(translate "Interfaces defined with 'auto' but no 'iface' block"): $auto_only\n"
((issues_found++))
fi
analysis_report+="$(translate "Physical interfaces available"): ${#physical_interfaces[@]}\n\n"
if [ $issues_found -gt 0 ]; then
analysis_report+="$suggestions"
analysis_report+="⚠️ $(translate "IMPORTANT"): $(translate "No changes have been made to your system")\n"
analysis_report+="$(translate "Use the Guided Repair option to fix issues safely")\n"
else
analysis_report+="$(translate "No bridge configuration issues found")\n"
fi
# Show analysis in scrollable dialog
local temp_file=$(mktemp)
echo -e "$analysis_report" > "$temp_file"
dialog --backtitle "ProxMenux" --title "$(translate "Bridge Configuration Analysis")" \
--textbox "$temp_file" 25 80
rm -f "$temp_file"
# Offer guided repair if issues found
if [ $issues_found -gt 0 ]; then
if dialog --backtitle "ProxMenux" --title "$(translate "Guided Repair Available")" \
--yesno "$(translate "Issues were found. Would you like to use the Guided Repair Assistant?")" 8 60; then
guided_bridge_repair
fi
fi
}
guided_bridge_repair() {
local step=1
local total_steps=5
local timestamp=$(date +"%Y%m%d_%H%M%S")
local preview_backup_file="$BACKUP_DIR/interfaces_backup_$timestamp"
if ! dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Safety Backup")" \
--yesno "$(translate "Before making any changes, we'll create a safety backup.")\n\n$(translate "Backup location"): $preview_backup_file\n\n$(translate "Continue?")" 12 70; then
return
fi
((step++))
show_proxmenux_logo
local backup_file=$(backup_network_config)
sleep 1
dialog --backtitle "ProxMenux" --title "$(translate "Backup Created")" \
--msgbox "$(translate "Safety backup created"): $backup_file\n\n$(translate "You can restore it anytime with"):\ncp $backup_file /etc/network/interfaces" 10 70
# Step 2: Show current configuration
if ! dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Current Configuration")" \
--yesno "$(translate "Let's review your current network configuration.")\n\n$(translate "Would you like to see the current") /etc/network/interfaces $(translate "file?")" 10 70; then
return
fi
((step++))
# Show current config
local temp_config=$(mktemp)
cat /etc/network/interfaces > "$temp_config"
dialog --backtitle "ProxMenux" --title "$(translate "Current Network Configuration")" \
--textbox "$temp_config" 20 80
rm -f "$temp_config"
# Step 3: Identify specific changes needed
local physical_interfaces=($(detect_physical_interfaces))
local bridges=($(detect_bridge_interfaces))
local changes_needed=""
for bridge in "${bridges[@]}"; do
local current_ports=$(grep -A5 "iface $bridge" /etc/network/interfaces 2>/dev/null | grep "bridge-ports" | cut -d' ' -f2-)
if [ -n "$current_ports" ]; then
for port in $current_ports; do
if ! ip link show "$port" >/dev/null 2>&1; then
if [ ${#physical_interfaces[@]} -gt 0 ]; then
changes_needed+="$(translate "Bridge") $bridge: $(translate "Replace") '$port' $(translate "with") '${physical_interfaces[0]}'\n"
else
changes_needed+="$(translate "Bridge") $bridge: $(translate "Remove invalid port") '$port'\n"
fi
fi
done
fi
done
if [ -z "$changes_needed" ]; then
dialog --backtitle "ProxMenux" --title "$(translate "No Changes Needed")" \
--msgbox "$(translate "After detailed analysis, no changes are needed.")" 8 50
return
fi
if ! dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Proposed Changes")" \
--yesno "$(translate "These are the changes that will be made"):\n\n$changes_needed\n$(translate "Do you want to proceed?")" 15 70; then
return
fi
((step++))
# Step 4: Apply changes with verification
dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Applying Changes")" \
--infobox "$(translate "Applying changes safely...")\n\n$(translate "This may take a few seconds...")" 8 50
# Apply the changes
for bridge in "${bridges[@]}"; do
local current_ports=$(grep -A5 "iface $bridge" /etc/network/interfaces 2>/dev/null | grep "bridge-ports" | cut -d' ' -f2-)
if [ -n "$current_ports" ]; then
local new_ports=""
for port in $current_ports; do
if ip link show "$port" >/dev/null 2>&1; then
new_ports+="$port "
fi
done
# If no valid ports and we have physical interfaces, use the first one
if [ -z "$new_ports" ] && [ ${#physical_interfaces[@]} -gt 0 ]; then
new_ports="${physical_interfaces[0]}"
fi
# Apply the change
if [ "$new_ports" != "$current_ports" ]; then
sed -i "/iface $bridge/,/bridge-ports/ s/bridge-ports.*/bridge-ports $new_ports/" /etc/network/interfaces
fi
fi
done
((step++))
# Step 5: Verification
local verification_report=""
verification_report+="$(translate "CHANGES APPLIED SUCCESSFULLY")\n\n"
verification_report+="$(translate "Verification"):\n"
for bridge in "${bridges[@]}"; do
local new_ports=$(grep -A5 "iface $bridge" /etc/network/interfaces 2>/dev/null | grep "bridge-ports" | cut -d' ' -f2-)
verification_report+="$(translate "Bridge") $bridge: $new_ports\n"
# Verify each port exists
for port in $new_ports; do
if ip link show "$port" >/dev/null 2>&1; then
verification_report+="$port: $(translate "EXISTS")\n"
else
verification_report+="$port: $(translate "NOT FOUND")\n"
fi
done
done
verification_report+="\n$(translate "Backup available at"): $backup_file\n"
verification_report+="$(translate "To restore"): cp $backup_file /etc/network/interfaces"
dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Repair Complete")" \
--msgbox "$verification_report" 18 70
# Ask about network restart
if dialog --backtitle "ProxMenux" --title "$(translate "Network Restart")" \
--yesno "$(translate "Changes have been applied to the configuration file.")\n\n$(translate "Do you want to restart the network service to apply changes?")\n\n$(translate "WARNING: This may cause a brief disconnection.")" 12 70; then
clear
msg_info "$(translate "Restarting network service...")"
if systemctl restart networking; then
msg_ok "$(translate "Network service restarted successfully")"
else
msg_error "$(translate "Failed to restart network service")"
msg_warn "$(translate "You can restore the backup with"): cp $backup_file /etc/network/interfaces"
fi
msg_success "$(translate "Press ENTER to continue...")"
read -r
fi
}
analyze_network_configuration() {
NETWORK_METHOD=$(detect_network_method)
if [[ "$NETWORK_METHOD" != "classic" ]]; then
dialog --title "Unsupported Network Stack" \
--msgbox "WARNING: This script only supports the classic Debian/Proxmox network configuration (/etc/network/interfaces).\n\nDetected: $NETWORK_METHOD.\n\nAborting for safety.\n\nPlease configure your network using your distribution's supported tools." 14 70
exit 1
fi
show_proxmenux_logo
msg_info "$(translate "Analyzing Network Configuration - READ ONLY MODE")"
sleep 1
local configured_interfaces=($(grep "^iface" /etc/network/interfaces | awk '{print $2}' | grep -v "lo"))
local analysis_report=""
local issues_found=0
local suggestions=""
analysis_report+="🔍 $(translate "NETWORK CONFIGURATION ANALYSIS")\n"
analysis_report+="$(printf '=%.0s' {1..50})\n\n"
cleanup
if [ ${#configured_interfaces[@]} -eq 0 ]; then
analysis_report+=" $(translate "No network interfaces configured (besides loopback)")\n"
dialog --title "$(translate "Configuration Analysis")" --msgbox "$analysis_report" 10 60
return
fi
analysis_report+="📋 $(translate "CONFIGURED INTERFACES")\n"
analysis_report+="$(printf '=%.0s' {1..30})\n"
# Analyze each configured interface
for iface in "${configured_interfaces[@]}"; do
analysis_report+="🔌 $(translate "Interface"): $iface\n"
# Check if interface exists physically
if ip link show "$iface" >/dev/null 2>&1; then
local status=$(ip link show "$iface" 2>/dev/null | grep -o "state [A-Z]*" | cut -d' ' -f2)
local ip=$(ip -4 addr show "$iface" 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}/\d+' | head -1)
analysis_report+="$(translate "Status"): $(translate "EXISTS") ($status)\n"
analysis_report+=" 🌐 $(translate "IP"): ${ip:-$(translate "No IP assigned")}\n"
# Check if it's a bridge or bond (these are virtual, so it's normal they exist)
if [[ $iface =~ ^(vmbr|bond) ]]; then
analysis_report+=" $(translate "Type"): $(translate "Virtual interface (normal)")\n"
else
analysis_report+=" $(translate "Type"): $(translate "Physical interface")\n"
fi
else
analysis_report+="$(translate "Status"): $(translate "NOT FOUND")\n"
analysis_report+=" ⚠️ $(translate "Issue"): $(translate "Configured but doesn't exist")\n"
((issues_found++))
# Only suggest removal for non-virtual interfaces
if [[ ! $iface =~ ^(vmbr|bond) ]]; then
suggestions+="🔧 $(translate "SUGGESTION FOR") $iface:\n"
suggestions+=" $(translate "This interface is configured but doesn't exist physically")\n"
suggestions+=" $(translate "Consider removing its configuration")\n"
suggestions+=" $(translate "Command"): sed -i '/iface $iface/,/^$/d' /etc/network/interfaces\n\n"
fi
fi
analysis_report+="\n"
done
# Summary
analysis_report+="📊 $(translate "ANALYSIS SUMMARY")\n"
analysis_report+="$(printf '=%.0s' {1..25})\n"
analysis_report+="$(translate "Interfaces configured"): ${#configured_interfaces[@]}\n"
analysis_report+="$(translate "Issues found"): $issues_found\n\n"
if [ $issues_found -gt 0 ]; then
analysis_report+="$suggestions"
analysis_report+="⚠️ $(translate "IMPORTANT"): $(translate "No changes have been made to your system")\n"
analysis_report+="$(translate "Use the Guided Cleanup option to fix issues safely")\n"
else
analysis_report+="$(translate "No configuration issues found")\n"
fi
# Show analysis in scrollable dialog
local temp_file=$(mktemp)
echo -e "$analysis_report" > "$temp_file"
dialog --backtitle "ProxMenux" --title "$(translate "Network Configuration Analysis")" \
--textbox "$temp_file" 25 80
rm -f "$temp_file"
# Offer guided cleanup if issues found
if [ $issues_found -gt 0 ]; then
if dialog --backtitle "ProxMenux" --title "$(translate "Guided Cleanup Available")" \
--yesno "$(translate "Issues were found. Would you like to use the Guided Cleanup Assistant?")" 8 60; then
guided_configuration_cleanup
fi
fi
}
guided_configuration_cleanup() {
local step=1
local total_steps=5
local timestamp=$(date +"%Y%m%d_%H%M%S")
local preview_backup_file="$BACKUP_DIR/interfaces_backup_$timestamp"
if ! dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Safety Backup")" \
--yesno "$(translate "Before making any changes, we'll create a safety backup.")\n\n$(translate "Backup location"): $preview_backup_file\n\n$(translate "Continue?")" 12 70; then
return
fi
((step++))
show_proxmenux_logo
local backup_file=$(backup_network_config)
sleep 1
dialog --backtitle "ProxMenux" --title "$(translate "Backup Created")" \
--msgbox "$(translate "Safety backup created"): $backup_file\n\n$(translate "You can restore it anytime with"):\ncp $backup_file /etc/network/interfaces" 10 70
# Step 2: Identify interfaces to remove
local configured_interfaces=($(grep "^iface" /etc/network/interfaces | awk '{print $2}' | grep -v "lo"))
local interfaces_to_remove=""
local removal_list=""
for iface in "${configured_interfaces[@]}"; do
if [[ ! $iface =~ ^(vmbr|bond) ]] && ! ip link show "$iface" >/dev/null 2>&1; then
interfaces_to_remove+="$iface "
removal_list+="$iface: $(translate "Configured but doesn't exist")\n"
fi
done
if [ -z "$interfaces_to_remove" ]; then
dialog --backtitle "ProxMenux" --title "$(translate "No Cleanup Needed")" \
--msgbox "$(translate "After detailed analysis, no cleanup is needed.")" 8 50
return
fi
if ! dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Interfaces to Remove")" \
--yesno "$(translate "These interface configurations will be removed"):\n\n$removal_list\n$(translate "Do you want to proceed?")" 15 70; then
return
fi
((step++))
# Step 3: Show what will be removed
local temp_preview=$(mktemp)
echo "$(translate "Configuration sections that will be REMOVED"):" > "$temp_preview"
echo "=================================================" >> "$temp_preview"
echo "" >> "$temp_preview"
for iface in $interfaces_to_remove; do
echo "# Interface: $iface" >> "$temp_preview"
sed -n "/^iface $iface/,/^$/p" /etc/network/interfaces >> "$temp_preview"
echo "" >> "$temp_preview"
done
if ! dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Preview Changes")" \
--yesno "$(translate "Review what will be removed"):\n\n$(translate "Press OK to see the preview, then confirm")" 10 60; then
rm -f "$temp_preview"
return
fi
dialog --backtitle "ProxMenux" --title "$(translate "Configuration to be Removed")" \
--textbox "$temp_preview" 20 80
rm -f "$temp_preview"
if ! dialog --backtitle "ProxMenux" --title "$(translate "Final Confirmation")" \
--yesno "$(translate "Are you sure you want to remove these configurations?")" 8 60; then
return
fi
((step++))
# Step 4: Apply changes
dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Applying Changes")" \
--infobox "$(translate "Removing invalid configurations...")\n\n$(translate "This may take a few seconds...")" 8 50
for iface in $interfaces_to_remove; do
sed -i "/^iface $iface/,/^$/d" /etc/network/interfaces
done
((step++))
# Step 5: Verification
local verification_report=""
verification_report+="$(translate "CLEANUP COMPLETED SUCCESSFULLY")\n\n"
verification_report+="$(translate "Removed configurations for"):\n"
for iface in $interfaces_to_remove; do
verification_report+="$iface\n"
done
verification_report+="\n$(translate "Verification"): $(translate "Checking remaining interfaces")\n"
local remaining_interfaces=($(grep "^iface" /etc/network/interfaces | awk '{print $2}' | grep -v "lo"))
for iface in "${remaining_interfaces[@]}"; do
if ip link show "$iface" >/dev/null 2>&1; then
verification_report+="$iface: $(translate "OK")\n"
else
verification_report+="⚠️ $iface: $(translate "Still has issues")\n"
fi
done
verification_report+="\n$(translate "Backup available at"): $backup_file\n"
verification_report+="$(translate "To restore"): cp $backup_file /etc/network/interfaces"
dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Cleanup Complete")" \
--msgbox "$verification_report" 18 70
}
restart_network_service() {
if dialog --title "$(translate "Restart Network")" \
--yesno "\n$(translate "This will restart the network service and may cause a brief disconnection. Continue?")" 10 60; then
show_proxmenux_logo
msg_info "$(translate "Restarting network service...")"
if systemctl restart networking; then
msg_ok "$(translate "Network service restarted successfully")"
else
msg_error "$(translate "Failed to restart network service")"
msg_warn "$(translate "If you lose connectivity, you can restore from backup using the console.")"
fi
msg_success "$(translate "Press ENTER to continue...")"
read -r
fi
}
# ==========================================================
# Configuration Management
show_network_config() {
NETWORK_METHOD=$(detect_network_method)
if [[ "$NETWORK_METHOD" != "classic" ]]; then
dialog --title "Unsupported Network Stack" \
--msgbox "WARNING: This script only supports the classic Debian/Proxmox network configuration (/etc/network/interfaces).\n\nDetected: $NETWORK_METHOD.\n\nAborting for safety.\n\nPlease configure your network using your distribution's supported tools." 14 70
exit 1
fi
local config_content
config_content=$(cat /etc/network/interfaces)
show_proxmenux_logo
echo -e
echo -e
echo "========== $(translate "Network Configuration File") =========="
echo
cat /etc/network/interfaces
echo
msg_success "$(translate "Press Enter to continue...")"
read -r
}
create_network_backup_manual() {
NETWORK_METHOD=$(detect_network_method)
if [[ "$NETWORK_METHOD" != "classic" ]]; then
dialog --title "Unsupported Network Stack" \
--msgbox "WARNING: This script only supports the classic Debian/Proxmox network configuration (/etc/network/interfaces).\n\nDetected: $NETWORK_METHOD.\n\nAborting for safety.\n\nPlease configure your network using your distribution's supported tools." 14 70
exit 1
fi
show_proxmenux_logo
echo -e
msg_info "$(translate "Creating backup of network interfaces configuration...")"
sleep 3
backup_network_config
echo -e
msg_success "$(translate "Press Enter to continue...")"
read -r
}
restore_network_backup() {
NETWORK_METHOD=$(detect_network_method)
if [[ "$NETWORK_METHOD" != "classic" ]]; then
dialog --title "Unsupported Network Stack" \
--msgbox "WARNING: This script only supports the classic Debian/Proxmox network configuration (/etc/network/interfaces).\n\nDetected: $NETWORK_METHOD.\n\nAborting for safety.\n\nPlease configure your network using your distribution's supported tools." 14 70
exit 1
fi
local backups=($(ls -1 "$BACKUP_DIR"/interfaces_backup_* 2>/dev/null | sort -r))
if [ ${#backups[@]} -eq 0 ]; then
dialog --backtitle "ProxMenux" --title "$(translate "No Backups")" \
--msgbox "\n$(translate "No network configuration backups found.")" 14 60
return
fi
local menu_items=()
local counter=1
for backup in "${backups[@]}"; do
local filename=$(basename "$backup")
local timestamp=$(basename "$backup" | sed 's/interfaces_backup_//')
menu_items+=("$counter" "$timestamp")
((counter++))
done
local selection=$(dialog --backtitle "ProxMenux" --title "$(translate "Restore Backup")" \
--menu "$(translate "Select backup to restore:"):" 20 70 12 \
"${menu_items[@]}" 3>&1 1>&2 2>&3)
if [ -n "$selection" ] && [ "$selection" -ge 1 ] && [ "$selection" -le ${#backups[@]} ]; then
local selected_backup="${backups[$((selection-1))]}"
if dialog --backtitle "ProxMenux" --title "$(translate "Preview Backup")" \
--yesno "\n$(translate "Do you want to view the selected backup before restoring?")" 8 60; then
dialog --backtitle "ProxMenux" --title "$(translate "Backup Preview")" \
--textbox "$selected_backup" 22 80
fi
if dialog --backtitle "ProxMenux" --title "$(translate "Confirm Restore")" \
--yesno "\n$(translate "Are you sure you want to restore this backup?\nCurrent configuration will be overwritten.")\n\n$(translate "For your safety, a backup of the current configuration will be created automatically before restoring.")" 14 70; then
local pre_restore_backup=$(backup_network_config)
cp "$selected_backup" /etc/network/interfaces
dialog --backtitle "ProxMenux" --title "$(translate "Backup Restored")" \
--msgbox "\n$(translate "Network configuration has been restored from backup.")" 8 60
if dialog --backtitle "ProxMenux" --title "$(translate "Restart Network")" \
--yesno "\n$(translate "Do you want to restart the network service now to apply changes?")" 8 60; then
if systemctl restart networking; then
dialog --backtitle "ProxMenux" --title "$(translate "Network Restarted")" \
--msgbox "\n$(translate "Network service restarted successfully.")" 8 50
fi
fi
fi
fi
}
# ==========================================================
# Main Menu
show_main_menu() {
while true; do
local selection=$(dialog --clear \
--backtitle "ProxMenux" \
--title "$(translate "Network Management - SAFE MODE")" \
--menu "$(translate "Select an option:"):" 20 70 12 \
"1" "$(translate "Show Interface Details")" \
"2" "$(translate "Show Bridge Status")" \
"3" "$(translate "Show Routing Table")" \
"4" "$(translate "Test Connectivity")" \
"5" "$(translate "Advanced Diagnostics")" \
"6" "$(translate "Analyze Bridge Configuration")" \
"7" "$(translate "Analyze Network Configuration")" \
"8" "$(translate "Restart Network Service")" \
"9" "$(translate "Show Network Config File")" \
"10" "$(translate "Create Network Backup")" \
"11" "$(translate "Restore Network Backup")" \
"0" "$(translate "Return to Main Menu")" \
3>&1 1>&2 2>&3)
case $selection in
1) show_interface_details ;;
2) show_bridge_status ;;
3) show_routing_table ;;
4) test_connectivity ;;
5) advanced_network_diagnostics ;;
6) analyze_bridge_configuration ;;
7) analyze_network_configuration ;;
8) restart_network_service ;;
9) show_network_config ;;
10) create_network_backup_manual ;;
11) restore_network_backup ;;
0|"") exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") ;;
esac
done
}
# ==========================================================
show_main_menu

View File

@@ -26,36 +26,32 @@ initialize_cache
while true; do
OPTION=$(whiptail --title "$(translate "Disk and Storage Manager Menu")" --menu "$(translate "Select an option:")" 20 70 10 \
"1" "$(translate "Add Disk Passthrough to a VM")" \
"2" "$(translate "Add Disk") Passthrough $(translate "to a CT")" \
OPTION=$(dialog --clear --backtitle "ProxMenux" --title "$(translate "Disk and Storage Manager Menu")" \
--menu "\n$(translate "Select an option:")" 20 70 10 \
"1" "$(translate "Add Disk") Passthrough $(translate "to a VM")" \
"2" "$(translate "Add Disk") Passthrough $(translate "to a LXC")" \
"3" "$(translate "Import Disk Image to a VM")" \
"4" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
"4" "$(translate "Return to Main Menu")" \
2>&1 >/dev/tty)
case $OPTION in
1)
#show_proxmenux_logo
msg_info2 "$(translate "Running script: Add Disk Passthrough to a VM")..."
clear
bash <(curl -s "$REPO_URL/scripts/storage/disk-passthrough.sh")
;;
2)
#show_proxmenux_logo
msg_info2 "$(translate "Running script: Add Disk Passthrough to a CT")..."
clear
bash <(curl -s "$REPO_URL/scripts/storage/disk-passthrough_ct.sh")
;;
3)
#show_proxmenux_logo
msg_info2 "$(translate "Running script: Import Disk Image to a VM")..."
clear
bash <(curl -s "$REPO_URL/scripts/storage/import-disk-image.sh")
;;
4)
#show_proxmenux_logo
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
;;
*)
#show_proxmenux_logo
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
;;
esac
done

View File

@@ -0,0 +1,75 @@
#!/bin/bash
# ==========================================================
# ProxMenux - A menu-driven script for Proxmox VE management
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 02/07/2025
# ==========================================================
# Configuration ============================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
# ==========================================================
while true; do
OPTION=$(dialog --clear --backtitle "ProxMenux" --title "$(translate "Utilities Menu")" \
--menu "\n$(translate "Select an option:")" 20 70 8 \
"1" "$(translate "UUp Dump ISO creator Custom")" \
"2" "$(translate "System Utilities Installer")" \
"3" "$(translate "Proxmox System Update")" \
"4" "$(translate "Return to Main Menu")" \
2>&1 >/dev/tty)
case $OPTION in
1)
bash <(curl -s "$REPO_URL/scripts/utilities/uup_dump_iso_creator.sh")
if [ $? -ne 0 ]; then
return
fi
;;
2)
bash <(curl -s "$REPO_URL/scripts/utilities/system_utils.sh")
if [ $? -ne 0 ]; then
return
fi
;;
3)
proxmox_update_msg="\n"
proxmox_update_msg+="$(translate "This script will update your Proxmox VE system with advanced options:")\n\n"
proxmox_update_msg+="$(translate "Repairs and optimizes repositories")\n"
proxmox_update_msg+="$(translate "Cleans duplicate or conflicting sources")\n"
proxmox_update_msg+="$(translate "Switches to the free no-subscription repository")\n"
proxmox_update_msg+="$(translate "Updates all Proxmox and Debian packages")\n"
proxmox_update_msg+="$(translate "Installs essential packages if missing")\n"
proxmox_update_msg+="$(translate "Checks for LVM and storage issues")\n"
proxmox_update_msg+="$(translate "Performs automatic cleanup after updating")\n\n"
proxmox_update_msg+="$(translate "Do you want to proceed and run the Proxmox System Update?")"
dialog --colors --backtitle "ProxMenux" --title "$(translate "Proxmox System Update")" \
--yesno "$proxmox_update_msg" 20 70
dialog_result=$?
if [[ $dialog_result -eq 0 ]]; then
bash <(curl -s "$REPO_URL/scripts/utilities/proxmox_update.sh")
if [ $? -ne 0 ]; then
return
fi
fi
;;
4) exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") ;;
*) exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") ;;
esac
done

View File

@@ -0,0 +1,894 @@
#!/bin/bash
# ==========================================================
# ProxMenux - Complete Post-Installation Script with Registration
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 06/07/2025
# ==========================================================
# Description:
#
# The script performs system optimizations including:
# - Repository configuration and system upgrades
# - Subscription banner removal and UI enhancements
# - Advanced memory management and kernel optimizations
# - Network stack tuning and security hardening
# - Storage optimizations including log2ram for SSD protection
# - System limits increases and entropy generation improvements
# - Journald and logrotate optimizations for better log management
# - Security enhancements including RPC disabling and time synchronization
# - Bash environment customization and system monitoring setup
#
# Key Features:
# - Zero-interaction automation: Runs completely unattended
# - Intelligent hardware detection: Automatically detects SSD/NVMe for log2ram
# - RAM-aware configurations: Adjusts settings based on available system memory
# - Comprehensive error handling: Robust installation with fallback mechanisms
# - Registration system: Tracks installed optimizations for easy management
# - Reboot management: Intelligently handles reboot requirements
# - Translation support: Multi-language compatible through ProxMenux framework
# - Rollback compatibility: All optimizations can be reversed using the uninstall script
#
# This script is based on the post-install script cutotomizable
# ==========================================================
# Configuration
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
VENV_PATH="/opt/googletrans-env"
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
# Global variables
OS_CODENAME="$(grep "VERSION_CODENAME=" /etc/os-release | cut -d"=" -f 2 | xargs)"
RAM_SIZE_GB=$(( $(vmstat -s | grep -i "total memory" | xargs | cut -d" " -f 1) / 1024 / 1000))
NECESSARY_REBOOT=0
SCRIPT_TITLE="Customizable post-installation optimization script"
# ==========================================================
# Tool registration system
ensure_tools_json() {
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
}
register_tool() {
local tool="$1"
local state="$2"
ensure_tools_json
jq --arg t "$tool" --argjson v "$state" '.[$t]=$v' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
}
# ==========================================================
lvm_repair_check() {
msg_info "$(translate "Checking and repairing old LVM PV headers (if needed)...")"
pvs_output=$(LC_ALL=C pvs -v 2>&1 | grep "old PV header")
if [ -z "$pvs_output" ]; then
msg_ok "$(translate "No PVs with old headers found.")"
register_tool "lvm_repair" true
return
fi
declare -A vg_map
while read -r line; do
pv=$(echo "$line" | grep -o '/dev/[^ ]*')
vg=$(pvs -o vg_name --noheadings "$pv" | awk '{print $1}')
if [ -n "$vg" ]; then
vg_map["$vg"]=1
fi
done <<< "$pvs_output"
for vg in "${!vg_map[@]}"; do
msg_warn "$(translate "Old PV header(s) found in VG $vg. Updating metadata...")"
vgck --updatemetadata "$vg"
vgchange -ay "$vg"
if [ $? -ne 0 ]; then
msg_warn "$(translate "Metadata update failed for VG $vg. Review manually.")"
else
msg_ok "$(translate "Metadata updated successfully for VG $vg")"
fi
done
msg_ok "$(translate "LVM PV headers check completed")"
}
# ==========================================================
cleanup_duplicate_repos() {
local sources_file="/etc/apt/sources.list"
local temp_file=$(mktemp)
local cleaned_count=0
declare -A seen_repos
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ "$line" =~ ^[[:space:]]*# ]] || [[ -z "$line" ]]; then
echo "$line" >> "$temp_file"
continue
fi
if [[ "$line" =~ ^deb ]]; then
read -r _ url dist components <<< "$line"
local key="${url}_${dist}"
if [[ -v "seen_repos[$key]" ]]; then
echo "# $line" >> "$temp_file"
cleaned_count=$((cleaned_count + 1))
else
echo "$line" >> "$temp_file"
seen_repos[$key]="$components"
fi
else
echo "$line" >> "$temp_file"
fi
done < "$sources_file"
mv "$temp_file" "$sources_file"
chmod 644 "$sources_file"
local pve_files=(/etc/apt/sources.list.d/*proxmox*.list /etc/apt/sources.list.d/*pve*.list)
local pve_content="deb http://download.proxmox.com/debian/pve ${OS_CODENAME} pve-no-subscription"
local pve_public_repo="/etc/apt/sources.list.d/pve-public-repo.list"
local pve_public_repo_exists=false
if [ -f "$pve_public_repo" ] && grep -q "^deb.*pve-no-subscription" "$pve_public_repo"; then
pve_public_repo_exists=true
fi
for file in "${pve_files[@]}"; do
if [ -f "$file" ] && grep -q "^deb.*pve-no-subscription" "$file"; then
if ! $pve_public_repo_exists && [[ "$file" == "$pve_public_repo" ]]; then
sed -i 's/^# *deb/deb/' "$file"
pve_public_repo_exists=true
elif [[ "$file" != "$pve_public_repo" ]]; then
sed -i 's/^deb/# deb/' "$file"
cleaned_count=$((cleaned_count + 1))
fi
fi
done
apt update
}
apt_upgrade() {
NECESSARY_REBOOT=1
if [ -f /etc/apt/sources.list.d/pve-enterprise.list ] && grep -q "^deb" /etc/apt/sources.list.d/pve-enterprise.list; then
msg_info "$(translate "Disabling enterprise Proxmox repository...")"
sed -i "s/^deb/#deb/g" /etc/apt/sources.list.d/pve-enterprise.list
msg_ok "$(translate "Enterprise Proxmox repository disabled")"
fi
if [ -f /etc/apt/sources.list.d/ceph.list ] && grep -q "^deb" /etc/apt/sources.list.d/ceph.list; then
msg_info "$(translate "Disabling enterprise Proxmox Ceph repository...")"
sed -i "s/^deb/#deb/g" /etc/apt/sources.list.d/ceph.list
msg_ok "$(translate "Enterprise Proxmox Ceph repository disabled")"
fi
if [ ! -f /etc/apt/sources.list.d/pve-public-repo.list ] || ! grep -q "pve-no-subscription" /etc/apt/sources.list.d/pve-public-repo.list; then
msg_info "$(translate "Enabling free public Proxmox repository...")"
echo "deb http://download.proxmox.com/debian/pve ${OS_CODENAME} pve-no-subscription" > /etc/apt/sources.list.d/pve-public-repo.list
msg_ok "$(translate "Free public Proxmox repository enabled")"
fi
sources_file="/etc/apt/sources.list"
need_update=false
sed -i 's|ftp.es.debian.org|deb.debian.org|g' "$sources_file"
if grep -q "^deb http://security.debian.org ${OS_CODENAME}-security main contrib" "$sources_file"; then
sed -i "s|^deb http://security.debian.org ${OS_CODENAME}-security main contrib|deb http://security.debian.org/debian-security ${OS_CODENAME}-security main contrib non-free non-free-firmware|" "$sources_file"
msg_ok "$(translate "Replaced security repository with full version")"
need_update=true
fi
if ! grep -q "deb http://security.debian.org/debian-security ${OS_CODENAME}-security" "$sources_file"; then
echo "deb http://security.debian.org/debian-security ${OS_CODENAME}-security main contrib non-free non-free-firmware" >> "$sources_file"
need_update=true
fi
if ! grep -q "deb http://deb.debian.org/debian ${OS_CODENAME} " "$sources_file"; then
echo "deb http://deb.debian.org/debian ${OS_CODENAME} main contrib non-free non-free-firmware" >> "$sources_file"
need_update=true
fi
if ! grep -q "deb http://deb.debian.org/debian ${OS_CODENAME}-updates" "$sources_file"; then
echo "deb http://deb.debian.org/debian ${OS_CODENAME}-updates main contrib non-free non-free-firmware" >> "$sources_file"
need_update=true
fi
msg_ok "$(translate "Debian repositories configured correctly")"
# ===================================================
if [ ! -f /etc/apt/apt.conf.d/no-bookworm-firmware.conf ]; then
msg_info "$(translate "Disabling non-free firmware warnings...")"
echo 'APT::Get::Update::SourceListWarnings::NonFreeFirmware "false";' > /etc/apt/apt.conf.d/no-bookworm-firmware.conf
msg_ok "$(translate "Non-free firmware warnings disabled")"
fi
msg_info "$(translate "Updating package lists...")"
if apt-get update > /dev/null 2>&1; then
msg_ok "$(translate "Package lists updated")"
else
msg_error "$(translate "Failed to update package lists")"
return 1
fi
msg_info "$(translate "Removing conflicting utilities...")"
if /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' purge ntp openntpd systemd-timesyncd > /dev/null 2>&1; then
msg_ok "$(translate "Conflicting utilities removed")"
else
msg_error "$(translate "Failed to remove conflicting utilities")"
fi
msg_info "$(translate "Performing packages upgrade...")"
apt-get install pv -y > /dev/null 2>&1
total_packages=$(apt-get -s dist-upgrade | grep "^Inst" | wc -l)
if [ "$total_packages" -eq 0 ]; then
total_packages=1
fi
msg_ok "$(translate "Packages upgrade successfull")"
tput civis
tput sc
(
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' dist-upgrade 2>&1 | \
while IFS= read -r line; do
if [[ "$line" =~ ^(Setting up|Unpacking|Preparing to unpack|Processing triggers for) ]]; then
package_name=$(echo "$line" | sed -E 's/.*(Setting up|Unpacking|Preparing to unpack|Processing triggers for) ([^ ]+).*/\2/')
[ -z "$package_name" ] && package_name="$(translate "Unknown")"
tput rc
tput ed
row=$(( $(tput lines) - 6 ))
tput cup $row 0; echo "$(translate "Installing packages...")"
tput cup $((row + 1)) 0; echo "──────────────────────────────────────────────"
tput cup $((row + 2)) 0; echo "Package: $package_name"
tput cup $((row + 3)) 0; echo "Progress: [ ] 0%"
tput cup $((row + 4)) 0; echo "──────────────────────────────────────────────"
for i in $(seq 1 10); do
progress=$((i * 10))
tput cup $((row + 3)) 9
printf "[%-50s] %3d%%" "$(printf "#%.0s" $(seq 1 $((progress/2))))" "$progress"
done
fi
done
)
if [ $? -eq 0 ]; then
tput rc
tput ed
msg_ok "$(translate "System upgrade completed")"
fi
msg_info "$(translate "Installing additional Proxmox packages...")"
if /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install zfsutils-linux proxmox-backup-restore-image chrony > /dev/null 2>&1; then
msg_ok "$(translate "Additional Proxmox packages installed")"
else
msg_error "$(translate "Failed to install additional Proxmox packages")"
fi
lvm_repair_check
cleanup_duplicate_repos
msg_ok "$(translate "Proxmox update completed")"
}
# ==========================================================
remove_subscription_banner_() {
msg_info "$(translate "Removing Proxmox subscription nag banner...")"
local JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
local GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
local APT_HOOK="/etc/apt/apt.conf.d/no-nag-script"
if [[ ! -f "$APT_HOOK" ]]; then
cat <<'EOF' > "$APT_HOOK"
DPkg::Post-Invoke { "dpkg -V proxmox-widget-toolkit | grep -q '/proxmoxlib\.js$'; if [ $? -eq 1 ]; then { echo 'Removing subscription nag from UI...'; sed -i '/.*data\.status.*{/{s/\!//;s/active/NoMoreNagging/;s/Active/NoMoreNagging/}' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js; rm -f /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz; }; fi"; };
EOF
fi
if [[ -f "$JS_FILE" ]]; then
sed -i '/.*data\.status.*{/{s/\!//;s/active/NoMoreNagging/;s/Active/NoMoreNagging/}' "$JS_FILE"
[[ -f "$GZ_FILE" ]] && rm -f "$GZ_FILE"
touch "$JS_FILE"
fi
apt --reinstall install proxmox-widget-toolkit -y > /dev/null 2>&1
msg_ok "$(translate "Subscription nag banner removed successfully")"
register_tool "subscription_banner" true
}
remove_subscription_banner() {
local JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
local GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
local APT_HOOK="/etc/apt/apt.conf.d/no-nag-script"
# Detect if already applied
if grep -q "NoMoreNagging" "$JS_FILE" 2>/dev/null && [[ -f "$APT_HOOK" ]]; then
# Already applied, just reapply silently
:
else
# Ask user
if ! whiptail --title "$(translate "Proxmox Subscription Banner")" \
--yesno "$(translate "Do you want to remove the Proxmox subscription banner from the web interface?")" 10 60; then
msg_warn "$(translate "Banner removal cancelled by user.")"
return 1
fi
fi
msg_info "$(translate "Removing Proxmox subscription nag banner...")"
if [[ ! -f "$APT_HOOK" ]]; then
cat <<'EOF' > "$APT_HOOK"
DPkg::Post-Invoke { "dpkg -V proxmox-widget-toolkit | grep -q '/proxmoxlib\.js$'; if [ $? -eq 1 ]; then { echo 'Removing subscription nag from UI...'; sed -i '/.*data\.status.*{/{s/\!//;s/active/NoMoreNagging/;s/Active/NoMoreNagging/}' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js; rm -f /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz; }; fi"; };
EOF
fi
if [[ -f "$JS_FILE" ]]; then
sed -i '/.*data\.status.*{/{s/\!//;s/active/NoMoreNagging/;s/Active/NoMoreNagging/}' "$JS_FILE"
[[ -f "$GZ_FILE" ]] && rm -f "$GZ_FILE"
touch "$JS_FILE"
fi
apt --reinstall install proxmox-widget-toolkit -y > /dev/null 2>&1
msg_ok "$(translate "Subscription nag banner removed successfully")"
register_tool "subscription_banner" true
}
# ==========================================================
configure_time_sync() {
msg_info "$(translate "Configuring system time settings...")"
this_ip=$(dig +short myip.opendns.com @resolver1.opendns.com)
if [ -z "$this_ip" ]; then
msg_warn "$(translate "Failed to obtain public IP address")"
timezone="UTC"
else
timezone=$(curl -s "https://ipapi.co/${this_ip}/timezone")
if [ -z "$timezone" ]; then
msg_warn "$(translate "Failed to determine timezone from IP address")"
timezone="UTC"
else
msg_ok "$(translate "Found timezone $timezone for IP $this_ip")"
fi
fi
msg_info "$(translate "Enabling automatic time synchronization...")"
if timedatectl set-ntp true; then
msg_ok "$(translate "Time settings configured - Timezone:") $timezone"
register_tool "time_sync" true
else
msg_error "$(translate "Failed to enable automatic time synchronization")"
fi
}
# ==========================================================
skip_apt_languages() {
msg_info "$(translate "Configuring APT to skip downloading additional languages...")"
local default_locale=""
if [ -f /etc/default/locale ]; then
default_locale=$(grep '^LANG=' /etc/default/locale | cut -d= -f2 | tr -d '"')
elif [ -f /etc/environment ]; then
default_locale=$(grep '^LANG=' /etc/environment | cut -d= -f2 | tr -d '"')
fi
default_locale="${default_locale:-en_US.UTF-8}"
local normalized_locale=$(echo "$default_locale" | tr 'A-Z' 'a-z' | sed 's/utf-8/utf8/;s/-/_/')
if ! locale -a | grep -qi "^$normalized_locale$"; then
if ! grep -qE "^${default_locale}[[:space:]]+UTF-8" /etc/locale.gen; then
echo "$default_locale UTF-8" >> /etc/locale.gen
fi
locale-gen "$default_locale" > /dev/null 2>&1
fi
echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/99-disable-translations
msg_ok "$(translate "APT configured to skip additional languages")"
register_tool "apt_languages" true
}
# ==========================================================
optimize_journald() {
msg_info "$(translate "Limiting size and optimizing journald...")"
NECESSARY_REBOOT=1
cat <<EOF > /etc/systemd/journald.conf
[Journal]
Storage=persistent
SplitMode=none
RateLimitInterval=0
RateLimitIntervalSec=0
RateLimitBurst=0
ForwardToSyslog=no
ForwardToWall=yes
Seal=no
Compress=yes
SystemMaxUse=64M
RuntimeMaxUse=60M
MaxLevelStore=warning
MaxLevelSyslog=warning
MaxLevelKMsg=warning
MaxLevelConsole=notice
MaxLevelWall=crit
EOF
systemctl restart systemd-journald.service > /dev/null 2>&1
journalctl --vacuum-size=64M --vacuum-time=1d > /dev/null 2>&1
journalctl --rotate > /dev/null 2>&1
msg_ok "$(translate "Journald optimized - Max size: 64M")"
register_tool "journald" true
}
# ==========================================================
optimize_logrotate() {
msg_info "$(translate "Optimizing logrotate configuration...")"
local logrotate_conf="/etc/logrotate.conf"
local backup_conf="${logrotate_conf}.bak"
if ! grep -q "# ProxMenux optimized configuration" "$logrotate_conf"; then
cp "$logrotate_conf" "$backup_conf"
cat <<EOF > "$logrotate_conf"
# ProxMenux optimized configuration
daily
su root adm
rotate 7
create
compress
size=10M
delaycompress
copytruncate
include /etc/logrotate.d
EOF
systemctl restart logrotate > /dev/null 2>&1
fi
msg_ok "$(translate "Logrotate optimization completed")"
register_tool "logrotate" true
}
# ==========================================================
increase_system_limits() {
msg_info "$(translate "Increasing various system limits...")"
NECESSARY_REBOOT=1
cat > /etc/sysctl.d/99-maxwatches.conf << EOF
# ProxMenux configuration
fs.inotify.max_user_watches = 1048576
fs.inotify.max_user_instances = 1048576
fs.inotify.max_queued_events = 1048576
EOF
cat > /etc/security/limits.d/99-limits.conf << EOF
# ProxMenux configuration
* soft nproc 1048576
* hard nproc 1048576
* soft nofile 1048576
* hard nofile 1048576
root soft nproc unlimited
root hard nproc unlimited
root soft nofile unlimited
root hard nofile unlimited
EOF
cat > /etc/sysctl.d/99-maxkeys.conf << EOF
# ProxMenux configuration
kernel.keys.root_maxkeys=1000000
kernel.keys.maxkeys=1000000
EOF
for file in /etc/systemd/system.conf /etc/systemd/user.conf; do
if ! grep -q "^DefaultLimitNOFILE=" "$file"; then
echo "DefaultLimitNOFILE=256000" >> "$file"
fi
done
for file in /etc/pam.d/common-session /etc/pam.d/runuser-l; do
if ! grep -q "^session required pam_limits.so" "$file"; then
echo 'session required pam_limits.so' >> "$file"
fi
done
if ! grep -q "ulimit -n 256000" /root/.profile; then
echo "ulimit -n 256000" >> /root/.profile
fi
cat > /etc/sysctl.d/99-swap.conf << EOF
# ProxMenux configuration
vm.swappiness = 10
vm.vfs_cache_pressure = 100
EOF
cat > /etc/sysctl.d/99-fs.conf << EOF
# ProxMenux configuration
fs.nr_open = 12000000
fs.file-max = 9223372036854775807
fs.aio-max-nr = 1048576
EOF
msg_ok "$(translate "System limits increase completed.")"
register_tool "system_limits" true
}
# ==========================================================
configure_entropy() {
msg_info "$(translate "Configuring entropy generation to prevent slowdowns...")"
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install haveged > /dev/null 2>&1
cat <<EOF > /etc/default/haveged
# -w sets low entropy watermark (in bits)
DAEMON_ARGS="-w 1024"
EOF
systemctl daemon-reload > /dev/null 2>&1
systemctl enable haveged > /dev/null 2>&1
msg_ok "$(translate "Entropy generation configuration completed")"
register_tool "entropy" true
}
# ==========================================================
optimize_memory_settings() {
msg_info "$(translate "Optimizing memory settings...")"
NECESSARY_REBOOT=1
cat <<EOF > /etc/sysctl.d/99-memory.conf
# Balanced Memory Optimization
vm.swappiness = 10
vm.dirty_ratio = 15
vm.dirty_background_ratio = 5
vm.overcommit_memory = 1
vm.max_map_count = 65530
EOF
if [ -f /proc/sys/vm/compaction_proactiveness ]; then
echo "vm.compaction_proactiveness = 20" >> /etc/sysctl.d/99-memory.conf
fi
msg_ok "$(translate "Memory optimization completed.")"
register_tool "memory_settings" true
}
# ==========================================================
configure_kernel_panic() {
msg_info "$(translate "Configuring kernel panic behavior")"
NECESSARY_REBOOT=1
cat <<EOF > /etc/sysctl.d/99-kernelpanic.conf
# Enable restart on kernel panic, kernel oops and hardlockup
kernel.core_pattern = /var/crash/core.%t.%p
kernel.panic = 10
kernel.panic_on_oops = 1
kernel.hardlockup_panic = 1
EOF
msg_ok "$(translate "Kernel panic behavior configuration completed")"
register_tool "kernel_panic" true
}
# ==========================================================
force_apt_ipv4() {
msg_info "$(translate "Configuring APT to use IPv4...")"
echo 'Acquire::ForceIPv4 "true";' > /etc/apt/apt.conf.d/99-force-ipv4
msg_ok "$(translate "APT IPv4 configuration completed")"
register_tool "apt_ipv4" true
}
# ==========================================================
apply_network_optimizations() {
msg_info "$(translate "Optimizing network settings...")"
NECESSARY_REBOOT=1
cat <<EOF > /etc/sysctl.d/99-network.conf
net.core.netdev_max_backlog=8192
net.core.optmem_max=8192
net.core.rmem_max=16777216
net.core.somaxconn=8151
net.core.wmem_max=16777216
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.all.log_martians = 0
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.conf.default.log_martians = 0
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.default.secure_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.ip_local_port_range=1024 65535
net.ipv4.tcp_base_mss = 1024
net.ipv4.tcp_challenge_ack_limit = 999999999
net.ipv4.tcp_fin_timeout=10
net.ipv4.tcp_keepalive_intvl=30
net.ipv4.tcp_keepalive_probes=3
net.ipv4.tcp_keepalive_time=240
net.ipv4.tcp_limit_output_bytes=65536
net.ipv4.tcp_max_syn_backlog=8192
net.ipv4.tcp_max_tw_buckets = 1440000
net.ipv4.tcp_mtu_probing = 1
net.ipv4.tcp_rfc1337=1
net.ipv4.tcp_rmem=8192 87380 16777216
net.ipv4.tcp_sack=1
net.ipv4.tcp_slow_start_after_idle=0
net.ipv4.tcp_syn_retries=3
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_tw_recycle = 0
net.ipv4.tcp_tw_reuse = 0
net.ipv4.tcp_wmem=8192 65536 16777216
net.netfilter.nf_conntrack_generic_timeout = 60
net.netfilter.nf_conntrack_helper=0
net.netfilter.nf_conntrack_max = 524288
net.netfilter.nf_conntrack_tcp_timeout_established = 28800
net.unix.max_dgram_qlen = 4096
EOF
sysctl --system > /dev/null 2>&1
local interfaces_file="/etc/network/interfaces"
if ! grep -q 'source /etc/network/interfaces.d/*' "$interfaces_file"; then
echo "source /etc/network/interfaces.d/*" >> "$interfaces_file"
fi
msg_ok "$(translate "Network optimization completed")"
register_tool "network_optimization" true
}
# ==========================================================
disable_rpc() {
msg_info "$(translate "Disabling portmapper/rpcbind for security...")"
systemctl disable rpcbind > /dev/null 2>&1
systemctl stop rpcbind > /dev/null 2>&1
msg_ok "$(translate "portmapper/rpcbind has been disabled and removed")"
register_tool "disable_rpc" true
}
# ==========================================================
customize_bashrc() {
msg_info "$(translate "Customizing bashrc for root user...")"
local bashrc="/root/.bashrc"
local bash_profile="/root/.bash_profile"
if [ ! -f "${bashrc}.bak" ]; then
cp "$bashrc" "${bashrc}.bak"
fi
cat >> "$bashrc" << 'EOF'
# ProxMenux customizations
export HISTTIMEFORMAT="%d/%m/%y %T "
export PS1="\[\e[31m\][\[\e[m\]\[\e[38;5;172m\]\u\[\e[m\]@\[\e[38;5;153m\]\h\[\e[m\] \[\e[38;5;214m\]\W\[\e[m\]\[\e[31m\]]\[\e[m\]\\$ "
alias l='ls -CF'
alias la='ls -A'
alias ll='ls -alF'
alias ls='ls --color=auto'
alias grep='grep --color=auto'
alias fgrep='fgrep --color=auto'
alias egrep='egrep --color=auto'
source /etc/profile.d/bash_completion.sh
EOF
if ! grep -q "source /root/.bashrc" "$bash_profile"; then
echo "source /root/.bashrc" >> "$bash_profile"
fi
msg_ok "$(translate "Bashrc customization completed")"
register_tool "bashrc_custom" true
}
# ==========================================================
install_log2ram_auto() {
msg_info "$(translate "Checking if system disk is SSD or M.2...")"
ROOT_PART=$(lsblk -no NAME,MOUNTPOINT | grep ' /$' | awk '{print $1}')
SYSTEM_DISK=$(lsblk -no PKNAME /dev/$ROOT_PART 2>/dev/null)
SYSTEM_DISK=${SYSTEM_DISK:-sda}
if [[ "$SYSTEM_DISK" == nvme* || "$(cat /sys/block/$SYSTEM_DISK/queue/rotational 2>/dev/null)" == "0" ]]; then
msg_ok "$(translate "System disk ($SYSTEM_DISK) is SSD or M.2. Proceeding with Log2RAM setup.")"
else
msg_warn "$(translate "System disk ($SYSTEM_DISK) is not SSD/M.2. Skipping Log2RAM installation.")"
return 0
fi
# Clean up previous state
rm -rf /tmp/log2ram
rm -f /etc/systemd/system/log2ram*
rm -f /etc/systemd/system/log2ram-daily.*
rm -f /etc/cron.d/log2ram*
rm -f /usr/sbin/log2ram
rm -f /etc/log2ram.conf
rm -f /usr/local/bin/log2ram-check.sh
rm -rf /var/log.hdd
systemctl daemon-reexec >/dev/null 2>&1
systemctl daemon-reload >/dev/null 2>&1
msg_info "$(translate "Installing log2ram from GitHub...")"
if ! command -v git >/dev/null 2>&1; then
apt-get update -qq >/dev/null 2>&1
apt-get install -y git >/dev/null 2>&1
fi
git clone https://github.com/azlux/log2ram.git /tmp/log2ram >/dev/null 2>>/tmp/log2ram_install.log
cd /tmp/log2ram || return 1
bash install.sh >>/tmp/log2ram_install.log 2>&1
if [[ -f /etc/log2ram.conf ]] && systemctl list-units --all | grep -q log2ram; then
msg_ok "$(translate "Log2RAM installed successfully")"
else
msg_error "$(translate "Failed to install Log2RAM. See /tmp/log2ram_install.log")"
return 1
fi
# Detect RAM
RAM_SIZE_GB=$(free -g | awk '/^Mem:/{print $2}')
[[ -z "$RAM_SIZE_GB" || "$RAM_SIZE_GB" -eq 0 ]] && RAM_SIZE_GB=4
if (( RAM_SIZE_GB <= 8 )); then
LOG2RAM_SIZE="128M"
CRON_HOURS=1
elif (( RAM_SIZE_GB <= 16 )); then
LOG2RAM_SIZE="256M"
CRON_HOURS=3
else
LOG2RAM_SIZE="512M"
CRON_HOURS=6
fi
msg_ok "$(translate "Detected RAM:") $RAM_SIZE_GB GB — $(translate "Log2RAM size set to:") $LOG2RAM_SIZE"
sed -i "s/^SIZE=.*/SIZE=$LOG2RAM_SIZE/" /etc/log2ram.conf
rm -f /etc/cron.hourly/log2ram
echo "0 */$CRON_HOURS * * * root /usr/sbin/log2ram write" > /etc/cron.d/log2ram
msg_ok "$(translate "log2ram write scheduled every") $CRON_HOURS $(translate "hour(s)")"
cat << 'EOF' > /usr/local/bin/log2ram-check.sh
#!/bin/bash
CONF_FILE="/etc/log2ram.conf"
LIMIT_KB=$(grep '^SIZE=' "$CONF_FILE" | cut -d'=' -f2 | tr -d 'M')000
USED_KB=$(df /var/log --output=used | tail -1)
THRESHOLD=$(( LIMIT_KB * 90 / 100 ))
if (( USED_KB > THRESHOLD )); then
/usr/sbin/log2ram write
fi
EOF
chmod +x /usr/local/bin/log2ram-check.sh
echo "*/5 * * * * root /usr/local/bin/log2ram-check.sh" > /etc/cron.d/log2ram-auto-sync
msg_ok "$(translate "Auto-sync enabled when /var/log exceeds 90% of") $LOG2RAM_SIZE"
register_tool "log2ram" true
}
# ==========================================================
run_complete_optimization() {
clear
show_proxmenux_logo
msg_title "$(translate "ProxMenux Optimization Post-Installation")"
ensure_tools_json
apt_upgrade
remove_subscription_banner
configure_time_sync
skip_apt_languages
optimize_journald
optimize_logrotate
increase_system_limits
configure_entropy
optimize_memory_settings
configure_kernel_panic
force_apt_ipv4
apply_network_optimizations
disable_rpc
customize_bashrc
install_log2ram_auto
echo -e
msg_success "$(translate "Complete post-installation optimization finished!")"
if [[ "$NECESSARY_REBOOT" -eq 1 ]]; then
whiptail --title "Reboot Required" \
--yesno "$(translate "Some changes require a reboot to take effect. Do you want to restart now?")" 10 60
if [[ $? -eq 0 ]]; then
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
apt-get -y autoremove >/dev/null 2>&1
apt-get -y autoclean >/dev/null 2>&1
msg_ok "$(translate "Cleanup finished")"
msg_success "$(translate "Press Enter to continue...")"
read -r
msg_warn "$(translate "Rebooting the system...")"
reboot
else
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
apt-get -y autoremove >/dev/null 2>&1
apt-get -y autoclean >/dev/null 2>&1
msg_ok "$(translate "Cleanup finished")"
msg_info2 "$(translate "You can reboot later manually.")"
msg_success "$(translate "Press Enter to continue...")"
read -r
exit 0
fi
fi
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
run_complete_optimization
fi

View File

@@ -6,8 +6,8 @@
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.2
# Last Updated: 11/03/2025
# Version : 1.3
# Last Updated: 30/06/2025
# ==========================================================
# Description:
# This script automates post-installation configurations and optimizations
@@ -68,14 +68,27 @@ RAM_SIZE_GB=$(( $(vmstat -s | grep -i "total memory" | xargs | cut -d" " -f 1) /
NECESSARY_REBOOT=0
SCRIPT_TITLE="Customizable post-installation optimization script"
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
ensure_tools_json() {
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
}
register_tool() {
local tool="$1"
local state="$2"
ensure_tools_json
jq --arg t "$tool" --argjson v "$state" '.[$t]=$v' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
}
# ==========================================================
enable_kexec() {
msg_info2 "$(translate "Configuring kexec for quick reboots...")"
NECESSARY_REBOOT=1
register_tool "kexec" true
# Set default answers for debconf
echo "kexec-tools kexec-tools/load_kexec boolean false" | debconf-set-selections > /dev/null 2>&1
@@ -279,7 +292,7 @@ apt_upgrade() {
progress=$((i * 10))
tput cup $((row + 3)) 9
printf "[%-50s] %3d%%" "$(printf "#%.0s" $(seq 1 $((progress/2))))" "$progress"
sleep 0.2
done
fi
done
@@ -292,51 +305,7 @@ apt_upgrade() {
fi
msg_info "$(translate "Updating PVE application manager, patience...")"
total_steps=$(pveam update 2>&1 | grep -E "^(Downloading|Importing)" | wc -l)
[ $total_steps -eq 0 ] && total_steps=1
tput sc
(
pveam update 2>&1 | while IFS= read -r line; do
if [[ $line == "Downloading"* ]] || [[ $line == "Importing"* ]]; then
file_name=$(echo "$line" | sed -E 's/.* (Downloading|Importing) ([^ ]+).*/\2/')
[ -z "$file_name" ] && file_name="$(translate "Unknown")"
tput rc
tput ed
row=$(( $(tput lines) - 6 ))
tput cup $row 0; echo "$(translate "Updating PVE application manager...")"
tput cup $((row + 1)) 0; echo "──────────────────────────────────────────────"
tput cup $((row + 2)) 0; echo "Downloading: $file_name"
tput cup $((row + 3)) 0; echo "Progress: [ ] 0%"
tput cup $((row + 4)) 0; echo "──────────────────────────────────────────────"
for i in $(seq 1 10); do
progress=$((i * 10))
tput cup $((row + 3)) 9
printf "[%-50s] %3d%%" "$(printf "#%.0s" $(seq 1 $((progress/2))))" "$progress"
sleep 0.2
done
fi
done
)
if [ $? -eq 0 ]; then
tput rc
tput ed
msg_ok "$(translate "PVE application manager updated")"
fi
tput cnorm
# Install additional Proxmox packages
@@ -591,7 +560,7 @@ fs.aio-max-nr = 1048576"
skip_apt_languages() {
skip_apt_languages_() {
msg_info2 "$(translate "Configuring APT to skip downloading additional languages")"
local config_file="/etc/apt/apt.conf.d/99-disable-translations"
@@ -612,6 +581,55 @@ skip_apt_languages() {
skip_apt_languages() {
msg_info2 "$(translate "Configuring APT to skip downloading additional languages")"
# 1. Detect locale
local default_locale=""
if [ -f /etc/default/locale ]; then
default_locale=$(grep '^LANG=' /etc/default/locale | cut -d= -f2 | tr -d '"')
elif [ -f /etc/environment ]; then
default_locale=$(grep '^LANG=' /etc/environment | cut -d= -f2 | tr -d '"')
fi
# Fallback
default_locale="${default_locale:-en_US.UTF-8}"
# Normalize for comparison (en_US.UTF-8 → en_US.utf8)
local normalized_locale
normalized_locale=$(echo "$default_locale" | tr 'A-Z' 'a-z' | sed 's/utf-8/utf8/;s/-/_/')
# 2. Only generate if missing
if ! locale -a | grep -qi "^$normalized_locale$"; then
# Only add to locale.gen if missing
if ! grep -qE "^${default_locale}[[:space:]]+UTF-8" /etc/locale.gen; then
echo "$default_locale UTF-8" >> /etc/locale.gen
fi
msg_info "$(translate "Generating missing locale:") $default_locale"
locale-gen "$default_locale"
msg_ok "$(translate "Locale generated")"
fi
# 3. Set APT to skip language downloads
local config_file="/etc/apt/apt.conf.d/99-disable-translations"
local config_content='Acquire::Languages "none";'
msg_info "$(translate "Setting APT language configuration...")"
if [ -f "$config_file" ] && grep -Fxq "$config_content" "$config_file"; then
msg_ok "$(translate "APT language configuration already set")"
else
echo "$config_content" > "$config_file"
msg_ok "$(translate "APT language configuration updated")"
fi
msg_success "$(translate "APT configured to skip downloading additional languages")"
}
# ==========================================================
@@ -665,9 +683,200 @@ configure_time_sync() {
install_system_utils() {
command_exists() {
command -v "$1" >/dev/null 2>&1
}
install_single_package() {
local package="$1"
local command_name="${2:-$package}"
local description="$3"
msg_info "$(translate "Installing") $package ($description)..."
local install_success=false
if command_exists apt; then
if apt update >/dev/null 2>&1 && apt install -y "$package" >/dev/null 2>&1; then
install_success=true
fi
elif command_exists yum; then
if yum install -y "$package" >/dev/null 2>&1; then
install_success=true
fi
elif command_exists dnf; then
if dnf install -y "$package" >/dev/null 2>&1; then
install_success=true
fi
elif command_exists pacman; then
if pacman -S --noconfirm "$package" >/dev/null 2>&1; then
install_success=true
fi
elif command_exists zypper; then
if zypper install -y "$package" >/dev/null 2>&1; then
install_success=true
fi
else
cleanup
msg_error "$(translate "No compatible package manager detected")"
return 1
fi
cleanup
if [ "$install_success" = true ]; then
hash -r 2>/dev/null
sleep 1
if command_exists "$command_name"; then
msg_ok "$package $(translate "installed correctly and available")"
return 0
else
msg_warn "$package $(translate "installed but command not immediately available")"
msg_info2 "$(translate "May need to restart terminal")"
return 2
fi
else
msg_error "$(translate "Error installing") $package"
return 1
fi
}
show_utilities_selection() {
local utilities=(
"axel" "$(translate "Download accelerator")" "OFF"
"dos2unix" "$(translate "Convert DOS/Unix text files")" "OFF"
"grc" "$(translate "Generic log/command colorizer")" "OFF"
"htop" "$(translate "Interactive process viewer")" "OFF"
"btop" "$(translate "Modern resource monitor")" "OFF"
"iftop" "$(translate "Real-time network usage")" "OFF"
"iotop" "$(translate "Monitor disk I/O usage")" "OFF"
"iperf3" "$(translate "Network performance testing")" "OFF"
"ipset" "$(translate "Manage IP sets")" "OFF"
"iptraf-ng" "$(translate "Network monitoring tool")" "OFF"
"mlocate" "$(translate "Locate files quickly")" "OFF"
"msr-tools" "$(translate "Access CPU MSRs")" "OFF"
"net-tools" "$(translate "Legacy networking tools")" "OFF"
"sshpass" "$(translate "Non-interactive SSH login")" "OFF"
"tmux" "$(translate "Terminal multiplexer")" "OFF"
"unzip" "$(translate "Extract ZIP files")" "OFF"
"zip" "$(translate "Create ZIP files")" "OFF"
"libguestfs-tools" "$(translate "VM disk utilities")" "OFF"
"aria2" "$(translate "Multi-source downloader")" "OFF"
"cabextract" "$(translate "Extract CAB files")" "OFF"
"wimtools" "$(translate "Manage WIM images")" "OFF"
"genisoimage" "$(translate "Create ISO images")" "OFF"
"chntpw" "$(translate "Edit Windows registry/passwords")" "OFF"
)
local selected
selected=$(dialog --clear --backtitle "ProxMenu - $(translate "System Utilities")" \
--title "$(translate "Select utilities to install")" \
--checklist "$(translate "Use SPACE to select/deselect, ENTER to confirm")" \
20 70 12 "${utilities[@]}" 2>&1 >/dev/tty)
echo "$selected"
}
install_selected_utilities() {
local selected="$1"
if [ -z "$selected" ]; then
dialog --clear --backtitle "ProxMenu" \
--title "$(translate "No Selection")" \
--msgbox "$(translate "No utilities were selected")" 8 40
return
fi
clear
show_proxmenux_logo
msg_title "$SCRIPT_TITLE"
msg_info2 "$(translate "Installing selected utilities")"
local failed=0
local success=0
local warning=0
local selected_array
IFS=' ' read -ra selected_array <<< "$selected"
declare -A package_to_command=(
["mlocate"]="locate"
["msr-tools"]="rdmsr"
["net-tools"]="netstat"
["libguestfs-tools"]="virt-filesystems"
["aria2"]="aria2c"
["wimtools"]="wimlib-imagex"
)
for util in "${selected_array[@]}"; do
util=$(echo "$util" | tr -d '"')
local verify_command="${package_to_command[$util]:-$util}"
install_single_package "$util" "$verify_command" "$util"
local install_result=$?
case $install_result in
0)
success=$((success + 1))
;;
1)
failed=$((failed + 1))
;;
2)
warning=$((warning + 1))
;;
esac
done
if [ -f ~/.bashrc ]; then
source ~/.bashrc >/dev/null 2>&1
fi
hash -r 2>/dev/null
echo
msg_info2 "$(translate "Installation summary"):"
msg_ok "$(translate "Successful"): $success"
msg_success "$(translate "Common system utilities installation completed")"
}
local selected_utilities
selected_utilities=$(show_utilities_selection)
if [ -n "$selected_utilities" ]; then
install_selected_utilities "$selected_utilities"
fi
}
install_system_utils_() {
msg_info2 "$(translate "Installing common system utilities...")"
if [[ "$LANGUAGE" != "en" ]]; then
@@ -742,7 +951,6 @@ packages_list=(
progress=$((i * 10))
tput cup $((row + 3)) 9
printf "[%-50s] %3d%%" "$(printf "#%.0s" $(seq 1 $((progress/2))))" "$progress"
sleep 0.2
done
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install "$package" > /dev/null 2>&1
@@ -1718,7 +1926,7 @@ configure_ksmtuned() {
enable_vfio_iommu() {
enable_vfio_iommu_() {
msg_info2 "$(translate "Enabling IOMMU and configuring VFIO for PCI passthrough...")"
NECESSARY_REBOOT=1
@@ -1818,6 +2026,92 @@ enable_vfio_iommu() {
enable_vfio_iommu() {
msg_info2 "$(translate "Enabling IOMMU and configuring VFIO for PCI passthrough...")"
NECESSARY_REBOOT=1
# Detect if system uses ZFS/systemd-boot (Proxmox)
local uses_zfs=false
local cmdline_file="/etc/kernel/cmdline"
if [[ -f "$cmdline_file" ]] && grep -qE 'root=ZFS=|root=ZFS/' "$cmdline_file"; then
uses_zfs=true
fi
# Enable IOMMU
local cpu_info=$(cat /proc/cpuinfo)
local grub_file="/etc/default/grub"
local iommu_param=""
local additional_params="pcie_acs_override=downstream,multifunction nofb nomodeset video=vesafb:off,efifb:off"
if [[ "$cpu_info" == *"GenuineIntel"* ]]; then
msg_info "$(translate "Detected Intel CPU")"
iommu_param="intel_iommu=on"
elif [[ "$cpu_info" == *"AuthenticAMD"* ]]; then
msg_info "$(translate "Detected AMD CPU")"
iommu_param="amd_iommu=on"
else
msg_warning "$(translate "Unknown CPU type. IOMMU might not be properly enabled.")"
return 1
fi
if [[ "$uses_zfs" == true ]]; then
# --- SYSTEMD-BOOT: /etc/kernel/cmdline ---
if grep -q "$iommu_param" "$cmdline_file"; then
msg_ok "$(translate "IOMMU already configured in /etc/kernel/cmdline")"
else
cp "$cmdline_file" "${cmdline_file}.bak"
# sed -i "s|\"$| $iommu_param iommu=pt|" "$cmdline_file"
sed -i "s|\s*$| $iommu_param iommu=pt|" "$cmdline_file"
msg_ok "$(translate "IOMMU parameters added to /etc/kernel/cmdline")"
fi
else
# --- GRUB ---
if grep -q "$iommu_param" "$grub_file"; then
msg_ok "$(translate "IOMMU already enabled in GRUB configuration")"
else
cp "$grub_file" "${grub_file}.bak"
sed -i "/GRUB_CMDLINE_LINUX_DEFAULT=/ s|\"$| $iommu_param iommu=pt\"|" "$grub_file"
msg_ok "$(translate "IOMMU enabled in GRUB configuration")"
fi
fi
# Configure VFIO modules (avoid duplicates)
local modules_file="/etc/modules"
msg_info "$(translate "Checking VFIO modules...")"
local vfio_modules=("vfio" "vfio_iommu_type1" "vfio_pci" "vfio_virqfd")
for module in "${vfio_modules[@]}"; do
grep -q "^$module" "$modules_file" || echo "$module" >> "$modules_file"
done
msg_ok "$(translate "VFIO modules configured.")"
# Blacklist conflicting drivers (avoid duplicates)
local blacklist_file="/etc/modprobe.d/blacklist.conf"
msg_info "$(translate "Checking conflicting drivers blacklist...")"
touch "$blacklist_file"
local blacklist_drivers=("nouveau" "lbm-nouveau" "amdgpu" "radeon" "nvidia" "nvidiafb")
for driver in "${blacklist_drivers[@]}"; do
grep -q "^blacklist $driver" "$blacklist_file" || echo "blacklist $driver" >> "$blacklist_file"
done
if ! grep -q "options nouveau modeset=0" "$blacklist_file"; then
echo "options nouveau modeset=0" >> "$blacklist_file"
fi
msg_ok "$(translate "Conflicting drivers blacklisted successfully.")"
# Propagate the settings
msg_info "$(translate "Updating initramfs, GRUB, and EFI boot, patience...")"
update-initramfs -u -k all > /dev/null 2>&1
if [[ "$uses_zfs" == true ]]; then
proxmox-boot-tool refresh > /dev/null 2>&1
else
update-grub > /dev/null 2>&1
fi
msg_success "$(translate "IOMMU and VFIO setup completed")"
}
# ==========================================================
@@ -1960,8 +2254,53 @@ EOF
remove_subscription_banner() {
msg_info2 "$(translate "Removing Proxmox subscription nag banner...")"
local JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
local GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
local APT_HOOK="/etc/apt/apt.conf.d/no-nag-script"
if [[ ! -f "$APT_HOOK" ]]; then
cat <<'EOF' > "$APT_HOOK"
DPkg::Post-Invoke { "dpkg -V proxmox-widget-toolkit | grep -q '/proxmoxlib\.js$'; if [ $? -eq 1 ]; then { echo 'Removing subscription nag from UI...'; sed -i '/.*data\.status.*{/{s/\!//;s/active/NoMoreNagging/;s/Active/NoMoreNagging/}' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js; rm -f /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz; }; fi"; };
EOF
msg_ok "$(translate "APT hook for nag removal created")"
else
msg_info "$(translate "APT hook for nag removal already exists")"
fi
if [[ -f "$JS_FILE" ]]; then
sed -i '/.*data\.status.*{/{s/\!//;s/active/NoMoreNagging/;s/Active/NoMoreNagging/}' "$JS_FILE"
if [[ -f "$GZ_FILE" ]]; then
rm -f "$GZ_FILE"
msg_info "$(translate "Deleted proxmoxlib.js.gz to force browser refresh")"
fi
touch "$JS_FILE"
msg_ok "$(translate "Patched proxmoxlib.js (banner should disappear after browser refresh)")"
else
msg_error "$(translate "proxmoxlib.js not found. Cannot patch subscription banner.")"
return 1
fi
apt --reinstall install proxmox-widget-toolkit -y > /dev/null 2>&1
msg_success "$(translate "Subscription nag banner removed.")"
}
remove_subscription_banner_() {
msg_info2 "$(translate "Checking Proxmox subscription banner and nag status...")"
local proxmox_js="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
@@ -2167,6 +2506,7 @@ enable_ha() {
configure_fastfetch() {
msg_info2 "$(translate "Installing and configuring Fastfetch...")"
register_tool "fastfetch" true
# Define paths
local fastfetch_bin="/usr/local/bin/fastfetch"
@@ -2476,6 +2816,131 @@ EOF
update_pve_appliance_manager() {
msg_info "$(translate "Updating PVE application manager...")"
if pveam update > /dev/null 2>&1; then
msg_ok "$(translate "PVE application manager updated")"
else
msg_warn "$(translate "No updates or failed to fetch templates")"
fi
}
# ==========================================================
configure_log2ram() {
msg_info2 "$(translate "Preparing Log2RAM configuration")"
sleep 2
RAM_SIZE_GB=$(free -g | awk '/^Mem:/{print $2}')
[[ -z "$RAM_SIZE_GB" || "$RAM_SIZE_GB" -eq 0 ]] && RAM_SIZE_GB=4
if (( RAM_SIZE_GB <= 8 )); then
DEFAULT_SIZE="128"
DEFAULT_HOURS="1"
elif (( RAM_SIZE_GB <= 16 )); then
DEFAULT_SIZE="256"
DEFAULT_HOURS="3"
else
DEFAULT_SIZE="512"
DEFAULT_HOURS="6"
fi
USER_SIZE=$(whiptail --title "Log2RAM" --inputbox "$(translate "Enter the maximum size (in MB) to allocate for /var/log in RAM (e.g. 128, 256, 512):")\n\n$(translate "Recommended for $RAM_SIZE_GB GB RAM:") ${DEFAULT_SIZE}M" 12 70 "$DEFAULT_SIZE" 3>&1 1>&2 2>&3) || return 0
LOG2RAM_SIZE="${USER_SIZE}M"
CRON_HOURS=$(whiptail --title "Log2RAM" --radiolist "$(translate "Select the sync interval (in hours):")\n\n$(translate "Suggested interval: every $DEFAULT_HOURS hour(s)")" 15 70 5 \
"1" "$(translate "Every hour")" OFF \
"3" "$(translate "Every 3 hours")" OFF \
"6" "$(translate "Every 6 hours")" OFF \
"12" "$(translate "Every 12 hours")" OFF \
3>&1 1>&2 2>&3) || return 0
# Activar auto-sync si se pasa del 90%
if whiptail --title "Log2RAM" --yesno "$(translate "Enable auto-sync if /var/log exceeds 90% of its size?")" 10 60; then
ENABLE_AUTOSYNC=true
else
ENABLE_AUTOSYNC=false
fi
# Instalación
msg_info "$(translate "Installing Log2RAM from GitHub...")"
rm -rf /tmp/log2ram
# Ensure git is available
if ! command -v git >/dev/null 2>&1; then
msg_info "$(translate "Installing required package: git")"
apt-get update -qq >/dev/null 2>&1
apt-get install -y git >/dev/null 2>&1
fi
git clone https://github.com/azlux/log2ram.git /tmp/log2ram >/dev/null 2>&1
cd /tmp/log2ram || return 1
bash install.sh >/dev/null 2>&1
if [[ -f /etc/log2ram.conf ]] && systemctl list-units --all | grep -q log2ram; then
msg_ok "$(translate "Log2RAM installed successfully")"
else
msg_error "$(translate "Failed to install Log2RAM.")"
return 1
fi
# Aplicar configuración
sed -i "s/^SIZE=.*/SIZE=$LOG2RAM_SIZE/" /etc/log2ram.conf
rm -f /etc/cron.hourly/log2ram
echo "0 */$CRON_HOURS * * * root /usr/sbin/log2ram write" > /etc/cron.d/log2ram
msg_ok "$(translate "Log2RAM write scheduled every") $CRON_HOURS $(translate "hour(s)")"
# Auto-sync
if [[ "$ENABLE_AUTOSYNC" == true ]]; then
cat << 'EOF' > /usr/local/bin/log2ram-check.sh
#!/bin/bash
CONF_FILE="/etc/log2ram.conf"
LIMIT_KB=$(grep '^SIZE=' "$CONF_FILE" | cut -d'=' -f2 | tr -d 'M')000
USED_KB=$(df /var/log --output=used | tail -1)
THRESHOLD=$(( LIMIT_KB * 90 / 100 ))
if (( USED_KB > THRESHOLD )); then
/usr/sbin/log2ram write
fi
EOF
chmod +x /usr/local/bin/log2ram-check.sh
echo "*/5 * * * * root /usr/local/bin/log2ram-check.sh" > /etc/cron.d/log2ram-auto-sync
msg_ok "$(translate "Auto-sync enabled when /var/log exceeds 90% of") $LOG2RAM_SIZE"
else
rm -f /usr/local/bin/log2ram-check.sh /etc/cron.d/log2ram-auto-sync
msg_info2 "$(translate "Auto-sync was not enabled")"
fi
msg_success "$(translate "Log2RAM installation and configuration completed successfully.")"
register_tool "log2ram" true
}
# ==========================================================
# ==========================================================
# Auxiliary help functions
# ==========================================================
@@ -2608,20 +3073,17 @@ lvm_repair_check() {
# Main menu function
main_menu() {
local HEADER=$(printf " %-56s %10s" "$(translate "Description")" "Category")
local HEADER
if [[ "$LANGUAGE" == "es" ]]; then
HEADER="Seleccione las opciones a configurar:\n\n Descripción | Categoría"
else
HEADER="$(translate "Choose options to configure:")\n\n Description | Category"
fi
declare -A category_order=(
["Basic Settings"]=1
["System"]=2
["Hardware"]=3
["Virtualization"]=4
["Network"]=5
["Storage"]=6
["Security"]=7
["Customization"]=8
["Monitoring"]=9
["Performance"]=10
["Optional"]=11
["Basic Settings"]=1 ["System"]=2 ["Hardware"]=3 ["Virtualization"]=4
["Network"]=5 ["Storage"]=6 ["Security"]=7 ["Customization"]=8
["Monitoring"]=9 ["Performance"]=10 ["Optional"]=11
)
local options=(
@@ -2657,10 +3119,12 @@ main_menu() {
"Monitoring|Install OVH Real Time Monitoring|OVHRTM"
"Performance|Use pigz for faster gzip compression|PIGZ"
"Optional|Install and configure Fastfetch|FASTFETCH"
"Optional|Update Proxmox VE Appliance Manager|PVEAM"
"Optional|Add latest Ceph support|CEPH"
"Optional|Add Proxmox testing repository|REPOTEST"
"Optional|Enable High Availability services|ENABLE_HA"
"Optional|Install Figurine|FIGURINE"
"Optional|Install and configure Log2RAM|LOG2RAM"
)
IFS=$'\n' sorted_options=($(for option in "${options[@]}"; do
@@ -2669,79 +3133,111 @@ main_menu() {
done | sort -n | cut -d'|' -f2-))
unset IFS
local total_width=65
local max_desc_width=50
local category_width=15
local category_position=$((total_width - category_width))
local max_desc_length=0
local temp_descriptions=()
local menu_items=()
for option in "${sorted_options[@]}"; do
IFS='|' read -r category description function_name <<< "$option"
local desc_translated="$(translate "$description")"
temp_descriptions+=("$desc_translated")
local desc_length=${#desc_translated}
if [ $desc_length -gt $max_desc_length ]; then
max_desc_length=$desc_length
fi
done
if [ $max_desc_length -gt 50 ]; then
max_desc_length=50
fi
local checklist_items=()
local i=1
local desc_index=0
local previous_category=""
for option in "${sorted_options[@]}"; do
IFS='|' read -r category description function_name <<< "$option"
translated_description="$(translate "$description")"
local max_cut=$((category_position - 3))
[[ "$max_cut" -lt 10 ]] && max_cut=10
if [[ ${#translated_description} -gt $max_cut ]]; then
translated_description="${translated_description:0:$((max_cut - 3))}..."
fi
if [[ "$category" != "$previous_category" && "$category" == "Optional" && -n "$previous_category" ]]; then
menu_items+=("" "================================================================" "")
checklist_items+=("" "==============================================================" "")
fi
local desc_translated="${temp_descriptions[$desc_index]}"
desc_index=$((desc_index + 1))
if [ ${#desc_translated} -gt $max_desc_length ]; then
desc_translated="${desc_translated:0:$((max_desc_length-3))}..."
fi
local line="$translated_description"
local spaces_needed=$((category_position - ${#translated_description}))
for ((j = 0; j < spaces_needed; j++)); do
line+=" "
local spaces_needed=$((max_desc_length - ${#desc_translated}))
local padding=""
for ((j=0; j<spaces_needed; j++)); do
padding+=" "
done
line+="$category"
menu_items+=("$i" "$line" "OFF")
local line="${desc_translated}${padding} | ${category}"
checklist_items+=("$i" "$line" "off")
i=$((i + 1))
previous_category="$category"
done
cleanup
exec 3>&1
selected_indices=$(dialog --clear \
--backtitle "ProxMenux" \
--title "$(translate "Post-Installation Options")" \
--checklist "$HEADER" 22 80 15 \
"${checklist_items[@]}" \
2>&1 1>&3)
local selected_indices=$(whiptail --title "$(translate "ProxMenux Custom Script for Post-Installation")" \
--checklist --separate-output \
"\n$HEADER\n\n$(translate "Choose options to configure:")\n$(translate "Use [SPACE] to select/deselect and [ENTER] to confirm:")" \
20 82 12 \
"${menu_items[@]}" \
3>&1 1>&2 2>&3)
local dialog_exit=$?
exec 3>&-
if [ $? -ne 0 ]; then
echo "User cancelled. Exiting."
if [[ $dialog_exit -ne 0 || -z "$selected_indices" ]]; then
exit 0
fi
IFS=$'\n' read -d '' -r -a selected_options <<< "$selected_indices"
declare -A selected_functions
if [ -n "$selected_indices" ]; then
declare -A selected_functions
read -ra indices_array <<< "$selected_indices"
for index in "${indices_array[@]}"; do
if [[ -z "$index" ]] || ! [[ "$index" =~ ^[0-9]+$ ]]; then
continue
fi
local item_index=$(( (index - 1) * 3 + 1 ))
if [[ $item_index -lt ${#checklist_items[@]} ]]; then
local selected_line="${checklist_items[$item_index]}"
if [[ "$selected_line" =~ ^.*(\-\-\-|===+).*$ ]]; then
return 1
fi
fi
option=${sorted_options[$((index - 1))]}
IFS='|' read -r _ description function_name <<< "$option"
selected_functions[$function_name]=1
[[ "$function_name" == "FASTFETCH" ]] && selected_functions[MOTD]=0
done
clear
show_proxmenux_logo
msg_title "$SCRIPT_TITLE"
for index in "${selected_options[@]}"; do
option=${sorted_options[$((index - 1))]}
IFS='|' read -r category description function_name <<< "$option"
selected_functions[$function_name]=1
[[ "$function_name" == "FASTFETCH" ]] && selected_functions[MOTD]=0
done
for index in "${!sorted_options[@]}"; do
option=${sorted_options[$index]}
IFS='|' read -r category description function_name <<< "$option"
for option in "${sorted_options[@]}"; do
IFS='|' read -r _ description function_name <<< "$option"
if [[ ${selected_functions[$function_name]} -eq 1 ]]; then
case $function_name in
APTUPGRADE) apt_upgrade ;;
@@ -2780,24 +3276,20 @@ main_menu() {
REPOTEST) add_repo_test ;;
ENABLE_HA) enable_ha ;;
FIGURINE) configure_figurine ;;
LOG2RAM) configure_log2ram ;;
PVEAM) update_pve_appliance_manager ;;
*) echo "Option $function_name not implemented yet" ;;
esac
fi
done
if [ "$NECESSARY_REBOOT" -eq 1 ]; then
whiptail --title "Reboot Required" --yesno "$(translate "Some changes require a reboot to take effect. Do you want to restart now?")" 10 60
if [ $? -eq 0 ]; then
if [[ "$NECESSARY_REBOOT" -eq 1 ]]; then
whiptail --title "Reboot Required" \
--yesno "$(translate "Some changes require a reboot to take effect. Do you want to restart now?")" 10 60
if [[ $? -eq 0 ]]; then
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' autoremove >/dev/null 2>&1
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' autoclean >/dev/null 2>&1
apt-get -y autoremove >/dev/null 2>&1
apt-get -y autoclean >/dev/null 2>&1
msg_ok "$(translate "Cleanup finished")"
msg_success "$(translate "Press Enter to continue...")"
read -r
@@ -2805,32 +3297,24 @@ main_menu() {
reboot
else
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' autoremove >/dev/null 2>&1
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' autoclean >/dev/null 2>&1
apt-get -y autoremove >/dev/null 2>&1
apt-get -y autoclean >/dev/null 2>&1
msg_ok "$(translate "Cleanup finished")"
msg_info2 "$(translate "You can reboot later manually.")"
msg_success "$(translate "Press Enter to continue...")"
read -r
exit 0
fi
fi
msg_success "$(translate "All changes applied. No reboot required.")"
msg_success "$(translate "Press Enter to return to menu...")"
read -r
else
exit 0
fi
clear
}
if [[ "$LANGUAGE" != "en" ]]; then
show_proxmenux_logo
msg_lang "$(translate "Generating automatic translations...")"
fi
main_menu

View File

@@ -0,0 +1,606 @@
#!/bin/bash
# ==========================================================
# ProxMenux - Complete Uninstall Optimizations Script
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 06/07/2025
# ==========================================================
# Description:
# This script provides a complete uninstallation and rollback system
# for all post-installation optimizations applied by ProxMenux.
#
# It allows administrators to safely revert any changes made during the
# optimization process, restoring the system to its original state.
#
# This ensures full control over system configurations and gives users
# the confidence to apply, test, and undo ProxMenux enhancements as needed.
# ==========================================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
RETURN_SCRIPT="$REPO_URL/scripts/menus/menu_post_install.sh"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
TOOLS_JSON="$BASE_DIR/installed_tools.json"
VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
# Tool registration system
ensure_tools_json() {
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
}
register_tool() {
local tool="$1"
local state="$2"
ensure_tools_json
jq --arg t "$tool" --argjson v "$state" '.[$t]=$v' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
}
################################################################
uninstall_fastfetch() {
if ! command -v fastfetch &>/dev/null; then
msg_warn "$(translate "Fastfetch is not installed.")"
return 0
fi
msg_info2 "$(translate "Uninstalling Fastfetch...")"
rm -f /usr/local/bin/fastfetch /usr/bin/fastfetch
rm -rf "$HOME/.config/fastfetch"
rm -rf /usr/local/share/fastfetch
sed -i '/fastfetch/d' "$HOME/.bashrc" "$HOME/.profile" 2>/dev/null
rm -f /etc/profile.d/fastfetch.sh /etc/update-motd.d/99-fastfetch
dpkg -r fastfetch &>/dev/null
msg_ok "$(translate "Fastfetch removed from system")"
register_tool "fastfetch" false
}
################################################################
uninstall_figurine() {
if ! command -v figurine &>/dev/null; then
msg_warn "$(translate "Figurine is not installed.")"
return 0
fi
msg_info2 "$(translate "Uninstalling Figurine...")"
rm -f /usr/local/bin/figurine
rm -f /etc/profile.d/figurine.sh
sed -i '/figurine/d' "$HOME/.bashrc" "$HOME/.profile" 2>/dev/null
msg_ok "$(translate "Figurine removed from system")"
register_tool "figurine" false
}
################################################################
uninstall_kexec() {
if ! dpkg -s kexec-tools >/dev/null 2>&1 && [ ! -f /etc/systemd/system/kexec-pve.service ]; then
msg_warn "$(translate "kexec-tools is not installed or already removed.")"
return 0
fi
msg_info2 "$(translate "Uninstalling kexec-tools and removing custom service...")"
systemctl disable --now kexec-pve.service &>/dev/null
rm -f /etc/systemd/system/kexec-pve.service
sed -i "/alias reboot-quick='systemctl kexec'/d" /root/.bash_profile
apt-get purge -y kexec-tools >/dev/null 2>&1
msg_ok "$(translate "kexec-tools and related settings removed")"
register_tool "kexec" false
}
################################################################
uninstall_apt_upgrade() {
msg_info "$(translate "Restoring enterprise repositories...")"
# Re-enable enterprise repos
if [ -f /etc/apt/sources.list.d/pve-enterprise.list ]; then
sed -i "s/^#deb/deb/g" /etc/apt/sources.list.d/pve-enterprise.list
fi
if [ -f /etc/apt/sources.list.d/ceph.list ]; then
sed -i "s/^#deb/deb/g" /etc/apt/sources.list.d/ceph.list
fi
# Remove public repo
rm -f /etc/apt/sources.list.d/pve-public-repo.list
# Remove firmware warning config
rm -f /etc/apt/apt.conf.d/no-bookworm-firmware.conf
apt-get update > /dev/null 2>&1
msg_ok "$(translate "Enterprise repositories restored")"
register_tool "apt_upgrade" false
}
################################################################
uninstall_subscription_banner() {
msg_info "$(translate "Restoring subscription banner...")"
# Remove APT hook
rm -f /etc/apt/apt.conf.d/no-nag-script
# Reinstall proxmox-widget-toolkit to restore original
apt --reinstall install proxmox-widget-toolkit -y >/dev/null 2>&1
msg_ok "$(translate "Subscription banner restored")"
register_tool "subscription_banner" false
}
################################################################
uninstall_time_sync() {
msg_info "$(translate "Resetting time synchronization...")"
# Reset to UTC (safe default)
timedatectl set-timezone UTC >/dev/null 2>&1
msg_ok "$(translate "Time synchronization reset to UTC")"
register_tool "time_sync" false
}
################################################################
uninstall_apt_languages() {
msg_info "$(translate "Restoring APT language downloads...")"
# Remove the configuration that disables translations
rm -f /etc/apt/apt.conf.d/99-disable-translations
msg_ok "$(translate "APT language downloads restored")"
register_tool "apt_languages" false
}
################################################################
uninstall_journald() {
msg_info "$(translate "Restoring default journald configuration...")"
# Restore default journald configuration
cat > /etc/systemd/journald.conf << 'EOF'
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Entries in this file show the compile time defaults.
# You can change settings by editing this file.
# Defaults can be restored by simply deleting this file.
#
# See journald.conf(5) for details.
[Journal]
#Storage=auto
#Compress=yes
#Seal=yes
#SplitMode=uid
#SyncIntervalSec=5m
#RateLimitInterval=30s
#RateLimitBurst=1000
#SystemMaxUse=
#SystemKeepFree=
#SystemMaxFileSize=
#RuntimeMaxUse=
#RuntimeKeepFree=
#RuntimeMaxFileSize=
#MaxRetentionSec=
#MaxFileSec=1month
#ForwardToSyslog=yes
#ForwardToKMsg=no
#ForwardToConsole=no
#ForwardToWall=yes
#TTYPath=/dev/console
#MaxLevelStore=debug
#MaxLevelSyslog=debug
#MaxLevelKMsg=notice
#MaxLevelConsole=info
#MaxLevelWall=emerg
EOF
systemctl restart systemd-journald.service >/dev/null 2>&1
msg_ok "$(translate "Default journald configuration restored")"
register_tool "journald" false
}
################################################################
uninstall_logrotate() {
msg_info "$(translate "Restoring original logrotate configuration...")"
# Restore from backup if it exists
if [ -f /etc/logrotate.conf.bak ]; then
mv /etc/logrotate.conf.bak /etc/logrotate.conf
systemctl restart logrotate >/dev/null 2>&1
msg_ok "$(translate "Original logrotate configuration restored")"
else
msg_warn "$(translate "No backup found, logrotate configuration not changed")"
fi
register_tool "logrotate" false
}
################################################################
uninstall_system_limits() {
msg_info "$(translate "Removing system limits optimizations...")"
# Remove ProxMenux sysctl configurations
rm -f /etc/sysctl.d/99-maxwatches.conf
rm -f /etc/sysctl.d/99-maxkeys.conf
rm -f /etc/sysctl.d/99-swap.conf
rm -f /etc/sysctl.d/99-fs.conf
# Remove ProxMenux limits configuration
rm -f /etc/security/limits.d/99-limits.conf
# Remove systemd limits (restore defaults)
for file in /etc/systemd/system.conf /etc/systemd/user.conf; do
if [ -f "$file" ]; then
sed -i '/^DefaultLimitNOFILE=256000/d' "$file"
fi
done
# Remove PAM limits
for file in /etc/pam.d/common-session /etc/pam.d/runuser-l; do
if [ -f "$file" ]; then
sed -i '/^session required pam_limits.so/d' "$file"
fi
done
# Remove ulimit from profile
if [ -f /root/.profile ]; then
sed -i '/ulimit -n 256000/d' /root/.profile
fi
# Reload sysctl
sysctl --system >/dev/null 2>&1
msg_ok "$(translate "System limits optimizations removed")"
register_tool "system_limits" false
}
################################################################
uninstall_entropy() {
msg_info "$(translate "Removing entropy generation optimization...")"
# Stop and disable haveged
systemctl stop haveged >/dev/null 2>&1
systemctl disable haveged >/dev/null 2>&1
# Remove haveged package
apt-get purge -y haveged >/dev/null 2>&1
# Remove configuration
rm -f /etc/default/haveged
msg_ok "$(translate "Entropy generation optimization removed")"
register_tool "entropy" false
}
################################################################
uninstall_memory_settings() {
msg_info "$(translate "Removing memory optimizations...")"
# Remove ProxMenux memory configuration
rm -f /etc/sysctl.d/99-memory.conf
# Reload sysctl
sysctl --system >/dev/null 2>&1
msg_ok "$(translate "Memory optimizations removed")"
register_tool "memory_settings" false
}
################################################################
uninstall_kernel_panic() {
msg_info "$(translate "Removing kernel panic configuration...")"
# Remove ProxMenux kernel panic configuration
rm -f /etc/sysctl.d/99-kernelpanic.conf
# Reload sysctl
sysctl --system >/dev/null 2>&1
msg_ok "$(translate "Kernel panic configuration removed")"
register_tool "kernel_panic" false
}
################################################################
uninstall_apt_ipv4() {
msg_info "$(translate "Removing APT IPv4 configuration...")"
# Remove IPv4 force configuration
rm -f /etc/apt/apt.conf.d/99-force-ipv4
msg_ok "$(translate "APT IPv4 configuration removed")"
register_tool "apt_ipv4" false
}
################################################################
uninstall_network_optimization() {
msg_info "$(translate "Removing network optimizations...")"
# Remove ProxMenux network configuration
rm -f /etc/sysctl.d/99-network.conf
# Remove interfaces.d source line if we added it
local interfaces_file="/etc/network/interfaces"
if [ -f "$interfaces_file" ]; then
# Only remove if it's the last line and looks like our addition
if tail -1 "$interfaces_file" | grep -q "^source /etc/network/interfaces.d/\*$"; then
sed -i '$d' "$interfaces_file"
fi
fi
# Reload sysctl
sysctl --system >/dev/null 2>&1
msg_ok "$(translate "Network optimizations removed")"
register_tool "network_optimization" false
}
################################################################
uninstall_disable_rpc() {
msg_info "$(translate "Re-enabling RPC services...")"
# Re-enable and start rpcbind
systemctl enable rpcbind >/dev/null 2>&1
systemctl start rpcbind >/dev/null 2>&1
msg_ok "$(translate "RPC services re-enabled")"
register_tool "disable_rpc" false
}
################################################################
uninstall_bashrc_custom() {
msg_info "$(translate "Restoring original bashrc...")"
# Restore original bashrc from backup
if [ -f /root/.bashrc.bak ]; then
mv /root/.bashrc.bak /root/.bashrc
msg_ok "$(translate "Original bashrc restored")"
else
# Remove ProxMenux customizations manually
if [ -f /root/.bashrc ]; then
# Remove our customization block
sed -i '/# ProxMenux customizations/,/source \/etc\/profile\.d\/bash_completion\.sh/d' /root/.bashrc
fi
msg_ok "$(translate "ProxMenux customizations removed from bashrc")"
fi
# Remove bash_profile source line if we added it
if [ -f /root/.bash_profile ]; then
sed -i '/source \/root\/\.bashrc/d' /root/.bash_profile
fi
register_tool "bashrc_custom" false
}
################################################################
uninstall_log2ram() {
if [[ ! -f /etc/log2ram.conf ]] && ! systemctl list-units --all | grep -q log2ram; then
msg_warn "$(translate "log2ram is not installed.")"
return 0
fi
msg_info "$(translate "Uninstalling log2ram...")"
# Stop and disable services and timers
systemctl stop log2ram >/dev/null 2>&1
systemctl disable log2ram >/dev/null 2>&1
systemctl stop log2ram-daily.timer >/dev/null 2>&1
systemctl disable log2ram-daily.timer >/dev/null 2>&1
# Remove cron jobs
rm -f /etc/cron.d/log2ram
rm -f /etc/cron.d/log2ram-auto-sync
# Remove config and binaries
rm -f /usr/local/bin/log2ram-check.sh
rm -f /usr/sbin/log2ram
rm -f /etc/log2ram.conf*
rm -f /etc/systemd/system/log2ram.service
rm -f /etc/systemd/system/log2ram-daily.timer
rm -f /etc/systemd/system/log2ram-daily.service
# Clean up log2ram mount if active
if [ -d /var/log.hdd ]; then
if [ -d /var/log ] && mountpoint -q /var/log; then
rsync -a /var/log/ /var/log.hdd/ >/dev/null 2>&1
umount /var/log >/dev/null 2>&1
fi
rm -rf /var/log.hdd
fi
systemctl daemon-reexec >/dev/null 2>&1
systemctl daemon-reload >/dev/null 2>&1
msg_ok "$(translate "log2ram completely removed")"
register_tool "log2ram" false
}
################################################################
migrate_installed_tools() {
if [[ -f "$TOOLS_JSON" ]]; then
return
fi
clear
show_proxmenux_logo
msg_info "$(translate 'Detecting previous optimizations...')"
echo "{}" > "$TOOLS_JSON"
local updated=false
# APT configurations
if [[ -f /etc/apt/apt.conf.d/99-force-ipv4 ]]; then
jq '. + {"apt_ipv4": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
updated=true
fi
if [[ -f /etc/apt/apt.conf.d/99-disable-translations ]]; then
jq '. + {"apt_languages": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
updated=true
fi
# System configurations
if [[ -f /etc/sysctl.d/99-memory.conf ]]; then
jq '. + {"memory_settings": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
updated=true
fi
if [[ -f /etc/sysctl.d/99-network.conf ]]; then
jq '. + {"network_optimization": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
updated=true
fi
if [[ -f /etc/sysctl.d/99-kernelpanic.conf ]]; then
jq '. + {"kernel_panic": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
updated=true
fi
if [[ -f /etc/security/limits.d/99-limits.conf ]]; then
jq '. + {"system_limits": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
updated=true
fi
# Services
if systemctl is-active --quiet log2ram 2>/dev/null; then
jq '. + {"log2ram": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
updated=true
fi
if dpkg -l | grep -q haveged; then
jq '. + {"entropy": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
updated=true
fi
# Bashrc customization
if grep -q "# ProxMenux customizations" /root/.bashrc 2>/dev/null; then
jq '. + {"bashrc_custom": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
updated=true
fi
# Subscription banner
if [[ -f /etc/apt/apt.conf.d/no-nag-script ]]; then
jq '. + {"subscription_banner": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
updated=true
fi
if [[ "$updated" == true ]]; then
sleep 2
msg_ok "$(translate 'Optimizations detected and ready to revert.')"
sleep 1
fi
}
################################################################
show_uninstall_menu() {
ensure_tools_json
migrate_installed_tools
mapfile -t tools_installed < <(jq -r 'to_entries | map(select(.value==true)) | .[].key' "$TOOLS_JSON")
if [[ ${#tools_installed[@]} -eq 0 ]]; then
dialog --backtitle "ProxMenux" --title "ProxMenux" \
--msgbox "\n\n$(translate "No optimizations detected to uninstall.")" 10 60
return 0
fi
local menu_options=()
for tool in "${tools_installed[@]}"; do
case "$tool" in
lvm_repair) desc="LVM PV Headers Repair";;
repo_cleanup) desc="Repository Cleanup";;
apt_upgrade) desc="APT Upgrade & Repository Config";;
subscription_banner) desc="Subscription Banner Removal";;
time_sync) desc="Time Synchronization";;
apt_languages) desc="APT Language Skip";;
journald) desc="Journald Optimization";;
logrotate) desc="Logrotate Optimization";;
system_limits) desc="System Limits Increase";;
entropy) desc="Entropy Generation (haveged)";;
memory_settings) desc="Memory Settings Optimization";;
kernel_panic) desc="Kernel Panic Configuration";;
apt_ipv4) desc="APT IPv4 Force";;
network_optimization) desc="Network Optimizations";;
disable_rpc) desc="RPC/rpcbind Disable";;
bashrc_custom) desc="Bashrc Customization";;
log2ram) desc="Log2ram (SSD Protection)";;
*) desc="$tool";;
esac
menu_options+=("$tool" "$desc" "off")
done
selected_tools=$(dialog --backtitle "ProxMenux" \
--title "$(translate "Uninstall Optimizations")" \
--checklist "$(translate "Select optimizations to uninstall:")" 20 70 12 \
"${menu_options[@]}" 3>&1 1>&2 2>&3)
local dialog_result=$?
if [[ $dialog_result -ne 0 || -z "$selected_tools" ]]; then
return 0
fi
# Show confirmation
if ! dialog --backtitle "ProxMenux" \
--title "$(translate "Confirm Uninstallation")" \
--yesno "\n\n$(translate "Are you sure you want to uninstall the selected optimizations.")" 10 60; then
return 0
fi
# Execute uninstallations
for tool in $selected_tools; do
tool=$(echo "$tool" | tr -d '"')
if declare -f "uninstall_$tool" > /dev/null 2>&1; then
clear
show_proxmenux_logo
"uninstall_$tool"
else
msg_warn "$(translate "No uninstaller found for:") $tool"
fi
done
msg_success "$(translate "Selected optimizations have been uninstalled.")"
msg_warn "$(translate "A system reboot is recommended to ensure all changes take effect.")"
if dialog --backtitle "ProxMenux" \
--title "$(translate "Reboot Recommended")" \
--yesno "$(translate "Do you want to reboot now?")" 8 50; then
reboot
fi
}
################################################################
show_uninstall_menu

View File

@@ -6,8 +6,8 @@
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 28/01/2025
# Version : 1.1
# Last Updated: 28/06/2025
# ==========================================================
# Description:
# This script allows users to assign physical disks to existing
@@ -19,7 +19,6 @@
# - Configures the selected disks for the CT and verifies the assignment.
# ==========================================================
# Configuration ============================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
@@ -29,12 +28,12 @@ VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
# ==========================================================
get_disk_info() {
local disk=$1
MODEL=$(lsblk -dn -o MODEL "$disk" | xargs)
@@ -42,15 +41,13 @@ get_disk_info() {
echo "$MODEL" "$SIZE"
}
CT_LIST=$(pct list | awk 'NR>1 {print $1, $3}')
if [ -z "$CT_LIST" ]; then
whiptail --title "$(translate "Error")" --msgbox "$(translate "No CTs available in the system.")" 8 40
exit 1
fi
CTID=$(whiptail --title "$(translate "Select CT for destination disk")" --menu "$(translate "Select the CT to which you want to add disks:")" 15 60 8 $CT_LIST 3>&1 1>&2 2>&3)
if [ -z "$CTID" ]; then
@@ -60,32 +57,32 @@ fi
CTID=$(echo "$CTID" | tr -d '"')
clear
show_proxmenux_logo
echo -e
msg_info2 "$(translate "Add Disk") Passthrough $(translate "to a LXC")"
echo -e
msg_ok "$(translate "CT selected successfully.")"
CT_STATUS=$(pct status "$CTID" | awk '{print $2}')
if [ "$CT_STATUS" != "running" ]; then
msg_info "$(translate "Starting CT") $CTID..."
pct start "$CTID"
sleep 2
if [ "$(pct status "$CTID" | awk '{print $2}')" != "running" ]; then
msg_error "$(translate "Failed to start the CT.")"
sleep 2
exit 1
fi
msg_ok "$(translate "CT started successfully.")"
fi
CONF_FILE="/etc/pve/lxc/$CTID.conf"
if grep -q '^unprivileged: 1' "$CONF_FILE"; then
if whiptail --title "$(translate "Privileged Container")" \
--yesno "$(translate "The selected container is unprivileged. A privileged container is required for direct device passthrough.")\\n\\n$(translate "Do you want to convert it to a privileged container now?")" 12 70; then
msg_info "$(translate "Stopping container") $CTID..."
pct shutdown "$CTID" &>/dev/null
for i in {1..10}; do
@@ -94,20 +91,15 @@ if grep -q '^unprivileged: 1' "$CONF_FILE"; then
break
fi
done
if [ "$(pct status "$CTID" | awk '{print $2}')" == "running" ]; then
msg_error "$(translate "Failed to stop the container.")"
exit 1
fi
msg_ok "$(translate "Container stopped.")"
cp "$CONF_FILE" "$CONF_FILE.bak"
sed -i '/^unprivileged: 1/d' "$CONF_FILE"
echo "unprivileged: 0" >> "$CONF_FILE"
msg_ok "$(translate "Container successfully converted to privileged.")"
msg_info "$(translate "Starting container") $CTID..."
pct start "$CTID" &>/dev/null
sleep 2
@@ -116,7 +108,6 @@ if grep -q '^unprivileged: 1' "$CONF_FILE"; then
exit 1
fi
msg_ok "$(translate "Container started successfully.")"
else
whiptail --title "$(translate "Aborted")" \
--msgbox "$(translate "Operation cancelled. Cannot continue with an unprivileged container.")" 10 60
@@ -124,16 +115,7 @@ if grep -q '^unprivileged: 1' "$CONF_FILE"; then
fi
fi
##########################################
msg_info "$(translate "Detecting available disks...")"
USED_DISKS=$(lsblk -n -o PKNAME,TYPE | grep 'lvm' | awk '{print "/dev/" $1}')
@@ -151,7 +133,6 @@ for entry in $ZFS_RAW; do
elif [[ "$entry" == /dev/* ]]; then
path="$entry"
fi
if [ -n "$path" ]; then
base_disk=$(lsblk -no PKNAME "$path" 2>/dev/null)
if [ -n "$base_disk" ]; then
@@ -160,44 +141,36 @@ for entry in $ZFS_RAW; do
fi
done
ZFS_DISKS=$(echo "$ZFS_DISKS" | sort -u)
is_disk_in_use() {
local disk="$1"
while read -r part fstype; do
case "$fstype" in
zfs_member|linux_raid_member)
return 0 ;;
esac
if echo "$MOUNTED_DISKS" | grep -q "/dev/$part"; then
return 0
fi
done < <(lsblk -ln -o NAME,FSTYPE "$disk" | tail -n +2)
if echo "$USED_DISKS" | grep -q "$disk" || echo "$ZFS_DISKS" | grep -q "$disk"; then
return 0
fi
return 1
}
FREE_DISKS=()
LVM_DEVICES=$(pvs --noheadings -o pv_name 2> >(grep -v 'File descriptor .* leaked') | xargs -n1 readlink -f | sort -u)
RAID_ACTIVE=$(grep -Po 'md\d+\s*:\s*active\s+raid[0-9]+' /proc/mdstat | awk '{print $1}' | sort -u)
while read -r DISK; do
[[ "$DISK" =~ /dev/zd ]] && continue
INFO=($(get_disk_info "$DISK"))
MODEL="${INFO[@]::${#INFO[@]}-1}"
SIZE="${INFO[-1]}"
LABEL=""
SHOW_DISK=true
IS_MOUNTED=false
IS_RAID=false
IS_ZFS=false
@@ -217,11 +190,9 @@ while read -r DISK; do
IS_MOUNTED=true
fi
USED_BY=""
REAL_PATH=$(readlink -f "$DISK")
CONFIG_DATA=$(grep -vE '^\s*#' /etc/pve/qemu-server/*.conf /etc/pve/lxc/*.conf 2>/dev/null)
if grep -Fq "$REAL_PATH" <<< "$CONFIG_DATA"; then
USED_BY="$(translate "In use")"
else
@@ -235,8 +206,6 @@ while read -r DISK; do
done
fi
if $IS_RAID && grep -q "$DISK" <<< "$(cat /proc/mdstat)"; then
if grep -q "active raid" /proc/mdstat; then
SHOW_DISK=false
@@ -260,7 +229,6 @@ while read -r DISK; do
[[ "$IS_RAID" == true ]] && LABEL+=" ⚠ RAID"
[[ "$IS_LVM" == true ]] && LABEL+=" ⚠ LVM"
[[ "$IS_ZFS" == true ]] && LABEL+=" ⚠ ZFS"
DESCRIPTION=$(printf "%-30s %10s%s" "$MODEL" "$SIZE" "$LABEL")
FREE_DISKS+=("$DISK" "$DESCRIPTION" "OFF")
fi
@@ -275,19 +243,9 @@ fi
msg_ok "$(translate "Available disks detected.")"
######################################################
MAX_WIDTH=$(printf "%s\n" "${FREE_DISKS[@]}" | awk '{print length}' | sort -nr | head -n1)
TOTAL_WIDTH=$((MAX_WIDTH + 20))
if [ $TOTAL_WIDTH -lt 50 ]; then
TOTAL_WIDTH=50
fi
@@ -312,12 +270,10 @@ msg_info "$(translate "Processing selected disks...")"
for DISK in $SELECTED; do
DISK=$(echo "$DISK" | tr -d '"')
DISK_INFO=$(get_disk_info "$DISK")
ASSIGNED_TO=""
RUNNING_CTS=""
RUNNING_VMS=""
while read -r CT_ID CT_NAME; do
if [[ "$CT_ID" =~ ^[0-9]+$ ]] && pct config "$CT_ID" | grep -q "$DISK"; then
ASSIGNED_TO+="CT $CT_ID $CT_NAME\n"
@@ -328,7 +284,6 @@ for DISK in $SELECTED; do
fi
done < <(pct list | awk 'NR>1 {print $1, $3}')
while read -r VM_ID VM_NAME; do
if [[ "$VM_ID" =~ ^[0-9]+$ ]] && qm config "$VM_ID" | grep -q "$DISK"; then
ASSIGNED_TO+="VM $VM_ID $VM_NAME\n"
@@ -354,18 +309,12 @@ for DISK in $SELECTED; do
fi
cleanup
if lsblk "$DISK" | grep -q "raid" || grep -q "${DISK##*/}" /proc/mdstat; then
whiptail --title "$(translate "RAID Detected")" --msgbox "$(translate "The disk") $DISK_INFO $(translate "appears to be part of a") RAID. $(translate "For security reasons, the system cannot format it.")\\n\\n$(translate "If you are sure you want to use it, please remove the") RAID metadata $(translate "or format it manually using external tools.")\\n\\n$(translate "After that, run this script again to add it.")" 18 70
clear
exit
fi
MOUNT_POINT=$(whiptail --title "$(translate "Mount Point")" --inputbox "$(translate "Enter the mount point for the disk (e.g., /mnt/disk_passthrough):")" 10 60 "/mnt/disk_passthrough" 3>&1 1>&2 2>&3)
if [ -z "$MOUNT_POINT" ]; then
@@ -375,18 +324,12 @@ for DISK in $SELECTED; do
msg_ok "$(translate "Mount point specified: $MOUNT_POINT")"
PARTITION=$(lsblk -rno NAME "$DISK" | awk -v disk="$(basename "$DISK")" '$1 != disk {print $1; exit}')
SKIP_FORMAT=false
if [ -n "$PARTITION" ]; then
PARTITION="/dev/$PARTITION"
CURRENT_FS=$(lsblk -no FSTYPE "$PARTITION" | xargs)
if [[ "$CURRENT_FS" == "ext4" || "$CURRENT_FS" == "xfs" || "$CURRENT_FS" == "btrfs" ]]; then
SKIP_FORMAT=true
msg_ok "$(translate "Detected existing filesystem") $CURRENT_FS $(translate "on") $PARTITION."
@@ -397,27 +340,22 @@ for DISK in $SELECTED; do
fi
fi
else
CURRENT_FS=$(lsblk -no FSTYPE "$DISK" | xargs)
if [[ "$CURRENT_FS" == "ext4" || "$CURRENT_FS" == "xfs" || "$CURRENT_FS" == "btrfs" ]]; then
SKIP_FORMAT=true
PARTITION="$DISK"
msg_ok "$(translate "Detected filesystem") $CURRENT_FS $(translate "directly on disk") $DISK."
else
whiptail --title "$(translate "No Valid Partitions")" --yesno "$(translate "The disk has no partitions and no valid filesystem. Do you want to create a new partition and format it?")" 10 70
if [ $? -ne 0 ]; then
continue
fi
echo -e "$(translate "Creating partition table and partition...")"
parted -s "$DISK" mklabel gpt
parted -s "$DISK" mkpart primary 0% 100%
sleep 2
partprobe "$DISK"
sleep 2
PARTITION=$(lsblk -rno NAME "$DISK" | awk -v disk="$(basename "$DISK")" '$1 != disk {print $1; exit}')
if [ -n "$PARTITION" ]; then
PARTITION="/dev/$PARTITION"
@@ -428,17 +366,12 @@ for DISK in $SELECTED; do
fi
fi
if [ "$SKIP_FORMAT" != true ]; then
CURRENT_FS=$(lsblk -no FSTYPE "$PARTITION" | xargs)
if [[ "$CURRENT_FS" == "ext4" || "$CURRENT_FS" == "xfs" || "$CURRENT_FS" == "btrfs" ]]; then
SKIP_FORMAT=true
msg_ok "$(translate "Detected existing filesystem") $CURRENT_FS $(translate "on") $PARTITION. $(translate "Skipping format.")"
else
FORMAT_TYPE=$(whiptail --title "$(translate "Select Format Type")" --menu "$(translate "Select the filesystem type for") $DISK_INFO:" 15 60 6 \
"ext4" "$(translate "Extended Filesystem 4 (recommended)")" \
"xfs" "$(translate "XFS Filesystem")" \
@@ -450,6 +383,7 @@ for DISK in $SELECTED; do
fi
whiptail --title "$(translate "WARNING")" --yesno "$(translate "WARNING: This operation will FORMAT the disk") $DISK_INFO $(translate "with") $FORMAT_TYPE.\\n\\n$(translate "ALL DATA ON THIS DISK WILL BE PERMANENTLY LOST!")\\n\\n$(translate "Are you sure you want to continue")" 15 70
if [ $? -ne 0 ]; then
whiptail --title "$(translate "Format Cancelled")" --msgbox "$(translate "Format operation cancelled. The disk will not be added.")" 8 60
continue
@@ -457,13 +391,8 @@ for DISK in $SELECTED; do
fi
fi
if [ "$SKIP_FORMAT" != true ]; then
echo -e "$(translate "Formatting partition") $PARTITION $(translate "with") $FORMAT_TYPE..."
case "$FORMAT_TYPE" in
"ext4") mkfs.ext4 -F "$PARTITION" ;;
"xfs") mkfs.xfs -f "$PARTITION" ;;
@@ -480,28 +409,55 @@ for DISK in $SELECTED; do
fi
fi
INDEX=0
while pct config "$CTID" | grep -q "mp${INDEX}:"; do
((INDEX++))
done
# Determine the filesystem type for mount options
CURRENT_FS=$(lsblk -no FSTYPE "$PARTITION" | xargs)
if [[ -n "$CURRENT_FS" ]]; then
FORMAT_TYPE="$CURRENT_FS"
fi
# Install filesystem tools in container if needed
FS_PKG=""
FS_BIN=""
if [[ "$FORMAT_TYPE" == "xfs" ]]; then
FS_PKG="xfsprogs"
FS_BIN="mkfs.xfs"
elif [[ "$FORMAT_TYPE" == "btrfs" ]]; then
FS_PKG="btrfs-progs"
FS_BIN="mkfs.btrfs"
fi
if [[ -n "$FS_PKG" && -n "$FS_BIN" ]]; then
if ! pct exec "$CTID" -- sh -c "command -v $FS_BIN >/dev/null 2>&1"; then
msg_info "$(translate "Installing required tools for $FORMAT_TYPE in CT $CTID...")"
if pct exec "$CTID" -- sh -c "[ -f /etc/alpine-release ]"; then
pct exec "$CTID" -- sh -c "apk update >/dev/null && apk add --no-progress $FS_PKG >/dev/null"
elif pct exec "$CTID" -- sh -c "[ -f /etc/os-release ] && (grep -qE 'debian|ubuntu' /etc/os-release)"; then
pct exec "$CTID" -- sh -c "apt-get update -qq >/dev/null && apt-get install -y -qq $FS_PKG >/dev/null"
fi
msg_ok "$(translate "Required tools for $FORMAT_TYPE installed in CT $CTID.")"
fi
fi
##############################################################################
CURRENT_FS=$(lsblk -no FSTYPE "$PARTITION" | xargs)
if [ "$CURRENT_FS" == "xfs" ] || [ "$FORMAT_TYPE" == "xfs" ]; then
RESULT=$(pct set "$CTID" -mp${INDEX} "$PARTITION,mp=$MOUNT_POINT,backup=0,ro=0" 2>&1)
else
RESULT=$(pct set "$CTID" -mp${INDEX} "$PARTITION,mp=$MOUNT_POINT,backup=0,ro=0,acl=1" 2>&1)
fi
pct exec "$CTID" -- chmod -R 777 "$MOUNT_POINT"
pct exec "$CTID" -- chmod -R 777 "$MOUNT_POINT" 2>/dev/null || true
##############################################################################
if [ $? -eq 0 ]; then
MESSAGE="$(translate "The disk") $DISK_INFO $(translate "has been successfully added to CT") $CTID $(translate "as a mount point at") $MOUNT_POINT."
if [ -n "$ASSIGNED_TO" ]; then
@@ -513,10 +469,9 @@ for DISK in $SELECTED; do
else
ERROR_MESSAGES+="$(translate "Could not add disk") $DISK_INFO $(translate "to CT") $CTID.\\n$(translate "Error:") $RESULT\\n\\n"
fi
done
msg_ok "$(translate "Disk processing completed.")"
if [ -n "$SUCCESS_MESSAGES" ]; then

View File

@@ -6,15 +6,15 @@
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 28/01/2025
# Version : 1.1
# Last Updated: 29/05/2025
# ==========================================================
# Description:
# This script automates the process of importing disk images into Proxmox VE virtual machines (VMs),
# making it easy to attach pre-existing disk files without manual configuration.
#
# Before running the script, ensure that disk images are available in /var/lib/vz/template/images/.
# The script scans this directory for compatible formats (.img, .qcow2, .vmdk) and lists the available files.
# The script scans this directory for compatible formats (.img, .qcow2, .vmdk, .raw) and lists the available files.
#
# Using an interactive menu, you can:
# - Select a VM to attach the imported disk.
@@ -32,38 +32,63 @@ BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
[[ -f "$UTILS_FILE" ]] && source "$UTILS_FILE"
load_language
initialize_cache
# ==========================================================
# Path where disk images are stored
IMAGES_DIR="/var/lib/vz/template/images/"
# Configuration ============================================
# Initial setup
if [ ! -d "$IMAGES_DIR" ]; then
msg_info "$(translate 'Creating images directory')"
mkdir -p "$IMAGES_DIR"
chmod 755 "$IMAGES_DIR"
msg_ok "$(translate 'Images directory created:') $IMAGES_DIR"
fi
detect_image_dir() {
for store in $(pvesm status -content images | awk 'NR>1 {print $1}'); do
path=$(pvesm path "${store}:template" 2>/dev/null)
if [[ -d "$path" ]]; then
for ext in raw img qcow2 vmdk; do
if compgen -G "$path/*.$ext" > /dev/null; then
echo "$path"
return 0
fi
done
for sub in images iso; do
dir="$path/$sub"
if [[ -d "$dir" ]]; then
for ext in raw img qcow2 vmdk; do
if compgen -G "$dir/*.$ext" > /dev/null; then
echo "$dir"
return 0
fi
done
fi
done
fi
done
for fallback in /var/lib/vz/template/images /var/lib/vz/template/iso; do
if [[ -d "$fallback" ]]; then
for ext in raw img qcow2 vmdk; do
if compgen -G "$fallback/*.$ext" > /dev/null; then
echo "$fallback"
return 0
fi
done
fi
done
return 1
}
# Check if there are any images in the directory
IMAGES=$(ls -A "$IMAGES_DIR" | grep -E "\.(img|qcow2|vmdk)$")
if [ -z "$IMAGES" ]; then
whiptail --title "$(translate 'No Images Found')" \
--msgbox "$(translate 'No images available for import in:')\n\n$IMAGES_DIR\n\n$(translate 'Supported formats: .img, .qcow2, .vmdk')\n\n$(translate 'Please add some images and try again.')" 15 60
IMAGES_DIR=$(detect_image_dir)
if [[ -z "$IMAGES_DIR" ]]; then
dialog --title "$(translate 'No Images Found')" \
--msgbox "$(translate 'Could not find any directory containing disk images')\n\n$(translate 'Make sure there is at least one file with extension .img, .qcow2, .vmdk or .raw')" 15 60
exit 1
fi
# Display initial message
whiptail --title "$(translate 'Import Disk Image')" --msgbox "$(translate 'Make sure the disk images you want to import are located in:')\n\n$IMAGES_DIR\n\n$(translate 'Supported formats: .img, .qcow2, .vmdk.')" 15 60
IMAGES=$(ls -A "$IMAGES_DIR" | grep -E "\.(img|qcow2|vmdk|raw)$")
if [ -z "$IMAGES" ]; then
dialog --title "$(translate 'No Disk Images Found')" \
--msgbox "$(translate 'No compatible disk images found in:')\n\n$IMAGES_DIR\n\n$(translate 'Supported formats: .img, .qcow2, .vmdk, .raw')" 15 60
exit 1
fi
# 1. Select VM
@@ -78,7 +103,7 @@ msg_ok "$(translate 'VM list obtained')"
VMID=$(whiptail --title "$(translate 'Select VM')" --menu "$(translate 'Select the VM where you want to import the disk image:')" 15 60 8 $VM_LIST 3>&1 1>&2 2>&3)
if [ -z "$VMID" ]; then
# msg_error "$(translate 'No VM selected')"
exit 1
fi
@@ -93,7 +118,7 @@ if [ -z "$STORAGE_LIST" ]; then
fi
msg_ok "$(translate 'Storage volumes obtained')"
# Create an array of storage options for whiptail
STORAGE_OPTIONS=()
while read -r storage; do
STORAGE_OPTIONS+=("$storage" "")
@@ -102,7 +127,7 @@ done <<< "$STORAGE_LIST"
STORAGE=$(whiptail --title "$(translate 'Select Storage')" --menu "$(translate 'Select the storage volume for disk import:')" 15 60 8 "${STORAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3)
if [ -z "$STORAGE" ]; then
# msg_error "$(translate 'No storage selected')"
exit 1
fi
@@ -124,17 +149,19 @@ done <<< "$IMAGES"
SELECTED_IMAGES=$(whiptail --title "$(translate 'Select Disk Images')" --checklist "$(translate 'Select the disk images to import:')" 20 60 10 "${IMAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3)
if [ -z "$SELECTED_IMAGES" ]; then
# msg_error "$(translate 'No images selected')"
exit 1
fi
# 4. Import each selected image
for IMAGE in $SELECTED_IMAGES; do
# Remove quotes from selected image
IMAGE=$(echo "$IMAGE" | tr -d '"')
# 5. Select interface type for each image
INTERFACE=$(whiptail --title "$(translate 'Interface Type')" --menu "$(translate 'Select the interface type for the image:') $IMAGE" 15 40 4 \
"sata" "SATA" \
"scsi" "SCSI" \
@@ -148,57 +175,63 @@ for IMAGE in $SELECTED_IMAGES; do
FULL_PATH="$IMAGES_DIR/$IMAGE"
# Show initial message
msg_info "$(translate 'Importing image:')"
# Temporary file to capture the imported disk
TEMP_DISK_FILE=$(mktemp)
# Execute the command and process its output in real-time
qm importdisk "$VMID" "$FULL_PATH" "$STORAGE" 2>&1 | while read -r line; do
if [[ "$line" =~ transferred ]]; then
# Extract the progress percentage
PERCENT=$(echo "$line" | grep -oP "\(\d+\.\d+%\)" | tr -d '()%')
# Show progress with custom format without translation
echo -ne "\r${TAB}${YW}-$(translate 'Importing image:') $IMAGE-${CL} ${PERCENT}%"
PERCENT=$(echo "$line" | grep -oP "\d+\.\d+(?=%)")
echo -ne "\r${TAB}${BL}-$(translate 'Importing image:') $IMAGE-${CL} ${PERCENT}%"
elif [[ "$line" =~ successfully\ imported\ disk ]]; then
# Extract the imported disk name and save it to the temporary file
echo "$line" | grep -oP "(?<=successfully imported disk ').*(?=')" > "$TEMP_DISK_FILE"
fi
done
echo -ne "\n"
IMPORT_STATUS=${PIPESTATUS[0]} # Capture the exit status of the main command
IMPORT_STATUS=${PIPESTATUS[0]}
if [ $IMPORT_STATUS -eq 0 ]; then
msg_ok "$(translate 'Image imported successfully')"
# Read the imported disk from the temporary file
IMPORTED_DISK=$(cat "$TEMP_DISK_FILE")
rm -f "$TEMP_DISK_FILE" # Delete the temporary file
rm -f "$TEMP_DISK_FILE"
if [ -z "$IMPORTED_DISK" ]; then
STORAGE_TYPE=$(pvesm status -storage "$STORAGE" | awk 'NR>1 {print $2}')
if [[ "$STORAGE_TYPE" == "btrfs" || "$STORAGE_TYPE" == "dir" || "$STORAGE_TYPE" == "nfs" ]]; then
UNUSED_LINE=$(qm config "$VMID" | grep -E '^unused[0-9]+:')
IMPORTED_ID=$(echo "$UNUSED_LINE" | cut -d: -f1)
IMPORTED_DISK=$(echo "$UNUSED_LINE" | cut -d: -f2- | xargs)
else
IMPORTED_DISK=$(qm config "$VMID" | grep -E 'unused[0-9]+' | tail -1 | cut -d: -f2- | xargs)
IMPORTED_ID=$(qm config "$VMID" | grep -E 'unused[0-9]+' | tail -1 | cut -d: -f1)
fi
fi
if [ -n "$IMPORTED_DISK" ]; then
# Find the next available disk slot
EXISTING_DISKS=$(qm config "$VMID" | grep -oP "${INTERFACE}\d+" | sort -n)
if [ -z "$EXISTING_DISKS" ]; then
# If there are no existing disks, start from 0
NEXT_SLOT=0
else
# If there are existing disks, take the last one and add 1
LAST_SLOT=$(echo "$EXISTING_DISKS" | tail -n1 | sed "s/${INTERFACE}//")
NEXT_SLOT=$((LAST_SLOT + 1))
fi
# Ask if SSD emulation is desired (only for non-VirtIO interfaces)
if [ "$INTERFACE" != "virtio" ]; then
if (whiptail --title "$(translate 'SSD Emulation')" --yesno "$(translate 'Do you want to use SSD emulation for this disk?')" 10 60); then
SSD_OPTION=",ssd=1"
@@ -209,14 +242,18 @@ for IMAGE in $SELECTED_IMAGES; do
SSD_OPTION=""
fi
msg_info "$(translate 'Configuring disk')"
# Configure the disk in the VM
if qm set "$VMID" --${INTERFACE}${NEXT_SLOT} "$IMPORTED_DISK${SSD_OPTION}" &>/dev/null; then
msg_ok "$(translate 'Image') $IMAGE $(translate 'configured as') ${INTERFACE}${NEXT_SLOT}"
# Ask if the disk should be bootable
if [[ -n "$IMPORTED_ID" ]]; then
qm set "$VMID" -delete "$IMPORTED_ID" >/dev/null 2>&1
fi
if (whiptail --title "$(translate 'Make Bootable')" --yesno "$(translate 'Do you want to make this disk bootable?')" 10 60); then
msg_info "$(translate 'Configuring disk as bootable')"
@@ -228,14 +265,22 @@ for IMAGE in $SELECTED_IMAGES; do
fi
else
msg_error "$(translate 'Could not configure disk') ${INTERFACE}${NEXT_SLOT} $(translate 'for VM') $VMID"
echo "DEBUG: Tried to configure: --${INTERFACE}${NEXT_SLOT} \"$IMPORTED_DISK${SSD_OPTION}\""
echo "DEBUG: VM config after import:"
qm config "$VMID" | grep -E "(unused|${INTERFACE})"
fi
else
msg_error "$(translate 'Could not find the imported disk')"
echo "DEBUG: VM config after import:"
qm config "$VMID"
fi
else
msg_error "$(translate 'Could not import') $IMAGE"
fi
done
msg_ok "$(translate 'All selected images have been processed')"
sleep 2
msg_success "$(translate "Press Enter to return to menu...")"
read -r

View File

@@ -0,0 +1,446 @@
#!/bin/bash
# ==========================================================
# ProxMenu - Mount independent disk on Proxmox host
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT
# Version : 1.3-dialog
# Last Updated: 13/12/2024
# ==========================================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
mount_disk_host_bk() {
get_disk_info() {
local disk=$1
MODEL=$(lsblk -dn -o MODEL "$disk" | xargs)
SIZE=$(lsblk -dn -o SIZE "$disk" | xargs)
echo "$MODEL" "$SIZE"
}
is_usb_disk() {
local disk=$1
local disk_name=$(basename "$disk")
if readlink -f "/sys/block/$disk_name/device" 2>/dev/null | grep -q "usb"; then
return 0
fi
if udevadm info --query=property --name="$disk" 2>/dev/null | grep -q "ID_BUS=usb"; then
return 0
fi
return 1
}
is_system_disk() {
local disk=$1
local disk_name=$(basename "$disk")
local system_mounts=$(df -h | grep -E '^\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(/|/boot|/usr|/var|/home)$' | awk '{print $1}')
for mount_dev in $system_mounts; do
local mount_disk=""
if [[ "$mount_dev" =~ ^/dev/mapper/ ]]; then
local vg_name=$(lvs --noheadings -o vg_name "$mount_dev" 2>/dev/null | xargs)
if [[ -n "$vg_name" ]]; then
local pvs_list=$(pvs --noheadings -o pv_name -S vg_name="$vg_name" 2>/dev/null | xargs)
for pv in $pvs_list; do
if [[ -n "$pv" && -e "$pv" ]]; then
mount_disk=$(lsblk -no PKNAME "$pv" 2>/dev/null)
if [[ -n "$mount_disk" && "/dev/$mount_disk" == "$disk" ]]; then
return 0
fi
fi
done
fi
elif [[ "$mount_dev" =~ ^/dev/[hsv]d[a-z][0-9]* || "$mount_dev" =~ ^/dev/nvme[0-9]+n[0-9]+p[0-9]+ ]]; then
mount_disk=$(lsblk -no PKNAME "$mount_dev" 2>/dev/null)
if [[ -n "$mount_disk" && "/dev/$mount_disk" == "$disk" ]]; then
return 0
fi
fi
done
local fs_type=$(lsblk -no FSTYPE "$disk" 2>/dev/null | head -1)
if [[ "$fs_type" == "btrfs" ]]; then
local temp_mount=$(mktemp -d)
if mount -o ro "$disk" "$temp_mount" 2>/dev/null; then
if btrfs subvolume list "$temp_mount" 2>/dev/null | grep -qE '(@|@home|@var|@boot|@root|root)'; then
umount "$temp_mount" 2>/dev/null
rmdir "$temp_mount" 2>/dev/null
return 0
fi
umount "$temp_mount" 2>/dev/null
fi
rmdir "$temp_mount" 2>/dev/null
while read -r part; do
if [[ -n "$part" ]]; then
local part_fs=$(lsblk -no FSTYPE "/dev/$part" 2>/dev/null)
if [[ "$part_fs" == "btrfs" ]]; then
local mount_point=$(lsblk -no MOUNTPOINT "/dev/$part" 2>/dev/null)
if [[ "$mount_point" == "/" || "$mount_point" == "/boot" || "$mount_point" == "/home" || "$mount_point" == "/var" ]]; then
return 0
fi
fi
fi
done < <(lsblk -ln -o NAME "$disk" | tail -n +2)
fi
local disk_uuid=$(blkid -s UUID -o value "$disk" 2>/dev/null)
local part_uuids=()
while read -r part; do
if [[ -n "$part" ]]; then
local uuid=$(blkid -s UUID -o value "/dev/$part" 2>/dev/null)
if [[ -n "$uuid" ]]; then
part_uuids+=("$uuid")
fi
fi
done < <(lsblk -ln -o NAME "$disk" | tail -n +2)
for uuid in "${part_uuids[@]}" "$disk_uuid"; do
if [[ -n "$uuid" ]] && grep -q "UUID=$uuid" /etc/fstab; then
local mount_point=$(grep "UUID=$uuid" /etc/fstab | awk '{print $2}')
if [[ "$mount_point" == "/" || "$mount_point" == "/boot" || "$mount_point" == "/home" || "$mount_point" == "/var" ]]; then
return 0
fi
fi
done
if grep -q "$disk" /etc/fstab; then
local mount_point=$(grep "$disk" /etc/fstab | awk '{print $2}')
if [[ "$mount_point" == "/" || "$mount_point" == "/boot" || "$mount_point" == "/home" || "$mount_point" == "/var" ]]; then
return 0
fi
fi
local disk_count=$(lsblk -dn -e 7,11 -o PATH | wc -l)
if [[ "$disk_count" -eq 1 ]]; then
return 0
fi
return 1
}
msg_info "$(translate "Detecting available disks...")"
USED_DISKS=$(lsblk -n -o PKNAME,TYPE | grep 'lvm' | awk '{print "/dev/" $1}')
MOUNTED_DISKS=$(lsblk -ln -o NAME,MOUNTPOINT | awk '$2!="" {print "/dev/" $1}')
ZFS_DISKS=""
ZFS_RAW=$(zpool list -v -H 2>/dev/null | awk '{print $1}' | grep -v '^NAME$' | grep -v '^-' | grep -v '^mirror')
for entry in $ZFS_RAW; do
path=""
if [[ "$entry" == wwn-* || "$entry" == ata-* ]]; then
if [ -e "/dev/disk/by-id/$entry" ]; then
path=$(readlink -f "/dev/disk/by-id/$entry")
fi
elif [[ "$entry" == /dev/* ]]; then
path="$entry"
fi
if [ -n "$path" ]; then
base_disk=$(lsblk -no PKNAME "$path" 2>/dev/null)
if [ -n "$base_disk" ]; then
ZFS_DISKS+="/dev/$base_disk"$'\n'
fi
fi
done
ZFS_DISKS=$(echo "$ZFS_DISKS" | sort -u)
LVM_DEVICES=$(
pvs --noheadings -o pv_name 2> >(grep -v 'File descriptor .* leaked') |
while read -r dev; do
[[ -n "$dev" && -e "$dev" ]] && readlink -f "$dev"
done | sort -u
)
FREE_DISKS=()
while read -r DISK; do
[[ "$DISK" =~ /dev/zd ]] && continue
INFO=($(get_disk_info "$DISK"))
MODEL="${INFO[@]::${#INFO[@]}-1}"
SIZE="${INFO[-1]}"
LABEL=""
SHOW_DISK=true
IS_MOUNTED=false
IS_RAID=false
IS_ZFS=false
IS_LVM=false
IS_SYSTEM=false
IS_USB=false
if is_system_disk "$DISK"; then
IS_SYSTEM=true
fi
if is_usb_disk "$DISK"; then
IS_USB=true
fi
while read -r part fstype; do
[[ "$fstype" == "zfs_member" ]] && IS_ZFS=true
[[ "$fstype" == "linux_raid_member" ]] && IS_RAID=true
[[ "$fstype" == "LVM2_member" ]] && IS_LVM=true
if grep -q "/dev/$part" <<< "$MOUNTED_DISKS"; then
IS_MOUNTED=true
fi
done < <(lsblk -ln -o NAME,FSTYPE "$DISK" | tail -n +2)
REAL_PATH=""
if [[ -n "$DISK" && -e "$DISK" ]]; then
REAL_PATH=$(readlink -f "$DISK")
fi
if [[ -n "$REAL_PATH" ]] && echo "$LVM_DEVICES" | grep -qFx "$REAL_PATH"; then
IS_MOUNTED=true
fi
USED_BY=""
REAL_PATH=""
if [[ -n "$DISK" && -e "$DISK" ]]; then
REAL_PATH=$(readlink -f "$DISK")
fi
CONFIG_DATA=$(grep -vE '^\s*#' /etc/pve/qemu-server/*.conf /etc/pve/lxc/*.conf 2>/dev/null)
if grep -Fq "$REAL_PATH" <<< "$CONFIG_DATA"; then
USED_BY="$(translate "In use")"
else
for SYMLINK in /dev/disk/by-id/*; do
[[ -e "$SYMLINK" ]] || continue
if [[ "$(readlink -f "$SYMLINK")" == "$REAL_PATH" ]]; then
if grep -Fq "$SYMLINK" <<< "$CONFIG_DATA"; then
USED_BY="$(translate "In use")"
break
fi
fi
done
fi
if $IS_RAID && grep -q "$DISK" <<< "$(cat /proc/mdstat)"; then
if grep -q "active raid" /proc/mdstat; then
SHOW_DISK=false
fi
fi
if $IS_ZFS; then SHOW_DISK=false; fi
if $IS_MOUNTED; then SHOW_DISK=false; fi
if $IS_SYSTEM; then SHOW_DISK=false; fi
if $SHOW_DISK; then
[[ -n "$USED_BY" ]] && LABEL+=" [$USED_BY]"
[[ "$IS_RAID" == true ]] && LABEL+=" ⚠ RAID"
[[ "$IS_LVM" == true ]] && LABEL+=" ⚠ LVM"
[[ "$IS_ZFS" == true ]] && LABEL+=" ⚠ ZFS"
if $IS_USB; then
LABEL+=" USB"
else
LABEL+=" $(translate "Internal")"
fi
DESCRIPTION=$(printf "%-30s %10s%s" "$MODEL" "$SIZE" "$LABEL")
FREE_DISKS+=("$DISK" "$DESCRIPTION" "off")
fi
done < <(lsblk -dn -e 7,11 -o PATH)
if [ "${#FREE_DISKS[@]}" -eq 0 ]; then
dialog --title "$(translate "Error")" --msgbox "$(translate "No available disks found on the host.")" 8 60
clear
exit 1
fi
msg_ok "$(translate "Available disks detected.")"
# Building the array for dialog (format: tag item on/off tag item on/off...)
DLG_LIST=()
for ((i=0; i<${#FREE_DISKS[@]}; i+=3)); do
DLG_LIST+=("${FREE_DISKS[i]}" "${FREE_DISKS[i+1]}" "${FREE_DISKS[i+2]}")
done
SELECTED=$(dialog --clear --backtitle "ProxMenux" --title "$(translate "Select Disk")" \
--radiolist "\n$(translate "Select the disk you want to mount on the host:")" 20 90 10 \
"${DLG_LIST[@]}" 2>&1 >/dev/tty)
if [ -z "$SELECTED" ]; then
dialog --title "$(translate "Error")" --msgbox "$(translate "No disk was selected.")" 8 50
clear
exit 1
fi
msg_ok "$(translate "Disk selected successfully:") $SELECTED"
# ------------------- Partitions and formatting ------------------------
PARTITION=$(lsblk -rno NAME "$SELECTED" | awk -v disk="$(basename "$SELECTED")" '$1 != disk {print $1; exit}')
SKIP_FORMAT=false
DEFAULT_MOUNT="/mnt/backup"
if [ -n "$PARTITION" ]; then
PARTITION="/dev/$PARTITION"
CURRENT_FS=$(lsblk -no FSTYPE "$PARTITION" | xargs)
if [[ "$CURRENT_FS" == "ext4" || "$CURRENT_FS" == "xfs" || "$CURRENT_FS" == "btrfs" ]]; then
SKIP_FORMAT=true
msg_ok "$(translate "Detected existing filesystem") $CURRENT_FS $(translate "on") $PARTITION."
else
dialog --title "$(translate "Unsupported Filesystem")" --yesno \
"$(translate "The partition") $PARTITION $(translate "has an unsupported filesystem ($CURRENT_FS).\nDo you want to format it?")" 10 70
if [ $? -ne 0 ]; then exit 0; fi
fi
else
CURRENT_FS=$(lsblk -no FSTYPE "$SELECTED" | xargs)
if [[ "$CURRENT_FS" == "ext4" || "$CURRENT_FS" == "xfs" || "$CURRENT_FS" == "btrfs" ]]; then
SKIP_FORMAT=true
PARTITION="$SELECTED"
msg_ok "$(translate "Detected filesystem") $CURRENT_FS $(translate "directly on disk") $SELECTED."
else
dialog --title "$(translate "No Valid Partitions")" --yesno \
"$(translate "The disk has no partitions and no valid filesystem. Do you want to create a new partition and format it?")" 10 70
if [ $? -ne 0 ]; then exit 0; fi
echo -e "$(translate "Creating partition table and partition...")"
parted -s "$SELECTED" mklabel gpt
parted -s "$SELECTED" mkpart primary 0% 100%
sleep 2
partprobe "$SELECTED"
sleep 2
PARTITION=$(lsblk -rno NAME "$SELECTED" | awk -v disk="$(basename "$SELECTED")" '$1 != disk {print $1; exit}')
if [ -n "$PARTITION" ]; then
PARTITION="/dev/$PARTITION"
else
dialog --title "$(translate "Partition Error")" --msgbox \
"$(translate "Failed to create partition on disk") $SELECTED." 8 70
exit 1
fi
fi
fi
if [ "$SKIP_FORMAT" != true ]; then
FORMAT_TYPE=$(dialog --title "$(translate "Select Format Type")" --menu \
"$(translate "Select the filesystem type for") $PARTITION:" 15 60 5 \
"ext4" "$(translate "Extended Filesystem 4 (recommended)")" \
"xfs" "XFS" \
"btrfs" "Btrfs" 2>&1 >/dev/tty)
if [ -z "$FORMAT_TYPE" ]; then
dialog --title "$(translate "Format Cancelled")" --msgbox \
"$(translate "Format operation cancelled. The disk will not be added.")" 8 60
exit 0
fi
dialog --title "$(translate "WARNING")" --yesno \
"$(translate "WARNING: This operation will FORMAT the disk") $PARTITION $(translate "with") $FORMAT_TYPE.\n\n$(translate "ALL DATA ON THIS DISK WILL BE PERMANENTLY LOST!")\n\n$(translate "Are you sure you want to continue")" 15 70
if [ $? -ne 0 ]; then exit 0; fi
echo -e "$(translate "Formatting partition") $PARTITION $(translate "with") $FORMAT_TYPE..."
case "$FORMAT_TYPE" in
"ext4") mkfs.ext4 -F "$PARTITION" ;;
"xfs") mkfs.xfs -f "$PARTITION" ;;
"btrfs") mkfs.btrfs -f "$PARTITION" ;;
esac
if [ $? -ne 0 ]; then
cleanup
dialog --title "$(translate "Format Failed")" --msgbox \
"$(translate "Failed to format partition") $PARTITION $(translate "with") $FORMAT_TYPE." 12 70
exit 1
else
msg_ok "$(translate "Partition") $PARTITION $(translate "successfully formatted with") $FORMAT_TYPE."
partprobe "$SELECTED"
sleep 2
fi
fi
# ------------------- Mount point and permissions -------------------
MOUNT_POINT=$(dialog --title "$(translate "Mount Point")" \
--inputbox "$(translate "Enter the mount point for the disk (e.g., /mnt/backup):")" \
10 60 "$DEFAULT_MOUNT" 2>&1 >/dev/tty)
if [ -z "$MOUNT_POINT" ]; then
dialog --title "$(translate "Error")" --msgbox "$(translate "No mount point was specified.")" 8 40
exit 1
fi
msg_ok "$(translate "Mount point specified:") $MOUNT_POINT"
mkdir -p "$MOUNT_POINT"
UUID=$(blkid -s UUID -o value "$PARTITION")
FS_TYPE=$(lsblk -no FSTYPE "$PARTITION" | xargs)
FSTAB_ENTRY="UUID=$UUID $MOUNT_POINT $FS_TYPE defaults 0 0"
if grep -q "UUID=$UUID" /etc/fstab; then
sed -i "s|^.*UUID=$UUID.*|$FSTAB_ENTRY|" /etc/fstab
msg_ok "$(translate "fstab entry updated for") $UUID"
else
echo "$FSTAB_ENTRY" >> /etc/fstab
msg_ok "$(translate "fstab entry added for") $UUID"
fi
mount "$MOUNT_POINT" 2> >(grep -v "systemd still uses")
if [ $? -eq 0 ]; then
if ! getent group sharedfiles >/dev/null; then
groupadd sharedfiles
msg_ok "$(translate "Group 'sharedfiles' created")"
else
msg_ok "$(translate "Group 'sharedfiles' already exists")"
fi
chown root:sharedfiles "$MOUNT_POINT"
chmod 2775 "$MOUNT_POINT"
dialog --title "$(translate "Success")" --msgbox "$(translate "The disk has been successfully mounted at") $MOUNT_POINT" 8 60
echo "$MOUNT_POINT" > /usr/local/share/proxmenux/last_backup_mount.txt
msg_ok "$(translate "Disk mounted at") $MOUNT_POINT"
msg_success "$(translate "Press Enter to return to menu...")"
read -r
else
dialog --title "$(translate "Mount Error")" --msgbox "$(translate "Failed to mount the disk at") $MOUNT_POINT" 8 60
msg_success "$(translate "Press Enter to return to menu...")"
read -r
exit 1
fi
}

240
scripts/test/Iso.sh Normal file
View File

@@ -0,0 +1,240 @@
#!/usr/bin/env bash
# ==========================================================
# ProxMenux - UUP Dump ISO Creator
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 07/05/2025
# ==========================================================
# Description:
# This script is part of the ProxMenux tools for Proxmox VE.
# It allows downloading and converting official Windows ISO images
# from UUP Dump using a shared link (with ID, pack, and edition).
#
# Key features:
# - Automatically installs and verifies required dependencies (aria2c, cabextract, wimlib-imagex…)
# - Downloads the selected Windows edition from UUP Dump using aria2
# - Converts the downloaded files into a bootable ISO
# - Stores the resulting ISO in the default template path (/var/lib/vz/template/iso)
# - Provides a graphical prompt via whiptail for user-friendly usage
#
# This tool simplifies the creation of official Windows ISOs
# for use in virtual machines within Proxmox VE.
# ==========================================================
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
clear
show_proxmenux_logo
# ==========================================================
detect_iso_dir() {
for store in $(pvesm status -content iso | awk 'NR>1 {print $1}'); do
for ext in iso img; do
volid=$(pvesm list "$store" --content iso | awk -v ext="$ext" 'NR>1 && $2 ~ ext {print $1; exit}')
if [[ -n "$volid" ]]; then
path=$(pvesm path "$volid" 2>/dev/null)
dir=$(dirname "$path")
[[ -d "$dir" ]] && echo "$dir" && return 0
fi
done
done
if [[ -d /var/lib/vz/template/iso ]]; then
echo "/var/lib/vz/template/iso"
return 0
fi
return 1
}
function run_uupdump_creator() {
local DEPS=(curl aria2 cabextract wimtools genisoimage chntpw)
local CMDS=(curl aria2c cabextract wimlib-imagex genisoimage chntpw)
local MISSING=()
local FAILED=()
for i in "${!CMDS[@]}"; do
if ! command -v "${CMDS[$i]}" &>/dev/null; then
MISSING+=("${DEPS[$i]}")
fi
done
if [[ ${#MISSING[@]} -gt 0 ]]; then
msg_info "$(translate "Installing dependencies: ${MISSING[*]}")"
apt-get update -qq >/dev/null 2>&1
if ! apt-get install -y "${MISSING[@]}" >/dev/null 2>&1; then
msg_error "$(translate "Failed to install: ${MISSING[*]}")"
exit 1
fi
fi
for i in "${!CMDS[@]}"; do
if ! command -v "${CMDS[$i]}" &>/dev/null; then
FAILED+=("${CMDS[$i]}")
fi
done
if [[ ${#FAILED[@]} -eq 0 ]]; then
msg_ok "$(translate "All dependencies installed and verified.")"
else
msg_error "$(translate "Missing commands after installation: ${FAILED[*]}")"
exit 1
fi
ISO_DIR=$(detect_iso_dir)
if [[ -z "$ISO_DIR" ]]; then
msg_error "$(translate "Could not determine a valid ISO storage directory.")"
exit 1
fi
mkdir -p "$ISO_DIR"
TMP_DIR=$(dialog --inputbox "Enter temporary folder path (default: /root/uup-temp):" 10 60 "/root/uup-temp" 3>&1 1>&2 2>&3)
if [[ $? -ne 0 || -z "$TMP_DIR" ]]; then
TMP_DIR="/root/uup-temp"
fi
OUT_DIR="$ISO_DIR"
CONVERTER="/root/uup-converter"
mkdir -p "$TMP_DIR" "$OUT_DIR"
cd "$TMP_DIR" || exit 1
UUP_URL=$(whiptail --inputbox "$(translate "Paste the UUP Dump URL here")" 10 90 3>&1 1>&2 2>&3)
if [[ $? -ne 0 || -z "$UUP_URL" ]]; then
msg_warn "$(translate "Cancelled by user or empty URL.")"
return 1
fi
if [[ ! "$UUP_URL" =~ id=.+\&pack=.+\&edition=.+ ]]; then
msg_error "$(translate "The URL does not contain the required parameters (id, pack, edition).")"
sleep 2
return 1
fi
BUILD_ID=$(echo "$UUP_URL" | grep -oP 'id=\K[^&]+')
LANG=$(echo "$UUP_URL" | grep -oP 'pack=\K[^&]+')
EDITION=$(echo "$UUP_URL" | grep -oP 'edition=\K[^&]+')
ARCH="amd64"
echo -e "\n${BGN}=============== UUP Dump Creator ===============${CL}"
echo -e " ${BGN}🆔 ID:${CL} ${DGN}$BUILD_ID${CL}"
echo -e " ${BGN}🌐 Language:${CL} ${DGN}$LANG${CL}"
echo -e " ${BGN}💿 Edition:${CL} ${DGN}$EDITION${CL}"
echo -e " ${BGN}🖥️ Architecture:${CL} ${DGN}$ARCH${CL}"
echo -e "${BGN}===============================================${CL}\n"
if [[ ! -f "$CONVERTER/convert.sh" ]]; then
echo "📦 $(translate "Downloading UUP converter...")"
mkdir -p "$CONVERTER"
cd "$CONVERTER" || exit 1
wget -q https://git.uupdump.net/uup-dump/converter/archive/refs/heads/master.tar.gz -O converter.tar.gz
tar -xzf converter.tar.gz --strip-components=1
chmod +x convert.sh
cd "$TMP_DIR" || exit 1
fi
cat > uup_download_linux.sh <<EOF
#!/bin/bash
mkdir -p files
echo "https://git.uupdump.net/uup-dump/converter/archive/refs/heads/master.tar.gz" > files/converter_multi
for prog in aria2c cabextract wimlib-imagex chntpw; do
which \$prog &>/dev/null || { echo "\$prog not found."; exit 1; }
done
which genisoimage &>/dev/null || which mkisofs &>/dev/null || { echo "genisoimage/mkisofs not found."; exit 1; }
destDir="UUPs"
tempScript="aria2_script.\$RANDOM.txt"
aria2c --no-conf --console-log-level=warn --log-level=info --log="aria2_download.log" \
-x16 -s16 -j2 --allow-overwrite=true --auto-file-renaming=false -d"files" -i"files/converter_multi" || exit 1
aria2c --no-conf --console-log-level=warn --log-level=info --log="aria2_download.log" \
-o"\$tempScript" --allow-overwrite=true --auto-file-renaming=false \
"https://uupdump.net/get.php?id=$BUILD_ID&pack=$LANG&edition=$EDITION&aria2=2" || exit 1
grep '#UUPDUMP_ERROR:' "\$tempScript" && { echo "❌ Error generating UUP download list."; exit 1; }
aria2c --no-conf --console-log-level=warn --log-level=info --log="aria2_download.log" \
-x16 -s16 -j5 -c -R -d"\$destDir" -i"\$tempScript" || exit 1
EOF
chmod +x uup_download_linux.sh
# ==========================
./uup_download_linux.sh
# ==========================
UUP_FOLDER=$(find "$TMP_DIR" -type d -name "UUPs" | head -n1)
[[ -z "$UUP_FOLDER" ]] && msg_error "$(translate "No UUP folder found.")" && exit 1
echo -e "\n${GN}=======================================${CL}"
echo -e " 💿 ${GN}Starting ISO conversion...${CL}"
echo -e "${GN}=======================================${CL}\n"
"$CONVERTER/convert.sh" wim "$UUP_FOLDER" 1
ISO_FILE=$(find "$TMP_DIR" "$CONVERTER" "$UUP_FOLDER" -maxdepth 1 -iname "*.iso" | head -n1)
if [[ -f "$ISO_FILE" ]]; then
mv "$ISO_FILE" "$OUT_DIR/"
msg_ok "$(translate "ISO created successfully:") $OUT_DIR/$(basename "$ISO_FILE")"
msg_ok "$(translate "Cleaning temporary files...")"
rm -rf "$TMP_DIR" "$CONVERTER"
export OS_TYPE="windows"
export LANGUAGE=C
export LANG=C
export LC_ALL=C
load_language
initialize_cache
msg_success "$(translate "Press Enter to return to menu...")"
read -r
else
msg_warn "$(translate "No ISO was generated.")"
rm -rf "$TMP_DIR" "$CONVERTER"
export LANGUAGE=C
export LANG=C
export LC_ALL=C
load_language
initialize_cache
msg_success "$(translate "Press Enter to return to menu...")"
read -r
return 1
fi
}

View File

@@ -0,0 +1,327 @@
#!/bin/bash
# ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.1
# Last Updated: 04/06/2025
# ==========================================================
# Description:
# This script provides a simple and efficient way to access and execute Proxmox VE scripts
# from the Community Scripts project (https://community-scripts.github.io/ProxmoxVE/).
#
# It serves as a convenient tool to run key automation scripts that simplify system management,
# continuing the great work and legacy of tteck in making Proxmox VE more accessible.
# A streamlined solution for executing must-have tools in Proxmox VE.
# ==========================================================
# Configuration ============================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
# ==========================================================
HELPERS_JSON_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/refs/heads/main/json/helpers_cache.json"
METADATA_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/frontend/public/json/metadata.json"
for cmd in curl jq dialog; do
if ! command -v "$cmd" >/dev/null; then
echo "Missing required command: $cmd"
exit 1
fi
done
CACHE_JSON=$(curl -s "$HELPERS_JSON_URL")
META_JSON=$(curl -s "$METADATA_URL")
declare -A CATEGORY_NAMES
while read -r id name; do
CATEGORY_NAMES[$id]="$name"
done < <(echo "$META_JSON" | jq -r '.categories[] | "\(.id)\t\(.name)"')
declare -A CATEGORY_COUNT
for id in $(echo "$CACHE_JSON" | jq -r '.[].categories[]'); do
((CATEGORY_COUNT[$id]++))
done
get_type_label() {
local type="$1"
case "$type" in
ct) echo $'\Z1LXC\Zn' ;;
vm) echo $'\Z4VM\Zn' ;;
pve) echo $'\Z3PVE\Zn' ;;
addon) echo $'\Z2ADDON\Zn' ;;
*) echo $'\Z7GEN\Zn' ;;
esac
}
download_script() {
local url="$1"
local fallback_pve="${url/misc\/tools\/pve}"
local fallback_addon="${url/misc\/tools\/addon}"
local fallback_copydata="${url/misc\/tools\/copy-data}"
if curl --silent --head --fail "$url" >/dev/null; then
bash <(curl -s "$url")
elif curl --silent --head --fail "$fallback_pve" >/dev/null; then
bash <(curl -s "$fallback_pve")
elif curl --silent --head --fail "$fallback_addon" >/dev/null; then
bash <(curl -s "$fallback_addon")
elif curl --silent --head --fail "$fallback_copydata" >/dev/null; then
bash <(curl -s "$fallback_copydata")
else
dialog --title "Helper Scripts" --msgbox "Error: Failed to download the script." 12 70
fi
}
RETURN_TO_MAIN=false
format_credentials() {
local script_info="$1"
local credentials_info=""
local has_credentials
has_credentials=$(echo "$script_info" | base64 --decode | jq -r 'has("default_credentials")')
if [[ "$has_credentials" == "true" ]]; then
local username password
username=$(echo "$script_info" | base64 --decode | jq -r '.default_credentials.username // empty')
password=$(echo "$script_info" | base64 --decode | jq -r '.default_credentials.password // empty')
if [[ -n "$username" && -n "$password" ]]; then
credentials_info="Username: $username | Password: $password"
elif [[ -n "$username" ]]; then
credentials_info="Username: $username"
elif [[ -n "$password" ]]; then
credentials_info="Password: $password"
fi
fi
echo "$credentials_info"
}
run_script_by_slug() {
local slug="$1"
local script_info
script_info=$(echo "$CACHE_JSON" | jq -r --arg slug "$slug" '.[] | select(.slug == $slug) | @base64')
decode() {
echo "$1" | base64 --decode | jq -r "$2"
}
local name desc script_url notes
name=$(decode "$script_info" ".name")
desc=$(decode "$script_info" ".desc")
script_url=$(decode "$script_info" ".script_url")
notes=$(decode "$script_info" ".notes | join(\"\n\")")
local notes_dialog=""
if [[ -n "$notes" ]]; then
while IFS= read -r line; do
notes_dialog+="$line\n"
done <<< "$notes"
notes_dialog="${notes_dialog%\\n}"
fi
local credentials
credentials=$(format_credentials "$script_info")
local msg="\Zb\Z4Descripción:\Zn\n$desc"
[[ -n "$notes_dialog" ]] && msg+="\n\n\Zb\Z4Notes:\Zn\n$notes_dialog"
[[ -n "$credentials" ]] && msg+="\n\n\Zb\Z4Default Credentials:\Zn\n$credentials"
dialog --clear --colors --backtitle "ProxMenux" --title "$name" --yesno "$msg\n\nExecute this script?" 22 85
if [[ $? -eq 0 ]]; then
download_script "$script_url"
echo
echo
if [[ -n "$desc" || -n "$notes" || -n "$credentials" ]]; then
echo -e "$TAB\e[1;36mScript Information:\e[0m"
if [[ -n "$notes" ]]; then
echo -e "$TAB\e[1;33mNotes:\e[0m"
while IFS= read -r line; do
[[ -z "$line" ]] && continue
echo -e "$TAB$line"
done <<< "$notes"
echo
fi
if [[ -n "$credentials" ]]; then
echo -e "$TAB\e[1;32mDefault Credentials:\e[0m"
echo "$TAB$credentials"
echo
fi
fi
msg_success "Press Enter to return to the main menu..."
read -r
RETURN_TO_MAIN=true
fi
}
search_and_filter_scripts() {
local search_term=""
while true; do
search_term=$(dialog --inputbox "Enter search term (leave empty to show all scripts):" \
8 65 "$search_term" 3>&1 1>&2 2>&3)
[[ $? -ne 0 ]] && return
local filtered_json
if [[ -z "$search_term" ]]; then
filtered_json="$CACHE_JSON"
else
local search_lower
search_lower=$(echo "$search_term" | tr '[:upper:]' '[:lower:]')
filtered_json=$(echo "$CACHE_JSON" | jq --arg term "$search_lower" '
[.[] | select(
(.name | ascii_downcase | contains($term)) or
(.desc | ascii_downcase | contains($term))
)]')
fi
local count
count=$(echo "$filtered_json" | jq length)
if [[ $count -eq 0 ]]; then
dialog --msgbox "No scripts found for: '$search_term'\n\nTry a different search term." 8 50
continue
fi
while true; do
declare -A index_to_slug
local menu_items=()
local i=1
while IFS=$'\t' read -r slug name type; do
index_to_slug[$i]="$slug"
local label
label=$(get_type_label "$type")
local padded_name
padded_name=$(printf "%-42s" "$name")
local entry="$padded_name $label"
menu_items+=("$i" "$entry")
((i++))
done < <(echo "$filtered_json" | jq -r '
sort_by(.name)[] | [.slug, .name, .type] | @tsv')
menu_items+=("" "")
menu_items+=("new_search" "New Search")
menu_items+=("show_all" "Show All Scripts")
local title="Search Results"
if [[ -n "$search_term" ]]; then
title="Search Results for: '$search_term' ($count found)"
else
title="All Available Scripts ($count total)"
fi
local selected
selected=$(dialog --colors --backtitle "ProxMenux" \
--title "$title" \
--menu "Select a script or action:" \
22 75 15 "${menu_items[@]}" 3>&1 1>&2 2>&3)
if [[ $? -ne 0 ]]; then
return
fi
case "$selected" in
"new_search")
break
;;
"show_all")
search_term=""
filtered_json="$CACHE_JSON"
count=$(echo "$filtered_json" | jq length)
continue
;;
"back"|"")
return
;;
*)
if [[ -n "${index_to_slug[$selected]}" ]]; then
run_script_by_slug "${index_to_slug[$selected]}"
[[ "$RETURN_TO_MAIN" == true ]] && { RETURN_TO_MAIN=false; return; }
fi
;;
esac
done
done
}
while true; do
MENU_ITEMS=()
MENU_ITEMS+=("search" "Search/Filter Scripts")
MENU_ITEMS+=("" "")
for id in $(printf "%s\n" "${!CATEGORY_COUNT[@]}" | sort -n); do
name="${CATEGORY_NAMES[$id]:-Category $id}"
count="${CATEGORY_COUNT[$id]}"
padded_name=$(printf "%-35s" "$name")
padded_count=$(printf "(%2d)" "$count")
MENU_ITEMS+=("$id" "$padded_name $padded_count")
done
SELECTED=$(dialog --backtitle "ProxMenux" --title "Proxmox VE Helper-Scripts" --menu \
"Select a category or search for scripts:" 20 70 14 \
"${MENU_ITEMS[@]}" 3>&1 1>&2 2>&3) || {
dialog --title "Proxmox VE Helper-Scripts" \
--msgbox "\n\n$(translate "Visit the website to discover more scripts, stay updated with the latest updates, and support the project:")\n\nhttps://community-scripts.github.io/ProxmoxVE" 15 70
clear
break
}
if [[ "$SELECTED" == "search" ]]; then
search_and_filter_scripts
continue
fi
while true; do
declare -A INDEX_TO_SLUG
SCRIPTS=()
i=1
while IFS=$'\t' read -r slug name type; do
INDEX_TO_SLUG[$i]="$slug"
label=$(get_type_label "$type")
padded_name=$(printf "%-42s" "$name")
entry="$padded_name $label"
SCRIPTS+=("$i" "$entry")
((i++))
done < <(echo "$CACHE_JSON" | jq -r --argjson id "$SELECTED" \
'[.[] | select(.categories | index($id)) | {slug, name, type}] | sort_by(.name)[] | [.slug, .name, .type] | @tsv')
SCRIPT_INDEX=$(dialog --colors --backtitle "ProxMenux" --title "Scripts in ${CATEGORY_NAMES[$SELECTED]}" --menu \
"Choose a script to execute:" 20 70 14 \
"${SCRIPTS[@]}" 3>&1 1>&2 2>&3) || break
SCRIPT_SELECTED="${INDEX_TO_SLUG[$SCRIPT_INDEX]}"
run_script_by_slug "$SCRIPT_SELECTED"
[[ "$RETURN_TO_MAIN" == true ]] && { RETURN_TO_MAIN=false; break; }
done
done

View File

@@ -1,128 +0,0 @@
#!/bin/bash
# ==========================================================
# ProxMenux - Uninstall Tools Menu for Proxmox
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 02/05/2025
# ==========================================================
# Description:
# This script provides a dynamic menu for uninstalling optional tools
# installed through ProxMenux on Proxmox Virtual Environment (VE).
# ==========================================================
# Configuration ============================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
RETURN_SCRIPT="$REPO_URL/scripts/menus/menu_post_install.sh"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
# ==========================================================
uninstall_fastfetch() {
if ! command -v fastfetch &>/dev/null; then
msg_warn "$(translate "Fastfetch is not installed.")"
return 0
fi
msg_info2 "$(translate "Uninstalling Fastfetch...")"
rm -f /usr/local/bin/fastfetch /usr/bin/fastfetch
rm -rf "$HOME/.config/fastfetch"
rm -rf /usr/local/share/fastfetch
sed -i '/fastfetch/d' "$HOME/.bashrc" "$HOME/.profile" 2>/dev/null
rm -f /etc/profile.d/fastfetch.sh /etc/update-motd.d/99-fastfetch
dpkg -r fastfetch &>/dev/null
msg_ok "$(translate "Fastfetch removed from system")"
msg_success "$(translate "You can reinstall it anytime from the post-installation script")"
msg_success "$(translate "Press Enter to return...")"
read -r
}
# ==========================================================
uninstall_figurine() {
if ! command -v figurine &>/dev/null; then
msg_warn "$(translate "Figurine is not installed.")"
return 0
fi
msg_info2 "$(translate "Uninstalling Figurine...")"
rm -f /usr/local/bin/figurine
rm -f /etc/profile.d/figurine.sh
sed -i '/figurine/d' "$HOME/.bashrc" "$HOME/.profile" 2>/dev/null
msg_ok "$(translate "Figurine removed from system")"
msg_success "$(translate "You can reinstall it anytime from the post-installation script")"
msg_success "$(translate "Press ENTER to continue...")"
read -r
}
# ==========================================================
show_uninstall_menu() {
local options=()
local index=1
declare -A uninstall_map
if command -v fastfetch >/dev/null 2>&1; then
options+=("$index" "$(translate "Uninstall") Fastfetch")
uninstall_map[$index]="uninstall_fastfetch"
index=$((index + 1))
fi
if command -v figurine >/dev/null 2>&1; then
options+=("$index" "$(translate "Uninstall") Figurine")
uninstall_map[$index]="uninstall_figurine"
index=$((index + 1))
fi
if [ ${#options[@]} -eq 0 ]; then
whiptail --title "ProxMenux" --msgbox "$(translate "No uninstallable tools detected.")" 10 60
return_to_menu
fi
local choice
choice=$(whiptail --title "$(translate "Uninstall Tools")" \
--menu "$(translate "Select a tool to uninstall:")" 15 60 6 \
"${options[@]}" 3>&1 1>&2 2>&3)
[ -z "$choice" ] && return_to_menu
local func="${uninstall_map[$choice]}"
if [[ -n "$func" ]]; then
"$func"
fi
return_to_menu
}
return_to_menu() {
# Descargar temporalmente el script
TEMP_SCRIPT=$(mktemp)
if curl --fail -s -o "$TEMP_SCRIPT" "$RETURN_SCRIPT"; then
bash "$TEMP_SCRIPT"
rm -f "$TEMP_SCRIPT"
else
msg_error "$(translate "Error: Could not return to menu. URL returned 404.")"
msg_info2 "$(translate "Please check the menu URL in the script.")"
read -r
exit 1
fi
}
show_uninstall_menu

View File

@@ -0,0 +1,504 @@
#!/bin/bash
# ==========================================================
# ProxMenu - A menu-driven script for Proxmox VE management
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 04/07/2025
# ==========================================================
# Description:
# This script safely updates your Proxmox VE system and underlying Debian packages
# through an interactive and automated process.
#
# Main features:
# - Repairs and optimizes APT repositories (Proxmox & Debian)
# - Removes duplicate or conflicting sources
# - Switches to the recommended 'no-subscription' Proxmox repository
# - Updates all Proxmox and Debian system packages
# - Installs essential packages if missing (e.g., zfsutils, chrony)
# - Checks for LVM and storage issues and repairs headers if needed
# - Removes conflicting time sync packages automatically
# - Performs a system cleanup after updating (autoremove, autoclean)
# - Provides a summary and prompts for reboot if necessary
#
# The goal of this script is to simplify and secure the update process for Proxmox,
# reduce manual intervention, and prevent common repository and package errors.
# ==========================================================
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
# ==========================================================
OS_CODENAME="$(grep "VERSION_CODENAME=" /etc/os-release | cut -d"=" -f 2 | xargs )"
if [ -z "$OS_CODENAME" ]; then
OS_CODENAME=$(lsb_release -cs 2>/dev/null || echo "bookworm")
fi
# ======================================================
# Auxiliary functions
# ======================================================
lvm_repair_check() {
msg_info "$(translate "Checking and repairing old LVM PV headers (if needed)...")"
pvs_output=$(LC_ALL=C pvs -v 2>&1 | grep "old PV header")
if [ -z "$pvs_output" ]; then
msg_ok "$(translate "No PVs with old headers found.")"
return
fi
declare -A vg_map
while read -r line; do
pv=$(echo "$line" | grep -o '/dev/[^ ]*')
vg=$(pvs -o vg_name --noheadings "$pv" | awk '{print $1}')
if [ -n "$vg" ]; then
vg_map["$vg"]=1
fi
done <<< "$pvs_output"
for vg in "${!vg_map[@]}"; do
msg_warn "$(translate "Old PV header(s) found in VG $vg. Updating metadata...")"
vgck --updatemetadata "$vg"
vgchange -ay "$vg"
if [ $? -ne 0 ]; then
msg_warn "$(translate "Metadata update failed for VG $vg. Review manually.")"
else
msg_ok "$(translate "Metadata updated successfully for VG $vg")"
fi
done
}
cleanup_duplicate_repos() {
local sources_file="/etc/apt/sources.list"
local temp_file=$(mktemp)
local cleaned_count=0
declare -A seen_repos
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ "$line" =~ ^[[:space:]]*# ]] || [[ -z "$line" ]]; then
echo "$line" >> "$temp_file"
continue
fi
if [[ "$line" =~ ^deb ]]; then
read -r _ url dist components <<< "$line"
local key="${url}_${dist}"
if [[ -v "seen_repos[$key]" ]]; then
echo "# $line" >> "$temp_file"
cleaned_count=$((cleaned_count + 1))
else
echo "$line" >> "$temp_file"
seen_repos[$key]="$components"
fi
else
echo "$line" >> "$temp_file"
fi
done < "$sources_file"
mv "$temp_file" "$sources_file"
chmod 644 "$sources_file"
local pve_files=(/etc/apt/sources.list.d/*proxmox*.list /etc/apt/sources.list.d/*pve*.list)
local pve_public_repo="/etc/apt/sources.list.d/pve-public-repo.list"
local pve_public_repo_exists=false
local pve_repo_count=0
if [ -f "$pve_public_repo" ] && grep -q "^deb.*pve-no-subscription" "$pve_public_repo"; then
pve_public_repo_exists=true
pve_repo_count=1
fi
for file in "${pve_files[@]}"; do
if [ -f "$file" ] && grep -q "^deb.*pve-no-subscription" "$file"; then
if ! $pve_public_repo_exists && [[ "$file" == "$pve_public_repo" ]]; then
sed -i 's/^# *deb/deb/' "$file"
pve_public_repo_exists=true
pve_repo_count=1
elif [[ "$file" != "$pve_public_repo" ]]; then
sed -i 's/^deb/# deb/' "$file"
cleaned_count=$((cleaned_count + 1))
fi
fi
done
if [ $cleaned_count -gt 0 ]; then
msg_ok "$(translate "Duplicate repositories cleaned: $cleaned_count")"
fi
apt update
}
apt_upgrade() {
local start_time=$(date +%s)
local log_file="/var/log/proxmox-update-$(date +%Y%m%d-%H%M%S).log"
local changes_made=false
clear
show_proxmenux_logo
echo -e
msg_title "$(translate "Proxmox system update")"
# ======================================================
# Basic checks
# ======================================================
# Check minimum disk space
local available_space=$(df /var/cache/apt/archives | awk 'NR==2 {print int($4/1024)}')
if [ "$available_space" -lt 1024 ]; then
msg_error "$(translate "Insufficient disk space. Available: ${available_space}MB")"
return 1
fi
# Check connectivity
if ! ping -c 1 download.proxmox.com >/dev/null 2>&1; then
msg_error "$(translate "Cannot reach Proxmox repositories")"
return 1
fi
# ======================================================
# Proxmox repository configuration
# ======================================================
# Disable enterprise Proxmox repository
if [ -f /etc/apt/sources.list.d/pve-enterprise.list ] && grep -q "^deb" /etc/apt/sources.list.d/pve-enterprise.list; then
msg_info "$(translate "Disabling enterprise Proxmox repository...")"
sed -i "s/^deb/#deb/g" /etc/apt/sources.list.d/pve-enterprise.list
msg_ok "$(translate "Enterprise Proxmox repository disabled")"
changes_made=true
fi
# Disable enterprise Ceph repository
if [ -f /etc/apt/sources.list.d/ceph.list ] && grep -q "^deb" /etc/apt/sources.list.d/ceph.list; then
msg_info "$(translate "Disabling enterprise Ceph repository...")"
sed -i "s/^deb/#deb/g" /etc/apt/sources.list.d/ceph.list
msg_ok "$(translate "Enterprise Ceph repository disabled")"
changes_made=true
fi
# Enable free public repository
if [ ! -f /etc/apt/sources.list.d/pve-public-repo.list ] || ! grep -q "pve-no-subscription" /etc/apt/sources.list.d/pve-public-repo.list; then
msg_info "$(translate "Enabling free public Proxmox repository...")"
echo "deb http://download.proxmox.com/debian/pve ${OS_CODENAME} pve-no-subscription" > /etc/apt/sources.list.d/pve-public-repo.list
msg_ok "$(translate "Free public Proxmox repository enabled")"
changes_made=true
fi
# ======================================================
# Debian repository configuration
# ======================================================
local sources_file="/etc/apt/sources.list"
local debian_changes=false
# Clean up malformed entries first
if grep -q -E "(debian-security -security|debian main$|debian -updates)" "$sources_file"; then
msg_info "$(translate "Cleaning malformed repository entries...")"
# Remove malformed lines that cause 404 errors
sed -i '/^deb.*debian-security -security/d' "$sources_file"
sed -i '/^deb.*debian main$/d' "$sources_file"
sed -i '/^deb.*debian -updates/d' "$sources_file"
debian_changes=true
msg_ok "$(translate "Cleaning malformed repository sucefull")"
fi
# Replace old mirrors
if grep -q "ftp.es.debian.org" "$sources_file"; then
sed -i 's|ftp.es.debian.org|deb.debian.org|g' "$sources_file"
debian_changes=true
fi
# Fix incomplete security repository line
if grep -q "^deb http://security.debian.org ${OS_CODENAME}-security main contrib$" "$sources_file"; then
sed -i "s|^deb http://security.debian.org ${OS_CODENAME}-security main contrib$|deb http://security.debian.org/debian-security ${OS_CODENAME}-security main contrib non-free non-free-firmware|" "$sources_file"
debian_changes=true
fi
local temp_sources=$(mktemp)
grep -E '^[[:space:]]*#|^[[:space:]]*$' "$sources_file" > "$temp_sources"
cat >> "$temp_sources" << EOF
# Debian ${OS_CODENAME} repositories
deb http://deb.debian.org/debian ${OS_CODENAME} main contrib non-free non-free-firmware
deb http://deb.debian.org/debian ${OS_CODENAME}-updates main contrib non-free non-free-firmware
deb http://security.debian.org/debian-security ${OS_CODENAME}-security main contrib non-free non-free-firmware
EOF
if ! cmp -s "$sources_file" "$temp_sources"; then
cp "$sources_file" "${sources_file}.backup-$(date +%Y%m%d-%H%M%S)"
mv "$temp_sources" "$sources_file"
debian_changes=true
msg_ok "$(translate "Debian repositories updated")"
else
rm "$temp_sources"
fi
if [ ! -f /etc/apt/apt.conf.d/no-bookworm-firmware.conf ]; then
echo 'APT::Get::Update::SourceListWarnings::NonFreeFirmware "false";' > /etc/apt/apt.conf.d/no-bookworm-firmware.conf
debian_changes=true
fi
if [ "$debian_changes" = true ]; then
changes_made=true
fi
# ======================================================
# Clean duplicate repositories
# ======================================================
cleanup_duplicate_repos
# ======================================================
# System update
# ======================================================
# Update package lists
if [ "$changes_made" = true ]; then
msg_info "$(translate "Updating package lists...")"
else
msg_info "$(translate "Checking for available updates...")"
fi
if apt-get update > "$log_file" 2>&1; then
msg_ok "$(translate "Package lists updated")"
else
msg_error "$(translate "Failed to update package lists. Check log: $log_file")"
echo "$(translate "Repository errors found:")"
grep -E "Err:|E:" "$log_file" | head -5
return 1
fi
# Remove conflicting packages
local conflicting_packages=$(dpkg -l 2>/dev/null | grep -E "^ii.*(ntp|openntpd|systemd-timesyncd)" | awk '{print $2}')
if [ -n "$conflicting_packages" ]; then
msg_info "$(translate "Removing conflicting packages...")"
DEBIAN_FRONTEND=noninteractive apt-get -y purge $conflicting_packages >> "$log_file" 2>&1
msg_ok "$(translate "Conflicting packages removed")"
fi
# Show update information
local upgradable=$(apt list --upgradable 2>/dev/null | grep -c "upgradable")
if [ "$upgradable" -gt 0 ]; then
# Show with dialog if available
if command -v whiptail >/dev/null 2>&1; then
if whiptail --title "$(translate "Proxmox Update")" \
--yesno "$(translate "Found $upgradable packages to upgrade.\n\nProceed with system update?")" 10 60; then
msg_info "$(translate "Performing system upgrade. This process may take several minutes...")"
else
msg_info2 "$(translate "Update cancelled by user")"
return 0
fi
fi
else
msg_success "$(translate "System is already up to date")"
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
read -r
return 0
fi
# Perform update
# msg_info "$(translate "Performing system upgrade...")"
# echo "$(translate "This process may take several minutes...")"
# Update with logging
if DEBIAN_FRONTEND=noninteractive apt-get -y \
-o Dpkg::Options::='--force-confdef' \
-o Dpkg::Options::='--force-confold' \
dist-upgrade >> "$log_file" 2>&1; then
msg_ok "$(translate "System upgrade completed successfully")"
else
msg_error "$(translate "System upgrade failed. Check log: $log_file")"
return 1
fi
# Install essential Proxmox packages if missing
local essential_packages=("zfsutils-linux" "proxmox-backup-restore-image" "chrony")
local missing_packages=()
for package in "${essential_packages[@]}"; do
if ! dpkg -l 2>/dev/null | grep -q "^ii $package "; then
missing_packages+=("$package")
fi
done
if [ ${#missing_packages[@]} -gt 0 ]; then
msg_info "$(translate "Installing essential Proxmox packages...")"
DEBIAN_FRONTEND=noninteractive apt-get -y install "${missing_packages[@]}" >> "$log_file" 2>&1
msg_ok "$(translate "Essential Proxmox packages installed")"
fi
# Check LVM
lvm_repair_check
# ======================================================
# Final summary - BEFORE reboot logic
# ======================================================
local end_time=$(date +%s)
local duration=$((end_time - start_time))
local minutes=$((duration / 60))
local seconds=$((duration % 60))
echo ""
echo "$(translate "=== UPDATE COMPLETED ===")"
echo "$(translate "Duration"): ${minutes}m ${seconds}s"
echo "$(translate "Log file"): $log_file"
echo "$(translate "Packages upgraded"): $upgradable"
echo ""
msg_success "$(translate "Proxmox system update completed successfully")"
# ======================================================
# Reboot logic - After summary
# ======================================================
# Check if reboot is needed (kernel updates, system packages, etc.)
NECESSARY_REBOOT=0
# Check for reboot-required file
if [ -f /var/run/reboot-required ]; then
NECESSARY_REBOOT=1
fi
# Check if kernel was updated
if grep -q "linux-image" "$log_file" 2>/dev/null; then
NECESSARY_REBOOT=1
fi
# For system updates, it's generally safer to reboot
if [ "$upgradable" -gt 0 ]; then
NECESSARY_REBOOT=1
fi
if [[ "$NECESSARY_REBOOT" -eq 1 ]]; then
if command -v whiptail >/dev/null 2>&1; then
if whiptail --title "$(translate "Reboot Required")" \
--yesno "$(translate "Some changes require a reboot to take effect. Do you want to restart now?")" 10 60; then
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
apt-get -y autoremove >/dev/null 2>&1
apt-get -y autoclean >/dev/null 2>&1
msg_ok "$(translate "Cleanup finished")"
msg_success "$(translate "Press Enter to continue...")"
read -r
msg_warn "$(translate "Rebooting the system...")"
reboot
else
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
apt-get -y autoremove >/dev/null 2>&1
apt-get -y autoclean >/dev/null 2>&1
msg_ok "$(translate "Cleanup finished")"
msg_info2 "$(translate "You can reboot later manually.")"
msg_success "$(translate "Press Enter to continue...")"
read -r
return 0
fi
else
# Fallback without whiptail
echo "$(translate "Reboot now? (y/N): ")"
read -r -t 30 response
if [[ "$response" =~ ^[Yy]$ ]]; then
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
apt-get -y autoremove >/dev/null 2>&1
apt-get -y autoclean >/dev/null 2>&1
msg_ok "$(translate "Cleanup finished")"
msg_warn "$(translate "Rebooting the system...")"
sleep 3
reboot
else
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
apt-get -y autoremove >/dev/null 2>&1
apt-get -y autoclean >/dev/null 2>&1
msg_ok "$(translate "Cleanup finished")"
msg_info2 "$(translate "You can reboot later manually.")"
return 0
fi
fi
else
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
apt-get -y autoremove >/dev/null 2>&1
apt-get -y autoclean >/dev/null 2>&1
msg_ok "$(translate "Cleanup finished")"
msg_success "$(translate "All changes applied. No reboot required.")"
msg_success "$(translate "Press Enter to return to menu...")"
read -r
fi
return 0
}
# Function to show available update information
check_updates_available() {
msg_info "$(translate "Checking for available updates...")"
apt-get update >/dev/null 2>&1
local upgradable=$(apt list --upgradable 2>/dev/null | grep -c "upgradable")
local security_updates=$(apt list --upgradable 2>/dev/null | grep -c "security")
if [ "$upgradable" -gt 0 ]; then
echo "$(translate "Updates available"): $upgradable"
echo "$(translate "Security updates"): $security_updates"
cleanup
if command -v whiptail >/dev/null 2>&1; then
whiptail --title "$(translate "Updates Available")" \
--msgbox "$(translate "Updates available: $upgradable\nSecurity updates: $security_updates\n\nUse the update option to proceed.")" 12 60
fi
else
msg_ok "$(translate "System is up to date")"
fi
}
# Execute function based on parameter
case "${1:-}" in
"check")
check_updates_available
;;
"")
apt_upgrade
;;
*)
echo "$(translate "Usage: $0 [check]")"
echo "$(translate " check - Check for available updates")"
echo "$(translate " (no args) - Perform full system update")"
;;
esac

View File

@@ -0,0 +1,470 @@
#!/bin/bash
# ==========================================================
# ProxMenux - A menu-driven script for Proxmox VE management
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 30/06/2025
# ==========================================================
# Description:
# This script provides an interactive system utilities installer with a
# comprehensive dialog-based interface for Proxmox VE and Linux systems.
# It simplifies the installation and management of essential command-line
# tools and utilities commonly used in server environments.
#
# The script offers both individual utility selection and predefined groups
# for different use cases, ensuring administrators can quickly set up their
# preferred toolset without manual package management.
#
# Supported utility categories:
# - Basic utilities: grc, htop, tree, curl, wget
# - Development tools: git, vim, nano, dos2unix
# - Compression tools: zip, unzip, rsync, cabextract
# - Network tools: iperf3, nmap, tcpdump, nethogs, iptraf-ng, sshpass
# - Analysis tools: jq, ncdu, iotop, btop, iftop
# - System tools: mlocate, net-tools, ipset, msr-tools
# - Virtualization tools: libguestfs-tools, wimtools, genisoimage, chntpw
# - Download tools: axel, aria2
#
# The script automatically handles package name differences across distributions
# and provides detailed feedback on installation success, warnings, and failures.
# It includes built-in troubleshooting for common PATH and command availability
# issues that may occur after package installation.
#
# Configuration ============================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
# ==========================================================
install_system_utils() {
command_exists() {
command -v "$1" >/dev/null 2>&1
}
install_single_package() {
local package="$1"
local command_name="${2:-$package}"
local description="$3"
msg_info "$(translate "Installing") $package ($description)..."
local install_success=false
if command_exists apt; then
if apt update >/dev/null 2>&1 && apt install -y "$package" >/dev/null 2>&1; then
install_success=true
fi
elif command_exists yum; then
if yum install -y "$package" >/dev/null 2>&1; then
install_success=true
fi
elif command_exists dnf; then
if dnf install -y "$package" >/dev/null 2>&1; then
install_success=true
fi
elif command_exists pacman; then
if pacman -S --noconfirm "$package" >/dev/null 2>&1; then
install_success=true
fi
elif command_exists zypper; then
if zypper install -y "$package" >/dev/null 2>&1; then
install_success=true
fi
else
cleanup
msg_error "$(translate "No compatible package manager detected")"
return 1
fi
cleanup
if [ "$install_success" = true ]; then
hash -r 2>/dev/null
sleep 1
if command_exists "$command_name"; then
msg_ok "$package $(translate "installed correctly and available")"
return 0
else
msg_warn "$package $(translate "installed but command not immediately available")"
msg_info2 "$(translate "May need to restart terminal")"
return 2
fi
else
msg_error "$(translate "Error installing") $package"
return 1
fi
}
show_main_utilities_menu() {
local choice
choice=$(dialog --clear --backtitle "ProxMenux" \
--title "$(translate "Utilities Installation Menu")" \
--menu "$(translate "Select an option"):" 20 70 12 \
"1" "$(translate "Custom selection")" \
"2" "$(translate "Install ALL utilities")" \
"3" "$(translate "Install basic utilities") (grc, htop, tree, curl, wget)" \
"4" "$(translate "Install development tools") (git, vim, nano)" \
"5" "$(translate "Install compression tools") (zip, unzip, rsync)" \
"6" "$(translate "Install terminal multiplexers") (screen, tmux)" \
"7" "$(translate "Install analysis tools") (jq, ncdu, iotop)" \
"8" "$(translate "Install network tools") (nethogs, nmap, tcpdump, lsof)" \
"9" "$(translate "Verify installations")" \
"0" "$(translate "Return to main menu")" 2>&1 >/dev/tty)
echo "$choice"
}
show_custom_selection() {
local utilities=(
"axel" "$(translate "Download accelerator")" "OFF"
"dos2unix" "$(translate "Convert DOS/Unix text files")" "OFF"
"grc" "$(translate "Generic log/command colorizer")" "OFF"
"htop" "$(translate "Interactive process viewer")" "OFF"
"btop" "$(translate "Modern resource monitor")" "OFF"
"iftop" "$(translate "Real-time network usage")" "OFF"
"iotop" "$(translate "Monitor disk I/O usage")" "OFF"
"iperf3" "$(translate "Network performance testing")" "OFF"
"ipset" "$(translate "Manage IP sets")" "OFF"
"iptraf-ng" "$(translate "Network monitoring tool")" "OFF"
"mlocate" "$(translate "Locate files quickly")" "OFF"
"msr-tools" "$(translate "Access CPU MSRs")" "OFF"
"net-tools" "$(translate "Legacy networking tools")" "OFF"
"sshpass" "$(translate "Non-interactive SSH login")" "OFF"
"tmux" "$(translate "Terminal multiplexer")" "OFF"
"unzip" "$(translate "Extract ZIP files")" "OFF"
"zip" "$(translate "Create ZIP files")" "OFF"
"libguestfs-tools" "$(translate "VM disk utilities")" "OFF"
"aria2" "$(translate "Multi-source downloader")" "OFF"
"cabextract" "$(translate "Extract CAB files")" "OFF"
"wimtools" "$(translate "Manage WIM images")" "OFF"
"genisoimage" "$(translate "Create ISO images")" "OFF"
"chntpw" "$(translate "Edit Windows registry/passwords")" "OFF"
)
local selected
selected=$(dialog --clear --backtitle "ProxMenux" \
--title "$(translate "Select utilities to install")" \
--checklist "$(translate "Use SPACE to select/deselect, ENTER to confirm")" \
25 80 20 "${utilities[@]}" 2>&1 >/dev/tty)
echo "$selected"
}
install_utility_group() {
local group_name="$1"
shift
local utilities=("$@")
clear
show_proxmenux_logo
msg_title "$(translate "Installing group"): $group_name"
local failed=0
local success=0
local warning=0
declare -A package_to_command=(
["mlocate"]="locate"
["msr-tools"]="rdmsr"
["net-tools"]="netstat"
["libguestfs-tools"]="virt-filesystems"
["aria2"]="aria2c"
["wimtools"]="wimlib-imagex"
)
for util_info in "${utilities[@]}"; do
IFS=':' read -r package command description <<< "$util_info"
local verify_command="${package_to_command[$package]:-$command}"
install_single_package "$package" "$verify_command" "$description"
local install_result=$?
case $install_result in
0) success=$((success + 1)) ;;
1) failed=$((failed + 1)) ;;
2) warning=$((warning + 1)) ;;
esac
done
echo
msg_info2 "$(translate "Installation summary") - $group_name:"
msg_ok "$(translate "Successful"): $success"
[ $warning -gt 0 ] && msg_warn "$(translate "With warnings"): $warning"
[ $failed -gt 0 ] && msg_error "$(translate "Failed"): $failed"
dialog --clear --backtitle "ProxMenux" \
--title "$(translate "Installation Complete")" \
--msgbox "$(translate "Group"): $group_name\n$(translate "Successful"): $success\n$(translate "With warnings"): $warning\n$(translate "Failed"): $failed" 10 50
}
install_selected_utilities() {
local selected="$1"
if [ -z "$selected" ]; then
dialog --clear --backtitle "ProxMenux" \
--title "$(translate "No Selection")" \
--msgbox "$(translate "No utilities were selected")" 8 40
return
fi
clear
show_proxmenux_logo
msg_title "$(translate "Installing selected utilities")"
local failed=0
local success=0
local warning=0
local selected_array
IFS=' ' read -ra selected_array <<< "$selected"
declare -A package_to_command=(
["mlocate"]="locate"
["msr-tools"]="rdmsr"
["net-tools"]="netstat"
["libguestfs-tools"]="virt-filesystems"
["aria2"]="aria2c"
["wimtools"]="wimlib-imagex"
)
for util in "${selected_array[@]}"; do
util=$(echo "$util" | tr -d '"')
local verify_command="${package_to_command[$util]:-$util}"
install_single_package "$util" "$verify_command" "$util"
local install_result=$?
case $install_result in
0) success=$((success + 1)) ;;
1) failed=$((failed + 1)) ;;
2) warning=$((warning + 1)) ;;
esac
done
if [ -f ~/.bashrc ]; then
source ~/.bashrc >/dev/null 2>&1
fi
hash -r 2>/dev/null
echo
msg_info2 "$(translate "Installation summary"):"
msg_ok "$(translate "Successful"): $success"
[ $warning -gt 0 ] && msg_warn "$(translate "With warnings"): $warning"
[ $failed -gt 0 ] && msg_error "$(translate "Failed"): $failed"
dialog --clear --backtitle "ProxMenux" \
--title "$(translate "Installation Complete")" \
--msgbox "$(translate "Selected utilities installation completed")\n$(translate "Successful"): $success\n$(translate "With warnings"): $warning\n$(translate "Failed"): $failed" 12 60
}
verify_installations() {
clear
show_proxmenux_logo
msg_info "$(translate "Verifying all utilities status")..."
local utilities=(
"axel:Download accelerator"
"dialog:Console GUI dialogs"
"dos2unix:Convert DOS/Unix text files"
"grc:Generic log/command colorizer"
"htop:Interactive process viewer"
"btop:Modern resource monitor"
"iftop:Real-time network usage"
"iotop:Monitor disk I/O usage"
"iperf3:Network performance testing"
"ipset:Manage IP sets"
"iptraf-ng:Network monitoring tool"
"locate:Locate files quickly"
"rdmsr:Access CPU MSRs"
"netstat:Legacy networking tools"
"sshpass:Non-interactive SSH login"
"tmux:Terminal multiplexer"
"unzip:Extract ZIP files"
"zip:Create ZIP files"
"virt-filesystems:VM disk utilities"
"aria2c:Multi-source downloader"
"cabextract:Extract CAB files"
"wimlib-imagex:Manage WIM images"
"genisoimage:Create ISO images"
"chntpw:Edit Windows registry/passwords"
)
local available=0
local missing=0
local status_text=""
for util in "${utilities[@]}"; do
IFS=':' read -r cmd desc <<< "$util"
if command_exists "$cmd"; then
status_text+="\n✓ $cmd - $desc"
available=$((available + 1))
else
status_text+="\n✗ $cmd - $desc"
missing=$((missing + 1))
fi
done
cleanup
local summary="$(translate "Total"): $((available + missing))\n$(translate "Available"): $available\n$(translate "Missing"): $missing"
dialog --clear --backtitle "ProxMenux" \
--title "$(translate "Utilities Verification")" \
--msgbox "$summary$status_text" 25 80
}
while true; do
choice=$(show_main_utilities_menu)
case $choice in
1)
selected=$(show_custom_selection)
install_selected_utilities "$selected"
;;
2)
all_utils=(
"axel:axel:Download accelerator"
"dos2unix:dos2unix:Convert DOS/Unix text files"
"grc:grc:Generic log/command colorizer"
"htop:htop:Interactive process viewer"
"btop:btop:Modern resource monitor"
"iftop:iftop:Real-time network usage"
"iotop:iotop:Monitor disk I/O usage"
"iperf3:iperf3:Network performance testing"
"ipset:ipset:Manage IP sets"
"iptraf-ng:iptraf-ng:Network monitoring tool"
"mlocate:locate:Locate files quickly"
"msr-tools:rdmsr:Access CPU MSRs"
"net-tools:netstat:Legacy networking tools"
"sshpass:sshpass:Non-interactive SSH login"
"tmux:tmux:Terminal multiplexer"
"unzip:unzip:Extract ZIP files"
"zip:zip:Create ZIP files"
"libguestfs-tools:virt-filesystems:VM disk utilities"
"aria2:aria2c:Multi-source downloader"
"cabextract:cabextract:Extract CAB files"
"wimtools:wimlib-imagex:Manage WIM images"
"genisoimage:genisoimage:Create ISO images"
"chntpw:chntpw:Edit Windows registry/passwords"
)
install_utility_group "$(translate "ALL Utilities")" "${all_utils[@]}"
;;
3)
basic_utils=(
"grc:grc:Generic Colouriser"
"htop:htop:Process monitor"
"tree:tree:Directory structure"
"curl:curl:Data transfer"
"wget:wget:Web downloader"
)
install_utility_group "$(translate "Basic Utilities")" "${basic_utils[@]}"
;;
4)
dev_utils=(
"git:git:Version control"
"vim:vim:Advanced editor"
"nano:nano:Simple editor"
)
install_utility_group "$(translate "Development Tools")" "${dev_utils[@]}"
;;
5)
compress_utils=(
"zip:zip:ZIP compressor"
"unzip:unzip:ZIP extractor"
"rsync:rsync:File synchronizer"
)
install_utility_group "$(translate "Compression Tools")" "${compress_utils[@]}"
;;
6)
multiplex_utils=(
"screen:screen:Terminal multiplexer"
"tmux:tmux:Advanced multiplexer"
)
install_utility_group "$(translate "Terminal Multiplexers")" "${multiplex_utils[@]}"
;;
7)
analysis_utils=(
"jq:jq:JSON processor"
"ncdu:ncdu:Disk analyzer"
"iotop:iotop:I/O monitor"
)
install_utility_group "$(translate "Analysis Tools")" "${analysis_utils[@]}"
;;
8)
network_utils=(
"nethogs:nethogs:Network monitor"
"nmap:nmap:Network scanner"
"tcpdump:tcpdump:Packet analyzer"
"lsof:lsof:Open files"
)
install_utility_group "$(translate "Network Tools")" "${network_utils[@]}"
;;
9)
verify_installations
;;
0|"")
break
;;
*)
dialog --clear --backtitle "ProxMenux" \
--title "$(translate "Invalid Option")" \
--msgbox "$(translate "Please select a valid option")" 8 40
;;
esac
done
clear
}
install_system_utils

View File

@@ -0,0 +1,328 @@
#!/usr/bin/env bash
# ==========================================================
# ProxMenux - UUP Dump ISO Creator Custom
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 30/06/2025
# ==========================================================
# Description:
# This script is part of the ProxMenux tools for Proxmox VE.
# It allows downloading and converting official Windows ISO images
# from UUP Dump using a shared link (with ID, pack, and edition).
#
# Key features:
# - Automatically installs and verifies required dependencies (aria2c, cabextract, wimlib-imagex…)
# - Downloads the selected Windows edition from UUP Dump using aria2
# - Converts the downloaded files into a bootable ISO
# - Stores the resulting ISO in the default template path (/var/lib/vz/template/iso)
# - Provides a graphical prompt via whiptail for user-friendly usage
#
# This tool simplifies the creation of official Windows ISOs
# for use in virtual machines within Proxmox VE.
# ==========================================================
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
# ==========================================================
detect_iso_dir() {
for store in $(pvesm status -content iso | awk 'NR>1 {print $1}'); do
for ext in iso img; do
volid=$(pvesm list "$store" --content iso | awk -v ext="$ext" 'NR>1 && $2 ~ ext {print $1; exit}')
if [[ -n "$volid" ]]; then
path=$(pvesm path "$volid" 2>/dev/null)
dir=$(dirname "$path")
[[ -d "$dir" ]] && echo "$dir" && return 0
fi
done
done
if [[ -d /var/lib/vz/template/iso ]]; then
echo "/var/lib/vz/template/iso"
return 0
fi
return 1
}
function get_destination_path() {
local default_path="$1"
local user_path=""
while true; do
######################################
user_path=$(dialog --backtitle "ProxMenux" --inputbox "$(translate "Enter destination path for ISO file")" 10 80 "$default_path" 3>&1 1>&2 2>&3)
######################################
if [[ $? -ne 0 ]]; then
return 1
fi
if [[ -z "$user_path" ]]; then
user_path="$default_path"
fi
if [[ ! -d "$user_path" ]]; then
if mkdir -p "$user_path" 2>/dev/null; then
#msg_ok "$(translate "Directory created successfully:") $user_path"
echo "$user_path"
return 0
else
dialog --backtitle "ProxMenux" --msgbox "$(translate "Error: Cannot create directory") '$user_path'. $(translate "Please check permissions and try again.")" 8 60
continue
fi
else
if [[ -w "$user_path" ]]; then
echo "$user_path"
return 0
else
dialog --backtitle "ProxMenux" --msgbox "$(translate "Error: No write permissions in directory") '$user_path'. $(translate "Please choose another path.")" 8 60
continue
fi
fi
done
}
function run_uupdump_creator() {
local DEPS=(curl aria2 cabextract wimtools genisoimage chntpw)
local CMDS=(curl aria2c cabextract wimlib-imagex genisoimage chntpw)
local MISSING=()
local FAILED=()
for i in "${!CMDS[@]}"; do
if ! command -v "${CMDS[$i]}" &>/dev/null; then
MISSING+=("${DEPS[$i]}")
fi
done
if [[ ${#MISSING[@]} -gt 0 ]]; then
clear
show_proxmenux_logo
echo -e
msg_info "$(translate "Installing dependencies: ${MISSING[*]}")"
apt-get update -qq >/dev/null 2>&1
msg_ok "$(translate "All dependencies installed and verified.")"
if ! apt-get install -y "${MISSING[@]}" >/dev/null 2>&1; then
msg_error "$(translate "Failed to install: ${MISSING[*]}")"
exit 1
fi
fi
for i in "${!CMDS[@]}"; do
if ! command -v "${CMDS[$i]}" &>/dev/null; then
FAILED+=("${CMDS[$i]}")
fi
done
#if [[ ${#FAILED[@]} -eq 0 ]]; then
# msg_ok "$(translate "All dependencies installed and verified.")"
#else
# msg_error "$(translate "Missing commands after installation: ${FAILED[*]}")"
# exit 1
#fi
ISO_DIR=$(detect_iso_dir)
if [[ -z "$ISO_DIR" ]]; then
msg_error "$(translate "Could not determine a valid ISO storage directory.")"
exit 1
fi
mkdir -p "$ISO_DIR"
######################################
DEFAULT_TMP="/root/uup-temp"
USER_INPUT=$(dialog --backtitle "ProxMenux" --inputbox "Enter temporary folder path (default: $DEFAULT_TMP):" 10 60 "$DEFAULT_TMP" 3>&1 1>&2 2>&3)
######################################
if [[ $? -ne 0 ]]; then
return
fi
if [[ $? -ne 0 || -z "$USER_INPUT" ]]; then
USER_INPUT="$DEFAULT_TMP"
fi
if [[ "$USER_INPUT" == "$DEFAULT_TMP" ]]; then
TMP_DIR="$USER_INPUT"
CLEAN_ALL=true
else
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
RANDOM_ID=$(head /dev/urandom | tr -dc a-z0-9 | head -c 4)
TMP_DIR="${USER_INPUT%/}/uup-session-${TIMESTAMP}-${RANDOM_ID}"
CLEAN_ALL=false
fi
mkdir -p "$TMP_DIR" || {
msg_error "$(translate "Failed to create temporary directory:") $TMP_DIR"
exit 1
}
OUT_DIR=$(get_destination_path "$ISO_DIR")
if [[ $? -ne 0 ]]; then
return 1
fi
######################################
UUP_URL=$(whiptail --backtitle "ProxMenux" --inputbox "$(translate "Paste the UUP Dump URL here")" 10 90 3>&1 1>&2 2>&3)
######################################
if [[ $? -ne 0 ]]; then
return
fi
if [[ $? -ne 0 || -z "$UUP_URL" ]]; then
msg_warn "$(translate "Cancelled by user or empty URL.")"
return 1
fi
if [[ ! "$UUP_URL" =~ id=.+\&pack=.+\&edition=.+ ]]; then
msg_error "$(translate "The URL does not contain the required parameters (id, pack, edition).")"
sleep 2
return
fi
BUILD_ID=$(echo "$UUP_URL" | grep -oP 'id=\K[^&]+')
LANG=$(echo "$UUP_URL" | grep -oP 'pack=\K[^&]+')
EDITION=$(echo "$UUP_URL" | grep -oP 'edition=\K[^&]+')
ARCH="amd64"
clear
show_proxmenux_logo
echo -e
echo -e "\n${BGN}=============== UUP Dump Creator ===============${CL}"
echo -e " ${BGN}🆔 ID:${CL} ${DGN}$BUILD_ID${CL}"
echo -e " ${BGN}🌐 Language:${CL} ${DGN}$LANG${CL}"
echo -e " ${BGN}💿 Edition:${CL} ${DGN}$EDITION${CL}"
echo -e " ${BGN}🖥️ Architecture:${CL} ${DGN}$ARCH${CL}"
echo -e " ${BGN}📁 Destination:${CL} ${DGN}$OUT_DIR${CL}"
echo -e "${BGN}===============================================${CL}\n"
CONVERTER="$TMP_DIR/converter"
if [[ ! -f "$CONVERTER/convert.sh" ]]; then
echo "📦 $(translate "Downloading UUP converter...")"
mkdir -p "$CONVERTER"
cd "$CONVERTER" || exit 1
wget -q https://git.uupdump.net/uup-dump/converter/archive/refs/heads/master.tar.gz -O converter.tar.gz
tar -xzf converter.tar.gz --strip-components=1
chmod +x convert.sh
cd "$TMP_DIR" || exit 1
fi
cat > uup_download_linux.sh <<EOF
#!/bin/bash
mkdir -p files
echo "https://git.uupdump.net/uup-dump/converter/archive/refs/heads/master.tar.gz" > files/converter_multi
for prog in aria2c cabextract wimlib-imagex chntpw; do
which \$prog &>/dev/null || { echo "\$prog not found."; exit 1; }
done
which genisoimage &>/dev/null || which mkisofs &>/dev/null || { echo "genisoimage/mkisofs not found."; exit 1; }
destDir="UUPs"
tempScript="aria2_script.\$RANDOM.txt"
aria2c --no-conf --console-log-level=warn --log-level=info --log="aria2_download.log" \\
-x16 -s16 -j2 --allow-overwrite=true --auto-file-renaming=false -d"files" -i"files/converter_multi" || exit 1
aria2c --no-conf --console-log-level=warn --log-level=info --log="aria2_download.log" \\
-o"\$tempScript" --allow-overwrite=true --auto-file-renaming=false \\
"https://uupdump.net/get.php?id=$BUILD_ID&pack=$LANG&edition=$EDITION&aria2=2" || exit 1
grep '#UUPDUMP_ERROR:' "\$tempScript" && { echo "❌ Error generating UUP download list."; exit 1; }
aria2c --no-conf --console-log-level=warn --log-level=info --log="aria2_download.log" \\
-x16 -s16 -j5 -c -R -d"\$destDir" -i"\$tempScript" || exit 1
EOF
chmod +x uup_download_linux.sh
./uup_download_linux.sh
UUP_FOLDER=$(find "$TMP_DIR" -type d -name "UUPs" | head -n1)
[[ -z "$UUP_FOLDER" ]] && msg_error "$(translate "No UUP folder found.")" && exit 1
echo -e "\n${GN}=======================================${CL}"
echo -e " 💿 ${GN}Starting ISO conversion...${CL}"
echo -e "${GN}=======================================${CL}\n"
"$CONVERTER/convert.sh" wim "$UUP_FOLDER" 1
ISO_FILE=$(find "$TMP_DIR" "$CONVERTER" "$UUP_FOLDER" -maxdepth 1 -iname "*.iso" | head -n1)
if [[ -f "$ISO_FILE" ]]; then
mv "$ISO_FILE" "$OUT_DIR/"
msg_ok "$(translate "ISO created successfully:") $OUT_DIR/$(basename "$ISO_FILE")"
msg_ok "$(translate "Cleaning temporary files...")"
if [[ "$CLEAN_ALL" == true ]]; then
rm -rf "$TMP_DIR" "$CONVERTER"
else
[[ -d "$TMP_DIR" ]] && rm -rf "$TMP_DIR"
[[ -d "$CONVERTER" ]] && rm -rf "$CONVERTER"
fi
export OS_TYPE="windows"
export LANGUAGE=C
export LANG=C
export LC_ALL=C
load_language
initialize_cache
msg_success "$(translate "Press Enter to return to menu...")"
read -r
else
msg_warn "$(translate "No ISO was generated.")"
rm -rf "$TMP_DIR" "$CONVERTER"
export LANGUAGE=C
export LANG=C
export LC_ALL=C
load_language
initialize_cache
msg_success "$(translate "Press Enter to return to menu...")"
read -r
return 1
fi
}
run_uupdump_creator

View File

@@ -193,24 +193,36 @@ msg_error() {
# Initialize cache
initialize_cache() {
if [[ "$LANGUAGE" != "en" ]]; then
if [ ! -f "$CACHE_FILE" ]; then
mkdir -p "$(dirname "$CACHE_FILE")"
echo "{}" > "$CACHE_FILE"
fi
}
load_language() {
if [ -f "$CONFIG_FILE" ]; then
LANGUAGE=$(jq -r '.language' "$CONFIG_FILE")
fi
}
# Translation with cache and predefined terms
# Load language
load_language() {
LANGUAGE="en"
if [ -f "$CONFIG_FILE" ]; then
lang_candidate=$(jq -r '.language // empty' "$CONFIG_FILE" 2>/dev/null)
if [[ -n "$lang_candidate" && "$lang_candidate" != "null" ]]; then
LANGUAGE="$lang_candidate"
fi
fi
}
########################################################
translate() {
local text="$1"
local dest_lang="$LANGUAGE"
# If the language is English, return the original text without translating or caching
if [ "$dest_lang" = "en" ]; then
echo "$text"
return
@@ -281,6 +293,11 @@ print(translate_text('$text', '$dest_lang'))
########################################################
show_proxmenux_logo() {
clear

View File

@@ -101,8 +101,12 @@ function select_linux_iso() {
function select_linux_iso_official() {
DISTROS=(
"Ubuntu 25.04|Desktop|ProxMenux|https://releases.ubuntu.com/25.04/ubuntu-25.04-desktop-amd64.iso"
"Ubuntu 24.04|Desktop|ProxMenux|https://releases.ubuntu.com/24.04/ubuntu-24.04.2-desktop-amd64.iso"
"Ubuntu 22.04|Desktop|ProxMenux|https://releases.ubuntu.com/22.04/ubuntu-22.04.5-desktop-amd64.iso"
"Ubuntu 20.04|Desktop|ProxMenux|https://releases.ubuntu.com/20.04/ubuntu-20.04.6-desktop-amd64.iso"
"Ubuntu 25.04 Server|CLI|ProxMenux|https://releases.ubuntu.com/25.04/ubuntu-25.04-live-server-amd64.iso"
"Ubuntu 24.04 Server|CLI|ProxMenux|https://releases.ubuntu.com/24.04/ubuntu-24.04.2-live-server-amd64.iso"
"Ubuntu 22.04 Server|CLI|ProxMenux|https://releases.ubuntu.com/22.04/ubuntu-22.04.5-live-server-amd64.iso"
"Ubuntu 20.04 Server|CLI|ProxMenux|https://releases.ubuntu.com/20.04/ubuntu-20.04.6-live-server-amd64.iso"
"Debian 12|Desktop|ProxMenux|https://cdimage.debian.org/debian-cd/current/amd64/iso-dvd/debian-12.10.0-amd64-DVD-1.iso"
@@ -164,7 +168,8 @@ function select_linux_cloudinit() {
"3" "Ubuntu 22.04 (Cloud-Init automated) │ Helper Scripts"
"4" "Ubuntu 24.04 (Cloud-Init automated) │ Helper Scripts"
"5" "Ubuntu 24.10 (Cloud-Init automated) │ Helper Scripts"
"6" "$(translate "Return to Main Menu")"
"6" "Ubuntu 25.04 (Cloud-Init automated) │ Helper Scripts"
"7" "$(translate "Return to Main Menu")"
)
local script_selection
@@ -191,6 +196,12 @@ function select_linux_cloudinit() {
bash <(curl -s "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/ubuntu2410-vm.sh")
;;
6)
bash <(curl -s "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/ubuntu2504-vm.sh")
echo -e
echo -e "after installation, checkout:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/272"
echo -e
;;
7)
return
;;
esac
@@ -241,40 +252,53 @@ function select_linux_custom_iso() {
function select_linux_other_scripts() {
local OTHER_OPTIONS=(
local OTHER_OPTIONS=(
"1" "Home Assistant OS VM (HAOS) │ Helper Scripts"
"2" "Docker VM (Debian + SSH + Docker) │ Helper Scripts"
"3" "$(translate "Return to Main Menu")"
)
"3" "Nextcloud │ Helper Scripts"
"4" "$(translate "Return to Main Menu")"
)
local choice
choice=$(dialog --backtitle "ProxMenux" \
local choice
choice=$(dialog --backtitle "ProxMenux" \
--title "$(translate "Other Prebuilt Linux VMs")" \
--menu "\n$(translate "Select one of the ready-to-run Linux VMs:")" 18 70 10 \
"${OTHER_OPTIONS[@]}" 3>&1 1>&2 2>&3)
if [[ $? -ne 0 || "$choice" == "3" ]]; then
if [[ $? -ne 0 || "$choice" == "4" ]]; then
return 1
fi
fi
case "$choice" in
case "$choice" in
1)
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/haos-vm.sh)"
;;
2)
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/docker-vm.sh)"
whiptail --title "Docker VM Info" \
--msgbox "$(translate "Default Login Credentials:\n\nUsername: root\nPassword: docker")" 12 50
echo -e
echo -e "${TAB}$(translate "Default Login Credentials:")"
echo -e "${TAB}Username: root"
echo -e "${TAB}Password: docker"
echo -e
;;
esac
3)
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/nextcloud-vm.sh)"
echo -e
echo -e "${TAB}$(translate "You can use the following credentials to login to the Nextcloud vm:")"
echo -e "${TAB}Username: admin"
echo -e "${TAB}$(translate "This VM requires extra installation steps, see install guide at:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144")"
echo -e
;;
esac
msg_success "$(translate "Press Enter to return to menu...")"
read -r
msg_success "$(translate "Press Enter to return to menu...")"
read -r
whiptail --title "Proxmox VE Helper-Scripts" \
whiptail --title "Proxmox VE Helper-Scripts" \
--msgbox "$(translate "Visit the website to discover more scripts, stay updated with the latest updates, and support the project:\n\nhttps://community-scripts.github.io/ProxmoxVE")" 15 70
return 1
return 1
}

View File

@@ -44,9 +44,11 @@ function select_nas_iso() {
"2" "TrueNAS SCALE VM (Fangtooth)"
"3" "TrueNAS CORE VM (FreeBSD based)"
"4" "OpenMediaVault VM (Debian based)"
"5" "Rockstor VM (openSUSE based)"
"6" "ZimaOS VM (R0GGER proxmox-zimaos)"
"7" "$(translate "Return to Main Menu")"
"5" "XigmaNAS VM (FreeBSD based)"
"6" "Rockstor VM (openSUSE based)"
"7" "ZimaOS VM (R0GGER proxmox-zimaos)"
"8" "Umbrel OS VM (Helper Scripts)"
"9" "$(translate "Return to Main Menu")"
)
local NAS_TYPE
@@ -87,18 +89,26 @@ function select_nas_iso() {
HN="OpenMediaVault"
;;
5)
ISO_NAME="XigmaNAS-13.3.0.5"
ISO_URL="https://sourceforge.net/projects/xigmanas/files/XigmaNAS-13.3.0.5/13.3.0.5.10153/XigmaNAS-x64-LiveCD-13.3.0.5.10153.iso/download"
ISO_FILE="XigmaNAS-x64-LiveCD-13.3.0.5.10153.iso"
ISO_PATH="$ISO_DIR/$ISO_FILE"
HN="XigmaNAS"
;;
6)
ISO_NAME="Rockstor"
ISO_URL="https://rockstor.com/downloads/installer/leap/15.6/x86_64/Rockstor-Leap15.6-generic.x86_64-5.0.15-0.install.iso"
ISO_FILE="Rockstor-Leap15.6-generic.x86_64-5.0.15-0.install.iso"
ISO_PATH="$ISO_DIR/$ISO_FILE"
HN="Rockstor"
;;
6)
7)
HN="ZimaOS-VM"
if ! confirm_vm_creation; then
return 1
fi
bash -c "$(wget -qLO - https://raw.githubusercontent.com/R0GGER/proxmox-zimaos/refs/heads/main/zimaos_zimacube.sh)"
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
read -r
@@ -107,8 +117,25 @@ function select_nas_iso() {
return 1
;;
8)
HN="Umbrel OS"
bash -c "$(wget -qLO - https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/umbrel-os-vm.sh)"
echo -e
echo -e "${TAB}$(translate "Default Login Credentials:")"
echo -e "${TAB}Username: umbrel"
echo -e "${TAB}Password: umbrel"
echo -e "${TAB}$(translate "After logging in, run: ip a to obtain the IP address.\nThen, enter that IP address in your web browser like this:\n http://IP_ADDRESS\n\nThis will open the Umbral OS dashboard.")"
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
read -r
7)
whiptail --title "Proxmox VE - Umbrel OS" \
--msgbox "$(translate "Umbrel OS installer script by Helper Scripts\n\nVisit the GitHub repo to learn more, contribute, or support the project:\n\nhttps://community-scripts.github.io/ProxmoxVE/scripts?id=umbrel-os-vm")" 15 70
return 1
;;
9)
return 1
;;
esac

View File

@@ -106,14 +106,35 @@ if [[ -z "$ISO_DIR" ]]; then
exit 1
fi
mkdir -p "$ISO_DIR"
TMP_DIR="/root/uup-temp"
OUT_DIR="$ISO_DIR"
CONVERTER="/root/uup-converter"
mkdir -p "$TMP_DIR" "$OUT_DIR"
cd "$TMP_DIR" || exit 1
DEFAULT_TMP="/root/uup-temp"
USER_INPUT=$(dialog --inputbox "Enter temporary folder path (default: $DEFAULT_TMP):" 10 60 "$DEFAULT_TMP" 3>&1 1>&2 2>&3)
if [[ $? -ne 0 || -z "$USER_INPUT" ]]; then
USER_INPUT="$DEFAULT_TMP"
fi
#
if [[ "$USER_INPUT" == "$DEFAULT_TMP" ]]; then
TMP_DIR="$USER_INPUT"
CLEAN_ALL=true
else
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
RANDOM_ID=$(head /dev/urandom | tr -dc a-z0-9 | head -c 4)
TMP_DIR="${USER_INPUT%/}/uup-session-${TIMESTAMP}-${RANDOM_ID}"
CLEAN_ALL=false
fi
mkdir -p "$TMP_DIR" || {
msg_error "$(translate "Failed to create temporary directory:") $TMP_DIR"
exit 1
}
OUT_DIR=$(detect_iso_dir)
[[ -z "$OUT_DIR" ]] && msg_error "$(translate "Could not determine a valid ISO directory.")" && exit 1
mkdir -p "$OUT_DIR"
UUP_URL=$(whiptail --inputbox "$(translate "Paste the UUP Dump URL here")" 10 90 3>&1 1>&2 2>&3)
@@ -207,7 +228,12 @@ if [[ -f "$ISO_FILE" ]]; then
msg_ok "$(translate "Cleaning temporary files...")"
if [[ "$CLEAN_ALL" == true ]]; then
rm -rf "$TMP_DIR" "$CONVERTER"
else
[[ -d "$TMP_DIR" ]] && rm -rf "$TMP_DIR"
[[ -d "$CONVERTER" ]] && rm -rf "$CONVERTER"
fi
export OS_TYPE="windows"
export LANGUAGE=C
@@ -218,15 +244,15 @@ if [[ -f "$ISO_FILE" ]]; then
msg_success "$(translate "Press Enter to return to menu...")"
read -r
else
msg_warn "$(translate "No ISO was generated.")"
rm -rf "$TMP_DIR" "$CONVERTER"
export LANGUAGE=C
export LANG=C
export LC_ALL=C
load_language
initialize_cache
msg_success "$(translate "Press Enter to return to menu...")"
read -r
return 1

View File

@@ -95,6 +95,10 @@ function load_default_vm_config() {
;;
esac
if [[ "$os_type" == "1" && "$HN" == "OpenMediaVault" ]]; then
BIOS_TYPE=" -bios seabios"
fi
[[ -z "$CORE_COUNT" ]] && CORE_COUNT="2"
[[ -z "$RAM_SIZE" ]] && RAM_SIZE="4096"
@@ -166,18 +170,37 @@ function configure_vm_advanced() {
[[ "$MACHINE_TYPE" == "q35" ]] && MACHINE=" -machine q35" && FORMAT="" || MACHINE="" && FORMAT=",efitype=4m"
# BIOS
if [[ "$HN" == "OpenMediaVault" ]]; then
BIOS_TYPE=" -bios seabios"
else
BIOS=$(whiptail --backtitle "ProxMenux" --title "$(translate "BIOS Type")" \
--radiolist "$(translate "Choose BIOS type")" 10 60 2 \
"ovmf" "UEFI (OVMF)" ON \
"seabios" "Legacy BIOS (SeaBIOS)" OFF 3>&1 1>&2 2>&3) || return 1
BIOS_TYPE=" -bios $BIOS"
fi
# CPU Type
# CPU_CHOICE=$(whiptail --backtitle "ProxMenux" --title "$(translate "CPU Model")" \
# --radiolist "$(translate "Select CPU model")" 10 60 2 \
# "host" "Host (recommended)" ON \
# "kvm64" "Generic KVM64" OFF 3>&1 1>&2 2>&3) || return 1
# [[ "$CPU_CHOICE" == "host" ]] && CPU_TYPE=" -cpu host" || CPU_TYPE=" -cpu kvm64"
CPU_CHOICE=$(whiptail --backtitle "ProxMenux" --title "$(translate "CPU Model")" \
--radiolist "$(translate "Select CPU model")" 10 60 2 \
--radiolist "$(translate "Select CPU model")" 17 70 11 \
"host" "Host (recommended)" ON \
"kvm64" "Generic KVM64" OFF 3>&1 1>&2 2>&3) || return 1
[[ "$CPU_CHOICE" == "host" ]] && CPU_TYPE=" -cpu host" || CPU_TYPE=" -cpu kvm64"
"kvm64" "Generic KVM64" OFF \
"kvm32" "Generic KVM32" OFF \
"qemu64" "QEMU 64-bit CPU" OFF \
"qemu32" "QEMU 32-bit CPU" OFF \
"max" "Expose all QEMU CPU features" OFF \
"x86-64-v2" "Nehalem-class (x86-64-v2)" OFF \
"x86-64-v2-AES" "Same as v2 but with AES" OFF \
"x86-64-v3" "Haswell-class (x86-64-v3)" OFF \
"x86-64-v4" "Skylake-class (x86-64-v4)" OFF 3>&1 1>&2 2>&3) || return 1
CPU_TYPE=" -cpu $CPU_CHOICE"
# Core Count
CORE_COUNT=$(whiptail --backtitle "ProxMenux" --inputbox "$(translate "Number of CPU cores (default: 2)")" \

View File

@@ -18,13 +18,9 @@ export default function CoralTPULXC() {
</p>
<h2 className="text-2xl font-semibold mt-8 mb-4">Overview</h2>
<p className="mb-4">The script automates the following steps:</p>
<ol className="list-decimal pl-6 space-y-2 mb-6">
<li>Allows selection of an existing LXC container.</li>
<li>Ensures the container is privileged for hardware access.</li>
<li>Configures LXC parameters for Coral TPU and Intel iGPU.</li>
<li>Installs required drivers and dependencies inside the container.</li>
</ol>
<p className="mb-4">The script automates the complete configuration of Coral TPU support in LXC containers, including USB and M.2 variants. It applies Proxmox-specific container settings, manages device passthrough permissions, and installs required drivers both on the host and inside the container.</p>
<p className="mb-4">The USB variant uses a persistent mapping based on <code>/dev/coral</code> via <code>udev</code> rules, avoiding reliance on dynamic USB paths like <code>/dev/bus/usb/*</code>. This ensures consistent device assignment across reboots and hardware reordering.</p>
<p className="mb-4">The M.2 version is detected automatically and configured only if present.</p>
<h2 className="text-2xl font-semibold mt-8 mb-4">Implementation Steps</h2>
<Steps>
@@ -39,13 +35,47 @@ export default function CoralTPULXC() {
<li>Sets device permissions for TPU and iGPU.</li>
<li>Configures proper device mounts.</li>
</ul>
<CopyableCode
code={`# Coral USB persistent passthrough example:
/etc/udev/rules.d/99-coral-usb.rules
SUBSYSTEM=="usb", ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="9302", SYMLINK+="coral", MODE="0666"
# LXC config:
lxc.cgroup2.devices.allow: c 189:* rwm
lxc.mount.entry: /dev/coral dev/coral none bind,optional,create=file`}
className="my-4"
/>
<CopyableCode
code={`# Coral M.2 passthrough example (automatically added if detected):
lxc.cgroup2.devices.allow: c 245:0 rwm
lxc.mount.entry: /dev/apex_0 dev/apex_0 none bind,optional,create=file`}
className="my-4"
/>
</Steps.Step>
<Steps.Step title="Install Required Drivers">
<p>The script installs the necessary components inside the container:</p>
<ul className="list-disc pl-6 space-y-1 mt-2">
<li>GPU drivers (va-driver-all, intel-opencl-icd).</li>
<li>Coral TPU dependencies (Python, GPG keys, repository setup).</li>
<li>Coral TPU drivers (USB and M.2 support).</li>
<li>GPU drivers:</li>
<ul className="list-disc pl-10">
<li><code>va-driver-all</code></li>
<li><code>ocl-icd-libopencl1</code></li>
<li><code>intel-opencl-icd</code></li>
<li><code>vainfo</code></li>
<li><code>intel-gpu-tools</code></li>
</ul>
<li>Coral TPU dependencies:</li>
<ul className="list-disc pl-10">
<li><code>python3</code></li>
<li><code>python3-pip</code></li>
<li><code>python3-venv</code></li>
<li><code>gnupg</code></li>
<li><code>curl</code></li>
</ul>
<li>Coral TPU drivers:</li>
<ul className="list-disc pl-10">
<li><code>libedgetpu1-std</code> (standard performance)</li>
<li><code>libedgetpu1-max</code> (maximum performance, optional)</li>
</ul>
</ul>
</Steps.Step>
<Steps.Step title="Select Coral TPU Driver Version">
@@ -70,8 +100,9 @@ export default function CoralTPULXC() {
<li>The script supports both USB and M.2 Coral TPU devices.</li>
<li>The Proxmox host must have the required Coral TPU and Intel GPU drivers installed.</li>
<li>Additional application-specific configurations may be required inside the container.</li>
<li>Coral USB passthrough uses a persistent device alias <code>/dev/coral</code> created by a udev rule. This improves stability and avoids issues with changing USB port identifiers.</li>
<li>Coral M.2 devices are detected dynamically using <code>lspci</code> and configured only if present.</li>
</ul>
</div>
)
}

View File

@@ -11,9 +11,10 @@ export default function InstallCoralTPUHost() {
<div className="max-w-3xl mx-auto">
<h1 className="text-3xl font-bold mb-6">Install Coral TPU on the Host</h1>
<p className="mb-4"><strong>Before using Coral TPU inside an LXC container, the drivers must first be installed on the Proxmox VE host. This script automates that process, ensuring the necessary setup is completed.</strong><br/><br/>
This guide explains how to install and configure Google Coral TPU drivers on a Proxmox VE host using <strong>ProxMenux</strong>.
This setup enables hardware acceleration for AI-based applications that leverage Coral TPU.
<p className="mb-4">
<strong>Before using Coral TPU inside an LXC container, the drivers must first be installed on the Proxmox VE host. This script automates that process, ensuring the necessary setup is completed.</strong>
<br /><br />
This guide explains how to install and configure Google Coral TPU drivers on a Proxmox VE host using <strong>ProxMenux</strong>. This setup enables hardware acceleration for AI-based applications that leverage Coral TPU.
</p>
<h2 className="text-2xl font-semibold mt-8 mb-4">Overview</h2>
@@ -21,7 +22,7 @@ export default function InstallCoralTPUHost() {
<ol className="list-decimal pl-6 space-y-2 mb-6">
<li>Prompts for confirmation before proceeding with installation.</li>
<li>Verifies and configures necessary repositories on the host.</li>
<li>Installs required dependencies for driver compilation.</li>
<li>Installs required build dependencies and kernel headers for driver compilation.</li>
<li>Clones the Coral TPU driver repository and builds the drivers.</li>
<li>Installs the compiled Coral TPU drivers.</li>
<li>Prompts for a system restart to apply changes.</li>
@@ -32,23 +33,45 @@ export default function InstallCoralTPUHost() {
<Steps.Step title="Pre-Installation Confirmation">
<p>The script prompts the user for confirmation before proceeding, as a system restart is required after installation.</p>
</Steps.Step>
<Steps.Step title="Repository Configuration">
<p>The script verifies and configures required repositories:</p>
<ul className="list-disc pl-6 space-y-1 mt-2">
<li>Adds the <strong>pve-no-subscription</strong> repository if not present.</li>
<li>Adds <strong>non-free-firmware</strong> repositories for required packages.</li>
<li>Runs an update to fetch the latest package lists.</li>
<li>Runs <code>apt-get update</code> to fetch the latest package lists.</li>
</ul>
</Steps.Step>
<Steps.Step title="Driver Installation">
<p>The script installs and compiles the required drivers:</p>
<p>The script installs and compiles the required Coral TPU drivers:</p>
<ul className="list-disc pl-6 space-y-1 mt-2">
<li>Installs dependencies such as <strong>git, dkms, devscripts</strong>, and kernel headers.</li>
<li>Clones the <strong>gasket-driver</strong> repository from Google.</li>
<li>Builds the Coral TPU driver packages.</li>
<li>Installs the compiled drivers on the host.</li>
<li>Installs the following packages:</li>
<ul className="list-disc pl-10">
<li><code>git</code></li>
<li><code>devscripts</code></li>
<li><code>dh-dkms</code></li>
<li><code>dkms</code></li>
<li><code>pve-headers-$(uname -r)</code> (Proxmox kernel headers)</li>
</ul>
<li>Clones the Coral TPU driver source from:</li>
<ul className="list-disc pl-10">
<li><code>https://github.com/google/gasket-driver</code></li>
</ul>
<li>Builds the driver using <code>debuild</code> and installs it using <code>dpkg -i</code>.</li>
</ul>
<CopyableCode
code={`# Commands used to build and install Coral TPU driver on host
apt install -y git devscripts dh-dkms dkms pve-headers-$(uname -r)
git clone https://github.com/google/gasket-driver.git
cd gasket-driver
debuild -us -uc -tc -b
dpkg -i ../gasket-dkms_*.deb`}
className="my-4"
/>
</Steps.Step>
<Steps.Step title="Post-Installation Confirmation">
<p>The script prompts the user to restart the server to apply the changes.</p>
</Steps.Step>
@@ -60,8 +83,6 @@ export default function InstallCoralTPUHost() {
<li>Required repositories and dependencies are configured properly.</li>
<li>A system restart is performed to complete the installation.</li>
</ul>
</div>
)
}

View File

@@ -10,6 +10,31 @@ interface ChangelogEntry {
title: string
}
// Function to clean and format markdown content for RSS
function formatContentForRSS(content: string): string {
return (
content
// Convert ### headers to bold text
.replace(/^### (.+)$/gm, "**$1**")
// Convert ** bold ** to simple bold
.replace(/\*\*(.*?)\*\*/g, "$1")
// Clean code blocks - remove ``` and format nicely
.replace(/```[\s\S]*?```/g, (match) => {
const code = match.replace(/```/g, "").trim()
return `\n${code}\n`
})
// Convert - bullet points to •
.replace(/^- /gm, "• ")
// Clean up multiple newlines
.replace(/\n{3,}/g, "\n\n")
// Remove backslashes used for line breaks
.replace(/\\\s*$/gm, "")
// Clean up extra spaces
.replace(/\s+/g, " ")
.trim()
)
}
async function parseChangelog(): Promise<ChangelogEntry[]> {
try {
const changelogPath = path.join(process.cwd(), "..", "CHANGELOG.md")
@@ -21,45 +46,67 @@ async function parseChangelog(): Promise<ChangelogEntry[]> {
const fileContents = fs.readFileSync(changelogPath, "utf8")
const entries: ChangelogEntry[] = []
// Split by any heading (## or ###) to catch all changes, not just versions
const sections = fileContents.split(/^(##\s+.*$)/gm).filter((section) => section.trim())
// Split by ## headers (both versions and dates)
const lines = fileContents.split("\n")
let currentEntry: Partial<ChangelogEntry> | null = null
let contentLines: string[] = []
for (let i = 0; i < sections.length - 1; i += 2) {
const headerLine = sections[i]
const content = sections[i + 1] || ""
for (const line of lines) {
// Check for version header: ## [1.1.1] - 2025-03-21
const versionMatch = line.match(/^##\s+\[([^\]]+)\]\s*-\s*(\d{4}-\d{2}-\d{2})/)
// Check if it's a version header (## [version] - date)
const versionMatch = headerLine.match(/##\s+\[([^\]]+)\]\s*-\s*(\d{4}-\d{2}-\d{2})/)
// Check for date-only header: ## 2025-05-13
const dateMatch = line.match(/^##\s+(\d{4}-\d{2}-\d{2})$/)
if (versionMatch || dateMatch) {
// Save previous entry if exists
if (currentEntry && contentLines.length > 0) {
const rawContent = contentLines.join("\n").trim()
currentEntry.content = formatContentForRSS(rawContent)
if (currentEntry.version && currentEntry.date && currentEntry.title) {
entries.push(currentEntry as ChangelogEntry)
}
}
// Start new entry
if (versionMatch) {
const version = versionMatch[1]
const date = versionMatch[2]
entries.push({
currentEntry = {
version,
date,
content: content.trim(),
url: `https://macrimi.github.io/ProxMenux/changelog#${version}`,
title: `ProxMenux ${version}`,
})
} else {
// Check for date-only headers (## 2025-05-13)
const dateMatch = headerLine.match(/##\s+(\d{4}-\d{2}-\d{2})/)
if (dateMatch) {
}
} else if (dateMatch) {
const date = dateMatch[1]
entries.push({
currentEntry = {
version: date,
date,
content: content.trim(),
url: `https://macrimi.github.io/ProxMenux/changelog#${date}`,
title: `ProxMenux Update ${date}`,
})
}
}
contentLines = []
} else if (currentEntry && line.trim()) {
// Add content lines (skip empty lines at the beginning)
if (contentLines.length > 0 || line.trim() !== "") {
contentLines.push(line)
}
}
}
return entries.slice(0, 15) // Latest 15 entries
// Don't forget the last entry
if (currentEntry && contentLines.length > 0) {
const rawContent = contentLines.join("\n").trim()
currentEntry.content = formatContentForRSS(rawContent)
if (currentEntry.version && currentEntry.date && currentEntry.title) {
entries.push(currentEntry as ChangelogEntry)
}
}
return entries.slice(0, 20) // Latest 20 entries
} catch (error) {
console.error("Error parsing changelog:", error)
return []
@@ -87,7 +134,7 @@ export async function GET() {
(entry) => `
<item>
<title>${entry.title}</title>
<description><![CDATA[${entry.content.substring(0, 500)}${entry.content.length > 500 ? "..." : ""}]]></description>
<description><![CDATA[${entry.content.length > 1000 ? entry.content.substring(0, 1000) + "..." : entry.content}]]></description>
<link>${entry.url}</link>
<guid isPermaLink="true">${entry.url}</guid>
<pubDate>${new Date(entry.date).toUTCString()}</pubDate>

View File

@@ -81,7 +81,6 @@ export const sidebarItems: MenuItem[] = [
{
title: "Network",
submenu: [
{ title: "Repair Network", href: "/docs/network/repair-network" },
{ title: "Verify Network", href: "/docs/network/verify-network" },
{ title: "Show IP Information", href: "/docs/network/show-ip-information" },
],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB