734 Commits

Author SHA1 Message Date
MacRimi
5cb9e13ca7 Update CHANGELOG.md 2025-09-10 17:37:51 +02:00
MacRimi
0187010f94 Create main-menu.png 2025-09-10 16:28:47 +02:00
ProxMenuxBot
2c2ed21e59 Update helpers_cache.json 2025-09-10 12:24:52 +00:00
MacRimi
f8b2ccec40 Update commands_share.sh 2025-09-10 13:48:17 +02:00
MacRimi
e858dc582d Update commands_share.sh 2025-09-10 13:47:13 +02:00
MacRimi
dd737f4b46 Update commands_share.sh 2025-09-10 13:46:02 +02:00
MacRimi
f0bc238b6d Update commands_share.sh 2025-09-10 13:35:16 +02:00
MacRimi
af55424850 Update lxc-mount-manager_minimal.sh 2025-09-10 13:16:54 +02:00
MacRimi
902534baff update menu shared 2025-09-10 12:57:17 +02:00
MacRimi
6daa630040 Update nfs_host.sh 2025-09-10 12:53:09 +02:00
MacRimi
0b2b86673b Update lxc-mount-manager_minimal.sh 2025-09-10 12:31:49 +02:00
MacRimi
6aa5b58208 Update share_menu.sh 2025-09-10 12:17:03 +02:00
MacRimi
4430201cd2 Update lxc-mount-manager_minimal.sh 2025-09-10 12:16:25 +02:00
MacRimi
7c7963a83e Create lxc-mount-manager_minimal.sh 2025-09-10 11:10:02 +02:00
ProxMenuxBot
e2202cd2d8 Update helpers_cache.json 2025-09-09 18:17:13 +00:00
MacRimi
a931be83bc Update share-common.func 2025-09-09 19:20:15 +02:00
MacRimi
7350bea345 Update auto_post_install.sh 2025-09-09 19:16:13 +02:00
MacRimi
9b1e39dbb4 Update update-pve.sh 2025-09-09 19:14:27 +02:00
MacRimi
15cd118845 Update auto_post_install.sh 2025-09-09 19:06:33 +02:00
MacRimi
d58dff047c Update auto_post_install.sh 2025-09-08 19:24:49 +02:00
MacRimi
a2f83c896c Update common-functions.sh 2025-09-08 19:14:41 +02:00
MacRimi
6ef77c731c Update guia.md 2025-09-08 17:44:17 +02:00
MacRimi
29b0f61958 Update guia.md 2025-09-08 17:43:39 +02:00
MacRimi
e944b2ecdd Update guia.md 2025-09-08 17:24:23 +02:00
MacRimi
41819c46a3 Update guia.md 2025-09-08 17:21:58 +02:00
MacRimi
13f391a6f0 Update guia.md 2025-09-08 17:14:52 +02:00
MacRimi
85a3d44f2c Update guia.md 2025-09-08 17:05:26 +02:00
MacRimi
0792392058 Update guia.md 2025-09-08 16:42:46 +02:00
MacRimi
ff5083ada0 Update guia.md 2025-09-08 16:20:10 +02:00
MacRimi
62841677bc Update guia.md 2025-09-08 16:06:50 +02:00
MacRimi
1761cf53a2 Update guia.md 2025-09-08 15:27:32 +02:00
MacRimi
a771efc5fa Update guia.md 2025-09-08 15:08:02 +02:00
MacRimi
ed049da76a Update guia.md 2025-09-08 15:03:10 +02:00
MacRimi
5d1d357a2e Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-09-08 15:01:02 +02:00
MacRimi
30d0706a1c Create guia.md 2025-09-08 15:01:00 +02:00
ProxMenuxBot
e9667e1266 Update helpers_cache.json 2025-09-08 12:27:18 +00:00
MacRimi
73109483e7 Update share-common.func 2025-09-08 11:29:02 +02:00
MacRimi
a9c1acf204 Update lxc-mount-manager.sh 2025-09-08 11:15:52 +02:00
MacRimi
81c4f5814c Update samba_host.sh 2025-09-08 10:40:59 +02:00
MacRimi
c595f6d781 Update nfs_host.sh 2025-09-08 10:36:57 +02:00
MacRimi
24bb6b1d3d Update lxc-mount-manager.sh 2025-09-07 19:10:23 +02:00
MacRimi
49eeb6020d Update install_proxmenux.sh 2025-09-07 17:24:29 +02:00
MacRimi
7c272bd2a2 Update install_proxmenux.sh 2025-09-07 17:22:47 +02:00
MacRimi
cfbd865937 Update share-common.func 2025-09-07 09:29:43 +02:00
MacRimi
fe472f33ef Update lxc-mount-manager.sh 2025-09-07 09:28:09 +02:00
MacRimi
8d6b3d650f update menu share 2025-09-07 09:19:53 +02:00
MacRimi
3b0d5b5eb7 Update nfs_lxc_server.sh 2025-09-07 09:16:22 +02:00
MacRimi
875e8a99bd Update samba_client.sh 2025-09-07 09:06:47 +02:00
MacRimi
6c19d81844 Update samba_client.sh 2025-09-07 09:05:23 +02:00
MacRimi
ba535a931f Update nfs_client.sh 2025-09-07 09:04:13 +02:00
MacRimi
45dca5218d Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-09-07 09:00:39 +02:00
MacRimi
da3cb9971b Update share-common.func 2025-09-07 09:00:37 +02:00
ProxMenuxBot
b39270dc1e Update helpers_cache.json 2025-09-07 01:03:30 +00:00
MacRimi
ae8a7d0de9 Update commands_share.sh 2025-09-06 23:59:23 +02:00
MacRimi
2d501415bf Update commands_share.sh 2025-09-06 23:50:57 +02:00
MacRimi
da639ccaac Update commands_share.sh 2025-09-06 23:35:46 +02:00
MacRimi
a352770e2d Update share_menu.sh 2025-09-06 23:20:59 +02:00
MacRimi
e3e1899466 update menu share 2025-09-06 23:18:11 +02:00
MacRimi
e67288e623 Update lxc-mount-manager.sh 2025-09-06 22:55:28 +02:00
MacRimi
4019e49b07 Update share-common.func 2025-09-06 22:51:57 +02:00
MacRimi
cd8711f3bc Update lxc-mount-manager.sh 2025-09-06 22:50:30 +02:00
MacRimi
0d119379de Update lxc-mount-manager.sh 2025-09-06 22:46:52 +02:00
MacRimi
aa2b6ff112 Update lxc-mount-manager.sh 2025-09-06 22:44:09 +02:00
MacRimi
3482f7dc98 Update share-common.func 2025-09-06 22:42:17 +02:00
MacRimi
16c321f114 Update lxc-mount-manager.sh 2025-09-06 22:33:19 +02:00
MacRimi
a81e7f3c44 Update lxc-mount-manager.sh 2025-09-06 22:25:50 +02:00
MacRimi
d7cc001521 Update lxc-mount-manager.sh 2025-09-06 22:20:34 +02:00
MacRimi
eb11962231 Update share-common.func 2025-09-06 22:19:52 +02:00
MacRimi
9f73b8f159 Update lxc-mount-manager.sh 2025-09-06 22:17:08 +02:00
MacRimi
873a4abe24 Update share-common.func 2025-09-06 22:16:40 +02:00
MacRimi
56bc584f5e Update lxc-mount-manager.sh 2025-09-06 22:10:01 +02:00
MacRimi
2a9f2f3c2e Update lxc-mount-manager.sh 2025-09-06 22:09:11 +02:00
MacRimi
ee719cdd39 Update share-common.func 2025-09-06 22:06:02 +02:00
MacRimi
a571b57b30 Update share-common.func 2025-09-06 21:48:39 +02:00
MacRimi
5ee7a23bea Update share-common.func 2025-09-06 21:47:20 +02:00
MacRimi
fe159ea195 Update share-common.func 2025-09-06 21:42:30 +02:00
MacRimi
8fcdf6176b Update share-common.func 2025-09-06 21:31:03 +02:00
MacRimi
715166bbca Update share-common.func 2025-09-06 21:22:40 +02:00
MacRimi
1d58072c70 Update share-common.func 2025-09-06 21:21:39 +02:00
MacRimi
d667cde699 Update lxc-mount-manager.sh 2025-09-06 21:19:55 +02:00
MacRimi
4cd8889c38 Update share-common.func 2025-09-06 21:14:54 +02:00
MacRimi
93896f6fb7 Update share-common.func 2025-09-06 21:07:12 +02:00
MacRimi
3b3f0387bb Update share-common.func 2025-09-06 21:05:32 +02:00
MacRimi
2875c9af95 Update lxc-mount-manager.sh 2025-09-06 20:59:03 +02:00
MacRimi
93ef1bfccc update share menu 2025-09-06 20:57:04 +02:00
MacRimi
a886af1d87 Update samba_client.sh 2025-09-06 20:48:37 +02:00
MacRimi
d731ff3ae6 Update samba_host.sh 2025-09-06 20:40:45 +02:00
MacRimi
d44864637d Update nfs_host_auto.sh 2025-09-06 20:37:23 +02:00
MacRimi
674ee34ec6 Update nfs_host_auto.sh 2025-09-06 20:30:04 +02:00
MacRimi
a93eeda243 Update share-common.func 2025-09-06 19:56:24 +02:00
MacRimi
80fd92e2a1 Update share-common.func 2025-09-06 19:55:01 +02:00
MacRimi
d4ff2da473 Update share-common.func 2025-09-06 19:16:15 +02:00
MacRimi
9b7b271580 update menu shared 2025-09-06 19:13:52 +02:00
MacRimi
e1b340966a Update share-common.func 2025-09-06 18:56:10 +02:00
MacRimi
7ec4c331af Update nfs_client.sh 2025-09-06 18:39:35 +02:00
MacRimi
3102d596ee Update nfs_lxc_server.sh 2025-09-06 18:37:28 +02:00
MacRimi
af56dc546e Update nfs_host_auto.sh 2025-09-06 18:26:13 +02:00
MacRimi
15d47499fa Update nfs_host_auto.sh 2025-09-06 18:22:02 +02:00
MacRimi
53a34d0470 update nfs menu 2025-09-06 18:20:20 +02:00
MacRimi
3ee675cefe Update nfs_lxc_server.sh 2025-09-06 18:08:48 +02:00
MacRimi
d98c7bdc03 Update share_menu.sh 2025-09-06 16:48:07 +02:00
MacRimi
bb4f1ebed6 Update share_menu.sh 2025-09-06 16:44:58 +02:00
MacRimi
c8f73ea23b Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-09-06 16:41:55 +02:00
MacRimi
8292b12787 Create nfs_lxc_server.sh 2025-09-06 16:41:54 +02:00
ProxMenuxBot
0f518e3c35 Update helpers_cache.json 2025-09-06 12:22:12 +00:00
MacRimi
1c2f67d43d Update lxc-mount-manager.sh 2025-09-06 11:55:23 +02:00
MacRimi
a5560a3123 Update share-common.func 2025-09-06 11:50:48 +02:00
MacRimi
1332096360 Update share-common.func 2025-09-06 11:39:17 +02:00
MacRimi
80381a6375 Update share-common.func 2025-09-06 11:32:07 +02:00
MacRimi
acf92bd005 Update share-common.func 2025-09-06 11:30:23 +02:00
MacRimi
da4f8a3a19 Update share-common.func 2025-09-06 11:11:19 +02:00
MacRimi
3a332192e3 Update share-common.func 2025-09-06 11:08:23 +02:00
MacRimi
1fdb1d87cc Update share-common.func 2025-09-06 11:01:35 +02:00
MacRimi
b99aa55d7a Update share-common.func 2025-09-06 10:46:22 +02:00
MacRimi
de20da2dad Update lxc-mount-manager.sh 2025-09-06 10:10:37 +02:00
MacRimi
9444f0a68b Update nfs_host_auto.sh 2025-09-06 08:51:41 +02:00
MacRimi
48fd223a28 Update samba.sh 2025-09-04 20:38:11 +02:00
MacRimi
0845efe419 Update samba_client.sh 2025-09-04 20:37:18 +02:00
MacRimi
57b7ba91bc Update share_menu.sh 2025-09-04 20:34:43 +02:00
MacRimi
97af8a4892 Update share_menu.sh 2025-09-04 20:31:33 +02:00
MacRimi
d6f237e289 Update share_menu.sh 2025-09-04 20:30:36 +02:00
MacRimi
aba7109b35 Update samba_host.sh 2025-09-04 20:28:16 +02:00
MacRimi
d3ec71052e Update samba_client.sh 2025-09-04 20:26:51 +02:00
MacRimi
1be63f396b Update nfs_client.sh 2025-09-04 20:26:26 +02:00
MacRimi
9308742146 Update samba_client.sh 2025-09-04 20:23:00 +02:00
MacRimi
b32241082d Update share_menu.sh 2025-09-04 20:12:24 +02:00
MacRimi
1f8504d685 update shared 2025-09-04 20:04:08 +02:00
MacRimi
97c5c48150 Update nfs_host.sh 2025-09-03 23:13:21 +02:00
MacRimi
afe84dc46a Update nfs_client.sh 2025-09-03 23:10:01 +02:00
MacRimi
ffafd42f03 Update nfs.sh 2025-09-03 23:06:47 +02:00
MacRimi
7dca715c91 Update nfs.sh 2025-09-03 23:04:46 +02:00
MacRimi
7695e1d8dd Update nfs.sh 2025-09-03 22:55:45 +02:00
MacRimi
84b86d1db7 Update nfs.sh 2025-09-03 22:14:29 +02:00
MacRimi
bae3ef6460 Update nfs.sh 2025-09-03 22:06:34 +02:00
MacRimi
97c6ec8875 Update share-common.func 2025-09-03 16:47:03 +02:00
MacRimi
d33128dc26 Update share_menu.sh 2025-09-03 12:27:59 +02:00
MacRimi
10bdecabb6 Update share_menu.sh 2025-09-03 12:25:35 +02:00
MacRimi
de88f530c8 Update share_menu.sh 2025-09-03 12:23:54 +02:00
MacRimi
fb511b7596 Update share_menu.sh 2025-09-03 12:22:49 +02:00
MacRimi
322665ce91 Update share_menu.sh 2025-09-03 12:21:21 +02:00
MacRimi
baeca1fcfb Update share-common.func 2025-09-03 11:35:38 +02:00
MacRimi
095b98c36a Update share-common.func 2025-09-03 11:28:37 +02:00
MacRimi
29bb7e7608 Update share-common.func 2025-09-03 11:16:34 +02:00
MacRimi
e3d137efba Update share-common.func 2025-09-02 22:56:46 +02:00
MacRimi
207e915393 Update share-common.func 2025-09-02 22:45:33 +02:00
MacRimi
614e629a2b Update share-common.func 2025-09-02 22:44:42 +02:00
MacRimi
f35de5c749 Update share-common.func 2025-09-02 21:34:13 +02:00
MacRimi
c1623bd4df Update share-common.func 2025-09-02 21:23:57 +02:00
ProxMenuxBot
8690da5017 Update helpers_cache.json 2025-09-02 18:17:01 +00:00
MacRimi
696adcdc24 Update share-common.func 2025-09-02 18:48:57 +02:00
MacRimi
2756bd06c1 Update share-common.func 2025-09-02 18:48:20 +02:00
MacRimi
4893f6ea00 Update lxc-mount-manager.sh 2025-09-02 18:45:53 +02:00
MacRimi
35a7348197 Update lxc-mount-manager.sh 2025-09-02 18:45:04 +02:00
MacRimi
cdd6333d0a Update share_menu.sh 2025-09-02 18:43:27 +02:00
MacRimi
54399b5b5d Update share-common.func 2025-09-02 18:25:39 +02:00
MacRimi
f6b192cc1e Update lxc-mount-manager.sh 2025-09-02 18:22:34 +02:00
MacRimi
cd231b90d8 Update share-common.func 2025-09-02 17:21:09 +02:00
ProxMenuxBot
87fe788358 Update helpers_cache.json 2025-09-02 12:26:27 +00:00
MacRimi
3e9bd21ea8 update share menu 2025-09-02 00:02:16 +02:00
MacRimi
b6d4029797 Update local-shared-manager.sh 2025-09-01 19:10:46 +02:00
MacRimi
ec65e96148 Update share_menu.sh 2025-09-01 19:08:48 +02:00
MacRimi
926f1f971f update share menu 2025-09-01 19:06:52 +02:00
MacRimi
5d69fad73f Update share_menu.sh 2025-09-01 18:45:14 +02:00
MacRimi
a796761023 Update share-common.func 2025-09-01 17:39:45 +02:00
MacRimi
5d1338e485 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-09-01 17:21:41 +02:00
MacRimi
ce25a167f1 Update auto_post_install.sh 2025-09-01 17:21:39 +02:00
MacRimi
1c44969580 Update share_menu.sh 2025-09-01 14:43:46 +02:00
MacRimi
b6e04e3ede Update share-common.func 2025-09-01 14:30:16 +02:00
ProxMenuxBot
84c26be703 Update helpers_cache.json 2025-09-01 12:26:47 +00:00
MacRimi
d201160722 Update share-common.func 2025-09-01 14:08:10 +02:00
MacRimi
e112361b43 Update share-common.func 2025-09-01 13:53:54 +02:00
MacRimi
3e69795c9d Update lxc-mount-manager.sh 2025-09-01 13:33:48 +02:00
MacRimi
b11baf2e5d Update auto_post_install.sh 2025-09-01 12:42:46 +02:00
MacRimi
233770b553 Update auto_post_install.sh 2025-09-01 12:41:26 +02:00
MacRimi
187db73798 Update zimaos.sh 2025-09-01 12:20:45 +02:00
MacRimi
0e3fc6f682 Update zimaos.sh 2025-09-01 12:15:30 +02:00
MacRimi
d11e3a4ac4 Update auto_post_install.sh 2025-08-31 23:45:37 +02:00
MacRimi
d3b4ca3e66 Update customizable_post_install.sh 2025-08-31 21:46:28 +02:00
MacRimi
f37fbbfb8b Update menu share 2025-08-30 18:56:49 +02:00
MacRimi
52b7aac424 Update share-common.func 2025-08-30 18:05:02 +02:00
MacRimi
d42f3f8f0c Update share-common.func 2025-08-30 18:02:20 +02:00
MacRimi
91b5c7c9bc Update share-common.func 2025-08-30 17:51:45 +02:00
MacRimi
48feebc092 Update share-common.func 2025-08-30 16:59:46 +02:00
MacRimi
14e2d66d96 Update share-common.func 2025-08-30 11:43:02 +02:00
MacRimi
10d844a195 Update share-common.func 2025-08-30 11:36:52 +02:00
MacRimi
bbf91ae5d6 Update main_menu.sh 2025-08-30 09:40:42 +02:00
ProxMenuxBot
cb82eda49a Update helpers_cache.json 2025-08-30 00:57:37 +00:00
MacRimi
bc1dbb1c27 Update help_info_menu.sh 2025-08-30 00:17:51 +02:00
ProxMenuxBot
9496a7f1ce Update helpers_cache.json 2025-08-28 18:19:20 +00:00
ProxMenuxBot
7241fa31b4 Update helpers_cache.json 2025-08-28 12:26:37 +00:00
MacRimi
fed7216436 Update share-common.func 2025-08-27 18:15:26 +02:00
MacRimi
ffe7d7c4c6 Create group_manager.sh 2025-08-27 10:54:03 +02:00
MacRimi
f430ac8d6c Update upgrade_pve8_to_pve9.sh 2025-08-26 19:40:38 +02:00
MacRimi
70dfd7c9a3 Update upgrade_pve8_to_pve9.sh 2025-08-26 19:25:59 +02:00
MacRimi
ed3140932b Update upgrade_pve8_to_pve9.sh 2025-08-26 19:24:41 +02:00
MacRimi
3cd2bd6ce8 Update share-common.func 2025-08-26 17:46:48 +02:00
MacRimi
982bf45fc4 Update share-common.func 2025-08-26 17:37:09 +02:00
MacRimi
aaba8569fc Update share-common.func 2025-08-26 17:19:11 +02:00
MacRimi
4111e15eb9 Update share-common.func 2025-08-26 17:15:47 +02:00
MacRimi
2012478f26 Update share-common.func 2025-08-26 17:05:17 +02:00
MacRimi
88869d3239 Update share-common.func 2025-08-26 17:02:24 +02:00
ProxMenuxBot
f3c2549b18 Update helpers_cache.json 2025-08-26 12:28:24 +00:00
MacRimi
57e3b839d0 Update share-common.func 2025-08-26 13:56:00 +02:00
MacRimi
faf3f43413 Create share-common.func 2025-08-26 13:26:22 +02:00
ProxMenuxBot
52e5bb3386 Update helpers_cache.json 2025-08-26 01:02:34 +00:00
ProxMenuxBot
89405f6670 Update helpers_cache.json 2025-08-25 18:20:02 +00:00
ProxMenuxBot
73111c4139 Update helpers_cache.json 2025-08-25 12:27:14 +00:00
ProxMenuxBot
04e9c5db8c Update helpers_cache.json 2025-08-24 12:24:09 +00:00
MacRimi
69278902de Update customizable_post_install.sh 2025-08-24 10:55:25 +02:00
MacRimi
efa95b0858 Update customizable_post_install.sh 2025-08-24 10:29:00 +02:00
MacRimi
660128cd5c Update customizable_post_install.sh 2025-08-24 10:28:14 +02:00
MacRimi
ef1e052e47 Update customizable_post_install.sh 2025-08-24 10:06:20 +02:00
ProxMenuxBot
0b346bc343 Update helpers_cache.json 2025-08-24 06:19:28 +00:00
MacRimi
2272eaf833 Update lxc-unprivileged-to-privileged.sh 2025-08-22 19:08:03 +02:00
MacRimi
4adee98bce new menu lxc 2025-08-22 19:05:36 +02:00
MacRimi
cbdb2c0705 Rename lxc-manual-guide.sh to lxc-manual-guide.sh 2025-08-22 19:04:10 +02:00
MacRimi
4f438aabbf update manual lxc guide 2025-08-22 19:03:08 +02:00
MacRimi
b6ccc06963 Update lxc_menu.sh 2025-08-22 18:58:10 +02:00
MacRimi
5b89a15bfc menu lxc 2025-08-22 18:57:00 +02:00
MacRimi
5596ae551d Update storage_menu.sh 2025-08-22 18:34:12 +02:00
MacRimi
1360df592a Create backup_host4.sh 2025-08-22 18:17:01 +02:00
MacRimi
13684ff83c Update share_menu.sh 2025-08-22 18:10:34 +02:00
MacRimi
ae88f7870e Update share_menu.sh 2025-08-22 18:08:20 +02:00
MacRimi
810b6da60c Share menu 2025-08-22 18:05:14 +02:00
ProxMenuxBot
7bdf3e08f9 Update helpers_cache.json 2025-08-21 18:19:22 +00:00
MacRimi
fdad2a087f Update zimaos.sh 2025-08-21 19:39:50 +02:00
MacRimi
c437a8c426 Update zimaos.sh 2025-08-21 19:36:28 +02:00
MacRimi
ef861e6d1d Update zimaos.sh 2025-08-21 19:02:58 +02:00
MacRimi
928a008688 Update zimaos.sh 2025-08-21 18:59:54 +02:00
MacRimi
638a124adb Update zimaos.sh 2025-08-21 18:31:32 +02:00
MacRimi
c2a63ae9bb Update zimaos.sh 2025-08-21 18:30:43 +02:00
MacRimi
28cf31e6e7 Update zimaos.sh 2025-08-21 18:27:30 +02:00
MacRimi
3cf416167d Update select_nas_iso.sh 2025-08-21 18:22:57 +02:00
MacRimi
ebf03923a0 Update select_nas_iso.sh 2025-08-21 18:14:58 +02:00
MacRimi
82797d2421 Update select_nas_iso.sh 2025-08-21 18:05:45 +02:00
MacRimi
52b6be946c Create zimaos.sh 2025-08-21 18:00:58 +02:00
MacRimi
dc46724d7b Update select_linux_iso.sh 2025-08-20 23:02:38 +02:00
MacRimi
ed7d43b6a9 Update select_linux_iso.sh 2025-08-20 22:58:14 +02:00
MacRimi
6f3fc51278 Update system_utils.sh 2025-08-20 22:40:42 +02:00
MacRimi
a446acc282 Update customizable_post_install.sh 2025-08-20 22:39:28 +02:00
MacRimi
d987d639ab Update version.txt 2025-08-20 21:38:45 +02:00
MacRimi
e7e180e468 Update CHANGELOG.md 2025-08-20 21:38:20 +02:00
MacRimi
76770f82cd Update system_utils.sh 2025-08-20 21:22:49 +02:00
MacRimi
4079d4fd7c Update customizable_post_install.sh 2025-08-20 21:21:13 +02:00
MacRimi
ac48178369 Update lxc-privileged-to-unprivileged.sh 2025-08-20 21:10:05 +02:00
MacRimi
c2e9f038ee Update lxc-privileged-to-unprivileged.sh 2025-08-20 21:04:50 +02:00
MacRimi
70220d9829 Update lxc-privileged-to-unprivileged.sh 2025-08-20 21:03:57 +02:00
MacRimi
b9a1f378ec Update lxc_menu.sh 2025-08-20 20:59:58 +02:00
MacRimi
f6bc090a98 Update lxc_menu.sh 2025-08-20 20:55:43 +02:00
MacRimi
be519f3932 Create lxc_menu.sh 2025-08-20 20:46:51 +02:00
MacRimi
0a46f77555 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-08-20 20:43:56 +02:00
MacRimi
0e6cc0c7e5 Create lxc-privileged-to-unprivileged.sh 2025-08-20 20:43:54 +02:00
ProxMenuxBot
11cd425162 Update helpers_cache.json 2025-08-20 18:19:44 +00:00
MacRimi
aa269688d6 Update utilities_menu.sh 2025-08-20 19:51:54 +02:00
MacRimi
4c9e94768e Update upgrade_pve8_to_pve9.sh 2025-08-20 19:48:25 +02:00
MacRimi
581157fa82 Update upgrade_pve8_to_pve9.sh 2025-08-20 19:46:30 +02:00
MacRimi
e748e479cc Update utilities_menu.sh 2025-08-20 19:44:16 +02:00
MacRimi
5c9e4eea1e Update upgrade_pve8_to_pve9.sh 2025-08-20 19:41:09 +02:00
MacRimi
0c1189b233 Update pve8to9_check.sh 2025-08-20 19:37:14 +02:00
MacRimi
5ec9b82b4a Update upgrade_pve8_to_pve9.sh 2025-08-20 19:22:22 +02:00
MacRimi
c84ec533da Create pve8to9_check.sh 2025-08-20 19:22:12 +02:00
MacRimi
fb80c6ad7a Update upgrade_pve8_to_pve9.sh 2025-08-20 19:09:52 +02:00
ProxMenuxBot
2e3bfff6a4 Update helpers_cache.json 2025-08-19 18:18:30 +00:00
ProxMenuxBot
e96ce30891 Update helpers_cache.json 2025-08-19 12:26:48 +00:00
MacRimi
10de5b2e5f Update customizable_post_install.sh 2025-08-19 14:15:34 +02:00
MacRimi
1966081239 Update auto_post_install.sh 2025-08-19 14:13:56 +02:00
MacRimi
b48d806d53 Update system_utils.sh 2025-08-19 13:42:42 +02:00
MacRimi
97784d74e7 Update customizable_post_install.sh 2025-08-19 13:38:59 +02:00
ProxMenuxBot
c42e92b07d Update helpers_cache.json 2025-08-19 01:03:35 +00:00
MacRimi
2c52943b54 Update customizable_post_install.sh 2025-08-18 23:36:30 +02:00
MacRimi
4ccb1902cb Update customizable_post_install.sh 2025-08-18 23:30:59 +02:00
MacRimi
349b0572cd Update customizable_post_install.sh 2025-08-18 23:23:35 +02:00
MacRimi
87fae8a9eb Update customizable_post_install.sh 2025-08-18 23:18:26 +02:00
ProxMenuxBot
a77a097f47 Update helpers_cache.json 2025-08-18 18:20:28 +00:00
MacRimi
a84d81143e Update auto_post_install.sh 2025-08-18 15:10:23 +02:00
MacRimi
d9cee50ef3 Update auto_post_install.sh 2025-08-18 15:08:56 +02:00
MacRimi
0fc414e5e9 Update customizable_post_install.sh 2025-08-18 15:08:25 +02:00
ProxMenuxBot
e18f20ce4c Update helpers_cache.json 2025-08-18 12:29:04 +00:00
MacRimi
c12af4060c Update system_utils.sh 2025-08-18 14:20:47 +02:00
MacRimi
9992ea0dee Update customizable_post_install.sh 2025-08-18 14:15:19 +02:00
MacRimi
b8310f1c5d Update main_menu.sh 2025-08-18 09:37:06 +02:00
MacRimi
78f66af702 Update auto_post_install.sh 2025-08-17 15:16:25 +02:00
MacRimi
4d7564094e Update auto_post_install.sh 2025-08-17 15:14:46 +02:00
MacRimi
370f4694d1 Update customizable_post_install.sh 2025-08-17 15:14:20 +02:00
MacRimi
fc7c740691 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-08-17 15:05:42 +02:00
MacRimi
8e1f955519 Update auto_post_install.sh 2025-08-17 15:04:27 +02:00
MacRimi
aa3d16d981 Update auto_post_install.sh 2025-08-17 15:02:42 +02:00
MacRimi
5447f0e4df Update auto_post_install.sh 2025-08-17 15:00:06 +02:00
MacRimi
97294df208 Update customizable_post_install.sh 2025-08-17 14:21:37 +02:00
MacRimi
c6f53629da Update auto_post_install.sh 2025-08-17 14:20:58 +02:00
MacRimi
fcba907658 Update upgrade_pve8_to_pve9.sh 2025-08-17 13:24:32 +02:00
MacRimi
f481df7b8d Update configure_igpu_lxc.sh 2025-08-17 13:08:39 +02:00
MacRimi
81079a35d9 Update uninstall-tools.sh 2025-08-17 11:23:13 +02:00
MacRimi
bda344c382 Update uninstall-tools.sh 2025-08-17 11:01:21 +02:00
MacRimi
c4ebc396af Update customizable_post_install.sh 2025-08-17 10:36:03 +02:00
MacRimi
4cdbf1231b Update customizable_post_install.sh 2025-08-16 18:32:19 +02:00
MacRimi
92db58a9f6 Update auto_post_install.sh 2025-08-16 18:31:10 +02:00
MacRimi
5f07f47308 Update configure_igpu_lxc.sh 2025-08-16 18:12:14 +02:00
MacRimi
2132ae79a6 Update configure_igpu_lxc.sh 2025-08-16 17:34:34 +02:00
MacRimi
bda7834a4f Update disk-passthrough_ct.sh 2025-08-16 17:22:33 +02:00
MacRimi
7693f313c4 Update disk-passthrough_ct.sh 2025-08-16 17:13:45 +02:00
MacRimi
d2200a64e0 Update upgrade_pve8_to_pve9.sh 2025-08-16 17:04:59 +02:00
MacRimi
a5c46ab837 Update upgrade_pve8_to_pve9.sh 2025-08-16 16:43:44 +02:00
MacRimi
a389282e23 Update upgrade_pve8_to_pve9.sh 2025-08-16 11:54:11 +02:00
MacRimi
1f90b5b739 Update upgrade_pve8_to_pve9.sh 2025-08-16 11:46:09 +02:00
MacRimi
2c59500046 Update configure_igpu_lxc.sh 2025-08-16 11:15:41 +02:00
MacRimi
a59a056e12 Update configure_igpu_lxc.sh 2025-08-16 11:11:43 +02:00
ProxMenuxBot
235364013b Update helpers_cache.json 2025-08-16 01:03:53 +00:00
MacRimi
1049ac6eac Update upgrade_pve8_to_pve9.sh 2025-08-15 12:15:42 +02:00
MacRimi
04dc7af25c Create remove-banner-pve9.sh 2025-08-15 12:06:54 +02:00
MacRimi
f62ea3ad04 update remove-banner-pve9.sh 2025-08-15 12:05:24 +02:00
MacRimi
14c75f2cd9 Update proxmox-upgrade-pve8-to-pve9-manual-guide.sh 2025-08-15 11:15:17 +02:00
MacRimi
9ae68b9653 Update customizable_post_install.sh 2025-08-15 10:22:57 +02:00
MacRimi
b7ab4c4568 Update customizable_post_install.sh 2025-08-15 10:13:44 +02:00
MacRimi
7d0b3a0c87 Update system_utils.sh 2025-08-15 09:55:22 +02:00
MacRimi
0d38f7f290 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-08-15 09:54:02 +02:00
MacRimi
12e5ef4231 Update customizable_post_install.sh 2025-08-15 09:54:00 +02:00
ProxMenuxBot
f3aa1f7414 Update helpers_cache.json 2025-08-15 01:06:47 +00:00
MacRimi
f2eaec6e02 Update upgrade_pve8_to_pve9.sh 2025-08-14 20:08:22 +02:00
MacRimi
0654a3ed55 Update upgrade_pve8_to_pve9.sh 2025-08-14 20:03:47 +02:00
MacRimi
9a27138d96 Update proxmox-upgrade-pve8-to-pve9-manual-guide.sh 2025-08-14 19:46:13 +02:00
MacRimi
b3c9f71c02 Update proxmox-upgrade-pve8-to-pve9-manual-guide.sh 2025-08-14 19:41:22 +02:00
MacRimi
e9c9b957db Delete upgrade_pve8_topve9.sh 2025-08-14 19:39:36 +02:00
MacRimi
29cdf6fa48 Create upgrade_pve8_to_pve9.sh 2025-08-14 19:39:12 +02:00
MacRimi
8466a8e21e Update upgrade_pve8_topve9.sh 2025-08-14 19:37:15 +02:00
MacRimi
1523b6b8a8 Update proxmox-upgrade-pve8-to-pve9-manual-guide.sh 2025-08-14 19:34:42 +02:00
MacRimi
33205e1008 Create upgrade_pve8_topve9.sh 2025-08-14 19:24:29 +02:00
MacRimi
537af385f8 Update main menu PVE 9 2025-08-14 17:58:59 +02:00
MacRimi
7259b0a850 Update install_proxmenux.sh 2025-08-14 17:52:57 +02:00
MacRimi
11fbfda6bf Update cache.json 2025-08-13 22:46:58 +02:00
MacRimi
4f0353d0fb Create proxmox-upgrade-pve8-to-pve9-manual-guide.sh 2025-08-13 22:46:38 +02:00
MacRimi
a605d68d73 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-08-13 20:29:26 +02:00
MacRimi
237b7fbf1b Update cache.json 2025-08-13 20:29:24 +02:00
ProxMenuxBot
4a7e21f6b4 Update helpers_cache.json 2025-08-13 18:19:42 +00:00
MacRimi
b7017573b8 Update update-pve8.sh 2025-08-13 19:45:31 +02:00
MacRimi
a98b087c5d Update log2RAM 2025-08-13 15:55:22 +02:00
MacRimi
161c840136 Update utils.sh 2025-08-11 08:57:26 +02:00
MacRimi
4dd2abc202 Update utils.sh 2025-08-10 21:56:07 +02:00
MacRimi
cc0e9f61a7 Update utils.sh 2025-08-10 21:43:05 +02:00
MacRimi
21a658f1f4 Update utils.sh 2025-08-10 21:23:19 +02:00
MacRimi
b99f391c2a Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-08-10 21:09:25 +02:00
MacRimi
9abe25b91a Update README.md 2025-08-10 21:09:01 +02:00
ProxMenuxBot
2531fc6dac Update helpers_cache.json 2025-08-09 12:25:42 +00:00
ProxMenuxBot
e5551cb179 Update helpers_cache.json 2025-08-09 01:05:21 +00:00
MacRimi
4728b7a8b7 Update utils.sh 2025-08-08 18:26:32 +02:00
ProxMenuxBot
2863921e15 Update helpers_cache.json 2025-08-08 12:29:54 +00:00
ProxMenuxBot
b93668edfe Update helpers_cache.json 2025-08-08 06:23:01 +00:00
ProxMenuxBot
8ae91b8c31 Update helpers_cache.json 2025-08-07 18:21:56 +00:00
MacRimi
894a23d701 Update common-functions.sh 2025-08-07 20:10:36 +02:00
MacRimi
3598906cbd Update common-functions.sh 2025-08-07 19:37:28 +02:00
MacRimi
75c12e2d4b update common funtions 2025-08-07 19:24:39 +02:00
MacRimi
d6e0519a3d Update common-functions.sh 2025-08-07 19:08:44 +02:00
MacRimi
41efc71626 Update update-pve.sh 2025-08-07 19:05:52 +02:00
MacRimi
6e3d97f472 Update common-functions.sh 2025-08-07 19:05:23 +02:00
MacRimi
9d58d02522 Update proxmox_update.sh 2025-08-07 18:11:32 +02:00
MacRimi
fe86396b21 Update proxmox_update.sh 2025-08-07 18:08:37 +02:00
MacRimi
97994f7632 Update update-pve.sh 2025-08-07 17:58:24 +02:00
MacRimi
33d63457b3 Update update-pve8.sh 2025-08-07 17:58:00 +02:00
MacRimi
ed36d9e953 Update proxmox_update.sh 2025-08-07 15:49:22 +02:00
MacRimi
9a478d74d2 Update proxmox_update.sh 2025-08-07 15:45:46 +02:00
MacRimi
72d72544a4 Update update-pve.sh 2025-08-07 15:31:03 +02:00
MacRimi
4bbbe81182 Update update-pve.sh 2025-08-07 15:05:54 +02:00
MacRimi
a0af0c2492 Update update-pve.sh 2025-08-07 14:47:58 +02:00
MacRimi
ce7d3e4702 Update update-pve.sh 2025-08-07 14:30:48 +02:00
MacRimi
1bb4ca8541 Update common-functions.sh 2025-08-07 14:23:33 +02:00
MacRimi
ea65445772 Update update-pve.sh 2025-08-07 14:02:02 +02:00
MacRimi
972db8fcea Update update-pve8.sh 2025-08-07 13:04:05 +02:00
MacRimi
a3c12631f0 Update auto_post_install.sh 2025-08-07 11:16:24 +02:00
MacRimi
3cadfd08d8 Update install_proxmenux.sh 2025-08-07 08:04:04 +02:00
MacRimi
104f3de013 Update version.txt 2025-08-06 23:10:08 +02:00
MacRimi
713b41bd52 Update CHANGELOG.md 2025-08-06 23:09:36 +02:00
MacRimi
253093fa2f Update update-pve.sh 2025-08-06 23:08:53 +02:00
MacRimi
f36af5af64 Update update-pve.sh 2025-08-06 22:37:13 +02:00
MacRimi
97b6c0e44d Delete common-functions_.sh 2025-08-06 22:14:12 +02:00
MacRimi
c4f6dabd4d Update system_utils.sh 2025-08-06 22:02:40 +02:00
MacRimi
d1c8aeb25d update pve 8 2025-08-06 22:01:33 +02:00
MacRimi
6e1cb2e0fe Update common-functions.sh 2025-08-06 21:47:53 +02:00
MacRimi
da9762f60e Update update-pve.sh 2025-08-06 21:45:09 +02:00
MacRimi
27affdec14 Update common-functions.sh 2025-08-06 21:17:06 +02:00
MacRimi
433a19e46a Update customizable_post_install.sh 2025-08-06 20:10:38 +02:00
MacRimi
da9db9d3d1 Update update-pve.sh 2025-08-06 20:09:01 +02:00
MacRimi
ed4b0eba2f Update auto_post_install.sh 2025-08-06 20:04:52 +02:00
MacRimi
615aecf80f Update common funtions 2025-08-06 20:04:12 +02:00
MacRimi
4622d1a610 Update update-pve8.sh 2025-08-06 19:31:15 +02:00
MacRimi
f1b80d8f57 Update customizable_post_install.sh 2025-08-06 19:22:11 +02:00
MacRimi
e76e303383 Update update-pve8.sh 2025-08-06 19:20:03 +02:00
MacRimi
97133c3fcb Update auto_post_install.sh 2025-08-06 19:17:45 +02:00
MacRimi
450610b6e6 Update update-pve8.sh 2025-08-06 19:14:32 +02:00
MacRimi
4dc3fd92cc Update update-pve8.sh 2025-08-06 19:04:32 +02:00
MacRimi
f4b5e7c044 Create update-pve8.sh 2025-08-06 18:47:41 +02:00
MacRimi
6c0b2a468d Update common-functions.sh 2025-08-06 18:47:16 +02:00
MacRimi
e33f724f1b Create common-functions.sh 2025-08-06 18:17:51 +02:00
MacRimi
b0d5562917 Update remove-banner-pve8.sh 2025-08-06 17:45:22 +02:00
MacRimi
eecf7a2194 Update remove subscription banner PVE 8.4.9 2025-08-06 17:17:00 +02:00
MacRimi
54fd8a0332 Update remove-banner-pve8.sh 2025-08-06 17:12:16 +02:00
MacRimi
b6ca91980b global update 2025-08-06 16:50:16 +02:00
MacRimi
6af7e2d749 Update remove-banner-pve8.sh 2025-08-06 15:27:26 +02:00
MacRimi
86d334c204 Update remove-banner-pve9.sh 2025-08-06 15:21:31 +02:00
MacRimi
585a4fa449 Update remove-banner-pve9.sh 2025-08-06 15:19:56 +02:00
MacRimi
7438073e7e Update remove-banner-pve8.sh 2025-08-06 15:19:33 +02:00
MacRimi
6e808ae35a Update remove-banner-pve8.sh 2025-08-06 14:32:14 +02:00
MacRimi
99ec64e852 Create remove-banner-pve9.sh 2025-08-06 14:31:19 +02:00
MacRimi
eeac63c0a5 Create remove-banner-pve8.sh 2025-08-06 14:23:22 +02:00
MacRimi
5d5a3c3301 update funtions to pve9 2025-08-05 20:30:29 +02:00
ProxMenuxBot
31e9730236 Update helpers_cache.json 2025-08-05 12:31:00 +00:00
MacRimi
69b32a02ff Update utils.sh 2025-08-05 09:46:30 +02:00
ProxMenuxBot
a222df8176 Update helpers_cache.json 2025-08-04 12:30:37 +00:00
MacRimi
7f4c99be60 Update customizable_post_install.sh 2025-08-04 11:29:43 +02:00
MacRimi
ccff657a62 Update auto_post_install.sh 2025-08-04 11:29:28 +02:00
MacRimi
fb258499e1 Update customizable_post_install.sh 2025-08-04 11:22:10 +02:00
MacRimi
79c6d6c742 Update customizable_post_install.sh 2025-08-04 11:20:38 +02:00
MacRimi
80d9d5480c Update customizable_post_install.sh 2025-08-04 11:19:41 +02:00
MacRimi
958a553922 Update auto_post_install.sh 2025-08-04 11:19:27 +02:00
MacRimi
a44bbc3513 Update customizable_post_install.sh 2025-08-04 11:16:52 +02:00
MacRimi
d7f2f4a3e7 Update customizable_post_install.sh 2025-08-04 11:15:59 +02:00
MacRimi
073566a23e Update auto_post_install.sh 2025-08-04 11:11:14 +02:00
MacRimi
590aecfcf1 Update customizable_post_install.sh 2025-08-04 11:11:00 +02:00
MacRimi
77ab52310e Update auto_post_install.sh 2025-08-04 11:03:31 +02:00
MacRimi
2c2ccddbe4 Update customizable_post_install.sh 2025-08-04 11:02:23 +02:00
MacRimi
87062db9d5 Update customizable_post_install.sh 2025-08-04 10:44:58 +02:00
MacRimi
b74701dbc5 Update system_utils.sh 2025-08-04 08:52:55 +02:00
MacRimi
a88db8830b Update customizable_post_install.sh 2025-08-04 08:34:40 +02:00
ProxMenuxBot
36cd83c796 Update helpers_cache.json 2025-08-04 01:16:40 +00:00
MacRimi
a039c93c05 Update customizable_post_install.sh 2025-08-03 23:13:36 +02:00
MacRimi
57b4ade3be Update auto_post_install.sh 2025-08-03 23:12:32 +02:00
MacRimi
87ce6cfa98 Update auto_post_install.sh 2025-08-03 23:08:06 +02:00
MacRimi
6a99c8c81d Update customizable_post_install.sh 2025-08-03 23:06:31 +02:00
ProxMenuxBot
46e8188d5a Update helpers_cache.json 2025-08-02 18:19:25 +00:00
MacRimi
3c990df1fe Actualizar main_menu.sh 2025-08-02 12:02:10 +02:00
ProxMenuxBot
8969bc5aa6 Update helpers_cache.json 2025-08-02 01:07:27 +00:00
MacRimi
45e8ca8d42 Update customizable_post_install.sh 2025-08-01 08:28:12 +02:00
MacRimi
39930153c9 Update customizable_post_install.sh 2025-08-01 07:25:44 +02:00
MacRimi
5890e46db3 Update auto_post_install.sh 2025-08-01 06:57:21 +02:00
MacRimi
c5c06a08ba Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-08-01 06:50:09 +02:00
MacRimi
2a0e677a89 Update proxmox_update.sh 2025-08-01 06:50:07 +02:00
ProxMenuxBot
8f7a968dc9 Update helpers_cache.json 2025-07-31 18:21:21 +00:00
MacRimi
20cfc50448 Update select_linux_iso.sh 2025-07-31 19:39:03 +02:00
MacRimi
4bf019ec7e Update jd2.sh 2025-07-31 19:11:18 +02:00
MacRimi
57fe45484c Update jd2.sh 2025-07-31 18:00:30 +02:00
MacRimi
7744f4ed76 Update jd2.sh 2025-07-31 17:43:14 +02:00
MacRimi
a43e81e229 Update jd2.sh 2025-07-31 15:14:00 +02:00
MacRimi
f6ad7e250b Update jd2.sh 2025-07-31 14:45:02 +02:00
MacRimi
0f2b0482ec Create jd2.sh 2025-07-31 14:27:12 +02:00
MacRimi
21d850d39e Rename jd2.sh to jd2_.sh 2025-07-31 14:26:46 +02:00
MacRimi
d3f2e42301 Create jd2.sh 2025-07-31 12:50:19 +02:00
MacRimi
df68154f10 Update disk-passthrough_ct.sh 2025-07-30 18:43:54 +02:00
MacRimi
f8ebf03afd Update system_utils.sh 2025-07-30 18:18:13 +02:00
MacRimi
23f8b97319 Update customizable_post_install.sh 2025-07-30 17:59:12 +02:00
MacRimi
d712054353 Update customizable_post_install.sh 2025-07-30 17:55:51 +02:00
MacRimi
58da896b14 Update system_utils.sh 2025-07-30 17:54:15 +02:00
MacRimi
8db57bda6e Update install utilities 2025-07-30 17:34:44 +02:00
MacRimi
53f29ec710 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-07-29 22:22:38 +02:00
MacRimi
43a8fc0e86 Update customizable_post_install.sh 2025-07-29 22:22:36 +02:00
ProxMenuxBot
7f2adb068e Update helpers_cache.json 2025-07-29 18:21:40 +00:00
MacRimi
218ae9f9bf Update uninstall-tools.sh 2025-07-29 20:15:55 +02:00
MacRimi
350c03874d Update customizable_post_install.sh 2025-07-29 20:09:54 +02:00
MacRimi
575c0e5bf9 Update customizable_post_install.sh 2025-07-29 19:58:49 +02:00
ProxMenuxBot
3a890ba2c7 Update helpers_cache.json 2025-07-29 12:30:07 +00:00
ProxMenuxBot
2a345f4869 Update helpers_cache.json 2025-07-28 12:30:05 +00:00
ProxMenuxBot
658ebbd84d Update helpers_cache.json 2025-07-27 12:26:49 +00:00
ProxMenuxBot
0c54ade367 Update helpers_cache.json 2025-07-26 17:08:21 +00:00
ProxMenuxBot
f16ba64026 Update helpers_cache.json 2025-07-25 12:28:04 +00:00
ProxMenuxBot
d72127aaeb Update helpers_cache.json 2025-07-25 01:09:29 +00:00
ProxMenuxBot
40e0b1291c Update helpers_cache.json 2025-07-24 18:20:25 +00:00
MacRimi
0fae7f0166 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-07-22 22:34:12 +02:00
MacRimi
6f916a4c32 Update customizable_post_install.sh 2025-07-22 22:33:58 +02:00
ProxMenuxBot
41979d5389 Update helpers_cache.json 2025-07-22 18:20:56 +00:00
MacRimi
0d51205bd6 Update customizable_post_install.sh 2025-07-22 19:10:35 +02:00
MacRimi
416dd52a30 Update fastfetch 2025-07-22 19:03:41 +02:00
MacRimi
850e45b9a5 Update uninstall-tools.sh 2025-07-22 18:54:14 +02:00
MacRimi
040d2ca7f6 Update customizable_post_install.sh 2025-07-22 18:39:48 +02:00
ProxMenuxBot
e173072622 Update helpers_cache.json 2025-07-22 12:29:23 +00:00
MacRimi
991dd80382 Update select_linux_iso.sh 2025-07-22 12:02:38 +02:00
MacRimi
5fc5d02134 Update auto_post_install.sh 2025-07-22 09:27:40 +02:00
MacRimi
0f51256add Update auto_post_install.sh 2025-07-22 09:22:59 +02:00
ProxMenuxBot
a78860dbc4 Update helpers_cache.json 2025-07-21 18:20:56 +00:00
MacRimi
f655a3c52d Update auto_post_install.sh 2025-07-20 20:03:12 +02:00
MacRimi
b69aebd5be update post-install menu 2025-07-20 19:36:59 +02:00
MacRimi
769a7c391f Update customizable_post_install.sh 2025-07-20 14:18:06 +02:00
MacRimi
9a4d55aa36 Update customizable_post_install.sh 2025-07-20 12:34:21 +02:00
MacRimi
e776acfbab Update CHANGELOG.md 2025-07-20 12:08:37 +02:00
MacRimi
b4f58286b4 Update network_menu.sh 2025-07-20 11:50:04 +02:00
MacRimi
59779cc931 Update network_menu.sh 2025-07-20 11:46:26 +02:00
MacRimi
567bcecc80 Update network_menu.sh 2025-07-20 11:42:25 +02:00
MacRimi
0ca87dc29b Update network_menu.sh 2025-07-20 11:38:54 +02:00
MacRimi
6e0824e357 Update customizable_post_install.sh 2025-07-20 11:27:08 +02:00
MacRimi
9a8a620658 Update uninstall-tools.sh 2025-07-20 11:17:33 +02:00
MacRimi
eb1db3120d Update auto_post_install.sh 2025-07-20 10:55:04 +02:00
MacRimi
98a9225c32 Update customizable_post_install.sh 2025-07-20 02:27:46 +02:00
MacRimi
1b8fb766a8 Update auto_post_install.sh 2025-07-20 02:26:26 +02:00
MacRimi
c28ef3ec3b Update auto_post_install.sh 2025-07-20 02:12:27 +02:00
MacRimi
fab3f0630c Update customizable_post_install.sh 2025-07-20 02:09:11 +02:00
MacRimi
d2499ad157 Update customizable_post_install.sh 2025-07-20 01:55:47 +02:00
MacRimi
724a37bbf4 Update auto_post_install.sh 2025-07-20 01:51:14 +02:00
MacRimi
c5f1c30b1c Update disk-passthrough_ct.sh 2025-07-19 18:28:28 +02:00
MacRimi
e90363df71 Update customizable_post_install.sh 2025-07-19 17:43:31 +02:00
MacRimi
0932008619 Update auto_post_install.sh 2025-07-19 17:40:52 +02:00
MacRimi
a24e00ad5a Update auto_post_install.sh 2025-07-19 17:25:43 +02:00
MacRimi
fab938055f Update auto_post_install.sh 2025-07-19 17:19:06 +02:00
MacRimi
b2026b0dac Update auto_post_install.sh 2025-07-19 17:06:33 +02:00
MacRimi
c1c742084e Update auto_post_install.sh 2025-07-19 17:02:38 +02:00
MacRimi
7140f590cf Update uninstall-tools.sh 2025-07-19 16:52:13 +02:00
MacRimi
dcc26ad666 Update customizable_post_install.sh 2025-07-19 16:43:14 +02:00
MacRimi
70f1ecad49 Update customizable_post_install.sh 2025-07-19 16:38:55 +02:00
ProxMenuxBot
03c7383b54 Update helpers_cache.json 2025-07-17 18:20:17 +00:00
ProxMenuxBot
d3734971cc Update helpers_cache.json 2025-07-14 18:20:49 +00:00
ProxMenuxBot
cff8358b3e Update helpers_cache.json 2025-07-14 12:28:40 +00:00
ProxMenuxBot
42d691c4ce Update helpers_cache.json 2025-07-13 18:18:07 +00:00
MacRimi
33dcbe8b5a Update install_coral_lxc.sh 2025-07-12 18:34:49 +02:00
MacRimi
980c7a4390 Update uupdump_creator.sh 2025-07-11 21:20:18 +02:00
MacRimi
1b7f881d5a Update uupdump_creator.sh 2025-07-11 20:20:32 +02:00
MacRimi
8635e2cf67 Update uupdump_creator.sh 2025-07-11 20:13:33 +02:00
MacRimi
e36bc8bab2 Update uupdump_creator.sh 2025-07-11 20:09:16 +02:00
MacRimi
693da7733f Update uupdump_creator.sh 2025-07-11 19:59:34 +02:00
MacRimi
6b6128d92d Update uupdump_creator.sh 2025-07-11 17:28:02 +02:00
MacRimi
64586a44b4 Update customizable_post_install.sh 2025-07-10 19:25:50 +02:00
MacRimi
2d28ecca32 Update network_menu.sh 2025-07-10 19:16:16 +02:00
MacRimi
92f3edb337 Update network_menu.sh 2025-07-10 19:11:32 +02:00
MacRimi
32f8edecdd Update network_menu.sh 2025-07-10 17:09:59 +02:00
MacRimi
14b061cd28 Update cache.json 2025-07-10 12:37:59 +02:00
MacRimi
aeb90cbdd2 Update network_menu.sh 2025-07-10 12:31:51 +02:00
MacRimi
e2a0b627b2 Update cache.json 2025-07-09 22:29:03 +02:00
MacRimi
de5eb0d914 Update version.txt 2025-07-09 22:07:26 +02:00
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
93 changed files with 37807 additions and 2559 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,360 @@
## 2025-01-10
### New version v1.1.6
![Shared Resources Menu](https://macrimi.github.io/ProxMenux/share/main-menu.png)
### Added
- **New Menu: Mount and Share Manager**
Introduced a comprehensive new menu for managing shared resources between Proxmox host and LXC containers:
**Host Configuration Options:**
1. **Configure NFS Shared on Host** - Add, view, and remove NFS shared resources on the Proxmox server with automatic export management
2. **Configure Samba Shared on Host** - Add, view, and remove Samba/CIFS shared resources on the Proxmox server with share configuration
3. **Configure Local Shared on Host** - Create and manage local shared directories with proper permissions on the Proxmox host
**LXC Integration Options:**
4. **Configure LXC Mount Points (Host ↔ Container)** - **Core feature** that enables mounting host directories into LXC containers with automatic permission handling. Includes the ability to **view existing mount points** for each container in a clear, organized way and **remove mount points** with proper verification that the process completed successfully. Especially optimized for **unprivileged containers** where UID/GID mapping is critical.
5. **Configure NFS Client in LXC** - Set up NFS client inside privileged containers
6. **Configure Samba Client in LXC** - Set up Samba client inside privileged containers
7. **Configure NFS Server in LXC** - Install NFS server inside privileged containers
8. **Configure Samba Server in LXC** - Install Samba server inside privileged containers
**Documentation & Support:**
- **Help & Info (commands)** - Comprehensive guides with step-by-step manual instructions for all sharing scenarios
The entire system is built around the **LXC Mount Points** functionality, which automatically detects filesystem types, handles permission mapping between host and container users, and provides seamless integration for both privileged and unprivileged containers.
---
### Improved
- **Log2RAM Auto-Detection Enhancement**
In the automatic post-install script, the Log2RAM installation function now prompts the user when automatic disk ssd/m2 detection fails.
This ensures Log2RAM can still be installed on systems where automatic disk detection doesn't work properly.
---
### Fixed
- **Proxmox Update Repository Verification**
Fixed an issue in the Proxmox update function where empty repository source files would cause errors during conflict verification. The function now properly handles empty `/etc/apt/sources.list.d/` files without throwing false warnings.
Thanks to **@JF_Car** for reporting this issue.
---
### Acknowledgments
Special thanks to **@JF_Car**, **@ghosthvj**, and **@jonatanc** for their testing, valuable feedback, and suggestions that helped refine the shared resources functionality and improve the overall user experience.
## 2025-08-20
### New version v1.1.5
### Added
- **New Script: Upgrade PVE 8 to PVE 9**
Added a full upgrade tool located under `Utilities and Tools`. It provides:
1. **Automatic upgrade** from PVE 8 to 9
2. **Interactive upgrade** with step-by-step confirmations
3. **Check-only mode** using `check-pve8to9`
4. **Manual instructions** shown in order for users who prefer to upgrade manually
- **New Tools in System Utilities**
- [`s-tui`](https://github.com/amanusk/s-tui): Terminal-based CPU monitoring with graphs
- [`intel-gpu-tools`](https://gitlab.freedesktop.org/drm/igt-gpu-tools): Useful for Intel GPU diagnostics
---
### Improved
- **APT Upgrade Handling**
The PVE upgrade function now blocks the process if any package prompts for manual confirmation. This avoids partial upgrades and ensures consistency.
- **Network Optimization (sysctl)**
- Obsolete kernel parameters removed (e.g., `tcp_tw_recycle`, `nf_conntrack_helper`) to prevent warnings in **Proxmox 9 / kernel 6.14**
- Now generates only valid, up-to-date sysctl parameters
- **AMD CPU Patch Handling**
- Now applies correct `idle=nomwait` and KVM options (`ignore_msrs=1`, `report_ignored_msrs=0`)
- Expected warning is now documented and safely handled for stability with Ryzen/EPYC
- **Timezone & NTP Fixes**
- Automatically detects timezone using public IP geolocation
- Falls back to UTC if detection fails
- Restarts Postfix after timezone set → resolves `/var/spool/postfix/etc/localtime` mismatch warning
- **Repository & Package Installer Logic**
- Now verifies that working repositories exist before installing any package
- If none are available, adds a fallback **Debian stable** repository
- Replaces deprecated `mlocate` with `plocate` (compatible with Debian 13 and Proxmox 9)
- **Improved Logs and User Feedback**
- Actions that fail now provide precise messages (instead of falsely marking as success)
- Helps users clearly understand what's been applied or skipped
## 2025-08-06
### New version v1.1.4
### Added
- **Proxmox 9 Compatibility Preparation**
This version prepares **ProxMenux** for the upcoming **Proxmox VE 9**:
- The function to add the official Proxmox repositories now supports the new `.sources` format used in Proxmox 9, while maintaining backward compatibility with Proxmox 8.
- Banner removal is now optionally supported for Proxmox 9.
- **xshok-proxmox Detection**
Added a check to detect if the `xshok-proxmox` post-install script has already been executed.
If detected, a warning is shown to avoid conflicting adjustments:
```
It appears that you have already executed the xshok-proxmox post-install script on this system.
If you continue, some adjustments may be duplicated or conflict with those already made by xshok.
Do you want to continue anyway?
```
---
### Improved
- **Banner Removal (Proxmox 8.4.9+)**
Updated the logic for removing the subscription banner in **Proxmox 8.4.9**, due to changes in `proxmoxlib.js`.
- **LXC Disk Passthrough (Persistent UUID)**
The function to add a physical disk to an LXC container now uses **UUID-based persistent paths**.
This ensures that disks remain correctly mounted, even if the `/dev/sdX` order changes due to new hardware.
```bash
PERSISTENT_DISK=$(get_persistent_path "$DISK")
if [[ "$PERSISTENT_DISK" != "$DISK" ]] ...
```
- **System Utilities Installer**
Now checks whether APT sources are available before installing selected tools.
If a new Proxmox installation has no active repos, it will **automatically add the default sources** to avoid installation failure.
- **IOMMU Activation on ZFS Systems**
The function that enables IOMMU for passthrough now verifies existing kernel parameters to avoid duplication if the user has already configured them manually.
---
### Fixed
- Minor code cleanup and improved runtime performance across several modules.
## 2025-07-20
### Changed
- **Subscription Banner Removal (Proxmox 8.4.5+)**
Improved the `remove_subscription_banner` function to ensure compatibility with Proxmox 8.4.5, where the banner removal method was failing after clean installations.
- **Improved Log2RAM Detection**
In both the automatic and customizable post-install scripts, the logic for Log2RAM installation has been improved.
Now it correctly detects if Log2RAM is already configured and avoids triggering errors or reconfiguration.
- **Optimized Figurine Installation**
The `install_figurine` function now avoids duplicating `.bashrc` entries if the customization for the root prompt already exists.
### Added
- **New Function: Persistent Network Interface Naming**
Added a new function `setup_persistent_network` to create stable network interface names using `.link` files based on MAC addresses.
This avoids unpredictable renaming (e.g., `enp2s0` becoming `enp3s0`) when hardware changes, PCI topology shifts, or passthrough configurations are applied.
**Why use `.link` files?**
Because predictable interface names in `systemd` can change with hardware reordering or replacement. Using static `.link` files bound to MAC addresses ensures consistency, especially on systems with multiple NICs or passthrough setups.
Special thanks to [@Andres_Eduardo_Rojas_Moya] for contributing the persistent
network naming function and for the original idea.
```bash
[Match]
MACAddress=XX:XX:XX:XX:XX:XX
[Link]
Name=eth0
```
## 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

View File

@@ -59,7 +59,7 @@ Then, follow the on-screen options to manage your Proxmox server efficiently.
## 📌 System Requirements
🖥 **Compatible with:**
- Proxmox VE 8.x**
- Proxmox VE 8.x and 9.x
📦 **Dependencies:**
- `bash`, `curl`, `wget`, `jq`, `whiptail`, `python3-venv` (These dependencies are installed automatically during setup.)

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,109 +41,388 @@ 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 ProxMenux" --yesno "Are you sure you want to uninstall ProxMenux?" 10 60; then
return 1
fi
fi
echo "Uninstalling ProxMenux..."
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 "ProxMenux 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)
jq --arg comp "$component" --arg stat "$status" --arg time "$timestamp" \
'.[$comp] = {status: $stat, timestamp: $time}' "$CONFIG_FILE" > "$tmp" && mv "$tmp" "$CONFIG_FILE"
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_file" && mv "$tmp_file" "$CONFIG_FILE"
fi
[ -f "$tmp_file" ] && rm -f "$tmp_file"
fi
}
show_progress() {
local step="$1"
local total="$2"
local message="$3"
echo -e "\n${BOLD}${BL}${TAB}Installing ProxMenu: Step $step of $total${CL}"
echo -e "\n${BOLD}${BL}${TAB}Installing ProxMenux: Step $step of $total${CL}"
echo
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() {
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
show_progress $current_step $total_steps "Installing basic 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
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
((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
# Step 1: Check and install system dependencies
show_progress $current_step $total_steps "Language selection"
select_language
((current_step++))
show_progress $current_step $total_steps "Checking system dependencies"
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
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..."
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
((current_step++))
DEPS=("whiptail" "dialog" "curl" "python3" "python3-venv" "python3-pip")
for pkg in "${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
show_progress $current_step $total_steps "Setting up translation environment"
((current_step++))
# 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,145 @@ 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 pve_version
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
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
if [[ "$pve_version" -ge 9 ]]; then
INSTALL_TYPE=$(whiptail --backtitle "ProxMenux" --title "$menu_title" --menu "\n$menu_text" 14 70 2 \
"1" "Normal Version (English only)" 3>&1 1>&2 2>&3)
if [ -z "$INSTALL_TYPE" ]; then
show_proxmenux_logo
msg_warn "Installation cancelled."
exit 1
fi
else
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
show_proxmenux_logo
msg_warn "Installation cancelled."
exit 1
fi
fi
if [ -z "$INSTALL_TYPE" ]; then
show_proxmenux_logo
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
show_proxmenux_logo
msg_warn "Installation cancelled."
exit 1
fi
fi
if ! handle_installation_change "$current_install_type" "$INSTALL_TYPE"; then
show_proxmenux_logo
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

File diff suppressed because it is too large Load Diff

5447
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

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

@@ -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: 17/08/2025
# ==========================================================
# Description:
# This script automates the process of enabling and configuring Intel Integrated GPU (iGPU) support in Proxmox VE LXC containers.
@@ -32,7 +32,7 @@ initialize_cache
# Select LXC container
select_container() {
CONTAINERS=$(pct list | awk 'NR>1 {print $1, $3}' | xargs -n2)
@@ -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.')"
@@ -59,14 +59,14 @@ select_container() {
# 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"
@@ -76,77 +76,103 @@ validate_container_id() {
# Configure LXC for Coral TPU and iGPU
configure_lxc_for_igpu() {
validate_container_id
CONFIG_FILE="/etc/pve/lxc/${CONTAINER_ID}.conf"
if [ ! -f "$CONFIG_FILE" ]; then
msg_error "$(translate 'Configuration file for container') $CONTAINER_ID $(translate 'not found.')"
exit 1
validate_container_id
CONFIG_FILE="/etc/pve/lxc/${CONTAINER_ID}.conf"
[[ -f "$CONFIG_FILE" ]] || { msg_error "$(translate 'Configuration file for container') $CONTAINER_ID $(translate 'not found.')"; exit 1; }
if [[ ! -d /dev/dri ]]; then
modprobe i915 2>/dev/null || true
for _ in {1..5}; do
[[ -d /dev/dri ]] && break
sleep 1
done
fi
CT_TYPE=$(pct config "$CONTAINER_ID" | awk '/^unprivileged:/ {print $2}')
[[ -z "$CT_TYPE" ]] && CT_TYPE="0"
msg_info "$(translate 'Configuring Intel iGPU passthrough for container...')"
for rn in /dev/dri/renderD*; do
[[ -e "$rn" ]] || continue
chmod 660 "$rn" 2>/dev/null || true
chgrp render "$rn" 2>/dev/null || true
done
mapfile -t RENDER_NODES < <(find /dev/dri -maxdepth 1 -type c -name 'renderD*' 2>/dev/null || true)
mapfile -t CARD_NODES < <(find /dev/dri -maxdepth 1 -type c -name 'card*' 2>/dev/null || true)
FB_NODE=""
[[ -e /dev/fb0 ]] && FB_NODE="/dev/fb0"
if [[ ${#RENDER_NODES[@]} -eq 0 && ${#CARD_NODES[@]} -eq 0 && -z "$FB_NODE" ]]; then
msg_warn "$(translate 'No VA-API devices found on host (/dev/dri*, /dev/fb0). Is i915 loaded?')"
return 0
fi
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"
STORAGE_TYPE=$(pct config "$CONTAINER_ID" | grep "^rootfs:" | awk -F, '{print $2}' | cut -d'=' -f2)
if [[ "$STORAGE_TYPE" == "dir" ]]; then
STORAGE_PATH=$(pct config "$CONTAINER_ID" | grep "^rootfs:" | awk '{print $2}' | cut -d',' -f1)
chown -R root:root "$STORAGE_PATH"
fi
msg_ok "$(translate 'Container changed to privileged.')"
fi
if grep -q "^lxc.apparmor.profile" "$CONFIG_FILE"; then
msg_info "$(translate 'Disabling AppArmor profile to avoid conflicts...')"
sed -i "/^lxc.apparmor.profile/d" "$CONFIG_FILE"
msg_ok "$(translate 'AppArmor profile removed.')"
fi
# Configure iGPU
if ! grep -q "features: nesting=1" "$CONFIG_FILE"; then
if grep -q '^features:' "$CONFIG_FILE"; then
grep -Eq '^features:.*(^|,)\s*nesting=1(\s|,|$)' "$CONFIG_FILE" || sed -i 's/^features:\s*/&nesting=1, /' "$CONFIG_FILE"
else
echo "features: nesting=1" >> "$CONFIG_FILE"
fi
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"
if [[ "$CT_TYPE" == "0" ]]; then
sed -i '/^lxc\.cgroup2\.devices\.allow:\s*c\s*226:/d' "$CONFIG_FILE"
sed -i '\|^lxc\.mount\.entry:\s*/dev/dri|d' "$CONFIG_FILE"
sed -i '\|^lxc\.mount\.entry:\s*/dev/fb0|d' "$CONFIG_FILE"
echo "lxc.cgroup2.devices.allow: c 226:* rwm" >> "$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
if ! grep -q "c 29:0 rwm # Framebuffer" "$CONFIG_FILE"; then
echo "lxc.cgroup2.devices.allow: c 29:0 rwm # Framebuffer" >> "$CONFIG_FILE"
fi
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
[[ -n "$FB_NODE" ]] && echo "lxc.mount.entry: /dev/fb0 dev/fb0 none bind,optional,create=file" >> "$CONFIG_FILE"
msg_ok "$(translate 'Coral TPU and iGPU configuration added to container') $CONTAINER_ID."
else
sed -i '/^dev[0-9]\+:/d' "$CONFIG_FILE"
idx=0
for c in "${CARD_NODES[@]}"; do
echo "dev${idx}: $c,gid=44" >> "$CONFIG_FILE"
idx=$((idx+1))
done
for r in "${RENDER_NODES[@]}"; do
echo "dev${idx}: $r,gid=104" >> "$CONFIG_FILE"
idx=$((idx+1))
done
fi
msg_ok "$(translate 'iGPU configuration added to container') $CONTAINER_ID."
}
# Install iGPU drivers in the container
install_igpu_in_container() {
msg_info2 "$(translate 'Installing iGPU drivers inside the container...')"
tput sc
LOG_FILE=$(mktemp)
msg_info "$(translate 'Installing iGPU drivers...')"
pct start "$CONTAINER_ID" >/dev/null 2>&1
script -q -c "pct exec \"$CONTAINER_ID\" -- bash -c '
set -e
getent group video >/dev/null || groupadd -g 44 video
getent group render >/dev/null || groupadd -g 104 render
usermod -aG video,render root || true
apt-get update >/dev/null 2>&1
apt-get install -y va-driver-all ocl-icd-libopencl1 intel-opencl-icd vainfo intel-gpu-tools
chgrp video /dev/dri && chmod 755 /dev/dri
adduser root video && adduser root render
chgrp video /dev/dri 2>/dev/null || true
chmod 755 /dev/dri 2>/dev/null || true
'" "$LOG_FILE"
if [ $? -eq 0 ]; then
@@ -165,11 +191,14 @@ install_igpu_in_container() {
}
select_container
show_proxmenux_logo
msg_title "$(translate "Add HW iGPU acceleration to an LXC")"
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

View File

@@ -31,7 +31,7 @@ if [[ -f "$UTILS_FILE" ]]; then
fi
load_language
initialize_cache
show_proxmenux_logo
# ==========================================================
@@ -187,7 +187,12 @@ is_disk_in_use() {
FREE_DISKS=()
LVM_DEVICES=$(pvs --noheadings -o pv_name 2> >(grep -v 'File descriptor .* leaked') | xargs -n1 readlink -f | sort -u)
LVM_DEVICES=$(pvs --noheadings -o pv_name 2> >(grep -v 'File descriptor .* leaked') | xargs -r -n1 readlink -f | sort -u)
if [[ -n "$LVM_DEVICES" ]] && echo "$LVM_DEVICES" | grep -qFx "$REAL_PATH"; then
IS_MOUNTED=true
fi
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

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

@@ -0,0 +1,338 @@
#!/bin/bash
# ==========================================================
# Common Functions for Proxmox VE Scripts
# ==========================================================
# 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
get_pve_info() {
local pve_full_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+\.[0-9]+' | head -1)
local pve_major=$(echo "$pve_full_version" | cut -d. -f1)
local 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)
fi
local target_codename
if [ "$pve_major" -ge 9 ] 2>/dev/null; then
target_codename="trixie"
else
target_codename="$os_codename"
if [ -z "$target_codename" ]; then
target_codename="bookworm"
fi
fi
echo "$pve_full_version|$pve_major|$os_codename|$target_codename"
}
lvm_repair_check() {
msg_info "$(translate "Checking and repairing old LVM PV headers (if needed)...")"
if ! command -v pvs >/dev/null 2>&1; then
msg_info "$(translate "LVM tools not available, skipping LVM check")"
return
fi
pvs_output=$(LC_ALL=C pvs -v 2>&1 | grep "old PV header" || true)
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/[^ ]*' || true)
if [ -n "$pv" ]; then
vg=$(pvs -o vg_name --noheadings "$pv" 2>/dev/null | awk '{print $1}' || true)
if [ -n "$vg" ]; then
vg_map["$vg"]=1
fi
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" 2>/dev/null
vgchange -ay "$vg" 2>/dev/null
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_pve9() {
msg_info "$(translate "Cleaning up duplicate repositories...")"
local sources_file="/etc/apt/sources.list"
local temp_file=$(mktemp)
local cleaned_count=0
declare -A seen_repos
if [ ! -s "$sources_file" ]; then
return 0
fi
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))
msg_info "$(translate "Commented duplicate: $url $dist")"
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"
if [ -f "/etc/apt/sources.list.d/proxmox.sources" ]; then
if grep -q "^deb.*download\.proxmox\.com" "$sources_file"; then
sed -i '/^deb.*download\.proxmox\.com/s/^/# /' "$sources_file"
cleaned_count=$((cleaned_count + 1))
fi
for list_file in /etc/apt/sources.list.d/pve-*.list; do
if [ -f "$list_file" ] && [[ "$list_file" != "/etc/apt/sources.list.d/pve-enterprise.list" ]]; then
if grep -q "^deb" "$list_file"; then
sed -i 's/^deb/# deb/g' "$list_file"
cleaned_count=$((cleaned_count + 1))
fi
fi
done
if [ -f "/etc/apt/sources.list.d/debian.sources" ]; then
if grep -q "^deb.*deb\.debian\.org" "$sources_file"; then
sed -i '/^deb.*deb\.debian\.org/s/^/# /' "$sources_file"
cleaned_count=$((cleaned_count + 1))
fi
if grep -q "^deb.*security\.debian\.org" "$sources_file"; then
sed -i '/^deb.*security\.debian\.org/s/^/# /' "$sources_file"
cleaned_count=$((cleaned_count + 1))
fi
fi
fi
if [ -f "/etc/apt/sources.list.d/proxmox.sources" ]; then
for old_file in /etc/apt/sources.list.d/pve-public-repo.list /etc/apt/sources.list.d/pve-install-repo.list; do
if [ -f "$old_file" ]; then
rm -f "$old_file"
cleaned_count=$((cleaned_count + 1))
fi
done
fi
if [ $cleaned_count -gt 0 ]; then
msg_ok "$(translate "Cleaned up $cleaned_count duplicate/old repositories")"
apt-get update > /dev/null 2>&1 || true
else
msg_ok "$(translate "No duplicate repositories found")"
fi
}
cleanup_duplicate_repos_pve9_() {
msg_info "$(translate "Cleaning up duplicate repositories...")"
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"
for src in proxmox debian ceph; do
local sources_path="/etc/apt/sources.list.d/${src}.sources"
if [ -f "$sources_path" ]; then
case "$src" in
proxmox)
url_match="download.proxmox.com"
;;
debian)
url_match="deb.debian.org"
;;
ceph)
url_match="download.proxmox.com/ceph"
;;
*)
url_match=""
;;
esac
if [[ -n "$url_match" ]]; then
if grep -q "^deb.*$url_match" "$sources_file"; then
sed -i "/^deb.*$url_match/s/^/# /" "$sources_file"
cleaned_count=$((cleaned_count + 1))
fi
fi
for list_file in /etc/apt/sources.list.d/*.list; do
[[ -f "$list_file" ]] || continue
if grep -q "^deb.*$url_match" "$list_file"; then
sed -i "/^deb.*$url_match/s/^/# /" "$list_file"
cleaned_count=$((cleaned_count + 1))
fi
done
fi
done
if [ $cleaned_count -gt 0 ]; then
msg_ok "$(translate "Cleaned up $cleaned_count duplicate/old repositories")"
apt-get update > /dev/null 2>&1 || true
else
msg_ok "$(translate "No duplicate repositories found")"
fi
}
cleanup_duplicate_repos_pve8() {
msg_info "$(translate "Cleaning up duplicate repositories...")"
local cleaned_count=0
local sources_file="/etc/apt/sources.list"
if [[ -f "$sources_file" ]]; then
local temp_file
temp_file=$(mktemp)
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" =~ ^[[:space:]]*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"
fi
local old_pve_files=(/etc/apt/sources.list.d/pve-*.list /etc/apt/sources.list.d/proxmox.list)
for file in "${old_pve_files[@]}"; do
if [[ -f "$file" ]]; then
local base_name
base_name=$(basename "$file" .list)
local sources_equiv="/etc/apt/sources.list.d/${base_name}.sources"
if [[ -f "$sources_equiv" ]] && grep -q "^Enabled: *true" "$sources_equiv"; then
msg_info "$(translate "Removing old repository file: $(basename "$file")")"
rm -f "$file"
cleaned_count=$((cleaned_count + 1))
fi
fi
done
if [ "$cleaned_count" -gt 0 ]; then
msg_ok "$(translate "Cleaned up $cleaned_count duplicate/old repositories")"
apt-get update > /dev/null 2>&1 || true
else
msg_ok "$(translate "No duplicate repositories found")"
fi
}
cleanup_duplicate_repos() {
local pve_version
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
if [[ -z "$pve_version" ]]; then
msg_error "Unable to detect Proxmox version."
return 1
fi
if [[ "$pve_version" -ge 9 ]]; then
cleanup_duplicate_repos_pve9
else
cleanup_duplicate_repos_pve8
fi
}

View File

@@ -0,0 +1,76 @@
#!/bin/bash
# ==========================================================
# Remove Subscription Banner - Proxmox VE 8.4.9
# ==========================================================
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
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"
}
remove_subscription_banner_pve8() {
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"
local BACKUP_FILE="${JS_FILE}.bak.$(date +%F_%T)"
local pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+' | head -1)
local pve_major=$(echo "$pve_version" | cut -d. -f1)
if [[ "$pve_major" -ne 8 ]]; then
msg_error "This script is only for Proxmox VE 8.x. Detected: $pve_version"
return 1
fi
msg_info "Detected Proxmox VE $pve_version - Applying safe JS patch..."
if [[ ! -f "$JS_FILE" ]]; then
msg_error "JavaScript file not found: $JS_FILE"
return 1
fi
cp "$JS_FILE" "$BACKUP_FILE"
sed -i "s/No valid subscription/Subscription active/g" "$JS_FILE"
sed -i "s/Ext.Msg.WARNING/Ext.Msg.INFO/g" "$JS_FILE"
sed -i "s/res.data.status.toLowerCase() !== 'active'/false/g" "$JS_FILE"
sed -i "s/subscriptionActive: ''/subscriptionActive: true/g" "$JS_FILE"
[[ -f "$GZ_FILE" ]] && rm -f "$GZ_FILE"
find /var/cache/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
find /var/lib/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
[[ -f "$APT_HOOK" ]] && rm -f "$APT_HOOK"
msg_ok "Subscription banner removed successfully."
register_tool "subscription_banner" true
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
remove_subscription_banner_pve8
fi

View File

@@ -0,0 +1,124 @@
#!/bin/bash
# ==========================================================
# Remove Subscription Banner - Proxmox VE 9.x
# ==========================================================
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
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"
}
remove_subscription_banner_pve9() {
local JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
local MIN_JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js"
local GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
local APT_HOOK="/etc/apt/apt.conf.d/no-nag-script"
local pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+' | head -1)
local pve_major=$(echo "$pve_version" | cut -d. -f1)
if [ "$pve_major" -lt 9 ] 2>/dev/null; then
msg_error "This script is for PVE 9.x only. Detected PVE $pve_version"
return 1
fi
msg_info "Detected Proxmox VE $pve_version - Applying PVE 9.x patches"
if [ ! -f "$JS_FILE" ]; then
msg_error "JavaScript file not found: $JS_FILE"
return 1
fi
local backup_file="${JS_FILE}.backup.pve9.$(date +%Y%m%d_%H%M%S)"
cp "$JS_FILE" "$backup_file"
for f in /etc/apt/apt.conf.d/*nag*; do
[[ -e "$f" ]] && rm -f "$f"
done
[[ -f "$GZ_FILE" ]] && rm -f "$GZ_FILE"
[[ -f "$MIN_JS_FILE" ]] && rm -f "$MIN_JS_FILE"
find /var/cache/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
find /var/lib/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
find /var/cache/nginx/ -type f -delete 2>/dev/null || true
sed -i "s/res\.data\.status\.toLowerCase() !== 'active'/false/g" "$JS_FILE"
sed -i "s/subscriptionActive: ''/subscriptionActive: true/g" "$JS_FILE"
sed -i "s/title: gettext('No valid subscription')/title: gettext('Community Edition')/g" "$JS_FILE"
sed -i "s/You do not have a valid subscription for this server/Community Edition - No subscription required/g" "$JS_FILE"
sed -i "s/Enterprise repository needs valid subscription/Enterprise repository configured/g" "$JS_FILE"
sed -i "s/icon: Ext\.Msg\.WARNING/icon: Ext.Msg.INFO/g" "$JS_FILE"
sed -i "s/subscription = !(/subscription = false \&\& (/g" "$JS_FILE"
if grep -q "res\.data\.status\.toLowerCase() !== 'active'" "$JS_FILE"; then
msg_warn "Some patches may not have applied correctly, retrying..."
sed -i "s/res\.data\.status\.toLowerCase() !== 'active'/false/g" "$JS_FILE"
fi
[[ -f "$APT_HOOK" ]] && rm -f "$APT_HOOK"
cat > "$APT_HOOK" << 'EOF'
DPkg::Post-Invoke {
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/res\\.data\\.status\\.toLowerCase() !== '\''active'\''/false/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/subscriptionActive: '\'\'\''/subscriptionActive: true/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/title: gettext('\''No valid subscription'\'')/title: gettext('\''Community Edition'\'')/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/subscription = !(/subscription = false \\&\\& (/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
"rm -f /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz || true";
};
EOF
chmod 644 "$APT_HOOK"
if ! apt-config dump >/dev/null 2>&1; then
msg_warn "APT hook has syntax issues, removing..."
rm -f "$APT_HOOK"
else
msg_ok "APT hook created successfully"
fi
systemctl reload nginx 2>/dev/null || true
msg_ok "Subscription banner removed successfully for Proxmox VE $pve_version"
msg_ok "Banner removal process completed - refresh your browser to see changes"
register_tool "subscription_banner" true
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
remove_subscription_banner_pve9
fi

View File

@@ -0,0 +1,119 @@
#!/bin/bash
# ==========================================================
# Remove Subscription Banner - Proxmox VE 9.x ONLY
# ==========================================================
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
# 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"
}
remove_subscription_banner_pve9() {
local JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
local MIN_JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js"
local GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
local APT_HOOK="/etc/apt/apt.conf.d/no-nag-script"
# Verify PVE 9.x
local pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+' | head -1)
local pve_major=$(echo "$pve_version" | cut -d. -f1)
if [ "$pve_major" -lt 9 ] 2>/dev/null; then
msg_error "This script is for PVE 9.x only. Detected PVE $pve_version"
return 1
fi
msg_info "Detected Proxmox VE $pve_version - Applying PVE 9.x patches"
# Verify that the file exists
if [ ! -f "$JS_FILE" ]; then
msg_error "JavaScript file not found: $JS_FILE"
return 1
fi
# Create backup of original file
local backup_file="${JS_FILE}.backup.pve9.$(date +%Y%m%d_%H%M%S)"
cp "$JS_FILE" "$backup_file"
# Clean any existing problematic APT hooks
for f in /etc/apt/apt.conf.d/*nag*; do
[[ -e "$f" ]] && rm -f "$f"
done
# Main subscription check patches for PVE 9
sed -i "s/res\.data\.status\.toLowerCase() !== 'active'/false/g" "$JS_FILE"
sed -i "s/subscriptionActive: ''/subscriptionActive: true/g" "$JS_FILE"
sed -i "s/title: gettext('No valid subscription')/title: gettext('Community Edition')/g" "$JS_FILE"
# Additional UX improvements for PVE 9
sed -i "s/You do not have a valid subscription for this server/Community Edition - No subscription required/g" "$JS_FILE"
sed -i "s/Enterprise repository needs valid subscription/Enterprise repository configured/g" "$JS_FILE"
sed -i "s/icon: Ext\.Msg\.WARNING/icon: Ext.Msg.INFO/g" "$JS_FILE"
# Additional subscription patterns that may exist in PVE 9
sed -i "s/subscription = !(/subscription = false \&\& (/g" "$JS_FILE"
# Remove compressed/minified files to force regeneration
[[ -f "$GZ_FILE" ]] && rm -f "$GZ_FILE"
[[ -f "$MIN_JS_FILE" ]] && rm -f "$MIN_JS_FILE"
# Clear various caches
find /var/cache/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
find /var/lib/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
# Create PVE 9.x specific APT hook
[[ -f "$APT_HOOK" ]] && rm -f "$APT_HOOK"
cat > "$APT_HOOK" << 'EOF'
DPkg::Post-Invoke {
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/res\\.data\\.status\\.toLowerCase() !== '\''active'\''/false/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/subscriptionActive: '\'\'\''/subscriptionActive: true/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/title: gettext('\''No valid subscription'\'')/title: gettext('\''Community Edition'\'')/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/subscription = !(/subscription = false \\&\\& (/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
"rm -f /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz || true";
};
EOF
chmod 644 "$APT_HOOK"
# Verify APT hook syntax
if ! apt-config dump >/dev/null 2>&1; then
msg_warn "APT hook has syntax issues, removing..."
rm -f "$APT_HOOK"
else
msg_ok "APT hook created successfully"
fi
msg_ok "Subscription banner removed successfully for Proxmox VE $pve_version"
msg_ok "Banner removal process completed"
register_tool "subscription_banner" true
}
# Execute function if called directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
remove_subscription_banner_pve9
fi

View File

@@ -0,0 +1,901 @@
#!/usr/bin/env bash
# ==========================================================
# ProxMenux - Global Share Functions (reusable)
# File: scripts/global/share_common.func
# ==========================================================
if [[ -n "${__PROXMENUX_SHARE_COMMON__}" ]]; then
return 0
fi
__PROXMENUX_SHARE_COMMON__=1
: "${PROXMENUX_DEFAULT_SHARE_GROUP:=sharedfiles}"
: "${PROXMENUX_SHARE_MAP_DB:=/usr/local/share/proxmenux/share-map.db}"
mkdir -p "$(dirname "$PROXMENUX_SHARE_MAP_DB")" 2>/dev/null || true
touch "$PROXMENUX_SHARE_MAP_DB" 2>/dev/null || true
pmx_share_map_get() {
local key="$1"
awk -F'=' -v k="$key" '$1==k {print $2}' "$PROXMENUX_SHARE_MAP_DB" 2>/dev/null | tail -n1
}
pmx_share_map_set() {
local key="$1" val="$2"
sed -i "\|^${key}=|d" "$PROXMENUX_SHARE_MAP_DB" 2>/dev/null || true
echo "${key}=${val}" >> "$PROXMENUX_SHARE_MAP_DB"
}
pmx_choose_or_create_group() {
local default_group="${1:-$PROXMENUX_DEFAULT_SHARE_GROUP}"
local choice group_name groups menu_args gid_min
gid_min="$(awk '/^\s*GID_MIN\s+[0-9]+/ {print $2}' /etc/login.defs 2>/dev/null | tail -n1)"
[[ -z "$gid_min" ]] && gid_min=1000
choice=$(whiptail --title "$(translate "Shared Group")" \
--menu "$(translate "Choose a group policy for this shared directory:")" 18 78 6 \
"1" "$(translate "Use default group:") $default_group $(translate "(recommended)")" \
"2" "$(translate "Create a new group for isolation")" \
"3" "$(translate "Select an existing group")" \
3>&1 1>&2 2>&3) || { echo ""; return 1; }
case "$choice" in
1)
pmx_ensure_host_group "$default_group" >/dev/null || { echo ""; return 1; }
echo "$default_group"
;;
2)
group_name=$(whiptail --inputbox "$(translate "Enter new group name:")" 10 70 "sharedfiles-project" \
--title "$(translate "New Group")" 3>&1 1>&2 2>&3) || { echo ""; return 1; }
if [[ -z "$group_name" ]]; then
msg_error "$(translate "Group name cannot be empty.")"
echo ""; return 1
fi
if ! [[ "$group_name" =~ ^[a-zA-Z_][a-zA-Z0-9_-]*$ ]]; then
msg_error "$(translate "Invalid group name. Use letters, digits, underscore or hyphen, and start with a letter or underscore.")"
echo ""; return 1
fi
pmx_ensure_host_group "$group_name" >/dev/null || { echo ""; return 1; }
echo "$group_name"
;;
3)
groups=$(getent group | awk -F: -v MIN="$gid_min" '
$3 >= MIN && $1 != "nogroup" && $1 !~ /^pve/ {print $0}
' | sort -t: -k1,1)
if [[ -z "$groups" ]]; then
whiptail --title "$(translate "Groups")" --msgbox "$(translate "No user groups found.")" 8 60
echo ""; return 1
fi
menu_args=()
while IFS=: read -r gname _ gid members; do
menu_args+=("$gname" "GID=$gid")
done <<< "$groups"
group_name=$(whiptail --title "$(translate "Existing Groups")" \
--menu "$(translate "Select an existing group:")" 20 70 12 \
"${menu_args[@]}" 3>&1 1>&2 2>&3) || { echo ""; return 1; }
pmx_ensure_host_group "$group_name" >/dev/null || { echo ""; return 1; }
echo "$group_name"
;;
*)
echo ""; return 1
;;
esac
}
pmx_ensure_host_group() {
local group_name="$1"
local suggested_gid="${2:-}"
local base_gid=101000
local new_gid gid
if getent group "$group_name" >/dev/null 2>&1; then
gid="$(getent group "$group_name" | cut -d: -f3)"
echo "$gid"
return 0
fi
if [[ -n "$suggested_gid" ]]; then
if getent group "$suggested_gid" >/dev/null 2>&1; then
msg_error "$(translate "GID already in use:") $suggested_gid"
echo ""
return 1
fi
if ! groupadd -g "$suggested_gid" "$group_name" >/dev/null 2>&1; then
msg_error "$(translate "Failed to create group:") $group_name"
echo ""
return 1
fi
msg_ok "$(translate "Group created:") $group_name"
else
new_gid="$base_gid"
while getent group "$new_gid" >/dev/null 2>&1; do
new_gid=$((new_gid+1))
done
if ! groupadd -g "$new_gid" "$group_name" >/dev/null 2>&1; then
msg_error "$(translate "Failed to create group:") $group_name"
echo ""
return 1
fi
msg_ok "$(translate "Group created:") $group_name"
fi
gid="$(getent group "$group_name" | cut -d: -f3)"
if [[ -z "$gid" ]]; then
msg_error "$(translate "Failed to resolve group GID for") $group_name"
echo ""
return 1
fi
echo "$gid"
return 0
}
pmx_prepare_host_shared_dir() {
local dir="$1" group_name="$2"
[[ -z "$dir" || -z "$group_name" ]] && { msg_error "$(translate "Internal error: missing arguments in pmx_prepare_host_shared_dir")"; return 1; }
if [[ ! -d "$dir" ]]; then
if mkdir -p "$dir" 2>/dev/null; then
msg_ok "$(translate "Created directory on host:") $dir"
else
msg_error "$(translate "Failed to create directory on host:") $dir"
return 1
fi
fi
chown -R root:"$group_name" "$dir" 2>/dev/null || true
chmod -R 2775 "$dir" 2>/dev/null || true
if command -v setfacl >/dev/null 2>&1; then
setfacl -R -m d:g:"$group_name":rwx -m d:o::rx -m g:"$group_name":rwx "$dir" 2>/dev/null || true
msg_ok "$(translate "Default ACLs applied for group inheritance.")"
fi
return 0
}
pmx_select_host_mount_point() {
local title="${1:-$(translate "Select Mount Point")}"
local default_path="${2:-/mnt/shared}"
local context="${3:-local}"
local choice folder_name result existing_dirs mount_point
while true; do
choice=$(whiptail --title "$title" --menu "$(translate "Where do you want the host folder?")" 16 76 3 \
"1" "$(translate "Create new folder in /mnt")" \
"2" "$(translate "Enter custom pathr")" 3>&1 1>&2 2>&3) || { echo ""; return 1; }
case "$choice" in
1)
folder_name=$(whiptail --inputbox "$(translate "Enter folder name for /mnt:")" 10 70 "$(basename "$default_path")" --title "$(translate "Folder Name")" 3>&1 1>&2 2>&3) || { echo ""; return 1; }
[[ -z "$folder_name" ]] && continue
mount_point="/mnt/$folder_name"
echo "$mount_point"; return 0
;;
2)
result=$(whiptail --inputbox "$(translate "Enter full path:")" 10 80 "$default_path" --title "$(translate "Custom Path")" 3>&1 1>&2 2>&3) || { echo ""; return 1; }
[[ -z "$result" ]] && continue
echo "$result"; return 0
;;
esac
done
}
select_host_directory_() {
local method choice result
method=$(whiptail --title "$(translate "Select Host Directory")" --menu "$(translate "How do you want to select the HOST folder to mount?")" 15 70 4 \
"mnt" "$(translate "Select from /mnt directories")" \
"manual" "$(translate "Enter path manually")" 3>&1 1>&2 2>&3) || return 1
case "$method" in
mnt|srv|media)
local base_path="/$method"
local host_dirs=("$base_path"/*)
local options=()
for dir in "${host_dirs[@]}"; do
if [[ -d "$dir" ]]; then
options+=("$dir" "$(basename "$dir")")
fi
done
if [[ ${#options[@]} -eq 0 ]]; then
msg_error "$(translate "No directories found in") $base_path"
return 1
fi
result=$(whiptail --title "$(translate "Select Host Folder")" \
--menu "$(translate "Select the folder to mount:")" 20 80 10 "${options[@]}" 3>&1 1>&2 2>&3)
;;
manual)
result=$(whiptail --title "$(translate "Enter Path")" \
--inputbox "$(translate "Enter the full path to the host folder:")" 10 70 "/mnt/" 3>&1 1>&2 2>&3)
;;
esac
if [[ -z "$result" ]]; then
return 1
fi
if [[ ! -d "$result" ]]; then
msg_error "$(translate "The selected path is not a valid directory:") $result"
return 1
fi
echo "$result"
}
select_host_directory__() {
local method result
method=$(whiptail --title "$(translate "Select Host Directory")" \
--menu "$(translate "How do you want to select the HOST folder to mount?")" 15 70 4 \
"mnt" "$(translate "Select from /mnt directories")" \
"manual" "$(translate "Enter path manually")" \
3>&1 1>&2 2>&3) || return 1
case "$method" in
mnt|srv|media)
local base_path="/$method"
local host_dirs=("$base_path"/*)
local options=()
for dir in "${host_dirs[@]}"; do
[[ -d "$dir" ]] && options+=("$dir" "$(basename "$dir")")
done
if [[ ${#options[@]} -eq 0 ]]; then
msg_error "$(translate "No directories found in") $base_path"
return 1
fi
result=$(whiptail --title "$(translate "Select Host Folder")" \
--menu "$(translate "Select the folder to mount:")" 20 80 10 \
"${options[@]}" 3>&1 1>&2 2>&3) || return 1
;;
manual)
result=$(whiptail --title "$(translate "Enter Path")" \
--inputbox "$(translate "Enter the full path to the host folder:")" \
10 70 "/mnt/" 3>&1 1>&2 2>&3) || return 1
;;
*)
return 1
;;
esac
[[ -z "$result" ]] && return 1
[[ ! -d "$result" ]] && {
msg_error "$(translate "The selected path is not a valid directory:") $result"
return 1
}
echo "$result"
}
select_host_directory() {
local method result
method=$(whiptail --title "$(translate "Select Host Directory")" \
--menu "$(translate "How do you want to select the HOST folder to mount?")" 15 70 4 \
"mnt" "$(translate "Select from /mnt directories")" \
"manual" "$(translate "Enter path manually")" \
3>&1 1>&2 2>&3) || return 1
case "$method" in
mnt|srv|media)
local base_path="/$method"
local host_dirs=("$base_path"/*)
local options=()
for dir in "${host_dirs[@]}"; do
[[ -d "$dir" ]] && options+=("$dir" "$(basename "$dir")")
done
if [[ ${#options[@]} -eq 0 ]]; then
msg_error "$(translate "No directories found in") $base_path"
return 1
fi
result=$(whiptail --title "$(translate "Select Host Folder")" \
--menu "$(translate "Select the folder to mount:")" 20 80 10 \
"${options[@]}" 3>&1 1>&2 2>&3) || return 1
;;
manual)
result=$(whiptail --title "$(translate "Enter Path")" \
--inputbox "$(translate "Enter the full path to the host folder:")" \
10 70 "/mnt/" 3>&1 1>&2 2>&3) || return 1
;;
*)
return 1
;;
esac
[[ -z "$result" ]] && return 1
[[ ! -d "$result" ]] && {
msg_error "$(translate "The selected path is not a valid directory:") $result"
return 1
}
echo "$result"
}
select_lxc_container() {
local ct_list ctid ct_status
ct_list=$(pct list | awk 'NR>1 {print $1, $2, $3}')
if [[ -z "$ct_list" ]]; then
dialog --title "$(translate "Error")" \
--msgbox "$(translate "No LXC containers available")" 8 50
return 1
fi
local options=()
while read -r id name status; do
if [[ -n "$id" ]]; then
options+=("$id" "$name ($status)")
fi
done <<< "$ct_list"
ctid=$(dialog --title "$(translate "Select LXC Container")" \
--menu "\n$(translate "Select container:")" 25 80 15 \
"${options[@]}" 3>&1 1>&2 2>&3)
if [[ -z "$ctid" ]]; then
return 1
fi
echo "$ctid"
return 0
}
select_container_mount_point_() {
local ctid="$1"
local host_dir="$2"
local choice mount_point existing_dirs options
while true; do
choice=$(whiptail --title "$(translate "Configure Mount Point inside LXC")" \
--menu "$(translate "Where to mount inside container?")" 18 70 5 \
"1" "$(translate "Create new directory in /mnt")" \
"2" "$(translate "Enter path manually")" \
"3" "$(translate "Cancel")" 3>&1 1>&2 2>&3) || return 1
case "$choice" in
1)
mount_point=$(whiptail --inputbox "$(translate "Enter folder name for /mnt:")" 10 60 "shared" 3>&1 1>&2 2>&3) || continue
[[ -z "$mount_point" ]] && continue
mount_point="/mnt/$mount_point"
pct exec "$ctid" -- mkdir -p "$mount_point" 2>/dev/null
;;
2)
mount_point=$(whiptail --inputbox "$(translate "Enter full path:")" 10 70 "/mnt/shared" 3>&1 1>&2 2>&3) || continue
[[ -z "$mount_point" ]] && continue
mount_point="/mnt/$mount_point"
pct exec "$ctid" -- mkdir -p "$mount_point" 2>/dev/null
;;
3)
return 1
;;
esac
if pct exec "$ctid" -- test -d "$mount_point" 2>/dev/null; then
echo "$mount_point"
return 0
else
whiptail --msgbox "$(translate "Could not create or access directory:") $mount_point" 8 70
continue
fi
done
}
select_container_mount_point() {
local ctid="$1"
local host_dir="$2"
local choice mount_point base_name
base_name=$(basename "$host_dir")
while true; do
choice=$(whiptail --title "$(translate "Configure Mount Point inside LXC")" \
--menu "$(translate "Where to mount inside container?")" 18 70 5 \
"1" "$(translate "Create new directory in /mnt")" \
"2" "$(translate "Enter path manually")" \
"3" "$(translate "Cancel")" 3>&1 1>&2 2>&3) || return 1
case "$choice" in
1)
mount_point=$(whiptail --inputbox "$(translate "Enter folder name for /mnt:")" \
10 60 "$base_name" 3>&1 1>&2 2>&3) || continue
[[ -z "$mount_point" ]] && continue
mount_point="/mnt/$mount_point"
pct exec "$ctid" -- mkdir -p "$mount_point" 2>/dev/null
;;
2)
mount_point=$(whiptail --inputbox "$(translate "Enter full path:")" \
10 70 "/mnt/$base_name" 3>&1 1>&2 2>&3) || continue
[[ -z "$mount_point" ]] && continue
pct exec "$ctid" -- mkdir -p "$mount_point" 2>/dev/null
;;
3)
return 1
;;
esac
if pct exec "$ctid" -- test -d "$mount_point" 2>/dev/null; then
echo "$mount_point"
return 0
else
whiptail --msgbox "$(translate "Could not create or access directory:") $mount_point" 8 70
continue
fi
done
}
# ==========================================================
# CLIENT MOUNT FUNCTIONS (NFS/SAMBA COMMON)
# ==========================================================
# Check if container is privileged (required for client mounts)
select_privileged_lxc() {
# === Select CT ===
local ct_list ctid ct_status conf unpriv
ct_list=$(pct list | awk 'NR>1 {print $1, $3}')
if [[ -z "$ct_list" ]]; then
dialog --backtitle "ProxMenux" --title "$(translate "Error")" \
--msgbox "$(translate "No CTs available in the system.")" 8 50
return 1
fi
ctid=$(dialog --backtitle "ProxMenux" --title "$(translate "Select CT")" \
--menu "$(translate "Select the CT to manage NFS/Samba client:")" 20 70 12 \
$ct_list 3>&1 1>&2 2>&3)
if [[ -z "$ctid" ]]; then
dialog --backtitle "ProxMenux" --title "$(translate "Error")" \
--msgbox "$(translate "No CT was selected.")" 8 50
return 1
fi
# === Start CT if not running ===
ct_status=$(pct status "$ctid" | awk '{print $2}')
if [[ "$ct_status" != "running" ]]; then
show_proxmenux_logo
echo -e
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.")"
echo -e ""
msg_success "$(translate 'Press Enter to continue...')"
read -r
return 1
fi
msg_ok "$(translate "CT started successfully.")"
fi
# === Check privileged/unprivileged ===
conf="/etc/pve/lxc/${ctid}.conf"
unpriv=$(awk '/^unprivileged:/ {print $2}' "$conf" 2>/dev/null)
if [[ "$unpriv" == "1" ]]; then
dialog --backtitle "ProxMenux" --title "$(translate "Privileged Container Required")" \
--msgbox "\n$(translate "Network share mounting (NFS/Samba) requires a PRIVILEGED container.")\n\n$(translate "Selected container") $ctid $(translate "is UNPRIVILEGED.")\n\n$(translate "For unprivileged containers, use instead:")\n • $(translate "Configure LXC mount points")\n • $(translate "Mount shares on HOST first")\n • $(translate "Then bind-mount to container")" 15 75
exit 1
fi
# Export CTID if all good
echo "$ctid"
CTID="$ctid"
return 0
}
# Common mount point selection for containers
pmx_select_container_mount_point() {
local ctid="$1"
local share_name="${2:-shared}"
while true; do
local choice=$(whiptail --title "$(translate "Select Mount Point")" --menu "$(translate "Where do you want to mount inside container?")" 15 70 3 \
"existing" "$(translate "Select from existing folders in /mnt")" \
"new" "$(translate "Create new folder in /mnt")" \
"custom" "$(translate "Enter custom path")" 3>&1 1>&2 2>&3)
case "$choice" in
existing)
local existing_dirs=$(pct exec "$ctid" -- find /mnt -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort)
if [[ -z "$existing_dirs" ]]; then
whiptail --title "$(translate "No Folders")" --msgbox "$(translate "No folders found in /mnt. Please create a new folder.")" 8 60
continue
fi
local options=()
while IFS= read -r dir; do
if [[ -n "$dir" ]]; then
local name=$(basename "$dir")
if pct exec "$ctid" -- [ "$(ls -A "$dir" 2>/dev/null | wc -l)" -eq 0 ]; then
local status="$(translate "Empty")"
else
local status="$(translate "Contains files")"
fi
options+=("$dir" "$name ($status)")
fi
done <<< "$existing_dirs"
local mount_point=$(whiptail --title "$(translate "Select Existing Folder")" --menu "$(translate "Choose a folder to mount:")" 20 80 10 "${options[@]}" 3>&1 1>&2 2>&3)
if [[ -n "$mount_point" ]]; then
if pct exec "$ctid" -- [ "$(ls -A "$mount_point" 2>/dev/null | wc -l)" -gt 0 ]; then
local file_count=$(pct exec "$ctid" -- ls -A "$mount_point" 2>/dev/null | wc -l || true)
if ! whiptail --yesno "$(translate "WARNING: The selected directory is not empty!")\n\n$(translate "Directory:"): $mount_point\n$(translate "Contains:"): $file_count $(translate "files/folders")\n\n$(translate "Mounting here will hide existing files until unmounted.")\n\n$(translate "Do you want to continue?")" 14 70 --title "$(translate "Directory Not Empty")"; then
continue
fi
fi
echo "$mount_point"
return 0
fi
;;
new)
local folder_name=$(whiptail --inputbox "$(translate "Enter new folder name:")" 10 60 "$share_name" --title "$(translate "New Folder in /mnt")" 3>&1 1>&2 2>&3)
if [[ -n "$folder_name" ]]; then
local mount_point="/mnt/$folder_name"
echo "$mount_point"
return 0
fi
;;
custom)
local mount_point=$(whiptail --inputbox "$(translate "Enter full path for mount point:")" 10 70 "/mnt/${share_name}" --title "$(translate "Custom Path")" 3>&1 1>&2 2>&3)
if [[ -n "$mount_point" ]]; then
echo "$mount_point"
return 0
fi
;;
*)
return 1
;;
esac
done
}
# Common server discovery function
pmx_discover_network_servers() {
local service_type="$1" # "NFS" or "Samba"
local port="$2" # "2049" for NFS, "139,445" for Samba
local host_ip=$(hostname -I | awk '{print $1}')
local network=$(echo "$host_ip" | cut -d. -f1-3).0/24
# Install nmap if needed
if ! which nmap >/dev/null 2>&1; then
apt-get install -y nmap &>/dev/null
fi
local servers
if [[ "$service_type" == "Samba" ]]; then
servers=$(nmap -p 139,445 --open "$network" 2>/dev/null | grep -B 4 -E "(139|445)/tcp open" | grep "Nmap scan report" | awk '{print $5}' | sort -u || true)
else
servers=$(nmap -p 2049 --open "$network" 2>/dev/null | grep -B 4 "2049/tcp open" | grep "Nmap scan report" | awk '{print $5}' | sort -u || true)
fi
if [[ -z "$servers" ]]; then
whiptail --title "$(translate "No Servers Found")" --msgbox "$(translate "No") $service_type $(translate "servers found on the network.")\n\n$(translate "You can add servers manually.")" 10 60
return 1
fi
local options=()
while IFS= read -r server; do
if [[ -n "$server" ]]; then
if [[ "$service_type" == "Samba" ]]; then
# Try to get NetBIOS name for Samba
local nb_name=$(nmblookup -A "$server" 2>/dev/null | awk '/<00> -.*B <ACTIVE>/ {print $1; exit}')
if [[ -z "$nb_name" || "$nb_name" == "$server" || "$nb_name" == "address" || "$nb_name" == "-" ]]; then
nb_name="Unknown"
fi
options+=("$server" "$nb_name ($server)")
else
# For NFS, show export count
local exports_count=$(showmount -e "$server" 2>/dev/null | tail -n +2 | wc -l || echo "0")
options+=("$server" "NFS Server ($exports_count exports)")
fi
fi
done <<< "$servers"
if [[ ${#options[@]} -eq 0 ]]; then
whiptail --title "$(translate "No Valid Servers")" --msgbox "$(translate "No accessible") $service_type $(translate "servers found.")" 8 50
return 1
fi
local selected_server=$(whiptail --title "$(translate "Select") $service_type $(translate "Server")" --menu "$(translate "Choose a server:")" 20 80 10 "${options[@]}" 3>&1 1>&2 2>&3)
if [[ -n "$selected_server" ]]; then
echo "$selected_server"
return 0
else
return 1
fi
}
# Common server selection function
pmx_select_server() {
local service_type="$1" # "NFS" or "Samba"
local port="$2" # "2049" for NFS, "139,445" for Samba
local method=$(whiptail --title "$(translate "$service_type Server Selection")" --menu "$(translate "How do you want to select the") $service_type $(translate "server?")" 15 70 3 \
"auto" "$(translate "Auto-discover servers on network")" \
"manual" "$(translate "Enter server IP/hostname manually")" \
"recent" "$(translate "Select from recent servers")" 3>&1 1>&2 2>&3)
local result_code=$?
if [[ $result_code -ne 0 ]]; then
return 1
fi
case "$method" in
auto)
local discovered_server
discovered_server=$(pmx_discover_network_servers "$service_type" "$port")
local discover_result=$?
if [[ $discover_result -eq 0 && -n "$discovered_server" ]]; then
echo "$discovered_server"
return 0
else
return 1
fi
;;
manual)
local server=$(whiptail --inputbox "$(translate "Enter") $service_type $(translate "server IP or hostname:")" 10 60 --title "$(translate "$service_type Server")" 3>&1 1>&2 2>&3)
local input_result=$?
if [[ $input_result -eq 0 && -n "$server" ]]; then
echo "$server"
return 0
else
return 1
fi
;;
recent)
local fs_type
if [[ "$service_type" == "NFS" ]]; then
fs_type="nfs"
else
fs_type="cifs"
fi
# Fix the recent servers detection for NFS
local recent
if [[ "$service_type" == "NFS" ]]; then
recent=$(grep "$fs_type" /etc/fstab 2>/dev/null | awk '{print $1}' | cut -d: -f1 | sort -u || true)
else
recent=$(grep "$fs_type" /etc/fstab 2>/dev/null | awk '{print $1}' | cut -d/ -f3 | sort -u || true)
fi
if [[ -z "$recent" ]]; then
whiptail --title "$(translate "No Recent Servers")" --msgbox "\n$(translate "No recent") $service_type $(translate "servers found.")" 8 50
return 1
fi
local options=()
while IFS= read -r server; do
[[ -n "$server" ]] && options+=("$server" "$(translate "Recent") $service_type $(translate "server")")
done <<< "$recent"
local selected_server=$(whiptail --title "$(translate "Recent") $service_type $(translate "Servers")" --menu "$(translate "Choose a recent server:")" 20 70 10 "${options[@]}" 3>&1 1>&2 2>&3)
local select_result=$?
if [[ $select_result -eq 0 && -n "$selected_server" ]]; then
echo "$selected_server"
return 0
else
return 1
fi
;;
*)
return 1
;;
esac
}
# Common mount options configuration
pmx_configure_mount_options() {
local service_type="$1" # "NFS" or "CIFS"
local mount_type
if [[ "$service_type" == "NFS" ]]; then
mount_type=$(whiptail --title "$(translate "Mount Options")" --menu "$(translate "Select mount configuration:")" 15 70 4 \
"default" "$(translate "Default options")" \
"readonly" "$(translate "Read-only mount")" \
"performance" "$(translate "Performance optimized")" \
"custom" "$(translate "Custom options")" 3>&1 1>&2 2>&3)
case "$mount_type" in
default)
echo "rw,hard,intr,rsize=8192,wsize=8192,timeo=14"
;;
readonly)
echo "ro,hard,intr,rsize=8192,timeo=14"
;;
performance)
echo "rw,hard,intr,rsize=1048576,wsize=1048576,timeo=14,retrans=2"
;;
custom)
local options=$(whiptail --inputbox "$(translate "Enter custom mount options:")" 10 70 "rw,hard,intr" --title "$(translate "Custom Options")" 3>&1 1>&2 2>&3)
echo "${options:-rw,hard,intr}"
;;
*)
echo "rw,hard,intr,rsize=8192,wsize=8192,timeo=14"
;;
esac
else
# CIFS options
mount_type=$(whiptail --title "$(translate "Mount Options")" --menu "$(translate "Select mount configuration:")" 15 70 4 \
"default" "$(translate "Default options")" \
"readonly" "$(translate "Read-only mount")" \
"performance" "$(translate "Performance optimized")" \
"custom" "$(translate "Custom options")" 3>&1 1>&2 2>&3)
case "$mount_type" in
default)
echo "rw,file_mode=0664,dir_mode=0775,iocharset=utf8"
;;
readonly)
echo "ro,file_mode=0444,dir_mode=0555,iocharset=utf8"
;;
performance)
echo "rw,file_mode=0664,dir_mode=0775,iocharset=utf8,cache=strict,rsize=1048576,wsize=1048576"
;;
custom)
local options=$(whiptail --inputbox "$(translate "Enter custom mount options:")" 10 70 "rw,file_mode=0664,dir_mode=0775" --title "$(translate "Custom Options")" 3>&1 1>&2 2>&3)
echo "${options:-rw,file_mode=0664,dir_mode=0775}"
;;
*)
echo "rw,file_mode=0664,dir_mode=0775,iocharset=utf8"
;;
esac
fi
}
# Common permanent mount question
pmx_ask_permanent_mount() {
if whiptail --yesno "$(translate "Do you want to make this mount permanent?")\n\n$(translate "This will add the mount to /etc/fstab so it persists after reboot.")" 10 70 --title "$(translate "Permanent Mount")"; then
echo "true"
else
echo "false"
fi
}

View File

@@ -0,0 +1,340 @@
#!/bin/bash
# ==========================================================
# Proxmox VE Update Script
# ==========================================================
# 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
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"
}
download_common_functions() {
if ! source <(curl -s "$REPO_URL/scripts/global/common-functions.sh"); then
return 1
fi
}
update_pve9() {
local start_time=$(date +%s)
local log_file="/var/log/proxmox-update-$(date +%Y%m%d-%H%M%S).log"
local changes_made=false
local OS_CODENAME="$(grep "VERSION_CODENAME=" /etc/os-release | cut -d"=" -f 2 | xargs)"
local TARGET_CODENAME="trixie"
if [ -z "$OS_CODENAME" ]; then
OS_CODENAME=$(lsb_release -cs 2>/dev/null || echo "trixie")
fi
download_common_functions
msg_info2 "$(translate "Detected: Proxmox VE 9.x (Current: $OS_CODENAME, Target: $TARGET_CODENAME)")"
echo
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")"
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
read -r
return 1
fi
if ! ping -c 1 download.proxmox.com >/dev/null 2>&1; then
msg_error "$(translate "Cannot reach Proxmox repositories")"
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
read -r
return 1
fi
disable_sources_repo() {
local file="$1"
if [[ -f "$file" ]]; then
sed -i ':a;/^\n*$/{$d;N;ba}' "$file"
if grep -q "^Enabled:" "$file"; then
sed -i 's/^Enabled:.*$/Enabled: false/' "$file"
else
echo "Enabled: false" >> "$file"
fi
if ! grep -q "^Types: " "$file"; then
msg_warn "$(translate "Malformed .sources file detected, removing: $(basename "$file")")"
rm -f "$file"
fi
return 0
fi
return 1
}
if disable_sources_repo "/etc/apt/sources.list.d/pve-enterprise.sources"; then
msg_ok "$(translate "Enterprise Proxmox repository disabled")"
changes_made=true
fi
if disable_sources_repo "/etc/apt/sources.list.d/ceph.sources"; then
msg_ok "$(translate "Enterprise Proxmox Ceph repository disabled")"
changes_made=true
fi
for legacy_file in /etc/apt/sources.list.d/pve-public-repo.list \
/etc/apt/sources.list.d/pve-install-repo.list \
/etc/apt/sources.list.d/debian.list; do
if [[ -f "$legacy_file" ]]; then
rm -f "$legacy_file"
msg_ok "$(translate "Removed legacy repository: $(basename "$legacy_file")")"
fi
done
if [[ -f /etc/apt/sources.list.d/debian.sources ]]; then
rm -f /etc/apt/sources.list.d/debian.sources
msg_ok "$(translate "Old debian.sources file removed to prevent duplication")"
fi
msg_info "$(translate "Creating Proxmox VE 9.x no-subscription repository...")"
cat > /etc/apt/sources.list.d/proxmox.sources << EOF
Enabled: true
Types: deb
URIs: http://download.proxmox.com/debian/pve
Suites: ${TARGET_CODENAME}
Components: pve-no-subscription
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
EOF
msg_ok "$(translate "Proxmox VE 9.x no-subscription repository created")"
changes_made=true
msg_info "$(translate "Creating Debian ${TARGET_CODENAME} sources file...")"
cat > /etc/apt/sources.list.d/debian.sources << EOF
Types: deb
URIs: http://deb.debian.org/debian/
Suites: ${TARGET_CODENAME} ${TARGET_CODENAME}-updates
Components: main contrib non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
Types: deb
URIs: http://security.debian.org/debian-security/
Suites: ${TARGET_CODENAME}-security
Components: main contrib non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
EOF
msg_ok "$(translate "Debian repositories configured for $TARGET_CODENAME")"
local firmware_conf="/etc/apt/apt.conf.d/no-firmware-warnings.conf"
if [ ! -f "$firmware_conf" ]; then
msg_info "$(translate "Disabling non-free firmware warnings...")"
echo 'APT::Get::Update::SourceListWarnings::NonFreeFirmware "false";' > "$firmware_conf"
msg_ok "$(translate "Non-free firmware warnings disabled")"
fi
cleanup_duplicate_repos
update_output=$(apt-get update 2>&1)
update_exit_code=$?
if [ $update_exit_code -eq 0 ]; then
msg_ok "$(translate "Package lists updated successfully")"
else
if echo "$update_output" | grep -q "NO_PUBKEY\|GPG error"; then
msg_info "$(translate "Fixing GPG key issues...")"
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys $(echo "$update_output" | grep "NO_PUBKEY" | sed 's/.*NO_PUBKEY //' | head -1) 2>/dev/null
if apt-get update > "$log_file" 2>&1; then
msg_ok "$(translate "Package lists updated after GPG fix")"
else
msg_error "$(translate "Failed to update package lists. Check log: $log_file")"
return 1
fi
elif echo "$update_output" | grep -q "404\|Failed to fetch"; then
msg_warn "$(translate "Some repositories are not available, continuing with available ones...")"
else
msg_error "$(translate "Failed to update package lists. Check log: $log_file")"
echo "Error details: $update_output"
return 1
fi
fi
if apt policy 2>/dev/null | grep -q "${TARGET_CODENAME}.*pve-no-subscription"; then
msg_ok "$(translate "Proxmox VE 9.x repositories verified")"
else
msg_warn "$(translate "Proxmox VE 9.x repositories verification inconclusive, continuing...")"
fi
local current_pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+\.[0-9]+' | head -1)
local available_pve_version=$(apt-cache policy pve-manager 2>/dev/null | grep -oP 'Candidate: \K[0-9]+\.[0-9]+\.[0-9]+' | head -1)
local upgradable=$(apt list --upgradable 2>/dev/null | grep -c "upgradable")
local security_updates=$(apt list --upgradable 2>/dev/null | grep -c "security")
show_update_menu() {
local current_version="$1"
local target_version="$2"
local upgradable_count="$3"
local security_count="$4"
local menu_text="$(translate "System Update Information")\n\n"
menu_text+="$(translate "Current PVE Version"): $current_version\n"
if [ -n "$target_version" ] && [ "$target_version" != "$current_version" ]; then
menu_text+="$(translate "Available PVE Version"): $target_version\n"
fi
menu_text+="\n$(translate "Package Updates Available"): $upgradable_count\n"
menu_text+="$(translate "Security Updates"): $security_count\n\n"
if [ "$upgradable_count" -eq 0 ]; then
menu_text+="$(translate "System is already up to date")"
whiptail --title "$(translate "Update Status")" --msgbox "$menu_text" 15 70
return 2
else
menu_text+="$(translate "Do you want to proceed with the system update?")"
if whiptail --title "$(translate "Proxmox Update")" --yesno "$menu_text" 18 70; then
return 0
else
return 1
fi
fi
}
show_update_menu "$current_pve_version" "$available_pve_version" "$upgradable" "$security_updates"
MENU_RESULT=$?
if [[ $MENU_RESULT -eq 1 ]]; then
msg_info2 "$(translate "Update cancelled by user")"
apt-get -y autoremove > /dev/null 2>&1 || true
apt-get -y autoclean > /dev/null 2>&1 || true
return 0
elif [[ $MENU_RESULT -eq 2 ]]; then
msg_ok "$(translate "System is already up to date. No update needed.")"
apt-get -y autoremove > /dev/null 2>&1 || true
apt-get -y autoclean > /dev/null 2>&1 || true
return 0
fi
msg_info "$(translate "Cleaning up unused time synchronization services...")"
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 "Old time services removed successfully")"
else
msg_warn "$(translate "Some old time services could not be removed (not installed)")"
fi
msg_info "$(translate "Updating packages...")"
apt-get install pv -y > /dev/null 2>&1
msg_ok "$(translate "Packages updated successfully")"
tput sc
DEBIAN_FRONTEND=noninteractive apt-get -y \
-o Dpkg::Options::='--force-confdef' \
-o Dpkg::Options::='--force-confold' \
dist-upgrade 2>&1 | while IFS= read -r line; do
echo "$line" >> "$log_file"
if [[ "$line" =~ \[[#=\-]+\]\ *[0-9]{1,3}% ]]; then
continue
fi
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")"
row=$(( $(tput lines) - 6 ))
tput cup $row 0; printf "%s\n" "$(translate "Installing packages...")"
tput cup $((row + 1)) 0; printf "%s\n" "──────────────────────────────────────────────"
tput cup $((row + 2)) 0; printf "%s %s\n" "$(translate "Package:")" "$package_name"
tput cup $((row + 3)) 0; printf "%s\n" "Progress: [ ] 0%"
tput cup $((row + 4)) 0; printf "%s\n" "──────────────────────────────────────────────"
for i in $(seq 1 10); do
sleep 0.1
progress=$((i * 10))
tput cup $((row + 3)) 9
printf "[%-50s] %3d%%" "$(printf "#%.0s" $(seq 1 $((progress/2))))" "$progress"
done
fi
done
tput rc
tput ed
upgrade_exit_code=${PIPESTATUS[0]}
if [ $upgrade_exit_code -eq 0 ]; then
msg_ok "$(translate "System upgrade completed successfully")"
else
msg_error "$(translate "System upgrade failed. Check log: $log_file")"
return 1
fi
msg_info "$(translate "Installing essential Proxmox packages...")"
local additional_packages="zfsutils-linux proxmox-backup-restore-image chrony"
if /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install $additional_packages >> "$log_file" 2>&1; then
msg_ok "$(translate "Essential Proxmox packages installed")"
else
msg_warn "$(translate "Some essential Proxmox packages may not have been installed")"
fi
lvm_repair_check
cleanup_duplicate_repos
msg_info "$(translate "Performing system cleanup...")"
apt-get -y autoremove > /dev/null 2>&1 || true
apt-get -y autoclean > /dev/null 2>&1 || true
msg_ok "$(translate "Cleanup finished")"
local end_time=$(date +%s)
local duration=$((end_time - start_time))
local minutes=$((duration / 60))
local seconds=$((duration % 60))
echo -e "${TAB}${BGN}$(translate "====== PVE UPDATE COMPLETED ======")${CL}"
echo -e "${TAB}${GN}⏱️ $(translate "Duration")${CL}: ${BL}${minutes}m ${seconds}s${CL}"
echo -e "${TAB}${GN}📄 $(translate "Log file")${CL}: ${BL}$log_file${CL}"
echo -e "${TAB}${GN}📦 $(translate "Packages upgraded")${CL}: ${BL}$upgradable${CL}"
echo -e "${TAB}${GN}🖥️ $(translate "Proxmox VE")${CL}: ${BL}$target_version (Debian $OS_CODENAME)${CL}"
msg_ok "$(translate "Proxmox VE 9.x configuration completed.")"
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
update_pve9
fi

View File

@@ -0,0 +1,284 @@
#!/bin/bash
# ==========================================================
# Proxmox VE 8.x Update Script
# ==========================================================
# 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
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"
}
download_common_functions() {
if ! source <(curl -s "$REPO_URL/scripts/global/common-functions.sh"); then
return 1
fi
}
update_pve8() {
local start_time=$(date +%s)
local log_file="/var/log/proxmox-update-$(date +%Y%m%d-%H%M%S).log"
local changes_made=false
local 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
download_common_functions
msg_info2 "$(translate "Detected: Proxmox VE 8.x (Debian $OS_CODENAME)")"
echo
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")"
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
read -r
return 1
fi
if ! ping -c 1 download.proxmox.com >/dev/null 2>&1; then
msg_error "$(translate "Cannot reach Proxmox repositories")"
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
read -r
return 1
fi
if [ -f /etc/apt/sources.list.d/pve-enterprise.list ] && grep -q "^deb" /etc/apt/sources.list.d/pve-enterprise.list; then
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
if [ -f /etc/apt/sources.list.d/ceph.list ] && grep -q "^deb" /etc/apt/sources.list.d/ceph.list; then
sed -i "s/^deb/#deb/g" /etc/apt/sources.list.d/ceph.list
msg_ok "$(translate "Enterprise Proxmox Ceph repository disabled")"
changes_made=true
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
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
local sources_file="/etc/apt/sources.list"
cp "$sources_file" "${sources_file}.backup.$(date +%Y%m%d_%H%M%S)"
if grep -q -E "(debian-security -security|debian main$|debian -updates)" "$sources_file"; then
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"
changes_made=true
msg_ok "$(translate "Malformed repository entries cleaned")"
fi
cat > "$sources_file" << 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
msg_ok "$(translate "Debian repositories configured for $OS_CODENAME")"
local firmware_conf="/etc/apt/apt.conf.d/no-firmware-warnings.conf"
if [ ! -f "$firmware_conf" ]; then
echo 'APT::Get::Update::SourceListWarnings::NonFreeFirmware "false";' > "$firmware_conf"
fi
cleanup_duplicate_repos
msg_info "$(translate "Updating package lists...")"
if apt-get update > "$log_file" 2>&1; then
msg_ok "$(translate "Package lists updated successfully")"
else
msg_error "$(translate "Failed to update package lists. Check log: $log_file")"
return 1
fi
local current_pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+\.[0-9]+' | head -1)
local available_pve_version=$(apt-cache policy pve-manager 2>/dev/null | grep -oP 'Candidate: \K[0-9]+\.[0-9]+\.[0-9]+' | head -1)
local upgradable=$(apt list --upgradable 2>/dev/null | grep -c "upgradable")
local security_updates=$(apt list --upgradable 2>/dev/null | grep -c "security")
show_update_menu() {
local current_version="$1"
local target_version="$2"
local upgradable_count="$3"
local security_count="$4"
local menu_text="$(translate "System Update Information")\n\n"
menu_text+="$(translate "Current PVE Version"): $current_version\n"
if [ -n "$target_version" ] && [ "$target_version" != "$current_version" ]; then
menu_text+="$(translate "Available PVE Version"): $target_version\n"
fi
menu_text+="\n$(translate "Package Updates Available"): $upgradable_count\n"
menu_text+="$(translate "Security Updates"): $security_count\n\n"
if [ "$upgradable_count" -eq 0 ]; then
menu_text+="$(translate "System is already up to date")"
whiptail --title "$(translate "Update Status")" --msgbox "$menu_text" 15 70
return 2
else
menu_text+="$(translate "Do you want to proceed with the system update?")"
if whiptail --title "$(translate "Proxmox Update")" --yesno "$menu_text" 18 70; then
return 0
else
return 1
fi
fi
}
show_update_menu "$current_pve_version" "$available_pve_version" "$upgradable" "$security_updates"
MENU_RESULT=$?
if [[ $MENU_RESULT -eq 1 ]]; then
msg_info2 "$(translate "Update cancelled by user")"
apt-get -y autoremove > /dev/null 2>&1 || true
apt-get -y autoclean > /dev/null 2>&1 || true
return 0
elif [[ $MENU_RESULT -eq 2 ]]; then
msg_ok "$(translate "System is already up to date. No update needed.")"
apt-get -y autoremove > /dev/null 2>&1 || true
apt-get -y autoclean > /dev/null 2>&1 || true
return 0
fi
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 utilities...")"
DEBIAN_FRONTEND=noninteractive apt-get -y purge $conflicting_packages >> "$log_file" 2>&1
msg_ok "$(translate "Conflicting utilities removed")"
fi
export DEBIAN_FRONTEND=noninteractive
export APT_LISTCHANGES_FRONTEND=none
export NEEDRESTART_MODE=a
export UCF_FORCE_CONFOLD=1
export DPKG_OPTIONS="--force-confdef --force-confold"
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)
msg_ok "$(translate "Packages upgrade successfull")"
if [ "$total_packages" -eq 0 ]; then
total_packages=1
fi
tput civis
tput sc
(
/usr/bin/env \
DEBIAN_FRONTEND=noninteractive \
APT_LISTCHANGES_FRONTEND=none \
NEEDRESTART_MODE=a \
UCF_FORCE_CONFOLD=1 \
apt-get -y \
-o Dpkg::Options::="--force-confdef" \
-o Dpkg::Options::="--force-confold" \
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
tput cnorm
msg_ok "$(translate "System upgrade completed")"
fi
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
lvm_repair_check
cleanup_duplicate_repos
msg_info "$(translate "Performing system cleanup...")"
apt-get -y autoremove > /dev/null 2>&1 || true
apt-get -y autoclean > /dev/null 2>&1 || true
msg_ok "$(translate "Cleanup finished")"
local end_time=$(date +%s)
local duration=$((end_time - start_time))
local minutes=$((duration / 60))
local seconds=$((duration % 60))
echo -e "${TAB}${BGN}$(translate "====== PVE UPDATE COMPLETED ======")${CL}"
echo -e "${TAB}${GN}⏱️ $(translate "Duration")${CL}: ${BL}${minutes}m ${seconds}s${CL}"
echo -e "${TAB}${GN}📄 $(translate "Log file")${CL}: ${BL}$log_file${CL}"
echo -e "${TAB}${GN}📦 $(translate "Packages upgraded")${CL}: ${BL}$upgradable${CL}"
echo -e "${TAB}${GN}🖥️ $(translate "Proxmox VE")${CL}: ${BL}$target_version (Debian $OS_CODENAME)${CL}"
msg_ok "$(translate "Proxmox VE 8 system update completed successfully")"
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
update_pve8
fi

View File

@@ -197,13 +197,12 @@ show_vm_ct_commands() {
echo -e "\n${YELLOW}$(translate 'Listing relevant CT users and their mapped UID/GID on host...')${NC}\n"
# Obtener el shift de UID del CT (por defecto 100000 si no está configurado)
UID_SHIFT=$(grep "^lxc.idmap" /etc/pve/lxc/"$id".conf | grep 'u 0' | awk '{print $5}')
UID_SHIFT=${UID_SHIFT:-100000}
# Obtener todos los usuarios y filtrar solo root o UID >= 1000
pct exec "$id" -- getent passwd | while IFS=: read -r username _ uid gid _ home _; do
if [ "$uid" -eq 0 ] || [ "$uid" -ge 1000 ]; then
if [ "$uid" -eq 0 ] || [ "$uid" -eq 65534 ] || [ "$uid" -ge 30 ]; then
real_uid=$((UID_SHIFT + uid))
real_gid=$((UID_SHIFT + gid))
echo -e "${GREEN}$(translate 'User')${NC}: $username"

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,211 +32,163 @@ 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
exit 1
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
IMAGE=$(echo "$IMAGE" | tr -d '"')
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)
[[ -z "$INTERFACE" ]] && { msg_error "$(translate 'No interface type selected for') $IMAGE"; continue; }
# Remove quotes from selected image
IMAGE=$(echo "$IMAGE" | tr -d '"')
FULL_PATH="$IMAGES_DIR/$IMAGE"
msg_info "$(translate 'Importing image:') $IMAGE"
TEMP_DISK_FILE=$(mktemp)
# 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
qm importdisk "$VMID" "$FULL_PATH" "$STORAGE" 2>&1 | while read -r line; do
if [[ "$line" =~ transferred ]]; then
PERCENT=$(echo "$line" | grep -oP "\(\d+\.\d+%\)" | tr -d '()%')
echo -ne "\r${TAB}${BL}-$(translate 'Importing image:') $IMAGE-${CL} ${PERCENT}%"
elif [[ "$line" =~ successfully\ imported\ disk ]]; then
echo "$line" | grep -oP "(?<=successfully imported disk ').*(?=')" > "$TEMP_DISK_FILE"
fi
FULL_PATH="$IMAGES_DIR/$IMAGE"
# Show initial message
msg_info "$(translate 'Importing image:')"
# Temporary file to capture the imported disk
TEMP_DISK_FILE=$(mktemp)
done
echo -ne "\n"
IMPORT_STATUS=${PIPESTATUS[0]}
# 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 '()%')
if [ "$IMPORT_STATUS" -eq 0 ]; then
msg_ok "$(translate 'Image imported successfully')"
IMPORTED_DISK=$(cat "$TEMP_DISK_FILE")
rm -f "$TEMP_DISK_FILE"
# Show progress with custom format without translation
echo -ne "\r${TAB}${YW}-$(translate 'Importing image:') $IMAGE-${CL} ${PERCENT}%"
if [ -n "$IMPORTED_DISK" ]; then
EXISTING_DISKS=$(qm config "$VMID" | grep -oP "${INTERFACE}\d+" | sort -n)
NEXT_SLOT=0
[[ -n "$EXISTING_DISKS" ]] && NEXT_SLOT=$(( $(echo "$EXISTING_DISKS" | tail -n1 | sed "s/${INTERFACE}//") + 1 ))
elif [[ "$line" =~ successfully\ imported\ disk ]]; then
SSD_OPTION=""
if [ "$INTERFACE" != "virtio" ]; then
whiptail --yesno "$(translate 'Do you want to use SSD emulation for this disk?')" 10 60 && SSD_OPTION=",ssd=1"
fi
# 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
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
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"
else
SSD_OPTION=""
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
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
else
msg_error "$(translate 'Could not find the imported disk')"
fi
msg_info "$(translate 'Configuring disk')"
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}"
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
}
else
msg_error "$(translate 'Could not configure disk') ${INTERFACE}${NEXT_SLOT} $(translate 'for VM') $VMID"
fi
else
msg_error "$(translate 'Could not import') $IMAGE"
msg_error "$(translate 'Could not find the imported disk')"
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

@@ -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"
@@ -81,7 +73,48 @@ validate_container_id() {
}
# Configure LXC for Coral TPU and iGPU
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_udev_rule_for_coral_usb() {
RULE_FILE="/etc/udev/rules.d/99-coral-usb.rules"
RULE_CONTENT='# Coral USB Accelerator
SUBSYSTEM=="usb", ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="9302", MODE="0666", TAG+="uaccess", SYMLINK+="coral"
# Coral Dev Board / Mini PCIe
SUBSYSTEM=="usb", ATTRS{idVendor}=="1a6e", ATTRS{idProduct}=="089a", MODE="0666", TAG+="uaccess", SYMLINK+="coral"'
if [[ ! -f "$RULE_FILE" ]] || ! grep -q "18d1.*9302\|1a6e.*089a" "$RULE_FILE"; then
echo "$RULE_CONTENT" > "$RULE_FILE"
udevadm control --reload-rules && udevadm trigger
msg_ok "$(translate 'Udev rules for Coral USB devices added and rules reloaded.')"
else
msg_ok "$(translate 'Udev rules for Coral USB devices already exist.')"
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_hardware() {
validate_container_id
CONFIG_FILE="/etc/pve/lxc/${CONTAINER_ID}.conf"
@@ -90,6 +123,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 +137,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 +245,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

135
scripts/lxc/jd2.sh Normal file
View File

@@ -0,0 +1,135 @@
#!/bin/bash
# Script para instalar JDownloader en un contenedor LXC desde el host Proxmox
# Autor: MacRimi
# Mostrar lista de CTs
CT_LIST=$(pct list | awk 'NR>1 {print $1, $3}')
if [ -z "$CT_LIST" ]; then
whiptail --title "Error" --msgbox "No hay contenedores LXC disponibles en el sistema." 8 50
exit 1
fi
# Seleccionar CT
CTID=$(whiptail --title "Instalación de JDownloader" --menu "Selecciona el contenedor donde instalar JDownloader:" 20 60 10 $CT_LIST 3>&1 1>&2 2>&3)
if [ -z "$CTID" ]; then
whiptail --title "Cancelado" --msgbox "No se ha seleccionado ningún contenedor." 8 40
exit 1
fi
# Solicitar email
EMAIL=$(whiptail --title "Cuenta My JDownloader" --inputbox "Introduce tu correo electrónico para vincular JDownloader:" 10 60 3>&1 1>&2 2>&3)
if [ -z "$EMAIL" ]; then
whiptail --title "Error" --msgbox "No se ha introducido ningún correo." 8 40
exit 1
fi
# Solicitar contraseña con confirmación
while true; do
PASSWORD=$(whiptail --title "Cuenta My JDownloader" --passwordbox "Introduce tu contraseña de My JDownloader:" 10 60 3>&1 1>&2 2>&3)
[ -z "$PASSWORD" ] && whiptail --title "Error" --msgbox "No se ha introducido ninguna contraseña." 8 40 && exit 1
CONFIRM=$(whiptail --title "Confirmación de contraseña" --passwordbox "Repite tu contraseña para confirmar:" 10 60 3>&1 1>&2 2>&3)
[ "$PASSWORD" = "$CONFIRM" ] && break
whiptail --title "Error" --msgbox "Las contraseñas no coinciden. Intenta de nuevo." 8 50
done
# Confirmación final
whiptail --title "Confirmar datos" --yesno "¿Deseas continuar con los siguientes datos?\n\nCorreo: $EMAIL\nContraseña: (oculta)\n\nEsta información se usará para vincular el contenedor con tu cuenta de My.JDownloader." 14 60
[ $? -ne 0 ] && whiptail --title "Cancelado" --msgbox "Instalación cancelada por el usuario." 8 40 && exit 1
clear
echo "🔍 Detectando sistema operativo dentro del CT $CTID..."
OS_ID=$(pct exec "$CTID" -- awk -F= '/^ID=/{gsub("\"",""); print $2}' /etc/os-release)
echo "Sistema detectado: $OS_ID"
echo "🧰 Preparando entorno..."
case "$OS_ID" in
debian)
# Repositorio adicional para Java 8
pct exec "$CTID" -- wget -q http://www.mirbsd.org/~tg/Debs/sources.txt/wtf-bookworm.sources
pct exec "$CTID" -- mv wtf-bookworm.sources /etc/apt/sources.list.d/
pct exec "$CTID" -- apt update -y
pct exec "$CTID" -- apt install -y openjdk-8-jdk wget
JAVA_PATH="/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java"
;;
ubuntu)
pct exec "$CTID" -- apt update -y
pct exec "$CTID" -- apt install -y openjdk-8-jdk wget
JAVA_PATH="/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java"
;;
alpine)
pct exec "$CTID" -- apk update
pct exec "$CTID" -- apk add openjdk8 wget
JAVA_PATH="/usr/lib/jvm/java-1.8-openjdk/bin/java"
;;
*)
echo "❌ Sistema operativo no soportado: $OS_ID"
exit 1
;;
esac
# Crear carpeta de instalación
pct exec "$CTID" -- mkdir -p /opt/jdownloader
pct exec "$CTID" -- bash -c 'cd /opt/jdownloader && curl -O https://installer.jdownloader.org/JDownloader.jar'
# Crear archivo de configuración JSON para My JDownloader
pct exec "$CTID" -- bash -c "mkdir -p /opt/jdownloader/cfg && cat > /opt/jdownloader/cfg/org.jdownloader.api.myjdownloader.MyJDownloaderSettings.json" <<EOF
{
"email" : "$EMAIL",
"password" : "$PASSWORD",
"enabled" : true
}
EOF
# Crear servicio según sistema
if [[ "$OS_ID" == "alpine" ]]; then
# Servicio OpenRC para Alpine
pct exec "$CTID" -- bash -c 'cat > /etc/init.d/jdownloader <<EOF
#!/sbin/openrc-run
command="/usr/bin/java"
command_args="-jar /opt/jdownloader/JDownloader.jar -norestart"
pidfile="/var/run/jdownloader.pid"
name="JDownloader"
depend() {
need net
}
EOF'
pct exec "$CTID" -- chmod +x /etc/init.d/jdownloader
pct exec "$CTID" -- rc-update add jdownloader default
pct exec "$CTID" -- rc-service jdownloader start
else
# Servicio systemd para Debian/Ubuntu
pct exec "$CTID" -- bash -c 'cat > /etc/systemd/system/jdownloader.service <<EOF
[Unit]
Description=JDownloader
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/jdownloader
ExecStart=/usr/bin/java -jar JDownloader.jar -norestart
Restart=always
[Install]
WantedBy=multi-user.target
EOF'
pct exec "$CTID" -- systemctl daemon-reexec
pct exec "$CTID" -- systemctl daemon-reload
pct exec "$CTID" -- systemctl enable jdownloader
pct exec "$CTID" -- systemctl start jdownloader
fi
pct exec "$CTID" -- reboot
echo -e "\n\033[1;32m✅ JDownloader se ha instalado correctamente en el CT $CTID y está funcionando como servicio.\033[0m"
echo -e "\n➡ Accede a \033[1;34mhttps://my.jdownloader.org\033[0m con tu cuenta para gestionarlo.\n"

99
scripts/lxc/jd2_.sh Normal file
View File

@@ -0,0 +1,99 @@
#!/bin/bash
# Script para instalar JDownloader en un contenedor LXC desde el host Proxmox
# Autor: MacRimi
# Mostrar lista de CTs
CT_LIST=$(pct list | awk 'NR>1 {print $1, $3}')
if [ -z "$CT_LIST" ]; then
whiptail --title "Error" --msgbox "No hay contenedores LXC disponibles en el sistema." 8 50
exit 1
fi
# Seleccionar CT
CTID=$(whiptail --title "Instalación de JDownloader" --menu "Selecciona el contenedor donde instalar JDownloader:" 20 60 10 $CT_LIST 3>&1 1>&2 2>&3)
if [ -z "$CTID" ]; then
whiptail --title "Cancelado" --msgbox "No se ha seleccionado ningún contenedor." 8 40
exit 1
fi
# Solicitar email
EMAIL=$(whiptail --title "Cuenta My JDownloader" --inputbox "Introduce tu correo electrónico para vincular JDownloader:" 10 60 3>&1 1>&2 2>&3)
if [ -z "$EMAIL" ]; then
whiptail --title "Error" --msgbox "No se ha introducido ningún correo." 8 40
exit 1
fi
# Solicitar contraseña
while true; do
PASSWORD=$(whiptail --title "Cuenta My JDownloader" --passwordbox "Introduce tu contraseña de My JDownloader:" 10 60 3>&1 1>&2 2>&3)
if [ -z "$PASSWORD" ]; then
whiptail --title "Error" --msgbox "No se ha introducido ninguna contraseña." 8 40
exit 1
fi
CONFIRM_PASSWORD=$(whiptail --title "Confirmación de contraseña" --passwordbox "Repite tu contraseña para confirmar:" 10 60 3>&1 1>&2 2>&3)
if [ "$PASSWORD" = "$CONFIRM_PASSWORD" ]; then
break
else
whiptail --title "Error" --msgbox "Las contraseñas no coinciden. Intenta de nuevo." 8 50
fi
done
# Confirmar datos
whiptail --title "Confirmar datos" --yesno "¿Deseas continuar con los siguientes datos?\n\nCorreo: $EMAIL\nContraseña: (establecida)\n\nEsta información se usará para vincular el contenedor con tu cuenta de My.JDownloader." 14 60
if [ $? -ne 0 ]; then
whiptail --title "Cancelado" --msgbox "Instalación cancelada por el usuario." 8 40
exit 1
fi
echo
echo "Instalando JDownloader en CT $CTID..."
echo
# Añadir repositorio alternativo para Java 8 y actualizar
pct exec "$CTID" -- wget -q http://www.mirbsd.org/~tg/Debs/sources.txt/wtf-bookworm.sources
pct exec "$CTID" -- mv wtf-bookworm.sources /etc/apt/sources.list.d/
pct exec "$CTID" -- apt update -y
pct exec "$CTID" -- apt install -y openjdk-8-jdk wget
# Crear carpeta y descargar JDownloader
pct exec "$CTID" -- mkdir -p /root/jdownloader
pct exec "$CTID" -- bash -c "cd /root/jdownloader && wget -q http://installer.jdownloader.org/JDownloader.jar"
# Crear archivo de configuración JSON para My JDownloader
pct exec "$CTID" -- bash -c "mkdir -p /root/jdownloader/cfg && cat > /root/jdownloader/cfg/org.jdownloader.api.myjdownloader.MyJDownloaderSettings.json" <<EOF
{
"email" : "$EMAIL",
"password" : "$PASSWORD",
"enabled" : true
}
EOF
# Crear servicio systemd
pct exec "$CTID" -- bash -c "cat > /etc/systemd/system/jdownloader.service <<EOF
[Unit]
Description=JDownloader Headless
After=network.target
[Service]
Type=simple
WorkingDirectory=/root/jdownloader
ExecStart=/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java -jar JDownloader.jar -norestart
Restart=always
User=root
[Install]
WantedBy=multi-user.target
EOF"
# Activar y arrancar servicio
pct exec "$CTID" -- systemctl daemon-reexec
pct exec "$CTID" -- systemctl daemon-reload
pct exec "$CTID" -- systemctl enable jdownloader
pct exec "$CTID" -- systemctl start jdownloader
echo -e "\n\033[1;32m✅ JDownloader se ha instalado y está funcionando como servicio en el CT $CTID.\033[0m"
echo -e "\nPuedes acceder a \033[1;34mhttps://my.jdownloader.org\033[0m con tu cuenta para gestionarlo.\n"

View File

@@ -0,0 +1,284 @@
#!/bin/bash
# ==========================================================
# ProxMenux - Manual LXC Conversion Guide
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 19/08/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
# ==========================================================
show_command() {
local step="$1"
local description="$2"
local command="$3"
local note="$4"
local command_extra="$5"
echo -e "${BGN}${step}.${CL} ${BL}${description}${CL}"
echo ""
echo -e "${TAB}${command}"
echo -e
[[ -n "$note" ]] && echo -e "${TAB}${DARK_GRAY}${note}${CL}"
[[ -n "$command_extra" ]] && echo -e "${TAB}${YW}${command_extra}${CL}"
echo ""
}
show_privileged_to_unprivileged_guide() {
clear
show_proxmenux_logo
msg_title "$(translate "Manual Guide: Convert LXC Privileged to Unprivileged")"
echo -e "${TAB}${BL}------------------------------------------------------------------------${CL}"
echo -e
echo -e "${TAB}${BGN}$(translate "Source:")${CL} ${BL}https://forum.proxmox.com/threads/converting-between-privileged-and-unprivileged-containers.97243/${CL}"
echo -e
echo -e
echo -e "${TAB}${BOLD}$(translate "IMPORTANT PREREQUISITES:")${CL}"
echo -e
echo -e "${TAB}${BGN}$(translate "Container must be stopped before conversion")${CL}"
echo -e "${TAB}${BGN}$(translate "Create a backup of your container before proceeding")${CL}"
echo -e "${TAB}${BGN}$(translate "This process changes file ownership inside the container")${CL}"
echo -e "${TAB}${BGN}$(translate "Process may take several minutes depending on container size")${CL}"
echo -e "${TAB}${BGN}$(translate "Works with LVM, ZFS, and BTRFS storage types")${CL}"
echo -e
echo -e "${TAB}${BL}------------------------------------------------------------------------${CL}"
echo -e
show_command "1" \
"$(translate "List all containers to identify the privileged one:")" \
"pct list" \
"$(translate "Look for containers without 'unprivileged: 1' in their config")"
show_command "2" \
"$(translate "Stop the container if it's running:")" \
"pct stop <container-id>" \
"$(translate "Replace <container-id> with your actual container ID")" \
"$(translate "Example: pct stop 114")"
show_command "3" \
"$(translate "Create a backup of the container configuration:")" \
"cp /etc/pve/lxc/<container-id>.conf /etc/pve/lxc/<container-id>.conf.bak" \
"$(translate "This creates a backup in case you need to revert changes")" \
"$(translate "Example: cp /etc/pve/lxc/114.conf /etc/pve/lxc/114.conf.bak")"
show_command "4" \
"$(translate "Get the container's storage information:")" \
"grep '^rootfs:' /etc/pve/lxc/<container-id>.conf" \
"$(translate "This shows the storage type and disk identifier")" \
"$(translate "Example output: rootfs: local-lvm:vm-114-disk-0,size=8G")"
show_command "5" \
"$(translate "Get the actual disk path:")" \
"pvesm path <storage-identifier>" \
"$(translate "Replace <storage-identifier> with the value from step 4")" \
"$(translate "Example: pvesm path local-lvm:vm-114-disk-0")"
echo -e "${TAB}${BOLD}$(translate "STEP 6: Choose commands based on your storage type")${CL}"
echo -e
echo -e "${TAB}${BGN}$(translate "If pvesm path returned a DIRECTORY (ZFS/BTRFS):")${CL}"
echo -e "${TAB}${YW}$(translate "Example: /rpool/data/subvol-114-disk-0")${CL}"
echo -e
show_command "6a" \
"$(translate "For ZFS/BTRFS - Set the mount path:")" \
"MOUNT_PATH=\"/rpool/data/subvol-<container-id>-disk-0\"" \
"$(translate "Replace with your actual path from step 5")" \
"$(translate "Example: MOUNT_PATH=\"/rpool/data/subvol-114-disk-0\"")"
echo -e "${TAB}${BGN}$(translate "If pvesm path returned a DEVICE (LVM):")${CL}"
echo -e "${TAB}${YW}$(translate "Example: /dev/pve/vm-114-disk-0")${CL}"
echo -e
show_command "6b" \
"$(translate "For LVM - Create mount directory and mount:")" \
"mkdir -p /tmp/lxc_convert_<container-id>\nmount -o loop /dev/path/to/disk /tmp/lxc_convert_<container-id>\nMOUNT_PATH=\"/tmp/lxc_convert_<container-id>\"" \
"$(translate "Replace paths with your actual values from step 5")" \
"$(translate "Example: mkdir -p /tmp/lxc_convert_114")"
show_command "7" \
"$(translate "Convert file ownership (this takes time):")" \
"find \"\$MOUNT_PATH\" -type f | while read file; do\n if [ -e \"\$file\" ]; then\n CURRENT_UID=\$(stat -c '%u' \"\$file\")\n CURRENT_GID=\$(stat -c '%g' \"\$file\")\n NEW_UID=\$((100000 + CURRENT_UID))\n NEW_GID=\$((100000 + CURRENT_GID))\n chown \"\$NEW_UID:\$NEW_GID\" \"\$file\"\n fi\ndone" \
"$(translate "This converts all file UIDs/GIDs by adding 100000")" \
"$(translate "Process may take several minutes for large containers")"
show_command "8" \
"$(translate "Convert directory ownership:")" \
"find \"\$MOUNT_PATH\" -type d | while read dir; do\n if [ -e \"\$dir\" ]; then\n CURRENT_UID=\$(stat -c '%u' \"\$dir\")\n CURRENT_GID=\$(stat -c '%g' \"\$dir\")\n NEW_UID=\$((100000 + CURRENT_UID))\n NEW_GID=\$((100000 + CURRENT_GID))\n chown \"\$NEW_UID:\$NEW_GID\" \"\$dir\"\n fi\ndone" \
"$(translate "This converts all directory UIDs/GIDs by adding 100000")"
echo -e "${TAB}${BOLD}$(translate "STEP 9: Cleanup (LVM only)")${CL}"
echo -e "${TAB}${YW}$(translate "Only run this if you used LVM (step 6b):")${CL}"
echo -e
show_command "9" \
"$(translate "Unmount and cleanup (LVM only):")" \
"umount /tmp/lxc_convert_<container-id>\nrmdir /tmp/lxc_convert_<container-id>" \
"$(translate "Only needed if you mounted the filesystem in step 6b")" \
"$(translate "Skip this step for ZFS/BTRFS")"
show_command "10" \
"$(translate "Add unprivileged flag to container configuration:")" \
"echo 'unprivileged: 1' >> /etc/pve/lxc/<container-id>.conf" \
"$(translate "This marks the container as unprivileged")"
show_command "11" \
"$(translate "Start the converted container:")" \
"pct start <container-id>" \
"$(translate "The container should now start as unprivileged")"
show_command "12" \
"$(translate "Verify the conversion:")" \
"pct config <container-id> | grep unprivileged" \
"$(translate "Should show 'unprivileged: 1'")"
echo -e "${TAB}${BL}------------------------------------------------------------------------${CL}"
echo -e
echo -e "${TAB}${BOLD}$(translate "STORAGE TYPE IDENTIFICATION:")${CL}"
echo -e
echo -e "${TAB}${BGN}$(translate "LVM:")${CL} ${YW}pvesm path returns /dev/xxx (block device)${CL}"
echo -e "${TAB}${BGN}$(translate "ZFS:")${CL} ${YW}pvesm path returns /rpool/xxx (directory)${CL}"
echo -e "${TAB}${BGN}$(translate "BTRFS:")${CL} ${YW}pvesm path returns directory path${CL}"
echo -e
echo -e "${TAB}${BOLD}$(translate "TROUBLESHOOTING:")${CL}"
echo -e
echo -e "${TAB}${BGN}$(translate "If mount fails (LVM):")${CL} ${YW}Check that the container is stopped and disk path is correct${CL}"
echo -e "${TAB}${BGN}$(translate "If path not accessible (ZFS/BTRFS):")${CL} ${YW}Verify the dataset/subvolume exists and is mounted${CL}"
echo -e "${TAB}${BGN}$(translate "If container won't start:")${CL} ${YW}Check /var/log/pve/tasks/ for detailed error messages${CL}"
echo -e "${TAB}${BGN}$(translate "To revert changes:")${CL} ${YW}cp /etc/pve/lxc/<container-id>.conf.bak /etc/pve/lxc/<container-id>.conf${CL}"
echo -e
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
echo -e
read -r
}
show_unprivileged_to_privileged_guide() {
clear
show_proxmenux_logo
msg_title "$(translate "Manual Guide: Convert LXC Unprivileged to Privileged")"
echo -e "${TAB}${BL}------------------------------------------------------------------------${CL}"
echo -e
echo -e "${TAB}${RD}$(translate "SECURITY WARNING:")${CL} ${YW}$(translate "Privileged containers have full root access to the host system!")${CL}"
echo -e "${TAB}${YW}$(translate "Only convert to privileged if absolutely necessary for your use case.")${CL}"
echo -e
echo -e
echo -e "${TAB}${BOLD}$(translate "IMPORTANT PREREQUISITES:")${CL}"
echo -e
echo -e "${TAB}${BGN}$(translate "Container must be stopped before conversion")${CL}"
echo -e "${TAB}${BGN}$(translate "Create a backup of your container before proceeding")${CL}"
echo -e "${TAB}${BGN}$(translate "Understand the security implications of privileged containers")${CL}"
echo -e "${TAB}${BGN}$(translate "This is a simple configuration change")${CL}"
echo -e
echo -e "${TAB}${BL}------------------------------------------------------------------------${CL}"
echo -e
show_command "1" \
"$(translate "List all containers to identify the unprivileged one:")" \
"pct list" \
"$(translate "Look for containers with 'unprivileged: 1' in their config")"
show_command "2" \
"$(translate "Check if container is unprivileged:")" \
"pct config <container-id> | grep unprivileged" \
"$(translate "Should show 'unprivileged: 1' if it's unprivileged")" \
"$(translate "Example: pct config 110 | grep unprivileged")"
show_command "3" \
"$(translate "Stop the container if it's running:")" \
"pct stop <container-id>" \
"$(translate "Replace <container-id> with your actual container ID")" \
"$(translate "Example: pct stop 110")"
show_command "4" \
"$(translate "Create a backup of the container configuration:")" \
"cp /etc/pve/lxc/<container-id>.conf /etc/pve/lxc/<container-id>.conf.bak" \
"$(translate "This creates a backup in case you need to revert changes")" \
"$(translate "Example: cp /etc/pve/lxc/110.conf /etc/pve/lxc/110.conf.bak")"
show_command "5" \
"$(translate "Remove the unprivileged flag from configuration:")" \
"sed -i '/^unprivileged: 1/d' /etc/pve/lxc/<container-id>.conf" \
"$(translate "This removes the 'unprivileged: 1' line from the config")" \
"$(translate "Example: sed -i '/^unprivileged: 1/d' /etc/pve/lxc/110.conf")"
show_command "6" \
"$(translate "Add explicit privileged flag (optional but recommended):")" \
"echo 'unprivileged: 0' >> /etc/pve/lxc/<container-id>.conf" \
"$(translate "This explicitly marks the container as privileged")"
show_command "7" \
"$(translate "Start the converted container:")" \
"pct start <container-id>" \
"$(translate "The container should now start as privileged")"
show_command "8" \
"$(translate "Verify the conversion:")" \
"pct config <container-id> | grep unprivileged" \
"$(translate "Should show 'unprivileged: 0' or no unprivileged line")"
echo -e "${TAB}${BL}------------------------------------------------------------------------${CL}"
echo -e
echo -e
echo -e "${TAB}${BOLD}$(translate "SECURITY CONSIDERATIONS:")${CL}"
echo -e
echo -e "${TAB}${RD}$(translate "Privileged containers can access host devices directly")${CL}"
echo -e "${TAB}${RD}$(translate "Root inside container = root on host system")${CL}"
echo -e "${TAB}${RD}$(translate "Use only when unprivileged containers cannot meet your needs")${CL}"
echo -e "${TAB}${RD}$(translate "Consider security implications for production environments")${CL}"
echo -e
echo -e
echo -e "${TAB}${BOLD}$(translate "TROUBLESHOOTING:")${CL}"
echo -e
echo -e "${TAB}${BGN}$(translate "If container won't start:")${CL} ${YW}Check /var/log/pve/tasks/ for detailed error messages${CL}"
echo -e "${TAB}${BGN}$(translate "To revert changes:")${CL} ${YW}cp /etc/pve/lxc/<container-id>.conf.bak /etc/pve/lxc/<container-id>.conf${CL}"
echo -e "${TAB}${BGN}$(translate "If config issues occur:")${CL} ${YW}Manually edit /etc/pve/lxc/<container-id>.conf${CL}"
echo -e
echo -e
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
echo -e
read -r
}
show_lxc_conversion_manual_menu() {
while true; do
CHOICE=$(dialog --title "$(translate "LXC Conversion Manual Guides")" \
--menu "$(translate "Select conversion guide:")" 18 70 10 \
"1" "$(translate "Convert Privileged to Unprivileged")" \
"2" "$(translate "Convert Unprivileged to Privileged")" \
"3" "$(translate "Return to Main Menu")" \
3>&1 1>&2 2>&3)
case $CHOICE in
1) show_privileged_to_unprivileged_guide ;;
2) show_unprivileged_to_privileged_guide ;;
3) return ;;
*) return ;;
esac
done
}
# Main execution
show_lxc_conversion_manual_menu

View File

@@ -0,0 +1,271 @@
#!/bin/bash
# ==========================================================
# ProxMenu - LXC Privileged to Unprivileged Converter
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.1
# Last Updated: 19/08/2025
# ==========================================================
# Description:
# This script converts a privileged LXC container to an unprivileged one
# using the direct conversion method (mount and chown).
# ==========================================================
# 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
# ==========================================================
select_privileged_container() {
CONTAINERS=$(pct list | awk 'NR>1 {print $1, $3}' | while read id name; do
if pct config "$id" | grep -q "^unprivileged: 0" || ! pct config "$id" | grep -q "^unprivileged:"; then
echo "$id" "$name"
fi
done | xargs -n2)
if [ -z "$CONTAINERS" ]; then
msg_error "$(translate 'No privileged containers available in Proxmox.')"
exit 1
fi
cleanup
CONTAINER_ID=$(whiptail --title "$(translate 'Select Privileged Container')" \
--menu "$(translate 'Select the privileged LXC container to convert:')" 20 70 10 $CONTAINERS 3>&1 1>&2 2>&3)
if [ -z "$CONTAINER_ID" ]; then
msg_error "$(translate 'No container selected. Exiting.')"
exit 1
fi
msg_ok "$(translate 'Privileged container selected:') $CONTAINER_ID"
}
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
if pct config "$CONTAINER_ID" | grep -q "^unprivileged: 1"; then
msg_error "$(translate 'Container') $CONTAINER_ID $(translate 'is already unprivileged.')"
exit 1
fi
if pct status "$CONTAINER_ID" | grep -q "running"; then
msg_info "$(translate 'Stopping the container before conversion...')"
pct stop "$CONTAINER_ID"
msg_ok "$(translate 'Container stopped.')"
fi
}
show_backup_warning() {
local message="$(translate 'It is strongly recommended to create a backup of your container before proceeding with the conversion.')"
message="$message\n\n$(translate 'Do you want to continue with the conversion now, or exit to create a backup first?')"
message="$message\n\n$(translate 'Continue: Proceed with conversion')"
message="$message\n$(translate 'Exit: Stop to create backup manually')"
if whiptail --title "$(translate 'Backup Recommendation')" \
--yes-button "$(translate 'Continue')" \
--no-button "$(translate 'Exit')" \
--yesno "$message" 18 80; then
return 0
else
msg_info2 "$(translate 'User chose to exit for manual backup creation.')"
exit 0
fi
}
convert_direct_method() {
msg_info2 "$(translate 'Starting direct conversion of container') $CONTAINER_ID..."
TEMP_DIR="/tmp/lxc_convert_$CONTAINER_ID"
mkdir -p "$TEMP_DIR"
ROOTFS_CONFIG=$(pct config "$CONTAINER_ID" | grep "^rootfs:")
if [ -z "$ROOTFS_CONFIG" ]; then
msg_error "$(translate 'Could not find rootfs configuration for container.')"
exit 1
fi
STORAGE_DISK=$(echo "$ROOTFS_CONFIG" | awk '{print $2}' | cut -d, -f1)
msg_ok "$(translate 'Storage disk identifier:') $STORAGE_DISK"
DISK_PATH=$(pvesm path "$STORAGE_DISK" 2>/dev/null)
if [ -n "$DISK_PATH" ] && [ -e "$DISK_PATH" ]; then
msg_ok "$(translate 'Disk path resolved via pvesm:') $DISK_PATH"
else
STORAGE_NAME=$(echo "$STORAGE_DISK" | cut -d: -f1)
DISK_NAME=$(echo "$STORAGE_DISK" | cut -d: -f2)
msg_info2 "$(translate 'pvesm path failed, trying manual detection...')"
msg_info2 "$(translate 'Storage:') $STORAGE_NAME, $(translate 'Disk:') $DISK_NAME"
for vg in pve $(vgs --noheadings -o vg_name 2>/dev/null | tr -d ' '); do
if [ -e "/dev/$vg/$DISK_NAME" ]; then
DISK_PATH="/dev/$vg/$DISK_NAME"
break
fi
done
if [ -z "$DISK_PATH" ] || [ ! -e "$DISK_PATH" ]; then
ZFS_PATH="/dev/zvol/$STORAGE_NAME/$DISK_NAME"
if [ -e "$ZFS_PATH" ]; then
DISK_PATH="$ZFS_PATH"
fi
fi
fi
if [ -z "$DISK_PATH" ] || [ ! -e "$DISK_PATH" ]; then
msg_error "$(translate 'Could not determine disk path for:') $STORAGE_DISK"
msg_error "$(translate 'Tried pvesm path and manual detection methods')"
msg_info2 "$(translate 'Available storage information:')"
pvesm status 2>/dev/null || msg_error "$(translate 'pvesm status failed')"
rmdir "$TEMP_DIR"
exit 1
fi
msg_ok "$(translate 'Mounting container filesystem')"
if ! mount "$DISK_PATH" "$TEMP_DIR" 2>/dev/null; then
if ! mount -o loop "$DISK_PATH" "$TEMP_DIR" 2>/dev/null; then
msg_error "$(translate 'Failed to mount container filesystem.')"
msg_error "$(translate 'Disk path:') $DISK_PATH"
msg_success "$(translate "Press Enter to return")"
read -r
rmdir "$TEMP_DIR"
exit 1
fi
fi
msg_info "$(translate 'Converting file ownership (this may take several minutes)...')"
find "$TEMP_DIR" -type f -print0 | while IFS= read -r -d '' S; do
if [ ! -e "$S" ] || [ ! -r "$S" ]; then
continue
fi
if STAT_OUTPUT=$(stat -c "%u %g" "$S" 2>/dev/null); then
U=$(echo "$STAT_OUTPUT" | awk '{print $1}')
G=$(echo "$STAT_OUTPUT" | awk '{print $2}')
F=100000
NEW_UID=$((F + U))
NEW_GID=$((F + G))
if ! chown "$NEW_UID:$NEW_GID" "$S" 2>/dev/null; then
continue
fi
fi
done
find "$TEMP_DIR" -type d -print0 | while IFS= read -r -d '' S; do
if [ ! -e "$S" ] || [ ! -r "$S" ]; then
continue
fi
if STAT_OUTPUT=$(stat -c "%u %g" "$S" 2>/dev/null); then
U=$(echo "$STAT_OUTPUT" | awk '{print $1}')
G=$(echo "$STAT_OUTPUT" | awk '{print $2}')
F=100000
NEW_UID=$((F + U))
NEW_GID=$((F + G))
if ! chown "$NEW_UID:$NEW_GID" "$S" 2>/dev/null; then
continue
fi
fi
done
[ -e "$TEMP_DIR/var/spool/postfix/dev/-random" ] && rm -f "$TEMP_DIR/var/spool/postfix/dev/-random"
[ -e "$TEMP_DIR/var/spool/postfix/dev/-urandom" ] && rm -f "$TEMP_DIR/var/spool/postfix/dev/-urandom"
[ -e "$TEMP_DIR/usr/bin/sudo" ] && chmod u+s "$TEMP_DIR/usr/bin/sudo"
umount "$TEMP_DIR"
rmdir "$TEMP_DIR"
CONFIG_FILE="/etc/pve/lxc/$CONTAINER_ID.conf"
if ! grep -q "^unprivileged:" "$CONFIG_FILE"; then
echo "unprivileged: 1" >> "$CONFIG_FILE"
else
sed -i 's/^unprivileged:.*/unprivileged: 1/' "$CONFIG_FILE"
fi
msg_ok "$(translate 'Direct conversion completed for container') $CONTAINER_ID"
echo -e
msg_success "Press Enter to continue..."
read -r
}
cleanup_and_finalize() {
if whiptail --yesno "$(translate 'Do you want to start the converted unprivileged container') $CONTAINER_ID $(translate 'now?')" 10 60; then
msg_info2 "$(translate 'Starting unprivileged container...')"
pct start "$CONTAINER_ID"
msg_ok "$(translate 'Unprivileged container') $CONTAINER_ID $(translate 'started successfully.')"
fi
}
main() {
show_proxmenux_logo
msg_title "$(translate "LXC Privileged to Unprivileged conversion")"
msg_info "$(translate 'Starting LXC Privileged to Unprivileged conversion process...')"
select_privileged_container
validate_container_id
show_backup_warning
convert_direct_method
cleanup_and_finalize
msg_ok "$(translate 'Converted container ID:') $CONTAINER_ID"
msg_ok "$(translate 'LXC conversion from privileged to unprivileged completed successfully!')"
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
read -r
exit 0
}
main

View File

@@ -0,0 +1,144 @@
#!/bin/bash
# ==========================================================
# ProxMenu - LXC Unprivileged to Privileged Converter
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 2.0
# Last Updated: 19/08/2025
# ==========================================================
# Description:
# This script converts an unprivileged LXC container to a privileged one
# by directly modifying the configuration file.
# WARNING: This reduces security. Use only when necessary.
# ==========================================================
# 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
# ==========================================================
select_unprivileged_container() {
CONTAINERS=$(pct list | awk 'NR>1 {print $1, $3}' | while read id name; do
if pct config "$id" | grep -q "^unprivileged: 1"; then
echo "$id" "$name"
fi
done | xargs -n2)
if [ -z "$CONTAINERS" ]; then
msg_error "$(translate 'No unprivileged containers available in Proxmox.')"
exit 1
fi
cleanup
CONTAINER_ID=$(whiptail --title "$(translate 'Select Unprivileged Container')" \
--menu "$(translate 'Select the unprivileged LXC container to convert:')" 20 70 10 $CONTAINERS 3>&1 1>&2 2>&3)
if [ -z "$CONTAINER_ID" ]; then
msg_error "$(translate 'No container selected. Exiting.')"
exit 1
fi
msg_ok "$(translate 'Unprivileged container selected:') $CONTAINER_ID"
}
show_backup_warning() {
if ! whiptail --title "$(translate 'Backup Recommendation')" \
--yes-button "$(translate 'Continue')" \
--no-button "$(translate 'Exit')" \
--yesno "$(translate 'It is recommended to create a backup before continuing.')" \
12 70; then
msg_info "$(translate 'Operation cancelled by user to create backup.')"
exit 0
fi
}
convert_to_privileged() {
CONF_FILE="/etc/pve/lxc/$CONTAINER_ID.conf"
CONTAINER_STATUS=$(pct status "$CONTAINER_ID" | awk '{print $2}')
if [ "$CONTAINER_STATUS" == "running" ]; then
msg_info "$(translate 'Stopping container') $CONTAINER_ID..."
pct shutdown "$CONTAINER_ID"
# Wait for container to stop
for i in {1..10}; do
sleep 1
if [ "$(pct status "$CONTAINER_ID" | awk '{print $2}')" != "running" ]; then
break
fi
done
# Verify container stopped
if [ "$(pct status "$CONTAINER_ID" | awk '{print $2}')" == "running" ]; then
msg_error "$(translate 'Failed to stop the container.')"
exit 1
fi
msg_ok "$(translate 'Container stopped.')"
else
msg_ok "$(translate 'Container is already stopped.')"
fi
msg_info "$(translate 'Creating backup of configuration file...')"
cp "$CONF_FILE" "$CONF_FILE.bak"
msg_ok "$(translate 'Configuration backup created:') $CONF_FILE.bak"
msg_info "$(translate 'Converting container to privileged...')"
sed -i '/^unprivileged: 1/d' "$CONF_FILE"
echo "unprivileged: 0" >> "$CONF_FILE"
msg_ok "$(translate 'Container successfully converted to privileged.')"
echo -e
msg_success "Press Enter to continue..."
read -r
}
finalize_conversion() {
if whiptail --yesno "$(translate 'Do you want to start the privileged container') $CONTAINER_ID $(translate 'now?')" 10 60; then
msg_info "$(translate 'Starting privileged container...')"
pct start "$CONTAINER_ID"
msg_ok "$(translate 'Privileged container') $CONTAINER_ID $(translate 'started successfully.')"
fi
}
main() {
show_proxmenux_logo
msg_title "$(translate "LXC Unprivileged to Privileged conversion")"
msg_info "$(translate 'Starting LXC Unprivileged to Privileged conversion process...')"
select_unprivileged_container
show_backup_warning
convert_to_privileged
finalize_conversion
msg_ok "$(translate 'LXC conversion from unprivileged to privileged completed successfully!')"
msg_ok "$(translate 'Converted container ID:') $CONTAINER_ID"
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
read -r
exit 0
}
# Execute main function
main

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)")" \
"es" "$(translate "Spanish")" \
"fr" "$(translate "French")" \
"de" "$(translate "German")" \
"it" "$(translate "Italian")" \
"pt" "$(translate "Portuguese")" 3>&1 1>&2 2>&3)
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
version=$(<"$LOCAL_VERSION_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,131 +199,129 @@ 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"
info_message+="\n$(translate "Virtual Environment:")\n"
if [ -d "$VENV_PATH" ] && [ -f "$VENV_PATH/bin/activate" ]; then
info_message+="$(translate "Installed")$VENV_PATH\n"
[ -f "$VENV_PATH/bin/pip" ] && info_message+="✓ pip: $(translate "Installed")$VENV_PATH/bin/pip\n" || info_message+="✗ pip: $(translate "Not installed")\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"
[ -f "$VENV_PATH/bin/pip" ] && info_message+="✓ pip: $(translate "Installed")$VENV_PATH/bin/pip\n" || info_message+="✗ pip: $(translate "Not installed")\n"
else
info_message+="$(translate "Virtual Environment"): $(translate "Not installed")\n"
info_message+="✗ pip: $(translate "Not installed")\n"
fi
current_language=$(jq -r '.language // "en"' "$CONFIG_FILE")
info_message+="\n$(translate "Current language:")\n$current_language\n"
else
info_message+="$(translate "Virtual Environment"): $(translate "Not installed")\n"
info_message+="✗ pip: $(translate "Not installed")\n"
info_message+="\n$(translate "Language:")\nEnglish (Fixed)\n"
fi
current_language=$(jq -r '.language // "en"' "$CONFIG_FILE")
info_message+="\n$(translate "Current language:")\n$current_language\n"
# 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
rm -f "$tmpfile"
fi
#show_proxmenux_logo
tmpfile=$(mktemp)
echo -e "$info_message" > "$tmpfile"
dialog --clear --backtitle "ProxMenux Configuration" \
--title "$(translate "ProxMenux Information")" \
--textbox "$tmpfile" 25 80
rm -f "$tmpfile"
}
# ==========================================================
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 \
"python3-venv" "Python virtual environment" OFF \
"python3-pip" "Python package installer" OFF \
"jq" "JSON processor" OFF \
3>&1 1>&2 2>&3)
echo "Uninstalling ProxMenu..."
# Remove googletrans if virtual environment exists
if [ -f "$VENV_PATH/bin/activate" ]; then
echo "Removing googletrans..."
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..."
# Remove quotes and process each package
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
done
# Run autoremove to clean up any leftover dependencies
echo "Cleaning up unnecessary packages..."
apt-get autoremove -y --purge >/dev/null 2>&1
fi
local deps_to_remove=""
# 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
if [ -f /etc/motd.bak ]; then
echo "$(translate "Restoring original MOTD...")"
mv /etc/motd.bak /etc/motd
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
# Remove custom MOTD line if present
sed -i '/This system is optimised by: ProxMenux/d' /etc/motd
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
# Remove ProxMenu files
rm -f "/usr/local/bin/menu"
rm -rf "$BASE_DIR"
(
echo "10" ; echo "Removing ProxMenu files..."
sleep 1
echo "ProxMenu has been uninstalled."
if [ -n "$DEPS_TO_REMOVE" ]; then
echo "The following dependencies have been removed successfully: $DEPS_TO_REMOVE"
if [ -f "$VENV_PATH/bin/activate" ]; then
echo "30" ; 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
echo "50" ; echo "Removing ProxMenu files..."
rm -f "$INSTALL_DIR/$MENU_SCRIPT"
rm -rf "$BASE_DIR"
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
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
echo "90" ; echo "Restoring system files..."
[ -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 "100" ; echo "Uninstallation complete!"
sleep 1
) | dialog --clear --backtitle "ProxMenux Configuration" \
--title "Uninstalling ProxMenux" \
--gauge "Starting uninstallation..." 10 60 0
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 \
"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)
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")" \
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

104
scripts/menus/lxc_menu.sh Normal file
View File

@@ -0,0 +1,104 @@
#!/bin/bash
# ==========================================================
# ProxMenu - LXC Conversion Management Menu
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 19/08/2025
# ==========================================================
# Description:
# This script provides a menu interface for LXC container privilege conversions.
# Allows converting between privileged and unprivileged containers safely.
# ==========================================================
# 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
# ==========================================================
show_main_menu() {
CHOICE=$(dialog --backtitle "ProxMenux" --title "$(translate 'LXC Management')" \
--menu "$(translate 'Select conversion option:')" 20 70 10 \
"1" "$(translate 'Convert Privileged to Unprivileged')" \
"2" "$(translate 'Convert Unprivileged to Privileged')" \
"3" "$(translate 'Show Container Privilege Status')" \
"4" "$(translate "Help & Info (commands)")" \
"5" "$(translate 'Exit')" 3>&1 1>&2 2>&3)
case $CHOICE in
1)
bash <(curl -s "$REPO_URL/scripts/lxc/lxc-privileged-to-unprivileged.sh")
;;
2)
bash <(curl -s "$REPO_URL/scripts/lxc/lxc-unprivileged-to-privileged.sh")
;;
3)
show_container_status
;;
4)
bash <(curl -s "$REPO_URL/scripts/lxc/lxc-conversion-manual-guide.sh")
;;
5)
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
;;
*)
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
esac
}
show_container_status() {
msg_info "$(translate 'Gathering container privilege information...')"
TEMP_FILE=$(mktemp)
echo "$(translate 'LXC Container Privilege Status')" > "$TEMP_FILE"
echo "=================================" >> "$TEMP_FILE"
echo "" >> "$TEMP_FILE"
pct list | awk 'NR>1 {print $1, $3}' | while read id name; do
if pct config "$id" | grep -q "^unprivileged: 1"; then
status="$(translate 'Unprivileged')"
else
status="$(translate 'Privileged')"
fi
running_status=$(pct status "$id" | grep -q "running" && echo "$(translate 'Running')" || echo "$(translate 'Stopped')")
printf "ID: %-4s | %-20s | %-12s | %s\n" "$id" "$name" "$status" "$running_status" >> "$TEMP_FILE"
done
echo "" >> "$TEMP_FILE"
echo "$(translate 'Legend:')" >> "$TEMP_FILE"
echo "$(translate 'Privileged: Full host access (less secure)')" >> "$TEMP_FILE"
echo "$(translate 'Unprivileged: Limited access (more secure)')" >> "$TEMP_FILE"
cleanup
dialog --title "$(translate 'Container Status')" --textbox "$TEMP_FILE" 25 80
rm -f "$TEMP_FILE"
show_main_menu
}
show_main_menu

View File

@@ -16,44 +16,104 @@ 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
# ==========================================================
if ! command -v dialog &>/dev/null; then
apt update -qq >/dev/null 2>&1
apt install -y dialog >/dev/null 2>&1
fi
check_pve9_translation_compatibility() {
local pve_version
if command -v pveversion &>/dev/null; then
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
else
return 0
fi
if [[ -n "$pve_version" ]] && [[ "$pve_version" -ge 9 ]] && [[ -d "$VENV_PATH" ]]; then
local has_googletrans=false
local has_cache=false
if [[ -f "$VENV_PATH/bin/pip" ]]; then
if "$VENV_PATH/bin/pip" list 2>/dev/null | grep -q "googletrans"; then
has_googletrans=true
fi
fi
if [[ -f "$BASE_DIR/cache.json" ]]; then
has_cache=true
fi
if [[ "$has_googletrans" = true ]] || [[ "$has_cache" = true ]]; then
dialog --clear \
--backtitle "ProxMenux - Compatibility Required" \
--title "Translation Environment Incompatible with PVE $pve_version" \
--msgbox "NOTICE: You are running Proxmox VE $pve_version with translation components installed.\n\nTranslations are NOT supported in PVE 9+. This causes:\n• Menu loading errors\n• Translation failures\n• System instability\n\nREQUIRED ACTION:\nProxMenux will now automatically reinstall the Normal Version.\n\nThis process will:\n• Remove incompatible translation components\n• Install PVE 9+ compatible version\n• Preserve all your settings and preferences\n\nPress OK to continue with automatic reinstallation..." 20 75
bash <(curl -sSL "$REPO_URL/install_proxmenux.sh")
fi
exit
fi
}
check_pve9_translation_compatibility
# ==========================================================
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ "$PROXMENUX_PVE9_WARNING_SHOWN" = "1" ]]; then
if ! load_language 2>/dev/null; then
LANGUAGE="en"
fi
else
load_language
initialize_cache
fi
# ==========================================================
show_menu() {
local TEMP_FILE
TEMP_FILE=$(mktemp)
while true; do
local menu_title="Main ProxMenux"
if [[ -n "$PROXMENUX_PVE9_WARNING_SHOWN" ]]; then
menu_title="Main ProxMenux"
fi
dialog --clear \
--backtitle "ProxMenux" \
--title "$(translate "Main ProxMenux")" \
--menu "$(translate "Select an option:")" 18 70 10 \
--title "$(translate "$menu_title")" \
--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=$?
if [[ $EXIT_STATUS -ne 0 ]]; then
# ESC pressed or Cancel
clear
msg_ok "$(translate "Thank you for using ProxMenu. Goodbye!")"
msg_ok "$(translate "Thank you for using ProxMenux. Goodbye!")"
rm -f "$TEMP_FILE"
exit 0
fi
@@ -67,14 +127,13 @@ 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
}
show_menu

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

@@ -0,0 +1,82 @@
#!/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 : 2.0
# Last Updated: 04/04/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
# ==========================================================
if ! command -v dialog &>/dev/null; then
apt update -qq >/dev/null 2>&1
apt install -y dialog >/dev/null 2>&1
fi
show_menu() {
local TEMP_FILE
TEMP_FILE=$(mktemp)
while true; do
dialog --clear \
--backtitle "ProxMenux" \
--title "$(translate "Main ProxMenux")" \
--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 "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=$?
if [[ $EXIT_STATUS -ne 0 ]]; then
# ESC pressed or Cancel
clear
msg_ok "$(translate "Thank you for using ProxMenux. Goodbye!")"
rm -f "$TEMP_FILE"
exit 0
fi
OPTION=$(<"$TEMP_FILE")
case $OPTION in
1) exec bash <(curl -s "$REPO_URL/scripts/menus/menu_post_install.sh") ;;
2) bash <(curl -s "$REPO_URL/scripts/help_info_menu.sh") ;;
3) exec bash <(curl -s "$REPO_URL/scripts/menus/hw_grafics_menu.sh") ;;
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/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
}
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,156 +31,297 @@ 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 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
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"
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
}
msg_success "$(translate "Press Enter to return to menu...")"
read -r
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\")")
# 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"
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")
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-
}
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
IFS=$'\n' sorted_scripts=($(printf "%s\n" "${scripts[@]}" | custom_sort))
unset IFS
declare -A index_to_slug
local menu_items=()
local i=1
HEADER=$(printf " %-57s %-20s" "$(translate "Name")" "$(translate "Category")")
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=()
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+=("" "")
menu_items+=("new_search" "New Search")
menu_items+=("show_all" "Show All Scripts")
menu_items+=("$(translate "Return to Main Menu")" "")
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
cleanup
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)
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 [[ $? -ne 0 ]]; then
return
fi
if [ -n "$script_selection" ]; then
script_selection=$(echo "$script_selection" | xargs)
if [ "$script_selection" = "$(translate "Return to Main Menu")" ]; then
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")
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...")"
read -r
clear
else
msg_info2 "$(translate "Script execution cancelled.")"
sleep 2
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
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=()
if [[ "$LANGUAGE" != "en" ]]; then
show_proxmenux_logo
msg_lang "$(translate "Generating automatic translations...")"
fi
show_menu
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)"
)
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
echo "${description}${spacing}${source}"
}
# ==========================================================
show_menu() {
while true; do
HEADER=$(printf " %-52s %-20s" "$(translate "Name")" "$(translate "Repository")")
local menu_items=()
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 script_commands
local counter=1
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+=("$(printf "%2d %-40s" "$((${#scripts[@]}+1))" "$(translate "Return to Main Menu")")" "")
cleanup
menu_items+=("" "")
menu_items+=("-" "───────────────────── $(translate "Community Scripts") ──────────────────────")
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}')
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
if [ "$selected_number" = "$((${#scripts[@]}+1))" ]; then
#show_proxmenux_logo
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"
fi
menu_items+=("" "")
menu_items+=("0" "$(translate "Return to Main Menu")")
else
#show_proxmenux_logo
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
if [[ "$script_selection" == "-" || "$script_selection" == "" ]]; then
continue
fi
if [[ -n "${script_commands[$script_selection]}" ]]; then
eval "${script_commands[$script_selection]}"
else
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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,88 @@
#!/bin/bash
# ==========================================================
# ProxMenux - Network Storage Manager Menu
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT
# Version : 1.2
# Last Updated: $(date +%d/%m/%Y)
# ==========================================================
# 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 --colors --backtitle "ProxMenux" \
--title "$(translate "Mount and Share Manager")" \
--menu "\n$(translate "Select an option:")" 25 80 15 \
"" "\Z4──────────────────────── $(translate "HOST") ─────────────────────────\Zn" \
"1" "$(translate "Configure NFS shared on Host")" \
"2" "$(translate "Configure Samba shared on Host")" \
"3" "$(translate "Configure Local Shared on Host")" \
"" "\Z4──────────────────────── $(translate "LXC") ─────────────────────────\Zn" \
"4" "$(translate "Configure LXC Mount Points (Host ↔ Container)")" \
"" "" \
"5" "$(translate "Configure NFS Client in LXC (only privileged)")" \
"6" "$(translate "Configure Samba Client in LXC (only privileged)")" \
"7" "$(translate "Configure NFS Server in LXC (only privileged)")" \
"8" "$(translate "configure Samba Server in LXC (only privileged)")" \
"" "" \
"h" "$(translate "Help & Info (commands)")" \
"0" "$(translate "Return to Main Menu")" \
2>&1 >/dev/tty
) || { exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh"); }
case "$OPTION" in
lxctitle|hosttitle)
continue
;;
1)
bash <(curl -s "$REPO_URL/scripts/share/nfs_host.sh")
;;
2)
bash <(curl -s "$REPO_URL/scripts/share/samba_host.sh")
;;
3)
bash <(curl -s "$REPO_URL/scripts/share/local-shared-manager.sh")
;;
4)
bash <(curl -s "$REPO_URL/scripts/share/lxc-mount-manager_minimal.sh")
;;
5)
bash <(curl -s "$REPO_URL/scripts/share/nfs_client.sh")
;;
6)
bash <(curl -s "$REPO_URL/scripts/share/samba_client.sh")
;;
7)
bash <(curl -s "$REPO_URL/scripts/share/nfs_lxc_server.sh")
;;
8)
bash <(curl -s "$REPO_URL/scripts/share/samba_lxc_server.sh")
;;
h)
bash <(curl -s "$REPO_URL/scripts/share/commands_share.sh")
;;
0)
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

@@ -26,36 +26,29 @@ 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")" \
"3" "$(translate "Import Disk Image to a VM")" \
"4" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
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")" \
2>&1 >/dev/tty)
case $OPTION in
1)
#show_proxmenux_logo
msg_info2 "$(translate "Running script: Add Disk Passthrough to a VM")..."
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")..."
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")..."
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,82 @@
#!/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 "$(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 "Upgrade PVE 8 to PVE 9")" \
"5" "$(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)
bash <(curl -s "$REPO_URL/scripts/utilities/upgrade_pve8_to_pve9.sh")
if [ $? -ne 0 ]; then
return
fi
;;
5) 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,896 @@
#!/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"
}
check_extremeshok_warning() {
local marker_file="/etc/extremeshok"
if [[ -f "$marker_file" ]]; then
dialog --backtitle "ProxMenux" --title "xshok-proxmox Post-Install Detected" \
--yesno "\n$(translate "It appears that you have already executed the xshok-proxmox post-install script on this system.")\n\n\
$(translate "If you continue, some adjustments may be duplicated or conflict with those already made by xshok.")\n\n\
$(translate "Do you want to continue anyway?")" 13 70
local response=$?
if [[ $response -ne 0 ]]; then
show_proxmenux_logo
msg_warn "$(translate "Action cancelled due to previous xshok-proxmox modifications.")"
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
read -r
exit 1
fi
fi
}
# ==========================================================
apt_upgrade() {
local pve_version
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
if [[ -z "$pve_version" ]]; then
msg_error "Unable to detect Proxmox version."
return 1
fi
if [[ "$pve_version" -ge 9 ]]; then
bash <(curl -fsSL "$REPO_URL/scripts/global/update-pve.sh")
else
bash <(curl -fsSL "$REPO_URL/scripts/global/update-pve8.sh")
fi
}
# ==========================================================
remove_subscription_banner() {
local pve_version
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
if [[ -z "$pve_version" ]]; then
msg_error "Unable to detect Proxmox version."
return 1
fi
if [[ "$pve_version" -ge 9 ]]; then
if ! whiptail --title "Proxmox VE 9.x Subscription Banner Removal" \
--yesno "Do you want to remove the Proxmox subscription banner from the web interface for PVE $pve_version?" 10 70; then
msg_warn "Banner removal cancelled by user."
return 1
fi
bash <(curl -fsSL "$REPO_URL/scripts/global/remove-banner-pve9.sh")
else
if ! whiptail --title "Proxmox VE 8.x Subscription Banner Removal" \
--yesno "Do you want to remove the Proxmox subscription banner from the web interface for PVE $pve_version?" 10 70; then
msg_warn "Banner removal cancelled by user."
return 1
fi
bash <(curl -fsSL "$REPO_URL/scripts/global/remove-banner-pve8.sh")
fi
}
# ==========================================================
configure_time_sync_() {
msg_info2 "$(translate "Configuring system time settings...")"
# Get public IP address
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
# Get timezone based on IP
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
# Set the timezone
if timedatectl set-timezone "$timezone"; then
msg_ok "$(translate "Timezone set to $timezone")"
else
msg_error "$(translate "Failed to set timezone to $timezone")"
fi
# Configure time synchronization
msg_info "$(translate "Enabling automatic time synchronization...")"
if timedatectl set-ntp true; then
systemctl restart postfix 2>/dev/null || true
msg_ok "$(translate "Automatic time synchronization enabled")"
register_tool "time_sync" true
else
msg_error "$(translate "Failed to enable automatic time synchronization")"
fi
}
configure_time_sync() {
msg_info2 "$(translate "Configuring system time settings...")"
this_ip=$(dig +short myip.opendns.com @resolver1.opendns.com 2>/dev/null)
if [ -z "$this_ip" ]; then
msg_warn "$(translate "Failed to obtain public IP address - keeping current timezone settings")"
return 0
fi
timezone=$(curl -s --connect-timeout 10 "https://ipapi.co/${this_ip}/timezone" 2>/dev/null)
if [ -z "$timezone" ] || [ "$timezone" = "undefined" ]; then
msg_warn "$(translate "Failed to determine timezone from IP address - keeping current timezone settings")"
return 0
fi
msg_ok "$(translate "Found timezone $timezone for IP $this_ip")"
if timedatectl set-timezone "$timezone"; then
msg_ok "$(translate "Timezone set to $timezone")"
if timedatectl set-ntp true; then
msg_ok "$(translate "Time settings configured - Timezone:") $timezone"
register_tool "time_sync" true
systemctl restart postfix 2>/dev/null || true
else
msg_warn "$(translate "Failed to enable automatic time synchronization")"
fi
else
msg_warn "$(translate "Failed to set timezone - keeping current settings")"
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
RateLimitIntervalSec=30s
RateLimitBurst=1000
ForwardToSyslog=no
ForwardToWall=no
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
# ==========================================================
# ProxMenux - Network tuning (PVE 9 compatible)
# ==========================================================
# Core buffers & queues
net.core.netdev_max_backlog = 8192
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.somaxconn = 8192
# IPv4
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.conf.default.secure_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.default.log_martians = 1
# rp_filter: loose multi-homed/bridges
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2
# ICMP
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
# TCP/IP
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_mtu_probing = 1
net.ipv4.tcp_rfc1337 = 1
net.ipv4.tcp_sack = 1
net.ipv4.tcp_rmem = 8192 87380 16777216
net.ipv4.tcp_wmem = 8192 65536 16777216
# Unix sockets
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
}
customize_bashrc() {
msg_info "$(translate "Customizing bashrc for root user...")"
local bashrc="/root/.bashrc"
local bash_profile="/root/.bash_profile"
local marker_begin="# BEGIN PMX_CORE_BASHRC"
local marker_end="# END PMX_CORE_BASHRC"
[ -f "${bashrc}.bak" ] || cp "$bashrc" "${bashrc}.bak" > /dev/null 2>&1
if grep -q "^${marker_begin}$" "$bashrc" 2>/dev/null; then
sed -i "/^${marker_begin}$/,/^${marker_end}$/d" "$bashrc"
fi
cat >> "$bashrc" << 'EOF'
${marker_begin}
# ProxMenux core 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
${marker_end}
EOF
if ! grep -q "source /root/.bashrc" "$bash_profile" 2>/dev/null; then
echo "source /root/.bashrc" >> "$bash_profile" 2>/dev/null
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...")"
local is_ssd=false
local pool disks disk byid_path dev rot
if grep -qE '^root=ZFS=' /etc/kernel/cmdline 2>/dev/null || mount | grep -q 'on / type zfs'; then
pool=$(zfs list -Ho name,mountpoint 2>/dev/null | awk '$2=="/"{print $1}' | cut -d/ -f1)
disks=$(zpool status "$pool" 2>/dev/null | awk '/ONLINE/ && $1 !~ /:|mirror|raidz|log|spare|config|NAME|rpool|state/ {print $1}' | sort -u)
is_ssd=true
for disk in $disks; do
byid_path=$(readlink -f /dev/disk/by-id/*$disk* 2>/dev/null) || continue
dev=$(basename "$byid_path" | sed -E 's|[0-9]+$||' | sed -E 's|p$||')
rot=$(cat /sys/block/$dev/queue/rotational 2>/dev/null)
[[ "$rot" != "0" ]] && is_ssd=false && break
done
else
ROOT_PART=$(lsblk -no NAME,MOUNTPOINT | grep ' /$' | awk '{print $1}')
#SYSTEM_DISK=$(lsblk -no PKNAME /dev/$ROOT_PART 2>/dev/null)
SYSTEM_DISK=$(lsblk -no PKNAME /dev/$ROOT_PART 2>/dev/null | grep -E '^[a-z]+' | head -n1)
SYSTEM_DISK=${SYSTEM_DISK:-sda}
if [[ "$SYSTEM_DISK" == nvme* || "$(cat /sys/block/$SYSTEM_DISK/queue/rotational 2>/dev/null)" == "0" ]]; then
is_ssd=true
fi
fi
if [[ "$is_ssd" == true ]]; then
msg_ok "$(translate "System disk is SSD or M.2. Proceeding with Log2RAM setup.")"
else
if whiptail --yesno "$(translate "Do you want to install Log2RAM anyway to reduce log write load?")" \
10 70 --title "Log2RAM"; then
:
else
return 0
fi
fi
if [[ -f /etc/log2ram.conf ]] && command -v log2ram >/dev/null 2>&1 && systemctl list-units --all | grep -q log2ram; then
msg_ok "$(translate "Log2RAM is already installed and configured correctly.")"
register_tool "log2ram" true
return 0
fi
msg_info "$(translate "Log2RAM proceeding with installation...")"
if [[ -d /tmp/log2ram ]]; then
rm -rf /tmp/log2ram
fi
[[ -f /etc/systemd/system/log2ram.service ]] && rm -f /etc/systemd/system/log2ram*
[[ -f /etc/systemd/system/log2ram-daily.service ]] && rm -f /etc/systemd/system/log2ram-daily.*
[[ -f /etc/cron.d/log2ram ]] && rm -f /etc/cron.d/log2ram*
[[ -f /usr/sbin/log2ram ]] && rm -f /usr/sbin/log2ram
[[ -f /etc/log2ram.conf ]] && rm -f /etc/log2ram.conf
[[ -f /usr/local/bin/log2ram-check.sh ]] && rm -f /usr/local/bin/log2ram-check.sh
[[ -d /var/log.hdd ]] && rm -rf /var/log.hdd
systemctl daemon-reexec >/dev/null 2>&1 || true
systemctl daemon-reload >/dev/null 2>&1 || true
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
#msg_ok "$(translate "Git installed successfully")"
fi
if ! git clone https://github.com/azlux/log2ram.git /tmp/log2ram >/dev/null 2>>/tmp/log2ram_install.log; then
msg_error "$(translate "Failed to clone log2ram repository. Check /tmp/log2ram_install.log")"
return 1
fi
cd /tmp/log2ram || {
msg_error "$(translate "Failed to access log2ram directory")"
return 1
}
if ! bash install.sh >>/tmp/log2ram_install.log 2>&1; then
msg_error "$(translate "Failed to run log2ram installer. Check /tmp/log2ram_install.log")"
return 1
fi
if [[ -f /etc/log2ram.conf ]] && command -v log2ram >/dev/null 2>&1; then
msg_ok "$(translate "Log2RAM installed successfully")"
else
msg_error "$(translate "Log2RAM installation verification failed. Check /tmp/log2ram_install.log")"
return 1
fi
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
LOG2RAM_BIN="$(command -v log2ram || echo /usr/local/bin/log2ram)"
rm -f /etc/cron.daily/log2ram /etc/cron.weekly/log2ram /etc/cron.monthly/log2ram 2>/dev/null || true
rm -f /etc/cron.hourly/log2ram
{
echo 'MAILTO=""'
echo "0 */$CRON_HOURS * * * root $LOG2RAM_BIN write >/dev/null 2>&1"
} > /etc/cron.d/log2ram
chmod 0644 /etc/cron.d/log2ram
chown root:root /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
$(command -v log2ram) write
fi
EOF
chmod +x /usr/local/bin/log2ram-check.sh
{
echo 'MAILTO=""'
echo "*/5 * * * * root /usr/local/bin/log2ram-check.sh >/dev/null 2>&1"
} > /etc/cron.d/log2ram-auto-sync
chmod 0644 /etc/cron.d/log2ram-auto-sync
chown root:root /etc/cron.d/log2ram-auto-sync
msg_ok "$(translate "Auto-sync enabled when /var/log exceeds 90% of") $LOG2RAM_SIZE"
register_tool "log2ram" true
}
# ==========================================================
setup_persistent_network() {
local LINK_DIR="/etc/systemd/network"
local BACKUP_DIR="/etc/systemd/network/backup-$(date +%Y%m%d-%H%M%S)"
msg_info "$(translate "Setting up persistent network interfaces")"
sleep 2
mkdir -p "$LINK_DIR"
if ls "$LINK_DIR"/*.link >/dev/null 2>&1; then
mkdir -p "$BACKUP_DIR"
cp "$LINK_DIR"/*.link "$BACKUP_DIR"/ 2>/dev/null || true
fi
local count=0
for iface in $(ls /sys/class/net/ | grep -vE "lo|docker|veth|br-|vmbr|tap|fwpr|fwln|virbr|bond|cilium|zt|wg"); do
if [[ -e "/sys/class/net/$iface/device" ]] || [[ -e "/sys/class/net/$iface/phy80211" ]]; then
local MAC=$(cat /sys/class/net/$iface/address 2>/dev/null)
if [[ "$MAC" =~ ^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$ ]]; then
local LINK_FILE="$LINK_DIR/10-$iface.link"
cat > "$LINK_FILE" <<EOF
[Match]
MACAddress=$MAC
[Link]
Name=$iface
EOF
chmod 644 "$LINK_FILE"
((count++))
fi
fi
done
if [[ $count -gt 0 ]]; then
msg_ok "$(translate "Created persistent names for") $count $(translate "interfaces")"
msg_ok "$(translate "Changes will apply after reboot.")"
else
msg_warn "$(translate "No physical interfaces found")"
fi
register_tool "persistent_network" true
}
# ==========================================================
run_complete_optimization() {
clear
show_proxmenux_logo
msg_title "$(translate "ProxMenux Optimization Post-Installation")"
ensure_tools_json
apt_upgrade
cleanup
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
setup_persistent_network
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
}
check_extremeshok_warning
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
run_complete_optimization
fi

View File

@@ -0,0 +1,742 @@
#!/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 && [[ ! -f /usr/local/bin/fastfetch ]]; 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" /etc/profile 2>/dev/null
sed -i '/# BEGIN FASTFETCH/,/# END FASTFETCH/d' "$HOME/.bashrc"
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_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 '/lxcclean/d;/lxcupdate/d;/kernelclean/d;/cpugov/d;/updatecerts/d;/seqwrite/d;/seqread/d;/ranwrite/d;/ranread/d' "$HOME/.bashrc" "$HOME/.profile" 2>/dev/null
sed -i '/# ProxMenux Figurine aliases and tools/,+20d' "$HOME/.bashrc" "$HOME/.profile" 2>/dev/null
sed -i '/# BEGIN PROXMENUX ALIASES/,/# END PROXMENUX ALIASES/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
}
################################################################
uninstall_persistent_network() {
local LINK_DIR="/etc/systemd/network"
msg_info "$(translate "Removing all .link files from") $LINK_DIR"
sleep 2
if ! ls "$LINK_DIR"/*.link >/dev/null 2>&1; then
msg_warn "$(translate "No .link files found in") $LINK_DIR"
return 0
fi
rm -f "$LINK_DIR"/*.link
msg_ok "$(translate "Removed all .link files from") $LINK_DIR"
msg_info "$(translate "Interface names will return to default systemd behavior.")"
register_tool "persistent_network" false
}
uninstall_amd_fixes() {
msg_info2 "$(translate "Reverting AMD (Ryzen/EPYC) fixes...")"
NECESSARY_REBOOT=1
if grep -q "root=ZFS=" /proc/cmdline 2>/dev/null; then
cmdline_file="/etc/kernel/cmdline"
if [[ -f "$cmdline_file" ]] && grep -q "idle=nomwait" "$cmdline_file"; then
cp "$cmdline_file" "${cmdline_file}.bak.$(date +%Y%m%d_%H%M%S)" || {
msg_error "$(translate "Failed to backup $cmdline_file")"
return 1
}
sed -i 's/\bidle=nomwait\b//g; s/[[:space:]]\+/ /g; s/^ //; s/ $//' "$cmdline_file"
if command -v proxmox-boot-tool >/dev/null 2>&1; then
proxmox-boot-tool refresh >/dev/null 2>&1 || {
msg_error "$(translate "Failed to refresh boot configuration")"
return 1
}
fi
msg_ok "$(translate "Removed idle=nomwait from /etc/kernel/cmdline (ZFS)")"
fi
else
grub_file="/etc/default/grub"
if [[ -f "$grub_file" ]] && grep -q 'GRUB_CMDLINE_LINUX_DEFAULT=' "$grub_file"; then
if grep -q "idle=nomwait" "$grub_file"; then
cp "$grub_file" "${grub_file}.bak.$(date +%Y%m%d_%H%M%S)" || {
msg_error "$(translate "Failed to backup $grub_file")"
return 1
}
sed -i -E 's/(GRUB_CMDLINE_LINUX_DEFAULT=")/\1/; s/\bidle=nomwait\b//g' "$grub_file"
awk -F\" '
$1=="GRUB_CMDLINE_LINUX_DEFAULT=" {
gsub(/[[:space:]]+/," ",$2); sub(/^ /,"",$2); sub(/ $/,"",$2)
}1
' OFS="\"" "$grub_file" > "${grub_file}.tmp" && mv "${grub_file}.tmp" "$grub_file"
update-grub >/dev/null 2>&1 || {
msg_error "$(translate "Failed to update GRUB configuration")"
return 1
}
msg_ok "$(translate "Removed idle=nomwait from GRUB configuration")"
fi
fi
fi
kvm_conf="/etc/modprobe.d/kvm.conf"
if [[ -f "$kvm_conf" ]]; then
if grep -Eq '(ignore_msrs|report_ignored_msrs)' "$kvm_conf"; then
cp "$kvm_conf" "${kvm_conf}.bak.$(date +%Y%m%d_%H%M%S)" || {
msg_error "$(translate "Failed to backup $kvm_conf")"
return 1
}
sed -i -E '/ignore_msrs|report_ignored_msrs/d' "$kvm_conf"
if [[ ! -s "$kvm_conf" ]]; then
rm -f "$kvm_conf"
msg_ok "$(translate "Removed empty KVM configuration file")"
else
msg_ok "$(translate "Removed KVM MSR options from configuration")"
fi
update-initramfs -u -k all >/dev/null 2>&1 || true
else
msg_ok "$(translate "KVM MSR options not present, nothing to revert")"
fi
fi
msg_success "$(translate "AMD fixes have been successfully reverted")"
register_tool "amd_fixes" 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";;
kexec) desc="kexec for quick reboots";;
network_optimization) desc="Network Optimizations";;
disable_rpc) desc="RPC/rpcbind Disable";;
bashrc_custom) desc="Bashrc Customization";;
figurine) desc="Figurine";;
fastfetch) desc="Fastfetch";;
log2ram) desc="Log2ram (SSD Protection)";;
amd_fixes) desc="AMD CPU (Ryzen/EPYC) fixes";;
persistent_network) desc="Setting persistent network interfaces";;
*) 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

@@ -0,0 +1,472 @@
#!/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.5
# Last Updated: 04/08/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
# ==========================================================
show_command() {
local step="$1"
local description="$2"
local command="$3"
local note="$4"
local command_extra="$5"
echo -e "${BGN}${step}.${CL} ${BL}${description}${CL}"
echo ""
echo -e "${TAB}${command}"
echo -e
[[ -n "$note" ]] && echo -e "${TAB}${DARK_GRAY}${note}${CL}"
[[ -n "$command_extra" ]] && echo -e "${TAB}${YW}${command_extra}${CL}"
echo ""
}
show_how_to_enter_lxc() {
clear
show_proxmenux_logo
msg_title "$(translate "How to Access an LXC Terminal from Proxmox Host")"
msg_info2 "$(translate "Use these commands on your Proxmox host to access an LXC container's terminal:")"
echo -e
show_command "1" \
"$(translate "Get a list of all your containers:")" \
"pct list" \
"" \
""
show_command "2" \
"$(translate "Enter the container's terminal")" \
"pct enter ${CUS}<container-id>${CL}" \
"$(translate "Replace <container-id> with the actual ID.")"\
"$(translate "For example: pct enter 101")"
show_command "3" \
"$(translate "To exit the container's terminal, press:")" \
"CTRL + D" \
"" \
""
echo -e ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
show_host_mount_resources_help() {
clear
show_proxmenux_logo
msg_title "$(translate "Mount Remote Resources on Proxmox Host")"
msg_info2 "$(translate "How to mount NFS and Samba shares directly on the Proxmox host. Proxmox already has the necessary tools installed.")"
echo -e
echo -e "${BOLD}${BL}=== MOUNT NFS SHARE ===${CL}"
echo -e
show_command "1" \
"$(translate "Create mount point:")" \
"mkdir -p ${CUS}/mnt/nfs_share${CL}" \
"$(translate "Replace with your preferred path.")" \
""
show_command "2" \
"$(translate "Mount NFS share:")" \
"mount -t nfs ${CUS}192.168.1.100${CL}:${CUS}/path/to/share${CL} ${CUS}/mnt/nfs_share${CL}" \
"$(translate "Replace IP and paths with your values.")" \
""
show_command "3" \
"$(translate "Make permanent (optional):")" \
"echo '${CUS}192.168.1.100${CL}:${CUS}/path/to/share${CL} ${CUS}/mnt/nfs_share${CL} nfs4 rw,hard,intr,_netdev,rsize=1048576,wsize=1048576,timeo=600,retrans=2 0 0' >> /etc/fstab" \
"$(translate "_netdev waits for network before mounting.")" \
""
echo -e "${BOLD}${BL}=== MOUNT SAMBA SHARE ===${CL}"
echo -e
show_command "4" \
"$(translate "Create mount point:")" \
"mkdir -p ${CUS}/mnt/samba_share${CL}" \
"$(translate "Replace with your preferred path.")" \
""
show_command "5" \
"$(translate "Mount Samba share:")" \
"mount -t cifs //${CUS}192.168.1.100${CL}/${CUS}sharename${CL} ${CUS}/mnt/samba_share${CL} -o username=${CUS}user${CL}" \
"$(translate "You will be prompted for password. Replace IP, share and user.")" \
""
show_command "6" \
"$(translate "Make permanent (optional):")" \
"echo '//${CUS}192.168.1.100${CL}/${CUS}sharename${CL} ${CUS}/mnt/samba_share${CL} cifs username=${CUS}user${CL},password=${CUS}pass${CL},_netdev 0 0' >> /etc/fstab" \
"$(translate "Replace with your credentials.")" \
""
echo -e "${BOLD}${BL}=== CREATE LOCAL DIRECTORY ===${CL}"
echo -e
show_command "7" \
"$(translate "Create directory:")" \
"mkdir -p ${CUS}/mnt/local_share${CL}" \
"$(translate "Creates a local directory on Proxmox host.")" \
""
show_command "8" \
"$(translate "Set permissions:")" \
"chmod 755 ${CUS}/mnt/local_share${CL}" \
"$(translate "Sets basic read/write permissions.")" \
""
show_command "9" \
"$(translate "Verify mounts:")" \
"df -h" \
"$(translate "Shows all mounted filesystems.")" \
""
echo -e ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
show_host_to_lxc_mount_help() {
clear
show_proxmenux_logo
msg_title "$(translate "Mount Host Directory to LXC Container")"
msg_info2 "$(translate "How to mount a Proxmox host directory into an LXC container. Execute these commands on the Proxmox host.")"
echo -e
show_command "1" \
"$(translate "Add mount point to container:")" \
"pct set ${CUS}<container-id>${CL} -mp0 ${CUS}/host/directory${CL},mp=${CUS}/container/path${CL},backup=0,shared=1" \
"$(translate "Replace container-id, host directory and container path.")" \
"$(translate "Example: pct set 101 -mp0 /mnt/shared,mp=/mnt/shared,,backup=0,shared=1")"
show_command "2" \
"$(translate "Restart container:")" \
"pct reboot ${CUS}<container-id>${CL}" \
"$(translate "Required to activate the mount point.")" \
""
show_command "3" \
"$(translate "Verify mount inside container:")" \
"pct enter ${CUS}<container-id>${CL}
df -h | grep ${CUS}/container/path${CL}" \
"$(translate "Check if the directory is mounted.")" \
""
show_command "4" \
"$(translate "Remove mount point (if needed):")" \
"pct set ${CUS}<container-id>${CL} --delete mp0" \
"$(translate "Removes the mount point. Use mp1, mp2, etc. for other mounts.")" \
""
echo -e "${BOR}"
echo -e "${BOLD}$(translate "Notes:")${CL}"
echo -e "${TAB}${BGN}$(translate "Mount indices:")${CL} ${BL}Use mp0, mp1, mp2, etc. for multiple mounts${CL}"
echo -e "${TAB}${BGN}$(translate "Permissions:")${CL} ${BL}May need adjustment depending on directory type${CL}"
echo -e "${TAB}${BGN}$(translate "Container types:")${CL} ${BL}Works with both privileged and unprivileged containers${CL}"
echo -e ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
show_nfs_server_help() {
clear
show_proxmenux_logo
msg_title "$(translate "NFS Server Installation")"
msg_info2 "$(translate "How to install and configure an NFS server in an LXC container.")"
echo -e
show_command "1" \
"$(translate "Update and install packages:")" \
"apt-get update && apt-get install -y nfs-kernel-server" \
"" \
""
show_command "2" \
"$(translate "Create export directory:")" \
"mkdir -p ${CUS}/mnt/nfs_export${CL}" \
"$(translate "Replace with your preferred path.")" \
""
show_command "3" \
"$(translate "Set directory permissions:")" \
"chmod 755 ${CUS}/mnt/nfs_export${CL}" \
"" \
""
show_command "4.1" \
"$(translate "Configure exports (safe root_squash):")" \
"echo '${CUS}/mnt/nfs_export${CL} ${CUS}192.168.1.0/24${CL}(rw,sync,no_subtree_check,root_squash)' >> /etc/exports" \
"$(translate "Replace directory path and network range.")" \
""
show_command "4.2" \
"$(translate "Or Configure exports (map all users):")" \
"echo '${CUS}/mnt/nfs_export${CL} ${CUS}192.168.1.0/24${CL}(rw,sync,no_subtree_check,all_squash,anonuid=0,anongid=0)' >> /etc/exports" \
"$(translate "Replace directory path and network range.")" \
""
show_command "5" \
"$(translate "Apply configuration:")" \
"exportfs -ra" \
"" \
""
show_command "6" \
"$(translate "Start and enable service:")" \
"systemctl restart nfs-kernel-server
systemctl enable nfs-kernel-server" \
"" \
""
show_command "7" \
"$(translate "Verify exports:")" \
"showmount -e localhost" \
"$(translate "Shows available NFS exports.")" \
""
echo -e "${BOR}"
echo -e "${BOLD}$(translate "Export Options:")${CL}"
echo -e "${TAB}${BGN}$(translate "rw:")${CL} ${BL}Read-write access${CL}"
echo -e "${TAB}${BGN}$(translate "sync:")${CL} ${BL}Synchronous writes${CL}"
echo -e "${TAB}${BGN}$(translate "no_subtree_check:")${CL} ${BL}Improves performance${CL}"
echo -e ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
show_samba_server_help() {
clear
show_proxmenux_logo
msg_title "$(translate "Samba Server Installation")"
msg_info2 "$(translate "How to install and configure a Samba server in an LXC container.")"
echo -e
show_command "1" \
"$(translate "Update and install packages:")" \
"apt-get update && apt-get install -y samba" \
"" \
""
show_command "2" \
"$(translate "Create share directory:")" \
"mkdir -p ${CUS}/mnt/samba_share${CL}" \
"$(translate "Replace with your preferred path.")" \
""
show_command "3" \
"$(translate "Set directory permissions:")" \
"chmod 755 ${CUS}/mnt/samba_share${CL}" \
"" \
""
show_command "4" \
"$(translate "Create Samba user:")" \
"adduser ${CUS}sambauser${CL}
smbpasswd -a ${CUS}sambauser${CL}" \
"$(translate "Replace with your username. You'll be prompted for password.")" \
""
show_command "5" \
"$(translate "Configure share:")" \
"cat >> /etc/samba/smb.conf << EOF
[shared]
comment = Shared folder
path = ${CUS}/mnt/samba_share${CL}
read only = no
browseable = yes
valid users = ${CUS}sambauser${CL}
EOF" \
"$(translate "Replace path and username.")" \
""
show_command "6" \
"$(translate "Restart and enable service:")" \
"systemctl restart smbd
systemctl enable smbd" \
"" \
""
show_command "7" \
"$(translate "Test configuration:")" \
"smbclient -L localhost -U ${CUS}sambauser${CL}" \
"$(translate "Lists available shares. You'll be prompted for password.")" \
""
echo -e "${BOR}"
echo -e "${BOLD}$(translate "Connection Examples:")${CL}"
echo -e "${TAB}${BGN}$(translate "Windows:")${CL} ${YW}\\\\<server-ip>\\shared${CL}"
echo -e "${TAB}${BGN}$(translate "Linux:")${CL} ${YW}smbclient //server-ip/shared -U sambauser${CL}"
echo -e ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
show_nfs_client_help() {
clear
show_proxmenux_logo
msg_title "$(translate "NFS Client Configuration")"
msg_info2 "$(translate "How to configure an NFS client in an LXC container.")"
echo -e
show_command "1" \
"$(translate "Update and install packages:")" \
"apt-get update && apt-get install -y nfs-common" \
"" \
""
show_command "2" \
"$(translate "Create mount point:")" \
"mkdir -p ${CUS}/mnt/nfsmount${CL}" \
"$(translate "Replace with your preferred path.")" \
""
show_command "3" \
"$(translate "Mount NFS share:")" \
"mount -t nfs ${CUS}192.168.1.100${CL}:${CUS}/mnt/nfs_export${CL} ${CUS}/mnt/nfsmount${CL}" \
"$(translate "Replace server IP and paths.")" \
""
show_command "4" \
"$(translate "Test access:")" \
"ls -la ${CUS}/mnt/nfsmount${CL}" \
"$(translate "Verify you can access the mounted share.")" \
""
show_command "5" \
"$(translate "Make permanent (optional):")" \
"echo '${CUS}192.168.1.100${CL}:${CUS}/path/to/share${CL} ${CUS}/mnt/nfs_share${CL} nfs4 rw,hard,intr,_netdev,rsize=1048576,wsize=1048576,timeo=600,retrans=2 0 0' >> /etc/fstab" \
"$(translate "Replace with your server IP and paths.")" \
""
show_command "6" \
"$(translate "Verify mount:")" \
"df -h | grep nfs" \
"$(translate "Shows NFS mounts.")" \
""
echo -e ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
show_samba_client_help() {
clear
show_proxmenux_logo
msg_title "$(translate "Samba Client Configuration")"
msg_info2 "$(translate "How to configure a Samba client in an LXC container.")"
echo -e
show_command "1" \
"$(translate "Update and install packages:")" \
"apt-get update && apt-get install -y cifs-utils" \
"" \
""
show_command "2" \
"$(translate "Create mount point:")" \
"mkdir -p ${CUS}/mnt/sambamount${CL}" \
"$(translate "Replace with your preferred path.")" \
""
show_command "3" \
"$(translate "Mount Samba share:")" \
"mount -t cifs //${CUS}192.168.1.100${CL}/${CUS}shared${CL} ${CUS}/mnt/sambamount${CL} -o username=${CUS}sambauser${CL}" \
"$(translate "Replace server IP, share name and username. You'll be prompted for password.")" \
""
show_command "4" \
"$(translate "Test access:")" \
"ls -la ${CUS}/mnt/sambamount${CL}" \
"$(translate "Verify you can access the mounted share.")" \
""
show_command "5" \
"$(translate "Create credentials file (optional):")" \
"cat > /etc/samba/credentials << EOF
username=${CUS}sambauser${CL}
password=${CUS}your_password${CL}
EOF
chmod 600 /etc/samba/credentials" \
"$(translate "Secure way to store credentials.")" \
""
show_command "6" \
"$(translate "Mount with credentials file:")" \
"mount -t cifs //${CUS}192.168.1.100${CL}/${CUS}shared${CL} ${CUS}/mnt/sambamount${CL} -o credentials=/etc/samba/credentials" \
"$(translate "No password prompt needed.")" \
""
show_command "7" \
"$(translate "Make permanent (optional):")" \
"echo '//${CUS}192.168.1.100${CL}/${CUS}shared${CL} ${CUS}/mnt/sambamount${CL} cifs credentials=/etc/samba/credentials,_netdev 0 0' >> /etc/fstab" \
"$(translate "Replace with your values.")" \
""
show_command "8" \
"$(translate "Verify mount:")" \
"df -h | grep cifs" \
"$(translate "Shows CIFS/Samba mounts.")" \
""
echo -e ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
show_help_menu() {
while true; do
CHOICE=$(dialog --title "$(translate "Help & Information")" \
--menu "$(translate "Select help topic:")" 24 80 14 \
"0" "$(translate "How to Access an LXC Terminal")" \
"1" "$(translate "Mount Remote Resources on Proxmox Host")" \
"2" "$(translate "Mount Host Directory to LXC Container")" \
"3" "$(translate "NFS Server Installation")" \
"4" "$(translate "Samba Server Installation")" \
"5" "$(translate "NFS Client Configuration")" \
"6" "$(translate "Samba Client Configuration")" \
"7" "$(translate "Return to Main Menu")" \
3>&1 1>&2 2>&3)
case $CHOICE in
0) show_how_to_enter_lxc ;;
1) show_host_mount_resources_help ;;
2) show_host_to_lxc_mount_help ;;
3) show_nfs_server_help ;;
4) show_samba_server_help ;;
5) show_nfs_client_help ;;
6) show_samba_client_help ;;
7) return ;;
*) return ;;
esac
done
}
show_help_menu

View File

@@ -0,0 +1,271 @@
#!/usr/bin/env bash
# ==========================================================
# ProxMenux - Shared Groups Manager
# ==========================================================
# Author : MacRimi
# Description : Manage host groups for shared directories
# ==========================================================
# Configuration
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
pmx_list_groups() {
local groups
groups=$(getent group | awk -F: '$3 >= 1000 && $1 != "nogroup" && $1 !~ /^pve/ {print $1 ":" $3}')
if [[ -z "$groups" ]]; then
whiptail --title "$(translate "Groups")" --msgbox "$(translate "No user groups found.")" 8 60
return
fi
show_proxmenux_logo
msg_title "$(translate "Existing Groups")"
echo "$groups" | column -t -s: | while read -r name gid; do
members=$(getent group "$name" | awk -F: '{print $4}')
echo -e "${BL}$name${CL} (GID: $gid) -> ${YW}${members:-no members}${CL}"
done
echo ""
msg_success "$(translate "Press Enter to continue...")"
read -r
}
pmx_create_group() {
group_name=$(dialog --inputbox "$(translate "Enter new group name:")" 10 60 "sharedfiles-new" \
--title "$(translate "New Group")" 3>&1 1>&2 2>&3) || return
[[ -z "$group_name" ]] && return
if getent group "$group_name" >/dev/null; then
dialog --title "$(translate "Error")" --msgbox "$(translate "Group already exists.")" 8 50
return
fi
if groupadd "$group_name"; then
show_proxmenux_logo
msg_title "$(translate "Create Group")"
msg_ok "$(translate "Group created successfully:") $group_name"
else
show_proxmenux_logo
msg_title "$(translate "Create Group")"
msg_error "$(translate "Failed to create group.")"
fi
echo -e
msg_success "$(translate "Press Enter to continue...")"
read -r
}
pmx_edit_group() {
local groups group_name action
groups=$(getent group | awk -F: '$3 >= 1000 && $1 != "nogroup" && $1 !~ /^pve/ {print $1}')
if [[ -z "$groups" ]]; then
dialog --title "$(translate "Error")" --msgbox "$(translate "No groups available to edit.")" 8 50
return
fi
local menu_options=""
while read -r group; do
if [[ -n "$group" ]]; then
local gid=$(getent group "$group" | cut -d: -f3)
menu_options="$menu_options $group \"GID:$gid\""
fi
done <<< "$groups"
group_name=$(eval "dialog --title \"$(translate "Edit Group")\" --menu \
\"$(translate "Select a group:")\" 20 60 10 \
$menu_options 3>&1 1>&2 2>&3")
if [[ -z "$group_name" ]]; then
return
fi
action=$(dialog --title "$(translate "Edit Group")" --menu \
"$(translate "What do you want to edit in group:") $group_name" 15 60 3 \
"rename" "$(translate "Rename group")" \
"gid" "$(translate "Change GID")" \
"users" "$(translate "Add/Remove users")" 3>&1 1>&2 2>&3)
if [[ -z "$action" ]]; then
return
fi
case "$action" in
rename)
new_name=$(dialog --inputbox "$(translate "Enter new group name:")" 10 60 \
"$group_name" --title "$(translate "Rename Group")" 3>&1 1>&2 2>&3)
if [[ -n "$new_name" && "$new_name" != "$group_name" ]]; then
if groupmod -n "$new_name" "$group_name" 2>/dev/null; then
show_proxmenux_logo
msg_title "$(translate "Rename Group")"
msg_ok "$(translate "Group renamed to:") $new_name"
else
show_proxmenux_logo
msg_title "$(translate "Rename Group")"
msg_error "$(translate "Failed to rename group")"
fi
fi
;;
gid)
current_gid=$(getent group "$group_name" | cut -d: -f3)
new_gid=$(dialog --inputbox "$(translate "Enter new GID:")" 10 60 \
"$current_gid" --title "$(translate "Change GID")" 3>&1 1>&2 2>&3)
if [[ -n "$new_gid" && "$new_gid" != "$current_gid" ]]; then
if groupmod -g "$new_gid" "$group_name" 2>/dev/null; then
show_proxmenux_logo
msg_title "$(translate "Change GID")"
msg_ok "$(translate "GID changed to:") $new_gid"
else
show_proxmenux_logo
msg_title "$(translate "Change GID")"
msg_error "$(translate "Failed to change GID")"
fi
fi
;;
users)
user_action=$(dialog --title "$(translate "User Management")" --menu \
"$(translate "Choose an action for group:") $group_name" 15 60 2 \
"add" "$(translate "Add user to group")" \
"remove" "$(translate "Remove user from group")" 3>&1 1>&2 2>&3)
case "$user_action" in
add)
username=$(dialog --inputbox "$(translate "Enter username to add:")" 10 60 \
--title "$(translate "Add User")" 3>&1 1>&2 2>&3)
if [[ -n "$username" ]]; then
if id "$username" >/dev/null 2>&1; then
if usermod -aG "$group_name" "$username" 2>/dev/null; then
show_proxmenux_logo
msg_title "$(translate "Add User")"
msg_ok "$(translate "User added:") $username"
else
show_proxmenux_logo
msg_title "$(translate "Add User")"
msg_error "$(translate "Failed to add user")"
fi
else
show_proxmenux_logo
msg_title "$(translate "Add User")"
msg_error "$(translate "User does not exist:") $username"
fi
fi
;;
remove)
members=$(getent group "$group_name" | awk -F: '{print $4}' | tr ',' ' ')
if [[ -z "$members" ]]; then
dialog --title "$(translate "Info")" --msgbox "$(translate "No users in this group.")" 8 50
return
fi
local user_options=""
for user in $members; do
user_options="$user_options $user \"\""
done
username=$(eval "dialog --title \"$(translate "Remove User")\" --menu \
\"$(translate "Select user to remove:")\" 15 60 5 \
$user_options 3>&1 1>&2 2>&3")
if [[ -n "$username" ]]; then
if gpasswd -d "$username" "$group_name" 2>/dev/null; then
show_proxmenux_logo
msg_title "$(translate "Remove User")"
msg_ok "$(translate "User removed:") $username"
else
show_proxmenux_logo
msg_title "$(translate "Remove User")"
msg_error "$(translate "Failed to remove user")"
fi
fi
;;
esac
;;
esac
echo -e
msg_success "$(translate "Press Enter to continue...")"
read -r
}
pmx_delete_group() {
local groups group_name menu_options
groups=$(getent group | awk -F: '$3 >= 1000 && $1 != "nogroup" && $1 !~ /^pve/ {print $1}')
if [[ -z "$groups" ]]; then
dialog --title "$(translate "Error")" --msgbox "$(translate "No groups available to delete.")" 8 50
return
fi
menu_options=""
while read -r group; do
if [[ -n "$group" ]]; then
menu_options="$menu_options $group \"\""
fi
done <<< "$groups"
group_name=$(eval "dialog --title \"$(translate "Delete Group")\" --menu \
\"$(translate "Select a group to delete:")\" 20 60 10 \
$menu_options 3>&1 1>&2 2>&3") || return
if dialog --yesno "$(translate "Are you sure you want to delete group:") $group_name ?" 10 60; then
if groupdel "$group_name" 2>/dev/null; then
show_proxmenux_logo
msg_title "$(translate "Deleting Groups")"
msg_ok "$(translate "Group deleted:") $group_name"
else
show_proxmenux_logo
msg_title "$(translate "Deleting Groups")"
msg_ok "$(translate "Group deleted:") $group_name"
msg_error "$(translate "Failed to delete group")"
fi
fi
echo -e
msg_success "$(translate "Press Enter to continue...")"
read -r
}
pmx_manage_groups() {
while true; do
CHOICE=$(dialog --title "$(translate "Shared Groups Manager")" \
--menu "$(translate "Select an option:")" 20 70 10 \
"list" "$(translate "View existing groups")" \
"create" "$(translate "Create new group")" \
"edit" "$(translate "Edit existing group")" \
"delete" "$(translate "Delete a group")" \
"exit" "$(translate "Exit")" \
3>&1 1>&2 2>&3) || return 0
case "$CHOICE" in
list) pmx_list_groups ;;
create) pmx_create_group ;;
edit) pmx_edit_group ;;
delete) pmx_delete_group ;;
exit) return 0 ;;
esac
done
}
pmx_manage_groups

1292
scripts/share/guia.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,93 @@
#!/bin/bash
# ==========================================================
# ProxMenux - Local Shared Directory Manager
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT
# Version : 1.0
# Last Updated: $(date +%d/%m/%Y)
# ==========================================================
# Configuration
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
SHARE_COMMON_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main/scripts/global/share-common.func"
if ! source <(curl -s "$SHARE_COMMON_URL" 2>/dev/null); then
SHARE_COMMON_LOADED=false
else
SHARE_COMMON_LOADED=true
fi
load_language
initialize_cache
# ==========================================================
create_shared_directory() {
SHARED_DIR=$(pmx_select_host_mount_point "$(translate "Select Shared Directory Location")" "/mnt/shared")
[[ -z "$SHARED_DIR" ]] && return
if [[ -d "$SHARED_DIR" ]]; then
if ! whiptail --yesno "$(translate "Directory already exists. Continue with permission setup?")" 10 70 --title "$(translate "Directory Exists")"; then
return
fi
fi
SHARE_GROUP=$(pmx_choose_or_create_group "sharedfiles") || return 1
SHARE_GID=$(pmx_ensure_host_group "$SHARE_GROUP" 101000) || return 1
if command -v setfacl >/dev/null 2>&1; then
setfacl -k /mnt 2>/dev/null || true
setfacl -b /mnt 2>/dev/null || true
fi
chmod 755 /mnt 2>/dev/null || true
pmx_prepare_host_shared_dir "$SHARED_DIR" "$SHARE_GROUP" || return 1
if command -v setfacl >/dev/null 2>&1; then
setfacl -b -R "$SHARED_DIR" 2>/dev/null || true
fi
chown root:"$SHARE_GROUP" "$SHARED_DIR"
chmod 2775 "$SHARED_DIR"
pmx_share_map_set "$SHARED_DIR" "$SHARE_GROUP"
show_proxmenux_logo
msg_title "$(translate "Create Shared Directory")"
echo -e ""
echo -e "${TAB}${BOLD}$(translate "Shared Directory Created:")${CL}"
echo -e "${TAB}${BGN}$(translate "Directory:")${CL} ${BL}$SHARED_DIR${CL}"
echo -e "${TAB}${BGN}$(translate "Group:")${CL} ${BL}$SHARE_GROUP (GID: $SHARE_GID)${CL}"
echo -e "${TAB}${BGN}$(translate "Permissions:")${CL} ${BL}2775 (rwxrwsr-x)${CL}"
echo -e "${TAB}${BGN}$(translate "Owner:")${CL} ${BL}root:$SHARE_GROUP${CL}"
echo -e "${TAB}${BGN}$(translate "ACL Status:")${CL} ${BL}$(translate "Cleaned and set for POSIX inheritance")${CL}"
echo -e ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
create_shared_directory

View File

@@ -0,0 +1,480 @@
#!/bin/bash
# ==========================================================
# ProxMenux - LXC Mount Manager
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT
# Version : 3.1-enhanced
# Last Updated: $(date +%d/%m/%Y)
# ==========================================================
BASE_DIR="/usr/local/share/proxmenux"
source "$BASE_DIR/utils.sh"
SHARE_COMMON_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main/scripts/global/share-common.func"
if ! source <(curl -s "$SHARE_COMMON_URL" 2>/dev/null); then
SHARE_COMMON_LOADED=false
else
SHARE_COMMON_LOADED=true
fi
load_language
initialize_cache
# ==========================================================
get_container_uid_shift() {
local ctid="$1"
local conf="/etc/pve/lxc/${ctid}.conf"
local uid_shift
if [[ ! -f "$conf" ]]; then
echo "100000"
return 0
fi
local unpriv
unpriv=$(grep "^unprivileged:" "$conf" | awk '{print $2}')
if [[ "$unpriv" == "1" ]]; then
uid_shift=$(grep "^lxc.idmap" "$conf" | grep 'u 0' | awk '{print $5}' | head -1)
echo "${uid_shift:-100000}"
return 0
fi
echo "0"
return 0
}
setup_container_access() {
local ctid="$1" group_name="$2" host_gid="$3" host_dir="$4"
local uid_shift mapped_gid
if [[ ! "$ctid" =~ ^[0-9]+$ ]]; then
msg_error "$(translate 'Invalid container ID format:') $ctid"
return 1
fi
uid_shift=$(get_container_uid_shift "$ctid")
# ===================================================================
# CONTAINER TYPE DETECTION AND STRATEGY
# ===================================================================
if [[ "$uid_shift" -eq 0 ]]; then
msg_ok "$(translate "PRIVILEGED container detected - using direct UID/GID mapping")"
mapped_gid="$host_gid"
container_type="privileged"
else
msg_ok "$(translate "UNPRIVILEGED container detected - using mapped UID/GID")"
mapped_gid=$((uid_shift + host_gid))
container_type="unprivileged"
msg_ok "UID shift: $uid_shift, Host GID: $host_gid → Container GID: $mapped_gid"
fi
# ===================================================================
# STEP 1: ACL TOOLS (only for unprivileged containers)
# ===================================================================
if [[ "$container_type" == "unprivileged" ]]; then
if ! command -v setfacl >/dev/null 2>&1; then
msg_info "$(translate "Installing ACL tools (REQUIRED for unprivileged containers)...")"
apt-get update >/dev/null 2>&1
apt-get install -y acl >/dev/null 2>&1
if command -v setfacl >/dev/null 2>&1; then
msg_ok "$(translate "ACL tools installed successfully")"
else
msg_error "$(translate "Failed to install ACL tools - permissions may not work correctly")"
fi
else
msg_ok "$(translate "ACL tools already available")"
fi
else
msg_ok "$(translate "Privileged container - ACL tools not required (using POSIX permissions)")"
fi
# ===================================================================
# STEP 2: CONTAINER GROUP CONFIGURATION
# ===================================================================
msg_info "$(translate "Configuring container group with") $container_type $(translate "strategy...")"
pct exec "$ctid" -- sh -c "
# Remove existing group if GID is wrong
if getent group $group_name >/dev/null 2>&1; then
current_gid=\$(getent group $group_name | cut -d: -f3)
if [ \"\$current_gid\" != \"$mapped_gid\" ]; then
groupdel $group_name 2>/dev/null || true
fi
fi
# Create group with correct GID
groupadd -g $mapped_gid $group_name 2>/dev/null || true
" 2>/dev/null
msg_ok "$(translate "Container group configured:") $group_name (GID: $mapped_gid)"
# ===================================================================
# STEP 3: USER PROCESSING (different strategies)
# ===================================================================
local container_users
container_users=$(pct exec "$ctid" -- getent passwd | awk -F: '{print $1 ":" $3}' 2>/dev/null)
local users_added=0
local acls_applied=0
if [[ "$container_type" == "privileged" ]]; then
msg_ok "$(translate "Privileged container:") $users_added $(translate "users added to group (no ACLs needed)")"
else
msg_info "$(translate "Using UNPRIVILEGED strategy: mapped UIDs + ACL permissions")"
while IFS=: read -r username ct_uid; do
if [[ -n "$username" && "$ct_uid" =~ ^[0-9]+$ ]]; then
local host_uid=$((uid_shift + ct_uid))
if pct exec "$ctid" -- usermod -aG "$group_name" "$username" 2>/dev/null; then
users_added=$((users_added + 1))
if command -v setfacl >/dev/null 2>&1; then
setfacl -m u:$host_uid:rwx "$host_dir" 2>/dev/null
setfacl -m d:u:$host_uid:rwx "$host_dir" 2>/dev/null
acls_applied=$((acls_applied + 1))
fi
case "$username" in
root|www-data|ncp|nobody|ubuntu|debian)
msg_ok "$(translate "Configured user:") $username (CT_UID:$ct_uid → HOST_UID:$host_uid)"
;;
esac
fi
fi
done <<< "$container_users"
msg_ok "$(translate "Unprivileged container:") $users_added $(translate "users added,") $acls_applied $(translate "ACL entries applied")"
fi
# ===================================================================
# STEP 4: DIRECTORY PERMISSIONS
# ===================================================================
msg_info "$(translate "Setting optimal directory permissions...")"
chmod 2775 "$host_dir" 2>/dev/null || true
chgrp "$group_name" "$host_dir" 2>/dev/null || true
msg_ok "$(translate "Host directory permissions:") 2775 root:$group_name"
# ===================================================================
# STEP 5: VERIFICATION
# ===================================================================
msg_info "$(translate "Verifying configuration...")"
if [[ "$container_type" == "unprivileged" ]] && command -v getfacl >/dev/null 2>&1; then
local acl_count=$(getfacl "$host_dir" 2>/dev/null | grep "^user:" | grep -v "^user::" | wc -l)
msg_ok "$(translate "ACL entries configured:") $acl_count"
# Show sample ACL entries
if [[ $acl_count -gt 0 ]]; then
echo -e "${TAB}${BGN}$(translate " ACL entries:")${CL}"
getfacl "$host_dir" 2>/dev/null | grep "^user:" | grep -v "^user::" | head -3 | while read acl_line; do
echo -e "${TAB} ${BL}$acl_line${CL}"
done
fi
fi
local test_users=("www-data" "root" "ncp" "nobody")
local successful_tests=0
for test_user in "${test_users[@]}"; do
if pct exec "$ctid" -- id "$test_user" >/dev/null 2>&1; then
if pct exec "$ctid" -- su -s /bin/bash "$test_user" -c "ls '$4' >/dev/null 2>&1" 2>/dev/null; then
successful_tests=$((successful_tests + 1))
fi
fi
done
if [[ $successful_tests -gt 0 ]]; then
msg_ok "$(translate "Access verification:") $successful_tests $(translate "users can access mount point")"
fi
if [[ "$container_type" == "privileged" ]]; then
msg_ok "$(translate "PRIVILEGED container configuration completed - using direct POSIX permissions")"
else
msg_ok "$(translate "UNPRIVILEGED container configuration completed - using ACL permissions")"
fi
return 0
}
get_next_mp_index() {
local ctid="$1"
local conf="/etc/pve/lxc/${ctid}.conf"
if [[ ! "$ctid" =~ ^[0-9]+$ ]] || [[ ! -f "$conf" ]]; then
echo "0"
return 0
fi
local used idx next=0
used=$(awk -F: '/^mp[0-9]+:/ {print $1}' "$conf" | sed 's/mp//' | sort -n)
for idx in $used; do
[[ "$idx" -ge "$next" ]] && next=$((idx+1))
done
echo "$next"
}
add_bind_mount() {
local ctid="$1" host_path="$2" ct_path="$3"
local mpidx result
if [[ ! "$ctid" =~ ^[0-9]+$ ]]; then
msg_error "$(translate 'Invalid container ID format:') $ctid"
return 1
fi
if [[ -z "$ctid" || -z "$host_path" || -z "$ct_path" ]]; then
msg_error "$(translate "Missing arguments")"
return 1
fi
if pct config "$ctid" | grep -q "$host_path"; then
echo -e
msg_warn "$(translate "Directory already mounted in container configuration.")"
echo -e ""
msg_success "$(translate 'Press Enter to return to menu...')"
read -r
return 1
fi
mpidx=$(get_next_mp_index "$ctid")
result=$(pct set "$ctid" -mp${mpidx} "$host_path,mp=$ct_path,shared=1,backup=0,acl=1" 2>&1)
if [[ $? -eq 0 ]]; then
msg_ok "$(translate "Successfully mounted:") $host_path$ct_path"
return 0
else
msg_error "$(translate "Error mounting folder:") $result"
return 1
fi
}
mount_host_directory_to_lxc() {
# Step 1: Select container
local container_id
container_id=$(select_lxc_container)
if [[ $? -ne 0 || -z "$container_id" ]]; then
return 1
fi
show_proxmenux_logo
msg_title "$(translate 'Mount Host Directory to LXC Container')"
# Step 1.1: Ensure running
ct_status=$(pct status "$container_id" | awk '{print $2}')
if [[ "$ct_status" != "running" ]]; then
msg_info "$(translate "Starting container") $container_id..."
if pct start "$container_id"; then
sleep 3
msg_ok "$(translate "Container started")"
else
msg_error "$(translate "Failed to start container")"
echo -e ""
msg_success "$(translate 'Press Enter to continue...')"
read -r
return 1
fi
fi
msg_ok "$(translate 'Container selected and running')"
sleep 2
# Step 2: Select host directory
local host_dir
host_dir=$(select_host_directory)
if [[ -z "$host_dir" ]]; then
return 1
fi
msg_ok "$(translate 'Host directory selected')"
# Step 3: Setup group
local group_name="sharedfiles"
local group_gid
group_gid=$(pmx_ensure_host_group "$group_name")
if [[ -z "$group_gid" ]]; then
return 1
fi
# Set basic permissions
chown -R root:"$group_name" "$host_dir" 2>/dev/null || true
chmod -R 2775 "$host_dir" 2>/dev/null || true
msg_ok "$(translate 'Host group configured')"
# Step 4: Select container mount point
local ct_mount_point
ct_mount_point=$(select_container_mount_point "$container_id" "$host_dir")
if [[ -z "$ct_mount_point" ]]; then
return 1
fi
# Step 5: Confirmation
local uid_shift container_type
uid_shift=$(get_container_uid_shift "$container_id")
if [[ "$uid_shift" -eq 0 ]]; then
container_type="$(translate 'Privileged')"
else
container_type="$(translate 'Unprivileged')"
fi
local confirm_msg="$(translate "Mount Configuration:")
$(translate "Container ID:"): $container_id ($container_type)
$(translate "Host Directory:"): $host_dir
$(translate "Container Mount Point:"): $ct_mount_point
$(translate "Shared Group:"): $group_name (GID: $group_gid)
$(translate "Proceed?")"
if ! whiptail --title "$(translate "Confirm Mount")" --yesno "$confirm_msg" 16 70; then
return 1
fi
# Step 6: Add mount
if ! add_bind_mount "$container_id" "$host_dir" "$ct_mount_point"; then
return 1
fi
# Step 7: Setup access (handles both privileged and unprivileged)
setup_container_access "$container_id" "$group_name" "$group_gid" "$host_dir"
# Step 8: Final setup
pct exec "$container_id" -- chgrp "$group_name" "$ct_mount_point" 2>/dev/null || true
pct exec "$container_id" -- chmod 2775 "$ct_mount_point" 2>/dev/null || true
# Step 9: Summary
echo -e ""
echo -e "${TAB}${BOLD}$(translate 'Mount Added Successfully:')${CL}"
echo -e "${TAB}${BGN}$(translate 'Container:')${CL} ${BL}$container_id ($container_type)${CL}"
echo -e "${TAB}${BGN}$(translate 'Host Directory:')${CL} ${BL}$host_dir${CL}"
echo -e "${TAB}${BGN}$(translate 'Mount Point:')${CL} ${BL}$ct_mount_point${CL}"
echo -e "${TAB}${BGN}$(translate 'Group:')${CL} ${BL}$group_name (GID: $group_gid)${CL}"
if [[ "$uid_shift" -eq 0 ]]; then
echo -e "${TAB}${BGN}$(translate 'Permission Strategy:')${CL} ${BL}POSIX (direct mapping)${CL}"
else
echo -e "${TAB}${BGN}$(translate 'Permission Strategy:')${CL} ${BL}ACL (mapped UIDs)${CL}"
fi
echo -e ""
if whiptail --yesno "$(translate "Restart container to activate mount?")" 8 60; then
msg_info "$(translate 'Restarting container...')"
if pct reboot "$container_id"; then
sleep 5
msg_ok "$(translate 'Container restarted successfully')"
echo -e
echo -e "${TAB}${BOLD}$(translate 'Testing access and read/write:')${CL}"
test_user=$(pct exec "$container_id" -- sh -c "id -u ncp >/dev/null 2>&1 && echo ncp || echo www-data")
if pct exec "$container_id" -- su -s /bin/bash $test_user -c "touch $ct_mount_point/test_access.txt" 2>/dev/null; then
msg_ok "$(translate "Mount access and read/write successful (tested as $test_user)")"
rm -f "$host_dir/test_access.txt" 2>/dev/null || true
else
msg_warn "$(translate "⚠ Access test failed - check permissions (user: $test_user)")"
fi
else
msg_warn "$(translate 'Failed to restart - restart manually')"
fi
fi
echo -e ""
msg_success "$(translate 'Press Enter to continue...')"
read -r
}
# Main menu
main_menu() {
while true; do
choice=$(dialog --title "$(translate 'LXC Mount Manager')" \
--menu "\n$(translate 'Choose an option:')" 25 80 15 \
"1" "$(translate 'Mount Host Directory to LXC')" \
"2" "$(translate 'View Mount Points')" \
"3" "$(translate 'Remove Mount Point')" \
"4" "$(translate 'Exit')" 3>&1 1>&2 2>&3)
case $choice in
1)
mount_host_directory_to_lxc
;;
2)
msg_info2 "$(translate 'Feature coming soon...')"
read -p "$(translate 'Press Enter to continue...')"
;;
3)
msg_info2 "$(translate 'Feature coming soon...')"
read -p "$(translate 'Press Enter to continue...')"
;;
4|"")
exit 0
;;
esac
done
}
#main_menu
mount_host_directory_to_lxc

File diff suppressed because it is too large Load Diff

687
scripts/share/nfs_client.sh Normal file
View File

@@ -0,0 +1,687 @@
#!/bin/bash
# ==========================================================
# ProxMenu CT - NFS Client Manager for Proxmox LXC
# ==========================================================
# Based on ProxMenux by MacRimi
# ==========================================================
# Description:
# This script allows you to manage NFS client mounts inside Proxmox CTs:
# - Mount NFS shares (temporary and permanent)
# - View current mounts
# - Unmount and remove NFS shares
# - Auto-discover NFS servers
# ==========================================================
# Configuration
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
# Load shared functions
SHARE_COMMON_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main/scripts/global/share-common.func"
if ! source <(curl -s "$SHARE_COMMON_URL" 2>/dev/null); then
msg_error "$(translate "Could not load shared functions. Script cannot continue.")"
exit 1
fi
load_language
initialize_cache
select_privileged_lxc
install_nfs_client() {
if pct exec "$CTID" -- dpkg -s nfs-common &>/dev/null; then
return 0
fi
show_proxmenux_logo
msg_title "$(translate "Installing NFS Client in LXC")"
msg_info "$(translate "Installing NFS client packages...")"
if ! pct exec "$CTID" -- apt-get update >/dev/null 2>&1; then
msg_error "$(translate "Failed to update package list.")"
msg_success "$(translate "Press Enter to return to menu...")"
read -r
return 1
fi
if ! pct exec "$CTID" -- apt-get install -y nfs-common >/dev/null 2>&1; then
msg_error "$(translate "Failed to install NFS client packages.")"
msg_success "$(translate "Press Enter to return to menu...")"
read -r
return 1
fi
if ! pct exec "$CTID" -- which showmount >/dev/null 2>&1; then
msg_error "$(translate "showmount command not found after installation.")"
msg_success "$(translate "Press Enter to return to menu...")"
read -r
return 1
fi
if ! pct exec "$CTID" -- which mount.nfs >/dev/null 2>&1; then
msg_error "$(translate "mount.nfs command not found after installation.")"
msg_success "$(translate "Press Enter to return to menu...")"
read -r
return 1
fi
msg_ok "$(translate "NFS client installed successfully.")"
return 0
}
discover_nfs_servers() {
show_proxmenux_logo
msg_title "$(translate "Mount NFS Client in LXC")"
msg_info "$(translate "Scanning network for NFS servers...")"
HOST_IP=$(hostname -I | awk '{print $1}')
NETWORK=$(echo "$HOST_IP" | cut -d. -f1-3).0/24
if ! which nmap >/dev/null 2>&1; then
apt-get install -y nmap &>/dev/null
fi
SERVERS=$(nmap -p 2049 --open "$NETWORK" 2>/dev/null | grep -B 4 "2049/tcp open" | grep "Nmap scan report" | awk '{print $5}' | sort -u || true)
if [[ -z "$SERVERS" ]]; then
cleanup
whiptail --title "$(translate "No Servers Found")" --msgbox "$(translate "No NFS servers found on the network.")\n\n$(translate "You can add servers manually.")" 10 60
return 1
fi
OPTIONS=()
while IFS= read -r server; do
if [[ -n "$server" ]]; then
EXPORTS_COUNT=$(showmount -e "$server" 2>/dev/null | tail -n +2 | wc -l || echo "0")
SERVER_INFO="NFS Server ($EXPORTS_COUNT exports)"
OPTIONS+=("$server" "$SERVER_INFO")
fi
done <<< "$SERVERS"
if [[ ${#OPTIONS[@]} -eq 0 ]]; then
cleanup
whiptail --title "$(translate "No Valid Servers")" --msgbox "$(translate "No accessible NFS servers found.")" 8 50
return 1
fi
msg_ok "$(translate "NFS servers detected")"
NFS_SERVER=$(whiptail --title "$(translate "Select NFS Server")" --menu "$(translate "Choose an NFS server:")" 20 80 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3)
[[ -n "$NFS_SERVER" ]] && return 0 || return 1
}
select_nfs_server() {
METHOD=$(whiptail --backtitle "ProxMenux" --title "$(translate "NFS Server Selection")" --menu "$(translate "How do you want to select the NFS server?")" 15 70 3 \
"auto" "$(translate "Auto-discover servers on network")" \
"manual" "$(translate "Enter server IP/hostname manually")" 3>&1 1>&2 2>&3)
case "$METHOD" in
auto)
discover_nfs_servers || return 1
;;
manual)
NFS_SERVER=$(whiptail --inputbox "$(translate "Enter NFS server IP or hostname:")" 10 60 --title "$(translate "NFS Server")" 3>&1 1>&2 2>&3)
[[ -z "$NFS_SERVER" ]] && return 1
;;
*)
return 1
;;
esac
return 0
}
select_nfs_export() {
if ! pct exec "$CTID" -- which showmount >/dev/null 2>&1; then
whiptail --title "$(translate "NFS Client Error")" \
--msgbox "$(translate "showmount command is not working properly.")\n\n$(translate "Please check the installation.")" \
10 60
return 1
fi
if ! pct exec "$CTID" -- ping -c 1 -W 3 "$NFS_SERVER" >/dev/null 2>&1; then
whiptail --title "$(translate "Connection Error")" \
--msgbox "$(translate "Cannot reach server") $NFS_SERVER\n\n$(translate "Please check:")\n• $(translate "Server IP/hostname is correct")\n• $(translate "Network connectivity")\n• $(translate "Server is online")" \
12 70
return 1
fi
if ! pct exec "$CTID" -- nc -z -w 3 "$NFS_SERVER" 2049 2>/dev/null; then
whiptail --title "$(translate "NFS Port Error")" \
--msgbox "$(translate "NFS port (2049) is not accessible on") $NFS_SERVER\n\n$(translate "Please check:")\n• $(translate "NFS server is running")\n• $(translate "Firewall settings")\n• $(translate "NFS service is enabled")" \
12 70
return 1
fi
EXPORTS_OUTPUT=$(pct exec "$CTID" -- showmount -e "$NFS_SERVER" 2>&1)
EXPORTS_RESULT=$?
if [[ $EXPORTS_RESULT -ne 0 ]]; then
ERROR_MSG=$(echo "$EXPORTS_OUTPUT" | grep -i "error\|failed\|denied" | head -1)
if echo "$EXPORTS_OUTPUT" | grep -qi "connection refused\|network unreachable"; then
whiptail --title "$(translate "Network Error")" \
--msgbox "$(translate "Network connection failed to") $NFS_SERVER\n\n$(translate "Error:"): $ERROR_MSG\n\n$(translate "Please check:")\n• $(translate "Server is running")\n• $(translate "Network connectivity")\n• $(translate "Firewall settings")" \
14 80
else
whiptail --title "$(translate "NFS Error")" \
--msgbox "$(translate "Failed to connect to") $NFS_SERVER\n\n$(translate "Error:"): $ERROR_MSG" \
12 80
fi
return 1
fi
EXPORTS=$(echo "$EXPORTS_OUTPUT" | tail -n +2 | awk '{print $1}' | grep -v "^$")
if [[ -z "$EXPORTS" ]]; then
whiptail --title "$(translate "No Exports Found")" \
--msgbox "$(translate "No exports found on server") $NFS_SERVER\n\n$(translate "Server response:")\n$(echo "$EXPORTS_OUTPUT" | head -10)\n\n$(translate "You can enter the export path manually.")" \
16 80
NFS_EXPORT=$(whiptail --inputbox "$(translate "Enter NFS export path (e.g., /mnt/shared):")" 10 60 --title "$(translate "Export Path")" 3>&1 1>&2 2>&3)
[[ -z "$NFS_EXPORT" ]] && return 1
return 0
fi
# Build options for whiptail
OPTIONS=()
while IFS= read -r export_line; do
if [[ -n "$export_line" ]]; then
EXPORT_PATH=$(echo "$export_line" | awk '{print $1}')
# Get allowed clients if available
CLIENTS=$(echo "$EXPORTS_OUTPUT" | grep "^$EXPORT_PATH" | awk '{for(i=2;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/[[:space:]]*$//')
if [[ -n "$CLIENTS" ]]; then
OPTIONS+=("$EXPORT_PATH" "$CLIENTS")
else
OPTIONS+=("$EXPORT_PATH" "$(translate "NFS export")")
fi
fi
done <<< "$EXPORTS"
if [[ ${#OPTIONS[@]} -eq 0 ]]; then
whiptail --title "$(translate "No Available Exports")" \
--msgbox "$(translate "No accessible exports found.")\n\n$(translate "You can enter the export path manually.")" \
10 70
NFS_EXPORT=$(whiptail --inputbox "$(translate "Enter NFS export path (e.g., /mnt/shared):")" 10 60 --title "$(translate "Export Path")" 3>&1 1>&2 2>&3)
[[ -n "$NFS_EXPORT" ]] && return 0 || return 1
fi
NFS_EXPORT=$(whiptail --title "$(translate "Select NFS Export")" --menu "$(translate "Choose an export to mount:")" 20 70 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3)
[[ -n "$NFS_EXPORT" ]] && return 0 || return 1
}
select_mount_point() {
while true; do
METHOD=$(whiptail --title "$(translate "Select Mount Point")" --menu "$(translate "Where do you want to mount the NFS export?")" 15 70 3 \
"1" "$(translate "Create new folder in /mnt")" \
"2" "$(translate "Select from existing folders in /mnt")" \
"3" "$(translate "Enter custom path")" 3>&1 1>&2 2>&3)
case "$METHOD" in
1)
# Create default name from server and export
EXPORT_NAME=$(basename "$NFS_EXPORT")
DEFAULT_NAME="nfs_${NFS_SERVER}_${EXPORT_NAME}"
FOLDER_NAME=$(whiptail --inputbox "$(translate "Enter new folder name:")" 10 60 "$DEFAULT_NAME" --title "$(translate "New Folder in /mnt")" 3>&1 1>&2 2>&3)
if [[ -n "$FOLDER_NAME" ]]; then
MOUNT_POINT="/mnt/$FOLDER_NAME"
return 0
fi
;;
2)
DIRS=$(pct exec "$CTID" -- find /mnt -maxdepth 1 -mindepth 1 -type d 2>/dev/null)
if [[ -z "$DIRS" ]]; then
whiptail --title "$(translate "No Folders")" --msgbox "$(translate "No folders found in /mnt. Please create a new folder.")" 8 60
continue
fi
OPTIONS=()
while IFS= read -r dir; do
if [[ -n "$dir" ]]; then
name=$(basename "$dir")
if pct exec "$CTID" -- [ "$(ls -A "$dir" 2>/dev/null | wc -l)" -eq 0 ]; then
status="$(translate "Empty")"
else
status="$(translate "Contains files")"
fi
OPTIONS+=("$dir" "$name ($status)")
fi
done <<< "$DIRS"
MOUNT_POINT=$(whiptail --title "$(translate "Select Existing Folder")" --menu "$(translate "Choose a folder to mount the export:")" 20 80 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3)
if [[ -n "$MOUNT_POINT" ]]; then
if pct exec "$CTID" -- [ "$(ls -A "$MOUNT_POINT" 2>/dev/null | wc -l)" -gt 0 ]; then
FILE_COUNT=$(pct exec "$CTID" -- ls -A "$MOUNT_POINT" 2>/dev/null | wc -l)
if ! whiptail --yesno "$(translate "WARNING: The selected directory is not empty!")\n\n$(translate "Directory:"): $MOUNT_POINT\n$(translate "Contains:"): $FILE_COUNT $(translate "files/folders")\n\n$(translate "Mounting here will hide existing files until unmounted.")\n\n$(translate "Do you want to continue?")" 14 70 --title "$(translate "Directory Not Empty")"; then
continue
fi
fi
return 0
fi
;;
3)
MOUNT_POINT=$(whiptail --inputbox "$(translate "Enter full path for mount point:")" 10 70 "/mnt/nfs_share" --title "$(translate "Custom Path")" 3>&1 1>&2 2>&3)
if [[ -n "$MOUNT_POINT" ]]; then
return 0
fi
;;
*)
return 1
;;
esac
done
}
configure_mount_options() {
MOUNT_TYPE=$(whiptail --title "$(translate "Mount Options")" --menu "$(translate "Select mount configuration:")" 15 70 4 \
"1" "$(translate "Default options read/write")" \
"2" "$(translate "Read-only mount")" \
"3" "$(translate "Custom options")" 3>&1 1>&2 2>&3)
case "$MOUNT_TYPE" in
1)
MOUNT_OPTIONS="rw,hard,rsize=1048576,wsize=1048576,timeo=600,retrans=2"
;;
2)
MOUNT_OPTIONS="ro,hard,rsize=1048576,wsize=1048576,timeo=600,retrans=2"
;;
3)
MOUNT_OPTIONS=$(whiptail --inputbox "$(translate "Enter custom mount options:")" \
10 70 "rw,hard,rsize=1048576,wsize=1048576,timeo=600,retrans=2" \
--title "$(translate "Custom Options")" 3>&1 1>&2 2>&3)
[[ $? -ne 0 ]] && return 1
[[ -z "$MOUNT_OPTIONS" ]] && MOUNT_OPTIONS="rw,hard"
;;
*)
MOUNT_OPTIONS="rw,hard,rsize=65536,wsize=65536,timeo=600,retrans=2"
;;
esac
if whiptail --yesno "$(translate "Do you want to make this mount permanent?")\n\n$(translate "This will add the mount to /etc/fstab so it persists after reboot.")" 10 70 --title "$(translate "Permanent Mount")"; then
PERMANENT_MOUNT=true
else
PERMANENT_MOUNT=false
fi
}
validate_export_exists() {
local server="$1"
local export="$2"
VALIDATION_OUTPUT=$(pct exec "$CTID" -- showmount -e "$server" 2>/dev/null | grep "^$export[[:space:]]")
if [[ -n "$VALIDATION_OUTPUT" ]]; then
return 0
else
show_proxmenux_logo
echo -e
msg_error "$(translate "Export not found on server:") $export"
msg_success "$(translate "Press Enter to return to menu...")"
read -r
return 1
fi
}
mount_nfs_share() {
# Step 0: Install NFS client first
install_nfs_client || return
# Step 1: Select server
select_nfs_server || return
show_proxmenux_logo
msg_title "$(translate "Mount NFS Share on Host")"
msg_ok "$(translate "NFS server Selected")"
# Step 2: Select export
select_nfs_export || return
msg_ok "$(translate "NFS export Selected")"
# Step 2.5: Validate export exists
if ! validate_export_exists "$NFS_SERVER" "$NFS_EXPORT"; then
echo -e ""
msg_error "$(translate "Cannot proceed with invalid export path.")"
msg_success "$(translate "Press Enter to return to menu...")"
read -r
return
fi
# Step 3: Select mount point
select_mount_point || return
# Step 4: Configure mount options
configure_mount_options || return
if ! pct exec "$CTID" -- test -d "$MOUNT_POINT"; then
if pct exec "$CTID" -- mkdir -p "$MOUNT_POINT"; then
msg_ok "$(translate "Mount point created.")"
else
msg_error "$(translate "Failed to create mount point.")"
return 1
fi
fi
if pct exec "$CTID" -- mount | grep -q "$MOUNT_POINT"; then
msg_warn "$(translate "Something is already mounted at") $MOUNT_POINT"
if ! whiptail --yesno "$(translate "Do you want to unmount it first?")" 8 60 --title "$(translate "Already Mounted")"; then
return
fi
pct exec "$CTID" -- umount "$MOUNT_POINT" 2>/dev/null || true
fi
# Build mount command
NFS_PATH="$NFS_SERVER:$NFS_EXPORT"
msg_info "$(translate "Testing NFS connection...")"
if pct exec "$CTID" -- mount -t nfs -o "$MOUNT_OPTIONS" "$NFS_PATH" "$MOUNT_POINT"; then
msg_ok "$(translate "NFS share mounted successfully!")"
# Test write access
if pct exec "$CTID" -- touch "$MOUNT_POINT/.test_write" 2>/dev/null; then
pct exec "$CTID" -- rm "$MOUNT_POINT/.test_write" 2>/dev/null
msg_ok "$(translate "Write access confirmed.")"
else
msg_warn "$(translate "Read-only access (or no write permissions).")"
fi
# Add to fstab if permanent
if [[ "$PERMANENT_MOUNT" == "true" ]]; then
pct exec "$CTID" -- sed -i "\|$MOUNT_POINT|d" /etc/fstab
FSTAB_ENTRY="$NFS_PATH $MOUNT_POINT nfs $MOUNT_OPTIONS 0 0"
pct exec "$CTID" -- bash -c "echo '$FSTAB_ENTRY' >> /etc/fstab"
msg_ok "$(translate "Added to /etc/fstab for permanent mounting.")"
fi
# Show mount information
echo -e ""
echo -e "${TAB}${BOLD}$(translate "Mount Information:")${CL}"
echo -e "${TAB}${BGN}$(translate "Server:")${CL} ${BL}$NFS_SERVER${CL}"
echo -e "${TAB}${BGN}$(translate "Export:")${CL} ${BL}$NFS_EXPORT${CL}"
echo -e "${TAB}${BGN}$(translate "Mount Point:")${CL} ${BL}$MOUNT_POINT${CL}"
echo -e "${TAB}${BGN}$(translate "Options:")${CL} ${BL}$MOUNT_OPTIONS${CL}"
echo -e "${TAB}${BGN}$(translate "Permanent:")${CL} ${BL}$PERMANENT_MOUNT${CL}"
else
msg_error "$(translate "Failed to mount NFS share.")"
echo -e "${TAB}$(translate "Please check:")"
echo -e "${TAB}$(translate "Server is accessible:"): $NFS_SERVER"
echo -e "${TAB}$(translate "Export exists:"): $NFS_EXPORT"
echo -e "${TAB}$(translate "Network connectivity")"
echo -e "${TAB}$(translate "NFS server is running")"
echo -e "${TAB}$(translate "Export permissions allow access")"
fi
echo -e ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
view_nfs_mounts() {
show_proxmenux_logo
msg_title "$(translate "Current NFS Mounts")"
echo -e "$(translate "NFS mounts in CT") $CTID:"
echo "=================================="
# Show currently mounted NFS shares - VERSIÓN CORREGIDA
CURRENT_MOUNTS=$(pct exec "$CTID" -- mount | grep -E "type nfs|:.*on.*nfs" 2>/dev/null || true)
if [[ -n "$CURRENT_MOUNTS" ]]; then
echo -e "${BOLD}$(translate "Currently Mounted:")${CL}"
echo "$CURRENT_MOUNTS"
echo ""
else
# Verificar si hay montajes NFS en fstab que estén activos
ACTIVE_NFS_MOUNTS=$(pct exec "$CTID" -- grep "nfs" /etc/fstab 2>/dev/null | grep -v "^#" | while read -r line; do
MOUNT_POINT=$(echo "$line" | awk '{print $2}')
if pct exec "$CTID" -- mount | grep -q "$MOUNT_POINT"; then
echo "$MOUNT_POINT"
fi
done)
if [[ -n "$ACTIVE_NFS_MOUNTS" ]]; then
echo -e "${BOLD}$(translate "Currently Mounted:")${CL}"
while IFS= read -r mount_point; do
if [[ -n "$mount_point" ]]; then
MOUNT_INFO=$(pct exec "$CTID" -- mount | grep "$mount_point")
echo "$MOUNT_INFO"
fi
done <<< "$ACTIVE_NFS_MOUNTS"
echo ""
else
echo "$(translate "No NFS shares currently mounted.")"
echo ""
fi
fi
# Show fstab entries
FSTAB_NFS=$(pct exec "$CTID" -- grep "nfs" /etc/fstab 2>/dev/null || true)
if [[ -n "$FSTAB_NFS" ]]; then
echo -e "${BOLD}$(translate "Permanent Mounts (fstab):")${CL}"
echo "$FSTAB_NFS"
echo ""
echo -e "${TAB}${BOLD}$(translate "Mount Details:")${CL}"
while IFS= read -r fstab_line; do
if [[ -n "$fstab_line" && ! "$fstab_line" =~ ^# ]]; then
NFS_PATH=$(echo "$fstab_line" | awk '{print $1}')
MOUNT_POINT=$(echo "$fstab_line" | awk '{print $2}')
OPTIONS=$(echo "$fstab_line" | awk '{print $4}')
# Extract server and export from NFS path
SERVER=$(echo "$NFS_PATH" | cut -d: -f1)
EXPORT=$(echo "$NFS_PATH" | cut -d: -f2)
echo -e "${TAB}${BGN}$(translate "Server:")${CL} ${BL}$SERVER${CL}"
echo -e "${TAB}${BGN}$(translate "Export:")${CL} ${BL}$EXPORT${CL}"
echo -e "${TAB}${BGN}$(translate "Mount Point:")${CL} ${BL}$MOUNT_POINT${CL}"
echo -e "${TAB}${BGN}$(translate "Options:")${CL} ${BL}$OPTIONS${CL}"
# Check if currently mounted
if pct exec "$CTID" -- mount | grep -q "$MOUNT_POINT"; then
echo -e "${TAB}${BGN}$(translate "Status:")${CL} ${GN}$(translate "Mounted")${CL}"
else
echo -e "${TAB}${BGN}$(translate "Status:")${CL} ${RD}$(translate "Not Mounted")${CL}"
fi
echo ""
fi
done <<< "$FSTAB_NFS"
else
echo "$(translate "No permanent NFS mounts configured.")"
fi
echo ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
unmount_nfs_share() {
# Get current NFS mounts
MOUNTS=$(pct exec "$CTID" -- mount | grep -E "type nfs|:.*on.*nfs" | awk '{print $3}' | sort -u || true)
FSTAB_MOUNTS=$(pct exec "$CTID" -- grep -E "nfs" /etc/fstab 2>/dev/null | grep -v "^#" | awk '{print $2}' | sort -u || true)
# Combine and deduplicate
ALL_MOUNTS=$(echo -e "$MOUNTS\n$FSTAB_MOUNTS" | sort -u | grep -v "^$" || true)
if [[ -z "$ALL_MOUNTS" ]]; then
dialog --backtitle "ProxMenux" --title "$(translate "No Mounts")" --msgbox "\n$(translate "No NFS mounts found.")" 8 50
return
fi
OPTIONS=()
while IFS= read -r mount_point; do
[[ -n "$mount_point" ]] && OPTIONS+=("$mount_point" "")
done <<< "$ALL_MOUNTS"
SELECTED_MOUNT=$(dialog --backtitle "ProxMenux" --title "$(translate "Unmount NFS Share")" --menu "$(translate "Select mount point to unmount:")" 20 80 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3)
[[ -z "$SELECTED_MOUNT" ]] && return
if whiptail --yesno "$(translate "Are you sure you want to unmount this NFS share?")\n\n$(translate "Mount Point:"): $SELECTED_MOUNT\n\n$(translate "This will remove the mount from /etc/fstab.")" 12 80 --title "$(translate "Confirm Unmount")"; then
show_proxmenux_logo
msg_title "$(translate "Unmount NFS Share")"
# Remove from fstab
pct exec "$CTID" -- sed -i "\|[[:space:]]$SELECTED_MOUNT[[:space:]]|d" /etc/fstab
msg_ok "$(translate "Removed from /etc/fstab.")"
echo -e ""
msg_ok "$(translate "NFS share unmount successfully. Reboot LXC required to take effect.")"
fi
echo -e ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
test_nfs_connectivity() {
show_proxmenux_logo
msg_title "$(translate "Test NFS Connectivity")"
echo -e "$(translate "NFS Client Status in CT") $CTID:"
echo "=================================="
# Check if NFS client is installed
if pct exec "$CTID" -- dpkg -s nfs-common &>/dev/null; then
echo "$(translate "NFS Client: INSTALLED")"
# Check showmount
if pct exec "$CTID" -- which showmount >/dev/null 2>&1; then
echo "$(translate "NFS Client Tools: AVAILABLE")"
else
echo "$(translate "NFS Client Tools: NOT AVAILABLE")"
fi
# Check rpcbind service
if pct exec "$CTID" -- systemctl is-active --quiet rpcbind 2>/dev/null; then
echo "$(translate "RPC Bind Service: RUNNING")"
else
echo "$(translate "RPC Bind Service: STOPPED")"
msg_warn "$(translate "Starting rpcbind service...")"
pct exec "$CTID" -- systemctl start rpcbind 2>/dev/null || true
fi
echo ""
echo "$(translate "Current NFS mounts:")"
CURRENT_MOUNTS=$(pct exec "$CTID" -- mount | grep -E "type nfs|:.*on.*nfs" 2>/dev/null || true)
if [[ -n "$CURRENT_MOUNTS" ]]; then
echo "$CURRENT_MOUNTS"
else
# Check for active NFS mounts from fstab
ACTIVE_NFS_MOUNTS=$(pct exec "$CTID" -- grep "nfs" /etc/fstab 2>/dev/null | grep -v "^#" | while read -r line; do
MOUNT_POINT=$(echo "$line" | awk '{print $2}')
if pct exec "$CTID" -- mount | grep -q "$MOUNT_POINT"; then
pct exec "$CTID" -- mount | grep "$MOUNT_POINT"
fi
done)
if [[ -n "$ACTIVE_NFS_MOUNTS" ]]; then
echo "$ACTIVE_NFS_MOUNTS"
else
echo "$(translate "No NFS mounts active.")"
fi
fi
echo ""
echo "$(translate "Testing network connectivity...")"
# Test connectivity to known NFS servers from fstab
FSTAB_SERVERS=$(pct exec "$CTID" -- grep "nfs" /etc/fstab 2>/dev/null | awk '{print $1}' | cut -d: -f1 | sort -u || true)
if [[ -n "$FSTAB_SERVERS" ]]; then
while IFS= read -r server; do
if [[ -n "$server" ]]; then
echo -n "$(translate "Testing") $server: "
if pct exec "$CTID" -- ping -c 1 -W 2 "$server" >/dev/null 2>&1; then
echo -e "\033[1;92m$(translate "Reachable")\033[0m"
# Test NFS port
echo -n " $(translate "NFS port 2049"): "
if pct exec "$CTID" -- nc -z -w 2 "$server" 2049 2>/dev/null; then
echo -e "\033[1;92m$(translate "Open")\033[0m"
else
echo -e "\033[1;91m$(translate "Closed")\033[0m"
fi
# Try to list exports
echo -n " $(translate "Export list test"): "
if pct exec "$CTID" -- showmount -e "$server" >/dev/null 2>&1; then
echo -e "\033[1;92m$(translate "Available")\033[0m"
else
echo -e "\033[1;91m$(translate "Failed")\033[0m"
fi
else
echo -e "\033[1;91m$(translate "Unreachable")\033[0m"
fi
fi
done <<< "$FSTAB_SERVERS"
else
echo "$(translate "No NFS servers configured to test.")"
fi
else
echo "$(translate "NFS Client: NOT INSTALLED")"
echo ""
echo "$(translate "Run 'Mount NFS Share' to install NFS client automatically.")"
fi
echo ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
# === Main Menu ===
while true; do
CHOICE=$(dialog --backtitle "ProxMenux" --title "$(translate "NFS Client Manager - CT") $CTID" \
--menu "$(translate "Choose an option:")" 20 70 12 \
"1" "$(translate "Mount NFS Share")" \
"2" "$(translate "View Current Mounts")" \
"3" "$(translate "Unmount NFS Share")" \
"4" "$(translate "Test NFS Connectivity")" \
"5" "$(translate "Exit")" \
3>&1 1>&2 2>&3)
RETVAL=$?
if [[ $RETVAL -ne 0 ]]; then
exit 0
fi
case $CHOICE in
1) mount_nfs_share ;;
2) view_nfs_mounts ;;
3) unmount_nfs_share ;;
4) test_nfs_connectivity ;;
5) exit 0 ;;
*) exit 0 ;;
esac
done

852
scripts/share/nfs_host.sh Normal file
View File

@@ -0,0 +1,852 @@
#!/bin/bash
# ==========================================================
# ProxMenu Host - NFS Host Manager for Proxmox Host
# ==========================================================
# Based on ProxMenux by MacRimi
# ==========================================================
# Description:
# This script allows you to manage NFS client mounts on Proxmox Host:
# - Mount external NFS shares on the host
# - Configure permanent mounts
# - Auto-discover NFS servers
# - Integrate with Proxmox storage system
# ==========================================================
# 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
# Load common share functions
SHARE_COMMON_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main/scripts/global/share-common.func"
if ! source <(curl -s "$SHARE_COMMON_URL" 2>/dev/null); then
msg_warn "$(translate "Could not load shared functions. Using fallback methods.")"
SHARE_COMMON_LOADED=false
else
SHARE_COMMON_LOADED=true
fi
discover_nfs_servers() {
show_proxmenux_logo
msg_title "$(translate "Mount NFS Share on Host")"
msg_info "$(translate "Scanning network for NFS servers...")"
HOST_IP=$(hostname -I | awk '{print $1}')
NETWORK=$(echo "$HOST_IP" | cut -d. -f1-3).0/24
if ! which nmap >/dev/null 2>&1; then
apt-get install -y nmap &>/dev/null
fi
SERVERS=$(nmap -p 2049 --open "$NETWORK" 2>/dev/null | grep -B 4 "2049/tcp open" | grep "Nmap scan report" | awk '{print $5}' | sort -u || true)
if [[ -z "$SERVERS" ]]; then
cleanup
dialog --clear --title "$(translate "No Servers Found")" --msgbox "$(translate "No NFS servers found on the network.")\n\n$(translate "You can add servers manually.")" 10 60
return 1
fi
OPTIONS=()
while IFS= read -r server; do
if [[ -n "$server" ]]; then
EXPORTS_COUNT=$(showmount -e "$server" 2>/dev/null | tail -n +2 | wc -l || echo "0")
SERVER_INFO="NFS Server ($EXPORTS_COUNT exports)"
OPTIONS+=("$server" "$SERVER_INFO")
fi
done <<< "$SERVERS"
if [[ ${#OPTIONS[@]} -eq 0 ]]; then
cleanup
dialog --clear --title "$(translate "No Valid Servers")" --msgbox "$(translate "No accessible NFS servers found.")" 8 50
return 1
fi
cleanup
NFS_SERVER=$(whiptail --backtitle "ProxMenux" --title "$(translate "Select NFS Server")" --menu "$(translate "Choose an NFS server:")" 20 80 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3)
[[ -n "$NFS_SERVER" ]] && return 0 || return 1
}
select_nfs_server() {
METHOD=$(dialog --backtitle "ProxMenux" --title "$(translate "NFS Server Selection")" --menu "$(translate "How do you want to select the NFS server?")" 15 70 3 \
"auto" "$(translate "Auto-discover servers on network")" \
"manual" "$(translate "Enter server IP/hostname manually")" \
"recent" "$(translate "Select from recent servers")" 3>&1 1>&2 2>&3)
case "$METHOD" in
auto)
discover_nfs_servers || return 1
;;
manual)
clear
NFS_SERVER=$(whiptail --inputbox "$(translate "Enter NFS server IP or hostname:")" 10 60 --title "$(translate "NFS Server")" 3>&1 1>&2 2>&3)
[[ -z "$NFS_SERVER" ]] && return 1
;;
recent)
clear
RECENT=$(grep "nfs" /etc/fstab 2>/dev/null | awk '{print $1}' | cut -d: -f1 | sort -u || true)
if [[ -z "$RECENT" ]]; then
dialog --title "$(translate "No Recent Servers")" --msgbox "\n$(translate "No recent NFS servers found.")" 8 50
return 1
fi
OPTIONS=()
while IFS= read -r server; do
[[ -n "$server" ]] && OPTIONS+=("$server" "$(translate "Recent NFS server")")
done <<< "$RECENT"
NFS_SERVER=$(whiptail --title "$(translate "Recent NFS Servers")" --menu "$(translate "Choose a recent server:")" 20 70 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3)
[[ -n "$NFS_SERVER" ]] && return 0 || return 1
;;
*)
return 1
;;
esac
return 0
}
select_nfs_export() {
if ! which showmount >/dev/null 2>&1; then
whiptail --title "$(translate "NFS Client Error")" \
--msgbox "$(translate "showmount command is not working properly.")\n\n$(translate "Please check the installation.")" \
10 60
return 1
fi
if ! ping -c 1 -W 3 "$NFS_SERVER" >/dev/null 2>&1; then
whiptail --title "$(translate "Connection Error")" \
--msgbox "$(translate "Cannot reach server") $NFS_SERVER\n\n$(translate "Please check:")\n• $(translate "Server IP/hostname is correct")\n• $(translate "Network connectivity")\n• $(translate "Server is online")" \
12 70
return 1
fi
if ! nc -z -w 3 "$NFS_SERVER" 2049 2>/dev/null; then
whiptail --title "$(translate "NFS Port Error")" \
--msgbox "$(translate "NFS port (2049) is not accessible on") $NFS_SERVER\n\n$(translate "Please check:")\n• $(translate "NFS server is running")\n• $(translate "Firewall settings")\n• $(translate "NFS service is enabled")" \
12 70
return 1
fi
EXPORTS_OUTPUT=$(showmount -e "$NFS_SERVER" 2>&1)
EXPORTS_RESULT=$?
if [[ $EXPORTS_RESULT -ne 0 ]]; then
ERROR_MSG=$(echo "$EXPORTS_OUTPUT" | grep -i "error\|failed\|denied" | head -1)
if echo "$EXPORTS_OUTPUT" | grep -qi "connection refused\|network unreachable"; then
whiptail --title "$(translate "Network Error")" \
--msgbox "$(translate "Network connection failed to") $NFS_SERVER\n\n$(translate "Error:"): $ERROR_MSG\n\n$(translate "Please check:")\n• $(translate "Server is running")\n• $(translate "Network connectivity")\n• $(translate "Firewall settings")" \
14 80
else
whiptail --title "$(translate "NFS Error")" \
--msgbox "$(translate "Failed to connect to") $NFS_SERVER\n\n$(translate "Error:"): $ERROR_MSG" \
12 80
fi
return 1
fi
EXPORTS=$(echo "$EXPORTS_OUTPUT" | tail -n +2 | awk '{print $1}' | grep -v "^$")
if [[ -z "$EXPORTS" ]]; then
whiptail --title "$(translate "No Exports Found")" \
--msgbox "$(translate "No exports found on server") $NFS_SERVER\n\n$(translate "Server response:")\n$(echo "$EXPORTS_OUTPUT" | head -10)\n\n$(translate "You can enter the export path manually.")" \
16 80
NFS_EXPORT=$(whiptail --inputbox "$(translate "Enter NFS export path (e.g., /mnt/shared):")" 10 60 --title "$(translate "Export Path")" 3>&1 1>&2 2>&3)
[[ -z "$NFS_EXPORT" ]] && return 1
return 0
fi
OPTIONS=()
while IFS= read -r export_line; do
if [[ -n "$export_line" ]]; then
EXPORT_PATH=$(echo "$export_line" | awk '{print $1}')
CLIENTS=$(echo "$EXPORTS_OUTPUT" | grep "^$EXPORT_PATH" | awk '{for(i=2;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/[[:space:]]*$//')
if [[ -n "$CLIENTS" ]]; then
OPTIONS+=("$EXPORT_PATH" "$CLIENTS")
else
OPTIONS+=("$EXPORT_PATH" "$(translate "NFS export")")
fi
fi
done <<< "$EXPORTS"
if [[ ${#OPTIONS[@]} -eq 0 ]]; then
whiptail --title "$(translate "No Available Exports")" \
--msgbox "$(translate "No accessible exports found.")\n\n$(translate "You can enter the export path manually.")" \
10 70
NFS_EXPORT=$(whiptail --inputbox "$(translate "Enter NFS export path (e.g., /mnt/shared):")" 10 60 --title "$(translate "Export Path")" 3>&1 1>&2 2>&3)
[[ -n "$NFS_EXPORT" ]] && return 0 || return 1
fi
NFS_EXPORT=$(whiptail --title "$(translate "Select NFS Export")" --menu "$(translate "Choose an export to mount:")" 20 70 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3)
[[ -n "$NFS_EXPORT" ]] && return 0 || return 1
}
select_host_mount_point() {
local export_name=$(basename "$NFS_EXPORT")
local default_path="/mnt/shared_nfs_${export_name}"
MOUNT_POINT=$(pmx_select_host_mount_point "$(translate "NFS Mount Point")" "$default_path")
[[ -n "$MOUNT_POINT" ]] && return 0 || return 1
}
configure_host_mount_options() {
MOUNT_TYPE=$(whiptail --title "$(translate "Mount Options")" --menu "$(translate "Select mount configuration:")" 15 70 4 \
"1" "$(translate "Default options read/write")" \
"2" "$(translate "Read-only mount")" \
"3" "$(translate "Enter custom options")" 3>&1 1>&2 2>&3)
[[ $? -ne 0 ]] && return 1
case "$MOUNT_TYPE" in
1)
MOUNT_OPTIONS="rw,hard,nofail,rsize=131072,wsize=131072,timeo=600,retrans=2"
;;
2)
MOUNT_OPTIONS="ro,hard,nofail,rsize=131072,wsize=131072,timeo=600,retrans=2"
;;
3)
MOUNT_OPTIONS=$(whiptail --inputbox "$(translate "Enter custom mount options:")" \
10 70 "rw,hard,nofail,rsize=131072,wsize=131072,timeo=600,retrans=2" \
--title "$(translate "Custom Options")" 3>&1 1>&2 2>&3)
[[ $? -ne 0 ]] && return 1
[[ -z "$MOUNT_OPTIONS" ]] && MOUNT_OPTIONS="rw,hard,nofail"
;;
*)
MOUNT_OPTIONS="rw,hard,nofail,rsize=131072,wsize=131072,timeo=600,retrans=2"
;;
esac
if whiptail --yesno "$(translate "Do you want to make this mount permanent?")\n\n$(translate "This will add the mount to /etc/fstab so it persists after reboot.")" 10 70 --title "$(translate "Permanent Mount")"; then
PERMANENT_MOUNT=true
else
if [[ $? -eq 1 ]]; then
PERMANENT_MOUNT=false
else
return 1
fi
fi
TEMP_MOUNT="/tmp/nfs_test_$$"
mkdir -p "$TEMP_MOUNT" 2>/dev/null
NFS_PATH="$NFS_SERVER:$NFS_EXPORT"
if timeout 10 mount -t nfs -o ro,soft,timeo=5 "$NFS_PATH" "$TEMP_MOUNT" 2>/dev/null; then
umount "$TEMP_MOUNT" 2>/dev/null || true
rmdir "$TEMP_MOUNT" 2>/dev/null || true
msg_ok "$(translate "NFS export is accessible")"
if whiptail --yesno "$(translate "Do you want to add this as Proxmox storage?")\n\n$(translate "This will make the NFS share available as storage in Proxmox web interface.")" 10 70 --title "$(translate "Proxmox Storage")"; then
PROXMOX_STORAGE=true
STORAGE_ID=$(whiptail --inputbox "$(translate "Enter storage ID for Proxmox:")" 10 60 "nfs-$(echo $NFS_SERVER | tr '.' '-')" --title "$(translate "Storage ID")" 3>&1 1>&2 2>&3)
STORAGE_ID_RESULT=$?
if [[ $STORAGE_ID_RESULT -ne 0 ]]; then
if whiptail --yesno "$(translate "Storage ID input was cancelled.")\n\n$(translate "Do you want to continue without Proxmox storage integration?")" 10 70 --title "$(translate "Continue Without Storage")"; then
PROXMOX_STORAGE=false
else
return 1
fi
else
[[ -z "$STORAGE_ID" ]] && STORAGE_ID="nfs-$(echo $NFS_SERVER | tr '.' '-')"
fi
else
DIALOG_RESULT=$?
if [[ $DIALOG_RESULT -eq 1 ]]; then
PROXMOX_STORAGE=false
else
return 1
fi
fi
else
rmdir "$TEMP_MOUNT" 2>/dev/null || true
msg_warn "$(translate "NFS export accessibility test failed")"
if whiptail --yesno "$(translate "The NFS export could not be validated for accessibility.")\n\n$(translate "This might be due to:")\n• $(translate "Network connectivity issues")\n• $(translate "Export permission restrictions")\n• $(translate "Firewall blocking access")\n\n$(translate "Do you want to continue mounting anyway?")\n$(translate "(Proxmox storage integration will be skipped)")" 16 80 --title "$(translate "Export Validation Failed")"; then
PROXMOX_STORAGE=false
msg_info2 "$(translate "Continuing without Proxmox storage integration due to accessibility issues.")"
sleep 2
else
return 1
fi
fi
return 0
}
validate_host_export_exists() {
local server="$1"
local export="$2"
VALIDATION_OUTPUT=$(showmount -e "$server" 2>/dev/null | grep "^$export[[:space:]]")
if [[ -n "$VALIDATION_OUTPUT" ]]; then
return 0
else
show_proxmenux_logo
echo -e
msg_error "$(translate "Export not found on server:") $export"
return 1
fi
}
add_proxmox_nfs_storage() {
local storage_id="$1"
local server="$2"
local export="$3"
local content="${4:-backup,iso,vztmpl}"
msg_info "$(translate "Starting Proxmox storage integration...")"
if ! command -v pvesm >/dev/null 2>&1; then
show_proxmenux_logo
msg_error "$(translate "pvesm command not found. This should not happen on Proxmox.")"
echo "Press Enter to continue..."
read -r
return 1
fi
msg_ok "$(translate "pvesm command found")"
# Check if storage ID already exists
if pvesm status "$storage_id" >/dev/null 2>&1; then
msg_warn "$(translate "Storage ID already exists:") $storage_id"
if ! whiptail --yesno "$(translate "Storage ID already exists. Do you want to remove and recreate it?")" 8 60 --title "$(translate "Storage Exists")"; then
return 0
fi
pvesm remove "$storage_id" 2>/dev/null || true
fi
msg_ok "$(translate "Storage ID is available")"
# Let Proxmox handle NFS version negotiation automatically
if pvesm_output=$(pvesm add nfs "$storage_id" \
--server "$server" \
--export "$export" \
--content "$content" 2>&1); then
msg_ok "$(translate "NFS storage added successfully!")"
# Get the actual NFS version that Proxmox negotiated
local nfs_version="Auto-negotiated"
if pvesm config "$storage_id" 2>/dev/null | grep -q "options.*vers="; then
nfs_version="v$(pvesm config "$storage_id" | grep "options" | grep -o "vers=[0-9.]*" | cut -d= -f2)"
fi
echo -e ""
echo -e "${TAB}${BGN}$(translate "Storage ID:")${CL} ${BL}$storage_id${CL}"
echo -e "${TAB}${BGN}$(translate "Server:")${CL} ${BL}$server${CL}"
echo -e "${TAB}${BGN}$(translate "Export:")${CL} ${BL}$export${CL}"
echo -e "${TAB}${BGN}$(translate "Content Types:")${CL} ${BL}$content${CL}"
echo -e "${TAB}${BGN}$(translate "NFS Version:")${CL} ${BL}$nfs_version${CL}"
echo -e ""
msg_ok "$(translate "Storage is now available in Proxmox web interface under Datacenter > Storage")"
return 0
else
msg_error "$(translate "Failed to add NFS storage to Proxmox.")"
echo "$(translate "Error details:"): $pvesm_output"
msg_warn "$(translate "The NFS share is still mounted, but not added as Proxmox storage.")"
msg_info2 "$(translate "You can add it manually through:")"
echo -e "${TAB}$(translate "Proxmox web interface: Datacenter > Storage > Add > NFS")"
echo -e "${TAB}$(translate "Command line:"): pvesm add nfs $storage_id --server $server --export $export --content backup,iso,vztmpl"
return 1
fi
}
prepare_host_directory() {
local mount_point="$1"
if [[ "$SHARE_COMMON_LOADED" == "true" ]]; then
# Use common functions for advanced directory preparation
local group_name
group_name=$(pmx_choose_or_create_group "sharedfiles")
if [[ -n "$group_name" ]]; then
local host_gid
host_gid=$(pmx_ensure_host_group "$group_name")
if [[ -n "$host_gid" ]]; then
pmx_prepare_host_shared_dir "$mount_point" "$group_name"
pmx_share_map_set "$mount_point" "$group_name"
msg_ok "$(translate "Directory prepared with shared group:") $group_name (GID: $host_gid)"
return 0
fi
fi
msg_warn "$(translate "Failed to use shared functions, using basic directory creation.")"
fi
# Fallback: basic directory creation
if ! test -d "$mount_point"; then
if mkdir -p "$mount_point"; then
msg_ok "$(translate "Mount point created on host.")"
return 0
else
msg_error "$(translate "Failed to create mount point on host.")"
return 1
fi
fi
return 0
}
mount_host_nfs_share() {
if ! which showmount >/dev/null 2>&1; then
msg_error "$(translate "NFS client tools not found. Please check Proxmox installation.")"
return 1
fi
# Step 1:
select_nfs_server || return
# Step 2:
select_nfs_export || return
# Step 2.5:
if ! validate_host_export_exists "$NFS_SERVER" "$NFS_EXPORT"; then
echo -e ""
msg_error "$(translate "Cannot proceed with invalid export path.")"
msg_success "$(translate "Press Enter to return to menu...")"
read -r
return
fi
# Step 3:
select_host_mount_point || return
# Step 4:
configure_host_mount_options || return
show_proxmenux_logo
msg_title "$(translate "Mount NFS Share on Host")"
msg_ok "$(translate "NFS server selected")"
prepare_host_directory "$MOUNT_POINT" || return 1
if mount | grep -q "$MOUNT_POINT"; then
msg_warn "$(translate "Something is already mounted at") $MOUNT_POINT"
if ! whiptail --yesno "$(translate "Do you want to unmount it first?")" 8 60 --title "$(translate "Already Mounted")"; then
return
fi
umount "$MOUNT_POINT" 2>/dev/null || true
fi
NFS_PATH="$NFS_SERVER:$NFS_EXPORT"
if mount -t nfs -o "$MOUNT_OPTIONS" "$NFS_PATH" "$MOUNT_POINT" > /dev/null 2>&1; then
msg_ok "$(translate "NFS share mounted successfully on host!")"
if touch "$MOUNT_POINT/.test_write" 2>/dev/null; then
rm "$MOUNT_POINT/.test_write" 2>/dev/null
msg_ok "$(translate "Write access confirmed.")"
else
msg_warn "$(translate "Read-only access (or no write permissions).")"
fi
if [[ "$PERMANENT_MOUNT" == "true" ]]; then
sed -i "\|$MOUNT_POINT|d" /etc/fstab
FSTAB_ENTRY="$NFS_PATH $MOUNT_POINT nfs $MOUNT_OPTIONS 0 0"
echo "$FSTAB_ENTRY" >> /etc/fstab
msg_ok "$(translate "Added to /etc/fstab for permanent mounting.")"
msg_info "$(translate "Reloading systemd configuration...")"
systemctl daemon-reload 2>/dev/null || true
msg_ok "$(translate "Systemd configuration reloaded.")"
fi
if [[ "$PROXMOX_STORAGE" == "true" ]]; then
add_proxmox_nfs_storage "$STORAGE_ID" "$NFS_SERVER" "$NFS_EXPORT" "$MOUNT_CONTENT"
fi
echo -e ""
echo -e "${TAB}${BOLD}$(translate "Host Mount Information:")${CL}"
echo -e "${TAB}${BGN}$(translate "Server:")${CL} ${BL}$NFS_SERVER${CL}"
echo -e "${TAB}${BGN}$(translate "Export:")${CL} ${BL}$NFS_EXPORT${CL}"
echo -e "${TAB}${BGN}$(translate "Host Mount Point:")${CL} ${BL}$MOUNT_POINT${CL}"
echo -e "${TAB}${BGN}$(translate "Options:")${CL} ${BL}$MOUNT_OPTIONS${CL}"
echo -e "${TAB}${BGN}$(translate "Permanent:")${CL} ${BL}$PERMANENT_MOUNT${CL}"
if [[ "$PROXMOX_STORAGE" == "true" ]]; then
echo -e "${TAB}${BGN}$(translate "Proxmox Storage ID:")${CL} ${BL}$STORAGE_ID${CL}"
fi
else
msg_error "$(translate "Failed to mount NFS share on host.")"
echo -e "${TAB}$(translate "Please check:")"
echo -e "${TAB}$(translate "Server is accessible:"): $NFS_SERVER"
echo -e "${TAB}$(translate "Export exists:"): $NFS_EXPORT"
echo -e "${TAB}$(translate "Network connectivity")"
echo -e "${TAB}$(translate "NFS server is running")"
echo -e "${TAB}$(translate "Export permissions allow access")"
fi
echo -e ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
view_host_nfs_mounts() {
show_proxmenux_logo
msg_title "$(translate "Current NFS Mounts on Host")"
echo -e "$(translate "NFS mounts on Proxmox host:"):"
echo "=================================="
CURRENT_MOUNTS=$(mount | grep -E "type nfs|:.*on.*nfs" 2>/dev/null || true)
if [[ -n "$CURRENT_MOUNTS" ]]; then
echo -e "${BOLD}$(translate "Currently Mounted:")${CL}"
echo "$CURRENT_MOUNTS"
echo ""
else
echo "$(translate "No NFS shares currently mounted on host.")"
echo ""
fi
FSTAB_NFS=$(grep "nfs" /etc/fstab 2>/dev/null || true)
if [[ -n "$FSTAB_NFS" ]]; then
echo -e "${BOLD}$(translate "Permanent Mounts (fstab):")${CL}"
echo "$FSTAB_NFS"
echo ""
echo -e "${TAB}${BOLD}$(translate "Mount Details:")${CL}"
while IFS= read -r fstab_line; do
if [[ -n "$fstab_line" && ! "$fstab_line" =~ ^# ]]; then
NFS_PATH=$(echo "$fstab_line" | awk '{print $1}')
MOUNT_POINT=$(echo "$fstab_line" | awk '{print $2}')
OPTIONS=$(echo "$fstab_line" | awk '{print $4}')
SERVER=$(echo "$NFS_PATH" | cut -d: -f1)
EXPORT=$(echo "$NFS_PATH" | cut -d: -f2)
echo -e "${TAB}${BGN}$(translate "Server:")${CL} ${BL}$SERVER${CL}"
echo -e "${TAB}${BGN}$(translate "Export:")${CL} ${BL}$EXPORT${CL}"
echo -e "${TAB}${BGN}$(translate "Host Mount Point:")${CL} ${BL}$MOUNT_POINT${CL}"
echo -e "${TAB}${BGN}$(translate "Options:")${CL} ${BL}$OPTIONS${CL}"
if mount | grep -q "$MOUNT_POINT"; then
echo -e "${TAB}${BGN}$(translate "Status:")${CL} ${GN}$(translate "Mounted")${CL}"
else
echo -e "${TAB}${BGN}$(translate "Status:")${CL} ${RD}$(translate "Not Mounted")${CL}"
fi
echo ""
fi
done <<< "$FSTAB_NFS"
else
echo "$(translate "No NFS mounts found in fstab.")"
fi
echo ""
echo "$(translate "Proxmox NFS Storage Status:")"
if which pvesm >/dev/null 2>&1; then
NFS_STORAGES=$(pvesm status 2>/dev/null | grep "nfs" || true)
if [[ -n "$NFS_STORAGES" ]]; then
echo "$NFS_STORAGES"
else
echo "$(translate "No NFS storage configured in Proxmox.")"
fi
else
echo "$(translate "pvesm command not available.")"
fi
echo ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
unmount_host_nfs_share() {
MOUNTS=$(mount | grep -E "type nfs|:.*on.*nfs" | awk '{print $3}' | sort -u || true)
FSTAB_MOUNTS=$(grep -E "nfs" /etc/fstab 2>/dev/null | grep -v "^#" | awk '{print $2}' | sort -u || true)
ALL_MOUNTS=$(echo -e "$MOUNTS\n$FSTAB_MOUNTS" | sort -u | grep -v "^$" || true)
if [[ -z "$ALL_MOUNTS" ]]; then
dialog --backtitle "ProxMenux" --title "$(translate "No Mounts")" --msgbox "\n$(translate "No NFS mounts found on host.")" 8 50
return
fi
OPTIONS=()
while IFS= read -r mount_point; do
if [[ -n "$mount_point" ]]; then
NFS_PATH=$(mount | grep "$mount_point" | awk '{print $1}' || grep "$mount_point" /etc/fstab | awk '{print $1}' || echo "Unknown")
SERVER=$(echo "$NFS_PATH" | cut -d: -f1)
EXPORT=$(echo "$NFS_PATH" | cut -d: -f2)
OPTIONS+=("$mount_point" "$SERVER:$EXPORT")
fi
done <<< "$ALL_MOUNTS"
SELECTED_MOUNT=$(dialog --backtitle "ProxMenux" --title "$(translate "Unmount NFS Share")" --menu "$(translate "Select mount point to unmount:")" 20 80 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3)
[[ -z "$SELECTED_MOUNT" ]] && return
NFS_PATH=$(mount | grep "$SELECTED_MOUNT" | awk '{print $1}' || grep "$SELECTED_MOUNT" /etc/fstab | awk '{print $1}' || echo "Unknown")
SERVER=$(echo "$NFS_PATH" | cut -d: -f1)
EXPORT=$(echo "$NFS_PATH" | cut -d: -f2)
PROXMOX_STORAGE=""
if which pvesm >/dev/null 2>&1; then
NFS_STORAGES=$(pvesm status 2>/dev/null | grep "nfs" | awk '{print $1}' || true)
while IFS= read -r storage_id; do
if [[ -n "$storage_id" ]]; then
STORAGE_INFO=$(pvesm config "$storage_id" 2>/dev/null || true)
STORAGE_SERVER=$(echo "$STORAGE_INFO" | grep "server" | awk '{print $2}')
STORAGE_EXPORT=$(echo "$STORAGE_INFO" | grep "export" | awk '{print $2}')
if [[ "$STORAGE_SERVER" == "$SERVER" && "$STORAGE_EXPORT" == "$EXPORT" ]]; then
PROXMOX_STORAGE="$storage_id"
break
fi
fi
done <<< "$NFS_STORAGES"
fi
CONFIRMATION_MSG="$(translate "Are you sure you want to unmount this NFS share?")\n\n$(translate "Mount Point:"): $SELECTED_MOUNT\n$(translate "Server:"): $SERVER\n$(translate "Export:"): $EXPORT\n\n$(translate "This will:")\n• $(translate "Unmount the NFS share")\n• $(translate "Remove from /etc/fstab")"
if [[ -n "$PROXMOX_STORAGE" ]]; then
CONFIRMATION_MSG="$CONFIRMATION_MSG\n• $(translate "Remove Proxmox storage:"): $PROXMOX_STORAGE"
fi
CONFIRMATION_MSG="$CONFIRMATION_MSG\n• $(translate "Remove mount point directory")"
if whiptail --yesno "$CONFIRMATION_MSG" 16 80 --title "$(translate "Confirm Unmount")"; then
show_proxmenux_logo
msg_title "$(translate "Unmount NFS Share from Host")"
if [[ -n "$PROXMOX_STORAGE" ]]; then
if pvesm remove "$PROXMOX_STORAGE" 2>/dev/null; then
msg_ok "$(translate "Proxmox storage removed successfully.")"
else
msg_warn "$(translate "Failed to remove Proxmox storage, continuing with unmount...")"
fi
fi
if mount | grep -q "$SELECTED_MOUNT"; then
if umount "$SELECTED_MOUNT"; then
msg_ok "$(translate "Successfully unmounted.")"
else
msg_warn "$(translate "Failed to unmount. Trying force unmount...")"
if umount -f "$SELECTED_MOUNT" 2>/dev/null; then
msg_ok "$(translate "Force unmount successful.")"
else
msg_error "$(translate "Failed to unmount. Mount point may be busy.")"
echo -e "${TAB}$(translate "Try closing any applications using the mount point.")"
fi
fi
fi
msg_info "$(translate "Removing from /etc/fstab...")"
sed -i "\|[[:space:]]$SELECTED_MOUNT[[:space:]]|d" /etc/fstab
msg_ok "$(translate "Removed from /etc/fstab.")"
echo -e ""
msg_ok "$(translate "NFS share unmounted successfully from host!")"
if [[ -n "$PROXMOX_STORAGE" ]]; then
echo -e "${TAB}${BGN}$(translate "Proxmox storage removed:")${CL} ${BL}$PROXMOX_STORAGE${CL}"
fi
echo -e "${TAB}${BGN}$(translate "Mount point unmounted:")${CL} ${BL}$SELECTED_MOUNT${CL}"
echo -e "${TAB}${BGN}$(translate "Removed from fstab:")${CL} ${BL}Yes${CL}"
fi
echo -e ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
manage_proxmox_storage() {
if ! command -v pvesm >/dev/null 2>&1; then
dialog --backtitle "ProxMenux" --title "$(translate "Error")" --msgbox "\n$(translate "pvesm command not found. This should not happen on Proxmox.")" 8 60
return
fi
NFS_STORAGES=$(pvesm status 2>/dev/null | awk '$2 == "nfs" {print $1}')
if [[ -z "$NFS_STORAGES" ]]; then
dialog --backtitle "ProxMenux" --title "$(translate "No NFS Storage")" --msgbox "\n$(translate "No NFS storage found in Proxmox.")" 8 60
return
fi
OPTIONS=()
while IFS= read -r storage_id; do
if [[ -n "$storage_id" ]]; then
STORAGE_INFO=$(pvesm config "$storage_id" 2>/dev/null || true)
SERVER=$(echo "$STORAGE_INFO" | grep "server" | awk '{print $2}')
EXPORT=$(echo "$STORAGE_INFO" | grep "export" | awk '{print $2}')
if [[ -n "$SERVER" && -n "$EXPORT" ]]; then
OPTIONS+=("$storage_id" "$SERVER:$EXPORT")
else
OPTIONS+=("$storage_id" "$(translate "NFS Storage")")
fi
fi
done <<< "$NFS_STORAGES"
SELECTED_STORAGE=$(dialog --backtitle "ProxMenux" --title "$(translate "Manage Proxmox NFS Storage")" --menu "$(translate "Select storage to manage:")" 20 80 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3)
[[ -z "$SELECTED_STORAGE" ]] && return
STORAGE_INFO=$(pvesm config "$SELECTED_STORAGE" 2>/dev/null || true)
SERVER=$(echo "$STORAGE_INFO" | grep "server" | awk '{print $2}')
EXPORT=$(echo "$STORAGE_INFO" | grep "export" | awk '{print $2}')
CONTENT=$(echo "$STORAGE_INFO" | grep "content" | awk '{print $2}')
FSTAB_NFS=$(grep "nfs" /etc/fstab 2>/dev/null || true)
if [[ -n "$FSTAB_NFS" ]]; then
while IFS= read -r fstab_line; do
if [[ -n "$fstab_line" && ! "$fstab_line" =~ ^# ]]; then
NFS_PATH=$(echo "$fstab_line" | awk '{print $1}')
MOUNT_POINT=$(echo "$fstab_line" | awk '{print $2}')
OPTIONS=$(echo "$fstab_line" | awk '{print $4}')
SERVER=$(echo "$NFS_PATH" | cut -d: -f1)
EXPORT=$(echo "$NFS_PATH" | cut -d: -f2)
fi
done <<< "$FSTAB_NFS"
fi
if whiptail --yesno "$(translate "Are you sure you want to REMOVE storage") $SELECTED_STORAGE?\n\n$(translate "Server:"): $SERVER\n$(translate "Export:"): $EXPORT\n\n$(translate "WARNING: This will permanently remove the storage from Proxmox configuration.")\n$(translate "The NFS mount on the host will NOT be affected.")" 14 80 --title "$(translate "Remove Storage")"; then
show_proxmenux_logo
msg_title "$(translate "Remove Storage")"
if pvesm remove "$SELECTED_STORAGE" 2>/dev/null; then
msg_ok "$(translate "Storage removed successfully from Proxmox.")"
echo -e ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
else
msg_error "$(translate "Failed to remove storage.")"
fi
fi
}
test_host_nfs_connectivity() {
show_proxmenux_logo
msg_title "$(translate "Test NFS Connectivity on Host")"
echo -e "$(translate "NFS Client Status on Proxmox Host:"):"
echo "=================================="
if which showmount >/dev/null 2>&1; then
echo "$(translate "NFS Client Tools: AVAILABLE")"
if systemctl is-active --quiet rpcbind 2>/dev/null; then
echo "$(translate "RPC Bind Service: RUNNING")"
else
echo "$(translate "RPC Bind Service: STOPPED")"
msg_warn "$(translate "Starting rpcbind service...")"
systemctl start rpcbind 2>/dev/null || true
fi
echo ""
echo "$(translate "Current NFS mounts on host:")"
CURRENT_MOUNTS=$(mount | grep -E "type nfs|:.*on.*nfs" 2>/dev/null || true)
if [[ -n "$CURRENT_MOUNTS" ]]; then
echo "$CURRENT_MOUNTS"
else
echo "$(translate "No NFS mounts active on host.")"
fi
echo ""
echo "$(translate "Testing network connectivity...")"
FSTAB_SERVERS=$(grep "nfs" /etc/fstab 2>/dev/null | awk '{print $1}' | cut -d: -f1 | sort -u || true)
if [[ -n "$FSTAB_SERVERS" ]]; then
while IFS= read -r server; do
if [[ -n "$server" ]]; then
echo -n "$(translate "Testing") $server: "
if ping -c 1 -W 2 "$server" >/dev/null 2>&1; then
echo -e "${GN}$(translate "Reachable")${CL}"
echo -n " $(translate "NFS port 2049"): "
if nc -z -w 2 "$server" 2049 2>/dev/null; then
echo -e "${GN}$(translate "Open")${CL}"
else
echo -e "${RD}$(translate "Closed")${CL}"
fi
echo -n " $(translate "Export list test"): "
if showmount -e "$server" >/dev/null 2>&1; then
echo -e "${GN}$(translate "Available")${CL}"
else
echo -e "${RD}$(translate "Failed")${CL}"
fi
else
echo -e "${RD}$(translate "Unreachable")${CL}"
fi
fi
done <<< "$FSTAB_SERVERS"
else
echo "$(translate "No NFS servers configured to test.")"
fi
echo ""
echo "$(translate "Proxmox NFS Storage Status:")"
if which pvesm >/dev/null 2>&1; then
NFS_STORAGES=$(pvesm status 2>/dev/null | grep "nfs" || true)
if [[ -n "$NFS_STORAGES" ]]; then
echo "$NFS_STORAGES"
else
echo "$(translate "No NFS storage configured in Proxmox.")"
fi
else
echo "$(translate "pvesm command not available.")"
fi
else
echo "$(translate "NFS Client Tools: NOT AVAILABLE")"
echo ""
echo "$(translate "This is unusual for Proxmox. NFS client tools should be installed.")"
fi
echo ""
echo "$(translate "ProxMenux Extensions:")"
if [[ "$SHARE_COMMON_LOADED" == "true" ]]; then
echo "$(translate "Shared Functions: LOADED")"
if [[ -f "$PROXMENUX_SHARE_MAP_DB" ]]; then
MAPPED_DIRS=$(wc -l < "$PROXMENUX_SHARE_MAP_DB" 2>/dev/null || echo "0")
echo "$(translate "Mapped directories:"): $MAPPED_DIRS"
fi
else
echo "$(translate "Shared Functions: NOT LOADED (using fallback methods)")"
fi
echo ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
# === Main Menu ===
while true; do
CHOICE=$(dialog --backtitle "ProxMenux" --title "$(translate "NFS Host Manager - Proxmox Host")" \
--menu "$(translate "Choose an option:")" 22 80 14 \
"1" "$(translate "Mount NFS Share on Host")" \
"2" "$(translate "View Current Host NFS Mounts")" \
"3" "$(translate "Unmount NFS Share from Host")" \
"4" "$(translate "Remove Proxmox NFS Storage")" \
"5" "$(translate "Test NFS Connectivity")" \
"6" "$(translate "Exit")" \
3>&1 1>&2 2>&3)
RETVAL=$?
if [[ $RETVAL -ne 0 ]]; then
exit 0
fi
case $CHOICE in
1) mount_host_nfs_share ;;
2) view_host_nfs_mounts ;;
3) unmount_host_nfs_share ;;
4) manage_proxmox_storage ;;
5) test_host_nfs_connectivity ;;
6) exit 0 ;;
*) exit 0 ;;
esac
done

View File

@@ -0,0 +1,572 @@
#!/bin/bash
# ==========================================================
# ProxMenu CT - NFS Manager for Proxmox LXC (Simple + Universal)
# ==========================================================
# Based on ProxMenux by MacRimi
# ==========================================================
# Description:
# This script allows you to manage NFS shares inside Proxmox CTs:
# - Create NFS exports with universal sharedfiles group
# - View configured exports
# - Delete existing exports
# - Check NFS service status
# ==========================================================
# Configuration
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
# Load shared functions
SHARE_COMMON_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main/scripts/global/share-common.func"
if ! source <(curl -s "$SHARE_COMMON_URL" 2>/dev/null); then
msg_error "$(translate "Could not load shared functions. Script cannot continue.")"
exit 1
fi
load_language
initialize_cache
select_privileged_lxc
setup_universal_sharedfiles_group() {
local ctid="$1"
msg_info "$(translate "Setting sharedfiles group with UID remapping...")"
if ! pct exec "$ctid" -- getent group sharedfiles >/dev/null 2>&1; then
pct exec "$ctid" -- groupadd -g 101000 sharedfiles
msg_ok "$(translate "Created sharedfiles group (GID: 101000)")"
else
local current_gid=$(pct exec "$ctid" -- getent group sharedfiles | cut -d: -f3)
if [[ "$current_gid" != "101000" ]]; then
pct exec "$ctid" -- groupmod -g 101000 sharedfiles
msg_ok "$(translate "Updated sharedfiles group to GID: 101000")"
else
msg_ok "$(translate "Sharedfiles group already exists (GID: 101000)")"
fi
fi
local lxc_users=$(pct exec "$ctid" -- awk -F: '$3 >= 1000 && $3 < 65534 {print $1 ":" $3}' /etc/passwd)
if [[ -n "$lxc_users" ]]; then
msg_info "$(translate "Adding existing users to sharedfiles group...")"
while IFS=: read -r username uid; do
if [[ -n "$username" ]]; then
pct exec "$ctid" -- usermod -a -G sharedfiles "$username" 2>/dev/null || true
msg_ok "$(translate "Added user") $username (UID: $uid) $(translate "to sharedfiles group")"
fi
done <<< "$lxc_users"
fi
msg_info "$(translate "Creating UID remapping for unprivileged container compatibility...")"
local remapped_count=0
if [[ -n "$lxc_users" ]]; then
while IFS=: read -r username uid; do
if [[ -n "$uid" ]]; then
local remapped_uid=$((uid + 100000))
local remapped_username="remap_${uid}"
if ! pct exec "$ctid" -- id "$remapped_username" >/dev/null 2>&1; then
pct exec "$ctid" -- useradd -u "$remapped_uid" -g sharedfiles -s /bin/false -M "$remapped_username" 2>/dev/null || true
msg_ok "$(translate "Created remapped user") $remapped_username (UID: $remapped_uid)"
((remapped_count++))
else
pct exec "$ctid" -- usermod -g sharedfiles "$remapped_username" 2>/dev/null || true
fi
fi
done <<< "$lxc_users"
fi
local common_uids=(33 1000 1001 1002)
for base_uid in "${common_uids[@]}"; do
local remapped_uid=$((base_uid + 100000))
local remapped_username="remap_${base_uid}"
if ! pct exec "$ctid" -- id "$remapped_username" >/dev/null 2>&1; then
pct exec "$ctid" -- useradd -u "$remapped_uid" -g sharedfiles -s /bin/false -M "$remapped_username" 2>/dev/null || true
msg_ok "$(translate "Created common remapped user") $remapped_username (UID: $remapped_uid)"
((remapped_count++))
fi
done
msg_ok "$(translate "Universal sharedfiles group configured with") $remapped_count $(translate "remapped users")"
}
select_mount_point() {
while true; do
METHOD=$(whiptail --backtitle "ProxMenux" --title "$(translate "Select Folder")" \
--menu "$(translate "How do you want to select the folder to export?")" 15 60 5 \
"auto" "$(translate "Select from folders inside /mnt")" \
"manual" "$(translate "Enter path manually")" \
3>&1 1>&2 2>&3)
if [[ $? -ne 0 ]]; then
return 1
fi
case "$METHOD" in
auto)
DIRS=$(pct exec "$CTID" -- find /mnt -maxdepth 1 -mindepth 1 -type d 2>/dev/null)
if [[ -z "$DIRS" ]]; then
whiptail --title "$(translate "No Folders")" --msgbox "$(translate "No folders found inside /mnt in the CT.")" 8 60
continue
fi
OPTIONS=()
while IFS= read -r dir; do
name=$(basename "$dir")
OPTIONS+=("$dir" "$name")
done <<< "$DIRS"
MOUNT_POINT=$(whiptail --backtitle "ProxMenux" --title "$(translate "Select Folder")" \
--menu "$(translate "Choose a folder to export:")" 20 60 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3)
if [[ $? -ne 0 ]]; then
return 1
fi
[[ -n "$MOUNT_POINT" ]] && return 0
;;
manual)
CT_NAME=$(pct config "$CTID" | awk -F: '/hostname/ {print $2}' | xargs)
DEFAULT_MOUNT_POINT="/mnt/${CT_NAME}_nfs"
MOUNT_POINT=$(whiptail --title "$(translate "Mount Point")" \
--inputbox "$(translate "Enter the mount point for the NFS export (e.g., /mnt/mynfs):")" \
10 70 "$DEFAULT_MOUNT_POINT" 3>&1 1>&2 2>&3)
if [[ $? -ne 0 ]]; then
return 1
fi
if [[ -z "$MOUNT_POINT" ]]; then
whiptail --title "$(translate "Error")" \
--msgbox "$(translate "No mount point was specified.")" 8 50
continue
fi
pct exec "$CTID" -- mkdir -p "$MOUNT_POINT" 2>/dev/null
return 0
;;
esac
done
}
get_network_config() {
NETWORK=$(whiptail --backtitle "ProxMenux" --title "$(translate "Network Configuration")" --menu "\n$(translate "Select network access level:")" 15 70 4 \
"1" "$(translate "Local network only (192.168.0.0/16)")" \
"2" "$(translate "Specific subnet (enter manually)")" \
"3" "$(translate "Specific host (enter IP)")" 3>&1 1>&2 2>&3)
case "$NETWORK" in
1)
NETWORK_RANGE="192.168.0.0/16"
;;
2)
clear
NETWORK_RANGE=$(whiptail --inputbox "$(translate "Enter subnet (e.g., 192.168.0.0/24):")" 10 60 "192.168.0.0/24" --title "$(translate "Subnet")" 3>&1 1>&2 2>&3)
[[ -z "$NETWORK_RANGE" ]] && return 1
;;
3)
dialog
NETWORK_RANGE=$(whiptail --inputbox "$(translate "Enter host IP (e.g., 192.168.0.100):")" 10 60 --title "$(translate "Host IP")" 3>&1 1>&2 2>&3)
[[ -z "$NETWORK_RANGE" ]] && return 1
;;
*)
return 1
;;
esac
return 0
}
select_export_options() {
EXPORT_OPTIONS=$(whiptail --title "$(translate "Export Options")" --menu \
"\n$(translate "Select export permissions:")" 15 70 3 \
"1" "$(translate "Read-Write (recommended)")" \
"2" "$(translate "Read-Only")" \
"3" "$(translate "Custom options")" 3>&1 1>&2 2>&3)
case "$EXPORT_OPTIONS" in
1)
OPTIONS="rw,sync,no_subtree_check,all_squash,anonuid=0,anongid=101000"
;;
2)
OPTIONS="ro,sync,no_subtree_check,all_squash,anonuid=0,anongid=101000"
;;
3)
OPTIONS=$(whiptail --inputbox "$(translate "Enter custom NFS options:")" \
10 70 "rw,sync,no_subtree_check,all_squash,anonuid=0,anongid=101000" \
--title "$(translate "Custom Options")" 3>&1 1>&2 2>&3)
[[ -z "$OPTIONS" ]] && OPTIONS="rw,sync,no_subtree_check,all_squash,anonuid=0,anongid=101000"
;;
*)
OPTIONS="rw,sync,no_subtree_check,all_squash,anonuid=0,anongid=101000"
;;
esac
}
create_nfs_export() {
show_proxmenux_logo
msg_title "$(translate "Create LXC server NFS")"
sleep 2
select_mount_point || return
get_network_config || return
select_export_options || return
msg_ok "$(translate "Directory successfully.")"
if ! pct exec "$CTID" -- dpkg -s nfs-kernel-server &>/dev/null; then
msg_info "$(translate "Installing NFS server packages inside the CT...")"
pct exec "$CTID" -- bash -c "apt-get update && apt-get install -y nfs-kernel-server nfs-common rpcbind"
pct exec "$CTID" -- systemctl enable --now rpcbind nfs-kernel-server
msg_ok "$(translate "NFS server installed successfully.")"
else
msg_ok "$(translate "NFS server is already installed.")"
fi
setup_universal_sharedfiles_group "$CTID"
msg_info "$(translate "Setting directory ownership and permissions...")"
pct exec "$CTID" -- chown root:sharedfiles "$MOUNT_POINT"
pct exec "$CTID" -- chmod 2775 "$MOUNT_POINT"
msg_ok "$(translate "Directory configured with sharedfiles group ownership")"
EXPORT_LINE="$MOUNT_POINT $NETWORK_RANGE($OPTIONS)"
if pct exec "$CTID" -- grep -q "^$MOUNT_POINT " /etc/exports; then
if dialog --yesno "$(translate "Do you want to update the existing export?")" \
10 60 --title "$(translate "Update Export")"; then
pct exec "$CTID" -- sed -i "\|^$MOUNT_POINT |d" /etc/exports
pct exec "$CTID" -- bash -c "echo '$EXPORT_LINE' >> /etc/exports"
show_proxmenux_logo
msg_title "$(translate "Create LXC server NFS")"
msg_ok "$(translate "Directory successfully.")"
msg_ok "$(translate "Export updated successfully.")"
msg_ok "$(translate "NFS server is already installed.")"
msg_ok "$(translate "Directory configured with sharedfiles group ownership")"
fi
else
pct exec "$CTID" -- bash -c "echo '$EXPORT_LINE' >> /etc/exports"
msg_ok "$(translate "Export added successfully.")"
fi
pct exec "$CTID" -- systemctl restart rpcbind nfs-kernel-server
pct exec "$CTID" -- exportfs -ra
CT_IP=$(pct exec "$CTID" -- hostname -I | awk '{print $1}')
echo -e ""
msg_ok "$(translate "NFS export created successfully!")"
echo -e ""
echo -e "${TAB}${BOLD}$(translate "Connection details:")${CL}"
echo -e "${TAB}${BGN}$(translate "Server IP:")${CL} ${BL}$CT_IP${CL}"
echo -e "${TAB}${BGN}$(translate "Export path:")${CL} ${BL}$MOUNT_POINT${CL}"
echo -e "${TAB}${BGN}$(translate "Mount options:")${CL} ${BL}$OPTIONS${CL}"
echo -e "${TAB}${BGN}$(translate "Network access:")${CL} ${BL}$NETWORK_RANGE${CL}"
echo -e "${TAB}${BGN}$(translate "NFS Version:")${CL} ${BL}Auto-negotiation (NFSv3/NFSv4)${CL}"
echo -e ""
echo -e "${TAB}${BOLD}$(translate "Mount Examples:")${CL}"
echo -e "${TAB}${BGN}$(translate "Auto-negotiate:")${CL} ${BL}mount -t nfs $CT_IP:$MOUNT_POINT /mnt/nfs${CL}"
echo -e "${TAB}${BGN}$(translate "Force NFSv4:")${CL} ${BL}mount -t nfs4 $CT_IP:$MOUNT_POINT /mnt/nfs${CL}"
echo -e "${TAB}${BGN}$(translate "Force NFSv3:")${CL} ${BL}mount -t nfs -o vers=3 $CT_IP:$MOUNT_POINT /mnt/nfs${CL}"
echo ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
view_exports() {
show_proxmenux_logo
msg_title "$(translate "View Current Exports")"
echo -e "$(translate "Current NFS exports in CT") $CTID:"
echo "=================================="
if pct exec "$CTID" -- test -f /etc/exports; then
EXPORTS=$(pct exec "$CTID" -- cat /etc/exports | grep -v '^#' | grep -v '^$')
if [[ -n "$EXPORTS" ]]; then
echo "$EXPORTS"
echo ""
echo "$(translate "Active exports:")"
pct exec "$CTID" -- showmount -e localhost 2>/dev/null || echo "$(translate "No active exports or showmount not available")"
echo ""
echo "$(translate "Universal Group Configuration:")"
echo "=================================="
if pct exec "$CTID" -- getent group sharedfiles >/dev/null 2>&1; then
local group_members=$(pct exec "$CTID" -- getent group sharedfiles | cut -d: -f4)
local sharedfiles_gid=$(pct exec "$CTID" -- getent group sharedfiles | cut -d: -f3)
echo "$(translate "Shared group: sharedfiles (GID:") $sharedfiles_gid)"
local member_count=$(echo "$group_members" | tr ',' '\n' | wc -l)
echo "$(translate "Total members:") $member_count $(translate "users")"
local remapped_users=$(pct exec "$CTID" -- getent passwd | grep "^remap_" | wc -l)
if [[ "$remapped_users" -gt 0 ]]; then
echo "$(translate "Remapped users:") $remapped_users $(translate "users (for unprivileged compatibility)")"
fi
echo "$(translate "Universal compatibility: ENABLED")"
echo "$(translate "NFS Version: Auto-negotiation (NFSv3/NFSv4)")"
else
echo "$(translate "Universal group: NOT CONFIGURED")"
fi
CT_IP=$(pct exec "$CTID" -- hostname -I | awk '{print $1}')
echo ""
echo "=================================="
echo -e "${TAB}${BOLD}$(translate "Connection Details:")${CL}"
echo -e "${TAB}${BGN}$(translate "Server IP:")${CL} ${BL}$CT_IP${CL}"
while IFS= read -r export_line; do
if [[ -n "$export_line" ]]; then
EXPORT_PATH=$(echo "$export_line" | awk '{print $1}')
echo -e "${TAB}${BGN}$(translate "Export path:")${CL} ${BL}$EXPORT_PATH${CL}"
echo ""
fi
done <<< "$EXPORTS"
else
echo "$(translate "No exports configured.")"
fi
else
echo "$(translate "/etc/exports file does not exist.")"
fi
echo ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
delete_export() {
if ! pct exec "$CTID" -- test -f /etc/exports; then
dialog --title "$(translate "Error")" --msgbox "\n$(translate "No exports file found.")" 8 50
return
fi
EXPORTS=$(pct exec "$CTID" -- awk '!/^#|^$/ {print NR, $0}' /etc/exports)
if [[ -z "$EXPORTS" ]]; then
dialog --title "$(translate "No Exports")" --msgbox "$(translate "No exports found in /etc/exports.")" 8 60
return
fi
OPTIONS=()
while read -r line; do
[[ -z "$line" ]] && continue
NUM=$(echo "$line" | awk '{print $1}')
EXPORT_LINE=$(echo "$line" | cut -d' ' -f2-)
EXPORT_PATH=$(echo "$EXPORT_LINE" | awk '{print $1}')
EXPORT_CLIENT=$(echo "$EXPORT_LINE" | awk '{print $2}' | cut -d'(' -f1)
[[ -z "$EXPORT_PATH" || -z "$EXPORT_CLIENT" ]] && continue
OPTIONS+=("$NUM" "$EXPORT_PATH $EXPORT_CLIENT")
done <<< "$EXPORTS"
SELECTED_NUM=$(dialog --title "$(translate "Delete Export")" --menu "$(translate "Select an export to delete:")" 20 70 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3)
[ -z "$SELECTED_NUM" ] && return
EXPORT_LINE=$(echo "$EXPORTS" | awk -v num="$SELECTED_NUM" '$1 == num {$1=""; print substr($0,2)}')
if whiptail --yesno "$(translate "Are you sure you want to delete this export?")\n\n$EXPORT_LINE" 10 70 --title "$(translate "Confirm Deletion")"; then
show_proxmenux_logo
msg_title "$(translate "Delete Export")"
pct exec "$CTID" -- sed -i "${SELECTED_NUM}d" /etc/exports
pct exec "$CTID" -- exportfs -ra
pct exec "$CTID" -- systemctl restart nfs-kernel-server
msg_ok "$(translate "Export deleted and NFS service restarted.")"
fi
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
check_nfs_status() {
show_proxmenux_logo
msg_title "$(translate "Check NFS Status")"
echo -e "$(translate "NFS Service Status in CT") $CTID:"
echo "=================================="
if pct exec "$CTID" -- dpkg -s nfs-kernel-server &>/dev/null; then
echo "$(translate "NFS Server: INSTALLED")"
if pct exec "$CTID" -- systemctl is-active --quiet nfs-kernel-server; then
echo "$(translate "NFS Service: RUNNING")"
else
echo "$(translate "NFS Service: STOPPED")"
fi
if pct exec "$CTID" -- systemctl is-active --quiet rpcbind; then
echo "$(translate "RPC Bind Service: RUNNING")"
else
echo "$(translate "RPC Bind Service: STOPPED")"
fi
echo ""
echo "$(translate "NFS Version Configuration:")"
echo "$(translate "Version: Auto-negotiation (NFSv3/NFSv4)")"
echo "$(translate "Client determines best version to use")"
echo ""
echo "$(translate "Universal Group Configuration:")"
if pct exec "$CTID" -- getent group sharedfiles >/dev/null 2>&1; then
echo "$(translate "Shared group: CONFIGURED")"
local group_members=$(pct exec "$CTID" -- getent group sharedfiles | cut -d: -f4)
local sharedfiles_gid=$(pct exec "$CTID" -- getent group sharedfiles | cut -d: -f3)
echo "$(translate "Group GID:") $sharedfiles_gid"
local member_count=$(echo "$group_members" | tr ',' '\n' | wc -l)
echo "$(translate "Total members:") $member_count $(translate "users")"
local remapped_users=$(pct exec "$CTID" -- getent passwd | grep "^remap_" | wc -l)
echo "$(translate "Remapped users:") $remapped_users $(translate "users")"
echo "$(translate "Universal compatibility: ENABLED")"
else
echo "$(translate "Universal group: NOT CONFIGURED")"
fi
echo ""
echo "$(translate "Listening ports:")"
pct exec "$CTID" -- ss -tlnp | grep -E ':(111|2049|20048)' || echo "$(translate "No NFS ports found")"
else
echo "$(translate "NFS Server: NOT INSTALLED")"
fi
echo ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
uninstall_nfs() {
if ! pct exec "$CTID" -- dpkg -s nfs-kernel-server &>/dev/null; then
dialog --title "$(translate "NFS Not Installed")" --msgbox "\n$(translate "NFS server is not installed in this CT.")" 8 60
return
fi
if ! whiptail --title "$(translate "Uninstall NFS Server")" \
--yesno "$(translate "WARNING: This will completely remove NFS server from the CT.")\n\n$(translate "This action will:")\n$(translate "• Stop all NFS services")\n$(translate "• Remove all exports")\n$(translate "• Uninstall NFS packages")\n$(translate "• Remove universal sharedfiles group")\n$(translate "• Clean up remapped users")\n\n$(translate "Are you sure you want to continue?")" \
18 70; then
return
fi
show_proxmenux_logo
msg_title "$(translate "Uninstall NFS Server")"
msg_info "$(translate "Stopping NFS services...")"
pct exec "$CTID" -- systemctl stop nfs-kernel-server 2>/dev/null || true
pct exec "$CTID" -- systemctl stop rpcbind 2>/dev/null || true
pct exec "$CTID" -- systemctl disable nfs-kernel-server 2>/dev/null || true
pct exec "$CTID" -- systemctl disable rpcbind 2>/dev/null || true
msg_ok "$(translate "NFS services stopped and disabled.")"
if pct exec "$CTID" -- test -f /etc/exports; then
pct exec "$CTID" -- truncate -s 0 /etc/exports
msg_ok "$(translate "Exports cleared.")"
fi
msg_info "$(translate "Removing remapped users...")"
local remapped_users=$(pct exec "$CTID" -- getent passwd | grep "^remap_" | cut -d: -f1)
if [[ -n "$remapped_users" ]]; then
while IFS= read -r username; do
if [[ -n "$username" ]]; then
pct exec "$CTID" -- userdel "$username" 2>/dev/null || true
msg_ok "$(translate "Removed remapped user:") $username"
fi
done <<< "$remapped_users"
fi
if pct exec "$CTID" -- getent group sharedfiles >/dev/null 2>&1; then
local regular_members=$(pct exec "$CTID" -- getent group sharedfiles | cut -d: -f4 | tr ',' '\n' | grep -v "^remap_" | wc -l)
if [[ "$regular_members" -eq 0 ]]; then
pct exec "$CTID" -- groupdel sharedfiles 2>/dev/null || true
msg_ok "$(translate "Removed sharedfiles group.")"
else
msg_warn "$(translate "Kept sharedfiles group (has regular users assigned).")"
fi
fi
pct exec "$CTID" -- apt-get remove --purge -y nfs-kernel-server nfs-common 2>/dev/null || true
pct exec "$CTID" -- apt-get autoremove -y 2>/dev/null || true
msg_ok "$(translate "NFS packages removed.")"
msg_info "$(translate "Cleaning up remaining processes...")"
pct exec "$CTID" -- pkill -f nfs 2>/dev/null || true
pct exec "$CTID" -- pkill -f rpc 2>/dev/null || true
sleep 2
msg_ok "$(translate "Universal NFS server has been completely uninstalled!")"
echo -e ""
echo -e "${TAB}${BOLD}$(translate "Uninstallation Summary:")${CL}"
echo -e "${TAB}${BGN}$(translate "Services:")${CL} ${BL}$(translate "Stopped and disabled")${CL}"
echo -e "${TAB}${BGN}$(translate "Packages:")${CL} ${BL}$(translate "Removed")${CL}"
echo -e "${TAB}${BGN}$(translate "Exports:")${CL} ${BL}$(translate "Cleared")${CL}"
echo -e "${TAB}${BGN}$(translate "Universal Group:")${CL} ${BL}$(translate "Cleaned up")${CL}"
echo -e "${TAB}${BGN}$(translate "Remapped Users:")${CL} ${BL}$(translate "Removed")${CL}"
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
# === Main Menu ===
while true; do
CHOICE=$(dialog --title "$(translate "NFS LXC Manager - CT") $CTID" --menu "$(translate "Choose an option:")" 20 70 12 \
"1" "$(translate "Create Universal NFS Export")" \
"2" "$(translate "View Current Exports")" \
"3" "$(translate "Delete Export")" \
"4" "$(translate "Check NFS Status")" \
"5" "$(translate "Uninstall NFS Server")" \
"6" "$(translate "Exit")" 3>&1 1>&2 2>&3)
case $CHOICE in
1) create_nfs_export ;;
2) view_exports ;;
3) delete_export ;;
4) check_nfs_status ;;
5) uninstall_nfs ;;
6) exit 0 ;;
*) exit 0 ;;
esac
done

File diff suppressed because it is too large Load Diff

1266
scripts/share/samba_host.sh Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,576 @@
#!/bin/bash
# ==========================================================
# ProxMenu CT - Samba Manager for Proxmox LXC
# ==========================================================
# Based on ProxMenux by MacRimi
# ==========================================================
# Description:
# This script allows you to manage Samba shares inside Proxmox CTs:
# - Create shared folders
# - View configured shares
# - Delete existing shares
# - Check Samba service status
# ==========================================================
# 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"
CREDENTIALS_DIR="/etc/samba/credentials"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
SHARE_COMMON_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main/scripts/global/share-common.func"
if ! source <(curl -s "$SHARE_COMMON_URL" 2>/dev/null); then
msg_error "$(translate "Could not load shared functions. Script cannot continue.")"
exit 1
fi
load_language
initialize_cache
select_privileged_lxc
select_mount_point() {
while true; do
METHOD=$(whiptail --backtitle "ProxMenux" --title "$(translate "Select Folder")" \
--menu "$(translate "How do you want to select the folder to share?")" 15 60 5 \
"auto" "$(translate "Select from folders inside /mnt")" \
"manual" "$(translate "Enter path manually")" \
3>&1 1>&2 2>&3)
if [[ $? -ne 0 ]]; then
return 1
fi
case "$METHOD" in
auto)
DIRS=$(pct exec "$CTID" -- find /mnt -maxdepth 1 -mindepth 1 -type d 2>/dev/null)
if [[ -z "$DIRS" ]]; then
whiptail --title "$(translate "No Folders")" --msgbox "$(translate "No folders found inside /mnt in the CT.")" 8 60
continue
fi
OPTIONS=()
while IFS= read -r dir; do
name=$(basename "$dir")
OPTIONS+=("$dir" "$name")
done <<< "$DIRS"
MOUNT_POINT=$(whiptail --title "$(translate "Select Folder")" \
--menu "$(translate "Choose a folder to share:")" 20 60 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3)
if [[ $? -ne 0 ]]; then
return 1
fi
[[ -n "$MOUNT_POINT" ]] && return 0
;;
manual)
CT_NAME=$(pct config "$CTID" | awk -F: '/hostname/ {print $2}' | xargs)
DEFAULT_MOUNT_POINT="/mnt/${CT_NAME}_share"
MOUNT_POINT=$(whiptail --title "$(translate "Mount Point")" \
--inputbox "$(translate "Enter the mount point for the shared folder (e.g., /mnt/myshare):")" \
10 70 "$DEFAULT_MOUNT_POINT" 3>&1 1>&2 2>&3)
if [[ $? -ne 0 ]]; then
return 1
fi
clear
if [[ -z "$MOUNT_POINT" ]]; then
whiptail --title "$(translate "Error")" --msgbox "\n$(translate "No mount point was specified.")" 8 50
continue
else
return 0
fi
;;
esac
done
}
create_share() {
show_proxmenux_logo
msg_title "$(translate "Create Samba server service")"
sleep 2
select_mount_point || return
if ! pct exec "$CTID" -- test -d "$MOUNT_POINT"; then
if whiptail --yesno "$(translate "The directory does not exist in the CT.")\n\n$MOUNT_POINT\n\n$(translate "Do you want to create it?")" 12 70 --title "$(translate "Create Directory")"; then
pct exec "$CTID" -- mkdir -p "$MOUNT_POINT"
msg_ok "$(translate "Directory created successfully.")"
else
msg_error "$(translate "Directory does not exist and was not created.")"
return
fi
fi
if pct exec "$CTID" -- dpkg -s samba &>/dev/null; then
SAMBA_INSTALLED=true
else
SAMBA_INSTALLED=false
fi
if [ "$SAMBA_INSTALLED" = false ]; then
echo -e "${TAB}$(translate "Installing Samba server packages inside the CT...")"
pct exec "$CTID" -- bash -c "apt-get update && apt-get install -y samba samba-common-bin acl"
USERNAME=$(whiptail --inputbox "$(translate "Enter the Samba username:")" 10 60 "proxmenux" --title "$(translate "Samba User")" 3>&1 1>&2 2>&3)
[[ -z "$USERNAME" ]] && msg_error "$(translate "No username provided.")" && return
while true; do
PASSWORD1=$(whiptail --passwordbox "$(translate "Enter the password for Samba user:")" 10 60 --title "$(translate "Samba Password")" 3>&1 1>&2 2>&3)
[[ -z "$PASSWORD1" ]] && msg_error "$(translate "No password provided.")" && return
PASSWORD2=$(whiptail --passwordbox "$(translate "Confirm the password:")" 10 60 --title "$(translate "Confirm Password")" 3>&1 1>&2 2>&3)
[[ -z "$PASSWORD2" ]] && msg_error "$(translate "Password confirmation is required.")" && return
if [[ "$PASSWORD1" != "$PASSWORD2" ]]; then
whiptail --title "$(translate "Password Mismatch")" --msgbox "$(translate "The passwords do not match. Please try again.")" 10 60
else
PASSWORD="$PASSWORD1"
break
fi
done
if ! pct exec "$CTID" -- id "$USERNAME" &>/dev/null; then
pct exec "$CTID" -- adduser --disabled-password --gecos "" "$USERNAME"
fi
pct exec "$CTID" -- bash -c "echo -e '$PASSWORD\n$PASSWORD' | smbpasswd -a '$USERNAME'"
msg_ok "$(translate "Samba server installed successfully.")"
else
USERNAME=$(pct exec "$CTID" -- pdbedit -L | awk -F: '{print $1}' | head -n1)
msg_ok "$(translate "Samba server is already installed.")"
echo -e "$(translate "Detected existing Samba user:") $USERNAME"
fi
IS_MOUNTED=$(pct exec "$CTID" -- mount | grep "$MOUNT_POINT" || true)
if [[ -n "$IS_MOUNTED" ]]; then
msg_info "$(translate "Detected a mounted directory from host. Setting up shared group...")"
SHARE_GID=999
GROUP_EXISTS=$(pct exec "$CTID" -- getent group sharedfiles || true)
GID_IN_USE=$(pct exec "$CTID" -- getent group "$SHARE_GID" | cut -d: -f1 || true)
if [[ -z "$GROUP_EXISTS" ]]; then
if [[ -z "$GID_IN_USE" ]]; then
pct exec "$CTID" -- groupadd -g "$SHARE_GID" sharedfiles
msg_ok "$(translate "Group 'sharedfiles' created with GID $SHARE_GID")"
else
pct exec "$CTID" -- groupadd sharedfiles
msg_warn "$(translate "GID $SHARE_GID already in use. Group 'sharedfiles' created with dynamic GID.")"
fi
else
msg_ok "$(translate "Group 'sharedfiles' already exists inside the CT")"
fi
if pct exec "$CTID" -- getent group sharedfiles >/dev/null; then
pct exec "$CTID" -- usermod -aG sharedfiles "$USERNAME"
pct exec "$CTID" -- chown root:sharedfiles "$MOUNT_POINT"
pct exec "$CTID" -- chmod 2775 "$MOUNT_POINT"
else
msg_error "$(translate "Group 'sharedfiles' was not created successfully. Skipping chown/usermod.")"
fi
HAS_ACCESS=$(pct exec "$CTID" -- su -s /bin/bash -c "test -w '$MOUNT_POINT' && echo yes || echo no" "$USERNAME" 2>/dev/null)
if [ "$HAS_ACCESS" = "no" ]; then
pct exec "$CTID" -- setfacl -R -m "u:$USERNAME:rwx" "$MOUNT_POINT"
msg_warn "$(translate "ACL permissions applied to allow write access for user:") $USERNAME"
else
msg_ok "$(translate "Write access confirmed for user:") $USERNAME"
fi
else
msg_ok "$(translate "No shared mount detected. Applying standard local access.")"
pct exec "$CTID" -- chown -R "$USERNAME:$USERNAME" "$MOUNT_POINT"
pct exec "$CTID" -- chmod -R 755 "$MOUNT_POINT"
HAS_ACCESS=$(pct exec "$CTID" -- su -s /bin/bash -c "test -w '$MOUNT_POINT' && echo yes || echo no" "$USERNAME" 2>/dev/null)
if [ "$HAS_ACCESS" = "no" ]; then
pct exec "$CTID" -- setfacl -R -m "u:$USERNAME:rwx" "$MOUNT_POINT"
msg_warn "$(translate "ACL permissions applied for local access for user:") $USERNAME"
else
msg_ok "$(translate "Write access confirmed for user:") $USERNAME"
fi
fi
SHARE_OPTIONS=$(whiptail --title "$(translate "Share Options")" --menu "$(translate "Select share permissions:")" 15 70 3 \
"rw" "$(translate "Read-Write access")" \
"ro" "$(translate "Read-Only access")" \
"custom" "$(translate "Custom options")" 3>&1 1>&2 2>&3)
SHARE_NAME=$(basename "$MOUNT_POINT")
case "$SHARE_OPTIONS" in
rw)
CONFIG=$(cat <<EOF
[$SHARE_NAME]
comment = Shared folder for $USERNAME
path = $MOUNT_POINT
read only = no
writable = yes
browseable = yes
guest ok = no
valid users = $USERNAME
force group = sharedfiles
create mask = 0664
directory mask = 2775
force create mode = 0664
force directory mode = 2775
veto files = /lost+found/
EOF
)
;;
ro)
CONFIG=$(cat <<EOF
[$SHARE_NAME]
comment = Read-only shared folder for $USERNAME
path = $MOUNT_POINT
read only = yes
writable = no
browseable = yes
guest ok = no
valid users = $USERNAME
force group = sharedfiles
veto files = /lost+found/
EOF
)
;;
custom)
CUSTOM_CONFIG=$(whiptail --inputbox "$(translate "Enter custom Samba configuration for this share:")" 15 80 "read only = no\nwritable = yes\nbrowseable = yes\nguest ok = no" --title "$(translate "Custom Configuration")" 3>&1 1>&2 2>&3)
CONFIG=$(cat <<EOF
[$SHARE_NAME]
comment = Custom shared folder for $USERNAME
path = $MOUNT_POINT
valid users = $USERNAME
force group = sharedfiles
$CUSTOM_CONFIG
veto files = /lost+found/
EOF
)
;;
*)
CONFIG=$(cat <<EOF
[$SHARE_NAME]
comment = Shared folder for $USERNAME
path = $MOUNT_POINT
read only = no
writable = yes
browseable = yes
guest ok = no
valid users = $USERNAME
force group = sharedfiles
create mask = 0664
directory mask = 2775
force create mode = 0664
force directory mode = 2775
veto files = /lost+found/
EOF
)
;;
esac
if pct exec "$CTID" -- grep -q "\[$SHARE_NAME\]" /etc/samba/smb.conf; then
msg_warn "$(translate "The share already exists in smb.conf:") [$SHARE_NAME]"
if whiptail --yesno "$(translate "Do you want to update the existing share?")" 10 60 --title "$(translate "Update Share")"; then
pct exec "$CTID" -- sed -i "/^\[$SHARE_NAME\]/,/^$/d" /etc/samba/smb.conf
pct exec "$CTID" -- bash -c "echo '$CONFIG' >> /etc/samba/smb.conf"
msg_ok "$(translate "Share updated successfully.")"
else
return
fi
else
msg_ok "$(translate "Adding new share to smb.conf...")"
pct exec "$CTID" -- bash -c "echo '$CONFIG' >> /etc/samba/smb.conf"
msg_ok "$(translate "Share added successfully.")"
fi
pct exec "$CTID" -- systemctl restart smbd.service
CT_IP=$(pct exec "$CTID" -- hostname -I | awk '{print $1}')
echo -e ""
msg_ok "$(translate "Samba share created successfully!")"
echo -e ""
echo -e "${TAB}${BOLD}$(translate "Connection details:")${CL}"
echo -e "${TAB}${BGN}$(translate "Server IP:")${CL} ${BL}$CT_IP${CL}"
echo -e "${TAB}${BGN}$(translate "Share name:")${CL} ${BL}$SHARE_NAME${CL}"
echo -e "${TAB}${BGN}$(translate "Share path:")${CL} ${BL}$MOUNT_POINT${CL}"
echo -e "${TAB}${BGN}$(translate "Username:")${CL} ${BL}$USERNAME${CL}"
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
view_shares() {
show_proxmenux_logo
msg_title "$(translate "View Current Shares")"
echo -e "$(translate "Current Samba shares in CT") $CTID:"
echo "=================================="
if pct exec "$CTID" -- test -f /etc/samba/smb.conf; then
SHARES=$(pct exec "$CTID" -- awk '/^\[.*\]/ && !/^\[global\]/ && !/^\[homes\]/ && !/^\[printers\]/ {print $0}' /etc/samba/smb.conf)
if [[ -n "$SHARES" ]]; then
while IFS= read -r share_line; do
if [[ -n "$share_line" ]]; then
SHARE_NAME=$(echo "$share_line" | sed 's/\[//g' | sed 's/\]//g')
SHARE_PATH=$(pct exec "$CTID" -- awk "/^\[$SHARE_NAME\]/,/^$/ {if(/path =/) print \$3}" /etc/samba/smb.conf)
echo "$share_line -> $SHARE_PATH"
fi
done <<< "$SHARES"
CT_IP=$(pct exec "$CTID" -- hostname -I | awk '{print $1}')
USERNAME=$(pct exec "$CTID" -- pdbedit -L | awk -F: '{print $1}' | head -n1)
echo ""
echo "=================================="
echo -e "${TAB}${BOLD}$(translate "Connection Details:")${CL}"
echo -e "${TAB}${BGN}$(translate "Server IP:")${CL} ${BL}$CT_IP${CL}"
echo -e "${TAB}${BGN}$(translate "Username:")${CL} ${BL}$USERNAME${CL}"
echo ""
echo -e "${TAB}${BOLD}$(translate "Available Shares:")${CL}"
while IFS= read -r share_line; do
if [[ -n "$share_line" ]]; then
SHARE_NAME=$(echo "$share_line" | sed 's/\[//g' | sed 's/\]//g')
SHARE_PATH=$(pct exec "$CTID" -- awk "/^\[$SHARE_NAME\]/,/^$/ {if(/path =/) print \$3}" /etc/samba/smb.conf)
echo -e "${TAB}${BGN}$(translate "Share name:")${CL} ${BL}$SHARE_NAME${CL}"
echo -e "${TAB}${BGN}$(translate "Share path:")${CL} ${BL}$SHARE_PATH${CL}"
echo -e "${TAB}${BGN}$(translate "Windows path:")${CL} ${YW}\\\\$CT_IP\\$SHARE_NAME${CL}"
echo -e "${TAB}${BGN}$(translate "Linux/Mac path:")${CL} ${YW}smb://$CT_IP/$SHARE_NAME${CL}"
echo ""
fi
done <<< "$SHARES"
else
echo "$(translate "No shares configured.")"
fi
else
echo "$(translate "/etc/samba/smb.conf file does not exist.")"
fi
echo ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
delete_share() {
if ! pct exec "$CTID" -- test -f /etc/samba/smb.conf; then
dialog --backtitle "ProxMenux" --title "$(translate "Error")" --msgbox "\n$(translate "No smb.conf file found.")" 8 50
return
fi
SHARES=$(pct exec "$CTID" -- awk '/^\[.*\]/ && !/^\[global\]/ && !/^\[homes\]/ && !/^\[printers\]/ {gsub(/\[|\]/, ""); print NR, $0}' /etc/samba/smb.conf)
if [[ -z "$SHARES" ]]; then
dialog --backtitle "ProxMenux" --title "$(translate "No Shares")" --msgbox "\n$(translate "No shares found in smb.conf.")" 8 60
return
fi
OPTIONS=()
while read -r line; do
[[ -z "$line" ]] && continue
NUM=$(echo "$line" | awk '{print $1}')
SHARE_NAME=$(echo "$line" | awk '{print $2}')
SHARE_PATH=$(pct exec "$CTID" -- awk "/^\[$SHARE_NAME\]/,/^$/ {if(/path =/) print \$3}" /etc/samba/smb.conf)
[[ -z "$SHARE_NAME" ]] && continue
OPTIONS+=("$SHARE_NAME" "$SHARE_NAME -> $SHARE_PATH")
done <<< "$SHARES"
SELECTED_SHARE=$(dialog --backtitle "ProxMenux" --title "$(translate "Delete Share")" --menu "$(translate "Select a share to delete:")" 20 70 10 "${OPTIONS[@]}" 3>&1 1>&2 2>&3)
[ -z "$SELECTED_SHARE" ] && return
SHARE_PATH=$(pct exec "$CTID" -- awk "/^\[$SELECTED_SHARE\]/,/^$/ {if(/path =/) print \$3}" /etc/samba/smb.conf)
if whiptail --yesno "$(translate "Are you sure you want to delete this share?")\n\n$(translate "Share name:"): $SELECTED_SHARE\n$(translate "Share path:"): $SHARE_PATH" 12 70 --title "$(translate "Confirm Deletion")"; then
show_proxmenux_logo
msg_title "$(translate "Delete Share")"
pct exec "$CTID" -- sed -i "/^\[$SELECTED_SHARE\]/,/^$/d" /etc/samba/smb.conf
pct exec "$CTID" -- systemctl restart smbd.service
msg_ok "$(translate "Share deleted and Samba service restarted.")"
fi
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
check_samba_status() {
show_proxmenux_logo
msg_title "$(translate "Check Samba Status")"
echo -e "$(translate "Samba Service Status in CT") $CTID:"
echo "=================================="
if pct exec "$CTID" -- dpkg -s samba &>/dev/null; then
echo "$(translate "Samba Server: INSTALLED")"
if pct exec "$CTID" -- systemctl is-active --quiet smbd; then
echo "$(translate "Samba Service: RUNNING")"
else
echo "$(translate "Samba Service: STOPPED")"
fi
if pct exec "$CTID" -- systemctl is-active --quiet nmbd; then
echo "$(translate "NetBIOS Service: RUNNING")"
else
echo "$(translate "NetBIOS Service: STOPPED")"
fi
echo ""
echo "$(translate "Listening ports:")"
pct exec "$CTID" -- ss -tlnp | grep -E ':(139|445)' || echo "$(translate "No Samba ports found")"
echo ""
echo "$(translate "Samba users:")"
pct exec "$CTID" -- pdbedit -L 2>/dev/null || echo "$(translate "No Samba users found")"
else
echo "$(translate "Samba Server: NOT INSTALLED")"
fi
echo ""
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
uninstall_samba() {
if ! pct exec "$CTID" -- dpkg -s samba &>/dev/null; then
dialog --backtitle "ProxMenux" --title "$(translate "Samba Not Installed")" --msgbox "\n$(translate "Samba server is not installed in this CT.")" 8 60
return
fi
if ! whiptail --title "$(translate "Uninstall Samba Server")" \
--yesno "$(translate "WARNING: This will completely remove Samba server from the CT.")\n\n$(translate "This action will:")\n$(translate "• Stop all Samba services")\n$(translate "• Remove all shares")\n$(translate "• Remove all Samba users")\n$(translate "• Uninstall Samba packages")\n$(translate "• Remove Samba groups")\n\n$(translate "Are you sure you want to continue?")" \
18 70; then
return
fi
show_proxmenux_logo
msg_title "$(translate "Uninstall Samba Server")"
msg_info "$(translate "Stopping Samba services...")"
pct exec "$CTID" -- systemctl stop smbd 2>/dev/null || true
pct exec "$CTID" -- systemctl stop nmbd 2>/dev/null || true
pct exec "$CTID" -- systemctl disable smbd 2>/dev/null || true
pct exec "$CTID" -- systemctl disable nmbd 2>/dev/null || true
msg_ok "$(translate "Samba services stopped and disabled.")"
if pct exec "$CTID" -- test -f /etc/samba/smb.conf; then
pct exec "$CTID" -- cp /etc/samba/smb.conf /etc/samba/smb.conf.backup.$(date +%Y%m%d_%H%M%S)
msg_ok "$(translate "Samba configuration backed up.")"
fi
SAMBA_USERS=$(pct exec "$CTID" -- pdbedit -L 2>/dev/null | awk -F: '{print $1}' || true)
if [[ -n "$SAMBA_USERS" ]]; then
while IFS= read -r user; do
if [[ -n "$user" ]]; then
pct exec "$CTID" -- smbpasswd -x "$user" 2>/dev/null || true
fi
done <<< "$SAMBA_USERS"
msg_ok "$(translate "Samba users removed.")"
fi
pct exec "$CTID" -- apt-get remove --purge -y samba samba-common-bin samba-common 2>/dev/null || true
pct exec "$CTID" -- apt-get autoremove -y 2>/dev/null || true
msg_ok "$(translate "Samba packages removed.")"
if pct exec "$CTID" -- getent group sharedfiles >/dev/null 2>&1; then
GROUP_USERS=$(pct exec "$CTID" -- getent group sharedfiles | cut -d: -f4)
if [[ -z "$GROUP_USERS" ]]; then
pct exec "$CTID" -- groupdel sharedfiles 2>/dev/null || true
msg_ok "$(translate "Samba group removed.")"
else
msg_warn "$(translate "Samba group kept (has users assigned).")"
fi
fi
msg_info "$(translate "Cleaning up Samba directories...")"
pct exec "$CTID" -- pkill -f smbd 2>/dev/null || true
pct exec "$CTID" -- pkill -f nmbd 2>/dev/null || true
pct exec "$CTID" -- rm -rf /var/lib/samba 2>/dev/null || true
pct exec "$CTID" -- rm -rf /var/cache/samba 2>/dev/null || true
sleep 2
msg_ok "$(translate "Samba server has been completely uninstalled!")"
echo -e ""
echo -e "${TAB}${BOLD}$(translate "Uninstallation Summary:")${CL}"
echo -e "${TAB}${BGN}$(translate "Services:")${CL} ${BL}$(translate "Stopped and disabled")${CL}"
echo -e "${TAB}${BGN}$(translate "Packages:")${CL} ${BL}$(translate "Removed")${CL}"
echo -e "${TAB}${BGN}$(translate "Users:")${CL} ${BL}$(translate "Removed")${CL}"
echo -e "${TAB}${BGN}$(translate "Configuration:")${CL} ${BL}$(translate "Backed up and cleared")${CL}"
echo -e "${TAB}${BGN}$(translate "Groups:")${CL} ${BL}$(translate "Cleaned up")${CL}"
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
read -r
}
# === Main Menu ===
while true; do
CHOICE=$(dialog --backtitle "ProxMenux" --title "$(translate "Samba Manager - CT") $CTID" --menu "$(translate "Choose an option:")" 20 70 12 \
"1" "$(translate "Create Samba server service")" \
"2" "$(translate "View Current Shares")" \
"3" "$(translate "Delete Share")" \
"4" "$(translate "Check Samba Status")" \
"5" "$(translate "Uninstall Samba Server")" \
"6" "$(translate "Exit")" 3>&1 1>&2 2>&3)
case $CHOICE in
1) create_share ;;
2) view_shares ;;
3) delete_share ;;
4) check_samba_status ;;
5) uninstall_samba ;;
6) exit 0 ;;
*) exit 0 ;;
esac
done

View File

@@ -1,13 +1,12 @@
#!/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.2
# Last Updated: 30/07/2025
# ==========================================================
# Description:
# This script allows users to assign physical disks to existing
@@ -17,9 +16,9 @@
# - Identifies and displays unassigned physical disks.
# - Allows the user to select multiple disks and attach them to a CT.
# - Configures the selected disks for the CT and verifies the assignment.
# - Uses persistent device paths to avoid issues with device order changes.
# ==========================================================
# Configuration ============================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
@@ -29,11 +28,69 @@ VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
# Get OS codename for repository configuration
OS_CODENAME="$(grep "VERSION_CODENAME=" /etc/os-release | cut -d"=" -f 2 | xargs )"
# ==========================================================
# Function to get persistent device path
get_persistent_path() {
local device="$1"
local persistent_path=""
# Try by-id first (most reliable)
for path in /dev/disk/by-id/*; do
if [[ -e "$path" && "$(readlink -f "$path")" == "$device" ]]; then
# Prefer ata- or scsi- over wwn- for readability
if [[ "$path" =~ ata-|scsi- ]]; then
echo "$path"
return 0
elif [[ -z "$persistent_path" ]]; then
persistent_path="$path"
fi
fi
done
# Return the first found by-id path if any
if [[ -n "$persistent_path" ]]; then
echo "$persistent_path"
return 0
fi
# Try by-path as fallback
for path in /dev/disk/by-path/*; do
if [[ -e "$path" && "$(readlink -f "$path")" == "$device" ]]; then
echo "$path"
return 0
fi
done
# Fallback to original device if no persistent path found
msg_warn "$(translate "No persistent path found for") $device, $(translate "using direct path")"
echo "$device"
}
# Function to ensure repositories are properly configured
ensure_repositories() {
local sources_file="/etc/apt/sources.list"
local need_update=false
# Only verify the main repository with contrib and non-free
if ! grep -q "deb.*${OS_CODENAME}.*main" "$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 [ "$need_update" = true ]; then
apt update >/dev/null 2>&1
fi
return 0
}
get_disk_info() {
local disk=$1
@@ -42,7 +99,8 @@ get_disk_info() {
echo "$MODEL" "$SIZE"
}
# Ensure repositories are configured
ensure_repositories
CT_LIST=$(pct list | awk 'NR>1 {print $1, $3}')
if [ -z "$CT_LIST" ]; then
@@ -50,7 +108,6 @@ if [ -z "$CT_LIST" ]; then
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,11 +117,13 @@ fi
CTID=$(echo "$CTID" | tr -d '"')
clear
show_proxmenux_logo
echo -e
msg_title "$(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..."
@@ -72,16 +131,13 @@ if [ "$CT_STATUS" != "running" ]; then
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
@@ -99,13 +155,11 @@ if grep -q '^unprivileged: 1' "$CONF_FILE"; 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..."
@@ -116,7 +170,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 +177,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}')
@@ -141,7 +185,6 @@ 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
@@ -159,19 +202,15 @@ for entry in $ZFS_RAW; do
fi
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
@@ -180,13 +219,16 @@ is_disk_in_use() {
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 -r -n1 readlink -f | sort -u)
if [[ -n "$LVM_DEVICES" ]] && echo "$LVM_DEVICES" | grep -qFx "$REAL_PATH"; then
IS_MOUNTED=true
fi
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
@@ -197,7 +239,6 @@ while read -r DISK; do
SIZE="${INFO[-1]}"
LABEL=""
SHOW_DISK=true
IS_MOUNTED=false
IS_RAID=false
IS_ZFS=false
@@ -217,7 +258,6 @@ 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)
@@ -235,8 +275,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
@@ -251,8 +289,15 @@ while read -r DISK; do
SHOW_DISK=false
fi
# Check if disk is already assigned to this CT using persistent paths
if pct config "$CTID" | grep -vE '^\s*#|^description:' | grep -q "$DISK"; then
SHOW_DISK=false
else
# Also check persistent paths
PERSISTENT_DISK=$(get_persistent_path "$DISK")
if [[ "$PERSISTENT_DISK" != "$DISK" ]] && pct config "$CTID" | grep -vE '^\s*#|^description:' | grep -q "$PERSISTENT_DISK"; then
SHOW_DISK=false
fi
fi
if $SHOW_DISK; then
@@ -275,19 +320,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
@@ -317,7 +352,6 @@ for DISK in $SELECTED; do
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 +362,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"
@@ -355,17 +388,12 @@ for DISK in $SELECTED; do
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 +403,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,15 +419,12 @@ 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
@@ -428,17 +447,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")" \
@@ -457,13 +471,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,34 +489,68 @@ 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
##############################################################################
# Get persistent path for the partition
PERSISTENT_PARTITION=$(get_persistent_path "$PARTITION")
RESULT=$(pct set "$CTID" -mp${INDEX} "$PARTITION,mp=$MOUNT_POINT,backup=0,ro=0,acl=1" 2>&1)
# Apply passthrough with persistent path
CURRENT_FS=$(lsblk -no FSTYPE "$PARTITION" | xargs)
if [ "$CURRENT_FS" == "xfs" ] || [ "$FORMAT_TYPE" == "xfs" ]; then
RESULT=$(pct set "$CTID" -mp${INDEX} "$PERSISTENT_PARTITION,mp=$MOUNT_POINT,backup=0,ro=0" 2>&1)
else
RESULT=$(pct set "$CTID" -mp${INDEX} "$PERSISTENT_PARTITION,mp=$MOUNT_POINT,backup=0,ro=0,acl=1" 2>&1)
fi
pct exec "$CTID" -- chmod -R 777 "$MOUNT_POINT"
# Adjust permissions inside the CT
pct exec "$CTID" -- chmod -R 775 "$MOUNT_POINT" 2>/dev/null || true
# Show confirmation with persistent identifier
msg_ok "$(translate "Assigned using") $PERSISTENT_PARTITION"
##############################################################################
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."
MESSAGE+="\\n$(translate "Using persistent path"): $PERSISTENT_PARTITION"
if [ -n "$ASSIGNED_TO" ]; then
MESSAGE+="\\n\\n$(translate "WARNING: This disk is also assigned to the following CT(s):")\\n$ASSIGNED_TO"
MESSAGE+="\\n$(translate "Make sure not to start CTs that share this disk at the same time to avoid data corruption.")"
fi
SUCCESS_MESSAGES+="$MESSAGE\\n\\n"
((DISKS_ADDED++))
else
@@ -515,8 +558,6 @@ for DISK in $SELECTED; do
fi
done
msg_ok "$(translate "Disk processing completed.")"
if [ -n "$SUCCESS_MESSAGES" ]; then
@@ -530,5 +571,4 @@ fi
msg_success "$(translate "Press Enter to return to menu...")"
read -r
exit 0

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,40 +32,65 @@ 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"
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
exit 1
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
msg_info "$(translate 'Getting VM list')"
VM_LIST=$(qm list | awk 'NR>1 {print $1" "$2}')
@@ -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,275 @@
#!/bin/bash
# ==========================================================
# ProxMenux - Manual Proxmox VE 8 to 9 Upgrade Guide
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 13/08/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
# ==========================================================
show_command() {
local step="$1"
local description="$2"
local command="$3"
local note="$4"
local command_extra="$5"
echo -e "${BGN}${step}.${CL} ${BL}${description}${CL}"
echo ""
echo -e "${TAB}${command}"
echo -e
[[ -n "$note" ]] && echo -e "${TAB}${DARK_GRAY}${note}${CL}"
[[ -n "$command_extra" ]] && echo -e "${TAB}${YW}${command_extra}${CL}"
echo ""
}
show_proxmox_upgrade_manual_guide() {
clear
show_proxmenux_logo
msg_title "$(translate "Proxmox VE 8 to 9 Manual Upgrade Guide")"
echo -e "${TAB}${BL}------------------------------------------------------------------------${CL}"
echo -e
echo -e "${TAB}${BGN}$(translate "Source:")${CL} ${BL}https://pve.proxmox.com/wiki/Upgrade_from_8_to_9${CL}"
echo -e
echo -e
echo -e "${TAB}${BOLD}$(translate "IMPORTANT PREREQUISITES:")${CL}"
echo -e
echo -e "${TAB}${BGN}$(translate "System must be updated to latest PVE 8.4+ before starting")${CL}"
echo -e "${TAB}${BGN}$(translate "Use SSH or terminal access (SSH recommended)")${CL}"
echo -e "${TAB}${BGN}$(translate "Use tmux or screen to avoid interruptions")${CL}"
echo -e "${TAB}${BGN}$(translate "Have valid backups of all VMs and containers")${CL}"
echo -e "${TAB}${BGN}$(translate "At least 5GB free space on root filesystem")${CL}"
echo -e "${TAB}${BGN}$(translate "Do not run the upgrade from the Web UI virtual console (it will disconnect)")${CL}"
echo -e
echo -e "${TAB}${BL}------------------------------------------------------------------------${CL}"
echo -e
show_command "1" \
"$(translate "Update system to latest PVE 8.4+ (if not done already):")\n\n" \
"apt update && apt dist-upgrade -y" \
"$(translate "Or use ProxMenux update function")" \
"\n\n"
show_command "2" \
"$(translate "Verify PVE version (must be 8.4.1 or newer):")\n\n" \
"pveversion" \
"" \
"\n"
show_command "2.1" \
"${YW}$(translate "If this node runs hyper-converged Ceph: ensure Ceph is 19.x (Squid) BEFORE upgrading PVE.")${CL}\n\n" \
"ceph --version" \
"$(translate "If not 19.x, upgrade Ceph (Reef→Squid) first per the official guide:") ${BL}https://pve.proxmox.com/wiki/Ceph_Squid${CL}" \
"\n"
show_command "3" \
"$(translate "Run upgrade checklist script:")\n\n" \
"pve8to9 --full" \
"${YW}$(translate "If it warns about 'systemd-boot' meta-package, remove it:")${CL} apt remove systemd-boot" \
"\n"
show_command "4" \
"$(translate "Start terminal multiplexer (recommended):")\n\n" \
"tmux new-session -s upgrade ${DARK_GRAY}$(translate "# Recommended: avoids disconnection during upgrade")${CL}\n\n screen -S upgrade ${DARK_GRAY}$(translate "# Alternative if you prefer screen")${CL}" \
"" \
"\n"
show_command "5" \
"$(translate "Update Debian repositories to Trixie:")\n\n" \
"sed -i 's/bookworm/trixie/g' /etc/apt/sources.list" \
"" \
"\n"
show_command "6" \
"${YW}$(translate "Update PVE enterprise repository (Only if using enterprise):")${CL}\n\n" \
"${CUS}sed -i 's/bookworm/trixie/g' /etc/apt/sources.list.d/pve-enterprise.list${CL}" \
"$(translate "Skip this step if using no-subscription repository")" \
"\n\n"
show_command "7" \
"${YW}$(translate "Add new PVE 9 enterprise repository (deb822 format) (Only if using enterprise):")${CL}\n\n" \
"${CUS}cat > /etc/apt/sources.list.d/pve-enterprise.sources << EOF
Types: deb
URIs: https://enterprise.proxmox.com/debian/pve
Suites: trixie
Components: pve-enterprise
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
EOF${CL}" \
"$(translate "Only if using enterprise subscription")" \
"\n\n"
show_command "8" \
"$(translate "OR add new PVE 9 no-subscription repository:")\n\n" \
"cat > /etc/apt/sources.list.d/proxmox.sources << EOF
Types: deb
URIs: http://download.proxmox.com/debian/pve
Suites: trixie
Components: pve-no-subscription
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
EOF" \
"$(translate "Only if using no-subscription repository")" \
"\n\n"
show_command "8.1" \
"$(translate "Refresh APT index and verify repositories:")\n\n" \
"apt update && apt policy | sed -n '1,120p'" \
"$(translate "Ensure there are no errors and that proxmox-ve candidate shows 9.x")" \
"\n"
show_command "9" \
"${YW}$(translate "Update Ceph repository (Only if using Ceph):")${CL}\n\n" \
"${CUS}cat > /etc/apt/sources.list.d/ceph.sources << EOF
Types: deb
URIs: http://download.proxmox.com/debian/ceph-squid
Suites: trixie
Components: no-subscription
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
EOF${CL}" \
"$(translate "Use enterprise URL if you have subscription.")" \
"\n\n"
show_command "10" \
"$(translate "Remove old repository files:")\n\n" \
"rm -f /etc/apt/sources.list.d/pve-enterprise.list /etc/apt/sources.list.d/ceph.list" \
"$(translate "Also comment any remaining 'bookworm' entries in *.list if present.")" \
"\n"
show_command "11" \
"$(translate "Update package index:")\n\n" \
"apt update" \
"" \
"\n"
show_command "12" \
"$(translate "Disable kernel audit messages (optional but recommended):")\n\n" \
"systemctl disable --now systemd-journald-audit.socket" \
"" \
"\n"
show_command "13" \
"$(translate "Start the main system upgrade:")\n\n" \
"apt dist-upgrade" \
"$(translate "This will take time. Answer prompts carefully - see notes below.")\n" \
"\n"
echo -e "${TAB}${BOLD}$(translate "UPGRADE PROMPTS - RECOMMENDED ANSWERS:")${CL}"
echo -e
echo -e "${TAB}${BGN}/etc/issue:${CL} ${YW}$(translate "Keep current version (N)")${CL}"
echo -e "${TAB}${BGN}/etc/lvm/lvm.conf:${CL} ${YW}$(translate "Install maintainer's version (Y)")${CL}"
echo -e "${TAB}${BGN}/etc/ssh/sshd_config:${CL} ${YW}$(translate "Install maintainer's version (Y)")${CL}"
echo -e "${TAB}${BGN}/etc/default/grub:${CL} ${YW}$(translate "Keep current version (N) if modified")${CL}"
echo -e "${TAB}${BGN}/etc/chrony/chrony.conf:${CL} ${YW}$(translate "Install maintainer's version (Y)")${CL}"
echo -e "${TAB}${BGN}$(translate "Service restarts:")${CL} ${YW}$(translate "Use default (Yes)")${CL}"
echo -e "${TAB}${BGN}apt-listchanges:${CL} ${YW}$(translate "Press 'q' to exit")${CL}"
echo -e
echo -e
echo -e
show_command "13.1" \
"${YW}$(translate "If booting in EFI mode with root on LVM: install GRUB for EFI")${CL}\n\n" \
"[ -d /sys/firmware/efi ] && apt install grub-efi-amd64" \
"$(translate "Per official known issues; ensures proper boot after upgrade")" \
"\n"
show_command "14" \
"$(translate "Run checklist again to verify upgrade:")\n\n" \
"pve8to9 --full" \
"$(translate "Should show fewer or no issues")" \
"\n"
show_command "15" \
"$(translate "Reboot the system:")\n\n" \
"reboot" \
"" \
"\n"
show_command "16" \
"$(translate "After reboot, verify PVE version:")\n\n" \
"pveversion" \
"$(translate "Should show pve-manager/9.x.x")" \
"\n"
show_command "17" \
"$(translate "Optional: Modernize repository sources:")\n\n" \
"apt modernize-sources" \
"$(translate "Converts to deb822; keeps .list backups as .bak")" \
"\n"
echo -e "${TAB}${BL}------------------------------------------------------------------------${CL}"
echo -e
echo -e
echo -e "${TAB}${BOLD}$(translate "CLUSTER UPGRADE NOTES:")${CL}"
echo -e
echo -e "${TAB}${BGN}$(translate "Upgrade one node at a time")${CL}"
echo -e "${TAB}${BGN}$(translate "Migrate VMs away from node being upgraded")${CL}"
echo -e "${TAB}${BGN}$(translate "Wait for each node to complete before starting next")${CL}"
echo -e "${TAB}${BGN}$(translate "HA groups will be migrated to HA rules automatically")${CL}"
echo -e
echo -e
echo -e
echo -e "${TAB}${BOLD}$(translate "TROUBLESHOOTING:")${CL}"
echo -e
echo -e "${TAB}${BGN}$(translate "If GUI does not load:")${CL} ${YW}Check with 'systemctl status pveproxy' and restart with 'systemctl restart pveproxy'${CL}"
echo -e "${TAB}${BGN}$(translate "If ZFS errors occur:")${CL} ${YW}Ensure the 'zfsutils-linux' package is up to date${CL}"
echo -e "${TAB}${BGN}$(translate "If network does not work:")${CL} ${YW}Check /etc/network/interfaces and ensure 'ifupdown2' is installed${CL}"
echo -e "${TAB}${BGN}$(translate "If upgrade fails:")${CL} ${YW}apt -f install${CL}"
echo -e "${TAB}${BGN}$(translate "If repositories error:")${CL} ${YW}Check /etc/apt/sources.list*${CL}"
echo -e "${TAB}${BGN}$(translate "If 'proxmox-ve' removal warning:")${CL} ${YW}Fix repository configuration (ensure PVE 9 repo active)${CL}"
echo -e "${TAB}${BGN}$(translate "Emergency recovery:")${CL} ${YW}Boot from rescue system${CL}"
echo -e
echo -e
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
echo -e
read -r
clear
exit 0
#bash <(curl -fsSL "$REPO_URL/scripts/utilities/upgrade_pve8_to_pve9.sh")
}
# Main execution
show_proxmox_upgrade_manual_guide

View File

@@ -0,0 +1,131 @@
#!/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
# ==========================================================
NECESSARY_REBOOT=1
apt_upgrade() {
local pve_version
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
if [[ -z "$pve_version" ]]; then
msg_error "Unable to detect Proxmox version."
return 1
fi
if [[ "$pve_version" -ge 9 ]]; then
show_proxmenux_logo
msg_title "$(translate "Proxmox system update")"
bash <(curl -fsSL "$REPO_URL/scripts/global/update-pve.sh")
else
show_proxmenux_logo
msg_title "$(translate "Proxmox system update")"
bash <(curl -fsSL "$REPO_URL/scripts/global/update-pve8.sh")
fi
}
check_reboot() {
NECESSARY_REBOOT=0
if [ -f /var/run/reboot-required ]; then
NECESSARY_REBOOT=1
fi
if grep -q "linux-image" "$log_file" 2>/dev/null; then
NECESSARY_REBOOT=1
fi
if [[ "$NECESSARY_REBOOT" -eq 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")"
echo -e
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")"
echo -e
msg_info2 "$(translate "You can reboot later manually.")"
echo -e
msg_success "$(translate "Press Enter to continue...")"
read -r
return 0
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")"
echo -e
msg_ok "$(translate "All changes applied. No reboot required.")"
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
read -r
fi
}
apt_upgrade
check_reboot

View File

@@ -0,0 +1,180 @@
#!/bin/bash
# ==========================================================
# ProxMenuX - Upgrade PVE 8 → 9 (Simplified, per official guide)
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 14/08/2025
# ==========================================================
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
# ==========================================================
LOG="/var/log/pve8-a-pve9-$(date +%Y%m%d-%H%M%S).log"
: > "$LOG"
disable_translation_post_upgrade() {
translate() { echo "$1"; }
}
disable_translation_post_upgrade
if [[ "$pve_version" -ge 9 ]]; then
disable_translation_post_upgrade
fi
run_pve8to9_check2() {
local tmp
tmp="$(mktemp)"
echo -e
set -o pipefail
pve8to9 --full 2>&1 | tee -a "$LOG" | tee "$tmp"
local rc=${PIPESTATUS[0]}
local fails warns
fails=$(grep -c 'FAIL:' "$tmp" || true)
warns=$(grep -c 'WARN:' "$tmp" || true)
if (( fails > 0 )); then
echo -e
echo -e "${BFR}${RD}[ERROR] $(translate "Pre-check found") $fails $(translate "blocking issue(s).")\n$(translate "Please resolve the problem(s) as described above, then re-run the upgrade script.")${CL}"
echo -e
local repair_commands=()
local repair_descriptions=()
# Error 1: systemd-boot meta-package
if grep -q 'systemd-boot meta-package installed' "$tmp"; then
repair_commands+=("apt install systemd-boot-efi systemd-boot-tools -y && apt remove systemd-boot -y")
repair_descriptions+=("$(translate "Fix systemd-boot meta-package conflict")")
echo -e "${YW}$(translate "Fix systemd-boot:") ${CL}apt install systemd-boot-efi systemd-boot-tools -y && apt remove systemd-boot -y"
fi
# Error 2: Ceph version incompatible
if grep -q -E '(ceph.*version|ceph.*incompatible)' "$tmp"; then
repair_commands+=("ceph versions && pveceph upgrade")
repair_descriptions+=("$(translate "Upgrade Ceph to compatible version")")
echo -e "${YW}$(translate "Fix Ceph version:") ${CL}ceph versions && pveceph upgrade"
fi
# Error 3: Repository configuration issues
if grep -q -E '(repository.*issue|repo.*problem|sources.*error)' "$tmp"; then
repair_commands+=("cleanup_duplicate_repos && configure_repositories")
repair_descriptions+=("$(translate "Fix repository configuration")")
echo -e "${YW}$(translate "Fix repositories:") ${CL}cleanup_duplicate_repos && configure_repositories"
fi
# Error 4: Package conflicts
if grep -q -E '(package.*conflict|dependency.*problem)' "$tmp"; then
repair_commands+=("apt update && apt autoremove -y && apt autoclean")
repair_descriptions+=("$(translate "Resolve package conflicts")")
echo -e "${YW}$(translate "Fix package conflicts:") ${CL}apt update && apt autoremove -y && apt autoclean"
fi
# Error 5: Disk space issues
if grep -q -E '(disk.*space|storage.*full|no.*space)' "$tmp"; then
repair_commands+=("apt clean && apt autoremove -y && journalctl --vacuum-time=7d")
repair_descriptions+=("$(translate "Free up disk space")")
echo -e "${YW}$(translate "Fix disk space:") ${CL}apt clean && apt autoremove -y && journalctl --vacuum-time=7d"
fi
# Error 6: Network/DNS issues
if grep -q -E '(network.*error|dns.*problem|connection.*failed)' "$tmp"; then
repair_commands+=("systemctl restart networking && systemctl restart systemd-resolved")
repair_descriptions+=("$(translate "Fix network connectivity")")
echo -e "${YW}$(translate "Fix network:") ${CL}systemctl restart networking && systemctl restart systemd-resolved"
fi
echo -e
if [[ ${#repair_commands[@]} -gt 0 ]]; then
echo -e "${BFR}${CY}$(translate "Repair Options:")${CL}"
echo -e "${TAB}${GN}1.${CL} $(translate "Try automatic repair of detected issues")"
echo -e "${TAB}${GN}2.${CL} $(translate "Show manual repair commands")"
echo -e
echo -n "$(translate "Select option [1-2] (default: 2): ")"
read -r repair_choice
case "$repair_choice" in
1)
echo -e
msg_info2 "$(translate "Attempting automatic repair...")"
local repair_success=0
for i in "${!repair_commands[@]}"; do
echo -e "${TAB}${YW}$(translate "Executing:") ${repair_descriptions[$i]}${CL}"
if eval "${repair_commands[$i]}"; then
msg_ok "${repair_descriptions[$i]} - $(translate "Success")"
else
msg_error "${repair_descriptions[$i]} - $(translate "Failed")"
repair_success=1
fi
done
if [[ $repair_success -eq 0 ]]; then
echo -e
msg_info2 "$(translate "Re-running pre-check after repairs...")"
sleep 2
run_pve8to9_check
return $?
else
echo -e
msg_error "$(translate "Some repairs failed. Please fix manually and re-run the script.")"
fi
;;
2)
echo -e
echo -e "$(translate "${BFR}${CY}Manual Repair Commands:${CL}")"
for i in "${!repair_commands[@]}"; do
echo -e "${TAB}${BL}# ${repair_descriptions[$i]}${CL}"
echo -e
echo -e "${TAB}${repair_commands[$i]}"
echo -e
done
echo -e
msg_info2 "$(translate "Once finished, re-run the script 'PVE 8 to 9 check' to verify that all issues.")"
echo -e
msg_success "$(translate "Press Enter to exit the script after reading instructions...")"
read -r
rm -f "$tmp"
exit 1
;;
esac
fi
msg_success "$(translate "Press Enter to continue")"
read -r
fi
echo -e
msg_ok "$(translate "Checklist post-upgrade finished. Warnings:") $warns"
echo -e
msg_success "$(translate "Press Enter to continue")"
read -r
rm -f "$tmp"
return $rc
}
show_proxmenux_logo
msg_title "$(translate "Run PVE 8 to 9 check")"
run_pve8to9_check2

View File

@@ -0,0 +1,548 @@
#!/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.1
# Last Updated: 30/07/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
OS_CODENAME="$(grep "VERSION_CODENAME=" /etc/os-release | cut -d"=" -f 2 | xargs )"
# ==========================================================
install_system_utils() {
command_exists() {
command -v "$1" >/dev/null 2>&1
}
ensure_repositories_() {
local sources_file="/etc/apt/sources.list"
local need_update=false
if [[ ! -f "$sources_file" ]]; then
msg_warn "$(translate "sources.list not found, creating default Debian repository...")"
cat > "$sources_file" << EOF
# Default Debian ${OS_CODENAME} repository
deb http://deb.debian.org/debian ${OS_CODENAME} main contrib non-free non-free-firmware
EOF
need_update=true
else
if ! grep -q "deb.*${OS_CODENAME}.*main" "$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
fi
if [[ "$need_update" == true ]] || ! apt list --installed >/dev/null 2>&1; then
msg_info "$(translate "Updating APT package lists...")"
apt-get update -o Acquire::AllowInsecureRepositories=true >/dev/null 2>&1
fi
return 0
}
ensure_repositories() {
local pve_version need_update=false
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
if [[ -z "$pve_version" ]]; then
msg_error "Unable to detect Proxmox version."
return 1
fi
if (( pve_version >= 9 )); then
# ===== PVE 9 (Debian 13 - trixie) =====
# proxmox.sources (no-subscription) ─ create if missing
if [[ ! -f /etc/apt/sources.list.d/proxmox.sources ]]; then
cat > /etc/apt/sources.list.d/proxmox.sources <<'EOF'
Enabled: true
Types: deb
URIs: http://download.proxmox.com/debian/pve
Suites: trixie
Components: pve-no-subscription
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
EOF
need_update=true
fi
# debian.sources ─ create if missing
if [[ ! -f /etc/apt/sources.list.d/debian.sources ]]; then
cat > /etc/apt/sources.list.d/debian.sources <<'EOF'
Types: deb
URIs: http://deb.debian.org/debian/
Suites: trixie trixie-updates
Components: main contrib non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
Types: deb
URIs: http://security.debian.org/debian-security/
Suites: trixie-security
Components: main contrib non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
EOF
need_update=true
fi
# apt-get update only if needed or lists are empty
if [[ "$need_update" == true ]] || [[ ! -d /var/lib/apt/lists || -z "$(ls -A /var/lib/apt/lists 2>/dev/null)" ]]; then
msg_info "$(translate "Updating APT package lists...")"
apt-get update >/dev/null 2>&1 || apt-get update
fi
else
# ===== PVE 8 (Debian 12 - bookworm) =====
local sources_file="/etc/apt/sources.list"
# Debian base (create or append minimal lines if missing)
if ! grep -qE 'deb .* bookworm .* main' "$sources_file" 2>/dev/null; then
{
echo "deb http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware"
echo "deb http://deb.debian.org/debian bookworm-updates main contrib non-free non-free-firmware"
echo "deb http://security.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware"
} >> "$sources_file"
need_update=true
fi
# Proxmox no-subscription list (classic) if missing
if [[ ! -f /etc/apt/sources.list.d/pve-no-subscription.list ]]; then
echo "deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription" \
> /etc/apt/sources.list.d/pve-no-subscription.list
need_update=true
fi
fi
# apt-get update only if needed or lists are empty
if [[ "$need_update" == true ]] || [[ ! -d /var/lib/apt/lists || -z "$(ls -A /var/lib/apt/lists 2>/dev/null)" ]]; then
msg_info "$(translate "Updating APT package lists...")"
apt-get update >/dev/null 2>&1 || apt-get update
fi
return 0
}
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 apt install -y "$package" >/dev/null 2>&1; then
install_success=true
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"
"intel-gpu-tools" "$(translate "tools for the Intel graphics driver")" "OFF"
"s-tui" "$(translate "Stress-Terminal UI")" "OFF"
"ipset" "$(translate "Manage IP sets")" "OFF"
"iptraf-ng" "$(translate "Network monitoring tool")" "OFF"
"plocate" "$(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"
if ! ensure_repositories; then
msg_error "$(translate "Failed to configure repositories. Installation aborted.")"
return 1
fi
local failed=0
local success=0
local warning=0
declare -A package_to_command=(
["plocate"]="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
sleep 2
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")"
if ! ensure_repositories; then
msg_error "$(translate "Failed to configure repositories. Installation aborted.")"
return 1
fi
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
sleep 2
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"
"intel-gpu-tools:tools for the Intel graphics driver"
"s-tui:Stress-Terminal UI"
"ipset:Manage IP sets"
"iptraf-ng:Network monitoring tool"
"plocate: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
}
# Main menu loop
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"
"intel-gpu-tools:tools for the Intel graphics driver"
"s-tui:Stress-Terminal UI"
"ipset:ipset:Manage IP sets"
"iptraf-ng:iptraf-ng:Network monitoring tool"
"plocate: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

File diff suppressed because it is too large Load Diff

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

@@ -52,8 +52,7 @@ NEON_PURPLE_BLUE="\033[38;5;99m"
WHITE="\033[38;5;15m"
RESET="\033[0m"
DARK_GRAY="\033[38;5;244m"
DARK_GRAY="\033[38;5;244m"
DARK_GRAY="\033[38;5;244m"
ORANGE="\033[38;5;208m"
YW="\033[33m"
YWB="\033[1;33m"
GN="\033[1;92m"
@@ -137,6 +136,12 @@ msg_info2() {
echo -e "${TAB}${BOLD}${YW}${HOLD}${msg}${CL}"
}
# Display info message with spinner
msg_info3() {
local msg="$1"
echo -ne "${TAB}${YW}${HOLD}${msg}${CL}"
}
# Display success message
msg_success() {
if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID > /dev/null; then
@@ -165,7 +170,7 @@ msg_warn() {
fi
printf "\e[?25h"
local msg="$1"
echo -e "${BFR}${TAB}${NV}${CL} ${YWB}${msg}${CL}"
echo -e "${BFR}${TAB}${CL} ${YWB}${msg}${CL}"
}
@@ -179,6 +184,12 @@ msg_ok() {
echo -e "${BFR}${TAB}${CM}${GN}${msg}${CL}"
}
msg_ok2() {
printf "\e[?25h"
local msg="$1"
echo -e "${BFR}${TAB}${CM}${GN}${msg}${CL}"
}
# Display error message
msg_error() {
@@ -193,24 +204,36 @@ msg_error() {
# Initialize cache
initialize_cache() {
if [ ! -f "$CACHE_FILE" ]; then
mkdir -p "$(dirname "$CACHE_FILE")"
echo "{}" > "$CACHE_FILE"
if [[ "$LANGUAGE" != "en" ]]; then
if [ ! -f "$CACHE_FILE" ]; then
mkdir -p "$(dirname "$CACHE_FILE")"
echo "{}" > "$CACHE_FILE"
fi
fi
}
# Load language
load_language() {
LANGUAGE="en"
if [ -f "$CONFIG_FILE" ]; then
LANGUAGE=$(jq -r '.language' "$CONFIG_FILE")
lang_candidate=$(jq -r '.language // empty' "$CONFIG_FILE" 2>/dev/null)
if [[ -n "$lang_candidate" && "$lang_candidate" != "null" ]]; then
LANGUAGE="$lang_candidate"
fi
fi
}
# Translation with cache and predefined terms
########################################################
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
@@ -237,21 +260,23 @@ translate() {
from googletrans import Translator
import sys, json, re
def translate_text(text, dest_lang):
def translate_text(text, dest_lang, context):
translator = Translator()
context = '$TRANSLATION_CONTEXT'
try:
full_text = context + ' ' + text
result = translator.translate(full_text, dest=dest_lang).text
# Remove context and any leading/trailing whitespace
translated = re.sub(r'^.*?(Translate:|Traducir:|Traduire:|Übersetzen:|Tradurre:|Traduzir:|翻译:|翻訳:)', '', result, flags=re.IGNORECASE | re.DOTALL).strip()
translated = re.sub(r'^.*?(Context:|Contexto:|Contexte:|Kontext:|Contesto:|上下文:|コンテキスト:).*?:', '', translated, flags=re.IGNORECASE | re.DOTALL).strip()
return json.dumps({'success': True, 'text': translated})
print(json.dumps({'success': True, 'text': translated}))
except Exception as e:
return json.dumps({'success': False, 'error': str(e)})
print(json.dumps({'success': False, 'error': str(e)}))
print(translate_text('$text', '$dest_lang'))
")
translate_text(
json.loads(sys.argv[1]),
sys.argv[2],
json.loads(sys.argv[3])
)
" "$(jq -Rn --arg t "$text" '$t')" "$dest_lang" "$(jq -Rn --arg ctx "$TRANSLATION_CONTEXT" '$ctx')")
deactivate
local translation_result=$(echo "$translated" | jq -r '.')
@@ -281,6 +306,11 @@ print(translate_text('$text', '$dest_lang'))
########################################################
show_proxmenux_logo() {
clear

View File

@@ -101,15 +101,22 @@ 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 13|Desktop|ProxMenux|https://cdimage.debian.org/debian-cd/current/amd64/iso-dvd/debian-13.0.0-amd64-DVD-1.iso"
"Debian 12|Desktop|ProxMenux|https://cdimage.debian.org/debian-cd/current/amd64/iso-dvd/debian-12.10.0-amd64-DVD-1.iso"
"Debian 11|Desktop|ProxMenux|https://cdimage.debian.org/cdimage/archive/11.11.0/amd64/iso-dvd/debian-11.11.0-amd64-DVD-1.iso"
"Debian 13 Netinst|CLI|ProxMenux|https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-13.0.0-amd64-netinst.iso"
"Debian 12 Netinst|CLI|ProxMenux|https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.10.0-amd64-netinst.iso"
"Debian 11 Netinst|CLI|ProxMenux|https://cdimage.debian.org/cdimage/archive/11.11.0/amd64/iso-cd/debian-11.11.0-amd64-netinst.iso"
"Fedora Workstation 42|Desktop|ProxMenux|https://download.fedoraproject.org/pub/fedora/linux/releases/42/Workstation/x86_64/iso/Fedora-Workstation-Live-42-1.1.x86_64.iso"
"Arch Linux|CLI|ProxMenux|https://geo.mirror.pkgbuild.com/iso/2025.07.01/archlinux-2025.07.01-x86_64.iso"
"Rocky Linux 9.5|Desktop|ProxMenux|https://download.rockylinux.org/pub/rocky/9/isos/x86_64/Rocky-9.5-x86_64-dvd.iso"
"Linux Mint 22.1|Desktop|ProxMenux|https://mirrors.edge.kernel.org/linuxmint/stable/22.1/linuxmint-22.1-cinnamon-64bit.iso"
"openSUSE Leap 15.6|Desktop|ProxMenux|https://download.opensuse.org/distribution/leap/15.6/iso/openSUSE-Leap-15.6-DVD-x86_64-Media.iso"
@@ -160,11 +167,13 @@ function select_linux_iso_official() {
function select_linux_cloudinit() {
local CLOUDINIT_OPTIONS=(
"1" "Arch Linux (Cloud-Init automated) │ Helper Scripts"
"2" "Debian 12 (Cloud-Init automated) │ Helper Scripts"
"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")"
"2" "Debian 13 (Cloud-Init automated) │ Helper Scripts"
"3" "Debian 12 (Cloud-Init automated) │ Helper Scripts"
"4" "Ubuntu 22.04 (Cloud-Init automated) │ Helper Scripts"
"5" "Ubuntu 24.04 (Cloud-Init automated) │ Helper Scripts"
"6" "Ubuntu 24.10 (Cloud-Init automated) │ Helper Scripts"
"7" "Ubuntu 25.04 (Cloud-Init automated) │ Helper Scripts"
"8" "$(translate "Return to Main Menu")"
)
local script_selection
@@ -179,18 +188,27 @@ function select_linux_cloudinit() {
bash <(curl -s "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/archlinux-vm.sh")
;;
2)
bash <(curl -s "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/debian-vm.sh")
bash <(curl -s "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/debian-13-vm.sh")
;;
3)
bash <(curl -s "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/ubuntu2204-vm.sh")
bash <(curl -s "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/debian-vm.sh")
;;
4)
bash <(curl -s "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/ubuntu2404-vm.sh")
bash <(curl -s "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/ubuntu2204-vm.sh")
;;
5)
bash <(curl -s "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/ubuntu2410-vm.sh")
bash <(curl -s "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/ubuntu2404-vm.sh")
;;
6)
bash <(curl -s "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/ubuntu2410-vm.sh")
;;
7)
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
;;
8)
return
;;
esac
@@ -241,40 +259,53 @@ function select_linux_custom_iso() {
function select_linux_other_scripts() {
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")"
)
local OTHER_OPTIONS=(
"1" "Home Assistant OS VM (HAOS) │ Helper Scripts"
"2" "Docker VM (Debian + SSH + Docker) │ Helper Scripts"
"3" "Nextcloud │ Helper Scripts"
"4" "$(translate "Return to Main Menu")"
)
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
return 1
fi
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
;;
esac
msg_success "$(translate "Press Enter to return to menu...")"
read -r
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
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" == "4" ]]; then
return 1
fi
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)"
echo -e
echo -e "${TAB}$(translate "Default Login Credentials:")"
echo -e "${TAB}Username: root"
echo -e "${TAB}Password: docker"
echo -e
;;
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
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
}

View File

@@ -44,15 +44,17 @@ 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 (Proxmox-zimaos)"
"8" "Umbrel OS VM (Helper Scripts)"
"9" "$(translate "Return to Main Menu")"
)
local NAS_TYPE
NAS_TYPE=$(dialog --backtitle "ProxMenux" \
--title "$(translate "NAS Systems")" \
--menu "\n$(translate "Select the NAS system to install:")" 18 70 10 \
--menu "\n$(translate "Select the NAS system to install:")" 20 70 10 \
"${NAS_OPTIONS[@]}" 3>&1 1>&2 2>&3)
@@ -87,28 +89,44 @@ 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)
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)"
7)
bash <(curl -s "$REPO_URL/scripts/vm/zimaos.sh")
msg_success "$(translate "Press Enter to return to menu...")"
read -r
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
whiptail --title "Proxmox VE - ZimaOS" \
--msgbox "$(translate "ZimaOS installer script by R0GGER\n\nVisit the GitHub repo to learn more, contribute, or support the project:\n\nhttps://github.com/R0GGER/proxmox-zimaos")" 15 70
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
;;
7)
9)
return 1
;;
esac

View File

@@ -36,9 +36,6 @@ fi
load_language
initialize_cache
clear
show_proxmenux_logo
# ==========================================================
@@ -78,12 +75,15 @@ function run_uupdump_creator() {
done
if [[ ${#MISSING[@]} -gt 0 ]]; then
show_proxmenux_logo
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[*]}")"
sleep 2
exit 1
fi
msg_ok "$(translate "All dependencies installed and verified.")"
fi
for i in "${!CMDS[@]}"; do
@@ -92,37 +92,84 @@ function run_uupdump_creator() {
fi
done
if [[ ${#FAILED[@]} -eq 0 ]]; then
msg_ok "$(translate "All dependencies installed and verified.")"
else
msg_error "$(translate "Missing commands after installation: ${FAILED[*]}")"
if [[ ${#FAILED[@]} -gt 0 ]]; then
show_proxmenux_logo
msg_error "$(translate "Missing commands after installation:") ${FAILED[*]}"
sleep 2
exit 1
fi
ISO_DIR=$(detect_iso_dir)
if [[ -z "$ISO_DIR" ]]; then
show_proxmenux_logo
msg_error "$(translate "Could not determine a valid ISO storage directory.")"
sleep 2
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_BASE="/root/uup-temp"
BASE_INPUT=$(dialog --clear --inputbox "$(translate "Enter base folder for temporary files and converter (default:") $DEFAULT_BASE):" 10 60 "$DEFAULT_BASE" 3>&1 1>&2 2>&3)
if [[ $? -ne 0 || -z "$BASE_INPUT" ]]; then
BASE_INPUT="$DEFAULT_BASE"
fi
BASE_CLEAN="$(echo "$BASE_INPUT" | sed 's:[[:space:]]*$::' | sed 's:/*$::')"
if [[ ! -d "$BASE_CLEAN" ]]; then
if ! mkdir -p "$BASE_CLEAN"; then
show_proxmenux_logo
msg_error "$(translate "The selected base folder does not exist and could not be created:") $BASE_CLEAN"
sleep 2
exit 1
fi
fi
if [[ ! -w "$BASE_CLEAN" ]]; then
show_proxmenux_logo
msg_error "$(translate "No write permissions on:") $BASE_CLEAN"
sleep 2
exit 1
fi
TMP_DIR="$BASE_CLEAN/uup-temp"
OUT_DIR="$ISO_DIR"
CONVERTER="$BASE_CLEAN/uup-converter"
if ! mkdir -p "$TMP_DIR"; then
show_proxmenux_logo
msg_error "$(translate "Could not create temporary directory:") $TMP_DIR"
sleep 2
exit 1
fi
if ! mkdir -p "$CONVERTER"; then
show_proxmenux_logo
msg_error "$(translate "Could not create converter directory:") $CONVERTER"
sleep 2
exit 1
fi
cd "$TMP_DIR" || { msg_error "$(translate "Failed to access:") $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
show_proxmenux_logo
msg_error "$(translate "The URL does not contain the required parameters (id, pack, edition).")"
sleep 2
return 1
@@ -134,6 +181,8 @@ LANG=$(echo "$UUP_URL" | grep -oP 'pack=\K[^&]+')
EDITION=$(echo "$UUP_URL" | grep -oP 'edition=\K[^&]+')
ARCH="amd64"
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}"
@@ -207,7 +256,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 +272,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
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"
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 \
"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"
--radiolist "$(translate "Select CPU model")" 17 70 11 \
"host" "Host (recommended)" ON \
"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)")" \

1207
scripts/vm/zimaos.sh Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
1.1.2
1.1.5

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) {
const version = versionMatch[1]
const date = versionMatch[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)
}
}
entries.push({
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) {
// Start new entry
if (versionMatch) {
const version = versionMatch[1]
const date = versionMatch[2]
currentEntry = {
version,
date,
url: `https://macrimi.github.io/ProxMenux/changelog#${version}`,
title: `ProxMenux ${version}`,
}
} 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: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB