diff --git a/.github/scripts/tests/test_log2ram_setup.py b/.github/scripts/tests/test_log2ram_setup.py index 40b0af1e..864dfa4f 100644 --- a/.github/scripts/tests/test_log2ram_setup.py +++ b/.github/scripts/tests/test_log2ram_setup.py @@ -5,6 +5,7 @@ and interval the host already has.""" import os from pathlib import Path import shutil +import shlex import subprocess import tempfile import unittest @@ -25,10 +26,176 @@ class KeepOnDiskTests(unittest.TestCase): (self.log / "journal/abc/system.journal").write_text("entries") (self.log / "pveproxy").mkdir() (self.log / "pveproxy/access.log").write_text("GET /") + self.config = self.root / "etc/log2ram.conf" + self.config.parent.mkdir() + self.config.write_text("SIZE=128M\nJOURNALD_AWARE=true\nNOTIFICATION=true\n") + + def run_setup(self, edit_failure=False): + # Extract only the shared setup function, never an administrative caller. + body = SETUP.read_text().split("pmx_log2ram_setup() {", 1)[1].split("\n}\n", 1)[0] + body = body.replace("/etc/", str(self.root / "etc") + "/") + for directory in ("cron.d", "tmpfiles.d"): + (self.root / "etc" / directory).mkdir(exist_ok=True) + stubs = r''' +translate() { printf '%s' "$1"; } +msg_info() { :; } +msg_ok() { :; } +pmx_install_pkg() { :; } +_pmx_log2ram_install_release() { :; } +_pmx_log2ram_pbs_logrotate() { :; } +_pmx_log2ram_remove_legacy() { :; } +pmx_edit_file() { local file="$1"; shift; sed -i "$@" "$file"; } +pmx_append_file() { cat >> "$1"; } +pmx_write_file() { cat > "$1"; } +pmx_remove_file() { rm -f "$1"; } +pmx_apply_setting() { :; } +systemctl() { printf '%s\n' "$*" >> "$SYSTEMCTL_TRACE"; } +''' + if edit_failure: + # A failed configuration writer must be handled even by root runners. + stubs += "\npmx_edit_file() { return 1; }\n" + script = (stubs + "\npmx_log2ram_setup() {" + body + "\n}\n" + + "pmx_log2ram_setup 256 3 false\n") + env = dict(os.environ, SYSTEMCTL_TRACE=str(self.root / "systemctl.trace"), + PMX_L2R_TAG="1.7.2", PMX_L2R_SCRIPTS=str(KEEP.parent), + PMX_L2R_REVERT_UNIT=str(self.root / "etc/revert.service")) + return subprocess.run(["bash", "-c", script], env=env, capture_output=True, text=True, timeout=10) + + def test_setup_keeps_journal_aware_syncing_until_boot(self): + self.config.write_text("SIZE=128M\nJOURNALD_AWARE=false\nNOTIFICATION=true\n") + result = self.run_setup() + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("JOURNALD_AWARE=true\n", self.config.read_text()) + self.assertIn("SIZE=256M\n", self.config.read_text()) + self.assertFalse((self.log / "journal").is_symlink()) + self.assertEqual((self.root / "systemctl.trace").read_text(), "daemon-reload\n") + + def test_boot_disables_journal_aware_syncing_after_relocation(self): + self.assertEqual(self.run_setup().returncode, 0) + result = self.run_script() + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(os.readlink(self.log / "journal"), "/var/hdd.journal") + self.assertEqual((self.root / "var/hdd.journal/abc/system.journal").read_text(), "entries") + self.assertIn("JOURNALD_AWARE=false\n", self.config.read_text()) + self.assertIn("SIZE=256M\n", self.config.read_text()) + # Reapplying the repair to an already-relocated installation is safe. + self.assertEqual(self.run_setup().returncode, 0) + self.assertEqual(self.run_script().returncode, 0) + self.assertIn("JOURNALD_AWARE=false\n", self.config.read_text()) + self.assertEqual(sorted(p.name for p in (self.root / "var/hdd.journal").iterdir()), ["abc"]) + + def test_boot_migration_failure_is_not_ignored_by_startup(self): + self.assertEqual(self.run_setup().returncode, 0) + # A non-directory destination causes a real copy failure, without mounts. + conflict = self.root / "var/hdd.journal" + conflict.write_text("blocked") + unit = self.root / "etc/systemd/system/log2ram.service.d/proxmenux.conf" + command = next(line.split("=", 1)[1] for line in unit.read_text().splitlines() + if line.startswith("ExecStartPre=")) + result = subprocess.run(["bash", *shlex.split(command.lstrip("-"))], + env=dict(os.environ, PMX_L2R_ROOT=str(self.root)), + capture_output=True, text=True, timeout=10) + self.assertNotEqual(result.returncode, 0, result.stderr) + self.assertIn("JOURNALD_AWARE=true\n", self.config.read_text()) + self.assertFalse(command.startswith("-"), "systemd must not ignore the helper's failure") + conflict.unlink() + self.assertEqual(self.run_script().returncode, 0) + self.assertIn("JOURNALD_AWARE=false\n", self.config.read_text()) + + def test_setup_adds_journal_aware_setting_when_missing(self): + self.config.write_text("SIZE=128M\n#JOURNALD_AWARE=false\n") + result = self.run_setup() + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("\nJOURNALD_AWARE=true\n", self.config.read_text()) + self.assertEqual(self.run_script().returncode, 0) + self.assertIn("\nJOURNALD_AWARE=false\n", self.config.read_text()) + + def check_missing_setting_without_final_newline(self, content): + self.config.write_text(content) + result = self.run_setup() + self.assertEqual(result.returncode, 0, result.stderr) + for expected in ("true", "false"): + with self.subTest(journal_aware=expected): + # Read shell semantics in a fresh Bash, not a substring match. + sourced = subprocess.run( + ["bash", "--noprofile", "--norc", "-c", + 'unset JOURNALD_AWARE NOTIFICATION; source "$1"; ' + 'printf "%s\\n%s\\n" "${JOURNALD_AWARE-UNSET}" "${NOTIFICATION-UNSET}"', + "fixture", str(self.config)], + capture_output=True, text=True, timeout=10) + self.assertEqual(sourced.returncode, 0, sourced.stderr) + self.assertEqual(sourced.stdout, expected + "\nfalse\n") + + if expected == "true": + self.assertFalse((self.log / "journal").is_symlink()) + self.assertEqual((self.log / "journal/abc/system.journal").read_text(), "entries") + result = self.run_script() + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(os.readlink(self.log / "journal"), "/var/hdd.journal") + self.assertEqual((self.root / "var/hdd.journal/abc/system.journal").read_text(), "entries") + + def test_missing_setting_after_comment_without_final_newline(self): + self.check_missing_setting_without_final_newline( + "SIZE=128M\nNOTIFICATION=false\n#JOURNALD_AWARE=false") + + def test_missing_setting_after_assignment_without_final_newline(self): + self.check_missing_setting_without_final_newline("SIZE=128M\nNOTIFICATION=false") + + def test_setup_reports_configuration_edit_failure(self): + self.config.write_text("SIZE=128M\nJOURNALD_AWARE=false\nNOTIFICATION=false\n") + result = self.run_setup(edit_failure=True) + self.assertNotEqual(result.returncode, 0, result.stderr) + self.assertIn("JOURNALD_AWARE=false\n", self.config.read_text()) + + def test_an_existing_link_with_an_unusable_disk_target_fails(self): + self.assertEqual(self.run_script().returncode, 0) + self.config.write_text("JOURNALD_AWARE=true\n") + disk = self.root / "var/hdd.journal" + shutil.rmtree(disk) + disk.write_text("blocked") + result = self.run_script() + self.assertNotEqual(result.returncode, 0, result.stderr) + self.assertEqual(self.config.read_text(), "JOURNALD_AWARE=true\n") + + def test_an_unexpected_journal_link_does_not_complete_migration(self): + shutil.rmtree(self.log / "journal") + (self.log / "journal").symlink_to("pveproxy") + result = self.run_script() + self.assertNotEqual(result.returncode, 0, result.stderr) + self.assertEqual(os.readlink(self.log / "journal"), "pveproxy") + self.assertIn("JOURNALD_AWARE=true\n", self.config.read_text()) + + def test_a_missing_journal_with_an_unusable_disk_target_fails(self): + shutil.rmtree(self.log / "journal") + (self.root / "var/hdd.journal").write_text("blocked") + result = self.run_script() + self.assertNotEqual(result.returncode, 0, result.stderr) + self.assertIn("JOURNALD_AWARE=true\n", self.config.read_text()) + self.assertFalse((self.log / "journal").is_symlink()) + + def test_failed_source_removal_does_not_complete_migration(self): + (self.root / "var/hdd.journal").mkdir() + result = subprocess.run( + ["bash", "-c", 'rm() { return 1; }; export -f rm; exec bash "$1"', "fixture", str(KEEP)], + env=dict(os.environ, PMX_L2R_ROOT=str(self.root)), capture_output=True, text=True, timeout=10) + self.assertNotEqual(result.returncode, 0, result.stderr) + self.assertIn("JOURNALD_AWARE=true\n", self.config.read_text()) + self.assertTrue((self.log / "journal/abc/system.journal").is_file()) + + def test_boot_configuration_failure_is_reported_and_can_be_retried(self): + self.config.unlink() + self.config.mkdir() + result = self.run_script() + self.assertNotEqual(result.returncode, 0, result.stderr) + self.assertEqual(os.readlink(self.log / "journal"), "/var/hdd.journal") + self.config.rmdir() + self.config.write_text("JOURNALD_AWARE=true\n") + self.assertEqual(self.run_script().returncode, 0) + self.assertEqual(self.config.read_text(), "JOURNALD_AWARE=false\n") def run_script(self, revert=False): env = dict(os.environ, PMX_L2R_ROOT=str(self.root), PMX_L2R_REVERT="1" if revert else "0") - return subprocess.run(["bash", str(KEEP)], env=env, capture_output=True, text=True) + return subprocess.run(["bash", str(KEEP)], env=env, capture_output=True, text=True, timeout=10) def test_the_journal_moves_to_disk_behind_a_link(self): self.assertEqual(self.run_script().returncode, 0) @@ -78,6 +245,7 @@ class KeepOnDiskTests(unittest.TestCase): def test_removing_log2ram_puts_the_folders_back(self): (self.log / "proxmox-backup").mkdir() self.run_script() + self.config.unlink() # Revert must not require or rewrite Log2RAM settings. self.assertEqual(self.run_script(revert=True).returncode, 0) for name in ("journal", "proxmox-backup"): self.assertFalse((self.log / name).is_symlink(), name) @@ -99,7 +267,7 @@ class KeptSettingsTests(unittest.TestCase): library = Path(folder) / "setup.sh" library.write_text(script) result = subprocess.run(["bash", "-c", f'source "{library}"; {function}'], - capture_output=True, text=True) + capture_output=True, text=True, timeout=10) return result.stdout.strip() def test_the_size_comes_from_the_installed_configuration(self): @@ -114,6 +282,24 @@ class KeptSettingsTests(unittest.TestCase): cron = "MAILTO=\"\"\n0 */3 * * * root /usr/local/bin/log2ram write >/dev/null 2>&1\n" self.assertEqual(self.read("pmx_log2ram_current_hours", {"/etc/cron.d/log2ram": cron}), "3") + def test_both_modes_offer_a_repair_to_version_2_0_installs(self): + for filename, function, updater in ( + ("auto_post_install.sh", "install_log2ram_auto", "_update_existing_log2ram_auto"), + ("customizable_post_install.sh", "configure_log2ram", "_update_existing_log2ram_custom"), + ): + with self.subTest(mode=filename), tempfile.TemporaryDirectory() as folder: + config = Path(folder) / "log2ram.conf" + config.write_text("JOURNALD_AWARE=false\n") + source = (ROOT / "scripts/post_install" / filename).read_text() + body = source.split(function + "() {", 1)[1].split("\n}\n", 1)[0] + body = body.replace("/etc/log2ram.conf", str(config)) + script = ("pmx_journal_context() { :; }\n" + + updater + "() { printf '%s\\n' \"$1\"; }\n" + + function + "() {" + body + "\n}\n" + function) + result = subprocess.run(["bash", "-c", script], capture_output=True, text=True, timeout=10) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.strip(), "2.1") + def test_the_release_is_pinned_to_a_verified_commit(self): text = SETUP.read_text() self.assertIn('PMX_L2R_TAG="1.7.2"', text) diff --git a/.github/scripts/tests/test_oci_pve_version_gate.py b/.github/scripts/tests/test_oci_pve_version_gate.py index 80703fd6..b012197e 100644 --- a/.github/scripts/tests/test_oci_pve_version_gate.py +++ b/.github/scripts/tests/test_oci_pve_version_gate.py @@ -14,15 +14,16 @@ class OciPveVersionGateTests(unittest.TestCase): @classmethod def setUpClass(cls): text = LAUNCHER.read_text(encoding='utf-8') - cls.function = re.search(r'^pve_supports_oci\(\) \{\n.*?^\}\n', text, re.M | re.S).group(0) + cls.function = ''.join(re.search(rf'^{name}\(\) \{{\n.*?^\}}\n', text, re.M | re.S).group(0) + for name in ('pve_manager_version', 'pve_supports_oci')) cls.text = text def supported(self, version): with tempfile.TemporaryDirectory() as folder: - fake = Path(folder, 'pveversion') - # Without Proxmox the command is missing: a stand-in that fails hides the host's own. - fake.write_text('#!/bin/sh\nexit 127\n' if version is None else - f'#!/bin/sh\necho "pve-manager/{version}/b9984c6d90a4bd80 (running kernel: 6.17)"\n') + fake = Path(folder, 'dpkg-query') + # Without Proxmox the package is not installed: dpkg-query fails. + fake.write_text('#!/bin/sh\nexit 1\n' if version is None else + f'#!/bin/sh\nprintf %s "{version}"\n') fake.chmod(0o755) result = subprocess.run(['bash', '-c', f'PATH={folder}:/usr/bin:/bin\n{self.function}pve_supports_oci'], capture_output=True, text=True) @@ -38,6 +39,9 @@ class OciPveVersionGateTests(unittest.TestCase): with self.subTest(version=version): self.assertFalse(self.supported(version)) + def test_the_version_is_read_without_the_slow_pveversion(self): + self.assertNotIn('pveversion', self.function) + def test_it_is_checked_before_anything_is_installed(self): self.assertLess(self.text.index('if ! pve_supports_oci; then'), self.text.index('ensure_oci_dependencies || exit 1')) diff --git a/.github/scripts/tests/test_oci_storage_sk.py b/.github/scripts/tests/test_oci_storage_sk.py index ba88229c..99a5bb76 100644 --- a/.github/scripts/tests/test_oci_storage_sk.py +++ b/.github/scripts/tests/test_oci_storage_sk.py @@ -12,7 +12,7 @@ from unittest.mock import Mock, patch ROOT=Path(__file__).resolve().parents[3] sys.path[:0]=[str(ROOT/'oci/src'),str(ROOT/'oci/remote')] -from proxmenux_oci import cli, custom, custom_mounts, extra_devices, i18n, installer, ui as native_ui, updates +from proxmenux_oci import catalog_refresh, cli, custom, custom_mounts, extra_devices, i18n, installer, ui as native_ui, updates from proxmenux_oci.catalog import Catalog @@ -203,8 +203,15 @@ class InputAndCancellationTests(StorageFixture): def test_real_menu_error_handler_has_slovak_heading_and_preserves_detail(self): ui=Mock();ui.choose.side_effect=['custom',None] with patch.object(cli,'interactive_ui',return_value=ui),patch.object(custom,'explore',side_effect=ValueError('fiction-detail')), \ - patch.object(updates,'UpdateCheck') as checker: + patch.object(updates,'UpdateCheck') as checker, \ + patch.object(catalog_refresh,'CatalogRefresh') as catalog_checker: + # Keep this wording/error-handler test offline and free of writes. + # The real background worker has its own catalog lifecycle tests. + catalog_check=catalog_checker.return_value.__enter__.return_value + catalog_check.active=False;catalog_check.consume.return_value=False + catalog_check.failed=False;catalog_check.label.return_value='Refresh catalog' check=checker.return_value.__enter__.return_value check.active=False;check.notice.return_value='';check.status.return_value={'available':[]} + check.label.return_value='Check for updates' self.assertEqual(cli.interactive(self.catalog),0) ui.message.assert_called_once_with('Operáciu sa nepodarilo dokončiť:\n\nfiction-detail') diff --git a/.github/scripts/tests/test_vm_backup_jobs_wiring.py b/.github/scripts/tests/test_vm_backup_jobs_wiring.py index 930c82e3..d6148e37 100644 --- a/.github/scripts/tests/test_vm_backup_jobs_wiring.py +++ b/.github/scripts/tests/test_vm_backup_jobs_wiring.py @@ -24,7 +24,10 @@ class VmBackupJobsWiring(TestCase): source = ROUTES.read_text() self.assertNotIn('shell=True', source) self.assertNotIn('os.system', source) - self.assertEqual(source.count('subprocess.run('), 1) + # pvesh, and the transient unit a backup started by hand runs in. + programs = re.findall(r'subprocess\.run\(\s*\[\s*"([a-z-]+)"', source) + self.assertEqual(len(programs), source.count('subprocess.run(')) + self.assertEqual(set(programs), {'pvesh', 'systemd-run', 'systemctl'}) self.assertIn('subprocess.run(["pvesh", *args]', source) def test_the_monitor_ships_and_registers_it(self): diff --git a/.github/workflows/oci-validation.yml b/.github/workflows/oci-validation.yml index e5f04502..5e8f2fda 100644 --- a/.github/workflows/oci-validation.yml +++ b/.github/workflows/oci-validation.yml @@ -25,6 +25,11 @@ on: permissions: contents: read +# A newer push to the same branch or pull request replaces the run in progress. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: check: runs-on: ubuntu-latest diff --git a/.github/workflows/test-features.yml b/.github/workflows/test-features.yml index a6d6f94f..b4031997 100644 --- a/.github/workflows/test-features.yml +++ b/.github/workflows/test-features.yml @@ -22,6 +22,9 @@ on: - 'tests/test_vm_backup_jobs_api.py' - 'tests/test_backup_storage_retention.cjs' - 'tests/test_figurine_installation.py' + - 'scripts/gpu_tpu/nvidia_installer.sh' + - 'tests/test_nvidia*.py' + - 'AppImage/scripts/managed_installs.py' - 'scripts/post_install/customizable_post_install.sh' - 'AppImage/scripts/post_install_versions.py' - 'AppImage/scripts/flask_proxmenux_routes.py' @@ -101,6 +104,9 @@ on: - 'AppImage/scripts/tests/test_proxmox_node_name.py' - 'AppImage/scripts/tests/test_pve_notifications_cfg_writes.py' - 'tests/test_lvm_disk_read_policy.py' + - 'scripts/gpu_tpu/nvidia_installer.sh' + - 'tests/test_nvidia*.py' + - 'AppImage/scripts/managed_installs.py' - 'oci/**' - 'AppImage/scripts/oci_menu_bridge.py' - 'AppImage/scripts/lxc_apps.py' @@ -112,6 +118,11 @@ on: permissions: contents: read +# A newer push to the same branch or pull request replaces the run in progress. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: apprise-services: runs-on: ubuntu-latest @@ -164,6 +175,21 @@ jobs: NODE_PATH: ${{ github.workspace }}/.github/ci/offline-node/node_modules run: node tests/test_backup_storage_retention.cjs + nvidia-installer: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Check NVIDIA parser, cache and Secure Boot regressions + run: | + bash -n scripts/gpu_tpu/nvidia_installer.sh + python3 -B -m unittest discover -s tests -p 'test_nvidia*.py' -v + installer-and-release: runs-on: ubuntu-latest timeout-minutes: 10 diff --git a/.github/workflows/test-i18n.yml b/.github/workflows/test-i18n.yml index 828db33a..f83272dc 100644 --- a/.github/workflows/test-i18n.yml +++ b/.github/workflows/test-i18n.yml @@ -51,6 +51,11 @@ on: permissions: contents: read +# A newer push to the same branch or pull request replaces the run in progress. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: # Preserve this check ID for existing branch protection rules. This suite # includes menu, download and scheduler regressions as well as i18n tests. diff --git a/.github/workflows/test-offline-qualified.yml b/.github/workflows/test-offline-qualified.yml index 006fdaef..eb45dbe5 100644 --- a/.github/workflows/test-offline-qualified.yml +++ b/.github/workflows/test-offline-qualified.yml @@ -57,6 +57,11 @@ on: permissions: contents: read +# A newer push to the same branch or pull request replaces the run in progress. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: offline-python: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 0af426b3..badc7402 100644 --- a/.gitignore +++ b/.gitignore @@ -54,6 +54,7 @@ Thumbs.db # Cache .cache +/oci/.catalog-cache/ /web/.cache # IDE/editor files diff --git a/AppImage/components/lxc-app-panel.tsx b/AppImage/components/lxc-app-panel.tsx index f55a0c6b..f3a65942 100644 --- a/AppImage/components/lxc-app-panel.tsx +++ b/AppImage/components/lxc-app-panel.tsx @@ -405,6 +405,10 @@ const SELFHST_THEME_LOGOS: Record= since), + key=lambda task: task["starttime"]) + return started[0]["upid"].rstrip(":") if started else None + + +def _start_vzdump(node, arguments, job_id): + """Start the part of a job that runs on a node and return its task UPID. + + pvesh waits for the whole backup and prints the UPID only at the end, and + a child of the Monitor is killed with its service, so vzdump runs in its + own transient unit and its UPID is read from the node's active tasks. The + task then runs to the end on its own. Returns (upid, error).""" + if not shutil.which("systemd-run"): + return None, "systemd-run not found" + run_dir = RUN_DIR + try: + os.makedirs(run_dir, mode=0o700, exist_ok=True) + now = time.time() + for old in os.listdir(run_dir): + old_path = os.path.join(run_dir, old) + if old.startswith("vzdump-") and now - os.path.getmtime(old_path) > 86400: + os.remove(old_path) + except OSError: + pass + unit = f"proxmenux-vzdump-job-{uuid.uuid4().hex[:8]}" + out_path = os.path.join(run_dir, f"vzdump-{unit}.out") + since = int(time.time()) - 1 + try: + launch = subprocess.run( + ["systemd-run", f"--unit={unit}", "--collect", "--quiet", + f"--description=ProxMenux backup job {job_id}", + f"--property=StandardOutput=file:{out_path}", + "pvesh", "create", f"/nodes/{node}/vzdump", *arguments], + capture_output=True, text=True, timeout=15) + except subprocess.TimeoutExpired: + return None, "systemd-run timed out" + if launch.returncode != 0: + return None, (launch.stderr or launch.stdout).strip() + buf = "" + deadline = time.time() + 10.0 + while time.time() < deadline: + try: + with open(out_path, encoding="utf-8", errors="replace") as handle: + buf = handle.read() + except OSError: + buf = "" + found = UPID_RE.search(buf) + if found: + return found.group(0).rstrip(":"), None + state = subprocess.run(["systemctl", "is-active", unit], capture_output=True, text=True).stdout.strip() + if state not in ("active", "activating"): + break + upid = active_vzdump(node, since) + if upid: + return upid, None + time.sleep(0.5) + # A short backup can finish between two reads: its UPID is the last line. + try: + with open(out_path, encoding="utf-8", errors="replace") as handle: + buf = handle.read() + except OSError: + pass + found = UPID_RE.search(buf) + if found: + return found.group(0).rstrip(":"), None + return None, (buf.strip() or "pvesh exited without producing a UPID")[-800:] + + @vm_backup_bp.route("/api/vm-backup-jobs//run", methods=["POST"]) @require_admin_scope def run_job(job_id): @@ -418,12 +501,11 @@ def run_job(job_id): for node in nodes: if not NODE_RE.match(node): continue - ok, out = _pvesh(["create", f"/nodes/{node}/vzdump", *arguments]) - if ok: - found = re.search(r"UPID:[^\s\"']+", out) - tasks.append({"node": node, "upid": found.group(0) if found else ""}) + upid, error = _start_vzdump(node, arguments, job_id) + if upid: + tasks.append({"node": node, "upid": upid}) else: - failures.append({"node": node, "error": out}) + failures.append({"node": node, "error": error}) if not tasks: return _err(failures[0]["error"] if failures else "The backup could not be started", 500) return jsonify({"success": True, "tasks": tasks, "failures": failures}) diff --git a/AppImage/scripts/managed_installs.py b/AppImage/scripts/managed_installs.py index b05ab307..fe3dc74f 100644 --- a/AppImage/scripts/managed_installs.py +++ b/AppImage/scripts/managed_installs.py @@ -1188,21 +1188,15 @@ def _check_oci_app(entry: dict) -> dict: # ── NVIDIA driver checker ── # -# Source of truth for what's available upstream: -# `https://download.nvidia.com/XFree86/Linux-x86_64/latest.txt` -# returns the single newest version, e.g. "580.105.08" -# `https://download.nvidia.com/XFree86/Linux-x86_64/` -# HTML directory listing — we scrape it for per-branch latest -# (so a user on 570.x gets 570.x's latest, without an automatic -# cross-branch upgrade). -# -# Cache TTL is 7 days because NVIDIA's release cadence on each branch -# is roughly monthly. The cache is in-memory only; AppImage restarts -# refresh it for free. +# The driver update is the version the NVIDIA installer itself would +# recommend on this host: it filters by the running kernel, NVIDIA's +# maintained branches, the PCI IDs of the host's GPUs and the NVENC patch, +# and keeps the installed branch when it can. Asking the installer means the +# Monitor never announces a version the installer then refuses. The installer +# keeps its own caches of NVIDIA's pages for 24 hours. -_NVIDIA_BASE = "https://download.nvidia.com/XFree86/Linux-x86_64" -_NVIDIA_CACHE_TTL = 7 * 86400 -_nvidia_cache: dict[str, Any] = {"versions": [], "fetched_at": 0} +_NVIDIA_INSTALLER = "/usr/local/share/proxmenux/scripts/gpu_tpu/nvidia_installer.sh" +_NVIDIA_CHECK_TIMEOUT = 300 def _version_tuple(v: str) -> tuple: @@ -1219,64 +1213,46 @@ def _version_tuple(v: str) -> tuple: return tuple(out[:3]) -def _fetch_nvidia_versions(force: bool = False) -> list[str]: - """Return the cached list of all upstream versions, or fetch fresh.""" - now = time.time() - if not force and _nvidia_cache["versions"] and \ - now - _nvidia_cache["fetched_at"] < _NVIDIA_CACHE_TTL: - return _nvidia_cache["versions"] +def _nvidia_recommended_version(current: str) -> tuple[Optional[str], Optional[str]]: + """The version the installer recommends for this host, or an error.""" try: - req = urllib.request.Request( - _NVIDIA_BASE + "/", - headers={"User-Agent": "ProxMenux-Monitor/1.0"}, + # An older installer would ignore the option and open its menu. + with open(_NVIDIA_INSTALLER, encoding="utf-8", errors="replace") as handle: + if "--recommended-version" not in handle.read(): + return None, "the NVIDIA installer on this host is older than ProxMenux Monitor" + done = subprocess.run( + ["bash", _NVIDIA_INSTALLER, "--recommended-version", current], + capture_output=True, text=True, timeout=_NVIDIA_CHECK_TIMEOUT, + # The host's curl and Python, not the AppImage's libraries. + env={k: v for k, v in os.environ.items() if k not in ("PYTHONPATH", "LD_LIBRARY_PATH")}, ) - with urllib.request.urlopen(req, timeout=15) as resp: - html = resp.read().decode("utf-8", errors="replace") - except Exception as e: - print(f"[ProxMenux] NVIDIA version fetch failed: {e}") - return _nvidia_cache.get("versions", []) - versions = sorted( - {m.group(1) for m in re.finditer( - r"""href=['"](\d+\.\d+(?:\.\d+)?)/?['"]""", html)}, - key=_version_tuple, - reverse=True, - ) + except (OSError, subprocess.TimeoutExpired) as e: + return None, f"the NVIDIA installer could not be run: {e}" + versions = [line.strip() for line in done.stdout.splitlines() + if re.fullmatch(r"\d+\.\d+(\.\d+)?", line.strip())] if versions: - _nvidia_cache["versions"] = versions - _nvidia_cache["fetched_at"] = now - return versions + return versions[-1], None + if done.returncode == 0: + return None, None + return None, "the NVIDIA installer could not read the version list" def _check_nvidia_xfree86(entry: dict) -> dict: - """Report same-branch bugfix upgrades for the installed driver.""" + """Report the update the NVIDIA installer would recommend.""" current = entry.get("current_version") if not current or not re.match(r"^\d+\.\d+(\.\d+)?$", current): return {"available": False, "latest": None, "last_check": _now_iso(), "error": "no installed version"} - versions = _fetch_nvidia_versions() - if not versions: - return {"available": False, "latest": None, - "last_check": _now_iso(), - "error": "could not parse upstream version listing"} - - current_branch = current.split(".")[0] - same_branch = [v for v in versions if v.split(".")[0] == current_branch] - same_branch_latest = same_branch[0] if same_branch else None - - available = False - latest: Optional[str] = None - if same_branch_latest and \ - _version_tuple(same_branch_latest) > _version_tuple(current): - latest = same_branch_latest - available = True - + recommended, error = _nvidia_recommended_version(current) + available = bool(recommended) and _version_tuple(recommended) > _version_tuple(current) + same_branch = bool(recommended) and recommended.split(".")[0] == current.split(".")[0] return { "available": available, - "latest": latest, + "latest": recommended if available else None, "last_check": _now_iso(), - "error": None, - "_upgrade_kind": "patch" if available else None, + "error": error, + "_upgrade_kind": ("patch" if same_branch else "branch") if available else None, } @@ -1842,13 +1818,9 @@ def check_for_updates(force: bool = False) -> list[dict]: UI reads ``get_active_items()`` to render the inline "update available" line. - ``force`` invalidates the per-source caches (currently only the - NVIDIA versions list — OCI keeps its own internal cache). + ``force`` is kept for callers; the NVIDIA installer and OCI keep + their own caches. """ - if force: - _nvidia_cache["versions"] = [] - _nvidia_cache["fetched_at"] = 0 - updates_available: list[dict] = [] successful_lxc_checks: list[tuple[int, int]] = [] with _lock: diff --git a/AppImage/scripts/tests/test_openai_compatible_ai_models.py b/AppImage/scripts/tests/test_openai_compatible_ai_models.py index 96bceffa..1c8441a8 100644 --- a/AppImage/scripts/tests/test_openai_compatible_ai_models.py +++ b/AppImage/scripts/tests/test_openai_compatible_ai_models.py @@ -20,8 +20,9 @@ class CapturingOpenAIProvider(OpenAIProvider): super().__init__(*args, **kwargs) self.captured_payload = None - def _make_request(self, url, payload, headers): + def _make_request(self, url, payload, headers, timeout=15): self.captured_payload = payload + self.captured_timeout = timeout return {"choices": [{"message": {"content": "ok"}}]} @@ -119,6 +120,14 @@ class OpenAICompatibleModelTests(unittest.TestCase): self.assertNotIn("reasoning_effort", provider.captured_payload) self.assertNotIn("max_completion_tokens", provider.captured_payload) + def test_a_request_waits_as_long_as_the_monitor_allows_a_rewrite(self): + # A local model behind an OpenAI-compatible endpoint is slow; the + # request must not give up before the Monitor's own limit. + provider = CapturingOpenAIProvider(api_key="token", model="gemma-3-1b-it-Q4_K_M", + base_url="http://192.0.2.22:8080/v1") + provider.generate("system", "user", max_tokens=100) + self.assertEqual(provider.captured_timeout, notification_manager.AI_REWRITE_TIMEOUT_SECONDS) + def test_official_openai_reasoning_model_still_uses_reasoning_contract(self): provider = CapturingOpenAIProvider( api_key="token", diff --git a/lang/de.json b/lang/de.json index 6a5046c5..8cdd233f 100644 --- a/lang/de.json +++ b/lang/de.json @@ -184,6 +184,7 @@ "A tunneling daemon by Cloudflare that safely exposes your web servers into the internet.": "Ein Tunnel-Daemon von Cloudflare, der Ihre Webserver sicher im Internet aussetzt.", "A unified email and collaboration server.": "Ein einheitlicher E-Mail- und Kollaborationsserver.", "A unified platform for logs, metrics and traces.": "Eine einheitliche Plattform für Logs, Metriken und Traces.", + "A validating, recursive DNS resolver for your private network.": "Ein validierender, rekursiver DNS-Resolver für Ihr privates Netzwerk.", "A versatile file conversion tool that supports multiple formats.": "Ein vielseitiges Dateikonvertierungstool, das mehrere Formate unterstützt.", "A visual tool for exploring and documenting database schemas.": "Ein visuelles Werkzeug zum Erkunden und Dokumentieren von Datenbankschemata.", "A web GUI client of Project V which supports VMess, VLESS, SS, SSR, Trojan, Tuic and Juicity protocols": "Ein Web-GUI-Client von Project V, der die Protokolle VMess, VLESS, SS, SSR, Trojan, Tuic und Juicity unterstützt", @@ -256,6 +257,7 @@ "Acceleration for llama.cpp": "Beschleunigung für llama.cpp", "Accept routes from other nodes?": "Routen von anderen Knoten akzeptieren?", "Accept this host monitoring profile?": "Akzeptieren Sie dieses Host Monitoring Profil?", + "Access Movistar and O2 Cloud from WebDAV clients and backup tools.": "Zugriff auf die Movistar- und O2-Cloud über WebDAV-Clients und Backup-Werkzeuge.", "Access Scope:": "Zugriffsbereich:", "Access bridge": "Zugangsbrücke", "Access bridge for Immich": "Access Bridge für Immich", @@ -410,6 +412,7 @@ "After the reboot, you will only be able to access the Proxmox host via:": "Nach dem Neustart können Sie nur noch auf den Proxmox-Host zugreifen über:", "After this LXC → VM switch, reboot the host so the new binding state is applied cleanly.": "Starten Sie nach diesem Wechsel von LXC → VM den Host neu, damit der neue Bindungsstatus sauber angewendet wird.", "Agentless monitoring and observability platform.": "Agentenlose Plattform für Monitoring und Observability.", + "AirDC++ administrator": "AirDC++-Administrator", "Airsonic Advanced web interface": "Airsonic Advanced Web-Schnittstelle", "Airsonic-advanced is a free, web-based media streamer, providing ubiquitious access to your music. Use it to share your music with friends, or to listen to your own music while at work. You can stream to multiple players simultaneously, for instance to one player in your kitchen and another in your living room.": "Airsonic-advanced ist ein kostenloser, webbasierter Medienstreamer, der ubiquitious Zugang zu Ihrer Musik bietet. Verwenden Sie es, um Ihre Musik mit Freunden zu teilen oder Ihre eigene Musik während der Arbeit zu hören. Sie können zu mehreren Spielern gleichzeitig streamen, zum Beispiel zu einem Spieler in Ihrer Küche und einem anderen in Ihrem Wohnzimmer.", "Aliases added to .bashrc": "Aliase wurden zu .bashrc hinzugefügt", @@ -598,11 +601,15 @@ "Attention: Removing the subscription banner may cause issues in the web interface after a future update.": "Achtung: Das Entfernen des Abonnementbanners kann nach einem zukünftigen Update zu Problemen in der Weboberfläche führen.", "Audacity is an easy-to-use, multi-track audio editor and recorder. Developed by a group of volunteers as open source.": "Audacity ist ein einfach zu bedienender, mehrspuriger Audio-Editor und Recorder. Entwickelt von einer Gruppe von Freiwilligen als Open Source.", "Audio device directory": "Audiogeräteverzeichnis", + "Audio recording and mixing in your browser": "Audioaufnahme und -mischung im Browser", "Audiobookshelf is a self-hosted audiobook and podcast server.": "Audiobookshelf ist ein selbst gehosteter Hörbuch- und Podcast-Server.", "Audit completed. Press Enter to continue...": "Prüfung abgeschlossen. Drücken Sie die Eingabetaste, um fortzufahren...", "Audit socket disabled or not required": "Audit-Socket deaktiviert oder nicht erforderlich", "Auth key is required.": "Authentifizierungsschlüssel ist erforderlich.", "Auth:": "Authentifizierung:", + "Authelia Web UI": "Authelia Web-Benutzeroberfläche", + "Authelia administrator": "Authelia-Administrator", + "Authelia session cookie domain for this portal (for example example.com)": "Authelia Sitzungscookie-Domäne für dieses Portal (z. B. example.com)", "Authentication": "Authentifizierung", "Authentication & Security": "Authentifizierung und Sicherheit", "Authentication Error": "Authentifizierungsfehler", @@ -642,6 +649,7 @@ "Available PVE Version": "Verfügbare PVE-Version", "Available Shares:": "Verfügbare Aktien:", "Available action": "Verfügbare Aktion", + "Available applications: {count}": "Verfügbare Anwendungen: {count}", "Available archives:": "Verfügbare Archive:", "Available disks detected.": "Verfügbare Festplatten erkannt.", "Available host backups:": "Verfügbare Host-Backups:", @@ -873,6 +881,7 @@ "Cannot write the image archive to {storage}. Check storage access and mount status.": "Das Bildarchiv kann nicht in {storage} geschrieben werden. Überprüfen Sie den Speicherzugriff und den Bereitstellungsstatus.", "Capabilities cannot be kept and all dropped at the same time": "Fähigkeiten können nicht gehalten werden und alle gleichzeitig fallen gelassen werden", "Catalog options": "Katalogoptionen", + "Catalog refresh unavailable; using the local catalog.": "Der Katalog konnte nicht aktualisiert werden; der lokale Katalog wird verwendet.", "Category": "Kategorie", "Caution: Maximum mode generates more heat.": "Achtung: Im Maximalmodus entsteht mehr Wärme.", "Central server for file and image backups.": "Zentraler Server für Datei- und Bildsicherungen.", @@ -1159,6 +1168,7 @@ "Compressing": "Komprimieren", "Compression": "Komprimierung", "Compression Tools": "Komprimierungswerkzeuge", + "Computer-aided design and drafting in your browser": "Computergestütztes Konstruieren und technisches Zeichnen im Browser", "Concise output of logical volumes": "Übersichtliche Ausgabe logischer Volumes", "Concise output of physical volumes": "Übersichtliche Ausgabe physischer Datenträger", "Concise output of volume groups": "Übersichtliche Ausgabe von Volumengruppen", @@ -1393,6 +1403,7 @@ "Controller + NVMe": "Controller + NVMe", "Controller + NVMe assignment will be written now and become active after host reboot.": "Die Controller + NVMe-Zuweisung wird jetzt geschrieben und wird nach dem Neustart des Hosts aktiv.", "Controller + NVMe passthrough requires machine type q35. Skipping controller assignment.": "Controller + NVMe-Passthrough erfordern Maschinentyp q35. Controller-Zuweisung wird übersprungen.", + "Controller path on the Proxmox host; prefer /dev/serial/by-id/...": "Controller-Pfad auf dem Proxmox-Host;lieber /dev/serial/by-id/...", "Controller/NVMe Conflict Policy": "Controller-/NVMe-Konfliktrichtlinie", "Controller/NVMe already present in VM config": "Controller/NVMe bereits in der VM-Konfiguration vorhanden", "Controller/NVMe already present in VM config (%s)": "Controller/NVMe ist bereits in der VM-Konfiguration vorhanden (%s)", @@ -1927,6 +1938,7 @@ "Dgraph": "Dgraph", "Diff: current system vs backup (--- system +++ backup)": "Unterschied: aktuelles System vs. Backup (--- System +++ Backup)", "Different host. Backup from:": "Anderer Gastgeber. Backup von:", + "Direct Connect file sharing with a web interface": "Direct-Connect-Dateifreigabe mit Weboberfläche", "Direct conversion completed for container": "Direkte Konvertierung für Container abgeschlossen", "Directory Exists": "Verzeichnis existiert", "Directory Not Empty": "Verzeichnis nicht leer", @@ -3340,6 +3352,7 @@ "Initial Stirling-PDF account": "Erstes Stirling-PDF-Konto", "Initial Tandoor administrator": "Initial Tandoor Administrator", "Initial Umami administrator": "Erster Administrator für Umami", + "Initial Z-Wave JS UI admin password": "Ursprüngliches Z-Wave JS UI-Administratorkennwort", "Initial administrator created:": "Erster Administrator erstellt:", "Initial administrator user": "Erster Administrator", "Initializing Borg repository if needed...": "Initialisierung des Borg-Repositorys bei Bedarf ...", @@ -3525,6 +3538,7 @@ "Interface MTU": "Schnittstellen-MTU", "Interface Names (persistent)": "Schnittstellennamen (persistent)", "Interface Type": "Schnittstellentyp", + "Interface design and prototyping in your browser": "Oberflächendesign und Prototyping im Browser", "Interface names will return to default systemd behavior.": "Schnittstellennamen kehren zum Standardverhalten von systemd zurück.", "Interface type:": "Schnittstellentyp:", "Interfaces configured": "Schnittstellen konfiguriert", @@ -3740,6 +3754,7 @@ "Jupyter Lab (token only)": "Jupyter Lab (nur Token)", "JupyterLab": "JupyterLab", "JupyterLab access token": "Zugriffstoken für JupyterLab", + "KDE's twin-panel file manager in a browser-based desktop.": "Der Twin-Panel-Dateimanager von KDE in einem browserbasierten Desktop.", "KDF:": "KDF:", "KROMA": "KROMA", "KVM MSR options added to /etc/modprobe.d/kvm.conf": "KVM-MSR-Optionen zu /etc/modprobe.d/kvm.conf hinzugefügt", @@ -3787,12 +3802,14 @@ "KiCad - A Cross Platform and Open Source Electronics Design Automation Suite.": "KiCad - Eine Cross-Plattform und Open Source Electronics Design Automation Suite.", "Kimai has no default account. Enter the container with: pct enter {main_vmid}": "Kimai hat kein Standardkonto. Öffnen Sie eine Shell im Container mit: pct enter {main_vmid}", "Kimai is a professional grade time-tracking application, free and open-source.": "Kimai ist eine professionelle Time-Tracking-Anwendung, kostenlos und Open-Source.", + "KitchenOwl": "KitchenOwl", "Kometa is a powerful tool designed to give you complete control over your media libraries. With Kometa, you can take your customization to the next level, with granular control over metadata, collections, overlays, and much more.": "Kometa ist ein leistungsstarkes Tool, das Ihnen die vollständige Kontrolle über Ihre Medienbibliotheken gibt. Mit Kometa können Sie Ihre Anpassung auf die nächste Ebene bringen, mit granularer Kontrolle über Metadaten, Sammlungen, Overlays und vielem mehr.", "Kometa reads its configuration from /config/config.yml and the container only ships /config/config.yml.template. Copy the template to config.yml, fill in the required Plex and TMDb connections, then restart the container.": "Kometa liest seine Konfiguration aus /config/config.yml und der Container liefert nur /config/config.yml.template. Kopieren Sie die Vorlage auf config.yml, füllen Sie die required Plex und TMDb-Verbindungen aus und starten Sie dann den Container neu.", "Komga is a media server for your comics, mangas, BDs, magazines and eBooks.": "Komga ist ein Medienserver für Ihre Comics, Mangas, BDs, Magazine und eBooks.", "Kopia Web UI": "Kopia-Weboberfläche", "Kopia Web UI login": "Anmeldung an der Kopia-Weboberfläche", "Krita is a professional FREE and open source painting program. It is made by artists that want to see affordable art tools for everyone.": "Krita ist ein professionelles kostenloses und Open-Source-Malprogramm. Es wird von Künstlern gemacht, die erschwingliche Kunstwerkzeuge für alle sehen wollen.", + "Krusader Web UI": "Krusader Web-Benutzeroberfläche", "LAN access to the kept containers (stack configuration incomplete):": "LAN-Zugriff auf die aufbewahrten Container (Stack-Konfiguration unvollständig):", "LAN address applied to the application URLs": "LAN-Adresse für die Anwendungs-URLs", "LLDAP": "LLDAP", @@ -4020,6 +4037,7 @@ "Manage OCI stack": "Verwalten des OCI-Stacks", "Manage PBS encryption keyfile": "PBS-Verschlüsselungsschlüsseldatei verwalten", "Manage Secure Gateway": "Secure Gateway verwalten", + "Manage a Z-Wave network from a web dashboard and WebSocket gateway.": "Verwalten Sie ein Z-Wave-Netzwerk über ein Web-Dashboard und ein WebSocket-Gateway.", "Manage and inspect VM disk images": "Verwalten und überprüfen Sie VM-Festplatten-Images", "Manage custom backup paths": "Benutzerdefinierte Sicherungspfade verwalten", "Manage custom paths (add / remove your folders)": "Benutzerdefinierte Pfade verwalten (Ordner hinzufügen/entfernen)", @@ -4029,6 +4047,7 @@ "Manage local backup target": "Lokales Backup-Ziel verwalten", "Manage private image registries.\nSaved credentials let ProxMenux access your private images.": "Private Bildregister verwalten.\nGespeicherte Anmeldeinformationen ermöglichen ProxMenux den Zugriff auf Ihre privaten Bilder.", "Manage registry accounts as root on the Proxmox node.": "Registrierungskonten als Root auf dem Proxmox-Knoten verwalten.", + "Manage teams of AI agents from a web dashboard.": "Verwalte Teams von KI-Agenten über ein Web-Dashboard.", "Managed disks must have backup enabled and a valid size": "Managed Disks müssen Backup aktiviert und eine gültige Größe haben", "Managed public route include is missing": "Die Konfigurationsdatei für die verwalteten öffentlichen Routen fehlt", "Managed public route installation failed": "Die verwalteten öffentlichen Routen konnten nicht eingerichtet werden", @@ -4134,6 +4153,7 @@ "Monitoring": "Überwachung", "Monitoring & Analytics": "Überwachung und Auswertung", "Most common cause: the archive is corrupted (interrupted write, partial copy, or storage issue).": "Häufigste Ursache: Das Archiv ist beschädigt (unterbrochener Schreibvorgang, teilweises Kopieren oder Speicherproblem).", + "Motion graphics and compositing in your browser": "Motion-Design und Compositing im Browser", "Mount Added Successfully:": "Mount erfolgreich hinzugefügt:", "Mount CIFS share:": "CIFS-Freigabe bereitstellen:", "Mount Configuration Summary:": "Zusammenfassung der Mount-Konfiguration:", @@ -4293,6 +4313,7 @@ "NVIDIA (CUDA; official GPU image)": "NVIDIA (CUDA; offizielles GPU-Bild)", "NVIDIA (NVDEC/CUDA)": "NVIDIA (NVDEC/CUDA)", "NVIDIA (NVENC/NVDEC)": "NVIDIA (NVENC/NVDEC)", + "NVIDIA (Selkies rendering and NVENC)": "NVIDIA (Selkies-Rendering und NVENC)", "NVIDIA Actions": "NVIDIA-Aktionen", "NVIDIA CPU hiding already configured": "NVIDIA-CPU-Ausblendung bereits konfiguriert", "NVIDIA Container Toolkit": "NVIDIA Container Toolkit", @@ -4434,6 +4455,8 @@ "New Search": "Neue Suche", "New VM:": "Neue VM:", "New Virtual Machine": "Neue virtuelle Maschine", + "New applications ({count})": "Neue Anwendungen ({count})", + "New applications since your last visit: {count}": "Neue Anwendungen seit Ihrem letzten Besuch: {count}", "New backup job": "Neuer Sicherungsauftrag", "New backups on this host will be unencrypted until a new keyfile is set up.": "Neue Backups auf diesem Host werden unverschlüsselt sein, bis eine neue Schlüsseldatei eingerichtet wird.", "New image compatible:": "Neues Bild kompatibel:", @@ -4896,6 +4919,8 @@ "Open the page of an application in the menu": "Die Seite einer Anwendung im Menü öffnen", "Open the web address and create the first account: the daemon registers by itself and starts the first scan.": "Öffne die Webadresse und erstelle das erste Konto: Der Daemon registriert sich selbst und startet den ersten Scan.", "Open the web address and create the password of the akadmin user in the first-run setup.": "Öffnen Sie die Webadresse und erstellen Sie im ersten Setup das Passwort des akadmin-Benutzers.", + "Open the web address and follow KitchenOwl’s onboarding to create the first account; no default account is configured.": "Öffnen Sie die Webadresse und befolgen Sie die Onboarding-Anweisungen von KitchenOwl, um das erste Konto zu erstellen. Es ist kein Standardkonto konfiguriert.", + "Open the web dashboard and link your Movistar/O2 account using its SMS flow.": "Öffnen Sie das Web-Dashboard und verknüpfen Sie Ihr Movistar/O2-Konto über die SMS-Bestätigung.", "Open-source AI-powered coding assistant": "Open-Source AI-powered Coding Assistant", "Open-source UI for building and debugging multi-agent and RAG applications": "Open-Source-Benutzeroberfläche zum Erstellen und Debuggen von Multi-Agenten- und RAG-Anwendungen", "Open-source in-memory data store.": "Open-Source-Datenspeicher im Arbeitsspeicher.", @@ -5018,6 +5043,7 @@ "Packages:": "Pakete:", "Packaging OVA file...": "OVA-Datei verpacken...", "Packing installer archive...": "Installationsarchiv packen...", + "Page layout and publishing in your browser": "Seitenlayout und Publizieren im Browser", "PairDrop is a sublime alternative to AirDrop that works on all platforms. Send images, documents or text via peer to peer connection to devices in the same local network/Wi-Fi or to paired devices.": "PairDrop ist eine großartige Alternative zu AirDrop, die auf allen Plattformen funktioniert. Senden Sie Bilder, Dokumente oder Text per Peer-to-Peer-Verbindung an Geräte im gleichen lokalen Netzwerk / WLAN oder an gekoppelte Geräte.", "Paperless configuration cancelled": "Papierlose Konfiguration abgesagt", "Paperless-ngx WebUI": "Paperless-ngx WebUI", @@ -5036,6 +5062,7 @@ "Pass /dev/video12 to the LXC": "Pass /dev/video12 zum LXC", "Pass a host device to the LXC": "Übergeben Sie ein Host-Gerät an den LXC", "Pass the UPS to the LXC": "Übergeben Sie die USV an LXC", + "Pass through a Z-Wave USB or serial controller": "Durchlaufen eines Z-Wave-USB- oder seriellen Controllers", "Passing NVIDIA does not enable acceleration inside the application. The host needs NVIDIA Container Toolkit and a compatible image. Continue?": "Durch die Übergabe von NVIDIA wird keine Beschleunigung innerhalb der Anwendung aktiviert. Der Host benötigt NVIDIA Container Toolkit und ein kompatibles Image. Weitermachen?", "Passkey-based identity provider for self-hosted apps.": "Passkey-basierter Identitätsanbieter für selbst gehostete Apps.", "Passphrase used to unlock the imported keyfile (leave blank if the keyfile is unencrypted / kdf=none):": "Passphrase, die zum Entsperren der importierten Schlüsseldatei verwendet wird (leer lassen, wenn die Schlüsseldatei unverschlüsselt ist / kdf=none):", @@ -5143,6 +5170,8 @@ "Personal finance management application": "Persönliche Finanzverwaltung Anwendung", "Personal media server for browsing and playing a local video library, with direct play and optional modules.": "Persönlicher Medienserver zum Durchsuchen und Abspielen einer lokalen Videosammlung, mit Direktwiedergabe und optionalen Modulen.", "Photo and video library with optional GPU transcoding and machine learning": "Foto- und Videobibliothek mit optionaler GPU-Transcodierung und maschinellem Lernen", + "Photo editing in your browser": "Fotobearbeitung im Browser", + "Photo library and RAW development in your browser": "Fotoverwaltung und RAW-Entwicklung im Browser", "PhotoPrism": "PhotoPrism", "Photofield": "Photofield", "Phpmyadmin is a free software tool written in PHP, intended to handle the administration of MySQL over the Web. phpMyAdmin supports a wide range of operations on MySQL and MariaDB.": "Phpmyadmin ist ein in PHP geschriebenes kostenloses Software-Tool, das die Verwaltung von MySQL über das Web übernehmen soll. phpMyAdmin unterstützt eine breite Palette von operationen auf MySQL und MariaDB.", @@ -5235,6 +5264,7 @@ "Preparing the machine learning container for the new choice...": "Container für maschinelles Lernen wird für die neue Auswahl vorbereitet...", "Preparing the recreation...": "Vorbereitung der Erholung...", "Preparing the update...": "Vorbereitung des Updates...", + "Presentations and slide decks in your browser": "Präsentationen und Folien im Browser", "Preserving logs to /var/log.hdd before unmounting...": "Protokolle werden vor dem Aufheben der Bereitstellung in /var/log.hdd beibehalten ...", "Press 'q' to exit": "Drücken Sie „q“, um den Vorgang zu beenden", "Press Ctrl+C to stop the server and return to menu.": "Drücken Sie Strg+C, um den Server zu stoppen und zum Menü zurückzukehren.", @@ -5395,6 +5425,7 @@ "Proxmox web interface: Datacenter > Storage > Add > ZFS": "Proxmox-Weboberfläche: Datencenter > Speicher > Hinzufügen > ZFS", "Proxmox web interface: Datacenter > Storage > Add > iSCSI": "Proxmox-Weboberfläche: Rechenzentrum > Speicher > Hinzufügen > iSCSI", "Proxy address to reach the API (optional)": "Proxy-Adresse zum Erreichen der API (optional)", + "Public HTTPS URL used to open this Authelia portal (for example https://auth.example.com)": "Öffentliche HTTPS-URL, die zum Öffnen dieses Authelia-Portals verwendet wird (zum Beispiel https://auth.example.com)", "Public UDP port clients connect to": "Öffentliche UDP Port Clients verbinden sich mit", "Public UDP port peers connect to": "Öffentliche UDP-Port Peers verbinden sich mit", "Public URL of phpMyAdmin when it is served behind a reverse proxy": "Öffentliche URL von phpMyAdmin, wenn sie hinter einem Reverse-Proxy bereitgestellt wird", @@ -5455,6 +5486,7 @@ "Re-running pre-check after repairs...": "Nach der Reparatur wird die Vorkontrolle erneut durchgeführt...", "Reachable": "Erreichbar", "Read an oci-compose from a file of this host": "Lesen Sie ein oci-compose aus einer Datei dieses Hosts", + "Read and edit PDF documents in your browser": "PDF-Dokumente im Browser lesen und bearbeiten", "Read its Compose file from a file of this host": "Lesen Sie die Compose-Datei aus einer Datei dieses Hosts", "Read manga through a self-hosted server and web UI.": "Manga über einen selbst gehosteten Server mit Weboberfläche lesen.", "Read the link with pct console CTID on the Proxmox host, or from the Console panel of the container in the Proxmox web interface, then open the https://playit.gg/claim/ address it shows in a browser and sign in to playit.gg. Ctrl+a q leaves pct console.": "Lesen Sie den Link mit pct-Konsole CTID auf dem Proxmox-Host oder vom Konsolen-Panel des Containers in der Proxmox-Weboberfläche, öffnen Sie dann die https://playit.gg/claim/Adresse, die in einem Browser angezeigt wird, und melden Sie sich bei playit.gg an. Strg + a q verlässt pct Konsole.", @@ -5560,6 +5592,7 @@ "Recursive and authoritative DNS server.": "Rekursiver und autoritativer DNS-Server.", "Redmine": "Redmine", "Refresh APT index and verify repositories:": "APT-Index aktualisieren und Repositorys überprüfen:", + "Refresh catalog": "Katalog aktualisieren", "Refresh the configured APT repositories and review the PBS package upgrade? Repository files will be preserved.": "Die konfigurierten APT-Repositories aktualisieren und die PBS-Paketaktualisierung prüfen? Repository-Dateien bleiben erhalten.", "Refresh the index from the image sources": "Den Index aus den Image-Quellen aktualisieren", "Refresh token of the connector, from the Twingate admin console": "Aktualisierungstoken des Connectors aus der Twingate-Verwaltungskonsole", @@ -5567,6 +5600,7 @@ "Refresh your browser to see changes (server restart may be required)": "Aktualisieren Sie Ihren Browser, um Änderungen zu sehen (möglicherweise ist ein Neustart des Servers erforderlich).", "Refreshing apt cache...": "Apt-Cache wird aktualisiert...", "Refreshing the NVIDIA runtime...": "Erfrischung der NVIDIA Runtime...", + "Refreshing the application catalog...": "Anwendungskatalog wird aktualisiert...", "Refusing an unexpected rootfs path:": "Ablehnung eines unerwarteten Rootfs-Pfades:", "Regenerate the catalog templates": "Die Katalogvorlagen neu erstellen", "Regenerate the template of one application": "Die Vorlage einer Anwendung neu erstellen", @@ -6258,6 +6292,7 @@ "Self-hosted file sync and sharing.": "Selbst gehostete Dateisynchronisierung und -freigabe.", "Self-hosted file toolkit for images, video, audio, PDFs, and files": "Selbst gehostetes Datei-Toolkit für Bilder, Video, Audio, PDFs und Dateien", "Self-hosted forms and workflows.": "Selbst gehostete Formulare und Arbeitsabläufe.", + "Self-hosted grocery list and recipe manager": "Selbstgehostete Einkaufsliste und Rezeptmanager", "Self-hosted household plant-care tracker with reminders, photos, and multiple user accounts.": "Selbst gehostete Pflegeverwaltung für Zimmerpflanzen mit Erinnerungen, Fotos und mehreren Benutzerkonten.", "Self-hosted image-hosting service with authenticated uploads, image processing, public links, and local storage.": "Selbst gehosteter Bildhosting-Dienst mit authentifizierten Uploads, Bildverarbeitung, öffentlichen Links und lokalem Speicher.", "Self-hosted internet archiving solution": "Selbst gehostete Internet-Archivierungslösung", @@ -6452,6 +6487,7 @@ "Simple and easy to use DDNS": "Einfach und einfach zu bedienen DDNS", "Simple self-hosted file sharing.": "Einfacher, selbst gehosteter Dateiaustausch.", "Single GPU Warning": "Warnung vor einer einzelnen GPU", + "Single sign-on and multi-factor authentication": "Single Sign-On und Multi-Faktor-Authentifizierung", "Single target found — selected automatically:": "Einzelnes Ziel gefunden – automatisch ausgewählt:", "Single-node log storage and query UI.": "Logspeicher mit Abfrageoberfläche für einen einzelnen Knoten.", "Single-node observability database for metrics, logs and traces.": "Einzelknoten-Datenbank für Metriken, Protokolle und Traces.", @@ -6520,7 +6556,9 @@ "Specific subnet (enter manually)": "Spezifisches Subnetz (manuell eingeben)", "Speedtest Tracker web interface": "Speedtest Tracker Web-Schnittstelle", "Speedtest-tracker is a self-hosted internet performance tracking application that runs speedtest checks against Ookla's Speedtest service.": "Speedtest-Tracker ist eine selbst gehostete Internet-Performance-Tracking-Anwendung, die Speedtest-Checks gegen den Speedtest-Service von Ookla durchführt.", + "Spoolman web interface": "Spoolman-Weboberfläche", "Spotube is an open source, cross-platform Spotify client compatible across multiple platforms utilizing Spotify's data API and YouTube, Piped.video or JioSaavn as an audio source, eliminating the need for Spotify Premium": "Spotube ist ein Open-Source-, plattformübergreifender Spotify-Client, der auf mehreren Plattformen mit Spotifys Daten-API und YouTube, Piped, kompatibel ist. Video oder JioSaavn als Audioquelle, wodurch Spotify Premium entfällt", + "Spreadsheets in your browser": "Tabellenkalkulation im Browser", "Sshwifty": "Sshwifty", "Sshwifty web access (password only)": "Sshwifty-Webzugriff (nur Passwort)", "Stable (main branch)": "Stabil (Hauptzweig)", @@ -7417,6 +7455,7 @@ "Then bind-mount to container": "Dann bind-mounten Sie es an den Container", "Then change the VM display to none (vga: none) when the guest is stable.": "Ändern Sie dann die VM-Anzeige auf „Keine“ (vga: none), wenn der Gast stabil ist.", "Then change the VM display to none (vga: none) when the system is stable.": "Ändern Sie dann die VM-Anzeige auf „Keine“ (vga: none), wenn das System stabil ist.", + "Then restart the container so the new model appears in the list.": "Starten Sie den Container anschließend neu, damit das neue Modell in der Liste erscheint.", "Then run this option again:": "Führen Sie dann diese Option erneut aus:", "Then update /etc/fstab on the host with the same options.": "Aktualisieren Sie dann /etc/fstab auf dem Host mit denselben Optionen.", "There are extra disks or bind mounts outside the journal; the rootfs is not replaced": "Es gibt zusätzliche Festplatten oder Bindehalterungen außerhalb des Journals; die Rootfs werden nicht ersetzt", @@ -7464,6 +7503,7 @@ "This container does not have apt-get. NFS client installation only supports Debian/Ubuntu containers.": "Dieser Container verfügt nicht über apt-get. Die NFS-Client-Installation unterstützt nur Debian/Ubuntu-Container.", "This container does not have apt-get. Samba client installation only supports Debian/Ubuntu containers.": "Dieser Container verfügt nicht über apt-get. Die Samba-Client-Installation unterstützt nur Debian/Ubuntu-Container.", "This container has no GPU configured. Coral TPU works best alongside hardware video decoding (Quick Sync, VA-API, NVENC) for apps like Frigate.": "Für diesen Container ist keine GPU konfiguriert. Coral TPU funktioniert am besten zusammen mit der Hardware-Videodekodierung (Quick Sync, VA-API, NVENC) für Apps wie Frigate.", + "This container is managed by high availability. Remove ct:{vmid} from HA (Datacenter → HA, or ha-manager remove ct:{vmid}), repeat the operation and add it back to HA with the same settings.": "Dieser Container wird von der Hochverfügbarkeit verwaltet. Entfernen Sie ct:{vmid} aus HA (Rechenzentrum → HA oder ha-manager remove ct:{vmid}), wiederholen Sie den Vorgang und fügen Sie ihn mit denselben Einstellungen wieder zu HA hinzu.", "This container is not a member of a multi-container application": "Dieser Container ist kein Mitglied einer Multi-Container-Anwendung", "This container is not a registered OCI instance.": "Dieser Container ist keine registrierte OCI-Instanz.", "This container was restored or came back from another host after its record on this host was written. Open this menu again to recover it; nothing was changed.": "Dieser Container wurde wiederhergestellt oder kam von einem anderen Host zurück, nachdem sein Datensatz auf diesem Host geschrieben wurde. Öffnen Sie dieses Menü erneut, um ihn zu registrieren; es wurde nichts geändert.", @@ -7582,6 +7622,7 @@ "Tip: You can install the QEMU Guest Agent inside the VM with:": "Tipp: Sie können den QEMU Guest Agent in der VM installieren mit:", "Tip: zfs set acltype=posixacl xattr=sa / enables full ACL support.": "Tipp: zfs set acltype=posixacl xattr=sa / aktiviert die vollständige ACL-Unterstützung.", "Tmpfs size in MiB for": "Tmpfs Größe in MiB für", + "To add a model, open the console of the container in Proxmox and download it into /models, for example:": "Um ein Modell hinzuzufügen, öffnen Sie die Konsole des Containers in Proxmox und laden Sie es nach /models herunter, zum Beispiel:", "To allow LXC write access, change the NFS export on the server to include:": "Um LXC-Schreibzugriff zu ermöglichen, ändern Sie den NFS-Export auf dem Server so, dass er Folgendes enthält:", "To apply it to the current shell now, run:": "Um sie jetzt auf die aktuelle Shell anzuwenden, führen Sie Folgendes aus:", "To assign VFs to VMs or LXCs, edit the configuration manually via the Proxmox web interface. The Physical Function will remain bound to the native driver.": "Um VFs VMs oder LXCs zuzuweisen, bearbeiten Sie die Konfiguration manuell über die Proxmox-Weboberfläche. Die physische Funktion bleibt an den nativen Treiber gebunden.", @@ -7621,6 +7662,7 @@ "Total routes": "Gesamtrouten", "Total size:": "Gesamtgröße:", "Toxiproxy": "Toxiproxy", + "Track 3D printer filament spools and usage.": "Verfolgen Sie die Filamentspulen und den Verbrauch von 3D-Druckern.", "Track movies, television, anime and games you want to enjoy.": "Behalten Sie Filme, Serien, Anime und Spiele im Blick, die Sie ansehen oder spielen möchten.", "Track subscriptions and recurring expenses.": "Abonnements und wiederkehrende Ausgaben verwalten.", "Track your music listening history on your own server.": "Hörverlauf für Musik auf dem eigenen Server erfassen.", @@ -7855,6 +7897,7 @@ "Update the whole stack?": "Aktualisieren Sie den gesamten Stack?", "Update, modify or recreate one installed OCI instance": "Eine installierte OCI-Instanz aktualisieren, ändern oder neu erstellen", "Updated": "Aktualisiert", + "Updated recipes since your last visit: {count}": "Aktualisierte Rezepte seit Ihrem letzten Besuch: {count}", "Updated sharedfiles group to GID: 101000": "Sharedfiles-Gruppe auf GID aktualisiert: 101000", "Updated stack checked": "Aktualisierter Stapel geprüft", "Updated:": "Aktualisiert:", @@ -8060,6 +8103,7 @@ "Variables": "Variablen", "Variables the installation asks for:": "Variablen, die von der Anlage verlangt werden:", "Vaultwarden Web Vault": "Vaultwarden Web Vault", + "Vector illustration in your browser": "Vektorillustrationen im Browser", "Vector search engine with a built-in dashboard.": "Vektorsuchmaschine mit integriertem Dashboard.", "Verbose pool status": "Ausführlicher Poolstatus", "Verification": "Überprüfung", @@ -8090,6 +8134,7 @@ "Versions shown belong to maintained NVIDIA branches that list your GPU PCI ID and are new enough to build against the running kernel. DKMS compilation is the final validation. The recommended version keeps the current branch, or uses the NVIDIA Production Branch on a fresh install.": "Die angezeigten Versionen gehören zu gepflegten NVIDIA-Zweigen, die Ihre GPU-PCI-ID auflisten und neu genug sind, um auf dem laufenden Kernel zu erstellen. Die DKMS-Kompilierung ist die endgültige Validierung. Die empfohlene Version behält den aktuellen Zweig bei oder verwendet den NVIDIA Production Branch bei einer Neuinstallation.", "VictoriaLogs": "VictoriaLogs", "Video acceleration and object detection are independent choices; the installer does not write camera or detector YAML.": "Videobeschleunigung und Objekterkennung sind unabhängige Entscheidungen; der Installer schreibt keine Kamera oder Detektor YAML.", + "Video editing in your browser": "Videoschnitt im Browser", "Video transcoding acceleration": "Videotranscodierungsbeschleunigung", "Video transcoding profile not implemented:": "Videotranskodierungsprofil nicht implementiert:", "View CIFS Mounts (pvesm + fstab)": "CIFS-Mounts anzeigen (pvesm + fstab)", @@ -8199,12 +8244,17 @@ "Web access": "Webzugang", "Web address of the AdGuard Home that receives the settings (e.g. http://192.168.1.3)": "Webadresse des AdGuard Home, der die Einstellungen erhält (z. B. http://192.168.1.3)", "Web address of the main AdGuard Home, whose settings are copied (e.g. http://192.168.1.2)": "Webadresse des AdGuard Home, dessen Einstellungen kopiert sind (z. B. http://192.168.1.2)", + "Web dashboard": "Web-Dashboard", + "Web dashboard and WebDAV": "Web-Dashboard und WebDAV", "Web interface": "Weboberfläche", "Web interface for Borg Backup repositories and jobs.": "Weboberfläche für Borg-Backup-Repositories und -Jobs.", "Web interface for managing 3D printers.": "Weboberfläche zur Verwaltung von 3D-Druckern.", "Web interface to manage devices running Tasmota firmware.": "Web-Schnittstelle zur Verwaltung von Geräten mit Tasmota-Firmware.", "Web reverse proxy with an administration interface.": "Web-Reverse-Proxy mit Administrationsoberfläche.", "WebCord can be summarized as a pack of security and privacy hardenings, Discord features reimplementations, Electron / Chromium / Discord bugs workarounds, stylesheets, internal pages and wrapped https://discord.com page, designed to conform with ToS as much as it is possible (or hide the changes that might violate it from Discord's eyes).": "WebCord kann als ein Paket von Sicherheits- und Datenschutz-Härtungen, Discord-Features-Reimplementierungen, Electron / Chromium / Discord-Bugs-Workarounds, Stylesheets, interne Seiten und verpackte https://discord.com-Seite zusammengefasst werden, die so weit wie möglich mit ToS konform sind (oder die Änderungen, die es verletzen könnten, vor Discords Augen verbergen).", + "WebDAV": "WebDAV", + "WebDAV and dashboard password (leave empty to generate)": "Passwort für WebDAV und Dashboard (leer = generiert)", + "WebDAV is available on container port 8080. Account linking is required before cloud file operations.": "WebDAV ist auf Port 8080 des Containers verfügbar. Vor Dateioperationen in der Cloud muss das Konto verknüpft werden.", "Webgrabplus is a multi-site incremental xmltv epg grabber. It collects tv-program guide data from selected tvguide sites for your favourite channels.": "Webgrabplus ist ein multi-site inkrementeller xmltv epg grabber. Es sammelt tv-programm-guide-daten von ausgewählten tvguide-seiten für ihre lieblingskanäle.", "Webservers & Proxies": "Webserver und Proxys", "Website": "Webseite", @@ -8262,6 +8312,7 @@ "Without a usable reset path, passthrough reliability is poor and VM": "Ohne einen nutzbaren Reset-Pfad ist die Passthrough-Zuverlässigkeit schlecht und VM", "Wizard cancelled": "Assistent abgebrochen", "Wolfenstein: Blade of Agony is a story-driven WWII shooter inspired by Wolfenstein and Doom.": "Wolfenstein: Blade of Agony ist ein Story-basierter WWII-Shooter, inspiriert von Wolfenstein und Doom.", + "Word processing in your browser": "Textverarbeitung im Browser", "Workflow automation tool": "Workflow Automation Tool", "Working directory:": "Arbeitsverzeichnis:", "Works with LVM, ZFS, and BTRFS storage types": "Funktioniert mit LVM-, ZFS- und BTRFS-Speichertypen", @@ -8312,6 +8363,8 @@ "Your machine learning Env work with Jupyter Lab": "Ihre Machine Learning Env arbeitet mit Jupyter Lab", "Your next YouTube media manager": "Dein nächster YouTube Media Manager", "Your_spotify is a self-hosted application that tracks what you listen and offers you a dashboard to explore statistics about it! It's composed of a web server which polls the Spotify API every now and then and a web application on which you can explore your statistics.": "Your_spotify ist eine selbst gehostete Anwendung, die verfolgt, was Sie hören und bietet Ihnen ein Dashboard, um Statistiken darüber zu erkunden! Es besteht aus einem Webserver, der hin und wieder die Spotify-API abfragt, und einer Webanwendung, auf der Sie Ihre Statistiken erkunden können.", + "Z-Wave JS UI": "Z-Wave JS UI", + "Z-Wave JS UI admin login": "Z-Wave JS UI Admin-Login", "ZFS ARC config removed (kernel defaults will apply on reboot)": "ZFS ARC-Konfiguration entfernt (Kernel-Standardeinstellungen gelten beim Neustart)", "ZFS ARC maximum configured:": "ZFS ARC maximal konfiguriert:", "ZFS ARC optimization completed": "ZFS ARC-Optimierung abgeschlossen", @@ -8425,6 +8478,7 @@ "could not be compiled for kernel": "konnte nicht für den Kernel kompiliert werden", "could not validate NVIDIA; exit code": "konnte NVIDIA nicht validieren; Exit-Code", "cpuunits must be between 8 and 10000": "cpuunits müssen zwischen 8 und 10000 sein", + "curl -L -o /models/gemma-3-1b-it-Q4_K_M.gguf https://huggingface.co/ggml-org/gemma-3-1b-it-GGUF/resolve/main/gemma-3-1b-it-Q4_K_M.gguf": "curl -L -o /models/gemma-3-1b-it-Q4_K_M.gguf https://huggingface.co/ggml-org/gemma-3-1b-it-GGUF/resolve/main/gemma-3-1b-it-Q4_K_M.gguf", "custom": "Custom", "custom dependency commands are not yet supported": "Custom Dependency Commands werden noch nicht unterstützt", "custom path(s) saved.": "Benutzerdefinierte(r) Pfad(e) gespeichert.", @@ -8631,6 +8685,7 @@ "kexec-tools is not installed or already removed.": "kexec-tools ist nicht installiert oder bereits entfernt.", "layers": "Schichten", "legacy .link file(s) to the ProxMenux-managed format": "ältere .link-Dateien in das von ProxMenux verwaltete Format", + "llama.cpp shows every GGUF model saved in /models in its web UI and loads the one you choose.": "llama.cpp zeigt in seiner Weboberfläche alle in /models gespeicherten GGUF-Modelle an und lädt das ausgewählte.", "log2ram completely removed from system": "log2ram vollständig aus dem System entfernt", "main container of the application": "Hauptcontainer der Anwendung", "make cloned mount tree read-only": "geklonten Mount-Baum schreibgeschützt machen", diff --git a/lang/es.json b/lang/es.json index de005560..18af7e3c 100644 --- a/lang/es.json +++ b/lang/es.json @@ -183,6 +183,7 @@ "A tunneling daemon by Cloudflare that safely exposes your web servers into the internet.": "Un daemon de túneles de Cloudflare que expone tus servidores web a Internet de forma segura.", "A unified email and collaboration server.": "Un servidor unificado de correo electrónico y colaboración.", "A unified platform for logs, metrics and traces.": "Una plataforma unificada para registros, métricas y trazas.", + "A validating, recursive DNS resolver for your private network.": "un solucionador de DNS recursivo y de validación para su red privada.", "A versatile file conversion tool that supports multiple formats.": "Una herramienta versátil de conversión de archivos compatible con múltiples formatos.", "A visual tool for exploring and documenting database schemas.": "Una herramienta visual para explorar y documentar esquemas de bases de datos.", "A web GUI client of Project V which supports VMess, VLESS, SS, SSR, Trojan, Tuic and Juicity protocols": "Un cliente web de Project V compatible con los protocolos VMess, VLESS, SS, SSR, Trojan, Tuic y Juicity", @@ -255,6 +256,7 @@ "Acceleration for llama.cpp": "Aceleración para llama.cpp", "Accept routes from other nodes?": "¿Aceptar rutas de otros nodos?", "Accept this host monitoring profile?": "¿Aceptar este perfil de monitorización del host?", + "Access Movistar and O2 Cloud from WebDAV clients and backup tools.": "Accede a la nube de Movistar y O2 desde clientes WebDAV y herramientas de backup.", "Access Scope:": "Ámbito de acceso:", "Access bridge": "Puente de acceso", "Access bridge for Immich": "Puente de acceso para Immich", @@ -408,6 +410,7 @@ "After the reboot, you will only be able to access the Proxmox host via:": "Después del reinicio, solo podrá acceder al host Proxmox a través de:", "After this LXC → VM switch, reboot the host so the new binding state is applied cleanly.": "Después de este cambio LXC → VM, reinicia el host para que el nuevo estado de enlace se aplique limpiamente.", "Agentless monitoring and observability platform.": "Plataforma de monitorización y observabilidad sin agentes.", + "AirDC++ administrator": "Administrador de AirDC++", "Airsonic Advanced web interface": "Interfaz web de Airsonic Advanced", "Airsonic-advanced is a free, web-based media streamer, providing ubiquitious access to your music. Use it to share your music with friends, or to listen to your own music while at work. You can stream to multiple players simultaneously, for instance to one player in your kitchen and another in your living room.": "Airsonic-advanced es un servidor de streaming multimedia gratuito y basado en web que da acceso a tu música desde cualquier lugar. Úsalo para compartir tu música con amigos o para escuchar la tuya en el trabajo. Puede transmitir a varios reproductores a la vez, por ejemplo a uno en la cocina y a otro en el salón.", "Aliases added to .bashrc": "Alias añadidos a .bashrc", @@ -595,12 +598,16 @@ "Attention: Removing the subscription banner may cause issues in the web interface after a future update.": "Atención: Eliminar el banner de suscripción puede causar problemas en la interfaz web después de una actualización futura.", "Audacity is an easy-to-use, multi-track audio editor and recorder. Developed by a group of volunteers as open source.": "Audacity es un editor y grabador de audio multipista fácil de usar, desarrollado como código abierto por un grupo de voluntarios.", "Audio device directory": "Directorio de dispositivos de audio", + "Audio recording and mixing in your browser": "Grabación y mezcla de audio en el navegador", "Audio/Video": "Audio/vídeo", "Audiobookshelf is a self-hosted audiobook and podcast server.": "Audiobookshelf es un servidor autoalojado de audiolibros y podcasts.", "Audit completed. Press Enter to continue...": "Auditoría completada. Pulsa Intro para continuar...", "Audit socket disabled or not required": "Socket de auditoría deshabilitado o no requerido", "Auth key is required.": "Se requiere clave de autenticación.", "Auth:": "Autenticación:", + "Authelia Web UI": "Authelia Interfaz de usuario web", + "Authelia administrator": "administrador Authelia", + "Authelia session cookie domain for this portal (for example example.com)": "dominio de cookie de sesión Authelia para este portal (por ejemplo, ejemplo.com)", "Authentication": "Autenticación", "Authentication & Security": "Authentication & Security", "Authentication Error": "Error de autenticación", @@ -640,6 +647,7 @@ "Available PVE Version": "Versión PVE disponible", "Available Shares:": "Recursos compartidos disponibles:", "Available action": "Acción disponible", + "Available applications: {count}": "Aplicaciones disponibles: {count}", "Available archives:": "Archivos disponibles:", "Available disks detected.": "Discos disponibles detectados.", "Available host backups:": "Backups de host disponibles:", @@ -872,6 +880,7 @@ "Cannot write the image archive to {storage}. Check storage access and mount status.": "No se puede escribir el archivo de la imagen en {storage}. Comprueba el acceso al almacenamiento y que esté montado.", "Capabilities cannot be kept and all dropped at the same time": "No se pueden mantener las capacidades y todas se disminuyen al mismo tiempo", "Catalog options": "Opciones del catálogo", + "Catalog refresh unavailable; using the local catalog.": "No se pudo actualizar el catálogo; se está usando la copia local.", "Category": "Categoría", "Caution: Maximum mode generates more heat.": "Precaución: el modo máximo genera más calor.", "Central server for file and image backups.": "Servidor central para copias de seguridad de archivos e imágenes.", @@ -1159,6 +1168,7 @@ "Compressing": "Comprimiendo", "Compression": "Compresión", "Compression Tools": "Herramientas de compresión", + "Computer-aided design and drafting in your browser": "Diseño CAD y dibujo técnico en el navegador", "Concise output of logical volumes": "Salida concisa de volúmenes lógicos", "Concise output of physical volumes": "Salida concisa de volúmenes físicos", "Concise output of volume groups": "Salida concisa de grupos de volúmenes", @@ -1391,6 +1401,7 @@ "Controller + NVMe": "Controlador + NVMe", "Controller + NVMe assignment will be written now and become active after host reboot.": "La asignación de controlador + NVMe se escribirá ahora y se activará después de reiniciar el host.", "Controller + NVMe passthrough requires machine type q35. Skipping controller assignment.": "El passthrough de controlador + NVMe requiere el tipo de máquina q35. Se omite la asignación del controlador.", + "Controller path on the Proxmox host; prefer /dev/serial/by-id/...": "Ruta del controlador en el host Proxmox;prefiero /dev/serial/by-id/...", "Controller/NVMe Conflict Policy": "Política de conflictos entre controlador y NVMe", "Controller/NVMe already present in VM config": "Controlador/NVMe ya presente en la configuración de VM", "Controller/NVMe already present in VM config (%s)": "El controlador/NVMe ya está en la configuración de la VM (%s)", @@ -1925,6 +1936,7 @@ "Dgraph": "Dgraph", "Diff: current system vs backup (--- system +++ backup)": "Diferencias: sistema actual frente al backup (--- sistema +++ backup)", "Different host. Backup from:": "Host diferente. Backup procedente de:", + "Direct Connect file sharing with a web interface": "Intercambio de archivos Direct Connect con interfaz web", "Direct conversion completed for container": "Conversión directa completada para el contenedor", "Directory Exists": "El directorio existe", "Directory Not Empty": "Directorio no vacío", @@ -3336,6 +3348,7 @@ "Initial Stirling-PDF account": "Cuenta inicial de Stirling-PDF", "Initial Tandoor administrator": "Administrador inicial de Tandoor", "Initial Umami administrator": "Administrador inicial de Umami", + "Initial Z-Wave JS UI admin password": "Contraseña de administrador inicial Z-Wave JS UI", "Initial administrator created:": "Administrador inicial creado:", "Initial administrator user": "Usuario administrador inicial", "Initializing Borg repository if needed...": "Inicializando el repositorio de Borg si es necesario...", @@ -3521,6 +3534,7 @@ "Interface MTU": "MTU de interfaz", "Interface Names (persistent)": "Nombres de interfaces persistentes", "Interface Type": "Tipo de interfaz", + "Interface design and prototyping in your browser": "Diseño de interfaces y prototipos en el navegador", "Interface names will return to default systemd behavior.": "Los nombres de las interfaces volverán al comportamiento predeterminado de systemd.", "Interface type:": "Tipo de interfaz:", "Interfaces configured": "Interfaces configuradas", @@ -3736,6 +3750,7 @@ "Jupyter Lab (token only)": "Jupyter Lab (solo token)", "JupyterLab": "JupyterLab", "JupyterLab access token": "Token de acceso de JupyterLab", + "KDE's twin-panel file manager in a browser-based desktop.": "administrador de archivos de panel doble de KDE en un escritorio basado en navegador.", "KDF:": "KDF:", "KROMA": "KROMA", "KVM MSR options added to /etc/modprobe.d/kvm.conf": "Opciones de KVM MSR añadidas a /etc/modprobe.d/kvm.conf", @@ -3783,12 +3798,14 @@ "KiCad - A Cross Platform and Open Source Electronics Design Automation Suite.": "KiCad - Una suite de automatización de diseño electrónico multiplataforma y de código abierto.", "Kimai has no default account. Enter the container with: pct enter {main_vmid}": "Kimai no tiene una cuenta predeterminada. Entra en el contenedor con: pct enter {main_vmid}", "Kimai is a professional grade time-tracking application, free and open-source.": "Kimai es una aplicación profesional de control de tiempo, gratuita y de código abierto.", + "KitchenOwl": "KitchenOwl", "Kometa is a powerful tool designed to give you complete control over your media libraries. With Kometa, you can take your customization to the next level, with granular control over metadata, collections, overlays, and much more.": "Kometa es una potente herramienta que te da control total sobre tus bibliotecas multimedia. Con Kometa llevas la personalización al siguiente nivel, con control detallado de metadatos, colecciones, overlays y mucho más.", "Kometa reads its configuration from /config/config.yml and the container only ships /config/config.yml.template. Copy the template to config.yml, fill in the required Plex and TMDb connections, then restart the container.": "Kometa lee su configuración de /config/config.yml y el contenedor solo incluye /config/config.yml.template. Copia la plantilla a config.yml, rellena las conexiones obligatorias de Plex y TMDb y reinicia el contenedor.", "Komga is a media server for your comics, mangas, BDs, magazines and eBooks.": "Komga es un servidor multimedia para tus cómics, mangas, BDs, revistas y eBooks.", "Kopia Web UI": "Interfaz web de Kopia", "Kopia Web UI login": "Acceso a la interfaz web de Kopia", "Krita is a professional FREE and open source painting program. It is made by artists that want to see affordable art tools for everyone.": "Krita es un programa de pintura profesional, GRATUITO y de código abierto. Lo hacen artistas que quieren herramientas de arte asequibles para todos.", + "Krusader Web UI": "Krusader Interfaz de usuario web", "LAN access to the kept containers (stack configuration incomplete):": "Acceso LAN a los contenedores conservados (configuración de la pila incompleta):", "LAN address applied to the application URLs": "Dirección LAN aplicada a las URL de la aplicación", "LLDAP": "LLDAP", @@ -4015,6 +4032,7 @@ "Manage OCI stack": "Administrar la pila de OCI", "Manage PBS encryption keyfile": "Administrar el archivo de claves de cifrado PBS", "Manage Secure Gateway": "Administrar Secure Gateway", + "Manage a Z-Wave network from a web dashboard and WebSocket gateway.": "administre una red Z-Wave desde un panel web y una puerta de enlace WebSocket.", "Manage and inspect VM disk images": "Administrar e inspeccionar imágenes de disco de VM", "Manage custom backup paths": "Configurar rutas de backup personalizadas", "Manage custom paths (add / remove your folders)": "Configurar rutas personalizadas (añadir/eliminar carpetas)", @@ -4024,6 +4042,7 @@ "Manage local backup target": "Configurar el destino del backup local", "Manage private image registries.\nSaved credentials let ProxMenux access your private images.": "Gestiona los registros de imágenes privadas.\nCon las credenciales guardadas, ProxMenux puede acceder a tus imágenes privadas.", "Manage registry accounts as root on the Proxmox node.": "Gestiona las cuentas de registro como root en el nodo Proxmox.", + "Manage teams of AI agents from a web dashboard.": "Gestiona equipos de agentes de IA desde un panel web.", "Managed disks must have backup enabled and a valid size": "Los discos gestionados deben tener habilitado backup y un tamaño válido", "Managed public route include is missing": "Falta el archivo de configuración de las rutas públicas gestionadas", "Managed public route installation failed": "No se han podido configurar las rutas públicas gestionadas", @@ -4129,6 +4148,7 @@ "Monitoring": "Monitorización", "Monitoring & Analytics": "Monitoring & Analytics", "Most common cause: the archive is corrupted (interrupted write, partial copy, or storage issue).": "Causa más común: el archivo está dañado (escritura interrumpida, copia parcial o problema de almacenamiento).", + "Motion graphics and compositing in your browser": "Animación gráfica y composición en el navegador", "Mount Added Successfully:": "Montaje añadido correctamente:", "Mount CIFS share:": "Monta el recurso compartido CIFS:", "Mount Configuration Summary:": "Resumen de configuración de montaje:", @@ -4288,6 +4308,7 @@ "NVIDIA (CUDA; official GPU image)": "NVIDIA (CUDA; imagen oficial para GPU)", "NVIDIA (NVDEC/CUDA)": "NVIDIA (NVDEC/CUDA)", "NVIDIA (NVENC/NVDEC)": "NVIDIA (NVENC/NVDEC)", + "NVIDIA (Selkies rendering and NVENC)": "NVIDIA (renderizado de Selkies y NVENC)", "NVIDIA Actions": "Acciones de NVIDIA", "NVIDIA CPU hiding already configured": "Ocultación de CPU NVIDIA ya configurada", "NVIDIA Container Toolkit": "Kit de herramientas de contenedor NVIDIA", @@ -4429,6 +4450,8 @@ "New Search": "Nueva búsqueda", "New VM:": "Nueva máquina virtual:", "New Virtual Machine": "Nueva máquina virtual", + "New applications ({count})": "Aplicaciones nuevas ({count})", + "New applications since your last visit: {count}": "Aplicaciones nuevas desde tu última visita: {count}", "New backup job": "Nuevo trabajo de backup", "New backups on this host will be unencrypted until a new keyfile is set up.": "Los nuevos backups de este host no se cifrarán hasta que se configure un nuevo archivo de claves.", "New image compatible:": "Nueva imagen compatible:", @@ -4887,6 +4910,8 @@ "Open the page of an application in the menu": "Abrir la página de una aplicación en el menú", "Open the web address and create the first account: the daemon registers by itself and starts the first scan.": "Abre la dirección web y crea la primera cuenta: el daemon se registra solo y empieza el primer escaneo.", "Open the web address and create the password of the akadmin user in the first-run setup.": "abra la dirección web y cree la contraseña del usuario akaadmin en la configuración de primera ejecución.", + "Open the web address and follow KitchenOwl’s onboarding to create the first account; no default account is configured.": "abra la dirección web y siga la incorporación de KitchenOwl para crear la primera cuenta;no hay ninguna cuenta predeterminada configurada.", + "Open the web dashboard and link your Movistar/O2 account using its SMS flow.": "Abre el panel web y vincula tu cuenta de Movistar/O2 con su verificación por SMS.", "Open-source AI-powered coding assistant": "Asistente de programación con IA de código abierto", "Open-source UI for building and debugging multi-agent and RAG applications": "Interfaz de código abierto para crear y depurar aplicaciones multiagente y RAG", "Open-source in-memory data store.": "Almacén de datos en memoria de código abierto.", @@ -5009,6 +5034,7 @@ "Packages:": "Paquetes:", "Packaging OVA file...": "Embalaje archivo OVA...", "Packing installer archive...": "Empaquetando el archivo del instalador...", + "Page layout and publishing in your browser": "Maquetación y publicación en el navegador", "PairDrop is a sublime alternative to AirDrop that works on all platforms. Send images, documents or text via peer to peer connection to devices in the same local network/Wi-Fi or to paired devices.": "PairDrop es una magnífica alternativa a AirDrop que funciona en todas las plataformas. Envía imágenes, documentos o texto mediante una conexión entre pares a dispositivos de la misma red local/Wi-Fi o a dispositivos emparejados.", "Paperless configuration cancelled": "Configuración de Paperless cancelada", "Paperless-ngx WebUI": "Paperless-ngx WebUI", @@ -5027,6 +5053,7 @@ "Pass /dev/video12 to the LXC": "Pasar /dev/video12 al LXC", "Pass a host device to the LXC": "Pasar un dispositivo del host al LXC", "Pass the UPS to the LXC": "Pase el UPS al LXC", + "Pass through a Z-Wave USB or serial controller": "pasar a través de un controlador serie o USB Z-Wave", "Passing NVIDIA does not enable acceleration inside the application. The host needs NVIDIA Container Toolkit and a compatible image. Continue?": "Pasar NVIDIA no habilita la aceleración dentro de la aplicación. El host necesita NVIDIA Container Toolkit y una imagen compatible. ¿Continuar?", "Passkey-based identity provider for self-hosted apps.": "Proveedor de identidad basado en passkeys para aplicaciones autoalojadas.", "Passphrase used to unlock the imported keyfile (leave blank if the keyfile is unencrypted / kdf=none):": "Frase de contraseña utilizada para desbloquear el archivo de claves importado (déjalo en blanco si el archivo de claves no está cifrado/kdf=none):", @@ -5134,6 +5161,8 @@ "Personal finance management application": "Aplicación de gestión de finanzas personales", "Personal media server for browsing and playing a local video library, with direct play and optional modules.": "Servidor multimedia personal para explorar y reproducir una biblioteca local de vídeos, con reproducción directa y módulos opcionales.", "Photo and video library with optional GPU transcoding and machine learning": "Biblioteca de fotos y vídeos con transcodificación por GPU y aprendizaje automático opcionales", + "Photo editing in your browser": "Edición de fotos en el navegador", + "Photo library and RAW development in your browser": "Biblioteca de fotos y revelado RAW en el navegador", "PhotoPrism": "PhotoPrism", "Photofield": "Photofield", "Phpmyadmin is a free software tool written in PHP, intended to handle the administration of MySQL over the Web. phpMyAdmin supports a wide range of operations on MySQL and MariaDB.": "Phpmyadmin es una herramienta de software libre escrita en PHP para administrar MySQL por la web. phpMyAdmin admite una amplia gama de operaciones en MySQL y MariaDB.", @@ -5225,6 +5254,7 @@ "Preparing the changes...": "Preparando los cambios...", "Preparing the machine learning container for the new choice...": "Preparando el contenedor Machine learning para la nueva opción...", "Preparing the update...": "Preparando la actualización...", + "Presentations and slide decks in your browser": "Presentaciones y diapositivas en el navegador", "Preserving logs to /var/log.hdd before unmounting...": "Preservando logs en /var/log.hdd antes de desmontar...", "Press 'q' to exit": "Pulsa 'q' para salir", "Press Ctrl+C to stop the server and return to menu.": "Pulsa Ctrl+C para detener el servidor y volver al menú.", @@ -5385,6 +5415,7 @@ "Proxmox web interface: Datacenter > Storage > Add > ZFS": "Interfaz web de Proxmox: Datacenter > Storage > Add > ZFS", "Proxmox web interface: Datacenter > Storage > Add > iSCSI": "Interfaz web de Proxmox: Datacenter > Storage > Add > iSCSI", "Proxy address to reach the API (optional)": "Dirección del proxy para llegar a la API (opcional)", + "Public HTTPS URL used to open this Authelia portal (for example https://auth.example.com)": "URL HTTPS pública utilizada para abrir este portal Authelia (por ejemplo, https://auth.example.com)", "Public UDP port clients connect to": "Puerto UDP público al que se conectan los clientes", "Public UDP port peers connect to": "Puerto UDP público al que se conectan los peers", "Public URL of phpMyAdmin when it is served behind a reverse proxy": "URL pública de phpMyAdmin cuando se sirve detrás de un proxy inverso", @@ -5445,6 +5476,7 @@ "Re-running pre-check after repairs...": "Volviendo a ejecutar la verificación previa después de las reparaciones...", "Reachable": "Accesible", "Read an oci-compose from a file of this host": "Leer un oci-compose desde un archivo de este host", + "Read and edit PDF documents in your browser": "Lectura y edición de documentos PDF en el navegador", "Read its Compose file from a file of this host": "Leer su archivo Compose desde un archivo de este host", "Read manga through a self-hosted server and web UI.": "Lee manga desde un servidor autoalojado con interfaz web.", "Read the link with pct console CTID on the Proxmox host, or from the Console panel of the container in the Proxmox web interface, then open the https://playit.gg/claim/ address it shows in a browser and sign in to playit.gg. Ctrl+a q leaves pct console.": "Lee el enlace con pct console CTID en el host Proxmox, o desde el panel Console del contenedor en la interfaz web de Proxmox; después abre en un navegador la dirección https://playit.gg/claim/ que muestra e inicia sesión en playit.gg. Ctrl+a q sale de pct console.", @@ -5543,6 +5575,7 @@ "Recursive and authoritative DNS server.": "Servidor DNS recursivo y autoritativo.", "Redmine": "Redmine", "Refresh APT index and verify repositories:": "Actualizar el índice APT y verificar los repositorios:", + "Refresh catalog": "Actualizar catálogo", "Refresh the configured APT repositories and review the PBS package upgrade? Repository files will be preserved.": "¿Actualizar los índices de los repositorios de APT configurados y revisar la actualización de paquetes de PBS? Se conservarán los archivos de los repositorios.", "Refresh the index from the image sources": "Actualizar el índice desde las fuentes de imágenes", "Refresh token of the connector, from the Twingate admin console": "Token de renovación del conector, de la consola de administración de Twingate", @@ -5550,6 +5583,7 @@ "Refresh your browser to see changes (server restart may be required)": "Actualiza tu navegador para ver los cambios (es posible que sea necesario reiniciar el servidor)", "Refreshing apt cache...": "Actualizando caché de apt...", "Refreshing the NVIDIA runtime...": "Refrescando el tiempo de ejecución de NVIDIA...", + "Refreshing the application catalog...": "Actualizando el catálogo de aplicaciones...", "Refusing an unexpected rootfs path:": "Refusing an inesperado rootfs path:", "Regenerate the catalog templates": "Regenerar las plantillas del catálogo", "Regenerate the template of one application": "Regenerar la plantilla de una aplicación", @@ -6241,6 +6275,7 @@ "Self-hosted file sync and sharing.": "Sincronización y uso compartido de archivos autoalojados.", "Self-hosted file toolkit for images, video, audio, PDFs, and files": "Kit de herramientas autoalojado para imágenes, vídeo, audio, PDF y archivos", "Self-hosted forms and workflows.": "Formularios y flujos de trabajo autoalojados.", + "Self-hosted grocery list and recipe manager": "lista de compras y administrador de recetas autohospedado", "Self-hosted household plant-care tracker with reminders, photos, and multiple user accounts.": "Gestor autoalojado del cuidado de plantas domésticas, con recordatorios, fotos y varias cuentas de usuario.", "Self-hosted image-hosting service with authenticated uploads, image processing, public links, and local storage.": "Servicio autoalojado para alojar imágenes, con subidas autenticadas, procesamiento de imágenes, enlaces públicos y almacenamiento local.", "Self-hosted internet archiving solution": "Solución autoalojada de archivado de Internet", @@ -6435,6 +6470,7 @@ "Simple and easy to use DDNS": "DDNS simple y fácil de usar", "Simple self-hosted file sharing.": "Servicio sencillo y autoalojado para compartir archivos.", "Single GPU Warning": "Advertencia de GPU única", + "Single sign-on and multi-factor authentication": "inicio de sesión único y autenticación multifactor", "Single target found — selected automatically:": "Destino único encontrado: seleccionado automáticamente:", "Single-node log storage and query UI.": "Almacenamiento de logs para un solo nodo con interfaz de consulta.", "Single-node observability database for metrics, logs and traces.": "Base de datos de observabilidad de un solo nodo para métricas, registros y trazas.", @@ -6504,7 +6540,9 @@ "Specific subnet (enter manually)": "Subred específica (introducir manualmente)", "Speedtest Tracker web interface": "Interfaz web de Speedtest Tracker", "Speedtest-tracker is a self-hosted internet performance tracking application that runs speedtest checks against Ookla's Speedtest service.": "Speedtest-tracker es una aplicación autoalojada de seguimiento del rendimiento de Internet que ejecuta pruebas de velocidad contra el servicio Speedtest de Ookla.", + "Spoolman web interface": "interfaz web Spoolman", "Spotube is an open source, cross-platform Spotify client compatible across multiple platforms utilizing Spotify's data API and YouTube, Piped.video or JioSaavn as an audio source, eliminating the need for Spotify Premium": "Spotube es un cliente de Spotify de código abierto y multiplataforma que usa la API de datos de Spotify y YouTube, Piped.video o JioSaavn como fuente de audio, sin necesidad de Spotify Premium", + "Spreadsheets in your browser": "Hojas de cálculo en el navegador", "Sshwifty": "Sshwifty", "Sshwifty web access (password only)": "Acceso web a Sshwifty (solo con contraseña)", "Stable (main branch)": "Estable (rama main)", @@ -7392,6 +7430,7 @@ "Then bind-mount to container": "Luego vincule el montaje al contenedor", "Then change the VM display to none (vga: none) when the guest is stable.": "Después cambia la pantalla de la VM a none (vga: none) cuando el sistema de la VM sea estable.", "Then change the VM display to none (vga: none) when the system is stable.": "Luego cambia la visualización de VM a ninguno (vga: ninguno) cuando el sistema esté estable.", + "Then restart the container so the new model appears in the list.": "Después reinicia el contenedor para que el modelo nuevo aparezca en la lista.", "Then run this option again:": "Luego ejecuta esta opción nuevamente:", "Then update /etc/fstab on the host with the same options.": "Luego actualiza /etc/fstab en el host con las mismas opciones.", "There are extra disks or bind mounts outside the journal; the rootfs is not replaced": "Hay discos o bind mounts adicionales fuera del diario de la operación; el rootfs no se reemplaza", @@ -7438,6 +7477,7 @@ "This container does not have apt-get. NFS client installation only supports Debian/Ubuntu containers.": "Este contenedor no tiene apt-get. La instalación del cliente NFS solo admite contenedores Debian/Ubuntu.", "This container does not have apt-get. Samba client installation only supports Debian/Ubuntu containers.": "Este contenedor no tiene apt-get. La instalación del cliente Samba solo admite contenedores Debian/Ubuntu.", "This container has no GPU configured. Coral TPU works best alongside hardware video decoding (Quick Sync, VA-API, NVENC) for apps like Frigate.": "Este contenedor no tiene GPU configurada. Coral TPU funciona mejor junto con la decodificación de video por hardware (Quick Sync, VA-API, NVENC) para aplicaciones como Frigate.", + "This container is managed by high availability. Remove ct:{vmid} from HA (Datacenter → HA, or ha-manager remove ct:{vmid}), repeat the operation and add it back to HA with the same settings.": "Este contenedor está gestionado por alta disponibilidad. Elimina ct:{vmid} de HA (Centro de datos → HA, o ha-manager remove ct:{vmid}), repite la operación y vuelve a añadirlo a HA con la misma configuración.", "This container is not a member of a multi-container application": "Este contenedor no forma parte de una aplicación de varios contenedores", "This container is not a registered OCI instance.": "Este contenedor no es una instancia OCI registrada.", "This container was restored or came back from another host after its record on this host was written. Open this menu again to recover it; nothing was changed.": "Este contenedor se restauró o volvió de otro host después de que se escribiera su registro en este host. Abre de nuevo este menú para recuperarlo; no se ha cambiado nada.", @@ -7555,6 +7595,7 @@ "Tip: You can install the QEMU Guest Agent inside the VM with:": "Consejo: Puede instalar el Agente Invitado QEMU dentro de la VM con:", "Tip: zfs set acltype=posixacl xattr=sa / enables full ACL support.": "Consejo: zfs set acltype=posixacl xattr=sa / habilita la compatibilidad total con ACL.", "Tmpfs size in MiB for": "Tamaño de tmpfs en MiB para", + "To add a model, open the console of the container in Proxmox and download it into /models, for example:": "Para añadir un modelo, abre la consola del contenedor en Proxmox y descárgalo en /models, por ejemplo:", "To allow LXC write access, change the NFS export on the server to include:": "Para permitir el acceso de escritura a LXC, cambia la exportación NFS en el servidor para incluir:", "To apply it to the current shell now, run:": "Para aplicarlo ahora en la sesión actual, ejecuta:", "To assign VFs to VMs or LXCs, edit the configuration manually via the Proxmox web interface. The Physical Function will remain bound to the native driver.": "Para asignar VF a VM o LXC, edita la configuración manualmente a través de la interfaz web de Proxmox. La Función Física permanecerá ligada al controlador nativo.", @@ -7594,6 +7635,7 @@ "Total routes": "Rutas totales", "Total size:": "Tamaño total:", "Toxiproxy": "Toxiproxy", + "Track 3D printer filament spools and usage.": "realice un seguimiento de los carretes de filamentos de las impresoras 3D y su uso.", "Track movies, television, anime and games you want to enjoy.": "Lleva un registro de películas, series, anime y videojuegos que quieras disfrutar.", "Track subscriptions and recurring expenses.": "Seguimiento de suscripciones y gastos recurrentes.", "Track your music listening history on your own server.": "Registra tu historial de reproducción musical en tu propio servidor.", @@ -7826,6 +7868,7 @@ "Update the whole stack?": "¿Actualizar toda la pila?", "Update, modify or recreate one installed OCI instance": "Actualizar, modificar o recrear una instancia OCI instalada", "Updated": "Actualizado", + "Updated recipes since your last visit: {count}": "Recetas actualizadas desde tu última visita: {count}", "Updated sharedfiles group to GID: 101000": "Grupo de archivos compartidos actualizado a GID: 101000", "Updated stack checked": "Pila actualizada comprobada", "Updated:": "Actualizado:", @@ -8031,6 +8074,7 @@ "Variables": "Variables", "Variables the installation asks for:": "Variables que pide la instalación:", "Vaultwarden Web Vault": "Vaultwarden Web Vault", + "Vector illustration in your browser": "Ilustración vectorial en el navegador", "Vector search engine with a built-in dashboard.": "Motor de búsqueda vectorial con panel integrado.", "Vendor Specific": "Específico del proveedor", "Verbose pool status": "Estado detallado del grupo", @@ -8063,6 +8107,7 @@ "VictoriaLogs": "VictoriaLogs", "Video": "Vídeo", "Video acceleration and object detection are independent choices; the installer does not write camera or detector YAML.": "La aceleración de vídeo y la detección de objetos se eligen por separado; el instalador no escribe el YAML de cámaras ni de detectores.", + "Video editing in your browser": "Edición de vídeo en el navegador", "Video transcoding acceleration": "Aceleración de transcodificación de vídeo", "Video transcoding profile not implemented:": "Perfil de transcodificación de vídeo no implementado:", "View CIFS Mounts (pvesm + fstab)": "Ver montajes CIFS (pvesm + fstab)", @@ -8172,12 +8217,17 @@ "Web access": "Acceso web", "Web address of the AdGuard Home that receives the settings (e.g. http://192.168.1.3)": "Dirección web del AdGuard Home que recibe los ajustes (por ejemplo, http://192.168.1.3)", "Web address of the main AdGuard Home, whose settings are copied (e.g. http://192.168.1.2)": "Dirección web del AdGuard Home principal, cuyos ajustes se copian (por ejemplo, http://192.168.1.2)", + "Web dashboard": "Panel web", + "Web dashboard and WebDAV": "Panel web y WebDAV", "Web interface": "interfaz web", "Web interface for Borg Backup repositories and jobs.": "Interfaz web para gestionar repositorios y tareas de Borg Backup.", "Web interface for managing 3D printers.": "Interfaz web para gestionar impresoras 3D.", "Web interface to manage devices running Tasmota firmware.": "Interfaz web para gestionar dispositivos con firmware Tasmota.", "Web reverse proxy with an administration interface.": "Proxy inverso web con interfaz de administración.", "WebCord can be summarized as a pack of security and privacy hardenings, Discord features reimplementations, Electron / Chromium / Discord bugs workarounds, stylesheets, internal pages and wrapped https://discord.com page, designed to conform with ToS as much as it is possible (or hide the changes that might violate it from Discord's eyes).": "WebCord puede resumirse como un conjunto de medidas de hardening de seguridad y privacidad, reimplementaciones de funciones de Discord, soluciones a fallos de Electron / Chromium / Discord, hojas de estilo, páginas internas y la página https://discord.com encapsulada, diseñado para cumplir los ToS en lo posible (u ocultar a Discord los cambios que podrían incumplirlos).", + "WebDAV": "WebDAV", + "WebDAV and dashboard password (leave empty to generate)": "Contraseña de WebDAV y del panel (vacío = generada)", + "WebDAV is available on container port 8080. Account linking is required before cloud file operations.": "WebDAV está disponible en el puerto 8080 del contenedor. Hay que vincular la cuenta antes de trabajar con los archivos de la nube.", "Webgrabplus is a multi-site incremental xmltv epg grabber. It collects tv-program guide data from selected tvguide sites for your favourite channels.": "Webgrabplus es un capturador incremental de EPG xmltv para múltiples sitios. Recoge los datos de la guía de programación de los sitios de guías de TV seleccionados para tus canales favoritos.", "Webservers & Proxies": "Webservers & Proxies", "Website": "Sitio web", @@ -8234,6 +8284,7 @@ "Without a usable reset path, passthrough reliability is poor and VM": "Sin una ruta de reset utilizable, la fiabilidad del passthrough es baja y la VM", "Wizard cancelled": "Asistente cancelado", "Wolfenstein: Blade of Agony is a story-driven WWII shooter inspired by Wolfenstein and Doom.": "Wolfenstein: Blade of Agony es un shooter de la Segunda Guerra Mundial con historia, inspirado en Wolfenstein y Doom.", + "Word processing in your browser": "Procesador de textos en el navegador", "Workflow automation tool": "Herramienta de automatización de flujos de trabajo", "Working directory:": "Directorio de trabajo:", "Works with LVM, ZFS, and BTRFS storage types": "Funciona con tipos de almacenamiento LVM, ZFS y BTRFS", @@ -8284,6 +8335,8 @@ "Your machine learning Env work with Jupyter Lab": "Tu entorno de machine learning con Jupyter Lab", "Your next YouTube media manager": "Tu próximo gestor multimedia de YouTube", "Your_spotify is a self-hosted application that tracks what you listen and offers you a dashboard to explore statistics about it! It's composed of a web server which polls the Spotify API every now and then and a web application on which you can explore your statistics.": "¡Your_spotify es una aplicación autoalojada que registra lo que escuchas y te ofrece un panel para explorar tus estadísticas! Consta de un servidor web que consulta la API de Spotify cada cierto tiempo y una aplicación web donde explorar tus estadísticas.", + "Z-Wave JS UI": "Z-Wave JS UI", + "Z-Wave JS UI admin login": "Z-Wave JS UI inicio de sesión de administrador", "ZFS ARC config removed (kernel defaults will apply on reboot)": "Se eliminó la configuración de ZFS ARC (los valores predeterminados del kernel se aplicarán al reiniciar)", "ZFS ARC maximum configured:": "ZFS ARC máximo configurado:", "ZFS ARC optimization completed": "Optimización ZFS ARC completada", @@ -8397,6 +8450,7 @@ "could not be compiled for kernel": "no se pudo compilar para el kernel", "could not validate NVIDIA; exit code": "no puede validar NVIDIA; código de salida", "cpuunits must be between 8 and 10000": "cpuunits debe ser entre 8 y 10000", + "curl -L -o /models/gemma-3-1b-it-Q4_K_M.gguf https://huggingface.co/ggml-org/gemma-3-1b-it-GGUF/resolve/main/gemma-3-1b-it-Q4_K_M.gguf": "curl -L -o /models/gemma-3-1b-it-Q4_K_M.gguf https://huggingface.co/ggml-org/gemma-3-1b-it-GGUF/resolve/main/gemma-3-1b-it-Q4_K_M.gguf", "custom": "personalizado", "custom dependency commands are not yet supported": "los comandos de dependencia personalizados todavía no son compatibles", "custom path(s) saved.": "rutas personalizadas guardadas.", @@ -8602,6 +8656,7 @@ "kexec-tools is not installed or already removed.": "kexec-tools no está instalado o ya se ha eliminado.", "layers": "capas", "legacy .link file(s) to the ProxMenux-managed format": "archivo(s) .link antiguos al formato gestionado por ProxMenux", + "llama.cpp shows every GGUF model saved in /models in its web UI and loads the one you choose.": "llama.cpp muestra en su interfaz web todos los modelos GGUF guardados en /models y carga el que elijas.", "log2ram completely removed from system": "log2ram completamente eliminado del sistema", "main container of the application": "contenedor principal de la aplicación", "make cloned mount tree read-only": "hacer de solo lectura el árbol de montaje clonado", diff --git a/lang/fr.json b/lang/fr.json index c5ae697e..e8e0e257 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -184,6 +184,7 @@ "A tunneling daemon by Cloudflare that safely exposes your web servers into the internet.": "Un démon de tunnel par Cloudflare qui expose en toute sécurité vos serveurs web dans Internet.", "A unified email and collaboration server.": "Un serveur unifié de messagerie et de collaboration.", "A unified platform for logs, metrics and traces.": "Une plateforme unifiée pour les journaux, les métriques et les traces.", + "A validating, recursive DNS resolver for your private network.": "Un résolveur DNS récursif de validation pour votre réseau privé.", "A versatile file conversion tool that supports multiple formats.": "Un outil de conversion de fichiers polyvalent qui prend en charge plusieurs formats.", "A visual tool for exploring and documenting database schemas.": "Un outil visuel pour explorer et documenter les schémas de bases de données.", "A web GUI client of Project V which supports VMess, VLESS, SS, SSR, Trojan, Tuic and Juicity protocols": "Un client web GUI du Projet V qui prend en charge les protocoles VMess, VLESS, SS, SSR, Trojan, Tuic et Juicity", @@ -256,6 +257,7 @@ "Acceleration for llama.cpp": "Accélération pour llama.cpp", "Accept routes from other nodes?": "Accepter les routes d'autres nœuds ?", "Accept this host monitoring profile?": "Accepter ce profil de surveillance de l'hôte?", + "Access Movistar and O2 Cloud from WebDAV clients and backup tools.": "Accédez au cloud Movistar et O2 depuis des clients WebDAV et des outils de sauvegarde.", "Access Scope:": "Portée d'accès :", "Access bridge": "Pont d'accès", "Access bridge for Immich": "Pont d'accès pour Immich", @@ -410,6 +412,7 @@ "After the reboot, you will only be able to access the Proxmox host via:": "Après le redémarrage, vous ne pourrez accéder à l'hôte Proxmox que via :", "After this LXC → VM switch, reboot the host so the new binding state is applied cleanly.": "Après ce commutateur LXC → VM, redémarrez l'hôte afin que le nouvel état de liaison soit appliqué proprement.", "Agentless monitoring and observability platform.": "Plateforme de supervision et d’observabilité sans agent.", + "AirDC++ administrator": "Administrateur AirDC++", "Airsonic Advanced web interface": "Interface web Airsonic Advanced", "Airsonic-advanced is a free, web-based media streamer, providing ubiquitious access to your music. Use it to share your music with friends, or to listen to your own music while at work. You can stream to multiple players simultaneously, for instance to one player in your kitchen and another in your living room.": "Airsonic-Advanced est un flux multimédia gratuit, basé sur le web, offrant un accès ubiquitious à votre musique. Utilisez-le pour partager votre musique avec des amis, ou pour écouter votre propre musique au travail. Vous pouvez diffuser simultanément vers plusieurs joueurs, par exemple vers un joueur dans votre cuisine et un autre dans votre salon.", "Aliases added to .bashrc": "Alias ajoutés à .bashrc", @@ -598,11 +601,15 @@ "Attention: Removing the subscription banner may cause issues in the web interface after a future update.": "Attention : La suppression de la bannière d'abonnement peut entraîner des problèmes dans l'interface Web après une future mise à jour.", "Audacity is an easy-to-use, multi-track audio editor and recorder. Developed by a group of volunteers as open source.": "Audacity est un éditeur et un enregistreur audio facile à utiliser. Développé par un groupe de volontaires comme source ouverte.", "Audio device directory": "Répertoire des périphériques audio", + "Audio recording and mixing in your browser": "Enregistrement et mixage audio dans le navigateur", "Audiobookshelf is a self-hosted audiobook and podcast server.": "Audiobookshelf est un livre audio et un serveur podcast auto-organisé.", "Audit completed. Press Enter to continue...": "Vérification terminée. Appuyez sur Entrée pour continuer...", "Audit socket disabled or not required": "Socket d'audit désactivé ou non requis", "Auth key is required.": "La clé d'authentification est requise.", "Auth:": "Authentification :", + "Authelia Web UI": "interface utilisateur Web Authelia", + "Authelia administrator": "Administrateur Authelia", + "Authelia session cookie domain for this portal (for example example.com)": "domaine de cookie de session Authelia pour ce portail (par exemple exemple.com)", "Authentication": "Authentification", "Authentication & Security": "Authentification et sécurité", "Authentication Error": "Erreur d'authentification", @@ -642,6 +649,7 @@ "Available PVE Version": "Version PVE disponible", "Available Shares:": "Actions disponibles :", "Available action": "Action disponible", + "Available applications: {count}": "Applications disponibles : {count}", "Available archives:": "Archives disponibles :", "Available disks detected.": "Disques disponibles détectés.", "Available host backups:": "Sauvegardes d'hôte disponibles :", @@ -873,6 +881,7 @@ "Cannot write the image archive to {storage}. Check storage access and mount status.": "Impossible d'écrire l'archive d'images sur {storage}. Vérifiez l'accès au stockage et l'état du montage.", "Capabilities cannot be kept and all dropped at the same time": "Les capacités ne peuvent être conservées et toutes abandonnées en même temps", "Catalog options": "Options du catalogue", + "Catalog refresh unavailable; using the local catalog.": "Impossible de mettre à jour le catalogue ; le catalogue local est utilisé.", "Category": "Catégorie", "Caution: Maximum mode generates more heat.": "Attention : le mode maximum génère plus de chaleur.", "Central server for file and image backups.": "Serveur central pour les sauvegardes de fichiers et d’images.", @@ -1159,6 +1168,7 @@ "Compressing": "Compression", "Compression": "Compression", "Compression Tools": "Outils de compression", + "Computer-aided design and drafting in your browser": "Conception assistée par ordinateur et dessin technique dans le navigateur", "Concise output of logical volumes": "Sortie concise des volumes logiques", "Concise output of physical volumes": "Sortie concise des volumes physiques", "Concise output of volume groups": "Sortie concise des groupes de volumes", @@ -1393,6 +1403,7 @@ "Controller + NVMe": "Contrôleur + NVMe", "Controller + NVMe assignment will be written now and become active after host reboot.": "L'affectation Contrôleur + NVMe sera écrite maintenant et deviendra active après le redémarrage de l'hôte.", "Controller + NVMe passthrough requires machine type q35. Skipping controller assignment.": "Le contrôleur + le relais NVMe nécessite une machine de type q35. Ignorer l'affectation du contrôleur.", + "Controller path on the Proxmox host; prefer /dev/serial/by-id/...": "Chemin du contrôleur sur l’hôte Proxmox ;préférez /dev/serial/by-id/...", "Controller/NVMe Conflict Policy": "Politique de conflit contrôleur/NVMe", "Controller/NVMe already present in VM config": "Contrôleur/NVMe déjà présent dans la configuration de la VM", "Controller/NVMe already present in VM config (%s)": "Contrôleur/NVMe déjà présent dans la configuration de la VM (%s)", @@ -1927,6 +1938,7 @@ "Dgraph": "Dgraph", "Diff: current system vs backup (--- system +++ backup)": "Diff : système actuel vs sauvegarde (--- système +++ sauvegarde)", "Different host. Backup from:": "Hôte différent. Sauvegarde depuis :", + "Direct Connect file sharing with a web interface": "Partage de fichiers Direct Connect avec une interface web", "Direct conversion completed for container": "Conversion directe terminée pour le conteneur", "Directory Exists": "Le répertoire existe", "Directory Not Empty": "Répertoire non vide", @@ -3340,6 +3352,7 @@ "Initial Stirling-PDF account": "Compte initial Stirling-PDF", "Initial Tandoor administrator": "Administrateur Tandoor initial", "Initial Umami administrator": "Administrateur initial d’Umami", + "Initial Z-Wave JS UI admin password": "Mot de passe administrateur initial Z-Wave JS UI", "Initial administrator created:": "Administrateur initial créé :", "Initial administrator user": "Administrateur initial", "Initializing Borg repository if needed...": "Initialisation du référentiel Borg si nécessaire...", @@ -3525,6 +3538,7 @@ "Interface MTU": "MTU de l'interface", "Interface Names (persistent)": "noms d'interface (persistants)", "Interface Type": "Type d'interface", + "Interface design and prototyping in your browser": "Conception d’interfaces et prototypage dans le navigateur", "Interface names will return to default systemd behavior.": "Les noms d’interface reviendront au comportement systemd par défaut.", "Interface type:": "Type d'interface :", "Interfaces configured": "Interfaces configurées", @@ -3740,6 +3754,7 @@ "Jupyter Lab (token only)": "Jupyter Lab (jeton seulement)", "JupyterLab": "JupyterLab", "JupyterLab access token": "Jeton d’accès JupyterLab", + "KDE's twin-panel file manager in a browser-based desktop.": "le gestionnaire de fichiers à deux panneaux de KDE dans un bureau basé sur un navigateur.", "KDF:": "KDF :", "KROMA": "KROMA", "KVM MSR options added to /etc/modprobe.d/kvm.conf": "Options KVM MSR ajoutées à /etc/modprobe.d/kvm.conf", @@ -3787,12 +3802,14 @@ "KiCad - A Cross Platform and Open Source Electronics Design Automation Suite.": "KiCad - Une plateforme croisée et une suite d'automatisation de conception électronique ouverte.", "Kimai has no default account. Enter the container with: pct enter {main_vmid}": "Kimai n’a pas de compte par défaut. Ouvrez un shell dans le conteneur avec : pct enter {main_vmid}", "Kimai is a professional grade time-tracking application, free and open-source.": "Kimai est une application professionnelle de suivi du temps, libre et open-source.", + "KitchenOwl": "KitchenOwl", "Kometa is a powerful tool designed to give you complete control over your media libraries. With Kometa, you can take your customization to the next level, with granular control over metadata, collections, overlays, and much more.": "Kometa est un outil puissant conçu pour vous donner un contrôle complet sur vos bibliothèques multimédias. Avec Kometa, vous pouvez porter votre personnalisation au niveau suivant, avec un contrôle granulaire sur les métadonnées, les collections, les superpositions et bien plus encore.", "Kometa reads its configuration from /config/config.yml and the container only ships /config/config.yml.template. Copy the template to config.yml, fill in the required Plex and TMDb connections, then restart the container.": "Kometa lit sa configuration à partir de /config/config.yml et le conteneur ne livre que /config/config.yml.template. Copiez le modèle pour config.yml, remplissez les connexions required Plex et TMDb, puis redémarrez le conteneur.", "Komga is a media server for your comics, mangas, BDs, magazines and eBooks.": "Komga est un serveur multimédia pour vos BD, mangas, BD, magazines et eBooks.", "Kopia Web UI": "Interface web de Kopia", "Kopia Web UI login": "Connexion à l’interface web de Kopia", "Krita is a professional FREE and open source painting program. It is made by artists that want to see affordable art tools for everyone.": "Krita est un programme professionnel de peinture libre et GRATUIT. Il est fait par des artistes qui veulent voir des outils d'art abordables pour tout le monde.", + "Krusader Web UI": "interface utilisateur Web Krusader", "LAN access to the kept containers (stack configuration incomplete):": "Accès au réseau local des conteneurs gardés (configuration incomplète):", "LAN address applied to the application URLs": "Adresse LAN appliquée aux URL de l'application", "LLDAP": "LLDAP", @@ -4020,6 +4037,7 @@ "Manage OCI stack": "Gérer la pile OCI", "Manage PBS encryption keyfile": "Gérer le fichier de clés de chiffrement PBS", "Manage Secure Gateway": "Gérer Secure Gateway", + "Manage a Z-Wave network from a web dashboard and WebSocket gateway.": "Gérez un réseau Z-Wave à partir d'un tableau de bord Web et d'une passerelle WebSocket.", "Manage and inspect VM disk images": "Gérer et inspecter les images de disque de VM", "Manage custom backup paths": "Gérer les chemins de sauvegarde personnalisés", "Manage custom paths (add / remove your folders)": "Gérer les chemins personnalisés (ajouter/supprimer vos dossiers)", @@ -4029,6 +4047,7 @@ "Manage local backup target": "Gérer la cible de sauvegarde locale", "Manage private image registries.\nSaved credentials let ProxMenux access your private images.": "Gérer les registres d’images privés.\nLes informations d'identification enregistrées permettent à ProxMenux d'accéder à vos images privées.", "Manage registry accounts as root on the Proxmox node.": "Gérez les comptes de registre en tant que root sur le nœud Proxmox.", + "Manage teams of AI agents from a web dashboard.": "Gérez des équipes d’agents IA depuis un tableau de bord web.", "Managed disks must have backup enabled and a valid size": "Les disques gérés doivent avoir une sauvegarde activée et une taille valide", "Managed public route include is missing": "Le fichier de configuration des routes publiques gérées est manquant", "Managed public route installation failed": "Impossible de configurer les routes publiques gérées", @@ -4134,6 +4153,7 @@ "Monitoring": "Surveillance", "Monitoring & Analytics": "Surveillance et analyse", "Most common cause: the archive is corrupted (interrupted write, partial copy, or storage issue).": "Cause la plus courante : l'archive est corrompue (écriture interrompue, copie partielle ou problème de stockage).", + "Motion graphics and compositing in your browser": "Animation graphique et compositing dans le navigateur", "Mount Added Successfully:": "Montage ajouté avec succès :", "Mount CIFS share:": "Montez le partage CIFS :", "Mount Configuration Summary:": "Résumé de la configuration du montage :", @@ -4293,6 +4313,7 @@ "NVIDIA (CUDA; official GPU image)": "NVIDIA (CUDA; image officielle du GPU)", "NVIDIA (NVDEC/CUDA)": "NVIDIA (NVDEC/CUDA)", "NVIDIA (NVENC/NVDEC)": "NVIDIA (NVENC/NVDEC)", + "NVIDIA (Selkies rendering and NVENC)": "NVIDIA (rendu Selkies et NVENC)", "NVIDIA Actions": "Actions NVIDIA", "NVIDIA CPU hiding already configured": "Le masquage du processeur NVIDIA est déjà configuré", "NVIDIA Container Toolkit": "Boîte à outils de conteneur NVIDIA", @@ -4434,6 +4455,8 @@ "New Search": "Nouvelle recherche", "New VM:": "Nouvelle VM :", "New Virtual Machine": "Nouvelle machine virtuelle", + "New applications ({count})": "Nouvelles applications ({count})", + "New applications since your last visit: {count}": "Nouvelles applications depuis votre dernière visite : {count}", "New backup job": "Nouveau travail de sauvegarde", "New backups on this host will be unencrypted until a new keyfile is set up.": "Les nouvelles sauvegardes sur cet hôte ne seront pas chiffrées jusqu'à ce qu'un nouveau fichier de clés soit configuré.", "New image compatible:": "Nouvelle image compatible & #160;:", @@ -4896,6 +4919,8 @@ "Open the page of an application in the menu": "Ouvrir la page d'une application dans le menu", "Open the web address and create the first account: the daemon registers by itself and starts the first scan.": "Ouvrez l'adresse web et créez le premier compte : le daemon s'enregistre tout seul et lance la première analyse.", "Open the web address and create the password of the akadmin user in the first-run setup.": "ouvrez l'adresse Web et créez le mot de passe de l'utilisateur akadmin lors de la configuration de première exécution.", + "Open the web address and follow KitchenOwl’s onboarding to create the first account; no default account is configured.": "ouvrez l'adresse Web et suivez l'intégration de KitchenOwl pour créer le premier compte ;aucun compte par défaut n'est configuré.", + "Open the web dashboard and link your Movistar/O2 account using its SMS flow.": "Ouvrez le tableau de bord web et liez votre compte Movistar/O2 avec sa vérification par SMS.", "Open-source AI-powered coding assistant": "Assistant de codage à source ouverte", "Open-source UI for building and debugging multi-agent and RAG applications": "UI open-source pour la construction et le débogage d'applications multi-agents et RAG", "Open-source in-memory data store.": "Magasin de données en mémoire libre.", @@ -5018,6 +5043,7 @@ "Packages:": "Forfaits :", "Packaging OVA file...": "Emballage du fichier OVA...", "Packing installer archive...": "Archive du programme d'installation de l'emballage...", + "Page layout and publishing in your browser": "Mise en page et publication dans le navigateur", "PairDrop is a sublime alternative to AirDrop that works on all platforms. Send images, documents or text via peer to peer connection to devices in the same local network/Wi-Fi or to paired devices.": "PairDrop est une sublime alternative à AirDrop qui fonctionne sur toutes les plateformes. Envoyez des images, des documents ou du texte via une connexion pair à des appareils du même réseau local/Wi-Fi ou à des appareils appariés.", "Paperless configuration cancelled": "Configuration sans papier annulée", "Paperless-ngx WebUI": "Paperless-ngx WebUI", @@ -5036,6 +5062,7 @@ "Pass /dev/video12 to the LXC": "Passer /dev/video12 au LXC", "Pass a host device to the LXC": "Passer un appareil hôte au LXC", "Pass the UPS to the LXC": "Passer l'UPS au LXC", + "Pass through a Z-Wave USB or serial controller": "Passer par un contrôleur USB ou série Z-Wave", "Passing NVIDIA does not enable acceleration inside the application. The host needs NVIDIA Container Toolkit and a compatible image. Continue?": "Passer NVIDIA n’active pas l’accélération dans l’application. L'hôte a besoin du NVIDIA Container Toolkit et d'une image compatible. Continuer?", "Passkey-based identity provider for self-hosted apps.": "Fournisseur d’identité utilisant des passkeys pour les applications auto-hébergées.", "Passphrase used to unlock the imported keyfile (leave blank if the keyfile is unencrypted / kdf=none):": "Phrase secrète utilisée pour déverrouiller le fichier de clés importé (laisser vide si le fichier de clés n'est pas chiffré / kdf=aucun) :", @@ -5143,6 +5170,8 @@ "Personal finance management application": "Demande de gestion des finances personnelles", "Personal media server for browsing and playing a local video library, with direct play and optional modules.": "Serveur multimédia personnel pour parcourir et lire une vidéothèque locale, avec lecture directe et modules facultatifs.", "Photo and video library with optional GPU transcoding and machine learning": "Photothèque et vidéothèque avec transcodage GPU optionnel et apprentissage automatique", + "Photo editing in your browser": "Retouche photo dans le navigateur", + "Photo library and RAW development in your browser": "Photothèque et développement RAW dans le navigateur", "PhotoPrism": "PhotoPrism", "Photofield": "Photofield", "Phpmyadmin is a free software tool written in PHP, intended to handle the administration of MySQL over the Web. phpMyAdmin supports a wide range of operations on MySQL and MariaDB.": "Phpmyadmin est un logiciel libre écrit en PHP, destiné à gérer l'administration de MySQL sur le Web. phpMyAdmin prend en charge une large gamme de operations sur MySQL et MariaDB.", @@ -5235,6 +5264,7 @@ "Preparing the machine learning container for the new choice...": "Préparation du conteneur d'apprentissage automatique pour le nouveau choix...", "Preparing the recreation...": "Préparer les loisirs...", "Preparing the update...": "Préparation de la mise à jour...", + "Presentations and slide decks in your browser": "Présentations et diaporamas dans le navigateur", "Preserving logs to /var/log.hdd before unmounting...": "Conserver les journaux dans /var/log.hdd avant de démonter...", "Press 'q' to exit": "Appuyez sur « q » pour quitter", "Press Ctrl+C to stop the server and return to menu.": "Appuyez sur Ctrl+C pour arrêter le serveur et revenir au menu.", @@ -5395,6 +5425,7 @@ "Proxmox web interface: Datacenter > Storage > Add > ZFS": "Interface web Proxmox : Datacenter > Stockage > Ajouter > ZFS", "Proxmox web interface: Datacenter > Storage > Add > iSCSI": "Interface Web Proxmox : Datacenter > Stockage > Ajouter > iSCSI", "Proxy address to reach the API (optional)": "Adresse proxy pour accéder à l'API (facultatif)", + "Public HTTPS URL used to open this Authelia portal (for example https://auth.example.com)": "URL HTTPS publique utilisée pour ouvrir ce portail Authelia (par exemple https://auth.example.com)", "Public UDP port clients connect to": "Les clients du port UDP public se connectent à", "Public UDP port peers connect to": "Les pairs du port UDP public se connectent à", "Public URL of phpMyAdmin when it is served behind a reverse proxy": "URL publique de phpMyAdmin quand elle est servie derrière un proxy inversé", @@ -5455,6 +5486,7 @@ "Re-running pre-check after repairs...": "Nouvelle vérification préalable après réparation...", "Reachable": "Accessible", "Read an oci-compose from a file of this host": "Lire un oci-compose à partir d'un fichier de cet hôte", + "Read and edit PDF documents in your browser": "Lire et modifier des documents PDF dans le navigateur", "Read its Compose file from a file of this host": "Lire son fichier Composer à partir d'un fichier de cet hôte", "Read manga through a self-hosted server and web UI.": "Lire des mangas via un serveur auto-hébergé et son interface web.", "Read the link with pct console CTID on the Proxmox host, or from the Console panel of the container in the Proxmox web interface, then open the https://playit.gg/claim/ address it shows in a browser and sign in to playit.gg. Ctrl+a q leaves pct console.": "Lisez le lien avec la console pct CTID sur l'hôte Proxmox, ou depuis le panneau Console du conteneur dans l'interface web Proxmox, puis ouvrez l'adresse https://playit.gg/claim/ qu'elle affiche dans un navigateur et connectez-vous à playit.gg. Ctrl+a q quitte la console pct.", @@ -5560,6 +5592,7 @@ "Recursive and authoritative DNS server.": "Serveur DNS récursif et faisant autorité.", "Redmine": "Redmine", "Refresh APT index and verify repositories:": "Actualisez l'index APT et vérifiez les référentiels :", + "Refresh catalog": "Actualiser le catalogue", "Refresh the configured APT repositories and review the PBS package upgrade? Repository files will be preserved.": "Actualiser les dépôts APT configurés et examiner la mise à niveau des paquets PBS ? Les fichiers des dépôts seront conservés.", "Refresh the index from the image sources": "Actualiser l'index à partir des sources d'images", "Refresh token of the connector, from the Twingate admin console": "Jeton d'actualisation du connecteur, depuis la console d'administration Twingate", @@ -5567,6 +5600,7 @@ "Refresh your browser to see changes (server restart may be required)": "Actualisez votre navigateur pour voir les modifications (un redémarrage du serveur peut être nécessaire)", "Refreshing apt cache...": "Actualisation du cache apt...", "Refreshing the NVIDIA runtime...": "Rafraîchir l'exécution NVIDIA...", + "Refreshing the application catalog...": "Mise à jour du catalogue des applications...", "Refusing an unexpected rootfs path:": "Refuser un chemin inattendu de roofs :", "Regenerate the catalog templates": "Régénérer les modèles du catalogue", "Regenerate the template of one application": "Régénérer le modèle d'une application", @@ -6258,6 +6292,7 @@ "Self-hosted file sync and sharing.": "Service auto-hébergé de synchronisation et de partage de fichiers.", "Self-hosted file toolkit for images, video, audio, PDFs, and files": "Boîte à outils de fichiers auto-organisé pour les images, vidéo, audio, PDF et fichiers", "Self-hosted forms and workflows.": "Formulaires et workflows auto-hébergés.", + "Self-hosted grocery list and recipe manager": "Liste d'épicerie et gestionnaire de recettes auto-hébergés", "Self-hosted household plant-care tracker with reminders, photos, and multiple user accounts.": "Suivi auto-hébergé de l'entretien des plantes à la maison, avec rappels, photos et plusieurs comptes utilisateur.", "Self-hosted image-hosting service with authenticated uploads, image processing, public links, and local storage.": "Service auto-hébergé d'hébergement d'images avec téléversements authentifiés, traitement des images, liens publics et stockage local.", "Self-hosted internet archiving solution": "Solution d'archivage internet auto-organisé", @@ -6452,6 +6487,7 @@ "Simple and easy to use DDNS": "Simple et facile à utiliser DDNS", "Simple self-hosted file sharing.": "Service simple et auto-hébergé de partage de fichiers.", "Single GPU Warning": "Avertissement sur un seul GPU", + "Single sign-on and multi-factor authentication": "authentification unique et authentification multifacteur", "Single target found — selected automatically:": "Cible unique trouvée — sélectionnée automatiquement :", "Single-node log storage and query UI.": "Stockage de journaux sur un nœud unique avec interface de requête.", "Single-node observability database for metrics, logs and traces.": "Base de données d’observabilité à nœud unique pour les métriques, les journaux et les traces.", @@ -6520,7 +6556,9 @@ "Specific subnet (enter manually)": "Sous-réseau spécifique (saisir manuellement)", "Speedtest Tracker web interface": "Interface web Speedtest Tracker", "Speedtest-tracker is a self-hosted internet performance tracking application that runs speedtest checks against Ookla's Speedtest service.": "Speedtest-tracker est une application de suivi de performance internet auto-organisé qui effectue des contrôles de test de vitesse contre le service Speedtest d'Ookla.", + "Spoolman web interface": "interface Web Spoolman", "Spotube is an open source, cross-platform Spotify client compatible across multiple platforms utilizing Spotify's data API and YouTube, Piped.video or JioSaavn as an audio source, eliminating the need for Spotify Premium": "Spotube est un client Spotify ouvert et multiplateforme compatible avec plusieurs plateformes utilisant l'API de données de Spotify et YouTube, Piped. vidéo ou JioSaavn comme source audio, éliminant le besoin de Spotify Premium", + "Spreadsheets in your browser": "Tableur dans le navigateur", "Sshwifty": "Sshwifty", "Sshwifty web access (password only)": "Accès web à Sshwifty (mot de passe uniquement)", "Stable (main branch)": "Stable (branche principale)", @@ -7416,6 +7454,7 @@ "Then bind-mount to container": "Puis liez le montage au conteneur", "Then change the VM display to none (vga: none) when the guest is stable.": "Modifiez ensuite l'affichage de la VM sur aucun (vga : aucun) lorsque l'invité est stable.", "Then change the VM display to none (vga: none) when the system is stable.": "Modifiez ensuite l'affichage de la VM sur aucun (vga : aucun) lorsque le système est stable.", + "Then restart the container so the new model appears in the list.": "Redémarrez ensuite le conteneur pour que le nouveau modèle apparaisse dans la liste.", "Then run this option again:": "Ensuite, réexécutez cette option :", "Then update /etc/fstab on the host with the same options.": "Mettez ensuite à jour /etc/fstab sur l'hôte avec les mêmes options.", "There are extra disks or bind mounts outside the journal; the rootfs is not replaced": "Il y a des disques supplémentaires ou des fixations en dehors du journal; les rootfs ne sont pas remplacés", @@ -7463,6 +7502,7 @@ "This container does not have apt-get. NFS client installation only supports Debian/Ubuntu containers.": "Ce conteneur n'a pas apt-get. L'installation du client NFS prend uniquement en charge les conteneurs Debian/Ubuntu.", "This container does not have apt-get. Samba client installation only supports Debian/Ubuntu containers.": "Ce conteneur n'a pas apt-get. L'installation du client Samba prend uniquement en charge les conteneurs Debian/Ubuntu.", "This container has no GPU configured. Coral TPU works best alongside hardware video decoding (Quick Sync, VA-API, NVENC) for apps like Frigate.": "Ce conteneur n'a aucun GPU configuré. Coral TPU fonctionne mieux avec le décodage vidéo matériel (Quick Sync, VA-API, NVENC) pour des applications comme Frigate.", + "This container is managed by high availability. Remove ct:{vmid} from HA (Datacenter → HA, or ha-manager remove ct:{vmid}), repeat the operation and add it back to HA with the same settings.": "Ce conteneur est géré par la haute disponibilité. Retirez ct:{vmid} de HA (Centre de données → HA, ou ha-manager remove ct:{vmid}), relancez l'opération et rajoutez-le à HA avec les mêmes paramètres.", "This container is not a member of a multi-container application": "Ce conteneur n'est pas membre d'une application multi-conteneurs", "This container is not a registered OCI instance.": "Ce conteneur n'est pas une instance OCI enregistrée.", "This container was restored or came back from another host after its record on this host was written. Open this menu again to recover it; nothing was changed.": "Ce conteneur a été restauré ou est revenu d'un autre hôte après l'écriture de son enregistrement sur cet hôte. Ouvrez à nouveau ce menu pour le récupérer ; rien n'a été changé.", @@ -7581,6 +7621,7 @@ "Tip: You can install the QEMU Guest Agent inside the VM with:": "Astuce : Vous pouvez installer l'agent invité QEMU dans la VM avec :", "Tip: zfs set acltype=posixacl xattr=sa / enables full ACL support.": "Astuce : zfs set acltype=posixacl xattr=sa / active la prise en charge complète des ACL.", "Tmpfs size in MiB for": "Taille Tmpfs en MiB pour", + "To add a model, open the console of the container in Proxmox and download it into /models, for example:": "Pour ajouter un modèle, ouvrez la console du conteneur dans Proxmox et téléchargez-le dans /models, par exemple :", "To allow LXC write access, change the NFS export on the server to include:": "Pour autoriser l'accès en écriture LXC, modifiez l'exportation NFS sur le serveur pour inclure :", "To apply it to the current shell now, run:": "Pour l’appliquer maintenant à la session shell actuelle, exécutez :", "To assign VFs to VMs or LXCs, edit the configuration manually via the Proxmox web interface. The Physical Function will remain bound to the native driver.": "Pour attribuer des VF aux VM ou LXC, modifiez la configuration manuellement via l'interface Web Proxmox. La fonction physique restera liée au pilote natif.", @@ -7620,6 +7661,7 @@ "Total routes": "Total des itinéraires", "Total size:": "Taille totale :", "Toxiproxy": "Toxiproxy", + "Track 3D printer filament spools and usage.": "Suivez les bobines de filament d’imprimante 3D et leur utilisation.", "Track movies, television, anime and games you want to enjoy.": "Suivez les films, séries, animés et jeux vidéo que vous souhaitez découvrir.", "Track subscriptions and recurring expenses.": "Suivi des abonnements et des dépenses récurrentes.", "Track your music listening history on your own server.": "Historique d’écoute musicale hébergé sur votre propre serveur.", @@ -7854,6 +7896,7 @@ "Update the whole stack?": "Mettre à jour toute la pile ?", "Update, modify or recreate one installed OCI instance": "Mettre à jour, modifier ou recréer une instance OCI installée", "Updated": "Mis à jour", + "Updated recipes since your last visit: {count}": "Recettes mises à jour depuis votre dernière visite : {count}", "Updated sharedfiles group to GID: 101000": "Groupe de fichiers partagés mis à jour vers GID : 101000", "Updated stack checked": "Correction de la pile", "Updated:": "Mise à jour :", @@ -8059,6 +8102,7 @@ "Variables": "Variables", "Variables the installation asks for:": "Variables que l'installation demande:", "Vaultwarden Web Vault": "Vaultwarden Web Vault", + "Vector illustration in your browser": "Illustration vectorielle dans le navigateur", "Vector search engine with a built-in dashboard.": "Moteur de recherche vectorielle avec tableau de bord intégré.", "Verbose pool status": "État du pool détaillé", "Verification": "Vérification", @@ -8089,6 +8133,7 @@ "Versions shown belong to maintained NVIDIA branches that list your GPU PCI ID and are new enough to build against the running kernel. DKMS compilation is the final validation. The recommended version keeps the current branch, or uses the NVIDIA Production Branch on a fresh install.": "Les versions affichées appartiennent aux branches NVIDIA maintenues qui répertorient votre ID PCI GPU et sont suffisamment récentes pour être construites sur le noyau en cours d'exécution. La compilation DKMS est la validation finale. La version recommandée conserve la branche actuelle ou utilise la branche de production NVIDIA sur une nouvelle installation.", "VictoriaLogs": "VictoriaLogs", "Video acceleration and object detection are independent choices; the installer does not write camera or detector YAML.": "L'accélération vidéo et la détection d'objets sont des choix indépendants; l'installateur n'écrit pas la caméra ou le détecteur YAML.", + "Video editing in your browser": "Montage vidéo dans le navigateur", "Video transcoding acceleration": "Accélération du transcodage vidéo", "Video transcoding profile not implemented:": "Profil de transcodage vidéo non implémenté :", "View CIFS Mounts (pvesm + fstab)": "Afficher les montages CIFS (pvesm + fstab)", @@ -8198,12 +8243,17 @@ "Web access": "Accès au Web", "Web address of the AdGuard Home that receives the settings (e.g. http://192.168.1.3)": "Adresse Web du AdGuard Home qui reçoit les paramètres (p. ex. http://192.168.1.3)", "Web address of the main AdGuard Home, whose settings are copied (e.g. http://192.168.1.2)": "Adresse Web du principal AdGuard Home, dont les paramètres sont copiés (par exemple http://192.168.1.2)", + "Web dashboard": "Tableau de bord web", + "Web dashboard and WebDAV": "Tableau de bord web et WebDAV", "Web interface": "interface Web", "Web interface for Borg Backup repositories and jobs.": "Interface web pour les dépôts et tâches Borg Backup.", "Web interface for managing 3D printers.": "Interface web de gestion des imprimantes 3D.", "Web interface to manage devices running Tasmota firmware.": "Interface Web pour gérer les appareils utilisant le firmware Tasmota.", "Web reverse proxy with an administration interface.": "Proxy inverse web doté d’une interface d’administration.", "WebCord can be summarized as a pack of security and privacy hardenings, Discord features reimplementations, Electron / Chromium / Discord bugs workarounds, stylesheets, internal pages and wrapped https://discord.com page, designed to conform with ToS as much as it is possible (or hide the changes that might violate it from Discord's eyes).": "WebCord peut être résumé comme un paquet de durcissements de sécurité et de confidentialité, les fonctionnalités Discord réimplémentations, Electron / Chromium / Discord bugs workarounds, les feuilles de style, les pages internes et la page https://discord.com enveloppée, conçue pour se conformer à ToS autant qu'il est possible (ou cacher les changements qui pourraient la violer des yeux de Discord).", + "WebDAV": "WebDAV", + "WebDAV and dashboard password (leave empty to generate)": "Mot de passe WebDAV et du tableau de bord (vide = généré)", + "WebDAV is available on container port 8080. Account linking is required before cloud file operations.": "WebDAV est disponible sur le port 8080 du conteneur. Le compte doit être lié avant toute opération sur les fichiers du cloud.", "Webgrabplus is a multi-site incremental xmltv epg grabber. It collects tv-program guide data from selected tvguide sites for your favourite channels.": "Webgrabplus est un accrocheur différentiel multisite xmltv egg. Il recueille les données du guide TV-programme à partir de sites sélectionnés pour vos chaînes préférées.", "Webservers & Proxies": "Serveurs web et proxys", "Website": "Site web", @@ -8261,6 +8311,7 @@ "Without a usable reset path, passthrough reliability is poor and VM": "Sans chemin de réinitialisation utilisable, la fiabilité du relais est médiocre et la VM", "Wizard cancelled": "Assistant annulé", "Wolfenstein: Blade of Agony is a story-driven WWII shooter inspired by Wolfenstein and Doom.": "Wolfenstein: Blade of Agony est un jeu de tir inspiré par Wolfenstein et Doom.", + "Word processing in your browser": "Traitement de texte dans le navigateur", "Workflow automation tool": "Outil d'automatisation du flux de travail", "Working directory:": "Répertoire de travail :", "Works with LVM, ZFS, and BTRFS storage types": "Fonctionne avec les types de stockage LVM, ZFS et BTRFS", @@ -8311,6 +8362,8 @@ "Your machine learning Env work with Jupyter Lab": "Votre machine learning Env travaille avec Jupyter Lab", "Your next YouTube media manager": "Votre prochain gestionnaire de médias YouTube", "Your_spotify is a self-hosted application that tracks what you listen and offers you a dashboard to explore statistics about it! It's composed of a web server which polls the Spotify API every now and then and a web application on which you can explore your statistics.": "Your_spotify est une application auto-hôte qui suit ce que vous écoutez et vous offre un tableau de bord pour explorer les statistiques à ce sujet! Il est composé d'un serveur web qui interroge l'API Spotify de temps en temps et d'une application web sur laquelle vous pouvez explorer vos statistiques.", + "Z-Wave JS UI": "Z-Wave JS UI", + "Z-Wave JS UI admin login": "connexion administrateur Z-Wave JS UI", "ZFS ARC config removed (kernel defaults will apply on reboot)": "Configuration ZFS ARC supprimée (les valeurs par défaut du noyau s'appliqueront au redémarrage)", "ZFS ARC maximum configured:": "ZFS ARC maximum configuré :", "ZFS ARC optimization completed": "Optimisation ZFS ARC terminée", @@ -8424,6 +8477,7 @@ "could not be compiled for kernel": "n'a pas pu être compilé pour le noyau", "could not validate NVIDIA; exit code": "n'a pas pu valider NVIDIA; code de sortie", "cpuunits must be between 8 and 10000": "cpuunits doivent être compris entre 8 et 10000", + "curl -L -o /models/gemma-3-1b-it-Q4_K_M.gguf https://huggingface.co/ggml-org/gemma-3-1b-it-GGUF/resolve/main/gemma-3-1b-it-Q4_K_M.gguf": "curl -L -o /models/gemma-3-1b-it-Q4_K_M.gguf https://huggingface.co/ggml-org/gemma-3-1b-it-GGUF/resolve/main/gemma-3-1b-it-Q4_K_M.gguf", "custom": "personnalisé", "custom dependency commands are not yet supported": "commandes de dépendance personnalisées ne sont pas encore prises en charge", "custom path(s) saved.": "chemin(s) personnalisé(s) enregistré(s).", @@ -8630,6 +8684,7 @@ "kexec-tools is not installed or already removed.": "kexec-tools n'est pas installé ou déjà supprimé.", "layers": "couches", "legacy .link file(s) to the ProxMenux-managed format": "les anciens fichiers .link au format géré par ProxMenux", + "llama.cpp shows every GGUF model saved in /models in its web UI and loads the one you choose.": "llama.cpp affiche dans son interface web tous les modèles GGUF enregistrés dans /models et charge celui que vous choisissez.", "log2ram completely removed from system": "log2ram complètement supprimé du système", "main container of the application": "conteneur principal de l'application", "make cloned mount tree read-only": "rendre l'arborescence de montage clonée en lecture seule", diff --git a/lang/it.json b/lang/it.json index ee4877e4..6ca7a171 100644 --- a/lang/it.json +++ b/lang/it.json @@ -184,6 +184,7 @@ "A tunneling daemon by Cloudflare that safely exposes your web servers into the internet.": "Un servizio di tunneling di Cloudflare che espone in modo sicuro i tuoi server web su Internet.", "A unified email and collaboration server.": "Un server unificato per email e collaborazione.", "A unified platform for logs, metrics and traces.": "Una piattaforma unificata per log, metriche e tracce.", + "A validating, recursive DNS resolver for your private network.": "un risolutore DNS ricorsivo e di convalida per la tua rete privata.", "A versatile file conversion tool that supports multiple formats.": "Uno strumento versatile per convertire file in diversi formati.", "A visual tool for exploring and documenting database schemas.": "Uno strumento visivo per esplorare e documentare gli schemi dei database.", "A web GUI client of Project V which supports VMess, VLESS, SS, SSR, Trojan, Tuic and Juicity protocols": "Un client web di Project V che supporta i protocolli VMess, VLESS, SS, SSR, Trojan, Tuic e Juicity", @@ -256,6 +257,7 @@ "Acceleration for llama.cpp": "Accelerazione per llama.cpp", "Accept routes from other nodes?": "Accettare rotte da altri nodi?", "Accept this host monitoring profile?": "Accettare questo profilo di monitoraggio host?", + "Access Movistar and O2 Cloud from WebDAV clients and backup tools.": "Accedi al cloud di Movistar e O2 da client WebDAV e strumenti di backup.", "Access Scope:": "Ambito di accesso:", "Access bridge": "Bridge di accesso", "Access bridge for Immich": "Bridge di accesso per Immich", @@ -410,6 +412,7 @@ "After the reboot, you will only be able to access the Proxmox host via:": "Dopo il riavvio, potrai accedere all'host Proxmox solo tramite:", "After this LXC → VM switch, reboot the host so the new binding state is applied cleanly.": "Dopo questo passaggio LXC → VM, riavviare l’host in modo che il nuovo stato di associazione venga applicato correttamente.", "Agentless monitoring and observability platform.": "Piattaforma di monitoraggio e osservabilità senza agent.", + "AirDC++ administrator": "Amministratore AirDC++", "Airsonic Advanced web interface": "Interfaccia web Airsonic Advanced", "Airsonic-advanced is a free, web-based media streamer, providing ubiquitious access to your music. Use it to share your music with friends, or to listen to your own music while at work. You can stream to multiple players simultaneously, for instance to one player in your kitchen and another in your living room.": "Airsonic-advanced è un servizio gratuito di streaming multimediale via web che permette di accedere alla propria musica ovunque. Puoi condividerla con gli amici o ascoltarla mentre lavori. Puoi riprodurla contemporaneamente su più dispositivi, per esempio in cucina e in soggiorno.", "Aliases added to .bashrc": "Alias aggiunti a .bashrc", @@ -598,11 +601,15 @@ "Attention: Removing the subscription banner may cause issues in the web interface after a future update.": "Attenzione: la rimozione del banner di abbonamento potrebbe causare problemi nell'interfaccia web dopo un futuro aggiornamento.", "Audacity is an easy-to-use, multi-track audio editor and recorder. Developed by a group of volunteers as open source.": "Audacity è un editor e registratore audio multitraccia facile da usare, sviluppato come software open source da un gruppo di volontari.", "Audio device directory": "Directory del dispositivo audio", + "Audio recording and mixing in your browser": "Registrazione e missaggio audio nel browser", "Audiobookshelf is a self-hosted audiobook and podcast server.": "Audiobookshelf è un server self-hosted per audiolibri e podcast.", "Audit completed. Press Enter to continue...": "Audit completato. Premi Invio per continuare...", "Audit socket disabled or not required": "Socket di audit disabilitato o non richiesto", "Auth key is required.": "È richiesta la chiave di autenticazione.", "Auth:": "Autenticazione:", + "Authelia Web UI": "interfaccia utente Web Authelia", + "Authelia administrator": "amministratore Authelia", + "Authelia session cookie domain for this portal (for example example.com)": "dominio del cookie di sessione Authelia per questo portale (ad esempio example.com)", "Authentication": "Autenticazione", "Authentication & Security": "Autenticazione e sicurezza", "Authentication Error": "Errore di autenticazione", @@ -642,6 +649,7 @@ "Available PVE Version": "Versione PVE disponibile", "Available Shares:": "Condivisioni disponibili:", "Available action": "Azione disponibile", + "Available applications: {count}": "Applicazioni disponibili: {count}", "Available archives:": "Archivi disponibili:", "Available disks detected.": "Rilevati dischi disponibili.", "Available host backups:": "Backup host disponibili:", @@ -873,6 +881,7 @@ "Cannot write the image archive to {storage}. Check storage access and mount status.": "Impossibile scrivere l'archivio dell'immagine su {storage}. Controlla l'accesso allo storage e lo stato di montaggio.", "Capabilities cannot be kept and all dropped at the same time": "Non è possibile conservare alcune capability e rimuoverle tutte contemporaneamente", "Catalog options": "Opzioni del catalogo", + "Catalog refresh unavailable; using the local catalog.": "Impossibile aggiornare il catalogo; viene usata la copia locale.", "Category": "Categoria", "Caution: Maximum mode generates more heat.": "Attenzione: la modalità massima genera più calore.", "Central server for file and image backups.": "Server centrale per il backup di file e immagini.", @@ -1159,6 +1168,7 @@ "Compressing": "Compressione", "Compression": "Compressione", "Compression Tools": "Strumenti di compressione", + "Computer-aided design and drafting in your browser": "Progettazione CAD e disegno tecnico nel browser", "Concise output of logical volumes": "Output conciso di volumi logici", "Concise output of physical volumes": "Output conciso di volumi fisici", "Concise output of volume groups": "Output conciso dei gruppi di volumi", @@ -1393,6 +1403,7 @@ "Controller + NVMe": "Controller + NVMe", "Controller + NVMe assignment will be written now and become active after host reboot.": "L'assegnazione Controller + NVMe verrà scritta ora e diventerà attiva dopo il riavvio dell'host.", "Controller + NVMe passthrough requires machine type q35. Skipping controller assignment.": "Il passthrough di controller + NVMe richiede il tipo di macchina q35. Assegnazione del controller saltata.", + "Controller path on the Proxmox host; prefer /dev/serial/by-id/...": "percorso del controller sull'host Proxmox;preferisco /dev/serial/by-id/...", "Controller/NVMe Conflict Policy": "Policy conflitti controller/NVMe", "Controller/NVMe already present in VM config": "Controller/NVMe già presente nella configurazione VM", "Controller/NVMe already present in VM config (%s)": "Controller/NVMe già presente nella configurazione della VM (%s)", @@ -1927,6 +1938,7 @@ "Dgraph": "Dgraph", "Diff: current system vs backup (--- system +++ backup)": "Differenza: sistema attuale vs backup (--- sistema +++ backup)", "Different host. Backup from:": "Host diverso. Backup da:", + "Direct Connect file sharing with a web interface": "Condivisione di file Direct Connect con interfaccia web", "Direct conversion completed for container": "Conversione diretta completata per il container", "Directory Exists": "Directory esistente", "Directory Not Empty": "Directory non vuota", @@ -3340,6 +3352,7 @@ "Initial Stirling-PDF account": "Account iniziale di Stirling-PDF", "Initial Tandoor administrator": "Amministratore iniziale Tandoor", "Initial Umami administrator": "Amministratore iniziale di Umami", + "Initial Z-Wave JS UI admin password": "password amministratore iniziale Z-Wave JS UI", "Initial administrator created:": "Amministratore iniziale creato:", "Initial administrator user": "Utente amministratore iniziale", "Initializing Borg repository if needed...": "Inizializzazione del repository Borg, se necessario...", @@ -3525,6 +3538,7 @@ "Interface MTU": "MTU interfaccia", "Interface Names (persistent)": "Nomi interfacce (persistenti)", "Interface Type": "Tipo interfaccia", + "Interface design and prototyping in your browser": "Progettazione di interfacce e prototipi nel browser", "Interface names will return to default systemd behavior.": "I nomi delle interfacce torneranno al comportamento systemd predefinito.", "Interface type:": "Tipo interfaccia:", "Interfaces configured": "Interfacce configurate", @@ -3740,6 +3754,7 @@ "Jupyter Lab (token only)": "Jupyter Lab (solo token)", "JupyterLab": "JupyterLab", "JupyterLab access token": "Token di accesso JupyterLab", + "KDE's twin-panel file manager in a browser-based desktop.": "il file manager a doppio pannello di KDE in un desktop basato su browser.", "KDF:": "KDF:", "KROMA": "KROMA", "KVM MSR options added to /etc/modprobe.d/kvm.conf": "Opzioni KVM MSR aggiunte a /etc/modprobe.d/kvm.conf", @@ -3787,12 +3802,14 @@ "KiCad - A Cross Platform and Open Source Electronics Design Automation Suite.": "KiCad: una suite multipiattaforma e open source per la progettazione elettronica.", "Kimai has no default account. Enter the container with: pct enter {main_vmid}": "Kimai non ha un account predefinito. Accedi al container con: pct enter {main_vmid}", "Kimai is a professional grade time-tracking application, free and open-source.": "Kimai è un'applicazione professionale, gratuita e open source per il monitoraggio del tempo.", + "KitchenOwl": "KitchenOwl", "Kometa is a powerful tool designed to give you complete control over your media libraries. With Kometa, you can take your customization to the next level, with granular control over metadata, collections, overlays, and much more.": "Kometa è uno strumento che ti offre un controllo completo sulle tue librerie multimediali. Permette di personalizzare nel dettaglio metadati, raccolte, sovrimpressioni e molto altro.", "Kometa reads its configuration from /config/config.yml and the container only ships /config/config.yml.template. Copy the template to config.yml, fill in the required Plex and TMDb connections, then restart the container.": "Kometa legge la configurazione da /config/config.yml, ma il container include solo /config/config.yml.template. Copia il modello in config.yml, configura le connessioni necessarie a Plex e TMDb, quindi riavvia il container.", "Komga is a media server for your comics, mangas, BDs, magazines and eBooks.": "Komga è un server multimediale per fumetti, manga, bande dessinée, riviste ed e-book.", "Kopia Web UI": "Interfaccia web di Kopia", "Kopia Web UI login": "Accesso all’interfaccia web di Kopia", "Krita is a professional FREE and open source painting program. It is made by artists that want to see affordable art tools for everyone.": "Krita è un programma professionale, gratuito e open source per dipingere. È creato da artisti che vogliono rendere gli strumenti artistici accessibili a tutti.", + "Krusader Web UI": "interfaccia utente Web Krusader", "LAN access to the kept containers (stack configuration incomplete):": "Accesso LAN ai container conservati (configurazione stack incompleta):", "LAN address applied to the application URLs": "Indirizzo LAN applicato agli URL dell'applicazione", "LLDAP": "LLDAP", @@ -4020,6 +4037,7 @@ "Manage OCI stack": "Gestisci stack OCI", "Manage PBS encryption keyfile": "Gestisci il file di chiavi di crittografia PBS", "Manage Secure Gateway": "Gestisci Secure Gateway", + "Manage a Z-Wave network from a web dashboard and WebSocket gateway.": "gestisci una rete Z-Wave da una dashboard Web e da un gateway WebSocket.", "Manage and inspect VM disk images": "Gestisci e ispeziona le immagini disco delle VM", "Manage custom backup paths": "Gestisci percorsi di backup personalizzati", "Manage custom paths (add / remove your folders)": "Gestisci percorsi personalizzati (aggiungi/rimuovi le tue cartelle)", @@ -4029,6 +4047,7 @@ "Manage local backup target": "Gestisci la destinazione di backup locale", "Manage private image registries.\nSaved credentials let ProxMenux access your private images.": "Gestisci i registri di immagini private.\nLe credenziali salvate consentono a ProxMenux di accedere alle tue immagini private.", "Manage registry accounts as root on the Proxmox node.": "Gestisci gli account del registro come root sul nodo Proxmox.", + "Manage teams of AI agents from a web dashboard.": "Gestisci team di agenti IA da una dashboard web.", "Managed disks must have backup enabled and a valid size": "I dischi gestiti devono avere il backup abilitato e una dimensione valida", "Managed public route include is missing": "Manca la direttiva di inclusione per le rotte pubbliche gestite", "Managed public route installation failed": "Impossibile configurare le rotte pubbliche gestite", @@ -4134,6 +4153,7 @@ "Monitoring": "Monitoraggio", "Monitoring & Analytics": "Monitoraggio e analisi", "Most common cause: the archive is corrupted (interrupted write, partial copy, or storage issue).": "Causa più comune: l'archivio è danneggiato (scrittura interrotta, copia parziale o problema di archiviazione).", + "Motion graphics and compositing in your browser": "Grafica animata e compositing nel browser", "Mount Added Successfully:": "Montaggio aggiunto con successo:", "Mount CIFS share:": "Monta la condivisione CIFS:", "Mount Configuration Summary:": "Riepilogo configurazione montaggio:", @@ -4293,6 +4313,7 @@ "NVIDIA (CUDA; official GPU image)": "NVIDIA (CUDA; immagine GPU ufficiale)", "NVIDIA (NVDEC/CUDA)": "NVIDIA (NVDEC/CUDA)", "NVIDIA (NVENC/NVDEC)": "NVIDIA (NVENC/NVDEC)", + "NVIDIA (Selkies rendering and NVENC)": "NVIDIA (rendering Selkies e NVENC)", "NVIDIA Actions": "Azioni NVIDIA", "NVIDIA CPU hiding already configured": "Occultamento CPU per NVIDIA già configurato", "NVIDIA Container Toolkit": "NVIDIA Container Toolkit", @@ -4434,6 +4455,8 @@ "New Search": "Nuova ricerca", "New VM:": "Nuova VM:", "New Virtual Machine": "Nuova VM", + "New applications ({count})": "Nuove applicazioni ({count})", + "New applications since your last visit: {count}": "Nuove applicazioni dalla tua ultima visita: {count}", "New backup job": "Nuova attività di backup", "New backups on this host will be unencrypted until a new keyfile is set up.": "I nuovi backup su questo host non saranno crittografati finché non verrà impostato un nuovo file di chiavi.", "New image compatible:": "Nuova immagine compatibile:", @@ -4896,6 +4919,8 @@ "Open the page of an application in the menu": "Apri la pagina di un'applicazione nel menu", "Open the web address and create the first account: the daemon registers by itself and starts the first scan.": "Apri l'indirizzo web e crea il primo account: il daemon si registra da solo e avvia la prima scansione.", "Open the web address and create the password of the akadmin user in the first-run setup.": "Apri l'indirizzo web e imposta la password dell'utente akadmin durante la configurazione iniziale.", + "Open the web address and follow KitchenOwl’s onboarding to create the first account; no default account is configured.": "apri l'indirizzo web e segui l'onboarding di KitchenOwl per creare il primo account;non è configurato alcun account predefinito.", + "Open the web dashboard and link your Movistar/O2 account using its SMS flow.": "Apri la dashboard web e collega il tuo account Movistar/O2 con la verifica tramite SMS.", "Open-source AI-powered coding assistant": "Assistente open source per la programmazione basato sull'IA", "Open-source UI for building and debugging multi-agent and RAG applications": "Interfaccia open source per creare ed eseguire il debug di applicazioni multiagente e RAG", "Open-source in-memory data store.": "Archivio dati in memoria open source.", @@ -5018,6 +5043,7 @@ "Packages:": "Pacchetti:", "Packaging OVA file...": "Creazione del file OVA...", "Packing installer archive...": "Compressione dell'archivio del programma di installazione...", + "Page layout and publishing in your browser": "Impaginazione e pubblicazione nel browser", "PairDrop is a sublime alternative to AirDrop that works on all platforms. Send images, documents or text via peer to peer connection to devices in the same local network/Wi-Fi or to paired devices.": "PairDrop è un'ottima alternativa ad AirDrop che funziona su tutte le piattaforme. Invia immagini, documenti o testo con una connessione peer-to-peer a dispositivi sulla stessa rete locale/Wi-Fi o a dispositivi associati.", "Paperless configuration cancelled": "Configurazione di Paperless annullata", "Paperless-ngx WebUI": "Paperless-ngx WebUI", @@ -5036,6 +5062,7 @@ "Pass /dev/video12 to the LXC": "Collega /dev/video12 al container LXC", "Pass a host device to the LXC": "Collega un dispositivo dell’host al container LXC", "Pass the UPS to the LXC": "Passa l'UPS al container LXC", + "Pass through a Z-Wave USB or serial controller": "passa attraverso un controller USB o seriale Z-Wave", "Passing NVIDIA does not enable acceleration inside the application. The host needs NVIDIA Container Toolkit and a compatible image. Continue?": "il passaggio di NVIDIA non abilita l'accelerazione all'interno dell'applicazione. L'host necessita di NVIDIA Container Toolkit e di un'immagine compatibile. Continuare?", "Passkey-based identity provider for self-hosted apps.": "Provider di identità basato su passkey per app self-hosted.", "Passphrase used to unlock the imported keyfile (leave blank if the keyfile is unencrypted / kdf=none):": "Passphrase per sbloccare il file di chiavi importato (lascia vuoto se non è crittografato / kdf=none):", @@ -5143,6 +5170,8 @@ "Personal finance management application": "Applicazione per la gestione delle finanze personali", "Personal media server for browsing and playing a local video library, with direct play and optional modules.": "Server multimediale personale per sfogliare e riprodurre una raccolta video locale, con riproduzione diretta e moduli opzionali.", "Photo and video library with optional GPU transcoding and machine learning": "Libreria di foto e video con transcodifica GPU e apprendimento automatico opzionali", + "Photo editing in your browser": "Fotoritocco nel browser", + "Photo library and RAW development in your browser": "Libreria fotografica e sviluppo RAW nel browser", "PhotoPrism": "PhotoPrism", "Photofield": "Photofield", "Phpmyadmin is a free software tool written in PHP, intended to handle the administration of MySQL over the Web. phpMyAdmin supports a wide range of operations on MySQL and MariaDB.": "phpMyAdmin è uno strumento software libero scritto in PHP per amministrare MySQL via web. Supporta numerose operazioni su MySQL e MariaDB.", @@ -5235,6 +5264,7 @@ "Preparing the machine learning container for the new choice...": "Preparazione del container per machine learning con la nuova scelta...", "Preparing the recreation...": "Preparazione ricreazione...", "Preparing the update...": "Preparazione dell'aggiornamento...", + "Presentations and slide decks in your browser": "Presentazioni e diapositive nel browser", "Preserving logs to /var/log.hdd before unmounting...": "Conservazione dei log su /var/log.hdd prima dello smontaggio...", "Press 'q' to exit": "Premi 'q' per uscire", "Press Ctrl+C to stop the server and return to menu.": "Premi Ctrl+C per arrestare il server e tornare al menu.", @@ -5395,6 +5425,7 @@ "Proxmox web interface: Datacenter > Storage > Add > ZFS": "Interfaccia web Proxmox: Datacenter > Storage > Aggiungi > ZFS", "Proxmox web interface: Datacenter > Storage > Add > iSCSI": "Interfaccia web Proxmox: Datacenter > Storage > Aggiungi > iSCSI", "Proxy address to reach the API (optional)": "Indirizzo proxy per raggiungere l'API (facoltativo)", + "Public HTTPS URL used to open this Authelia portal (for example https://auth.example.com)": "URL HTTPS pubblico utilizzato per aprire questo portale Authelia (ad esempio https://auth.example.com)", "Public UDP port clients connect to": "Porta UDP pubblica a cui si connettono i client", "Public UDP port peers connect to": "Porta UDP pubblica a cui si connettono i peer", "Public URL of phpMyAdmin when it is served behind a reverse proxy": "URL pubblico di phpMyAdmin quando viene servito dietro un proxy inverso", @@ -5455,6 +5486,7 @@ "Re-running pre-check after repairs...": "Nuova esecuzione del controllo preliminare dopo la riparazione...", "Reachable": "Raggiungibile", "Read an oci-compose from a file of this host": "Leggi un oci-compose da un file dell’host", + "Read and edit PDF documents in your browser": "Lettura e modifica di documenti PDF nel browser", "Read its Compose file from a file of this host": "Leggi il file Compose da un file dell’host", "Read manga through a self-hosted server and web UI.": "Lettura di manga tramite un server self-hosted e un’interfaccia web.", "Read the link with pct console CTID on the Proxmox host, or from the Console panel of the container in the Proxmox web interface, then open the https://playit.gg/claim/ address it shows in a browser and sign in to playit.gg. Ctrl+a q leaves pct console.": "Leggi il link con pct console CTID sull'host Proxmox oppure nel pannello Console del container nell'interfaccia web Proxmox. Apri nel browser l'indirizzo https://playit.gg/claim/ mostrato e accedi a playit.gg. Ctrl+a q chiude pct console.", @@ -5560,6 +5592,7 @@ "Recursive and authoritative DNS server.": "Server DNS ricorsivo e autorevole.", "Redmine": "Redmine", "Refresh APT index and verify repositories:": "Aggiorna l'indice APT e verifica i repository:", + "Refresh catalog": "Aggiorna catalogo", "Refresh the configured APT repositories and review the PBS package upgrade? Repository files will be preserved.": "Aggiornare gli indici dei repository APT configurati ed esaminare l'aggiornamento dei pacchetti PBS? I file dei repository verranno conservati.", "Refresh the index from the image sources": "Aggiorna l'indice dalle sorgenti delle immagini", "Refresh token of the connector, from the Twingate admin console": "Token di refresh del connettore, dalla console di amministrazione Twingate", @@ -5567,6 +5600,7 @@ "Refresh your browser to see changes (server restart may be required)": "Aggiorna il browser per vedere le modifiche (potrebbe essere necessario il riavvio del server)", "Refreshing apt cache...": "Aggiornamento cache apt...", "Refreshing the NVIDIA runtime...": "Aggiornamento del runtime NVIDIA...", + "Refreshing the application catalog...": "Aggiornamento del catalogo delle applicazioni...", "Refusing an unexpected rootfs path:": "Rifiutato un percorso rootfs inatteso:", "Regenerate the catalog templates": "Rigenera i modelli del catalogo", "Regenerate the template of one application": "Rigenera il modello di un'applicazione", @@ -6258,6 +6292,7 @@ "Self-hosted file sync and sharing.": "Servizio self-hosted per sincronizzare e condividere file.", "Self-hosted file toolkit for images, video, audio, PDFs, and files": "Set di strumenti self-hosted per immagini, video, audio, PDF e altri file", "Self-hosted forms and workflows.": "Moduli e flussi di lavoro auto-ospitati.", + "Self-hosted grocery list and recipe manager": "lista della spesa e gestore di ricette self-hosted", "Self-hosted household plant-care tracker with reminders, photos, and multiple user accounts.": "Strumento per la cura delle piante domestiche da ospitare sul proprio server, con promemoria, foto e più account utente.", "Self-hosted image-hosting service with authenticated uploads, image processing, public links, and local storage.": "Servizio per ospitare immagini sul proprio server, con caricamenti autenticati, elaborazione delle immagini, link pubblici e archiviazione locale.", "Self-hosted internet archiving solution": "Soluzione self-hosted per archiviare contenuti Internet", @@ -6452,6 +6487,7 @@ "Simple and easy to use DDNS": "DDNS semplice e facile da usare", "Simple self-hosted file sharing.": "Servizio semplice e self-hosted per condividere file.", "Single GPU Warning": "Avviso GPU singola", + "Single sign-on and multi-factor authentication": "Single Sign-On e autenticazione a più fattori", "Single target found — selected automatically:": "Unica destinazione trovata - selezionata automaticamente:", "Single-node log storage and query UI.": "Archiviazione dei log su un singolo nodo e interfaccia di interrogazione.", "Single-node observability database for metrics, logs and traces.": "Database di osservabilità a nodo singolo per metriche, log e tracce.", @@ -6520,7 +6556,9 @@ "Specific subnet (enter manually)": "Sottorete specifica (immettere manualmente)", "Speedtest Tracker web interface": "Interfaccia web Speedtest Tracker", "Speedtest-tracker is a self-hosted internet performance tracking application that runs speedtest checks against Ookla's Speedtest service.": "Speedtest-tracker è un'applicazione self-hosted per monitorare le prestazioni della connessione Internet eseguendo test tramite il servizio Speedtest di Ookla.", + "Spoolman web interface": "interfaccia web Spoolman", "Spotube is an open source, cross-platform Spotify client compatible across multiple platforms utilizing Spotify's data API and YouTube, Piped.video or JioSaavn as an audio source, eliminating the need for Spotify Premium": "Spotube è un client Spotify open source e multipiattaforma che usa l'API dati di Spotify e YouTube, Piped.video o JioSaavn come sorgenti audio, eliminando la necessità di Spotify Premium", + "Spreadsheets in your browser": "Fogli di calcolo nel browser", "Sshwifty": "Sshwifty", "Sshwifty web access (password only)": "Accesso web a Sshwifty (solo password)", "Stable (main branch)": "Stabile (ramo main)", @@ -7417,6 +7455,7 @@ "Then bind-mount to container": "Quindi eseguire un bind mount nel container", "Then change the VM display to none (vga: none) when the guest is stable.": "Quindi impostare il display VM su none (vga: none) quando il guest è stabile.", "Then change the VM display to none (vga: none) when the system is stable.": "Quindi impostare il display VM su none (vga: none) quando il sistema è stabile.", + "Then restart the container so the new model appears in the list.": "Poi riavvia il container perché il nuovo modello compaia nell'elenco.", "Then run this option again:": "Quindi eseguire di nuovo questa opzione:", "Then update /etc/fstab on the host with the same options.": "Quindi aggiornare /etc/fstab sull’host con le stesse opzioni.", "There are extra disks or bind mounts outside the journal; the rootfs is not replaced": "Ci sono dischi aggiuntivi o bind mount non registrati nel journal; la rootfs non viene sostituita", @@ -7464,6 +7503,7 @@ "This container does not have apt-get. NFS client installation only supports Debian/Ubuntu containers.": "Questo container non ha apt-get. L'installazione del client NFS supporta solo i container Debian/Ubuntu.", "This container does not have apt-get. Samba client installation only supports Debian/Ubuntu containers.": "Questo container non ha apt-get. L'installazione del client Samba supporta solo i container Debian/Ubuntu.", "This container has no GPU configured. Coral TPU works best alongside hardware video decoding (Quick Sync, VA-API, NVENC) for apps like Frigate.": "Questo container non ha una GPU configurata. Coral TPU funziona meglio insieme alla decodifica video hardware (Quick Sync, VA-API, NVENC) per app come Frigate.", + "This container is managed by high availability. Remove ct:{vmid} from HA (Datacenter → HA, or ha-manager remove ct:{vmid}), repeat the operation and add it back to HA with the same settings.": "Questo container è gestito dall'alta disponibilità. Rimuovi ct:{vmid} da HA (Datacenter → HA, oppure ha-manager remove ct:{vmid}), ripeti l'operazione e aggiungilo di nuovo a HA con le stesse impostazioni.", "This container is not a member of a multi-container application": "Questo container non fa parte di un’applicazione con più container", "This container is not a registered OCI instance.": "Questo container non è un’istanza OCI registrata.", "This container was restored or came back from another host after its record on this host was written. Open this menu again to recover it; nothing was changed.": "Questo contenitore è stato ripristinato o è tornato da un altro host dopo la scrittura del suo record su questo host. Apri nuovamente questo menu per recuperarlo; non è stato modificato nulla.", @@ -7582,6 +7622,7 @@ "Tip: You can install the QEMU Guest Agent inside the VM with:": "Suggerimento: è possibile installare QEMU Guest Agent nella VM con:", "Tip: zfs set acltype=posixacl xattr=sa / enables full ACL support.": "Suggerimento: zfs set acltype=posixacl xattr=sa / abilita il supporto ACL completo.", "Tmpfs size in MiB for": "Dimensione tmpfs in MiB per", + "To add a model, open the console of the container in Proxmox and download it into /models, for example:": "Per aggiungere un modello, apri la console del container in Proxmox e scaricalo in /models, ad esempio:", "To allow LXC write access, change the NFS export on the server to include:": "Per consentire l'accesso in scrittura LXC, modificare l'esportazione NFS sul server per includere:", "To apply it to the current shell now, run:": "Per applicarlo ora alla shell corrente, eseguire:", "To assign VFs to VMs or LXCs, edit the configuration manually via the Proxmox web interface. The Physical Function will remain bound to the native driver.": "Per assegnare VF a VM o LXC, modificare manualmente la configurazione tramite l'interfaccia web Proxmox. La funzione fisica resterà associata al driver nativo.", @@ -7621,6 +7662,7 @@ "Total routes": "Rotte totali", "Total size:": "Dimensione totale:", "Toxiproxy": "Toxiproxy", + "Track 3D printer filament spools and usage.": "monitora le bobine e l'utilizzo dei filamenti della stampante 3D.", "Track movies, television, anime and games you want to enjoy.": "Tieni traccia di film, serie TV, anime e videogiochi che vuoi guardare o provare.", "Track subscriptions and recurring expenses.": "Tieni traccia degli abbonamenti e delle spese ricorrenti.", "Track your music listening history on your own server.": "Tieni traccia della cronologia di ascolto musicale sul tuo server.", @@ -7855,6 +7897,7 @@ "Update the whole stack?": "Aggiornare l’intero stack?", "Update, modify or recreate one installed OCI instance": "Aggiorna, modifica o ricrea un'istanza OCI installata", "Updated": "Aggiornato", + "Updated recipes since your last visit: {count}": "Ricette aggiornate dalla tua ultima visita: {count}", "Updated sharedfiles group to GID: 101000": "Gruppo sharedfiles aggiornato a GID: 101000", "Updated stack checked": "Stack aggiornato verificato", "Updated:": "Aggiornato:", @@ -8060,6 +8103,7 @@ "Variables": "Variabili", "Variables the installation asks for:": "Variabili richieste durante l’installazione:", "Vaultwarden Web Vault": "Vaultwarden Web Vault", + "Vector illustration in your browser": "Illustrazione vettoriale nel browser", "Vector search engine with a built-in dashboard.": "Motore di ricerca vettoriale con dashboard integrata.", "Verbose pool status": "Stato pool dettagliato", "Verification": "Verifica", @@ -8090,6 +8134,7 @@ "Versions shown belong to maintained NVIDIA branches that list your GPU PCI ID and are new enough to build against the running kernel. DKMS compilation is the final validation. The recommended version keeps the current branch, or uses the NVIDIA Production Branch on a fresh install.": "Le versioni mostrate appartengono a rami NVIDIA mantenuti che elencano l’ID PCI della tua GPU e sono abbastanza recenti da poter essere compilate per il kernel in esecuzione. La compilazione DKMS costituisce la verifica finale. La versione consigliata mantiene il ramo corrente oppure, per una nuova installazione, utilizza NVIDIA Production Branch.", "VictoriaLogs": "VictoriaLogs", "Video acceleration and object detection are independent choices; the installer does not write camera or detector YAML.": "L'accelerazione video e il rilevamento degli oggetti sono scelte indipendenti; l'installer non scrive la configurazione YAML delle telecamere o dei rilevatori.", + "Video editing in your browser": "Montaggio video nel browser", "Video transcoding acceleration": "Accelerazione transcodifica video", "Video transcoding profile not implemented:": "Profilo transcodifica video non implementato:", "View CIFS Mounts (pvesm + fstab)": "Visualizza montaggi CIFS (pvesm + fstab)", @@ -8199,12 +8244,17 @@ "Web access": "Accesso web", "Web address of the AdGuard Home that receives the settings (e.g. http://192.168.1.3)": "Indirizzo web dell'istanza AdGuard Home che riceve le impostazioni (ad es. http://192.168.1.3)", "Web address of the main AdGuard Home, whose settings are copied (e.g. http://192.168.1.2)": "Indirizzo web dell'istanza AdGuard Home principale, da cui vengono copiate le impostazioni (ad es. http://192.168.1.2)", + "Web dashboard": "Dashboard web", + "Web dashboard and WebDAV": "Dashboard web e WebDAV", "Web interface": "Interfaccia web", "Web interface for Borg Backup repositories and jobs.": "Interfaccia web per repository e job di Borg Backup.", "Web interface for managing 3D printers.": "Interfaccia web per gestire stampanti 3D.", "Web interface to manage devices running Tasmota firmware.": "Interfaccia web per gestire dispositivi con firmware Tasmota.", "Web reverse proxy with an administration interface.": "Proxy inverso web con interfaccia di amministrazione.", "WebCord can be summarized as a pack of security and privacy hardenings, Discord features reimplementations, Electron / Chromium / Discord bugs workarounds, stylesheets, internal pages and wrapped https://discord.com page, designed to conform with ToS as much as it is possible (or hide the changes that might violate it from Discord's eyes).": "WebCord comprende miglioramenti alla sicurezza e alla privacy, reimplementazioni di funzioni di Discord, soluzioni alternative ai bug di Electron, Chromium e Discord, fogli di stile, pagine interne e la pagina https://discord.com incapsulata. È progettato per rispettare i termini di servizio per quanto possibile (o nascondere a Discord le modifiche che potrebbero violarli).", + "WebDAV": "WebDAV", + "WebDAV and dashboard password (leave empty to generate)": "Password di WebDAV e della dashboard (campo vuoto = generata)", + "WebDAV is available on container port 8080. Account linking is required before cloud file operations.": "WebDAV è disponibile sulla porta 8080 del container. Prima di operare sui file del cloud bisogna collegare l'account.", "Webgrabplus is a multi-site incremental xmltv epg grabber. It collects tv-program guide data from selected tvguide sites for your favourite channels.": "Webgrabplus acquisisce dati EPG XMLTV incrementalmente da più siti. Raccoglie le guide TV dai siti selezionati per i canali preferiti.", "Webservers & Proxies": "Server web e proxy", "Website": "Sito web", @@ -8262,6 +8312,7 @@ "Without a usable reset path, passthrough reliability is poor and VM": "Senza un metodo di reset utilizzabile, l'affidabilità del passthrough è scarsa e per le VM", "Wizard cancelled": "Procedura guidata annullata", "Wolfenstein: Blade of Agony is a story-driven WWII shooter inspired by Wolfenstein and Doom.": "Wolfenstein: Blade of Agony è uno sparatutto ambientato nella seconda guerra mondiale e incentrato sulla storia, ispirato a Wolfenstein e Doom.", + "Word processing in your browser": "Elaborazione di testi nel browser", "Workflow automation tool": "Strumento automazione flussi di lavoro", "Working directory:": "Directory di lavoro:", "Works with LVM, ZFS, and BTRFS storage types": "Funziona con storage LVM, ZFS e BTRFS", @@ -8312,6 +8363,8 @@ "Your machine learning Env work with Jupyter Lab": "Ambiente machine learning con Jupyter Lab", "Your next YouTube media manager": "Il tuo prossimo gestore di video YouTube", "Your_spotify is a self-hosted application that tracks what you listen and offers you a dashboard to explore statistics about it! It's composed of a web server which polls the Spotify API every now and then and a web application on which you can explore your statistics.": "Your_spotify è un’applicazione self-hosted che traccia gli ascolti e offre una dashboard per consultarne le statistiche. Comprende un server web che interroga periodicamente l’API Spotify e un’applicazione web per esplorare le statistiche.", + "Z-Wave JS UI": "Z-Wave JS UI", + "Z-Wave JS UI admin login": "accesso amministratore Z-Wave JS UI", "ZFS ARC config removed (kernel defaults will apply on reboot)": "Configurazione ZFS ARC rimossa (valori predefiniti del kernel applicati al riavvio)", "ZFS ARC maximum configured:": "Limite massimo ZFS ARC configurato:", "ZFS ARC optimization completed": "Ottimizzazione ZFS ARC completata", @@ -8425,6 +8478,7 @@ "could not be compiled for kernel": "non è stato possibile compilarlo per il kernel", "could not validate NVIDIA; exit code": "impossibile convalidare NVIDIA; codice di uscita", "cpuunits must be between 8 and 10000": "cpuunits deve essere compreso tra 8 e 10000", + "curl -L -o /models/gemma-3-1b-it-Q4_K_M.gguf https://huggingface.co/ggml-org/gemma-3-1b-it-GGUF/resolve/main/gemma-3-1b-it-Q4_K_M.gguf": "curl -L -o /models/gemma-3-1b-it-Q4_K_M.gguf https://huggingface.co/ggml-org/gemma-3-1b-it-GGUF/resolve/main/gemma-3-1b-it-Q4_K_M.gguf", "custom": "personalizzato", "custom dependency commands are not yet supported": "i comandi di dipendenza personalizzati non sono ancora supportati", "custom path(s) saved.": "percorsi personalizzati salvati.", @@ -8631,6 +8685,7 @@ "kexec-tools is not installed or already removed.": "kexec-tools non è installato o è già stato rimosso.", "layers": "layer", "legacy .link file(s) to the ProxMenux-managed format": "file .link legacy nel formato gestito da ProxMenux", + "llama.cpp shows every GGUF model saved in /models in its web UI and loads the one you choose.": "llama.cpp mostra nella sua interfaccia web tutti i modelli GGUF salvati in /models e carica quello scelto.", "log2ram completely removed from system": "log2ram completamente rimosso dal sistema", "main container of the application": "container principale dell'applicazione", "make cloned mount tree read-only": "impostazione in sola lettura dell'albero di mount clonato", diff --git a/lang/pt.json b/lang/pt.json index d6139773..d21c6f98 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -184,6 +184,7 @@ "A tunneling daemon by Cloudflare that safely exposes your web servers into the internet.": "Um daemon de túneis pela Cloudflare que expõe com segurança seus servidores web na internet.", "A unified email and collaboration server.": "Um servidor unificado de e-mail e colaboração.", "A unified platform for logs, metrics and traces.": "Uma plataforma unificada para logs, métricas e traces.", + "A validating, recursive DNS resolver for your private network.": "um resolvedor DNS recursivo e validador para sua rede privada.", "A versatile file conversion tool that supports multiple formats.": "Uma ferramenta versátil de conversão de arquivos que suporta vários formatos.", "A visual tool for exploring and documenting database schemas.": "Uma ferramenta visual para explorar e documentar esquemas de bases de dados.", "A web GUI client of Project V which supports VMess, VLESS, SS, SSR, Trojan, Tuic and Juicity protocols": "Um cliente web GUI do Projeto V que suporta protocolos VMess, VLESS, SS, SS, SSR, Trojan, Tuic e Juicity", @@ -256,6 +257,7 @@ "Acceleration for llama.cpp": "Aceleração para llama.cpp", "Accept routes from other nodes?": "Aceita rotas de outros nós?", "Accept this host monitoring profile?": "Aceita este perfil de monitorização da máquina?", + "Access Movistar and O2 Cloud from WebDAV clients and backup tools.": "Acesse a nuvem da Movistar e da O2 a partir de clientes WebDAV e ferramentas de backup.", "Access Scope:": "Escopo de acesso:", "Access bridge": "Ponte de acesso", "Access bridge for Immich": "Ponte de acesso para Immich", @@ -410,6 +412,7 @@ "After the reboot, you will only be able to access the Proxmox host via:": "Após a reinicialização, você só poderá acessar o host Proxmox via:", "After this LXC → VM switch, reboot the host so the new binding state is applied cleanly.": "Após esta opção LXC → VM, reinicialize o host para que o novo estado de ligação seja aplicado de forma limpa.", "Agentless monitoring and observability platform.": "Plataforma de monitoramento e observabilidade sem agentes.", + "AirDC++ administrator": "Administrador do AirDC++", "Airsonic Advanced web interface": "Interface Web Airsonic Advanced", "Airsonic-advanced is a free, web-based media streamer, providing ubiquitious access to your music. Use it to share your music with friends, or to listen to your own music while at work. You can stream to multiple players simultaneously, for instance to one player in your kitchen and another in your living room.": "Airsonic-avanced é uma transmissão de mídia gratuita, baseada na web, proporcionando ubiquitious acesso à sua música. Use-o para compartilhar sua música com amigos, ou para ouvir sua própria música durante o trabalho. Você pode transmitir para vários jogadores simultaneamente, por exemplo para um jogador na sua cozinha e outro na sua sala de estar.", "Aliases added to .bashrc": "Aliases adicionados a .bashrc", @@ -598,11 +601,15 @@ "Attention: Removing the subscription banner may cause issues in the web interface after a future update.": "Atenção: A remoção do banner de assinatura pode causar problemas na interface web após uma atualização futura.", "Audacity is an easy-to-use, multi-track audio editor and recorder. Developed by a group of volunteers as open source.": "Audacity é um editor e gravador de áudio de várias faixas fácil de usar. Desenvolvido por um grupo de voluntários como código aberto.", "Audio device directory": "Directório do dispositivo de áudio", + "Audio recording and mixing in your browser": "Gravação e mixagem de áudio no navegador", "Audiobookshelf is a self-hosted audiobook and podcast server.": "Audiobookshelf é um servidor de áudio e podcast self-hosted.", "Audit completed. Press Enter to continue...": "Auditoria concluída. Pressione Enter para continuar...", "Audit socket disabled or not required": "soquete de auditoria desabilitado ou não necessário", "Auth key is required.": "A chave de autenticação é obrigatória.", "Auth:": "Autenticação:", + "Authelia Web UI": "UI da Web Authelia", + "Authelia administrator": "administrador Authelia", + "Authelia session cookie domain for this portal (for example example.com)": "domínio de cookie de sessão Authelia para este portal (por exemplo example.com)", "Authentication": "Autenticação", "Authentication & Security": "Autenticação e segurança", "Authentication Error": "Erro de autenticação", @@ -642,6 +649,7 @@ "Available PVE Version": "Versão PVE disponível", "Available Shares:": "Ações disponíveis:", "Available action": "Ação disponível", + "Available applications: {count}": "Aplicações disponíveis: {count}", "Available archives:": "Arquivos disponíveis:", "Available disks detected.": "Discos disponíveis detectados.", "Available host backups:": "Backups de host disponíveis:", @@ -873,6 +881,7 @@ "Cannot write the image archive to {storage}. Check storage access and mount status.": "Não é possível gravar o arquivo de imagem em {storage}. Verifique o acesso ao armazenamento e o status de montagem.", "Capabilities cannot be kept and all dropped at the same time": "Capacidades não podem ser mantidas e todas retiradas ao mesmo tempo", "Catalog options": "Opções do catálogo", + "Catalog refresh unavailable; using the local catalog.": "Não foi possível atualizar o catálogo; está a ser usada a cópia local.", "Category": "Categoria", "Caution: Maximum mode generates more heat.": "Cuidado: O modo máximo gera mais calor.", "Central server for file and image backups.": "Servidor central para backups de arquivos e imagens.", @@ -1159,6 +1168,7 @@ "Compressing": "Comprimindo", "Compression": "Compressão", "Compression Tools": "Ferramentas de compressão", + "Computer-aided design and drafting in your browser": "Projeto CAD e desenho técnico no navegador", "Concise output of logical volumes": "Saída concisa de volumes lógicos", "Concise output of physical volumes": "Saída concisa de volumes físicos", "Concise output of volume groups": "Saída concisa de grupos de volumes", @@ -1393,6 +1403,7 @@ "Controller + NVMe": "Controlador + NVMe", "Controller + NVMe assignment will be written now and become active after host reboot.": "A atribuição de controlador + NVMe será gravada agora e se tornará ativa após a reinicialização do host.", "Controller + NVMe passthrough requires machine type q35. Skipping controller assignment.": "A passagem Controlador + NVMe requer tipo de máquina q35. Ignorando atribuição do controlador.", + "Controller path on the Proxmox host; prefer /dev/serial/by-id/...": "caminho do controlador no host Proxmox;prefira /dev/serial/by-id/...", "Controller/NVMe Conflict Policy": "Política de Conflito Controlador/NVMe", "Controller/NVMe already present in VM config": "Controlador/NVMe já presente na configuração da VM", "Controller/NVMe already present in VM config (%s)": "Controlador/NVMe já presente na configuração da VM (%s)", @@ -1927,6 +1938,7 @@ "Dgraph": "Dgraph", "Diff: current system vs backup (--- system +++ backup)": "Diferença: sistema atual vs backup (--- backup do sistema +++)", "Different host. Backup from:": "Anfitrião diferente. Backup de:", + "Direct Connect file sharing with a web interface": "Partilha de ficheiros Direct Connect com interface web", "Direct conversion completed for container": "Conversão direta concluída para contêiner", "Directory Exists": "O diretório existe", "Directory Not Empty": "Diretório não vazio", @@ -3340,6 +3352,7 @@ "Initial Stirling-PDF account": "Conta inicial do Stirling-PDF", "Initial Tandoor administrator": "Administrador inicial Tandoor", "Initial Umami administrator": "Administrador inicial do Umami", + "Initial Z-Wave JS UI admin password": "Senha de administrador inicial do Z-Wave JS UI", "Initial administrator created:": "Administrador inicial criado:", "Initial administrator user": "Utilizador de administrador inicial", "Initializing Borg repository if needed...": "Inicializando o repositório Borg, se necessário...", @@ -3525,6 +3538,7 @@ "Interface MTU": "Interface MTU", "Interface Names (persistent)": "Nomes de Interface (persistente)", "Interface Type": "Tipo de interface", + "Interface design and prototyping in your browser": "Design de interfaces e protótipos no navegador", "Interface names will return to default systemd behavior.": "Os nomes das interfaces retornarão ao comportamento padrão do systemd.", "Interface type:": "Tipo de interface:", "Interfaces configured": "Interfaces configuradas", @@ -3740,6 +3754,7 @@ "Jupyter Lab (token only)": "Laboratório Jupyter (apenas por token)", "JupyterLab": "JupyterLab", "JupyterLab access token": "Token de acesso do JupyterLab", + "KDE's twin-panel file manager in a browser-based desktop.": "Gerenciador de arquivos de painel duplo do KDE em uma área de trabalho baseada em navegador.", "KDF:": "KDF:", "KROMA": "KROMA", "KVM MSR options added to /etc/modprobe.d/kvm.conf": "Opções KVM MSR adicionadas a /etc/modprobe.d/kvm.conf", @@ -3787,12 +3802,14 @@ "KiCad - A Cross Platform and Open Source Electronics Design Automation Suite.": "KiCad - Uma plataforma cruzada e Open Source Electronics Design Automation Suite.", "Kimai has no default account. Enter the container with: pct enter {main_vmid}": "O Kimai não tem uma conta predefinida. Aceda ao contentor com: pct enter {main_vmid}", "Kimai is a professional grade time-tracking application, free and open-source.": "Kimai é um aplicativo de rastreamento de tempo profissional, gratuito e de código aberto.", + "KitchenOwl": "KitchenOwl", "Kometa is a powerful tool designed to give you complete control over your media libraries. With Kometa, you can take your customization to the next level, with granular control over metadata, collections, overlays, and much more.": "Kometa é uma ferramenta poderosa projetada para lhe dar controle completo sobre suas bibliotecas de mídia. Com o Kometa, você pode levar sua personalização para o próximo nível, com controle granular sobre metadados, coleções, sobreposições e muito mais.", "Kometa reads its configuration from /config/config.yml and the container only ships /config/config.yml.template. Copy the template to config.yml, fill in the required Plex and TMDb connections, then restart the container.": "Kometa lê a sua configuração a partir de /config/config.yml e o recipiente apenas navios /config/config.yml.template. Copie o modelo para config.yml, preencha o requi vermelho Plex e conexões TMDb, em seguida, reinicie o recipiente.", "Komga is a media server for your comics, mangas, BDs, magazines and eBooks.": "Komga é um servidor de mídia para seus quadrinhos, mangás, BDs, revistas e eBooks.", "Kopia Web UI": "Interface web do Kopia", "Kopia Web UI login": "Acesso à interface web do Kopia", "Krita is a professional FREE and open source painting program. It is made by artists that want to see affordable art tools for everyone.": "Krita é um programa de pintura livre e livre profissional. É feito por artistas que querem ver ferramentas de arte acessíveis para todos.", + "Krusader Web UI": "UI da Web Krusader", "LAN access to the kept containers (stack configuration incomplete):": "Acesso LAN aos recipientes mantidos (configuração incompleta):", "LAN address applied to the application URLs": "Endereço LAN aplicado aos URLs da aplicação", "LLDAP": "LLDAP", @@ -4020,6 +4037,7 @@ "Manage OCI stack": "Gerenciar a pilha OCI", "Manage PBS encryption keyfile": "Gerenciar arquivo de chave de criptografia PBS", "Manage Secure Gateway": "Gerenciar Secure Gateway", + "Manage a Z-Wave network from a web dashboard and WebSocket gateway.": "gerencie uma rede Z-Wave a partir de um painel da web e gateway WebSocket.", "Manage and inspect VM disk images": "Gerenciar e inspecionar imagens de disco de VM", "Manage custom backup paths": "Gerencie caminhos de backup personalizados", "Manage custom paths (add / remove your folders)": "Gerencie caminhos personalizados (adicione/remova suas pastas)", @@ -4029,6 +4047,7 @@ "Manage local backup target": "Gerenciar destino de backup local", "Manage private image registries.\nSaved credentials let ProxMenux access your private images.": "Gerencie registros de imagens privadas.\nAs credenciais salvas permitem que ProxMenux acesse suas imagens privadas.", "Manage registry accounts as root on the Proxmox node.": "Gerencie contas de registro como root no nó Proxmox.", + "Manage teams of AI agents from a web dashboard.": "Gerencie equipes de agentes de IA a partir de um painel web.", "Managed disks must have backup enabled and a valid size": "Os discos gerenciados devem ter backup ativado e um tamanho válido", "Managed public route include is missing": "Falta o ficheiro de configuração das rotas públicas geridas", "Managed public route installation failed": "Não foi possível configurar as rotas públicas geridas", @@ -4134,6 +4153,7 @@ "Monitoring": "Monitoramento", "Monitoring & Analytics": "Monitorização e análise", "Most common cause: the archive is corrupted (interrupted write, partial copy, or storage issue).": "Causa mais comum: o arquivo está corrompido (gravação interrompida, cópia parcial ou problema de armazenamento).", + "Motion graphics and compositing in your browser": "Animação gráfica e composição no navegador", "Mount Added Successfully:": "Montagem adicionada com sucesso:", "Mount CIFS share:": "Monte o compartilhamento CIFS:", "Mount Configuration Summary:": "Resumo da configuração de montagem:", @@ -4293,6 +4313,7 @@ "NVIDIA (CUDA; official GPU image)": "NVIDIA (CUDA; imagem oficial da GPU)", "NVIDIA (NVDEC/CUDA)": "NVIDIA (NVDEC/CUDA)", "NVIDIA (NVENC/NVDEC)": "NVIDIA (NVENC/NVDEC)", + "NVIDIA (Selkies rendering and NVENC)": "NVIDIA (renderização Selkies e NVENC)", "NVIDIA Actions": "Ações NVIDIA", "NVIDIA CPU hiding already configured": "Ocultação de CPU NVIDIA já configurada", "NVIDIA Container Toolkit": "NVIDIA Container Toolkit", @@ -4434,6 +4455,8 @@ "New Search": "Nova pesquisa", "New VM:": "Nova VM:", "New Virtual Machine": "Nova máquina virtual", + "New applications ({count})": "Novas aplicações ({count})", + "New applications since your last visit: {count}": "Novas aplicações desde a sua última visita: {count}", "New backup job": "Nova tarefa de backup", "New backups on this host will be unencrypted until a new keyfile is set up.": "Novos backups neste host serão descriptografados até que um novo arquivo-chave seja configurado.", "New image compatible:": "Nova imagem compatível:", @@ -4896,6 +4919,8 @@ "Open the page of an application in the menu": "Abrir a página de uma aplicação no menu", "Open the web address and create the first account: the daemon registers by itself and starts the first scan.": "Abra o endereço web e crie a primeira conta: o daemon regista-se sozinho e inicia a primeira análise.", "Open the web address and create the password of the akadmin user in the first-run setup.": "Abra o endereço da web e crie a senha do usuário akadmin na configuração inicial.", + "Open the web address and follow KitchenOwl’s onboarding to create the first account; no default account is configured.": "Abra o endereço web e siga o onboarding do KitchenOwl para criar a primeira conta;nenhuma conta padrão está configurada.", + "Open the web dashboard and link your Movistar/O2 account using its SMS flow.": "Abra o painel web e vincule sua conta Movistar/O2 com a verificação por SMS.", "Open-source AI-powered coding assistant": "Assistente de codificação com código aberto", "Open-source UI for building and debugging multi-agent and RAG applications": "UI de código aberto para a construção e depuração de aplicações multi-agente e RAG", "Open-source in-memory data store.": "Armazenamento de dados em memória de código aberto.", @@ -5018,6 +5043,7 @@ "Packages:": "Pacotes:", "Packaging OVA file...": "Empacotando arquivo OVA...", "Packing installer archive...": "Empacotando o arquivo do instalador...", + "Page layout and publishing in your browser": "Diagramação e publicação no navegador", "PairDrop is a sublime alternative to AirDrop that works on all platforms. Send images, documents or text via peer to peer connection to devices in the same local network/Wi-Fi or to paired devices.": "PairDrop é uma alternativa sublime para AirDrop que funciona em todas as plataformas. Envie imagens, documentos ou texto via peer para conexão por pares a dispositivos na mesma rede local/Wi-Fi ou para dispositivos pareados.", "Paperless configuration cancelled": "Configuração sem papel cancelada", "Paperless-ngx WebUI": "Paperless-ngx WebUI", @@ -5036,6 +5062,7 @@ "Pass /dev/video12 to the LXC": "Passe /dev/video12 para o LXC", "Pass a host device to the LXC": "Passe um dispositivo da máquina para o LXC", "Pass the UPS to the LXC": "Passe o UPS para o LXC", + "Pass through a Z-Wave USB or serial controller": "passe por um controlador serial ou USB Z-Wave", "Passing NVIDIA does not enable acceleration inside the application. The host needs NVIDIA Container Toolkit and a compatible image. Continue?": "Passar NVIDIA não habilita a aceleração dentro do aplicativo. O host precisa do NVIDIA Container Toolkit e de uma imagem compatível. Continuar?", "Passkey-based identity provider for self-hosted apps.": "Fornecedor de identidade baseado em passkeys para aplicações autoalojadas.", "Passphrase used to unlock the imported keyfile (leave blank if the keyfile is unencrypted / kdf=none):": "Senha usada para desbloquear o arquivo-chave importado (deixe em branco se o arquivo-chave não estiver criptografado / kdf=none):", @@ -5143,6 +5170,8 @@ "Personal finance management application": "Aplicação de gestão de finanças pessoais", "Personal media server for browsing and playing a local video library, with direct play and optional modules.": "Servidor multimídia pessoal para navegar e reproduzir uma biblioteca local de vídeos, com reprodução direta e módulos opcionais.", "Photo and video library with optional GPU transcoding and machine learning": "Biblioteca de fotos e vídeos com transcodificação GPU opcional e aprendizado de máquina", + "Photo editing in your browser": "Edição de fotos no navegador", + "Photo library and RAW development in your browser": "Biblioteca de fotos e revelação RAW no navegador", "PhotoPrism": "PhotoPrism", "Photofield": "Photofield", "Phpmyadmin is a free software tool written in PHP, intended to handle the administration of MySQL over the Web. phpMyAdmin supports a wide range of operations on MySQL and MariaDB.": "Phpmyadmin é uma ferramenta de software livre escrita em PHP, destinada a lidar com a administração do MySQL através da Web. phpMyAdmin suporta uma ampla gama de operations no MySQL e MariaDB.", @@ -5235,6 +5264,7 @@ "Preparing the machine learning container for the new choice...": "Preparando o contêiner de aprendizado de máquina para a nova escolha...", "Preparing the recreation...": "Preparando a recreação...", "Preparing the update...": "Preparando a atualização...", + "Presentations and slide decks in your browser": "Apresentações e slides no navegador", "Preserving logs to /var/log.hdd before unmounting...": "Preservando logs em /var/log.hdd antes de desmontar...", "Press 'q' to exit": "Pressione 'q' para sair", "Press Ctrl+C to stop the server and return to menu.": "Pressione Ctrl+C para parar o servidor e retornar ao menu.", @@ -5395,6 +5425,7 @@ "Proxmox web interface: Datacenter > Storage > Add > ZFS": "Interface web Proxmox: Datacenter > Armazenamento > Adicionar > ZFS", "Proxmox web interface: Datacenter > Storage > Add > iSCSI": "Interface web Proxmox: Datacenter > Armazenamento > Adicionar > iSCSI", "Proxy address to reach the API (optional)": "Endereço proxy para acessar a API (opcional)", + "Public HTTPS URL used to open this Authelia portal (for example https://auth.example.com)": "URL HTTPS público usado para abrir este portal Authelia (por exemplo https://auth.example.com)", "Public UDP port clients connect to": "Clientes públicos de porta UDP se conectam", "Public UDP port peers connect to": "Os pares públicos de portas UDP se conectam", "Public URL of phpMyAdmin when it is served behind a reverse proxy": "URL público do phpMyAdmin quando é servido por trás de um proxy reverso", @@ -5455,6 +5486,7 @@ "Re-running pre-check after repairs...": "Executando novamente a pré-verificação após os reparos...", "Reachable": "Acessível", "Read an oci-compose from a file of this host": "Leia um oci-compose de um arquivo deste host", + "Read and edit PDF documents in your browser": "Leitura e edição de documentos PDF no navegador", "Read its Compose file from a file of this host": "Leia seu arquivo Compor de um arquivo desta máquina", "Read manga through a self-hosted server and web UI.": "Leia mangás em um servidor self-hosted com interface web.", "Read the link with pct console CTID on the Proxmox host, or from the Console panel of the container in the Proxmox web interface, then open the https://playit.gg/claim/ address it shows in a browser and sign in to playit.gg. Ctrl+a q leaves pct console.": "Leia o link com o PCT console CTID no host Proxmox, ou a partir do painel Console do recipiente na interface web Proxmox, em seguida, abra o https://playit.gg/claim/ endereço que ele mostra em um navegador e entre para playit.gg. Ctrl+a q deixa o console pct.", @@ -5560,6 +5592,7 @@ "Recursive and authoritative DNS server.": "Servidor DNS recursivo e autoritativo.", "Redmine": "Redmine", "Refresh APT index and verify repositories:": "Atualize o índice APT e verifique os repositórios:", + "Refresh catalog": "Atualizar catálogo", "Refresh the configured APT repositories and review the PBS package upgrade? Repository files will be preserved.": "Atualizar os índices dos repositórios APT configurados e rever a atualização de pacotes PBS? Os ficheiros dos repositórios serão preservados.", "Refresh the index from the image sources": "Atualizar o índice a partir das origens de imagens", "Refresh token of the connector, from the Twingate admin console": "Token de atualização do conector, no console administrativo Twingate", @@ -5567,6 +5600,7 @@ "Refresh your browser to see changes (server restart may be required)": "Atualize seu navegador para ver as alterações (pode ser necessário reiniciar o servidor)", "Refreshing apt cache...": "Atualizando o cache do apt...", "Refreshing the NVIDIA runtime...": "Atualizando o tempo de execução NVIDIA...", + "Refreshing the application catalog...": "A atualizar o catálogo de aplicações...", "Refusing an unexpected rootfs path:": "Recusando um caminho inesperado do rootfs:", "Regenerate the catalog templates": "Regenerar os modelos do catálogo", "Regenerate the template of one application": "Regenerar o modelo de uma aplicação", @@ -6258,6 +6292,7 @@ "Self-hosted file sync and sharing.": "Sincronização e compartilhamento de arquivos self-hosted.", "Self-hosted file toolkit for images, video, audio, PDFs, and files": "Kit de ferramentas de arquivo auto hospedado para imagens, vídeo, áudio, PDFs e arquivos", "Self-hosted forms and workflows.": "Formulários e fluxos de trabalho autoalojados.", + "Self-hosted grocery list and recipe manager": "lista de compras auto-hospedada e gerenciador de receitas", "Self-hosted household plant-care tracker with reminders, photos, and multiple user accounts.": "Gerenciador auto-hospedado de cuidados com plantas domésticas, com lembretes, fotos e várias contas de usuário.", "Self-hosted image-hosting service with authenticated uploads, image processing, public links, and local storage.": "Serviço auto-hospedado de hospedagem de imagens, com uploads autenticados, processamento de imagens, links públicos e armazenamento local.", "Self-hosted internet archiving solution": "Solução de arquivamento de internet self-hosted", @@ -6452,6 +6487,7 @@ "Simple and easy to use DDNS": "Simples e fácil de usar DDNS", "Simple self-hosted file sharing.": "Serviço simples e autoalojado para partilha de ficheiros.", "Single GPU Warning": "Aviso de GPU única", + "Single sign-on and multi-factor authentication": "login único e autenticação multifator", "Single target found — selected automatically:": "Único alvo encontrado — selecionado automaticamente:", "Single-node log storage and query UI.": "Armazenamento de logs para um único nó com interface de consulta.", "Single-node observability database for metrics, logs and traces.": "Banco de dados de observabilidade de nó único para métricas, logs e traces.", @@ -6520,7 +6556,9 @@ "Specific subnet (enter manually)": "Sub-rede específica (inserir manualmente)", "Speedtest Tracker web interface": "Interface Web Speedtest Tracker", "Speedtest-tracker is a self-hosted internet performance tracking application that runs speedtest checks against Ookla's Speedtest service.": "Speedtest-tracker é um aplicativo de rastreamento de desempenho da internet que executa verificações de velocidade contra o serviço Speedtest da Ookla.", + "Spoolman web interface": "interface web Spoolman", "Spotube is an open source, cross-platform Spotify client compatible across multiple platforms utilizing Spotify's data API and YouTube, Piped.video or JioSaavn as an audio source, eliminating the need for Spotify Premium": "Spotube é um cliente Spotify de código aberto, multi-plataforma compatível em várias plataformas utilizando a API de dados do Spotify e YouTube, Piped. vídeo ou JioSaavn como uma fonte de áudio, eliminando a necessidade de Spotify Premium", + "Spreadsheets in your browser": "Planilhas no navegador", "Sshwifty": "Sshwifty", "Sshwifty web access (password only)": "Acesso web ao Sshwifty (somente com senha)", "Stable (main branch)": "Estável (ramo principal)", @@ -7417,6 +7455,7 @@ "Then bind-mount to container": "Em seguida, ligue a montagem ao contêiner", "Then change the VM display to none (vga: none) when the guest is stable.": "Em seguida, altere a exibição da VM para nenhum (vga: nenhum) quando o convidado estiver estável.", "Then change the VM display to none (vga: none) when the system is stable.": "Em seguida, altere a exibição da VM para nenhum (vga: nenhum) quando o sistema estiver estável.", + "Then restart the container so the new model appears in the list.": "Depois reinicie o contêiner para que o novo modelo apareça na lista.", "Then run this option again:": "Em seguida, execute esta opção novamente:", "Then update /etc/fstab on the host with the same options.": "Em seguida, atualize /etc/fstab no host com as mesmas opções.", "There are extra disks or bind mounts outside the journal; the rootfs is not replaced": "Existem discos extras ou montagens de ligação fora do diário; o rootfs não é substituído", @@ -7464,6 +7503,7 @@ "This container does not have apt-get. NFS client installation only supports Debian/Ubuntu containers.": "Este contêiner não possui o apt-get. A instalação do cliente NFS suporta apenas contêineres Debian/Ubuntu.", "This container does not have apt-get. Samba client installation only supports Debian/Ubuntu containers.": "Este contêiner não possui o apt-get. A instalação do cliente Samba suporta apenas contêineres Debian/Ubuntu.", "This container has no GPU configured. Coral TPU works best alongside hardware video decoding (Quick Sync, VA-API, NVENC) for apps like Frigate.": "Este contêiner não tem GPU configurada. Coral TPU funciona melhor com decodificação de vídeo de hardware (Quick Sync, VA-API, NVENC) para aplicativos como Frigate.", + "This container is managed by high availability. Remove ct:{vmid} from HA (Datacenter → HA, or ha-manager remove ct:{vmid}), repeat the operation and add it back to HA with the same settings.": "Este contentor é gerido pela alta disponibilidade. Remova ct:{vmid} da HA (Datacenter → HA, ou ha-manager remove ct:{vmid}), repita a operação e volte a adicioná-lo à HA com as mesmas definições.", "This container is not a member of a multi-container application": "Este contêiner não é membro de um aplicativo de vários contêineres", "This container is not a registered OCI instance.": "Este contêiner não é uma instância OCI registrada.", "This container was restored or came back from another host after its record on this host was written. Open this menu again to recover it; nothing was changed.": "Este contêiner foi restaurado ou voltou de outro host depois que seu registro neste host foi gravado. Abra este menu novamente para recuperá-lo; nada foi alterado.", @@ -7582,6 +7622,7 @@ "Tip: You can install the QEMU Guest Agent inside the VM with:": "Dica: você pode instalar o agente convidado QEMU dentro da VM com:", "Tip: zfs set acltype=posixacl xattr=sa / enables full ACL support.": "Dica: zfs set acltype=posixacl xattr=sa / permite suporte completo a ACL.", "Tmpfs size in MiB for": "Tamanho do Tmpfs em MiB para", + "To add a model, open the console of the container in Proxmox and download it into /models, for example:": "Para adicionar um modelo, abra o console do contêiner no Proxmox e baixe-o em /models, por exemplo:", "To allow LXC write access, change the NFS export on the server to include:": "Para permitir o acesso de gravação LXC, altere a exportação NFS no servidor para incluir:", "To apply it to the current shell now, run:": "Para aplicá-lo agora à sessão shell atual, execute:", "To assign VFs to VMs or LXCs, edit the configuration manually via the Proxmox web interface. The Physical Function will remain bound to the native driver.": "Para atribuir VFs a VMs ou LXCs, edite a configuração manualmente por meio da interface web do Proxmox. A Função Física permanecerá vinculada ao driver nativo.", @@ -7621,6 +7662,7 @@ "Total routes": "Rotas totais", "Total size:": "Tamanho total:", "Toxiproxy": "Toxiproxy", + "Track 3D printer filament spools and usage.": "rastreie os carretéis e o uso do filamento da impressora 3D.", "Track movies, television, anime and games you want to enjoy.": "Acompanhe os filmes, séries, anime e videojogos que quer ver ou jogar.", "Track subscriptions and recurring expenses.": "Acompanhe subscrições e despesas recorrentes.", "Track your music listening history on your own server.": "Acompanhe seu histórico de músicas ouvidas no seu próprio servidor.", @@ -7855,6 +7897,7 @@ "Update the whole stack?": "Atualizar toda a pilha?", "Update, modify or recreate one installed OCI instance": "Atualizar, modificar ou recriar uma instância OCI instalada", "Updated": "Atualizado", + "Updated recipes since your last visit: {count}": "Receitas atualizadas desde a sua última visita: {count}", "Updated sharedfiles group to GID: 101000": "Grupo de arquivos compartilhados atualizado para GID: 101000", "Updated stack checked": "Stack atualizado verificado", "Updated:": "Actualizado:", @@ -8060,6 +8103,7 @@ "Variables": "Variáveis", "Variables the installation asks for:": "Variáveis que a instalação solicita:", "Vaultwarden Web Vault": "Vaultwarden Web Vault", + "Vector illustration in your browser": "Ilustração vetorial no navegador", "Vector search engine with a built-in dashboard.": "Mecanismo de busca vetorial com painel integrado.", "Verbose pool status": "Status detalhado do pool", "Verification": "Verificação", @@ -8090,6 +8134,7 @@ "Versions shown belong to maintained NVIDIA branches that list your GPU PCI ID and are new enough to build against the running kernel. DKMS compilation is the final validation. The recommended version keeps the current branch, or uses the NVIDIA Production Branch on a fresh install.": "As versões mostradas pertencem a ramificações NVIDIA mantidas que listam seu ID PCI da GPU e são novas o suficiente para serem construídas no kernel em execução. A compilação DKMS é a validação final. A versão recomendada mantém a ramificação atual ou usa a ramificação de produção NVIDIA em uma nova instalação.", "VictoriaLogs": "VictoriaLogs", "Video acceleration and object detection are independent choices; the installer does not write camera or detector YAML.": "Aceleração de vídeo e detecção de objetos são escolhas independentes; o instalador não grava câmera ou detector YAML.", + "Video editing in your browser": "Edição de vídeo no navegador", "Video transcoding acceleration": "Aceleração da transcodificação de vídeo", "Video transcoding profile not implemented:": "Perfil de transcodificação de vídeo não implementado:", "View CIFS Mounts (pvesm + fstab)": "Ver montagens CIFS (pvesm + fstab)", @@ -8199,12 +8244,17 @@ "Web access": "Acesso à Internet", "Web address of the AdGuard Home that receives the settings (e.g. http://192.168.1.3)": "Endereço Web do AdGuard Home que recebe as definições (por exemplo, http://192.168.1.3)", "Web address of the main AdGuard Home, whose settings are copied (e.g. http://192.168.1.2)": "Endereço Web do AdGuard Home principal, cujas configurações são copiadas (por exemplo, http://192.168.1.2)", + "Web dashboard": "Painel web", + "Web dashboard and WebDAV": "Painel web e WebDAV", "Web interface": "Interface da Web", "Web interface for Borg Backup repositories and jobs.": "Interface web para gerenciar repositórios e tarefas do Borg Backup.", "Web interface for managing 3D printers.": "Interface web para gerenciar impressoras 3D.", "Web interface to manage devices running Tasmota firmware.": "Interface Web para gerenciar dispositivos rodando firmware Tasmota.", "Web reverse proxy with an administration interface.": "Proxy reverso web com interface de administração.", "WebCord can be summarized as a pack of security and privacy hardenings, Discord features reimplementations, Electron / Chromium / Discord bugs workarounds, stylesheets, internal pages and wrapped https://discord.com page, designed to conform with ToS as much as it is possible (or hide the changes that might violate it from Discord's eyes).": "WebCord pode ser resumido como um pacote de endurecimentos de segurança e privacidade, recursos de discórdia reimplementações, Electron / Chromium / Erros de discórdia workarounds, stylesheets, páginas internas e página envolto https://discord.com, projetado para se conformar com ToS tanto quanto possível (ou ocultar as alterações que podem violá-lo dos olhos da Discord).", + "WebDAV": "WebDAV", + "WebDAV and dashboard password (leave empty to generate)": "Senha do WebDAV e do painel (vazio = gerada)", + "WebDAV is available on container port 8080. Account linking is required before cloud file operations.": "O WebDAV está disponível na porta 8080 do contêiner. É preciso vincular a conta antes de operar com os arquivos da nuvem.", "Webgrabplus is a multi-site incremental xmltv epg grabber. It collects tv-program guide data from selected tvguide sites for your favourite channels.": "Webgrabplus é um multi-site incremental xmltv epg grabber. Ele coleta dados de guias de tv-programa de sites selecionados para seus canais favoritos.", "Webservers & Proxies": "Servidores web e proxies", "Website": "Site", @@ -8262,6 +8312,7 @@ "Without a usable reset path, passthrough reliability is poor and VM": "Sem um caminho de redefinição utilizável, a confiabilidade da passagem é baixa e a VM", "Wizard cancelled": "Assistente cancelado", "Wolfenstein: Blade of Agony is a story-driven WWII shooter inspired by Wolfenstein and Doom.": "Wolfenstein: Blade of Agony é um atirador inspirado em Wolfenstein e Doom.", + "Word processing in your browser": "Processamento de texto no navegador", "Workflow automation tool": "Ferramenta de automação de fluxo de trabalho", "Working directory:": "Diretório de trabalho:", "Works with LVM, ZFS, and BTRFS storage types": "Funciona com tipos de armazenamento LVM, ZFS e BTRFS", @@ -8312,6 +8363,8 @@ "Your machine learning Env work with Jupyter Lab": "O seu Env de aprendizagem de máquina trabalha com o Jupyter Lab", "Your next YouTube media manager": "Seu próximo gerenciador de mídia do YouTube", "Your_spotify is a self-hosted application that tracks what you listen and offers you a dashboard to explore statistics about it! It's composed of a web server which polls the Spotify API every now and then and a web application on which you can explore your statistics.": "Your_spotify é uma aplicação auto-anfitriã que rastreia o que você ouve e oferece um painel para explorar estatísticas sobre isso! É composto por um servidor web que pesquisa a API do Spotify de vez em quando e uma aplicação web na qual você pode explorar suas estatísticas.", + "Z-Wave JS UI": "Z-Wave JS UI", + "Z-Wave JS UI admin login": "login de administrador Z-Wave JS UI", "ZFS ARC config removed (kernel defaults will apply on reboot)": "Configuração do ZFS ARC removida (os padrões do kernel serão aplicados na reinicialização)", "ZFS ARC maximum configured:": "máximo do ZFS ARC configurado:", "ZFS ARC optimization completed": "Otimização ZFS ARC concluída", @@ -8425,6 +8478,7 @@ "could not be compiled for kernel": "não foi possível compilar para o kernel", "could not validate NVIDIA; exit code": "não foi possível validar o NVIDIA; código de saída", "cpuunits must be between 8 and 10000": "cpuunidades devem estar entre 8 e 10000", + "curl -L -o /models/gemma-3-1b-it-Q4_K_M.gguf https://huggingface.co/ggml-org/gemma-3-1b-it-GGUF/resolve/main/gemma-3-1b-it-Q4_K_M.gguf": "curl -L -o /models/gemma-3-1b-it-Q4_K_M.gguf https://huggingface.co/ggml-org/gemma-3-1b-it-GGUF/resolve/main/gemma-3-1b-it-Q4_K_M.gguf", "custom": "personalizado", "custom dependency commands are not yet supported": "comandos de dependência personalizados ainda não são suportados", "custom path(s) saved.": "caminhos personalizados salvos.", @@ -8631,6 +8685,7 @@ "kexec-tools is not installed or already removed.": "kexec-tools não está instalado ou já foi removido.", "layers": "camadas", "legacy .link file(s) to the ProxMenux-managed format": "arquivo(s) .link herdado(s) para o formato gerenciado pelo ProxMenux", + "llama.cpp shows every GGUF model saved in /models in its web UI and loads the one you choose.": "O llama.cpp mostra na sua interface web todos os modelos GGUF salvos em /models e carrega o que você escolher.", "log2ram completely removed from system": "log2ram completamente removido do sistema", "main container of the application": "contentor principal da aplicação", "make cloned mount tree read-only": "tornar só de leitura a árvore de montagem clonada", diff --git a/lang/sk.json b/lang/sk.json index fce024a1..cc09d7bf 100644 --- a/lang/sk.json +++ b/lang/sk.json @@ -184,6 +184,7 @@ "A tunneling daemon by Cloudflare that safely exposes your web servers into the internet.": "Tunelový démon Cloudflare, ktorý bezpečne vystavuje vaše webové servery na internet.", "A unified email and collaboration server.": "Jednotný server na e-mail a spoluprácu.", "A unified platform for logs, metrics and traces.": "Jednotná platforma pre logy, metriky a trasovanie.", + "A validating, recursive DNS resolver for your private network.": "Validujúci rekurzívny DNS resolver pre vašu súkromnú sieť.", "A versatile file conversion tool that supports multiple formats.": "Univerzálny nástroj na konverziu súborov, ktorý podporuje viacero formátov.", "A visual tool for exploring and documenting database schemas.": "Vizuálny nástroj na skúmanie a dokumentovanie schém databáz.", "A web GUI client of Project V which supports VMess, VLESS, SS, SSR, Trojan, Tuic and Juicity protocols": "Webový GUI klient projektu V, ktorý podporuje protokoly VMess, VLESS, SS, SSR, Trojan, Tuic a Juicity", @@ -256,6 +257,7 @@ "Acceleration for llama.cpp": "Zrýchlenie pre llama.cpp", "Accept routes from other nodes?": "Prijímať trasy z iných uzlov?", "Accept this host monitoring profile?": "Prijať tento profil monitorovania hostiteľa?", + "Access Movistar and O2 Cloud from WebDAV clients and backup tools.": "Prístup ku cloudu Movistar a O2 z klientov WebDAV a nástrojov na zálohovanie.", "Access Scope:": "Rozsah prístupu:", "Access bridge": "Sieťový most (bridge)", "Access bridge for Immich": "Sieťový most (bridge) pre Immich", @@ -410,6 +412,7 @@ "After the reboot, you will only be able to access the Proxmox host via:": "Po reštarte sa k Proxmox hostovi dostanete iba cez:", "After this LXC → VM switch, reboot the host so the new binding state is applied cleanly.": "Po tomto prepnutí LXC → VM reštartujte host, aby sa nové naviazanie použilo čisto.", "Agentless monitoring and observability platform.": "Platforma na monitorovanie a observabilitu bez klientskych agentov.", + "AirDC++ administrator": "Správca AirDC++", "Airsonic Advanced web interface": "Webové rozhranie Airsonic Advanced", "Airsonic-advanced is a free, web-based media streamer, providing ubiquitious access to your music. Use it to share your music with friends, or to listen to your own music while at work. You can stream to multiple players simultaneously, for instance to one player in your kitchen and another in your living room.": "Airsonic-advanced je bezplatný webový server na streamovanie hudby. Umožňuje zdieľať hudbu s priateľmi alebo počúvať vlastnú zbierku aj v práci. Podporuje súčasné prehrávanie na viacerých zariadeniach, napríklad v kuchyni a obývačke.", "Aliases added to .bashrc": "Aliasy boli pridané do .bashrc", @@ -598,11 +601,15 @@ "Attention: Removing the subscription banner may cause issues in the web interface after a future update.": "Upozornenie: odstránenie hlásenia o predplatnom môže po budúcej aktualizácii spôsobiť problém vo webovom rozhraní.", "Audacity is an easy-to-use, multi-track audio editor and recorder. Developed by a group of volunteers as open source.": "Audacity je jednoduchý viacstopový zvukový editor a rekordér, ktorý dobrovoľníci vyvíjajú ako softvér s otvoreným zdrojovým kódom.", "Audio device directory": "Adresár zvukových zariadení", + "Audio recording and mixing in your browser": "Nahrávanie a mixovanie zvuku v prehliadači", "Audiobookshelf is a self-hosted audiobook and podcast server.": "Audiobookshelf je server na audioknihy a podcasty, ktorý prevádzkujete sami.", "Audit completed. Press Enter to continue...": "Audit dokončený. Pokračujte stlačením Enter...", "Audit socket disabled or not required": "Zásuvka auditu je zakázaná alebo sa nevyžaduje", "Auth key is required.": "Auth key je povinný.", "Auth:": "Prihlásenie:", + "Authelia Web UI": "Webové rozhranie Authelia", + "Authelia administrator": "Správca Authelia", + "Authelia session cookie domain for this portal (for example example.com)": "Doména súboru cookie relácie Authelia pre tento portál (napríklad example.com)", "Authentication": "Prihlásenie", "Authentication & Security": "Overovanie a zabezpečenie", "Authentication Error": "Chyba prihlásenia", @@ -642,6 +649,7 @@ "Available PVE Version": "Dostupná verzia PVE", "Available Shares:": "Dostupné zdieľania:", "Available action": "Dostupná akcia", + "Available applications: {count}": "Dostupné aplikácie: {count}", "Available archives:": "Dostupné archívy:", "Available disks detected.": "Dostupné disky boli nájdené.", "Available host backups:": "Dostupné zálohy hosta:", @@ -873,6 +881,7 @@ "Cannot write the image archive to {storage}. Check storage access and mount status.": "Archív obrazu sa nedá zapísať do úložiska {storage}. Skontrolujte prístup k úložisku a stav jeho pripojenia.", "Capabilities cannot be kept and all dropped at the same time": "Oprávnenia capabilities nemožno súčasne ponechať aj všetky odobrať", "Catalog options": "Možnosti katalógu", + "Catalog refresh unavailable; using the local catalog.": "Katalóg sa nepodarilo aktualizovať; používa sa miestna kópia.", "Category": "Kategória", "Caution: Maximum mode generates more heat.": "Pozor: maximálny režim vytvára viac tepla.", "Central server for file and image backups.": "Centrálny server na zálohovanie súborov a obrazov.", @@ -1159,6 +1168,7 @@ "Compressing": "Komprimujem", "Compression": "Kompresia", "Compression Tools": "Kompresné nástroje", + "Computer-aided design and drafting in your browser": "Počítačom podporované navrhovanie a technické kreslenie v prehliadači", "Concise output of logical volumes": "Stručný výpis logických zväzkov", "Concise output of physical volumes": "Stručný výpis fyzických zväzkov", "Concise output of volume groups": "Stručný výpis skupín zväzkov", @@ -1393,6 +1403,7 @@ "Controller + NVMe": "Controller + NVMe", "Controller + NVMe assignment will be written now and become active after host reboot.": "Priradenie Controller + NVMe sa teraz zapíše a aktivuje sa po reštarte servera.", "Controller + NVMe passthrough requires machine type q35. Skipping controller assignment.": "Priame priradenie Controller + NVMe vyžaduje typ stroja q35. Priradenie controllera sa preskočí.", + "Controller path on the Proxmox host; prefer /dev/serial/by-id/...": "Cesta ku kontroléru na hostiteľovi Proxmox; uprednostnite /dev/serial/by-id/...", "Controller/NVMe Conflict Policy": "Riešenie konfliktu Controller/NVMe", "Controller/NVMe already present in VM config": "Controller/NVMe už je v nastavení VM", "Controller/NVMe already present in VM config (%s)": "Radič/NVMe už je v nastaveniach VM (%s)", @@ -1927,6 +1938,7 @@ "Dgraph": "Dgraph", "Diff: current system vs backup (--- system +++ backup)": "Rozdiel: aktuálny systém vs záloha (--- systém +++ záloha)", "Different host. Backup from:": "Iný host. Záloha z:", + "Direct Connect file sharing with a web interface": "Zdieľanie súborov cez Direct Connect s webovým rozhraním", "Direct conversion completed for container": "Priama konverzia bola dokončená pre kontajner", "Directory Exists": "Priečinok už existuje", "Directory Not Empty": "Priečinok nie je prázdny", @@ -3339,6 +3351,7 @@ "Initial Stirling-PDF account": "Počiatočný účet Stirling-PDF", "Initial Tandoor administrator": "Počiatočný správca Tandoor", "Initial Umami administrator": "Počiatočný správca Umami", + "Initial Z-Wave JS UI admin password": "Počiatočné heslo správcu Z-Wave JS UI", "Initial administrator account": "Úvodný účet správcu", "Initial administrator created:": "Pôvodný správca vytvorený:", "Initial administrator user": "Počiatočné používateľské meno správcu", @@ -3525,6 +3538,7 @@ "Interface MTU": "MTU rozhrania", "Interface Names (persistent)": "Trvalé názvy rozhraní", "Interface Type": "Typ rozhrania", + "Interface design and prototyping in your browser": "Návrh rozhraní a prototypov v prehliadači", "Interface names will return to default systemd behavior.": "Názvy rozhraní sa vrátia k predvolenému správaniu systemd.", "Interface type:": "Typ rozhrania:", "Interfaces configured": "Nastavené rozhrania", @@ -3740,6 +3754,7 @@ "Jupyter Lab (token only)": "Jupyter Lab (iba token)", "JupyterLab": "JupyterLab", "JupyterLab access token": "Prístupový token JupyterLab", + "KDE's twin-panel file manager in a browser-based desktop.": "Dvojpanelový správca súborov KDE v pracovnej ploche dostupnej cez webový prehliadač.", "KDF:": "KDF:", "KROMA": "KROMA", "KVM MSR options added to /etc/modprobe.d/kvm.conf": "Možnosti KVM MSR boli pridané do /etc/modprobe.d/kvm.conf", @@ -3787,12 +3802,14 @@ "KiCad - A Cross Platform and Open Source Electronics Design Automation Suite.": "KiCad – multiplatformový balík s otvoreným zdrojovým kódom na návrh elektroniky.", "Kimai has no default account. Enter the container with: pct enter {main_vmid}": "Kimai nemá predvolený účet. Do kontajnera vstúpite príkazom: pct enter {main_vmid}", "Kimai is a professional grade time-tracking application, free and open-source.": "Kimai je profesionálna bezplatná aplikácia na sledovanie pracovného času s otvoreným zdrojovým kódom.", + "KitchenOwl": "KitchenOwl", "Kometa is a powerful tool designed to give you complete control over your media libraries. With Kometa, you can take your customization to the next level, with granular control over metadata, collections, overlays, and much more.": "Kometa poskytuje podrobnú kontrolu nad mediálnymi knižnicami. Umožňuje prispôsobiť metadáta, kolekcie, grafické prekrytia a ďalšie prvky.", "Kometa reads its configuration from /config/config.yml and the container only ships /config/config.yml.template. Copy the template to config.yml, fill in the required Plex and TMDb connections, then restart the container.": "Kometa číta nastavenia z /config/config.yml, kontajner však obsahuje iba /config/config.yml.template. Skopírujte šablónu do config.yml, vyplňte požadované pripojenia Plex a TMDb a kontajner reštartujte.", "Komga is a media server for your comics, mangas, BDs, magazines and eBooks.": "Komga je mediálny server na komiksy, mangu, grafické romány, časopisy a elektronické knihy.", "Kopia Web UI": "Webové rozhranie Kopia", "Kopia Web UI login": "Prihlásenie do webového rozhrania Kopia", "Krita is a professional FREE and open source painting program. It is made by artists that want to see affordable art tools for everyone.": "Krita je profesionálny bezplatný maliarsky program s otvoreným zdrojovým kódom. Vytvárajú ho umelci, ktorí chcú sprístupniť umelecké nástroje každému.", + "Krusader Web UI": "Webové rozhranie Krusaderu", "LAN access to the kept containers (stack configuration incomplete):": "Prístup z LAN k ponechaným kontajnerom (nastavenie zostavy nie je dokončené):", "LAN address applied to the application URLs": "LAN adresa použitá na URL aplikácie", "LLDAP": "LLDAP", @@ -4020,6 +4037,7 @@ "Manage OCI stack": "Spravovať aplikáciu s viacerými kontajnermi", "Manage PBS encryption keyfile": "Spravovať šifrovací keyfile pre PBS", "Manage Secure Gateway": "Spravovať Secure Gateway", + "Manage a Z-Wave network from a web dashboard and WebSocket gateway.": "Spravujte sieť Z-Wave cez webový panel a bránu WebSocket.", "Manage and inspect VM disk images": "Spravovať a kontrolovať diskové obrazy VM", "Manage custom backup paths": "Spravovať vlastné cesty zálohy", "Manage custom paths (add / remove your folders)": "Spravovať vlastné cesty (pridať / odstrániť priečinky)", @@ -4029,6 +4047,7 @@ "Manage local backup target": "Spravovať lokálny cieľ záloh", "Manage private image registries.\nSaved credentials let ProxMenux access your private images.": "Správa súkromných registrov obrazov.\nUložené prihlasovacie údaje umožňujú ProxMenuxu pristupovať k vašim súkromným obrazom.", "Manage registry accounts as root on the Proxmox node.": "Spravujte účty registra ako root na uzle Proxmox.", + "Manage teams of AI agents from a web dashboard.": "Spravujte tímy AI agentov z webového panela.", "Managed disks must have backup enabled and a valid size": "Spravované disky musia mať zapnuté zálohovanie a platnú veľkosť", "Managed public route include is missing": "Chýba konfiguračný súbor spravovaných verejných trás", "Managed public route installation failed": "Nepodarilo sa nastaviť spravované verejné trasy", @@ -4134,6 +4153,7 @@ "Monitoring": "Monitorovanie", "Monitoring & Analytics": "Monitorovanie a analýza", "Most common cause: the archive is corrupted (interrupted write, partial copy, or storage issue).": "Najčastejšia príčina: archív je poškodený (prerušený zápis, neúplná kópia alebo problém s úložiskom).", + "Motion graphics and compositing in your browser": "Animovaná grafika a kompoziting v prehliadači", "Mount Added Successfully:": "Mount bol úspešne pridaný:", "Mount CIFS share:": "Pripojiť CIFS zdieľanie:", "Mount Configuration Summary:": "Zhrnutie nastavenia mountu:", @@ -4293,6 +4313,7 @@ "NVIDIA (CUDA; official GPU image)": "NVIDIA (CUDA; oficiálny obraz GPU)", "NVIDIA (NVDEC/CUDA)": "NVIDIA (NVDEC/CUDA)", "NVIDIA (NVENC/NVDEC)": "NVIDIA (NVENC/NVDEC)", + "NVIDIA (Selkies rendering and NVENC)": "NVIDIA (vykresľovanie Selkies a NVENC)", "NVIDIA Actions": "Akcie pre NVIDIA", "NVIDIA CPU hiding already configured": "NVIDIA CPU skrytie už je nastavené", "NVIDIA Container Toolkit": "NVIDIA Container Toolkit", @@ -4434,6 +4455,8 @@ "New Search": "Nové hľadanie", "New VM:": "Nová VM:", "New Virtual Machine": "Nová virtuálna mašina", + "New applications ({count})": "Nové aplikácie ({count})", + "New applications since your last visit: {count}": "Nové aplikácie od vašej poslednej návštevy: {count}", "New backup job": "Nová úloha zálohy", "New backups on this host will be unencrypted until a new keyfile is set up.": "Nové zálohy na tomto hostovi budú nešifrované, kým nenastavíte nový keyfile.", "New image compatible:": "Nový obraz je kompatibilný:", @@ -4898,6 +4921,8 @@ "Open the page of an application in the menu": "Otvoriť stránku aplikácie v ponuke", "Open the web address and create the first account: the daemon registers by itself and starts the first scan.": "Otvorte webovú adresu a vytvorte prvé konto: daemon sa zaregistruje sám a spustí prvé skenovanie.", "Open the web address and create the password of the akadmin user in the first-run setup.": "Otvorte webovú adresu a v prvotnom nastavení vytvorte heslo používateľa akadmin.", + "Open the web address and follow KitchenOwl’s onboarding to create the first account; no default account is configured.": "Otvorte webovú adresu a podľa pokynov KitchenOwl vytvorte prvý účet; predvolený účet nie je nastavený.", + "Open the web dashboard and link your Movistar/O2 account using its SMS flow.": "Otvorte webový panel a prepojte svoj účet Movistar/O2 pomocou overenia cez SMS.", "Open-source AI-powered coding assistant": "Programátorský asistent využívajúci AI s otvoreným zdrojovým kódom", "Open-source UI for building and debugging multi-agent and RAG applications": "Rozhranie s otvoreným zdrojovým kódom na tvorbu a ladenie aplikácií s viacerými agentmi a RAG", "Open-source in-memory data store.": "Úložisko údajov v pamäti s otvoreným zdrojovým kódom.", @@ -5020,6 +5045,7 @@ "Packages:": "Balíky:", "Packaging OVA file...": "Balím OVA súbor...", "Packing installer archive...": "Balím archív inštalátora...", + "Page layout and publishing in your browser": "Zalomenie strán a publikovanie v prehliadači", "PairDrop is a sublime alternative to AirDrop that works on all platforms. Send images, documents or text via peer to peer connection to devices in the same local network/Wi-Fi or to paired devices.": "PairDrop je skvelá alternatíva k AirDrop, ktorá pracuje na všetkých platformách. Poslať obrázky, dokumenty alebo text prostredníctvom peer-to-peer pripojenie k zariadeniam v rovnakej miestnej sieti/Wi-Fi alebo spárované zariadenia.", "Paperless configuration cancelled": "Nastavenie Paperless bolo zrušené", "Paperless-ngx WebUI": "Paperless-ngx WebUI", @@ -5038,6 +5064,7 @@ "Pass /dev/video12 to the LXC": "Sprístupniť /dev/video12 kontajneru LXC", "Pass a host device to the LXC": "Sprístupniť zariadenie hostiteľa kontajneru LXC", "Pass the UPS to the LXC": "Sprístupniť UPS kontajneru LXC", + "Pass through a Z-Wave USB or serial controller": "Pripojiť USB alebo sériový radič Z-Wave", "Passing NVIDIA does not enable acceleration inside the application. The host needs NVIDIA Container Toolkit and a compatible image. Continue?": "Prejdenie NVIDIA neumožní zrýchlenie v aplikácii. Hostiteľ potrebuje NVIDIA Container Toolkit a kompatibilný obrázok. Pokračovať?", "Passkey-based identity provider for self-hosted apps.": "Poskytovateľ identity založený na passkeys pre samohostované aplikácie.", "Passphrase used to unlock the imported keyfile (leave blank if the keyfile is unencrypted / kdf=none):": "Fráza na odomknutie importovaného keyfile (nechajte prázdne, ak keyfile nie je šifrovaný / kdf=none):", @@ -5145,6 +5172,8 @@ "Personal finance management application": "Aplikácia na správu osobných financií", "Personal media server for browsing and playing a local video library, with direct play and optional modules.": "Osobný mediálny server na prehliadanie a prehrávanie miestnej videoknižnice s priamym prehrávaním a voliteľnými modulmi.", "Photo and video library with optional GPU transcoding and machine learning": "Knižnica fotografií a videí s voliteľným prevodom videa pomocou GPU a strojovým učením", + "Photo editing in your browser": "Úprava fotografií v prehliadači", + "Photo library and RAW development in your browser": "Fotoknižnica a vyvolávanie RAW v prehliadači", "PhotoPrism": "PhotoPrism", "Photofield": "Photofield", "Phpmyadmin is a free software tool written in PHP, intended to handle the administration of MySQL over the Web. phpMyAdmin supports a wide range of operations on MySQL and MariaDB.": "phpMyAdmin je bezplatný nástroj napísaný v PHP na správu MySQL cez web. Podporuje širokú škálu operácií nad MySQL a MariaDB.", @@ -5237,6 +5266,7 @@ "Preparing the machine learning container for the new choice...": "Príprava kontajnera strojového učenia pre novú voľbu...", "Preparing the recreation...": "Príprava rekreácie...", "Preparing the update...": "Príprava aktualizácie...", + "Presentations and slide decks in your browser": "Prezentácie a snímky v prehliadači", "Preserving logs to /var/log.hdd before unmounting...": "Pred odpojením zachovávam logy do /var/log.hdd...", "Press 'q' to exit": "Stlačte 'q' na ukončenie", "Press Ctrl+C to stop the server and return to menu.": "Server zastavíte stlačením Ctrl+C a vrátite sa do menu.", @@ -5397,6 +5427,7 @@ "Proxmox web interface: Datacenter > Storage > Add > ZFS": "Webové rozhranie Proxmoxu: Datacenter > Storage > Add > ZFS", "Proxmox web interface: Datacenter > Storage > Add > iSCSI": "Webové rozhranie Proxmoxu: Datacenter > Storage > Add > iSCSI", "Proxy address to reach the API (optional)": "Adresa proxy na prístup k API (voliteľné)", + "Public HTTPS URL used to open this Authelia portal (for example https://auth.example.com)": "Verejná adresa HTTPS na otvorenie tohto portálu Authelia (napríklad https://auth.example.com)", "Public UDP port clients connect to": "Verejný port UDP na pripojenie klientov", "Public UDP port peers connect to": "Verejný port UDP na pripojenie účastníkov", "Public URL of phpMyAdmin when it is served behind a reverse proxy": "Verejné URL phpMyAdmin, keď sa podáva za reverzným proxy", @@ -5457,6 +5488,7 @@ "Re-running pre-check after repairs...": "Spúšťam predbežnú kontrolu znova po opravách...", "Reachable": "Dostupné", "Read an oci-compose from a file of this host": "Načítať oci-compose zo súboru tohto hostiteľa", + "Read and edit PDF documents in your browser": "Čítanie a úprava dokumentov PDF v prehliadači", "Read its Compose file from a file of this host": "Načítať súbor Compose z tohto hostiteľa", "Read manga through a self-hosted server and web UI.": "Čítajte mangu cez vlastný server a webové rozhranie.", "Read the link with pct console CTID on the Proxmox host, or from the Console panel of the container in the Proxmox web interface, then open the https://playit.gg/claim/ address it shows in a browser and sign in to playit.gg. Ctrl+a q leaves pct console.": "Odkaz zobrazíte na hostiteľovi Proxmox príkazom pct console CTID alebo v paneli Console kontajnera vo webovom rozhraní Proxmox. Zobrazenú adresu https://playit.gg/claim/ otvorte v prehliadači a prihláste sa do playit.gg. Konzolu pct console ukončíte pomocou Ctrl+a q.", @@ -5562,6 +5594,7 @@ "Recursive and authoritative DNS server.": "Rekurzívny a autoritatívny DNS server.", "Redmine": "Redmine", "Refresh APT index and verify repositories:": "Obnovte APT index a overte repozitáre:", + "Refresh catalog": "Aktualizovať katalóg", "Refresh the configured APT repositories and review the PBS package upgrade? Repository files will be preserved.": "Obnoviť indexy nakonfigurovaných repozitárov APT a skontrolovať aktualizáciu balíkov PBS? Súbory repozitárov sa zachovajú.", "Refresh the index from the image sources": "Obnoviť index zo zdrojov obrazov", "Refresh token of the connector, from the Twingate admin console": "Obnovovací token konektora z administrátorskej konzoly Twingate", @@ -5569,6 +5602,7 @@ "Refresh your browser to see changes (server restart may be required)": "Obnovte stránku v prehliadači, aby ste videli zmeny (môže byť potrebný reštart servera)", "Refreshing apt cache...": "Obnovujem apt cache...", "Refreshing the NVIDIA runtime...": "Obnovenie prevádzky NVIDIA...", + "Refreshing the application catalog...": "Aktualizuje sa katalóg aplikácií...", "Refusing an unexpected rootfs path:": "Odmietnutá neočakávaná cesta koreňového súborového systému:", "Regenerate the catalog templates": "Znova vygenerovať šablóny katalógu", "Regenerate the template of one application": "Znova vygenerovať šablónu jednej aplikácie", @@ -6260,6 +6294,7 @@ "Self-hosted file sync and sharing.": "Vlastná služba na synchronizáciu a zdieľanie súborov.", "Self-hosted file toolkit for images, video, audio, PDFs, and files": "Súprava nástrojov na vlastnom serveri na prácu s obrázkami, videom, zvukom, PDF a ďalšími súbormi", "Self-hosted forms and workflows.": "Formuláre a pracovné postupy prevádzkované na vlastnom serveri.", + "Self-hosted grocery list and recipe manager": "Správca nákupných zoznamov a receptov, ktorý si môžete prevádzkovať sami.", "Self-hosted household plant-care tracker with reminders, photos, and multiple user accounts.": "Prehľad starostlivosti o izbové rastliny prevádzkovaný na vlastnom serveri s pripomienkami, fotografiami a viacerými používateľskými účtami.", "Self-hosted image-hosting service with authenticated uploads, image processing, public links, and local storage.": "Služba na ukladanie a zdieľanie obrázkov na vlastnom serveri s nahrávaním pre overených používateľov, spracovaním obrázkov, verejnými odkazmi a lokálnym úložiskom.", "Self-hosted internet archiving solution": "Riešenie na archiváciu internetového obsahu na vlastnom serveri", @@ -6454,6 +6489,7 @@ "Simple and easy to use DDNS": "Jednoducho použiteľné dynamické DNS (DDNS)", "Simple self-hosted file sharing.": "Jednoduchá samohostovaná služba na zdieľanie súborov.", "Single GPU Warning": "Upozornenie na jednu GPU", + "Single sign-on and multi-factor authentication": "Jednotné prihlásenie a viacfaktorové overovanie", "Single target found — selected automatically:": "Našiel sa jeden target - vybraný automaticky:", "Single-node log storage and query UI.": "Úložisko logov a vyhľadávacie rozhranie pre jeden uzol.", "Single-node observability database for metrics, logs and traces.": "Databáza na jednom uzle na ukladanie metrík, logov a záznamov o priebehu požiadaviek.", @@ -6522,7 +6558,9 @@ "Specific subnet (enter manually)": "Konkrétna podsieť (zadať ručne)", "Speedtest Tracker web interface": "Webové rozhranie Speedtest Tracker", "Speedtest-tracker is a self-hosted internet performance tracking application that runs speedtest checks against Ookla's Speedtest service.": "Speedtest Tracker je aplikácia na vlastnom serveri na sledovanie výkonu internetového pripojenia pomocou meraní služby Ookla Speedtest.", + "Spoolman web interface": "Webové rozhranie Spoolmanu", "Spotube is an open source, cross-platform Spotify client compatible across multiple platforms utilizing Spotify's data API and YouTube, Piped.video or JioSaavn as an audio source, eliminating the need for Spotify Premium": "Spotube je multiplatformový klient Spotify s otvoreným zdrojovým kódom. Využíva dátové API Spotify a zvuk získava z YouTube, Piped.video alebo JioSaavn, takže nevyžaduje Spotify Premium.", + "Spreadsheets in your browser": "Tabuľkový procesor v prehliadači", "Sshwifty": "Sshwifty", "Sshwifty web access (password only)": "Webový prístup do Sshwifty (iba heslom)", "Stable (main branch)": "Stabilná verzia (vetva main)", @@ -7418,6 +7456,7 @@ "Then bind-mount to container": "Potom pripojiť do kontajnera cez bind mount", "Then change the VM display to none (vga: none) when the guest is stable.": "Keď bude VM stabilná, zmeňte jej display na none (vga: none).", "Then change the VM display to none (vga: none) when the system is stable.": "Keď bude systém stabilný, zmeňte display VM na none (vga: none).", + "Then restart the container so the new model appears in the list.": "Potom kontajner reštartujte, aby sa nový model zobrazil v zozname.", "Then run this option again:": "Potom túto možnosť spustite znova:", "Then update /etc/fstab on the host with the same options.": "Potom aktualizujte /etc/fstab na hoste s rovnakými možnosťami.", "There are extra disks or bind mounts outside the journal; the rootfs is not replaced": "Ďalšie disky alebo pripojené adresáre nie sú uvedené v zázname operácie; rootfs sa nenahradí", @@ -7465,6 +7504,7 @@ "This container does not have apt-get. NFS client installation only supports Debian/Ubuntu containers.": "Tento kontajner nemá apt-get. Inštalácia NFS klienta je podporovaná iba v Debian/Ubuntu kontajneroch.", "This container does not have apt-get. Samba client installation only supports Debian/Ubuntu containers.": "Tento kontajner nemá apt-get. Inštalácia Samba klienta je podporovaná iba v Debian/Ubuntu kontajneroch.", "This container has no GPU configured. Coral TPU works best alongside hardware video decoding (Quick Sync, VA-API, NVENC) for apps like Frigate.": "Tento kontajner nemá nastavenú GPU. Coral TPU funguje najlepšie spolu s hardvérovým dekódovaním videa (Quick Sync, VA-API, NVENC) v aplikáciách ako Frigate.", + "This container is managed by high availability. Remove ct:{vmid} from HA (Datacenter → HA, or ha-manager remove ct:{vmid}), repeat the operation and add it back to HA with the same settings.": "Tento kontajner spravuje vysoká dostupnosť. Odstráňte ct:{vmid} z HA (Dátové centrum → HA alebo ha-manager remove ct:{vmid}), zopakujte operáciu a potom ho znova pridajte do HA s rovnakými nastaveniami.", "This container is not a member of a multi-container application": "Tento kontajner nie je členom aplikácie s viacerými kontajnermi", "This container is not a registered OCI instance.": "Tento kontajner nie je registrovanou inštanciou OCI.", "This container was restored or came back from another host after its record on this host was written. Open this menu again to recover it; nothing was changed.": "Tento kontajner bol obnovený alebo sa vrátil z iného hostiteľa po napísaní jeho záznamu na tomto hostiteľovi. Znova otvorte túto ponuku a obnovte ju; nič sa nezmenilo.", @@ -7582,6 +7622,7 @@ "Tip: You can install the QEMU Guest Agent inside the VM with:": "Tip: QEMU Guest Agent môžete vo VM nainštalovať pomocou:", "Tip: zfs set acltype=posixacl xattr=sa / enables full ACL support.": "Tip: zfs set acltype=posixacl xattr=sa / zapne plnú podporu ACL.", "Tmpfs size in MiB for": "Veľkosť Tmpfs v MiB pre", + "To add a model, open the console of the container in Proxmox and download it into /models, for example:": "Ak chcete pridať model, otvorte konzolu kontajnera v Proxmoxe a stiahnite ho do /models, napríklad:", "To allow LXC write access, change the NFS export on the server to include:": "Aby LXC mohol zapisovať, upravte NFS export na serveri tak, aby obsahoval:", "To apply it to the current shell now, run:": "Ak ho chcete použiť v aktuálnej relácii shellu, spustite:", "To assign VFs to VMs or LXCs, edit the configuration manually via the Proxmox web interface. The Physical Function will remain bound to the native driver.": "Ak chcete priradiť VF do VM alebo LXC, upravte nastavenie ručne cez webové rozhranie Proxmoxu. Physical Function zostane naviazaná na natívny ovládač.", @@ -7621,6 +7662,7 @@ "Total routes": "Počet trás", "Total size:": "Celková veľkosť:", "Toxiproxy": "Toxiproxy", + "Track 3D printer filament spools and usage.": "Sledujte cievky filamentu pre 3D tlačiareň a ich spotrebu.", "Track movies, television, anime and games you want to enjoy.": "Sledujte filmy, seriály, anime a hry, ktoré si chcete pozrieť alebo zahrať.", "Track subscriptions and recurring expenses.": "Sledovanie predplatných a opakovaných výdavkov.", "Track your music listening history on your own server.": "Sledujte históriu počúvania hudby na vlastnom serveri.", @@ -7855,6 +7897,7 @@ "Update the whole stack?": "Aktualizovať celú aplikáciu?", "Update, modify or recreate one installed OCI instance": "Aktualizovať, upraviť alebo znova vytvoriť jednu nainštalovanú inštanciu OCI", "Updated": "Aktualizované", + "Updated recipes since your last visit: {count}": "Aktualizované recepty od vašej poslednej návštevy: {count}", "Updated sharedfiles group to GID: 101000": "Skupina sharedfiles bola aktualizovaná na GID: 101000", "Updated stack checked": "Aktualizovaná aplikácia je skontrolovaná", "Updated:": "Aktualizované:", @@ -8060,6 +8103,7 @@ "Variables": "Premenné", "Variables the installation asks for:": "Premenné, ktoré požaduje inštalácia:", "Vaultwarden Web Vault": "Vaultwarden Web Vault", + "Vector illustration in your browser": "Vektorová ilustrácia v prehliadači", "Vector search engine with a built-in dashboard.": "Vektorový vyhľadávač so vstavaným panelom.", "Verbose pool status": "Podrobný stav poolu", "Verification": "Overenie", @@ -8090,6 +8134,7 @@ "Versions shown belong to maintained NVIDIA branches that list your GPU PCI ID and are new enough to build against the running kernel. DKMS compilation is the final validation. The recommended version keeps the current branch, or uses the NVIDIA Production Branch on a fresh install.": "Zobrazené verzie patria do udržiavaných pobočiek NVIDIA, ktoré uvádzajú vaše ID PCI GPU a sú dostatočne nové na to, aby sa dali postaviť proti bežiacemu jadru. Kompilácia DKMS je konečným overením. Odporúčaná verzia ponecháva aktuálnu vetvu alebo používa NVIDIA Production Branch pri novej inštalácii.", "VictoriaLogs": "VictoriaLogs", "Video acceleration and object detection are independent choices; the installer does not write camera or detector YAML.": "Zrýchlenie videa a detekcia objektov sú nezávislé voľby. Inštalačný sprievodca nezapisuje nastavenia kamier ani detektorov do YAML.", + "Video editing in your browser": "Strih videa v prehliadači", "Video transcoding acceleration": "Zrýchlenie priepustnosti videa", "Video transcoding profile not implemented:": "Profil transkódovania videa nie je implementovaný:", "View CIFS Mounts (pvesm + fstab)": "Zobraziť CIFS mounty (pvesm + fstab)", @@ -8199,12 +8244,17 @@ "Web access": "Adresa webového rozhrania", "Web address of the AdGuard Home that receives the settings (e.g. http://192.168.1.3)": "Webová adresa AdGuard Home, ktorá prijíma nastavenia (napr. http://192.168.1.3)", "Web address of the main AdGuard Home, whose settings are copied (e.g. http://192.168.1.2)": "Webová adresa hlavného AdGuard Home, ktorého nastavenia sú kopírované (napr. http://192.168.1.2)", + "Web dashboard": "Webový panel", + "Web dashboard and WebDAV": "Webový panel a WebDAV", "Web interface": "Webové rozhranie", "Web interface for Borg Backup repositories and jobs.": "Webové rozhranie na správu úložísk a úloh Borg Backup.", "Web interface for managing 3D printers.": "Webové rozhranie na správu 3D tlačiarní.", "Web interface to manage devices running Tasmota firmware.": "Webové rozhranie pre správu zariadení bežiacich na Tasmota firmware.", "Web reverse proxy with an administration interface.": "Webový reverzný proxy server s administračným rozhraním.", "WebCord can be summarized as a pack of security and privacy hardenings, Discord features reimplementations, Electron / Chromium / Discord bugs workarounds, stylesheets, internal pages and wrapped https://discord.com page, designed to conform with ToS as much as it is possible (or hide the changes that might violate it from Discord's eyes).": "WebCord je klient obklopujúci stránku https://discord.com, ktorý zlepšuje bezpečnosť a súkromie, upravuje funkcie Discordu, obchádza chyby Electronu, Chromia a Discordu a pridáva štýly aj interné stránky. Snaží sa čo najviac dodržiavať podmienky používania alebo skryť pred Discordom úpravy, ktoré by ich mohli porušovať.", + "WebDAV": "WebDAV", + "WebDAV and dashboard password (leave empty to generate)": "Heslo pre WebDAV a panel (prázdne = generované)", + "WebDAV is available on container port 8080. Account linking is required before cloud file operations.": "WebDAV je dostupný na porte 8080 kontajnera. Pred prácou so súbormi v cloude je potrebné prepojiť účet.", "Webgrabplus is a multi-site incremental xmltv epg grabber. It collects tv-program guide data from selected tvguide sites for your favourite channels.": "WebGrab+Plus priebežne získava elektronický programový sprievodca (EPG) vo formáte XMLTV z viacerých webov. Zhromažďuje televízny program vybraných staníc z podporovaných stránok.", "Webservers & Proxies": "Webové servery a proxy", "Website": "Webová stránka", @@ -8262,6 +8312,7 @@ "Without a usable reset path, passthrough reliability is poor and VM": "Bez použiteľnej reset cesty je spoľahlivosť passthrough nízka a VM", "Wizard cancelled": "Sprievodca zrušený", "Wolfenstein: Blade of Agony is a story-driven WWII shooter inspired by Wolfenstein and Doom.": "Wolfenstein: Blade of Agony je príbehová strieľačka z druhej svetovej vojny inšpirovaná hrami Wolfenstein a Doom.", + "Word processing in your browser": "Spracovanie textu v prehliadači", "Workflow automation tool": "Nástroj na automatizáciu pracovného toku", "Working directory:": "Pracovný priečinok:", "Works with LVM, ZFS, and BTRFS storage types": "Funguje s typmi úložísk LVM, ZFS a BTRFS", @@ -8312,6 +8363,8 @@ "Your machine learning Env work with Jupyter Lab": "Prostredie strojového učenia s JupyterLab", "Your next YouTube media manager": "Váš ďalší manažér médií YouTube", "Your_spotify is a self-hosted application that tracks what you listen and offers you a dashboard to explore statistics about it! It's composed of a web server which polls the Spotify API every now and then and a web application on which you can explore your statistics.": "Your_spotify je aplikácia na vlastnom serveri, ktorá sleduje počúvanie hudby a zobrazuje štatistiky. Tvorí ju server pravidelne zisťujúci údaje cez Spotify API a webové rozhranie na ich prezeranie.", + "Z-Wave JS UI": "Z-Wave JS UI", + "Z-Wave JS UI admin login": "Prihlásenie správcu Z-Wave JS UI", "ZFS ARC config removed (kernel defaults will apply on reboot)": "Nastavenie ZFS ARC bolo odstránené (predvolené hodnoty kernelu sa použijú po reštarte)", "ZFS ARC maximum configured:": "Maximálna veľkosť ZFS ARC je nastavená na:", "ZFS ARC optimization completed": "Optimalizácia ZFS ARC je dokončená", @@ -8425,6 +8478,7 @@ "could not be compiled for kernel": "nepodarilo sa skompilovať pre jadro", "could not validate NVIDIA; exit code": "nemohol potvrdiť NVIDIA; kód výstupu", "cpuunits must be between 8 and 10000": "Hodnota cpuunits musí byť od 8 do 10000", + "curl -L -o /models/gemma-3-1b-it-Q4_K_M.gguf https://huggingface.co/ggml-org/gemma-3-1b-it-GGUF/resolve/main/gemma-3-1b-it-Q4_K_M.gguf": "curl -L -o /models/gemma-3-1b-it-Q4_K_M.gguf https://huggingface.co/ggml-org/gemma-3-1b-it-GGUF/resolve/main/gemma-3-1b-it-Q4_K_M.gguf", "custom": "vlastné", "custom dependency commands are not yet supported": "Vlastné príkazy závislosti ešte nie sú podporované", "custom path(s) saved.": "uložených vlastných ciest.", @@ -8631,6 +8685,7 @@ "kexec-tools is not installed or already removed.": "kexec-tools nie je nainštalovaný alebo už bol odstránený.", "layers": "vrstvy", "legacy .link file(s) to the ProxMenux-managed format": "staršie .link súbory do formátu spravovaného ProxMenuxom", + "llama.cpp shows every GGUF model saved in /models in its web UI and loads the one you choose.": "llama.cpp zobrazuje vo svojom webovom rozhraní všetky modely GGUF uložené v /models a načíta ten, ktorý si vyberiete.", "log2ram completely removed from system": "log2ram bol úplne odstránený zo systému", "main container of the application": "hlavný kontajner aplikácie", "make cloned mount tree read-only": "nastavenie klonovaného stromu pripojenia iba na čítanie", diff --git a/lang/sv.json b/lang/sv.json index d3f44455..784f0e00 100644 --- a/lang/sv.json +++ b/lang/sv.json @@ -184,6 +184,7 @@ "A tunneling daemon by Cloudflare that safely exposes your web servers into the internet.": "En tunneldemon av Cloudflare som säkert avslöjar dina webbservrar på internet.", "A unified email and collaboration server.": "En enhetlig server för e-post och samarbete.", "A unified platform for logs, metrics and traces.": "En enhetlig plattform för loggar, mätvärden och spårningar.", + "A validating, recursive DNS resolver for your private network.": "En validerande, rekursiv DNS-resolver för ditt privata nätverk.", "A versatile file conversion tool that supports multiple formats.": "Ett mångsidigt filkonverteringsverktyg som stöder flera format.", "A visual tool for exploring and documenting database schemas.": "Ett visuellt verktyg för att utforska och dokumentera databasscheman.", "A web GUI client of Project V which supports VMess, VLESS, SS, SSR, Trojan, Tuic and Juicity protocols": "En webb GUI klient av Project V som stöder VMess, VLESS, SS, SSR, Trojan, Tuic och Juicity protokoll", @@ -256,6 +257,7 @@ "Acceleration for llama.cpp": "Acceleration för llama.cpp", "Accept routes from other nodes?": "Acceptera rutter från andra noder?", "Accept this host monitoring profile?": "Acceptera denna värdövervakningsprofil?", + "Access Movistar and O2 Cloud from WebDAV clients and backup tools.": "Kom åt Movistar- och O2-molnet från WebDAV-klienter och säkerhetskopieringsverktyg.", "Access Scope:": "Åtkomstomfång:", "Access bridge": "Tillgång bridge", "Access bridge for Immich": "Tillgångsbro för Immich", @@ -410,6 +412,7 @@ "After the reboot, you will only be able to access the Proxmox host via:": "Efter omstarten kommer du bara att kunna komma åt Proxmox-värden via:", "After this LXC → VM switch, reboot the host so the new binding state is applied cleanly.": "Efter denna LXC → VM-switch, starta om värden så att det nya bindningsläget tillämpas korrekt.", "Agentless monitoring and observability platform.": "Agentlös plattform för övervakning och observerbarhet.", + "AirDC++ administrator": "AirDC++-administratör", "Airsonic Advanced web interface": "Airsonic Advanced webbgränssnitt", "Airsonic-advanced is a free, web-based media streamer, providing ubiquitious access to your music. Use it to share your music with friends, or to listen to your own music while at work. You can stream to multiple players simultaneously, for instance to one player in your kitchen and another in your living room.": "Airsonic-advanced är en gratis, webbaserad media streamer, som ger ubiquitious tillgång till din musik. Använd den för att dela din musik med vänner, eller för att lyssna på din egen musik på jobbet. Du kan strömma till flera spelare samtidigt, till exempel till en spelare i ditt kök och en annan i ditt vardagsrum.", "Aliases added to .bashrc": "Alias har lagts till i .bashrc", @@ -598,11 +601,15 @@ "Attention: Removing the subscription banner may cause issues in the web interface after a future update.": "Observera: Att ta bort prenumerationsbannern kan orsaka problem i webbgränssnittet efter en framtida uppdatering.", "Audacity is an easy-to-use, multi-track audio editor and recorder. Developed by a group of volunteers as open source.": "Audacity är en lättanvänd, multi-track ljudredigerare och inspelare. Utvecklad av en grupp frivilliga som öppen källkod.", "Audio device directory": "Ljudenhetskatalog", + "Audio recording and mixing in your browser": "Ljudinspelning och mixning i webbläsaren", "Audiobookshelf is a self-hosted audiobook and podcast server.": "Audiobookshelf är en själv värd ljudbok och podcast server.", "Audit completed. Press Enter to continue...": "Revision avslutad. Tryck på Enter för att fortsätta...", "Audit socket disabled or not required": "Granskningsuttaget inaktiverat eller inte nödvändigt", "Auth key is required.": "Auth-nyckel krävs.", "Auth:": "Auth:", + "Authelia Web UI": "Authelias webbgränssnitt", + "Authelia administrator": "Authelia administratör", + "Authelia session cookie domain for this portal (for example example.com)": "Authelia sessionscookiedomän för denna portal (till exempel example.com)", "Authentication": "Autentisering", "Authentication & Security": "Autentisering och säkerhet", "Authentication Error": "Autentiseringsfel", @@ -642,6 +649,7 @@ "Available PVE Version": "Tillgänglig PVE-version", "Available Shares:": "Tillgängliga andelar:", "Available action": "Tillgänglig åtgärd", + "Available applications: {count}": "Tillgängliga appar: {count}", "Available archives:": "Tillgängliga arkiv:", "Available disks detected.": "Tillgängliga diskar har upptäckts.", "Available host backups:": "Tillgängliga värdsäkerhetskopior:", @@ -873,6 +881,7 @@ "Cannot write the image archive to {storage}. Check storage access and mount status.": "Kan inte skriva bildarkivet till {storage}. Kontrollera lagringsåtkomst och monteringsstatus.", "Capabilities cannot be kept and all dropped at the same time": "Förmågor kan inte hållas och alla tappas samtidigt", "Catalog options": "Katalogalternativ", + "Catalog refresh unavailable; using the local catalog.": "Katalogen kunde inte uppdateras; den lokala katalogen används.", "Category": "Kategori", "Caution: Maximum mode generates more heat.": "Varning: Maximalt läge genererar mer värme.", "Central server for file and image backups.": "Central server för säkerhetskopiering av filer och bilder.", @@ -1159,6 +1168,7 @@ "Compressing": "Komprimerar", "Compression": "Kompression", "Compression Tools": "Kompressionsverktyg", + "Computer-aided design and drafting in your browser": "CAD och teknisk ritning i webbläsaren", "Concise output of logical volumes": "Kortfattad utdata av logiska volymer", "Concise output of physical volumes": "Kortfattad utdata av fysiska volymer", "Concise output of volume groups": "Kortfattad utdata av volymgrupper", @@ -1393,6 +1403,7 @@ "Controller + NVMe": "Styrenhet + NVMe", "Controller + NVMe assignment will be written now and become active after host reboot.": "Tilldelningen av styrenhet + NVMe skrivs nu och aktiveras efter omstart av värden.", "Controller + NVMe passthrough requires machine type q35. Skipping controller assignment.": "Styrenhets-/NVMe-passthrough kräver maskintypen q35. Hoppar över tilldelning av styrenheten.", + "Controller path on the Proxmox host; prefer /dev/serial/by-id/...": "Styrsökväg på Proxmox-värden;föredrar /dev/serial/by-id/...", "Controller/NVMe Conflict Policy": "Konfliktpolicy för styrenhet/NVMe", "Controller/NVMe already present in VM config": "Styrenhet/NVMe finns redan i VM-konfigurationen", "Controller/NVMe already present in VM config (%s)": "Styrenhet/NVMe finns redan i VM-konfigurationen (%s)", @@ -1927,6 +1938,7 @@ "Dgraph": "Dgraph", "Diff: current system vs backup (--- system +++ backup)": "Diff: nuvarande system vs säkerhetskopia (--- system +++ säkerhetskopia)", "Different host. Backup from:": "Annorlunda värd. Säkerhetskopiering från:", + "Direct Connect file sharing with a web interface": "Fildelning via Direct Connect med webbgränssnitt", "Direct conversion completed for container": "Direkt konvertering slutförd för container", "Directory Exists": "Katalog finns", "Directory Not Empty": "Katalog inte tom", @@ -3340,6 +3352,7 @@ "Initial Stirling-PDF account": "Första Stirling-PDF-kontot", "Initial Tandoor administrator": "Initial Tandoor administratör", "Initial Umami administrator": "Umamis första administratör", + "Initial Z-Wave JS UI admin password": "Initialt Z-Wave JS UI administratörslösenord", "Initial administrator created:": "Initial administratör skapad:", "Initial administrator user": "Initial administratör användare", "Initializing Borg repository if needed...": "Initierar Borg-arkivet om det behövs...", @@ -3525,6 +3538,7 @@ "Interface MTU": "Gränssnitt MTU", "Interface Names (persistent)": "Gränssnittsnamn (beständig)", "Interface Type": "Gränssnittstyp", + "Interface design and prototyping in your browser": "Gränssnittsdesign och prototyper i webbläsaren", "Interface names will return to default systemd behavior.": "Gränssnittsnamn kommer att återgå till standardsystembeteende.", "Interface type:": "Gränssnittstyp:", "Interfaces configured": "Gränssnitt konfigurerade", @@ -3740,6 +3754,7 @@ "Jupyter Lab (token only)": "Jupyter Lab (endast token)", "JupyterLab": "JupyterLab", "JupyterLab access token": "Åtkomsttoken för JupyterLab", + "KDE's twin-panel file manager in a browser-based desktop.": "KDE:s filhanterare med två paneler i ett webbläsarbaserat skrivbord.", "KDF:": "KDF:", "KROMA": "KROMA", "KVM MSR options added to /etc/modprobe.d/kvm.conf": "KVM MSR-alternativ har lagts till i /etc/modprobe.d/kvm.conf", @@ -3787,12 +3802,14 @@ "KiCad - A Cross Platform and Open Source Electronics Design Automation Suite.": "KiCad - En Cross Platform och Open Source Electronics Design Automation Suite.", "Kimai has no default account. Enter the container with: pct enter {main_vmid}": "Kimai har inget standardkonto. Öppna ett skal i containern med: pct enter {main_vmid}", "Kimai is a professional grade time-tracking application, free and open-source.": "Kimai är en professionell tidsspårningsapplikation, fri och öppen källkod.", + "KitchenOwl": "KitchenOwl", "Kometa is a powerful tool designed to give you complete control over your media libraries. With Kometa, you can take your customization to the next level, with granular control over metadata, collections, overlays, and much more.": "Kometa är ett kraftfullt verktyg för att ge dig fullständig kontroll över dina mediebibliotek. Med Kometa kan du ta din anpassning till nästa nivå, med granulär kontroll över metadata, samlingar, överlagringar och mycket mer.", "Kometa reads its configuration from /config/config.yml and the container only ships /config/config.yml.template. Copy the template to config.yml, fill in the required Plex and TMDb connections, then restart the container.": "Kometa läser sin konfiguration från /config/config.yml och behållaren endast fartyg /config/config.yml.template. Kopiera mallen till config.yml, fyll i required Plex och TMDb-anslutningar och starta sedan om behållaren.", "Komga is a media server for your comics, mangas, BDs, magazines and eBooks.": "Komga är en mediaserver för dina serier, mangas, BD, tidskrifter och e-böcker.", "Kopia Web UI": "Kopia-webbgränssnitt", "Kopia Web UI login": "Inloggning till Kopias webbgränssnitt", "Krita is a professional FREE and open source painting program. It is made by artists that want to see affordable art tools for everyone.": "Krita är ett professionellt gratis och öppen källkod målning program. Den är gjord av konstnärer som vill se prisvärda konstverktyg för alla.", + "Krusader Web UI": "Krusaders webbgränssnitt", "LAN access to the kept containers (stack configuration incomplete):": "LAN tillgång till de bevarade behållarna (stack konfiguration ofullständig):", "LAN address applied to the application URLs": "LAN-adress tillämpad på ansökningsadresserna", "LLDAP": "LLDAP", @@ -4020,6 +4037,7 @@ "Manage OCI stack": "Hantera OCI stack", "Manage PBS encryption keyfile": "Hantera PBS-krypteringsnyckelfil", "Manage Secure Gateway": "Hantera Secure Gateway", + "Manage a Z-Wave network from a web dashboard and WebSocket gateway.": "Hantera ett Z-Wave-nätverk från en webbinstrumentpanel och WebSocket-gateway.", "Manage and inspect VM disk images": "Hantera och inspektera VM-diskavbildningar", "Manage custom backup paths": "Hantera anpassade säkerhetskopieringsvägar", "Manage custom paths (add / remove your folders)": "Hantera anpassade sökvägar (lägg till / ta bort dina mappar)", @@ -4029,6 +4047,7 @@ "Manage local backup target": "Hantera lokalt mål för säkerhetskopiering", "Manage private image registries.\nSaved credentials let ProxMenux access your private images.": "Hantera privata bildregister.\nSparade referenser låter ProxMenux komma åt dina privata bilder.", "Manage registry accounts as root on the Proxmox node.": "Hantera registerkonton som root på Proxmox-noden.", + "Manage teams of AI agents from a web dashboard.": "Hantera team av AI-agenter från en webbpanel.", "Managed disks must have backup enabled and a valid size": "Hanterade diskar måste ha backup aktiverad och en giltig storlek", "Managed public route include is missing": "Konfigurationsfilen för de hanterade publika rutterna saknas", "Managed public route installation failed": "Det gick inte att konfigurera de hanterade publika rutterna", @@ -4134,6 +4153,7 @@ "Monitoring": "Övervakning", "Monitoring & Analytics": "Övervakning och analys", "Most common cause: the archive is corrupted (interrupted write, partial copy, or storage issue).": "Vanligaste orsaken: arkivet är skadat (avbruten skrivning, delvis kopiering eller lagringsproblem).", + "Motion graphics and compositing in your browser": "Rörlig grafik och komposition i webbläsaren", "Mount Added Successfully:": "Montering har lagts till:", "Mount CIFS share:": "Montera CIFS-andel:", "Mount Configuration Summary:": "Sammanfattning av monteringskonfiguration:", @@ -4293,6 +4313,7 @@ "NVIDIA (CUDA; official GPU image)": "NVIDIA (CUDA, officiell GPU-bild)", "NVIDIA (NVDEC/CUDA)": "NVIDIA (NVDEC/CUDA)", "NVIDIA (NVENC/NVDEC)": "NVIDIA (NVENC/NVDEC)", + "NVIDIA (Selkies rendering and NVENC)": "NVIDIA (Selkies-rendering och NVENC)", "NVIDIA Actions": "NVIDIA-åtgärder", "NVIDIA CPU hiding already configured": "NVIDIA CPU-döljning har redan konfigurerats", "NVIDIA Container Toolkit": "NVIDIA Container Toolkit", @@ -4434,6 +4455,8 @@ "New Search": "Ny sökning", "New VM:": "Ny VM:", "New Virtual Machine": "Ny virtuell maskin", + "New applications ({count})": "Nya program ({count})", + "New applications since your last visit: {count}": "Nya appar sedan ditt senaste besök: {count}", "New backup job": "Nytt säkerhetskopia jobb", "New backups on this host will be unencrypted until a new keyfile is set up.": "Nya säkerhetskopior på denna värd kommer att vara okrypterade tills en ny nyckelfil ställs in.", "New image compatible:": "Ny bildkompatibel:", @@ -4896,6 +4919,8 @@ "Open the page of an application in the menu": "Öppna en applikations sida i menyn", "Open the web address and create the first account: the daemon registers by itself and starts the first scan.": "Öppna webbadressen och skapa det första kontot: daemonen registrerar sig själv och startar den första skanningen.", "Open the web address and create the password of the akadmin user in the first-run setup.": "Öppna webbadressen och skapa lösenordet för akadmin-användaren i den första körningen.", + "Open the web address and follow KitchenOwl’s onboarding to create the first account; no default account is configured.": "Öppna webbadressen och följ KitchenOwls introduktion för att skapa det första kontot;inget standardkonto är konfigurerat.", + "Open the web dashboard and link your Movistar/O2 account using its SMS flow.": "Öppna webbpanelen och koppla ditt Movistar/O2-konto med SMS-verifieringen.", "Open-source AI-powered coding assistant": "Open-source AI-driven kodningsassistent", "Open-source UI for building and debugging multi-agent and RAG applications": "Open-source UI för att bygga och debugga multi-agent- och RAG-applikationer", "Open-source in-memory data store.": "Öppen datalagring i minnet.", @@ -5018,6 +5043,7 @@ "Packages:": "Paket:", "Packaging OVA file...": "Förpackning av OVA-fil...", "Packing installer archive...": "Packar installationsarkiv...", + "Page layout and publishing in your browser": "Sidlayout och publicering i webbläsaren", "PairDrop is a sublime alternative to AirDrop that works on all platforms. Send images, documents or text via peer to peer connection to devices in the same local network/Wi-Fi or to paired devices.": "PairDrop är ett sublimt alternativ till AirDrop som fungerar på alla plattformar. Skicka bilder, dokument eller text via peer till peer-anslutning till enheter i samma lokala nätverk / Wi-Fi eller till parade enheter.", "Paperless configuration cancelled": "Papperslös konfiguration inställd", "Paperless-ngx WebUI": "Paperless-ngx WebUI", @@ -5036,6 +5062,7 @@ "Pass /dev/video12 to the LXC": "Pass /dev/video12 till LXC", "Pass a host device to the LXC": "Passera en värdenhet till LXC", "Pass the UPS to the LXC": "Skicka UPS:en till LXC", + "Pass through a Z-Wave USB or serial controller": "Passera genom en Z-Wave USB eller seriell styrenhet", "Passing NVIDIA does not enable acceleration inside the application. The host needs NVIDIA Container Toolkit and a compatible image. Continue?": "Att passera NVIDIA möjliggör inte acceleration inuti applikationen. Värden behöver NVIDIA Container Toolkit och en kompatibel bild. Fortsätta?", "Passkey-based identity provider for self-hosted apps.": "Identitetsleverantör med passkeys för självhostade appar.", "Passphrase used to unlock the imported keyfile (leave blank if the keyfile is unencrypted / kdf=none):": "Lösenfras som används för att låsa upp den importerade nyckelfilen (lämna tomt om nyckelfilen är okrypterad / kdf=ingen):", @@ -5143,6 +5170,8 @@ "Personal finance management application": "Personlig ekonomi management ansökan", "Personal media server for browsing and playing a local video library, with direct play and optional modules.": "Personlig medieserver för att bläddra bland och spela upp ett lokalt videobibliotek, med direktuppspelning och valfria moduler.", "Photo and video library with optional GPU transcoding and machine learning": "Foto- och videobibliotek med valfri GPU-överkodning och maskininlärning", + "Photo editing in your browser": "Fotoredigering i webbläsaren", + "Photo library and RAW development in your browser": "Fotobibliotek och RAW-framkallning i webbläsaren", "PhotoPrism": "PhotoPrism", "Photofield": "Photofield", "Phpmyadmin is a free software tool written in PHP, intended to handle the administration of MySQL over the Web. phpMyAdmin supports a wide range of operations on MySQL and MariaDB.": "Phpmyadmin är ett gratis mjukvaruverktyg skrivet i PHP, avsedd att hantera administrationen av MySQL över webben. phpMyAdmin stöder ett brett utbud av operations på MySQL och MariaDB.", @@ -5235,6 +5264,7 @@ "Preparing the machine learning container for the new choice...": "Förbereder maskininlärningsbehållaren för det nya valet...", "Preparing the recreation...": "Förbereda rekreationen...", "Preparing the update...": "Förbereda uppdateringen...", + "Presentations and slide decks in your browser": "Presentationer och bildspel i webbläsaren", "Preserving logs to /var/log.hdd before unmounting...": "Sparar loggar till /var/log.hdd innan avmontering...", "Press 'q' to exit": "Tryck på 'q' för att avsluta", "Press Ctrl+C to stop the server and return to menu.": "Tryck på Ctrl+C för att stoppa servern och återgå till menyn.", @@ -5395,6 +5425,7 @@ "Proxmox web interface: Datacenter > Storage > Add > ZFS": "Proxmox webbgränssnitt: Datacenter > Lagring > Lägg till > ZFS", "Proxmox web interface: Datacenter > Storage > Add > iSCSI": "Proxmox webbgränssnitt: Datacenter > Lagring > Lägg till > iSCSI", "Proxy address to reach the API (optional)": "Proxyadress för att nå API:t (valfritt)", + "Public HTTPS URL used to open this Authelia portal (for example https://auth.example.com)": "Offentlig HTTPS-URL som används för att öppna denna Authelia-portal (till exempel https://auth.example.com)", "Public UDP port clients connect to": "Offentliga UDP-portklienter ansluter till", "Public UDP port peers connect to": "Offentliga UDP-portpeers ansluter till", "Public URL of phpMyAdmin when it is served behind a reverse proxy": "Public URL av phpMyAdmin när den serveras bakom en omvänd proxy", @@ -5455,6 +5486,7 @@ "Re-running pre-check after repairs...": "Kör förkontroll igen efter reparationer...", "Reachable": "Kan nåbar", "Read an oci-compose from a file of this host": "Läs en oci-compose från en fil på denna värd", + "Read and edit PDF documents in your browser": "Läs och redigera PDF-dokument i webbläsaren", "Read its Compose file from a file of this host": "Läs dess Compose-fil från en fil av denna värd", "Read manga through a self-hosted server and web UI.": "Läs manga via en självhostad server och ett webbgränssnitt.", "Read the link with pct console CTID on the Proxmox host, or from the Console panel of the container in the Proxmox web interface, then open the https://playit.gg/claim/ address it shows in a browser and sign in to playit.gg. Ctrl+a q leaves pct console.": "Läs länken med pct console CTID på Proxmox-värden, eller från Console-panelen i behållaren i Proxmox-webbgränssnittet, öppna sedan https://playit.gg/claim/adressen visas i en webbläsare och logga in för att spelait.gg. Ctrl+a q lämnar pct konsol.", @@ -5560,6 +5592,7 @@ "Recursive and authoritative DNS server.": "Rekursiv och auktoritativ DNS-server.", "Redmine": "Redmine", "Refresh APT index and verify repositories:": "Uppdatera APT-index och verifiera förråd:", + "Refresh catalog": "Uppdatera katalogen", "Refresh the configured APT repositories and review the PBS package upgrade? Repository files will be preserved.": "Uppdatera de konfigurerade APT-paketförråden och granska PBS-paketuppgraderingen? Paketförrådens filer bevaras.", "Refresh the index from the image sources": "Uppdatera indexet från avbildskällorna", "Refresh token of the connector, from the Twingate admin console": "Uppdateringstoken för kontakten, från Twingate-administratörskonsolen", @@ -5567,6 +5600,7 @@ "Refresh your browser to see changes (server restart may be required)": "Uppdatera din webbläsare för att se ändringar (serverns omstart kan krävas)", "Refreshing apt cache...": "Uppdaterar apt cache...", "Refreshing the NVIDIA runtime...": "Uppfriskande NVIDIA runtime...", + "Refreshing the application catalog...": "Uppdaterar appkatalogen...", "Refusing an unexpected rootfs path:": "Vägrar en oväntad rootfs väg:", "Regenerate the catalog templates": "Generera om katalogmallarna", "Regenerate the template of one application": "Generera om mallen för en applikation", @@ -6258,6 +6292,7 @@ "Self-hosted file sync and sharing.": "Självhostad filsynkronisering och delning.", "Self-hosted file toolkit for images, video, audio, PDFs, and files": "Självvärd filverktyg för bilder, video, ljud, PDF-filer och filer", "Self-hosted forms and workflows.": "Självhostade formulär och arbetsflöden.", + "Self-hosted grocery list and recipe manager": "Self-hosted inköpslista och recept manager", "Self-hosted household plant-care tracker with reminders, photos, and multiple user accounts.": "Självhostad skötselapp för krukväxter med påminnelser, foton och flera användarkonton.", "Self-hosted image-hosting service with authenticated uploads, image processing, public links, and local storage.": "Självhostad bildtjänst med autentiserade uppladdningar, bildbehandling, offentliga länkar och lokal lagring.", "Self-hosted internet archiving solution": "Självhäftad internetarkiveringslösning", @@ -6452,6 +6487,7 @@ "Simple and easy to use DDNS": "Enkelt och enkelt att använda DDNS", "Simple self-hosted file sharing.": "Enkel självhostad fildelning.", "Single GPU Warning": "Varning för enkel GPU", + "Single sign-on and multi-factor authentication": "Enkel inloggning och multifaktorautentisering", "Single target found — selected automatically:": "Enstaka mål hittades — väljs automatiskt:", "Single-node log storage and query UI.": "Gränssnitt för logglagring och loggsökning på en enda nod.", "Single-node observability database for metrics, logs and traces.": "Observabilitetsdatabas med en nod för mätvärden, loggar och spår.", @@ -6520,7 +6556,9 @@ "Specific subnet (enter manually)": "Specifikt subnät (skriv in manuellt)", "Speedtest Tracker web interface": "Speedtest Tracker webbgränssnitt", "Speedtest-tracker is a self-hosted internet performance tracking application that runs speedtest checks against Ookla's Speedtest service.": "Speedtest-tracker är en självhäftad internetprestationsspårningsapplikation som kör snabbaste kontroller mot Ooklas Speedtest-tjänst.", + "Spoolman web interface": "Spoolman webbgränssnitt", "Spotube is an open source, cross-platform Spotify client compatible across multiple platforms utilizing Spotify's data API and YouTube, Piped.video or JioSaavn as an audio source, eliminating the need for Spotify Premium": "Spotube är en öppen källkod, plattform Spotify klient kompatibel över flera plattformar som använder Spotifys data API och YouTube, Piped. video eller JioSaavn som ljudkälla, vilket eliminerar behovet av Spotify Premium", + "Spreadsheets in your browser": "Kalkylblad i webbläsaren", "Sshwifty": "Sshwifty", "Sshwifty web access (password only)": "Sshwifty-webbåtkomst (endast lösenord)", "Stable (main branch)": "Stabil (huvudgren)", @@ -7417,6 +7455,7 @@ "Then bind-mount to container": "Bind-montera sedan till behållaren", "Then change the VM display to none (vga: none) when the guest is stable.": "Ändra sedan VM-skärmen till ingen (vga: ingen) när gästen är stabil.", "Then change the VM display to none (vga: none) when the system is stable.": "Ändra sedan VM-skärmen till ingen (vga: ingen) när systemet är stabilt.", + "Then restart the container so the new model appears in the list.": "Starta sedan om containern så att den nya modellen visas i listan.", "Then run this option again:": "Kör sedan det här alternativet igen:", "Then update /etc/fstab on the host with the same options.": "Then update /etc/fstab on the host with the same options.", "There are extra disks or bind mounts outside the journal; the rootfs is not replaced": "Det finns extra diskar eller binda fästen utanför tidskriften; rootfs ersätts inte", @@ -7464,6 +7503,7 @@ "This container does not have apt-get. NFS client installation only supports Debian/Ubuntu containers.": "Den här behållaren har inte apt-get. NFS-klientinstallation stöder endast Debian/Ubuntu-behållare.", "This container does not have apt-get. Samba client installation only supports Debian/Ubuntu containers.": "Den här behållaren har inte apt-get. Samba-klientinstallationen stöder endast Debian/Ubuntu-behållare.", "This container has no GPU configured. Coral TPU works best alongside hardware video decoding (Quick Sync, VA-API, NVENC) for apps like Frigate.": "Den här behållaren har ingen GPU konfigurerad. Coral TPU fungerar bäst tillsammans med hårdvaruvideoavkodning (Quick Sync, VA-API, NVENC) för appar som Frigate.", + "This container is managed by high availability. Remove ct:{vmid} from HA (Datacenter → HA, or ha-manager remove ct:{vmid}), repeat the operation and add it back to HA with the same settings.": "Den här containern hanteras av hög tillgänglighet. Ta bort ct:{vmid} från HA (Datacenter → HA, eller ha-manager remove ct:{vmid}), upprepa åtgärden och lägg sedan tillbaka den i HA med samma inställningar.", "This container is not a member of a multi-container application": "Den här behållaren är inte medlem i ett program för flera behållare", "This container is not a registered OCI instance.": "Denna behållare är inte en registrerad OCI-instans.", "This container was restored or came back from another host after its record on this host was written. Open this menu again to recover it; nothing was changed.": "Den här behållaren återställdes eller kom tillbaka från en annan värd efter att dess post på denna värd skrevs. Öppna den här menyn igen för att återställa den; ingenting ändrades.", @@ -7582,6 +7622,7 @@ "Tip: You can install the QEMU Guest Agent inside the VM with:": "Tips: Du kan installera QEMU Guest Agent inuti den virtuella datorn med:", "Tip: zfs set acltype=posixacl xattr=sa / enables full ACL support.": "Tips: zfs set acltype=posixacl xattr=sa / möjliggör fullt ACL-stöd.", "Tmpfs size in MiB for": "Tmpfs storlek i MiB för", + "To add a model, open the console of the container in Proxmox and download it into /models, for example:": "Om du vill lägga till en modell öppnar du containerns konsol i Proxmox och laddar ned den till /models, till exempel:", "To allow LXC write access, change the NFS export on the server to include:": "För att tillåta LXC-skrivåtkomst, ändra NFS-exporten på servern så att den inkluderar:", "To apply it to the current shell now, run:": "För att använda den i den aktuella shellsessionen nu, kör:", "To assign VFs to VMs or LXCs, edit the configuration manually via the Proxmox web interface. The Physical Function will remain bound to the native driver.": "För att tilldela VF:er till virtuella datorer eller LXC:er, redigera konfigurationen manuellt via Proxmox webbgränssnitt. Den fysiska funktionen förblir bunden till den ursprungliga drivrutinen.", @@ -7621,6 +7662,7 @@ "Total routes": "Totalt antal rutter", "Total size:": "Total storlek:", "Toxiproxy": "Toxiproxy", + "Track 3D printer filament spools and usage.": "Spåra 3D-skrivarens filamentspolar och användning.", "Track movies, television, anime and games you want to enjoy.": "Håll koll på filmer, tv-serier, anime och spel som du vill se eller spela.", "Track subscriptions and recurring expenses.": "Håll koll på prenumerationer och återkommande utgifter.", "Track your music listening history on your own server.": "Följ din musikhistorik på din egen server.", @@ -7855,6 +7897,7 @@ "Update the whole stack?": "Uppdatera hela stacken?", "Update, modify or recreate one installed OCI instance": "Uppdatera, ändra eller återskapa en installerad OCI-instans", "Updated": "Uppdaterad", + "Updated recipes since your last visit: {count}": "Uppdaterade recept sedan ditt senaste besök: {count}", "Updated sharedfiles group to GID: 101000": "Uppdaterad sharedfiles-grupp till GID: 101000", "Updated stack checked": "Uppdaterad stack kontrollerad", "Updated:": "Uppdaterad:", @@ -8060,6 +8103,7 @@ "Variables": "Variables", "Variables the installation asks for:": "Variabler som installationen begär:", "Vaultwarden Web Vault": "Vaultwarden Web Vault", + "Vector illustration in your browser": "Vektorillustration i webbläsaren", "Vector search engine with a built-in dashboard.": "Vektorsökmotor med inbyggd kontrollpanel.", "Verbose pool status": "Utförlig poolstatus", "Verification": "Kontroll", @@ -8090,6 +8134,7 @@ "Versions shown belong to maintained NVIDIA branches that list your GPU PCI ID and are new enough to build against the running kernel. DKMS compilation is the final validation. The recommended version keeps the current branch, or uses the NVIDIA Production Branch on a fresh install.": "Versioner som visas tillhör underhållna NVIDIA-grenar som listar ditt GPU PCI-ID och är tillräckligt nya för att bygga mot den körande kärnan. DKMS kompilering är den slutliga valideringen. Den rekommenderade versionen behåller den aktuella grenen eller använder NVIDIA Production Branch på en nyinstallation.", "VictoriaLogs": "VictoriaLogs", "Video acceleration and object detection are independent choices; the installer does not write camera or detector YAML.": "Videoacceleration och objektdetektering är oberoende val; installatören skriver inte kamera eller detektor YAML.", + "Video editing in your browser": "Videoredigering i webbläsaren", "Video transcoding acceleration": "Videoöverskridande acceleration", "Video transcoding profile not implemented:": "Videokodningsprofilen har inte implementerats:", "View CIFS Mounts (pvesm + fstab)": "Visa CIFS-fästen (pvesm + fstab)", @@ -8199,12 +8244,17 @@ "Web access": "Web access", "Web address of the AdGuard Home that receives the settings (e.g. http://192.168.1.3)": "Webbadress för AdGuard Home som tar emot inställningarna (t.ex. http://192.168.1.3)", "Web address of the main AdGuard Home, whose settings are copied (e.g. http://192.168.1.2)": "Webbadressen för AdGuard Home, vars inställningar kopieras (t.ex. http://192.168.1.2)", + "Web dashboard": "Webbpanel", + "Web dashboard and WebDAV": "Webbpanel och WebDAV", "Web interface": "Webbgränssnitt", "Web interface for Borg Backup repositories and jobs.": "Webbgränssnitt för Borg Backup-arkiv och jobb.", "Web interface for managing 3D printers.": "Webbgränssnitt för hantering av 3D-skrivare.", "Web interface to manage devices running Tasmota firmware.": "Webbgränssnitt för att hantera enheter som kör Tasmota firmware.", "Web reverse proxy with an administration interface.": "Omvänd proxy för webben med administrationsgränssnitt.", "WebCord can be summarized as a pack of security and privacy hardenings, Discord features reimplementations, Electron / Chromium / Discord bugs workarounds, stylesheets, internal pages and wrapped https://discord.com page, designed to conform with ToS as much as it is possible (or hide the changes that might violate it from Discord's eyes).": "WebCord kan sammanfattas som ett paket av säkerhets- och sekretesshärdningar, Discord-funktioner reimplementations, Electron / Chromium / Discord bugs workarounds, stylesheets, interna sidor och wrapped https://discord.com sida, utformad för att överensstämma med ToS så mycket som möjligt (eller dölja de förändringar som kan bryta mot det från Discords ögon).", + "WebDAV": "WebDAV", + "WebDAV and dashboard password (leave empty to generate)": "Lösenord för WebDAV och panelen (tom = genererat)", + "WebDAV is available on container port 8080. Account linking is required before cloud file operations.": "WebDAV är tillgängligt på containerns port 8080. Kontot måste kopplas innan du arbetar med filerna i molnet.", "Webgrabplus is a multi-site incremental xmltv epg grabber. It collects tv-program guide data from selected tvguide sites for your favourite channels.": "Webgrabplus är en multi-site inkrementell xmltv epg grabber. Det samlar in tv-programguide data från utvalda tvguide webbplatser för dina favoritkanaler.", "Webservers & Proxies": "Webbservrar och proxyservrar", "Website": "Webbplats", @@ -8262,6 +8312,7 @@ "Without a usable reset path, passthrough reliability is poor and VM": "Utan en användbar återställningsväg är tillförlitligheten för genomkoppling dålig och VM", "Wizard cancelled": "Wizard avbruten", "Wolfenstein: Blade of Agony is a story-driven WWII shooter inspired by Wolfenstein and Doom.": "Wolfenstein: Blade of Agony är en story-driven WWII skytt inspirerad av Wolfenstein och Doom.", + "Word processing in your browser": "Ordbehandling i webbläsaren", "Workflow automation tool": "Workflow automation verktyg", "Working directory:": "Arbetskatalog:", "Works with LVM, ZFS, and BTRFS storage types": "Fungerar med LVM, ZFS och BTRFS lagringstyper", @@ -8312,6 +8363,8 @@ "Your machine learning Env work with Jupyter Lab": "Din maskininlärning Env arbetar med Jupyter Lab", "Your next YouTube media manager": "Din nästa YouTube Media Manager", "Your_spotify is a self-hosted application that tracks what you listen and offers you a dashboard to explore statistics about it! It's composed of a web server which polls the Spotify API every now and then and a web application on which you can explore your statistics.": "Your_spotify är en egen värd program som spårar vad du lyssnar och erbjuder dig en instrumentpanel för att utforska statistik om det! Den består av en webbserver som undersöker Spotify API då och då och en webbapplikation där du kan utforska din statistik.", + "Z-Wave JS UI": "Z-Wave JS UI", + "Z-Wave JS UI admin login": "Administratörsinloggning för Z-Wave JS UI", "ZFS ARC config removed (kernel defaults will apply on reboot)": "ZFS ARC-konfigurationen togs bort (kärnans standardinställningar gäller vid omstart)", "ZFS ARC maximum configured:": "ZFS ARC maximalt konfigurerat:", "ZFS ARC optimization completed": "ZFS ARC-optimering slutförd", @@ -8425,6 +8478,7 @@ "could not be compiled for kernel": "kunde inte kompileras för kärnan", "could not validate NVIDIA; exit code": "kan inte validera NVIDIA; exitkod", "cpuunits must be between 8 and 10000": "cpuunits måste vara mellan 8 och 10000", + "curl -L -o /models/gemma-3-1b-it-Q4_K_M.gguf https://huggingface.co/ggml-org/gemma-3-1b-it-GGUF/resolve/main/gemma-3-1b-it-Q4_K_M.gguf": "curl -L -o /models/gemma-3-1b-it-Q4_K_M.gguf https://huggingface.co/ggml-org/gemma-3-1b-it-GGUF/resolve/main/gemma-3-1b-it-Q4_K_M.gguf", "custom": "anpassade", "custom dependency commands are not yet supported": "Anpassade beroendekommandon stöds ännu inte", "custom path(s) saved.": "anpassade sökvägar har sparats.", @@ -8631,6 +8685,7 @@ "kexec-tools is not installed or already removed.": "kexec-tools är inte installerat eller redan borttaget.", "layers": "Lager", "legacy .link file(s) to the ProxMenux-managed format": "äldre .link-fil(er) till det ProxMenux-hanterade formatet", + "llama.cpp shows every GGUF model saved in /models in its web UI and loads the one you choose.": "llama.cpp visar alla GGUF-modeller som sparats i /models i sitt webbgränssnitt och läser in den du väljer.", "log2ram completely removed from system": "log2ram helt borttagen från systemet", "main container of the application": "applikationens huvudbehållare", "make cloned mount tree read-only": "gör det klonade monteringsträdet skrivskyddat", diff --git a/oci/CATALOG-REFRESH.md b/oci/CATALOG-REFRESH.md new file mode 100644 index 00000000..eaa693c5 --- /dev/null +++ b/oci/CATALOG-REFRESH.md @@ -0,0 +1,69 @@ +# Automatic catalog refresh + +Opening OCI Manager displays its local catalog immediately and starts a separate +worker. The menu shows the available total, additions since the last displayed +catalog, and changed recipes. Returning from a search or installation does not +start another download. A running wizard keeps the catalog it started with. +The menu also offers **Refresh catalog** to check again manually or retry after +a connection failure. Selecting it during a refresh does not start a second worker. +Manual refresh bypasses the conditional check and verifies the complete manifest, +allowing it to repair damaged cached files even when the upstream revision has not +changed. A repaired snapshot gets a separate immutable directory so existing +readers retain their original snapshot until they return to the menu. Each +Catalog reader holds an OS file-lock lease. Every worker run, including unchanged +checks and failed downloads, prunes snapshots that are neither a channel's active +snapshot nor leased by an open reader. Reader exit or a crash releases its lease +automatically. Snapshot selection, publication and pruning share a short local +lock; opening a menu never waits for the worker's network lock. + +The worker checks the latest commit affecting `oci/catalog` in the official +MacRimi/ProxMenux repository. GitHub's ETag avoids repeated transfers. When the +revision changes, its immutable Git tree supplies the file manifest. Only changed +JSON files are downloaded from that commit; Git blob hashes and sizes are checked. +The complete snapshot, including overlays, categories, volume policy and +verification records, is validated before publishing its pointer atomically. +Runtime Python or shell files are never downloaded by catalog refresh. + +State and snapshots live in `/usr/local/share/proxmenux/oci/catalog-cache`, outside +the engine replaced by the normal ProxMenux updater. The bundled catalog remains +the fallback. A network failure, incomplete manifest, invalid recipe, different +template schema or unsupported catalog engine API leaves the previous catalog +active. The private `CHANNEL-result.json` records the diagnostic. +The revision, snapshot and ETag are committed together in one atomic pointer; +failure to publish it cannot acknowledge an update that was not applied. + +The channel follows `install_branch`, an active beta program or `beta_version.txt`; +otherwise it is `main`. A host can explicitly select `main` or `develop` with +`/usr/local/share/proxmenux/oci/catalog-source.json`: + +```json +{"channel": "develop", "local_apps": ["my-local-preview"]} +``` + +`local_apps` optionally keeps explicitly named bundled preview recipes available +when they are absent upstream. These are local overrides, not automatic retention +of withdrawn upstream applications. An upstream recipe with the same ID wins. + +The first visit uses the bundled/current catalog as its baseline. Additions are +compared by application ID, not net growth, and only visible installable entries +are counted, using the same rule as the existing menu. A downloaded catalog is +acknowledged only after the menu displays it. Exiting during a download therefore +preserves its news for the next visit. The new-app count remains visible throughout +the current visit and becomes zero on the next visit after acknowledgement. + +Downloaded recipes are also checked against the top-level `installer_profile` +keys used by the engine's bundled apps, curated recipes and overlays. A new app +using an unknown key is silently excluded; an existing app retains its previous +compatible, fully composed recipe and its matching settings. Hidden entries do +not contribute to the available/new counters, and retaining an old recipe does +not count it as an update. When a ProxMenux update adds support for that key, the +next background check re-evaluates the catalog even at the same remote revision. +This is a conservative key-presence gate, not validation of new values or nested +options of an already known key. Changes in meaning still require `engine_api`. + +Catalog maintainers must increase `catalog/compatibility.json`'s `engine_api` when +recipes require incompatible runtime capabilities. The consumer currently +supports API 1. Changes to the template schema also require a ProxMenux engine +update. Catalog delivery alone neither changes installed CTs nor applies a new +recipe to an existing deployment; the established management actions remain in +charge of those changes. diff --git a/oci/README.md b/oci/README.md index 031576bc..78a008c6 100644 --- a/oci/README.md +++ b/oci/README.md @@ -1117,3 +1117,9 @@ have not been tested. No custom supervisor or image entrypoint is introduced. DeepSeek OCR remains deferred at the user's request: registry inspection on 2026-09-14 found only `v2.2.0` for both IceWhaleTech images and no `latest` tag. No fixed-version exception or deployment has been introduced. + +## Automatic catalog refresh + +OCI Manager opens with its local catalog and checks for new applications and recipes +in the background. **Refresh catalog** repeats the check manually. See +[Automatic catalog refresh](CATALOG-REFRESH.md) for caching, channels and compatibility. diff --git a/oci/VALIDATION.md b/oci/VALIDATION.md index 1680d3d3..ad2a6f97 100644 --- a/oci/VALIDATION.md +++ b/oci/VALIDATION.md @@ -2,7 +2,7 @@ -52 of 532 applications in the OCI catalog have been tested for real: 49 in the ProxMenux lab and 3 by the community. The OCI installer shows them as verified, with a ✓ in the lists. +52 of 554 applications in the OCI catalog have been tested for real: 49 in the ProxMenux lab and 3 by the community. The OCI installer shows them as verified, with a ✓ in the lists. Each test describes the scenario that was run and names the image it ran on. It does not cover every possible configuration of the application, and a newer image has not been tested until someone reports it. diff --git a/oci/catalog/apps/airdcpp-webclient.json b/oci/catalog/apps/airdcpp-webclient.json new file mode 100644 index 00000000..3081c3d9 --- /dev/null +++ b/oci/catalog/apps/airdcpp-webclient.json @@ -0,0 +1,585 @@ +{ + "schema_version": "0.5.0", + "kind": "proxmenux.oci-template", + "id": "image-airdcpp-webclient", + "status": "generated-unvalidated", + "catalog_ui": { + "title": { + "en_US": "AirDC++ Web Client", + "es_ES": "AirDC++ Web Client" + }, + "tagline": { + "en_US": "Direct Connect file sharing with a web interface", + "es_ES": "Intercambio de archivos Direct Connect con interfaz web" + }, + "description": { + "en_US": "AirDC++ Web Client using the community-maintained gangefors image. Configuration, downloads and shared content use separate persistent paths.", + "es_ES": "AirDC++ Web Client con la imagen comunitaria de gangefors. La configuración, las descargas y los archivos compartidos usan rutas persistentes separadas." + }, + "category": "downloads", + "category_label": "Files & Downloads", + "author": "gangefors", + "developer": "AirDC++", + "icon": "https://raw.githubusercontent.com/airdcpp-web/airdcpp-webui/eeeea5025a5e720f38dac53ced8e002940f00a74/resources/images/logo.svg", + "thumbnail": null, + "screenshots": [], + "architectures": [ + "amd64" + ], + "launch": { + "scheme": "http", + "port": 5600, + "path": "/" + }, + "website": "https://airdcpp-web.github.io/", + "documentation": "https://github.com/gangefors/docker-airdcpp-webclient", + "repository": "https://github.com/gangefors/docker-airdcpp-webclient", + "tips": [ + "The upstream initial login is admin / password. Change it in Settings > System > Users before allowing access beyond a trusted LAN.", + "Configuration uses /.airdcpp on a managed Proxmox volume included in backups. /Downloads and /Share can use managed volumes or explicitly selected host directories.", + "Files under /Share are shared by AirDC++. Select the contents deliberately; router forwarding and hub configuration are separate administrator actions.", + "PUID and PGID default to 1000 and must be at least 101. The original entrypoint creates the application account; volume ownership follows these values.", + "Open HTTP port 5600 on the dedicated LXC address. HTTPS port 5601 uses the upstream self-signed certificate; use a trusted reverse proxy certificate for remote web access.", + "Incoming Direct Connect traffic uses TCP and UDP 21248 and TLS TCP 21249. ProxMenux keeps those native ports and does not configure your router." + ], + "mini_changelog": [], + "display_version": null, + "updated_at": "2026-10-25" + }, + "source": { + "provider": "gangefors", + "repository": "https://github.com/gangefors/docker-airdcpp-webclient", + "revision": "37f2e939c152fb2c0158bc83bc76cc972aed965e", + "image_repository_url": "https://hub.docker.com/r/gangefors/airdcpp-webclient", + "readme_pushed_at": "2026-10-25T11:51:53Z", + "compose_sha256": "4ce8aec542cfc3e7f5ca142e309a41f04aca444bd1724ca1741c96acb5c3d5bf", + "generated_at": "2026-11-03T11:47:04+00:00", + "default_branch": "master", + "readme_raw_url": "https://raw.githubusercontent.com/gangefors/docker-airdcpp-webclient/37f2e939c152fb2c0158bc83bc76cc972aed965e/docker-compose.yml" + }, + "container_contract": { + "service_name": "airdcpp", + "container_name": "airdcpp", + "image": { + "reference": "docker.io/gangefors/airdcpp-webclient:latest", + "registry": "docker.io", + "repository": "docker.io/gangefors/airdcpp-webclient", + "tag": "latest", + "digest": null, + "pull_policy": "resolve-written-reference-to-architecture-digest-at-install" + }, + "environment": [ + { + "name": "PUID", + "example": "1000", + "required": true, + "sensitive": false, + "source": "upstream-compose" + }, + { + "name": "PGID", + "example": "1000", + "required": true, + "sensitive": false, + "source": "upstream-compose" + }, + { + "name": "UMASK", + "example": "0022", + "required": true, + "sensitive": false, + "source": "upstream-compose" + } + ], + "volumes": [ + { + "id": "config", + "container_path": "/.airdcpp", + "compose_source_example": "./config", + "read_only": false, + "required": true, + "installation_choice": [ + "managed-volume" + ], + "default": "managed-volume", + "managed_volume": { + "backup": true, + "default_size_gb": 16 + } + }, + { + "id": "downloads", + "container_path": "/Downloads", + "compose_source_example": "./downloads", + "read_only": false, + "required": true, + "installation_choice": [ + "managed-volume", + "host-bind" + ], + "default": "managed-volume", + "managed_volume": { + "backup": true, + "default_size_gb": 32 + } + }, + { + "id": "share", + "container_path": "/Share", + "compose_source_example": "./share", + "read_only": false, + "required": true, + "installation_choice": [ + "managed-volume", + "host-bind" + ], + "default": "managed-volume", + "managed_volume": { + "backup": true, + "default_size_gb": 32 + } + } + ], + "ports": [ + { + "container_port": 5600, + "published_example": 5600, + "protocol": "tcp", + "required": true, + "proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat" + }, + { + "container_port": 5601, + "published_example": 5601, + "protocol": "tcp", + "required": true, + "proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat" + }, + { + "container_port": 21248, + "published_example": 21248, + "protocol": "tcp", + "required": true, + "proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat" + }, + { + "container_port": 21248, + "published_example": 21248, + "protocol": "udp", + "required": true, + "proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat" + }, + { + "container_port": 21249, + "published_example": 21249, + "protocol": "tcp", + "required": true, + "proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat" + } + ], + "related_services": [], + "restart": "unless-stopped", + "stop_grace_period": null, + "original_compose": "name: airdcpp-webclient\nservices:\n airdcpp:\n image: docker.io/gangefors/airdcpp-webclient:latest\n restart: unless-stopped\n environment:\n PUID: '1000'\n PGID: '1000'\n UMASK: '0022'\n ports:\n - 5600:5600\n - 5601:5601\n - 21248:21248/tcp\n - 21248:21248/udp\n - 21249:21249/tcp\n volumes:\n - ./config:/.airdcpp\n - ./downloads:/Downloads\n - ./share:/Share\n" + }, + "compose_stack": { + "project_name": "airdcpp-webclient", + "deployment_model": "one-native-oci-lxc-per-compose-service", + "user_experience": "single-application-install", + "main_service": "airdcpp", + "service_count": 1, + "services": [ + { + "name": "airdcpp", + "image": "docker.io/gangefors/airdcpp-webclient:latest", + "is_main": true, + "role": "frontend", + "vmid_offset": 0, + "depends_on": [], + "frontend_network": true, + "private_network": false, + "compose": { + "image": "docker.io/gangefors/airdcpp-webclient:latest", + "restart": "unless-stopped", + "environment": { + "PUID": "1000", + "PGID": "1000", + "UMASK": "0022" + }, + "ports": [ + "5600:5600", + "5601:5601", + "21248:21248/tcp", + "21248:21248/udp", + "21249:21249/tcp" + ], + "volumes": [ + "./config:/.airdcpp", + "./downloads:/Downloads", + "./share:/Share" + ] + } + } + ], + "top_level": { + "name": "airdcpp-webclient" + }, + "networking": { + "frontend": "selected-proxmox-bridge", + "private_required": false, + "private_creation": "automatic-create-if-missing", + "private_address_allocation": "automatic-static-address-per-service", + "service_discovery": "private-addresses-with-compose-service-host-aliases", + "dependency_external_access": "disabled-unless-service-publishes-ports", + "prompt_user_for_private_network": false + }, + "storage": [ + { + "id": "airdcpp-volume-0", + "service": "airdcpp", + "container_path": "/.airdcpp", + "mode": "managed-volume", + "user_selectable": false, + "backup": true, + "shared_with_other_lxc": false, + "source_path": null, + "source_path_prompt": null + }, + { + "id": "airdcpp-volume-1", + "service": "airdcpp", + "container_path": "/Downloads", + "mode": "managed-volume", + "user_selectable": false, + "backup": true, + "shared_with_other_lxc": false, + "source_path": null, + "source_path_prompt": null + }, + { + "id": "airdcpp-volume-2", + "service": "airdcpp", + "container_path": "/Share", + "mode": "managed-volume", + "user_selectable": false, + "backup": true, + "shared_with_other_lxc": false, + "source_path": null, + "source_path_prompt": null + } + ], + "orchestration": { + "reserve_vmids_atomically": 1, + "start_order": [ + "airdcpp" + ], + "stop_order": [ + "airdcpp" + ], + "dependency_readiness": "compose-healthcheck-then-port-or-process-fallback", + "rollback_on_failure": "remove-new-rootfs-preserve-created-persistent-volumes" + }, + "installer_inputs": { + "prompted": [ + "stack_name", + "base_vmid", + "rootfs_storage", + "persistent_data_destinations", + "frontend_bridge", + "frontend_ipv4_mode" + ], + "automatic": [ + "dependent_vmids", + "private_bridge", + "private_subnet", + "private_service_addresses", + "compose_service_aliases", + "generated_secrets", + "dependency_start_and_stop_order" + ], + "generated_secrets": [] + } + }, + "first_run": { + "endpoints": [ + { + "label": "Web UI", + "scheme": "http", + "port": 5600, + "path": "/", + "source": "https://github.com/gangefors/docker-airdcpp-webclient" + } + ], + "credentials": [ + { + "label": "AirDC++ administrator", + "type": "static-default", + "username": "admin", + "password": "password", + "change_required": true, + "source": "https://github.com/gangefors/docker-airdcpp-webclient#run-the-application" + } + ] + }, + "proxmox": { + "runtime": "native-oci-lxc", + "technology_status": "proxmox-technology-preview", + "defaults": { + "unprivileged": true, + "ostype": "auto-from-image", + "cores": 2, + "memory_mb": 1024, + "swap_mb": 512, + "rootfs_size_gb": 8, + "rootfs_storage": "local-lvm", + "volume_storage": "local-lvm", + "template_storage": "local", + "bridge": "vmbr0", + "ipv4": "dhcp", + "firewall": true, + "host_managed_network": true, + "onboot": false, + "features": [ + "nesting=1" + ], + "shutdown_timeout_seconds": 30 + }, + "image_metadata_policy": { + "entrypoint": "import-from-oci-image", + "cmd": "import-from-oci-image", + "environment": "import-image-env-then-apply-compose-overrides", + "user": "import-from-oci-image", + "working_dir": "import-from-oci-image", + "stop_signal": "import-from-oci-image" + }, + "adaptations": [ + { + "id": "imported-compose-source", + "upstream_behavior": "The source definition deploys the complete Docker Compose application model.", + "native_lxc_behavior": "The source model is preserved and remains blocked until every service option has a reviewed native Proxmox mapping.", + "reason": "Catalog import must not imply runtime compatibility.", + "behavioral_impact": "No automatic installation before review.", + "validation": "pending-per-application" + }, + { + "id": "rolling-latest-image", + "upstream_behavior": "A discovered Compose may pin a release tag or digest.", + "native_lxc_behavior": "ProxMenux selects the same image repository with the latest tag for catalog installations.", + "reason": "The automatic catalog intentionally offers rolling latest images; pinned versions belong to the future manual installer.", + "behavioral_impact": "The installed release can be newer than the discovered Compose revision.", + "validation": "pending-per-application" + }, + { + "id": "dedicated-lxc-network", + "upstream_behavior": "Docker publishes selected container ports on the Docker host.", + "native_lxc_behavior": "A reviewed native application will listen on its original container ports at a dedicated LXC address.", + "reason": "A native LXC has its own address and does not require Docker port NAT.", + "behavioral_impact": "Published ports are metadata; ProxMenux URLs use the matching container target port.", + "validation": "pending-per-application" + }, + { + "id": "compose-shm-size", + "upstream_behavior": "Compose sets the size of the container /dev/shm tmpfs.", + "native_lxc_behavior": "ProxMenux mounts a native LXC tmpfs at /dev/shm with the same requested capacity.", + "reason": "The OCI image runs directly as an LXC and therefore needs the equivalent Proxmox mount entry.", + "behavioral_impact": "None expected.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-command", + "upstream_behavior": "Compose replaces the image Cmd while retaining its Entrypoint.", + "native_lxc_behavior": "ProxMenux reads the official OCI Entrypoint and combines it with the Compose command as the native LXC init command.", + "reason": "Proxmox stores the effective OCI process as one entrypoint string.", + "behavioral_impact": "None expected.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-privileged-mode", + "upstream_behavior": "Compose selects whether the container runs in privileged mode.", + "native_lxc_behavior": "ProxMenux keeps the LXC unprivileged by default and exposes the broad Compose privilege request only as an explicit compatibility option.", + "reason": "A Compose privilege request is source metadata, not proof that the image technically requires a privileged LXC.", + "behavioral_impact": "A privileged LXC has weaker isolation from the Proxmox host.", + "validation": "native-equivalent" + }, + { + "id": "compose-process-runtime", + "upstream_behavior": "Compose can replace Entrypoint, User and WorkingDir and request an init process or interactive terminal.", + "native_lxc_behavior": "ProxMenux applies the process overrides through native LXC init directives; lxc-init provides PID 1 supervision and the CT console provides terminal access.", + "reason": "The OCI process must start with the same identity, command and working directory without Docker.", + "behavioral_impact": "Compose stdin_open and tty become access through the Proxmox LXC console.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-healthcheck", + "upstream_behavior": "Docker periodically executes the declared container healthcheck.", + "native_lxc_behavior": "For a single-service LXC, ProxMenux translates HTTP localhost checks into a mandatory first-start service check.", + "reason": "Proxmox has no persistent Docker health state, while the installer still must detect a failed first boot.", + "behavioral_impact": "The check runs during installation rather than continuously after installation.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-cpu-priority", + "upstream_behavior": "Docker cpu_shares sets a relative scheduling weight with 1024 as its neutral value.", + "native_lxc_behavior": "ProxMenux converts the relative weight to Proxmox cpuunits with 100 as its neutral value and lets the user review it.", + "reason": "Both settings express relative CPU priority on different scales.", + "behavioral_impact": "Rounding and Proxmox minimum limits can slightly change very low weights.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-resource-limits", + "upstream_behavior": "mem_limit sets the memory ceiling; ulimits sets process soft/hard resource limits.", + "native_lxc_behavior": "mem_limit supplies the editable Proxmox memory default, rounded up to MiB; ulimits maps to lxc.prlimit with -1 represented as unlimited.", + "reason": "Use native Proxmox memory and LXC prlimits, without a wrapper or changing the host kernel configuration.", + "behavioral_impact": "User-selected memory overrides Compose. Swap is a separate choice. Kernel restrictions still apply; nproc counts processes by real UID, not by container.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-network-identity", + "upstream_behavior": "Compose can set hostname, MAC address, extra hosts and attach a service to Docker networks.", + "native_lxc_behavior": "ProxMenux applies hostname and MAC to net0, writes additional host aliases into the LXC and uses its dedicated bridge connection for single-service networks.", + "reason": "A dedicated LXC has its own network namespace and does not need a Docker bridge per service.", + "behavioral_impact": "host-gateway resolves to the IPv4 address of the selected Proxmox bridge.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-network-mode", + "upstream_behavior": "Docker host mode removes Docker network isolation; bridge and default use a Docker-managed network.", + "native_lxc_behavior": "The OCI process uses the dedicated LXC network namespace directly, so host, bridge and default all listen on the LXC address without Docker NAT.", + "reason": "The LXC is the application host and already has its own address and port namespace.", + "behavioral_impact": "host means the LXC host, never the Proxmox host; this preserves Proxmox network isolation.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-capabilities-and-sysctls", + "upstream_behavior": "Compose can add Linux capabilities and set kernel parameters in the container network namespace.", + "native_lxc_behavior": "ProxMenux validates requested capabilities against the native LXC capability set and writes namespaced network settings as lxc.sysctl directives.", + "reason": "A native OCI-LXC already starts with the namespaced capability set; lxc.cap.keep would incorrectly discard unrelated required capabilities.", + "behavioral_impact": "Host-global capabilities such as SYS_MODULE remain blocked until their host prerequisite is explicitly adapted.", + "validation": "not-requested-by-compose" + }, + { + "id": "docker-engine-metadata", + "upstream_behavior": "Compose labels annotate Docker objects and the json-file logging driver rotates Docker-managed logs.", + "native_lxc_behavior": "Labels remain source metadata; Docker json-file settings are not applied because the OCI process runs directly under LXC.", + "reason": "There is no Docker object or Docker json-file log behind a native OCI-LXC application.", + "behavioral_impact": "Docker-only label consumers and Docker log-driver rotation do not exist in the native deployment.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-device-passthrough", + "upstream_behavior": "Compose passes host character devices or requests an NVIDIA runtime GPU.", + "native_lxc_behavior": "ProxMenux converts recognized device declarations to native Proxmox dev resources; NVIDIA profiles also inject compatible host driver libraries read-only.", + "reason": "Native OCI-LXC does not execute Docker device or NVIDIA runtime hooks.", + "behavioral_impact": "Hardware is exposed only after explicit user confirmation and host-path validation.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-host-ipc", + "upstream_behavior": "ipc: host shares the Docker host IPC namespace, commonly to avoid Docker's small default shared-memory allocation.", + "native_lxc_behavior": "The application keeps the LXC IPC namespace and receives a configurable 1 GiB /dev/shm instead of sharing Proxmox host IPC.", + "reason": "Processes in a single native LXC already share one IPC namespace; retaining isolation is safer than exposing host IPC.", + "behavioral_impact": "The application cannot exchange IPC objects with processes on the Proxmox host.", + "validation": "not-requested-by-compose" + } + ], + "installer_profile": { + "volume_preparations": [ + { + "container_path": "/.airdcpp", + "remove_lost_found": true, + "owner_strategy": "mapped-application-user", + "only_when_mount_type": "managed-volume" + }, + { + "container_path": "/Downloads", + "remove_lost_found": true, + "owner_strategy": "mapped-application-user", + "only_when_mount_type": "managed-volume" + }, + { + "container_path": "/Share", + "remove_lost_found": true, + "owner_strategy": "mapped-application-user", + "only_when_mount_type": "managed-volume" + } + ], + "startup_healthcheck": { + "scheme": "http", + "port": 5600, + "path": "/index.html", + "timeout_seconds": 180, + "request_timeout_seconds": 5, + "stability_seconds": 4, + "verify_tls": true + } + } + }, + "compatibility": { + "automatic_install_candidate": true, + "validated": false, + "supported_compose_keys": [ + "cap_add", + "command", + "container_name", + "cpu_shares", + "deploy", + "devices", + "entrypoint", + "environment", + "extra_hosts", + "group_add", + "healthcheck", + "hostname", + "image", + "init", + "ipc", + "labels", + "logging", + "mac_address", + "mem_limit", + "network_mode", + "networks", + "ports", + "privileged", + "restart", + "runtime", + "security_opt", + "shm_size", + "stdin_open", + "stop_grace_period", + "stop_signal", + "sysctls", + "tty", + "ulimits", + "user", + "volumes", + "working_dir" + ], + "untranslated_blockers": [], + "policy": "Community image and single-LXC contract reviewed against the publisher Compose and entrypoint; Linux amd64 confirmed in the registry manifest. Runtime lifecycle checks remain pending." + }, + "validation": { + "schema": "passed-at-generation", + "clean_install": "pending", + "service_health": "pending", + "restart_persistence": "pending", + "backup_restore": "pending", + "update_preserves_data": "pending", + "authenticated_login": "pending", + "advanced_install": "pending", + "host_bind_storage": "pending", + "managed_storage": "pending", + "removal": "pending" + }, + "lifecycle": { + "update_strategy": "resolve-latest-image-then-apply-reviewed-native-lxc-update", + "registry_state": { + "resolved_architecture": null, + "resolved_digest": null, + "image_version_label": null, + "image_created": null + }, + "change_detection": "compare-compose-sha256-and-resolved-latest-image-digest", + "automatic_unattended_updates": false + } +} diff --git a/oci/catalog/apps/authelia.json b/oci/catalog/apps/authelia.json new file mode 100644 index 00000000..adcb37c6 --- /dev/null +++ b/oci/catalog/apps/authelia.json @@ -0,0 +1,453 @@ +{ + "schema_version": "0.4.0", + "kind": "proxmenux.oci-template", + "id": "image-authelia", + "status": "generated-unvalidated", + "catalog_ui": { + "title": { + "en_US": "Authelia", + "es_ES": "Authelia" + }, + "tagline": { + "en_US": "Single sign-on and multi-factor authentication", + "es_ES": "Inicio de sesión único y autenticación multifactor" + }, + "description": { + "en_US": "An open-source authentication and authorization server for protecting web applications. This single-image profile provides a local SQLite database and file notifications; configure your domain, reverse proxy and access rules before protecting other services.", + "es_ES": "Servidor de autenticación y autorización de código abierto para proteger aplicaciones web. Este perfil de una sola imagen incluye una base de datos SQLite local y notificaciones en archivo; configura el dominio, el proxy inverso y las reglas de acceso antes de proteger otros servicios." + }, + "category": "security", + "category_label": "Authentication & Security", + "author": "Authelia", + "developer": "Authelia", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/authelia.webp", + "thumbnail": null, + "screenshots": [], + "architectures": [ + "amd64", + "arm64" + ], + "launch": { + "scheme": "http", + "port": 9091, + "path": "/" + }, + "website": "https://www.authelia.com/", + "documentation": "https://www.authelia.com/integration/deployment/docker/", + "repository": "https://github.com/authelia/authelia", + "tips": [ + "The installer creates a unique admin password and stores only its Argon2id hash in /config/users_database.yml. The password is shown in the installation result.", + "Authelia requires an HTTPS portal URL and a cookie domain matching that hostname. If you use Nginx Proxy Manager, follow the official integration guide and configure trusted proxies and forward-auth separately.", + "The default notifier writes messages to /config/notification.txt. Configure SMTP in /config/configuration.yml if you want email delivery.", + "The initial access-control policy is one_factor. Add explicit access-control rules before attaching Authelia to protected proxy hosts." + ], + "mini_changelog": [], + "display_version": "4.39.28", + "updated_at": "2026-09-17" + }, + "source": { + "provider": "authelia", + "repository": "https://github.com/authelia/authelia", + "default_branch": "master", + "revision": "8da42b2348b0351c6284a2940a9c5eec3d4cd3fd", + "readme_raw_url": "https://raw.githubusercontent.com/authelia/authelia/v4.39.28/examples/compose/lite/compose.yml", + "readme_pushed_at": "2026-09-17T12:13:30Z", + "compose_sha256": "848ce0f70e502981a050ac200d9d1882978a6c3b0f9f0dfcc6d52ce66ca37fb5", + "generated_at": "2026-11-03T09:10:57+00:00", + "image_repository_url": "https://hub.docker.com/r/authelia/authelia" + }, + "container_contract": { + "service_name": "authelia", + "container_name": "authelia", + "image": { + "reference": "docker.io/authelia/authelia:4.39.28", + "registry": "docker.io", + "repository": "docker.io/authelia/authelia", + "tag": "4.39.28", + "digest": null, + "pull_policy": "resolve-selected-tag-to-architecture-digest-at-install" + }, + "environment": [ + { + "name": "PROXMENUX_ADMIN_PASSWORD", + "example": "", + "required": true, + "sensitive": true, + "source": "proxmenux-first-start" + }, + { + "name": "AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET", + "example": "", + "required": true, + "sensitive": true, + "source": "proxmenux-generated" + }, + { + "name": "AUTHELIA_SESSION_SECRET", + "example": "", + "required": true, + "sensitive": true, + "source": "proxmenux-generated" + }, + { + "name": "AUTHELIA_STORAGE_ENCRYPTION_KEY", + "example": "", + "required": true, + "sensitive": true, + "source": "proxmenux-generated" + }, + { + "name": "X_AUTHELIA_CONFIG_FILTERS", + "example": "template", + "required": true, + "sensitive": false, + "source": "authelia-container" + }, + { + "name": "PROXMENUX_AUTHELIA_COOKIE_DOMAIN", + "example": "", + "prompt": "Authelia session cookie domain for this portal (for example example.com)", + "required": true, + "sensitive": false, + "source": "administrator" + }, + { + "name": "PROXMENUX_AUTHELIA_URL", + "example": "", + "prompt": "Public HTTPS URL used to open this Authelia portal (for example https://auth.example.com)", + "required": true, + "sensitive": false, + "source": "administrator" + } + ], + "volumes": [ + { + "id": "config", + "container_path": "/config", + "compose_source_example": "./authelia", + "read_only": false, + "required": true, + "installation_choice": [ + "managed-volume" + ], + "default": "managed-volume", + "managed_volume": { + "backup": true, + "default_size_gb": 4 + } + } + ], + "ports": [ + { + "container_port": 9091, + "published_example": 9091, + "protocol": "tcp", + "required": true, + "proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat" + } + ], + "related_services": [], + "restart": "unless-stopped", + "stop_grace_period": null, + "original_compose": "services:\n authelia:\n entrypoint:\n - /bin/sh\n - /usr/local/bin/proxmenux-authelia-bootstrap\n environment:\n AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET: ${GENERATED_RESET_PASSWORD_JWT_SECRET}\n AUTHELIA_SESSION_SECRET: ${GENERATED_SESSION_SECRET}\n AUTHELIA_STORAGE_ENCRYPTION_KEY: ${GENERATED_STORAGE_ENCRYPTION_KEY}\n PROXMENUX_ADMIN_PASSWORD: ${GENERATED_ADMIN_PASSWORD}\n PROXMENUX_AUTHELIA_COOKIE_DOMAIN: ${AUTHELIA_COOKIE_DOMAIN}\n PROXMENUX_AUTHELIA_URL: ${AUTHELIA_URL}\n X_AUTHELIA_CONFIG_FILTERS: template\n image: docker.io/authelia/authelia:4.39.28\n ports:\n - 9091:9091\n restart: unless-stopped\n volumes:\n - ./authelia:/config\n" + }, + "first_run": { + "endpoints": [ + { + "label": "Authelia Web UI", + "scheme": "http", + "port": 9091, + "path": "/", + "source": "official-image" + } + ], + "credentials": [ + { + "label": "Authelia administrator", + "type": "configured-or-installer-generated", + "username": "admin", + "password": null, + "password_environment": "PROXMENUX_ADMIN_PASSWORD", + "change_required": false, + "source": "Generated on first start and stored as an Argon2id hash in the persistent users file." + } + ] + }, + "proxmox": { + "runtime": "native-oci-lxc", + "technology_status": "proxmox-technology-preview", + "defaults": { + "unprivileged": true, + "ostype": "auto-from-image", + "cores": 2, + "memory_mb": 512, + "swap_mb": 512, + "rootfs_size_gb": 8, + "rootfs_storage": "local-lvm", + "volume_storage": "local-lvm", + "template_storage": "local", + "bridge": "vmbr0", + "ipv4": "dhcp", + "firewall": true, + "host_managed_network": true, + "onboot": false, + "features": [ + "nesting=1" + ], + "shutdown_timeout_seconds": 60 + }, + "image_metadata_policy": { + "entrypoint": "import-from-oci-image", + "cmd": "import-from-oci-image", + "environment": "import-image-env-then-apply-compose-overrides", + "user": "import-from-oci-image", + "working_dir": "import-from-oci-image", + "stop_signal": "import-from-oci-image" + }, + "installer_profile": { + "runtime": { + "compose_entrypoint": [ + "/bin/sh", + "/usr/local/bin/proxmenux-authelia-bootstrap" + ] + }, + "generated_sensitive_environment": { + "PROXMENUX_ADMIN_PASSWORD": { + "strategy": "token-hex", + "bytes": 24, + "prompt": false + }, + "AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET": { + "strategy": "token-hex", + "bytes": 32, + "prompt": false + }, + "AUTHELIA_SESSION_SECRET": { + "strategy": "token-hex", + "bytes": 32, + "prompt": false + }, + "AUTHELIA_STORAGE_ENCRYPTION_KEY": { + "strategy": "token-hex", + "bytes": 32, + "prompt": false + } + }, + "minimum_secret_length": { + "PROXMENUX_ADMIN_PASSWORD": 24, + "AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET": 32, + "AUTHELIA_SESSION_SECRET": 32, + "AUTHELIA_STORAGE_ENCRYPTION_KEY": 32 + }, + "generated_files": [ + { + "container_path": "/config/configuration.yml", + "mode": "0600", + "owner": "mapped-root", + "only_if_missing": true, + "content": "---\nserver:\n address: 'tcp://:9091'\n\nlog:\n level: 'info'\n\ntotp:\n issuer: 'Authelia'\n\nidentity_validation:\n reset_password:\n jwt_secret: 'configured-by-environment'\n\nauthentication_backend:\n file:\n path: '/config/users_database.yml'\n watch: true\n\naccess_control:\n default_policy: 'one_factor'\n\nsession:\n secret: 'configured-by-environment'\n cookies:\n - name: 'authelia_session'\n domain: '{{ env \"PROXMENUX_AUTHELIA_COOKIE_DOMAIN\" }}'\n authelia_url: '{{ env \"PROXMENUX_AUTHELIA_URL\" }}'\n expiration: '1 hour'\n inactivity: '5 minutes'\n\nstorage:\n encryption_key: 'configured-by-environment'\n local:\n path: '/config/db.sqlite3'\n\nnotifier:\n filesystem:\n filename: '/config/notification.txt'\n" + }, + { + "container_path": "/usr/local/bin/proxmenux-authelia-bootstrap", + "mode": "0700", + "owner": "mapped-root", + "only_if_missing": false, + "content": "#!/bin/sh\nset -eu\numask 077\n\nusers_file=/config/users_database.yml\nif [ ! -s \"$users_file\" ]; then\n : \"${PROXMENUX_ADMIN_PASSWORD:?ProxMenux did not provide an initial administrator password}\"\n mkdir -p /config\n hash_output=$(authelia crypto hash generate argon2 --password \"$PROXMENUX_ADMIN_PASSWORD\" --no-confirm)\n password_hash=$(printf '%s\\n' \"$hash_output\" | sed -n 's/^Digest: //p')\n case \"$password_hash\" in\n '$argon2id$'*) ;;\n *) printf '%s\\n' 'Authelia did not return an Argon2id password hash' >&2; exit 1 ;;\n esac\n temporary_file=\"${users_file}.tmp.$$\"\n cat >\"$temporary_file\" < System > Users before allowing access beyond a trusted LAN.", + "Configuration uses /.airdcpp on a managed Proxmox volume included in backups. /Downloads and /Share can use managed volumes or explicitly selected host directories.", + "Files under /Share are shared by AirDC++. Select the contents deliberately; router forwarding and hub configuration are separate administrator actions.", + "PUID and PGID default to 1000 and must be at least 101. The original entrypoint creates the application account; volume ownership follows these values.", + "Open HTTP port 5600 on the dedicated LXC address. HTTPS port 5601 uses the upstream self-signed certificate; use a trusted reverse proxy certificate for remote web access.", + "Incoming Direct Connect traffic uses TCP and UDP 21248 and TLS TCP 21249. ProxMenux keeps those native ports and does not configure your router." + ], + "mini_changelog": [], + "display_version": null, + "updated_at": "2026-10-25" + }, + "source": { + "provider": "gangefors", + "repository": "https://github.com/gangefors/docker-airdcpp-webclient", + "revision": "37f2e939c152fb2c0158bc83bc76cc972aed965e", + "image_repository_url": "https://hub.docker.com/r/gangefors/airdcpp-webclient", + "readme_pushed_at": "2026-10-25T11:51:53Z", + "compose_sha256": "4ce8aec542cfc3e7f5ca142e309a41f04aca444bd1724ca1741c96acb5c3d5bf", + "generated_at": "2026-11-03T11:47:04+00:00", + "default_branch": "master", + "readme_raw_url": "https://raw.githubusercontent.com/gangefors/docker-airdcpp-webclient/37f2e939c152fb2c0158bc83bc76cc972aed965e/docker-compose.yml" + }, + "container_contract": { + "service_name": "airdcpp", + "container_name": "airdcpp", + "image": { + "reference": "docker.io/gangefors/airdcpp-webclient:latest", + "registry": "docker.io", + "repository": "docker.io/gangefors/airdcpp-webclient", + "tag": "latest", + "digest": null, + "pull_policy": "resolve-written-reference-to-architecture-digest-at-install" + }, + "environment": [ + { + "name": "PUID", + "example": "1000", + "required": true, + "sensitive": false, + "source": "upstream-compose" + }, + { + "name": "PGID", + "example": "1000", + "required": true, + "sensitive": false, + "source": "upstream-compose" + }, + { + "name": "UMASK", + "example": "0022", + "required": true, + "sensitive": false, + "source": "upstream-compose" + } + ], + "volumes": [ + { + "id": "config", + "container_path": "/.airdcpp", + "compose_source_example": "./config", + "read_only": false, + "required": true, + "installation_choice": [ + "managed-volume" + ], + "default": "managed-volume", + "managed_volume": { + "backup": true, + "default_size_gb": 16 + } + }, + { + "id": "downloads", + "container_path": "/Downloads", + "compose_source_example": "./downloads", + "read_only": false, + "required": true, + "installation_choice": [ + "managed-volume", + "host-bind" + ], + "default": "managed-volume", + "managed_volume": { + "backup": true, + "default_size_gb": 32 + } + }, + { + "id": "share", + "container_path": "/Share", + "compose_source_example": "./share", + "read_only": false, + "required": true, + "installation_choice": [ + "managed-volume", + "host-bind" + ], + "default": "managed-volume", + "managed_volume": { + "backup": true, + "default_size_gb": 32 + } + } + ], + "ports": [ + { + "container_port": 5600, + "published_example": 5600, + "protocol": "tcp", + "required": true, + "proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat" + }, + { + "container_port": 5601, + "published_example": 5601, + "protocol": "tcp", + "required": true, + "proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat" + }, + { + "container_port": 21248, + "published_example": 21248, + "protocol": "tcp", + "required": true, + "proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat" + }, + { + "container_port": 21248, + "published_example": 21248, + "protocol": "udp", + "required": true, + "proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat" + }, + { + "container_port": 21249, + "published_example": 21249, + "protocol": "tcp", + "required": true, + "proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat" + } + ], + "related_services": [], + "restart": "unless-stopped", + "stop_grace_period": null, + "original_compose": "name: airdcpp-webclient\nservices:\n airdcpp:\n image: docker.io/gangefors/airdcpp-webclient:latest\n restart: unless-stopped\n environment:\n PUID: '1000'\n PGID: '1000'\n UMASK: '0022'\n ports:\n - 5600:5600\n - 5601:5601\n - 21248:21248/tcp\n - 21248:21248/udp\n - 21249:21249/tcp\n volumes:\n - ./config:/.airdcpp\n - ./downloads:/Downloads\n - ./share:/Share\n" + }, + "compose_stack": { + "project_name": "airdcpp-webclient", + "deployment_model": "one-native-oci-lxc-per-compose-service", + "user_experience": "single-application-install", + "main_service": "airdcpp", + "service_count": 1, + "services": [ + { + "name": "airdcpp", + "image": "docker.io/gangefors/airdcpp-webclient:latest", + "is_main": true, + "role": "frontend", + "vmid_offset": 0, + "depends_on": [], + "frontend_network": true, + "private_network": false, + "compose": { + "image": "docker.io/gangefors/airdcpp-webclient:latest", + "restart": "unless-stopped", + "environment": { + "PUID": "1000", + "PGID": "1000", + "UMASK": "0022" + }, + "ports": [ + "5600:5600", + "5601:5601", + "21248:21248/tcp", + "21248:21248/udp", + "21249:21249/tcp" + ], + "volumes": [ + "./config:/.airdcpp", + "./downloads:/Downloads", + "./share:/Share" + ] + } + } + ], + "top_level": { + "name": "airdcpp-webclient" + }, + "networking": { + "frontend": "selected-proxmox-bridge", + "private_required": false, + "private_creation": "automatic-create-if-missing", + "private_address_allocation": "automatic-static-address-per-service", + "service_discovery": "private-addresses-with-compose-service-host-aliases", + "dependency_external_access": "disabled-unless-service-publishes-ports", + "prompt_user_for_private_network": false + }, + "storage": [ + { + "id": "airdcpp-volume-0", + "service": "airdcpp", + "container_path": "/.airdcpp", + "mode": "managed-volume", + "user_selectable": false, + "backup": true, + "shared_with_other_lxc": false, + "source_path": null, + "source_path_prompt": null + }, + { + "id": "airdcpp-volume-1", + "service": "airdcpp", + "container_path": "/Downloads", + "mode": "managed-volume", + "user_selectable": false, + "backup": true, + "shared_with_other_lxc": false, + "source_path": null, + "source_path_prompt": null + }, + { + "id": "airdcpp-volume-2", + "service": "airdcpp", + "container_path": "/Share", + "mode": "managed-volume", + "user_selectable": false, + "backup": true, + "shared_with_other_lxc": false, + "source_path": null, + "source_path_prompt": null + } + ], + "orchestration": { + "reserve_vmids_atomically": 1, + "start_order": [ + "airdcpp" + ], + "stop_order": [ + "airdcpp" + ], + "dependency_readiness": "compose-healthcheck-then-port-or-process-fallback", + "rollback_on_failure": "remove-new-rootfs-preserve-created-persistent-volumes" + }, + "installer_inputs": { + "prompted": [ + "stack_name", + "base_vmid", + "rootfs_storage", + "persistent_data_destinations", + "frontend_bridge", + "frontend_ipv4_mode" + ], + "automatic": [ + "dependent_vmids", + "private_bridge", + "private_subnet", + "private_service_addresses", + "compose_service_aliases", + "generated_secrets", + "dependency_start_and_stop_order" + ], + "generated_secrets": [] + } + }, + "first_run": { + "endpoints": [ + { + "label": "Web UI", + "scheme": "http", + "port": 5600, + "path": "/", + "source": "https://github.com/gangefors/docker-airdcpp-webclient" + } + ], + "credentials": [ + { + "label": "AirDC++ administrator", + "type": "static-default", + "username": "admin", + "password": "password", + "change_required": true, + "source": "https://github.com/gangefors/docker-airdcpp-webclient#run-the-application" + } + ] + }, + "proxmox": { + "runtime": "native-oci-lxc", + "technology_status": "proxmox-technology-preview", + "defaults": { + "unprivileged": true, + "ostype": "auto-from-image", + "cores": 2, + "memory_mb": 1024, + "swap_mb": 512, + "rootfs_size_gb": 8, + "rootfs_storage": "local-lvm", + "volume_storage": "local-lvm", + "template_storage": "local", + "bridge": "vmbr0", + "ipv4": "dhcp", + "firewall": true, + "host_managed_network": true, + "onboot": false, + "features": [ + "nesting=1" + ], + "shutdown_timeout_seconds": 30 + }, + "image_metadata_policy": { + "entrypoint": "import-from-oci-image", + "cmd": "import-from-oci-image", + "environment": "import-image-env-then-apply-compose-overrides", + "user": "import-from-oci-image", + "working_dir": "import-from-oci-image", + "stop_signal": "import-from-oci-image" + }, + "adaptations": [ + { + "id": "imported-compose-source", + "upstream_behavior": "The source definition deploys the complete Docker Compose application model.", + "native_lxc_behavior": "The source model is preserved and remains blocked until every service option has a reviewed native Proxmox mapping.", + "reason": "Catalog import must not imply runtime compatibility.", + "behavioral_impact": "No automatic installation before review.", + "validation": "pending-per-application" + }, + { + "id": "rolling-latest-image", + "upstream_behavior": "A discovered Compose may pin a release tag or digest.", + "native_lxc_behavior": "ProxMenux selects the same image repository with the latest tag for catalog installations.", + "reason": "The automatic catalog intentionally offers rolling latest images; pinned versions belong to the future manual installer.", + "behavioral_impact": "The installed release can be newer than the discovered Compose revision.", + "validation": "pending-per-application" + }, + { + "id": "dedicated-lxc-network", + "upstream_behavior": "Docker publishes selected container ports on the Docker host.", + "native_lxc_behavior": "A reviewed native application will listen on its original container ports at a dedicated LXC address.", + "reason": "A native LXC has its own address and does not require Docker port NAT.", + "behavioral_impact": "Published ports are metadata; ProxMenux URLs use the matching container target port.", + "validation": "pending-per-application" + }, + { + "id": "compose-shm-size", + "upstream_behavior": "Compose sets the size of the container /dev/shm tmpfs.", + "native_lxc_behavior": "ProxMenux mounts a native LXC tmpfs at /dev/shm with the same requested capacity.", + "reason": "The OCI image runs directly as an LXC and therefore needs the equivalent Proxmox mount entry.", + "behavioral_impact": "None expected.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-command", + "upstream_behavior": "Compose replaces the image Cmd while retaining its Entrypoint.", + "native_lxc_behavior": "ProxMenux reads the official OCI Entrypoint and combines it with the Compose command as the native LXC init command.", + "reason": "Proxmox stores the effective OCI process as one entrypoint string.", + "behavioral_impact": "None expected.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-privileged-mode", + "upstream_behavior": "Compose selects whether the container runs in privileged mode.", + "native_lxc_behavior": "ProxMenux keeps the LXC unprivileged by default and exposes the broad Compose privilege request only as an explicit compatibility option.", + "reason": "A Compose privilege request is source metadata, not proof that the image technically requires a privileged LXC.", + "behavioral_impact": "A privileged LXC has weaker isolation from the Proxmox host.", + "validation": "native-equivalent" + }, + { + "id": "compose-process-runtime", + "upstream_behavior": "Compose can replace Entrypoint, User and WorkingDir and request an init process or interactive terminal.", + "native_lxc_behavior": "ProxMenux applies the process overrides through native LXC init directives; lxc-init provides PID 1 supervision and the CT console provides terminal access.", + "reason": "The OCI process must start with the same identity, command and working directory without Docker.", + "behavioral_impact": "Compose stdin_open and tty become access through the Proxmox LXC console.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-healthcheck", + "upstream_behavior": "Docker periodically executes the declared container healthcheck.", + "native_lxc_behavior": "For a single-service LXC, ProxMenux translates HTTP localhost checks into a mandatory first-start service check.", + "reason": "Proxmox has no persistent Docker health state, while the installer still must detect a failed first boot.", + "behavioral_impact": "The check runs during installation rather than continuously after installation.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-cpu-priority", + "upstream_behavior": "Docker cpu_shares sets a relative scheduling weight with 1024 as its neutral value.", + "native_lxc_behavior": "ProxMenux converts the relative weight to Proxmox cpuunits with 100 as its neutral value and lets the user review it.", + "reason": "Both settings express relative CPU priority on different scales.", + "behavioral_impact": "Rounding and Proxmox minimum limits can slightly change very low weights.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-resource-limits", + "upstream_behavior": "mem_limit sets the memory ceiling; ulimits sets process soft/hard resource limits.", + "native_lxc_behavior": "mem_limit supplies the editable Proxmox memory default, rounded up to MiB; ulimits maps to lxc.prlimit with -1 represented as unlimited.", + "reason": "Use native Proxmox memory and LXC prlimits, without a wrapper or changing the host kernel configuration.", + "behavioral_impact": "User-selected memory overrides Compose. Swap is a separate choice. Kernel restrictions still apply; nproc counts processes by real UID, not by container.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-network-identity", + "upstream_behavior": "Compose can set hostname, MAC address, extra hosts and attach a service to Docker networks.", + "native_lxc_behavior": "ProxMenux applies hostname and MAC to net0, writes additional host aliases into the LXC and uses its dedicated bridge connection for single-service networks.", + "reason": "A dedicated LXC has its own network namespace and does not need a Docker bridge per service.", + "behavioral_impact": "host-gateway resolves to the IPv4 address of the selected Proxmox bridge.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-network-mode", + "upstream_behavior": "Docker host mode removes Docker network isolation; bridge and default use a Docker-managed network.", + "native_lxc_behavior": "The OCI process uses the dedicated LXC network namespace directly, so host, bridge and default all listen on the LXC address without Docker NAT.", + "reason": "The LXC is the application host and already has its own address and port namespace.", + "behavioral_impact": "host means the LXC host, never the Proxmox host; this preserves Proxmox network isolation.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-capabilities-and-sysctls", + "upstream_behavior": "Compose can add Linux capabilities and set kernel parameters in the container network namespace.", + "native_lxc_behavior": "ProxMenux validates requested capabilities against the native LXC capability set and writes namespaced network settings as lxc.sysctl directives.", + "reason": "A native OCI-LXC already starts with the namespaced capability set; lxc.cap.keep would incorrectly discard unrelated required capabilities.", + "behavioral_impact": "Host-global capabilities such as SYS_MODULE remain blocked until their host prerequisite is explicitly adapted.", + "validation": "not-requested-by-compose" + }, + { + "id": "docker-engine-metadata", + "upstream_behavior": "Compose labels annotate Docker objects and the json-file logging driver rotates Docker-managed logs.", + "native_lxc_behavior": "Labels remain source metadata; Docker json-file settings are not applied because the OCI process runs directly under LXC.", + "reason": "There is no Docker object or Docker json-file log behind a native OCI-LXC application.", + "behavioral_impact": "Docker-only label consumers and Docker log-driver rotation do not exist in the native deployment.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-device-passthrough", + "upstream_behavior": "Compose passes host character devices or requests an NVIDIA runtime GPU.", + "native_lxc_behavior": "ProxMenux converts recognized device declarations to native Proxmox dev resources; NVIDIA profiles also inject compatible host driver libraries read-only.", + "reason": "Native OCI-LXC does not execute Docker device or NVIDIA runtime hooks.", + "behavioral_impact": "Hardware is exposed only after explicit user confirmation and host-path validation.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-host-ipc", + "upstream_behavior": "ipc: host shares the Docker host IPC namespace, commonly to avoid Docker's small default shared-memory allocation.", + "native_lxc_behavior": "The application keeps the LXC IPC namespace and receives a configurable 1 GiB /dev/shm instead of sharing Proxmox host IPC.", + "reason": "Processes in a single native LXC already share one IPC namespace; retaining isolation is safer than exposing host IPC.", + "behavioral_impact": "The application cannot exchange IPC objects with processes on the Proxmox host.", + "validation": "not-requested-by-compose" + } + ], + "installer_profile": { + "volume_preparations": [ + { + "container_path": "/.airdcpp", + "remove_lost_found": true, + "owner_strategy": "mapped-application-user", + "only_when_mount_type": "managed-volume" + }, + { + "container_path": "/Downloads", + "remove_lost_found": true, + "owner_strategy": "mapped-application-user", + "only_when_mount_type": "managed-volume" + }, + { + "container_path": "/Share", + "remove_lost_found": true, + "owner_strategy": "mapped-application-user", + "only_when_mount_type": "managed-volume" + } + ], + "startup_healthcheck": { + "scheme": "http", + "port": 5600, + "path": "/index.html", + "timeout_seconds": 180, + "request_timeout_seconds": 5, + "stability_seconds": 4, + "verify_tls": true + } + } + }, + "compatibility": { + "automatic_install_candidate": true, + "validated": false, + "supported_compose_keys": [ + "cap_add", + "command", + "container_name", + "cpu_shares", + "deploy", + "devices", + "entrypoint", + "environment", + "extra_hosts", + "group_add", + "healthcheck", + "hostname", + "image", + "init", + "ipc", + "labels", + "logging", + "mac_address", + "mem_limit", + "network_mode", + "networks", + "ports", + "privileged", + "restart", + "runtime", + "security_opt", + "shm_size", + "stdin_open", + "stop_grace_period", + "stop_signal", + "sysctls", + "tty", + "ulimits", + "user", + "volumes", + "working_dir" + ], + "untranslated_blockers": [], + "policy": "Community image and single-LXC contract reviewed against the publisher Compose and entrypoint; Linux amd64 confirmed in the registry manifest. Runtime lifecycle checks remain pending." + }, + "validation": { + "schema": "passed-at-generation", + "clean_install": "pending", + "service_health": "pending", + "restart_persistence": "pending", + "backup_restore": "pending", + "update_preserves_data": "pending", + "authenticated_login": "pending", + "advanced_install": "pending", + "host_bind_storage": "pending", + "managed_storage": "pending", + "removal": "pending" + }, + "lifecycle": { + "update_strategy": "resolve-latest-image-then-apply-reviewed-native-lxc-update", + "registry_state": { + "resolved_architecture": null, + "resolved_digest": null, + "image_version_label": null, + "image_created": null + }, + "change_detection": "compare-compose-sha256-and-resolved-latest-image-digest", + "automatic_unattended_updates": false + } +} diff --git a/oci/catalog/curated/authelia.json b/oci/catalog/curated/authelia.json new file mode 100644 index 00000000..adcb37c6 --- /dev/null +++ b/oci/catalog/curated/authelia.json @@ -0,0 +1,453 @@ +{ + "schema_version": "0.4.0", + "kind": "proxmenux.oci-template", + "id": "image-authelia", + "status": "generated-unvalidated", + "catalog_ui": { + "title": { + "en_US": "Authelia", + "es_ES": "Authelia" + }, + "tagline": { + "en_US": "Single sign-on and multi-factor authentication", + "es_ES": "Inicio de sesión único y autenticación multifactor" + }, + "description": { + "en_US": "An open-source authentication and authorization server for protecting web applications. This single-image profile provides a local SQLite database and file notifications; configure your domain, reverse proxy and access rules before protecting other services.", + "es_ES": "Servidor de autenticación y autorización de código abierto para proteger aplicaciones web. Este perfil de una sola imagen incluye una base de datos SQLite local y notificaciones en archivo; configura el dominio, el proxy inverso y las reglas de acceso antes de proteger otros servicios." + }, + "category": "security", + "category_label": "Authentication & Security", + "author": "Authelia", + "developer": "Authelia", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/authelia.webp", + "thumbnail": null, + "screenshots": [], + "architectures": [ + "amd64", + "arm64" + ], + "launch": { + "scheme": "http", + "port": 9091, + "path": "/" + }, + "website": "https://www.authelia.com/", + "documentation": "https://www.authelia.com/integration/deployment/docker/", + "repository": "https://github.com/authelia/authelia", + "tips": [ + "The installer creates a unique admin password and stores only its Argon2id hash in /config/users_database.yml. The password is shown in the installation result.", + "Authelia requires an HTTPS portal URL and a cookie domain matching that hostname. If you use Nginx Proxy Manager, follow the official integration guide and configure trusted proxies and forward-auth separately.", + "The default notifier writes messages to /config/notification.txt. Configure SMTP in /config/configuration.yml if you want email delivery.", + "The initial access-control policy is one_factor. Add explicit access-control rules before attaching Authelia to protected proxy hosts." + ], + "mini_changelog": [], + "display_version": "4.39.28", + "updated_at": "2026-09-17" + }, + "source": { + "provider": "authelia", + "repository": "https://github.com/authelia/authelia", + "default_branch": "master", + "revision": "8da42b2348b0351c6284a2940a9c5eec3d4cd3fd", + "readme_raw_url": "https://raw.githubusercontent.com/authelia/authelia/v4.39.28/examples/compose/lite/compose.yml", + "readme_pushed_at": "2026-09-17T12:13:30Z", + "compose_sha256": "848ce0f70e502981a050ac200d9d1882978a6c3b0f9f0dfcc6d52ce66ca37fb5", + "generated_at": "2026-11-03T09:10:57+00:00", + "image_repository_url": "https://hub.docker.com/r/authelia/authelia" + }, + "container_contract": { + "service_name": "authelia", + "container_name": "authelia", + "image": { + "reference": "docker.io/authelia/authelia:4.39.28", + "registry": "docker.io", + "repository": "docker.io/authelia/authelia", + "tag": "4.39.28", + "digest": null, + "pull_policy": "resolve-selected-tag-to-architecture-digest-at-install" + }, + "environment": [ + { + "name": "PROXMENUX_ADMIN_PASSWORD", + "example": "", + "required": true, + "sensitive": true, + "source": "proxmenux-first-start" + }, + { + "name": "AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET", + "example": "", + "required": true, + "sensitive": true, + "source": "proxmenux-generated" + }, + { + "name": "AUTHELIA_SESSION_SECRET", + "example": "", + "required": true, + "sensitive": true, + "source": "proxmenux-generated" + }, + { + "name": "AUTHELIA_STORAGE_ENCRYPTION_KEY", + "example": "", + "required": true, + "sensitive": true, + "source": "proxmenux-generated" + }, + { + "name": "X_AUTHELIA_CONFIG_FILTERS", + "example": "template", + "required": true, + "sensitive": false, + "source": "authelia-container" + }, + { + "name": "PROXMENUX_AUTHELIA_COOKIE_DOMAIN", + "example": "", + "prompt": "Authelia session cookie domain for this portal (for example example.com)", + "required": true, + "sensitive": false, + "source": "administrator" + }, + { + "name": "PROXMENUX_AUTHELIA_URL", + "example": "", + "prompt": "Public HTTPS URL used to open this Authelia portal (for example https://auth.example.com)", + "required": true, + "sensitive": false, + "source": "administrator" + } + ], + "volumes": [ + { + "id": "config", + "container_path": "/config", + "compose_source_example": "./authelia", + "read_only": false, + "required": true, + "installation_choice": [ + "managed-volume" + ], + "default": "managed-volume", + "managed_volume": { + "backup": true, + "default_size_gb": 4 + } + } + ], + "ports": [ + { + "container_port": 9091, + "published_example": 9091, + "protocol": "tcp", + "required": true, + "proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat" + } + ], + "related_services": [], + "restart": "unless-stopped", + "stop_grace_period": null, + "original_compose": "services:\n authelia:\n entrypoint:\n - /bin/sh\n - /usr/local/bin/proxmenux-authelia-bootstrap\n environment:\n AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET: ${GENERATED_RESET_PASSWORD_JWT_SECRET}\n AUTHELIA_SESSION_SECRET: ${GENERATED_SESSION_SECRET}\n AUTHELIA_STORAGE_ENCRYPTION_KEY: ${GENERATED_STORAGE_ENCRYPTION_KEY}\n PROXMENUX_ADMIN_PASSWORD: ${GENERATED_ADMIN_PASSWORD}\n PROXMENUX_AUTHELIA_COOKIE_DOMAIN: ${AUTHELIA_COOKIE_DOMAIN}\n PROXMENUX_AUTHELIA_URL: ${AUTHELIA_URL}\n X_AUTHELIA_CONFIG_FILTERS: template\n image: docker.io/authelia/authelia:4.39.28\n ports:\n - 9091:9091\n restart: unless-stopped\n volumes:\n - ./authelia:/config\n" + }, + "first_run": { + "endpoints": [ + { + "label": "Authelia Web UI", + "scheme": "http", + "port": 9091, + "path": "/", + "source": "official-image" + } + ], + "credentials": [ + { + "label": "Authelia administrator", + "type": "configured-or-installer-generated", + "username": "admin", + "password": null, + "password_environment": "PROXMENUX_ADMIN_PASSWORD", + "change_required": false, + "source": "Generated on first start and stored as an Argon2id hash in the persistent users file." + } + ] + }, + "proxmox": { + "runtime": "native-oci-lxc", + "technology_status": "proxmox-technology-preview", + "defaults": { + "unprivileged": true, + "ostype": "auto-from-image", + "cores": 2, + "memory_mb": 512, + "swap_mb": 512, + "rootfs_size_gb": 8, + "rootfs_storage": "local-lvm", + "volume_storage": "local-lvm", + "template_storage": "local", + "bridge": "vmbr0", + "ipv4": "dhcp", + "firewall": true, + "host_managed_network": true, + "onboot": false, + "features": [ + "nesting=1" + ], + "shutdown_timeout_seconds": 60 + }, + "image_metadata_policy": { + "entrypoint": "import-from-oci-image", + "cmd": "import-from-oci-image", + "environment": "import-image-env-then-apply-compose-overrides", + "user": "import-from-oci-image", + "working_dir": "import-from-oci-image", + "stop_signal": "import-from-oci-image" + }, + "installer_profile": { + "runtime": { + "compose_entrypoint": [ + "/bin/sh", + "/usr/local/bin/proxmenux-authelia-bootstrap" + ] + }, + "generated_sensitive_environment": { + "PROXMENUX_ADMIN_PASSWORD": { + "strategy": "token-hex", + "bytes": 24, + "prompt": false + }, + "AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET": { + "strategy": "token-hex", + "bytes": 32, + "prompt": false + }, + "AUTHELIA_SESSION_SECRET": { + "strategy": "token-hex", + "bytes": 32, + "prompt": false + }, + "AUTHELIA_STORAGE_ENCRYPTION_KEY": { + "strategy": "token-hex", + "bytes": 32, + "prompt": false + } + }, + "minimum_secret_length": { + "PROXMENUX_ADMIN_PASSWORD": 24, + "AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET": 32, + "AUTHELIA_SESSION_SECRET": 32, + "AUTHELIA_STORAGE_ENCRYPTION_KEY": 32 + }, + "generated_files": [ + { + "container_path": "/config/configuration.yml", + "mode": "0600", + "owner": "mapped-root", + "only_if_missing": true, + "content": "---\nserver:\n address: 'tcp://:9091'\n\nlog:\n level: 'info'\n\ntotp:\n issuer: 'Authelia'\n\nidentity_validation:\n reset_password:\n jwt_secret: 'configured-by-environment'\n\nauthentication_backend:\n file:\n path: '/config/users_database.yml'\n watch: true\n\naccess_control:\n default_policy: 'one_factor'\n\nsession:\n secret: 'configured-by-environment'\n cookies:\n - name: 'authelia_session'\n domain: '{{ env \"PROXMENUX_AUTHELIA_COOKIE_DOMAIN\" }}'\n authelia_url: '{{ env \"PROXMENUX_AUTHELIA_URL\" }}'\n expiration: '1 hour'\n inactivity: '5 minutes'\n\nstorage:\n encryption_key: 'configured-by-environment'\n local:\n path: '/config/db.sqlite3'\n\nnotifier:\n filesystem:\n filename: '/config/notification.txt'\n" + }, + { + "container_path": "/usr/local/bin/proxmenux-authelia-bootstrap", + "mode": "0700", + "owner": "mapped-root", + "only_if_missing": false, + "content": "#!/bin/sh\nset -eu\numask 077\n\nusers_file=/config/users_database.yml\nif [ ! -s \"$users_file\" ]; then\n : \"${PROXMENUX_ADMIN_PASSWORD:?ProxMenux did not provide an initial administrator password}\"\n mkdir -p /config\n hash_output=$(authelia crypto hash generate argon2 --password \"$PROXMENUX_ADMIN_PASSWORD\" --no-confirm)\n password_hash=$(printf '%s\\n' \"$hash_output\" | sed -n 's/^Digest: //p')\n case \"$password_hash\" in\n '$argon2id$'*) ;;\n *) printf '%s\\n' 'Authelia did not return an Argon2id password hash' >&2; exit 1 ;;\n esac\n temporary_file=\"${users_file}.tmp.$$\"\n cat >\"$temporary_file\" </dev/null) || status=$? + code=$(oci_curl "$VMID" "${CURL_ARGS[@]}" "$url" 2>/dev/null) || status=$? (( status == 0 )) && [[ $code =~ ^[1-4][0-9][0-9]$ ]] && return 0 else - oci_curl "$VMID" "${CURL_ARGS[@]}" "$HC_URL" >/dev/null 2>&1 || status=$? + oci_curl "$VMID" "${CURL_ARGS[@]}" "$url" >/dev/null 2>&1 || status=$? (( status == 0 )) && return 0 fi # A port taken from the compose file may serve a database or SSH: an answer @@ -373,6 +373,19 @@ healthcheck_probe() { && (( status == 1 || status == 8 || status == 52 || status == 56 )) } +# Any of the addresses answering means the application is up; HC_URL keeps the +# one that answered. +healthcheck_probe() { + local url + for url in "${HC_URLS[@]}"; do + if healthcheck_probe_url "$url"; then + HC_URL=$url + return 0 + fi + done + return 1 +} + cleanup_failed_install() { local status=$? stop_spinner @@ -2333,6 +2346,12 @@ if [[ $START_AFTER == 1 && $HAS_STARTUP_HEALTHCHECK == 1 ]]; then || die "$(translate "The stability period must be shorter than the healthcheck timeout")" [[ -n $IP ]] || die "$(translate "The healthcheck cannot run without an IP address")" HC_URL="${HC_SCHEME}://${IP}:${HC_PORT}${HC_PATH}" + HC_URLS=("$HC_URL") + while IFS= read -r HC_ALT_PORT; do + [[ $HC_ALT_PORT =~ ^[0-9]+$ && $HC_ALT_PORT -ge 1 && $HC_ALT_PORT -le 65535 ]] \ + || die "$(translate "Invalid healthcheck port")" + HC_URLS+=("${HC_SCHEME}://${IP}:${HC_ALT_PORT}${HC_PATH}") + done < <(jq -r '.alternative_ports[]? | tostring' <<<"$STARTUP_HEALTHCHECK") HC_ANY_STATUS=$(jq -r '.accept_any_status // false' <<<"$STARTUP_HEALTHCHECK") HC_ACCEPT_NON_HTTP=$(jq -r '.accept_non_http // false' <<<"$STARTUP_HEALTHCHECK") if [[ $HC_ANY_STATUS == true ]]; then diff --git a/oci/remote/oci_instance_transaction.py b/oci/remote/oci_instance_transaction.py index ca92744d..5639d97d 100644 --- a/oci/remote/oci_instance_transaction.py +++ b/oci/remote/oci_instance_transaction.py @@ -239,6 +239,26 @@ def recover_untouched(root, journal): return False +def recover_failed_update(root, journal): + """Attempt the guarded rollback after an update/Modify command fails. + + Unlike recovery of an untouched guest, replacement failures may have + parked its volumes or removed its root disk. The existing recovery + journal validates ownership, configuration and backup before restoring. + Stack transactions remain the responsibility of their coordinator. + """ + try: + with instances.locked(root): + state = json.loads(Path(journal).read_text()) + if state.get('coordinated') or state.get('phase') in TERMINAL: + return False + recover(root, Path(journal)) + return True + except (OSError, ValueError, KeyError, RuntimeError, subprocess.SubprocessError) as error: + log(f'automatic update recovery failed: {error}') + return False + + def fit(text): """A step line that is rewritten in place must not wrap.""" width = max(shutil.get_terminal_size((80, 24)).columns - 8, 30) @@ -415,13 +435,22 @@ def probe_http(args): return result.stdout, result.returncode +def setup_then_web_port(template, check): + """AdGuard Home answers its setup wizard on 3000 and, once set up, its web UI + on 80. A check on 3000, derived or saved with an installation, takes 80 too.""" + if (template.get('id') == 'image-adguard-home' and check.get('port') == 3000 + and not check.get('alternative_ports')): + return dict(check, alternative_ports=[80]) + return check + + def effective_healthcheck(template): """The template's own health check or, when it has none, one derived from its web address: any HTTP answer below 500 means the application is up.""" profile = template.get('proxmox', {}).get('installer_profile', {}) check = profile.get('startup_healthcheck') if check: - return check + return setup_then_web_port(template, check) if profile.get('haos_healthcheck'): # Home Assistant OS has a check of its own: Supervisor, Core and the # services it runs inside. The installer runs it on the new container. @@ -433,6 +462,7 @@ def effective_healthcheck(template): check = {'scheme': endpoint['scheme'], 'port': int(endpoint['port']), 'path': path, 'timeout_seconds': 300, 'request_timeout_seconds': 10, 'stability_seconds': 4, 'verify_tls': False, 'accept_any_status': True} + check = setup_then_web_port(template, check) # A port taken from the compose file because the recipe names no # web address may serve a database or SSH: any answer counts. if endpoint.get('source') == GUESSED_ENDPOINT: @@ -601,6 +631,21 @@ def without_public_routes(config, vmid): return b'\n'.join(line for line in config.splitlines() if not public_routes.own(line, vmid)) + b'\n' +def refuse_high_availability(vmid): + """Recreating a container that HA manages would race the HA manager, which + restarts or relocates it in the middle of the replacement.""" + try: + answer = run('pvesh', 'get', '/cluster/ha/resources', '--output-format', 'json') + except FileNotFoundError: + return # Not a Proxmox VE node: there is no HA manager to race. + resources = json.loads(answer) + if any(r.get('sid') == f'ct:{vmid}' for r in resources): + raise ValueError(translate( + 'This container is managed by high availability. Remove ct:{vmid} from HA (Datacenter → HA, ' + 'or ha-manager remove ct:{vmid}), repeat the operation and add it back to HA with the same settings.' + ).replace('{vmid}', str(vmid))) + + def preflight(record, candidate, config, coordinated=None, in_place=False, baseline=True): deployment = record['deployment'] desired = candidate['deployment'] @@ -610,6 +655,9 @@ def preflight(record, candidate, config, coordinated=None, in_place=False, basel raise ValueError(translate('Stack members are updated together with their stack')) if instances.identity(config) != record['installation_id']: raise ValueError(translate('The container identity changed; the container is not replaced')) + if not in_place and not coordinated: + # A stack checks its members itself; an in-place edit keeps the container. + refuse_high_availability(record['vmid']) for key, value in (external_changes(record, config, adopt=not coordinated) if baseline else {}).items(): section, name = ADOPTABLE[key] recorded = deployment.get(section, {}) if section else deployment @@ -885,10 +933,12 @@ def healthcheck(vmid, template): time.sleep(2) log(f'container {vmid} kept running for {stability}s') return None - scheme, port, path = check['scheme'], int(check['port']), check['path'] + scheme, path = check['scheme'], check['path'] + # Any of the ports answering means the application is up. + ports = [int(check['port']), *(int(p) for p in check.get('alternative_ports') or [])] timeout = int(check.get('timeout_seconds', 120)) stability = int(check.get('stability_seconds', 0)) - if (scheme not in ('http', 'https') or not 1 <= port <= 65535 + if (scheme not in ('http', 'https') or not all(1 <= port <= 65535 for port in ports) or not path.startswith('/') or any(c.isspace() for c in path) or not 0 <= stability < timeout <= 3600): raise ValueError(translate('Invalid health check')) @@ -909,10 +959,13 @@ def healthcheck(vmid, template): args += ['-w', '%{http_code}'] if not check.get('verify_tls', False): args.append('-k') - answer, status = probe_http([*args, f'{scheme}://{ip}:{port}{path}']) - ok = status == 0 and (not any_status or re.fullmatch(rb'[1-4][0-9][0-9]', answer.strip()) is not None) - if not ok and check.get('accept_non_http') and status in NON_HTTP_ANSWER: - ok = True + for port in ports: + answer, status = probe_http([*args, f'{scheme}://{ip}:{port}{path}']) + ok = status == 0 and (not any_status or re.fullmatch(rb'[1-4][0-9][0-9]', answer.strip()) is not None) + if not ok and check.get('accept_non_http') and status in NON_HTTP_ANSWER: + ok = True + if ok: + break if ok: stable_since = stable_since or time.monotonic() if time.monotonic() - stable_since >= stability: @@ -1182,9 +1235,8 @@ def apply(root, vmid, archive, operation, proposal=None, registry_digest=None, i runtime_settings.file_binds(before)) original_gpu = gpu_devices.planned(record['deployment']) desired_gpu = gpu_devices.planned(candidate['deployment']) - resources = json.loads(run('pvesh', 'get', '/cluster/ha/resources', '--output-format', 'json')) - if any(r.get('sid') == f'ct:{vmid}' for r in resources): - raise ValueError(translate('High availability resources are not supported by this profile')) + if coordinated: + refuse_high_availability(vmid) archive = archive.resolve(strict=True) check_archive(archive) image = image_from_archive(str(archive)) diff --git a/oci/remote/oci_update_current.py b/oci/remote/oci_update_current.py index 2d49ed5c..e8163fb9 100644 --- a/oci/remote/oci_update_current.py +++ b/oci/remote/oci_update_current.py @@ -232,6 +232,7 @@ def update(vmid, acknowledge_external_data=False, proposal=None, keep_backup=Non # An unsupported native edit still uses the existing installer. Decide # the route before changing anything; actual validation/API failures # must not silently switch to a destructive fallback. + # Also refuses a container that HA manages, before the image is downloaded. transaction.preflight(record, desired, config) msg_ok(translate('Container checked')) current = record['observed']['image'] if operation == 'update' else None @@ -289,7 +290,7 @@ def main(): except (OSError, ValueError, KeyError, RuntimeError, subprocess.SubprocessError) as error: transaction.report_error(error, f'update-{args.vmid}') journal = transaction.pending_journal() - if journal and not transaction.recover_untouched(instances.ROOT, journal): + if journal and not transaction.recover_failed_update(instances.ROOT, journal): transaction.recovery_hint() return 1 diff --git a/oci/src/proxmenux_oci/catalog.py b/oci/src/proxmenux_oci/catalog.py index ed9638ce..aa5d81f2 100644 --- a/oci/src/proxmenux_oci/catalog.py +++ b/oci/src/proxmenux_oci/catalog.py @@ -1,6 +1,7 @@ from __future__ import annotations import json +import weakref from concurrent.futures import ThreadPoolExecutor, as_completed from datetime import datetime, timezone from pathlib import Path @@ -56,9 +57,15 @@ def is_multi_container(template: dict[str, Any]) -> bool: class Catalog: - def __init__(self, root: Path, source: GitHubSource | None = None) -> None: + def __init__(self, root: Path, source: GitHubSource | None = None, *, + catalog_dir: Path | None = None) -> None: self.root = root - self.catalog_dir = root / "catalog" + from .catalog_refresh import pin_catalog + self.catalog_dir, lease = pin_catalog(root, catalog_dir) + if lease is not None: + self._snapshot_lease = weakref.finalize(self, lease.close) + self._include_local = catalog_dir is None and self.catalog_dir != root / "catalog" + self.local_app_ids: set[str] = set() self.apps_dir = self.catalog_dir / "apps" self.curated_dir = self.catalog_dir / "curated" self.overlays_dir = self.catalog_dir / "overlays" @@ -345,6 +352,16 @@ class Catalog: payload = json.loads(self.index_path.read_text(encoding="utf-8")) self._enrich_index_from_templates(payload) self._apply_verification_to_index(payload) + if self._include_local: + from .catalog_refresh import source_settings + requested = source_settings(self.root).get("local_apps", []) + if isinstance(requested, list) and requested: + bundled = Catalog(self.root, catalog_dir=self.root / "catalog").load_index() + present = {item["id"] for item in payload["applications"]} + local = [item for item in bundled["applications"] + if item["id"] in requested and item["id"] not in present] + self.local_app_ids = {item["id"] for item in local} + payload["applications"].extend(local) return payload def categories(self) -> dict[str, Any]: @@ -588,7 +605,7 @@ class Catalog: item["id"], ) elif item.get("template_family") == "curated-profile": - template = json.loads((self.root / item["curated_path"]).read_text(encoding="utf-8")) + template = json.loads((self._curated_path(item)).read_text(encoding="utf-8")) else: raise ConversionError(f"Proveedor no soportado: {provider}") catalog_id = item["id"] @@ -641,7 +658,7 @@ class Catalog: ) elif item.get("template_family") == "curated-profile": template = json.loads( - (self.root / item["curated_path"]).read_text(encoding="utf-8") + (self._curated_path(item)).read_text(encoding="utf-8") ) else: raise ConversionError(f"Proveedor no soportado: {item_provider}") @@ -785,18 +802,35 @@ class Catalog: if errors: raise ConversionError(f"La plantilla generada no cumple el esquema: {'; '.join(errors)}") + def _curated_path(self, item: dict[str, Any]) -> Path: + path = Path(item["curated_path"]) + if path.parts and path.parts[0] == "catalog": + return self.catalog_dir / path.relative_to("catalog") + # Older local profiles may use an engine-relative path. Downloaded + # catalogs are checked separately and must use catalog/curated paths. + return self.root / path + def compose(self, app_id: str) -> dict[str, Any]: """The installable template, built only from the files shipped with ProxMenux: the curated profile or the pre-generated template, with its overlay applied. Nothing is downloaded and nothing is written.""" - item = self.find_item(app_id) + return self._compose_item(self.find_item(app_id)) + + def _compose_item(self, item: dict[str, Any]) -> dict[str, Any]: + """Compose an already loaded item when validating a complete snapshot.""" + app_id = item["id"] + if app_id in self.local_app_ids: + return Catalog(self.root, catalog_dir=self.root / "catalog").compose(app_id) if item.get("template_family") == "curated-profile": - path = self.root / item["curated_path"] + path = self._curated_path(item) else: path = self.apps_dir / f"{item['id']}.json" if not path.exists(): raise ConversionError(f"No template is available for '{app_id}'") template = json.loads(path.read_text(encoding="utf-8")) + if item.get("_retained_recipe"): + self.validate(template, required=False) + return template self._apply_overlay(item["id"], template) self._apply_category(item["id"], template["catalog_ui"]) self._apply_volume_policy(item["id"], template["container_contract"], diff --git a/oci/src/proxmenux_oci/catalog_refresh.py b/oci/src/proxmenux_oci/catalog_refresh.py new file mode 100644 index 00000000..30dded5b --- /dev/null +++ b/oci/src/proxmenux_oci/catalog_refresh.py @@ -0,0 +1,542 @@ +"""Refresh the reviewed ProxMenux catalog without blocking the OCI menu. + +GitHub's catalog commit and tree form the manifest. Files are fetched from +that immutable commit and checked against their Git blob IDs. Runtime code is +never downloaded. An installed engine always retains its bundled fallback. +""" +from __future__ import annotations + +import argparse +from contextlib import contextmanager +from concurrent.futures import ThreadPoolExecutor +import fcntl +import hashlib +import json +import os +from pathlib import Path, PurePosixPath +import re +import shutil +import subprocess +import sys +import tempfile +import threading +import time +import urllib.error +import urllib.request +import uuid + +from .i18n import translate + +REPOSITORY = "MacRimi/ProxMenux" +CATALOG_API = 1 +MAX_FILE = 4 * 1024 * 1024 +MAX_TOTAL = 40 * 1024 * 1024 +SHA = re.compile(r"[0-9a-f]{40}") +ROOT_FILES = {"index.json", "categories.json", "volume-policy.json", + "exclusions.json", "verification.json", "compatibility.json"} + + +def source_settings(root): + root = Path(root) + base = (root.parent.parent if root.name == "engine" else + root.parent if root.name == "oci" else root) + return read_json(base / "oci/catalog-source.json") + + +def channel_for(root): + """Keep stable and beta catalogs separate; allow a local channel choice.""" + root = Path(root) + base = (root.parent.parent if root.name == "engine" else + root.parent if root.name == "oci" else root) + for name in ("oci/catalog-source.json", "config.json"): + try: + value = read_json(base / name) + channel = value.get("channel") or value.get("install_branch") + if channel in ("main", "develop"): + return channel + beta = value.get("beta_program") + if isinstance(beta, dict) and beta.get("status") == "active": + return "develop" + except (OSError, ValueError, TypeError): + pass + return "develop" if (base / "beta_version.txt").exists() else "main" + + +def cache_root(root): + root = Path(root) + return root.parent / "catalog-cache" if root.name == "engine" else root / ".catalog-cache" + + +def read_json(path, default=None): + try: + value = json.loads(Path(path).read_text()) + if isinstance(value, dict): + return value + except (OSError, ValueError): + pass + return {} if default is None else default + + +def write_json(path, value): + path = Path(path) + path.parent.mkdir(parents=True, exist_ok=True) + fd, name = tempfile.mkstemp(prefix=".write-", dir=path.parent) + try: + with os.fdopen(fd, "w") as stream: + json.dump(value, stream, ensure_ascii=False) + stream.write("\n") + stream.flush() + os.fsync(stream.fileno()) + os.replace(name, path) + finally: + Path(name).unlink(missing_ok=True) + + +def schema_hash(root): + return hashlib.sha256((Path(root) / "schemas/oci-template.schema.json").read_bytes()).hexdigest() + + +def bundle_hash(root): + # Cheap menu-time engine-update detection; no scan of the bundled recipes. + return hashlib.sha256((Path(root) / "catalog/index.json").read_bytes()).hexdigest() + + +@contextmanager +def snapshot_lock(root): + cache = cache_root(root) + cache.mkdir(parents=True, exist_ok=True) + with (cache / "snapshots.lock").open("a") as lock: + fcntl.flock(lock, fcntl.LOCK_EX) + yield + + +def pin_catalog(root, directory=None): + """Select and lease atomically against pruning; no network/worker wait.""" + explicit = directory + directory = Path(directory) if directory is not None else current_catalog(root) + if (directory.parent != cache_root(root) / "snapshots" + or not re.fullmatch(r"[0-9a-f]{40}(?:-[0-9a-f]{12})?", directory.name)): + return directory, None + with snapshot_lock(root): + directory = Path(explicit) if explicit is not None else current_catalog(root) + snapshots = cache_root(root) / "snapshots" + if directory.parent != snapshots or not directory.is_dir(): + return directory, None + leases = cache_root(root) / "leases" + leases.mkdir(exist_ok=True) + lease = (leases / directory.name).open("a") + fcntl.flock(lease, fcntl.LOCK_SH) + return directory, lease + + +def prune_snapshots(root): + """Retain channel pointers and live readers; locks survive PID reuse/crashes.""" + cache = cache_root(root) + with snapshot_lock(root): + keep = {read_json(p).get("snapshot", read_json(p).get("revision")) + for p in cache.glob("*-current.json")} + leases = cache / "leases" + leases.mkdir(exist_ok=True) + for directory in (cache / "snapshots").glob("*"): + if (not SHA.fullmatch(directory.name.split("-")[0]) + or directory.name in keep or directory.is_symlink() or not directory.is_dir()): + continue + lease_path = leases / directory.name + with lease_path.open("a") as lease: + try: + fcntl.flock(lease, fcntl.LOCK_EX | fcntl.LOCK_NB) + except BlockingIOError: + continue + shutil.rmtree(directory) + lease_path.unlink(missing_ok=True) + + +def supported_profile_keys(root): + keys = set() + for folder in ("apps", "curated", "overlays"): + for path in (Path(root) / "catalog" / folder).glob("*.json"): + value = json.loads(path.read_text()) + keys.update(value.get("proxmox", {}).get("installer_profile", {})) + return keys + + +def filter_compatible_recipes(root, directory, previous): + """Silently defer new engine capabilities, retaining compatible old apps.""" + from .catalog import Catalog + supported = supported_profile_keys(root) + candidate = Catalog(Path(root), catalog_dir=directory) + old = Catalog(Path(root), catalog_dir=previous) + old_items = {item["id"]: item for item in old.load_index()["applications"]} + effective_items = {item["id"]: item for item in candidate.load_index()["applications"]} + index = json.loads((directory / "index.json").read_text()) + accepted = [] + old_fingerprints = None + for item in index["applications"]: + effective = effective_items[item["id"]] + if effective.get("hidden") or not effective.get("automatic_install_candidate"): + accepted.append(item) + continue + template = candidate._compose_item(item) + profile = template.get("proxmox", {}).get("installer_profile", {}) + if set(profile) <= supported: + accepted.append(item) + continue + fallback = old_items.get(item["id"]) + if fallback is None: + continue + template = old._compose_item(fallback) + if not set(template.get("proxmox", {}).get("installer_profile", {})) <= supported: + continue + # Materialize the previously composed contract: its old overlay/volume + # policy must not be combined with a newly downloaded incompatible one. + app_id = item["id"] + for folder in ("apps", "curated"): + path = directory / folder / f"{app_id}.json" + path.parent.mkdir(exist_ok=True) + write_json(path, template) + (directory / "overlays" / f"{app_id}.json").unlink(missing_ok=True) + if old_fingerprints is None: + old_fingerprints = visible_recipes(old) + fallback = dict(fallback, _retained_recipe_hash=old_fingerprints.get(app_id), template_family="curated-profile", + curated_path=f"catalog/curated/{app_id}.json", + _retained_recipe=True) + accepted.append(fallback) + index["applications"] = accepted + write_json(directory / "index.json", index) + + +def current_catalog(root, channel=None): + root = Path(root) + channel = channel or channel_for(root) + value = read_json(cache_root(root) / f"{channel}-current.json") + revision = value.get("revision", "") + if (isinstance(revision, str) and SHA.fullmatch(revision) + and value.get("schema_hash") == schema_hash(root) + and value.get("engine_api") == CATALOG_API + and value.get("bundle_hash") == bundle_hash(root)): + snapshot = value.get("snapshot", revision) + if not isinstance(snapshot, str) or not re.fullmatch(revision + r"(?:-[0-9a-f]{12})?", snapshot): + return root / "catalog" + path = cache_root(root) / "snapshots" / snapshot + index = read_json(path / "index.json") + if (index.get("kind") == "proxmenux.oci-catalog-index" + and isinstance(index.get("applications"), list)): + return path + return root / "catalog" + + +def git_blob(data): + return hashlib.sha1(b"blob " + str(len(data)).encode() + b"\0" + data).hexdigest() + + +def request(url, etag=None, limit=MAX_FILE): + headers = {"User-Agent": "ProxMenux-OCI-Catalog", "Accept": "application/vnd.github+json"} + if etag: + headers["If-None-Match"] = etag + try: + with urllib.request.urlopen(urllib.request.Request(url, headers=headers), timeout=8) as response: + body = response.read(limit + 1) + if len(body) > limit: + raise ValueError("Catalog response exceeds the size limit") + return body, response.headers.get("ETag") + except urllib.error.HTTPError as error: + error.close() + if error.code == 304: + return None, etag + raise + + +def catalog_path(name): + path = PurePosixPath(name) + if (str(path) != name or path.is_absolute() or ".." in path.parts + or path.suffix != ".json"): + raise ValueError("Invalid catalog file path") + if name not in ROOT_FILES and not (len(path.parts) == 2 and path.parts[0] in + ("apps", "curated", "overlays")): + raise ValueError("Unsupported catalog file path") + return path + + +def validate_snapshot(root, directory, *, allow_empty=False): + from .catalog import Catalog + from jsonschema import Draft202012Validator + compatibility = (json.loads((directory / "compatibility.json").read_text()) + if (directory / "compatibility.json").exists() else {"engine_api": 1}) + if compatibility.get("engine_api") != CATALOG_API: + raise ValueError("This catalog requires a newer OCI engine") + validator = Draft202012Validator(json.loads((Path(root) / "schemas/oci-template.schema.json").read_text())) + for folder in ("apps", "curated"): + for file in (directory / folder).glob("*.json"): + validator.validate(json.loads(file.read_text())) + index = json.loads((directory / "index.json").read_text()) + if index.get("kind") != "proxmenux.oci-catalog-index": + raise ValueError("Invalid catalog index") + ids = set() + for item in index.get("applications", []): + app_id = item.get("id", "") + if not re.fullmatch(r"[a-zA-Z0-9][a-zA-Z0-9._-]*", app_id) or app_id in ids: + raise ValueError("Invalid or duplicate catalog application") + ids.add(app_id) + if item.get("template_family") == "curated-profile": + curated = item.get("curated_path", "") + if not curated.startswith("catalog/"): + raise ValueError("Invalid curated recipe path") + catalog_path(curated.removeprefix("catalog/")) + if not ids and not allow_empty: + raise ValueError("The remote catalog is empty") + catalog = Catalog(Path(root), catalog_dir=directory) + for item in catalog.load_index()["applications"]: + if not item.get("hidden") and item.get("automatic_install_candidate"): + validator.validate(catalog._compose_item(item)) + + +def synchronize(root, channel, fetch=request, force=False): + """Download changed JSON files into a private snapshot, then publish once.""" + root = Path(root) + cache = cache_root(root) + cache.mkdir(parents=True, exist_ok=True) + pointer = cache / f"{channel}-current.json" + previous = read_json(pointer) + active = current_catalog(root, channel) + capabilities = hashlib.sha256(json.dumps(sorted(supported_profile_keys(root))).encode()).hexdigest() + force = force or previous.get("profile_capabilities") != capabilities + etag = previous.get("etag") if active != root / "catalog" and not force else None + url = f"https://api.github.com/repos/{REPOSITORY}/commits?sha={channel}&path=oci/catalog&per_page=1" + body, etag = fetch(url, etag) + if body is None: + return False + commits = json.loads(body) + revision = commits[0]["sha"] + if not isinstance(revision, str) or not SHA.fullmatch(revision): + raise ValueError("Invalid catalog revision") + if previous.get("revision") == revision and active != root / "catalog" and not force: + write_json(pointer, {**previous, "etag": etag}) + return False + body, _ = fetch(f"https://api.github.com/repos/{REPOSITORY}/git/trees/{revision}?recursive=1") + tree = json.loads(body) + if tree.get("truncated"): + raise ValueError("GitHub returned an incomplete catalog manifest") + entries = {} + schema = None + for entry in tree["tree"]: + name = entry["path"] + if name == "oci/schemas/oci-template.schema.json": + schema = entry["sha"] + if not name.startswith("oci/catalog/") or entry.get("type") != "blob": + continue + relative = name.removeprefix("oci/catalog/") + if relative.startswith("generation-report"): + continue + catalog_path(relative) + if entry.get("mode") != "100644" or not SHA.fullmatch(entry.get("sha", "")): + raise ValueError("Invalid catalog manifest entry") + if type(entry.get("size")) is not int or not 0 <= entry["size"] <= MAX_FILE: + raise ValueError("Invalid catalog file size") + if relative in entries: + raise ValueError("Duplicate catalog manifest path") + entries[relative] = entry + if schema != git_blob((root / "schemas/oci-template.schema.json").read_bytes()): + raise ValueError("Catalog schema changed; update ProxMenux first") + if not entries or len(entries) > 3000 or sum(e["size"] for e in entries.values()) > MAX_TOTAL: + raise ValueError("Catalog exceeds the size limit") + if not {"index.json", "categories.json", "volume-policy.json", "verification.json"} <= entries.keys(): + raise ValueError("Catalog manifest is incomplete") + snapshots = cache / "snapshots" + snapshots.mkdir(exist_ok=True) + with tempfile.TemporaryDirectory(prefix=".download-", dir=snapshots) as name: + staging = Path(name) + deadline = time.monotonic() + 90 + + def download(pair): + if time.monotonic() > deadline: + raise TimeoutError("Catalog download exceeded its time limit") + relative, entry = pair + old = active / relative + data = old.read_bytes() if old.is_file() else b"" + if git_blob(data) != entry["sha"]: + data, _ = fetch(f"https://raw.githubusercontent.com/{REPOSITORY}/{revision}/oci/catalog/{relative}") + if data is None or len(data) != entry["size"] or git_blob(data) != entry["sha"]: + raise ValueError("Catalog file failed its integrity check") + json.loads(data) + target = staging / relative + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(data) + + pool = ThreadPoolExecutor(max_workers=4) + try: + list(pool.map(download, entries.items())) + finally: + pool.shutdown(wait=True, cancel_futures=True) + validate_snapshot(root, staging) + filter_compatible_recipes(root, staging, active) + validate_snapshot(root, staging, allow_empty=True) + destination = (active if previous.get("revision") == revision and active != root / "catalog" + else snapshots / revision) + if destination.exists(): + staged_files = {str(p.relative_to(staging)) for p in staging.rglob("*.json")} + intact = all((destination / relative).is_file() + and not (destination / relative).is_symlink() + and (destination / relative).read_bytes() == (staging / relative).read_bytes() + for relative in staged_files) + existing = {str(p.relative_to(destination)) for p in destination.rglob("*.json")} + if not intact or existing != staged_files: + # Repair without removing a snapshot another wizard may still hold. + destination = snapshots / (revision + "-" + uuid.uuid4().hex[:12]) + with snapshot_lock(root): + if not destination.exists(): + os.replace(staging, destination) + write_json(pointer, {"revision": revision, "snapshot": destination.name, "etag": etag, + "schema_hash": schema_hash(root), "engine_api": CATALOG_API, + "bundle_hash": bundle_hash(root), "profile_capabilities": capabilities}) + return destination != active + + +def worker(root, channel, force=False): + cache = cache_root(root) + cache.mkdir(parents=True, exist_ok=True) + with (cache / "refresh.lock").open("a") as lock: + fcntl.flock(lock, fcntl.LOCK_EX) + try: + for partial in (cache / "snapshots").glob(".download-*"): + if partial.is_dir() and not partial.is_symlink(): + shutil.rmtree(partial) + changed = synchronize(root, channel, force=force) + write_json(cache / f"{channel}-result.json", {"ok": True, "changed": changed}) + except Exception as error: + write_json(cache / f"{channel}-result.json", {"ok": False, "error": str(error)[:400]}) + finally: + prune_snapshots(root) + + +def visible_recipes(catalog): + """Same availability rule as the menu; count new IDs, not net growth.""" + result = {} + policies = {} + for item in catalog.load_index()["applications"]: + if item.get("hidden") or not item.get("automatic_install_candidate"): + continue + if item.get("_retained_recipe") and item.get("_retained_recipe_hash"): + result[item["id"]] = item["_retained_recipe_hash"] + continue + directory = catalog.root / "catalog" if item["id"] in catalog.local_app_ids else catalog.catalog_dir + if item.get("template_family") == "curated-profile": + path = directory / item["curated_path"].removeprefix("catalog/") + else: + path = directory / "apps" / f"{item['id']}.json" + overlay = directory / "overlays" / f"{item['id']}.json" + recipe = json.loads(path.read_text()) if path.exists() else {} + # Formatting and regenerated source timestamps are not recipe changes. + recipe.pop("source", None) + recipe.pop("validation", None) + correction = json.loads(overlay.read_text()) if overlay.exists() else {} + if directory not in policies: + policies[directory] = read_json(directory / "volume-policy.json") + policy = policies[directory] + paths = {volume.get("container_path") for volume in recipe.get("container_contract", {}).get("volumes", [])} + relevant = {"shared_paths": sorted(paths & set(policy.get("shared_paths", []))), + "application": policy.get("applications", {}).get(item["id"], []), + "sizes": policy.get("sizes", {}).get(item["id"], {})} + data = json.dumps([recipe, correction, relevant, item.get("category"), item.get("category_label")], + sort_keys=True, separators=(",", ":")).encode() + result[item["id"]] = hashlib.sha256(data).hexdigest() + return result + + +class CatalogRefresh: + def __init__(self, root, catalog): + self.root = Path(root) + self.channel = channel_for(root) + self.seen_path = cache_root(root) / f"{self.channel}-seen.json" + self.recipes = visible_recipes(catalog) + self.displayed_directory = catalog.catalog_dir + baseline = read_json(self.seen_path).get("recipes") + self.process = None + self.failed = False + self._finished = False + if isinstance(baseline, dict): + self.baseline = baseline + else: + # The first visit sets what counts as already known. + self.baseline = self.recipes.copy() + self.mark_seen() + + def __enter__(self): + self.start() + return self + + def start(self, force=False): + if self.active: + return False + self._finished = False + self.failed = False + environment = dict(os.environ, PYTHONPATH=str(self.root / "src")) + try: + command = [sys.executable, "-m", __name__, str(self.root), self.channel] + if force: + command.append("--force") + self.process = subprocess.Popen(command, + env=environment, stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, + start_new_session=True) + except OSError: + self.failed = True + self._finished = True + return False + return True + + def __exit__(self, *args): + # The worker can finish downloading after the user leaves. Its new + # applications stay new until the user opens their list. + if self.process and self.process.poll() is not None: + self.process.wait() + elif self.process: + threading.Thread(target=self.process.wait, daemon=True).start() + + @property + def active(self): + return self.process is not None and not self._finished + + def changed(self): + return self.active and self.process.poll() is not None + + def consume(self): + if not self.changed(): + return False + self._finished = True + result = read_json(cache_root(self.root) / f"{self.channel}-result.json") + self.failed = self.process.returncode != 0 or not result.get("ok") + directory = current_catalog(self.root, self.channel) + changed = directory != self.displayed_directory + if changed: + self.displayed_directory = directory + return changed + + def new_ids(self): + """The applications added since the user last opened the list of new ones.""" + return sorted(self.recipes.keys() - self.baseline.keys()) + + def label(self): + added = len(self.new_ids()) + return (translate("New applications ({count})").format(count=added) if added + else translate("Refresh catalog")) + + def wait(self): + if self.process is not None and not self._finished: + self.process.wait() + + def mark_seen(self): + """Opening the list of new applications makes them known.""" + self.baseline = self.recipes.copy() + try: + write_json(self.seen_path, {"recipes": self.recipes}) + except OSError: + pass + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("root", type=Path) + parser.add_argument("channel", choices=("main", "develop")) + parser.add_argument("--force", action="store_true") + args = parser.parse_args() + worker(args.root, args.channel, args.force) diff --git a/oci/src/proxmenux_oci/cli.py b/oci/src/proxmenux_oci/cli.py index 72767a75..76706924 100644 --- a/oci/src/proxmenux_oci/cli.py +++ b/oci/src/proxmenux_oci/cli.py @@ -531,6 +531,25 @@ def _app_list(catalog: Catalog, ui, applications: list[dict[str, Any]], title: s _app_detail(catalog, ui, index[selection]) +def _new_applications(catalog: Catalog, ui, applications: list[dict[str, Any]], catalog_refresh) -> bool: + """The applications added to the catalog since the user last opened this + list, ready to install. Returns whether a catalog refresh was asked for.""" + options, index, header = _app_menu(applications) + options = [("refresh", translate("Refresh catalog")), ("", "")] + options + title = translate("New applications ({count})").format(count=len(applications)) + # Opening the list makes them known: the entry stops counting them. + catalog_refresh.mark_seen() + selection = None + while True: + selection = ui.choose(header, options, selection, title=title, size=MENU_SIZE, colors=True) + if selection is None: + return False + if selection == "refresh": + return True + if selection in index: + _app_detail(catalog, ui, index[selection]) + + def _search(catalog: Catalog, ui, applications: list[dict[str, Any]]) -> None: query = ui.ask(translate("Name or part of the description of the application"), required=False).strip() if not query: @@ -557,13 +576,30 @@ def _search(catalog: Catalog, ui, applications: list[dict[str, Any]]) -> None: keep_order=True) -def interactive(catalog: Catalog) -> int: - from .ui import MenuRefresh - from .updates import UpdateCheck, select_updates - ui = interactive_ui() - if not catalog.index_path.exists(): - ui.message(translate("The OCI catalog is not installed. Update ProxMenux and try again.")) - return 1 +COUNTED_LABEL = re.compile(r"^(.*\S) \((\d+)\)$") + + +def _aligned(entries: list[tuple[str, str]]) -> list[tuple[str, str]]: + """The counts of the menu in one column, two spaces past the longest entry + that has one, whatever the language: "Updates available (28)" lines up + with "(554)".""" + counted = [COUNTED_LABEL.match(label) for _, label in entries] + width = max((len(match[1]) for match in counted if match), default=0) + 2 + return [(tag, f"{match[1]:<{width}}({int(match[2]):>3})" if match else label) + for (tag, label), match in zip(entries, counted)] + + +def _installed_count() -> int | None: + """How many OCI containers the manage list shows, read from their saved + records. None while another operation holds the registry.""" + from .management import saved_inventory + try: + return len(saved_inventory(PROJECT_ROOT)) + except (BlockingIOError, OSError, ValueError): + return None + + +def _catalog_options(catalog): applications = _installable(catalog.load_index()["applications"]) categories: dict[str, list[dict[str, Any]]] = {} labels: dict[str, str] = {} @@ -575,18 +611,50 @@ def interactive(catalog: Catalog) -> int: category_by_index = {str(number): key for number, key in enumerate(order, 1)} options = [ ("search", translate("Search applications")), - ("all", f"{translate('All applications'):<35} ({len(applications):>3})"), + ("all", f"{translate('All applications')} ({len(applications)})"), ("manage", translate("Manage installed OCI applications")), + ("refresh", translate("Refresh catalog")), ("registries", translate("Registry accounts")), ("custom", translate("Install an image that is not in the catalog")), ("", ""), - ] + [(number, f"{translate(labels[key]):<35} ({len(categories[key]):>3})") + ] + [(number, f"{translate(labels[key])} ({len(categories[key])})") for number, key in category_by_index.items()] + return applications, categories, labels, category_by_index, options + + +def interactive(catalog: Catalog) -> int: + from .ui import MenuRefresh + from .catalog_refresh import CatalogRefresh, visible_recipes + from .updates import UpdateCheck, select_updates + ui = interactive_ui() + if not catalog.index_path.exists(): + ui.message(translate("The OCI catalog is not installed. Update ProxMenux and try again.")) + return 1 + applications, categories, labels, category_by_index, options = _catalog_options(catalog) selection = "search" - with UpdateCheck(PROJECT_ROOT) as updates: + refresh_requested = False + with CatalogRefresh(PROJECT_ROOT, catalog) as catalog_refresh, UpdateCheck(PROJECT_ROOT) as updates: while True: + if catalog_refresh.consume(): + catalog = Catalog(PROJECT_ROOT) + applications, categories, labels, category_by_index, options = _catalog_options(catalog) + catalog_refresh.recipes = visible_recipes(catalog) + if refresh_requested: + # Only a refresh the user asked for says that it failed. + refresh_requested = False + if catalog_refresh.failed: + ui.message(translate("Catalog refresh unavailable; using the local catalog.")) state = updates.status() - entries = ReadableTags(options[:3] + [('updates', updates.label(state))] + options[3:]) + # The catalog entry names the new applications when there are some. + installed = _installed_count() + shown = [] + for tag, label in options: + if tag == "refresh": + label = catalog_refresh.label() + elif tag == "manage" and installed: + label = f"{label} ({installed})" + shown.append((tag, label)) + entries = ReadableTags(_aligned(shown[:3] + [('updates', updates.label(state))] + shown[3:])) text = translate("Select a category or search for applications:") if notice := updates.notice(state): text += '\n\n' + notice @@ -595,6 +663,8 @@ def interactive(catalog: Catalog) -> int: updates.wait() continue try: + # The catalog downloads behind the open menu without closing it: + # a new catalog is shown the next time the menu is drawn. kwargs = {'refresh': updates.changed} if updates.active else {} selected = ui.choose(text, entries, selection, size=MENU_SIZE, **kwargs) except MenuRefresh: @@ -611,6 +681,14 @@ def interactive(catalog: Catalog) -> int: elif selection == "manage": from .management import interactive_management interactive_management(PROJECT_ROOT, ui) + elif selection == "refresh": + new = [item for item in applications if item["id"] in set(catalog_refresh.new_ids())] + if not new or _new_applications(catalog, ui, new, catalog_refresh): + # Asked for: wait for it and say if it could not be done. + catalog_refresh.start(force=True) + ui.info(translate("Refreshing the application catalog...")) + catalog_refresh.wait() + refresh_requested = True elif selection == "updates": if not updates.active: if state['available']: diff --git a/oci/src/proxmenux_oci/converter.py b/oci/src/proxmenux_oci/converter.py index 2ed0c5fd..880ac085 100644 --- a/oci/src/proxmenux_oci/converter.py +++ b/oci/src/proxmenux_oci/converter.py @@ -187,6 +187,24 @@ def _split_short_mount(value: str) -> tuple[str | None, str, str | None]: return ":".join(parts[:-2]), parts[-2], parts[-1] +# Host device nodes a Compose file may bind as a volume. They are passed to +# the LXC as devices: a mount at that path would hide the device. +DEVICE_NODE_BINDS = ("/dev/net/tun", "/dev/fuse", "/dev/kvm") + + +def _device_bind(item: Any) -> str | None: + """The `source:target` of a volume that binds a host device node.""" + if isinstance(item, dict): + if item.get("type", "bind") != "bind": + return None + source, target = item.get("source"), item.get("target") + else: + source, target, _ = _split_short_mount(str(item)) + if str(source or "") in DEVICE_NODE_BINDS and str(target or "") == str(source): + return f"{source}:{target}" + return None + + def _mount_contract(value: Any, optional: set[str]) -> list[dict[str, Any]]: if value is None: return [] @@ -194,6 +212,8 @@ def _mount_contract(value: Any, optional: set[str]) -> list[dict[str, Any]]: raise ConversionError("volumes must be a list") result: list[dict[str, Any]] = [] for index, item in enumerate(value): + if _device_bind(item): + continue if isinstance(item, dict): source = item.get("source") target = item.get("target") @@ -606,6 +626,10 @@ def _device_installer_profile( ) -> tuple[dict[str, Any], list[str]]: optional = optional or set() raw_devices = service.get("devices") + volumes = service.get("volumes") + device_binds = [bind for bind in map(_device_bind, volumes) if bind] if isinstance(volumes, list) else [] + if device_binds and (raw_devices is None or isinstance(raw_devices, list)): + raw_devices = [*(raw_devices or []), *device_binds] requests: list[dict[str, Any]] = [] blockers: list[str] = [] nvidia_requested = _nvidia_requested(service) diff --git a/oci/src/proxmenux_oci/gpu.py b/oci/src/proxmenux_oci/gpu.py index f9a7c851..c7d4426b 100644 --- a/oci/src/proxmenux_oci/gpu.py +++ b/oci/src/proxmenux_oci/gpu.py @@ -70,7 +70,12 @@ def apply_gpu_contract(template: dict[str, Any]) -> None: def apply_selkies_contract(template): profile = template['proxmox']['installer_profile'] - if not template['container_contract']['image']['reference'].startswith('lscr.io/linuxserver/'): + # A LinuxServer image, or one whose reviewed Dockerfile is built on the + # LinuxServer Selkies base and so has the same init and GPU variables. + built_on_base = str(profile['selkies'].get('base', '')).startswith( + 'FROM ghcr.io/linuxserver/baseimage-selkies:') + if not (template['container_contract']['image']['reference'].startswith('lscr.io/linuxserver/') + or built_on_base): raise ValueError(translate('The Selkies profile requires a verified LinuxServer image')) environment = template['container_contract']['environment'] if not any(e['name'] == 'LC_ALL' for e in environment): diff --git a/oci/src/proxmenux_oci/installer.py b/oci/src/proxmenux_oci/installer.py index 72f0c284..96d47bd6 100644 --- a/oci/src/proxmenux_oci/installer.py +++ b/oci/src/proxmenux_oci/installer.py @@ -962,15 +962,14 @@ def _ask_immich_acceleration(ui, rootfs_storage: str | None = None) -> tuple[str return software # Recognition on an AMD GPU depends on ROCm: it is offered when the host # can run it, marked as experimental on a GPU ROCm does not support - # officially, and left out, with the reason, when it cannot. + # officially, and left out when it cannot. A GPU of a generation ROCm does + # not run is simply not offered; what the host can fix is explained. amd_blocker = host.rocm_blocker(rootfs_storage) if found["amd"] else None amd_experimental = (found["amd"] and not amd_blocker and host.rocm_support(found.get("amd_gfx_target")) == "experimental") - if amd_blocker: + if amd_blocker in ("kfd", "space"): reason = (translate("The AMD driver does not offer its compute interface (/dev/kfd) on this host.") if amd_blocker == "kfd" else - f"{translate('The ROCm image has no support for the AMD GPU of this host:')} {host.amd_gpu_name()}." - if amd_blocker == "generation" else translate("The storage has less than 40 GB free for the ROCm image.")) real.message(f"{reason}\n\n{translate('Recognition is not offered on the AMD GPU; video transcoding is.')}") mark = f" — {translate('experimental on this GPU')}" if amd_experimental else "" @@ -1916,11 +1915,6 @@ def configure_acceleration(installer_profile, environment, unprivileged, ui, mod found.get("amd_gfx_target"), tuple(item["amd_gfx_targets"])) == "experimental"} options = [(item["id"], item["label"]) for item in usable] asked = advanced or not installer_profile.get("selkies") - if asked and found["amd"] and any(item.get("amd_gfx_targets") and item not in usable and host.rocm_support( - found.get("amd_gfx_target"), tuple(item["amd_gfx_targets"])) is None for item in profiles): - # Its owner may expect the GPU to be offered: say why it is not. - ui.message(f"{translate('The ROCm image of this application has no support for the AMD GPU of this host:')} " - f"{host.amd_gpu_name()}. {translate('Its ROCm profile is not offered.')}") if asked and len(usable) < len(profiles) and len(usable) == 1: # Nothing but the CPU is left: say why there is nothing to choose. ui.message(translate("No usable GPU was found on this host. The application will be installed " diff --git a/oci/tests/test_airdcpp_recipe.py b/oci/tests/test_airdcpp_recipe.py new file mode 100644 index 00000000..67b4beed --- /dev/null +++ b/oci/tests/test_airdcpp_recipe.py @@ -0,0 +1,61 @@ +"""The publisher contract survives native catalog composition and both wizards.""" +from pathlib import Path +import json +import sys +import unittest +from unittest.mock import patch + +ROOT = Path(__file__).resolve().parents[1] +sys.path[:0] = [str(ROOT / 'src'), str(ROOT / 'remote'), str(ROOT / 'tests')] +from proxmenux_oci.catalog import Catalog +from proxmenux_oci.custom import template_from_oci_compose +from proxmenux_oci.installer import build_deployment, DEFAULT_MODE, ADVANCED_MODE +from test_advanced_flow_order import RecordingUI, storages, addresses + + +class AirDCppRecipeTests(unittest.TestCase): + def test_import_roundtrip_keeps_udp_and_all_persistent_paths(self): + catalog = Catalog(ROOT) + template = catalog.compose('airdcpp-webclient') + restored = template_from_oci_compose(json.dumps(template)) + catalog.validate(restored) + ports = {(p['container_port'], p['protocol']) for p in restored['container_contract']['ports']} + self.assertEqual(ports, {(5600,'tcp'),(5601,'tcp'),(21248,'tcp'),(21248,'udp'),(21249,'tcp')}) + paths = {v['container_path']:v for v in restored['container_contract']['volumes']} + self.assertEqual(set(paths), {'/.airdcpp','/Downloads','/Share'}) + self.assertEqual(paths['/.airdcpp']['installation_choice'], ['managed-volume']) + for path in ('/Downloads','/Share'): + self.assertIn('host-bind', paths[path]['installation_choice']) + self.assertEqual(restored['catalog_ui']['architectures'], ['amd64']) + self.assertEqual(restored['proxmox']['installer_profile']['startup_healthcheck']['path'], '/index.html') + self.assertEqual(restored['proxmox']['image_metadata_policy']['entrypoint'], 'import-from-oci-image') + + def test_wizards_preserve_managed_configuration_and_use_selected_shared_directories(self): + from contextlib import ExitStack + with ExitStack() as stack: + for target, value in [ + ('proxmenux_oci.installer.host.storages', storages), + ('proxmenux_oci.installer.access.ask_addresses', addresses)]: + stack.enter_context(patch(target, side_effect=value)) + stack.enter_context(patch('proxmenux_oci.i18n.language', return_value='en')) + stack.enter_context(patch('proxmenux_oci.installer.host.bridges',return_value=[{'iface':'vmbr0','cidr':'192.0.2.10/24'}])) + stack.enter_context(patch('proxmenux_oci.installer.host.timezone',return_value='Europe/Madrid')) + stack.enter_context(patch('proxmenux_oci.installer.host.gpus',return_value={'intel':[],'amd':[],'nvidia':False})) + stack.enter_context(patch('proxmenux_oci.installer.access.ask_ipv4',return_value=('dhcp',None))) + for mode in (DEFAULT_MODE, ADVANCED_MODE): + with self.subTest(mode=mode): + ui = RecordingUI({'Where to store /Downloads':'host-bind','Where to store /Share':'host-bind'}) + plan = build_deployment(Catalog(ROOT).compose('airdcpp-webclient'), ui, mode) + mounts = {item['container_path']:item for item in plan['mounts']} + self.assertEqual(mounts['/.airdcpp']['type'], 'managed-volume') + self.assertTrue(mounts['/.airdcpp']['backup']) + self.assertEqual(mounts['/Downloads']['type'], 'host-bind') + self.assertEqual(mounts['/Share']['type'], 'host-bind') + env = {item['name']:item['value'] for item in plan['environment']} + self.assertEqual((env['PUID'],env['PGID']),('1000','1000')) + self.assertEqual(plan['resources']['cores'],2) + self.assertTrue(plan['security']['unprivileged']) + + +if __name__ == '__main__': + unittest.main() diff --git a/oci/tests/test_artcraft_suite_recipes.py b/oci/tests/test_artcraft_suite_recipes.py new file mode 100644 index 00000000..431f1a6e --- /dev/null +++ b/oci/tests/test_artcraft_suite_recipes.py @@ -0,0 +1,65 @@ +"""Shared files, image identity and desktop runtime survive catalog composition.""" +import json +from pathlib import Path +import sys +import unittest +from unittest.mock import patch +ROOT=Path(__file__).resolve().parents[1] +sys.path[:0]=[str(ROOT/'src'),str(ROOT/'remote'),str(ROOT/'tests')] +from proxmenux_oci.catalog import Catalog +from proxmenux_oci.custom import template_from_oci_compose +from proxmenux_oci.installer import build_deployment,DEFAULT_MODE,ADVANCED_MODE +from test_advanced_flow_order import RecordingUI,storages,addresses +APPS=('cadcraft','deckcraft','designcraft','effectcraft','figcraft','filmcraft','gridcraft','lightcraft','pdfcraft','photocraft','soundcraft','vectorcraft','wordcraft') + +class ArtCraftRecipeTests(unittest.TestCase): + def test_native_images_remain_distinct_from_flatpak_and_wasm_builds(self): + catalog=Catalog(ROOT) + for app in APPS: + with self.subTest(app=app): + t=catalog.compose(app);catalog.validate(t) + self.assertEqual(t['container_contract']['image']['reference'],'docker.io/junkerderprovinz/'+app+':latest') + self.assertEqual(t['catalog_ui']['author'],'junkerderprovinz') + self.assertEqual(t['catalog_ui']['repository'],'https://github.com/storytold/'+app) + self.assertEqual(t['catalog_ui']['architectures'],['amd64','arm64']) + self.assertEqual(t['proxmox']['image_metadata_policy']['entrypoint'],'import-from-oci-image') + self.assertTrue(t['catalog_ui']['icon'].endswith('/'+app+'.png')) + self.assertFalse(t['compatibility']['validated']) + + def test_import_preserves_document_storage_backup_and_runtime_mounts(self): + catalog=Catalog(ROOT) + for app in APPS: + with self.subTest(app=app): + t=template_from_oci_compose(json.dumps(catalog.compose(app)));catalog.validate(t) + volumes={m['container_path']:m for m in t['container_contract']['volumes']} + self.assertEqual(set(volumes),{'/config','/storage'}) + self.assertEqual(volumes['/config']['installation_choice'],['managed-volume']) + self.assertEqual(volumes['/storage']['installation_choice'],['managed-volume','host-bind']) + self.assertTrue(all(m['managed_volume']['backup'] and not m['read_only'] for m in volumes.values())) + runtime={m['container_path']:m for m in t['proxmox']['installer_profile']['tmpfs_mounts']} + self.assertEqual(runtime['/dev/shm']['default_size_mb'],1024) + self.assertIn('/run/nginx',runtime) + self.assertEqual(t['proxmox']['installer_profile']['startup_healthcheck']['port'],3000) + self.assertEqual(t['catalog_ui']['launch']['port'],3001) + + def test_both_wizards_offer_shared_documents_without_exposing_private_config(self): + from contextlib import ExitStack + with ExitStack() as stack: + for target,value in [('proxmenux_oci.installer.host.storages',storages),('proxmenux_oci.installer.access.ask_addresses',addresses)]:stack.enter_context(patch(target,side_effect=value)) + stack.enter_context(patch('proxmenux_oci.i18n.language',return_value='en')) + stack.enter_context(patch('proxmenux_oci.installer.host.bridges',return_value=[{'iface':'vmbr0','cidr':'192.0.2.10/24'}])) + stack.enter_context(patch('proxmenux_oci.installer.host.timezone',return_value='Europe/Madrid')) + stack.enter_context(patch('proxmenux_oci.installer.host.gpus',return_value={'intel':[],'amd':[],'nvidia':False})) + stack.enter_context(patch('proxmenux_oci.installer.access.ask_ipv4',return_value=('dhcp',None))) + for app in APPS: + for mode in (DEFAULT_MODE,ADVANCED_MODE): + with self.subTest(app=app,mode=mode): + plan=build_deployment(Catalog(ROOT).compose(app),RecordingUI({'Where to store /storage':'host-bind'}),mode) + mounts={m['container_path']:m for m in plan['mounts']} + self.assertEqual(mounts['/config']['type'],'managed-volume') + self.assertTrue(mounts['/config']['backup']) + self.assertEqual(mounts['/storage']['type'],'host-bind') + self.assertFalse(mounts['/storage']['backup']) + self.assertTrue(plan['security']['unprivileged']) + +if __name__=='__main__':unittest.main() diff --git a/oci/tests/test_authelia_recipe.py b/oci/tests/test_authelia_recipe.py new file mode 100644 index 00000000..a061cce3 --- /dev/null +++ b/oci/tests/test_authelia_recipe.py @@ -0,0 +1,99 @@ +"""The Authelia profile bootstraps a usable single-image authentication portal.""" + +import json +from pathlib import Path +import sys +import unittest + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "src")) + +from proxmenux_oci.catalog import Catalog + + +class AutheliaRecipeTests(unittest.TestCase): + def setUp(self): + self.path = ROOT / "catalog/curated/authelia.json" + self.recipe = json.loads(self.path.read_text(encoding="utf-8")) + self.index_copy = json.loads( + (ROOT / "catalog/apps/authelia.json").read_text(encoding="utf-8") + ) + self.profile = self.recipe["proxmox"]["installer_profile"] + self.generated = { + item["container_path"]: item + for item in self.profile["generated_files"] + } + + def test_local_catalog_lists_and_loads_the_curated_recipe(self): + catalog = Catalog(ROOT) + item = catalog.find_item("authelia") + self.assertEqual(item["template_family"], "curated-profile") + self.assertEqual(catalog.compose("authelia")["id"], "image-authelia") + self.assertEqual(self.recipe, self.index_copy) + + def test_official_image_tag_icon_and_supported_architectures_are_declared(self): + self.assertEqual( + self.recipe["container_contract"]["image"]["reference"], + "docker.io/authelia/authelia:4.39.28", + ) + self.assertEqual(self.recipe["catalog_ui"]["architectures"], ["amd64", "arm64"]) + self.assertEqual( + self.recipe["catalog_ui"]["icon"], + "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/authelia.webp", + ) + + def test_sensitive_secrets_are_generated_and_domain_settings_are_prompted(self): + environment = { + item["name"]: item + for item in self.recipe["container_contract"]["environment"] + } + for name in ( + "PROXMENUX_ADMIN_PASSWORD", + "AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET", + "AUTHELIA_SESSION_SECRET", + "AUTHELIA_STORAGE_ENCRYPTION_KEY", + ): + self.assertTrue(environment[name]["sensitive"]) + self.assertEqual(self.profile["generated_sensitive_environment"][name]["strategy"], "token-hex") + self.assertTrue(environment["PROXMENUX_AUTHELIA_COOKIE_DOMAIN"]["required"]) + self.assertTrue(environment["PROXMENUX_AUTHELIA_URL"]["required"]) + self.assertIn("cookie domain", environment["PROXMENUX_AUTHELIA_COOKIE_DOMAIN"]["prompt"]) + self.assertIn("HTTPS URL", environment["PROXMENUX_AUTHELIA_URL"]["prompt"]) + self.assertEqual(environment["PROXMENUX_AUTHELIA_COOKIE_DOMAIN"]["example"], "") + self.assertEqual(environment["PROXMENUX_AUTHELIA_URL"]["example"], "") + self.assertEqual(environment["X_AUTHELIA_CONFIG_FILTERS"]["example"], "template") + + def test_config_is_persistent_and_has_a_working_initial_policy(self): + config = self.generated["/config/configuration.yml"]["content"] + self.assertIn("path: '/config/users_database.yml'", config) + self.assertIn("path: '/config/db.sqlite3'", config) + self.assertIn("filename: '/config/notification.txt'", config) + self.assertIn("default_policy: 'one_factor'", config) + self.assertIn('{{ env \"PROXMENUX_AUTHELIA_COOKIE_DOMAIN\" }}', config) + self.assertIn('{{ env \"PROXMENUX_AUTHELIA_URL\" }}', config) + self.assertTrue(self.generated["/config/configuration.yml"]["only_if_missing"]) + volume = self.recipe["container_contract"]["volumes"][0] + self.assertEqual(volume["container_path"], "/config") + self.assertEqual(volume["installation_choice"], ["managed-volume"]) + self.assertTrue(volume["managed_volume"]["backup"]) + + def test_bootstrap_hashes_initial_password_and_never_ships_a_default_password(self): + bootstrap = self.generated["/usr/local/bin/proxmenux-authelia-bootstrap"]["content"] + self.assertIn("authelia crypto hash generate argon2", bootstrap) + self.assertIn("unset PROXMENUX_ADMIN_PASSWORD", bootstrap) + self.assertIn("exec /app/entrypoint.sh", bootstrap) + self.assertNotIn("password: authelia", bootstrap) + self.assertEqual( + self.recipe["first_run"]["credentials"][0]["password_environment"], + "PROXMENUX_ADMIN_PASSWORD", + ) + + def test_healthcheck_uses_authelias_documented_endpoint_and_profile_remains_unvalidated(self): + healthcheck = self.profile["startup_healthcheck"] + self.assertEqual((healthcheck["port"], healthcheck["path"]), (9091, "/api/health")) + self.assertFalse(self.recipe["compatibility"]["validated"]) + self.assertEqual(self.recipe["status"], "generated-unvalidated") + + +if __name__ == "__main__": + unittest.main() diff --git a/oci/tests/test_catalog_new_apps_entry.py b/oci/tests/test_catalog_new_apps_entry.py new file mode 100644 index 00000000..aa536dfb --- /dev/null +++ b/oci/tests/test_catalog_new_apps_entry.py @@ -0,0 +1,91 @@ +"""The catalog entry of the OCI menu: it names the new applications and leads +to them; a refresh the user asks for says when it could not be done.""" +from pathlib import Path +import sys +import unittest +from unittest.mock import Mock, patch + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / 'src')) + +from proxmenux_oci import cli + + +APPS = [{'id': 'alpha', 'title': 'Alpha', 'architectures': ['amd64']}, + {'id': 'beta', 'title': 'Beta', 'architectures': ['amd64']}] + + +class NewApplicationsListTests(unittest.TestCase): + def test_opening_the_list_acknowledges_the_new_apps_and_offers_them(self): + refresh = Mock() + ui = Mock() + ui.choose.side_effect = ['1', None] + with patch.object(cli, '_app_detail') as detail: + self.assertFalse(cli._new_applications(Mock(), ui, APPS, refresh)) + refresh.mark_seen.assert_called_once_with() + detail.assert_called_once() + options = ui.choose.call_args_list[0].args[1] + self.assertEqual(options[0], ('refresh', 'Refresh catalog')) + self.assertIn('New applications (2)', ui.choose.call_args_list[0].kwargs['title']) + + def test_its_refresh_line_asks_for_a_refresh(self): + ui = Mock() + ui.choose.return_value = 'refresh' + self.assertTrue(cli._new_applications(Mock(), ui, APPS, Mock())) + + +class ManualRefreshTests(unittest.TestCase): + def run_menu(self, failed): + status = Mock(failed=failed) + status.consume.return_value = False + status.new_ids.return_value = [] + status.label.return_value = 'Refresh catalog' + context = Mock() + context.__enter__ = Mock(return_value=status) + context.__exit__ = Mock(return_value=False) + checker = Mock(active=False) + checker.status.return_value = {'available': []} + checker.notice.return_value = '' + checker.label.return_value = 'Check for updates' + checker_context = Mock() + checker_context.__enter__ = Mock(return_value=checker) + checker_context.__exit__ = Mock(return_value=False) + ui = Mock(live_refresh=True) + ui.choose.side_effect = ['refresh', None] + catalog = Mock() + catalog.index_path.exists.return_value = True + catalog.load_index.return_value = {'applications': []} + with patch('proxmenux_oci.catalog_refresh.CatalogRefresh', return_value=context), \ + patch('proxmenux_oci.updates.UpdateCheck', return_value=checker_context), \ + patch.object(cli, 'interactive_ui', return_value=ui): + self.assertEqual(cli.interactive(catalog), 0) + status.start.assert_called_once_with(force=True) + status.wait.assert_called_once_with() + return [call.args[0] for call in ui.message.call_args_list] + + def test_a_failed_refresh_asked_for_is_reported(self): + self.assertEqual(self.run_menu(True), ['Catalog refresh unavailable; using the local catalog.']) + + def test_a_successful_one_says_nothing(self): + self.assertEqual(self.run_menu(False), []) + + +class AlignedCountsTests(unittest.TestCase): + def test_counts_line_up_two_spaces_past_the_longest_counted_entry(self): + entries = cli._aligned([('all', 'All applications (554)'), + ('manage', 'Gestionar aplicaciones OCI instaladas (32)'), + ('updates', 'Checking for updates (3/28)'), ('1', 'DNS (2)')]) + self.assertEqual(entries[0][1], f"{'All applications':<39}(554)") + self.assertEqual(entries[1][1], 'Gestionar aplicaciones OCI instaladas ( 32)') + self.assertEqual(entries[2][1], 'Checking for updates (3/28)') # progress keeps its form + self.assertEqual(entries[3][1], f"{'DNS':<39}( 2)") + + def test_the_installed_count_is_left_out_while_the_registry_is_busy(self): + with patch('proxmenux_oci.management.saved_inventory', side_effect=BlockingIOError): + self.assertIsNone(cli._installed_count()) + with patch('proxmenux_oci.management.saved_inventory', return_value=[{}, {}]): + self.assertEqual(cli._installed_count(), 2) + + +if __name__ == '__main__': + unittest.main() diff --git a/oci/tests/test_catalog_refresh.py b/oci/tests/test_catalog_refresh.py new file mode 100644 index 00000000..fdb049bc --- /dev/null +++ b/oci/tests/test_catalog_refresh.py @@ -0,0 +1,750 @@ +"""Catalog delivery is atomic, offline-safe, and never acknowledges unseen apps.""" +import json +import gc +import errno +import http.server +import itertools +from pathlib import Path +import shutil +import sys +import tempfile +import threading +import time +import unittest +import urllib.error +import urllib.parse +from unittest.mock import Mock, patch + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "src")) +from proxmenux_oci.catalog import Catalog +from proxmenux_oci import catalog_refresh as refresh + + +class CatalogRefreshTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name) / "oci" / "engine" + self.root.mkdir(parents=True) + shutil.copytree(ROOT / "schemas", self.root / "schemas") + index = json.loads((ROOT / "catalog/index.json").read_text()) + self.item = next(x for x in index["applications"] + if x.get("template_family") == "curated-profile" + and x.get("automatic_install_candidate") and not x.get("hidden")) + self.files = {} + for name in ("categories.json", "volume-policy.json", "exclusions.json", "verification.json"): + self.files[name] = (ROOT / "catalog" / name).read_bytes() + for name in (f'apps/{self.item["id"]}.json', self.item['curated_path'].removeprefix("catalog/")): + self.files[name] = (ROOT / "catalog" / name).read_bytes() + self.files["index.json"] = json.dumps({"kind": "proxmenux.oci-catalog-index", + "applications": [self.item]}).encode() + for name, data in self.files.items(): + file = self.root / "catalog" / name + file.parent.mkdir(parents=True, exist_ok=True) + file.write_bytes(data) + self.revision = "a" * 40 + self.requests = [] + + def fetch(self, url, etag=None): + self.requests.append((url, etag)) + if "/commits?" in url: + return json.dumps([{"sha": self.revision}]).encode(), '"catalog-etag"' + if "/git/trees/" in url: + entries = [{"path": "oci/catalog/" + name, "type": "blob", "mode": "100644", + "sha": refresh.git_blob(data), "size": len(data)} + for name, data in self.files.items()] + schema = (self.root / "schemas/oci-template.schema.json").read_bytes() + entries.append({"path": "oci/schemas/oci-template.schema.json", "type": "blob", + "sha": refresh.git_blob(schema)}) + return json.dumps({"tree": entries, "truncated": False}).encode(), None + name = url.split("/oci/catalog/", 1)[1] + return self.files[name], None + + def sync(self): + return refresh.synchronize(self.root, "main", self.fetch) + + def add_app(self): + item = dict(self.item, id="sample-added", template_id="image-sample-added", + curated_path="catalog/curated/sample-added.json", template="apps/sample-added.json") + recipe = json.loads(self.files[self.item["curated_path"].removeprefix("catalog/")]) + recipe["id"] = item["template_id"] + for name in ("apps/sample-added.json", "curated/sample-added.json"): + self.files[name] = json.dumps(recipe).encode() + index = json.loads(self.files["index.json"]) + index["applications"].append(item) + self.files["index.json"] = json.dumps(index).encode() + + def future_profile(self, app_id): + index = json.loads(self.files["index.json"]) + item = next(x for x in index["applications"] if x["id"] == app_id) + path = item["curated_path"].removeprefix("catalog/") + recipe = json.loads(self.files[path]) + recipe.setdefault("proxmox", {}).setdefault("installer_profile", {})["future_engine_key"] = True + self.files[path] = json.dumps(recipe).encode() + + def test_new_unknown_profile_key_is_silent_and_not_counted(self): + self.sync() + baseline = Catalog(self.root) + status = refresh.CatalogRefresh(self.root, baseline) + self.revision = "b" * 40 + self.add_app() + self.future_profile("sample-added") + self.assertTrue(self.sync()) + catalog = Catalog(self.root) + self.assertNotIn("sample-added", refresh.visible_recipes(catalog)) + self.assertEqual(set(refresh.visible_recipes(catalog)), set(status.recipes)) + status.recipes = refresh.visible_recipes(catalog) + self.assertEqual(status.new_ids(), []) + + def test_compatibility_gate_uses_effective_menu_visibility(self): + self.add_app() + self.future_profile("sample-added") + index = json.loads(self.files["index.json"]) + added = next(x for x in index["applications"] if x["id"] == "sample-added") + added.update(hidden=True, automatic_install_candidate=False) + self.files["index.json"] = json.dumps(index).encode() + # The app template makes this stale index entry visible/installable; + # use the same rule as Catalog.load_index(), not the raw index flags. + self.sync() + self.assertNotIn("sample-added", refresh.visible_recipes(Catalog(self.root))) + + def test_incompatible_existing_app_retains_complete_previous_contract(self): + self.sync() + old = Catalog(self.root).compose(self.item["id"]) + old_visible = refresh.visible_recipes(Catalog(self.root)) + self.revision = "b" * 40 + self.future_profile(self.item["id"]) + # A new policy must not mutate the retained contract. + policy = json.loads(self.files["volume-policy.json"]) + policy["shared_paths"] = [v["container_path"] for v in old["container_contract"].get("volumes", [])] + self.files["volume-policy.json"] = json.dumps(policy).encode() + self.sync() + self.assertEqual(Catalog(self.root).compose(self.item["id"]), old) + self.assertEqual(refresh.visible_recipes(Catalog(self.root)), old_visible) + # A later incompatible revision still retains the same original contract. + self.revision = "c" * 40 + self.sync() + self.assertEqual(Catalog(self.root).compose(self.item["id"]), old) + + def test_engine_capability_update_rechecks_same_revision_without_etag(self): + self.add_app() + self.future_profile("sample-added") + self.sync() + self.assertNotIn("sample-added", refresh.visible_recipes(Catalog(self.root))) + bundled = self.root / "catalog" / self.item["curated_path"].removeprefix("catalog/") + value = json.loads(bundled.read_text()) + value.setdefault("proxmox", {}).setdefault("installer_profile", {})["future_engine_key"] = True + bundled.write_text(json.dumps(value)) + self.requests.clear() + self.assertTrue(self.sync()) + self.assertIsNone(self.requests[0][1]) + self.assertIn("sample-added", refresh.visible_recipes(Catalog(self.root))) + + def test_known_profile_key_accepts_new_values(self): + self.add_app() + path = "curated/sample-added.json" + value = json.loads(self.files[path]) + self.assertIn("completion_notes", refresh.supported_profile_keys(self.root)) + value["proxmox"]["installer_profile"]["completion_notes"].append("A new supported setting") + self.files[path] = json.dumps(value).encode() + self.sync() + self.assertIn("sample-added", refresh.visible_recipes(Catalog(self.root))) + + def test_all_incompatible_apps_can_produce_empty_visible_catalog(self): + self.future_profile(self.item["id"]) + # No compatible old recipe exists on this host. + path = self.root / "catalog" / self.item["curated_path"].removeprefix("catalog/") + value = json.loads(path.read_text()) + value["proxmox"]["installer_profile"].pop("future_engine_key", None) + # Use a new ID instead of retaining the bundled application. + self.add_app() + self.future_profile("sample-added") + index = json.loads(self.files["index.json"]) + index["applications"] = [x for x in index["applications"] if x["id"] == "sample-added"] + self.files["index.json"] = json.dumps(index).encode() + self.sync() + self.assertNotEqual(refresh.current_catalog(self.root), self.root / "catalog") + self.assertEqual(refresh.visible_recipes(Catalog(self.root)), {}) + + def test_pruning_preserves_active_and_leased_snapshots_then_reclaims_reader(self): + self.sync() + old = Catalog(self.root) + old_directory = old.catalog_dir + self.revision = "b" * 40 + self.add_app() + self.sync() + refresh.prune_snapshots(self.root) + self.assertTrue(old_directory.exists()) + self.assertEqual(old.compose(self.item["id"])["id"], self.item["template_id"]) + del old + gc.collect() + refresh.prune_snapshots(self.root) + self.assertFalse(old_directory.exists()) + self.assertTrue(refresh.current_catalog(self.root).exists()) + + def test_pruning_preserves_both_channels_and_unlinks_orphan_lease(self): + self.sync() + main = refresh.current_catalog(self.root, "main") + self.revision = "b" * 40 + refresh.synchronize(self.root, "develop", self.fetch) + orphan = refresh.cache_root(self.root) / "snapshots" / ("c" * 40) + orphan.mkdir() + refresh.prune_snapshots(self.root) + self.assertTrue(main.exists()) + self.assertTrue(refresh.current_catalog(self.root, "develop").exists()) + self.assertFalse(orphan.exists()) + self.assertFalse((refresh.cache_root(self.root) / "leases" / orphan.name).exists()) + + def test_worker_prunes_even_when_no_change_or_network_failure(self): + self.sync() + for error in (None, OSError("offline")): + orphan = refresh.cache_root(self.root) / "snapshots" / ("c" * 40) + orphan.mkdir() + with patch.object(refresh, "synchronize", side_effect=error, return_value=False): + refresh.worker(self.root, "main") + self.assertFalse(orphan.exists()) + + def test_reader_process_exit_releases_lease_without_pid_cleanup(self): + import subprocess + self.sync() + directory = refresh.current_catalog(self.root) + code = ("import fcntl,sys,time; " + "f=open(sys.argv[1], 'a'); fcntl.flock(f,fcntl.LOCK_SH); " + "print('ready',flush=True); time.sleep(30)") + leases = refresh.cache_root(self.root) / "leases" + leases.mkdir(exist_ok=True) + child = subprocess.Popen([sys.executable, "-c", code, str(leases / directory.name)], + stdout=subprocess.PIPE, text=True) + self.addCleanup(child.stdout.close) + self.addCleanup(lambda: child.poll() is None and child.kill()) + self.assertEqual(child.stdout.readline().strip(), "ready") + self.revision = "b" * 40 + self.sync() + refresh.prune_snapshots(self.root) + self.assertTrue(directory.exists()) + child.kill() + child.wait(timeout=3) + refresh.prune_snapshots(self.root) + self.assertFalse(directory.exists()) + + def test_menu_pin_does_not_wait_for_network_worker_lock(self): + self.sync() + lock = (refresh.cache_root(self.root) / "refresh.lock").open("a") + self.addCleanup(lock.close) + refresh.fcntl.flock(lock, refresh.fcntl.LOCK_EX) + catalog = Catalog(self.root) + self.assertEqual(catalog.compose(self.item["id"])["id"], self.item["template_id"]) + + def test_complete_catalog_is_selected_atomically_without_redownloading_unchanged_files(self): + self.assertTrue(self.sync()) + directory = refresh.current_catalog(self.root, "main") + self.assertEqual(directory.name, self.revision) + self.assertEqual(Catalog(self.root).compose(self.item["id"])["id"], self.item["template_id"]) + self.assertFalse(any("raw.githubusercontent" in url for url, _ in self.requests)) + self.assertTrue((self.root / "catalog/index.json").exists()) + + def test_changed_file_uses_immutable_commit_and_updates_recipe(self): + name = self.item["curated_path"].removeprefix("catalog/") + recipe = json.loads(self.files[name]); recipe["catalog_ui"]["tips"].append("New setup tip") + self.files[name] = json.dumps(recipe).encode() + self.sync() + self.assertTrue(any(f"/{self.revision}/oci/catalog/{name}" in u for u, _ in self.requests)) + self.assertIn("New setup tip", Catalog(self.root).compose(self.item["id"])["catalog_ui"]["tips"]) + + def test_unchanged_revision_does_not_fetch_tree_or_recipes(self): + self.sync(); self.requests.clear() + self.assertFalse(self.sync()) + self.assertEqual(len(self.requests), 1) + self.assertEqual(self.requests[0][1], '"catalog-etag"') + + def test_not_modified_uses_cached_snapshot(self): + self.sync() + fetch = Mock(return_value=(None, '"catalog-etag"')) + self.assertFalse(refresh.synchronize(self.root, "main", fetch)) + self.assertEqual(fetch.call_count, 1) + + def test_formatting_and_unrelated_categories_do_not_count_as_recipe_updates(self): + before = refresh.visible_recipes(Catalog(self.root)) + for name in self.files: + path = self.root / 'catalog' / name + value = json.loads(path.read_text()) + if name == 'categories.json': + value['applications']['unrelated-app'] = 'misc' + path.write_text(json.dumps(value, indent=4) + '\n') + self.assertEqual(refresh.visible_recipes(Catalog(self.root)), before) + + def test_failure_keeps_previous_catalog_and_does_not_commit_remote_etag(self): + self.sync() + old = refresh.current_catalog(self.root) + self.revision = "b" * 40 + self.files[self.item["curated_path"].removeprefix("catalog/")] = b'{}' + with self.assertRaises(Exception): + self.sync() + self.assertEqual(refresh.current_catalog(self.root), old) + self.assertFalse((refresh.cache_root(self.root) / "snapshots" / self.revision).exists()) + + def test_missing_recipe_cannot_publish_index_alone(self): + self.files.pop(self.item["curated_path"].removeprefix("catalog/")) + with self.assertRaises(Exception): + self.sync() + self.assertEqual(refresh.current_catalog(self.root), self.root / "catalog") + + def test_hash_mismatch_is_rejected(self): + real = self.fetch + def corrupt(url, etag=None): + body, tag = real(url, etag) + if "/git/trees/" in url: + tree = json.loads(body) + tree["tree"][0]["sha"] = "f" * 40 + body = json.dumps(tree).encode() + return body, tag + with self.assertRaisesRegex(ValueError, "integrity"): + refresh.synchronize(self.root, "main", corrupt) + self.assertEqual(refresh.current_catalog(self.root), self.root / "catalog") + + def test_schema_mismatch_requires_engine_update(self): + real = self.fetch + def incompatible(url, etag=None): + body, tag = real(url, etag) + if "/git/trees/" in url: + tree = json.loads(body); tree["tree"][-1]["sha"] = "f" * 40 + body = json.dumps(tree).encode() + return body, tag + with self.assertRaisesRegex(ValueError, "schema changed"): + refresh.synchronize(self.root, "main", incompatible) + + def test_new_engine_api_requires_engine_update(self): + self.files["compatibility.json"] = b'{"engine_api": 999}' + with self.assertRaisesRegex(ValueError, "newer OCI engine"): + self.sync() + + def test_paths_and_symlink_entries_are_rejected(self): + for name in ("../index.json", "/index.json", "apps/../../secret.json", "remote/install.sh"): + with self.subTest(name=name), self.assertRaises(ValueError): + refresh.catalog_path(name) + + def test_offline_worker_keeps_local_catalog(self): + with patch.object(refresh, "synchronize", side_effect=OSError("Network unavailable")): + refresh.worker(self.root, "main") + self.assertFalse(refresh.read_json(refresh.cache_root(self.root) / "main-result.json")["ok"]) + self.assertEqual(refresh.current_catalog(self.root), self.root / "catalog") + + def test_first_visit_has_no_new_apps_and_opening_their_list_acknowledges_them(self): + status = refresh.CatalogRefresh(self.root, Catalog(self.root)) + self.assertEqual((status.new_ids(), status.label()), ([], "Refresh catalog")) + status.recipes["new-app"] = "new-recipe-hash" + self.assertEqual((status.new_ids(), status.label()), (["new-app"], "New applications (1)")) + # A new application stays new on later visits until its list is opened. + later = refresh.CatalogRefresh(self.root, Catalog(self.root)) + later.recipes = status.recipes.copy() + self.assertEqual(later.new_ids(), ["new-app"]) + later.mark_seen() + same = refresh.CatalogRefresh(self.root, Catalog(self.root)) + same.recipes = status.recipes.copy() + self.assertEqual(same.new_ids(), []) + + def test_new_apps_are_counted_by_id_not_net_growth_or_changed_recipes(self): + status = refresh.CatalogRefresh(self.root, Catalog(self.root)) + original = self.item["id"] + status.baseline = {original: "old-recipe", "removed-app": "hash"} + status.recipes["new-app"] = "hash" + self.assertEqual(status.new_ids(), ["new-app"]) + self.assertEqual(status.label(), "New applications (1)") + + def test_exit_before_download_finishes_does_not_acknowledge_remote_apps(self): + status = refresh.CatalogRefresh(self.root, Catalog(self.root)) + status.mark_seen() + status.process = Mock(); status.process.poll.return_value = None + with patch.object(refresh.threading, "Thread"): + status.__exit__(None, None, None) + status.process.terminate.assert_not_called() + self.add_app() + self.sync() + next_visit = refresh.CatalogRefresh(self.root, Catalog(self.root)) + self.assertEqual(len(next_visit.new_ids()), 1) + + def test_the_download_never_closes_the_open_menu_and_shows_on_the_next_draw(self): + from proxmenux_oci import cli, updates + status = refresh.CatalogRefresh(self.root, Catalog(self.root)) + status.process = Mock(returncode=0) + status.process.poll.return_value = None + screens = [] + owner = self + class UI: + live_refresh = True + def choose(self, text, entries, default, **kwargs): + screens.append((text, dict(entries))) + if len(screens) == 1: + # The catalog finishes downloading while the menu is open: + # nothing may close it, so no refresh is watched. + owner.assertNotIn('refresh', kwargs) + owner.assertNotIn('Refreshing the application catalog', text) + owner.add_app() + owner.sync() + refresh.write_json(refresh.cache_root(owner.root) / 'main-result.json', + {"ok": True, "changed": True}) + status.process.poll.return_value = 0 + return 'search' # any action brings the user back to the menu + return None + checker = Mock(active=False) + checker.status.return_value = {"available": []} + checker.notice.return_value = "" + checker.label.return_value = "Check for updates" + checker.changed.return_value = False + checker_context = Mock() + checker_context.__enter__ = Mock(return_value=checker) + checker_context.__exit__ = Mock(return_value=False) + original_class = refresh.CatalogRefresh + with patch.object(original_class, '__enter__', return_value=status), \ + patch.object(refresh, 'CatalogRefresh', return_value=status), \ + patch.object(cli, 'PROJECT_ROOT', self.root), \ + patch.object(cli, 'interactive_ui', return_value=UI()), \ + patch.object(cli, '_search'), \ + patch.object(updates, 'UpdateCheck', return_value=checker_context): + self.assertEqual(cli.interactive(Catalog(self.root)), 0) + # The header is one line; the catalog entry names the new application. + self.assertEqual(screens[0][0], cli.translate('Select a category or search for applications:')) + self.assertEqual(screens[0][1]['refresh'], cli.translate('Refresh catalog')) + self.assertRegex(screens[1][1]['refresh'], r' {2,}\( 1\)$') + self.assertIn('( 2)', screens[1][1]['all']) + next_visit = original_class(self.root, Catalog(self.root)) + # Seeing the menu does not acknowledge it: only opening the list does. + self.assertEqual(len(next_visit.new_ids()), 1) + + def test_explicit_local_apps_survive_refresh_without_preventing_upstream_removal(self): + # This app is a local preview, not a member of the upstream manifest. + self.add_app() + for name, data in self.files.items(): + file = self.root / 'catalog' / name + file.parent.mkdir(parents=True, exist_ok=True) + file.write_bytes(data) + index = json.loads(self.files['index.json']) + index['applications'] = [self.item] + self.files['index.json'] = json.dumps(index).encode() + for name in ('apps/sample-added.json', 'curated/sample-added.json'): + self.files.pop(name) + with patch.object(refresh, 'source_settings', return_value={'local_apps': ['sample-added']}): + self.sync() + catalog = Catalog(self.root) + self.assertIn('sample-added', refresh.visible_recipes(catalog)) + self.assertEqual(catalog.compose('sample-added')['id'], 'image-sample-added') + self.assertNotIn('sample-added', refresh.visible_recipes(Catalog(self.root))) + + def test_channel_switch_does_not_use_other_channel_snapshot(self): + self.sync() + self.assertEqual(refresh.current_catalog(self.root, "develop"), self.root / "catalog") + + def test_engine_schema_change_invalidates_cached_catalog(self): + self.sync() + schema = self.root / "schemas/oci-template.schema.json" + schema.write_text(schema.read_text() + "\n") + self.assertEqual(refresh.current_catalog(self.root), self.root / "catalog") + + def test_corrupted_cached_index_falls_back_to_bundled_catalog(self): + self.sync() + snapshot = refresh.current_catalog(self.root) + (snapshot / 'index.json').write_text('{broken json') + self.assertEqual(refresh.current_catalog(self.root), self.root / 'catalog') + + def test_refresh_process_is_started_without_waiting(self): + status = refresh.CatalogRefresh(self.root, Catalog(self.root)) + with patch.object(refresh.subprocess, "Popen") as spawn: + spawn.return_value.poll.return_value = None + with patch.object(refresh.threading, "Thread") as thread: + with status: + self.assertTrue(status.active) + # The download is transparent: the open menu does not announce it. + self.assertEqual(status.label(), "Refresh catalog") + thread.return_value.start.assert_called_once() + spawn.return_value.wait.assert_not_called() + + def test_manual_refresh_does_not_duplicate_worker_and_can_retry_failure(self): + status = refresh.CatalogRefresh(self.root, Catalog(self.root)) + with patch.object(refresh.subprocess, 'Popen') as spawn: + spawn.return_value.poll.return_value = None + self.assertTrue(status.start()) + self.assertFalse(status.start()) + self.assertEqual(spawn.call_count, 1) + spawn.return_value.poll.return_value = 0 + spawn.return_value.returncode = 0 + refresh.write_json(refresh.cache_root(self.root) / 'main-result.json', {'ok': False}) + self.assertFalse(status.consume()) + self.assertTrue(status.failed) + self.assertTrue(status.start()) + self.assertFalse(status.failed) + self.assertEqual(spawn.call_count, 2) + + def test_manual_refresh_is_offered_in_the_main_menu(self): + from proxmenux_oci.cli import _catalog_options + options = dict(_catalog_options(Catalog(self.root))[-1]) + self.assertEqual(options['refresh'], 'Refresh catalog') + + def test_sequence_new_app_failed_update_retry_and_withdrawal(self): + self.sync() + old = Catalog(self.root) + status = refresh.CatalogRefresh(self.root, old) + status.mark_seen() + self.revision = 'b' * 40 + self.add_app() + self.assertTrue(self.sync()) + self.assertIn('sample-added', refresh.visible_recipes(Catalog(self.root))) + self.assertNotIn('sample-added', refresh.visible_recipes(old)) # Wizard remains pinned. + next_visit = refresh.CatalogRefresh(self.root, Catalog(self.root)) + self.assertEqual(len(next_visit.new_ids()), 1) + next_visit.mark_seen() + self.revision = 'c' * 40 + recipe_path = self.item['curated_path'].removeprefix('catalog/') + good = self.files[recipe_path] + self.files[recipe_path] = b'{invalid-json' + with self.assertRaises(ValueError): + self.sync() + self.assertEqual(refresh.current_catalog(self.root).name, 'b' * 40) + self.files[recipe_path] = good + self.assertTrue(self.sync()) + index = json.loads(self.files['index.json']) + index['applications'] = [self.item] + self.files['index.json'] = json.dumps(index).encode() + for name in ('apps/sample-added.json', 'curated/sample-added.json'): + self.files.pop(name) + self.revision = 'd' * 40 + self.assertTrue(self.sync()) + current = Catalog(self.root) + self.assertNotIn('sample-added', refresh.visible_recipes(current)) + self.assertFalse((current.catalog_dir / 'curated/sample-added.json').exists()) + self.assertEqual(refresh.CatalogRefresh(self.root, current).new_ids(), []) + + def test_http_errors_and_timeouts_keep_snapshot_then_retry_succeeds(self): + self.sync() + old = refresh.current_catalog(self.root) + errors = [TimeoutError('timeout'), urllib.error.URLError('DNS unavailable')] + errors += [urllib.error.HTTPError('https://api.github.com', code, 'test', {}, None) + for code in (403, 404, 429, 500, 503)] + for error in errors: + if isinstance(error, urllib.error.HTTPError): self.addCleanup(error.close) + self.revision = 'b' * 40 + for error in errors: + with self.subTest(error=str(error)), self.assertRaises(Exception): + refresh.synchronize(self.root, 'main', Mock(side_effect=error)) + self.assertEqual(refresh.current_catalog(self.root), old) + self.assertTrue(self.sync()) + + def test_failed_file_transfer_does_not_publish_partial_download(self): + self.sync(); old = refresh.current_catalog(self.root) + self.revision = 'b' * 40; self.add_app() + def interrupted(url, etag=None): + if 'raw.githubusercontent' in url: + raise TimeoutError('Connection lost during download') + return self.fetch(url, etag) + with self.assertRaises(TimeoutError): + refresh.synchronize(self.root, 'main', interrupted) + self.assertEqual(refresh.current_catalog(self.root), old) + self.assertFalse(list((refresh.cache_root(self.root) / 'snapshots').glob('.download-*'))) + self.assertTrue(self.sync()) + + def test_disk_full_and_pointer_permission_failure_preserve_previous_revision_and_etag(self): + for error in (OSError(errno.ENOSPC, 'Disk full'), PermissionError('Read only')): + with self.subTest(error=error): + self.sync() + pointer = refresh.cache_root(self.root) / 'main-current.json' + before = pointer.read_bytes() + old = refresh.current_catalog(self.root) + self.revision = 'b' * 40 + real = refresh.write_json + def fail(path, value): + if Path(path) == pointer: + raise error + return real(path, value) + with patch.object(refresh, 'write_json', side_effect=fail), self.assertRaises(OSError): + self.sync() + self.assertEqual(pointer.read_bytes(), before) + self.assertEqual(refresh.current_catalog(self.root), old) + self.assertTrue(self.sync()) + self.revision = 'c' * 40 + + def test_manual_force_repairs_corrupted_recipe_at_same_revision(self): + self.sync() + old = refresh.current_catalog(self.root) + recipe = old / self.item['curated_path'].removeprefix('catalog/') + recipe.write_text('{}') + self.requests.clear() + self.assertTrue(refresh.synchronize(self.root, 'main', self.fetch, force=True)) + self.assertIsNone(self.requests[0][1]) + self.assertNotEqual(refresh.current_catalog(self.root), old) + self.assertEqual(Catalog(self.root).compose(self.item['id'])['id'], self.item['template_id']) + self.assertFalse(refresh.synchronize(self.root, 'main', self.fetch, force=True)) + + def test_corrupt_cached_index_can_be_repaired_by_automatic_refresh(self): + self.sync() + old = refresh.current_catalog(self.root) + (old / 'index.json').write_text('{broken') + self.assertTrue(self.sync()) + self.assertNotEqual(refresh.current_catalog(self.root), self.root / 'catalog') + self.assertEqual(Catalog(self.root).compose(self.item['id'])['id'], self.item['template_id']) + + def test_second_session_result_cannot_hide_successful_update(self): + status = refresh.CatalogRefresh(self.root, Catalog(self.root)) + self.sync() + status.process = Mock(returncode=0) + status.process.poll.return_value = 0 + # Another session checked the newly installed revision and found no change. + refresh.write_json(refresh.cache_root(self.root) / 'main-result.json', + {'ok': True, 'changed': False}) + self.assertTrue(status.consume()) + + def test_crashed_worker_does_not_trust_stale_success(self): + status = refresh.CatalogRefresh(self.root, Catalog(self.root)) + status.process = Mock(returncode=1); status.process.poll.return_value = 1 + refresh.write_json(refresh.cache_root(self.root) / 'main-result.json', + {'ok': True, 'changed': True}) + self.assertFalse(status.consume()) + self.assertTrue(status.failed) + + def test_force_manual_refresh_passes_flag_to_worker(self): + status = refresh.CatalogRefresh(self.root, Catalog(self.root)) + with patch.object(refresh.subprocess, 'Popen') as spawn: + self.assertTrue(status.start(force=True)) + self.assertIn('--force', spawn.call_args.args[0]) + + def test_duplicate_manifest_path_symlink_and_truncated_tree_are_rejected(self): + for change in ('duplicate', 'symlink', 'truncated'): + def bad_tree(url, etag=None): + body, tag = self.fetch(url, etag) + if '/git/trees/' in url: + tree = json.loads(body) + if change == 'duplicate': tree['tree'].append(tree['tree'][0]) + elif change == 'symlink': tree['tree'][0]['mode'] = '120000' + else: tree['truncated'] = True + body = json.dumps(tree).encode() + return body, tag + with self.subTest(change=change), self.assertRaises(ValueError): + refresh.synchronize(self.root, 'main', bad_tree) + self.assertEqual(refresh.current_catalog(self.root), self.root / 'catalog') + + def test_bad_channel_config_falls_back_without_crashing_menu(self): + config = self.root.parent / 'catalog-source.json' + for value in ([], None, 'invalid', {'channel': 'other'}, {'beta_program': 'invalid'}): + config.write_text(json.dumps(value)) + self.assertEqual(refresh.channel_for(self.root), 'main') + + def test_full_disk_during_file_write_leaves_no_partial_catalog(self): + self.sync(); old = refresh.current_catalog(self.root) + self.revision = 'b' * 40; self.add_app() + real = Path.write_bytes + def full(path, data): + if any(part.startswith('.download-') for part in path.parts): + raise OSError(errno.ENOSPC, 'Disk full') + return real(path, data) + with patch.object(Path, 'write_bytes', new=full), self.assertRaises(OSError): + self.sync() + self.assertEqual(refresh.current_catalog(self.root), old) + self.assertFalse(list((refresh.cache_root(self.root) / 'snapshots').glob('.download-*'))) + + def test_orphaned_download_is_removed_before_retry(self): + partial = refresh.cache_root(self.root) / 'snapshots/.download-crashed' + partial.mkdir(parents=True); (partial/'partial.json').write_text('{}') + synchronize = refresh.synchronize + with patch.object(refresh, 'synchronize', side_effect=lambda *a, **kw: + synchronize(self.root, 'main', self.fetch)): + refresh.worker(self.root, 'main') + self.assertFalse(partial.exists()) + self.assertTrue(refresh.read_json(refresh.cache_root(self.root)/'main-result.json')['ok']) + + def test_request_rejects_oversized_response_and_handles_304(self): + context = Mock() + context.__enter__ = Mock(return_value=Mock(read=Mock(return_value=b'oversized'))) + context.__exit__ = Mock(return_value=False) + with patch.object(refresh.urllib.request, 'urlopen', return_value=context), self.assertRaises(ValueError): + refresh.request('https://api.github.com/test', limit=2) + error = urllib.error.HTTPError('https://api.github.com', 304, 'unchanged', {}, None) + with patch.object(refresh.urllib.request, 'urlopen', side_effect=error): + self.assertEqual(refresh.request('https://api.github.com/test', etag='old'), (None, 'old')) + + def test_unwritable_seen_state_does_not_crash_menu(self): + status = refresh.CatalogRefresh(self.root, Catalog(self.root)) + with patch.object(refresh, 'write_json', side_effect=PermissionError('Read only')): + status.mark_seen() + self.assertFalse(status.failed) + + def test_concurrent_workers_serialize_the_catalog_transaction(self): + barrier = threading.Barrier(2) + guard = threading.Lock() + active = maximum = 0 + errors = [] + def operation(*args, **kwargs): + nonlocal active, maximum + with guard: + active += 1; maximum = max(maximum, active) + time.sleep(0.03) + with guard: active -= 1 + return False + def run(): + try: + barrier.wait(timeout=2) + refresh.worker(self.root, 'main') + except Exception as error: + errors.append(error) + with patch.object(refresh, 'synchronize', side_effect=operation): + threads = [threading.Thread(target=run) for _ in range(2)] + for thread in threads: thread.start() + for thread in threads: thread.join(timeout=3) + self.assertFalse(errors) + self.assertTrue(all(not thread.is_alive() for thread in threads)) + self.assertEqual(maximum, 1) + + def test_download_deadline_preserves_previous_catalog(self): + self.sync(); old = refresh.current_catalog(self.root) + self.revision = 'b' * 40; self.add_app() + with patch.object(refresh.time, 'monotonic', side_effect=itertools.chain([0], itertools.repeat(100))), \ + self.assertRaises(TimeoutError): + self.sync() + self.assertEqual(refresh.current_catalog(self.root), old) + + def test_http_end_to_end_new_release_outage_retry_manual_refresh_and_repair(self): + owner = self + unavailable = False + class Handler(http.server.BaseHTTPRequestHandler): + def log_message(self, *args): pass + def do_GET(self): + if unavailable: + self.send_error(503); return + etag = '"' + owner.revision + '"' + if self.headers.get('If-None-Match') == etag: + self.send_response(304); self.end_headers(); return + host = ('https://api.github.com' if self.path.startswith('/repos/') + else 'https://raw.githubusercontent.com') + body, _ = owner.fetch(host + self.path) + self.send_response(200) + self.send_header('Content-Length', str(len(body))) + self.send_header('ETag', etag) + self.end_headers(); self.wfile.write(body) + server = http.server.ThreadingHTTPServer(('127.0.0.1', 0), Handler) + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + def fetch(url, etag=None): + original = urllib.parse.urlsplit(url) + path = original.path + ('?' + original.query if original.query else '') + return refresh.request(f'http://127.0.0.1:{server.server_port}' + path, etag) + try: + self.assertTrue(refresh.synchronize(self.root, 'main', fetch)) + self.assertFalse(refresh.synchronize(self.root, 'main', fetch)) + first = refresh.current_catalog(self.root) + unavailable = True + self.revision = 'b' * 40; self.add_app() + with self.assertRaises(urllib.error.HTTPError): + refresh.synchronize(self.root, 'main', fetch) + self.assertEqual(refresh.current_catalog(self.root), first) + unavailable = False + self.assertTrue(refresh.synchronize(self.root, 'main', fetch)) + current = Catalog(self.root) + self.assertEqual(len(refresh.visible_recipes(current)), 2) + self.assertFalse(refresh.synchronize(self.root, 'main', fetch, force=True)) + (current.catalog_dir / 'curated/sample-added.json').write_text('{}') + self.assertTrue(refresh.synchronize(self.root, 'main', fetch, force=True)) + self.assertEqual(Catalog(self.root).compose('sample-added')['id'], 'image-sample-added') + finally: + server.shutdown(); server.server_close(); thread.join(timeout=2) + + +if __name__ == "__main__": + unittest.main() diff --git a/oci/tests/test_device_node_binds.py b/oci/tests/test_device_node_binds.py new file mode 100644 index 00000000..c9c791f1 --- /dev/null +++ b/oci/tests/test_device_node_binds.py @@ -0,0 +1,39 @@ +"""A Compose volume that binds a host device node, such as /dev/net/tun for +Tailscale, is passed to the LXC as a device: a mount there hides the device.""" +from pathlib import Path +import sys +import unittest + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) +from proxmenux_oci import converter + + +class DeviceNodeBindTests(unittest.TestCase): + service = {"image": "tailscale/tailscale:latest", "volumes": [ + {"type": "bind", "source": "/DATA/AppData/tailscale", "target": "/var/lib/tailscale"}, + {"type": "bind", "source": "/dev/net/tun", "target": "/dev/net/tun"}, + "/dev/fuse:/dev/fuse"]} + + def test_device_nodes_are_not_volumes(self): + mounts = converter._mount_contract(self.service["volumes"], set()) + self.assertEqual([m["container_path"] for m in mounts], ["/var/lib/tailscale"]) + + def test_device_nodes_become_device_requests(self): + profile, blockers = converter._device_installer_profile(self.service) + self.assertEqual(blockers, []) + self.assertEqual([(r["id"], r["container_path"]) for r in profile["device_requests"]], + [("dev-net-tun", "/dev/net/tun"), ("dev-fuse", "/dev/fuse")]) + + def test_a_device_directory_is_left_to_its_own_handling(self): + self.assertIsNone(converter._device_bind("/dev/dri:/dev/dri")) + self.assertIsNone(converter._device_bind({"type": "volume", "source": "/dev/net/tun", "target": "/dev/net/tun"})) + + def test_the_tailscale_recipe_passes_the_tun_device(self): + import json + recipe = json.loads((Path(__file__).resolve().parents[1] / "catalog/apps/tailscale.json").read_text()) + self.assertNotIn("/dev/net/tun", [v["container_path"] for v in recipe["container_contract"]["volumes"]]) + self.assertIn("dev-net-tun", [r["id"] for r in recipe["proxmox"]["installer_profile"]["device_requests"]]) + + +if __name__ == "__main__": + unittest.main() diff --git a/oci/tests/test_frigate_amd_profile.py b/oci/tests/test_frigate_amd_profile.py index ae121d10..447eae02 100644 --- a/oci/tests/test_frigate_amd_profile.py +++ b/oci/tests/test_frigate_amd_profile.py @@ -191,8 +191,10 @@ class FrigateExperimentalRocmTests(unittest.TestCase): self.assertEqual(plan["hardware_profile"], "vaapi") self.assertEqual(self.override(plan), []) - def test_a_gpu_the_image_cannot_use_is_not_offered_and_the_reason_is_said(self, *_): + def test_a_gpu_the_image_cannot_use_is_simply_not_offered(self, *_): + # The menu offers what works on the GPU; it does not explain ROCm. with patch("proxmenux_oci.installer.host.amd_gpu_name", return_value="Lucienne (gfx90c)"): ui, plan = self.build(90012, {}) self.assertNotIn("rocm", ui.labels[PROMPT]) - self.assertTrue(any("Lucienne (gfx90c)" in message and "not offered" in message for message in ui.messages)) + self.assertIn("vaapi", ui.labels[PROMPT]) + self.assertFalse(any("ROCm" in message for message in ui.messages)) diff --git a/oci/tests/test_guessed_endpoint_healthcheck.py b/oci/tests/test_guessed_endpoint_healthcheck.py index b5393948..afbfa9aa 100644 --- a/oci/tests/test_guessed_endpoint_healthcheck.py +++ b/oci/tests/test_guessed_endpoint_healthcheck.py @@ -23,8 +23,8 @@ DECLARED = {'first_run': {'endpoints': [{'label': 'Web UI', 'scheme': 'http', 'p def probe_function(): source = INSTALLER.read_text() - start = source.index('healthcheck_probe() {') - end = source.index('\n}\n', start) + 3 + start = source.index('healthcheck_probe_url() {') + end = source.index('\n}\n', source.index('healthcheck_probe() {')) + 3 return source[start:end] @@ -38,7 +38,7 @@ class GuessedEndpointCheckTests(unittest.TestCase): fake = Path(directory) / 'curl' fake.write_text(f'#!/bin/sh\nprintf %s {code}\nexit {exit_code}\n') fake.chmod(0o755) - script = (probe_function() + '\noci_curl() { shift; curl "$@"; }\nCURL_ARGS=()\nHC_URL=http://192.0.2.1:3306/\n' + script = (probe_function() + '\noci_curl() { shift; curl "$@"; }\nCURL_ARGS=()\nHC_URLS=(http://192.0.2.1:3306/)\n' f'HC_ANY_STATUS={any_status}\nHC_ACCEPT_NON_HTTP={accept_non_http}\nhealthcheck_probe\n') env = {**os.environ, 'PATH': f'{directory}:{os.environ["PATH"]}'} return subprocess.run(['bash', '-c', script], env=env, capture_output=True).returncode @@ -83,5 +83,62 @@ class GuessedEndpointCheckTests(unittest.TestCase): transaction.healthcheck(113, template) +ADGUARD = {'id': 'image-adguard-home', + 'first_run': {'endpoints': [{'label': 'Web UI', 'scheme': 'http', 'port': 3000, 'path': '/'}]}} + + +class SetupThenWebPortTests(unittest.TestCase): + """AdGuard Home answers on 3000 until it is set up, then on 80: an update of + a configured installation must not wait for the setup port.""" + + def test_adguard_also_accepts_its_web_port(self): + check = transaction.effective_healthcheck(ADGUARD) + self.assertEqual((check['port'], check['alternative_ports']), (3000, [80])) + self.assertNotIn('alternative_ports', transaction.effective_healthcheck(DECLARED)) + + def test_a_check_saved_with_an_installation_takes_the_web_port_too(self): + # An installation stores the check it was given: still on 3000 only. + saved = {'scheme': 'http', 'port': 3000, 'path': '/', 'timeout_seconds': 300, 'accept_any_status': True} + template = dict(ADGUARD, proxmox={'installer_profile': {'startup_healthcheck': saved}}) + self.assertEqual(transaction.effective_healthcheck(template)['alternative_ports'], [80]) + self.assertNotIn('alternative_ports', saved) + other = dict(template, id='image-other') + self.assertNotIn('alternative_ports', transaction.effective_healthcheck(other)) + + def test_recovery_check_passes_on_the_web_port_when_setup_is_closed(self): + check = transaction.effective_healthcheck(ADGUARD) + check.update(timeout_seconds=6, stability_seconds=0) + template = {'proxmox': {'installer_profile': {'startup_healthcheck': check}}} + + def run(*args): + if args[:2] == ('pct', 'status'): + return b'status: running\n' + if args[:2] == ('pct', 'config'): + return b'' + if args[0] == 'lxc-info': + return b'192.0.2.10\n' + raise AssertionError(args) + + def probe(args): + return (b'200', 0) if args[-1].endswith(':80/') else (b'000', 7) + with patch.object(transaction, 'run', side_effect=run), \ + patch.object(transaction, 'probe_http', side_effect=probe), \ + patch.object(transaction, 'log'): + self.assertEqual(transaction.healthcheck(101, template), 'http://192.0.2.10:80/') + + def test_installer_probe_tries_every_address_and_keeps_the_one_that_answered(self): + with tempfile.TemporaryDirectory() as directory: + fake = Path(directory) / 'curl' + fake.write_text('#!/bin/sh\nfor last; do :; done\ncase "$last" in *:80/) printf 200; exit 0;; esac\n' + 'printf 000\nexit 7\n') + fake.chmod(0o755) + script = (probe_function() + '\noci_curl() { shift; curl "$@"; }\nCURL_ARGS=()\n' + 'HC_URLS=(http://192.0.2.1:3000/ http://192.0.2.1:80/)\nHC_ANY_STATUS=true\n' + 'healthcheck_probe && echo "$HC_URL"\n') + env = {**os.environ, 'PATH': f'{directory}:{os.environ["PATH"]}'} + done = subprocess.run(['bash', '-c', script], env=env, capture_output=True, text=True) + self.assertEqual((done.returncode, done.stdout.strip()), (0, 'http://192.0.2.1:80/')) + + if __name__ == '__main__': unittest.main() diff --git a/oci/tests/test_immich_acceleration.py b/oci/tests/test_immich_acceleration.py index 4a52f6e9..586dca5b 100644 --- a/oci/tests/test_immich_acceleration.py +++ b/oci/tests/test_immich_acceleration.py @@ -92,16 +92,21 @@ class ImmichAccelerationTests(unittest.TestCase): self.assertIn("No usable GPU", ui.messages[0]) self.assertEqual(result, ("cpu", "cpu"), mode) - def test_an_amd_host_that_cannot_run_rocm_offers_video_only_and_says_why(self, *_): - for blocker, text in (("kfd", "/dev/kfd"), ("space", "40 GB"), ("generation", "no support for the AMD GPU")): + def test_an_amd_host_that_cannot_run_rocm_offers_video_only(self, *_): + # What the host can fix is said; a GPU generation ROCm does not run is not. + for blocker, text in (("kfd", "/dev/kfd"), ("space", "40 GB"), ("generation", None)): with patch("proxmenux_oci.installer.host.rocm_blocker", return_value=blocker), \ patch("proxmenux_oci.installer.host.amd_gpu_name", return_value="Lucienne (gfx90c)"): ui, result, _ = self.plan(AMD, DEFAULT_MODE) self.assertEqual(ui.options[PROMPT], ["cpu", "amd-video"], blocker) self.assertEqual(ui.defaults[PROMPT], "amd-video", blocker) self.assertEqual(result, ("vaapi", "cpu"), blocker) - self.assertTrue(any(text in message and "Recognition is not offered" in message - for message in ui.messages), blocker) + if text: + self.assertTrue(any(text in message and "Recognition is not offered" in message + for message in ui.messages), blocker) + else: + self.assertFalse(any("ROCm" in message or "Recognition" in message + for message in ui.messages), blocker) def test_a_gpu_rocm_supports_is_proposed_whole(self, *_): for target in (100300, 110501, None): diff --git a/oci/tests/test_kitchenowl_recipe.py b/oci/tests/test_kitchenowl_recipe.py new file mode 100644 index 00000000..b831381f --- /dev/null +++ b/oci/tests/test_kitchenowl_recipe.py @@ -0,0 +1,151 @@ +"""The KitchenOwl entry follows the upstream two-service Compose deployment.""" +import json +from pathlib import Path +import sys +import unittest +from unittest.mock import patch + +from jsonschema import validate + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "src")) +from proxmenux_oci.catalog import Catalog +from proxmenux_oci.custom import template_from_oci_compose +from proxmenux_oci.stack import assess, build_stack, DefaultsUI +from proxmenux_oci.installer import DEFAULT_MODE + +CURATED_PATH = ROOT / "catalog/curated/kitchenowl.json" +APPS_PATH = ROOT / "catalog/apps/kitchenowl.json" + + +def compile_stack(template): + """Compile the recipe with deterministic answers, without touching Proxmox.""" + with patch("proxmenux_oci.installer.ask_storage", return_value="local-zfs"), \ + patch("proxmenux_oci.installer.ask_bridge", return_value="vmbr0"), \ + patch("proxmenux_oci.host.timezone", return_value="UTC"), \ + patch("proxmenux_oci.network.ask_addresses", + side_effect=lambda ui, bridge, labels: ({name: "dhcp" for name in labels}, None)): + return build_stack(template, DefaultsUI(), DEFAULT_MODE) + + +class KitchenOwlRecipeTests(unittest.TestCase): + def setUp(self): + self.curated = json.loads(CURATED_PATH.read_text(encoding="utf-8")) + self.generated = json.loads(APPS_PATH.read_text(encoding="utf-8")) + self.index = json.loads((ROOT / "catalog/index.json").read_text(encoding="utf-8")) + self.services = { + service["name"]: service + for service in self.curated["compose_stack"]["services"] + } + + def test_curated_and_catalog_templates_match_and_validate(self): + self.assertEqual(self.curated, self.generated) + schema = json.loads((ROOT / "schemas/oci-template.schema.json").read_text(encoding="utf-8")) + validate(self.curated, schema) + + def test_official_compose_is_the_two_image_frontend_backend_stack(self): + self.assertEqual(set(self.services), {"front", "back"}) + self.assertEqual(self.curated["compose_stack"]["service_count"], 2) + self.assertEqual(self.curated["compose_stack"]["main_service"], "front") + self.assertEqual(self.services["front"]["image"], "tombursch/kitchenowl-web:latest") + self.assertEqual(self.services["back"]["image"], "tombursch/kitchenowl-backend:latest") + self.assertEqual(self.services["front"]["depends_on"], ["back"]) + self.assertEqual(self.curated["compose_stack"]["orchestration"]["start_order"], ["back", "front"]) + self.assertTrue(self.services["front"]["frontend_network"]) + self.assertTrue(self.services["front"]["private_network"]) + self.assertFalse(self.services["back"]["frontend_network"]) + self.assertTrue(self.services["back"]["private_network"]) + self.assertNotIn("postgres", self.services) + self.assertNotIn("redis", self.services) + + def test_frontend_uses_the_official_backend_alias_and_port(self): + # BACK_URL is inherited from the official web image (back:5000); leaving + # the override absent preserves the image default and Compose service alias. + self.assertNotIn("BACK_URL", self.services["front"]["compose"].get("environment", {})) + image_policy = self.curated["proxmox"]["image_metadata_policy"] + self.assertEqual(image_policy["environment"], "import-image-env-then-apply-compose-overrides") + self.assertEqual(self.curated["container_contract"]["ports"][0]["container_port"], 80) + + def test_backend_data_and_generated_jwt_key_are_preserved_safely(self): + back = self.services["back"]["compose"] + self.assertEqual(back["volumes"][0]["target"], "/data") + self.assertEqual(back["environment"]["JWT_SECRET_KEY"], "${GENERATED_JWT_SECRET_KEY}") + self.assertNotIn("PLEASE_CHANGE_ME", json.dumps(back)) + secret = self.curated["compose_stack"]["installer_inputs"]["generated_secrets"] + self.assertEqual(len(secret), 1) + self.assertEqual(secret[0]["bindings"], [{"service": "back", "environment_variable": "JWT_SECRET_KEY"}]) + volume = next(v for v in self.curated["compose_stack"]["storage"] if v["service"] == "back") + self.assertEqual(volume["container_path"], "/data") + self.assertEqual(volume["mode"], "managed-volume") + self.assertTrue(volume["user_selectable"]) + self.assertTrue(volume["backup"]) + + def test_image_healthchecks_and_first_run_setup_are_recorded(self): + self.assertIn("uwsgi_curl localhost:5000 /api/health/", str(self.services["back"]["compose"]["healthcheck"])) + self.assertIn("curl -f http://localhost/", str(self.services["front"]["compose"]["healthcheck"])) + self.assertEqual(self.curated["first_run"]["endpoints"][0]["port"], 80) + self.assertEqual(self.curated["first_run"]["credentials"], []) + self.assertTrue(self.curated["proxmox"]["stack_completion_notes"]) + + def test_candidate_passes_stack_assessment_and_round_trips_through_catalog_reader(self): + catalog = Catalog(ROOT) + template = catalog.compose("kitchenowl") + self.assertEqual(assess(template), []) + self.assertEqual(template["compose_stack"]["project_name"], "kitchenowl") + reloaded = template_from_oci_compose(json.dumps(template)) + expected = dict(template) + # Reading a pasted/shared recipe deliberately resets trust status; the + # catalog reapplies laboratory verification only for its own entry. + expected["status"] = "generated-unvalidated" + self.assertEqual(reloaded, expected) + + def test_compiled_plan_keeps_private_backend_health_and_persistent_data(self): + template = Catalog(ROOT).compose("kitchenowl") + plan = compile_stack(template) + self.assertEqual(plan["stack_name"], "kitchenowl") + self.assertEqual(plan["network"]["private_allocation"], "automatic") + self.assertEqual(plan["network"]["private_bridge"], "vmbr10") + + services = {service["name"]: service for service in plan["services"]} + self.assertEqual(list(services), ["back", "front"]) + back = services["back"] + front = services["front"] + self.assertIn("back", back["aliases"]) + self.assertIn("front", front["aliases"]) + self.assertNotIn("frontend", back) + self.assertTrue(front["frontend"]) + + backend_health = back["healthcheck"] + self.assertEqual(backend_health["type"], "exec") + self.assertEqual(backend_health["timeout_seconds"], 189) + self.assertIn("uwsgi_curl", backend_health["argv"][-1]) + frontend_health = front["healthcheck"] + self.assertEqual(frontend_health["type"], "exec") + self.assertEqual(frontend_health["timeout_seconds"], 99) + self.assertIn("curl -f http://localhost/", frontend_health["argv"][-1]) + self.assertEqual(frontend_health["endpoint"], {"scheme": "http", "port": 80, "path": "/"}) + + self.assertEqual([mount["container_path"] for mount in back["deployment"]["mounts"]], ["/data"]) + self.assertEqual(back["deployment"]["mounts"][0]["type"], "managed-volume") + self.assertTrue(back["deployment"]["mounts"][0]["backup"]) + self.assertEqual(front["deployment"]["mounts"], []) + preparation = back["template"]["proxmox"]["installer_profile"]["volume_preparations"] + self.assertEqual(preparation[0]["container_path"], "/data") + self.assertEqual(preparation[0]["only_when_mount_type"], "managed-volume") + self.assertEqual(template["compose_stack"]["storage"][0]["service"], "back") + self.assertFalse(template["compose_stack"]["storage"][0]["shared_with_other_lxc"]) + + def test_icon_architectures_and_unvalidated_status_are_accurate(self): + ui = self.curated["catalog_ui"] + self.assertEqual(ui["icon"], "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/kitchenowl.webp") + self.assertEqual(ui["architectures"], ["amd64", "arm64"]) + self.assertEqual(self.curated["status"], "generated-unvalidated") + self.assertFalse(self.curated["compatibility"]["validated"]) + entry = next(app for app in self.index["applications"] if app["id"] == "kitchenowl") + self.assertEqual(entry["template"], "apps/kitchenowl.json") + self.assertTrue(entry["multi_container"]) + self.assertEqual(entry["category"], "productivity") + + +if __name__ == "__main__": + unittest.main() diff --git a/oci/tests/test_krusader_recipe.py b/oci/tests/test_krusader_recipe.py new file mode 100644 index 00000000..7fda13e9 --- /dev/null +++ b/oci/tests/test_krusader_recipe.py @@ -0,0 +1,135 @@ +"""Keep the native Krusader OCI profile aligned with its upstream image.""" + +import json +from pathlib import Path +import sys +import unittest + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "src")) + +from proxmenux_oci.catalog import Catalog + + +class KrusaderRecipeTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.curated = json.loads( + (ROOT / "catalog/curated/krusader.json").read_text(encoding="utf-8") + ) + cls.app = json.loads( + (ROOT / "catalog/apps/krusader.json").read_text(encoding="utf-8") + ) + cls.template = Catalog(ROOT).compose("krusader") + + def test_catalog_copy_matches_curated_profile(self): + self.assertEqual(self.curated, self.app) + + def test_upstream_image_and_manifest_architectures_are_recorded(self): + contract = self.curated["container_contract"] + self.assertEqual(contract["image"]["reference"], "jlesage/krusader:latest") + self.assertEqual(self.curated["source"]["provider"], "jlesage") + self.assertEqual(self.curated["catalog_ui"]["architectures"], ["amd64", "arm64"]) + + def test_web_gui_uses_authenticated_https_and_does_not_expose_vnc(self): + env = { + entry["name"]: entry.get("example") + for entry in self.template["container_contract"]["environment"] + } + self.assertEqual(env["WEB_AUTHENTICATION"], "1") + self.assertEqual(env["SECURE_CONNECTION"], "1") + self.assertEqual(env["VNC_LOCALHOST_ONLY"], "1") + self.assertEqual( + self.template["catalog_ui"]["launch"], + {"scheme": "https", "port": 5800, "path": "/"}, + ) + self.assertEqual( + [port["container_port"] for port in self.template["container_contract"]["ports"]], + [5800], + ) + credential = self.template["first_run"]["credentials"][0] + self.assertEqual(credential["username"], "admin") + self.assertEqual(credential["password_environment"], "WEB_AUTHENTICATION_PASSWORD") + self.assertIn( + "WEB_AUTHENTICATION_PASSWORD", + self.template["proxmox"]["installer_profile"]["generated_sensitive_environment"], + ) + + def test_only_config_and_selected_storage_are_persistent(self): + volumes = { + volume["container_path"]: volume + for volume in self.template["container_contract"]["volumes"] + } + self.assertEqual(set(volumes), {"/config", "/storage"}) + self.assertEqual(volumes["/config"]["installation_choice"], ["managed-volume"]) + self.assertEqual(volumes["/storage"]["installation_choice"], ["managed-volume", "host-bind"]) + self.assertEqual(volumes["/storage"]["default"], "host-bind") + self.assertTrue(all(volume["managed_volume"]["backup"] for volume in volumes.values())) + self.assertEqual(volumes["/config"]["managed_volume"]["default_size_gb"], 4) + compose = self.template["compose_stack"]["services"][0]["compose"] + self.assertEqual(compose["image"], "jlesage/krusader:latest") + + def test_storage_directory_offers_host_bind_with_the_image_user_identity(self): + profile = self.template["proxmox"]["installer_profile"] + storage = next( + item for item in self.template["container_contract"]["volumes"] + if item["container_path"] == "/storage" + ) + compose_storage = next( + item for item in self.template["compose_stack"]["storage"] + if item["container_path"] == "/storage" + ) + + self.assertEqual(storage["installation_choice"], ["managed-volume", "host-bind"]) + self.assertEqual(storage["default"], "host-bind") + self.assertTrue(compose_storage["user_selectable"]) + self.assertEqual(compose_storage["mode"], "user-selectable") + self.assertEqual( + compose_storage["installation_choice"], ["managed-volume", "host-bind"] + ) + self.assertEqual( + profile["volume_owner"], {"uid": 1000, "gid": 1000} + ) + self.assertEqual( + {entry["name"]: entry["example"] for entry in self.template["container_contract"]["environment"]} + ["USER_ID"], + "1000", + ) + self.assertEqual( + {entry["name"]: entry["example"] for entry in self.template["container_contract"]["environment"]} + ["GROUP_ID"], + "1000", + ) + + def test_partial_live_evidence_does_not_mark_app_as_fully_verified(self): + self.assertEqual( + self.curated["validation"]["clean_install"], "passed-first-start" + ) + self.assertEqual( + self.curated["validation"]["service_health"], + "passed-login-page-and-authenticated-session-http-200", + ) + self.assertEqual( + self.curated["validation"]["credentials"], + "passed-generated-password-login-and-session-cookie", + ) + self.assertEqual( + self.curated["validation"]["web_authentication"], + "passed-generated-credential-post-and-session-cookie", + ) + self.assertEqual( + self.curated["validation"]["restart_persistence"], + "passed-container-restart-and-host-bind-write-check", + ) + self.assertEqual( + self.curated["validation"]["browser_visual"], + "pending-self-signed-certificate-review", + ) + self.assertEqual(self.curated["validation"]["backup_restore"], "pending") + self.assertEqual(self.curated["validation"]["update_preserves_data"], "pending") + self.assertEqual(self.curated["status"], "generated-unvalidated") + self.assertFalse(self.curated["compatibility"]["validated"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/oci/tests/test_modify_review_transaction.py b/oci/tests/test_modify_review_transaction.py index 08bf79d3..5e46905a 100644 --- a/oci/tests/test_modify_review_transaction.py +++ b/oci/tests/test_modify_review_transaction.py @@ -11,7 +11,7 @@ import socket import sys import tempfile import unittest -from unittest.mock import patch +from unittest.mock import Mock, patch sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'remote')) import oci_instances as instances @@ -129,6 +129,37 @@ class UpdateRetainedDiskTests(unittest.TestCase): self.assertEqual(instances.read(self.root,115)['observed']['image'],self.saved['observed']['image']) self.assertEqual(json.loads(self.journal().read_text())['phase'],'rolled-back') + def test_update_command_recovers_a_failed_replacement_without_manual_recovery(self): + import oci_update_current as update + self.fail_install = True + with patch.object(update, 'update', side_effect=lambda *a: self.apply()), \ + patch.object(instances, 'ROOT', self.root), \ + patch.object(update, 'os', Mock(geteuid=lambda: 0)), \ + patch.object(sys, 'argv', ['oci_update_current.py', '115']), \ + patch.object(transaction, 'report_error'), \ + patch.object(transaction, 'recovery_hint') as hint: + self.assertEqual(update.main(), 1) + self.assert_retained() + self.assertEqual(instances.read(self.root,115)['status'], 'installed') + self.assertEqual(instances.read(self.root,115)['observed']['image'],self.saved['observed']['image']) + self.assertEqual(json.loads(self.journal().read_text())['phase'],'rolled-back') + hint.assert_not_called() + + def test_update_command_keeps_the_recovery_hint_when_rollback_is_blocked(self): + import oci_update_current as update + self.fail_install = True + with patch.object(update, 'update', side_effect=lambda *a: self.apply()), \ + patch.object(instances, 'ROOT', self.root), \ + patch.object(update, 'os', Mock(geteuid=lambda: 0)), \ + patch.object(sys, 'argv', ['oci_update_current.py', '115']), \ + patch.object(transaction, 'report_error'), \ + patch.object(transaction, 'recover', side_effect=ValueError('configuration changed')), \ + patch.object(transaction, 'recovery_hint') as hint: + self.assertEqual(update.main(), 1) + hint.assert_called_once_with() + self.assertEqual(instances.read(self.root,115)['status'], 'updating') + self.assertNotEqual(json.loads(self.journal().read_text())['phase'],'rolled-back') + def test_recovery_after_parking_data_returns_the_original_unused_disks(self): with self.assertRaisesRegex(RuntimeError,'Lab interruption'):self.apply(interrupt_after='data-parked') transaction.recover(self.root,self.journal());self.assert_retained() @@ -145,4 +176,31 @@ class UpdateRetainedDiskTests(unittest.TestCase): self.assert_retained() +class AutomaticRecoveryGuardTests(unittest.TestCase): + def test_committed_rolled_back_and_coordinated_journals_are_not_recovered(self): + for state in ({'phase':'committed'}, {'phase':'rolled-back'}, + {'phase':'installing-candidate','coordinated':True}): + with self.subTest(state=state), tempfile.TemporaryDirectory() as directory: + root=Path(directory)/'instances'; journal=Path(directory)/'journal.json' + journal.write_text(json.dumps(state)) + with patch.object(transaction,'recover') as recover: + self.assertFalse(transaction.recover_failed_update(root,journal)) + recover.assert_not_called() + + def test_missing_and_malformed_journals_leave_recovery_pending(self): + with tempfile.TemporaryDirectory() as directory: + root=Path(directory)/'instances'; journal=Path(directory)/'journal.json' + with patch.object(transaction,'recover') as recover, patch.object(transaction,'log'): + self.assertFalse(transaction.recover_failed_update(root,journal)) + journal.write_text('{') + self.assertFalse(transaction.recover_failed_update(root,journal)) + recover.assert_not_called() + + def test_a_busy_registry_does_not_start_recovery(self): + with patch.object(instances,'locked',side_effect=BlockingIOError('busy')), \ + patch.object(transaction,'recover') as recover, patch.object(transaction,'log'): + self.assertFalse(transaction.recover_failed_update(Path('/unused'),Path('/unused/journal'))) + recover.assert_not_called() + + if __name__=='__main__':unittest.main() diff --git a/oci/tests/test_native_modify.py b/oci/tests/test_native_modify.py index 48b89886..e108a557 100644 --- a/oci/tests/test_native_modify.py +++ b/oci/tests/test_native_modify.py @@ -151,6 +151,7 @@ class NativeApplyTests(unittest.TestCase): self.commands.append(args) if args[:2]==('pct','status'):return b'status: running' if self.state['running'] else b'status: stopped' if args[:2]==('pct','start'):self.state['running']=True + if args[:3]==('pvesh','get','/cluster/ha/resources'):return b'[]' return b'' def stop(vmid):self.state['running']=False def setter(vmid,identity,expected,values,delete=()): diff --git a/oci/tests/test_registry_accounts.py b/oci/tests/test_registry_accounts.py index 4e349c95..37235012 100644 --- a/oci/tests/test_registry_accounts.py +++ b/oci/tests/test_registry_accounts.py @@ -126,8 +126,14 @@ class RegistryMenuTests(unittest.TestCase): catalog = SimpleNamespace(index_path=SimpleNamespace(exists=lambda: True), load_index=lambda: {'applications': []}) ui = UI(['registries', None]) - with patch.object(cli, 'interactive_ui', return_value=ui), \ + with patch('proxmenux_oci.catalog_refresh.CatalogRefresh') as refresh_type, \ + patch.object(cli, 'interactive_ui', return_value=ui), \ patch.object(accounts, 'open_registry_accounts') as open_accounts: + refresher = refresh_type.return_value.__enter__.return_value + refresher.active = False + refresher.consume.return_value = False + refresher.failed = False + refresher.label.return_value = 'Refresh catalog' self.assertEqual(cli.interactive(catalog), 0) open_accounts.assert_called_once_with(ui) diff --git a/oci/tests/test_selkies_base_images.py b/oci/tests/test_selkies_base_images.py new file mode 100644 index 00000000..9c949bba --- /dev/null +++ b/oci/tests/test_selkies_base_images.py @@ -0,0 +1,50 @@ +"""An image built on the LinuxServer Selkies base, but published by someone +else, receives the same GPU profile as the LinuxServer Selkies images.""" +import copy +import json +from pathlib import Path +import sys +import unittest + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / 'src')) + +from proxmenux_oci.catalog import Catalog +from proxmenux_oci.gpu import apply_selkies_contract + +ARTCRAFT = ('cadcraft', 'deckcraft', 'designcraft', 'effectcraft', 'figcraft', 'filmcraft', 'gridcraft', + 'lightcraft', 'pdfcraft', 'photocraft', 'soundcraft', 'vectorcraft', 'wordcraft') + + +def template(reference, base): + return {'container_contract': {'image': {'reference': reference}, 'environment': []}, + 'proxmox': {'installer_profile': {'selkies': {'base': base}}}} + + +class SelkiesBaseImageTests(unittest.TestCase): + def test_an_image_built_on_the_selkies_base_gets_the_gpu_profile(self): + built = template('docker.io/junkerderprovinz/photocraft:latest', + 'FROM ghcr.io/linuxserver/baseimage-selkies:ubunturesolute') + apply_selkies_contract(built) + profiles = built['proxmox']['installer_profile']['hardware_acceleration']['profiles'] + self.assertEqual([item['id'] for item in profiles], ['none', 'vaapi', 'nvidia']) + + def test_any_other_image_is_still_refused(self): + other = template('docker.io/someone/desktop:latest', 'FROM ubuntu:24.04') + with self.assertRaises(ValueError): + apply_selkies_contract(copy.deepcopy(other)) + + def test_every_artcraft_app_offers_the_gpu_to_its_desktop(self): + catalog = Catalog(ROOT) + for app in ARTCRAFT: + with self.subTest(app=app): + profile = catalog.compose(app)['proxmox']['installer_profile'] + vaapi = next(item for item in profile['hardware_acceleration']['profiles'] if item['id'] == 'vaapi') + self.assertEqual(set(vaapi['environment_from_devices']), + {'DRINODE', 'DRI_NODE', 'ATTACHED_DEVICES_PERMS'}) + self.assertEqual(profile['device_permissions']['environment'], 'ATTACHED_DEVICES_PERMS') + self.assertEqual(profile['hardware_acceleration']['default'], 'none') + + +if __name__ == '__main__': + unittest.main() diff --git a/oci/tests/test_spoolman_recipe.py b/oci/tests/test_spoolman_recipe.py new file mode 100644 index 00000000..2e7b9203 --- /dev/null +++ b/oci/tests/test_spoolman_recipe.py @@ -0,0 +1,140 @@ +"""Spoolman uses its upstream single-image SQLite deployment in OCI Manager.""" + +import json +from pathlib import Path +import sys +import unittest +from unittest.mock import patch + + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "src")) + +from proxmenux_oci.catalog import Catalog +from proxmenux_oci.installer import DEFAULT_MODE, build_deployment + + +class StorageChoiceUI: + def __init__(self, volume_mode): + self.volume_mode = volume_mode + + def choose(self, _prompt, options, default=None, **_kwargs): + available = {key for key, _label in options} + if self.volume_mode not in available: + raise AssertionError(f"{self.volume_mode} was not offered; got {available}") + return self.volume_mode + + def ask(self, _prompt, default="", **_kwargs): + return default + + def confirm(self, _prompt, default=False): + return default + + def message(self, *_args, **_kwargs): + pass + + +class SpoolmanRecipeTests(unittest.TestCase): + def setUp(self): + self.catalog = Catalog(ROOT) + self.recipe = self.catalog.compose("spoolman") + self.generated = json.loads( + (ROOT / "catalog/apps/spoolman.json").read_text(encoding="utf-8") + ) + self.curated = json.loads( + (ROOT / "catalog/curated/spoolman.json").read_text(encoding="utf-8") + ) + + def test_generated_and_curated_recipes_match(self): + self.assertEqual(self.generated, self.curated) + + def test_official_image_and_catalog_architectures_match_engine_support(self): + image = self.recipe["container_contract"]["image"] + self.assertEqual(image["reference"], "ghcr.io/donkie/spoolman:latest") + self.assertEqual(image["registry"], "ghcr.io") + self.assertEqual(image["repository"], "ghcr.io/donkie/spoolman") + # The OCI catalog currently supports amd64 and arm64. The upstream + # manifest also has arm/v7, which the current catalog engine does not + # offer as an installable architecture. + self.assertEqual(self.recipe["catalog_ui"]["architectures"], ["amd64", "arm64"]) + self.assertEqual( + self.recipe["source"]["image_repository_url"], + "https://github.com/Donkie/Spoolman/pkgs/container/spoolman", + ) + + def test_sqlite_is_the_default_and_no_database_service_is_required(self): + contract = self.recipe["container_contract"] + self.assertEqual(self.recipe["compose_stack"]["service_count"], 1) + self.assertEqual(self.recipe["compose_stack"]["services"][0]["image"], + "ghcr.io/donkie/spoolman:latest") + self.assertEqual(contract["related_services"], []) + env_names = {item["name"] for item in contract["environment"]} + self.assertNotIn("SPOOLMAN_DB_TYPE", env_names) + self.assertNotIn("SPOOLMAN_DB_NAME", env_names) + self.assertNotIn("SPOOLMAN_DIR_DATA", env_names) + data_path = "/home/app/.local/share/spoolman" + self.assertEqual([volume["container_path"] for volume in contract["volumes"]], + [data_path]) + self.assertIn("SQLite", self.recipe["catalog_ui"]["description"]["en_US"]) + + def test_web_launch_and_first_start_healthcheck_use_container_port_8000(self): + self.assertEqual(self.recipe["container_contract"]["ports"][0]["container_port"], 8000) + self.assertEqual(self.recipe["catalog_ui"]["launch"]["port"], 8000) + self.assertEqual(self.recipe["first_run"]["endpoints"][0]["port"], 8000) + health = self.recipe["proxmox"]["installer_profile"]["startup_healthcheck"] + self.assertEqual((health["scheme"], health["port"], health["path"]), + ("http", 8000, "/")) + self.assertIn("7912:8000", self.recipe["container_contract"]["original_compose"]) + + def test_persistent_data_volume_and_application_uid_are_declared(self): + path = "/home/app/.local/share/spoolman" + volume = self.recipe["container_contract"]["volumes"][0] + policy = json.loads( + (ROOT / "catalog/volume-policy.json").read_text(encoding="utf-8") + ) + self.assertIn(path, policy["applications"]["spoolman"]) + self.assertEqual(volume["installation_choice"], ["managed-volume", "host-bind"]) + self.assertEqual(volume["default"], "managed-volume") + preparation = self.recipe["proxmox"]["installer_profile"]["volume_preparations"][0] + self.assertEqual(preparation["container_path"], path) + self.assertEqual(preparation["owner_strategy"], "mapped-application-user") + self.assertEqual(preparation["only_when_mount_type"], "managed-volume") + self.assertEqual(self.recipe["proxmox"]["installer_profile"]["volume_owner"], + {"uid": 1000, "gid": 1000}) + + def test_default_install_preserves_data_for_both_storage_choices(self): + for mode in ("managed-volume", "host-bind"): + with self.subTest(mode=mode): + with patch("proxmenux_oci.installer.ask_storage", + return_value="local-zfs"), \ + patch("proxmenux_oci.installer.host.default_bridge", + return_value="vmbr0"), \ + patch("proxmenux_oci.installer.host.timezone", + return_value="UTC"), \ + patch("proxmenux_oci.installer.access.ask_ipv4", + return_value=("dhcp", None)): + plan = build_deployment( + self.recipe, StorageChoiceUI(mode), DEFAULT_MODE + ) + + self.assertEqual(len(plan["mounts"]), 1) + mount = plan["mounts"][0] + self.assertEqual(mount["container_path"], + "/home/app/.local/share/spoolman") + self.assertEqual(mount["type"], mode) + self.assertFalse(mount["read_only"]) + if mode == "managed-volume": + self.assertEqual(mount["source"], "local-zfs") + self.assertEqual(mount["size_gb"], 4) + self.assertTrue(mount["backup"]) + else: + self.assertEqual( + mount["source"], + "/mnt/oci-shared/spoolman/home-app-local-share-spoolman", + ) + self.assertIsNone(mount["size_gb"]) + self.assertFalse(mount["backup"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/oci/tests/test_unbound_recipe.py b/oci/tests/test_unbound_recipe.py new file mode 100644 index 00000000..9cf24adc --- /dev/null +++ b/oci/tests/test_unbound_recipe.py @@ -0,0 +1,128 @@ +"""Guard the Unbound catalog contract against upstream image behavior.""" + +import json +from pathlib import Path +import sys +import unittest + +import yaml + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "src")) +sys.path.insert(0, str(ROOT / "remote")) + +from proxmenux_oci.catalog import Catalog +import oci_instance_transaction + + +class UnboundRecipeTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.curated = json.loads((ROOT / "catalog/curated/unbound.json").read_text(encoding="utf-8")) + cls.app = json.loads((ROOT / "catalog/apps/unbound.json").read_text(encoding="utf-8")) + cls.composed = Catalog(ROOT).compose("unbound") + cls.index = json.loads((ROOT / "catalog/index.json").read_text(encoding="utf-8")) + cls.index_entry = next(item for item in cls.index["applications"] if item["id"] == "unbound") + cls.original_compose = yaml.safe_load(cls.curated["container_contract"]["original_compose"]) + + def test_both_catalog_templates_and_image_metadata_match_upstream(self): + self.assertEqual(self.curated, self.app) + image = self.curated["container_contract"]["image"] + self.assertEqual(image["reference"], "klutchell/unbound:main") + self.assertEqual(image["registry"], "docker.io") + self.assertEqual(image["repository"], "klutchell/unbound") + self.assertEqual(image["tag"], "main") + self.assertIsNone(image["digest"]) # resolve the selected tag for each host architecture at install time + self.assertEqual( + image["pull_policy"], + "resolve-selected-tag-to-architecture-digest-at-install", + ) + self.assertEqual( + self.curated["source"]["repository"], + "https://github.com/klutchell/unbound-docker", + ) + self.assertEqual( + self.curated["source"]["revision"], + "1efbea960bf9462b6fb6dd71aa702ad32d6820d7", + ) + + def test_icon_and_supported_architectures_are_in_both_catalog_views(self): + icon = "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/unbound.webp" + self.assertEqual(self.curated["catalog_ui"]["icon"], icon) + self.assertEqual(self.index_entry["icon"], icon) + self.assertEqual(self.index_entry["template_id"], "image-unbound") + # The upstream manifest has amd64, arm64, arm/v7 and arm/v6. The catalog + # normalizes the two 32-bit ARM variants to armhf; the install index + # only lists the architectures currently supported by ProxMenux. + self.assertEqual(self.curated["catalog_ui"]["architectures"], ["amd64", "arm64", "armhf"]) + self.assertEqual(self.index_entry["architectures"], ["amd64", "arm64"]) + + def test_dns_listener_publishes_tcp_and_udp_53(self): + expected = {("tcp", 53), ("udp", 53)} + contract_ports = { + (port["protocol"], port["container_port"]) + for port in self.curated["container_contract"]["ports"] + } + self.assertEqual(contract_ports, expected) + + original = self.original_compose["services"]["unbound"] + self.assertEqual(set(original["ports"]), {"53:53/tcp", "53:53/udp"}) + profile_service = self.curated["compose_stack"]["services"][0]["compose"] + self.assertEqual(set(profile_service["ports"]), {"53:53/tcp", "53:53/udp"}) + + def test_upstream_entrypoint_is_preserved_and_health_uses_running_fallback(self): + image_policy = self.curated["proxmox"]["image_metadata_policy"] + self.assertEqual(image_policy["entrypoint"], "import-from-oci-image") + self.assertEqual(image_policy["cmd"], "import-from-oci-image") + + # Upstream Dockerfile sets ENTRYPOINT ["unbound"] and no HEALTHCHECK. + # Preserve the image command; check process stability on install/update, + # then verify DNS answers separately with a query to port 53. + for service in (self.original_compose["services"]["unbound"], + self.curated["compose_stack"]["services"][0]["compose"]): + self.assertNotIn("entrypoint", service) + self.assertNotIn("command", service) + self.assertNotIn("healthcheck", service) + self.assertEqual(self.curated["catalog_ui"]["launch"]["port"], None) + self.assertEqual(self.curated["first_run"]["endpoints"], []) + check = self.composed["proxmox"]["installer_profile"]["startup_healthcheck"] + self.assertEqual(check, {"type": "running", "timeout_seconds": 90, "stability_seconds": 20}) + self.assertEqual(oci_instance_transaction.effective_healthcheck(self.composed), check) + + def test_custom_configuration_mount_is_persistent_and_exposes_both_storage_modes(self): + path = "/etc/unbound/custom.conf.d" + volume = next(v for v in self.curated["container_contract"]["volumes"] + if v["container_path"] == path) + self.assertFalse(volume["read_only"]) + self.assertFalse(volume["required"]) + self.assertEqual(volume["installation_choice"], ["managed-volume", "host-bind"]) + self.assertEqual(volume["default"], "host-bind") + self.assertTrue(volume["managed_volume"]["backup"]) + + original_service = self.original_compose["services"]["unbound"] + self.assertIn("./config:/etc/unbound/custom.conf.d", original_service["volumes"]) + profile_service = self.curated["compose_stack"]["services"][0]["compose"] + self.assertIn("./config:/etc/unbound/custom.conf.d", profile_service["volumes"]) + + storage = next(v for v in self.curated["compose_stack"]["storage"] + if v["container_path"] == path) + self.assertEqual(storage["mode"], "user-selectable") + self.assertEqual(storage["installation_choice"], ["managed-volume", "host-bind"]) + self.assertTrue(storage["backup"]) + + prepared = next(v for v in self.composed["proxmox"]["installer_profile"]["volume_preparations"] + if v["container_path"] == path) + self.assertEqual(prepared["owner_strategy"], "mapped-application-user") + + def test_dns_service_remains_unprivileged_and_data_is_not_misrepresented_as_web_ui(self): + self.assertTrue(self.curated["proxmox"]["defaults"]["unprivileged"]) + self.assertFalse(self.index_entry["requires_privileged_lxc"]) + self.assertEqual(self.curated["catalog_ui"]["launch"]["port"], None) + self.assertTrue(any("no web interface" in tip.casefold() + for tip in self.curated["catalog_ui"]["tips"])) + self.assertTrue(any("do not expose recursive dns publicly" in tip.casefold() + for tip in self.curated["catalog_ui"]["tips"])) + + +if __name__ == "__main__": + unittest.main() diff --git a/oci/tests/test_update_high_availability.py b/oci/tests/test_update_high_availability.py new file mode 100644 index 00000000..e2cfdbfd --- /dev/null +++ b/oci/tests/test_update_high_availability.py @@ -0,0 +1,71 @@ +"""A container that HA manages is refused before its new image is downloaded.""" +import contextlib +import json +from pathlib import Path +import sys +import unittest +from unittest.mock import Mock, patch + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'remote')) +import oci_instance_transaction as transaction +import oci_update_current as update + + +def ha_resources(*vmids): + answer = json.dumps([{'sid': f'ct:{vmid}', 'state': 'started'} for vmid in vmids]).encode() + return patch.object(transaction, 'run', return_value=answer) + + +class HighAvailabilityTests(unittest.TestCase): + def test_the_refusal_names_the_resource_and_how_to_proceed(self): + with ha_resources(101), self.assertRaises(ValueError) as refused: + transaction.refuse_high_availability(101) + self.assertIn('ha-manager remove ct:101', str(refused.exception)) + + def test_a_container_outside_ha_is_not_refused(self): + with ha_resources(200): + transaction.refuse_high_availability(101) + + def test_a_host_without_pvesh_has_no_ha_to_refuse(self): + with patch.object(transaction, 'run', side_effect=FileNotFoundError('pvesh')): + transaction.refuse_high_availability(101) + + def test_the_preflight_of_a_replacement_refuses_it(self): + record = {'vmid': 101, 'installation_id': 'install-1', 'deployment': {}} + with patch.object(transaction.instances, 'identity', return_value='install-1'), ha_resources(101), \ + self.assertRaises(ValueError) as refused: + transaction.preflight(record, {'deployment': {}}, b'') + self.assertIn('ct:101', str(refused.exception)) + + def test_an_in_place_edit_and_a_stack_member_are_not_refused_here(self): + record = {'vmid': 101, 'installation_id': 'install-1', 'deployment': {}} + refuse = Mock() + with patch.object(transaction.instances, 'identity', return_value='install-1'), \ + patch.object(transaction, 'refuse_high_availability', refuse), \ + patch.object(transaction, 'external_changes', side_effect=RuntimeError('past the check')): + for options in ({'in_place': True}, {'coordinated': {'id': 'stack'}}): + with self.subTest(options=options), self.assertRaises(RuntimeError): + transaction.preflight(record, {'deployment': {}}, b'', **options) + refuse.assert_not_called() + + def test_an_update_stops_before_downloading_the_image(self): + record = {'status': 'installed', 'observed': {'image': {}}} + resolve = Mock() + with contextlib.ExitStack() as stack: + stack.enter_context(patch.object(update.instances, 'locked', return_value=contextlib.nullcontext())) + stack.enter_context(patch.object(update.instances, 'read', return_value=record)) + stack.enter_context(patch.object(update.instances, 'command', return_value=b'')) + stack.enter_context(patch.object(update.transaction, 'candidate_contract', return_value={})) + stack.enter_context(patch.object(update.transaction, 'external_changes', return_value={})) + stack.enter_context(patch.object(update.transaction, 'preflight', + side_effect=ValueError('managed by high availability'))) + stack.enter_context(patch.object(update, 'resolve_archive', resolve)) + stack.enter_context(patch.object(update, 'msg_info')) + stack.enter_context(patch.object(update, 'msg_ok')) + with self.assertRaises(ValueError): + update.update(101) + resolve.assert_not_called() + + +if __name__ == '__main__': + unittest.main() diff --git a/oci/tests/test_update_menu.py b/oci/tests/test_update_menu.py index cbf196b5..84ef3274 100644 --- a/oci/tests/test_update_menu.py +++ b/oci/tests/test_update_menu.py @@ -280,11 +280,20 @@ class MainMenuTests(unittest.TestCase): checker.running = False; checker.done = True raise MenuRefresh() return None - with patch.object(cli, 'interactive_ui', return_value=UI()), \ + catalog_refresh = Mock(active=False, failed=False) + catalog_refresh.consume.return_value = False + catalog_refresh.label.return_value = 'Refresh catalog' + catalog_context = Mock() + catalog_context.__enter__ = Mock(return_value=catalog_refresh) + catalog_context.__exit__ = Mock(return_value=False) + with patch('proxmenux_oci.catalog_refresh.CatalogRefresh', return_value=catalog_context), \ + patch.object(cli, 'interactive_ui', return_value=UI()), \ patch.object(updates, 'UpdateCheck', return_value=checker): self.assertEqual(cli.interactive(cli.Catalog(ROOT)), 0) - self.assertEqual(seen, ['Check for updates', 'Checking for updates (0/2)', - 'Checking for updates (1/2)', 'Updates available (2)']) + # A count lines up with the counts of the categories; progress does not. + self.assertEqual(seen[:3], ['Check for updates', 'Checking for updates (0/2)', + 'Checking for updates (1/2)']) + self.assertRegex(seen[3], r'^Updates available {2,}\( 2\)$') class TranslationTests(unittest.TestCase): diff --git a/oci/tests/test_zwave_js_ui_recipe.py b/oci/tests/test_zwave_js_ui_recipe.py new file mode 100644 index 00000000..ee1ae24d --- /dev/null +++ b/oci/tests/test_zwave_js_ui_recipe.py @@ -0,0 +1,94 @@ +"""Z-Wave JS UI's native OCI profile is safe to start without a radio controller.""" +from pathlib import Path +import sys +import unittest + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "src")) +sys.path.insert(0, str(ROOT / "remote")) + +from proxmenux_oci.catalog import Catalog, supported_architectures +import oci_instance_transaction as transaction + + +class ZWaveJsUIRecipeTests(unittest.TestCase): + def setUp(self): + self.catalog = Catalog(ROOT) + self.curated_path = ROOT / "catalog/curated/zwave-js-ui.json" + self.generated_path = ROOT / "catalog/apps/zwave-js-ui.json" + + def test_generated_template_matches_curated_profile(self): + self.assertEqual(self.curated_path.read_bytes(), self.generated_path.read_bytes()) + + def test_uses_the_official_image_and_only_catalog_supported_architectures(self): + template = self.catalog.compose("zwave-js-ui") + self.assertEqual(template["id"], "image-zwave-js-ui") + self.assertEqual( + template["container_contract"]["image"]["reference"], + "docker.io/zwavejs/zwave-js-ui:latest", + ) + self.assertEqual( + template["catalog_ui"]["architectures"], ["amd64", "arm64"] + ) + self.assertEqual( + supported_architectures(template["catalog_ui"]["architectures"]), + ["amd64", "arm64"], + ) + self.assertIn( + "/c38e0d2cbbb826bf2bab2db596a3452e59ce0e8e/public/apple-touch-icon-180x180.png", + template["catalog_ui"]["icon"], + ) + + def test_web_ui_is_the_startup_check_when_no_controller_is_attached(self): + template = self.catalog.compose("zwave-js-ui") + check = transaction.effective_healthcheck(template) + self.assertEqual((check["scheme"], check["port"], check["path"]), + ("http", 8091, "/")) + self.assertNotEqual(check["path"], "/health") + endpoint = template["first_run"]["endpoints"][0] + self.assertEqual((endpoint["port"], endpoint["path"]), (8091, "/")) + devices = template["proxmox"]["installer_profile"]["optional_devices"] + self.assertEqual(len(devices), 1) + self.assertFalse(devices[0]["enabled_default"]) + self.assertFalse(devices[0]["required_by_compose"]) + self.assertTrue(devices[0]["host_path_default"].startswith("/dev/serial/by-id/")) + + def test_fresh_store_enables_login_and_keeps_generated_credentials_out_of_recipe(self): + template = self.catalog.compose("zwave-js-ui") + profile = template["proxmox"]["installer_profile"] + generated = profile["generated_sensitive_environment"] + self.assertEqual(generated["SESSION_SECRET"]["bytes"], 32) + self.assertEqual(generated["DEFAULT_PASSWORD"]["bytes"], 20) + seeded = next(item for item in profile["generated_files"] + if item["container_path"] == "/usr/src/app/store/settings.json") + self.assertTrue(seeded["only_if_missing"]) + self.assertTrue(seeded["owner"] == "mapped-root") + self.assertIn('"authEnabled":true', seeded["content"]) + credential = template["first_run"]["credentials"][0] + self.assertEqual(credential["username"], "admin") + self.assertIsNone(credential["password"]) + self.assertEqual(credential["password_environment"], "DEFAULT_PASSWORD") + self.assertNotIn("mysupersecretkey", template["container_contract"]["original_compose"]) + + def test_state_is_persisted_on_a_managed_volume(self): + template = self.catalog.compose("zwave-js-ui") + volume = template["container_contract"]["volumes"][0] + self.assertEqual(volume["container_path"], "/usr/src/app/store") + self.assertEqual(volume["installation_choice"], ["managed-volume"]) + self.assertTrue(volume["managed_volume"]["backup"]) + prep = template["proxmox"]["installer_profile"]["volume_preparations"] + self.assertEqual(prep[0]["container_path"], "/usr/src/app/store") + self.assertEqual(prep[0]["owner_strategy"], "mapped-root") + + def test_includes_web_and_optional_zwave_websocket_ports(self): + template = self.catalog.compose("zwave-js-ui") + ports = {item["container_port"]: item + for item in template["container_contract"]["ports"]} + self.assertTrue(ports[8091]["required"]) + self.assertFalse(ports[3000]["required"]) + self.assertEqual(ports[8091]["protocol"], "tcp") + self.assertEqual(ports[3000]["protocol"], "tcp") + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/global/log2ram_keep_on_disk.sh b/scripts/global/log2ram_keep_on_disk.sh index bbffe243..3e183e74 100755 --- a/scripts/global/log2ram_keep_on_disk.sh +++ b/scripts/global/log2ram_keep_on_disk.sh @@ -30,20 +30,21 @@ keep_on_disk() { local place="$LOG_DIR/$name" disk="$ROOT/var/hdd.$name" if [ -L "$place" ]; then - [ -d "$disk" ] || mkdir -p "$disk" + [ "$(readlink "$place")" = "/var/hdd.$name" ] || return 1 + [ -d "$disk" ] || mkdir -p "$disk" || return 1 return 0 fi # The journal is always kept on disk; any other folder only where it exists. if [ ! -d "$place" ]; then [ "$name" = journal ] || return 0 - mkdir -p "$disk" + mkdir -p "$disk" || return 1 chown root:systemd-journal "$disk" 2>/dev/null || true chmod 2755 "$disk" elif [ ! -e "$disk" ]; then mv "$place" "$disk" || return 1 else cp -a "$place/." "$disk/" || return 1 - rm -rf "${place:?}" + rm -rf "${place:?}" || return 1 fi ln -s "/var/hdd.$name" "$place" } @@ -70,4 +71,8 @@ for name in $KEPT_ON_DISK; do keep_on_disk "$name" || status=1 fi done +# Only a completed boot migration makes it safe to skip journal rotation. +if [ "$status" = 0 ] && [ "${PMX_L2R_REVERT:-0}" != 1 ]; then + sed -i 's/^JOURNALD_AWARE=.*/JOURNALD_AWARE=false/' "$ROOT/etc/log2ram.conf" || status=1 +fi exit "$status" diff --git a/scripts/global/log2ram_setup.sh b/scripts/global/log2ram_setup.sh index 02f5abff..fdb7a485 100644 --- a/scripts/global/log2ram_setup.sh +++ b/scripts/global/log2ram_setup.sh @@ -112,19 +112,22 @@ pmx_log2ram_setup() { _pmx_log2ram_install_release || return 1 msg_ok "$(translate "Log2RAM installed:") ${PMX_L2R_TAG}" - # The journal is not in RAM, so Log2RAM does not rotate it before a sync; - # and Proxmox has no mail command for its error notice. + # Keep journal-aware syncing through shutdown: the running journal may + # still be in RAM. The boot helper disables it only after relocation. + # Proxmox has no mail command for the error notice. pmx_edit_file /etc/log2ram.conf \ -e "s/^SIZE=.*/SIZE=${size_mb}M/" \ - -e "s/^JOURNALD_AWARE=.*/JOURNALD_AWARE=false/" \ - -e "s/^#\?NOTIFICATION=.*/NOTIFICATION=false/" + -e "s/^JOURNALD_AWARE=.*/JOURNALD_AWARE=true/" \ + -e "s/^#\?NOTIFICATION=.*/NOTIFICATION=false/" || return 1 + grep -q '^JOURNALD_AWARE=' /etc/log2ram.conf || \ + printf '\nJOURNALD_AWARE=true\n' | pmx_append_file /etc/log2ram.conf || return 1 grep -q '^NOTIFICATION=' /etc/log2ram.conf || printf 'NOTIFICATION=false\n' | pmx_append_file /etc/log2ram.conf mkdir -p /etc/systemd/system/log2ram.service.d /etc/systemd/system/log2ram-daily.timer.d pmx_write_file /etc/systemd/system/log2ram.service.d/proxmenux.conf <[0-9A-F]{4}' \ - | grep -aoiE '[0-9A-F]{4}' \ + | grep -aoiE '/dev/null) versions_list=$(list_available_versions 2>/dev/null) if [[ -z "$latest" ]] && [[ -z "$versions_list" ]]; then - stop_spinner - hybrid_msgbox "$(translate 'Error')" \ - "$(translate 'Could not retrieve versions list from NVIDIA. Please check your internet connection.')\n\nURL: ${NVIDIA_BASE_URL}" 10 80 - DRIVER_VERSION="cancel" return 1 fi @@ -2115,6 +2116,47 @@ show_version_menu() { latest=$(printf '%s\n' "$current_list" | head -n1 | tr -d '[:space:]') fi + NVIDIA_OFFERED_VERSIONS="$current_list" + NVIDIA_RECOMMENDED_VERSION="$latest" + NVIDIA_PATCH_FILTERED="$patch_filtered" + NVIDIA_PATCH_FILTER_NOTE="$patch_filter_note" +} + +# For ProxMenux Monitor: the version the menu would recommend on this host for +# the driver in use (passed in, or read from nvidia-smi), with no interface and +# no change to the host. Prints nothing when none is recommended. +print_recommended_version() { + detect_nvidia_gpus + $NVIDIA_GPU_PRESENT || return 1 + CURRENT_DRIVER_VERSION="${1:-}" + if [[ -z "$CURRENT_DRIVER_VERSION" ]] && command -v nvidia-smi >/dev/null 2>&1; then + CURRENT_DRIVER_VERSION=$(nvidia-smi --query-gpu=driver_version --format=csv,noheader 2>/dev/null | head -n1) + fi + compute_nvidia_offer || return 2 + [[ -z "$NVIDIA_RECOMMENDED_VERSION" ]] || printf '%s\n' "$NVIDIA_RECOMMENDED_VERSION" +} + +show_version_menu() { + local latest versions_list + local kernel_version + kernel_version=$(uname -r) + + show_proxmenux_logo + msg_title "$(translate 'NVIDIA GPU Driver Installation')" + msg_info "$(translate 'Fetching NVIDIA driver versions supported by your GPU...')" + + if ! compute_nvidia_offer; then + stop_spinner + hybrid_msgbox "$(translate 'Error')" \ + "$(translate 'Could not retrieve versions list from NVIDIA. Please check your internet connection.')\n\nURL: ${NVIDIA_BASE_URL}" 10 80 + DRIVER_VERSION="cancel" + return 1 + fi + local current_list="$NVIDIA_OFFERED_VERSIONS" + local patch_filtered="$NVIDIA_PATCH_FILTERED" + local patch_filter_note="$NVIDIA_PATCH_FILTER_NOTE" + latest="$NVIDIA_RECOMMENDED_VERSION" + local menu_text="$(translate 'Select the NVIDIA driver version to install:')\n\n" menu_text+="$(translate 'Versions shown belong to maintained NVIDIA branches that list your GPU PCI ID and are new enough to build against the running kernel. DKMS compilation is the final validation. The recommended version keeps the current branch, or uses the NVIDIA Production Branch on a fresh install.')" menu_text+="\n\n$(translate 'Running kernel:') ${kernel_version} — $(translate 'oldest driver offered:') $(nvidia_kernel_driver_floor)" @@ -2546,5 +2588,9 @@ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then auto_reinstall_from_state exit $? fi + if [[ "${1:-}" == "--recommended-version" ]]; then + print_recommended_version "${2:-}" + exit $? + fi main fi diff --git a/scripts/oci/oci_manager_apps.sh b/scripts/oci/oci_manager_apps.sh index 2e0f813f..6e6b2143 100755 --- a/scripts/oci/oci_manager_apps.sh +++ b/scripts/oci/oci_manager_apps.sh @@ -32,7 +32,11 @@ initialize_cache ensure_oci_dependencies() { local -a missing=() - python3 -c 'import yaml' >/dev/null 2>&1 || missing+=("python3-yaml") + # One Python start when both are present; each is checked only if not. + if ! python3 -c 'import yaml, jsonschema' >/dev/null 2>&1; then + python3 -c 'import yaml' >/dev/null 2>&1 || missing+=("python3-yaml") + python3 -c 'import jsonschema' >/dev/null 2>&1 || missing+=("python3-jsonschema") + fi command -v skopeo >/dev/null 2>&1 || missing+=("skopeo") [[ ${#missing[@]} -eq 0 ]] && return 0 @@ -56,12 +60,15 @@ ensure_oci_dependencies() { stop_spinner } -# Proxmox VE runs OCI images as LXC containers from 9.1 on. +# Proxmox VE runs OCI images as LXC containers from 9.1 on. The package +# database answers at once; pveversion takes about half a second. +pve_manager_version() { + dpkg-query -W -f='${Version}' pve-manager 2>/dev/null | grep -oE '^[0-9]+(\.[0-9]+)+' | head -1 +} + pve_supports_oci() { - local version major minor - version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+' | head -1) - major=${version%%.*} - minor=${version#*.} + local major minor + IFS=. read -r major minor _ <<< "$(pve_manager_version)" [[ $major =~ ^[0-9]+$ && $minor =~ ^[0-9]+$ ]] || return 1 (( major > 9 || (major == 9 && minor >= 1) )) } @@ -70,7 +77,7 @@ if ! pve_supports_oci; then show_proxmenux_logo msg_title "$(translate "OCI manager Apps (Technology Preview)")" msg_error "$(translate "OCI containers require Proxmox VE 9.1 or later.")" - PVE_VERSION=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9.]+' | head -1) + PVE_VERSION=$(pve_manager_version) [[ -n $PVE_VERSION ]] && msg_info2 "$(translate "Current version:") $PVE_VERSION" if [[ -t 0 ]]; then echo diff --git a/scripts/post_install/auto_post_install.sh b/scripts/post_install/auto_post_install.sh index 2dbc4992..1891d724 100644 --- a/scripts/post_install/auto_post_install.sh +++ b/scripts/post_install/auto_post_install.sh @@ -973,7 +973,7 @@ _update_existing_log2ram_auto() { install_log2ram_auto() { - local FUNC_VERSION="2.0" + local FUNC_VERSION="2.1" local existing_log2ram_bin="" pmx_journal_context "install_log2ram_auto" "$FUNC_VERSION" diff --git a/scripts/post_install/customizable_post_install.sh b/scripts/post_install/customizable_post_install.sh index d3512659..2d496ff7 100644 --- a/scripts/post_install/customizable_post_install.sh +++ b/scripts/post_install/customizable_post_install.sh @@ -3303,7 +3303,7 @@ _update_existing_log2ram_custom() { configure_log2ram() { - local FUNC_VERSION="2.0" + local FUNC_VERSION="2.1" local existing_log2ram_bin="" local watch=false pmx_journal_context "configure_log2ram" "$FUNC_VERSION" diff --git a/tests/test_kernel_reboot.py b/tests/test_kernel_reboot.py index 5efd7096..b4b3213a 100644 --- a/tests/test_kernel_reboot.py +++ b/tests/test_kernel_reboot.py @@ -101,7 +101,7 @@ class Reboot(unittest.TestCase): import __future__ scope=dict(os=kernel.os,time=time,subprocess=kernel.subprocess) exec(compile(ast.Module(body=[node],type_ignores=[]),'actual-health','exec',flags=__future__.annotations.compiler_flag),scope) - for age in (0,599.999): + for age in (0,590): cache=dict(status='OK',count=0,checks={}) owner=SimpleNamespace(last_check_times={'updates_check':time.time()-age},cached_results={'updates_check':cache}) with patch.object(kernel.os.path,'exists',return_value=True),patch.object(kernel.os.path,'getmtime',return_value=0),patch.object(kernel.subprocess,'run',return_value=subprocess.CompletedProcess([],0,listing('7.0.14-26-pve'),'')) as probe: diff --git a/tests/test_nvidia_monitor_uses_installer.py b/tests/test_nvidia_monitor_uses_installer.py new file mode 100644 index 00000000..202f46b5 --- /dev/null +++ b/tests/test_nvidia_monitor_uses_installer.py @@ -0,0 +1,91 @@ +"""ProxMenux Monitor announces the NVIDIA driver the installer would recommend, +worked out by the same installer function the version menu uses.""" +from pathlib import Path +import re +import subprocess +import sys +import unittest +from unittest.mock import patch + +ROOT = Path(__file__).resolve().parents[1] +INSTALLER = (ROOT / "scripts/gpu_tpu/nvidia_installer.sh").read_text() +sys.path.insert(0, str(ROOT / "AppImage/scripts")) + +import managed_installs # noqa: E402 + + +def function(name): + return re.search(rf"^{name}\(\) \{{\n.*?^\}}\n", INSTALLER, re.M | re.S).group(0) + + +def answer(stdout, returncode=0): + return subprocess.CompletedProcess([], returncode, stdout, "") + + +class MonitorAsksTheInstallerTests(unittest.TestCase): + def setUp(self): + installer = patch.object(managed_installs, "_NVIDIA_INSTALLER", str(ROOT / "scripts/gpu_tpu/nvidia_installer.sh")) + installer.start() + self.addCleanup(installer.stop) + + def check(self, current, stdout, returncode=0): + with patch.object(managed_installs.subprocess, "run", return_value=answer(stdout, returncode)) as run: + result = managed_installs._check_nvidia_xfree86({"current_version": current}) + return result, run + + def test_an_older_installer_is_not_run(self): + import tempfile + with tempfile.NamedTemporaryFile("w", suffix=".sh") as old: + old.write("main\n"); old.flush() + with patch.object(managed_installs, "_NVIDIA_INSTALLER", old.name), \ + patch.object(managed_installs.subprocess, "run") as run: + result = managed_installs._check_nvidia_xfree86({"current_version": "595.80"}) + run.assert_not_called() + self.assertFalse(result["available"]) + self.assertIn("older", result["error"]) + + def test_the_recommended_version_is_announced_and_the_host_tools_are_used(self): + with patch.dict(managed_installs.os.environ, {"LD_LIBRARY_PATH": "/appimage/lib"}): + result, run = self.check("595.80", "595.112.07\n") + self.assertEqual((result["available"], result["latest"], result["_upgrade_kind"]), + (True, "595.112.07", "patch")) + argv, kwargs = run.call_args.args[0], run.call_args.kwargs + self.assertEqual(argv[-2:], ["--recommended-version", "595.80"]) + self.assertNotIn("LD_LIBRARY_PATH", kwargs["env"]) + + def test_nothing_is_announced_when_the_installer_recommends_the_installed_driver(self): + result, _ = self.check("580.196.07", "580.196.07\n") + self.assertEqual((result["available"], result["latest"], result["error"]), (False, None, None)) + + def test_nothing_is_announced_when_the_installer_offers_nothing(self): + # A GPU no maintained branch supports: the installer recommends nothing. + result, _ = self.check("470.256.02", "") + self.assertEqual((result["available"], result["error"]), (False, None)) + + def test_a_failed_version_list_is_an_error_not_an_update(self): + result, _ = self.check("595.80", "", returncode=2) + self.assertFalse(result["available"]) + self.assertIn("could not read", result["error"]) + + def test_a_branch_change_is_marked(self): + result, _ = self.check("570.211.01", "595.112.07\n") + self.assertEqual(result["_upgrade_kind"], "branch") + + +class OneCalculationTests(unittest.TestCase): + def test_the_menu_and_the_monitor_mode_share_the_calculation(self): + self.assertIn("compute_nvidia_offer", function("show_version_menu")) + self.assertIn("compute_nvidia_offer", function("print_recommended_version")) + self.assertNotIn("download.nvidia.com", (ROOT / "AppImage/scripts/managed_installs.py").read_text()) + + def test_the_monitor_mode_prints_only_the_recommendation(self): + stubs = ('detect_nvidia_gpus() { NVIDIA_GPU_PRESENT=true; }\n' + 'compute_nvidia_offer() { NVIDIA_RECOMMENDED_VERSION="595.112.07"; }\n') + done = subprocess.run(["bash", "-c", stubs + function("print_recommended_version") + + "print_recommended_version 595.80"], + capture_output=True, text=True) + self.assertEqual((done.returncode, done.stdout), (0, "595.112.07\n")) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_nvidia_supported_gpu_ids.py b/tests/test_nvidia_supported_gpu_ids.py new file mode 100644 index 00000000..a50d7b43 --- /dev/null +++ b/tests/test_nvidia_supported_gpu_ids.py @@ -0,0 +1,72 @@ +"""Parse NVIDIA device rows without confusing subsystem or legacy IDs.""" +from pathlib import Path +import re +import subprocess +import tempfile +import unittest + + +SOURCE = (Path(__file__).resolve().parents[1] / "scripts/gpu_tpu/nvidia_installer.sh").read_text() + + +def function(name): + return re.search(rf"^{name}\(\) \{{\n.*?^\}}\n", SOURCE, re.M | re.S).group(0) + + +class SupportedGpuIdsTests(unittest.TestCase): + def parse(self, html): + result = subprocess.run( + ["bash", "-c", function("_nvidia_supported_ids_from_page") + + "_nvidia_supported_ids_from_page"], + input=html, text=True, capture_output=True, + ) + self.assertEqual(result.returncode, 0, result.stderr) + return result.stdout.splitlines() + + def test_subsystem_qualified_t400_and_t1000_are_supported(self): + # NVIDIA 595.112.07 lists these devices only in qualified rows. + self.assertEqual(self.parse(''' +NVIDIA T1000 8GB +1FF0 1028 1612 +NVIDIA T400 4GB +1FF2 10DE 18FF +'''), ["1ff0", "1ff2"]) + + def test_unqualified_and_duplicate_rows_are_sorted_and_deduplicated(self): + self.assertEqual(self.parse(''' +Quadro RTX 60001E30 +Quadro RTX 8000 +A10020B0 +'''), ["1e30", "20b0"]) + + def test_legacy_rows_and_unrelated_hex_cells_are_excluded(self): + self.assertEqual(self.parse(''' +A10020B0 +1234 +

Below are the legacy GPUs that are no longer supported in the +current driver.

+Quadro P10001CB1 +'''), ["20b0"]) + + def test_cache_refresh_does_not_reuse_fresh_v2_device_list(self): + prefix = re.search(r'^NVIDIA_GPU_SUPPORT_CACHE_PREFIX="([^"]+)"', SOURCE, re.M).group(1) + self.assertTrue(prefix.endswith("_v3_")) + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + (root / "nvidia_gpu_support_v2_595.txt").write_text("1e30\n") + script = '\n'.join([ + 'NVIDIA_GPU_SUPPORT_CACHE_PREFIX="' + str(root / Path(prefix).name) + '"', + 'NVIDIA_BRANCHES_TTL_SECONDS=86400', + 'get_nvidia_branch_head() { echo 595.112.07; }', + 'curl() { echo \'1FF2 10DE 18FF\'; }', + function("_nvidia_supported_ids_from_page"), + function("refresh_nvidia_gpu_support_for_major"), + 'refresh_nvidia_gpu_support_for_major 595', + ]) + result = subprocess.run(["bash", "-c", script], text=True, capture_output=True) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual((root / "nvidia_gpu_support_v3_595.txt").read_text(), "1ff2\n") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_vm_backup_jobs_api.py b/tests/test_vm_backup_jobs_api.py index b5e50396..70699ba9 100644 --- a/tests/test_vm_backup_jobs_api.py +++ b/tests/test_vm_backup_jobs_api.py @@ -1,8 +1,11 @@ """VM/CT backup jobs API: what reaches pvesh, with pvesh itself replaced.""" import importlib import importlib.util +import contextlib import json +import subprocess import sys +import tempfile import types import unittest from pathlib import Path @@ -95,7 +98,14 @@ class VmBackupJobsApiTests(unittest.TestCase): return False, "storage is not online" return True, f'"UPID:{node}:0001:0002:0003:vzdump::root@pam:"\n' return True, "" - return patch.object(self.routes, "_pvesh", side_effect=run) + + def start(node, arguments, job_id): + ok, out = run(["create", f"/nodes/{node}/vzdump", *arguments]) + return (out.strip().strip('"').rstrip(":"), None) if ok else (None, out) + stack = contextlib.ExitStack() + stack.enter_context(patch.object(self.routes, "_pvesh", side_effect=run)) + stack.enter_context(patch.object(self.routes, "_start_vzdump", side_effect=start)) + return stack def changes(self): return [call for call in self.calls if call[0] in ("create", "set", "delete")] @@ -234,6 +244,57 @@ class VmBackupJobsApiTests(unittest.TestCase): self.assertEqual([failure["node"] for failure in answer["failures"]], ["pve2"]) self.assertNotIn("/nodes/pve3/vzdump", [call[1] for call in self.calls]) + def test_a_run_starts_vzdump_detached_and_answers_with_its_upid(self): + # pvesh waits for the whole backup; a child of the Monitor is killed + # with it. The task runs in its own unit and is followed by its UPID. + upid = "UPID:pve1:0001:0002:0003:vzdump::root@pam:" + launched = [] + + def systemd_run(command, **kwargs): + launched.append(command) + if command[0] == "systemd-run": + output = next(part.split("file:", 1)[1] for part in command if part.startswith("--property=StandardOutput=file:")) + Path(output).write_text(f"{upid}\n") + return subprocess.CompletedProcess(command, 0, "active\n", "") + with tempfile.TemporaryDirectory() as folder, patch.object(self.routes, "RUN_DIR", folder), \ + patch.object(self.routes.shutil, "which", return_value="/usr/bin/systemd-run"), \ + patch.object(self.routes.subprocess, "run", side_effect=systemd_run): + self.assertEqual(self.routes._start_vzdump("pve1", ["--vmid", "101"], "backup-nightly"), (upid.rstrip(":"), None)) + unit = launched[0] + self.assertEqual(unit[0], "systemd-run") + self.assertEqual(unit[unit.index("pvesh"):], ["pvesh", "create", "/nodes/pve1/vzdump", "--vmid", "101"]) + + def test_a_long_run_is_found_among_the_active_tasks_while_pvesh_still_waits(self): + # Until the backup ends pvesh prints only its log, never the UPID. + upid = "UPID:pve1:0001:0002:0003:vzdump:107:root@pam:" + + def systemd_run(command, **kwargs): + if command[0] == "systemd-run": + output = next(part.split("file:", 1)[1] for part in command if part.startswith("--property=StandardOutput=file:")) + Path(output).write_text("INFO: starting new backup job: vzdump 107\n") + return subprocess.CompletedProcess(command, 0, "", "") + if command[0] == "systemctl": + return subprocess.CompletedProcess(command, 0, "active\n", "") + tasks = [{"upid": "UPID:pve1:0009:0002:0001:vzdump:200:root@pam:", "starttime": 1}, + {"upid": upid, "starttime": 2_000_000_000}] + return subprocess.CompletedProcess(command, 0, json.dumps(tasks), "") + with tempfile.TemporaryDirectory() as folder, patch.object(self.routes, "RUN_DIR", folder), \ + patch.object(self.routes.shutil, "which", return_value="/usr/bin/systemd-run"), \ + patch.object(self.routes.subprocess, "run", side_effect=systemd_run): + self.assertEqual(self.routes._start_vzdump("pve1", ["--vmid", "107"], "backup-nightly"), (upid.rstrip(":"), None)) + + def test_a_run_that_ends_without_a_upid_reports_what_pvesh_said(self): + def systemd_run(command, **kwargs): + if command[0] == "systemd-run": + output = next(part.split("file:", 1)[1] for part in command if part.startswith("--property=StandardOutput=file:")) + Path(output).write_text("storage 'pbs' is not online\n") + return subprocess.CompletedProcess(command, 0, "", "") + return subprocess.CompletedProcess(command, 3, "inactive\n", "") + with tempfile.TemporaryDirectory() as folder, patch.object(self.routes, "RUN_DIR", folder), \ + patch.object(self.routes.shutil, "which", return_value="/usr/bin/systemd-run"), \ + patch.object(self.routes.subprocess, "run", side_effect=systemd_run): + self.assertEqual(self.routes._start_vzdump("pve1", [], "backup-nightly"), (None, "storage 'pbs' is not online")) + def test_a_job_whose_guests_are_on_no_online_node_starts_nothing(self): self.job["vmid"] = "999" with self.pvesh(): diff --git a/web/app/[locale]/docs/backup-restore/creating-backups/page.tsx b/web/app/[locale]/docs/backup-restore/creating-backups/page.tsx index f7738a87..95a7dffe 100644 --- a/web/app/[locale]/docs/backup-restore/creating-backups/page.tsx +++ b/web/app/[locale]/docs/backup-restore/creating-backups/page.tsx @@ -58,7 +58,6 @@ export default async function CreatingBackupsPage({ matrix: { rows: MatrixRow[] } commonPipeline: { steps: StepRow[] } included: { - globalItems: string[] rootItems: string[] proxmenuxItems: string[] notInProfileItems: string[] @@ -71,7 +70,6 @@ export default async function CreatingBackupsPage({ const entryRows = cb.entryPoints.rows const matrixRows = cb.matrix.rows const pipelineSteps = cb.commonPipeline.steps - const globalItems = cb.included.globalItems const rootItems = cb.included.rootItems const proxmenuxItems = cb.included.proxmenuxItems const notInProfileItems = cb.included.notInProfileItems @@ -285,19 +283,6 @@ export default async function CreatingBackupsPage({ {t.rich("included.intro", { code })}

-

- {t.rich("included.globalTitle", { code })} -

- -
    - {globalItems.map((_, idx) => ( -
  • - • - {t.rich(`included.globalItems.${idx}`, { code })} -
  • - ))} -
-

{t.rich("included.rootTitle", { code })}

diff --git a/web/app/[locale]/docs/monitor/architecture/page.tsx b/web/app/[locale]/docs/monitor/architecture/page.tsx index 1002abe5..7a6aa18d 100644 --- a/web/app/[locale]/docs/monitor/architecture/page.tsx +++ b/web/app/[locale]/docs/monitor/architecture/page.tsx @@ -165,13 +165,13 @@ export default async function MonitorArchitecturePage({ "`} - name: "ProxMenux Monitor Update" unique_id: proxmenux_monitor_update value_template: > - {{ 'update available' if (value_json.stable or value_json.beta) else 'up to date' }} + {{ 'update available' if (value_json.update_available.stable or value_json.update_available.beta) else 'up to date' }} + json_attributes_path: "$.update_available" json_attributes: - stable - stable_version diff --git a/web/app/[locale]/docs/settings/beta-program/page.tsx b/web/app/[locale]/docs/settings/beta-program/page.tsx index 93aef76f..b848ee4f 100644 --- a/web/app/[locale]/docs/settings/beta-program/page.tsx +++ b/web/app/[locale]/docs/settings/beta-program/page.tsx @@ -144,7 +144,7 @@ export default async function BetaProgramPage({

{t("manual.heading")}

{t("manual.intro")}

-
{`# Check current channel (returns "beta" or "stable")
+      
{`# Check current channel (returns "active" for beta, or "stable")
 jq -r '.beta_program.status // "stable"' /usr/local/share/proxmenux/config.json
 
 # Switch to Stable
diff --git a/web/messages/de/docs/backup-restore/creating-backups.json b/web/messages/de/docs/backup-restore/creating-backups.json
index b33b1a24..0b9d3c22 100644
--- a/web/messages/de/docs/backup-restore/creating-backups.json
+++ b/web/messages/de/docs/backup-restore/creating-backups.json
@@ -21,8 +21,8 @@
     "rows": [
       {
         "entry": "ProxMenux Scripts (TUI)",
-        "path": "menu → Utilities → Host Config Backup",
-        "detail": "Dialogbasierter Flow, SSH-freundlich. Das Hauptmenü präsentiert die sechs Optionen direkt. Verwendet backup_menu in backup_host.sh."
+        "path": "menu → Host Backup & Restore → Backup host configuration",
+        "detail": "Dialogbasierter Flow, SSH-freundlich. Der Hauptmenüeintrag öffnet Host Config Backup / Restore; Backup host configuration führt zu den sechs Optionen. Verwendet backup_menu in backup_host.sh."
       },
       {
         "entry": "ProxMenux Monitor (Weboberfläche)",
@@ -83,7 +83,7 @@
   "profiles": {
     "heading": "Standardprofil und benutzerdefiniertes Profil",
     "defaultTitle": "Standardprofil",
-    "defaultBody": "Das Standardprofil ist die kuratierte Liste aus hb_default_profile_paths (dokumentiert in Funktionsweise unter Pfadkategorien) sowie jeder Eintrag in der persistenten Extras-Datei /usr/local/share/proxmenux/backup-extra-paths.txt. Der Benutzer bestätigt die Ziel- und Verschlüsselungsoptionen und das Backup verläuft ohne weitere Pfadauswahl.",
+    "defaultBody": "Das Standardprofil ist die kuratierte Liste aus hb_default_profile_paths (dokumentiert in Funktionsweise unter Pfadkategorien) sowie jeder Eintrag in der persistenten Extras-Datei /usr/local/share/proxmenux/backup-extra-paths.txt. Der Benutzer wählt das Ziel und die Verschlüsselungsoptionen aus und das Backup verläuft ohne weitere Pfadauswahl.",
     "customTitle": "Benutzerdefiniertes Profil",
     "customBody": "Das benutzerdefinierte Profil öffnet eine Checkliste mit allen Pfaden des Standardprofils (nicht markiert) und allen persistenten Extras (vormarkiert, mit dem Präfix [+]). Der Benutzer markiert die Pfade für diesen Durchlauf und kann über Benutzerdefinierten Pfad hinzufügen einen neuen absoluten Pfad ergänzen. Jeder dabei hinzugefügte Pfad wird in backup-extra-paths.txt gespeichert, damit künftige Backups ihn automatisch berücksichtigen. Das Abwählen eines persistenten Extras gilt nur für diesen Durchlauf und löscht es nicht aus der Datei — dafür gibt es außerhalb des Backup-Ablaufs die separate Aktion Benutzerdefinierte Pfade verwalten.",
     "customPickerAlt": "Benutzerdefinierte Profil-Checkliste mit den Standard-Profilpfaden (nicht markiert) und persistenten Extras (vormarkiert mit einem [+]-Präfix) sowie Schaltflächen, um einen neuen Pfad hinzuzufügen oder die Auswahl zu bestätigen.",
@@ -124,14 +124,7 @@
   },
   "included": {
     "heading": "Was reingeht und was draußen bleibt",
-    "intro": "Jeder Pfad im aufgelösten Profil (Standard + persistente Extras + Custom-Mode-Auswahl) wird mit rsync -aAXH --numeric-ids kopiert. Eine gemeinsame Ausschlussliste gilt für jeden Pfad, und zwei Verzeichnisse tragen zusätzliche pfadspezifische Ausschlüsse.",
-    "globalTitle": "Globale Ausschlüsse (auf jeden Pfad angewendet)",
-    "globalItems": [
-      "images/ — Image Dumps.",
-      "dump/ — Ausgaben von vzdump.",
-      "tmp/ — temporäre Dateien.",
-      "*.log — Protokolldateien."
-    ],
+    "intro": "Jeder Pfad im aufgelösten Profil (Standard + persistente Extras + Custom-Mode-Auswahl) wird mit rsync -aAXH --numeric-ids kopiert. Es gibt keine globale Ausschlussliste; einige Verzeichnisse tragen pfadspezifische Ausschlüsse.",
     "rootTitle": "/root/ Ausschlüsse",
     "rootBody": "/root/ ist Teil des Standardprofils, sodass Benutzerskripte und Konfigurationen im Archiv landen. Flüchtige Subpfade werden fallen gelassen:",
     "rootItems": [
@@ -160,16 +153,16 @@
       "/home/USER — nicht im Standardprofil. Fügen Sie es als benutzerdefinierten Pfad hinzu, wenn ein System Heimverzeichnisse des Benutzers trägt, die eine Wiederherstellung überleben müssen."
     ],
     "customPathsTitle": "Wie benutzerdefinierte Pfade gehandhabt werden",
-    "customPathsBody": "Ein benutzerdefinierter Pfad, der im benutzerdefinierten Modus hinzugefügt wurde oder in backup-extra-paths.txt persistiert wurde, durchläuft dieselbe rsync-Pipeline wie Standardprofilpfade. Globale Ausschlüsse gelten. Wenn der benutzerdefinierte Pfad unter /root/ oder /usr/local/share/proxmenux/ liegt, gelten die oben genannten pfadspezifischen Ausschlüsse weiterhin. Jeder archivierte Pfad – standardmäßig oder benutzerdefiniert – wird in metadata/paths_archived.txt aufgezeichnet. Pfade, die in der Quelle nicht vorhanden sind, werden in metadata/missing_paths.txt aufgezeichnet, ohne das Backup zu stoppen."
+    "customPathsBody": "Ein benutzerdefinierter Pfad, der im benutzerdefinierten Modus hinzugefügt wurde oder in backup-extra-paths.txt persistiert wurde, durchläuft dieselbe rsync-Pipeline wie Standardprofilpfade. Wenn der benutzerdefinierte Pfad unter /root/ oder /usr/local/share/proxmenux/ liegt, gelten die oben genannten pfadspezifischen Ausschlüsse weiterhin. Jeder archivierte Pfad – standardmäßig oder benutzerdefiniert – wird in metadata/selected_paths.txt aufgezeichnet. Pfade, die in der Quelle nicht vorhanden sind, werden in metadata/missing_paths.txt aufgezeichnet, ohne das Backup zu stoppen, und Pfade, deren Kopieren fehlschlägt, in metadata/failed_paths.txt."
   },
   "archiveStructure": {
     "heading": "Archivstruktur",
     "intro": "Das von jedem Backend erstellte Staging-Verzeichnis folgt unabhängig vom Ziel dem gleichen Layout. Das Tarball-, PBS .pxar oder Borg-Archiv speichert diesen Baum wortwörtlich.",
-    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state (kernel_params, hardware, storage, guests, components, source_host)\n├── metadata/\n│   ├── packages.manual.list       # output of apt-mark showmanual\n│   ├── run_info.env               # hostname, timestamp, kernel version, pmxcfs method\n│   ├── paths_archived.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, /etc/apt, ...\n    ├── root/                      # /root without volatile subpaths\n    ├── usr/local/                 # /usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux (state only)\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/crontabs"
+    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state (kernel_params, hardware, storage, guests, components, source_host)\n├── metadata/\n│   ├── packages.manual.list       # output of apt-mark showmanual\n│   ├── run_info.env               # hostname, timestamp, kernel version, pmxcfs method\n│   ├── selected_paths.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, /etc/apt, ...\n    ├── root/                      # /root without volatile subpaths\n    ├── usr/local/                 # /usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux (state only)\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/crontabs"
   },
   "confirmation": {
     "heading": "Bestätigungszusammenfassung",
-    "body": "Bevor das Backend etwas an das Ziel schreibt, zeigt ProxMenux einen zusammenfassenden Dialog mit dem Ziel, der Backup-ID oder dem Archivnamen, dem Verschlüsselungsstatus und der Liste der zu kopierenden Pfade an. Das Abbrechen hier bricht das Backup sauber ab - das Staging-Verzeichnis wird durch den trap-Hook entfernt, der auf der Backend-Funktion eingestellt ist, und keine Teildaten erreichen das Ziel."
+    "body": "Bevor das Backend etwas an das Ziel schreibt, gibt ProxMenux im Terminal eine Zusammenfassung mit dem Ziel, der Backup-ID oder dem Archivnamen, dem Verschlüsselungsstatus und der Liste der zu kopierenden Pfade aus. An dieser Stelle gibt es keinen Bestätigungs- oder Abbruchschritt; das Kopieren beginnt unmittelbar danach. Wenn das Backup fehlschlägt oder unterbrochen wird, wird das Staging-Verzeichnis durch den trap-Hook entfernt, der auf der Backend-Funktion eingestellt ist, und keine Teildaten erreichen das Ziel."
   },
   "writing": {
     "heading": "Schreiben an den Bestimmungsort",
diff --git a/web/messages/de/docs/backup-restore/destinations/local.json b/web/messages/de/docs/backup-restore/destinations/local.json
index eff0a73d..32cae493 100644
--- a/web/messages/de/docs/backup-restore/destinations/local.json
+++ b/web/messages/de/docs/backup-restore/destinations/local.json
@@ -18,11 +18,11 @@
   },
   "targetConfig": {
     "heading": "Konfiguration des Zielverzeichnisses",
-    "intro": "Das lokale Ziel ist ein einziges dauerhaft gespeichertes Zielverzeichnis – keine Liste. ProxMenux speichert die Auswahl des Betreibers bei /usr/local/share/proxmenux/local-target.conf und liest sie bei jedem Backup. Wenn kein Ziel konfiguriert wurde, wird der Standard-HB_LOCAL_TARGET_DEFAULT = /var/lib/vz/dump verwendet (das gleiche Verzeichnis, das Proxmox für seine eigenen vzdump-Ausgänge verwendet). Das Ziel wird unter Backup-Ziele konfigurieren → Lokale Ziele konfiguriert:",
+    "intro": "Das lokale Ziel ist ein einziges dauerhaft gespeichertes Zielverzeichnis – keine Liste. ProxMenux speichert die Auswahl des Betreibers bei /usr/local/share/proxmenux/local-target.conf und liest sie bei jedem Backup. Wenn kein Ziel konfiguriert wurde, wird der Standard-HB_LOCAL_TARGET_DEFAULT = /var/lib/vz/dump verwendet (das gleiche Verzeichnis, das Proxmox für seine eigenen vzdump-Ausgänge verwendet). Das Ziel wird unter Backup-Ziele konfigurieren → Lokale Archivziele → Lokales Backup-Ziel verwalten konfiguriert:",
     "options": [
       "Standard verwenden (/var/lib/vz/dump). Der lokale Proxmox-Speicher. Bei jeder Proxmox-Installation vorhanden; das Archiv befindet sich neben den vzdump-Ausgaben und wird automatisch von der Registerkarte Backups des Monitors aufgenommen.",
-      "Benutzerdefinierten Pfad eingeben. Jeder absolute Dateisystempfad kann verwendet werden: ein NFS-Mount, eine über fstab eingebundene SMB-Freigabe, ein eigenes ZFS-Dataset oder eine zweite interne Festplatte. ProxMenux prüft vor dem Speichern, ob der Pfad existiert und ein Verzeichnis ist.",
-      "Wählen Sie ein USB-Laufwerk. Öffnet das USB-Untermenü (unten), das Wechselgeräte erkennt und ein Gerät montieren oder formatieren kann."
+      "Benutzerdefinierten Pfad verwenden. Jeder absolute Dateisystempfad kann verwendet werden: ein NFS-Mount, eine über fstab eingebundene SMB-Freigabe, ein eigenes ZFS-Dataset oder eine zweite interne Festplatte. ProxMenux prüft vor dem Speichern, ob der Pfad existiert und ein Verzeichnis ist.",
+      "Ein USB-Laufwerk verwenden. Öffnet das USB-Untermenü (unten), das Wechselgeräte erkennt und ein Gerät montieren oder formatieren kann."
     ]
   },
   "usbFlow": {
@@ -38,7 +38,7 @@
       {
         "state": "nicht montiert",
         "shown": "Größe · Etikett · [fstype] · (nicht montiert — wird montiert)",
-        "action": "Ein Dateisystem ist vorhanden, aber nicht montiert. Bei Bestätigung führt ProxMenux hb_mount_usb_partition aus: erstellt /mnt/backup-LABEL (oder einen UUID-basierten Pfad, wenn kein Label vorhanden ist), mountet die Partition und speichert den Einhängepunkt als lokales Ziel."
+        "action": "Ein Dateisystem ist vorhanden, aber nicht montiert. Bei Bestätigung führt ProxMenux hb_mount_usb_partition aus: erstellt /mnt/proxmenux-backup-LABEL (UUID oder Gerätename, wenn kein Label vorhanden ist), mountet die Partition und speichert den Einhängepunkt als lokales Ziel."
       },
       {
         "state": "leer",
@@ -63,7 +63,7 @@
   },
   "sidecar": {
     "heading": "Die JSON-Begleitdatei",
-    "intro": "Jedes erfolgreiche lokale Backup erzeugt eine Begleitdatei: HOSTNAME-TIMESTAMP.tar.zst.proxmenux.json, geschrieben neben dem Archiv von hb_write_archive_sidecar. Diese kleine JSON-Datei ermöglicht es dem ProxMenux Monitor, das Archiv als ProxMenux-Host-Backup zu identifizieren, auch wenn es später an anderer Stelle verschoben, umbenannt oder archiviert wird.",
+    "intro": "Jedes erfolgreiche lokale Backup erzeugt eine Begleitdatei: hostcfg-HOSTNAME-TIMESTAMP.tar.zst.proxmenux.json, geschrieben neben dem Archiv von hb_write_archive_sidecar. Diese kleine JSON-Datei ermöglicht es dem ProxMenux Monitor, das Archiv als ProxMenux-Host-Backup zu identifizieren, auch wenn es später an anderer Stelle verschoben, umbenannt oder archiviert wird.",
     "contentTitle": "Inhalt der Begleitdatei",
     "contentBody": "Das Sidecar speichert die Schemaversion, ob das Backup von einem interaktiven Lauf oder einem geplanten Job (kind) stammt, die Job-ID für geplante Läufe, den verwendeten Profilmodus (default oder custom), den Quellhostnamen, den ursprünglichen Archivbasisnamen, einen ISO-8601-Erstellungszeitstempel und die Archivgröße in Bytes.",
     "whyBody": "Die Registerkarte Backups des Monitors scannt konfigurierte lokale Verzeichnisse für *.proxmenux.json-Begleitdateien - nicht für *.tar.zst-Dateien -, da ein .tar.zst ohne Sidecar möglicherweise überhaupt kein ProxMenux-Backup ist. Der Scan ist schnell (JSON-Dateien sind winzig) und die Paarung ist stabil bei Umbenennungen des Archivs, solange das Sidecar entsprechend umbenannt wird."
diff --git a/web/messages/de/docs/backup-restore/destinations/pbs.json b/web/messages/de/docs/backup-restore/destinations/pbs.json
index cd954e2c..d09feec8 100644
--- a/web/messages/de/docs/backup-restore/destinations/pbs.json
+++ b/web/messages/de/docs/backup-restore/destinations/pbs.json
@@ -33,7 +33,7 @@
       {
         "source": "ProxMenux manuelle Konfiguration",
         "path": "/usr/local/share/proxmenux/pbs-manual-configs.txt + pbs-pass-NAME.txt + pbs-fingerprint-NAME.txt",
-        "content": "Zusätzlich von  Backup-Ziele konfigurieren → PBS Ziele → PBS hinzufügen. Prompts für einen Namen, Benutzernamen (root@pam oder user@pbs!token), Host oder IP, Datenspeicher und Passwort. Das Passwort wird bei leeren Eingaben erneut angezeigt - ein leeres Speichern würde ansonsten still bleiben und jedes nachfolgende Backup würde mit einem undurchsichtigen Authentifizierungsfehler fehlschlagen. Dieser Pfad wird verwendet, wenn das Ziel PBS nicht als Proxmox-Speicher registriert ist."
+        "content": "Hinzugefügt unter Backup-Ziele konfigurieren → Proxmox Backup Server (PBS) Ziele → + Neuen PBS manuell hinzufügen. Fragt nach einem Namen, Benutzernamen (root@pam oder user@pbs!token), Host oder IP, Datenspeicher und Passwort. Das Passwort wird bei leeren Eingaben erneut abgefragt - ein leeres Speichern würde ansonsten still bleiben und jedes nachfolgende Backup würde mit einem undurchsichtigen Authentifizierungsfehler fehlschlagen. Dieser Pfad wird verwendet, wenn das Ziel PBS nicht als Proxmox-Speicher registriert ist."
       }
     ],
     "menuTitle": "Auswahlmenü",
diff --git a/web/messages/de/docs/backup-restore/how-it-works.json b/web/messages/de/docs/backup-restore/how-it-works.json
index f6f2d198..b2aed60d 100644
--- a/web/messages/de/docs/backup-restore/how-it-works.json
+++ b/web/messages/de/docs/backup-restore/how-it-works.json
@@ -20,7 +20,7 @@
     "heading": "Archivlayout",
     "intro": "Jedes Archiv folgt dem gleichen Baumlayout, unabhängig vom Zielort. Das metadata/-Unterverzeichnis enthält die strukturierten Nutzlasten; das rootfs/-Unterverzeichnis enthält die Dateisystemkopie.",
     "treeCaption": "Das beim Backup angelegte Vorbereitungsverzeichnis. Alle Ziele erhalten denselben Verzeichnisbaum in ihrem nativen Format: tar bei lokalen Zielen, PBS-Blöcke bei PBS und Borg-Segmente bei Borg.",
-    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state\n├── metadata/\n│   ├── packages.manual.list       # apt-mark showmanual\n│   ├── run_info.env               # backup run identity + kernel version + pmxcfs method\n│   ├── paths_archived.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, …\n    ├── root/                      # /root (with volatile subdirs excluded)\n    ├── usr/local/                 # /usr/local/bin, /usr/local/share/proxmenux, …\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/…"
+    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state\n├── metadata/\n│   ├── packages.manual.list       # apt-mark showmanual\n│   ├── run_info.env               # backup run identity + kernel version + pmxcfs method\n│   ├── selected_paths.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, …\n    ├── root/                      # /root (with volatile subdirs excluded)\n    ├── usr/local/                 # /usr/local/bin, /usr/local/share/proxmenux, …\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/…"
   },
   "rootfs": {
     "heading": "Die Rootfs Nutzlast",
@@ -176,7 +176,7 @@
         "stage": "3",
         "name": "Ausstehende Vorbereitung",
         "reads": "rootfs/ (Reboot + gefährliche Pfade)",
-        "action": "_rs_prepare_pending_restore bereitet riskante Pfade unter /var/lib/proxmenux/pending-restore/ vor, schreibt plan.env, apply-on-boot.list und rs-skip-paths.txt und aktiviert proxmenux-restore-onboot.service für den nächsten Start."
+        "action": "_rs_prepare_pending_restore bereitet riskante Pfade unter /var/lib/proxmenux/restore-pending/<restore_id>/ (verlinkt als current) vor, schreibt plan.env, apply-on-boot.list und rs-skip-paths.txt und aktiviert proxmenux-restore-onboot.service für den nächsten Start."
       },
       {
         "stage": "3b",
diff --git a/web/messages/de/docs/backup-restore/index.json b/web/messages/de/docs/backup-restore/index.json
index fa7bac95..9eb2700e 100644
--- a/web/messages/de/docs/backup-restore/index.json
+++ b/web/messages/de/docs/backup-restore/index.json
@@ -50,7 +50,7 @@
     "heading": "Zwei Schnittstellen, ein Backend",
     "intro": "Alle Schritte der Sicherung und Wiederherstellung sind über zwei Oberflächen erreichbar. Beide rufen dieselbe Shell-Bibliothek auf, erstellen identische Archive und verwenden dasselbe Auftragsverzeichnis.",
     "cliLabel": "ProxMenux Scripts (TUI)",
-    "cliDetail": "Menü → Dienstprogramme →\nHost-Sicherung/Wiederherstellung\n\nDialogbasierter Ablauf,\nSSH-freundlich, skriptfähig\nzur unbeaufsichtigten Nutzung.",
+    "cliDetail": "Menü →\nHost-Sicherung/Wiederherstellung\n\nDialogbasierter Ablauf,\nSSH-freundlich, skriptfähig\nzur unbeaufsichtigten Nutzung.",
     "webLabel": "ProxMenux Monitor (Web UI)",
     "webDetail": "Registerkarte Backups in der\nWeboberfläche von Monitor.\n\nWiederherstellung mit einem Klick,\nlaufende Protokollanzeige, integriert\nmit Benachrichtigungen und der\nAnzeige von Rollback-Differenzen."
   },
diff --git a/web/messages/de/docs/backup-restore/restoring.json b/web/messages/de/docs/backup-restore/restoring.json
index 0804b2dd..837ee4f4 100644
--- a/web/messages/de/docs/backup-restore/restoring.json
+++ b/web/messages/de/docs/backup-restore/restoring.json
@@ -92,11 +92,11 @@
   "fullFlow": {
     "heading": "Der vollständige Wiederherstellungsworkflow",
     "intro": "Die komplette Pipeline von der Archivauswahl bis zu einem funktionierenden wiederhergestellten Host. Jede Stufe füttert die nächste; der von früheren Stufen geschriebene Zustand wird von späteren verbraucht.",
-    "diagram": "┌─────────────────────────────────────────────────────────────────┐\n│  Archive → staging_root/                                        │\n│    manifest.json   metadata/   rootfs/                          │\n└──────────────────────────────┬──────────────────────────────────┘\n                               │\n                               ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  1. hb_compat_check                                     │\n   │     · hardware drift → RS_SKIP_PATHS                    │\n   │     · kernel direction → same / bk_newer / bk_older     │\n   │     · NIC remap plan                                    │\n   │     · hydration plan (bk_older only)                    │\n   │     · rollback plan                                     │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  2. Confirmation dialog                                 │\n   │     Shows: hot count, pending count, drift, hydration,  │\n   │     NIC remap, cross-kernel note, reinstalls preview    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │ (accepted)\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  3. Apply hot paths → LIVE                              │\n   │     _rs_apply rsyncs each hot path                      │\n   │     from staging_root/rootfs to /                       │\n   │     (skips paths in RS_SKIP_PATHS)                      │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4. _rs_prepare_pending_restore                         │\n   │     /var/lib/proxmenux/pending-restore/                 │\n   │       ├── apply-on-boot.list                            │\n   │       ├── plan.env                                      │\n   │       ├── rs-skip-paths.txt                             │\n   │       └── rootfs/ (deferred paths)                      │\n   │     Enable proxmenux-restore-onboot.service             │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4b. _rs_import_data_pools                              │\n   │      Imports non-root ZFS pools whose disks are all     │\n   │      present; retries with -f on foreign hostid;        │\n   │      skips with a warning when disks are missing.       │\n   │      Log: /var/log/proxmenux/restore-datapools-*.log    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  5. _rs_run_complete_extras                             │\n   │     packages.manual.list                                │\n   │       → cascade-safe filter                             │\n   │       → apt-get install                                 │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  6. REBOOT                                              │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  7. apply_pending_restore.sh (on early boot)            │\n   │     Reads plan.env                                      │\n   │     Applies apply-on-boot.list (filtered)               │\n   │     Installs the postboot systemd unit                  │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  8. apply_cluster_postboot.sh (~10 minutes)             │\n   │     Runs after pve-cluster is up                        │\n   │     · Applies /etc/pve entries to pmxcfs                │\n   │     · update-initramfs -u -k all                        │\n   │     · update-grub / proxmox-boot-tool refresh           │\n   │     · component --auto-reinstall (nvidia, coral, ...)   │\n   │     · Boot sanity check                                 │\n   │     · Notification: \"Host restore finished\"             │\n   └─────────────────────────────────────────────────────────┘"
+    "diagram": "┌─────────────────────────────────────────────────────────────────┐\n│  Archive → staging_root/                                        │\n│    manifest.json   metadata/   rootfs/                          │\n└──────────────────────────────┬──────────────────────────────────┘\n                               │\n                               ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  1. hb_compat_check                                     │\n   │     · hardware drift → RS_SKIP_PATHS                    │\n   │     · kernel direction → same / bk_newer / bk_older     │\n   │     · NIC remap plan                                    │\n   │     · hydration plan (bk_older only)                    │\n   │     · rollback plan                                     │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  2. Confirmation dialog                                 │\n   │     Shows: hot count, pending count, drift, hydration,  │\n   │     NIC remap, cross-kernel note, reinstalls preview    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │ (accepted)\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  3. Apply hot paths → LIVE                              │\n   │     _rs_apply rsyncs each hot path                      │\n   │     from staging_root/rootfs to /                       │\n   │     (skips paths in RS_SKIP_PATHS)                      │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4. _rs_prepare_pending_restore                         │\n   │     /var/lib/proxmenux/restore-pending/'<'id'>'/            │\n   │       ├── apply-on-boot.list                            │\n   │       ├── plan.env                                      │\n   │       ├── rs-skip-paths.txt                             │\n   │       └── rootfs/ (deferred paths)                      │\n   │     Enable proxmenux-restore-onboot.service             │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4b. _rs_import_data_pools                              │\n   │      Imports non-root ZFS pools whose disks are all     │\n   │      present; retries with -f on foreign hostid;        │\n   │      skips with a warning when disks are missing.       │\n   │      Log: /var/log/proxmenux/restore-datapools-*.log    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  5. _rs_run_complete_extras                             │\n   │     packages.manual.list                                │\n   │       → cascade-safe filter                             │\n   │       → apt-get install                                 │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  6. REBOOT                                              │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  7. apply_pending_restore.sh (on early boot)            │\n   │     Reads plan.env                                      │\n   │     Applies apply-on-boot.list (filtered)               │\n   │     Installs the postboot systemd unit                  │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  8. apply_cluster_postboot.sh (~10 minutes)             │\n   │     Runs after pve-cluster is up                        │\n   │     · Applies /etc/pve entries to pmxcfs                │\n   │     · update-initramfs -u -k all                        │\n   │     · update-grub / proxmox-boot-tool refresh           │\n   │     · component --auto-reinstall (nvidia, coral, ...)   │\n   │     · Boot sanity check                                 │\n   │     · Notification: \"Host restore finished\"             │\n   └─────────────────────────────────────────────────────────┘"
   },
   "pendingMachinery": {
     "heading": "Die Mechanik der vorgemerkten Wiederherstellung",
-    "intro": "Pfade, die als reboot oder dangerous klassifiziert sind, und Hydratationsschreiben, werden für den nächsten Boot inszeniert, anstatt live angewendet zu werden. Dies geschieht durch einen kleinen, in sich geschlossenen Satz von Dateien unter /var/lib/proxmenux/pending-restore/:",
+    "intro": "Pfade, die als reboot oder dangerous klassifiziert sind, und Hydratationsschreiben, werden für den nächsten Boot inszeniert, anstatt live angewendet zu werden. Dies geschieht durch einen kleinen, in sich geschlossenen Satz von Dateien unter /var/lib/proxmenux/restore-pending/<restore_id>/:",
     "rows": [
       {
         "file": "apply-on-boot.list",
@@ -115,7 +115,7 @@
         "content": "Die inszenierten Dateien selbst - die genauen Bytes, die festgelegt werden. Behielt auf dem gleichen Dateisystem wie /, so dass der On-Boot rsync schnell ist und nicht davon abhängt, dass externer Speicher beim Booten noch erreichbar ist."
       }
     ],
-    "unitBody": "Die systemd-Einheit proxmenux-restore-onboot.service wird am Ende des interaktiven Schritts aktiviert. Der einmalige Dienst startet früh beim nächsten Boot, ruft apply_pending_restore.sh auf und deaktiviert sich anschließend. Die Bedingung ConditionPathExists=/var/lib/proxmenux/pending-restore/state sorgt dafür, dass er bei Starts ohne vorgemerkte Wiederherstellung nichts ausführt."
+    "unitBody": "Die systemd-Einheit proxmenux-restore-onboot.service wird am Ende des interaktiven Schritts aktiviert. Der einmalige Dienst startet früh beim nächsten Boot (vor dem Netzwerk), ruft apply_pending_restore.sh auf und deaktiviert sich anschließend. Die Einheit hat keine Pfadbedingung: Das Skript selbst beendet sich, wenn /var/lib/proxmenux/restore-pending/current nicht existiert, sodass sie bei Starts ohne vorgemerkte Wiederherstellung nichts ausführt. Der separate proxmenux-apply-cluster-postboot.service wird durch ConditionPathExists=/var/lib/proxmenux/cluster-apply-pending gesteuert."
   },
   "postbootDispatcher": {
     "heading": "Der Postboot-Dispatcher — apply_cluster_postboot.sh",
@@ -238,11 +238,11 @@
     "heading": "Speicherorte der Protokolle",
     "rows": [
       {
-        "log": "/var/log/proxmenux/restore-YYYYMMDD_HHMMSS.log",
-        "detail": "Vom interaktiven Schritt geschrieben. Enthält Kompatibilitätsergebnisse, Filter für Abweichungen, Anpassungsplan, Ausgabe der direkten Anwendung und Vorbereitung vorgemerkter Änderungen sowie Paketinstallation."
+        "log": "/tmp/proxmenux--restore-YYYYMMDD_HHMMSS.log",
+        "detail": "Vom interaktiven Schritt geschrieben (pbs, borg oder local). Paketneuinstallationen und Schritte für Datenpools werden außerdem in /var/log/proxmenux/restore-apt-*.log und restore-datapools-*.log protokolliert."
       },
       {
-        "log": "/var/log/proxmenux/apply-pending-YYYYMMDD_HHMMSS.log",
+        "log": "/var/log/proxmenux/proxmenux-restore-onboot-YYYYMMDD_HHMMSS.log",
         "detail": "Von apply_pending_restore.sh beim frühen Start geschrieben. Enthält das Einlesen von plan.env, das Abarbeiten von apply-on-boot und die Pfadausschlüsse."
       },
       {
diff --git a/web/messages/de/docs/backup-restore/scheduled-jobs.json b/web/messages/de/docs/backup-restore/scheduled-jobs.json
index 7f540cc3..88339fe0 100644
--- a/web/messages/de/docs/backup-restore/scheduled-jobs.json
+++ b/web/messages/de/docs/backup-restore/scheduled-jobs.json
@@ -101,7 +101,7 @@
   },
   "management": {
     "heading": "Aufträge verwalten",
-    "intro": "Das Scheduler-Menü (Scripts TUI) und die Registerkarte Monitor Backups zeigen die gleichen Aktionen bei jedem Job.",
+    "intro": "Das Scheduler-Menü im Scripts TUI (Backup scheduler and retention) bietet Run now, Enable / Disable und Delete sowie eine Liste der Jobs mit ihrem letzten Ausführungsstatus. Edit und View log sind nur in der Registerkarte Monitor Backups verfügbar.",
     "rows": [
       {
         "action": "Jetzt ausführen",
@@ -112,7 +112,7 @@
         "detail": "Neuer Modus: systemctl enable/disable --now der Timer. Attach-Modus: Dreht das ENABLED-Flag im Job Env um — das Hook-Skript berücksichtigt es beim nächsten PVE-Aufruf."
       },
       {
-        "action": "Bearbeiten",
+        "action": "Bearbeiten (nur Monitor)",
         "detail": "Öffnet Ziel, Zeitplan, Profil und Aufbewahrungsaufforderungen neu und schreibt die Jobdateien neu. Bewahrt die Job ID auf."
       },
       {
@@ -120,7 +120,7 @@
         "detail": "Entfernt die Job env, Pfaddatei, systemd Timer + Service (Neuer Modus) und deaktiviert die Hakenbindung (Attach-Modus). Berührt keine Archive bereits am Zielort."
       },
       {
-        "action": "Protokoll anzeigen",
+        "action": "Protokoll anzeigen (nur Monitor)",
         "detail": "Zeigt laufend an: /var/log/proxmenux/backup-jobs/JOB_ID-YYYYMMDD_HHMMSS.log – eine Protokolldatei pro Lauf. Der Runner gibt außerdem eine kompakte Statuszeile an journald unter dem Dienst systemd aus."
       }
     ]
diff --git a/web/messages/de/docs/installation.json b/web/messages/de/docs/installation.json
index e8f31748..57707038 100644
--- a/web/messages/de/docs/installation.json
+++ b/web/messages/de/docs/installation.json
@@ -53,11 +53,11 @@
     "calloutBody": "Möchten Sie neue Funktionen vor der offiziellen Veröffentlichung testen und die endgültige Version mitgestalten? Das Beta-Programm von ProxMenux bietet frühzeitigen Zugriff auf Neuerungen einschließlich aktueller Monitor-Builds aus dem Zweig develop. Beta-Versionen können Fehler oder unvollständige Funktionen enthalten. Ihre Rückmeldungen helfen, diese vor der stabilen Veröffentlichung zu beheben.",
     "intro": "Installieren Sie die Beta-Version anstelle der stabilen mit:",
     "code": "bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/develop/install_proxmenux_beta.sh)\"",
-    "outro": "Die Beta ist freiwillig und muss ausdrücklich über den Beta-Installer installiert werden. Sobald eine stabile Version erscheint, informiert ProxMenux beim nächsten Start von menu und bietet den automatischen Wechsel an. Zum vorzeitigen Ausstieg wählen Sie unter Einstellungen Beta-Programm deaktivieren."
+    "outro": "Die Beta ist freiwillig und muss ausdrücklich über den Beta-Installer installiert werden. Sobald eine stabile Version erscheint, bietet der nächste Start von menu sie mit der regulären Update-Abfrage an. Zum Verlassen des Beta-Kanals wählen Sie unter Einstellungen Veröffentlichungskanal ändern und dann Stable."
   },
   "updating": {
     "heading": "Aktualisierung",
-    "body": "ProxMenux verwaltet seine Updates selbst. Ist eine neue Version verfügbar, bietet der nächste Aufruf von menu das Update an. Bei Zustimmung werden die Dateien und Einstellungen des Werkzeugs in der bestehenden Installation ersetzt. Ein manueller Download ist nicht nötig. Stabile Installationen erhalten stabile Updates, Beta-Installationen Beta-Updates sowie den oben beschriebenen Wechselhinweis. Die Anzeige hängt davon ab, wo menu läuft: Über SSH oder die Proxmox-Konsole erscheint ein Ja/Nein-Dialog; bei Zustimmung wird sofort aktualisiert. Im WebSocket-Terminal von ProxMenux Monitor erscheint stattdessen eine Information, weil dieses Terminal während des Updates unterbrochen würde. Sie zeigt den einzeiligen Installer (bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\" für stabil oder die entsprechende URL von install_proxmenux_beta.sh für Beta), den Sie über SSH oder die Konsole ausführen sollen."
+    "body": "ProxMenux verwaltet seine Updates selbst. Ist eine neue Version verfügbar, bietet der nächste Aufruf von menu das Update an. Bei Zustimmung werden die Dateien und Einstellungen des Werkzeugs in der bestehenden Installation ersetzt. Ein manueller Download ist nicht nötig. Stabile Installationen erhalten stabile Updates, Beta-Installationen Beta-Builds und werden außerdem auf neue stabile Versionen hingewiesen. Die Anzeige hängt davon ab, wo menu läuft: Über SSH oder die Proxmox-Konsole erscheint ein Ja/Nein-Dialog; bei Zustimmung wird sofort aktualisiert. Im WebSocket-Terminal von ProxMenux Monitor erscheint stattdessen eine Information, weil dieses Terminal während des Updates unterbrochen würde. Sie zeigt den einzeiligen Installer (bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\" für stabil oder die entsprechende URL von install_proxmenux_beta.sh für Beta), den Sie über SSH oder die Konsole ausführen sollen."
   },
   "uninstall": {
     "heading": "Deinstallation",
diff --git a/web/messages/de/docs/introduction.json b/web/messages/de/docs/introduction.json
index 73af0b8f..66c77bb4 100644
--- a/web/messages/de/docs/introduction.json
+++ b/web/messages/de/docs/introduction.json
@@ -21,11 +21,11 @@
     "intro": "ProxMenux umfasst zwei Komponenten im selben Quellbaum. Der Installer installiert beide auf dem Host: Scripts stellt die Terminaloberfläche bereit, Monitor das Webdashboard:",
     "scripts": {
       "title": "ProxMenux Scripts",
-      "body": "Die ursprüngliche CLI/TUI, die Sie mit menu starten. Neun Funktionsbereiche für Optimierungen nach der Installation, VMs und LXC-Container, GPU- und Coral-TPU-Durchreichung, Datenträger, Speicher, Netzwerk, Sicherheit und Hilfsprogramme sowie eine sorgfältig zusammengestellte Befehlsreferenz. Läuft in lokalen Terminals, per SSH und im Webterminal."
+      "body": "Die ursprüngliche CLI/TUI, die Sie mit menu starten. Funktionsbereiche für Optimierungen nach der Installation, VMs und LXC-Container, GPU- und Coral-TPU-Durchreichung, Datenträger, Speicher, OCI-Apps, Netzwerk, Sicherheit, Hilfsprogramme, Host-Sicherung und -Wiederherstellung, Proxmox VE Helper Scripts sowie eine sorgfältig zusammengestellte Befehlsreferenz. Läuft in lokalen Terminals, per SSH und im Webterminal."
     },
     "monitor": {
       "title": "ProxMenux Monitor",
-      "body": "Selbst gehostetes Webdashboard als AppImage. Hostmetriken in Echtzeit, proaktive Zustandsüberwachung, Benachrichtigungen, optionale KI-Umformulierung mit sechs Anbietern, integriertes Webterminal und REST-API. Aktivierung im Scripts-Menü unter Einstellungen → ProxMenux Monitor."
+      "body": "Selbst gehostetes Webdashboard als AppImage. Hostmetriken in Echtzeit, proaktive Zustandsüberwachung, Benachrichtigungen, optionale KI-Umformulierung mit sechs Anbietern, integriertes Webterminal und REST-API. Der Installer richtet es als Dienst ein, der beim Systemstart läuft; aktivieren, deaktivieren oder prüfen Sie ihn im Scripts-Menü unter Einstellungen → ProxMenux Monitor."
     },
     "calloutTitle": "Was ProxMenux verwaltet",
     "calloutIntro": "Drei Schichten, die alle aus dem gleichen Projekt erreichbar sind:",
@@ -37,7 +37,7 @@
   },
   "scriptsSection": {
     "heading": "ProxMenux Scripts - was Sie bekommen",
-    "intro": "Neun Funktionsbereiche mit jeweils eigenem Menü innerhalb von menu und einer Detailseite in der Dokumentation. Klicken Sie auf eine beliebige Karte, um direkt zur Übersicht dieses Abschnitts zu springen.",
+    "intro": "Die wichtigsten Funktionsbereiche mit jeweils eigenem Menü innerhalb von menu und einer Detailseite in der Dokumentation. Klicken Sie auf eine beliebige Karte, um direkt zur Übersicht dieses Abschnitts zu springen.",
     "items": [
       {
         "title": "Nach der Installation",
diff --git a/web/messages/de/docs/monitor/access-auth.json b/web/messages/de/docs/monitor/access-auth.json
index 305456c1..4711f744 100644
--- a/web/messages/de/docs/monitor/access-auth.json
+++ b/web/messages/de/docs/monitor/access-auth.json
@@ -18,7 +18,7 @@
   },
   "reaching": {
     "heading": "Dashboard öffnen",
-    "intro": "ProxMenux Monitor lauscht auf 0.0.0.0:8008. Drei übliche Zugriffswege:",
+    "intro": "ProxMenux Monitor lauscht auf TCP 8008 auf allen Schnittstellen (::, IPv4 und IPv6). Drei übliche Zugriffswege:",
     "outro": "Direktzugriff entspricht der Standard-systemd-Unit. Reverse-Proxy und Secure Gateway werden unten beschrieben. Das Vertrauen in Client-IPs aus X-Forwarded-For erfordert PROXMENUX_TRUST_PROXY=1 und einen von Ihnen kontrollierten Proxy. X-Forwarded-Proto oder X-Forwarded-Host allein aktivieren das Proxy-Vertrauen nicht. Auf den Ports 80/443 verwendet das Frontend relative /api/*-URLs; auf jedem anderen Port ruft es Port 8008 unter demselben Hostnamen auf."
   },
   "firstLaunch": {
@@ -158,7 +158,7 @@
     "authJsonTitle": "auth.json - was es enthält und wie es geschützt ist",
     "authJsonBody": "Die Authentifizierungsdaten liegen in /root/.config/proxmenux-monitor/auth.json, mit Modus 0600 und Eigentümer root. Die Datei enthält Hashes (PBKDF2) und geheimes Schlüsselmaterial (jwt_secret, totp_secret), niemals das Klartextpasswort. Sichern oder replizieren Sie den Host nur auf ein verschlüsseltes Ziel und nehmen Sie diese Datei niemals in die Versionsverwaltung auf.",
     "rotateTitle": "Ein Wechsel von jwt_secret macht bestehende JWTs ungültig",
-    "rotateBody": "Wird auth.json neu erstellt oder mit einem anderen Geheimnis wiederhergestellt, ändert sich jwt_secret. Bisherige Sitzungs- und API-Tokens scheitern dann mit „Invalid or expired token“. Die Oberfläche kennzeichnet betroffene Tokens mit Ungültig — neu erstellen. Widerrufen und ersetzen Sie sie auch in Home Assistant, Skripten und anderen Clients.",
+    "rotateBody": "Wird auth.json neu erstellt oder mit einem anderen Geheimnis wiederhergestellt, ändert sich jwt_secret. Bisherige Sitzungs- und API-Tokens scheitern dann mit „Invalid or expired token“. Die Oberfläche markiert betroffene API-Tokens rot, mit einem Badge, das zum Widerrufen und Neuerstellen auffordert. Danach benötigen auch Home Assistant, Skripte und andere externe Clients ein neues Token.",
     "recoverTitle": "Wiederherstellen eines verlorenen Passworts",
     "recoverIntro": "Es gibt keinen Online-Fluss für \"Passwort vergessen\" - von Grund auf, da das Dashboard auf dem eigenen Host des Betreibers läuft und der Wiederherstellungspfad Shell-Zugriff auf diesen Host ist. ProxMenux liefert einen geführten Reset im Konfigurationsmenü, sodass Sie auth.json nicht von Hand bearbeiten müssen:",
     "survivesTitle": "Was den Reset überlebt",
@@ -284,14 +284,14 @@
     "heading": "API-Token (langlebig)",
     "intro": "Browsersitzungen laufen nach 24 Stunden ab. Für Homepage-Widgets, Home Assistant, Grafana oder Uptime Kuma erstellen Sie ein separates API-Token mit 365 Tagen Laufzeit. Es verwendet dasselbe Signaturgeheimnis; die Angabe token_name erlaubt die Zuordnung und den einzelnen Widerruf.",
     "imageAlt": "API-Token-Panel zeigt die Token-Liste mit Name, Präfix, erstelltem Datum und Ablauf",
-    "imageCaption": "Die API-Tokenliste unter Einstellungen — Name, Präfix (die letzten 4-Zeichen werden zur Identifizierung angezeigt), erstellt und Ablaufdatum, Widerrufsaktion.",
+    "imageCaption": "Die API-Tokenliste unter Admin → Sicherheit: Name, Präfix (die letzten 4 Zeichen werden zur Identifizierung angezeigt), Erstellungs- und Ablaufdatum, Widerrufsaktion.",
     "generateTitle": "Generieren Sie ein Token",
     "generateIntro": "Aus dem Dashboard:",
     "generateSteps": [
-      "Unter Sicherheit → API-Zugriffstokens öffnen.",
+      "Öffnen Sie Admin → Sicherheit → API-Tokens und klicken Sie auf Neues Token generieren.",
       "Beschreibenden Namen angeben, z. B. „Home Assistant“.",
       "Geben Sie Ihr Passwort erneut ein. Wenn 2FA eingeschaltet ist, auch der aktuelle 6-stellige Code.",
-      "Token erstellen wählen. Es erscheint nur einmal; sofort kopieren."
+      "Klicken Sie auf Token generieren. Das Token erscheint nur einmal: Kopieren Sie es sofort."
     ],
     "generateCli": "Aus der Kommandozeile:",
     "useTitle": "Verwenden Sie ein Token",
@@ -310,7 +310,7 @@
     "intro": "Zwei Wege zu TLS:",
     "items": [
       "Reverse-Proxy (empfohlen). Beenden Sie TLS auf Nginx / Caddy / Traefik und leiten Sie HTTP auf Port 8008 zum Flask-Prozess weiter. Snippets unten.",
-      "Direktes HTTPS in der AppImage. Zertifikat über POST /api/ssl/configure einrichten (Oberfläche: Einstellungen → SSL). HTTPS verwendet gevent.pywsgi mit dem Standardhandler; flask-sock übernimmt WebSocket-Verbindungen für WSS. Zertifikatspfade stehen in der SSL-Konfiguration."
+      "Direktes HTTPS in der AppImage. Zertifikat über POST /api/ssl/configure einrichten (Oberfläche: Admin → Sicherheit → SSL / HTTPS). HTTPS verwendet gevent.pywsgi mit dem Standardhandler; flask-sock übernimmt WebSocket-Verbindungen für WSS. Zertifikatspfade stehen in der SSL-Konfiguration."
     ],
     "calloutTitle": "Direkte HTTPS-Beschränkungen",
     "calloutBody": "Der Monitor kann ein von Proxmox verwaltetes Zertifikat oder eigene Zertifikatsdateien verwenden. Er stellt selbst keine ACME-Zertifikate aus. Für automatische Ausstellung und Erneuerung Proxmox ACME oder einen Reverse-Proxy wie Caddy, Traefik oder Nginx verwenden; erneuerte Zertifikate müssen geladen werden."
diff --git a/web/messages/de/docs/monitor/ai-assistant.json b/web/messages/de/docs/monitor/ai-assistant.json
index 1294df39..3694435b 100644
--- a/web/messages/de/docs/monitor/ai-assistant.json
+++ b/web/messages/de/docs/monitor/ai-assistant.json
@@ -9,7 +9,7 @@
   },
   "header": {
     "title": "KI-Assistent",
-    "description": "Die optionale KI formuliert geeignete Benachrichtigungsvorlagen verständlich um: mit Detailstufen pro Kanal, dreizehn Oberflächensprachen, eigenen Prompts, öffentlicher Community-Bibliothek und zusätzlichem Kontext. Die Backup-Berichte backup_complete und backup_fail umgehen die KI vollständig.",
+    "description": "Die optionale KI formuliert geeignete Benachrichtigungsvorlagen verständlich um: mit Detailstufen pro Kanal, dreizehn Oberflächensprachen, eigenen Prompts, öffentlicher Community-Bibliothek und zusätzlichem Kontext. Die Backup-Berichte backup_complete und backup_fail werden nie umgeschrieben; mit aktivierten KI-Vorschlägen fügt die KI nur eine Erklärung an, wenn ein Backup Fehler meldet.",
     "section": "ProxMenux-Monitor"
   },
   "intro": {
@@ -83,7 +83,7 @@
     "intro1": "Kommerzielle Anbieter rechnen nach Token ab. Ein Token entspricht grob vier englischen Zeichen oder drei Vierteln eines Wortes. Der englische Satz \"Backup completed on storage local-bak\" hat ungefähr acht Token. Zehn Journalzeilen können je nach technischem Inhalt 200–400 Token umfassen.",
     "intro2": "Zwei Dinge werden bei jedem Anruf in Rechnung gestellt:",
     "items": [
-      "Eingabe-Token umfassen Systemprompt und Benutzernachricht: Schweregrad, Titel, Vorlagentext und Kontext. Die Anzahl hängt vom tatsächlichen Prompt und Ereignis ab. Jeder umgeschriebene Kanal kann eigene Eingabe-Token verbrauchen; Backup-Abschluss- und Fehlerberichte umgehen die KI.",
+      "Eingabe-Token umfassen Systemprompt und Benutzernachricht: Schweregrad, Titel, Vorlagentext und Kontext. Die Anzahl hängt vom tatsächlichen Prompt und Ereignis ab. Jeder umgeschriebene Kanal kann eigene Eingabe-Token verbrauchen; Backup-Abschluss- und Fehlerberichte verbrauchen nur Token, wenn die KI Backup-Fehler erklärt.",
       "Ausgabe-Token sind die Modellantwort. Monitor begrenzt sie mit max_tokens. Die Grenze ist ein Höchstwert, kein fester Verbrauch: Erzeugt das Modell 250 Token bei einem Limit von 1500, werden 250 berechnet."
     ],
     "capsIntro": "Dies sind die tatsächlichen Obergrenzen, die der Dispatcher anwendet, direkt aus AI_DETAIL_TOKENS in notification_templates.py entnommen:",
@@ -118,7 +118,7 @@
     "imageCaption": "Informationsfenster mit sechs Karten, einer je Anbieter, und den unten beschriebenen Angaben.",
     "groq": {
       "heading": "Groq",
-      "tagline": "Sehr schnelle, großzügige freie Stufe (30 req/min). Ideal zum Starten.",
+      "tagline": "Sehr schnell, mit großzügiger freier Stufe. Ein guter Einstieg.",
       "items": [
         "API-Schlüssel: console.groq.com/keys",
         "Beispiele aus der mitgelieferten Modellliste: allam-2-7b, qwen/qwen3.8-27b, openai/gpt-oss-20b, openai/gpt-oss-120b.",
@@ -234,7 +234,7 @@
   },
   "detailLevel": {
     "heading": "Detailebene pro Kanal",
-    "intro": "Telegram, Discord, Gotify und E-Mail haben jeweils eine eigene Auswahl der Detailstufe. Drei Werte steuern im Standardprompt sowohl das Ausgabe-Token-Limit als auch die Detailvorgaben:",
+    "intro": "Jeder der sechs Kanäle (Telegram, Discord, Gotify, E-Mail, Pushover, Apprise) hat eine eigene Auswahl der Detailstufe. Drei Werte steuern im Standardprompt sowohl das Ausgabe-Token-Limit als auch die Detailvorgaben:",
     "headerLevel": "Ebene",
     "headerLabel": "UI-Kennzeichnung",
     "headerCap": "Ausgabegrenze",
@@ -261,7 +261,7 @@
     ],
     "defaultsIntro": "Standardwerte bei der ersten Installation:",
     "defaults": [
-      "Telegram, Discord, Gotify — standard.",
+      "Telegram, Discord, Gotify, Pushover, Apprise: standard.",
       "Email — detailed. E-Mail ist der Kanal, in dem Sie normalerweise das vollständige Bild für die Archivierung wünschen."
     ],
     "emailTitle": "E-Mail-Detailebene fügt das Original hinzu",
@@ -280,7 +280,7 @@
   },
   "templates": {
     "heading": "Ein Hinweis auf Vorlagen",
-    "body1": "Die KI erhält formatierten Vorlagentext statt roher Ereignisdaten. Typen wie vm_start, auth_fail und health_degraded haben Vorlagen in notification_templates.py. Die KI formuliert geeignete Texte um; backup_complete und backup_fail behalten deterministische Berichte und umgehen die KI.",
+    "body1": "Die KI erhält formatierten Vorlagentext statt roher Ereignisdaten. Typen wie vm_start, auth_fail und health_degraded haben Vorlagen in notification_templates.py. Die KI formuliert geeignete Texte um; backup_complete und backup_fail behalten deterministische Berichte; mit aktivierten KI-Vorschlägen fügt die KI nur eine Erklärung an, wenn das Backup Fehler meldet.",
     "body2": "Zwei praktische Implikationen: Die KI sieht nie einen Hostnamen oder VMID, den sie erfinden muss - diese Felder werden von der Vorlage vor dem Umschreiben platziert. Und wenn AI deaktiviert ist, wird der vorgefertigte Körper direkt versandt. Die Notifications-Seite dokumentiert die Versandpipeline vollständig und ist der richtige Querverweis für alles, was mit einem Ereignis passiert, bevor es diese Ebene erreicht."
   },
   "privacy": {
diff --git a/web/messages/de/docs/monitor/api-reference.json b/web/messages/de/docs/monitor/api-reference.json
index 8581cd36..6039f582 100644
--- a/web/messages/de/docs/monitor/api-reference.json
+++ b/web/messages/de/docs/monitor/api-reference.json
@@ -76,7 +76,7 @@
       {
         "endpoint": "/api/system-info",
         "method": "GET",
-        "use": "Erweiterte Systemdaten der Dashboard-Kopfzeile: Gesamtmetriken und Startzeitpunkt."
+        "use": "Schlanke Kopfzeileninfos: Hostname, Betriebszeit und zwischengespeicherter Gesundheitsstatus. Öffentlich, kein Token nötig."
       },
       {
         "endpoint": "/api/hardware",
@@ -401,7 +401,7 @@
       {
         "endpoint": "/api/logs",
         "method": "GET",
-        "use": "Gefilterte Journaleinträge. Anfrage: ?level=&service=&limit=."
+        "use": "Gefilterte Journaleinträge. Anfrage: ?priority=&service=&limit=&since_days= (priority 0-7, since_days bis zu 90)."
       },
       {
         "endpoint": "/api/logs/download",
@@ -411,7 +411,7 @@
       {
         "endpoint": "/api/events",
         "method": "GET",
-        "use": "Interner Ereignisstrom - derselbe, der Benachrichtigungen füttert."
+        "use": "Aktuelle Proxmox-Cluster-Tasks und -Ereignisse aus pvesh get /cluster/tasks. Anfrage: ?limit= (Standard 50)."
       },
       {
         "endpoint": "/api/task-log/",
@@ -462,7 +462,7 @@
       {
         "endpoint": "/api/notifications/send",
         "method": "POST",
-        "use": "Erzeugt ein eigenes Ereignis. Anfrageinhalt: '{'\"event_type\":\"custom\",\"severity\":\"WARNING\",\"title\":\"...\",\"body\":\"...\",\"data\":'{''}''}'."
+        "use": "Erzeugt ein eigenes Ereignis. Anfrageinhalt: '{'\"event_type\":\"custom\",\"severity\":\"WARNING\",\"title\":\"...\",\"message\":\"...\",\"data\":'{''}''}'."
       },
       {
         "endpoint": "/api/notifications/test-ai",
diff --git a/web/messages/de/docs/monitor/architecture.json b/web/messages/de/docs/monitor/architecture.json
index be93e6be..8c9ed1d0 100644
--- a/web/messages/de/docs/monitor/architecture.json
+++ b/web/messages/de/docs/monitor/architecture.json
@@ -64,7 +64,7 @@
     "items": [
       "User=root: Der Dienst verwendet Root-Rechte für Host-Operationen wie SMART, pvesh und ZFS-Verwaltung. Die Webterminal-Prozesse erben diese Rechte.",
       "Restart=on-failure mit zehn Sekunden Wartezeit: Nach fehlerhaftem Ende startet der Dienst automatisch neu.",
-      "After=network.target: ordnet den Start nach diesem Ziel ein; garantiert keine IP-Adresse, DNS oder externe Verbindung."
+      "After=network.target pve-cluster.service: ordnet den Start nach dem Netzwerk-Target und dem Proxmox-Cluster-Dateisystem ein; garantiert keine IP-Adresse, DNS oder externe Verbindung."
     ],
     "inspectTitle": "Aktuelle Unit prüfen"
   },
diff --git a/web/messages/de/docs/monitor/dashboard/hardware.json b/web/messages/de/docs/monitor/dashboard/hardware.json
index f45c1bd6..73fc464c 100644
--- a/web/messages/de/docs/monitor/dashboard/hardware.json
+++ b/web/messages/de/docs/monitor/dashboard/hardware.json
@@ -168,7 +168,7 @@
   "ups": {
     "heading": "UPS-Status",
     "subHeading": "(bedingt)",
-    "body": "Erscheint bei konfiguriertem und erreichbarem NUT-Server (Network UPS Tools). Sichtbar sind Status (Netzbetrieb/Batteriebetrieb/Laden/Batterie schwach), Batterieladung, Restlaufzeit, Last, Eingangsspannung, Modell und Firmware. Dieselben Daten fließen in Sicherheit und Zertifikate der Zustandsüberwachung ein; Batteriebetrieb wird sofort gemeldet."
+    "body": "Erscheint bei konfiguriertem und erreichbarem NUT-Server (Network UPS Tools). Sichtbar sind Status (Netzbetrieb/Batteriebetrieb/Laden/Batterie schwach), Batterieladung, Restlaufzeit, Last, Eingangsspannung, Modell und Firmware."
   },
   "dataCollected": {
     "heading": "Wie die Daten gesammelt werden",
diff --git a/web/messages/de/docs/monitor/dashboard/index.json b/web/messages/de/docs/monitor/dashboard/index.json
index 61d63b51..de3e0b72 100644
--- a/web/messages/de/docs/monitor/dashboard/index.json
+++ b/web/messages/de/docs/monitor/dashboard/index.json
@@ -50,7 +50,7 @@
       },
       {
         "name": "Terminal",
-        "owns": "Shell-Zugriff auf Host oder VM/CT im Browser mit xterm.js über WebSockets."
+        "owns": "Browser-Shell auf dem Proxmox-Host mit xterm.js über WebSockets. LXC-Konsolen öffnen sich im Tab VMs und LXCs."
       },
       {
         "name": "Sicherheit",
diff --git a/web/messages/de/docs/monitor/dashboard/security.json b/web/messages/de/docs/monitor/dashboard/security.json
index 2ba938e6..6c3a62ed 100644
--- a/web/messages/de/docs/monitor/dashboard/security.json
+++ b/web/messages/de/docs/monitor/dashboard/security.json
@@ -183,7 +183,7 @@
       },
       {
         "card": "SSL / HTTPS",
-        "endpoint": "/api/auth/ssl/*",
+        "endpoint": "/api/ssl/*",
         "source": "openssl x509 auf /etc/pve/local/pve-ssl.pem + /etc/proxmenux/ssl_config.json."
       },
       {
diff --git a/web/messages/de/docs/monitor/dashboard/settings.json b/web/messages/de/docs/monitor/dashboard/settings.json
index 56d6cb32..2cec24a8 100644
--- a/web/messages/de/docs/monitor/dashboard/settings.json
+++ b/web/messages/de/docs/monitor/dashboard/settings.json
@@ -41,7 +41,7 @@
     "heading": "Netzwerkeinheiten",
     "imageAlt": "Karte Netzwerkeinheiten mit der Anzeigeauswahl Bytes",
     "imageCaption": "Wählen Sie zwischen Bits pro Sekunde und Bytes pro Sekunde für jede im Dashboard angezeigte Netzwerkrate.",
-    "body": "Zeigt den Netzwerkdurchsatz in Bits pro Sekunde (Mbps/Gbps) oder Bytes pro Sekunde (MB/s/GB/s). Standard sind Bits wie bei Netzwerkkarten und Internetanbietern; Dateiübertragungen verwenden oft Bytes. Diagramme, Labels und Hinweise ändern sich sofort ohne Neuladen."
+    "body": "Zeigt den Netzwerkdurchsatz in Bytes pro Sekunde (MB/s/GB/s) oder Bits pro Sekunde (Mbps/Gbps). Standard sind Bytes wie bei den meisten Dateiübertragungswerkzeugen; Bits verwenden Netzwerkkartenhersteller und Internetanbieter. Die Auswahl wird im Browser gespeichert, jeder Browser behält also seine eigene. Diagramme, Labels und Hinweise ändern sich sofort ohne Neuladen."
   },
   "health": {
     "heading": "Gesundheitsmonitor",
@@ -203,7 +203,7 @@
     "heading": "LXC-Update-Erkennung",
     "imageAlt": "LXC-Update-Erkennung mit Schalter zur Suche nach Paketupdates in laufenden Debian-, Ubuntu- und Alpine-Containern",
     "imageCaption": "Schalter für regelmäßige Prüfungen mit apt list --upgradable / apk list -u in laufenden CTs. Standardmäßig aktiviert. Der zugehörige Schalter unter Benachrichtigungen → Dienste erscheint bei aktiver Erkennung.",
-    "intro": "Der Schalter zwischen Schwellen und Benachrichtigungen steuert den LXC-Scan. Aktiv fragt er Paketmanager laufender CTs ab; Hardware zeigt Ergebnisse und lxc_updates_available erhält sie. Deaktiviert stoppt er seine pct exec-Aufrufe und entfernt LXC-Einträge aus managed_installs.json; /api/managed-installs meldet den Status dann ohne 24-Stunden-Wartezeit nicht mehr.",
+    "intro": "Der Schalter zwischen Schwellen und Benachrichtigungen steuert den LXC-Scan. Aktiv fragt er Paketmanager laufender CTs ab; VMs und LXCs zeigt Ergebnisse und lxc_updates_available erhält sie. Deaktiviert stoppt er seine pct exec-Aufrufe und entfernt LXC-Einträge aus managed_installs.json; /api/managed-installs meldet den Status dann ohne 24-Stunden-Wartezeit nicht mehr.",
     "whatRunsTitle": "Was die Prüfung ausführt",
     "whatRunsIntro": "Für jeden CT im Zustand running mit unterstütztem Paketmanager:",
     "whatRunsItems": [
@@ -249,7 +249,7 @@
     "body1": "Hier aktivieren Sie ProxMenux-Monitor-Benachrichtigungen und die KI-Umformulierung. Benachrichtigungen aktivieren startet den Hintergrundversand, registriert ein Proxmox-VE-Webhook-Ziel für dessen Ereignisse und öffnet die Kanaleinstellungen für Telegram, Discord, E-Mail, Gotify und weitere Dienste. Die KI-Umformulierung liegt im selben Bereich unter aufklappbaren erweiterten Einstellungen.",
     "body2": "Eigene Seiten erklären die Optionen: Kanäle, Ereignisschalter, formatierte Nachrichten, Anzeigename, Versand mit Duplikatfilter, Wartezeiten, Bündelung und Ruhezeiten, PVE-Webhook, KI-Anbieter und Prompt-Modi:",
     "items": [
-      "Benachrichtigungen: Kanaleinrichtung (Telegram, Discord, Gotify, E-Mail mit Gmail-/Microsoft-App-Passwörtern, ntfy, Slack, Teams und allgemeiner Webhook), Ereigniskategorien, formatierte Nachrichten, Anzeigename, Versand, PVE-Webhook, Verlauf und API.",
+      "Benachrichtigungen: Kanaleinrichtung (Telegram, Discord, Gotify, E-Mail mit Gmail-/Microsoft-App-Passwörtern, Pushover und Apprise für ntfy, Slack, Teams und weitere Dienste), Ereigniskategorien, formatierte Nachrichten, Anzeigename, Versand, PVE-Webhook, Verlauf und API.",
       "KI-Assistent: Anbieter (OpenAI, Anthropic, Gemini, Groq, OpenRouter, Ollama), Modelle, Standard- oder eigene Prompts, Ausgabesprache, Detailgrad je Kanal und KI-Vorschläge."
     ]
   },
@@ -294,8 +294,8 @@
       },
       {
         "card": "Netzwerkeinheiten",
-        "endpoint": "/api/settings",
-        "source": "In der SQLite-Einstellungstabelle des Dashboards gespeichert."
+        "endpoint": "localStorage",
+        "source": "Browser-Einstellung proxmenux-network-unit."
       },
       {
         "card": "Health-Monitor-Zeitdauern",
@@ -304,7 +304,7 @@
       },
       {
         "card": "Speicher-/Schnittstellenausschlüsse",
-        "endpoint": "/api/storage/exclusions, /api/network/exclusions",
+        "endpoint": "/api/health/storage-exclusions, /api/health/interface-exclusions",
         "source": "SQLite Tabellen excluded_storages und excluded_interfaces."
       },
       {
diff --git a/web/messages/de/docs/monitor/dashboard/storage.json b/web/messages/de/docs/monitor/dashboard/storage.json
index 3f75c928..0a3149a0 100644
--- a/web/messages/de/docs/monitor/dashboard/storage.json
+++ b/web/messages/de/docs/monitor/dashboard/storage.json
@@ -10,7 +10,7 @@
   },
   "intro": {
     "title": "Unterstützt durch drei Quellen",
-    "body": "Proxmox-Speicher stammen aus pvesm status, ZFS aus zpool status, physische Laufwerke aus lsblk + smartctl und NVMe-Felder aus nvme. Die Seite aktualisiert sich etwa alle 60 Sekunden; die Detailansicht fordert eine SMART-Abfrage an."
+    "body": "Proxmox-Speicher stammen aus pvesm status, ZFS aus zpool status, physische Laufwerke aus lsblk + smartctl und NVMe-Felder aus nvme. Die Seite aktualisiert sich etwa alle 30 Sekunden; die Detailansicht fordert eine SMART-Abfrage an."
   },
   "thresholds": {
     "title": "Hier angewendete Statusfarben und -schwellen",
diff --git a/web/messages/de/docs/monitor/dashboard/system-logs.json b/web/messages/de/docs/monitor/dashboard/system-logs.json
index 71083e95..c265c08e 100644
--- a/web/messages/de/docs/monitor/dashboard/system-logs.json
+++ b/web/messages/de/docs/monitor/dashboard/system-logs.json
@@ -18,7 +18,7 @@
       "Einträge insgesamt — Anzahl der Protokolleinträge im aktiven Filterzeitraum.",
       "Fehler — Anzahl der Einträge mit Schweregrad ≤ 3 (err / crit / alert / emerg).",
       "Warnungen — Anzahl der Einträge mit Schweregrad 4 (warning).",
-      "Sicherungen — Anzahl der vzdump-/PBS-Aufgabeneinträge im selben Zeitraum."
+      "Sicherungen: Anzahl der Backup-Archive auf den konfigurierten Backup-Speichern."
     ]
   },
   "subtabs": {
@@ -26,10 +26,10 @@
     "logsTitle": "Protokolle",
     "logsIntro": "Das Systemjournal wird auf dem Server mit journalctl abgerufen. Die Werkzeugleiste bietet folgende Filter :",
     "logsFilters": [
-      "Schweregrad — emerg / alert / crit / err / warning / notice / info / debug oder eine beliebige Kombination.",
-      "Zeitbereich — zuletzt 5 min. / 15 min. / 1 h / 6 h / 24 h / 7 d / Custom.",
-      "Freitextsuche — Teilzeichenfolge oder regulärer Ausdruck (journalctl --grep).",
-      "Unit-Filter — auf eine bestimmte systemd-Unit beschränken (pveproxy.service, nginx.service, …)."
+      "Stufe: Alle Ebenen, Fehler, Warnung oder Info.",
+      "Zeitbereich: 1 Tag / 3 Tage / 1 Woche / 2 Wochen / 1 Monat oder eine benutzerdefinierte Anzahl von Tagen (bis zu 90).",
+      "Freitextsuche: Teilzeichenfolge ohne Beachtung der Groß-/Kleinschreibung über Nachricht, Dienst, PID, Hostname und Unit, im Browser angewendet.",
+      "Dienstfilter: auf einen Dienst beschränken (der Journal-SYSLOG_IDENTIFIER)."
     ],
     "logsRowsAfter": "Jede Zeile zeigt Zeitstempel, Schweregradabzeichen, Quelleinheit und die Nachricht. Lange Nachrichten brechen mit einem \"mehr anzeigen\"-Schalter zusammen. Die Aktion Download bündelt den aktuellen Filter über GET /api/logs/download in einer einzigen .txt-Datei – nützlich, wenn Sie ein Stück Journal mit jemandem teilen möchten.",
     "logDetailsModalTitle": "Dialog Protokolldetails",
@@ -50,10 +50,10 @@
     "maxLevelStoreTitle": "Journald MaxLevelStore",
     "maxLevelStoreBody": "Bei einer neuen Proxmox-Installation verwendet journald standardmäßig MaxLevelStore=warning und verwirft dadurch Info-Meldungen. Monitor erkennt dies beim Start und ergänzt eine Drop-in-Datei (/etc/systemd/journald.conf.d/proxmenux-loglevel.conf) mit dem Grenzwert info. So werden Informationsmeldungen und schwerwiegendere Meldungen gespeichert; debug ist darin nicht enthalten.",
     "backupsTitle": "Sicherungen",
-    "backupsBody": "Proxmox-Taskhistorie gefiltert auf Backup-bezogene Einträge. Eine Zeile pro Aufgabe (vzdump, PBS-Transfers, Garbage Collect, Verify) mit dem Status (OK / WARNINGS / ERROR), beteiligter Gast, Quellspeicher, Dauer und UPID. Klicken Sie auf eine Zeile, um das vollständige Aufgabenprotokoll über GET /api/task-log/<upid> zu laden - die gleichen Daten, die Proxmox über Datacenter → Tasks, die auf Backups beschränkt sind, freigibt.",
+    "backupsBody": "Die Backup-Archive auf jedem backupfähigen Speicher (dir, NFS, CIFS, PBS), gelesen mit pvesh get /nodes/localhost/storage/<id>/content. Karten oben zeigen die Anzahl der VMs und LXCs sowie die Gesamtgröße; darunter steht eine Zeile pro Archiv mit Typ, Speicher, Größe und Datum. Die Wiederherstellung erfolgt in der Proxmox-Weboberfläche.",
     "notificationsTitle": "Benachrichtigungen",
-    "notificationsBody1": "Alle von Monitor versendeten Benachrichtigungen: Telegram, Discord, E-Mail, Gotify, ntfy, Slack, Teams und Webhook. Jede Zeile zeigt Zeitstempel, Kanal, Ereignistyp, Schweregrad, erzeugten Titel und Nachrichtentext sowie bei aktivierter KI einen Schalter zum Vergleich der KI-Fassung mit dem Original.",
-    "notificationsBody2": "Hier prüfen Sie, ob ein Kanal tatsächlich Nachrichten zustellt, und vergleichen die KI-Fassung mit der ursprünglichen Vorlage. Die Kanäle konfigurieren Sie auf der Detailseite Benachrichtigungen."
+    "notificationsBody1": "Proxmox-Benachrichtigungsaktivität: Journalzeilen von Proxmox-Diensten, die Benachrichtigungen, E-Mail, Webhooks oder Alarme erwähnen, die Ziele in /etc/pve/notifications.cfg und Backup-Aufgaben aus der Cluster-Aufgabenliste. Bei Zeilen von Backup-Aufgaben kann das vollständige Aufgabenprotokoll heruntergeladen werden. Der Verlauf dessen, was der Monitor selbst versendet hat, befindet sich unter Einstellungen → Benachrichtigungen → Benachrichtigungsverlauf.",
+    "notificationsBody2": "Hier prüfen Sie, was das eigene Benachrichtigungssystem von Proxmox tut. Die Kanäle des Monitors konfigurieren Sie auf der Detailseite Benachrichtigungen."
   },
   "dataCollected": {
     "heading": "Wie die Daten gesammelt werden",
@@ -64,7 +64,7 @@
       {
         "subtab": "Protokolle (Live-Filter)",
         "endpoint": "/api/logs",
-        "source": "journalctl --output json --since <range> mit serverseitigen Filtern für Schweregrad, Unit und Suche."
+        "source": "Der Tab ruft /api/logs?since_days=N ab; Stufe, Dienst und Textsuche werden im Browser gefiltert. Die API akzeptiert auch priority und service, die der Tab noch nicht nutzt."
       },
       {
         "subtab": "Herunterladen",
@@ -74,17 +74,17 @@
       {
         "subtab": "Sicherungen",
         "endpoint": "/api/backups",
-        "source": "PVE-Taskhistorie gefiltert nach vzdump, PBS-Transfers, Garbage Collect, Verify."
+        "source": "Backup-Archive, aufgelistet aus jedem Backup-Speicher mit pvesh get /nodes/localhost/storage/<id>/content."
       },
       {
-        "subtab": "Details einer Sicherungsaufgabe",
+        "subtab": "Protokoll der Sicherungsaufgabe (Benachrichtigungen)",
         "endpoint": "/api/task-log/<upid>",
         "source": "Vollständiges Aufgabenprotokoll im Klartext aus /var/log/pve/tasks/<index>/<upid>."
       },
       {
-        "subtab": "Meldeverlauf",
-        "endpoint": "/api/notifications/history",
-        "source": "Die SQLite-Tabelle notification_history wird von der Versandschleife gefüllt."
+        "subtab": "Benachrichtigungen",
+        "endpoint": "/api/notifications",
+        "source": "journalctl --grep über Proxmox-Units, /etc/pve/notifications.cfg und Backup-Aufgaben aus pvesh get /cluster/tasks."
       }
     ],
     "apiIntro": "Live-Filter und Downloads sind auch über die API erreichbar:",
diff --git a/web/messages/de/docs/monitor/dashboard/system-overview.json b/web/messages/de/docs/monitor/dashboard/system-overview.json
index cfc6c595..50404c89 100644
--- a/web/messages/de/docs/monitor/dashboard/system-overview.json
+++ b/web/messages/de/docs/monitor/dashboard/system-overview.json
@@ -46,7 +46,7 @@
     "thresholdsIntro": "Ringe, Balken und Minidiagramme verwenden dieselbe Einteilung:  grün unter der Warnschwelle,  bernsteinfarben zwischen Warn- und kritischer Schwelle,  rot ab der kritischen Schwelle. Empfohlene Standardwerte von ProxMenux :",
     "thresholdsItems": [
       "CPU-Auslastung — Warnung 85 %, kritisch 95 %.",
-      "Arbeitsspeicher — Warnung 85 %, kritisch 95 %. Swap löst ebenfalls ab 5 % Nutzung den kritischen Zustand aus; ein gesunder Proxmox-Host sollte Swap nur selten verwenden.",
+      "Arbeitsspeicher: Warnung 85 %, kritisch 95 %. Swap löst den kritischen Zustand nur aus, wenn die Swap-Nutzung über 80 % liegt und der verfügbare RAM unter 15 %.",
       "CPU-Temperatur — Warnung 80 °C, kritisch 90 °C."
     ],
     "thresholdsOutro": "Alle Werte sind pro Host konfigurierbar. Einstellungen → Grenzwerte der Zustandsüberwachung ist die zentrale Referenz und erklärt ihre Anpassung.",
@@ -58,7 +58,7 @@
     "intro": "Die Karten CPU-Auslastung und Arbeitsspeicher öffnen eine sortierbare Liste der 25 führenden Prozesse: nach CPU-Auslastung beim Öffnen über CPU, nach residentem Speicher beim Öffnen über Arbeitsspeicher.",
     "listTitle": "Der Listendialog",
     "listItems": [
-      "Automatische Aktualisierung — die Liste wird bei geöffnetem Dialog alle 5 Sekunden aktualisiert. Beim Schließen enden die Abfragen.",
+      "Automatische Aktualisierung — die Liste wird bei geöffnetem Dialog alle 3 Sekunden aktualisiert. Beim Schließen enden die Abfragen.",
       "Filter — das Suchfeld grenzt die Liste nach Befehl, Benutzer oder PID ein.",
       "Inline bar — die primäre metrische Spalte zeichnet einen kleinen Balken, der auf den höchsten Wert in der gefilterten Liste skaliert ist, so dass das Ranking auch dann sichtbar bleibt, wenn kein Prozess in der Nähe von 100 % liegt.",
       "Mobiles Layout — unter 640 px werden PID und Benutzer ausgeblendet. Befehl, CPU % und Arbeitsspeicher passen so ohne horizontales Scrollen auf den Telefonbildschirm."
@@ -79,7 +79,7 @@
   },
   "middle": {
     "heading": "Mitte: Knotenmetrikdiagramme",
-    "body1": "Unter den Karten zeigt NodeMetricsCharts CPU-, Arbeitsspeicher- und Datenträger-I/O-Verläufe aus dem RRD-Speicher von Proxmox über /api/node/metrics. Wählbar sind 1 Stunde / 24 Stunden / 7 Tage / 30 Tage / 1 Jahr. Bei größeren Zeiträumen sinkt die Datenauflösung, damit die Anzeige flüssig bleibt.",
+    "body1": "Unter den Karten zeigt NodeMetricsCharts CPU-, Arbeitsspeicher- und Datenträger-I/O-Verläufe aus dem RRD-Speicher von Proxmox über /api/node/metrics. Wählbar sind 1 Stunde / 24 Stunden / 7 Tage / 30 Tage. Bei größeren Zeiträumen sinkt die Datenauflösung, damit die Anzeige flüssig bleibt.",
     "body2": "Dies sind die gleichen Graphen, die die Proxmox-Web-Benutzeroberfläche für einen Knoten darstellt, nur in das dunkle Thema des Monitors konsolidiert und mit den anderen Panels ausgerichtet."
   },
   "bottom": {
diff --git a/web/messages/de/docs/monitor/dashboard/vms-lxcs.json b/web/messages/de/docs/monitor/dashboard/vms-lxcs.json
index 53ab3b16..116ed936 100644
--- a/web/messages/de/docs/monitor/dashboard/vms-lxcs.json
+++ b/web/messages/de/docs/monitor/dashboard/vms-lxcs.json
@@ -109,7 +109,7 @@
     "logsTitle": "Tab 5 — Protokolle (nur OCI-Container)",
     "logsBody": "Nur für Container aus OCI manager Apps. Zeigt die Hauptprozess-Konsole des Images aus /var/log/proxmenux/oci/VMID.console.log: letzte 100, 500 oder 1000 Zeilen, Live-Verfolgung bei laufendem Container, Filter und Download. Die Datei wird beim Öffnen neu gelesen, auch bei gestopptem Container. Details: OCI-Container in ProxMenux Monitor.",
     "backupsTitle": "Tab. 6 — Backups",
-    "backupsImageAlt": "Pro-Gast-Bohr-in-Modal - Backups-Registerkarte mit der verfügbaren Backup-Liste, Ziel-Tag, Größen und der Create Backup-Schaltfläche",
+    "backupsImageAlt": "Gast-Detaildialog: Registerkarte Backups mit der verfügbaren Backup-Liste, Ziel-Tag, Größen und der Schaltfläche „Backup erstellen“",
     "backupsImageCaption": "Alle Sicherungen dieses Gasts auf konfigurierten Proxmox-Speichern, neueste zuerst. Die Kopfzeile zeigt ihre Anzahl.",
     "backupsIntro": "Listet alle Gastsicherungen auf konfigurierten Proxmox-Speichern, neueste zuerst. Der Zähler im Titel zeigt, ob Sicherungen vorhanden sind. Jede Zeile enthält :",
     "backupsItems": [
@@ -117,7 +117,7 @@
       "Zielanzeige — Speicherort (PBS-Cloud, PBS-Local, NFS-Backup, …), nach Zustand eingefärbt.",
       "Größe — endgültige Größe der Sicherung auf dem Datenträger."
     ],
-    "backupsOutro": "+ Sicherung erstellen startet eine Sicherung auf dem in Proxmox als „Backup target“ markierten Speicher. Die Wiederherstellung erfolgt in der Proxmox-Weboberfläche; Monitor zeigt aktuelle Sicherungen des Gasts und erstellt neue.",
+    "backupsOutro": "Die Schaltfläche + Sicherung erstellen oben rechts öffnet einen Dialog, in dem Sie vor dem Start eines vzdump Speicher, Modus (snapshot / suspend / stop), Schutz und Notizen wählen. Die Wiederherstellung erfolgt in der Proxmox-Weboberfläche; Monitor zeigt aktuelle Sicherungen des Gasts und erstellt neue.",
     "firewallTitle": "Tab. 7 — Firewall",
     "firewallIntro": "Liest das Proxmox-Firewallprotokoll des Gasts direkt vom Host, ohne Zusatzdienst oder regelmäßige Abfragen. Der Reiter ist immer sichtbar; der Inhalt hängt davon ab, ob die Firewall aktiv ist und Regeln tatsächlich protokollieren.",
     "firewallItems": [
diff --git a/web/messages/de/docs/monitor/health-monitor.json b/web/messages/de/docs/monitor/health-monitor.json
index 6f3d16d5..c55c5f4f 100644
--- a/web/messages/de/docs/monitor/health-monitor.json
+++ b/web/messages/de/docs/monitor/health-monitor.json
@@ -153,8 +153,8 @@
     "pillTitle": "Die Statusanzeige folgt der schwerwiegendsten Kategorie",
     "pillBody": "Die Farbe im Dashboard-Kopf folgt dem höchsten Schweregrad: CRITICAL → rot, sonst WARNING → gelb, sonst INFO → blau, sonst grün. Dieselbe Logik steuert den Punkt im Favicon und das PWA-Symbol.",
     "updateNowTitle": "Schaltfläche Jetzt Aktualisieren im Overview-Header",
-    "updateNowBody1": "Meldet Systemupdates ausstehende Updates, erscheint neben der Statusanzeige Jetzt aktualisieren. Der Dialog startet denselben Hostablauf wie ProxMenux → Settings post-install Proxmox → Proxmox System Update: Repository-Bereinigung, apt full-upgrade über update-pve-safe.sh, DKMS-Neubau verwalteter Treiber bei neuem Kernel, autoremove/autoclean und gegebenenfalls eine Neustartabfrage. Der Fortschritt wird laufend angezeigt.",
-    "updateNowBody2": "Nach Abschluss ruft das Schließen des Dialogs GET /api/health/details?refresh=1 auf und verwirft damit den Cache dieses Endpunkts. Ausstehende Updates und Statusanzeige werden anhand des neuen Paketstands neu berechnet. Der Hinweis verschwindet ohne Warten auf den nächsten Zyklus. Auf einem aktuellen Host bleibt die Schaltfläche verborgen."
+    "updateNowBody1": "Meldet Systemupdates ausstehende Updates, erscheint neben der Statusanzeige Jetzt aktualisieren. Der Dialog startet denselben Hostablauf wie ProxMenux → Dienstprogramme und Tools → Proxmox-Systemupdate: Repository-Bereinigung, apt full-upgrade über update-pve-safe.sh, DKMS-Neubau verwalteter Treiber bei neuem Kernel, autoremove/autoclean und gegebenenfalls eine Neustartabfrage. Der Fortschritt wird laufend angezeigt.",
+    "updateNowBody2": "Nach Abschluss ruft das Schließen des Dialogs GET /api/health/full?refresh=1 auf und verwirft damit den Cache dieses Endpunkts. Ausstehende Updates und Statusanzeige werden anhand des neuen Paketstands neu berechnet. Der Hinweis verschwindet ohne Warten auf den nächsten Zyklus. Auf einem aktuellen Host bleibt die Schaltfläche verborgen."
   },
   "dismiss": {
     "heading": "Warnungen bestätigen und die Unterdrückungsdauer",
diff --git a/web/messages/de/docs/monitor/index.json b/web/messages/de/docs/monitor/index.json
index 4039ab4a..1f77e4be 100644
--- a/web/messages/de/docs/monitor/index.json
+++ b/web/messages/de/docs/monitor/index.json
@@ -113,13 +113,13 @@
   "api": {
     "heading": "REST API und Integrationen",
     "intro": "REST-Endpunkte liefern Überwachungsdaten als JSON über HTTP/HTTPS für Homepage, Home Assistant, Uptime Kuma und eigene Skripte. Grafana kann den separaten Prometheus-Textexporter unter /api/prometheus nutzen. Anfragen sind mit curl möglich.",
-    "tokens": "Langlebige API-Tokens (365 Tage) werden aus Einstellungen → API Zugriffstokens oder über POST /api/auth/generate-api-token generiert.",
+    "tokens": "Langlebige API-Tokens (365 Tage) werden aus Admin → Sicherheit → API-Tokens oder über POST /api/auth/generate-api-token generiert.",
     "bearer": "Token werden als Authorization: Bearer … gesendet. Auch /api/health erwartet den Bearer-Header, daher benötigen Verfügbarkeitsprüfungen ein read_only-Token. Unter /api/auth/* liegen sowohl öffentliche Setup-/Login-/Statusrouten als auch geschützte Routen der Konto-/Tokenverwaltung; nicht alle sind anonym.",
     "catalog": "Der vollständige Endpunktkatalog, Anleitungen zur Token-Rotation und Best Practices für die Sicherheit finden Sie in der API-Referenz; Vorgefertigte Beispiele für Homepage, Home Assistant, Grafana, Uptime Kuma und ein generisches cURL-Muster finden Sie in Integrationen."
   },
   "serviceControl": {
     "heading": "Dienststeuerung",
-    "intro": "Der Monitor wird wie jeder systemd-Dienst verwaltet. Zusätzlich gibt es zwei Einträge in der ProxMenux-Textoberfläche unter Einstellungen:",
+    "intro": "Der Monitor wird wie jeder systemd-Dienst verwaltet. Zusätzlich gibt es drei Einträge in der ProxMenux-Textoberfläche unter Einstellungen:",
     "codeComment": "# Manuelle Steuerung",
     "footer": "Siehe Einstellungen → ProxMenux Monitor für die Umschaltung im Menü und den Statusüberprüfungsablauf."
   },
diff --git a/web/messages/de/docs/monitor/integrations.json b/web/messages/de/docs/monitor/integrations.json
index 72610b7c..f9d14cd2 100644
--- a/web/messages/de/docs/monitor/integrations.json
+++ b/web/messages/de/docs/monitor/integrations.json
@@ -20,13 +20,13 @@
     "heading": "Authentifizierung: Das Einzige, was jedes Rezept braucht",
     "intro": "Die meisten von diesen Integrationen verwendeten Endpunkte werden authentifiziert. Sie haben zwei Möglichkeiten, diese Anforderung zu erfüllen.",
     "optAtitle": "Option A — API-Token (für Integrationen empfohlen)",
-    "optAbody1": "Öffnen Sie Einstellungen → Sicherheit → API-Token. Wählen Sie Token erzeugen, vergeben Sie einen Namen wie homepage, home-assistant oder prometheus und kopieren Sie das Token: Es erscheint nur einmal. Es läuft standardmäßig nach einem Jahr ab und ist einzeln widerrufbar. Nutzen Sie es für Clients außerhalb des Browsers.",
+    "optAbody1": "Öffnen Sie Admin → Sicherheit → API-Tokens. Wählen Sie Neues Token generieren, vergeben Sie einen Namen wie homepage, home-assistant oder prometheus, geben Sie Ihr Passwort ein (und bei aktivierter 2FA den Code), klicken Sie dann auf Token generieren und kopieren Sie das Token: Es erscheint nur einmal. Es läuft standardmäßig nach einem Jahr ab und ist einzeln widerrufbar. Nutzen Sie es für Clients außerhalb des Browsers.",
     "optAbody2": "Ab diesem Zeitpunkt ist jede Anfrage nur noch:",
     "optBtitle": "Option B — Login-Flow (Benutzername + Passwort)",
     "optBbody": "Nützlich für Skripte, die sich als menschlicher Benutzer authentifizieren. Das zurückgegebene Token ist kurzlebig; die meisten Integrationen sollten Option A bevorzugen.",
     "outro": "Das TOTP-Feld ist nur bei aktivierter Zwei-Faktor-Authentifizierung nötig. Tokenwechsel, Widerruf, Passwortrichtlinien und Prüfprotokoll sind unter Zugriff und Authentifizierung beschrieben.",
     "httpsTitle": "Verwendung von HTTPS anstelle von HTTP",
-    "httpsIntro": "Jedes Rezept unten verwendet http:// in den URLs, um die Beispiele kurz zu halten. Wenn Sie TLS auf dem Monitor aktiviert haben (Settings → Security → SSL/HTTPS), tauschen Sie http:// in jeder URL gegen https:// aus – das ist die einzige Änderung. Zwei spezielle Anmerkungen zu bestimmten Werkzeugen:",
+    "httpsIntro": "Jedes Rezept unten verwendet http:// in den URLs, um die Beispiele kurz zu halten. Wenn Sie TLS auf dem Monitor aktiviert haben (Admin → Sicherheit → SSL / HTTPS), tauschen Sie http:// in jeder URL gegen https:// aus – das ist die einzige Änderung. Zwei spezielle Anmerkungen zu bestimmten Werkzeugen:",
     "httpsItems": [
       "Selbstsignierte Zertifikate. Die Home-Assistant-Integration rest: prüft TLS standardmäßig. Für ein selbstsigniertes Monitor-Zertifikat setzen Sie verify_ssl: false in jeden REST-Block neben scan_interval: oder nehmen die Monitor-CA in den Vertrauensspeicher auf. Entsprechendes gilt für andere Werkzeuge mit Zertifikatsprüfung.",
       "Prometheus enthält unten bereits scheme: https. Aktivieren oder behalten Sie https, wenn TLS im Monitor aktiv ist."
@@ -43,7 +43,7 @@
     "basicTitle": "Grundlegendes Widget — keine Authentifizierung",
     "basicIntro": "Verwenden Sie dies, wenn sich ProxMenux Monitor in einem vertrauenswürdigen Netzwerk befindet und Sie die Authentifizierung auf der Monitorseite noch nicht aktiviert haben. Der einfachste mögliche services.yaml-Eintrag:",
     "authedTitle": "Authentifiziertes Widget",
-    "authedIntro": "Generieren Sie ein API-Token in Settings → Security → API Tokens auf dem Monitor, kopieren Sie es und fügen Sie es in den Authorization-Header unten ein - ersetzen Sie das Beispiel-Token, das nach Bearer angezeigt wird, durch das, das Sie gerade kopiert haben:",
+    "authedIntro": "Generieren Sie ein API-Token in Admin → Sicherheit → API-Tokens auf dem Monitor, kopieren Sie es und fügen Sie es in den Authorization-Header unten ein - ersetzen Sie das Beispiel-Token, das nach Bearer angezeigt wird, durch das, das Sie gerade kopiert haben:",
     "authedOutro": "Starten Sie die Homepage neu und die Karte leuchtet mit Live-Werten auf. Verwenden Sie das gleiche Token für alle Homepage-Widgets, die auf denselben ProxMenux Monitor-Host zeigen.",
     "multiTitle": "Einrichtung von Multiwidgets — System, Speicher, Netzwerk",
     "multiIntro": "Für mehr Details nutzen Sie drei Karten mit unterschiedlichen Endpunkten: Systemmetriken, Datenspeicher und Netzwerk. Für dieselbe Monitor-Instanz verwenden alle dasselbe Token.",
diff --git a/web/messages/de/docs/monitor/notifications.json b/web/messages/de/docs/monitor/notifications.json
index 075809d1..6a5b9e7d 100644
--- a/web/messages/de/docs/monitor/notifications.json
+++ b/web/messages/de/docs/monitor/notifications.json
@@ -314,7 +314,7 @@
   },
   "aiRewrite": {
     "heading": "Optionales AI-Rewrite",
-    "body1": "Geeignete Ereignisse können vor dem Versand durch ein LLM umgeschrieben werden. backup_complete und backup_fail sind ausgenommen. KI ist standardmäßig aus und läuft im Versandthread. Bei Fehler oder Zeitüberschreitung wird der Vorlagentext verwendet.",
+    "body1": "Geeignete Ereignisse können vor dem Versand durch ein LLM umgeschrieben werden. backup_complete und backup_fail werden nicht umgeschrieben; mit aktivierten KI-Vorschlägen fügt die KI nur eine Erklärung an, wenn ein Backup Fehler meldet. KI ist standardmäßig aus und läuft im Versandthread. Bei Fehler oder Zeitüberschreitung wird der Vorlagentext verwendet.",
     "body2": "Unterstützt OpenAI, Anthropic, Google Gemini, Groq, OpenRouter und lokales Ollama. Pro Kanal: Detailgrad (brief, standard, detailed), Sprache, Anweisungsmodus (default oder custom) und eigene Anweisung. Anleitung und Beispiele unter KI-Assistent.",
     "privacyTitle": "Datenschutzerklärung",
     "privacyBody": "KI-Anfragen können Titel, Schweregrad, Text, System-/Benutzeranweisungen und Kontext mit Hostnamen, IPs, Benutzernamen, Fehlern und Journalzeilen enthalten. Sie gehen an den gewählten Endpunkt. Ollama bleibt nur mit lokalem Server und lokalem Modell lokal; entfernte Server oder Cloudmodelle übertragen Daten nach außen. Bei lokalen Anforderungen KI deaktivieren oder kontrollierten lokalen Server und Modell nutzen."
diff --git a/web/messages/de/docs/security/ssl-letsencrypt.json b/web/messages/de/docs/security/ssl-letsencrypt.json
index fecde538..85958539 100644
--- a/web/messages/de/docs/security/ssl-letsencrypt.json
+++ b/web/messages/de/docs/security/ssl-letsencrypt.json
@@ -12,13 +12,13 @@
   },
   "intro": {
     "title": "Was diese Seite abdeckt",
-    "body": "So wechseln Sie Monitor unter Einstellungen → Sicherheit von HTTP auf HTTPS, welche Dateien die Zertifikatsquellen verwenden, wie die Proxmox-Option mit Let’s Encrypt umgeht und wie Sie eigene Pfade für außerhalb von Proxmox verwaltete Zertifikate festlegen."
+    "body": "So wechseln Sie Monitor unter Admin → Sicherheit von HTTP auf HTTPS, welche Dateien die Zertifikatsquellen verwenden, wie die Proxmox-Option mit Let’s Encrypt umgeht und wie Sie eigene Pfade für außerhalb von Proxmox verwaltete Zertifikate festlegen."
   },
   "wheresetting": {
     "heading": "Wo Sie die Einstellung finden",
-    "body": "Öffnen Sie Monitor und gehen Sie zu Einstellungen → Sicherheit → HTTPS / SSL. Der Bereich zeigt HTTP oder HTTPS und bei noch deaktiviertem HTTPS die erkannten Zertifikatsquellen.",
-    "imageAlt": "Einstellungen → Sicherheit → HTTPS / SSL mit deaktiviertem HTTPS, erkanntem Proxmox-Zertifikat (Inhaber, Aussteller, Ablauf) und der Option für ein eigenes Zertifikat",
-    "caption": "Bereich Einstellungen → Sicherheit → HTTPS / SSL vor der Aktivierung. Monitor zeigt das von Proxmox verwendete Zertifikat und bietet es zur Auswahl mit einem Klick an."
+    "body": "Öffnen Sie Monitor und gehen Sie zu Admin → Sicherheit → SSL / HTTPS. Der Bereich zeigt HTTP oder HTTPS und bei noch deaktiviertem HTTPS die erkannten Zertifikatsquellen.",
+    "imageAlt": "Bereich Admin → Sicherheit → SSL / HTTPS mit deaktiviertem HTTPS, erkanntem Proxmox-Zertifikat (Inhaber, Aussteller, Ablauf) und der Option für ein eigenes Zertifikat",
+    "caption": "Bereich Admin → Sicherheit → SSL / HTTPS vor der Aktivierung. Monitor zeigt das von Proxmox verwendete Zertifikat und bietet es zur Auswahl mit einem Klick an."
   },
   "twoways": {
     "heading": "Zwei Möglichkeiten, HTTPS zu aktivieren",
@@ -117,7 +117,7 @@
   },
   "switchToHttps": {
     "heading": "Schalten Sie den Monitor auf HTTPS",
-    "bodyRich": "Sobald /etc/pve/local/pveproxy-ssl.pem von Let's Encrypt signiert ist, öffnen Sie Einstellungen → Sicherheit → HTTPS / SSL, prüfen als Aussteller Let's Encrypt statt der lokalen Proxmox-CA und wählen Proxmox-Zertifikat verwenden. Monitor startet neu und der nächste Aufruf nutzt HTTPS auf 8008 mit öffentlich vertrauenswürdiger Zertifikatskette. Spätere ACME-Erneuerungen werden bei der nächsten neuen TLS-Verbindung geprüft und ausgewählt, ohne regelmäßige Abfrage oder Dienstneustart zur Erneuerung. Zertifikat aktualisieren bleibt unter Sicherheit für Diagnose und Wiederherstellung verfügbar."
+    "bodyRich": "Sobald /etc/pve/local/pveproxy-ssl.pem von Let's Encrypt signiert ist, öffnen Sie Admin → Sicherheit → SSL / HTTPS, prüfen als Aussteller Let's Encrypt statt der lokalen Proxmox-CA und wählen Proxmox-Zertifikat verwenden. Monitor startet neu und der nächste Aufruf nutzt HTTPS auf 8008 mit öffentlich vertrauenswürdiger Zertifikatskette. Spätere ACME-Erneuerungen werden bei der nächsten neuen TLS-Verbindung geprüft und ausgewählt, ohne regelmäßige Abfrage oder Dienstneustart zur Erneuerung. Zertifikat aktualisieren bleibt unter Sicherheit für Diagnose und Wiederherstellung verfügbar."
   },
   "custom": {
     "heading": "Benutzerdefiniertes Zertifikat — wann es sinnvoll ist",
diff --git a/web/messages/de/docs/settings/beta-program.json b/web/messages/de/docs/settings/beta-program.json
index 3dca055c..51ef5ace 100644
--- a/web/messages/de/docs/settings/beta-program.json
+++ b/web/messages/de/docs/settings/beta-program.json
@@ -51,7 +51,7 @@
     "intro": "Beide Richtungen durchlaufen das gleiche Installationsprogramm – es handelt sich nicht um Deinstallations- und Neuinstallationsabläufe in Sonderfällen. Jede Richtung führt einfach das eigene Installationsprogramm des Kanals aus, das idempotent und sicher für die erneute Ausführung einer vorhandenen Installation konzipiert ist. Praktische Konsequenzen:",
     "items": [
       "Ihre vorhandene Konfiguration, ProxMenux Monitor-Login (auth.json), Benachrichtigungskanäle, Registrierung nach der Installation, benutzerdefinierte Schwellenwerte - keine davon wird berührt. Der Channel Switch tauscht nur den Skriptbaum und den binären AppImage aus.",
-      "Ein Monitor, der vor dem Switch aktiv war, bleibt danach aktiv; einer, der deaktiviert wurde, bleibt deaktiviert.",
+      "Der Installer startet den Monitor-Dienst während des Wechsels neu, sodass ein zuvor deaktivierter Monitor wieder läuft. Deaktivieren Sie ihn bei Bedarf erneut unter Einstellungen.",
       "ProxMenux Monitor auth.json, API-Token und das JWT-Geheimnis werden über Kanaländerungen hinweg bewahrt. Sitzungen werden nicht abgemeldet.",
       "Sie können Kanäle so oft umdrehen, wie Sie möchten - der Dialog wird es jedes Mal akzeptieren und das Installationsprogramm erneut ausführen. Es ist kein Neustart von Proxmox erforderlich.",
       "Der Kanalwechsel ersetzt den ProxMenux-Skriptbaum und kann daher nicht im Browser-Terminal von Monitor laufen: Die PTY würde während des Updates beendet und der Dialog unterbrochen. Dort erscheint stattdessen ein Informationsfenster mit dem offiziellen einzeiligen Installationsbefehl des Zielkanals. Führen Sie ihn per SSH oder in der Proxmox-Hostkonsole aus."
diff --git a/web/messages/de/docs/settings/index.json b/web/messages/de/docs/settings/index.json
index 5bee3e2b..036d4c79 100644
--- a/web/messages/de/docs/settings/index.json
+++ b/web/messages/de/docs/settings/index.json
@@ -60,6 +60,12 @@
         "description": "Wechseln Sie in einem Dialog in beide Richtungen zwischen Stable (main) und Beta (develop). Führt den offiziellen Installer des gewählten Kanals aus; idempotent und immer sichtbar.",
         "badge": "Immer verfügbar"
       },
+      {
+        "icon": "Languages",
+        "href": "/docs/settings/change-language",
+        "title": "Sprache ändern",
+        "description": "Wechselt die ProxMenux-Oberfläche zwischen acht Sprachen. Die Auswahl wird in config.json gespeichert und das Menü lädt in der neuen Sprache neu."
+      },
       {
         "icon": "Info",
         "href": "/docs/settings/show-version-information",
@@ -70,7 +76,7 @@
         "icon": "Trash2",
         "href": "/docs/settings/uninstall-proxmenux",
         "title": "Deinstallieren Sie ProxMenux",
-        "description": "Entfernen Sie ProxMenux und (optional) seine Abhängigkeiten. Außerdem werden /root/.bashrc- und /etc/motd-Backups wiederhergestellt, die zum Zeitpunkt der Installation erstellt wurden."
+        "description": "Entfernt ProxMenux und (optional) seine Abhängigkeiten. Stellt außerdem die von Post-Install erstellten Backups von /root/.bashrc und /etc/motd wieder her."
       }
     ]
   },
diff --git a/web/messages/de/docs/settings/proxmenux-monitor.json b/web/messages/de/docs/settings/proxmenux-monitor.json
index f22df8b0..fc730419 100644
--- a/web/messages/de/docs/settings/proxmenux-monitor.json
+++ b/web/messages/de/docs/settings/proxmenux-monitor.json
@@ -33,7 +33,7 @@
   },
   "warnConditional": {
     "title": "Einstellungen Optionen nur sichtbar, wenn Monitor installiert ist",
-    "body": "Die beiden unten beschriebenen Menüeinträge erscheinen nur in den Einstellungen, wenn proxmenux-monitor.service bei systemd registriert ist. Wenn Sie sie nicht sehen, wurde der Monitor nicht installiert (er ist während der ProxMenux-Installation optional). Um es später hinzuzufügen, installieren Sie ProxMenux mit der neuesten Version neu und wählen Sie die Option Monitor aus."
+    "body": "Die drei unten beschriebenen Menüeinträge erscheinen nur in den Einstellungen, wenn proxmenux-monitor.service bei systemd registriert ist. Der Installer richtet ihn immer ein; wenn Sie sie nicht sehen, ist der Monitor-Schritt bei der Installation fehlgeschlagen. Führen Sie den offiziellen Installer erneut aus, um ihn zu wiederholen."
   },
   "toggle": {
     "heading": "Aktivieren/Deaktivieren",
@@ -92,8 +92,8 @@
   },
   "troubleshoot": {
     "heading": "Fehlerbehebung",
-    "missingTitle": "Beide Menüoptionen fehlen",
-    "missingBody": "Der Monitor ist nicht bei systemd registriert. Bestätigen Sie mit systemctl list-unit-files | grep proxmenux-monitor - wenn keine Ausgabe vorhanden ist, installieren Sie ProxMenux mit der neuesten Version erneut vom offiziellen Installer und wählen Sie während der Installation die Option Monitor aus.",
+    "missingTitle": "Die Monitor-Menüoptionen fehlen",
+    "missingBody": "Der Monitor ist nicht bei systemd registriert. Bestätigen Sie mit systemctl list-unit-files | grep proxmenux-monitor - wenn keine Ausgabe vorhanden ist, führen Sie den offiziellen Installer erneut aus; er installiert den Monitor bei jedem Durchlauf.",
     "unreachableTitle": "Service aktiviert, aber die URL ist nicht erreichbar",
     "unreachableBody": "Die Einstellungsstatusausgabe gibt Ihnen die IP. Bestätigung:",
     "unreachableCmd": "ss -tlnp | grep 8008                # is anything listening?\njournalctl -u proxmenux-monitor -n 100 --no-pager  # recent errors\ncurl -sI http://localhost:8008      # local reachability",
diff --git a/web/messages/de/docs/settings/uninstall-proxmenux.json b/web/messages/de/docs/settings/uninstall-proxmenux.json
index 3abbcb94..d195d328 100644
--- a/web/messages/de/docs/settings/uninstall-proxmenux.json
+++ b/web/messages/de/docs/settings/uninstall-proxmenux.json
@@ -12,7 +12,7 @@
   },
   "scopeWarn": {
     "title": "Beeinflusst nur die ProxMenux-Installation - nicht Ihre VMs / CTs / Speicher",
-    "body": "Die Deinstallation entfernt die ProxMenux-Skripte, den Menüstarter, den optionalen Monitor-Dienst und die von ProxMenux bei der Installation angelegten Sicherungen von Systemdateien. VMs, Container, Speicher, Netzwerkkonfiguration, Fail2Ban, NVIDIA-Treiber und andere über ProxMenux installierte Komponenten bleiben unberührt. Sie bleiben in ihrem bisherigen Zustand."
+    "body": "Die Deinstallation entfernt die ProxMenux-Skripte, den Menüstarter, den Monitor-Dienst und stellt die von Post-Install erstellten Sicherungen von /root/.bashrc und /etc/motd wieder her, sofern sie existieren. VMs, Container, Speicher, Netzwerkkonfiguration, Fail2Ban, NVIDIA-Treiber und andere über ProxMenux installierte Komponenten bleiben unberührt. Sie bleiben in ihrem bisherigen Zustand."
   },
   "flow": {
     "heading": "Ablauf der Deinstallation",
diff --git a/web/messages/de/docs/utils/export-vm.json b/web/messages/de/docs/utils/export-vm.json
index a94b427e..3f258bc4 100644
--- a/web/messages/de/docs/utils/export-vm.json
+++ b/web/messages/de/docs/utils/export-vm.json
@@ -59,8 +59,8 @@
   },
   "package": {
     "heading": "Was im Paket landet",
-    "intro": "Für eine OVA / OVF namens myvm-100-2026-04-26_18-30-15:",
-    "code": "# OVA mode (single .ova file is a TAR of):\nmyvm-100-2026-04-26_18-30-15.ovf      # XML descriptor (DMTF OVF 1.0 + RASD + VSSD)\nmyvm-100-2026-04-26_18-30-15.mf       # SHA1 manifest\nmyvm-100-2026-04-26_18-30-15-disk0.vmdk\nmyvm-100-2026-04-26_18-30-15-disk1.vmdk\n...\n\n# OVF mode (a directory):\nmyvm-100-2026-04-26_18-30-15-ovf/\n├── myvm-100-2026-04-26_18-30-15.ovf\n├── myvm-100-2026-04-26_18-30-15.mf\n├── myvm-100-2026-04-26_18-30-15-disk0.vmdk\n└── myvm-100-2026-04-26_18-30-15-disk1.vmdk"
+    "intro": "Für eine OVA / OVF namens myvm-100-20260426_183015:",
+    "code": "# OVA mode (single .ova file is a TAR of):\nmyvm-100-20260426_183015.ovf      # XML descriptor (DMTF OVF 1.0 + RASD + VSSD)\nmyvm-100-20260426_183015.mf       # SHA1 manifest\nmyvm-100-20260426_183015-disk1.vmdk\nmyvm-100-20260426_183015-disk2.vmdk\n...\n\n# OVF mode (a directory):\nmyvm-100-20260426_183015-ovf/\n├── myvm-100-20260426_183015.ovf\n├── myvm-100-20260426_183015.mf\n├── myvm-100-20260426_183015-disk1.vmdk\n└── myvm-100-20260426_183015-disk2.vmdk"
   },
   "exported": {
     "heading": "Was exportiert wird",
@@ -90,7 +90,7 @@
   "manifest": {
     "heading": "SHA1-Manifest",
     "intro": "Jedes Paket enthält eine .mf-Datei mit SHA1-Hashes des OVF-Deskriptors und jedes VMDK:",
-    "code": "SHA1(myvm-100-2026-04-26_18-30-15.ovf)= 3a7bd3e2360a3d29eea436fcfb7e44c735d117c9\nSHA1(myvm-100-2026-04-26_18-30-15-disk0.vmdk)= b7e23ec29af22b0b4e41da31e868d57226121c84\nSHA1(myvm-100-2026-04-26_18-30-15-disk1.vmdk)= a87ff679a2f3e71d9181a67b7542122c",
+    "code": "SHA1(myvm-100-20260426_183015.ovf)= 3a7bd3e2360a3d29eea436fcfb7e44c735d117c9\nSHA1(myvm-100-20260426_183015-disk1.vmdk)= b7e23ec29af22b0b4e41da31e868d57226121c84\nSHA1(myvm-100-20260426_183015-disk2.vmdk)= a87ff679a2f3e71d9181a67b7542122c",
     "outro": "In der OVF-Spezifikation ist das Manifest optional; viele Importprogramme ignorieren es. Es hilft jedoch zu prüfen, ob das Paket unbeschädigt übertragen wurde. Prüfen Sie es manuell mit sha1sum -c file.mf."
   },
   "import": {
diff --git a/web/messages/de/docs/utils/index.json b/web/messages/de/docs/utils/index.json
index b0b129ef..f3c7ed76 100644
--- a/web/messages/de/docs/utils/index.json
+++ b/web/messages/de/docs/utils/index.json
@@ -38,7 +38,7 @@
       "body": "Halten Sie den Host in gutem Zustand: mit CLI-Werkzeugen, regelmäßigen Aktualisierungen und dem Versionsupgrade, wenn es ansteht.",
       "bullets": [
         "Installationsprogramm für Systemwerkzeuge (25 CLI-Werkzeuge)",
-        "Proxmox-Systemaktualisierung (apt + dist-upgrade)",
+        "Proxmox-Systemaktualisierung (apt + full-upgrade)",
         "Versionsupgrade PVE 8 → PVE 9"
       ]
     },
@@ -58,11 +58,11 @@
   },
   "isoSection": {
     "heading": "ISO-Erstellung",
-    "body": "Erstellen Sie direkt auf dem Proxmox-Host eine neue, aktuelle Windows-Installations-ISO. Das Skript automatisiert den gesamten UUP-Dump-Ablauf: Installation der Abhängigkeiten (aria2c, cabextract, wimlib-imagex, …), paralleler Download der Aktualisierungsdateien mit aria2 und Umwandlung in eine bootfähige ISO. Diese wird unter /var/lib/vz/template/iso gespeichert und kann einer VM zugewiesen werden.",
+    "body": "Erstellen Sie direkt auf dem Proxmox-Host eine neue, aktuelle Windows-Installations-ISO. Das Skript automatisiert den gesamten UUP-Dump-Ablauf: Installation der Abhängigkeiten (aria2c, cabextract, wimlib-imagex, …), paralleler Download der Aktualisierungsdateien mit aria2 und Umwandlung in eine bootfähige ISO. Diese wird im erkannten Proxmox-ISO-Speicher gespeichert (Ausweichziel /var/lib/vz/template/iso) und kann einer VM zugewiesen werden.",
     "options": [
       {
         "title": "UUP-Dump-ISO-Erstellung",
-        "description": "Erstellen Sie eine aktuelle Windows-Installations-ISO über einen Freigabelink von UUP Dump. Abhängigkeiten, Download und Umwandlung werden vollständig automatisch abgewickelt. Die fertige ISO wird unter /var/lib/vz/template/iso für die VM-Erstellung gespeichert.",
+        "description": "Erstellen Sie eine aktuelle Windows-Installations-ISO über einen Freigabelink von UUP Dump. Abhängigkeiten, Download und Umwandlung werden vollständig automatisch abgewickelt. Die fertige ISO wird im erkannten Proxmox-ISO-Speicher für die VM-Erstellung gespeichert.",
         "href": "/docs/utils/UUp-Dump-ISO-Creator"
       }
     ]
@@ -78,7 +78,7 @@
       },
       {
         "title": "Proxmox-Systemupdate",
-        "description": "Repository-Prüfung + apt update + apt dist-upgrade mit Rückfrage vor dem Neustart. Erkennt die PVE-Hauptversion (8 oder 9), behält die konfigurierten Repositories bei und fragt nach, bevor ein Host ohne Abonnement auf das no-subscription-Repository umgestellt wird. Anschließend wird autoremove ausgeführt.",
+        "description": "Repository-Prüfung + apt update + apt full-upgrade mit Rückfrage vor dem Neustart. Erkennt die PVE-Hauptversion (8 oder 9), behält die konfigurierten Repositories bei und fragt nach, bevor ein Host ohne Abonnement auf das no-subscription-Repository umgestellt wird. Anschließend wird autoremove ausgeführt.",
         "href": "/docs/utils/system-update"
       },
       {
diff --git a/web/messages/de/docs/utils/system-update.json b/web/messages/de/docs/utils/system-update.json
index f9544cfc..f3501316 100644
--- a/web/messages/de/docs/utils/system-update.json
+++ b/web/messages/de/docs/utils/system-update.json
@@ -128,7 +128,7 @@
         "body": "Das Enterprise-Repository ist aktiviert, aber der Host hat kein aktives Abonnement. Das Arbeitsskript bietet vor apt-get update die Umstellung auf no-subscription an. Wurde sie abgelehnt, starten Sie die Aktualisierung erneut und stimmen Sie zu. Alternativ deaktivieren Sie Enterprise unter Node → Updates → Repositories und aktivieren no-subscription."
       },
       {
-        "title": "dist-upgrade bleibt bei \"Configuring grub-pc\" hängen",
+        "title": "full-upgrade bleibt bei \"Configuring grub-pc\" hängen",
         "body": "Falls eine interaktive GRUB-Abfrage erscheint, fragt sie nach den Geräten für den Bootloader. --force-confold gilt für Entscheidungen zu Konfigurationsdateien, nicht für diese Geräteauswahl. Wählen Sie mit Tab + Space die passenden Startfestplatten und dann OK. Die Auswahl lässt sich vorher mit dpkg-reconfigure grub-pc konfigurieren."
       },
       {
diff --git a/web/messages/de/docs/utils/system-utils.json b/web/messages/de/docs/utils/system-utils.json
index d250de76..03b19b77 100644
--- a/web/messages/de/docs/utils/system-utils.json
+++ b/web/messages/de/docs/utils/system-utils.json
@@ -235,7 +235,7 @@
       {
         "href": "/docs/utils/system-update",
         "label": "Proxmox-Systemupdate",
-        "tailRich": "— für den eigentlichen apt dist-upgrade auf dem Host."
+        "tailRich": " — für den eigentlichen apt full-upgrade auf dem Host."
       },
       {
         "href": "/docs/network/monitoring",
diff --git a/web/messages/de/docs/utils/upgrade-pve8-pve9.json b/web/messages/de/docs/utils/upgrade-pve8-pve9.json
index f1e2a324..5f903bba 100644
--- a/web/messages/de/docs/utils/upgrade-pve8-pve9.json
+++ b/web/messages/de/docs/utils/upgrade-pve8-pve9.json
@@ -16,9 +16,9 @@
     "items": [
       "Geprüfte Sicherungen aller VMs und CTs (PBS, vzdump, ZFS-Snapshots — auch die Wiederherstellung prüfen).",
       "Konsolen- / IPMI- / iKVM-Zugang zum Host, falls SSH während des Upgrades ausfällt.",
-      "Der Host ist bereits auf PVE 8.4.x auf dem neuesten Stand (das Skript erzwingt dies).",
+      "Der Host befindet sich auf dem neuesten PVE-8-Patchstand. Das Skript aktualisiert zuerst PVE 8; es prüft nicht auf eine bestimmte Version 8.4.x.",
       "Ceph (falls vorhanden) auf der Version 19.x Squid.",
-      "Mindestens 5 GB freier Speicher im Root-Dateisystem."
+      "Mindestens 5 GB freier Speicher im Root-Dateisystem, wie es der Proxmox-Leitfaden empfiehlt. Das Skript selbst prüft nur 1 GB in /var/cache/apt/archives."
     ]
   },
   "modeMenu": {
@@ -63,13 +63,13 @@
   },
   "webTerminal": {
     "heading": "Warum das Web-Terminal blockiert ist",
-    "body": "Alle drei Upgrade-Modi erkennen termproxy / vncshell in der Kette übergeordneter Prozesse und verweigern den Start. Das Proxmox-Webterminal wird von pveproxy bereitgestellt, einem Dienst, der beim Upgrade aktualisiert wird. Sobald pveproxy neu startet, endet die Terminalsitzung und damit auch das darin laufende Upgrade-Skript. Der Host kann unvollständig aktualisiert zurückbleiben.",
+    "body": "Die Modi Automatisch und Interaktiv erkennen termproxy / vncshell in der Kette übergeordneter Prozesse und verweigern den Start, es sei denn, die Sitzung läuft über SSH oder PVE_ALLOW_WEBTERM=1 ist gesetzt. Die Vorabprüfung und die manuelle Anleitung ändern nichts und laufen überall. Das Proxmox-Webterminal wird von pveproxy bereitgestellt, einem Dienst, der beim Upgrade aktualisiert wird. Sobald pveproxy neu startet, endet die Terminalsitzung und damit auch das darin laufende Upgrade-Skript. Der Host kann unvollständig aktualisiert zurückbleiben, ohne gute Möglichkeit zur Fortsetzung.",
     "warningTitle": "SSH oder eine physische / IPMI- / iKVM-Konsole verwenden",
-    "warningBody": "SSH ist die übliche Wahl. Starten Sie die Sitzung in tmux oder screen, damit das Upgrade kurze Netzunterbrechungen übersteht. Erkennt das Skript eine einfache SSH-Shell, zeigt es zu Beginn einen Hinweis auf tmux/screen an."
+    "warningBody": "SSH ist die übliche Wahl. Starten Sie die Sitzung in tmux oder screen, damit das Upgrade kurze Netzunterbrechungen übersteht. Der Bestätigungsdialog des Upgrades erinnert Sie daran, es von der Konsole oder per SSH in tmux/screen auszuführen."
   },
   "auto": {
     "heading": "Automatischer / unbeaufsichtigter Modus",
-    "intro": "Rückfragen zu Paketen und Konfigurationsdateien verwenden die Standardwerte aus der folgenden Tabelle, und ein 401 vom Enterprise-Repository wechselt ohne Rückfrage zu no-subscription. Die Upgrade-Bestätigung, die Zusammenfassung des PVE-8-Updates, Reparaturen der Vorabprüfung und die Neustart-Rückfrage werden weiterhin angezeigt.",
+    "intro": "Rückfragen zu Paketen und Konfigurationsdateien verwenden die Standardwerte aus der folgenden Tabelle. Die Upgrade-Bestätigung, die Zusammenfassung des PVE-8-Updates, Reparaturen der Vorabprüfung und die Neustart-Rückfrage werden weiterhin angezeigt.",
     "behaviourHeading": "Besonderheiten des automatischen Modus",
     "behaviourTable": {
       "settingHeader": "Einstellung",
@@ -94,8 +94,8 @@
       ]
     },
     "flowHeading": "Ablauf im automatischen Modus",
-    "flowIntro": "Sechs aufeinanderfolgende Phasen. Phase 2 aktualisiert die PVE-8-Pakete, Phase 3 schreibt die Repository-Konfiguration für PVE 9. Ein Fehler hält den Ablauf an, macht diese Änderungen aber nicht rückgängig; schlägt das PVE-8-Update fehl oder wird es abgebrochen, bleiben die Repositories unverändert. Phase 5 führt dist-upgrade auf PVE 9 aus.",
-    "flowDiagram": "1. PRÜFUNGEN\ntermproxy / vncshell blockieren; Upgrade bestätigen; PVE 9 ablehnen.\n\n2. PVE 8 VORBEREITEN\nscripts/global/update-pve8.sh\nPVE-8-Pakete aktualisieren; Abbruch, wenn das Update fehlschlägt oder abgebrochen wird.\npve8to9 --full (kann Reparaturen anbieten).\nSpeicher und Verbindung prüfen; Ceph 19.x Squid auf hyperkonvergenten Knoten.\n\n3. REPOSITORIES UMSTELLEN\nBookworm → Trixie; deb822-Dateien .sources schreiben.\nAlte .list-Dateien auskommentieren; apt-get update.\n\n4. PVE-9-KANDIDAT PRÜFEN\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nAbbruch, wenn der Kandidat nicht 9.x ist oder proxmox-ve entfernt würde.\n\n5. UPGRADE\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nOhne Subskription: pve-enterprise.sources deaktivieren.\nProtokoll: /var/log/pve8-a-pve9-.log\n\n6. PRÜFEN UND NEU STARTEN\nEFI: apt install grub-efi-amd64\npve8to9 --full; Ergebnis und Neustart-Rückfrage prüfen.",
+    "flowIntro": "Sechs aufeinanderfolgende Phasen. Phase 2 stellt die PVE-8-Repositories auf no-subscription um (Enterprise- und Ceph-Listen deaktiviert, /etc/apt/sources.list neu geschrieben), bevor sie die Update-Zusammenfassung anzeigt, und aktualisiert dann die Pakete; Phase 3 schreibt die Repository-Konfiguration für PVE 9. Ein Fehler hält den Ablauf an, macht diese Änderungen aber nicht rückgängig; schlägt das PVE-8-Update fehl oder wird es abgebrochen, startet die PVE-9-Migration nicht, die Änderungen an den PVE-8-Repositories bleiben aber bestehen. Phase 5 führt dist-upgrade auf PVE 9 aus.",
+    "flowDiagram": "1. PRÜFUNGEN\ntermproxy / vncshell blockieren; Upgrade bestätigen; PVE 9 ablehnen.\n\n2. PVE 8 VORBEREITEN\nscripts/global/update-pve8.sh\nPVE-8-Repositories auf no-subscription umstellen und sources.list neu schreiben;\nPVE-8-Pakete aktualisieren; Abbruch, wenn das Update fehlschlägt oder abgebrochen wird.\npve8to9 --full (kann Reparaturen anbieten).\nSpeicher und Verbindung prüfen; Ceph 19.x Squid auf hyperkonvergenten Knoten.\n\n3. REPOSITORIES UMSTELLEN\nBookworm → Trixie; deb822-Dateien .sources schreiben.\nAlte .list-Dateien auskommentieren; apt-get update.\n\n4. PVE-9-KANDIDAT PRÜFEN\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nAbbruch, wenn der Kandidat nicht 9.x ist oder proxmox-ve entfernt würde.\n\n5. UPGRADE\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nOhne Subskription: pve-enterprise.sources deaktivieren.\nProtokoll: /var/log/pve8-a-pve9-.log\n\n6. PRÜFEN UND NEU STARTEN\nEFI: apt install grub-efi-amd64\npve8to9 --full; Ergebnis und Neustart-Rückfrage prüfen.",
     "preflightHeading": "Vor dem Upgrade: diese Punkte werden geprüft",
     "preflightTable": {
       "checkHeader": "Prüfung",
@@ -158,7 +158,7 @@
   },
   "manual": {
     "heading": "Manuelles Upgrade-Verfahren",
-    "intro": "Dies zeigt die Menüoption Manuelle Upgrade-Anleitung: die offizielle Proxmox-Anleitung für das Upgrade von 8 auf 9, in vier Phasen gegliedert. Das Skript zeigt die Befehle; Sie führen sie selbst in einer separaten Root-Shell aus. Volle Einsicht in jeden Schritt.",
+    "intro": "Dies folgt der Anzeige der Menüoption Manuelle Upgrade-Anleitung, basierend auf der offiziellen Proxmox-Anleitung für das Upgrade von 8 auf 9 und in vier Phasen gegliedert. Die dpkg-Antworttabelle ergänzt die Hinweise des Wikis zu sshd_config und chrony.conf, die das Menü als einfaches Y anzeigt. Das Skript zeigt die Befehle; Sie führen sie selbst in einer separaten Root-Shell aus. Keine Automatisierung, volle Einsicht in jeden Schritt.",
     "rootCalloutTitle": "Alle Befehle brauchen root",
     "rootCalloutBody": "Verwenden Sie eine Root-Shell, auch für Umleitungen und Here-Dokumente. Bei sudo muss der gesamte Befehl mit Root-Rechten ausgeführt werden.",
     "phase1Heading": "Phase 1 — Vorbereitung",
@@ -230,7 +230,7 @@
   "clusterCeph": {
     "heading": "Cluster & Ceph",
     "clusterCalloutTitle": "Cluster: Upgrade-Knoten nacheinander",
-    "clusterCalloutBody": "Erkannt über pvecm status. Nicht mehrere Knoten gleichzeitig aktualisieren. Gäste zuerst verschieben, den Knoten aktualisieren, neu starten und die Cluster-Mitgliedschaft prüfen. Erst dann fortfahren. Während des Upgrades funktioniert Live-Migration von älteren zu neueren PVE-Versionen; die Gegenrichtung wird grundsätzlich nicht unterstützt. HA-Gruppen werden automatisch in HA-Regeln umgewandelt, sobald alle Knoten PVE 9 verwenden.",
+    "clusterCalloutBody": "Nicht mehrere Knoten gleichzeitig aktualisieren. Gäste zuerst verschieben, den Knoten aktualisieren, neu starten und die Cluster-Mitgliedschaft prüfen. Erst dann fortfahren. Während des Upgrades funktioniert Live-Migration von älteren zu neueren PVE-Versionen; die Gegenrichtung wird grundsätzlich nicht unterstützt. HA-Gruppen werden automatisch in HA-Regeln umgewandelt, sobald alle Knoten PVE 9 verwenden.",
     "cephCalloutTitle": "Ceph: ZUERST Ceph, danach Proxmox aktualisieren",
     "cephCalloutBody": "Ceph muss vor dem Proxmox-Upgrade bereits 19.x Squid verwenden. Folgen Sie der offiziellen Reihenfolge Quincy 17 → Reef 18 → Squid 19: Pakete aktualisieren und Ceph-Daemons schrittweise in der dokumentierten Reihenfolge neu starten. Vor dem Upgrade jedes Proxmox-Knotens ceph --version prüfen. Vollständiges Verfahren: Wiki zum Upgrade von Ceph Reef auf Squid."
   },
@@ -273,7 +273,7 @@
   },
   "files": {
     "heading": "Beteiligte Dateien",
-    "code": "scripts/utilities/upgrade_pve8_to_pve9.sh                         # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh                                # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh    # 17-step runbook\nscripts/global/update-pve8.sh                                     # PVE 8 worker (final patches)\n/etc/apt/sources.list                                             # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources                           # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources                            # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources                              # created if Ceph\n/etc/apt/sources.list.d/*.list                                    # legacy files commented out / removed\n/var/log/pve8-a-pve9-.log                              # full upgrade log"
+    "code": "scripts/utilities/upgrade_pve8_to_pve9.sh                         # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh                                # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh    # 17-step runbook\nscripts/global/update-pve8.sh                                     # PVE 8 worker (final patches)\n/etc/apt/sources.list                                             # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources                           # created (deb822 PVE 9)\n/etc/apt/sources.list.d/ceph.sources                              # created if Ceph\n/etc/apt/sources.list.d/*.list                                    # legacy files commented out / removed\n/var/log/pve8-a-pve9-.log                              # full upgrade log"
   },
   "references": {
     "heading": "Offizielle Quellen",
diff --git a/web/messages/en/docs/backup-restore/creating-backups.json b/web/messages/en/docs/backup-restore/creating-backups.json
index b2a17ef5..1e4bebf9 100644
--- a/web/messages/en/docs/backup-restore/creating-backups.json
+++ b/web/messages/en/docs/backup-restore/creating-backups.json
@@ -21,8 +21,8 @@
     "rows": [
       {
         "entry": "ProxMenux Scripts (TUI)",
-        "path": "menu → Utilities → Host Config Backup",
-        "detail": "Dialog-based flow, SSH-friendly. The main menu presents the six options directly. Uses backup_menu in backup_host.sh."
+        "path": "menu → Host Backup & Restore → Backup host configuration",
+        "detail": "Dialog-based flow, SSH-friendly. The main menu entry opens Host Config Backup / Restore; Backup host configuration leads to the six options. Uses backup_menu in backup_host.sh."
       },
       {
         "entry": "ProxMenux Monitor (Web UI)",
@@ -83,7 +83,7 @@
   "profiles": {
     "heading": "Default vs Custom profile",
     "defaultTitle": "Default profile",
-    "defaultBody": "The default profile is the curated list from hb_default_profile_paths (documented in How it works under Path categories) plus every entry in the persistent extras file /usr/local/share/proxmenux/backup-extra-paths.txt. The user confirms the destination and encryption options and the backup proceeds without further path selection.",
+    "defaultBody": "The default profile is the curated list from hb_default_profile_paths (documented in How it works under Path categories) plus every entry in the persistent extras file /usr/local/share/proxmenux/backup-extra-paths.txt. The user picks the destination and encryption options and the backup proceeds without further path selection.",
     "customTitle": "Custom profile",
     "customBody": "The custom profile opens a checklist showing every path in the default profile (unchecked) and every persistent extra (pre-checked, prefixed with [+]). The user ticks the set for this run and can press Add custom path to append a new absolute path. Any path added inline is persisted to backup-extra-paths.txt so future backups pick it up automatically without re-adding it. Removing a persistent extra unticks it for this run but does not delete it from the file — deletion is a separate Manage custom paths action outside the backup flow.",
     "customPickerAlt": "Custom profile checklist showing the default-profile paths (unchecked) and persistent extras (pre-checked with a [+] prefix), plus buttons to add a new path or confirm the selection.",
@@ -124,14 +124,7 @@
   },
   "included": {
     "heading": "What goes in and what stays out",
-    "intro": "Every path in the resolved profile (default + persistent extras + custom-mode selection) is copied with rsync -aAXH --numeric-ids. A shared exclusion list applies to every path, and two directories carry additional path-specific exclusions.",
-    "globalTitle": "Global exclusions (applied to every path)",
-    "globalItems": [
-      "images/ — image dumps.",
-      "dump/ — vzdump outputs.",
-      "tmp/ — temporary files.",
-      "*.log — log files."
-    ],
+    "intro": "Every path in the resolved profile (default + persistent extras + custom-mode selection) is copied with rsync -aAXH --numeric-ids. There is no global exclusion list; a few directories carry path-specific exclusions.",
     "rootTitle": "/root/ exclusions",
     "rootBody": "/root/ is part of the default profile so user scripts and config land in the archive. Volatile subpaths are dropped:",
     "rootItems": [
@@ -160,16 +153,16 @@
       "/home/USER — not in the default profile. Add it as a custom path when a system carries user home directories that must survive a restore."
     ],
     "customPathsTitle": "How custom paths are handled",
-    "customPathsBody": "A custom path added inline in Custom mode or persisted in backup-extra-paths.txt goes through the same rsync pipeline as default-profile paths. Global exclusions apply. If the custom path happens to be under /root/ or /usr/local/share/proxmenux/, the path-specific exclusions above still apply. Every archived path — default or custom — is recorded in metadata/paths_archived.txt. Paths that do not exist on the source are recorded in metadata/missing_paths.txt without stopping the backup."
+    "customPathsBody": "A custom path added inline in Custom mode or persisted in backup-extra-paths.txt goes through the same rsync pipeline as default-profile paths. If the custom path happens to be under /root/ or /usr/local/share/proxmenux/, the path-specific exclusions above still apply. Every archived path — default or custom — is recorded in metadata/selected_paths.txt. Paths that do not exist on the source are recorded in metadata/missing_paths.txt without stopping the backup, and paths that fail to copy in metadata/failed_paths.txt."
   },
   "archiveStructure": {
     "heading": "Archive structure",
     "intro": "The staging directory produced by every backend follows the same layout regardless of destination. The tarball, PBS .pxar or Borg archive stores this tree verbatim.",
-    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state (kernel_params, hardware, storage, guests, components, source_host)\n├── metadata/\n│   ├── packages.manual.list       # output of apt-mark showmanual\n│   ├── run_info.env               # hostname, timestamp, kernel version, pmxcfs method\n│   ├── paths_archived.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, /etc/apt, ...\n    ├── root/                      # /root without volatile subpaths\n    ├── usr/local/                 # /usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux (state only)\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/crontabs"
+    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state (kernel_params, hardware, storage, guests, components, source_host)\n├── metadata/\n│   ├── packages.manual.list       # output of apt-mark showmanual\n│   ├── run_info.env               # hostname, timestamp, kernel version, pmxcfs method\n│   ├── selected_paths.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, /etc/apt, ...\n    ├── root/                      # /root without volatile subpaths\n    ├── usr/local/                 # /usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux (state only)\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/crontabs"
   },
   "confirmation": {
     "heading": "Confirmation summary",
-    "body": "Before the backend writes anything to the destination, ProxMenux shows a summary dialog with the destination, backup ID or archive name, encryption state, and the list of paths being copied. Cancelling here aborts the backup cleanly — the staging directory is removed by the trap hook set on the backend function and no partial data reaches the destination."
+    "body": "Before the backend writes anything to the destination, ProxMenux prints a summary in the terminal with the destination, backup ID or archive name, encryption state, and the list of paths being copied. There is no confirm or cancel step at this point; the copy starts right after. If the backup fails or is interrupted, the staging directory is removed by the trap hook set on the backend function and no partial data reaches the destination."
   },
   "writing": {
     "heading": "Writing to the destination",
diff --git a/web/messages/en/docs/backup-restore/destinations/local.json b/web/messages/en/docs/backup-restore/destinations/local.json
index be7c0fb8..0b57eefa 100644
--- a/web/messages/en/docs/backup-restore/destinations/local.json
+++ b/web/messages/en/docs/backup-restore/destinations/local.json
@@ -18,11 +18,11 @@
   },
   "targetConfig": {
     "heading": "Configuring the target directory",
-    "intro": "The local destination is a single persisted target directory — not a list. ProxMenux stores the user's choice at /usr/local/share/proxmenux/local-target.conf and reads it on every backup. When no target has been configured, the default HB_LOCAL_TARGET_DEFAULT = /var/lib/vz/dump is used (the same directory Proxmox uses for its own vzdump outputs). The target is configured from Configure backup destinations → Local destinations:",
+    "intro": "The local destination is a single persisted target directory — not a list. ProxMenux stores the user's choice at /usr/local/share/proxmenux/local-target.conf and reads it on every backup. When no target has been configured, the default HB_LOCAL_TARGET_DEFAULT = /var/lib/vz/dump is used (the same directory Proxmox uses for its own vzdump outputs). The target is configured from Configure backup destinations → Local archive targets → Manage local backup target:",
     "options": [
       "Use default (/var/lib/vz/dump). The Proxmox local storage. Present on every Proxmox install; the archive sits next to vzdump outputs and is picked up by the Monitor's Backups tab automatically.",
-      "Enter a custom path. Any absolute filesystem path can be used: an NFS mount, an SMB share mounted via fstab, a dedicated ZFS dataset, a second internal disk. ProxMenux validates that the path exists and is a directory before persisting it.",
-      "Pick a USB drive. Opens the USB submenu (below), which detects removable devices and offers to mount or format one."
+      "Use a custom path. Any absolute filesystem path can be used: an NFS mount, an SMB share mounted via fstab, a dedicated ZFS dataset, a second internal disk. ProxMenux validates that the path exists and is a directory before persisting it.",
+      "Use a USB disk. Opens the USB submenu (below), which detects removable devices and offers to mount or format one."
     ]
   },
   "usbFlow": {
@@ -38,7 +38,7 @@
       {
         "state": "unmounted",
         "shown": "Size · label · [fstype] · (not mounted — will be mounted)",
-        "action": "A filesystem is present but not mounted. On confirmation, ProxMenux runs hb_mount_usb_partition: creates /mnt/backup-LABEL (or a UUID-based path if there is no label), mounts the partition and persists the mount point as the local target."
+        "action": "A filesystem is present but not mounted. On confirmation, ProxMenux runs hb_mount_usb_partition: creates /mnt/proxmenux-backup-LABEL (UUID or device name if there is no label), mounts the partition and persists the mount point as the local target."
       },
       {
         "state": "empty",
@@ -63,7 +63,7 @@
   },
   "sidecar": {
     "heading": "The sidecar JSON",
-    "intro": "Every successful local backup produces a companion file: HOSTNAME-TIMESTAMP.tar.zst.proxmenux.json, written next to the archive by hb_write_archive_sidecar. This small JSON file lets the ProxMenux Monitor identify the archive as a ProxMenux host backup even if it is later moved, renamed or archived elsewhere.",
+    "intro": "Every successful local backup produces a companion file: hostcfg-HOSTNAME-TIMESTAMP.tar.zst.proxmenux.json, written next to the archive by hb_write_archive_sidecar. This small JSON file lets the ProxMenux Monitor identify the archive as a ProxMenux host backup even if it is later moved, renamed or archived elsewhere.",
     "contentTitle": "Sidecar contents",
     "contentBody": "The sidecar stores the schema version, whether the backup came from an interactive run or a scheduled job (kind), the job ID for scheduled runs, the profile mode used (default or custom), the source hostname, the original archive basename, an ISO-8601 creation timestamp and the archive size in bytes.",
     "whyBody": "The Monitor's Backups tab scans configured local directories for *.proxmenux.json sidecars — not for *.tar.zst files — because a .tar.zst without a sidecar might not be a ProxMenux backup at all. The scan is fast (JSON files are tiny) and the pairing is stable across renames of the archive as long as the sidecar is renamed to match."
diff --git a/web/messages/en/docs/backup-restore/destinations/pbs.json b/web/messages/en/docs/backup-restore/destinations/pbs.json
index 462436b3..8e7a4fd1 100644
--- a/web/messages/en/docs/backup-restore/destinations/pbs.json
+++ b/web/messages/en/docs/backup-restore/destinations/pbs.json
@@ -33,7 +33,7 @@
       {
         "source": "ProxMenux manual config",
         "path": "/usr/local/share/proxmenux/pbs-manual-configs.txt + pbs-pass-NAME.txt + pbs-fingerprint-NAME.txt",
-        "content": "Added from Configure backup destinations → PBS destinations → Add PBS. Prompts for a name, username (root@pam or user@pbs!token), host or IP, datastore and password. The password re-prompts on empty input — an empty save would otherwise persist silently and every subsequent backup would fail with an opaque authentication error. This path is used when the target PBS is not registered as Proxmox storage."
+        "content": "Added from Configure backup destinations → Proxmox Backup Server (PBS) destinations → + Add new PBS manually. Prompts for a name, username (root@pam or user@pbs!token), host or IP, datastore and password. The password re-prompts on empty input — an empty save would otherwise persist silently and every subsequent backup would fail with an opaque authentication error. This path is used when the target PBS is not registered as Proxmox storage."
       }
     ],
     "menuTitle": "Selection menu",
diff --git a/web/messages/en/docs/backup-restore/how-it-works.json b/web/messages/en/docs/backup-restore/how-it-works.json
index 450a8e11..e2864115 100644
--- a/web/messages/en/docs/backup-restore/how-it-works.json
+++ b/web/messages/en/docs/backup-restore/how-it-works.json
@@ -20,7 +20,7 @@
     "heading": "Archive layout",
     "intro": "Every archive follows the same tree layout regardless of destination. The metadata/ subdirectory holds the structured payloads; the rootfs/ subdirectory holds the filesystem copy.",
     "treeCaption": "The staging directory laid out during a backup. Every destination receives the same tree (adapted to its native format: tar for local, PBS chunks for PBS, borg segments for Borg).",
-    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state\n├── metadata/\n│   ├── packages.manual.list       # apt-mark showmanual\n│   ├── run_info.env               # backup run identity + kernel version + pmxcfs method\n│   ├── paths_archived.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, …\n    ├── root/                      # /root (with volatile subdirs excluded)\n    ├── usr/local/                 # /usr/local/bin, /usr/local/share/proxmenux, …\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/…"
+    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state\n├── metadata/\n│   ├── packages.manual.list       # apt-mark showmanual\n│   ├── run_info.env               # backup run identity + kernel version + pmxcfs method\n│   ├── selected_paths.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, …\n    ├── root/                      # /root (with volatile subdirs excluded)\n    ├── usr/local/                 # /usr/local/bin, /usr/local/share/proxmenux, …\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/…"
   },
   "rootfs": {
     "heading": "The rootfs payload",
@@ -176,7 +176,7 @@
         "stage": "3",
         "name": "Pending prepare",
         "reads": "rootfs/ (reboot + dangerous paths)",
-        "action": "_rs_prepare_pending_restore stages risky paths under /var/lib/proxmenux/pending-restore/, writes plan.env, apply-on-boot.list and rs-skip-paths.txt, and enables proxmenux-restore-onboot.service to fire on the next boot."
+        "action": "_rs_prepare_pending_restore stages risky paths under /var/lib/proxmenux/restore-pending/<restore_id>/ (linked as current), writes plan.env, apply-on-boot.list and rs-skip-paths.txt, and enables proxmenux-restore-onboot.service to fire on the next boot."
       },
       {
         "stage": "3b",
diff --git a/web/messages/en/docs/backup-restore/index.json b/web/messages/en/docs/backup-restore/index.json
index 1c10d79c..bfd1378f 100644
--- a/web/messages/en/docs/backup-restore/index.json
+++ b/web/messages/en/docs/backup-restore/index.json
@@ -50,7 +50,7 @@
     "heading": "Two interfaces, one backend",
     "intro": "Every step of the backup and restore workflow is available from two entry points. Both call the same shell library, produce identical archives, and read the same job registry.",
     "cliLabel": "ProxMenux Scripts (TUI)",
-    "cliDetail": "menu → Utilities →\nHost Backup / Restore\n\nDialog-based flow,\nSSH-friendly, scriptable\nfor unattended use.",
+    "cliDetail": "menu →\nHost Backup & Restore\n\nDialog-based flow,\nSSH-friendly, scriptable\nfor unattended use.",
     "webLabel": "ProxMenux Monitor (Web UI)",
     "webDetail": "Backups tab in the\nMonitor's Web interface.\n\nOne-click restore, live\nlog tail, integrated with\nnotifications and the\nrollback delta viewer."
   },
diff --git a/web/messages/en/docs/backup-restore/restoring.json b/web/messages/en/docs/backup-restore/restoring.json
index cd16d81a..46c29680 100644
--- a/web/messages/en/docs/backup-restore/restoring.json
+++ b/web/messages/en/docs/backup-restore/restoring.json
@@ -92,11 +92,11 @@
   "fullFlow": {
     "heading": "The full restore workflow",
     "intro": "The complete pipeline from archive selection to a working restored host. Each stage feeds the next; the state written by earlier stages is consumed by later ones.",
-    "diagram": "┌─────────────────────────────────────────────────────────────────┐\n│  Archive → staging_root/                                        │\n│    manifest.json   metadata/   rootfs/                          │\n└──────────────────────────────┬──────────────────────────────────┘\n                               │\n                               ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  1. hb_compat_check                                     │\n   │     · hardware drift → RS_SKIP_PATHS                    │\n   │     · kernel direction → same / bk_newer / bk_older     │\n   │     · NIC remap plan                                    │\n   │     · hydration plan (bk_older only)                    │\n   │     · rollback plan                                     │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  2. Confirmation dialog                                 │\n   │     Shows: hot count, pending count, drift, hydration,  │\n   │     NIC remap, cross-kernel note, reinstalls preview    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │ (accepted)\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  3. Apply hot paths → LIVE                              │\n   │     _rs_apply rsyncs each hot path                      │\n   │     from staging_root/rootfs to /                       │\n   │     (skips paths in RS_SKIP_PATHS)                      │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4. _rs_prepare_pending_restore                         │\n   │     /var/lib/proxmenux/pending-restore/                 │\n   │       ├── apply-on-boot.list                            │\n   │       ├── plan.env                                      │\n   │       ├── rs-skip-paths.txt                             │\n   │       └── rootfs/ (deferred paths)                      │\n   │     Enable proxmenux-restore-onboot.service             │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4b. _rs_import_data_pools                              │\n   │      Imports non-root ZFS pools whose disks are all     │\n   │      present; retries with -f on foreign hostid;        │\n   │      skips with a warning when disks are missing.       │\n   │      Log: /var/log/proxmenux/restore-datapools-*.log    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  5. _rs_run_complete_extras                             │\n   │     packages.manual.list                                │\n   │       → cascade-safe filter                             │\n   │       → apt-get install                                 │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  6. REBOOT                                              │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  7. apply_pending_restore.sh (on early boot)            │\n   │     Reads plan.env                                      │\n   │     Applies apply-on-boot.list (filtered)               │\n   │     Installs the postboot systemd unit                  │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  8. apply_cluster_postboot.sh (~10 minutes)             │\n   │     Runs after pve-cluster is up                        │\n   │     · Applies /etc/pve entries to pmxcfs                │\n   │     · update-initramfs -u -k all                        │\n   │     · update-grub / proxmox-boot-tool refresh           │\n   │     · component --auto-reinstall (nvidia, coral, ...)   │\n   │     · Boot sanity check                                 │\n   │     · Notification: \"Host restore finished\"             │\n   └─────────────────────────────────────────────────────────┘"
+    "diagram": "┌─────────────────────────────────────────────────────────────────┐\n│  Archive → staging_root/                                        │\n│    manifest.json   metadata/   rootfs/                          │\n└──────────────────────────────┬──────────────────────────────────┘\n                               │\n                               ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  1. hb_compat_check                                     │\n   │     · hardware drift → RS_SKIP_PATHS                    │\n   │     · kernel direction → same / bk_newer / bk_older     │\n   │     · NIC remap plan                                    │\n   │     · hydration plan (bk_older only)                    │\n   │     · rollback plan                                     │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  2. Confirmation dialog                                 │\n   │     Shows: hot count, pending count, drift, hydration,  │\n   │     NIC remap, cross-kernel note, reinstalls preview    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │ (accepted)\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  3. Apply hot paths → LIVE                              │\n   │     _rs_apply rsyncs each hot path                      │\n   │     from staging_root/rootfs to /                       │\n   │     (skips paths in RS_SKIP_PATHS)                      │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4. _rs_prepare_pending_restore                         │\n   │     /var/lib/proxmenux/restore-pending/'<'id'>'/            │\n   │       ├── apply-on-boot.list                            │\n   │       ├── plan.env                                      │\n   │       ├── rs-skip-paths.txt                             │\n   │       └── rootfs/ (deferred paths)                      │\n   │     Enable proxmenux-restore-onboot.service             │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4b. _rs_import_data_pools                              │\n   │      Imports non-root ZFS pools whose disks are all     │\n   │      present; retries with -f on foreign hostid;        │\n   │      skips with a warning when disks are missing.       │\n   │      Log: /var/log/proxmenux/restore-datapools-*.log    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  5. _rs_run_complete_extras                             │\n   │     packages.manual.list                                │\n   │       → cascade-safe filter                             │\n   │       → apt-get install                                 │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  6. REBOOT                                              │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  7. apply_pending_restore.sh (on early boot)            │\n   │     Reads plan.env                                      │\n   │     Applies apply-on-boot.list (filtered)               │\n   │     Installs the postboot systemd unit                  │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  8. apply_cluster_postboot.sh (~10 minutes)             │\n   │     Runs after pve-cluster is up                        │\n   │     · Applies /etc/pve entries to pmxcfs                │\n   │     · update-initramfs -u -k all                        │\n   │     · update-grub / proxmox-boot-tool refresh           │\n   │     · component --auto-reinstall (nvidia, coral, ...)   │\n   │     · Boot sanity check                                 │\n   │     · Notification: \"Host restore finished\"             │\n   └─────────────────────────────────────────────────────────┘"
   },
   "pendingMachinery": {
     "heading": "The pending-restore machinery",
-    "intro": "Paths classified as reboot or dangerous, and hydration writes, are staged for the next boot instead of being applied live. This is done through a small, self-contained set of files under /var/lib/proxmenux/pending-restore/:",
+    "intro": "Paths classified as reboot or dangerous, and hydration writes, are staged for the next boot instead of being applied live. This is done through a small, self-contained set of files under /var/lib/proxmenux/restore-pending/<restore_id>/:",
     "rows": [
       {
         "file": "apply-on-boot.list",
@@ -115,7 +115,7 @@
         "content": "The staged files themselves — the exact bytes that will be laid down. Kept on the same filesystem as / so the on-boot rsync is fast and does not depend on external storage still being reachable at boot."
       }
     ],
-    "unitBody": "The proxmenux-restore-onboot.service systemd unit is enabled at the end of the interactive step. It is a one-shot service that fires early on the next boot, calls apply_pending_restore.sh, then disables itself. The unit is gated by ConditionPathExists=/var/lib/proxmenux/pending-restore/state, so on any boot without a pending restore it is a no-op."
+    "unitBody": "The proxmenux-restore-onboot.service systemd unit is enabled at the end of the interactive step. It is a one-shot service that fires early on the next boot (before networking), calls apply_pending_restore.sh, then disables itself. The unit has no path condition: the script itself exits when /var/lib/proxmenux/restore-pending/current does not exist, so on any boot without a pending restore it is a no-op. The separate proxmenux-apply-cluster-postboot.service is gated by ConditionPathExists=/var/lib/proxmenux/cluster-apply-pending."
   },
   "postbootDispatcher": {
     "heading": "The post-boot dispatcher — apply_cluster_postboot.sh",
@@ -238,11 +238,11 @@
     "heading": "Where the logs live",
     "rows": [
       {
-        "log": "/var/log/proxmenux/restore-YYYYMMDD_HHMMSS.log",
-        "detail": "Written by the interactive step. Contains: compatibility results, drift filter, hydration plan, apply hot output, prepare pending output, package install."
+        "log": "/tmp/proxmenux--restore-YYYYMMDD_HHMMSS.log",
+        "detail": "Written by the interactive step (pbs, borg or local). Package reinstalls and data-pool steps also log to /var/log/proxmenux/restore-apt-*.log and restore-datapools-*.log."
       },
       {
-        "log": "/var/log/proxmenux/apply-pending-YYYYMMDD_HHMMSS.log",
+        "log": "/var/log/proxmenux/proxmenux-restore-onboot-YYYYMMDD_HHMMSS.log",
         "detail": "Written by apply_pending_restore.sh on early boot. Contains: sourced plan.env, apply-on-boot iteration, skip-paths filtering."
       },
       {
diff --git a/web/messages/en/docs/backup-restore/scheduled-jobs.json b/web/messages/en/docs/backup-restore/scheduled-jobs.json
index 309941ec..f31bf6b9 100644
--- a/web/messages/en/docs/backup-restore/scheduled-jobs.json
+++ b/web/messages/en/docs/backup-restore/scheduled-jobs.json
@@ -101,7 +101,7 @@
   },
   "management": {
     "heading": "Managing jobs",
-    "intro": "The scheduler menu (Scripts TUI) and the Monitor Backups tab expose the same actions on any job.",
+    "intro": "The scheduler menu in the Scripts TUI (Backup scheduler and retention) offers Run now, Enable / Disable and Delete, plus a list of jobs with their last run status. Edit and View log are only available in the Monitor Backups tab.",
     "rows": [
       {
         "action": "Run now",
@@ -112,7 +112,7 @@
         "detail": "New mode: systemctl enable/disable --now the timer. Attach mode: flips the ENABLED flag in the job env — the hook script honours it on the next PVE fire."
       },
       {
-        "action": "Edit",
+        "action": "Edit (Monitor only)",
         "detail": "Reopens the destination, schedule, profile and retention prompts and rewrites the job files. Preserves the job ID."
       },
       {
@@ -120,7 +120,7 @@
         "detail": "Removes the job env, paths file, systemd timer + service (New mode), and disables the hook binding (Attach mode). Does not touch archives already on the destination."
       },
       {
-        "action": "View log",
+        "action": "View log (Monitor only)",
         "detail": "Streams /var/log/proxmenux/backup-jobs/JOB_ID-YYYYMMDD_HHMMSS.log — one log file per run. The runner also emits a compact status line to journald under the systemd service."
       }
     ]
diff --git a/web/messages/en/docs/installation.json b/web/messages/en/docs/installation.json
index b633ad91..ecfe8ab7 100644
--- a/web/messages/en/docs/installation.json
+++ b/web/messages/en/docs/installation.json
@@ -53,11 +53,11 @@
     "calloutBody": "Want to try the latest features before the official release and help shape the final version? The ProxMenux Beta Program gives early access to new functionality — including the newest builds of ProxMenux Monitor — directly from the develop branch. Beta builds may contain bugs or incomplete features. Your feedback helps fix them before the stable release.",
     "intro": "Install the beta version instead of the stable one with:",
     "code": "bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/develop/install_proxmenux_beta.sh)\"",
-    "outro": "Beta is opt-in — you have to explicitly run the beta installer. When a stable release is published, ProxMenux notifies you on the next menu launch and offers to switch automatically. To opt out earlier, use the Deactivate Beta Program option in Settings."
+    "outro": "Beta is opt-in — you have to explicitly run the beta installer. When a stable release is published, the next menu launch offers it with the regular update prompt. To leave the beta channel, use Change Release Channel in Settings and pick Stable."
   },
   "updating": {
     "heading": "Updating",
-    "body": "ProxMenux self-updates. When a new version is available, the next menu launch offers the update; accepting replaces utility files and configurations in place. No manual download needed. Stable users get stable releases; beta users get beta releases (and the auto-switch prompt above when a stable cuts over). The prompt shape depends on where menu runs: over SSH or the Proxmox console it is a classic yes / no dialog, and accepting applies the update immediately. Inside the WebSocket terminal of ProxMenux Monitor it is replaced by an informational msgbox — that terminal cannot survive a self-update mid-flight, so the msgbox shows the canonical one-line installer (bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\" for stable, or the equivalent install_proxmenux_beta.sh URL for beta) to run from SSH or console."
+    "body": "ProxMenux self-updates. When a new version is available, the next menu launch offers the update; accepting replaces utility files and configurations in place. No manual download needed. Stable users get stable releases; beta users get beta builds and are also offered new stable releases. The prompt shape depends on where menu runs: over SSH or the Proxmox console it is a classic yes / no dialog, and accepting applies the update immediately. Inside the WebSocket terminal of ProxMenux Monitor it is replaced by an informational msgbox — that terminal cannot survive a self-update mid-flight, so the msgbox shows the canonical one-line installer (bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\" for stable, or the equivalent install_proxmenux_beta.sh URL for beta) to run from SSH or console."
   },
   "uninstall": {
     "heading": "Uninstalling",
diff --git a/web/messages/en/docs/introduction.json b/web/messages/en/docs/introduction.json
index 7781d8dc..058d9a69 100644
--- a/web/messages/en/docs/introduction.json
+++ b/web/messages/en/docs/introduction.json
@@ -21,11 +21,11 @@
     "intro": "ProxMenux has two components in the same source tree. The installer deploys both on the host: Scripts provides the terminal interface, and Monitor provides the web dashboard:",
     "scripts": {
       "title": "ProxMenux Scripts",
-      "body": "The original CLI/TUI you launch with menu. Nine functional areas covering post-install optimizations, VM and LXC creation, GPU and Coral TPU passthrough, disks, storage, network, security, utilities, plus a curated commands reference. Runs in any terminal — local, SSH or web."
+      "body": "The original CLI/TUI you launch with menu. Functional areas covering post-install optimizations, VM and LXC creation, GPU and Coral TPU passthrough, disks, storage, OCI apps, network, security, utilities, host backup and restore, Proxmox VE Helper Scripts, plus a curated commands reference. Runs in any terminal — local, SSH or web."
     },
     "monitor": {
       "title": "ProxMenux Monitor",
-      "body": "Self-hosted web dashboard (AppImage). Real-time host metrics, a proactive Health Monitor, notifications, an optional AI rewrite hook (six providers), an integrated web terminal and a REST API. Enable from the Scripts menu (Settings → ProxMenux Monitor)."
+      "body": "Self-hosted web dashboard (AppImage). Real-time host metrics, a proactive Health Monitor, notifications, an optional AI rewrite hook (six providers), an integrated web terminal and a REST API. The installer sets it up as a service that starts on boot; activate, deactivate or check it from the Scripts menu (Settings → ProxMenux Monitor)."
     },
     "calloutTitle": "What ProxMenux acts on",
     "calloutIntro": "Three layers, all reachable from the same project:",
@@ -37,7 +37,7 @@
   },
   "scriptsSection": {
     "heading": "ProxMenux Scripts — what you get",
-    "intro": "Nine functional areas, each with its own menu inside menu and a detail page in the documentation. Click any card to jump straight to that section's overview.",
+    "intro": "The main functional areas, each with its own menu inside menu and a detail page in the documentation. Click any card to jump straight to that section's overview.",
     "items": [
       {
         "title": "Post-Install",
diff --git a/web/messages/en/docs/monitor/access-auth.json b/web/messages/en/docs/monitor/access-auth.json
index 1787f106..92ca47cd 100644
--- a/web/messages/en/docs/monitor/access-auth.json
+++ b/web/messages/en/docs/monitor/access-auth.json
@@ -18,7 +18,7 @@
   },
   "reaching": {
     "heading": "Reaching the dashboard",
-    "intro": "ProxMenux Monitor binds to 0.0.0.0:8008. There are three common ways to reach it:",
+    "intro": "ProxMenux Monitor listens on TCP 8008 on all interfaces (::, IPv4 and IPv6). There are three common ways to reach it:",
     "outro": "Direct access matches the default systemd unit. Reverse-proxy and Secure Gateway access are described below. Trust for client IPs in X-Forwarded-For requires PROXMENUX_TRUST_PROXY=1 and a proxy you control. The presence of X-Forwarded-Proto or X-Forwarded-Host alone does not enable proxy trust. On ports 80/443 the frontend uses relative /api/* URLs; on any other port it calls port 8008 on the same host name."
   },
   "firstLaunch": {
@@ -158,7 +158,7 @@
     "authJsonTitle": "auth.json — what it contains and how it's protected",
     "authJsonBody": "Everything ProxMenux Monitor needs to authenticate you lives in a single file: /root/.config/proxmenux-monitor/auth.json, mode 0600, owner root. The file holds hashes (PBKDF2) and signing material (jwt_secret, totp_secret) — never a plaintext password. Treat it like any other root-only secret: if you back up or replicate the host, encrypt the destination, and never commit it to version control.",
     "rotateTitle": "Rotating jwt_secret invalidates all existing JWTs",
-    "rotateBody": "If auth.json is regenerated (manual delete, reinstall, restore from a backup with a different secret) the jwt_secret changes and every previously-issued JWT — both interactive sessions and long-lived API tokens — fails verification with \"Invalid or expired token\". The UI flags affected API tokens with an Invalid — regenerate badge so the user knows to revoke and re-mint them; Home Assistant / scripts / any external client needs a fresh token after that.",
+    "rotateBody": "If auth.json is regenerated (manual delete, reinstall, restore from a backup with a different secret) the jwt_secret changes and every previously-issued JWT — both interactive sessions and long-lived API tokens — fails verification with \"Invalid or expired token\". The UI marks affected API tokens in red, with a badge telling you to revoke them and generate new ones; Home Assistant / scripts / any external client needs a fresh token after that.",
     "recoverTitle": "Recovering a lost password",
     "recoverIntro": "There is no online \"forgot password\" flow — by design, since the dashboard runs on the user's own host and the recovery path is shell access to that host. ProxMenux ships a guided reset inside the configuration menu so you don't have to hand-edit auth.json:",
     "survivesTitle": "What survives the reset",
@@ -284,14 +284,14 @@
     "heading": "API tokens (long-lived)",
     "intro": "Browser sessions expire after 24 hours. For unattended integrations (Homepage widgets, Home Assistant sensors, Grafana scrapers, Uptime Kuma probes…) you generate a separate API token that lives 365 days. The token is a JWT signed with the same secret as the session token, but its token_name claim makes it easy to track and revoke individually.",
     "imageAlt": "API tokens panel showing the token list with name, prefix, created date and expiry",
-    "imageCaption": "The API tokens list under Settings — name, prefix (last 4 chars are shown for identification), created and expiry dates, revoke action.",
+    "imageCaption": "The API tokens list under Admin → Security — name, prefix (last 4 chars are shown for identification), created and expiry dates, revoke action.",
     "generateTitle": "Generate a token",
     "generateIntro": "From the dashboard:",
     "generateSteps": [
-      "Navigate to the Security tab → API Access Tokens section.",
+      "Open Admin → Security → API Tokens and click Generate new token.",
       "Type a descriptive name (e.g. \"Home Assistant\").",
       "Re-enter your password. If 2FA is on, also the current 6-digit code.",
-      "Click Generate Token. The token appears once — copy it immediately."
+      "Click Generate token. The token appears once: copy it immediately."
     ],
     "generateCli": "From the command line:",
     "useTitle": "Use a token",
@@ -310,7 +310,7 @@
     "intro": "Two paths to TLS:",
     "items": [
       "Reverse proxy (recommended). Terminate TLS on Nginx / Caddy / Traefik and forward HTTP on port 8008 to the Flask process. Snippets below.",
-      "Direct HTTPS in the AppImage. Configure a certificate with POST /api/ssl/configure (UI: Settings → SSL). The HTTPS path uses gevent.pywsgi with its standard handler; flask-sock handles WebSocket upgrades for WSS. Certificate paths are stored in the SSL configuration."
+      "Direct HTTPS in the AppImage. Configure a certificate with POST /api/ssl/configure (UI: Admin → Security → SSL / HTTPS). The HTTPS path uses gevent.pywsgi with its standard handler; flask-sock handles WebSocket upgrades for WSS. Certificate paths are stored in the SSL configuration."
     ],
     "calloutTitle": "Direct HTTPS limitations",
     "calloutBody": "The Monitor can use a Proxmox-managed certificate or custom certificate files. It does not issue ACME certificates itself. For automatic issuance and renewal, use Proxmox ACME or a reverse proxy such as Caddy, Traefik or Nginx, and ensure renewed certificates are loaded."
diff --git a/web/messages/en/docs/monitor/ai-assistant.json b/web/messages/en/docs/monitor/ai-assistant.json
index 1412d29d..1564bbb6 100644
--- a/web/messages/en/docs/monitor/ai-assistant.json
+++ b/web/messages/en/docs/monitor/ai-assistant.json
@@ -9,7 +9,7 @@
   },
   "header": {
     "title": "AI Assistant",
-    "description": "Optional AI rewriting turns eligible notification templates into plain-language messages, with per-channel detail levels, thirteen UI languages, custom prompts, a public community library and context enrichment. Backup reports backup_complete and backup_fail bypass AI entirely.",
+    "description": "Optional AI rewriting turns eligible notification templates into plain-language messages, with per-channel detail levels, thirteen UI languages, custom prompts, a public community library and context enrichment. Backup reports backup_complete and backup_fail are never rewritten; with AI Suggestions on, the AI only appends an explanation when a backup reports errors.",
     "section": "ProxMenux Monitor"
   },
   "intro": {
@@ -83,7 +83,7 @@
     "intro1": "Every commercial provider charges per token, so it's worth understanding what a token is before picking a plan. A token is roughly four characters of English text or about three quarters of a word. The phrase \"Backup completed on storage local-bak\" is around eight tokens. A short journal excerpt of ten lines can be 200-400 tokens depending on the technical density.",
     "intro2": "Two things are billed on every call:",
     "items": [
-      "Input tokens cover the system prompt and user message: severity, title, rendered body and enriched context. Their number depends on the actual prompt and event content. Each rewritten channel can incur its own input-token usage; backup-complete and backup-fail reports bypass AI.",
+      "Input tokens cover the system prompt and user message: severity, title, rendered body and enriched context. Their number depends on the actual prompt and event content. Each rewritten channel can incur its own input-token usage; backup-complete and backup-fail reports only use tokens when the AI explains backup errors.",
       "Output tokens — what the model writes back. The Monitor caps this with max_tokens (see the table below). The cap is a limit, not a charge: if the model produces 250 tokens with a cap of 1500, you pay for 250."
     ],
     "capsIntro": "These are the actual caps the dispatcher applies, taken straight from AI_DETAIL_TOKENS in notification_templates.py:",
@@ -118,7 +118,7 @@
     "imageCaption": "The in-app modal — six cards, one per provider, with the same descriptions documented below.",
     "groq": {
       "heading": "Groq",
-      "tagline": "Very fast, generous free tier (30 req/min). Ideal to start.",
+      "tagline": "Very fast, with a generous free tier. A good place to start.",
       "items": [
         "API key: console.groq.com/keys",
         "Examples from the bundled model list: allam-2-7b, qwen/qwen3.8-27b, openai/gpt-oss-20b, openai/gpt-oss-120b.",
@@ -234,7 +234,7 @@
   },
   "detailLevel": {
     "heading": "Detail level per channel",
-    "intro": "Each of the four channels (Telegram, Discord, Gotify, Email) has its own detail-level dropdown. Three values are available, mapped to specific output token caps and to specific instructions in the default prompt:",
+    "intro": "Each of the six channels (Telegram, Discord, Gotify, Email, Pushover, Apprise) has its own detail-level dropdown. Three values are available, mapped to specific output token caps and to specific instructions in the default prompt:",
     "headerLevel": "Level",
     "headerLabel": "UI label",
     "headerCap": "Output cap",
@@ -261,7 +261,7 @@
     ],
     "defaultsIntro": "Defaults the Monitor applies on first install:",
     "defaults": [
-      "Telegram, Discord, Gotify — standard.",
+      "Telegram, Discord, Gotify, Pushover, Apprise — standard.",
       "Email — detailed. Email is the channel where you typically want the full picture for archival."
     ],
     "emailTitle": "Email detail level appends the original",
@@ -280,7 +280,7 @@
   },
   "templates": {
     "heading": "A note on templates",
-    "body1": "The AI receives a rendered template rather than raw event data. Types such as vm_start, auth_fail and health_degraded have templates in notification_templates.py. The AI rewrites eligible template output; backup_complete and backup_fail retain their deterministic reports and bypass AI.",
+    "body1": "The AI receives a rendered template rather than raw event data. Types such as vm_start, auth_fail and health_degraded have templates in notification_templates.py. The AI rewrites eligible template output; backup_complete and backup_fail retain their deterministic reports; with AI Suggestions on, the AI only appends an explanation when the backup reports errors.",
     "body2": "Two practical implications: the AI never sees a hostname or VMID it has to invent — those fields are placed by the template before the rewrite. And if AI is disabled, the templated body is what gets dispatched directly. The Notifications page documents the dispatch pipeline in full and is the right cross-reference for everything that happens to an event before it reaches this layer."
   },
   "privacy": {
diff --git a/web/messages/en/docs/monitor/api-reference.json b/web/messages/en/docs/monitor/api-reference.json
index eb5e3ff4..e2c180ab 100644
--- a/web/messages/en/docs/monitor/api-reference.json
+++ b/web/messages/en/docs/monitor/api-reference.json
@@ -76,7 +76,7 @@
       {
         "endpoint": "/api/system-info",
         "method": "GET",
-        "use": "Extended system snapshot used by the dashboard header (overall metrics + boot time)."
+        "use": "Lightweight header info: hostname, uptime and cached health status. Public, no token needed."
       },
       {
         "endpoint": "/api/hardware",
@@ -401,7 +401,7 @@
       {
         "endpoint": "/api/logs",
         "method": "GET",
-        "use": "Filtered journal entries. Query: ?level=&service=&limit=."
+        "use": "Filtered journal entries. Query: ?priority=&service=&limit=&since_days= (priority 0-7, since_days up to 90)."
       },
       {
         "endpoint": "/api/logs/download",
@@ -411,7 +411,7 @@
       {
         "endpoint": "/api/events",
         "method": "GET",
-        "use": "Internal event stream — the same one that feeds notifications."
+        "use": "Recent Proxmox cluster tasks and events from pvesh get /cluster/tasks. Query: ?limit= (default 50)."
       },
       {
         "endpoint": "/api/task-log/",
@@ -462,7 +462,7 @@
       {
         "endpoint": "/api/notifications/send",
         "method": "POST",
-        "use": "Emit a custom event. Body: '{'\"event_type\":\"custom\",\"severity\":\"WARNING\",\"title\":\"...\",\"body\":\"...\",\"data\":'{''}''}'."
+        "use": "Emit a custom event. Body: '{'\"event_type\":\"custom\",\"severity\":\"WARNING\",\"title\":\"...\",\"message\":\"...\",\"data\":'{''}''}'."
       },
       {
         "endpoint": "/api/notifications/test-ai",
diff --git a/web/messages/en/docs/monitor/architecture.json b/web/messages/en/docs/monitor/architecture.json
index d8415fa6..36c75d1e 100644
--- a/web/messages/en/docs/monitor/architecture.json
+++ b/web/messages/en/docs/monitor/architecture.json
@@ -64,7 +64,7 @@
     "items": [
       "User=root — the supported service runs with root privileges for host operations such as SMART access, pvesh and ZFS management. Its web-terminal processes inherit those privileges.",
       "Restart=on-failure with a 10-second back-off — non-zero exits relaunch automatically.",
-      "After=network.target — orders startup after that target; it does not guarantee an assigned IP, DNS or external connectivity."
+      "After=network.target pve-cluster.service: orders startup after the network target and the Proxmox cluster filesystem; it does not guarantee an assigned IP, DNS or external connectivity."
     ],
     "inspectTitle": "Inspect the live unit"
   },
diff --git a/web/messages/en/docs/monitor/dashboard/hardware.json b/web/messages/en/docs/monitor/dashboard/hardware.json
index e47b2bb7..b61f7c63 100644
--- a/web/messages/en/docs/monitor/dashboard/hardware.json
+++ b/web/messages/en/docs/monitor/dashboard/hardware.json
@@ -168,7 +168,7 @@
   "ups": {
     "heading": "UPS Status",
     "subHeading": "(conditional)",
-    "body": "Renders when a NUT (Network UPS Tools) server is configured and reachable. Shows: state (online / on battery / charging / low battery), battery charge percentage, runtime estimate, load percentage, input voltage, model and firmware. The same data feeds the Security & Certificates category of the Health Monitor — a UPS that goes on-battery surfaces immediately."
+    "body": "Renders when a NUT (Network UPS Tools) server is configured and reachable. Shows: state (online / on battery / charging / low battery), battery charge percentage, runtime estimate, load percentage, input voltage, model and firmware."
   },
   "dataCollected": {
     "heading": "How the data is collected",
diff --git a/web/messages/en/docs/monitor/dashboard/index.json b/web/messages/en/docs/monitor/dashboard/index.json
index c0bbdbd2..be2a5c9a 100644
--- a/web/messages/en/docs/monitor/dashboard/index.json
+++ b/web/messages/en/docs/monitor/dashboard/index.json
@@ -50,7 +50,7 @@
       },
       {
         "name": "Terminal",
-        "owns": "Browser shell to host or to any VM/CT, powered by xterm.js over WebSockets."
+        "owns": "Browser shell on the Proxmox host, powered by xterm.js over WebSockets. LXC consoles open from the VMs & LXCs tab."
       },
       {
         "name": "Security",
diff --git a/web/messages/en/docs/monitor/dashboard/security.json b/web/messages/en/docs/monitor/dashboard/security.json
index 36ac8340..c6873cb6 100644
--- a/web/messages/en/docs/monitor/dashboard/security.json
+++ b/web/messages/en/docs/monitor/dashboard/security.json
@@ -183,7 +183,7 @@
       },
       {
         "card": "SSL / HTTPS",
-        "endpoint": "/api/auth/ssl/*",
+        "endpoint": "/api/ssl/*",
         "source": "openssl x509 on /etc/pve/local/pve-ssl.pem + /etc/proxmenux/ssl_config.json."
       },
       {
diff --git a/web/messages/en/docs/monitor/dashboard/settings.json b/web/messages/en/docs/monitor/dashboard/settings.json
index d7d5107e..d63054aa 100644
--- a/web/messages/en/docs/monitor/dashboard/settings.json
+++ b/web/messages/en/docs/monitor/dashboard/settings.json
@@ -41,7 +41,7 @@
     "heading": "Network Units",
     "imageAlt": "Network Units card with Network Unit Display dropdown set to Bytes",
     "imageCaption": "Choose between bits per second and bytes per second for every network rate displayed in the dashboard.",
-    "body": "Choose how network throughput is displayed across the dashboard: bits per second (Mbps / Gbps) or bytes per second (MB/s / GB/s). Bits is the default because it's what NIC vendors and ISPs label their products with; bytes is what most file-transfer tools report. The setting affects every chart, badge and tooltip that shows network rate — applied immediately, no reload needed."
+    "body": "Choose how network throughput is displayed across the dashboard: bits per second (Mbps / Gbps) or bytes per second (MB/s / GB/s). Bytes is the default, as most file-transfer tools report; bits is what NIC vendors and ISPs label their products with. The choice is stored in the browser, so each browser keeps its own. The setting affects every chart, badge and tooltip that shows network rate — applied immediately, no reload needed."
   },
   "health": {
     "heading": "Health Monitor",
@@ -203,7 +203,7 @@
     "heading": "LXC Update Detection",
     "imageAlt": "LXC Update Detection card with a single switch — when enabled, the Monitor periodically scans running Debian/Ubuntu/Alpine LXC containers for pending package updates.",
     "imageCaption": "The toggle for the periodic apt list --upgradable / apk list -u scan across every running CT. Default is ON. The matching notification toggle in Notifications → Services only appears while detection is enabled.",
-    "intro": "A dedicated toggle for the LXC update scan, sitting between the Health Monitor Thresholds and the Notifications card. When ON, ProxMenux walks every running CT on the host and queries the in-container package manager for pending updates; results land in the Hardware tab badge counts and feed the lxc_updates_available notification. When OFF, the scan stops entirely (no pct exec calls) and any existing LXC entries in managed_installs.json are purged immediately, so the dashboard and the /api/managed-installs endpoint stop reporting LXC update state without waiting for the next 24h cycle.",
+    "intro": "A dedicated toggle for the LXC update scan, sitting between the Health Monitor Thresholds and the Notifications card. When ON, ProxMenux walks every running CT on the host and queries the in-container package manager for pending updates; results land in the VMs & LXCs tab badge counts and feed the lxc_updates_available notification. When OFF, the scan stops entirely (no pct exec calls) and any existing LXC entries in managed_installs.json are purged immediately, so the dashboard and the /api/managed-installs endpoint stop reporting LXC update state without waiting for the next 24h cycle.",
     "whatRunsTitle": "What the scan actually runs",
     "whatRunsIntro": "For each CT in running state with a supported package manager:",
     "whatRunsItems": [
@@ -249,7 +249,7 @@
     "body1": "This section of the Settings tab is where ProxMenux Monitor notifications and the AI rewriter are turned on. Pressing Enable Notifications starts the dispatch background thread, registers a Proxmox VE webhook target on the host so PVE-emitted events flow into the same pipeline, and unfolds the channel form so you can connect Telegram, Discord, Email, Gotify and the rest. The AI rewriter sits inside the same panel as a collapsible advanced section.",
     "body2": "Both surfaces have a lot of moving parts — channels, per-event toggles, Rich messages, the Display Name, the dispatch pipeline (dedup, cooldown, aggregation, quiet hours), the PVE webhook integration, AI providers, prompt modes — and live on their own dedicated pages rather than being repeated here:",
     "items": [
-      "Notifications — channel walk-throughs (Telegram, Discord, Gotify, Email + Gmail / Microsoft app passwords, ntfy, Slack, Teams, generic webhook), per-event categories, Rich messages, Display Name, dispatch pipeline, PVE webhook integration, history and API.",
+      "Notifications — channel walk-throughs (Telegram, Discord, Gotify, Email + Gmail / Microsoft app passwords, Pushover, and Apprise for ntfy, Slack, Teams and other services), per-event categories, Rich messages, Display Name, dispatch pipeline, PVE webhook integration, history and API.",
       "AI Assistant — providers (OpenAI, Anthropic, Gemini, Groq, OpenRouter, Ollama), model selection, prompt modes (default / custom), output language, per-channel detail levels and AI suggestions."
     ]
   },
@@ -294,8 +294,8 @@
       },
       {
         "card": "Network Units",
-        "endpoint": "/api/settings",
-        "source": "Persisted in the dashboard's SQLite settings table."
+        "endpoint": "localStorage",
+        "source": "Browser preference proxmenux-network-unit."
       },
       {
         "card": "Health Monitor durations",
@@ -304,7 +304,7 @@
       },
       {
         "card": "Storage / interface exclusions",
-        "endpoint": "/api/storage/exclusions, /api/network/exclusions",
+        "endpoint": "/api/health/storage-exclusions, /api/health/interface-exclusions",
         "source": "SQLite tables excluded_storages and excluded_interfaces."
       },
       {
diff --git a/web/messages/en/docs/monitor/dashboard/storage.json b/web/messages/en/docs/monitor/dashboard/storage.json
index 4675b60a..51943536 100644
--- a/web/messages/en/docs/monitor/dashboard/storage.json
+++ b/web/messages/en/docs/monitor/dashboard/storage.json
@@ -10,7 +10,7 @@
   },
   "intro": {
     "title": "Backed by three sources",
-    "body": "Proxmox storages come from pvesm status; ZFS state from zpool status; physical disks from lsblk + smartctl (and nvme for NVMe-specific fields). The tab refreshes every ~60 seconds; the per-disk drill-in triggers a fresh SMART read on demand."
+    "body": "Proxmox storages come from pvesm status; ZFS state from zpool status; physical disks from lsblk + smartctl (and nvme for NVMe-specific fields). The tab refreshes every ~30 seconds; the per-disk drill-in triggers a fresh SMART read on demand."
   },
   "thresholds": {
     "title": "Status colours and thresholds applied here",
diff --git a/web/messages/en/docs/monitor/dashboard/system-logs.json b/web/messages/en/docs/monitor/dashboard/system-logs.json
index 98b15096..276cb420 100644
--- a/web/messages/en/docs/monitor/dashboard/system-logs.json
+++ b/web/messages/en/docs/monitor/dashboard/system-logs.json
@@ -18,7 +18,7 @@
       "Total Entries — number of log records inside the active filter window.",
       "Errors — count of severity ≤ 3 (err / crit / alert / emerg).",
       "Warnings — count of severity 4 (warning).",
-      "Backups — count of vzdump / PBS task entries in the same window."
+      "Backups: number of backup archives on the configured backup storages."
     ]
   },
   "subtabs": {
@@ -26,10 +26,10 @@
     "logsTitle": "Logs",
     "logsIntro": "The system journal, served by journalctl on the backend. Filters available in the toolbar:",
     "logsFilters": [
-      "Severity — emerg / alert / crit / err / warning / notice / info / debug, or any combination.",
-      "Time range — last 5 min / 15 min / 1 h / 6 h / 24 h / 7 d / custom.",
-      "Free-text search — substring or regex (journalctl --grep).",
-      "Unit filter — restrict to a specific systemd unit (pveproxy.service, nginx.service, …)."
+      "Level: All levels, Error, Warning or Info.",
+      "Time range: 1 day / 3 days / 1 week / 2 weeks / 1 month, or a custom number of days (up to 90).",
+      "Free-text search: case-insensitive substring over message, service, PID, hostname and unit, applied in the browser.",
+      "Service filter: restrict to one service (the journal SYSLOG_IDENTIFIER)."
     ],
     "logsRowsAfter": "Each row shows timestamp, severity badge, source unit and the message. Long messages collapse with a \"show more\" toggle. The Download action bundles the current filter into a single .txt file via GET /api/logs/download — useful when you want to share a slice of journal with someone.",
     "logDetailsModalTitle": "Log Details modal",
@@ -50,10 +50,10 @@
     "maxLevelStoreTitle": "Journald MaxLevelStore",
     "maxLevelStoreBody": "On a fresh Proxmox install, journald defaults to MaxLevelStore=warning, which silently drops info-level messages. The Monitor detects this on startup and adds a drop-in (/etc/systemd/journald.conf.d/proxmenux-loglevel.conf) raising the threshold to info so informational and more severe messages are stored. This does not include debug messages.",
     "backupsTitle": "Backups",
-    "backupsBody": "Proxmox task history filtered to backup-related entries. One row per task (vzdump, PBS transfers, Garbage Collect, Verify) with the status (OK / WARNINGS / ERROR), guest involved, source storage, duration and the UPID. Click a row to load the full task log via GET /api/task-log/<upid> — the same data Proxmox exposes through Datacenter → Tasks, scoped to backups.",
+    "backupsBody": "The backup archives on every backup-capable storage (dir, NFS, CIFS, PBS), read with pvesh get /nodes/localhost/storage/<id>/content. Cards at the top show the VM and LXC counts and the total size; below, one row per archive with type, storage, size and date. Restore lives in the Proxmox web UI.",
     "notificationsTitle": "Notifications",
-    "notificationsBody1": "Every notification dispatched by the Monitor — Telegram, Discord, Email, Gotify, ntfy, Slack, Teams, webhook. Each row: timestamp, channel, event type, severity, the rendered title, the rendered body, and (if AI is enabled) a toggle to view the AI rewrite next to the original.",
-    "notificationsBody2": "Use this tab to verify a channel is actually delivering and to compare what the AI rewrite produced vs the template baseline. Channel configuration lives in the Notifications deep page."
+    "notificationsBody1": "Proxmox notification activity: journal lines from Proxmox services that mention notifications, email, webhooks or alerts, the targets in /etc/pve/notifications.cfg, and backup tasks from the cluster task list. Backup task rows can download the full task log. The history of what the Monitor itself dispatched lives in Settings → Notifications → Notification history.",
+    "notificationsBody2": "Use this tab to check what Proxmox's own notification system is doing. Monitor channel configuration lives in the Notifications deep page."
   },
   "dataCollected": {
     "heading": "How the data is collected",
@@ -64,7 +64,7 @@
       {
         "subtab": "Logs (live filter)",
         "endpoint": "/api/logs",
-        "source": "journalctl --output json --since <range> with severity / unit / search filters applied server-side."
+        "source": "The tab requests /api/logs?since_days=N; level, service and text filters run in the browser. The API also accepts priority and service, which the tab does not use yet."
       },
       {
         "subtab": "Download",
@@ -74,17 +74,17 @@
       {
         "subtab": "Backups",
         "endpoint": "/api/backups",
-        "source": "PVE task history filtered by vzdump, PBS transfers, Garbage Collect, Verify."
+        "source": "Backup archives listed from each backup storage with pvesh get /nodes/localhost/storage/<id>/content."
       },
       {
-        "subtab": "Backup task drill-in",
+        "subtab": "Backup task log (Notifications)",
         "endpoint": "/api/task-log/<upid>",
         "source": "Plain-text full task log read from /var/log/pve/tasks/<index>/<upid>."
       },
       {
-        "subtab": "Notifications history",
-        "endpoint": "/api/notifications/history",
-        "source": "SQLite notification_history table fed by the dispatch loop."
+        "subtab": "Notifications",
+        "endpoint": "/api/notifications",
+        "source": "journalctl --grep over Proxmox units, /etc/pve/notifications.cfg and backup tasks from pvesh get /cluster/tasks."
       }
     ],
     "apiIntro": "Both the live filter and the downloads are also reachable via the API:",
diff --git a/web/messages/en/docs/monitor/dashboard/system-overview.json b/web/messages/en/docs/monitor/dashboard/system-overview.json
index 98fc5884..c5c45a02 100644
--- a/web/messages/en/docs/monitor/dashboard/system-overview.json
+++ b/web/messages/en/docs/monitor/dashboard/system-overview.json
@@ -46,7 +46,7 @@
     "thresholdsIntro": "Every ring, bar, and sparkline on the four metric cards follows the same classification —  green below Warning,  amber from Warning to Critical,  red at Critical and above. Recommended defaults shipped with ProxMenux:",
     "thresholdsItems": [
       "CPU usage — Warning 85 %, Critical 95 %.",
-      "Memory — Warning 85 %, Critical 95 % (Swap also fires Critical at 5 % used — a healthy Proxmox host should rarely touch swap).",
+      "Memory: Warning 85 %, Critical 95 %. Swap fires Critical only when swap use is above 80 % and available RAM is below 15 %.",
       "CPU temperature — Warning 80 °C, Critical 90 °C."
     ],
     "thresholdsOutro": "Every value is configurable per host — Settings → Health Monitor Thresholds is the single source of truth and explains how to tune them.",
@@ -58,7 +58,7 @@
     "intro": "The CPU Usage and Memory cards on the Overview tab are clickable. Clicking either opens a sortable list of the top 25 processes — ordered by CPU usage when opened from the CPU card, by resident memory when opened from the Memory card.",
     "listTitle": "The list dialog",
     "listItems": [
-      "Auto-refresh — the list updates every 5 seconds while the dialog is open and stops polling as soon as it closes.",
+      "Auto-refresh — the list updates every 3 seconds while the dialog is open and stops polling as soon as it closes.",
       "Filter — the search box narrows the list by command, user or PID.",
       "Inline bar — the primary metric column draws a small bar scaled to the highest value in the filtered list, so ranking stays visible even when no process is near 100 %.",
       "Mobile layout — under 640 px the PID and User columns hide so Command, CPU % and Memory still fit on a phone screen without horizontal scroll."
@@ -79,7 +79,7 @@
   },
   "middle": {
     "heading": "Middle: node metrics charts",
-    "body1": "Below the top row sits the NodeMetricsCharts component — historical CPU, memory and disk-I/O graphs sourced from Proxmox's own RRD store via /api/node/metrics. A timeframe selector switches between 1 hour / 24 hours / 7 days / 30 days / 1 year; data resolution drops as the window grows so the chart stays smooth.",
+    "body1": "Below the top row sits the NodeMetricsCharts component — historical CPU, memory and disk-I/O graphs sourced from Proxmox's own RRD store via /api/node/metrics. A timeframe selector switches between 1 hour / 24 hours / 7 days / 30 days; data resolution drops as the window grows so the chart stays smooth.",
     "body2": "These are the same graphs that the Proxmox web UI renders for a node, just consolidated into the Monitor's dark theme and aligned with the other panels."
   },
   "bottom": {
diff --git a/web/messages/en/docs/monitor/dashboard/vms-lxcs.json b/web/messages/en/docs/monitor/dashboard/vms-lxcs.json
index b258a399..4cb0e36e 100644
--- a/web/messages/en/docs/monitor/dashboard/vms-lxcs.json
+++ b/web/messages/en/docs/monitor/dashboard/vms-lxcs.json
@@ -117,7 +117,7 @@
       "Destination tag — the storage where it lives (PBS-Cloud, PBS-Local, NFS-Backup, …) coloured by status.",
       "Size — final on-disk size of the backup."
     ],
-    "backupsOutro": "The + Create Backup button at the top right kicks off a new run on the storage marked as \"Backup target\" in the Proxmox storage config. Restore lives in the Proxmox web UI — the Monitor exposes the \"is this guest backed up recently?\" view, not the recovery flow.",
+    "backupsOutro": "The + Create Backup button at the top right opens a dialog to choose the storage, mode (snapshot / suspend / stop), protection and notes before starting a vzdump. Restore lives in the Proxmox web UI — the Monitor exposes the \"is this guest backed up recently?\" view, not the recovery flow.",
     "firewallTitle": "Tab 7 — Firewall",
     "firewallIntro": "Reads the per-guest Proxmox firewall log straight from the host (no extra service, no polling). The tab is always present in the navigation strip; the panel decides what to render depending on whether the firewall is enabled for that guest and whether any rule is actually logging:",
     "firewallItems": [
diff --git a/web/messages/en/docs/monitor/health-monitor.json b/web/messages/en/docs/monitor/health-monitor.json
index c4c3c725..8a338e0d 100644
--- a/web/messages/en/docs/monitor/health-monitor.json
+++ b/web/messages/en/docs/monitor/health-monitor.json
@@ -153,8 +153,8 @@
     "pillTitle": "The pill mirrors the worst category",
     "pillBody": "The dashboard header colour is the highest severity across the ten categories: any CRITICAL → red, else any WARNING → yellow, else any INFO → blue, else green. The same logic drives the favicon dot and the PWA badge.",
     "updateNowTitle": "Update Now button in the Overview header",
-    "updateNowBody1": "When the System Updates category reports one or more pending updates, the Overview header renders an Update Now button next to the health pill. The button opens a modal that runs the same host-side flow as ProxMenux → Settings post-install Proxmox → Proxmox System Update: repo hygiene, apt full-upgrade via the safe worker update-pve-safe.sh, automatic DKMS rebuild of ProxMenux-managed drivers when a new kernel lands, autoremove / autoclean and a reboot prompt if the kernel changed. Progress lines stream in the modal as the worker prints them.",
-    "updateNowBody2": "When the run finishes, closing the modal calls GET /api/health/details?refresh=1, which invalidates the health-endpoint cache before serving. The pending-updates row and the pill severity are recomputed from the new package state on the spot, so the badge disappears without waiting for the next five-minute cycle. When the host is already up to date, the button is hidden — no visual noise on a healthy system."
+    "updateNowBody1": "When the System Updates category reports one or more pending updates, the Overview header renders an Update Now button next to the health pill. The button opens a modal that runs the same host-side flow as ProxMenux → Utilities and Tools → Proxmox System Update: repo hygiene, apt full-upgrade via the safe worker update-pve-safe.sh, automatic DKMS rebuild of ProxMenux-managed drivers when a new kernel lands, autoremove / autoclean and a reboot prompt if the kernel changed. Progress lines stream in the modal as the worker prints them.",
+    "updateNowBody2": "When the run finishes, closing the modal calls GET /api/health/full?refresh=1, which invalidates the health-endpoint cache before serving. The pending-updates row and the pill severity are recomputed from the new package state on the spot, so the badge disappears without waiting for the next five-minute cycle. When the host is already up to date, the button is hidden — no visual noise on a healthy system."
   },
   "dismiss": {
     "heading": "Dismissing alerts and the Suppression Duration",
diff --git a/web/messages/en/docs/monitor/index.json b/web/messages/en/docs/monitor/index.json
index 2613e500..347bf877 100644
--- a/web/messages/en/docs/monitor/index.json
+++ b/web/messages/en/docs/monitor/index.json
@@ -113,13 +113,13 @@
   "api": {
     "heading": "REST API and integrations",
     "intro": "The dashboard’s REST endpoints expose monitoring data as JSON over HTTP/HTTPS for Homepage widgets, Home Assistant sensors, Uptime Kuma probes and custom scripts. Grafana can use the separate Prometheus text exporter at /api/prometheus. Requests can be made with curl.",
-    "tokens": "Long-lived API tokens (365 days) are generated from Settings → API Access Tokens or via POST /api/auth/generate-api-token.",
+    "tokens": "Long-lived API tokens (365 days) are generated from Admin → Security → API Tokens or via POST /api/auth/generate-api-token.",
     "bearer": "Send tokens as Authorization: Bearer …. /api/health also expects the bearer header, so uptime probes need a read_only token. Routes under /api/auth/* include both public setup/login/status routes and protected account/token management routes; they are not all anonymous.",
     "catalog": "The full endpoint catalog, token rotation guidance and security best-practices live in API Reference; ready-made examples for Homepage, Home Assistant, Grafana, Uptime Kuma and a generic cURL pattern are in Integrations."
   },
   "serviceControl": {
     "heading": "Service control",
-    "intro": "Day-to-day, the Monitor is managed exactly like any other systemd service. It is also exposed as two entries inside the ProxMenux TUI under Settings:",
+    "intro": "Day-to-day, the Monitor is managed exactly like any other systemd service. It is also exposed as three entries inside the ProxMenux TUI under Settings:",
     "codeComment": "# Manual control",
     "footer": "See Settings → ProxMenux Monitor for the in-menu toggle and status verification flow."
   },
diff --git a/web/messages/en/docs/monitor/integrations.json b/web/messages/en/docs/monitor/integrations.json
index d0b5a0bd..7b445bdb 100644
--- a/web/messages/en/docs/monitor/integrations.json
+++ b/web/messages/en/docs/monitor/integrations.json
@@ -20,13 +20,13 @@
     "heading": "Authentication: the one thing every recipe needs",
     "intro": "Most endpoints used by these integrations are authenticated. You have two ways to satisfy that requirement.",
     "optAtitle": "Option A — API token (recommended for integrations)",
-    "optAbody1": "Open the dashboard and go to Settings → Security → API Tokens. Click Generate token, give it a name (e.g. homepage, home-assistant, prometheus), and copy the token — it's shown once. Long-lived (one-year expiry by default), individually revocable, and what you should be using for any non-browser client.",
+    "optAbody1": "Open the dashboard and go to Admin → Security → API Tokens. Click Generate new token, give it a name (e.g. homepage, home-assistant, prometheus), enter your password (and the 2FA code if enabled), then click Generate token and copy the token: it's shown once. Long-lived (one-year expiry by default), individually revocable, and what you should be using for any non-browser client.",
     "optAbody2": "From that point every request is just:",
     "optBtitle": "Option B — login flow (username + password)",
     "optBbody": "Useful for scripts that authenticate as a human user. The returned token is short-lived; most integrations should prefer Option A.",
     "outro": "The TOTP field is only required when 2FA is enabled on the account. Token rotation, revocation, password policy and the audit log live in Access & Authentication.",
     "httpsTitle": "Using HTTPS instead of HTTP",
-    "httpsIntro": "Every recipe below uses http:// in the URLs to keep the examples short. If you've enabled TLS on the Monitor (Settings → Security → SSL/HTTPS), swap http:// for https:// in every URL — that's the only change. Two notes specific to certain tools:",
+    "httpsIntro": "Every recipe below uses http:// in the URLs to keep the examples short. If you've enabled TLS on the Monitor (Admin → Security → SSL / HTTPS), swap http:// for https:// in every URL — that's the only change. Two notes specific to certain tools:",
     "httpsItems": [
       "Self-signed certificates. Home Assistant's rest: integration verifies TLS by default. If the Monitor is using its own self-signed cert, add verify_ssl: false to each REST block (alongside scan_interval:), or import the Monitor's CA into HA's trust store. Same for any tool that refuses untrusted certs.",
       "Prometheus already has scheme: https ready in the scrape config below; uncomment / leave it as https if TLS is enabled on the Monitor."
@@ -43,7 +43,7 @@
     "basicTitle": "Basic widget — no authentication",
     "basicIntro": "Use this when ProxMenux Monitor is on a trusted network and you haven't enabled authentication on the Monitor side yet. The simplest possible services.yaml entry:",
     "authedTitle": "Authenticated widget",
-    "authedIntro": "Generate an API token in Settings → Security → API Tokens on the Monitor, copy it, and paste it into the Authorization header below — replace the example token shown after Bearer with the one you just copied:",
+    "authedIntro": "Generate an API token in Admin → Security → API Tokens on the Monitor, copy it, and paste it into the Authorization header below — replace the example token shown after Bearer with the one you just copied:",
     "authedOutro": "Restart Homepage and the card lights up with live values. Reuse the same token across all Homepage widgets pointing at the same ProxMenux Monitor host.",
     "multiTitle": "Multi-widget setup — system, storage, network",
     "multiIntro": "For a richer view, render three separate cards backed by different endpoints — one for system metrics, one for storage, one for network. Use the same token in every card; it's the same Monitor instance.",
diff --git a/web/messages/en/docs/monitor/notifications.json b/web/messages/en/docs/monitor/notifications.json
index 3a26f7d9..5c8bde90 100644
--- a/web/messages/en/docs/monitor/notifications.json
+++ b/web/messages/en/docs/monitor/notifications.json
@@ -314,7 +314,7 @@
   },
   "aiRewrite": {
     "heading": "Optional AI rewrite",
-    "body1": "Eligible events can be rewritten by an LLM before delivery. Backup results (backup_complete and backup_fail) bypass this rewrite. AI is off by default and runs in the dispatch thread when enabled. If the call fails or times out, the original template text is used.",
+    "body1": "Eligible events can be rewritten by an LLM before delivery. Backup results (backup_complete and backup_fail) are not rewritten; with AI Suggestions on, the AI only appends an explanation when a backup reports errors. AI is off by default and runs in the dispatch thread when enabled. If the call fails or times out, the original template text is used.",
     "body2": "Six providers are supported (OpenAI, Anthropic, Google Gemini, Groq, OpenRouter and local Ollama), with per-channel detail level (brief, standard, detailed), output language, prompt mode (default or custom) and an optional custom prompt. Full configuration walk-through, captures and prompt examples live in the dedicated AI Assistant page.",
     "privacyTitle": "Privacy note",
     "privacyBody": "AI requests can include the title, severity, rendered body, system/custom prompt and collected context, including hostnames, IPs, usernames, errors and journal lines. They go to the selected endpoint. Ollama keeps processing local only with both a local server and a local model; a remote server or cloud model sends data outside the host. Disable rewriting or use a controlled local endpoint and model when event content must stay on the network."
diff --git a/web/messages/en/docs/security/ssl-letsencrypt.json b/web/messages/en/docs/security/ssl-letsencrypt.json
index b5a65563..03be0a63 100644
--- a/web/messages/en/docs/security/ssl-letsencrypt.json
+++ b/web/messages/en/docs/security/ssl-letsencrypt.json
@@ -12,13 +12,13 @@
   },
   "intro": {
     "title": "What this page covers",
-    "body": "How to switch the Monitor from HTTP to HTTPS in the Settings → Security panel, what each certificate source actually points at on disk, how the Proxmox option behaves when the host has a Let's Encrypt cert, and how to set custom paths if you manage certs outside Proxmox."
+    "body": "How to switch the Monitor from HTTP to HTTPS in the Admin → Security panel, what each certificate source actually points at on disk, how the Proxmox option behaves when the host has a Let's Encrypt cert, and how to set custom paths if you manage certs outside Proxmox."
   },
   "wheresetting": {
     "heading": "Where the setting lives",
-    "body": "Open the Monitor and go to Settings → Security → HTTPS / SSL. The panel shows the current state (HTTP or HTTPS), and on a host without HTTPS yet it lists the certificate sources the Monitor was able to detect.",
-    "imageAlt": "Settings → Security → HTTPS / SSL panel in the Monitor showing the HTTPS-off state, the auto-detected Proxmox certificate (subject, issuer, expiry) and the Use Custom Certificate option",
-    "caption": "Settings → Security → HTTPS / SSL panel before activation. The Monitor surfaces the cert Proxmox itself is using and offers it as a one-click source."
+    "body": "Open the Monitor and go to Admin → Security → SSL / HTTPS. The panel shows the current state (HTTP or HTTPS), and on a host without HTTPS yet it lists the certificate sources the Monitor was able to detect.",
+    "imageAlt": "Admin → Security → SSL / HTTPS panel in the Monitor showing the HTTPS-off state, the auto-detected Proxmox certificate (subject, issuer, expiry) and the Use Custom Certificate option",
+    "caption": "Admin → Security → SSL / HTTPS panel before activation. The Monitor surfaces the cert Proxmox itself is using and offers it as a one-click source."
   },
   "twoways": {
     "heading": "Two ways to enable HTTPS",
@@ -117,7 +117,7 @@
   },
   "switchToHttps": {
     "heading": "Switch the Monitor to HTTPS",
-    "bodyRich": "Once /etc/pve/local/pveproxy-ssl.pem is signed by Let's Encrypt, the Monitor side is one click: open Settings → Security → HTTPS / SSL, confirm the issuer shown in the detected-certificate panel reads Let's Encrypt (and not the local Proxmox CA), and click Use Proxmox Certificate. The Monitor service restarts and the next browser load is HTTPS on port 8008 — no certificate warning, since the chain is publicly trusted. Later Proxmox ACME renewals are validated and selected during the next new TLS connection; there is no recurring certificate poller or renewal-time service restart. Update certificate remains available in Security as an explicit diagnostic and recovery action."
+    "bodyRich": "Once /etc/pve/local/pveproxy-ssl.pem is signed by Let's Encrypt, the Monitor side is one click: open Admin → Security → SSL / HTTPS, confirm the issuer shown in the detected-certificate panel reads Let's Encrypt (and not the local Proxmox CA), and click Use Proxmox Certificate. The Monitor service restarts and the next browser load is HTTPS on port 8008 — no certificate warning, since the chain is publicly trusted. Later Proxmox ACME renewals are validated and selected during the next new TLS connection; there is no recurring certificate poller or renewal-time service restart. Update certificate remains available in Security as an explicit diagnostic and recovery action."
   },
   "custom": {
     "heading": "Custom certificate — when to use it",
diff --git a/web/messages/en/docs/settings/beta-program.json b/web/messages/en/docs/settings/beta-program.json
index a926b253..f3590512 100644
--- a/web/messages/en/docs/settings/beta-program.json
+++ b/web/messages/en/docs/settings/beta-program.json
@@ -51,7 +51,7 @@
     "intro": "Both directions go through the same installer — they are not special-cased uninstall + reinstall flows. Each direction simply runs the channel's own installer, which is designed to be idempotent and safe to re-execute over an existing install. Practical consequences:",
     "items": [
       "Your existing config, ProxMenux Monitor login (auth.json), notification channels, post-install registry, custom thresholds — none of those are touched. The channel switch only swaps the script tree and the binary AppImage.",
-      "A Monitor that was active before the switch stays active after; one that was deactivated stays deactivated.",
+      "The installer restarts the Monitor service during the switch, so a Monitor you had deactivated runs again. Deactivate it again from Settings if needed.",
       "ProxMenux Monitor auth.json, API tokens and the JWT secret are preserved across channel changes. Sessions don't get logged out.",
       "You can flip channels as many times as you like — the dialog will accept it every time and rerun the installer. No restart of Proxmox is required.",
       "The channel switch installer replaces ProxMenux's own script tree, so it cannot run inside the Monitor's browser terminal (that PTY would be torn down mid-update and leave the dialog stranded). If you launch the option from there, the confirmation is replaced by an informational msgbox showing the canonical one-line installer for the target channel; run that command from an SSH session or the Proxmox host console."
diff --git a/web/messages/en/docs/settings/index.json b/web/messages/en/docs/settings/index.json
index 9fd02ecc..664f5966 100644
--- a/web/messages/en/docs/settings/index.json
+++ b/web/messages/en/docs/settings/index.json
@@ -60,6 +60,12 @@
         "description": "Switch between Stable (main) and Beta (develop) in either direction from a single dialog. Runs the official installer for the chosen channel; idempotent and always visible.",
         "badge": "Always available"
       },
+      {
+        "icon": "Languages",
+        "href": "/docs/settings/change-language",
+        "title": "Change Language",
+        "description": "Switch the ProxMenux interface between eight languages. The choice is saved to config.json and the menu reloads in the new language."
+      },
       {
         "icon": "Info",
         "href": "/docs/settings/show-version-information",
@@ -70,7 +76,7 @@
         "icon": "Trash2",
         "href": "/docs/settings/uninstall-proxmenux",
         "title": "Uninstall ProxMenux",
-        "description": "Remove ProxMenux and (optionally) its dependencies. Also restores /root/.bashrc and /etc/motd backups created at install time."
+        "description": "Remove ProxMenux and (optionally) its dependencies. Also restores /root/.bashrc and /etc/motd backups created by Post-Install."
       }
     ]
   },
diff --git a/web/messages/en/docs/settings/proxmenux-monitor.json b/web/messages/en/docs/settings/proxmenux-monitor.json
index f5284f6f..c7aaa875 100644
--- a/web/messages/en/docs/settings/proxmenux-monitor.json
+++ b/web/messages/en/docs/settings/proxmenux-monitor.json
@@ -33,7 +33,7 @@
   },
   "warnConditional": {
     "title": "Settings options only visible when Monitor is installed",
-    "body": "The two menu entries described below only appear in Settings if proxmenux-monitor.service is registered with systemd. If you don't see them, the Monitor wasn't installed (it's optional during ProxMenux install). To add it later, reinstall ProxMenux with the latest version and pick the Monitor option."
+    "body": "The three menu entries described below only appear in Settings if proxmenux-monitor.service is registered with systemd. The installer always sets it up, so if you don't see them the Monitor step failed during install. Run the official installer again to retry it."
   },
   "toggle": {
     "heading": "Activate / Deactivate",
@@ -92,8 +92,8 @@
   },
   "troubleshoot": {
     "heading": "Troubleshooting",
-    "missingTitle": "Both menu options are missing",
-    "missingBody": "The Monitor isn't registered with systemd. Confirm with systemctl list-unit-files | grep proxmenux-monitor — if there's no output, reinstall ProxMenux from the official installer with the latest version and pick the Monitor option during install.",
+    "missingTitle": "The Monitor menu options are missing",
+    "missingBody": "The Monitor isn't registered with systemd. Confirm with systemctl list-unit-files | grep proxmenux-monitor — if there's no output, run the official installer again; it installs the Monitor on every run.",
     "unreachableTitle": "Service activated but the URL is unreachable",
     "unreachableBody": "The Settings status output gives you the IP. Confirm:",
     "unreachableCmd": "ss -tlnp | grep 8008                # is anything listening?\njournalctl -u proxmenux-monitor -n 100 --no-pager  # recent errors\ncurl -sI http://localhost:8008      # local reachability",
diff --git a/web/messages/en/docs/settings/uninstall-proxmenux.json b/web/messages/en/docs/settings/uninstall-proxmenux.json
index 66942d3e..98f62c41 100644
--- a/web/messages/en/docs/settings/uninstall-proxmenux.json
+++ b/web/messages/en/docs/settings/uninstall-proxmenux.json
@@ -12,7 +12,7 @@
   },
   "scopeWarn": {
     "title": "Affects the ProxMenux install only — not your VMs / CTs / storage",
-    "body": "Uninstalling ProxMenux removes the ProxMenux scripts, the menu launcher, the optional Monitor service and any backups ProxMenux took of system files when you installed it. It does not touch your VMs, containers, storage, network configuration, Fail2Ban, NVIDIA driver or anything else you installed via ProxMenux. Those stay as they are."
+    "body": "Uninstalling ProxMenux removes the ProxMenux scripts, the menu launcher, the Monitor service and restores the /root/.bashrc and /etc/motd backups that Post-Install took, if they exist. It does not touch your VMs, containers, storage, network configuration, Fail2Ban, NVIDIA driver or anything else you installed via ProxMenux. Those stay as they are."
   },
   "flow": {
     "heading": "The uninstall flow",
diff --git a/web/messages/en/docs/utils/export-vm.json b/web/messages/en/docs/utils/export-vm.json
index a5eaafad..81a2704c 100644
--- a/web/messages/en/docs/utils/export-vm.json
+++ b/web/messages/en/docs/utils/export-vm.json
@@ -59,8 +59,8 @@
   },
   "package": {
     "heading": "What ends up in the package",
-    "intro": "For an OVA / OVF named myvm-100-2026-04-26_18-30-15:",
-    "code": "# OVA mode (single .ova file is a TAR of):\nmyvm-100-2026-04-26_18-30-15.ovf      # XML descriptor (DMTF OVF 1.0 + RASD + VSSD)\nmyvm-100-2026-04-26_18-30-15.mf       # SHA1 manifest\nmyvm-100-2026-04-26_18-30-15-disk0.vmdk\nmyvm-100-2026-04-26_18-30-15-disk1.vmdk\n...\n\n# OVF mode (a directory):\nmyvm-100-2026-04-26_18-30-15-ovf/\n├── myvm-100-2026-04-26_18-30-15.ovf\n├── myvm-100-2026-04-26_18-30-15.mf\n├── myvm-100-2026-04-26_18-30-15-disk0.vmdk\n└── myvm-100-2026-04-26_18-30-15-disk1.vmdk"
+    "intro": "For an OVA / OVF named myvm-100-20260426_183015:",
+    "code": "# OVA mode (single .ova file is a TAR of):\nmyvm-100-20260426_183015.ovf      # XML descriptor (DMTF OVF 1.0 + RASD + VSSD)\nmyvm-100-20260426_183015.mf       # SHA1 manifest\nmyvm-100-20260426_183015-disk1.vmdk\nmyvm-100-20260426_183015-disk2.vmdk\n...\n\n# OVF mode (a directory):\nmyvm-100-20260426_183015-ovf/\n├── myvm-100-20260426_183015.ovf\n├── myvm-100-20260426_183015.mf\n├── myvm-100-20260426_183015-disk1.vmdk\n└── myvm-100-20260426_183015-disk2.vmdk"
   },
   "exported": {
     "heading": "What gets exported",
@@ -90,7 +90,7 @@
   "manifest": {
     "heading": "SHA1 manifest",
     "intro": "Every package includes a .mf file with SHA1 hashes of the OVF descriptor and each VMDK:",
-    "code": "SHA1(myvm-100-2026-04-26_18-30-15.ovf)= 3a7bd3e2360a3d29eea436fcfb7e44c735d117c9\nSHA1(myvm-100-2026-04-26_18-30-15-disk0.vmdk)= b7e23ec29af22b0b4e41da31e868d57226121c84\nSHA1(myvm-100-2026-04-26_18-30-15-disk1.vmdk)= a87ff679a2f3e71d9181a67b7542122c",
+    "code": "SHA1(myvm-100-20260426_183015.ovf)= 3a7bd3e2360a3d29eea436fcfb7e44c735d117c9\nSHA1(myvm-100-20260426_183015-disk1.vmdk)= b7e23ec29af22b0b4e41da31e868d57226121c84\nSHA1(myvm-100-20260426_183015-disk2.vmdk)= a87ff679a2f3e71d9181a67b7542122c",
     "outro": "Optional in the OVF spec — many readers ignore it — but useful for verifying the package made it across intact. Verify manually with sha1sum -c file.mf."
   },
   "import": {
diff --git a/web/messages/en/docs/utils/index.json b/web/messages/en/docs/utils/index.json
index 5d9388df..c41da87e 100644
--- a/web/messages/en/docs/utils/index.json
+++ b/web/messages/en/docs/utils/index.json
@@ -38,7 +38,7 @@
       "body": "Keep the host healthy: CLI tooling, regular updates, and the major-version upgrade when it's time.",
       "bullets": [
         "System Utilities Installer (25 CLI tools)",
-        "Proxmox System Update (apt + dist-upgrade)",
+        "Proxmox System Update (apt + full-upgrade)",
         "PVE 8 → PVE 9 major upgrade"
       ]
     },
@@ -58,11 +58,11 @@
   },
   "isoSection": {
     "heading": "ISO creation",
-    "body": "Build a fresh, up-to-date Windows installation ISO directly on the Proxmox host. The script automates the whole UUP Dump flow: dependency install (aria2c, cabextract, wimlib-imagex, …), parallelised download of the update files via aria2, and conversion into a single bootable ISO that lands in /var/lib/vz/template/iso ready to attach to a VM.",
+    "body": "Build a fresh, up-to-date Windows installation ISO directly on the Proxmox host. The script automates the whole UUP Dump flow: dependency install (aria2c, cabextract, wimlib-imagex, …), parallelised download of the update files via aria2, and conversion into a single bootable ISO saved to the detected Proxmox ISO storage (fallback /var/lib/vz/template/iso), ready to attach to a VM.",
     "options": [
       {
         "title": "UUP Dump ISO Creator",
-        "description": "Build an up-to-date Windows installation ISO from a UUP Dump shared link. Dependencies, download and conversion fully automated; the resulting ISO lands in /var/lib/vz/template/iso ready for VM creation.",
+        "description": "Build an up-to-date Windows installation ISO from a UUP Dump shared link. Dependencies, download and conversion fully automated; the resulting ISO is saved to the detected Proxmox ISO storage, ready for VM creation.",
         "href": "/docs/utils/UUp-Dump-ISO-Creator"
       }
     ]
@@ -78,7 +78,7 @@
       },
       {
         "title": "Proxmox System Update",
-        "description": "Repository check + apt update + apt dist-upgrade with reboot prompt. Detects the PVE major version (8 or 9), keeps the configured repositories and asks before switching a host without subscription to no-subscription, then runs autoremove.",
+        "description": "Repository check + apt update + apt full-upgrade with reboot prompt. Detects the PVE major version (8 or 9), keeps the configured repositories and asks before switching a host without subscription to no-subscription, then runs autoremove.",
         "href": "/docs/utils/system-update"
       },
       {
diff --git a/web/messages/en/docs/utils/system-update.json b/web/messages/en/docs/utils/system-update.json
index dffc2bca..d64b4e1b 100644
--- a/web/messages/en/docs/utils/system-update.json
+++ b/web/messages/en/docs/utils/system-update.json
@@ -128,7 +128,7 @@
         "body": "The Enterprise repository is enabled and the host has no active subscription. The worker offers the switch to no-subscription before apt-get update; when it was declined, run the update again and accept it, or disable the Enterprise source in Node → Updates → Repositories and enable the no-subscription one."
       },
       {
-        "title": "dist-upgrade hangs at \"Configuring grub-pc\"",
+        "title": "full-upgrade hangs at \"Configuring grub-pc\"",
         "body": "If an interactive GRUB prompt is visible, it asks which devices should receive the boot loader. The --force-confold option applies to configuration-file decisions, not to this device selection. Use Tab + Space to select the appropriate boot disks, then OK. You can configure this selection beforehand with dpkg-reconfigure grub-pc."
       },
       {
diff --git a/web/messages/en/docs/utils/system-utils.json b/web/messages/en/docs/utils/system-utils.json
index a47a9e04..e15db3ce 100644
--- a/web/messages/en/docs/utils/system-utils.json
+++ b/web/messages/en/docs/utils/system-utils.json
@@ -235,7 +235,7 @@
       {
         "href": "/docs/utils/system-update",
         "label": "Proxmox System Update",
-        "tailRich": " — for the actual apt dist-upgrade on the host."
+        "tailRich": " — for the actual apt full-upgrade on the host."
       },
       {
         "href": "/docs/network/monitoring",
diff --git a/web/messages/en/docs/utils/upgrade-pve8-pve9.json b/web/messages/en/docs/utils/upgrade-pve8-pve9.json
index dff9360c..4b8b0ba3 100644
--- a/web/messages/en/docs/utils/upgrade-pve8-pve9.json
+++ b/web/messages/en/docs/utils/upgrade-pve8-pve9.json
@@ -16,9 +16,9 @@
     "items": [
       "Verified backups of every VM and CT (PBS, vzdump, ZFS snapshots — and verify they restore).",
       "Console / IPMI / iKVM access to the host for when SSH drops mid-upgrade.",
-      "The host already up-to-date on PVE 8.4.x (the script enforces this).",
+      "The host on the latest PVE 8 patch level. The script updates PVE 8 first; it does not check for a specific 8.4.x version.",
       "Ceph (if present) on version 19.x Squid.",
-      "At least 5 GB free on the root filesystem."
+      "At least 5 GB free on the root filesystem, as the Proxmox guide recommends. The script itself only checks for 1 GB in /var/cache/apt/archives."
     ]
   },
   "modeMenu": {
@@ -63,13 +63,13 @@
   },
   "webTerminal": {
     "heading": "Why the web terminal is blocked",
-    "body": "All three upgrade modes detect termproxy / vncshell in the parent process chain and refuse to run. Reason: the Proxmox web terminal is served by pveproxy, which is one of the services that gets upgraded mid-flow. As soon as pveproxy restarts, your terminal session dies — and so does the upgrade script that was running inside it. The host is left half-upgraded, no good way to resume.",
+    "body": "The Automatic and Interactive modes detect termproxy / vncshell in the parent process chain and refuse to run, unless the session comes over SSH or PVE_ALLOW_WEBTERM=1 is set. The pre-check and the manual guide change nothing and run anywhere. Reason: the Proxmox web terminal is served by pveproxy, which is one of the services that gets upgraded mid-flow. As soon as pveproxy restarts, your terminal session dies — and so does the upgrade script that was running inside it. The host is left half-upgraded, no good way to resume.",
     "warningTitle": "Use SSH or physical / IPMI / iKVM console",
-    "warningBody": "SSH is the most common choice. Open it inside tmux or screen so the upgrade survives a brief network blip. The very first thing the script does is print the tmux/screen reminder if it detects you're in a plain SSH shell."
+    "warningBody": "SSH is the most common choice. Open it inside tmux or screen so the upgrade survives a brief network blip. The upgrade confirmation dialog reminds you to run it from the console or from SSH inside tmux/screen."
   },
   "auto": {
     "heading": "Automatic / Unattended mode",
-    "intro": "Package and configuration-file prompts use the defaults in the table below, and a 401 from the enterprise repository switches to no-subscription without asking. The upgrade confirmation, the PVE 8 update summary, any pre-check repairs and the reboot prompt are still shown.",
+    "intro": "Package and configuration-file prompts use the defaults in the table below. The upgrade confirmation, the PVE 8 update summary, any pre-check repairs and the reboot prompt are still shown.",
     "behaviourHeading": "Auto-mode-specific behaviour",
     "behaviourTable": {
       "settingHeader": "Setting",
@@ -94,8 +94,8 @@
       ]
     },
     "flowHeading": "Auto-mode flow",
-    "flowIntro": "Six sequential phases. Phase 2 updates the PVE 8 packages and phase 3 writes the PVE 9 repository configuration. A failure stops the flow without rolling those changes back; if the PVE 8 update fails or is cancelled, the repositories are left unchanged. Phase 5 runs the PVE 9 dist-upgrade.",
-    "flowDiagram": "1. CHECKS\nBlock termproxy / vncshell; confirm the upgrade; reject PVE 9.\n\n2. PREPARE PVE 8\nscripts/global/update-pve8.sh\nUpdate PVE 8 packages; stop if the update fails or is cancelled.\npve8to9 --full (may offer repairs).\nCheck space and connectivity; Ceph 19.x Squid on hyper-converged nodes.\n\n3. MIGRATE REPOSITORIES\nBookworm → Trixie; write deb822 .sources files.\nComment legacy .list files; apt-get update.\n\n4. VALIDATE THE PVE 9 CANDIDATE\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nStop if the candidate is not 9.x or proxmox-ve would be removed.\n\n5. UPGRADE\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nNo subscription: disable pve-enterprise.sources.\nLog: /var/log/pve8-a-pve9-.log\n\n6. VERIFY AND REBOOT\nEFI: apt install grub-efi-amd64\npve8to9 --full; review results and the reboot prompt.",
+    "flowIntro": "Six sequential phases. Phase 2 switches the PVE 8 repositories to no-subscription (enterprise and Ceph lists disabled, /etc/apt/sources.list rewritten) before it shows the update summary, then updates the packages; phase 3 writes the PVE 9 repository configuration. A failure stops the flow without rolling those changes back; if the PVE 8 update fails or is cancelled, the PVE 9 migration does not start, but the PVE 8 repository changes stay. Phase 5 runs the PVE 9 dist-upgrade.",
+    "flowDiagram": "1. CHECKS\nBlock termproxy / vncshell; confirm the upgrade; reject PVE 9.\n\n2. PREPARE PVE 8\nscripts/global/update-pve8.sh\nSwitch PVE 8 repositories to no-subscription and rewrite sources.list;\nupdate PVE 8 packages; stop if the update fails or is cancelled.\npve8to9 --full (may offer repairs).\nCheck space and connectivity; Ceph 19.x Squid on hyper-converged nodes.\n\n3. MIGRATE REPOSITORIES\nBookworm → Trixie; write deb822 .sources files.\nComment legacy .list files; apt-get update.\n\n4. VALIDATE THE PVE 9 CANDIDATE\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nStop if the candidate is not 9.x or proxmox-ve would be removed.\n\n5. UPGRADE\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nNo subscription: disable pve-enterprise.sources.\nLog: /var/log/pve8-a-pve9-.log\n\n6. VERIFY AND REBOOT\nEFI: apt install grub-efi-amd64\npve8to9 --full; review results and the reboot prompt.",
     "preflightHeading": "Pre-flight: what gets checked",
     "preflightTable": {
       "checkHeader": "Check",
@@ -158,7 +158,7 @@
   },
   "manual": {
     "heading": "Manual upgrade procedure",
-    "intro": "This is what the Manual upgrade guide menu option displays — a faithful mirror of the official Proxmox 8-to-9 upgrade wiki organised in 4 phases. The script shows these commands; you type them yourself in a separate root shell. No automation — full visibility.",
+    "intro": "This follows what the Manual upgrade guide menu option displays, based on the official Proxmox 8-to-9 upgrade wiki and organised in 4 phases. The dpkg answer table adds the wiki's caveats for sshd_config and chrony.conf, which the menu shows as a plain Y. The script shows these commands; you type them yourself in a separate root shell. No automation — full visibility.",
     "rootCalloutTitle": "All commands need root",
     "rootCalloutBody": "Use a root shell for these commands, including redirections and here-documents. If you use sudo, ensure the entire command runs with root privileges.",
     "phase1Heading": "Phase 1 — Preparation",
@@ -230,7 +230,7 @@
   "clusterCeph": {
     "heading": "Cluster & Ceph",
     "clusterCalloutTitle": "Cluster: upgrade nodes one at a time",
-    "clusterCalloutBody": "Detected via pvecm status. Do not upgrade more than one node at a time. Move guests off the node, upgrade, reboot and verify cluster membership before proceeding. During the upgrade, live migration from an older PVE version to a newer one works; the reverse direction is generally unsupported. HA groups migrate automatically to HA rules once all nodes run PVE 9.",
+    "clusterCalloutBody": "Do not upgrade more than one node at a time. Move guests off the node, upgrade, reboot and verify cluster membership before proceeding. During the upgrade, live migration from an older PVE version to a newer one works; the reverse direction is generally unsupported. HA groups migrate automatically to HA rules once all nodes run PVE 9.",
     "cephCalloutTitle": "Ceph: upgrade Ceph FIRST, then Proxmox",
     "cephCalloutBody": "Ceph must already run 19.x Squid before upgrading Proxmox. Follow the official sequence: Quincy 17 → Reef 18 → Squid 19, updating packages and restarting Ceph daemons in the documented rolling order. Verify ceph --version before upgrading each Proxmox node. Follow the full procedure: Ceph Reef to Squid upgrade wiki."
   },
@@ -273,7 +273,7 @@
   },
   "files": {
     "heading": "Files involved",
-    "code": "scripts/utilities/upgrade_pve8_to_pve9.sh                         # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh                                # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh    # 17-step runbook\nscripts/global/update-pve8.sh                                     # PVE 8 worker (final patches)\n/etc/apt/sources.list                                             # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources                           # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources                            # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources                              # created if Ceph\n/etc/apt/sources.list.d/*.list                                    # legacy files commented out / removed\n/var/log/pve8-a-pve9-.log                              # full upgrade log"
+    "code": "scripts/utilities/upgrade_pve8_to_pve9.sh                         # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh                                # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh    # 17-step runbook\nscripts/global/update-pve8.sh                                     # PVE 8 worker (final patches)\n/etc/apt/sources.list                                             # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources                           # created (deb822 PVE 9)\n/etc/apt/sources.list.d/ceph.sources                              # created if Ceph\n/etc/apt/sources.list.d/*.list                                    # legacy files commented out / removed\n/var/log/pve8-a-pve9-.log                              # full upgrade log"
   },
   "references": {
     "heading": "Official references",
diff --git a/web/messages/es/docs/backup-restore/creating-backups.json b/web/messages/es/docs/backup-restore/creating-backups.json
index 21b9f6a2..58660f0f 100644
--- a/web/messages/es/docs/backup-restore/creating-backups.json
+++ b/web/messages/es/docs/backup-restore/creating-backups.json
@@ -21,8 +21,8 @@
     "rows": [
       {
         "entry": "ProxMenux Scripts (TUI)",
-        "path": "menu → Utilities → Host Config Backup",
-        "detail": "Flujo basado en diálogos, amigable por SSH. El menú principal presenta las seis opciones directamente. Usa backup_menu en backup_host.sh."
+        "path": "menu → Host Backup & Restore → Backup host configuration",
+        "detail": "Flujo basado en diálogos, amigable por SSH. La entrada del menú principal abre Host Config Backup / Restore; Backup host configuration lleva a las seis opciones. Usa backup_menu en backup_host.sh."
       },
       {
         "entry": "ProxMenux Monitor (UI Web)",
@@ -83,7 +83,7 @@
   "profiles": {
     "heading": "Perfil Default vs Custom",
     "defaultTitle": "Perfil Default",
-    "defaultBody": "El perfil por defecto es la lista curada de hb_default_profile_paths (documentada en Cómo funciona bajo Categorías de rutas) más cada entrada del fichero de extras persistentes /usr/local/share/proxmenux/backup-extra-paths.txt. El usuario confirma el destino y las opciones de cifrado y el backup continúa sin más selección de rutas.",
+    "defaultBody": "El perfil por defecto es la lista curada de hb_default_profile_paths (documentada en Cómo funciona bajo Categorías de rutas) más cada entrada del fichero de extras persistentes /usr/local/share/proxmenux/backup-extra-paths.txt. El usuario elige el destino y las opciones de cifrado y el backup continúa sin más selección de rutas.",
     "customTitle": "Perfil Custom",
     "customBody": "El perfil Custom abre un checklist mostrando cada ruta del perfil por defecto (sin marcar) y cada extra persistente (premarcado, prefijado con [+]). El usuario marca el conjunto para esa ejecución y puede pulsar Add custom path para añadir una nueva ruta absoluta. Cualquier ruta añadida en línea se persiste en backup-extra-paths.txt para que futuros backups la recojan automáticamente sin volver a añadirla. Quitar la marca a un extra persistente lo desmarca para esa ejecución pero no lo elimina del fichero — la eliminación es una acción Manage custom paths separada, fuera del flujo de backup.",
     "customPickerAlt": "Checklist del perfil Custom mostrando las rutas del perfil por defecto (sin marcar) y los extras persistentes (premarcados con prefijo [+]), más botones para añadir una ruta nueva o confirmar la selección.",
@@ -124,14 +124,7 @@
   },
   "included": {
     "heading": "Qué entra y qué se excluye",
-    "intro": "Cada ruta del perfil resuelto (default + extras persistentes + selección en modo Custom) se copia con rsync -aAXH --numeric-ids. Una lista de exclusiones compartida aplica a cada ruta, y dos directorios llevan exclusiones específicas adicionales.",
-    "globalTitle": "Exclusiones globales (aplican a cada ruta)",
-    "globalItems": [
-      "images/ — dumps de imágenes.",
-      "dump/ — salidas de vzdump.",
-      "tmp/ — ficheros temporales.",
-      "*.log — ficheros de log."
-    ],
+    "intro": "Cada ruta del perfil resuelto (default + extras persistentes + selección en modo Custom) se copia con rsync -aAXH --numeric-ids. No hay una lista de exclusiones global; unos pocos directorios llevan exclusiones específicas.",
     "rootTitle": "Exclusiones de /root/",
     "rootBody": "/root/ forma parte del perfil por defecto para que los scripts y config del usuario entren en el archivo. Se descartan los subpaths volátiles:",
     "rootItems": [
@@ -160,16 +153,16 @@
       "/home/USER — no está en el perfil por defecto. Añadirlo como ruta custom cuando un sistema tenga directorios home de usuario que deban sobrevivir a una restauración."
     ],
     "customPathsTitle": "Cómo se tratan las rutas custom",
-    "customPathsBody": "Una ruta custom añadida en línea en modo Custom o persistida en backup-extra-paths.txt pasa por el mismo pipeline de rsync que las rutas del perfil por defecto. Se aplican las exclusiones globales. Si la ruta custom está bajo /root/ o /usr/local/share/proxmenux/, siguen aplicando las exclusiones específicas de arriba. Cada ruta archivada — default o custom — queda registrada en metadata/paths_archived.txt. Las rutas que no existen en el origen se registran en metadata/missing_paths.txt sin detener el backup."
+    "customPathsBody": "Una ruta custom añadida en línea en modo Custom o persistida en backup-extra-paths.txt pasa por el mismo pipeline de rsync que las rutas del perfil por defecto. Si la ruta custom está bajo /root/ o /usr/local/share/proxmenux/, siguen aplicando las exclusiones específicas de arriba. Cada ruta archivada — default o custom — queda registrada en metadata/selected_paths.txt. Las rutas que no existen en el origen se registran en metadata/missing_paths.txt sin detener el backup, y las que fallan al copiarse en metadata/failed_paths.txt."
   },
   "archiveStructure": {
     "heading": "Estructura del archivo",
     "intro": "El directorio de staging que produce cada backend sigue el mismo layout con independencia del destino. El tarball, el .pxar de PBS o el archivo Borg almacenan este árbol verbatim.",
-    "tree": "backup-[timestamp]/\n├── manifest.json                  # estado estructurado del host (kernel_params, hardware, storage, guests, components, source_host)\n├── metadata/\n│   ├── packages.manual.list       # salida de apt-mark showmanual\n│   ├── run_info.env               # hostname, timestamp, versión del kernel, método pmxcfs\n│   ├── paths_archived.txt         # lista exacta de rutas que llegaron a rootfs/\n│   └── missing_paths.txt          # rutas del perfil ausentes en el origen\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, /etc/apt, ...\n    ├── root/                      # /root sin subpaths volátiles\n    ├── usr/local/                 # /usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux (sólo estado)\n    └── var/                       # /var/lib/pve-cluster (config.db vía sqlite3 .backup), /var/spool/cron/crontabs"
+    "tree": "backup-[timestamp]/\n├── manifest.json                  # estado estructurado del host (kernel_params, hardware, storage, guests, components, source_host)\n├── metadata/\n│   ├── packages.manual.list       # salida de apt-mark showmanual\n│   ├── run_info.env               # hostname, timestamp, versión del kernel, método pmxcfs\n│   ├── selected_paths.txt         # lista exacta de rutas que llegaron a rootfs/\n│   └── missing_paths.txt          # rutas del perfil ausentes en el origen\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, /etc/apt, ...\n    ├── root/                      # /root sin subpaths volátiles\n    ├── usr/local/                 # /usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux (sólo estado)\n    └── var/                       # /var/lib/pve-cluster (config.db vía sqlite3 .backup), /var/spool/cron/crontabs"
   },
   "confirmation": {
     "heading": "Resumen de confirmación",
-    "body": "Antes de que el backend escriba nada en el destino, ProxMenux muestra un diálogo resumen con el destino, el backup ID o nombre del archivo, el estado del cifrado y la lista de rutas que se copian. Cancelar aquí aborta el backup limpiamente — el directorio de staging se elimina por el hook trap definido en la función backend y ningún dato parcial llega al destino."
+    "body": "Antes de que el backend escriba nada en el destino, ProxMenux imprime en la terminal un resumen con el destino, el backup ID o nombre del archivo, el estado del cifrado y la lista de rutas que se copian. En este punto no hay paso de confirmar o cancelar; la copia empieza justo después. Si el backup falla o se interrumpe, el directorio de staging se elimina por el hook trap definido en la función backend y ningún dato parcial llega al destino."
   },
   "writing": {
     "heading": "Escritura en el destino",
diff --git a/web/messages/es/docs/backup-restore/destinations/local.json b/web/messages/es/docs/backup-restore/destinations/local.json
index 79d75136..5eb444f4 100644
--- a/web/messages/es/docs/backup-restore/destinations/local.json
+++ b/web/messages/es/docs/backup-restore/destinations/local.json
@@ -18,11 +18,11 @@
   },
   "targetConfig": {
     "heading": "Configurar el directorio de destino",
-    "intro": "El destino local es un único directorio de destino persistido — no una lista. ProxMenux guarda la elección del usuario en /usr/local/share/proxmenux/local-target.conf y la lee en cada backup. Cuando no hay ningún destino configurado, se usa el valor por defecto HB_LOCAL_TARGET_DEFAULT = /var/lib/vz/dump (el mismo directorio que Proxmox utiliza para las salidas de vzdump). El destino se configura desde Configure backup destinations → Local destinations:",
+    "intro": "El destino local es un único directorio de destino persistido — no una lista. ProxMenux guarda la elección del usuario en /usr/local/share/proxmenux/local-target.conf y la lee en cada backup. Cuando no hay ningún destino configurado, se usa el valor por defecto HB_LOCAL_TARGET_DEFAULT = /var/lib/vz/dump (el mismo directorio que Proxmox utiliza para las salidas de vzdump). El destino se configura desde Configure backup destinations → Local archive targets → Manage local backup target:",
     "options": [
       "Usar el valor por defecto (/var/lib/vz/dump). El almacenamiento local de Proxmox. Está presente en cualquier instalación de Proxmox; el archivo queda junto a las salidas de vzdump y lo detecta automáticamente la pestaña Backups del Monitor.",
-      "Introducir una ruta personalizada. Cualquier ruta absoluta del sistema de ficheros vale: un montaje NFS, un share SMB montado por fstab, un dataset ZFS dedicado, un segundo disco interno. ProxMenux valida que la ruta existe y es un directorio antes de persistirla.",
-      "Elegir una unidad USB. Abre el submenú USB (más abajo), que detecta los dispositivos extraíbles y ofrece montar o formatear uno."
+      "Usar una ruta personalizada. Cualquier ruta absoluta del sistema de ficheros vale: un montaje NFS, un share SMB montado por fstab, un dataset ZFS dedicado, un segundo disco interno. ProxMenux valida que la ruta existe y es un directorio antes de persistirla.",
+      "Usar un disco USB. Abre el submenú USB (más abajo), que detecta los dispositivos extraíbles y ofrece montar o formatear uno."
     ]
   },
   "usbFlow": {
@@ -38,7 +38,7 @@
       {
         "state": "unmounted",
         "shown": "Tamaño · label · [fstype] · (no montada — se montará)",
-        "action": "Hay un filesystem pero no está montado. Al confirmar, ProxMenux ejecuta hb_mount_usb_partition: crea /mnt/backup-LABEL (o una ruta basada en UUID si no hay label), monta la partición y persiste el punto de montaje como destino local."
+        "action": "Hay un filesystem pero no está montado. Al confirmar, ProxMenux ejecuta hb_mount_usb_partition: crea /mnt/proxmenux-backup-LABEL (UUID o nombre del dispositivo si no hay label), monta la partición y persiste el punto de montaje como destino local."
       },
       {
         "state": "empty",
@@ -63,7 +63,7 @@
   },
   "sidecar": {
     "heading": "El sidecar JSON",
-    "intro": "Cada backup local con éxito produce un fichero compañero: HOSTNAME-TIMESTAMP.tar.zst.proxmenux.json, escrito junto al archivo por hb_write_archive_sidecar. Este pequeño fichero JSON permite al Monitor de ProxMenux identificar el archivo como un backup de host de ProxMenux incluso si posteriormente se mueve, renombra o archiva en otro lugar.",
+    "intro": "Cada backup local con éxito produce un fichero compañero: hostcfg-HOSTNAME-TIMESTAMP.tar.zst.proxmenux.json, escrito junto al archivo por hb_write_archive_sidecar. Este pequeño fichero JSON permite al Monitor de ProxMenux identificar el archivo como un backup de host de ProxMenux incluso si posteriormente se mueve, renombra o archiva en otro lugar.",
     "contentTitle": "Contenido del sidecar",
     "contentBody": "El sidecar almacena la versión de schema, si el backup viene de una ejecución interactiva o de un trabajo programado (kind), el ID de trabajo para las ejecuciones programadas, el modo de perfil usado (default o custom), el hostname del origen, el basename original del archivo, un timestamp de creación en ISO-8601 y el tamaño del archivo en bytes.",
     "whyBody": "La pestaña Backups del Monitor escanea los directorios locales configurados buscando sidecars *.proxmenux.json — no ficheros *.tar.zst — porque un .tar.zst sin sidecar podría no ser un backup de ProxMenux en absoluto. El escaneo es rápido (los JSON son diminutos) y el emparejamiento es estable frente a renombrados del archivo mientras el sidecar se renombre en paralelo."
diff --git a/web/messages/es/docs/backup-restore/destinations/pbs.json b/web/messages/es/docs/backup-restore/destinations/pbs.json
index 5a209e6b..2f3bbe8f 100644
--- a/web/messages/es/docs/backup-restore/destinations/pbs.json
+++ b/web/messages/es/docs/backup-restore/destinations/pbs.json
@@ -33,7 +33,7 @@
       {
         "source": "Configuración manual de ProxMenux",
         "path": "/usr/local/share/proxmenux/pbs-manual-configs.txt + pbs-pass-NAME.txt + pbs-fingerprint-NAME.txt",
-        "content": "Se añade desde Configure backup destinations → PBS destinations → Add PBS. Pregunta por un nombre, usuario (root@pam o user@pbs!token), host o IP, datastore y contraseña. La contraseña se re-pregunta ante entrada vacía — un guardado vacío persistiría silenciosamente y cada backup posterior fallaría con un error de autenticación opaco. Este camino se usa cuando el PBS objetivo no está registrado como storage de Proxmox."
+        "content": "Se añade desde Configure backup destinations → Proxmox Backup Server (PBS) destinations → + Add new PBS manually. Pregunta por un nombre, usuario (root@pam o user@pbs!token), host o IP, datastore y contraseña. La contraseña se re-pregunta ante entrada vacía — un guardado vacío persistiría silenciosamente y cada backup posterior fallaría con un error de autenticación opaco. Este camino se usa cuando el PBS objetivo no está registrado como storage de Proxmox."
       }
     ],
     "menuTitle": "Menú de selección",
diff --git a/web/messages/es/docs/backup-restore/how-it-works.json b/web/messages/es/docs/backup-restore/how-it-works.json
index 7cfe2eaa..71322949 100644
--- a/web/messages/es/docs/backup-restore/how-it-works.json
+++ b/web/messages/es/docs/backup-restore/how-it-works.json
@@ -20,7 +20,7 @@
     "heading": "Layout del archivo",
     "intro": "Cada archivo respeta el mismo layout con independencia del destino. El subdirectorio metadata/ contiene los bloques estructurados; el subdirectorio rootfs/ contiene el backup del sistema de ficheros.",
     "treeCaption": "El directorio de staging generado durante un backup. Todos los destinos reciben el mismo árbol (adaptado a su formato nativo: tar para local, chunks PBS para PBS, segmentos borg para Borg).",
-    "tree": "backup-[timestamp]/\n├── manifest.json                  # estado estructurado del host\n├── metadata/\n│   ├── packages.manual.list       # apt-mark showmanual\n│   ├── run_info.env               # identidad de la ejecución + versión del kernel + método pmxcfs\n│   ├── paths_archived.txt         # lista exacta de rutas que llegaron a rootfs/\n│   └── missing_paths.txt          # rutas del perfil ausentes en el origen\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, …\n    ├── root/                      # /root (con subdirs volátiles excluidos)\n    ├── usr/local/                 # /usr/local/bin, /usr/local/share/proxmenux, …\n    └── var/                       # /var/lib/pve-cluster (config.db vía sqlite3 .backup), /var/spool/cron/…"
+    "tree": "backup-[timestamp]/\n├── manifest.json                  # estado estructurado del host\n├── metadata/\n│   ├── packages.manual.list       # apt-mark showmanual\n│   ├── run_info.env               # identidad de la ejecución + versión del kernel + método pmxcfs\n│   ├── selected_paths.txt         # lista exacta de rutas que llegaron a rootfs/\n│   └── missing_paths.txt          # rutas del perfil ausentes en el origen\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, …\n    ├── root/                      # /root (con subdirs volátiles excluidos)\n    ├── usr/local/                 # /usr/local/bin, /usr/local/share/proxmenux, …\n    └── var/                       # /var/lib/pve-cluster (config.db vía sqlite3 .backup), /var/spool/cron/…"
   },
   "rootfs": {
     "heading": "El bloque rootfs",
@@ -176,7 +176,7 @@
         "stage": "3",
         "name": "Preparación de pending",
         "reads": "rootfs/ (rutas reboot + dangerous)",
-        "action": "_rs_prepare_pending_restore deja las rutas de riesgo preparadas bajo /var/lib/proxmenux/pending-restore/, escribe plan.env, apply-on-boot.list y rs-skip-paths.txt, y habilita proxmenux-restore-onboot.service para que dispare en el siguiente arranque."
+        "action": "_rs_prepare_pending_restore deja las rutas de riesgo preparadas bajo /var/lib/proxmenux/restore-pending/<restore_id>/ (enlazado como current), escribe plan.env, apply-on-boot.list y rs-skip-paths.txt, y habilita proxmenux-restore-onboot.service para que dispare en el siguiente arranque."
       },
       {
         "stage": "3b",
diff --git a/web/messages/es/docs/backup-restore/index.json b/web/messages/es/docs/backup-restore/index.json
index dc1c3451..f216d571 100644
--- a/web/messages/es/docs/backup-restore/index.json
+++ b/web/messages/es/docs/backup-restore/index.json
@@ -50,7 +50,7 @@
     "heading": "Dos interfaces, un único backend",
     "intro": "Cada paso del flujo de backup y restauración está disponible desde dos puntos de entrada. Ambos invocan la misma librería de shell, producen archivos idénticos y leen el mismo registro de trabajos.",
     "cliLabel": "ProxMenux Scripts (TUI)",
-    "cliDetail": "menu → Utilities →\nHost Backup / Restore\n\nFlujo basado en diálogos,\namigable por SSH, admite\nejecución desatendida\ndesde scripts.",
+    "cliDetail": "menu →\nHost Backup & Restore\n\nFlujo basado en diálogos,\namigable por SSH, admite\nejecución desatendida\ndesde scripts.",
     "webLabel": "ProxMenux Monitor (Web UI)",
     "webDetail": "Pestaña Backups en la\ninterfaz Web del Monitor.\n\nRestauración con un clic,\nlog en vivo, integrado con\nlas notificaciones y con\nel visor de rollback."
   },
diff --git a/web/messages/es/docs/backup-restore/restoring.json b/web/messages/es/docs/backup-restore/restoring.json
index 5f3ea69c..dc4aab3f 100644
--- a/web/messages/es/docs/backup-restore/restoring.json
+++ b/web/messages/es/docs/backup-restore/restoring.json
@@ -92,11 +92,11 @@
   "fullFlow": {
     "heading": "El flujo completo de restauración",
     "intro": "El pipeline completo desde la selección del archivo hasta un host restaurado y operativo. Cada etapa alimenta a la siguiente; el estado escrito por etapas anteriores lo consumen las posteriores.",
-    "diagram": "┌─────────────────────────────────────────────────────────────────┐\n│  Archivo → staging_root/                                        │\n│    manifest.json   metadata/   rootfs/                          │\n└──────────────────────────────┬──────────────────────────────────┘\n                               │\n                               ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  1. hb_compat_check                                     │\n   │     · drift de hardware → RS_SKIP_PATHS                 │\n   │     · dirección kernel → same / bk_newer / bk_older     │\n   │     · plan de remapeo NIC                               │\n   │     · plan de hidratación (sólo bk_older)               │\n   │     · plan de rollback                                  │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  2. Diálogo de confirmación                             │\n   │     Muestra: rutas hot, rutas pending, drift,           │\n   │     hidratación, remapeo NIC, nota cross-kernel,        │\n   │     preview de reinstalaciones                          │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │ (aceptado)\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  3. Aplicar rutas hot → EN VIVO                         │\n   │     _rs_apply rsyncea cada ruta hot                     │\n   │     desde staging_root/rootfs a /                       │\n   │     (salta rutas en RS_SKIP_PATHS)                      │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4. _rs_prepare_pending_restore                         │\n   │     /var/lib/proxmenux/pending-restore/                 │\n   │       ├── apply-on-boot.list                            │\n   │       ├── plan.env                                      │\n   │       ├── rs-skip-paths.txt                             │\n   │       └── rootfs/ (rutas diferidas)                     │\n   │     Habilita proxmenux-restore-onboot.service           │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4b. _rs_import_data_pools                              │\n   │      Importa pools ZFS no-raíz cuyos discos             │\n   │      están todos presentes; retry con -f en             │\n   │      foreign hostid; skip con warning si faltan discos. │\n   │      Log: /var/log/proxmenux/restore-datapools-*.log    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  5. _rs_run_complete_extras                             │\n   │     packages.manual.list                                │\n   │       → filtro cascade-safe                             │\n   │       → apt-get install                                 │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  6. REINICIO                                            │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  7. apply_pending_restore.sh (arranque temprano)        │\n   │     Lee plan.env                                        │\n   │     Aplica apply-on-boot.list (filtrado)                │\n   │     Instala la unit systemd de postboot                 │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  8. apply_cluster_postboot.sh (~10 minutos)             │\n   │     Corre cuando pve-cluster está arriba                │\n   │     · Aplica entradas de /etc/pve a pmxcfs              │\n   │     · update-initramfs -u -k all                        │\n   │     · update-grub / proxmox-boot-tool refresh           │\n   │     · component --auto-reinstall (nvidia, coral, ...)   │\n   │     · Comprobación de sanidad de arranque               │\n   │     · Notificación: \"Host restore finished\"             │\n   └─────────────────────────────────────────────────────────┘"
+    "diagram": "┌─────────────────────────────────────────────────────────────────┐\n│  Archivo → staging_root/                                        │\n│    manifest.json   metadata/   rootfs/                          │\n└──────────────────────────────┬──────────────────────────────────┘\n                               │\n                               ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  1. hb_compat_check                                     │\n   │     · drift de hardware → RS_SKIP_PATHS                 │\n   │     · dirección kernel → same / bk_newer / bk_older     │\n   │     · plan de remapeo NIC                               │\n   │     · plan de hidratación (sólo bk_older)               │\n   │     · plan de rollback                                  │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  2. Diálogo de confirmación                             │\n   │     Muestra: rutas hot, rutas pending, drift,           │\n   │     hidratación, remapeo NIC, nota cross-kernel,        │\n   │     preview de reinstalaciones                          │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │ (aceptado)\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  3. Aplicar rutas hot → EN VIVO                         │\n   │     _rs_apply rsyncea cada ruta hot                     │\n   │     desde staging_root/rootfs a /                       │\n   │     (salta rutas en RS_SKIP_PATHS)                      │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4. _rs_prepare_pending_restore                         │\n   │     /var/lib/proxmenux/restore-pending/'<'id'>'/            │\n   │       ├── apply-on-boot.list                            │\n   │       ├── plan.env                                      │\n   │       ├── rs-skip-paths.txt                             │\n   │       └── rootfs/ (rutas diferidas)                     │\n   │     Habilita proxmenux-restore-onboot.service           │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4b. _rs_import_data_pools                              │\n   │      Importa pools ZFS no-raíz cuyos discos             │\n   │      están todos presentes; retry con -f en             │\n   │      foreign hostid; skip con warning si faltan discos. │\n   │      Log: /var/log/proxmenux/restore-datapools-*.log    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  5. _rs_run_complete_extras                             │\n   │     packages.manual.list                                │\n   │       → filtro cascade-safe                             │\n   │       → apt-get install                                 │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  6. REINICIO                                            │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  7. apply_pending_restore.sh (arranque temprano)        │\n   │     Lee plan.env                                        │\n   │     Aplica apply-on-boot.list (filtrado)                │\n   │     Instala la unit systemd de postboot                 │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  8. apply_cluster_postboot.sh (~10 minutos)             │\n   │     Corre cuando pve-cluster está arriba                │\n   │     · Aplica entradas de /etc/pve a pmxcfs              │\n   │     · update-initramfs -u -k all                        │\n   │     · update-grub / proxmox-boot-tool refresh           │\n   │     · component --auto-reinstall (nvidia, coral, ...)   │\n   │     · Comprobación de sanidad de arranque               │\n   │     · Notificación: \"Host restore finished\"             │\n   └─────────────────────────────────────────────────────────┘"
   },
   "pendingMachinery": {
     "heading": "La maquinaria del pending-restore",
-    "intro": "Las rutas clasificadas como reboot o dangerous, y las escrituras de hidratación, se preparan para el siguiente arranque en lugar de aplicarse en vivo. Esto se hace mediante un pequeño conjunto autocontenido de ficheros bajo /var/lib/proxmenux/pending-restore/:",
+    "intro": "Las rutas clasificadas como reboot o dangerous, y las escrituras de hidratación, se preparan para el siguiente arranque en lugar de aplicarse en vivo. Esto se hace mediante un pequeño conjunto autocontenido de ficheros bajo /var/lib/proxmenux/restore-pending/<restore_id>/:",
     "rows": [
       {
         "file": "apply-on-boot.list",
@@ -115,7 +115,7 @@
         "content": "Los ficheros preparados en sí — los bytes exactos que se colocarán. Se mantienen en el mismo sistema de ficheros que / para que el rsync de arranque sea rápido y no dependa de almacenamiento externo alcanzable en el arranque."
       }
     ],
-    "unitBody": "La unit systemd proxmenux-restore-onboot.service se habilita al final del paso interactivo. Es un servicio oneshot que dispara temprano en el siguiente arranque, invoca apply_pending_restore.sh, y después se autodeshabilita. La unit está protegida por la condición ConditionPathExists=/var/lib/proxmenux/pending-restore/state, por lo que en cualquier arranque sin restauración pendiente es un no-op."
+    "unitBody": "La unit systemd proxmenux-restore-onboot.service se habilita al final del paso interactivo. Es un servicio oneshot que dispara temprano en el siguiente arranque (antes de la red), invoca apply_pending_restore.sh, y después se autodeshabilita. La unit no tiene condición de ruta: es el propio script el que termina cuando /var/lib/proxmenux/restore-pending/current no existe, por lo que en cualquier arranque sin restauración pendiente es un no-op. El servicio separado proxmenux-apply-cluster-postboot.service está protegido por ConditionPathExists=/var/lib/proxmenux/cluster-apply-pending."
   },
   "postbootDispatcher": {
     "heading": "El dispatcher post-arranque — apply_cluster_postboot.sh",
@@ -238,11 +238,11 @@
     "heading": "Dónde viven los logs",
     "rows": [
       {
-        "log": "/var/log/proxmenux/restore-YYYYMMDD_HHMMSS.log",
-        "detail": "Lo escribe el paso interactivo. Contiene: resultados de compatibilidad, filtro de drift, plan de hidratación, salida de aplicación hot, salida de preparación pending, instalación de paquetes."
+        "log": "/tmp/proxmenux--restore-YYYYMMDD_HHMMSS.log",
+        "detail": "Lo escribe el paso interactivo (pbs, borg o local). Las reinstalaciones de paquetes y los pasos de data pools también registran en /var/log/proxmenux/restore-apt-*.log y restore-datapools-*.log."
       },
       {
-        "log": "/var/log/proxmenux/apply-pending-YYYYMMDD_HHMMSS.log",
+        "log": "/var/log/proxmenux/proxmenux-restore-onboot-YYYYMMDD_HHMMSS.log",
         "detail": "Lo escribe apply_pending_restore.sh en el arranque temprano. Contiene: plan.env sourceado, iteración de apply-on-boot, filtrado por skip-paths."
       },
       {
diff --git a/web/messages/es/docs/backup-restore/scheduled-jobs.json b/web/messages/es/docs/backup-restore/scheduled-jobs.json
index 692d60dc..c3b738dd 100644
--- a/web/messages/es/docs/backup-restore/scheduled-jobs.json
+++ b/web/messages/es/docs/backup-restore/scheduled-jobs.json
@@ -101,7 +101,7 @@
   },
   "management": {
     "heading": "Gestión de trabajos",
-    "intro": "El menú del scheduler (TUI de Scripts) y la pestaña Backups del Monitor exponen las mismas acciones sobre cualquier trabajo.",
+    "intro": "El menú del scheduler en la TUI de Scripts (Backup scheduler and retention) ofrece Run now, Enable / Disable y Delete, además de una lista de trabajos con el estado de su última ejecución. Edit y View log solo están disponibles en la pestaña Backups del Monitor.",
     "rows": [
       {
         "action": "Run now",
@@ -112,7 +112,7 @@
         "detail": "Modo Nuevo: systemctl enable/disable --now sobre el timer. Modo adjunto: cambia el flag ENABLED en el env del trabajo — el script-hook lo respeta en el siguiente disparo de PVE."
       },
       {
-        "action": "Edit",
+        "action": "Edit (solo en Monitor)",
         "detail": "Reabre los prompts de destino, horario, perfil y retención y reescribe los ficheros del trabajo. Preserva el ID del trabajo."
       },
       {
@@ -120,7 +120,7 @@
         "detail": "Elimina el env del trabajo, el fichero de rutas, el timer + service systemd (modo Nuevo) y desactiva el binding del hook (modo adjunto). No toca los archivos ya presentes en el destino."
       },
       {
-        "action": "View log",
+        "action": "View log (solo en Monitor)",
         "detail": "Muestra en streaming /var/log/proxmenux/backup-jobs/JOB_ID-YYYYMMDD_HHMMSS.log — un fichero de log por ejecución. El runner también emite una línea de estado compacta a journald bajo el service systemd."
       }
     ]
diff --git a/web/messages/es/docs/installation.json b/web/messages/es/docs/installation.json
index 829cf7b4..00fcd807 100644
--- a/web/messages/es/docs/installation.json
+++ b/web/messages/es/docs/installation.json
@@ -53,11 +53,11 @@
     "calloutBody": "¿Quieres probar las nuevas funciones antes de su publicación oficial y ayudar a dar forma a la versión final? El programa beta de ProxMenux da acceso anticipado a las novedades, incluidas las últimas compilaciones de ProxMenux Monitor, desde la rama develop. Las versiones beta pueden contener errores o funciones incompletas. Tus comentarios ayudan a corregirlos antes de la versión estable.",
     "intro": "Instala la versión beta en lugar de la estable con:",
     "code": "bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/develop/install_proxmenux_beta.sh)\"",
-    "outro": "La beta es voluntaria: debes ejecutar expresamente su instalador. Cuando se publica una versión estable, ProxMenux avisa al volver a ejecutar menu y ofrece cambiar automáticamente. Para salir antes de la beta, usa Desactivar programa beta en Configuración."
+    "outro": "La beta es voluntaria: debes ejecutar expresamente su instalador. Cuando se publica una versión estable, al volver a ejecutar menu se ofrece con el aviso de actualización habitual. Para salir del canal beta, usa Cambiar canal de versiones en Ajustes y elige Estable."
   },
   "updating": {
     "heading": "Actualizar",
-    "body": "ProxMenux gestiona sus actualizaciones. Si hay una nueva versión, al ejecutar menu ofrece actualizar: si aceptas, sustituye los archivos y la configuración de la herramienta en la instalación existente. No necesitas descargar nada a mano. La versión estable recibe actualizaciones estables y la beta, actualizaciones beta, con la opción de cambiar a estable descrita arriba. El aviso depende de dónde ejecutes menu: por SSH o desde la consola de Proxmox aparece un diálogo de sí o no, y al aceptar se aplica la actualización. En el terminal WebSocket de ProxMenux Monitor aparece un mensaje informativo, porque ese terminal se interrumpiría durante la actualización. El mensaje muestra el instalador de una línea (bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\" para estable, o la URL equivalente de install_proxmenux_beta.sh para beta), que debes ejecutar por SSH o desde la consola."
+    "body": "ProxMenux gestiona sus actualizaciones. Si hay una nueva versión, al ejecutar menu ofrece actualizar: si aceptas, sustituye los archivos y la configuración de la herramienta en la instalación existente. No necesitas descargar nada a mano. La versión estable recibe versiones estables y la beta, compilaciones beta, y además se le ofrecen las nuevas versiones estables. El aviso depende de dónde ejecutes menu: por SSH o desde la consola de Proxmox aparece un diálogo de sí o no, y al aceptar se aplica la actualización. En el terminal WebSocket de ProxMenux Monitor aparece un mensaje informativo, porque ese terminal se interrumpiría durante la actualización. El mensaje muestra el instalador de una línea (bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\" para estable, o la URL equivalente de install_proxmenux_beta.sh para beta), que debes ejecutar por SSH o desde la consola."
   },
   "uninstall": {
     "heading": "Desinstalar",
diff --git a/web/messages/es/docs/introduction.json b/web/messages/es/docs/introduction.json
index fdd39690..bf1e1532 100644
--- a/web/messages/es/docs/introduction.json
+++ b/web/messages/es/docs/introduction.json
@@ -21,11 +21,11 @@
     "intro": "ProxMenux tiene dos componentes en el mismo árbol de código fuente. El instalador despliega ambos en el host: Scripts ofrece la interfaz de terminal y Monitor el panel web:",
     "scripts": {
       "title": "ProxMenux Scripts",
-      "body": "La CLI/TUI original que se lanza con menu. Nueve áreas funcionales que cubren optimizaciones post-instalación, creación de VMs y LXC, passthrough de GPU y Coral TPU, discos, almacenamiento, red, seguridad, utilidades, además de una referencia curada de comandos. Funciona en cualquier terminal — local, SSH o web."
+      "body": "La CLI/TUI original que se lanza con menu. Áreas funcionales que cubren optimizaciones post-instalación, creación de VMs y LXC, passthrough de GPU y Coral TPU, discos, almacenamiento, aplicaciones OCI, red, seguridad, utilidades, backup y restauración del host, Proxmox VE Helper Scripts, además de una referencia curada de comandos. Funciona en cualquier terminal — local, SSH o web."
     },
     "monitor": {
       "title": "ProxMenux Monitor",
-      "body": "Panel web autoalojado (AppImage). Métricas del host en tiempo real, un monitor de salud proactivo, notificaciones, un gancho opcional de reescritura con IA (seis proveedores), un terminal web integrado y una API REST. Actívalo desde el menú de Scripts (Settings → ProxMenux Monitor)."
+      "body": "Panel web autoalojado (AppImage). Métricas del host en tiempo real, un monitor de salud proactivo, notificaciones, un gancho opcional de reescritura con IA (seis proveedores), un terminal web integrado y una API REST. El instalador lo configura como un servicio que arranca con el sistema; actívalo, desactívalo o consúltalo desde el menú de Scripts (Settings → ProxMenux Monitor)."
     },
     "calloutTitle": "Sobre qué actúa ProxMenux",
     "calloutIntro": "Tres capas, todas accesibles desde el mismo proyecto:",
@@ -37,7 +37,7 @@
   },
   "scriptsSection": {
     "heading": "ProxMenux Scripts — qué incluye",
-    "intro": "Nueve áreas funcionales, cada una con su propio menú dentro de menu y una página de detalle en la documentación. Pulsa cualquier tarjeta para ir directamente al resumen de esa sección.",
+    "intro": "Las principales áreas funcionales, cada una con su propio menú dentro de menu y una página de detalle en la documentación. Pulsa cualquier tarjeta para ir directamente al resumen de esa sección.",
     "items": [
       {
         "title": "Post-instalación",
diff --git a/web/messages/es/docs/monitor/access-auth.json b/web/messages/es/docs/monitor/access-auth.json
index 9c8ae824..8c7aeea2 100644
--- a/web/messages/es/docs/monitor/access-auth.json
+++ b/web/messages/es/docs/monitor/access-auth.json
@@ -18,7 +18,7 @@
   },
   "reaching": {
     "heading": "Cómo llegar al panel",
-    "intro": "ProxMenux Monitor escucha en 0.0.0.0:8008. Hay tres formas habituales de llegar a él:",
+    "intro": "ProxMenux Monitor escucha en el TCP 8008 en todas las interfaces (::, IPv4 e IPv6). Hay tres formas habituales de llegar a él:",
     "outro": "El acceso directo corresponde a la unidad systemd predeterminada. El proxy inverso y Secure Gateway se describen más abajo. Para confiar en la IP de X-Forwarded-For se requiere PROXMENUX_TRUST_PROXY=1 y un proxy que controles. La presencia de X-Forwarded-Proto o X-Forwarded-Host no activa esa confianza. En los puertos 80/443 el frontend usa URL relativas /api/*; en cualquier otro puerto llama al puerto 8008 del mismo nombre de host."
   },
   "firstLaunch": {
@@ -158,7 +158,7 @@
     "authJsonTitle": "auth.json — qué contiene y cómo se protege",
     "authJsonBody": "Todo lo que ProxMenux Monitor necesita para autenticarte vive en un único archivo: /root/.config/proxmenux-monitor/auth.json, modo 0600, propietario root. El archivo guarda hashes (PBKDF2) y material de firma (jwt_secret, totp_secret) — nunca una contraseña en texto plano. Trátalo como cualquier otro secreto de solo-root: si haces backup o replicas el host, cifra el destino y nunca lo subas a control de versiones.",
     "rotateTitle": "Rotar jwt_secret invalida todos los JWTs existentes",
-    "rotateBody": "Si auth.json se regenera (borrado manual, reinstalación, restauración desde un backup con un secret distinto) el jwt_secret cambia y cada JWT previamente emitido — tanto sesiones interactivas como API tokens de larga duración — falla la verificación con \"Invalid or expired token\". La UI marca los API tokens afectados con un badge Invalid — regenerate para que el usuario sepa que tiene que revocarlos y volver a emitirlos; Home Assistant / scripts / cualquier cliente externo necesita un token fresco después de eso.",
+    "rotateBody": "Si auth.json se regenera (borrado manual, reinstalación, restauración desde un backup con un secret distinto) el jwt_secret cambia y cada JWT previamente emitido — tanto sesiones interactivas como API tokens de larga duración — falla la verificación con \"Invalid or expired token\". La UI marca los API tokens afectados en rojo, con un badge que indica que hay que revocarlos y generar otros nuevos; Home Assistant / scripts / cualquier cliente externo necesita un token fresco después de eso.",
     "recoverTitle": "Recuperar una contraseña perdida",
     "recoverIntro": "No hay un flujo online de \"olvidé mi contraseña\" — por diseño, ya que el panel corre en el propio host del usuario y el camino de recuperación es acceso shell a ese host. ProxMenux trae un reset guiado dentro del menú de configuración para que no tengas que editar a mano auth.json:",
     "survivesTitle": "Qué sobrevive al reset",
@@ -284,14 +284,14 @@
     "heading": "API tokens (larga duración)",
     "intro": "Las sesiones de navegador caducan tras 24 horas. Para integraciones desatendidas (widgets de Homepage, sensores de Home Assistant, scrapers de Grafana, probes de Uptime Kuma…) generas un API token aparte que vive 365 días. El token es un JWT firmado con el mismo secret que el token de sesión, pero su claim token_name facilita rastrearlo y revocarlo individualmente.",
     "imageAlt": "Panel de API tokens mostrando la lista de tokens con nombre, prefijo, fecha de creación y caducidad",
-    "imageCaption": "La lista de API tokens bajo Settings — nombre, prefijo (se muestran los últimos 4 caracteres para identificación), fechas de creación y caducidad, acción de revocar.",
+    "imageCaption": "La lista de API tokens bajo Administración → Seguridad — nombre, prefijo (se muestran los últimos 4 caracteres para identificación), fechas de creación y caducidad, acción de revocar.",
     "generateTitle": "Generar un token",
     "generateIntro": "Desde el panel:",
     "generateSteps": [
-      "Abre Seguridad → Tokens de acceso API.",
+      "Abre Administración → Seguridad → Tokens API y pulsa Generar nuevo token.",
       "Escribe un nombre descriptivo (p. ej. \"Home Assistant\").",
       "Vuelve a introducir tu contraseña. Si el 2FA está activado, también el código actual de 6 dígitos.",
-      "Pulsa Generar token. Se muestra una sola vez: cópialo al momento."
+      "Pulsa Generar token. El token aparece una sola vez: cópialo de inmediato."
     ],
     "generateCli": "Desde la línea de comandos:",
     "useTitle": "Usar un token",
@@ -310,7 +310,7 @@
     "intro": "Dos rutas hacia TLS:",
     "items": [
       "Reverse proxy (recomendado). Termina TLS en Nginx / Caddy / Traefik y reenvía HTTP en el puerto 8008 al proceso Flask. Snippets más abajo.",
-      "HTTPS directo en AppImage. Configura un certificado con POST /api/ssl/configure (interfaz: Ajustes → SSL). HTTPS usa gevent.pywsgi con su manejador estándar; flask-sock gestiona la conexión WebSocket para WSS. Las rutas de los certificados se guardan en la configuración SSL."
+      "HTTPS directo en AppImage. Configura un certificado con POST /api/ssl/configure (interfaz: Administración → Seguridad → SSL/HTTPS). HTTPS usa gevent.pywsgi con su manejador estándar; flask-sock gestiona la conexión WebSocket para WSS. Las rutas de los certificados se guardan en la configuración SSL."
     ],
     "calloutTitle": "Limitaciones del HTTPS directo",
     "calloutBody": "Monitor puede usar un certificado gestionado por Proxmox o archivos de certificados propios. No emite certificados ACME. Para emitirlos y renovarlos automáticamente, usa ACME de Proxmox o un proxy inverso como Caddy, Traefik o Nginx y asegúrate de cargar los certificados renovados."
diff --git a/web/messages/es/docs/monitor/ai-assistant.json b/web/messages/es/docs/monitor/ai-assistant.json
index fc338758..fd6f447c 100644
--- a/web/messages/es/docs/monitor/ai-assistant.json
+++ b/web/messages/es/docs/monitor/ai-assistant.json
@@ -9,7 +9,7 @@
   },
   "header": {
     "title": "Asistente de IA",
-    "description": "La reescritura opcional con IA convierte las plantillas de notificación admitidas en mensajes claros, con detalle por canal, trece idiomas en la interfaz, prompts personalizados, una biblioteca comunitaria pública y contexto adicional. Los informes backup_complete y backup_fail omiten la IA por completo.",
+    "description": "La reescritura opcional con IA convierte las plantillas de notificación admitidas en mensajes claros, con detalle por canal, trece idiomas en la interfaz, prompts personalizados, una biblioteca comunitaria pública y contexto adicional. Los informes backup_complete y backup_fail nunca se reescriben; con las sugerencias de IA activadas, la IA solo añade una explicación cuando un backup informa de errores.",
     "section": "Monitor de ProxMenux"
   },
   "intro": {
@@ -83,7 +83,7 @@
     "intro1": "Cada proveedor comercial cobra por token, así que merece la pena entender qué es un token antes de elegir un plan. Un token son aproximadamente cuatro caracteres de texto en inglés o unas tres cuartas partes de una palabra. La frase \"Backup completed on storage local-bak\" son unos ocho tokens. Un extracto corto de journal de diez líneas puede ser 200-400 tokens según la densidad técnica.",
     "intro2": "Se facturan dos cosas en cada llamada:",
     "items": [
-      "Tokens de entrada: incluyen las instrucciones del sistema y el mensaje de usuario: severidad, título, texto de la plantilla y contexto adicional. La cantidad depende del prompt y del evento. Cada canal reescrito puede consumir sus propios tokens de entrada; los informes de backup completado y fallido omiten la IA.",
+      "Tokens de entrada: incluyen las instrucciones del sistema y el mensaje de usuario: severidad, título, texto de la plantilla y contexto adicional. La cantidad depende del prompt y del evento. Cada canal reescrito puede consumir sus propios tokens de entrada; los informes de backup completado y fallido solo consumen tokens cuando la IA explica errores del backup.",
       "Tokens de salida — lo que el modelo escribe de vuelta. El Monitor lo limita con max_tokens (mira la tabla de abajo). El límite es un tope, no un cargo: si el modelo produce 250 tokens con un tope de 1500, pagas 250."
     ],
     "capsIntro": "Estos son los topes reales que aplica el dispatcher, tomados directamente de AI_DETAIL_TOKENS en notification_templates.py:",
@@ -118,7 +118,7 @@
     "imageCaption": "El modal in-app — seis tarjetas, una por proveedor, con las mismas descripciones documentadas abajo.",
     "groq": {
       "heading": "Groq",
-      "tagline": "Muy rápido, nivel gratuito generoso (30 req/min). Ideal para empezar.",
+      "tagline": "Muy rápido, con un nivel gratuito generoso. Un buen punto de partida.",
       "items": [
         "Clave API: console.groq.com/keys",
         "Ejemplos de la lista de modelos incluida: allam-2-7b, qwen/qwen3.8-27b, openai/gpt-oss-20b, openai/gpt-oss-120b.",
@@ -234,7 +234,7 @@
   },
   "detailLevel": {
     "heading": "Nivel de detalle por canal",
-    "intro": "Cada uno de los cuatro canales (Telegram, Discord, Gotify, Email) tiene su propio dropdown de nivel de detalle. Hay tres valores disponibles, mapeados a topes específicos de tokens de salida y a instrucciones específicas en el prompt por defecto:",
+    "intro": "Cada uno de los seis canales (Telegram, Discord, Gotify, Email, Pushover, Apprise) tiene su propio dropdown de nivel de detalle. Hay tres valores disponibles, mapeados a topes específicos de tokens de salida y a instrucciones específicas en el prompt por defecto:",
     "headerLevel": "Nivel",
     "headerLabel": "Etiqueta UI",
     "headerCap": "Tope de salida",
@@ -261,7 +261,7 @@
     ],
     "defaultsIntro": "Valores por defecto que el Monitor aplica en la primera instalación:",
     "defaults": [
-      "Telegram, Discord, Gotify — standard.",
+      "Telegram, Discord, Gotify, Pushover, Apprise — standard.",
       "Email — detailed. Email es el canal donde típicamente quieres la foto completa para archivo."
     ],
     "emailTitle": "El nivel de detalle de Email añade el original",
@@ -280,7 +280,7 @@
   },
   "templates": {
     "heading": "Una nota sobre templates",
-    "body1": "La IA recibe una plantilla ya generada, no datos de evento sin procesar. Tipos como vm_start, auth_fail y health_degraded tienen plantillas en notification_templates.py. La IA reescribe los textos admitidos; backup_complete y backup_fail conservan sus informes deterministas y omiten la IA.",
+    "body1": "La IA recibe una plantilla ya generada, no datos de evento sin procesar. Tipos como vm_start, auth_fail y health_degraded tienen plantillas en notification_templates.py. La IA reescribe los textos admitidos; backup_complete y backup_fail conservan sus informes deterministas; con las sugerencias de IA activadas, la IA solo añade una explicación cuando el backup informa de errores.",
     "body2": "Dos implicaciones prácticas: la IA nunca ve un hostname o VMID que tenga que inventar — esos campos los pone la template antes de la reescritura. Y si la IA está desactivada, el cuerpo templated es lo que se despacha directamente. La página de Notifications documenta la pipeline de despacho completa y es la referencia cruzada correcta para todo lo que le pasa a un evento antes de llegar a esta capa."
   },
   "privacy": {
diff --git a/web/messages/es/docs/monitor/api-reference.json b/web/messages/es/docs/monitor/api-reference.json
index 480e144f..8b756459 100644
--- a/web/messages/es/docs/monitor/api-reference.json
+++ b/web/messages/es/docs/monitor/api-reference.json
@@ -76,7 +76,7 @@
       {
         "endpoint": "/api/system-info",
         "method": "GET",
-        "use": "Datos ampliados usados por la cabecera del panel: métricas generales y hora de arranque."
+        "use": "Información ligera de cabecera: hostname, uptime y estado de salud en caché. Público, no necesita token."
       },
       {
         "endpoint": "/api/hardware",
@@ -401,7 +401,7 @@
       {
         "endpoint": "/api/logs",
         "method": "GET",
-        "use": "Entradas del journal filtradas. Query: ?level=&service=&limit=."
+        "use": "Entradas del journal filtradas. Query: ?priority=&service=&limit=&since_days= (priority 0-7, since_days hasta 90)."
       },
       {
         "endpoint": "/api/logs/download",
@@ -411,7 +411,7 @@
       {
         "endpoint": "/api/events",
         "method": "GET",
-        "use": "Stream interno de eventos — el mismo que alimenta las notificaciones."
+        "use": "Tareas y eventos recientes del cluster de Proxmox desde pvesh get /cluster/tasks. Query: ?limit= (por defecto 50)."
       },
       {
         "endpoint": "/api/task-log/",
@@ -462,7 +462,7 @@
       {
         "endpoint": "/api/notifications/send",
         "method": "POST",
-        "use": "Emite un evento personalizado. Cuerpo: '{'\"event_type\":\"custom\",\"severity\":\"WARNING\",\"title\":\"...\",\"body\":\"...\",\"data\":'{''}''}'."
+        "use": "Emite un evento personalizado. Cuerpo: '{'\"event_type\":\"custom\",\"severity\":\"WARNING\",\"title\":\"...\",\"message\":\"...\",\"data\":'{''}''}'."
       },
       {
         "endpoint": "/api/notifications/test-ai",
diff --git a/web/messages/es/docs/monitor/architecture.json b/web/messages/es/docs/monitor/architecture.json
index 7dd9fce6..281239fa 100644
--- a/web/messages/es/docs/monitor/architecture.json
+++ b/web/messages/es/docs/monitor/architecture.json
@@ -64,7 +64,7 @@
     "items": [
       "User=root: el servicio usa privilegios root para operaciones del host como SMART, pvesh y gestión ZFS. Los procesos del terminal heredan esos permisos.",
       "Restart=on-failure con espera de 10 segundos: se reinicia automáticamente tras una salida con error.",
-      "After=network.target: ordena el inicio después de ese objetivo; no garantiza IP asignada, DNS ni conectividad externa."
+      "After=network.target pve-cluster.service: ordena el inicio después del objetivo de red y del sistema de ficheros del cluster de Proxmox; no garantiza IP asignada, DNS ni conectividad externa."
     ],
     "inspectTitle": "Consultar la unidad actual"
   },
diff --git a/web/messages/es/docs/monitor/dashboard/hardware.json b/web/messages/es/docs/monitor/dashboard/hardware.json
index b1104104..51716b21 100644
--- a/web/messages/es/docs/monitor/dashboard/hardware.json
+++ b/web/messages/es/docs/monitor/dashboard/hardware.json
@@ -168,7 +168,7 @@
   "ups": {
     "heading": "Estado del SAI",
     "subHeading": "(condicional)",
-    "body": "Aparece si hay un servidor NUT (Network UPS Tools) configurado y accesible. Muestra estado (en línea / batería / cargando / batería baja), carga de batería, autonomía estimada, porcentaje de carga soportada, tensión de entrada, modelo y firmware. Alimenta también Seguridad y certificados del monitor de salud: el paso a batería se señala inmediatamente."
+    "body": "Aparece si hay un servidor NUT (Network UPS Tools) configurado y accesible. Muestra estado (en línea / batería / cargando / batería baja), carga de batería, autonomía estimada, porcentaje de carga soportada, tensión de entrada, modelo y firmware."
   },
   "dataCollected": {
     "heading": "Cómo se recopilan los datos",
diff --git a/web/messages/es/docs/monitor/dashboard/index.json b/web/messages/es/docs/monitor/dashboard/index.json
index 71a9733c..9618dc3f 100644
--- a/web/messages/es/docs/monitor/dashboard/index.json
+++ b/web/messages/es/docs/monitor/dashboard/index.json
@@ -50,7 +50,7 @@
       },
       {
         "name": "Terminal",
-        "owns": "Shell del host o de cualquier VM/CT en el navegador, con xterm.js sobre WebSockets."
+        "owns": "Shell del navegador en el host Proxmox, con xterm.js sobre WebSockets. Las consolas LXC se abren desde la pestaña VM y LXC."
       },
       {
         "name": "Seguridad",
diff --git a/web/messages/es/docs/monitor/dashboard/security.json b/web/messages/es/docs/monitor/dashboard/security.json
index 7a09c303..c7f3939e 100644
--- a/web/messages/es/docs/monitor/dashboard/security.json
+++ b/web/messages/es/docs/monitor/dashboard/security.json
@@ -183,7 +183,7 @@
       },
       {
         "card": "SSL/HTTPS",
-        "endpoint": "/api/auth/ssl/*",
+        "endpoint": "/api/ssl/*",
         "source": "openssl x509 sobre /etc/pve/local/pve-ssl.pem + /etc/proxmenux/ssl_config.json."
       },
       {
diff --git a/web/messages/es/docs/monitor/dashboard/settings.json b/web/messages/es/docs/monitor/dashboard/settings.json
index c4d91dbc..95c3e5dc 100644
--- a/web/messages/es/docs/monitor/dashboard/settings.json
+++ b/web/messages/es/docs/monitor/dashboard/settings.json
@@ -41,7 +41,7 @@
     "heading": "Unidades de red",
     "imageAlt": "Tarjeta Unidades de red con el selector de visualización en Bytes",
     "imageCaption": "Elige bits por segundo o bytes por segundo para todas las velocidades de red del panel.",
-    "body": "Muestra la velocidad de red en bits por segundo (Mbps/Gbps) o bytes por segundo (MB/s/GB/s). Por defecto se usan bits, como en las especificaciones de interfaces y proveedores; las transferencias de archivos suelen usar bytes. El cambio se aplica de inmediato a gráficos, etiquetas y ayudas, sin recargar."
+    "body": "Muestra la velocidad de red en bits por segundo (Mbps/Gbps) o bytes por segundo (MB/s/GB/s). Por defecto se usan bytes, como en la mayoría de herramientas de transferencia de archivos; los bits son lo que usan fabricantes de interfaces y proveedores. La elección se guarda en el navegador, así que cada navegador conserva la suya. El cambio se aplica de inmediato a gráficos, etiquetas y ayudas, sin recargar."
   },
   "health": {
     "heading": "Monitor de salud",
@@ -203,7 +203,7 @@
     "heading": "Detección de actualización de LXC",
     "imageAlt": "Tarjeta de detección de actualizaciones LXC con interruptor para buscar paquetes pendientes en contenedores Debian, Ubuntu y Alpine en ejecución",
     "imageCaption": "Interruptor del análisis periódico con apt list --upgradable / apk list -u en los CT en ejecución. Viene activado. El interruptor de aviso en Notificaciones → Servicios aparece mientras la detección está activa.",
-    "intro": "El interruptor entre Umbrales y Notificaciones controla el análisis LXC. Al activarlo, consulta los gestores de paquetes de CT en ejecución; los resultados aparecen en Hardware y alimentan lxc_updates_available. Al desactivarlo, cesan sus llamadas pct exec y se eliminan las entradas LXC de managed_installs.json, por lo que /api/managed-installs deja de mostrar ese estado sin esperar 24 h.",
+    "intro": "El interruptor entre Umbrales y Notificaciones controla el análisis LXC. Al activarlo, consulta los gestores de paquetes de CT en ejecución; los resultados aparecen en VM y LXC y alimentan lxc_updates_available. Al desactivarlo, cesan sus llamadas pct exec y se eliminan las entradas LXC de managed_installs.json, por lo que /api/managed-installs deja de mostrar ese estado sin esperar 24 h.",
     "whatRunsTitle": "Qué ejecuta el análisis",
     "whatRunsIntro": "Para cada CT en estado running con un gestor de paquetes compatible:",
     "whatRunsItems": [
@@ -249,7 +249,7 @@
     "body1": "Aquí se activan las notificaciones de ProxMenux Monitor y la reformulación con IA. Activar notificaciones inicia el proceso de envío en segundo plano, registra un destino webhook de Proxmox VE para incorporar sus eventos y abre el formulario de canales: Telegram, Discord, correo, Gotify y otros. La reformulación con IA está en una sección avanzada desplegable del mismo panel.",
     "body2": "Sus opciones se explican en páginas propias: canales, controles por evento, mensajes enriquecidos, nombre visible, envío (duplicados, espera, agrupación y horario de silencio), webhook PVE, proveedores de IA y modos de instrucciones:",
     "items": [
-      "Notificaciones: configuración de canales (Telegram, Discord, Gotify, correo y contraseñas de aplicación de Gmail / Microsoft, ntfy, Slack, Teams y webhook genérico), categorías por evento, mensajes enriquecidos, nombre visible, envío, webhook PVE, historial y API.",
+      "Notificaciones: configuración de canales (Telegram, Discord, Gotify, correo y contraseñas de aplicación de Gmail / Microsoft, Pushover y Apprise para ntfy, Slack, Teams y otros servicios), categorías por evento, mensajes enriquecidos, nombre visible, envío, webhook PVE, historial y API.",
       "Asistente de IA: proveedores (OpenAI, Anthropic, Gemini, Groq, OpenRouter, Ollama), modelos, instrucciones predeterminadas o personalizadas, idioma de salida, detalle por canal y sugerencias de IA."
     ]
   },
@@ -294,8 +294,8 @@
       },
       {
         "card": "Unidades de red",
-        "endpoint": "/api/settings",
-        "source": "Guardado en la tabla de ajustes SQLite del panel."
+        "endpoint": "localStorage",
+        "source": "Preferencia del navegador proxmenux-network-unit."
       },
       {
         "card": "Duraciones de Health Monitor",
@@ -304,7 +304,7 @@
       },
       {
         "card": "Exclusiones de almacenamiento / interfaz",
-        "endpoint": "/api/storage/exclusions, /api/network/exclusions",
+        "endpoint": "/api/health/storage-exclusions, /api/health/interface-exclusions",
         "source": "Tablas SQLite excluded_storages y excluded_interfaces."
       },
       {
diff --git a/web/messages/es/docs/monitor/dashboard/storage.json b/web/messages/es/docs/monitor/dashboard/storage.json
index 7bb4c4ef..660f95d3 100644
--- a/web/messages/es/docs/monitor/dashboard/storage.json
+++ b/web/messages/es/docs/monitor/dashboard/storage.json
@@ -10,7 +10,7 @@
   },
   "intro": {
     "title": "Respaldado por tres fuentes",
-    "body": "Los almacenamientos Proxmox proceden de pvesm status; ZFS, de zpool status; los discos físicos, de lsblk + smartctl, y nvme para campos específicos. La pestaña se actualiza aproximadamente cada 60 segundos; abrir el detalle solicita una lectura SMART."
+    "body": "Los almacenamientos Proxmox proceden de pvesm status; ZFS, de zpool status; los discos físicos, de lsblk + smartctl, y nvme para campos específicos. La pestaña se actualiza aproximadamente cada 30 segundos; abrir el detalle solicita una lectura SMART."
   },
   "thresholds": {
     "title": "Colores de estado y umbrales aplicados aquí",
diff --git a/web/messages/es/docs/monitor/dashboard/system-logs.json b/web/messages/es/docs/monitor/dashboard/system-logs.json
index 7372eee7..fd2fa8c9 100644
--- a/web/messages/es/docs/monitor/dashboard/system-logs.json
+++ b/web/messages/es/docs/monitor/dashboard/system-logs.json
@@ -18,7 +18,7 @@
       "Entradas totales: número de registros dentro del intervalo del filtro activo.",
       "Errores: número de entradas con severidad ≤ 3 (err / crit / alert / emerg).",
       "Advertencias: número de entradas con severidad 4 (warning).",
-      "Backups: número de entradas de tareas vzdump / PBS en el mismo intervalo."
+      "Backups: número de archivos de backup en los almacenamientos de backup configurados."
     ]
   },
   "subtabs": {
@@ -26,10 +26,10 @@
     "logsTitle": "Registros",
     "logsIntro": "El journal del sistema, obtenido en el servidor con journalctl. La barra de herramientas ofrece estos filtros:",
     "logsFilters": [
-      "Severidad: emerg / alert / crit / err / warning / notice / info / debug o cualquier combinación.",
-      "Rango temporal — últimos 5 min / 15 min / 1 h / 6 h / 24 h / 7 d / personalizado.",
-      "Búsqueda de texto libre: subcadena o expresión regular (journalctl --grep).",
-      "Filtro de unidad: restringe a una unidad systemd concreta (pveproxy.service, nginx.service, …)."
+      "Nivel: Todos los niveles, Error, Advertencia o Info.",
+      "Rango temporal: 1 día / 3 días / 1 semana / 2 semanas / 1 mes, o un número personalizado de días (hasta 90).",
+      "Búsqueda de texto libre: subcadena sin distinguir mayúsculas de minúsculas sobre mensaje, servicio, PID, hostname y unidad, aplicada en el navegador.",
+      "Filtro de servicio: restringe a un servicio (el SYSLOG_IDENTIFIER del journal)."
     ],
     "logsRowsAfter": "Cada fila muestra timestamp, insignia de severidad, unidad de origen y el mensaje. Los mensajes largos se colapsan con un conmutador \"mostrar más\". La acción Download empaqueta el filtro actual en un único archivo .txt vía GET /api/logs/download — útil cuando quieres compartir un trozo del journal con alguien.",
     "logDetailsModalTitle": "Modal Log Details",
@@ -50,10 +50,10 @@
     "maxLevelStoreTitle": "MaxLevelStore de journald",
     "maxLevelStoreBody": "En una instalación nueva de Proxmox, journald usa por defecto MaxLevelStore=warning, que descarta los mensajes info. Monitor lo detecta al arrancar y añade un archivo de configuración complementario (/etc/systemd/journald.conf.d/proxmenux-loglevel.conf) con el umbral info para conservar los mensajes informativos y los de mayor gravedad. Esto no incluye debug.",
     "backupsTitle": "Backups",
-    "backupsBody": "Historial de tareas de Proxmox filtrado a las entradas relacionadas con backup. Una fila por tarea (vzdump, transferencias PBS, Garbage Collect, Verify) con el estado (OK / WARNINGS / ERROR), el guest implicado, el almacenamiento de origen, la duración y el UPID. Pulsa una fila para cargar el log completo de la tarea vía GET /api/task-log/<upid> — los mismos datos que Proxmox expone a través de Datacenter → Tasks, acotado a backups.",
+    "backupsBody": "Los archivos de backup de cada almacenamiento con capacidad de backup (dir, NFS, CIFS, PBS), leídos con pvesh get /nodes/localhost/storage/<id>/content. Las tarjetas de arriba muestran el número de VMs y LXC y el tamaño total; debajo, una fila por archivo con tipo, almacenamiento, tamaño y fecha. La restauración se hace en la interfaz web de Proxmox.",
     "notificationsTitle": "Notificaciones",
-    "notificationsBody1": "Todas las notificaciones enviadas por Monitor: Telegram, Discord, correo electrónico, Gotify, ntfy, Slack, Teams y webhook. Cada fila muestra fecha y hora, canal, tipo de evento, severidad, título y cuerpo generados y, si la IA está activa, un selector para comparar su reescritura con el original.",
-    "notificationsBody2": "Usa esta pestaña para verificar la entrega de un canal y comparar la reescritura de IA con la plantilla base. Los canales se configuran en la página detallada de Notificaciones."
+    "notificationsBody1": "Actividad de notificaciones de Proxmox: líneas del journal de servicios de Proxmox que mencionan notificaciones, correo, webhooks o alertas, los destinos de /etc/pve/notifications.cfg y las tareas de backup de la lista de tareas del cluster. Las filas de tareas de backup permiten descargar el log completo de la tarea. El historial de lo que el propio Monitor envió está en Ajustes → Notificaciones → Historial de notificaciones.",
+    "notificationsBody2": "Usa esta pestaña para comprobar qué está haciendo el propio sistema de notificaciones de Proxmox. Los canales del Monitor se configuran en la página detallada de Notificaciones."
   },
   "dataCollected": {
     "heading": "Cómo se recopilan los datos",
@@ -64,7 +64,7 @@
       {
         "subtab": "Logs (filtro en vivo)",
         "endpoint": "/api/logs",
-        "source": "journalctl --output json --since <range> con filtros de severidad, unidad y búsqueda aplicados en el servidor."
+        "source": "La pestaña pide /api/logs?since_days=N; los filtros de nivel, servicio y texto se aplican en el navegador. La API también acepta priority y service, que la pestaña todavía no usa."
       },
       {
         "subtab": "Descargar",
@@ -74,17 +74,17 @@
       {
         "subtab": "Backups",
         "endpoint": "/api/backups",
-        "source": "Historial de tareas PVE filtrado por vzdump, transferencias PBS, Garbage Collect, Verify."
+        "source": "Archivos de backup listados desde cada almacenamiento de backup con pvesh get /nodes/localhost/storage/<id>/content."
       },
       {
-        "subtab": "Detalles de una tarea de backup",
+        "subtab": "Log de tareas de backup (Notificaciones)",
         "endpoint": "/api/task-log/<upid>",
         "source": "Log completo de la tarea en texto plano leído de /var/log/pve/tasks/<index>/<upid>."
       },
       {
-        "subtab": "Historial de notificaciones",
-        "endpoint": "/api/notifications/history",
-        "source": "Tabla SQLite notification_history, alimentada por el bucle de envío."
+        "subtab": "Notificaciones",
+        "endpoint": "/api/notifications",
+        "source": "journalctl --grep sobre unidades de Proxmox, /etc/pve/notifications.cfg y tareas de backup de pvesh get /cluster/tasks."
       }
     ],
     "apiIntro": "Tanto el filtro en vivo como las descargas también son accesibles vía API:",
diff --git a/web/messages/es/docs/monitor/dashboard/system-overview.json b/web/messages/es/docs/monitor/dashboard/system-overview.json
index 90d47653..d7e9f185 100644
--- a/web/messages/es/docs/monitor/dashboard/system-overview.json
+++ b/web/messages/es/docs/monitor/dashboard/system-overview.json
@@ -46,7 +46,7 @@
     "thresholdsIntro": "Cada anillo, barra y minigráfico usa la misma escala:  verde por debajo de Advertencia,  ámbar entre Advertencia y Crítico,  rojo desde Crítico. Valores recomendados incluidos con ProxMenux:",
     "thresholdsItems": [
       "Uso de CPU: advertencia 85 %, crítico 95 %.",
-      "Memoria: advertencia 85 %, crítico 95 %. Swap también activa Crítico al 5 % de uso; un host Proxmox sano debería usarlo rara vez.",
+      "Memoria: advertencia 85 %, crítico 95 %. Swap activa Crítico solo cuando el uso de swap supera el 80 % y la RAM disponible está por debajo del 15 %.",
       "Temperatura de CPU: advertencia 80 °C, crítico 90 °C."
     ],
     "thresholdsOutro": "Los valores se configuran por host; Ajustes → Umbrales del monitor de salud documenta y centraliza su ajuste.",
@@ -58,7 +58,7 @@
     "intro": "Las tarjetas Uso de CPU y Memoria son pulsables. Abren una lista ordenable con los 25 procesos principales, por uso de CPU desde la primera tarjeta o por memoria residente desde la segunda.",
     "listTitle": "El diálogo con la lista",
     "listItems": [
-      "Actualización automática: la lista se actualiza cada 5 segundos mientras está abierta y deja de consultar datos al cerrar.",
+      "Actualización automática: la lista se actualiza cada 3 segundos mientras está abierta y deja de consultar datos al cerrar.",
       "Filtro: el buscador acota la lista por comando, usuario o PID.",
       "Barra en línea — la columna de la métrica principal dibuja una barra pequeña escalada al mayor valor de la lista filtrada, para que el orden siga siendo visible aunque ningún proceso esté cerca del 100 %.",
       "Layout móvil — por debajo de 640 px las columnas PID y User se ocultan para que Command, CPU % y Memory sigan cabiendo en la pantalla de un teléfono sin scroll horizontal."
@@ -79,7 +79,7 @@
   },
   "middle": {
     "heading": "Medio: gráficas de métricas del nodo",
-    "body1": "Bajo las tarjetas está NodeMetricsCharts, con gráficas históricas de CPU, memoria y E/S de disco del almacén RRD de Proxmox mediante /api/node/metrics. El selector permite 1 hora / 24 horas / 7 días / 30 días / 1 año; la resolución baja al ampliar el intervalo para mantener la fluidez.",
+    "body1": "Bajo las tarjetas está NodeMetricsCharts, con gráficas históricas de CPU, memoria y E/S de disco del almacén RRD de Proxmox mediante /api/node/metrics. El selector permite 1 hora / 24 horas / 7 días / 30 días; la resolución baja al ampliar el intervalo para mantener la fluidez.",
     "body2": "Son las mismas gráficas que la UI web de Proxmox renderiza para un nodo, simplemente consolidadas en el tema oscuro del Monitor y alineadas con el resto de paneles."
   },
   "bottom": {
diff --git a/web/messages/es/docs/monitor/dashboard/vms-lxcs.json b/web/messages/es/docs/monitor/dashboard/vms-lxcs.json
index c8ee65c2..986e3eda 100644
--- a/web/messages/es/docs/monitor/dashboard/vms-lxcs.json
+++ b/web/messages/es/docs/monitor/dashboard/vms-lxcs.json
@@ -117,7 +117,7 @@
       "Etiqueta de destino — el almacenamiento donde vive (PBS-Cloud, PBS-Local, NFS-Backup, …) coloreada por estado.",
       "Tamaño: tamaño final en disco del backup."
     ],
-    "backupsOutro": "+ Crear backup inicia un backup en el almacenamiento marcado «Backup target» en Proxmox. Para restaurar usa la interfaz web de Proxmox; Monitor muestra los backups recientes del invitado y permite crearlos.",
+    "backupsOutro": "+ Crear backup abre un diálogo para elegir el almacenamiento, el modo (snapshot / suspend / stop), la protección y las notas antes de iniciar un vzdump. Para restaurar usa la interfaz web de Proxmox; Monitor muestra los backups recientes del invitado y permite crearlos.",
     "firewallTitle": "Pestaña 7 — Cortafuegos",
     "firewallIntro": "Lee el registro del cortafuegos de cada invitado directamente del host, sin servicio extra ni consultas periódicas. La pestaña siempre aparece; el contenido depende de que el cortafuegos esté activado y de que alguna regla registre paquetes.",
     "firewallItems": [
diff --git a/web/messages/es/docs/monitor/health-monitor.json b/web/messages/es/docs/monitor/health-monitor.json
index 7e3a2071..b72116f8 100644
--- a/web/messages/es/docs/monitor/health-monitor.json
+++ b/web/messages/es/docs/monitor/health-monitor.json
@@ -153,8 +153,8 @@
     "pillTitle": "El indicador refleja la categoría más grave",
     "pillBody": "El color de la cabecera del panel es la severidad más alta entre las diez categorías: cualquier CRITICAL → rojo, si no cualquier WARNING → amarillo, si no cualquier INFO → azul, si no verde. La misma lógica dirige el punto del favicon y el badge del PWA.",
     "updateNowTitle": "Botón Update Now en la cabecera de Overview",
-    "updateNowBody1": "Cuando la categoría Actualizaciones del sistema reporta una o más actualizaciones pendientes, la cabecera de Overview renderiza un botón Update Now al lado de la pildora de salud. El botón abre una modal que ejecuta el mismo flujo del lado del host que ProxMenux → Settings post-install Proxmox → Proxmox System Update: higiene de repos, apt full-upgrade vía el worker seguro update-pve-safe.sh, recompilación DKMS automática de los drivers gestionados por ProxMenux cuando aterriza un kernel nuevo, autoremove / autoclean y prompt de reinicio si el kernel cambió. Las líneas de progreso van llegando a la modal según las va imprimiendo el worker.",
-    "updateNowBody2": "Al terminar la ejecución, cerrar la modal llama a GET /api/health/details?refresh=1, que invalida la caché del endpoint de health antes de servir. La fila de actualizaciones pendientes y la severidad de la pildora se recomputan al momento a partir del nuevo estado de paquetes, así que el badge desaparece sin esperar al siguiente ciclo de cinco minutos. Cuando el host ya está al día el botón queda oculto — sin ruido visual en un sistema sano."
+    "updateNowBody1": "Cuando la categoría Actualizaciones del sistema reporta una o más actualizaciones pendientes, la cabecera de Overview renderiza un botón Update Now al lado de la pildora de salud. El botón abre una modal que ejecuta el mismo flujo del lado del host que ProxMenux → Utilidades y herramientas → Actualización del sistema Proxmox: higiene de repos, apt full-upgrade vía el worker seguro update-pve-safe.sh, recompilación DKMS automática de los drivers gestionados por ProxMenux cuando aterriza un kernel nuevo, autoremove / autoclean y prompt de reinicio si el kernel cambió. Las líneas de progreso van llegando a la modal según las va imprimiendo el worker.",
+    "updateNowBody2": "Al terminar la ejecución, cerrar la modal llama a GET /api/health/full?refresh=1, que invalida la caché del endpoint de health antes de servir. La fila de actualizaciones pendientes y la severidad de la pildora se recomputan al momento a partir del nuevo estado de paquetes, así que el badge desaparece sin esperar al siguiente ciclo de cinco minutos. Cuando el host ya está al día el botón queda oculto — sin ruido visual en un sistema sano."
   },
   "dismiss": {
     "heading": "Hacer dismiss de alertas y la Suppression Duration",
diff --git a/web/messages/es/docs/monitor/index.json b/web/messages/es/docs/monitor/index.json
index 6d56db73..29f00770 100644
--- a/web/messages/es/docs/monitor/index.json
+++ b/web/messages/es/docs/monitor/index.json
@@ -113,13 +113,13 @@
   "api": {
     "heading": "API REST e integraciones",
     "intro": "Los endpoints REST ofrecen datos de monitorización JSON por HTTP/HTTPS para widgets Homepage, sensores Home Assistant, pruebas Uptime Kuma y scripts. Grafana puede usar el exportador de texto Prometheus en /api/prometheus. Las peticiones se pueden hacer con curl.",
-    "tokens": "Los tokens API de larga duración (365 días) se generan en Configuración → Tokens de acceso API o mediante POST /api/auth/generate-api-token.",
+    "tokens": "Los tokens API de larga duración (365 días) se generan en Administración → Seguridad → Tokens API o mediante POST /api/auth/generate-api-token.",
     "bearer": "Envía tokens con Authorization: Bearer …. /api/health también espera la cabecera Bearer, así que las pruebas de disponibilidad necesitan un token read_only. Bajo /api/auth/* hay rutas públicas de configuración/inicio/estado y rutas protegidas de cuentas y tokens; no todas son anónimas.",
     "catalog": "El catálogo de endpoints, rotación de tokens y prácticas de seguridad están en Referencia de la API. Los ejemplos para Homepage, Home Assistant, Grafana, Uptime Kuma y cURL están en Integraciones."
   },
   "serviceControl": {
     "heading": "Control del servicio",
-    "intro": "Monitor se gestiona como cualquier servicio systemd. También tiene dos entradas en la interfaz de texto ProxMenux bajo Configuración:",
+    "intro": "Monitor se gestiona como cualquier servicio systemd. También tiene tres entradas en la interfaz de texto ProxMenux bajo Configuración:",
     "codeComment": "# Control manual",
     "footer": "Consulta Configuración → ProxMenux Monitor para activar/desactivar el servicio y verificar su estado."
   },
diff --git a/web/messages/es/docs/monitor/integrations.json b/web/messages/es/docs/monitor/integrations.json
index c31f56c6..2fbba824 100644
--- a/web/messages/es/docs/monitor/integrations.json
+++ b/web/messages/es/docs/monitor/integrations.json
@@ -20,13 +20,13 @@
     "heading": "Autenticación: lo único que cada guía necesita",
     "intro": "La mayoría de los endpoints usados por estas integraciones están autenticados. Tienes dos formas de satisfacer ese requisito.",
     "optAtitle": "Opción A — API token (recomendado para integraciones)",
-    "optAbody1": "Entra en Ajustes → Seguridad → Tokens API. Pulsa Generar token, ponle un nombre, como homepage, home-assistant o prometheus, y cópialo: se muestra una sola vez. Caduca al año por defecto y puede revocarse individualmente. Úsalo para los clientes externos al navegador.",
+    "optAbody1": "Entra en Administración → Seguridad → Tokens API. Pulsa Generar nuevo token, ponle un nombre, como homepage, home-assistant o prometheus, introduce tu contraseña (y el código 2FA si está activado), pulsa Generar token y cópialo: se muestra una sola vez. Caduca al año por defecto y puede revocarse individualmente. Úsalo para los clientes externos al navegador.",
     "optAbody2": "A partir de ahí cada petición es simplemente:",
     "optBtitle": "Opción B — flujo de login (usuario + contraseña)",
     "optBbody": "Útil para scripts que se autentican como un usuario humano. El token devuelto es de corta duración; la mayoría de integraciones deberían preferir la Opción A.",
     "outro": "El campo TOTP solo hace falta con la autenticación en dos pasos activada. La rotación y revocación de tokens, la política de contraseñas y el registro de auditoría se explican en Acceso y autenticación.",
     "httpsTitle": "Usando HTTPS en lugar de HTTP",
-    "httpsIntro": "Cada guía de abajo usa http:// en las URLs para mantener los ejemplos cortos. Si has activado TLS en el Monitor (Settings → Security → SSL/HTTPS), cambia http:// por https:// en cada URL — ese es el único cambio. Dos notas específicas para ciertas herramientas:",
+    "httpsIntro": "Cada guía de abajo usa http:// en las URLs para mantener los ejemplos cortos. Si has activado TLS en el Monitor (Administración → Seguridad → SSL/HTTPS), cambia http:// por https:// en cada URL — ese es el único cambio. Dos notas específicas para ciertas herramientas:",
     "httpsItems": [
       "Certificados autofirmados. La integración rest: de Home Assistant verifica TLS por defecto. Si el Monitor está usando su propio cert autofirmado, añade verify_ssl: false a cada bloque REST (junto a scan_interval:), o importa la CA del Monitor al trust store de HA. Lo mismo para cualquier herramienta que rechace certs no confiables.",
       "Prometheus ya tiene scheme: https listo en la configuración de scrape de abajo; descomenta / déjalo como https si TLS está activado en el Monitor."
@@ -43,7 +43,7 @@
     "basicTitle": "Widget básico — sin autenticación",
     "basicIntro": "Usa esto cuando ProxMenux Monitor esté en una red de confianza y aún no hayas activado la autenticación en el lado del Monitor. La entrada más simple posible en services.yaml:",
     "authedTitle": "Widget autenticado",
-    "authedIntro": "Genera un API token en Settings → Security → API Tokens en el Monitor, cópialo y pégalo en la cabecera Authorization de abajo — reemplaza el token de ejemplo mostrado tras Bearer con el que acabas de copiar:",
+    "authedIntro": "Genera un API token en Administración → Seguridad → Tokens API en el Monitor, cópialo y pégalo en la cabecera Authorization de abajo — reemplaza el token de ejemplo mostrado tras Bearer con el que acabas de copiar:",
     "authedOutro": "Reinicia Homepage y la tarjeta se enciende con valores en vivo. Reutiliza el mismo token en todos los widgets Homepage que apunten al mismo host de ProxMenux Monitor.",
     "multiTitle": "Setup multi-widget — sistema, almacenamiento, red",
     "multiIntro": "Para una vista más rica, renderiza tres tarjetas separadas respaldadas por distintos endpoints — una para métricas del sistema, una para almacenamiento, una para red. Usa el mismo token en cada tarjeta; es la misma instancia del Monitor.",
diff --git a/web/messages/es/docs/monitor/notifications.json b/web/messages/es/docs/monitor/notifications.json
index e9dd8e17..30710673 100644
--- a/web/messages/es/docs/monitor/notifications.json
+++ b/web/messages/es/docs/monitor/notifications.json
@@ -314,7 +314,7 @@
   },
   "aiRewrite": {
     "heading": "Reescritura con IA opcional",
-    "body1": "Los eventos aptos pueden reescribirse con un LLM. Los resultados backup_complete y backup_fail lo omiten. La IA está desactivada por defecto y se ejecuta en el hilo de envío. Si falla o agota el tiempo, se usa la plantilla original.",
+    "body1": "Los eventos aptos pueden reescribirse con un LLM. Los resultados backup_complete y backup_fail no se reescriben; con las sugerencias de IA activadas, la IA solo añade una explicación cuando un backup informa de errores. La IA está desactivada por defecto y se ejecuta en el hilo de envío. Si falla o agota el tiempo, se usa la plantilla original.",
     "body2": "Admite OpenAI, Anthropic, Google Gemini, Groq, OpenRouter y Ollama local, con detalle por canal (brief, standard, detailed), idioma, modo de instrucciones (default o custom) e instrucciones propias. Guía y ejemplos en Asistente de IA.",
     "privacyTitle": "Nota de privacidad",
     "privacyBody": "La petición de IA puede incluir título, severidad, texto, instrucciones del sistema o propias y contexto, con nombres de host, IP, usuarios, errores y registros. Se envía al destino elegido. Ollama solo procesa localmente con servidor y modelo locales; un servidor remoto o modelo en la nube envía datos fuera del host. Desactiva la reescritura o usa un servidor y modelo locales controlados si el contenido debe permanecer en la red."
diff --git a/web/messages/es/docs/security/ssl-letsencrypt.json b/web/messages/es/docs/security/ssl-letsencrypt.json
index f7710e7a..2961e83c 100644
--- a/web/messages/es/docs/security/ssl-letsencrypt.json
+++ b/web/messages/es/docs/security/ssl-letsencrypt.json
@@ -12,13 +12,13 @@
   },
   "intro": {
     "title": "Qué cubre esta página",
-    "body": "Cómo cambiar el Monitor de HTTP a HTTPS en el panel Settings → Security, a qué apunta realmente cada fuente de certificado en disco, cómo se comporta la opción de Proxmox cuando el host tiene un certificado Let's Encrypt y cómo establecer rutas personalizadas si gestionas certificados fuera de Proxmox."
+    "body": "Cómo cambiar el Monitor de HTTP a HTTPS en el panel Administración → Seguridad, a qué apunta realmente cada fuente de certificado en disco, cómo se comporta la opción de Proxmox cuando el host tiene un certificado Let's Encrypt y cómo establecer rutas personalizadas si gestionas certificados fuera de Proxmox."
   },
   "wheresetting": {
     "heading": "Ubicación del ajuste",
-    "body": "Abre Monitor y ve a Ajustes → Seguridad → HTTPS / SSL. El panel muestra HTTP o HTTPS y, si HTTPS no está activo, las fuentes de certificados detectadas.",
-    "imageAlt": "Panel Settings → Security → HTTPS / SSL en el Monitor mostrando el estado HTTPS desactivado, el certificado de Proxmox autodetectado (subject, issuer, expiry) y la opción Use Custom Certificate",
-    "caption": "Panel Ajustes → Seguridad → HTTPS / SSL antes de activar HTTPS. Monitor muestra el certificado que usa Proxmox y permite elegirlo con un clic."
+    "body": "Abre Monitor y ve a Administración → Seguridad → SSL/HTTPS. El panel muestra HTTP o HTTPS y, si HTTPS no está activo, las fuentes de certificados detectadas.",
+    "imageAlt": "Panel Administración → Seguridad → SSL/HTTPS en el Monitor mostrando el estado HTTPS desactivado, el certificado de Proxmox autodetectado (subject, issuer, expiry) y la opción Use Custom Certificate",
+    "caption": "Panel Administración → Seguridad → SSL/HTTPS antes de activar HTTPS. Monitor muestra el certificado que usa Proxmox y permite elegirlo con un clic."
   },
   "twoways": {
     "heading": "Dos formas de habilitar HTTPS",
@@ -117,7 +117,7 @@
   },
   "switchToHttps": {
     "heading": "Cambiar el Monitor a HTTPS",
-    "bodyRich": "Una vez que /etc/pve/local/pveproxy-ssl.pem está firmado por Let's Encrypt, abre Ajustes → Seguridad → HTTPS / SSL, comprueba que el emisor sea Let's Encrypt y no la CA local de Proxmox, y pulsa Usar certificado de Proxmox. Monitor se reinicia y la siguiente carga usa HTTPS en 8008 con una cadena de confianza pública. Las renovaciones ACME posteriores se validan y seleccionan en la siguiente conexión TLS nueva, sin sondeo periódico ni reinicio del servicio por renovación. Actualizar certificado sigue disponible en Seguridad como acción explícita de diagnóstico y recuperación."
+    "bodyRich": "Una vez que /etc/pve/local/pveproxy-ssl.pem está firmado por Let's Encrypt, abre Administración → Seguridad → SSL/HTTPS, comprueba que el emisor sea Let's Encrypt y no la CA local de Proxmox, y pulsa Usar certificado de Proxmox. Monitor se reinicia y la siguiente carga usa HTTPS en 8008 con una cadena de confianza pública. Las renovaciones ACME posteriores se validan y seleccionan en la siguiente conexión TLS nueva, sin sondeo periódico ni reinicio del servicio por renovación. Actualizar certificado sigue disponible en Seguridad como acción explícita de diagnóstico y recuperación."
   },
   "custom": {
     "heading": "Certificado personalizado — cuándo usarlo",
diff --git a/web/messages/es/docs/settings/beta-program.json b/web/messages/es/docs/settings/beta-program.json
index 4f8199e3..bd2f1a90 100644
--- a/web/messages/es/docs/settings/beta-program.json
+++ b/web/messages/es/docs/settings/beta-program.json
@@ -51,7 +51,7 @@
     "intro": "Ambas direcciones pasan por el mismo instalador — no son flujos especiales de desinstalación + reinstalación. Cada dirección simplemente ejecuta el instalador propio del canal, que está diseñado para ser idempotente y seguro de reejecutar sobre una instalación existente. Consecuencias prácticas:",
     "items": [
       "Tu configuración existente, login de ProxMenux Monitor (auth.json), canales de notificación, registro post-instalación, umbrales personalizados — nada de eso se toca. El cambio de canal solo intercambia el árbol de scripts y el AppImage binario.",
-      "Un Monitor que estaba activo antes del cambio sigue activo después; uno que estaba desactivado sigue desactivado.",
+      "El instalador reinicia el servicio Monitor durante el cambio, así que un Monitor que habías desactivado vuelve a ejecutarse. Desactívalo de nuevo desde Ajustes si hace falta.",
       "El auth.json, los tokens de API y el secreto JWT de ProxMenux Monitor se preservan entre cambios de canal. Las sesiones no se cierran.",
       "Puedes saltar de canal tantas veces como quieras — el diálogo lo aceptará cada vez y volverá a ejecutar el instalador. No hace falta reiniciar Proxmox.",
       "El instalador de cambio de canal sustituye el árbol de scripts de ProxMenux, por lo que no puede ejecutarse desde el terminal del navegador de Monitor: la PTY se cerraría a mitad de la actualización y dejaría el diálogo bloqueado. Si lo abres desde allí, aparece un cuadro informativo con el comando oficial de una línea del canal elegido. Ejecútalo desde SSH o la consola del host Proxmox."
diff --git a/web/messages/es/docs/settings/index.json b/web/messages/es/docs/settings/index.json
index 53cf6776..08e1c67a 100644
--- a/web/messages/es/docs/settings/index.json
+++ b/web/messages/es/docs/settings/index.json
@@ -60,6 +60,12 @@
         "description": "Cambia entre estable (main) y beta (develop) en ambos sentidos desde un único diálogo. Ejecuta el instalador oficial del canal elegido; es idempotente y siempre está visible.",
         "badge": "Siempre disponible"
       },
+      {
+        "icon": "Languages",
+        "href": "/docs/settings/change-language",
+        "title": "Cambiar idioma",
+        "description": "Cambia la interfaz de ProxMenux entre ocho idiomas. La elección se guarda en config.json y el menú se recarga en el nuevo idioma."
+      },
       {
         "icon": "Info",
         "href": "/docs/settings/show-version-information",
@@ -70,7 +76,7 @@
         "icon": "Trash2",
         "href": "/docs/settings/uninstall-proxmenux",
         "title": "Desinstalar ProxMenux",
-        "description": "Elimina ProxMenux y (opcionalmente) sus dependencias. También restaura los backups de /root/.bashrc y /etc/motd creados en el momento de la instalación."
+        "description": "Elimina ProxMenux y (opcionalmente) sus dependencias. También restaura los backups de /root/.bashrc y /etc/motd creados por Post-Install."
       }
     ]
   },
diff --git a/web/messages/es/docs/settings/proxmenux-monitor.json b/web/messages/es/docs/settings/proxmenux-monitor.json
index d4ea04df..d7cf7a48 100644
--- a/web/messages/es/docs/settings/proxmenux-monitor.json
+++ b/web/messages/es/docs/settings/proxmenux-monitor.json
@@ -33,7 +33,7 @@
   },
   "warnConditional": {
     "title": "Las opciones de Ajustes solo aparecen cuando Monitor está instalado",
-    "body": "Las dos entradas de menú descritas debajo solo aparecen en Settings si proxmenux-monitor.service está registrado en systemd. Si no las ves, el Monitor no se instaló (es opcional durante la instalación de ProxMenux). Para añadirlo más tarde, reinstala ProxMenux con la última versión y elige la opción de Monitor."
+    "body": "Las tres entradas de menú descritas debajo solo aparecen en Settings si proxmenux-monitor.service está registrado en systemd. El instalador siempre lo configura, así que si no las ves el paso del Monitor falló durante la instalación. Ejecuta de nuevo el instalador oficial para reintentarlo."
   },
   "toggle": {
     "heading": "Activar / Desactivar",
@@ -92,8 +92,8 @@
   },
   "troubleshoot": {
     "heading": "Solución de problemas",
-    "missingTitle": "Ambas opciones del menú faltan",
-    "missingBody": "El Monitor no está registrado en systemd. Confirma con systemctl list-unit-files | grep proxmenux-monitor — si no hay salida, reinstala ProxMenux desde el instalador oficial con la última versión y elige la opción de Monitor durante la instalación.",
+    "missingTitle": "Faltan las opciones de menú del Monitor",
+    "missingBody": "El Monitor no está registrado en systemd. Confirma con systemctl list-unit-files | grep proxmenux-monitor — si no hay salida, ejecuta de nuevo el instalador oficial; instala el Monitor en cada ejecución.",
     "unreachableTitle": "Servicio activado pero la URL es inalcanzable",
     "unreachableBody": "El estado en Ajustes muestra la IP. Comprueba:",
     "unreachableCmd": "ss -tlnp | grep 8008                # is anything listening?\njournalctl -u proxmenux-monitor -n 100 --no-pager  # recent errors\ncurl -sI http://localhost:8008      # local reachability",
diff --git a/web/messages/es/docs/settings/uninstall-proxmenux.json b/web/messages/es/docs/settings/uninstall-proxmenux.json
index 99f77f70..f6b44232 100644
--- a/web/messages/es/docs/settings/uninstall-proxmenux.json
+++ b/web/messages/es/docs/settings/uninstall-proxmenux.json
@@ -12,7 +12,7 @@
   },
   "scopeWarn": {
     "title": "Afecta solo a la instalación de ProxMenux — no a tus VMs / CTs / almacenamiento",
-    "body": "Desinstalar ProxMenux elimina sus scripts, el lanzador del menú, el servicio opcional Monitor y las copias de archivos del sistema que creó durante la instalación. No modifica tus máquinas virtuales, contenedores, almacenamiento, configuración de red, Fail2Ban, el controlador NVIDIA ni nada más que hayas instalado mediante ProxMenux. Todo ello permanece como está."
+    "body": "Desinstalar ProxMenux elimina sus scripts, el lanzador del menú, el servicio Monitor y restaura las copias de /root/.bashrc y /etc/motd que hizo Post-Install, si existen. No modifica tus máquinas virtuales, contenedores, almacenamiento, configuración de red, Fail2Ban, el controlador NVIDIA ni nada más que hayas instalado mediante ProxMenux. Todo ello permanece como está."
   },
   "flow": {
     "heading": "El proceso de desinstalación",
diff --git a/web/messages/es/docs/utils/export-vm.json b/web/messages/es/docs/utils/export-vm.json
index 5cdb7953..048cc556 100644
--- a/web/messages/es/docs/utils/export-vm.json
+++ b/web/messages/es/docs/utils/export-vm.json
@@ -59,8 +59,8 @@
   },
   "package": {
     "heading": "Qué acaba en el paquete",
-    "intro": "Para un OVA / OVF llamado myvm-100-2026-04-26_18-30-15:",
-    "code": "# OVA mode (single .ova file is a TAR of):\nmyvm-100-2026-04-26_18-30-15.ovf      # XML descriptor (DMTF OVF 1.0 + RASD + VSSD)\nmyvm-100-2026-04-26_18-30-15.mf       # SHA1 manifest\nmyvm-100-2026-04-26_18-30-15-disk0.vmdk\nmyvm-100-2026-04-26_18-30-15-disk1.vmdk\n...\n\n# OVF mode (a directory):\nmyvm-100-2026-04-26_18-30-15-ovf/\n├── myvm-100-2026-04-26_18-30-15.ovf\n├── myvm-100-2026-04-26_18-30-15.mf\n├── myvm-100-2026-04-26_18-30-15-disk0.vmdk\n└── myvm-100-2026-04-26_18-30-15-disk1.vmdk"
+    "intro": "Para un OVA / OVF llamado myvm-100-20260426_183015:",
+    "code": "# OVA mode (single .ova file is a TAR of):\nmyvm-100-20260426_183015.ovf      # XML descriptor (DMTF OVF 1.0 + RASD + VSSD)\nmyvm-100-20260426_183015.mf       # SHA1 manifest\nmyvm-100-20260426_183015-disk1.vmdk\nmyvm-100-20260426_183015-disk2.vmdk\n...\n\n# OVF mode (a directory):\nmyvm-100-20260426_183015-ovf/\n├── myvm-100-20260426_183015.ovf\n├── myvm-100-20260426_183015.mf\n├── myvm-100-20260426_183015-disk1.vmdk\n└── myvm-100-20260426_183015-disk2.vmdk"
   },
   "exported": {
     "heading": "Qué se exporta",
@@ -90,7 +90,7 @@
   "manifest": {
     "heading": "Manifiesto SHA1",
     "intro": "Todo paquete incluye un archivo .mf con hashes SHA1 del descriptor OVF y de cada VMDK:",
-    "code": "SHA1(myvm-100-2026-04-26_18-30-15.ovf)= 3a7bd3e2360a3d29eea436fcfb7e44c735d117c9\nSHA1(myvm-100-2026-04-26_18-30-15-disk0.vmdk)= b7e23ec29af22b0b4e41da31e868d57226121c84\nSHA1(myvm-100-2026-04-26_18-30-15-disk1.vmdk)= a87ff679a2f3e71d9181a67b7542122c",
+    "code": "SHA1(myvm-100-20260426_183015.ovf)= 3a7bd3e2360a3d29eea436fcfb7e44c735d117c9\nSHA1(myvm-100-20260426_183015-disk1.vmdk)= b7e23ec29af22b0b4e41da31e868d57226121c84\nSHA1(myvm-100-20260426_183015-disk2.vmdk)= a87ff679a2f3e71d9181a67b7542122c",
     "outro": "Opcional en la especificación OVF — muchos lectores lo ignoran — pero útil para verificar que el paquete llegó intacto. Verifica manualmente con sha1sum -c file.mf."
   },
   "import": {
diff --git a/web/messages/es/docs/utils/index.json b/web/messages/es/docs/utils/index.json
index 769b1772..68cad8df 100644
--- a/web/messages/es/docs/utils/index.json
+++ b/web/messages/es/docs/utils/index.json
@@ -38,7 +38,7 @@
       "body": "Mantén el host sano: herramientas CLI, actualizaciones periódicas y el salto de versión mayor cuando toca.",
       "bullets": [
         "Instalador de utilidades del sistema (25 herramientas CLI)",
-        "Actualización del sistema Proxmox (apt + dist-upgrade)",
+        "Actualización del sistema Proxmox (apt + full-upgrade)",
         "Salto de versión mayor PVE 8 → PVE 9"
       ]
     },
@@ -58,11 +58,11 @@
   },
   "isoSection": {
     "heading": "Creación de ISOs",
-    "body": "Crea una ISO de instalación de Windows actualizada directamente en el host Proxmox. El script automatiza todo el flujo de UUP Dump: instalación de dependencias (aria2c, cabextract, wimlib-imagex, …), descarga paralelizada de los archivos de actualización vía aria2 y conversión en una única ISO arrancable que aterriza en /var/lib/vz/template/iso lista para asociar a una VM.",
+    "body": "Crea una ISO de instalación de Windows actualizada directamente en el host Proxmox. El script automatiza todo el flujo de UUP Dump: instalación de dependencias (aria2c, cabextract, wimlib-imagex, …), descarga paralelizada de los archivos de actualización vía aria2 y conversión en una única ISO arrancable guardada en el almacenamiento ISO de Proxmox detectado (alternativa /var/lib/vz/template/iso), lista para asociar a una VM.",
     "options": [
       {
         "title": "Creador de ISO UUP Dump",
-        "description": "Crea una ISO de instalación de Windows actualizada desde un enlace compartido de UUP Dump. Dependencias, descarga y conversión totalmente automatizadas; la ISO resultante aterriza en /var/lib/vz/template/iso lista para crear una VM.",
+        "description": "Crea una ISO de instalación de Windows actualizada desde un enlace compartido de UUP Dump. Dependencias, descarga y conversión totalmente automatizadas; la ISO resultante se guarda en el almacenamiento ISO de Proxmox detectado, lista para crear una VM.",
         "href": "/docs/utils/UUp-Dump-ISO-Creator"
       }
     ]
@@ -78,7 +78,7 @@
       },
       {
         "title": "Actualización del sistema Proxmox",
-        "description": "Comprobación de repositorios + apt update + apt dist-upgrade, con confirmación antes de reiniciar. Detecta la versión mayor de PVE (8 o 9), conserva los repositorios configurados y solicita confirmación antes de cambiar al repositorio no-subscription un host sin suscripción; después ejecuta autoremove.",
+        "description": "Comprobación de repositorios + apt update + apt full-upgrade, con confirmación antes de reiniciar. Detecta la versión mayor de PVE (8 o 9), conserva los repositorios configurados y solicita confirmación antes de cambiar al repositorio no-subscription un host sin suscripción; después ejecuta autoremove.",
         "href": "/docs/utils/system-update"
       },
       {
diff --git a/web/messages/es/docs/utils/system-update.json b/web/messages/es/docs/utils/system-update.json
index aa85497a..12912cc6 100644
--- a/web/messages/es/docs/utils/system-update.json
+++ b/web/messages/es/docs/utils/system-update.json
@@ -128,7 +128,7 @@
         "body": "El repositorio Enterprise está habilitado y el host no tiene suscripción activa. El worker ofrece el cambio a sin suscripción antes de apt-get update; si se rechazó, ejecuta de nuevo la actualización y acéptalo, o deshabilita la fuente Enterprise en Nodo → Actualizaciones → Repositorios y habilita la de sin suscripción."
       },
       {
-        "title": "dist-upgrade se queda colgado en \"Configuring grub-pc\"",
+        "title": "full-upgrade se queda colgado en \"Configuring grub-pc\"",
         "body": "Si aparece una pregunta interactiva de GRUB, solicita los dispositivos donde instalar el cargador de arranque. La opción --force-confold se aplica a decisiones sobre archivos de configuración, no a esta selección de dispositivos. Usa Tab + Space para seleccionar los discos de arranque adecuados y después OK. Puedes configurar esta selección de antemano con dpkg-reconfigure grub-pc."
       },
       {
diff --git a/web/messages/es/docs/utils/system-utils.json b/web/messages/es/docs/utils/system-utils.json
index 37c79df0..4536c4ce 100644
--- a/web/messages/es/docs/utils/system-utils.json
+++ b/web/messages/es/docs/utils/system-utils.json
@@ -235,7 +235,7 @@
       {
         "href": "/docs/utils/system-update",
         "label": "Actualización del sistema Proxmox",
-        "tailRich": " — para el apt dist-upgrade real en el host."
+        "tailRich": " — para el apt full-upgrade real en el host."
       },
       {
         "href": "/docs/network/monitoring",
diff --git a/web/messages/es/docs/utils/upgrade-pve8-pve9.json b/web/messages/es/docs/utils/upgrade-pve8-pve9.json
index 3085a7e4..bc73fd31 100644
--- a/web/messages/es/docs/utils/upgrade-pve8-pve9.json
+++ b/web/messages/es/docs/utils/upgrade-pve8-pve9.json
@@ -16,9 +16,9 @@
     "items": [
       "Backups verificados de cada VM y CT (PBS, vzdump, snapshots ZFS — y verifica que se restauran).",
       "Acceso por consola / IPMI / iKVM al host para cuando SSH se caiga a mitad del upgrade.",
-      "El host ya actualizado en PVE 8.4.x (el script lo fuerza).",
+      "El host en el último nivel de parche de PVE 8. El script actualiza primero PVE 8; no comprueba una versión 8.4.x concreta.",
       "Ceph (si está presente) en versión 19.x Squid.",
-      "Al menos 5 GB libres en el sistema de archivos raíz."
+      "Al menos 5 GB libres en el sistema de archivos raíz, como recomienda la guía de Proxmox. El propio script solo comprueba 1 GB en /var/cache/apt/archives."
     ]
   },
   "modeMenu": {
@@ -63,13 +63,13 @@
   },
   "webTerminal": {
     "heading": "Por qué se bloquea el terminal web",
-    "body": "Los tres modos de upgrade detectan termproxy / vncshell en la cadena de procesos padre y se niegan a ejecutarse. Razón: el terminal web de Proxmox lo sirve pveproxy, que es uno de los servicios que se actualiza a mitad del flujo. En cuanto pveproxy reinicia, tu sesión de terminal muere — y con ella el script de upgrade que se estaba ejecutando dentro. El host se queda medio actualizado, sin una buena forma de reanudar.",
+    "body": "Los modos Automático e Interactivo detectan termproxy / vncshell en la cadena de procesos padre y se niegan a ejecutarse, salvo que la sesión llegue por SSH o esté definida PVE_ALLOW_WEBTERM=1. La comprobación previa y la guía manual no cambian nada y se ejecutan en cualquier lugar. Razón: el terminal web de Proxmox lo sirve pveproxy, que es uno de los servicios que se actualiza a mitad del flujo. En cuanto pveproxy reinicia, tu sesión de terminal muere — y con ella el script de upgrade que se estaba ejecutando dentro. El host se queda medio actualizado, sin una buena forma de reanudar.",
     "warningTitle": "Usa SSH o consola física / IPMI / iKVM",
-    "warningBody": "SSH es la elección más común. Ábrelo dentro de tmux o screen para que el upgrade sobreviva a un parpadeo breve de red. Lo primerísimo que hace el script es imprimir el recordatorio de tmux/screen si detecta que estás en un shell SSH pelado."
+    "warningBody": "SSH es la elección más común. Ábrelo dentro de tmux o screen para que el upgrade sobreviva a un parpadeo breve de red. El diálogo de confirmación de la actualización te recuerda ejecutarla desde la consola o desde SSH dentro de tmux/screen."
   },
   "auto": {
     "heading": "Modo Automático / Desatendido",
-    "intro": "Las preguntas de paquetes y archivos de configuración usan los valores predeterminados de la tabla siguiente, y un 401 del repositorio Enterprise cambia a no-subscription sin preguntar. La confirmación de la actualización, el resumen de la actualización de PVE 8, las reparaciones de la comprobación previa y la pregunta de reinicio se siguen mostrando.",
+    "intro": "Las preguntas de paquetes y archivos de configuración usan los valores predeterminados de la tabla siguiente. La confirmación de la actualización, el resumen de la actualización de PVE 8, las reparaciones de la comprobación previa y la pregunta de reinicio se siguen mostrando.",
     "behaviourHeading": "Comportamiento específico del modo automático",
     "behaviourTable": {
       "settingHeader": "Ajuste",
@@ -94,8 +94,8 @@
       ]
     },
     "flowHeading": "Flujo del modo automático",
-    "flowIntro": "Seis fases consecutivas. La fase 2 actualiza los paquetes de PVE 8 y la fase 3 escribe la configuración de repositorios de PVE 9. Un fallo detiene el proceso sin revertir esos cambios; si la actualización de PVE 8 falla o se cancela, los repositorios no se modifican. La fase 5 ejecuta dist-upgrade para PVE 9.",
-    "flowDiagram": "1. COMPROBACIONES\nBloquea termproxy / vncshell; confirma la actualización; rechaza PVE 9.\n\n2. PREPARAR PVE 8\nscripts/global/update-pve8.sh\nActualiza los paquetes de PVE 8; se detiene si la actualización falla o se cancela.\npve8to9 --full (puede ofrecer reparaciones).\nComprueba espacio y conectividad; Ceph 19.x Squid en nodos hiperconvergentes.\n\n3. MIGRAR REPOSITORIOS\nBookworm → Trixie; escribe archivos deb822 .sources.\nComenta los archivos .list antiguos; apt-get update.\n\n4. VALIDAR EL CANDIDATO DE PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nSe detiene si el candidato no es 9.x o se eliminaría proxmox-ve.\n\n5. ACTUALIZAR\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nSin suscripción: desactiva pve-enterprise.sources.\nRegistro: /var/log/pve8-a-pve9-.log\n\n6. VERIFICAR Y REINICIAR\nEFI: apt install grub-efi-amd64\npve8to9 --full; revisa el resultado y la pregunta de reinicio.",
+    "flowIntro": "Seis fases consecutivas. La fase 2 cambia los repositorios de PVE 8 a no-subscription (listas enterprise y Ceph desactivadas, /etc/apt/sources.list reescrito) antes de mostrar el resumen de la actualización y después actualiza los paquetes; la fase 3 escribe la configuración de repositorios de PVE 9. Un fallo detiene el proceso sin revertir esos cambios; si la actualización de PVE 8 falla o se cancela, la migración a PVE 9 no empieza, pero los cambios en los repositorios de PVE 8 permanecen. La fase 5 ejecuta dist-upgrade para PVE 9.",
+    "flowDiagram": "1. COMPROBACIONES\nBloquea termproxy / vncshell; confirma la actualización; rechaza PVE 9.\n\n2. PREPARAR PVE 8\nscripts/global/update-pve8.sh\nCambia los repositorios de PVE 8 a no-subscription y reescribe sources.list;\nactualiza los paquetes de PVE 8; se detiene si la actualización falla o se cancela.\npve8to9 --full (puede ofrecer reparaciones).\nComprueba espacio y conectividad; Ceph 19.x Squid en nodos hiperconvergentes.\n\n3. MIGRAR REPOSITORIOS\nBookworm → Trixie; escribe archivos deb822 .sources.\nComenta los archivos .list antiguos; apt-get update.\n\n4. VALIDAR EL CANDIDATO DE PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nSe detiene si el candidato no es 9.x o se eliminaría proxmox-ve.\n\n5. ACTUALIZAR\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nSin suscripción: desactiva pve-enterprise.sources.\nRegistro: /var/log/pve8-a-pve9-.log\n\n6. VERIFICAR Y REINICIAR\nEFI: apt install grub-efi-amd64\npve8to9 --full; revisa el resultado y la pregunta de reinicio.",
     "preflightHeading": "Comprobaciones previas: qué se chequea",
     "preflightTable": {
       "checkHeader": "Chequeo",
@@ -158,7 +158,7 @@
   },
   "manual": {
     "heading": "Procedimiento manual de upgrade",
-    "intro": "Esto es lo que muestra la opción de menú Guía manual de upgrade — un espejo fiel del wiki oficial de upgrade Proxmox 8 a 9 organizado en 4 fases. El script muestra estos comandos; tú los tecleas en un shell root aparte. Sin automatización — visibilidad total.",
+    "intro": "Esto sigue lo que muestra la opción de menú Guía manual de upgrade, basada en el wiki oficial de upgrade Proxmox 8 a 9 y organizada en 4 fases. La tabla de respuestas de dpkg añade las salvedades del wiki para sshd_config y chrony.conf, que el menú muestra como una simple Y. El script muestra estos comandos; tú los tecleas en un shell root aparte. Sin automatización — visibilidad total.",
     "rootCalloutTitle": "Todos los comandos necesitan root",
     "rootCalloutBody": "Usa una sesión de shell como root para estos comandos, incluidas las redirecciones y los documentos here-document. Si utilizas sudo, asegúrate de ejecutar el comando completo con privilegios de root.",
     "phase1Heading": "Fase 1 — Preparación",
@@ -230,7 +230,7 @@
   "clusterCeph": {
     "heading": "Clúster y Ceph",
     "clusterCalloutTitle": "Clúster: actualiza los nodos uno a uno",
-    "clusterCalloutBody": "Se detecta mediante pvecm status. No actualices más de un nodo a la vez. Mueve sus máquinas, actualízalo, reinícialo y verifica su pertenencia al clúster antes de continuar. Durante la actualización funciona la migración en vivo desde versiones anteriores de PVE a posteriores; la dirección inversa generalmente no es compatible. Los grupos de HA se convierten automáticamente en reglas de HA cuando todos los nodos ejecutan PVE 9.",
+    "clusterCalloutBody": "No actualices más de un nodo a la vez. Mueve sus máquinas, actualízalo, reinícialo y verifica su pertenencia al clúster antes de continuar. Durante la actualización funciona la migración en vivo desde versiones anteriores de PVE a posteriores; la dirección inversa generalmente no es compatible. Los grupos de HA se convierten automáticamente en reglas de HA cuando todos los nodos ejecutan PVE 9.",
     "cephCalloutTitle": "Ceph: actualiza Ceph PRIMERO, después Proxmox",
     "cephCalloutBody": "Ceph debe ejecutar ya 19.x Squid antes de actualizar Proxmox. Sigue la secuencia oficial: Quincy 17 → Reef 18 → Squid 19, actualizando paquetes y reiniciando los demonios de Ceph de forma escalonada en el orden documentado. Comprueba ceph --version antes de actualizar cada nodo Proxmox. Sigue el procedimiento completo: guía de actualización de Ceph Reef a Squid."
   },
@@ -273,7 +273,7 @@
   },
   "files": {
     "heading": "Archivos implicados",
-    "code": "scripts/utilities/upgrade_pve8_to_pve9.sh                         # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh                                # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh    # 17-step runbook\nscripts/global/update-pve8.sh                                     # PVE 8 worker (final patches)\n/etc/apt/sources.list                                             # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources                           # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources                            # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources                              # created if Ceph\n/etc/apt/sources.list.d/*.list                                    # legacy files commented out / removed\n/var/log/pve8-a-pve9-.log                              # full upgrade log"
+    "code": "scripts/utilities/upgrade_pve8_to_pve9.sh                         # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh                                # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh    # 17-step runbook\nscripts/global/update-pve8.sh                                     # PVE 8 worker (final patches)\n/etc/apt/sources.list                                             # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources                           # created (deb822 PVE 9)\n/etc/apt/sources.list.d/ceph.sources                              # created if Ceph\n/etc/apt/sources.list.d/*.list                                    # legacy files commented out / removed\n/var/log/pve8-a-pve9-.log                              # full upgrade log"
   },
   "references": {
     "heading": "Referencias oficiales",
diff --git a/web/messages/fr/docs/backup-restore/creating-backups.json b/web/messages/fr/docs/backup-restore/creating-backups.json
index 8fcdb1fe..6ee37487 100644
--- a/web/messages/fr/docs/backup-restore/creating-backups.json
+++ b/web/messages/fr/docs/backup-restore/creating-backups.json
@@ -21,8 +21,8 @@
     "rows": [
       {
         "entry": "ProxMenux Scripts (TUI)",
-        "path": "menu → Utilities → Host Config Backup",
-        "detail": "Processus fondé sur des boîtes de dialogue, adapté à SSH. Le menu principal présente directement les six options. Utilise backup_menu dans backup_host.sh."
+        "path": "menu → Host Backup & Restore → Backup host configuration",
+        "detail": "Processus fondé sur des boîtes de dialogue, adapté à SSH. L’entrée du menu principal ouvre Host Config Backup / Restore ; Backup host configuration mène aux six options. Utilise backup_menu dans backup_host.sh."
       },
       {
         "entry": "ProxMenux Monitor (interface utilisateur Web)",
@@ -83,7 +83,7 @@
   "profiles": {
     "heading": "Profil par défaut vs personnalisé",
     "defaultTitle": "Profil par défaut",
-    "defaultBody": "Le profil par défaut est la liste prédéfinie de hb_default_profile_paths (documentée dans Comment ça marche sous Catégories de chemins) plus chaque entrée dans le fichier supplémentaire persistant /usr/local/share/proxmenux/backup-extra-paths.txt. L'utilisateur confirme les options de destination et de chiffrement et la sauvegarde se déroule sans autre sélection de chemin.",
+    "defaultBody": "Le profil par défaut est la liste prédéfinie de hb_default_profile_paths (documentée dans Comment ça marche sous Catégories de chemins) plus chaque entrée dans le fichier supplémentaire persistant /usr/local/share/proxmenux/backup-extra-paths.txt. L’utilisateur choisit les options de destination et de chiffrement et la sauvegarde se déroule sans autre sélection de chemin.",
     "customTitle": "Profil personnalisé",
     "customBody": "Le profil personnalisé ouvre une liste à cocher avec tous les chemins du profil par défaut (décochés) et tous les ajouts persistants (précochés, avec le préfixe [+]). L’utilisateur coche les chemins pour cette exécution et peut cliquer sur Ajouter un chemin personnalisé pour ajouter un chemin absolu. Tout chemin ainsi ajouté est enregistré dans backup-extra-paths.txt afin que les sauvegardes suivantes l’incluent automatiquement. Décocher un ajout persistant l’exclut de cette exécution sans le supprimer du fichier — la suppression se fait séparément par Gérer les chemins personnalisés, en dehors du processus de sauvegarde.",
     "customPickerAlt": "Liste de contrôle de profil personnalisé montrant les chemins de profil par défaut (non coché) et les extras persistants (pré coché avec un préfixe [+]), ainsi que des boutons pour ajouter un nouveau chemin ou confirmer la sélection.",
@@ -124,14 +124,7 @@
   },
   "included": {
     "heading": "Ce qui entre et ce qui reste en dehors",
-    "intro": "Chaque chemin dans le profil résolu (par défaut + suppléments persistants + sélection en mode personnalisé) est copié avec rsync -aAXH --numeric-ids. Une liste d'exclusion partagée s'applique à chaque chemin, et deux répertoires comportent d'autres exclusions spécifiques au chemin.",
-    "globalTitle": "Les exclusions globales (appliquées à chaque voie)",
-    "globalItems": [
-      "images/ — images de disque exportées.",
-      "dump/ — sorties vzdump.",
-      "tmp/ — fichiers temporaires.",
-      "*.log — fichiers journaux."
-    ],
+    "intro": "Chaque chemin dans le profil résolu (par défaut + suppléments persistants + sélection en mode personnalisé) est copié avec rsync -aAXH --numeric-ids. Il n’y a pas de liste d’exclusion globale ; quelques répertoires comportent des exclusions spécifiques au chemin.",
     "rootTitle": "Exclusions de /root/",
     "rootBody": "/root/ fait partie du profil par défaut, de sorte que les scripts utilisateur et la configuration se trouvent dans l'archive. Les sous-chemins volatils sont abandonnés:",
     "rootItems": [
@@ -160,16 +153,16 @@
       "/home/USER — pas dans le profil par défaut. Ajoutez-le comme un chemin personnalisé lorsqu'un système transporte des répertoires d'utilisateur qui doivent survivre à une restauration."
     ],
     "customPathsTitle": "Comment les chemins personnalisés sont gérés",
-    "customPathsBody": "Un chemin personnalisé ajouté en ligne en mode personnalisé ou maintenu dans backup-extra-paths.txt passe par le même pipeline rsync que les chemins de profil par défaut. Les exclusions globales s'appliquent. Si le chemin personnalisé se trouve sous /root/ ou /usr/local/share/proxmenux/, les exclusions spécifiques au chemin ci-dessus s'appliquent toujours. Chaque chemin archivé — par défaut ou personnalisé — est enregistré dans metadata/paths_archived.txt. Les chemins qui n'existent pas sur la source sont enregistrés dans metadata/missing_paths.txt sans arrêter la sauvegarde."
+    "customPathsBody": "Un chemin personnalisé ajouté en ligne en mode personnalisé ou maintenu dans backup-extra-paths.txt passe par le même pipeline rsync que les chemins de profil par défaut. Si le chemin personnalisé se trouve sous /root/ ou /usr/local/share/proxmenux/, les exclusions spécifiques au chemin ci-dessus s’appliquent toujours. Chaque chemin archivé — par défaut ou personnalisé — est enregistré dans metadata/selected_paths.txt. Les chemins qui n’existent pas sur la source sont enregistrés dans metadata/missing_paths.txt sans arrêter la sauvegarde, et les chemins dont la copie échoue dans metadata/failed_paths.txt."
   },
   "archiveStructure": {
     "heading": "Structure des archives",
     "intro": "Le répertoire de préparation temporaire produit par chaque moteur suit la même disposition quelle que soit la destination. L'archive tarball, PBS .pxar ou Borg stocke cet arbre dans son intégralité.",
-    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state (kernel_params, hardware, storage, guests, components, source_host)\n├── metadata/\n│   ├── packages.manual.list       # output of apt-mark showmanual\n│   ├── run_info.env               # hostname, timestamp, kernel version, pmxcfs method\n│   ├── paths_archived.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, /etc/apt, ...\n    ├── root/                      # /root without volatile subpaths\n    ├── usr/local/                 # /usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux (state only)\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/crontabs"
+    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state (kernel_params, hardware, storage, guests, components, source_host)\n├── metadata/\n│   ├── packages.manual.list       # output of apt-mark showmanual\n│   ├── run_info.env               # hostname, timestamp, kernel version, pmxcfs method\n│   ├── selected_paths.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, /etc/apt, ...\n    ├── root/                      # /root without volatile subpaths\n    ├── usr/local/                 # /usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux (state only)\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/crontabs"
   },
   "confirmation": {
     "heading": "Résumé de la confirmation",
-    "body": "Avant que le moteur n'écrive quoi que ce soit à la destination, ProxMenux affiche une boîte de dialogue de résumé avec la destination, l’identifiant de sauvegarde ou le nom de l’archive, l'état de chiffrement et la liste des chemins copiés. Annuler ici avorte la sauvegarde proprement — le répertoire temporaire est supprimé par le gestionnaire trap défini dans la fonction backend et aucune donnée partielle n'arrive à destination."
+    "body": "Avant que le moteur n’écrive quoi que ce soit à la destination, ProxMenux affiche dans le terminal un résumé avec la destination, l’identifiant de sauvegarde ou le nom de l’archive, l’état de chiffrement et la liste des chemins copiés. Il n’y a ni étape de confirmation ni d’annulation à ce stade ; la copie démarre juste après. Si la sauvegarde échoue ou est interrompue, le répertoire temporaire est supprimé par le gestionnaire trap défini dans la fonction backend et aucune donnée partielle n’arrive à destination."
   },
   "writing": {
     "heading": "Écrire à la destination",
diff --git a/web/messages/fr/docs/backup-restore/destinations/local.json b/web/messages/fr/docs/backup-restore/destinations/local.json
index 0a89acf8..af8eb495 100644
--- a/web/messages/fr/docs/backup-restore/destinations/local.json
+++ b/web/messages/fr/docs/backup-restore/destinations/local.json
@@ -18,11 +18,11 @@
   },
   "targetConfig": {
     "heading": "Configuration du répertoire cible",
-    "intro": "La destination locale est un unique répertoire cible enregistré durablement — pas une liste. ProxMenux stocke le choix de l'opérateur chez /usr/local/share/proxmenux/local-target.conf et le lit sur chaque sauvegarde. Lorsqu'aucune cible n'a été configurée, le HB_LOCAL_TARGET_DEFAULT = /var/lib/vz/dump par défaut est utilisé (le même répertoire que Proxmox utilise pour ses propres sorties vzdump). La cible est configurée à partir de Configurer les destinations de sauvegarde → Destinations locales:",
+    "intro": "La destination locale est un unique répertoire cible enregistré durablement — pas une liste. ProxMenux stocke le choix de l’opérateur chez /usr/local/share/proxmenux/local-target.conf et le lit sur chaque sauvegarde. Lorsqu’aucune cible n’a été configurée, le HB_LOCAL_TARGET_DEFAULT = /var/lib/vz/dump par défaut est utilisé (le même répertoire que Proxmox utilise pour ses propres sorties vzdump). La cible est configurée à partir de Configurer les destinations de sauvegarde → Local archive targets → Manage local backup target:",
     "options": [
       "Utilisez par défaut (/var/lib/vz/dump). Le stockage local Proxmox. Présent sur chaque install Proxmox ; l'archive se trouve à côté des sorties vzdump et est captée automatiquement par l'onglet Sauvegardes du moniteur.",
-      "Entrez un chemin personnalisé. Tout chemin absolu du système de fichiers peut être utilisé : un montage NFS, un partage SMB monté via fstab, un jeu de données ZFS dédié, un deuxième disque interne. ProxMenux valide que le chemin existe et est un répertoire avant de l’enregistrer.",
-      "Choisissez un lecteur USB. Ouvre le sous-menu USB (ci-dessous), qui détecte les appareils amovibles et propose de monter ou de formater un périphérique."
+      "Utilisez un chemin personnalisé. Tout chemin absolu du système de fichiers peut être utilisé : un montage NFS, un partage SMB monté via fstab, un jeu de données ZFS dédié, un deuxième disque interne. ProxMenux valide que le chemin existe et est un répertoire avant de l’enregistrer.",
+      "Utilisez un disque USB. Ouvre le sous-menu USB (ci-dessous), qui détecte les appareils amovibles et propose de monter ou de formater un périphérique."
     ]
   },
   "usbFlow": {
@@ -38,7 +38,7 @@
       {
         "state": "non monté",
         "shown": "Taille · étiquette · [fstype] · (non monté — sera monté)",
-        "action": "Un système de fichiers est présent mais n'est pas monté. Lors de la confirmation, ProxMenux exécute hb_mount_usb_partition : crée /mnt/backup-LABEL (ou un chemin basé sur UUID s'il n'y a pas d'étiquette), monte la partition et persiste le point de montage comme cible locale."
+        "action": "Un système de fichiers est présent mais n’est pas monté. Lors de la confirmation, ProxMenux exécute hb_mount_usb_partition : crée /mnt/proxmenux-backup-LABEL (UUID ou nom du périphérique s’il n’y a pas d’étiquette), monte la partition et persiste le point de montage comme cible locale."
       },
       {
         "state": "vide",
@@ -63,7 +63,7 @@
   },
   "sidecar": {
     "heading": "Le sidecar JSON",
-    "intro": "Chaque sauvegarde locale réussie produit un fichier compagnon : HOSTNAME-TIMESTAMP.tar.zst.proxmenux.json, écrit à côté de l'archive par hb_write_archive_sidecar. Ce petit fichier JSON permet au ProxMenux Monitor d'identifier l'archive comme une sauvegarde d'hôte ProxMenux même s'il est déplacé, rebaptisé ou archivé ailleurs.",
+    "intro": "Chaque sauvegarde locale réussie produit un fichier compagnon : hostcfg-HOSTNAME-TIMESTAMP.tar.zst.proxmenux.json, écrit à côté de l’archive par hb_write_archive_sidecar. Ce petit fichier JSON permet au ProxMenux Monitor d’identifier l’archive comme une sauvegarde d’hôte ProxMenux même s’il est déplacé, rebaptisé ou archivé ailleurs.",
     "contentTitle": "Contenu du sidecar",
     "contentBody": "Le sidecar stocke la version du schéma, que la sauvegarde soit issue d'une opération interactive ou d'un travail programmé (kind), l'ID de travail pour les opérations programmées, le mode de profil utilisé (default ou custom), le nom d'hôte source, le nom de base d'archive original, un horodatage de création ISO-8601 et la taille des archives en octets.",
     "whyBody": "L'onglet Sauvegardes du moniteur scanne les répertoires locaux configurés pour les sidecars *.proxmenux.json — pas pour les fichiers *.tar.zst — parce qu'un .tar.zst sans sidecar pourrait ne pas être une sauvegarde ProxMenux du tout. Le scan est rapide (les fichiers JSON sont minuscules) et l'appariement est stable à travers les renoms de l'archive aussi longtemps que le sidecar est renommé pour correspondre."
diff --git a/web/messages/fr/docs/backup-restore/destinations/pbs.json b/web/messages/fr/docs/backup-restore/destinations/pbs.json
index dc0139b8..c72921b5 100644
--- a/web/messages/fr/docs/backup-restore/destinations/pbs.json
+++ b/web/messages/fr/docs/backup-restore/destinations/pbs.json
@@ -33,7 +33,7 @@
       {
         "source": "Configuration manuelle ProxMenux",
         "path": "/usr/local/share/proxmenux/pbs-manual-configs.txt + pbs-pass-NAME.txt + pbs-fingerprint-NAME.txt",
-        "content": "Ajouté de Configurer les destinations de sauvegarde → PBS destinations → Ajouter PBS. Prompts pour un nom, nom d'utilisateur (root@pam ou user@pbs!token), hôte ou IP, datastore et mot de passe. Le mot de passe reprend sur l'entrée vide — une sauvegarde vide persisterait silencieusement et chaque sauvegarde ultérieure échouerait avec une erreur d'authentification opaque. Ce chemin est utilisé lorsque la cible PBS n'est pas enregistrée comme stockage Proxmox."
+        "content": "Ajouté depuis Configurer les destinations de sauvegarde → Proxmox Backup Server (PBS) destinations → + Add new PBS manually. Prompts pour un nom, nom d’utilisateur (root@pam ou user@pbs!token), hôte ou IP, datastore et mot de passe. Le mot de passe est redemandé si l’entrée est vide — une sauvegarde vide persisterait silencieusement et chaque sauvegarde ultérieure échouerait avec une erreur d’authentification opaque. Ce chemin est utilisé lorsque la cible PBS n’est pas enregistrée comme stockage Proxmox."
       }
     ],
     "menuTitle": "Menu de sélection",
diff --git a/web/messages/fr/docs/backup-restore/how-it-works.json b/web/messages/fr/docs/backup-restore/how-it-works.json
index 4bec322e..75ced147 100644
--- a/web/messages/fr/docs/backup-restore/how-it-works.json
+++ b/web/messages/fr/docs/backup-restore/how-it-works.json
@@ -20,7 +20,7 @@
     "heading": "Mise en page des archives",
     "intro": "Chaque archive suit la même disposition de l'arbre, quelle que soit la destination. Le sous-répertoire metadata/ détient les charges utiles structurées; le sous-répertoire rootfs/ détient la copie du système de fichiers.",
     "treeCaption": "Le répertoire de mise en scène a été établi lors d'une sauvegarde. Chaque destination reçoit le même arbre (adapté à son format natif: tar pour local, PBS morceaux pour PBS, segments borg pour Borg).",
-    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state\n├── metadata/\n│   ├── packages.manual.list       # apt-mark showmanual\n│   ├── run_info.env               # backup run identity + kernel version + pmxcfs method\n│   ├── paths_archived.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, …\n    ├── root/                      # /root (with volatile subdirs excluded)\n    ├── usr/local/                 # /usr/local/bin, /usr/local/share/proxmenux, …\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/…"
+    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state\n├── metadata/\n│   ├── packages.manual.list       # apt-mark showmanual\n│   ├── run_info.env               # backup run identity + kernel version + pmxcfs method\n│   ├── selected_paths.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, …\n    ├── root/                      # /root (with volatile subdirs excluded)\n    ├── usr/local/                 # /usr/local/bin, /usr/local/share/proxmenux, …\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/…"
   },
   "rootfs": {
     "heading": "La partie rootfs",
@@ -176,7 +176,7 @@
         "stage": "3",
         "name": "En attente de préparation",
         "reads": "rootfs/ (redémarrage + chemins dangereux)",
-        "action": "_rs_prepare_pending_restore prépare les chemins à risque dans /var/lib/proxmenux/pending-restore/, écrit plan.env, apply-on-boot.list et rs-skip-paths.txt, puis active proxmenux-restore-onboot.service pour le prochain démarrage."
+        "action": "_rs_prepare_pending_restore prépare les chemins à risque dans /var/lib/proxmenux/restore-pending/<restore_id>/ (lié en tant que current), écrit plan.env, apply-on-boot.list et rs-skip-paths.txt, puis active proxmenux-restore-onboot.service pour le prochain démarrage."
       },
       {
         "stage": "3b",
diff --git a/web/messages/fr/docs/backup-restore/index.json b/web/messages/fr/docs/backup-restore/index.json
index 2b40cfc2..b567eb4f 100644
--- a/web/messages/fr/docs/backup-restore/index.json
+++ b/web/messages/fr/docs/backup-restore/index.json
@@ -50,7 +50,7 @@
     "heading": "Deux interfaces, un backend",
     "intro": "Chaque étape du workflow de sauvegarde et de restauration est disponible à partir de deux points d'entrée. Les deux appellent la même bibliothèque shell, produisent des archives identiques et lisent le même registre de tâches.",
     "cliLabel": "ProxMenux Scripts (TUI)",
-    "cliDetail": "menu → Utilitaires →\nSauvegarde / Restauration de l'hôte\n\nFlux basé sur le dialogue,\nCompatible avec SSH, scriptable\npour une utilisation sans surveillance.",
+    "cliDetail": "menu →\nSauvegarde et restauration de l’hôte\n\nFlux basé sur le dialogue,\nCompatible avec SSH, scriptable\npour une utilisation sans surveillance.",
     "webLabel": "ProxMenux Monitor (Web UI)",
     "webDetail": "Onglet Sauvegardes dans\nl’interface web de Monitor.\n\nRestauration en un clic, suivi\ndes journaux en direct, intégré\naux notifications et à la vue\ndes différences de retour arrière."
   },
diff --git a/web/messages/fr/docs/backup-restore/restoring.json b/web/messages/fr/docs/backup-restore/restoring.json
index fcd555cf..fc577b0a 100644
--- a/web/messages/fr/docs/backup-restore/restoring.json
+++ b/web/messages/fr/docs/backup-restore/restoring.json
@@ -92,11 +92,11 @@
   "fullFlow": {
     "heading": "Le workflow complet de restauration",
     "intro": "Le pipeline complet de la sélection des archives à un hôte opérationnel restauré. Chaque étape alimente la suivante; l'état écrit par les étapes précédentes est consommé par les suivantes.",
-    "diagram": "┌─────────────────────────────────────────────────────────────────┐\n│  Archive → staging_root/                                        │\n│    manifest.json   metadata/   rootfs/                          │\n└──────────────────────────────┬──────────────────────────────────┘\n                               │\n                               ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  1. hb_compat_check                                     │\n   │     · hardware drift → RS_SKIP_PATHS                    │\n   │     · kernel direction → same / bk_newer / bk_older     │\n   │     · NIC remap plan                                    │\n   │     · hydration plan (bk_older only)                    │\n   │     · rollback plan                                     │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  2. Confirmation dialog                                 │\n   │     Shows: hot count, pending count, drift, hydration,  │\n   │     NIC remap, cross-kernel note, reinstalls preview    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │ (accepted)\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  3. Apply hot paths → LIVE                              │\n   │     _rs_apply rsyncs each hot path                      │\n   │     from staging_root/rootfs to /                       │\n   │     (skips paths in RS_SKIP_PATHS)                      │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4. _rs_prepare_pending_restore                         │\n   │     /var/lib/proxmenux/pending-restore/                 │\n   │       ├── apply-on-boot.list                            │\n   │       ├── plan.env                                      │\n   │       ├── rs-skip-paths.txt                             │\n   │       └── rootfs/ (deferred paths)                      │\n   │     Enable proxmenux-restore-onboot.service             │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4b. _rs_import_data_pools                              │\n   │      Imports non-root ZFS pools whose disks are all     │\n   │      present; retries with -f on foreign hostid;        │\n   │      skips with a warning when disks are missing.       │\n   │      Log: /var/log/proxmenux/restore-datapools-*.log    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  5. _rs_run_complete_extras                             │\n   │     packages.manual.list                                │\n   │       → cascade-safe filter                             │\n   │       → apt-get install                                 │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  6. REBOOT                                              │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  7. apply_pending_restore.sh (on early boot)            │\n   │     Reads plan.env                                      │\n   │     Applies apply-on-boot.list (filtered)               │\n   │     Installs the postboot systemd unit                  │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  8. apply_cluster_postboot.sh (~10 minutes)             │\n   │     Runs after pve-cluster is up                        │\n   │     · Applies /etc/pve entries to pmxcfs                │\n   │     · update-initramfs -u -k all                        │\n   │     · update-grub / proxmox-boot-tool refresh           │\n   │     · component --auto-reinstall (nvidia, coral, ...)   │\n   │     · Boot sanity check                                 │\n   │     · Notification: \"Host restore finished\"             │\n   └─────────────────────────────────────────────────────────┘"
+    "diagram": "┌─────────────────────────────────────────────────────────────────┐\n│  Archive → staging_root/                                        │\n│    manifest.json   metadata/   rootfs/                          │\n└──────────────────────────────┬──────────────────────────────────┘\n                               │\n                               ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  1. hb_compat_check                                     │\n   │     · hardware drift → RS_SKIP_PATHS                    │\n   │     · kernel direction → same / bk_newer / bk_older     │\n   │     · NIC remap plan                                    │\n   │     · hydration plan (bk_older only)                    │\n   │     · rollback plan                                     │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  2. Confirmation dialog                                 │\n   │     Shows: hot count, pending count, drift, hydration,  │\n   │     NIC remap, cross-kernel note, reinstalls preview    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │ (accepted)\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  3. Apply hot paths → LIVE                              │\n   │     _rs_apply rsyncs each hot path                      │\n   │     from staging_root/rootfs to /                       │\n   │     (skips paths in RS_SKIP_PATHS)                      │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4. _rs_prepare_pending_restore                         │\n   │     /var/lib/proxmenux/restore-pending/'<'id'>'/            │\n   │       ├── apply-on-boot.list                            │\n   │       ├── plan.env                                      │\n   │       ├── rs-skip-paths.txt                             │\n   │       └── rootfs/ (deferred paths)                      │\n   │     Enable proxmenux-restore-onboot.service             │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4b. _rs_import_data_pools                              │\n   │      Imports non-root ZFS pools whose disks are all     │\n   │      present; retries with -f on foreign hostid;        │\n   │      skips with a warning when disks are missing.       │\n   │      Log: /var/log/proxmenux/restore-datapools-*.log    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  5. _rs_run_complete_extras                             │\n   │     packages.manual.list                                │\n   │       → cascade-safe filter                             │\n   │       → apt-get install                                 │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  6. REBOOT                                              │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  7. apply_pending_restore.sh (on early boot)            │\n   │     Reads plan.env                                      │\n   │     Applies apply-on-boot.list (filtered)               │\n   │     Installs the postboot systemd unit                  │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  8. apply_cluster_postboot.sh (~10 minutes)             │\n   │     Runs after pve-cluster is up                        │\n   │     · Applies /etc/pve entries to pmxcfs                │\n   │     · update-initramfs -u -k all                        │\n   │     · update-grub / proxmox-boot-tool refresh           │\n   │     · component --auto-reinstall (nvidia, coral, ...)   │\n   │     · Boot sanity check                                 │\n   │     · Notification: \"Host restore finished\"             │\n   └─────────────────────────────────────────────────────────┘"
   },
   "pendingMachinery": {
     "heading": "Mécanisme de restauration différée",
-    "intro": "Les chemins classés comme reboot ou dangerous, et l'hydratation écrit, sont mis en scène pour le prochain démarrage au lieu d'être appliqués en direct. Cela se fait par le biais d'un petit ensemble autonome de fichiers sous /var/lib/proxmenux/pending-restore/ :",
+    "intro": "Les chemins classés comme reboot ou dangerous, et l’hydratation écrit, sont mis en scène pour le prochain démarrage au lieu d’être appliqués en direct. Cela se fait par le biais d’un petit ensemble autonome de fichiers sous /var/lib/proxmenux/restore-pending/<restore_id>/ :",
     "rows": [
       {
         "file": "apply-on-boot.list",
@@ -115,7 +115,7 @@
         "content": "Les fichiers mis en scène eux-mêmes — les octets exacts qui seront établis. Conservé sur le même système de fichiers que / de sorte que le rsync on-boot est rapide et ne dépend pas du stockage externe encore accessible au démarrage."
       }
     ],
-    "unitBody": "L’unité systemd proxmenux-restore-onboot.service est activée à la fin de l’étape interactive. Ce service ponctuel démarre tôt au prochain démarrage, appelle apply_pending_restore.sh puis se désactive. La condition ConditionPathExists=/var/lib/proxmenux/pending-restore/state empêche toute exécution lorsqu’aucune restauration n’est en attente."
+    "unitBody": "L’unité systemd proxmenux-restore-onboot.service est activée à la fin de l’étape interactive. Ce service ponctuel démarre tôt au prochain démarrage (avant le réseau), appelle apply_pending_restore.sh puis se désactive. L’unité n’a aucune condition de chemin : c’est le script lui-même qui se termine lorsque /var/lib/proxmenux/restore-pending/current n’existe pas, ce qui en fait une opération vide à tout démarrage sans restauration en attente. Le service distinct proxmenux-apply-cluster-postboot.service est conditionné par ConditionPathExists=/var/lib/proxmenux/cluster-apply-pending."
   },
   "postbootDispatcher": {
     "heading": "Le répartiteur après démarrage — apply_cluster_postboot.sh",
@@ -238,11 +238,11 @@
     "heading": "Emplacement des journaux",
     "rows": [
       {
-        "log": "/var/log/proxmenux/restore-YYYYMMDD_HHMMSS.log",
-        "detail": "Écrit lors de l’étape interactive : résultats de compatibilité, filtre des différences, plan d’adaptation, sorties de l’application à chaud et de la préparation différée, installation des paquets."
+        "log": "/tmp/proxmenux--restore-YYYYMMDD_HHMMSS.log",
+        "detail": "Écrit lors de l’étape interactive (pbs, borg ou local). Les réinstallations de paquets et les étapes des pools de données écrivent aussi dans /var/log/proxmenux/restore-apt-*.log et restore-datapools-*.log."
       },
       {
-        "log": "/var/log/proxmenux/apply-pending-YYYYMMDD_HHMMSS.log",
+        "log": "/var/log/proxmenux/proxmenux-restore-onboot-YYYYMMDD_HHMMSS.log",
         "detail": "Écrit par apply_pending_restore.sh au début du démarrage : chargement de plan.env, parcours de apply-on-boot et filtrage des chemins exclus."
       },
       {
diff --git a/web/messages/fr/docs/backup-restore/scheduled-jobs.json b/web/messages/fr/docs/backup-restore/scheduled-jobs.json
index 384fbe2c..a5e99cf8 100644
--- a/web/messages/fr/docs/backup-restore/scheduled-jobs.json
+++ b/web/messages/fr/docs/backup-restore/scheduled-jobs.json
@@ -101,7 +101,7 @@
   },
   "management": {
     "heading": "Gestion des tâches",
-    "intro": "Le menu planificateur (Scripts TUI) et l'onglet Sauvegardes Monitor exposent les mêmes actions sur n'importe quel travail.",
+    "intro": "Le menu planificateur du Scripts TUI (Backup scheduler and retention) propose Run now, Enable / Disable et Delete, ainsi qu’une liste des travaux avec l’état de leur dernière exécution. Edit et View log ne sont disponibles que dans l’onglet Sauvegardes de Monitor.",
     "rows": [
       {
         "action": "Exécuter maintenant",
@@ -112,7 +112,7 @@
         "detail": "Nouveau mode : systemctl enable/disable --now la minuterie. Attache le mode : retourne le drapeau ENABLED dans le travail env — le script hook l'honore au prochain déclenchement PVE."
       },
       {
-        "action": "Modifier",
+        "action": "Modifier (Monitor uniquement)",
         "detail": "Rouvre les champs de destination, calendrier, profil et rétention et réécrit les fichiers de la tâche. Conserve son identifiant."
       },
       {
@@ -120,7 +120,7 @@
         "detail": "Supprime le fichier env, la liste des chemins et le minuteur et service systemd (nouvelle tâche), et désactive l’association au hook (mode d’association). Les archives déjà présentes à destination restent intactes."
       },
       {
-        "action": "Voir le journal",
+        "action": "Voir le journal (Monitor uniquement)",
         "detail": "Affiche en continu /var/log/proxmenux/backup-jobs/JOB_ID-YYYYMMDD_HHMMSS.log : un fichier journal par exécution. Le programme d'exécution émet également une ligne d'état compacte à journald sous le service systemd."
       }
     ]
diff --git a/web/messages/fr/docs/installation.json b/web/messages/fr/docs/installation.json
index 46cbbbf9..62837865 100644
--- a/web/messages/fr/docs/installation.json
+++ b/web/messages/fr/docs/installation.json
@@ -53,11 +53,11 @@
     "calloutBody": "Vous souhaitez essayer les nouveautés avant leur sortie officielle et contribuer à la version finale ? Le programme bêta de ProxMenux donne accès aux nouvelles fonctionnalités, dont les dernières compilations de Monitor, depuis la branche develop. Les versions bêta peuvent contenir des bogues ou des fonctions incomplètes. Vos retours aident à les corriger avant la version stable.",
     "intro": "Installez la version bêta au lieu de la version stable avec :",
     "code": "bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/develop/install_proxmenux_beta.sh)\"",
-    "outro": "La bêta est facultative : vous devez lancer explicitement son installateur. Lorsqu’une version stable est publiée, ProxMenux vous avertit au prochain lancement de menu et propose de basculer automatiquement. Pour quitter la bêta plus tôt, choisissez Désactiver le programme bêta dans les paramètres."
+    "outro": "La bêta est facultative : vous devez lancer explicitement son installateur. Lorsqu’une version stable est publiée, le prochain lancement de menu la propose avec l’invite de mise à jour habituelle. Pour quitter le canal bêta, utilisez Changer le canal de publication dans les paramètres et choisissez Stable."
   },
   "updating": {
     "heading": "Mise à jour",
-    "body": "ProxMenux gère ses mises à jour. Si une nouvelle version est disponible, le prochain lancement de menu propose de l’installer. En acceptant, vous remplacez les fichiers et la configuration de l’outil dans l’installation existante, sans téléchargement manuel. Le canal stable reçoit les versions stables, et la bêta les versions bêta, avec la proposition de bascule décrite ci-dessus. L’affichage dépend de l’endroit où vous lancez menu : via SSH ou dans la console Proxmox, une boîte oui/non permet d’appliquer immédiatement la mise à jour. Dans le terminal WebSocket de ProxMenux Monitor, un message informatif la remplace, car ce terminal serait interrompu pendant l’opération. Il affiche l’installateur en une ligne (bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\" pour stable, ou l’URL correspondante de install_proxmenux_beta.sh pour bêta), à exécuter via SSH ou dans la console."
+    "body": "ProxMenux gère ses mises à jour. Si une nouvelle version est disponible, le prochain lancement de menu propose de l’installer. En acceptant, vous remplacez les fichiers et la configuration de l’outil dans l’installation existante, sans téléchargement manuel. Le canal stable reçoit les versions stables, et la bêta les versions bêta et se voit aussi proposer les nouvelles versions stables. L’affichage dépend de l’endroit où vous lancez menu : via SSH ou dans la console Proxmox, une boîte oui/non permet d’appliquer immédiatement la mise à jour. Dans le terminal WebSocket de ProxMenux Monitor, un message informatif la remplace, car ce terminal serait interrompu pendant l’opération. Il affiche l’installateur en une ligne (bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\" pour stable, ou l’URL correspondante de install_proxmenux_beta.sh pour bêta), à exécuter via SSH ou dans la console."
   },
   "uninstall": {
     "heading": "Désinstallation",
diff --git a/web/messages/fr/docs/introduction.json b/web/messages/fr/docs/introduction.json
index 2bba4cb5..1d1f9c9b 100644
--- a/web/messages/fr/docs/introduction.json
+++ b/web/messages/fr/docs/introduction.json
@@ -21,11 +21,11 @@
     "intro": "ProxMenux comprend deux composants dans le même arbre de sources. L’installateur déploie les deux sur l’hôte : Scripts fournit l’interface de terminal et Monitor le tableau de bord web :",
     "scripts": {
       "title": "ProxMenux Scripts",
-      "body": "La CLI/TUI d’origine, à lancer avec menu. Neuf domaines couvrent les optimisations après installation, les VM et conteneurs LXC, le passthrough GPU et Coral TPU, les disques, le stockage, le réseau, la sécurité et les utilitaires, avec une sélection de commandes de référence. Fonctionne dans un terminal local, SSH ou web."
+      "body": "La CLI/TUI d’origine, à lancer avec menu. Les domaines fonctionnels couvrent les optimisations après installation, les VM et conteneurs LXC, le passthrough GPU et Coral TPU, les disques, le stockage, les applications OCI, le réseau, la sécurité, les utilitaires, la sauvegarde et la restauration de l’hôte, les Proxmox VE Helper Scripts, avec une sélection de commandes de référence. Fonctionne dans un terminal local, SSH ou web."
     },
     "monitor": {
       "title": "ProxMenux Monitor",
-      "body": "Tableau de bord web auto-hébergé, distribué en AppImage. Métriques de l’hôte en temps réel, surveillance proactive, notifications, reformulation facultative par IA avec six fournisseurs, terminal web intégré et API REST. À activer dans le menu Scripts, sous Paramètres → ProxMenux Monitor."
+      "body": "Tableau de bord web auto-hébergé, distribué en AppImage. Métriques de l’hôte en temps réel, surveillance proactive, notifications, reformulation facultative par IA avec six fournisseurs, terminal web intégré et API REST. L’installateur le configure comme un service lancé au démarrage ; activez-le, désactivez-le ou vérifiez-le depuis le menu Scripts (Paramètres → ProxMenux Monitor)."
     },
     "calloutTitle": "Sur quoi ProxMenux intervient",
     "calloutIntro": "Trois couches, toutes accessibles du même projet :",
@@ -37,7 +37,7 @@
   },
   "scriptsSection": {
     "heading": "ProxMenux Scripts — ce que vous obtenez",
-    "intro": "Neuf zones fonctionnelles, chacune avec son propre menu à l'intérieur de menu et une page de détail dans la documentation. Cliquez sur n'importe quelle carte pour sauter directement à l'aperçu de cette section.",
+    "intro": "Les principales zones fonctionnelles, chacune avec son propre menu à l’intérieur de menu et une page de détail dans la documentation. Cliquez sur n’importe quelle carte pour sauter directement à l’aperçu de cette section.",
     "items": [
       {
         "title": "Après l'installation",
diff --git a/web/messages/fr/docs/monitor/access-auth.json b/web/messages/fr/docs/monitor/access-auth.json
index 8932f21e..39c80222 100644
--- a/web/messages/fr/docs/monitor/access-auth.json
+++ b/web/messages/fr/docs/monitor/access-auth.json
@@ -18,7 +18,7 @@
   },
   "reaching": {
     "heading": "Atteindre le tableau de bord",
-    "intro": "Le ProxMenux Monitor se lie au 0.0.0.0:8008. Il y a trois façons communes d'y parvenir:",
+    "intro": "Le ProxMenux Monitor écoute sur le TCP 8008 sur toutes les interfaces (::, IPv4 et IPv6). Il y a trois façons communes d’y accéder :",
     "outro": "L’accès direct correspond à l’unité systemd par défaut. Le proxy inverse et Secure Gateway sont décrits ci-dessous. La confiance accordée aux IP clientes de X-Forwarded-For nécessite PROXMENUX_TRUST_PROXY=1 et un proxy que vous contrôlez. La seule présence de X-Forwarded-Proto ou X-Forwarded-Host n’active pas la confiance envers le proxy. Sur les ports 80/443, le frontend utilise des URL relatives /api/* ; sur tout autre port, il appelle le port 8008 du même nom d’hôte."
   },
   "firstLaunch": {
@@ -158,7 +158,7 @@
     "authJsonTitle": "auth.json — ce qu'il contient et comment il est protégé",
     "authJsonBody": "Les données d’authentification se trouvent dans /root/.config/proxmenux-monitor/auth.json, mode 0600, propriétaire root. Le fichier contient des hachages PBKDF2 et des secrets cryptographiques (jwt_secret, totp_secret), jamais le mot de passe en clair. Chiffrez les destinations de sauvegarde ou de réplication et n’ajoutez jamais ce fichier au contrôle de version.",
     "rotateTitle": "La rotation de jwt_secret invalide tous les JWT existants",
-    "rotateBody": "Si auth.json est recréé ou restauré avec un autre secret, jwt_secret change. Les anciens JWT de session et d’API échouent alors avec « Invalid or expired token ». L’interface affiche Invalide — régénérer pour les jetons concernés. Révoquez-les et remplacez-les aussi dans Home Assistant, les scripts et les autres clients.",
+    "rotateBody": "Si auth.json est recréé ou restauré avec un autre secret, jwt_secret change. Les anciens JWT de session et d’API échouent alors avec « Invalid or expired token ». L’interface marque en rouge les jetons d’API concernés, avec un badge indiquant de les révoquer et d’en générer de nouveaux. Remplacez-les aussi dans Home Assistant, les scripts et les autres clients.",
     "recoverTitle": "Récupération d'un mot de passe perdu",
     "recoverIntro": "Il n'y a pas de flux \"mot de passe oublié\" en ligne — par conception, puisque le tableau de bord fonctionne sur le propre hôte de l'opérateur et le chemin de récupération est l'accès shell à cet hôte. ProxMenux envoie une réinitialisation guidée dans le menu de configuration afin que vous n'ayez pas à éditer manuellement auth.json:",
     "survivesTitle": "Ce qui survit à la réinitialisation",
@@ -284,14 +284,14 @@
     "heading": "Jetons API de longue durée",
     "intro": "Les sessions expirent après 24 heures. Pour Homepage, Home Assistant, Grafana ou Uptime Kuma, créez un jeton API valable 365 jours. Ce JWT utilise le même secret de signature ; le champ token_name permet de le suivre et de le révoquer individuellement.",
     "imageAlt": "Panneau de jetons API montrant la liste de jetons avec nom, préfixe, date de création et expiration",
-    "imageCaption": "Liste des jetons dans les paramètres : nom, préfixe avec quatre derniers caractères d’identification, dates de création et d’expiration, action de révocation.",
+    "imageCaption": "Liste des jetons d’API sous Administrateur → Sécurité : nom, préfixe avec quatre derniers caractères d’identification, dates de création et d’expiration, action de révocation.",
     "generateTitle": "Générer un jeton",
     "generateIntro": "Dans le tableau de bord:",
     "generateSteps": [
-      "Ouvrez Sécurité → Jetons d’accès API.",
+      "Ouvrez Administrateur → Sécurité → Jetons API et cliquez sur Générer un nouveau jeton.",
       "Tapez un nom descriptif (p.ex. \"Home Assistant\").",
       "Entrez de nouveau votre mot de passe. Si 2FA est activé, aussi le code à chiffres 6 actuel.",
-      "Cliquez sur Générer le jeton. Il s’affiche une seule fois : copiez-le immédiatement."
+      "Cliquez sur Générer un jeton. Le jeton n’apparaît qu’une fois : copiez-le immédiatement."
     ],
     "generateCli": "Depuis la ligne de commande :",
     "useTitle": "Utiliser un jeton",
@@ -310,7 +310,7 @@
     "intro": "Deux chemins vers TLS:",
     "items": [
       "Proxy inverse recommandé. Terminez TLS sur Nginx / Caddy / Traefik et transmettez HTTP au processus Flask sur le port 8008. Exemples ci-dessous.",
-      "HTTPS direct dans l’AppImage. Configurez un certificat via POST /api/ssl/configure (interface : Paramètres → SSL). HTTPS utilise gevent.pywsgi avec son gestionnaire standard ; flask-sock gère WebSocket pour WSS. Les chemins des certificats sont enregistrés dans la configuration SSL."
+      "HTTPS direct dans l’AppImage. Configurez un certificat via POST /api/ssl/configure (interface : Administrateur → Sécurité → SSL / HTTPS). HTTPS utilise gevent.pywsgi avec son gestionnaire standard ; flask-sock gère WebSocket pour WSS. Les chemins des certificats sont enregistrés dans la configuration SSL."
     ],
     "calloutTitle": "Limites directes de HTTPS",
     "calloutBody": "Monitor peut utiliser un certificat géré par Proxmox ou des fichiers personnalisés. Il n’émet pas lui-même de certificats ACME. Pour l’émission et le renouvellement automatiques, utilisez Proxmox ACME ou un proxy inverse Caddy, Traefik ou Nginx et veillez au chargement des certificats renouvelés."
diff --git a/web/messages/fr/docs/monitor/ai-assistant.json b/web/messages/fr/docs/monitor/ai-assistant.json
index 41862525..496db268 100644
--- a/web/messages/fr/docs/monitor/ai-assistant.json
+++ b/web/messages/fr/docs/monitor/ai-assistant.json
@@ -9,7 +9,7 @@
   },
   "header": {
     "title": "Assistant IA",
-    "description": "La reformulation IA facultative transforme les modèles de notification admissibles en messages clairs : niveaux de détail par canal, treize langues dans l’interface, instructions personnalisées, bibliothèque communautaire publique et contexte enrichi. Les rapports backup_complete et backup_fail ignorent entièrement l’IA.",
+    "description": "La reformulation IA facultative transforme les modèles de notification admissibles en messages clairs : niveaux de détail par canal, treize langues dans l’interface, instructions personnalisées, bibliothèque communautaire publique et contexte enrichi. Les rapports backup_complete et backup_fail ne sont jamais reformulés ; avec les suggestions d’IA activées, l’IA ajoute seulement une explication lorsqu’une sauvegarde signale des erreurs.",
     "section": "Moniteur ProxMenux"
   },
   "intro": {
@@ -83,7 +83,7 @@
     "intro1": "Les fournisseurs commerciaux facturent les tokens. Un token représente environ quatre caractères anglais ou trois quarts de mot. La phrase anglaise \"Backup completed on storage local-bak\" compte environ huit tokens. Dix lignes de journal peuvent représenter 200 à 400 tokens selon leur contenu technique.",
     "intro2": "Deux choses sont facturées à chaque appel :",
     "items": [
-      "Tokens d’entrée : instructions système et message utilisateur, avec gravité, titre, texte du modèle et contexte enrichi. Leur nombre dépend des instructions et de l’événement. Chaque canal reformulé peut consommer ses propres tokens ; les rapports de sauvegarde terminée ou échouée ignorent l’IA.",
+      "Tokens d’entrée : instructions système et message utilisateur, avec gravité, titre, texte du modèle et contexte enrichi. Leur nombre dépend des instructions et de l’événement. Chaque canal reformulé peut consommer ses propres tokens ; les rapports de sauvegarde terminée ou échouée ne consomment des tokens que lorsque l’IA explique des erreurs de sauvegarde.",
       "Tokens de sortie : la réponse du modèle. Monitor les limite avec max_tokens. Le plafond est une limite, pas une consommation fixe : pour 250 tokens produits avec un plafond de 1500, vous payez 250."
     ],
     "capsIntro": "Ce sont les plafonds réels que le régulateur applique, pris directement à partir de AI_DETAIL_TOKENS dans notification_templates.py:",
@@ -118,7 +118,7 @@
     "imageCaption": "La fenêtre de l’application : six fiches, une par fournisseur, avec les descriptions ci-dessous.",
     "groq": {
       "heading": "Groq",
-      "tagline": "Très rapide, généreux niveau libre (30 req/min). Idéal pour commencer.",
+      "tagline": "Très rapide, avec un généreux niveau gratuit. Un bon point de départ.",
       "items": [
         "Clé API : console.groq.com/keys",
         "Exemples de la liste de modèles fournie : allam-2-7b, qwen/qwen3.8-27b, openai/gpt-oss-20b, openai/gpt-oss-120b.",
@@ -234,7 +234,7 @@
   },
   "detailLevel": {
     "heading": "Niveau de détail par canal",
-    "intro": "Telegram, Discord, Gotify et e-mail ont chacun un choix de détail. Trois valeurs déterminent le plafond de tokens et les instructions du mode par défaut :",
+    "intro": "Chacun des six canaux (Telegram, Discord, Gotify, e-mail, Pushover, Apprise) a son propre choix de détail. Trois valeurs déterminent le plafond de tokens et les instructions du mode par défaut :",
     "headerLevel": "Niveau",
     "headerLabel": "Étiquette UI",
     "headerCap": "Limite de production",
@@ -261,7 +261,7 @@
     ],
     "defaultsIntro": "Valeurs par défaut à la première installation :",
     "defaults": [
-      "Telegram, Discord, Gotify — standard.",
+      "Telegram, Discord, Gotify, Pushover, Apprise — standard.",
       "Courriel — detailed. Le courriel est le canal où vous voulez généralement l'image complète pour l'archivage."
     ],
     "emailTitle": "Le niveau de détail du courriel ajoute l'original",
@@ -280,7 +280,7 @@
   },
   "templates": {
     "heading": "Note sur les modèles",
-    "body1": "L’IA reçoit le texte d’un modèle, pas les données brutes de l’événement. vm_start, auth_fail et health_degraded ont des modèles dans notification_templates.py. L’IA reformule les sorties admissibles ; backup_complete et backup_fail conservent leurs rapports déterministes sans IA.",
+    "body1": "L’IA reçoit le texte d’un modèle, pas les données brutes de l’événement. vm_start, auth_fail et health_degraded ont des modèles dans notification_templates.py. L’IA reformule les sorties admissibles ; backup_complete et backup_fail conservent leurs rapports déterministes ; avec les suggestions d’IA activées, l’IA ajoute seulement une explication lorsque la sauvegarde signale des erreurs.",
     "body2": "Deux implications pratiques: l'IA ne voit jamais un nom d'hôte ou VMID qu'il doit inventer — ces champs sont placés par le modèle avant la réécriture. Et si l'IA est désactivé, le corps modèle est ce qui est envoyé directement. La page Notifications documente le pipeline d'expédition en entier et est la bonne référence pour tout ce qui arrive à un événement avant qu'il atteigne ce calque."
   },
   "privacy": {
diff --git a/web/messages/fr/docs/monitor/api-reference.json b/web/messages/fr/docs/monitor/api-reference.json
index c11019c5..6e4dddd6 100644
--- a/web/messages/fr/docs/monitor/api-reference.json
+++ b/web/messages/fr/docs/monitor/api-reference.json
@@ -76,7 +76,7 @@
       {
         "endpoint": "/api/system-info",
         "method": "GET",
-        "use": "Données système étendues de l’en-tête : métriques générales et heure de démarrage."
+        "use": "Informations d’en-tête légères : nom d’hôte, durée de fonctionnement et état de santé en cache. Public, aucun jeton requis."
       },
       {
         "endpoint": "/api/hardware",
@@ -401,7 +401,7 @@
       {
         "endpoint": "/api/logs",
         "method": "GET",
-        "use": "Entrées de journaux filtrées. Demande: ?level=&service=&limit=."
+        "use": "Entrées de journaux filtrées. Demande : ?priority=&service=&limit=&since_days= (priority 0-7, since_days jusqu’à 90)."
       },
       {
         "endpoint": "/api/logs/download",
@@ -411,7 +411,7 @@
       {
         "endpoint": "/api/events",
         "method": "GET",
-        "use": "Flux d'événements internes — le même qui alimente les notifications."
+        "use": "Tâches et événements récents du cluster Proxmox issus de pvesh get /cluster/tasks. Demande : ?limit= (50 par défaut)."
       },
       {
         "endpoint": "/api/task-log/",
@@ -462,7 +462,7 @@
       {
         "endpoint": "/api/notifications/send",
         "method": "POST",
-        "use": "Émet un événement personnalisé. Corps : '{'\"event_type\":\"custom\",\"severity\":\"WARNING\",\"title\":\"...\",\"body\":\"...\",\"data\":'{''}''}'."
+        "use": "Émet un événement personnalisé. Corps : '{'\"event_type\":\"custom\",\"severity\":\"WARNING\",\"title\":\"...\",\"message\":\"...\",\"data\":'{''}''}'."
       },
       {
         "endpoint": "/api/notifications/test-ai",
diff --git a/web/messages/fr/docs/monitor/architecture.json b/web/messages/fr/docs/monitor/architecture.json
index 71a14829..6873337a 100644
--- a/web/messages/fr/docs/monitor/architecture.json
+++ b/web/messages/fr/docs/monitor/architecture.json
@@ -64,7 +64,7 @@
     "items": [
       "User=root : le service utilise les privilèges root pour SMART, pvesh et la gestion ZFS. Les processus du terminal web héritent de ces droits.",
       "Restart=on-failure avec attente de dix secondes : le service redémarre automatiquement après un échec.",
-      "After=network.target : ordonne le démarrage après cette cible ; ne garantit ni adresse IP, ni DNS, ni connexion externe."
+      "After=network.target pve-cluster.service : ordonne le démarrage après la cible réseau et le système de fichiers du cluster Proxmox ; ne garantit ni adresse IP attribuée, ni DNS, ni connexion externe."
     ],
     "inspectTitle": "Inspecter l’unité actuelle"
   },
diff --git a/web/messages/fr/docs/monitor/dashboard/hardware.json b/web/messages/fr/docs/monitor/dashboard/hardware.json
index 86a0428e..d5d7e1ff 100644
--- a/web/messages/fr/docs/monitor/dashboard/hardware.json
+++ b/web/messages/fr/docs/monitor/dashboard/hardware.json
@@ -168,7 +168,7 @@
   "ups": {
     "heading": "Statut UPS",
     "subHeading": "(conditionnel)",
-    "body": "Apparaît avec un serveur NUT (Network UPS Tools) configuré et joignable. Affiche état (secteur / batterie / charge / batterie faible), niveau de batterie, autonomie estimée, taux de charge de l'onduleur, tension d'entrée, modèle et firmware. Les données alimentent aussi Sécurité et certificats du moniteur de santé : le passage sur batterie est immédiatement signalé."
+    "body": "Apparaît avec un serveur NUT (Network UPS Tools) configuré et joignable. Affiche état (secteur / batterie / charge / batterie faible), niveau de batterie, autonomie estimée, taux de charge de l’onduleur, tension d’entrée, modèle et firmware."
   },
   "dataCollected": {
     "heading": "Comment les données sont collectées",
diff --git a/web/messages/fr/docs/monitor/dashboard/index.json b/web/messages/fr/docs/monitor/dashboard/index.json
index 4884cc4c..4f530d9b 100644
--- a/web/messages/fr/docs/monitor/dashboard/index.json
+++ b/web/messages/fr/docs/monitor/dashboard/index.json
@@ -50,7 +50,7 @@
       },
       {
         "name": "Terminal",
-        "owns": "Shell vers l'hôte ou toute VM/CT dans le navigateur, avec xterm.js via WebSockets."
+        "owns": "Shell dans le navigateur sur l’hôte Proxmox, avec xterm.js via WebSockets. Les consoles LXC s’ouvrent depuis l’onglet VM et LXC."
       },
       {
         "name": "Sécurité",
diff --git a/web/messages/fr/docs/monitor/dashboard/security.json b/web/messages/fr/docs/monitor/dashboard/security.json
index 26511643..8449dba8 100644
--- a/web/messages/fr/docs/monitor/dashboard/security.json
+++ b/web/messages/fr/docs/monitor/dashboard/security.json
@@ -183,7 +183,7 @@
       },
       {
         "card": "SSL/HTTPS",
-        "endpoint": "/api/auth/ssl/*",
+        "endpoint": "/api/ssl/*",
         "source": "openssl x509 sur /etc/pve/local/pve-ssl.pem + /etc/proxmenux/ssl_config.json."
       },
       {
diff --git a/web/messages/fr/docs/monitor/dashboard/settings.json b/web/messages/fr/docs/monitor/dashboard/settings.json
index ee877c2b..b22c1634 100644
--- a/web/messages/fr/docs/monitor/dashboard/settings.json
+++ b/web/messages/fr/docs/monitor/dashboard/settings.json
@@ -41,7 +41,7 @@
     "heading": "Unités réseau",
     "imageAlt": "Carte Unités réseau avec le sélecteur d’affichage sur Octets",
     "imageCaption": "Choisissez les bits ou les octets par seconde pour toutes les vitesses réseau du tableau de bord.",
-    "body": "Affiche le débit en bits par seconde (Mbps/Gbps) ou octets par seconde (MB/s/GB/s). Les bits sont utilisés par défaut comme dans les spécifications réseau ; les transferts de fichiers utilisent souvent les octets. Graphiques, étiquettes et infobulles changent immédiatement sans rechargement."
+    "body": "Affiche le débit réseau dans tout le tableau de bord en bits par seconde (Mbps / Gbps) ou octets par seconde (MB/s / GB/s). Les octets sont utilisés par défaut, comme dans la plupart des outils de transfert de fichiers ; les bits sont ceux dont les fabricants de cartes réseau et les FAI étiquettent leurs produits. Le choix est enregistré dans le navigateur, chacun conserve donc le sien. Graphiques, étiquettes et infobulles qui affichent un débit réseau changent immédiatement sans rechargement."
   },
   "health": {
     "heading": "Moniteur de santé",
@@ -203,7 +203,7 @@
     "heading": "Détection de mise à jour LXC",
     "imageAlt": "Carte de détection des mises à jour LXC avec interrupteur pour les paquets en attente des conteneurs Debian, Ubuntu et Alpine actifs",
     "imageCaption": "Interrupteur de l’analyse périodique par apt list --upgradable / apk list -u dans les CT actifs. Activé par défaut. Le réglage associé dans Notifications → Services apparaît lorsque la détection est active.",
-    "intro": "L’interrupteur entre Seuils et Notifications contrôle l’analyse LXC. Activé, il interroge les gestionnaires de paquets des CT actifs ; Matériel affiche les résultats et alimente lxc_updates_available. Désactivé, il arrête ses appels pct exec et supprime les entrées LXC de managed_installs.json ; /api/managed-installs cesse d’afficher cet état sans attendre 24 h.",
+    "intro": "L’interrupteur entre Seuils et Notifications contrôle l’analyse LXC. Activé, il interroge les gestionnaires de paquets des CT actifs ; l’onglet VM et LXC affiche les résultats et alimente lxc_updates_available. Désactivé, il arrête ses appels pct exec et supprime les entrées LXC de managed_installs.json ; /api/managed-installs cesse d’afficher cet état sans attendre 24 h.",
     "whatRunsTitle": "Ce que l’analyse exécute",
     "whatRunsIntro": "Pour chaque CT à l’état running avec un gestionnaire de paquets pris en charge :",
     "whatRunsItems": [
@@ -249,7 +249,7 @@
     "body1": "Cette section active les notifications de ProxMenux Monitor et la reformulation par IA. Activer les notifications démarre l’envoi en arrière-plan, enregistre une cible webhook Proxmox VE pour intégrer ses événements et ouvre le formulaire des canaux : Telegram, Discord, courriel, Gotify et autres. La reformulation par IA figure dans une section avancée repliable du même panneau.",
     "body2": "Des pages dédiées expliquent les options : canaux, événements, messages enrichis, nom affiché, envoi avec dédoublonnage, temporisation, regroupement et heures silencieuses, webhook PVE, fournisseurs d’IA et modes d’instructions :",
     "items": [
-      "Notifications : configuration des canaux (Telegram, Discord, Gotify, courriel et mots de passe d’application Gmail / Microsoft, ntfy, Slack, Teams et webhook générique), catégories d’événements, messages enrichis, nom affiché, envoi, webhook PVE, historique et API.",
+      "Notifications : configuration des canaux (Telegram, Discord, Gotify, courriel et mots de passe d’application Gmail / Microsoft, Pushover, et Apprise pour ntfy, Slack, Teams et d’autres services), catégories d’événements, messages enrichis, nom affiché, envoi, webhook PVE, historique et API.",
       "Assistant IA : fournisseurs (OpenAI, Anthropic, Gemini, Groq, OpenRouter, Ollama), modèles, instructions par défaut ou personnalisées, langue de sortie, niveau de détail par canal et suggestions d’IA."
     ]
   },
@@ -294,8 +294,8 @@
       },
       {
         "card": "Unités réseau",
-        "endpoint": "/api/settings",
-        "source": "Enregistré dans la table SQLite des paramètres du tableau de bord."
+        "endpoint": "localStorage",
+        "source": "Préférence du navigateur proxmenux-network-unit."
       },
       {
         "card": "Durées du Health Monitor",
@@ -304,7 +304,7 @@
       },
       {
         "card": "Exclusions de stockage / interface",
-        "endpoint": "/api/storage/exclusions, /api/network/exclusions",
+        "endpoint": "/api/health/storage-exclusions, /api/health/interface-exclusions",
         "source": "Tables SQLite excluded_storages et excluded_interfaces."
       },
       {
diff --git a/web/messages/fr/docs/monitor/dashboard/storage.json b/web/messages/fr/docs/monitor/dashboard/storage.json
index 7f5304ca..10662a9c 100644
--- a/web/messages/fr/docs/monitor/dashboard/storage.json
+++ b/web/messages/fr/docs/monitor/dashboard/storage.json
@@ -10,7 +10,7 @@
   },
   "intro": {
     "title": "Soutenu par trois sources",
-    "body": "Les stockages Proxmox viennent de pvesm status, ZFS de zpool status, les disques de lsblk + smartctl et les champs NVMe de nvme. L’onglet s’actualise environ toutes les 60 secondes ; ouvrir le détail demande une lecture SMART."
+    "body": "Les stockages Proxmox viennent de pvesm status, ZFS de zpool status, les disques de lsblk + smartctl et les champs NVMe de nvme. L’onglet s’actualise environ toutes les 30 secondes ; ouvrir le détail demande une lecture SMART."
   },
   "thresholds": {
     "title": "Couleurs et seuils appliqués ici",
diff --git a/web/messages/fr/docs/monitor/dashboard/system-logs.json b/web/messages/fr/docs/monitor/dashboard/system-logs.json
index 80cf6b27..e9c55e6d 100644
--- a/web/messages/fr/docs/monitor/dashboard/system-logs.json
+++ b/web/messages/fr/docs/monitor/dashboard/system-logs.json
@@ -18,7 +18,7 @@
       "Total des entrées — nombre de lignes dans la période du filtre actif.",
       "Erreurs — nombre de lignes de gravité ≤ 3 (err / crit / alert / emerg).",
       "Avertissements — nombre de lignes de gravité 4 (warning).",
-      "Sauvegardes — nombre de tâches vzdump / PBS dans la même période."
+      "Sauvegardes : nombre d’archives de sauvegarde sur les stockages de sauvegarde configurés."
     ]
   },
   "subtabs": {
@@ -26,10 +26,10 @@
     "logsTitle": "Journaux",
     "logsIntro": "Le journal système, obtenu sur le serveur avec journalctl. Filtres disponibles dans la barre d'outils :",
     "logsFilters": [
-      "Gravité — emerg / alert / crit / err / warning / notice / info / debug, ou toute combinaison.",
-      "Délais — dernier 5 min/ 15 min/ 1 h/ 6 h/ 24 h/ 7 d/sur mesure.",
-      "Recherche libre — sous-chaîne ou expression régulière (journalctl --grep).",
-      "Filtre par unité — limite les résultats à une unité systemd donnée (pveproxy.service, nginx.service, …)."
+      "Niveau : Tous niveaux, Erreur, Avertissement ou Informations.",
+      "Délais : 1 jour / 3 jours / 1 semaine / 2 semaines / 1 mois, ou un nombre de jours sur mesure (jusqu’à 90).",
+      "Recherche libre : sous-chaîne insensible à la casse dans le message, le service, le PID, le nom d’hôte et l’unité, appliquée dans le navigateur.",
+      "Filtre par service : limite les résultats à un service (le SYSLOG_IDENTIFIER du journal)."
     ],
     "logsRowsAfter": "Chaque ligne affiche l'horodatage, le badge de gravité, l'unité source et le message. Les longs messages s'effondrent avec un \"show more\" bascule. L'action Download regroupe le filtre actuel dans un seul fichier .txt via GET /api/logs/download — utile quand vous voulez partager une tranche de journal avec quelqu'un.",
     "logDetailsModalTitle": "Fenêtre Détails du journal",
@@ -50,10 +50,10 @@
     "maxLevelStoreTitle": "Journald MaxLevelStore",
     "maxLevelStoreBody": "Sur une nouvelle installation Proxmox, journald utilise par défaut MaxLevelStore=warning, ce qui écarte les messages info. Monitor le détecte au démarrage et ajoute un fichier de configuration complémentaire (/etc/systemd/journald.conf.d/proxmenux-loglevel.conf) avec le seuil info, pour conserver les messages informatifs et plus graves. Cela ne comprend pas debug.",
     "backupsTitle": "Sauvegardes",
-    "backupsBody": "Historique des tâches Proxmox limité aux sauvegardes. Une ligne par tâche (vzdump, transferts PBS, récupération de mémoire inutilisée, vérification), avec état (OK / WARNINGS / ERROR), invité concerné, stockage source, durée et UPID. Cliquez sur une ligne pour charger le journal complet via GET /api/task-log/<upid> : les mêmes données que dans Datacenter → Tâches, limitées aux sauvegardes.",
+    "backupsBody": "Les archives de sauvegarde de chaque stockage compatible avec les sauvegardes (dir, NFS, CIFS, PBS), lues avec pvesh get /nodes/localhost/storage/<id>/content. Les cartes en haut indiquent le nombre de VM et de LXC et la taille totale ; en dessous, une ligne par archive avec type, stockage, taille et date. La restauration se fait dans l’interface web de Proxmox.",
     "notificationsTitle": "Notifications",
-    "notificationsBody1": "Toutes les notifications envoyées par Monitor : Telegram, Discord, e-mail, Gotify, ntfy, Slack, Teams et webhook. Chaque ligne indique l'horodatage, le canal, le type d'événement, la gravité, le titre et le corps générés et, si l'IA est activée, un bouton pour comparer sa reformulation à l'original.",
-    "notificationsBody2": "Cet onglet permet de vérifier la bonne livraison par un canal et de comparer la reformulation de l'IA au texte du modèle. Les canaux se configurent sur la page détaillée Notifications."
+    "notificationsBody1": "Activité des notifications Proxmox : lignes de journal des services Proxmox qui mentionnent des notifications, des e-mails, des webhooks ou des alertes, les cibles de /etc/pve/notifications.cfg et les tâches de sauvegarde de la liste des tâches du cluster. Les lignes de tâche de sauvegarde permettent de télécharger le journal complet de la tâche. L’historique de ce que Monitor lui-même a envoyé se trouve dans Paramètres → Notifications → Historique des notifications.",
+    "notificationsBody2": "Cet onglet permet de vérifier ce que fait le système de notification propre à Proxmox. Les canaux de Monitor se configurent sur la page détaillée Notifications."
   },
   "dataCollected": {
     "heading": "Comment les données sont collectées",
@@ -64,7 +64,7 @@
       {
         "subtab": "Journaux (filtre en direct)",
         "endpoint": "/api/logs",
-        "source": "journalctl --output json --since <range> avec filtres de gravité, unité et recherche appliqués sur le serveur."
+        "source": "L’onglet demande /api/logs?since_days=N ; les filtres de niveau, de service et de texte s’appliquent dans le navigateur. L’API accepte aussi priority et service, que l’onglet n’utilise pas encore."
       },
       {
         "subtab": "Télécharger",
@@ -74,17 +74,17 @@
       {
         "subtab": "Sauvegardes",
         "endpoint": "/api/backups",
-        "source": "Historique des tâches PVE filtré sur vzdump, les transferts PBS, la récupération de mémoire inutilisée et la vérification."
+        "source": "Archives de sauvegarde listées depuis chaque stockage de sauvegarde avec pvesh get /nodes/localhost/storage/<id>/content."
       },
       {
-        "subtab": "Détails d'une tâche de sauvegarde",
+        "subtab": "Journal d’une tâche de sauvegarde (Notifications)",
         "endpoint": "/api/task-log/<upid>",
         "source": "Journal complet des tâches en texte clair lu à partir de /var/log/pve/tasks/<index>/<upid>."
       },
       {
-        "subtab": "Historique des notifications",
-        "endpoint": "/api/notifications/history",
-        "source": "Table SQLite notification_history alimentée par la boucle d'envoi."
+        "subtab": "Notifications",
+        "endpoint": "/api/notifications",
+        "source": "journalctl --grep sur les unités Proxmox, /etc/pve/notifications.cfg et les tâches de sauvegarde de pvesh get /cluster/tasks."
       }
     ],
     "apiIntro": "Le filtre en direct et les téléchargements sont aussi accessibles via l'API :",
diff --git a/web/messages/fr/docs/monitor/dashboard/system-overview.json b/web/messages/fr/docs/monitor/dashboard/system-overview.json
index 5f6424a5..3022d2ba 100644
--- a/web/messages/fr/docs/monitor/dashboard/system-overview.json
+++ b/web/messages/fr/docs/monitor/dashboard/system-overview.json
@@ -46,7 +46,7 @@
     "thresholdsIntro": "Anneaux, barres et mini-courbes suivent la même échelle :  vert sous le seuil Avertissement,  ambre entre Avertissement et Critique,  rouge à partir de Critique. Valeurs recommandées fournies avec ProxMenux :",
     "thresholdsItems": [
       "Utilisation CPU — avertissement 85 %, critique 95 %.",
-      "Mémoire — avertissement 85 %, critique 95 %. Le swap déclenche aussi le niveau Critique à 5 % d'utilisation ; un hôte Proxmox sain devrait rarement y recourir.",
+      "Mémoire : avertissement 85 %, critique 95 %. Le swap ne déclenche le niveau Critique que lorsque son utilisation dépasse 80 % et que la RAM disponible est inférieure à 15 %.",
       "Température CPU — avertissement 80 °C, critique 90 °C."
     ],
     "thresholdsOutro": "Les valeurs sont configurables par hôte. Paramètres → Seuils du moniteur de santé centralise leur réglage et explique comment les adapter.",
@@ -58,7 +58,7 @@
     "intro": "Les cartes CPU et Mémoire ouvrent une liste triable des 25 processus les plus consommateurs : par utilisation CPU depuis la carte CPU, ou par mémoire résidente depuis la carte Mémoire.",
     "listTitle": "La boîte de dialogue liste",
     "listItems": [
-      "Actualisation automatique — la liste se met à jour toutes les 5 secondes tant que la fenêtre est ouverte ; les requêtes cessent à sa fermeture.",
+      "Actualisation automatique — la liste se met à jour toutes les 3 secondes tant que la fenêtre est ouverte ; les requêtes cessent à sa fermeture.",
       "Filtre — la recherche limite la liste par commande, utilisateur ou PID.",
       "Barre en ligne — la colonne métrique primaire dessine une petite barre à l'échelle de la valeur la plus élevée de la liste filtrée, de sorte que le classement reste visible même si aucun processus n'est proche de 100 %.",
       "Disposition mobile — sous 640 px, les colonnes PID et Utilisateur sont masquées pour garder Commande, CPU % et Mémoire sans défilement horizontal."
@@ -79,7 +79,7 @@
   },
   "middle": {
     "heading": "Centre : graphiques des métriques du nœud",
-    "body1": "Sous les cartes, NodeMetricsCharts affiche les historiques CPU, mémoire et E/S disque du stockage RRD de Proxmox via /api/node/metrics. Le sélecteur propose 1 heure / 24 heures / 7 jours / 30 jours / 1 an. La résolution diminue pour les grandes périodes afin de conserver un affichage fluide.",
+    "body1": "Sous les cartes, NodeMetricsCharts affiche les historiques CPU, mémoire et E/S disque du stockage RRD de Proxmox via /api/node/metrics. Le sélecteur propose 1 heure / 24 heures / 7 jours / 30 jours. La résolution diminue pour les grandes périodes afin de conserver un affichage fluide.",
     "body2": "Les mêmes graphiques que l'interface web Proxmox pour un nœud, intégrés au thème sombre de Monitor et alignés avec les autres panneaux."
   },
   "bottom": {
diff --git a/web/messages/fr/docs/monitor/dashboard/vms-lxcs.json b/web/messages/fr/docs/monitor/dashboard/vms-lxcs.json
index febe9371..e1e2baf1 100644
--- a/web/messages/fr/docs/monitor/dashboard/vms-lxcs.json
+++ b/web/messages/fr/docs/monitor/dashboard/vms-lxcs.json
@@ -117,7 +117,7 @@
       "Destination — stockage (PBS-Cloud, PBS-Local, NFS-Backup, …), coloré selon son état.",
       "Taille — taille finale sur disque de la sauvegarde."
     ],
-    "backupsOutro": "+ Créer une sauvegarde lance une sauvegarde sur le stockage marqué « Backup target » dans Proxmox. La restauration se fait dans l'interface web Proxmox ; Monitor montre les sauvegardes récentes de l'invité et permet d'en créer.",
+    "backupsOutro": "Le bouton + Créer une sauvegarde en haut à droite ouvre une fenêtre pour choisir le stockage, le mode (snapshot / suspend / stop), la protection et les notes avant de lancer un vzdump. La restauration se fait dans l’interface web Proxmox ; Monitor montre les sauvegardes récentes de l’invité, pas le processus de récupération.",
     "firewallTitle": "Onglet 7 — Pare-feu",
     "firewallIntro": "Lit le journal du pare-feu Proxmox de l'invité directement sur l'hôte, sans service ni requêtes périodiques supplémentaires. L'onglet est toujours présent ; son contenu dépend de l'activation du pare-feu et de la journalisation effective d'une règle.",
     "firewallItems": [
diff --git a/web/messages/fr/docs/monitor/health-monitor.json b/web/messages/fr/docs/monitor/health-monitor.json
index 2e3654cd..782e5ffa 100644
--- a/web/messages/fr/docs/monitor/health-monitor.json
+++ b/web/messages/fr/docs/monitor/health-monitor.json
@@ -153,8 +153,8 @@
     "pillTitle": "L’indicateur reflète la catégorie la plus grave",
     "pillBody": "La couleur de l’en-tête reflète la gravité maximale : CRITICAL → rouge, sinon WARNING → jaune, sinon INFO → bleu, sinon vert. Le point de la favicon et le badge PWA suivent la même logique.",
     "updateNowTitle": "Bouton Mettre à jour maintenant dans l’en-tête de l’aperçu",
-    "updateNowBody1": "Si Mises à jour système signale des mises à jour en attente, Mettre à jour maintenant apparaît près de l’indicateur. La fenêtre lance la procédure de ProxMenux → Settings post-install Proxmox → Proxmox System Update : nettoyage des dépôts, apt full-upgrade via update-pve-safe.sh, reconstruction DKMS des pilotes gérés lors d’un nouveau noyau, autoremove/autoclean et proposition de redémarrage si nécessaire. La progression s’affiche en direct.",
-    "updateNowBody2": "À la fin, fermer la fenêtre appelle GET /api/health/details?refresh=1 et invalide le cache du point d’accès. Les mises à jour en attente et la gravité sont recalculées depuis le nouvel état des paquets ; le badge disparaît sans attendre le prochain cycle. Le bouton est masqué si l’hôte est déjà à jour."
+    "updateNowBody1": "Si Mises à jour système signale des mises à jour en attente, Mettre à jour maintenant apparaît près de l’indicateur. La fenêtre lance la procédure de ProxMenux → Utilitaires et outils → Mise à jour du système Proxmox : nettoyage des dépôts, apt full-upgrade via update-pve-safe.sh, reconstruction DKMS des pilotes gérés lors d’un nouveau noyau, autoremove/autoclean et proposition de redémarrage si le noyau a changé. La progression s’affiche en direct.",
+    "updateNowBody2": "À la fin, fermer la fenêtre appelle GET /api/health/full?refresh=1 et invalide le cache du point d’accès. Les mises à jour en attente et la gravité sont recalculées depuis le nouvel état des paquets ; le badge disparaît sans attendre le prochain cycle. Le bouton est masqué si l’hôte est déjà à jour."
   },
   "dismiss": {
     "heading": "Masquer les alertes et choisir la durée de mise en sourdine",
diff --git a/web/messages/fr/docs/monitor/index.json b/web/messages/fr/docs/monitor/index.json
index be430c57..cc78b0be 100644
--- a/web/messages/fr/docs/monitor/index.json
+++ b/web/messages/fr/docs/monitor/index.json
@@ -113,13 +113,13 @@
   "api": {
     "heading": "REST API et intégrations",
     "intro": "Les points d’accès REST exposent les mesures en JSON par HTTP/HTTPS pour Homepage, Home Assistant, Uptime Kuma et les scripts. Grafana peut utiliser l’exporteur texte Prometheus séparé sur /api/prometheus. Les requêtes peuvent être faites avec curl.",
-    "tokens": "Les jetons API à longue durée de vie (365 jours) sont générés à partir de Paramètres → API Tokens ou via POST /api/auth/generate-api-token.",
+    "tokens": "Les jetons API à longue durée de vie (365 jours) sont générés à partir de Administrateur → Sécurité → Jetons API ou via POST /api/auth/generate-api-token.",
     "bearer": "Envoyez les jetons via Authorization: Bearer …. /api/health attend aussi l’en-tête Bearer : les sondes de disponibilité ont donc besoin d’un jeton read_only. Sous /api/auth/* se trouvent à la fois des routes publiques de configuration/connexion/état et des routes protégées de gestion du compte et des jetons ; elles ne sont pas toutes anonymes.",
     "catalog": "Le catalogue complet des points d'accès, les conseils de rotation des jetons et les bonnes pratiques de sécurité se trouvent dans la référence API. Des exemples pour Homepage, Home Assistant, Grafana et Uptime Kuma, ainsi qu'un modèle générique cURL, sont proposés dans les intégrations."
   },
   "serviceControl": {
     "heading": "Contrôle des services",
-    "intro": "Au jour le jour, le moniteur est géré exactement comme tout autre service systemd. Il est également exposé comme deux entrées à l'intérieur du ProxMenux TUI sous Paramètres:",
+    "intro": "Au jour le jour, le moniteur est géré exactement comme tout autre service systemd. Il est également exposé comme trois entrées à l’intérieur du ProxMenux TUI sous Paramètres :",
     "codeComment": "# Contrôle manuel",
     "footer": "Voir Paramètres → ProxMenux Monitor pour activer/désactiver le service dans le menu et vérifier son état."
   },
diff --git a/web/messages/fr/docs/monitor/integrations.json b/web/messages/fr/docs/monitor/integrations.json
index dc4373ac..18d86b58 100644
--- a/web/messages/fr/docs/monitor/integrations.json
+++ b/web/messages/fr/docs/monitor/integrations.json
@@ -20,13 +20,13 @@
     "heading": "Authentification: la seule chose dont chaque recette ait besoin",
     "intro": "La plupart de ces points d’accès exigent une authentification. Deux méthodes sont proposées.",
     "optAtitle": "Option A — Jeton API (recommandé pour les intégrations)",
-    "optAbody1": "Ouvrez Paramètres → Sécurité → Jetons API. Choisissez Générer un jeton, nommez-le par exemple homepage, home-assistant ou prometheus, puis copiez-le : il n’est affiché qu’une fois. Sa durée par défaut est d’un an et il peut être révoqué individuellement. Utilisez-le pour les clients autres que le navigateur.",
+    "optAbody1": "Ouvrez Administrateur → Sécurité → Jetons API. Choisissez Générer un nouveau jeton, nommez-le par exemple homepage, home-assistant ou prometheus, saisissez votre mot de passe (et le code 2FA s’il est activé), cliquez sur Générer un jeton puis copiez-le : il n’est affiché qu’une fois. Sa durée par défaut est d’un an et il peut être révoqué individuellement. Utilisez-le pour les clients autres que le navigateur.",
     "optAbody2": "À partir de ce moment, chaque demande est juste:",
     "optBtitle": "Option B — flux de connexion (nom d'utilisateur + mot de passe)",
     "optBbody": "Utile pour les scripts qui authentifient en tant qu'utilisateur humain. Le jeton retourné est de courte durée; la plupart des intégrations devraient préférer l'option A.",
     "outro": "Le champ TOTP n’est requis que si la double authentification est activée. Le renouvellement et la révocation des jetons, les règles de mot de passe et le journal d’audit figurent dans Accès et authentification.",
     "httpsTitle": "Utiliser HTTPS au lieu de HTTP",
-    "httpsIntro": "Chaque recette ci-dessous utilise http:// dans les URLs pour garder les exemples courts. Si vous avez activé TLS sur le moniteur (Paramètres → Sécurité → SSL/HTTPS), échangez http:// contre https:// dans chaque URL — c'est le seul changement. Deux notes spécifiques à certains outils:",
+    "httpsIntro": "Chaque recette ci-dessous utilise http:// dans les URLs pour garder les exemples courts. Si vous avez activé TLS sur le moniteur (Administrateur → Sécurité → SSL / HTTPS), échangez http:// contre https:// dans chaque URL — c’est le seul changement. Deux notes spécifiques à certains outils :",
     "httpsItems": [
       "Certificats autosignés. L’intégration Home Assistant rest: vérifie TLS par défaut. Pour le certificat autosigné du Monitor, ajoutez verify_ssl: false à chaque bloc REST près de scan_interval:, ou ajoutez son AC au magasin de certificats approuvés. Même principe pour les autres outils qui vérifient les certificats.",
       "Prometheus dispose déjà de scheme: https dans l’exemple ci-dessous. Activez ou conservez https si TLS est activé sur le Monitor."
@@ -43,7 +43,7 @@
     "basicTitle": "Widget de base — pas d'authentification",
     "basicIntro": "Utilisez ceci lorsque ProxMenux Monitor est sur un réseau de confiance et que vous n'avez pas encore activé l'authentification du côté Monitor. L'entrée la plus simple possible services.yaml:",
     "authedTitle": "Widget authentifié",
-    "authedIntro": "Générez un jeton API dans Paramètres → Sécurité → API Jetons sur le moniteur, copiez-le et collez-le dans l'en-tête Authorization ci-dessous — remplacez le jeton d'exemple affiché après Bearer par celui que vous venez de copier:",
+    "authedIntro": "Générez un jeton API dans Administrateur → Sécurité → Jetons API sur le moniteur, copiez-le et collez-le dans l’en-tête Authorization ci-dessous — remplacez le jeton d’exemple affiché après Bearer par celui que vous venez de copier :",
     "authedOutro": "Redémarrez Homepage pour afficher les valeurs actualisées. Réutilisez le même jeton pour les widgets qui interrogent la même instance du Monitor.",
     "multiTitle": "Configuration multi-widget — système, stockage, réseau",
     "multiIntro": "Pour plus de détails, créez trois cartes utilisant des points d’accès distincts : système, stockage et réseau. Elles utilisent le même jeton pour cette instance du Monitor.",
diff --git a/web/messages/fr/docs/monitor/notifications.json b/web/messages/fr/docs/monitor/notifications.json
index 68f6f549..eedd2187 100644
--- a/web/messages/fr/docs/monitor/notifications.json
+++ b/web/messages/fr/docs/monitor/notifications.json
@@ -314,7 +314,7 @@
   },
   "aiRewrite": {
     "heading": "Réécriture optionnelle de l'IA",
-    "body1": "Les événements admissibles peuvent être réécrits par un LLM. backup_complete et backup_fail sont exclus. L’IA est désactivée par défaut et s’exécute dans le thread d’envoi. En cas d’erreur ou délai dépassé, le texte du modèle est utilisé.",
+    "body1": "Les événements admissibles peuvent être réécrits par un LLM. backup_complete et backup_fail ne sont pas reformulés ; avec les suggestions d’IA activées, l’IA ajoute seulement une explication lorsqu’une sauvegarde signale des erreurs. L’IA est désactivée par défaut et s’exécute dans le thread d’envoi. En cas d’erreur ou délai dépassé, le texte du modèle est utilisé.",
     "body2": "OpenAI, Anthropic, Google Gemini, Groq, OpenRouter et Ollama local sont disponibles. Par canal : détail (brief, standard, detailed), langue, mode d’instructions (default ou custom) et instructions propres. Guide et exemples : Assistant IA.",
     "privacyTitle": "Note de confidentialité",
     "privacyBody": "La requête IA peut inclure titre, gravité, texte, instructions système/personnelles et contexte : noms d’hôte, IP, utilisateurs, erreurs et journaux. Elle part vers le serveur choisi. Ollama reste local seulement avec serveur et modèle locaux ; serveur distant ou modèle cloud transmettent les données hors de l’hôte. Désactivez l’IA ou utilisez serveur et modèle locaux contrôlés si le contenu doit rester dans le réseau."
diff --git a/web/messages/fr/docs/security/ssl-letsencrypt.json b/web/messages/fr/docs/security/ssl-letsencrypt.json
index 7ee2fe14..9a14d1e1 100644
--- a/web/messages/fr/docs/security/ssl-letsencrypt.json
+++ b/web/messages/fr/docs/security/ssl-letsencrypt.json
@@ -12,13 +12,13 @@
   },
   "intro": {
     "title": "Ce que cette page couvre",
-    "body": "Comment changer le moniteur de HTTP à HTTPS dans le panneau Paramètres → Sécurité, ce que chaque source de certificat pointe en fait sur le disque, comment l'option Proxmox se comporte quand l'hôte a un cert Let's Encrypt et comment définir des chemins personnalisés si vous gérez des certs en dehors de Proxmox."
+    "body": "Comment changer le moniteur de HTTP à HTTPS dans le panneau Administrateur → Sécurité, ce que chaque source de certificat pointe en fait sur le disque, comment l’option Proxmox se comporte quand l’hôte a un cert Let’s Encrypt et comment définir des chemins personnalisés si vous gérez des certs en dehors de Proxmox."
   },
   "wheresetting": {
     "heading": "Emplacement du réglage",
-    "body": "Ouvrez Monitor et allez dans Paramètres → Sécurité → HTTPS / SSL. Le panneau affiche HTTP ou HTTPS et, si HTTPS est désactivé, les sources de certificats détectées.",
-    "imageAlt": "Paramètres → Sécurité → Panneau HTTPS / SSL dans le moniteur montrant l'état HTTPS-off, le certificat Proxmox détecté automatiquement (sujet, émetteur, expiration) et l'option Utiliser un certificat personnalisé",
-    "caption": "Panneau Paramètres → Sécurité → HTTPS / SSL avant activation. Monitor affiche le certificat utilisé par Proxmox et permet de le choisir en un clic."
+    "body": "Ouvrez Monitor et allez dans Administrateur → Sécurité → SSL / HTTPS. Le panneau affiche HTTP ou HTTPS et, si HTTPS est désactivé, les sources de certificats détectées.",
+    "imageAlt": "Panneau Administrateur → Sécurité → SSL / HTTPS dans le moniteur montrant l’état HTTPS-off, le certificat Proxmox détecté automatiquement (sujet, émetteur, expiration) et l’option Utiliser un certificat personnalisé",
+    "caption": "Panneau Administrateur → Sécurité → SSL / HTTPS avant activation. Monitor affiche le certificat utilisé par Proxmox et permet de le choisir en un clic."
   },
   "twoways": {
     "heading": "Deux façons d'activer HTTPS",
@@ -117,7 +117,7 @@
   },
   "switchToHttps": {
     "heading": "Changez le moniteur en HTTPS",
-    "bodyRich": "Une fois /etc/pve/local/pveproxy-ssl.pem signé par Let's Encrypt, ouvrez Paramètres → Sécurité → HTTPS / SSL, vérifiez que l'émetteur est Let's Encrypt et non l'AC locale de Proxmox, puis cliquez sur Utiliser le certificat Proxmox. Monitor redémarre et le prochain chargement utilise HTTPS sur 8008 avec une chaîne de confiance publique. Les renouvellements ACME suivants sont validés et sélectionnés à la prochaine nouvelle connexion TLS, sans interrogation périodique ni redémarrage à chaque renouvellement. Actualiser le certificat reste disponible dans Sécurité pour le diagnostic et la récupération."
+    "bodyRich": "Une fois /etc/pve/local/pveproxy-ssl.pem signé par Let’s Encrypt, ouvrez Administrateur → Sécurité → SSL / HTTPS, vérifiez que l’émetteur est Let’s Encrypt et non l’AC locale de Proxmox, puis cliquez sur Utiliser le certificat Proxmox. Monitor redémarre et le prochain chargement utilise HTTPS sur 8008 avec une chaîne de confiance publique. Les renouvellements ACME suivants sont validés et sélectionnés à la prochaine nouvelle connexion TLS, sans interrogation périodique ni redémarrage à chaque renouvellement. Actualiser le certificat reste disponible dans Sécurité pour le diagnostic et la récupération."
   },
   "custom": {
     "heading": "Certificat personnalisé — quand l'utiliser",
diff --git a/web/messages/fr/docs/settings/beta-program.json b/web/messages/fr/docs/settings/beta-program.json
index b0904365..63ff3113 100644
--- a/web/messages/fr/docs/settings/beta-program.json
+++ b/web/messages/fr/docs/settings/beta-program.json
@@ -51,7 +51,7 @@
     "intro": "Les deux directions passent par le même programme d'installation – il ne s'agit pas de flux de désinstallation et de réinstallation dans des cas particuliers. Chaque direction exécute simplement le propre programme d'installation du canal, qui est conçu pour être idempotent et pouvoir être réexécuté en toute sécurité sur une installation existante. Conséquences pratiques :",
     "items": [
       "Votre configuration existante, votre login ProxMenux Monitor (auth.json), les canaux de notification, le registre post-installation, les seuils personnalisés — aucun de ceux-ci n'est touché. Le commutateur de canal échange uniquement l'arborescence de script et le binaire AppImage.",
-      "Un moniteur qui était actif avant que l'interrupteur reste actif après; un moniteur qui a été désactivé reste désactivé.",
+      "L’installateur redémarre le service Monitor pendant le changement : un moniteur que vous aviez désactivé redémarre donc. Désactivez-le à nouveau depuis les paramètres si nécessaire.",
       "Les jetons ProxMenux Monitor auth.json, API et le secret JWT sont conservés à travers les changements de canaux. Les séances ne sont pas déconnectées.",
       "Vous pouvez retourner les canaux autant de fois que vous le souhaitez — le dialogue l'acceptera à chaque fois et relancera l'installateur. Aucun redémarrage de Proxmox n'est nécessaire.",
       "Le changement de canal remplace les scripts ProxMenux et ne peut donc pas être exécuté depuis le terminal du navigateur de Monitor : le PTY serait fermé pendant la mise à jour et laisserait le dialogue bloqué. Dans ce terminal, un message affiche à la place la commande officielle en une ligne pour le canal choisi. Exécutez-la depuis SSH ou la console de l'hôte Proxmox."
diff --git a/web/messages/fr/docs/settings/index.json b/web/messages/fr/docs/settings/index.json
index 55f0ca56..503a9f8f 100644
--- a/web/messages/fr/docs/settings/index.json
+++ b/web/messages/fr/docs/settings/index.json
@@ -60,6 +60,12 @@
         "description": "Passez de Stable (main) à Beta (develop) et inversement dans un seul dialogue. Exécute l'installateur officiel du canal choisi ; idempotent et toujours visible.",
         "badge": "Toujours disponible"
       },
+      {
+        "icon": "Languages",
+        "href": "/docs/settings/change-language",
+        "title": "Changer de langue",
+        "description": "Basculez l’interface de ProxMenux entre huit langues. Le choix est enregistré dans config.json et le menu se recharge dans la nouvelle langue."
+      },
       {
         "icon": "Info",
         "href": "/docs/settings/show-version-information",
@@ -70,7 +76,7 @@
         "icon": "Trash2",
         "href": "/docs/settings/uninstall-proxmenux",
         "title": "Désinstaller ProxMenux",
-        "description": "Supprimer ProxMenux et (facultativement) ses dépendances. restaure également les sauvegardes /root/.bashrc et /etc/motd créées au moment de l'installation."
+        "description": "Supprime ProxMenux et (facultativement) ses dépendances. Restaure aussi les sauvegardes de /root/.bashrc et /etc/motd créées par Post-Install."
       }
     ]
   },
diff --git a/web/messages/fr/docs/settings/proxmenux-monitor.json b/web/messages/fr/docs/settings/proxmenux-monitor.json
index 7ac354bd..85649015 100644
--- a/web/messages/fr/docs/settings/proxmenux-monitor.json
+++ b/web/messages/fr/docs/settings/proxmenux-monitor.json
@@ -33,7 +33,7 @@
   },
   "warnConditional": {
     "title": "Options de configuration uniquement visibles lorsque Monitor est installé",
-    "body": "Les deux entrées de menu décrites ci-dessous apparaissent seulement dans Paramètres si proxmenux-monitor.service est enregistré avec systemd. Si vous ne les voyez pas, le moniteur n'a pas été installé (il est optionnel pendant l'installation de ProxMenux). Pour l'ajouter plus tard, réinstallez ProxMenux avec la dernière version et choisissez l'option Monitor."
+    "body": "Les trois entrées de menu décrites ci-dessous apparaissent seulement dans Paramètres si proxmenux-monitor.service est enregistré avec systemd. L’installateur le configure toujours : si vous ne les voyez pas, l’étape Monitor a échoué pendant l’installation. Relancez l’installateur officiel pour la réessayer."
   },
   "toggle": {
     "heading": "Activer / Désactiver",
@@ -92,8 +92,8 @@
   },
   "troubleshoot": {
     "heading": "Dépannage",
-    "missingTitle": "Les deux options du menu sont manquantes",
-    "missingBody": "Le moniteur n'est pas enregistré avec systemd. Confirmez avec systemctl list-unit-files | grep proxmenux-monitor — s'il n'y a pas de sortie, réinstallez ProxMenux depuis l'installateur officiel avec la dernière version et choisissez l'option Monitor pendant l'installation.",
+    "missingTitle": "Les options de menu de Monitor sont manquantes",
+    "missingBody": "Le moniteur n’est pas enregistré avec systemd. Confirmez avec systemctl list-unit-files | grep proxmenux-monitor — s’il n’y a pas de sortie, relancez l’installateur officiel ; il installe Monitor à chaque exécution.",
     "unreachableTitle": "Service activé mais l'URL est inaccessible",
     "unreachableBody": "La sortie d'état de paramètres vous donne l'IP. Confirmer :",
     "unreachableCmd": "ss -tlnp | grep 8008                # is anything listening?\njournalctl -u proxmenux-monitor -n 100 --no-pager  # recent errors\ncurl -sI http://localhost:8008      # local reachability",
diff --git a/web/messages/fr/docs/settings/uninstall-proxmenux.json b/web/messages/fr/docs/settings/uninstall-proxmenux.json
index 2602ba09..02832847 100644
--- a/web/messages/fr/docs/settings/uninstall-proxmenux.json
+++ b/web/messages/fr/docs/settings/uninstall-proxmenux.json
@@ -12,7 +12,7 @@
   },
   "scopeWarn": {
     "title": "affecte uniquement l'installation de ProxMenux — pas vos VM / CT / stockage",
-    "body": "La désinstallation supprime les scripts ProxMenux, le lanceur de menu, le service Monitor facultatif et les sauvegardes de fichiers système créées par ProxMenux lors de son installation. Elle ne modifie pas vos VM, conteneurs, stockage, configuration réseau, Fail2Ban, pilote NVIDIA ni aucun autre élément installé via ProxMenux. Ces éléments restent en place."
+    "body": "La désinstallation de ProxMenux supprime les scripts ProxMenux, le lanceur de menu, le service Monitor et restaure les sauvegardes de /root/.bashrc et /etc/motd faites par Post-Install, si elles existent. Elle ne modifie pas vos VM, conteneurs, stockage, configuration réseau, Fail2Ban, pilote NVIDIA ni aucun autre élément installé via ProxMenux. Ces éléments restent en place."
   },
   "flow": {
     "heading": "Déroulement de la désinstallation",
diff --git a/web/messages/fr/docs/utils/export-vm.json b/web/messages/fr/docs/utils/export-vm.json
index a085445f..c3f84d94 100644
--- a/web/messages/fr/docs/utils/export-vm.json
+++ b/web/messages/fr/docs/utils/export-vm.json
@@ -59,8 +59,8 @@
   },
   "package": {
     "heading": "Ce qui finit dans le paquet",
-    "intro": "Pour une OVA / OVF nommée myvm-100-2026-04-26_18-30-15:",
-    "code": "# OVA mode (single .ova file is a TAR of):\nmyvm-100-2026-04-26_18-30-15.ovf      # XML descriptor (DMTF OVF 1.0 + RASD + VSSD)\nmyvm-100-2026-04-26_18-30-15.mf       # SHA1 manifest\nmyvm-100-2026-04-26_18-30-15-disk0.vmdk\nmyvm-100-2026-04-26_18-30-15-disk1.vmdk\n...\n\n# OVF mode (a directory):\nmyvm-100-2026-04-26_18-30-15-ovf/\n├── myvm-100-2026-04-26_18-30-15.ovf\n├── myvm-100-2026-04-26_18-30-15.mf\n├── myvm-100-2026-04-26_18-30-15-disk0.vmdk\n└── myvm-100-2026-04-26_18-30-15-disk1.vmdk"
+    "intro": "Pour une OVA / OVF nommée myvm-100-20260426_183015 :",
+    "code": "# OVA mode (single .ova file is a TAR of):\nmyvm-100-20260426_183015.ovf      # XML descriptor (DMTF OVF 1.0 + RASD + VSSD)\nmyvm-100-20260426_183015.mf       # SHA1 manifest\nmyvm-100-20260426_183015-disk1.vmdk\nmyvm-100-20260426_183015-disk2.vmdk\n...\n\n# OVF mode (a directory):\nmyvm-100-20260426_183015-ovf/\n├── myvm-100-20260426_183015.ovf\n├── myvm-100-20260426_183015.mf\n├── myvm-100-20260426_183015-disk1.vmdk\n└── myvm-100-20260426_183015-disk2.vmdk"
   },
   "exported": {
     "heading": "Ce qui est exporté",
@@ -90,7 +90,7 @@
   "manifest": {
     "heading": "Manifeste SHA1",
     "intro": "Chaque paquet contient un fichier .mf avec les empreintes SHA1 du descripteur OVF et de chaque VMDK :",
-    "code": "SHA1(myvm-100-2026-04-26_18-30-15.ovf)= 3a7bd3e2360a3d29eea436fcfb7e44c735d117c9\nSHA1(myvm-100-2026-04-26_18-30-15-disk0.vmdk)= b7e23ec29af22b0b4e41da31e868d57226121c84\nSHA1(myvm-100-2026-04-26_18-30-15-disk1.vmdk)= a87ff679a2f3e71d9181a67b7542122c",
+    "code": "SHA1(myvm-100-20260426_183015.ovf)= 3a7bd3e2360a3d29eea436fcfb7e44c735d117c9\nSHA1(myvm-100-20260426_183015-disk1.vmdk)= b7e23ec29af22b0b4e41da31e868d57226121c84\nSHA1(myvm-100-20260426_183015-disk2.vmdk)= a87ff679a2f3e71d9181a67b7542122c",
     "outro": "Facultatif dans la spécification OVF et ignoré par de nombreux outils, le manifeste permet toutefois de vérifier que le paquet a été transféré intact. Vérifiez-le manuellement avec sha1sum -c file.mf."
   },
   "import": {
diff --git a/web/messages/fr/docs/utils/index.json b/web/messages/fr/docs/utils/index.json
index 29fa3a50..6c6edbe1 100644
--- a/web/messages/fr/docs/utils/index.json
+++ b/web/messages/fr/docs/utils/index.json
@@ -38,7 +38,7 @@
       "body": "Maintenez l’hôte en bon état : outils CLI, mises à jour régulières et passage à la version majeure suivante le moment venu.",
       "bullets": [
         "Installateur d’utilitaires système (25 outils CLI)",
-        "Mise à jour du système Proxmox (apt + dist-upgrade)",
+        "Mise à jour du système Proxmox (apt + full-upgrade)",
         "PVE 8 → PVE 9 mise à jour majeure"
       ]
     },
@@ -58,11 +58,11 @@
   },
   "isoSection": {
     "heading": "Création ISO",
-    "body": "Créez une nouvelle ISO d’installation de Windows à jour directement sur l’hôte Proxmox. Le script automatise tout le processus UUP Dump : installation des dépendances (aria2c, cabextract, wimlib-imagex, …), téléchargement parallèle des fichiers de mise à jour avec aria2 et conversion en une ISO amorçable. Elle est enregistrée dans /var/lib/vz/template/iso, prête à être associée à une VM.",
+    "body": "Créez une nouvelle ISO d’installation de Windows à jour directement sur l’hôte Proxmox. Le script automatise tout le processus UUP Dump : installation des dépendances (aria2c, cabextract, wimlib-imagex, …), téléchargement parallèle des fichiers de mise à jour avec aria2 et conversion en une ISO amorçable. Elle est enregistrée dans le stockage ISO Proxmox détecté (repli : /var/lib/vz/template/iso), prête à être associée à une VM.",
     "options": [
       {
         "title": "Créateur d’ISO UUP Dump",
-        "description": "Créez une ISO d’installation de Windows à jour depuis un lien partagé UUP Dump. Les dépendances, le téléchargement et la conversion sont entièrement automatisés ; l’ISO obtenue est enregistrée dans /var/lib/vz/template/iso, prête pour la création d’une VM.",
+        "description": "Créez une ISO d’installation de Windows à jour depuis un lien partagé UUP Dump. Les dépendances, le téléchargement et la conversion sont entièrement automatisés ; l’ISO obtenue est enregistrée dans le stockage ISO Proxmox détecté, prête pour la création d’une VM.",
         "href": "/docs/utils/UUp-Dump-ISO-Creator"
       }
     ]
@@ -78,7 +78,7 @@
       },
       {
         "title": "Mise à jour du système Proxmox",
-        "description": "Vérification des dépôts + apt update + apt dist-upgrade, avec confirmation avant le redémarrage. Détecte la version majeure de PVE (8 ou 9), conserve les dépôts configurés et demande confirmation avant de faire passer un hôte sans abonnement au dépôt no-subscription, puis exécute autoremove.",
+        "description": "Vérification des dépôts + apt update + apt full-upgrade, avec confirmation avant le redémarrage. Détecte la version majeure de PVE (8 ou 9), conserve les dépôts configurés et demande confirmation avant de faire passer un hôte sans abonnement au dépôt no-subscription, puis exécute autoremove.",
         "href": "/docs/utils/system-update"
       },
       {
diff --git a/web/messages/fr/docs/utils/system-update.json b/web/messages/fr/docs/utils/system-update.json
index 5888fd02..41ab82fe 100644
--- a/web/messages/fr/docs/utils/system-update.json
+++ b/web/messages/fr/docs/utils/system-update.json
@@ -128,7 +128,7 @@
         "body": "Le dépôt Enterprise est activé sans abonnement actif. Avant apt-get update, le script propose le passage à no-subscription. Si vous avez refusé, relancez la mise à jour et acceptez, ou désactivez Enterprise dans Node → Updates → Repositories et activez no-subscription."
       },
       {
-        "title": "dist-upgrade reste bloqué sur \"Configuring grub-pc\"",
+        "title": "full-upgrade reste bloqué sur \"Configuring grub-pc\"",
         "body": "Si une question interactive GRUB est visible, elle demande les appareils où installer le chargeur de démarrage. L’option --force-confold concerne les fichiers de configuration, pas ce choix d’appareils. Sélectionnez les disques de démarrage appropriés avec Tab + Space, puis OK. Vous pouvez configurer ce choix à l’avance avec dpkg-reconfigure grub-pc."
       },
       {
diff --git a/web/messages/fr/docs/utils/system-utils.json b/web/messages/fr/docs/utils/system-utils.json
index fa6bbe54..78b3954e 100644
--- a/web/messages/fr/docs/utils/system-utils.json
+++ b/web/messages/fr/docs/utils/system-utils.json
@@ -235,7 +235,7 @@
       {
         "href": "/docs/utils/system-update",
         "label": "Mise à jour du système Proxmox",
-        "tailRich": "— pour le apt dist-upgrade réel sur l'hôte."
+        "tailRich": " — pour le apt full-upgrade réel sur l’hôte."
       },
       {
         "href": "/docs/network/monitoring",
diff --git a/web/messages/fr/docs/utils/upgrade-pve8-pve9.json b/web/messages/fr/docs/utils/upgrade-pve8-pve9.json
index 56ac6893..ad022338 100644
--- a/web/messages/fr/docs/utils/upgrade-pve8-pve9.json
+++ b/web/messages/fr/docs/utils/upgrade-pve8-pve9.json
@@ -16,9 +16,9 @@
     "items": [
       "Sauvegardes vérifiées de chaque VM et CT (PBS, vzdump, instantanés ZFS — vérifiez aussi leur restauration).",
       "Accès console / IPMI / iKVM à l’hôte en cas de coupure SSH pendant la mise à niveau.",
-      "L'hôte déjà mis à jour sur PVE 8.4.x (le script l'applique).",
+      "L’hôte au dernier niveau de correctifs PVE 8. Le script met d’abord à jour PVE 8 ; il ne vérifie pas une version 8.4.x précise.",
       "Ceph (le cas échéant) sur la version 19.x Squid.",
-      "Au moins 5 Go libres sur le système de fichiers racine."
+      "Au moins 5 Go libres sur le système de fichiers racine, comme le recommande le guide Proxmox. Le script lui-même ne vérifie que 1 Go dans /var/cache/apt/archives."
     ]
   },
   "modeMenu": {
@@ -63,13 +63,13 @@
   },
   "webTerminal": {
     "heading": "Pourquoi le terminal web est bloqué",
-    "body": "Les trois modes détectent termproxy / vncshell dans la chaîne des processus parents et refusent de démarrer. Le terminal web dépend de pveproxy, un service mis à jour pendant l’opération. Au redémarrage de pveproxy, la session se ferme, ainsi que le script lancé dedans. L’hôte peut rester partiellement mis à niveau.",
+    "body": "Les modes Automatique et Interactif détectent termproxy / vncshell dans la chaîne des processus parents et refusent de démarrer, sauf si la session passe par SSH ou si PVE_ALLOW_WEBTERM=1 est défini. Le contrôle préalable et le guide manuel ne changent rien et s’exécutent partout. Le terminal web dépend de pveproxy, un service mis à jour pendant l’opération. Au redémarrage de pveproxy, la session se ferme, ainsi que le script lancé dedans. L’hôte peut rester partiellement mis à niveau.",
     "warningTitle": "Utilisez SSH ou une console physique / IPMI / iKVM",
-    "warningBody": "SSH est le choix habituel. Lancez-le dans tmux ou screen pour résister aux brèves coupures réseau. Si le script détecte un simple shell SSH, il affiche d’abord un rappel concernant tmux/screen."
+    "warningBody": "SSH est le choix habituel. Lancez-le dans tmux ou screen pour résister aux brèves coupures réseau. La boîte de dialogue de confirmation de la mise à niveau rappelle de l’exécuter depuis la console ou depuis SSH dans tmux/screen."
   },
   "auto": {
     "heading": "Mode automatique / sans surveillance",
-    "intro": "Les questions sur les paquets et les fichiers de configuration utilisent les valeurs par défaut du tableau ci-dessous, et un 401 du dépôt Enterprise bascule vers no-subscription sans demander. La confirmation de la mise à niveau, le résumé de la mise à jour PVE 8, les réparations du contrôle préalable et la demande de redémarrage restent affichés.",
+    "intro": "Les questions sur les paquets et les fichiers de configuration utilisent les valeurs par défaut du tableau ci-dessous. La confirmation de la mise à niveau, le résumé de la mise à jour PVE 8, les réparations du contrôle préalable et la demande de redémarrage restent affichés.",
     "behaviourHeading": "Comportement propre au mode automatique",
     "behaviourTable": {
       "settingHeader": "Réglage",
@@ -94,8 +94,8 @@
       ]
     },
     "flowHeading": "Déroulement du mode automatique",
-    "flowIntro": "Six phases successives. La phase 2 met à jour les paquets PVE 8 et la phase 3 écrit la configuration des dépôts PVE 9. Un échec arrête le déroulement sans annuler ces modifications ; si la mise à jour PVE 8 échoue ou est annulée, les dépôts restent inchangés. La phase 5 lance dist-upgrade vers PVE 9.",
-    "flowDiagram": "1. CONTRÔLES\nBloque termproxy / vncshell ; confirme la mise à niveau ; refuse PVE 9.\n\n2. PRÉPARER PVE 8\nscripts/global/update-pve8.sh\nMet à jour les paquets PVE 8 ; arrêt si la mise à jour échoue ou est annulée.\npve8to9 --full (peut proposer des réparations).\nVérifie espace et connectivité ; Ceph 19.x Squid sur les nœuds hyperconvergés.\n\n3. MIGRER LES DÉPÔTS\nBookworm → Trixie ; écrit les fichiers deb822 .sources.\nCommente les anciens fichiers .list ; apt-get update.\n\n4. VALIDER LE CANDIDAT PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nArrêt si le candidat n’est pas 9.x ou si proxmox-ve serait supprimé.\n\n5. METTRE À NIVEAU\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nSans abonnement : désactive pve-enterprise.sources.\nJournal : /var/log/pve8-a-pve9-.log\n\n6. VÉRIFIER ET REDÉMARRER\nEFI : apt install grub-efi-amd64\npve8to9 --full ; examiner le résultat et la demande de redémarrage.",
+    "flowIntro": "Six phases successives. La phase 2 bascule les dépôts PVE 8 vers no-subscription (listes Enterprise et Ceph désactivées, /etc/apt/sources.list réécrit) avant d’afficher le résumé de la mise à jour, puis met à jour les paquets ; la phase 3 écrit la configuration des dépôts PVE 9. Un échec arrête le déroulement sans annuler ces modifications ; si la mise à jour PVE 8 échoue ou est annulée, la migration vers PVE 9 ne démarre pas, mais les modifications des dépôts PVE 8 restent. La phase 5 lance dist-upgrade vers PVE 9.",
+    "flowDiagram": "1. CONTRÔLES\nBloque termproxy / vncshell ; confirme la mise à niveau ; refuse PVE 9.\n\n2. PRÉPARER PVE 8\nscripts/global/update-pve8.sh\nBascule les dépôts PVE 8 vers no-subscription et réécrit sources.list ;\nmet à jour les paquets PVE 8 ; arrêt si la mise à jour échoue ou est annulée.\npve8to9 --full (peut proposer des réparations).\nVérifie espace et connectivité ; Ceph 19.x Squid sur les nœuds hyperconvergés.\n\n3. MIGRER LES DÉPÔTS\nBookworm → Trixie ; écrit les fichiers deb822 .sources.\nCommente les anciens fichiers .list ; apt-get update.\n\n4. VALIDER LE CANDIDAT PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nArrêt si le candidat n’est pas 9.x ou si proxmox-ve serait supprimé.\n\n5. METTRE À NIVEAU\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nSans abonnement : désactive pve-enterprise.sources.\nJournal : /var/log/pve8-a-pve9-.log\n\n6. VÉRIFIER ET REDÉMARRER\nEFI : apt install grub-efi-amd64\npve8to9 --full ; examiner le résultat et la demande de redémarrage.",
     "preflightHeading": "Contrôles avant la mise à niveau",
     "preflightTable": {
       "checkHeader": "Vérifier",
@@ -158,7 +158,7 @@
   },
   "manual": {
     "heading": "Procédure de mise à niveau manuelle",
-    "intro": "Voici ce qu’affiche l’option Guide de mise à niveau manuelle : le wiki officiel Proxmox de passage de 8 à 9, organisé en quatre phases. Le script affiche les commandes ; vous les exécutez dans un autre shell root. Chaque étape reste visible.",
+    "intro": "Voici ce qu’affiche l’option Guide de mise à niveau manuelle : le wiki officiel Proxmox de passage de 8 à 9, organisé en quatre phases. Le tableau des réponses dpkg ajoute les réserves du wiki pour sshd_config et chrony.conf, que le menu affiche comme un simple Y. Le script affiche les commandes ; vous les exécutez dans un autre shell root. Aucune automatisation, chaque étape reste visible.",
     "rootCalloutTitle": "Toutes les commandes ont besoin de root",
     "rootCalloutBody": "Utilisez un shell root pour ces commandes, y compris les redirections et les documents here-document. Avec sudo, assurez-vous que la commande entière bénéficie des privilèges root.",
     "phase1Heading": "Phase 1 — Préparation",
@@ -230,7 +230,7 @@
   "clusterCeph": {
     "heading": "Cluster & Ceph",
     "clusterCalloutTitle": "Cluster: mise à niveau des nœuds un à la fois",
-    "clusterCalloutBody": "Détecté par pvecm status. Ne mettez pas plusieurs nœuds à niveau simultanément. Déplacez les invités, mettez le nœud à niveau, redémarrez et vérifiez son appartenance au cluster avant de continuer. Pendant l’opération, la migration à chaud d’une ancienne version de PVE vers une plus récente fonctionne ; le sens inverse n’est généralement pas pris en charge. Les groupes HA deviennent automatiquement des règles HA lorsque tous les nœuds utilisent PVE 9.",
+    "clusterCalloutBody": "Ne mettez pas plusieurs nœuds à niveau simultanément. Déplacez les invités, mettez le nœud à niveau, redémarrez et vérifiez son appartenance au cluster avant de continuer. Pendant l’opération, la migration à chaud d’une ancienne version de PVE vers une plus récente fonctionne ; le sens inverse n’est généralement pas pris en charge. Les groupes HA deviennent automatiquement des règles HA lorsque tous les nœuds utilisent PVE 9.",
     "cephCalloutTitle": "Ceph : mettre Ceph à niveau D’ABORD, puis Proxmox",
     "cephCalloutBody": "Ceph doit déjà utiliser 19.x Squid avant la mise à niveau Proxmox. Suivez l’ordre officiel Quincy 17 → Reef 18 → Squid 19, avec mise à jour des paquets et redémarrages échelonnés des démons Ceph dans l’ordre documenté. Vérifiez ceph --version avant chaque nœud Proxmox. Procédure complète : wiki de passage de Ceph Reef à Squid."
   },
@@ -273,7 +273,7 @@
   },
   "files": {
     "heading": "Fichiers concernés",
-    "code": "scripts/utilities/upgrade_pve8_to_pve9.sh                         # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh                                # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh    # 17-step runbook\nscripts/global/update-pve8.sh                                     # PVE 8 worker (final patches)\n/etc/apt/sources.list                                             # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources                           # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources                            # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources                              # created if Ceph\n/etc/apt/sources.list.d/*.list                                    # legacy files commented out / removed\n/var/log/pve8-a-pve9-.log                              # full upgrade log"
+    "code": "scripts/utilities/upgrade_pve8_to_pve9.sh                         # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh                                # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh    # 17-step runbook\nscripts/global/update-pve8.sh                                     # PVE 8 worker (final patches)\n/etc/apt/sources.list                                             # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources                           # created (deb822 PVE 9)\n/etc/apt/sources.list.d/ceph.sources                              # created if Ceph\n/etc/apt/sources.list.d/*.list                                    # legacy files commented out / removed\n/var/log/pve8-a-pve9-.log                              # full upgrade log"
   },
   "references": {
     "heading": "Références officielles",
diff --git a/web/messages/it/docs/backup-restore/creating-backups.json b/web/messages/it/docs/backup-restore/creating-backups.json
index c3446fd6..fcf362f2 100644
--- a/web/messages/it/docs/backup-restore/creating-backups.json
+++ b/web/messages/it/docs/backup-restore/creating-backups.json
@@ -21,8 +21,8 @@
     "rows": [
       {
         "entry": "ProxMenux Scripts (TUI)",
-        "path": "menu → Utilities → Host Config Backup",
-        "detail": "Flusso basato sul dialogo, SSH-friendly. Il menu principale presenta le sei opzioni direttamente. Utilizza backup_menu in backup_host.sh."
+        "path": "menu → Host Backup & Restore → Backup host configuration",
+        "detail": "Flusso basato sul dialogo, SSH-friendly. La voce del menu principale apre Host Config Backup / Restore; Backup host configuration porta alle sei opzioni. Utilizza backup_menu in backup_host.sh."
       },
       {
         "entry": "ProxMenux Monitor (interfaccia web)",
@@ -83,7 +83,7 @@
   "profiles": {
     "heading": "Profilo predefinito e personalizzato",
     "defaultTitle": "Profilo predefinito",
-    "defaultBody": "Il profilo predefinito è l'elenco curato da hb_default_profile_paths (documentato in Come funziona sotto Categorie di percorsi) più ogni voce nel file extra persistente /usr/local/share/proxmenux/backup-extra-paths.txt. L'utente conferma le opzioni di destinazione e crittografia e il backup procede senza ulteriori selezioni dei percorsi.",
+    "defaultBody": "Il profilo predefinito è l'elenco curato da hb_default_profile_paths (documentato in Come funziona sotto Categorie di percorsi) più ogni voce nel file extra persistente /usr/local/share/proxmenux/backup-extra-paths.txt. L'utente sceglie le opzioni di destinazione e crittografia e il backup procede senza ulteriori selezioni dei percorsi.",
     "customTitle": "Profilo personalizzato",
     "customBody": "Il profilo personalizzato apre un elenco con tutti i percorsi del profilo predefinito (non selezionati) e tutti gli extra persistenti (già selezionati, con il prefisso [+]). L’utente seleziona i percorsi per questa esecuzione e può premere Aggiungi percorso personalizzato per aggiungere un nuovo percorso assoluto. I percorsi aggiunti vengono salvati in backup-extra-paths.txt, così i backup futuri li includono automaticamente senza doverli aggiungere di nuovo. Deselezionare un extra persistente lo esclude da questa esecuzione ma non lo elimina dal file — l’eliminazione avviene con l’azione separata Gestisci percorsi personalizzati, al di fuori del flusso di backup.",
     "customPickerAlt": "Elenco di controllo del profilo personalizzato che mostra i percorsi di predefinito (non selezionati) e gli extra persistenti (già selezionati con un prefisso [+]), più pulsanti per aggiungere un nuovo percorso o confermare la selezione.",
@@ -124,14 +124,7 @@
   },
   "included": {
     "heading": "Cosa entra e cosa rimane fuori",
-    "intro": "Ogni percorso nel profilo risolto (predefinito + extra persistenti + selezione personalizzata) viene copiato con rsync -aAXH --numeric-ids. Un elenco di esclusione condiviso si applica a ogni percorso, e due directory portano ulteriori esclusioni specifiche del percorso.",
-    "globalTitle": "Esclusioni globali (applicate ad ogni percorso)",
-    "globalItems": [
-      "images/ — dump immagine.",
-      "dump/ — output di vzdump.",
-      "tmp/ — file temporanei.",
-      "*.log — file di registro."
-    ],
+    "intro": "Ogni percorso nel profilo risolto (predefinito + extra persistenti + selezione personalizzata) viene copiato con rsync -aAXH --numeric-ids. Non esiste un elenco di esclusione globale; alcune directory hanno esclusioni specifiche del percorso.",
     "rootTitle": "/root/ esclusioni",
     "rootBody": "/root/ fa parte del profilo predefinito per includere nell’archivio gli script e la configurazione dell’utente. Sono esclusi i sottopercorsi volatili:",
     "rootItems": [
@@ -160,16 +153,16 @@
       "/home/USER — non nel profilo predefinito. Aggiungilo come un percorso personalizzato quando un sistema porta directory home degli utenti che devono sopravvivere a un ripristino."
     ],
     "customPathsTitle": "Come vengono gestiti i percorsi personalizzati",
-    "customPathsBody": "Un percorso personalizzato aggiunto in linea in modalità personalizzata o persistito in backup-extra-paths.txt passa attraverso la stessa pipeline rsync come percorsi di default-profile. Si applicano esclusioni globali. Se il percorso personalizzato avviene sotto /root/ o /usr/local/share/proxmenux/, si applicano ancora le esclusioni specifiche del percorso. Ogni percorso archiviato — predefinito o personalizzato — viene registrato in metadata/paths_archived.txt. I percorsi che non esistono sulla sorgente sono registrati in metadata/missing_paths.txt senza interrompere il backup."
+    "customPathsBody": "Un percorso personalizzato aggiunto in linea in modalità personalizzata o persistito in backup-extra-paths.txt passa attraverso la stessa pipeline rsync come percorsi di default-profile. Se il percorso personalizzato avviene sotto /root/ o /usr/local/share/proxmenux/, si applicano ancora le esclusioni specifiche del percorso. Ogni percorso archiviato — predefinito o personalizzato — viene registrato in metadata/selected_paths.txt. I percorsi che non esistono sulla sorgente sono registrati in metadata/missing_paths.txt senza interrompere il backup, e i percorsi la cui copia fallisce in metadata/failed_paths.txt."
   },
   "archiveStructure": {
     "heading": "Struttura dell'archivio",
     "intro": "La directory di staging prodotta da ogni backend segue lo stesso layout indipendentemente dalla destinazione. Il tarball, PBS .pxar o Borg archivio memorizza questo albero verbatim.",
-    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state (kernel_params, hardware, storage, guests, components, source_host)\n├── metadata/\n│   ├── packages.manual.list       # output of apt-mark showmanual\n│   ├── run_info.env               # hostname, timestamp, kernel version, pmxcfs method\n│   ├── paths_archived.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, /etc/apt, ...\n    ├── root/                      # /root without volatile subpaths\n    ├── usr/local/                 # /usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux (state only)\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/crontabs"
+    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state (kernel_params, hardware, storage, guests, components, source_host)\n├── metadata/\n│   ├── packages.manual.list       # output of apt-mark showmanual\n│   ├── run_info.env               # hostname, timestamp, kernel version, pmxcfs method\n│   ├── selected_paths.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, /etc/apt, ...\n    ├── root/                      # /root without volatile subpaths\n    ├── usr/local/                 # /usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux (state only)\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/crontabs"
   },
   "confirmation": {
     "heading": "Riepilogo conferma",
-    "body": "Prima che il backend scriva qualcosa alla destinazione, ProxMenux mostra una finestra di dialogo sommaria con la destinazione, l'ID di backup o il nome dell'archivio, lo stato di crittografia e l'elenco dei percorsi da copiare. Cancellando qui interrompe il backup in modo pulito — la directory di staging viene rimossa dal gancio trap impostato sulla funzione backend e nessun dato parziale raggiunge la destinazione."
+    "body": "Prima che il backend scriva qualcosa alla destinazione, ProxMenux stampa nel terminale un riepilogo con la destinazione, l'ID di backup o il nome dell'archivio, lo stato di crittografia e l'elenco dei percorsi da copiare. In questo punto non c'è alcun passaggio di conferma o annullamento; la copia inizia subito dopo. Se il backup fallisce o viene interrotto, la directory di staging viene rimossa dal gancio trap impostato sulla funzione backend e nessun dato parziale raggiunge la destinazione."
   },
   "writing": {
     "heading": "Scrivere alla destinazione",
diff --git a/web/messages/it/docs/backup-restore/destinations/local.json b/web/messages/it/docs/backup-restore/destinations/local.json
index 0bf1d672..09ebca55 100644
--- a/web/messages/it/docs/backup-restore/destinations/local.json
+++ b/web/messages/it/docs/backup-restore/destinations/local.json
@@ -18,11 +18,11 @@
   },
   "targetConfig": {
     "heading": "Configurazione della directory target",
-    "intro": "La destinazione locale è una unica directory di destinazione salvata in modo persistente — non un elenco. ProxMenux memorizza la scelta dell'operatore su /usr/local/share/proxmenux/local-target.conf e la legge su ogni backup. Quando non è stato configurato alcun obiettivo, viene utilizzato il HB_LOCAL_TARGET_DEFAULT = /var/lib/vz/dump predefinito (la stessa directory Proxmox utilizza per le proprie uscite vzdump). L'obiettivo è configurato da Configura destinazioni di backup → Destinazioni locali:",
+    "intro": "La destinazione locale è una unica directory di destinazione salvata in modo persistente — non un elenco. ProxMenux memorizza la scelta dell'operatore su /usr/local/share/proxmenux/local-target.conf e la legge su ogni backup. Quando non è stato configurato alcun obiettivo, viene utilizzato il HB_LOCAL_TARGET_DEFAULT = /var/lib/vz/dump predefinito (la stessa directory Proxmox utilizza per le proprie uscite vzdump). L'obiettivo è configurato da Configura destinazioni di backup → Destinazioni archivio locali → Gestisci destinazione di backup locale:",
     "options": [
       "Utilizzo predefinito (/var/lib/vz/dump). Lo storage locale Proxmox. Presente su ogni installazione Proxmox; l'archivio si trova accanto alle uscite vzdump e viene ritirato dalla scheda Monitor Backups automaticamente.",
-      "Inserisci un percorso personalizzato. Puoi usare qualsiasi percorso assoluto del filesystem: un montaggio NFS, una condivisione SMB montata tramite fstab, un dataset ZFS dedicato o un secondo disco interno. Prima di salvarlo, ProxMenux verifica che il percorso esista e sia una directory.",
-      "Scegli un’unità USB. Apre il sottomenu USB (sotto), che rileva dispositivi rimovibili e offre di montare o formattarne uno."
+      "Usa un percorso personalizzato. Puoi usare qualsiasi percorso assoluto del filesystem: un montaggio NFS, una condivisione SMB montata tramite fstab, un dataset ZFS dedicato o un secondo disco interno. Prima di salvarlo, ProxMenux verifica che il percorso esista e sia una directory.",
+      "Usa un disco USB. Apre il sottomenu USB (sotto), che rileva dispositivi rimovibili e offre di montare o formattarne uno."
     ]
   },
   "usbFlow": {
@@ -38,7 +38,7 @@
       {
         "state": "non montato",
         "shown": "Dimensione · etichetta · [fstype] · (non montato — sarà montato)",
-        "action": "Un filesystem è presente ma non montato. A conferma, ProxMenux gestisce hb_mount_usb_partition: crea /mnt/backup-LABEL (o un percorso basato su UUID se non c'è etichetta), monta la partizione e persiste il punto di montaggio come obiettivo locale."
+        "action": "Un filesystem è presente ma non montato. A conferma, ProxMenux gestisce hb_mount_usb_partition: crea /mnt/proxmenux-backup-LABEL (UUID o nome del dispositivo se non c'è etichetta), monta la partizione e persiste il punto di montaggio come obiettivo locale."
       },
       {
         "state": "vuoto",
@@ -63,7 +63,7 @@
   },
   "sidecar": {
     "heading": "Il sidecar JSON",
-    "intro": "Ogni backup locale di successo produce un file compagno: HOSTNAME-TIMESTAMP.tar.zst.proxmenux.json, scritto accanto all'archivio da hb_write_archive_sidecar. Questo piccolo file JSON consente a ProxMenux Monitor di identificare l'archivio come ProxMenux backup host anche se in seguito viene spostato, rinominato o archiviato altrove.",
+    "intro": "Ogni backup locale di successo produce un file compagno: hostcfg-HOSTNAME-TIMESTAMP.tar.zst.proxmenux.json, scritto accanto all'archivio da hb_write_archive_sidecar. Questo piccolo file JSON consente a ProxMenux Monitor di identificare l'archivio come ProxMenux backup host anche se in seguito viene spostato, rinominato o archiviato altrove.",
     "contentTitle": "Contenuto del sidecar",
     "contentBody": "Il sidecar memorizza la versione schema, se il backup proveniva da un run interattivo o da un lavoro programmato (kind), l'ID di lavoro per le esecuzioni pianificate, la modalità profilo utilizzata (default o custom), il nome host sorgente, il nome base originale dell'archivio, un timestamp di creazione ISO-8601 e la dimensione dell'archivio in byte.",
     "whyBody": "La scheda Backups di Monitor cerca i file ausiliari *.proxmenux.json nelle directory locali configurate, non i file *.tar.zst, perché un .tar.zst senza file ausiliario potrebbe non essere un backup ProxMenux. La scansione è veloce (i file JSON sono piccoli) e l’associazione resta valida quando l’archivio viene rinominato, purché anche il file ausiliario venga rinominato di conseguenza."
diff --git a/web/messages/it/docs/backup-restore/destinations/pbs.json b/web/messages/it/docs/backup-restore/destinations/pbs.json
index d6c90a2a..fec03cfc 100644
--- a/web/messages/it/docs/backup-restore/destinations/pbs.json
+++ b/web/messages/it/docs/backup-restore/destinations/pbs.json
@@ -33,7 +33,7 @@
       {
         "source": "Configurazione manuale ProxMenux",
         "path": "/usr/local/share/proxmenux/pbs-manual-configs.txt + pbs-pass-NAME.txt + pbs-fingerprint-NAME.txt",
-        "content": "Aggiunto da Configure destinazioni di backup → PBS destinazioni → Aggiungi PBS. Prompts per un nome, nome utente (root@pam o user@pbs!token), host o IP, datastore e password. I re-prompt password su input vuoto — un salvataggio vuoto altrimenti persisterebbe silenziosamente e ogni backup successivo fallirebbe con un errore di autenticazione opaco. Questo percorso viene utilizzato quando il target PBS non è registrato come storage Proxmox."
+        "content": "Aggiunto da Configure destinazioni di backup → Destinazioni Proxmox Backup Server (PBS) → + Aggiungi nuovo PBS manualmente. Prompts per un nome, nome utente (root@pam o user@pbs!token), host o IP, datastore e password. I re-prompt password su input vuoto — un salvataggio vuoto altrimenti persisterebbe silenziosamente e ogni backup successivo fallirebbe con un errore di autenticazione opaco. Questo percorso viene utilizzato quando il target PBS non è registrato come storage Proxmox."
       }
     ],
     "menuTitle": "Menu di selezione",
diff --git a/web/messages/it/docs/backup-restore/how-it-works.json b/web/messages/it/docs/backup-restore/how-it-works.json
index cc0aa5a8..3f4a29ad 100644
--- a/web/messages/it/docs/backup-restore/how-it-works.json
+++ b/web/messages/it/docs/backup-restore/how-it-works.json
@@ -20,7 +20,7 @@
     "heading": "Disposizione dell'archivio",
     "intro": "Ogni archivio segue lo stesso layout albero indipendentemente dalla destinazione. La sottodirectory metadata/ contiene i dati strutturati; la sottodirectory rootfs/ contiene la copia del filesystem.",
     "treeCaption": "La directory di preparazione creata durante il backup. Tutte le destinazioni ricevono lo stesso albero nel proprio formato nativo: tar per i backup locali, blocchi PBS per PBS e segmenti Borg per Borg.",
-    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state\n├── metadata/\n│   ├── packages.manual.list       # apt-mark showmanual\n│   ├── run_info.env               # backup run identity + kernel version + pmxcfs method\n│   ├── paths_archived.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, …\n    ├── root/                      # /root (with volatile subdirs excluded)\n    ├── usr/local/                 # /usr/local/bin, /usr/local/share/proxmenux, …\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/…"
+    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state\n├── metadata/\n│   ├── packages.manual.list       # apt-mark showmanual\n│   ├── run_info.env               # backup run identity + kernel version + pmxcfs method\n│   ├── selected_paths.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, …\n    ├── root/                      # /root (with volatile subdirs excluded)\n    ├── usr/local/                 # /usr/local/bin, /usr/local/share/proxmenux, …\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/…"
   },
   "rootfs": {
     "heading": "La parte rootfs",
@@ -176,7 +176,7 @@
         "stage": "3",
         "name": "Preparazione delle modifiche differite",
         "reads": "rootfs/ (riavvio + percorsi pericolosi)",
-        "action": "_rs_prepare_pending_restore prepara i percorsi rischiosi in /var/lib/proxmenux/pending-restore/, scrive plan.env, apply-on-boot.list e rs-skip-paths.txt e abilita proxmenux-restore-onboot.service per il prossimo avvio."
+        "action": "_rs_prepare_pending_restore prepara i percorsi rischiosi in /var/lib/proxmenux/restore-pending/<restore_id>/ (collegata come current), scrive plan.env, apply-on-boot.list e rs-skip-paths.txt e abilita proxmenux-restore-onboot.service per il prossimo avvio."
       },
       {
         "stage": "3b",
diff --git a/web/messages/it/docs/backup-restore/index.json b/web/messages/it/docs/backup-restore/index.json
index 0e1015af..3cc3e651 100644
--- a/web/messages/it/docs/backup-restore/index.json
+++ b/web/messages/it/docs/backup-restore/index.json
@@ -50,7 +50,7 @@
     "heading": "Due interfacce, un backend",
     "intro": "Ogni fase del flusso di lavoro di backup e ripristino è disponibile da due punti di ingresso. Entrambi chiamano la stessa libreria di shell, producono archivi identici e leggono lo stesso registro dei lavori.",
     "cliLabel": "ProxMenux Scripts (TUI)",
-    "cliDetail": "menu → Utilità →\nBackup/Ripristino dell'host\n\nFlusso basato su dialoghi,\nSSH-friendly, scriptabile\nper uso non presidiato.",
+    "cliDetail": "menu →\nBackup/Ripristino dell'host\n\nFlusso basato su dialoghi,\nSSH-friendly, scriptabile\nper uso non presidiato.",
     "webLabel": "ProxMenux Monitor (Web UI)",
     "webDetail": "Scheda Backups\nnell’interfaccia web di Monitor.\n\nRipristino con un clic, log\nin tempo reale, integrazione con\nle notifiche e il visualizzatore\ndelle differenze di rollback."
   },
diff --git a/web/messages/it/docs/backup-restore/restoring.json b/web/messages/it/docs/backup-restore/restoring.json
index 2f53b1e3..cbc37121 100644
--- a/web/messages/it/docs/backup-restore/restoring.json
+++ b/web/messages/it/docs/backup-restore/restoring.json
@@ -92,11 +92,11 @@
   "fullFlow": {
     "heading": "Il flusso di lavoro di ripristino completo",
     "intro": "La pipeline completa dalla selezione di archivi a un host restaurato funzionante. Ogni fase alimenta il successivo; lo stato scritto da fasi precedenti è consumato da quelli successivi.",
-    "diagram": "┌─────────────────────────────────────────────────────────────────┐\n│  Archive → staging_root/                                        │\n│    manifest.json   metadata/   rootfs/                          │\n└──────────────────────────────┬──────────────────────────────────┘\n                               │\n                               ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  1. hb_compat_check                                     │\n   │     · hardware drift → RS_SKIP_PATHS                    │\n   │     · kernel direction → same / bk_newer / bk_older     │\n   │     · NIC remap plan                                    │\n   │     · hydration plan (bk_older only)                    │\n   │     · rollback plan                                     │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  2. Confirmation dialog                                 │\n   │     Shows: hot count, pending count, drift, hydration,  │\n   │     NIC remap, cross-kernel note, reinstalls preview    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │ (accepted)\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  3. Apply hot paths → LIVE                              │\n   │     _rs_apply rsyncs each hot path                      │\n   │     from staging_root/rootfs to /                       │\n   │     (skips paths in RS_SKIP_PATHS)                      │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4. _rs_prepare_pending_restore                         │\n   │     /var/lib/proxmenux/pending-restore/                 │\n   │       ├── apply-on-boot.list                            │\n   │       ├── plan.env                                      │\n   │       ├── rs-skip-paths.txt                             │\n   │       └── rootfs/ (deferred paths)                      │\n   │     Enable proxmenux-restore-onboot.service             │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4b. _rs_import_data_pools                              │\n   │      Imports non-root ZFS pools whose disks are all     │\n   │      present; retries with -f on foreign hostid;        │\n   │      skips with a warning when disks are missing.       │\n   │      Log: /var/log/proxmenux/restore-datapools-*.log    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  5. _rs_run_complete_extras                             │\n   │     packages.manual.list                                │\n   │       → cascade-safe filter                             │\n   │       → apt-get install                                 │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  6. REBOOT                                              │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  7. apply_pending_restore.sh (on early boot)            │\n   │     Reads plan.env                                      │\n   │     Applies apply-on-boot.list (filtered)               │\n   │     Installs the postboot systemd unit                  │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  8. apply_cluster_postboot.sh (~10 minutes)             │\n   │     Runs after pve-cluster is up                        │\n   │     · Applies /etc/pve entries to pmxcfs                │\n   │     · update-initramfs -u -k all                        │\n   │     · update-grub / proxmox-boot-tool refresh           │\n   │     · component --auto-reinstall (nvidia, coral, ...)   │\n   │     · Boot sanity check                                 │\n   │     · Notification: \"Host restore finished\"             │\n   └─────────────────────────────────────────────────────────┘"
+    "diagram": "┌─────────────────────────────────────────────────────────────────┐\n│  Archive → staging_root/                                        │\n│    manifest.json   metadata/   rootfs/                          │\n└──────────────────────────────┬──────────────────────────────────┘\n                               │\n                               ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  1. hb_compat_check                                     │\n   │     · hardware drift → RS_SKIP_PATHS                    │\n   │     · kernel direction → same / bk_newer / bk_older     │\n   │     · NIC remap plan                                    │\n   │     · hydration plan (bk_older only)                    │\n   │     · rollback plan                                     │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  2. Confirmation dialog                                 │\n   │     Shows: hot count, pending count, drift, hydration,  │\n   │     NIC remap, cross-kernel note, reinstalls preview    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │ (accepted)\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  3. Apply hot paths → LIVE                              │\n   │     _rs_apply rsyncs each hot path                      │\n   │     from staging_root/rootfs to /                       │\n   │     (skips paths in RS_SKIP_PATHS)                      │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4. _rs_prepare_pending_restore                         │\n   │     /var/lib/proxmenux/restore-pending/'<'id'>'/            │\n   │       ├── apply-on-boot.list                            │\n   │       ├── plan.env                                      │\n   │       ├── rs-skip-paths.txt                             │\n   │       └── rootfs/ (deferred paths)                      │\n   │     Enable proxmenux-restore-onboot.service             │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4b. _rs_import_data_pools                              │\n   │      Imports non-root ZFS pools whose disks are all     │\n   │      present; retries with -f on foreign hostid;        │\n   │      skips with a warning when disks are missing.       │\n   │      Log: /var/log/proxmenux/restore-datapools-*.log    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  5. _rs_run_complete_extras                             │\n   │     packages.manual.list                                │\n   │       → cascade-safe filter                             │\n   │       → apt-get install                                 │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  6. REBOOT                                              │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  7. apply_pending_restore.sh (on early boot)            │\n   │     Reads plan.env                                      │\n   │     Applies apply-on-boot.list (filtered)               │\n   │     Installs the postboot systemd unit                  │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  8. apply_cluster_postboot.sh (~10 minutes)             │\n   │     Runs after pve-cluster is up                        │\n   │     · Applies /etc/pve entries to pmxcfs                │\n   │     · update-initramfs -u -k all                        │\n   │     · update-grub / proxmox-boot-tool refresh           │\n   │     · component --auto-reinstall (nvidia, coral, ...)   │\n   │     · Boot sanity check                                 │\n   │     · Notification: \"Host restore finished\"             │\n   └─────────────────────────────────────────────────────────┘"
   },
   "pendingMachinery": {
     "heading": "Il meccanismo di ripristino differito",
-    "intro": "I percorsi classificati come reboot o dangerous, e le scritture di idratazione, sono in scena per il prossimo avvio invece di essere applicati in diretta. Questo viene fatto attraverso un piccolo set autocontenuto di file sotto /var/lib/proxmenux/pending-restore/:",
+    "intro": "I percorsi classificati come reboot o dangerous, e le scritture di idratazione, sono in scena per il prossimo avvio invece di essere applicati in diretta. Questo viene fatto attraverso un piccolo set autocontenuto di file sotto /var/lib/proxmenux/restore-pending/<restore_id>/:",
     "rows": [
       {
         "file": "apply-on-boot.list",
@@ -115,7 +115,7 @@
         "content": "I file in scena stessi — i byte esatti che saranno stabiliti. Tenuto sullo stesso filesystem di / così il rsync on-boot è veloce e non dipende dallo storage esterno ancora raggiungibile al boot."
       }
     ],
-    "unitBody": "L’unità systemd proxmenux-restore-onboot.service viene abilitata alla fine della fase interattiva. Il servizio viene eseguito una sola volta, all’inizio del prossimo avvio, richiama apply_pending_restore.sh e poi si disabilita. La condizione ConditionPathExists=/var/lib/proxmenux/pending-restore/state evita l’esecuzione quando non esiste un ripristino in sospeso."
+    "unitBody": "L’unità systemd proxmenux-restore-onboot.service viene abilitata alla fine della fase interattiva. Il servizio viene eseguito una sola volta, all’inizio del prossimo avvio (prima della rete), richiama apply_pending_restore.sh e poi si disabilita. L’unità non ha condizioni sul percorso: è lo script stesso a terminare quando /var/lib/proxmenux/restore-pending/current non esiste, quindi a qualsiasi avvio senza un ripristino in sospeso non fa nulla. Il servizio separato proxmenux-apply-cluster-postboot.service è condizionato da ConditionPathExists=/var/lib/proxmenux/cluster-apply-pending."
   },
   "postbootDispatcher": {
     "heading": "Il processo dopo l’avvio — apply_cluster_postboot.sh",
@@ -238,11 +238,11 @@
     "heading": "Dove vivono i registri",
     "rows": [
       {
-        "log": "/var/log/proxmenux/restore-YYYYMMDD_HHMMSS.log",
-        "detail": "Scritto nella fase interattiva: risultati di compatibilità, filtro delle differenze, piano di adattamento, output dell’applicazione diretta e della preparazione differita, installazione dei pacchetti."
+        "log": "/tmp/proxmenux--restore-YYYYMMDD_HHMMSS.log",
+        "detail": "Scritto nella fase interattiva (pbs, borg o local). Anche le reinstallazioni dei pacchetti e i passaggi dei data pool scrivono in /var/log/proxmenux/restore-apt-*.log e restore-datapools-*.log."
       },
       {
-        "log": "/var/log/proxmenux/apply-pending-YYYYMMDD_HHMMSS.log",
+        "log": "/var/log/proxmenux/proxmenux-restore-onboot-YYYYMMDD_HHMMSS.log",
         "detail": "Scritto da apply_pending_restore.sh all’inizio dell’avvio: caricamento di plan.env, elaborazione di apply-on-boot e filtro dei percorsi esclusi."
       },
       {
diff --git a/web/messages/it/docs/backup-restore/scheduled-jobs.json b/web/messages/it/docs/backup-restore/scheduled-jobs.json
index 6a851dc3..5a2e8b95 100644
--- a/web/messages/it/docs/backup-restore/scheduled-jobs.json
+++ b/web/messages/it/docs/backup-restore/scheduled-jobs.json
@@ -101,7 +101,7 @@
   },
   "management": {
     "heading": "Gestione dei lavori",
-    "intro": "Il menu scheduler (Script TUI) e la scheda Monitor Backups espongono le stesse azioni su qualsiasi lavoro.",
+    "intro": "Il menu scheduler nella TUI degli script (Backup scheduler and retention) offre Run now, Enable / Disable e Delete, oltre a un elenco dei lavori con lo stato dell'ultima esecuzione. Modifica e Visualizza log sono disponibili solo nella scheda Monitor Backups.",
     "rows": [
       {
         "action": "Esegui ora",
@@ -112,7 +112,7 @@
         "detail": "Nuova modalità: systemctl enable/disable --now il timer. Modalità di aggancio: cambia il flag ENABLED nel job env — lo script gancio lo onora alla prossima esecuzione PVE."
       },
       {
-        "action": "Modifica",
+        "action": "Modifica (solo Monitor)",
         "detail": "Riapre le richieste di destinazione, pianificazione, profilo e ritenzione e riscrive i file di lavoro. Conserva l'ID del lavoro."
       },
       {
@@ -120,7 +120,7 @@
         "detail": "Rimuove il job env, il file dei percorsi, il timer + servizio systemd (Nuova modalità), e disabilita il binding del gancio (Modalità allegato). Non tocca archivi già sulla destinazione."
       },
       {
-        "action": "Visualizza log",
+        "action": "Visualizza log (solo Monitor)",
         "detail": "Mostra in tempo reale /var/log/proxmenux/backup-jobs/JOB_ID-YYYYMMDD_HHMMSS.log: un file di registro per esecuzione. Lo script di esecuzione emette anche una riga di stato compatta a journald sotto il servizio systemd."
       }
     ]
diff --git a/web/messages/it/docs/installation.json b/web/messages/it/docs/installation.json
index 84c3791b..c26e99cc 100644
--- a/web/messages/it/docs/installation.json
+++ b/web/messages/it/docs/installation.json
@@ -53,11 +53,11 @@
     "calloutBody": "Vuoi provare le novità prima dell’uscita ufficiale e contribuire alla versione finale? Il programma beta di ProxMenux offre accesso anticipato alle nuove funzionalità, incluse le ultime build di Monitor, dal ramo develop. Le versioni beta possono contenere bug o funzioni incomplete. I tuoi commenti aiutano a correggerli prima della versione stabile.",
     "intro": "Installare la versione beta invece di quella stabile con:",
     "code": "bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/develop/install_proxmenux_beta.sh)\"",
-    "outro": "La beta è facoltativa: devi avviarne esplicitamente il programma di installazione. Quando esce una versione stabile, ProxMenux avvisa al successivo avvio di menu e propone il passaggio automatico. Per uscire prima dalla beta, scegli Disattiva programma beta nelle impostazioni."
+    "outro": "La beta è facoltativa: devi avviarne esplicitamente il programma di installazione. Quando esce una versione stabile, il successivo avvio di menu la propone con la normale richiesta di aggiornamento. Per uscire dal canale beta, usa Cambia canale di rilascio nelle impostazioni e scegli Stable."
   },
   "updating": {
     "heading": "Aggiornamento",
-    "body": "ProxMenux gestisce i propri aggiornamenti. Se è disponibile una nuova versione, il successivo avvio di menu propone di installarla. Accettando, vengono sostituiti i file e la configurazione dello strumento nell’installazione esistente, senza download manuali. Il canale stabile riceve versioni stabili e la beta riceve versioni beta, con la proposta di passaggio descritta sopra. Il messaggio dipende da dove esegui menu: via SSH o dalla console Proxmox appare una finestra sì/no e, accettando, l’aggiornamento viene applicato subito. Nel terminale WebSocket di ProxMenux Monitor appare invece un messaggio informativo, perché il terminale verrebbe interrotto durante l’operazione. Il messaggio mostra l’installazione in una riga (bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\" per il canale stabile, oppure l’URL equivalente di install_proxmenux_beta.sh per la beta), da eseguire via SSH o dalla console."
+    "body": "ProxMenux gestisce i propri aggiornamenti. Se è disponibile una nuova versione, il successivo avvio di menu propone di installarla. Accettando, vengono sostituiti i file e la configurazione dello strumento nell’installazione esistente, senza download manuali. Il canale stabile riceve versioni stabili; la beta riceve build beta e le viene proposta anche ogni nuova versione stabile. Il messaggio dipende da dove esegui menu: via SSH o dalla console Proxmox appare una finestra sì/no e, accettando, l’aggiornamento viene applicato subito. Nel terminale WebSocket di ProxMenux Monitor appare invece un messaggio informativo, perché il terminale verrebbe interrotto durante l’operazione. Il messaggio mostra l’installazione in una riga (bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\" per il canale stabile, oppure l’URL equivalente di install_proxmenux_beta.sh per la beta), da eseguire via SSH o dalla console."
   },
   "uninstall": {
     "heading": "Disinstallare",
diff --git a/web/messages/it/docs/introduction.json b/web/messages/it/docs/introduction.json
index 024aeed4..021bcaad 100644
--- a/web/messages/it/docs/introduction.json
+++ b/web/messages/it/docs/introduction.json
@@ -21,11 +21,11 @@
     "intro": "ProxMenux comprende due componenti nello stesso albero dei sorgenti. Il programma di installazione li distribuisce entrambi sull’host: Scripts offre l’interfaccia da terminale e Monitor la dashboard web:",
     "scripts": {
       "title": "ProxMenux Scripts",
-      "body": "La CLI/TUI originale, da avviare con menu. Nove aree coprono ottimizzazioni dopo l’installazione, VM e container LXC, passthrough di GPU e Coral TPU, dischi, archiviazione, rete, sicurezza e utilità, con una raccolta di comandi di riferimento. Funziona nei terminali locali, SSH e web."
+      "body": "La CLI/TUI originale, da avviare con menu. Aree funzionali che coprono ottimizzazioni dopo l’installazione, creazione di VM e LXC, passthrough di GPU e Coral TPU, dischi, archiviazione, app OCI, rete, sicurezza, utilità, backup e ripristino dell’host, Proxmox VE Helper Scripts, con una raccolta di comandi di riferimento. Funziona nei terminali locali, SSH e web."
     },
     "monitor": {
       "title": "ProxMenux Monitor",
-      "body": "Dashboard web sul proprio server, distribuita come AppImage. Metriche dell’host in tempo reale, monitoraggio proattivo, notifiche, riformulazione facoltativa tramite IA con sei fornitori, terminale web integrato e API REST. Si attiva dal menu Scripts, in Impostazioni → ProxMenux Monitor."
+      "body": "Dashboard web sul proprio server, distribuita come AppImage. Metriche dell’host in tempo reale, monitoraggio proattivo, notifiche, riformulazione facoltativa tramite IA con sei fornitori, terminale web integrato e API REST. Il programma di installazione la configura come servizio che parte all’avvio; si attiva, disattiva o controlla dal menu Scripts (Impostazioni → ProxMenux Monitor)."
     },
     "calloutTitle": "Cosa gestisce ProxMenux",
     "calloutIntro": "Tre strati, tutti raggiungibili dallo stesso progetto:",
@@ -37,7 +37,7 @@
   },
   "scriptsSection": {
     "heading": "ProxMenux Scripts — che cosa si ottiene",
-    "intro": "Nove aree funzionali, ognuna con il proprio sottomenu in menu e una pagina dedicata nella documentazione. Seleziona una scheda per aprire direttamente la panoramica della sezione.",
+    "intro": "Le principali aree funzionali, ognuna con il proprio sottomenu in menu e una pagina dedicata nella documentazione. Seleziona una scheda per aprire direttamente la panoramica della sezione.",
     "items": [
       {
         "title": "Dopo l'installazione",
diff --git a/web/messages/it/docs/monitor/access-auth.json b/web/messages/it/docs/monitor/access-auth.json
index 59d77edd..768d1b9c 100644
--- a/web/messages/it/docs/monitor/access-auth.json
+++ b/web/messages/it/docs/monitor/access-auth.json
@@ -18,7 +18,7 @@
   },
   "reaching": {
     "heading": "Raggiungere la dashboard",
-    "intro": "ProxMenux Monitor si lega a 0.0.0.0:8008. Ci sono tre modi comuni per raggiungerlo:",
+    "intro": "ProxMenux Monitor è in ascolto sulla porta TCP 8008 su tutte le interfacce (::, IPv4 e IPv6). Ci sono tre modi comuni per raggiungerlo:",
     "outro": "L’accesso diretto corrisponde all’unità systemd predefinita. Proxy inverso e Secure Gateway sono descritti più avanti. Per fidarsi degli IP client in X-Forwarded-For servono PROXMENUX_TRUST_PROXY=1 e un proxy sotto il tuo controllo. La sola presenza di X-Forwarded-Proto o X-Forwarded-Host non attiva la fiducia nel proxy. Sulle porte 80/443 il frontend usa URL /api/* relativi; su qualsiasi altra porta chiama la porta 8008 sullo stesso nome host."
   },
   "firstLaunch": {
@@ -158,7 +158,7 @@
     "authJsonTitle": "auth.json — ciò che contiene e come è protetto",
     "authJsonBody": "I dati di autenticazione sono in /root/.config/proxmenux-monitor/auth.json, modalità 0600, proprietario root. Contiene hash PBKDF2 e segreti crittografici (jwt_secret, totp_secret), mai la password in chiaro. Cifra le destinazioni di backup o replica e non inserire questo file nel controllo di versione.",
     "rotateTitle": "La rotazione di jwt_secret invalida tutti i JWT esistenti",
-    "rotateBody": "Se auth.json viene ricreato o ripristinato con un segreto diverso, cambia jwt_secret. I vecchi JWT di sessione e API falliscono con \"Invalid or expired token\". L’interfaccia li segnala come Non valido — rigenerare. Revocali e sostituiscili anche in Home Assistant, script e altri client.",
+    "rotateBody": "Se auth.json viene ricreato o ripristinato con un segreto diverso, cambia jwt_secret. I vecchi JWT di sessione e API falliscono con \"Invalid or expired token\". L’interfaccia segna in rosso i token API interessati, con un badge che indica di revocarli e generarne di nuovi. Sostituiscili anche in Home Assistant, script e altri client.",
     "recoverTitle": "Recuperare una password persa",
     "recoverIntro": "Il recupero della password richiede accesso alla shell dell’host. ProxMenux offre una reimpostazione guidata nel menu di configurazione, senza modificare manualmente auth.json:",
     "survivesTitle": "Cosa sopravvive al reset",
@@ -284,11 +284,11 @@
     "heading": "Token API di lunga durata",
     "intro": "Le sessioni scadono dopo 24 ore. Per Homepage, Home Assistant, Grafana o Uptime Kuma crea un token API valido 365 giorni. Il JWT usa lo stesso segreto di firma; il campo token_name permette di identificarlo e revocarlo singolarmente.",
     "imageAlt": "Pannello di token API con nome, prefisso, data e scadenza",
-    "imageCaption": "Elenco token nelle impostazioni: nome, prefisso con ultimi quattro caratteri di identificazione, date di creazione e scadenza, revoca.",
+    "imageCaption": "Elenco token in Amministratore → Sicurezza: nome, prefisso con ultimi quattro caratteri di identificazione, date di creazione e scadenza, revoca.",
     "generateTitle": "Generare un token",
     "generateIntro": "Dal cruscotto:",
     "generateSteps": [
-      "Apri Sicurezza → Token di accesso API.",
+      "Apri Amministratore → Sicurezza → Token API e premi Genera nuovo token.",
       "Assegna un nome descrittivo, ad esempio \"Home Assistant\".",
       "Reinserisci la password e, con 2FA attivo, il codice attuale a sei cifre.",
       "Premi Genera token. Compare una sola volta: copialo subito."
@@ -310,7 +310,7 @@
     "intro": "Due percorsi per TLS:",
     "items": [
       "Proxy inverso consigliato. Termina TLS su Nginx / Caddy / Traefik e inoltra HTTP al processo Flask sulla porta 8008. Esempi sotto.",
-      "HTTPS diretto nell’AppImage. Configura un certificato con POST /api/ssl/configure (interfaccia: Impostazioni → SSL). HTTPS usa gevent.pywsgi con il gestore standard; flask-sock gestisce WebSocket per WSS. I percorsi sono salvati nella configurazione SSL."
+      "HTTPS diretto nell’AppImage. Configura un certificato con POST /api/ssl/configure (interfaccia: Amministratore → Sicurezza → SSL/HTTPS). HTTPS usa gevent.pywsgi con il gestore standard; flask-sock gestisce WebSocket per WSS. I percorsi sono salvati nella configurazione SSL."
     ],
     "calloutTitle": "Limitazioni dirette HTTPS",
     "calloutBody": "Monitor può usare un certificato gestito da Proxmox o file personalizzati. Non emette certificati ACME. Per emissione e rinnovo automatici usa Proxmox ACME o un proxy Caddy, Traefik o Nginx e assicurati che i certificati rinnovati vengano caricati."
diff --git a/web/messages/it/docs/monitor/ai-assistant.json b/web/messages/it/docs/monitor/ai-assistant.json
index 7e793add..79a87bb6 100644
--- a/web/messages/it/docs/monitor/ai-assistant.json
+++ b/web/messages/it/docs/monitor/ai-assistant.json
@@ -9,7 +9,7 @@
   },
   "header": {
     "title": "Assistente AI",
-    "description": "La riscrittura IA facoltativa trasforma le notifiche idonee in messaggi chiari, con dettaglio per canale, tredici lingue nell’interfaccia, prompt personalizzati, raccolta pubblica della comunità e contesto aggiuntivo. I rapporti backup_complete e backup_fail escludono completamente l’IA.",
+    "description": "La riscrittura IA facoltativa trasforma le notifiche idonee in messaggi chiari, con dettaglio per canale, tredici lingue nell’interfaccia, prompt personalizzati, raccolta pubblica della comunità e contesto aggiuntivo. I rapporti backup_complete e backup_fail non vengono mai riscritti; con i suggerimenti IA attivi, l’IA aggiunge solo una spiegazione quando un backup segnala errori.",
     "section": "Monitor di ProxMenux"
   },
   "intro": {
@@ -83,7 +83,7 @@
     "intro1": "I provider commerciali fatturano i token. Un token equivale indicativamente a quattro caratteri inglesi o tre quarti di una parola. La frase inglese \"Backup completed on storage local-bak\" contiene circa otto token. Dieci righe di log possono richiedere 200–400 token, secondo la densità tecnica.",
     "intro2": "Due cose sono fatturate su ogni chiamata:",
     "items": [
-      "Token in ingresso: prompt di sistema e messaggio utente con gravità, titolo, testo del modello e contesto aggiuntivo. La quantità dipende da prompt ed evento. Ogni canale riscritto può consumare i propri token; i rapporti di backup completato o fallito escludono l’IA.",
+      "Token in ingresso: prompt di sistema e messaggio utente con gravità, titolo, testo del modello e contesto aggiuntivo. La quantità dipende da prompt ed evento. Ogni canale riscritto può consumare i propri token; i rapporti di backup completato o fallito consumano token solo quando l’IA spiega errori del backup.",
       "Token in uscita: la risposta del modello. Monitor li limita con max_tokens. Il tetto è un limite, non un consumo fisso: se il modello produce 250 token con un limite di 1500, paghi per 250."
     ],
     "capsIntro": "Questi sono i limiti effettivi applicati dal sistema di invio, definiti in AI_DETAIL_TOKENS in notification_templates.py:",
@@ -118,7 +118,7 @@
     "imageCaption": "In-app modal — sei carte, una per fornitore, con le stesse descrizioni documentate di seguito.",
     "groq": {
       "heading": "Groq",
-      "tagline": "Molto veloce, generoso tier libero (30 req/min). Ideale per iniziare.",
+      "tagline": "Molto veloce, con un generoso tier gratuito. Un buon punto di partenza.",
       "items": [
         "Chiave API: console.groq.com/keys",
         "Esempi dall’elenco di modelli incluso: allam-2-7b, qwen/qwen3.8-27b, openai/gpt-oss-20b, openai/gpt-oss-120b.",
@@ -234,7 +234,7 @@
   },
   "detailLevel": {
     "heading": "Livello di dettaglio per canale",
-    "intro": "Telegram, Discord, Gotify ed e-mail hanno ciascuno una scelta del dettaglio. Tre valori regolano i limiti dei token e le istruzioni del prompt predefinito:",
+    "intro": "Ciascuno dei sei canali (Telegram, Discord, Gotify, e-mail, Pushover, Apprise) ha una propria scelta del dettaglio. Tre valori regolano i limiti dei token e le istruzioni del prompt predefinito:",
     "headerLevel": "Livello",
     "headerLabel": "Etichetta UI",
     "headerCap": "Limite di uscita",
@@ -261,7 +261,7 @@
     ],
     "defaultsIntro": "Impostazioni predefinite alla prima installazione:",
     "defaults": [
-      "Telegram, Discord, Gotify — standard.",
+      "Telegram, Discord, Gotify, Pushover, Apprise — standard.",
       "Email — detailed. Email è il canale in cui si desidera in genere l'immagine completa per l'archiviazione."
     ],
     "emailTitle": "Il livello di dettaglio e-mail aggiunge l'originale",
@@ -280,7 +280,7 @@
   },
   "templates": {
     "heading": "Una nota sui modelli",
-    "body1": "L’IA riceve il testo già prodotto da un modello, non dati grezzi. Tipi come vm_start, auth_fail e health_degraded hanno modelli in notification_templates.py. L’IA riscrive i testi idonei; backup_complete e backup_fail mantengono rapporti deterministici senza IA.",
+    "body1": "L’IA riceve il testo già prodotto da un modello, non dati grezzi. Tipi come vm_start, auth_fail e health_degraded hanno modelli in notification_templates.py. L’IA riscrive i testi idonei; backup_complete e backup_fail mantengono rapporti deterministici; con i suggerimenti IA attivi, l’IA aggiunge solo una spiegazione quando il backup segnala errori.",
     "body2": "Due implicazioni pratiche: l'IA non vede mai un hostname o VMID che deve inventare — quei campi sono posti dal modello prima della riscrittura. E se l'IA è disabilitata, il corpo modello è quello che viene inviato direttamente. La pagina Notifications documenta la pipeline di spedizione in pieno ed è la giusta cross-referenza per tutto ciò che accade ad un evento prima che raggiunga questo livello."
   },
   "privacy": {
diff --git a/web/messages/it/docs/monitor/api-reference.json b/web/messages/it/docs/monitor/api-reference.json
index a4789f6b..2ed9b70b 100644
--- a/web/messages/it/docs/monitor/api-reference.json
+++ b/web/messages/it/docs/monitor/api-reference.json
@@ -76,7 +76,7 @@
       {
         "endpoint": "/api/system-info",
         "method": "GET",
-        "use": "Dati di sistema estesi usati nell’intestazione: metriche generali e ora di avvio."
+        "use": "Informazioni leggere di intestazione: hostname, uptime e stato di integrità in cache. Pubblico, nessun token necessario."
       },
       {
         "endpoint": "/api/hardware",
@@ -401,7 +401,7 @@
       {
         "endpoint": "/api/logs",
         "method": "GET",
-        "use": "Le voci del diario filtrate. Query: ?level=&service=&limit=."
+        "use": "Le voci del diario filtrate. Query: ?priority=&service=&limit=&since_days= (priority 0-7, since_days fino a 90)."
       },
       {
         "endpoint": "/api/logs/download",
@@ -411,7 +411,7 @@
       {
         "endpoint": "/api/events",
         "method": "GET",
-        "use": "Flusso di eventi interno — lo stesso che alimenta le notifiche."
+        "use": "Attività ed eventi recenti del cluster Proxmox da pvesh get /cluster/tasks. Query: ?limit= (predefinito 50)."
       },
       {
         "endpoint": "/api/task-log/",
@@ -462,7 +462,7 @@
       {
         "endpoint": "/api/notifications/send",
         "method": "POST",
-        "use": "Genera un evento personalizzato. Corpo: '{'\"event_type\":\"custom\",\"severity\":\"WARNING\",\"title\":\"...\",\"body\":\"...\",\"data\":'{''}''}'."
+        "use": "Genera un evento personalizzato. Corpo: '{'\"event_type\":\"custom\",\"severity\":\"WARNING\",\"title\":\"...\",\"message\":\"...\",\"data\":'{''}''}'."
       },
       {
         "endpoint": "/api/notifications/test-ai",
diff --git a/web/messages/it/docs/monitor/architecture.json b/web/messages/it/docs/monitor/architecture.json
index b69a24ae..14bbc263 100644
--- a/web/messages/it/docs/monitor/architecture.json
+++ b/web/messages/it/docs/monitor/architecture.json
@@ -64,7 +64,7 @@
     "items": [
       "User=root: il servizio usa privilegi root per SMART, pvesh e gestione ZFS. I processi del terminale web ereditano questi permessi.",
       "Restart=on-failure con attesa di dieci secondi: il servizio riparte automaticamente dopo un’uscita con errore.",
-      "After=network.target: ordina l’avvio dopo quel target; non garantisce IP assegnato, DNS o connettività esterna."
+      "After=network.target pve-cluster.service: ordina l’avvio dopo il target di rete e il filesystem del cluster Proxmox; non garantisce IP assegnato, DNS o connettività esterna."
     ],
     "inspectTitle": "Esaminare l’unità attuale"
   },
diff --git a/web/messages/it/docs/monitor/dashboard/hardware.json b/web/messages/it/docs/monitor/dashboard/hardware.json
index 039d5771..8b89e2a4 100644
--- a/web/messages/it/docs/monitor/dashboard/hardware.json
+++ b/web/messages/it/docs/monitor/dashboard/hardware.json
@@ -168,7 +168,7 @@
   "ups": {
     "heading": "Stato UPS",
     "subHeading": "(condizionale)",
-    "body": "Appare con un server NUT (Network UPS Tools) configurato e raggiungibile. Mostra stato (rete / batteria / ricarica / batteria scarica), carica della batteria, autonomia stimata, percentuale di carico, tensione di ingresso, modello e firmware. I dati alimentano anche Sicurezza e certificati del monitor di integrità: il passaggio a batteria viene segnalato subito."
+    "body": "Appare con un server NUT (Network UPS Tools) configurato e raggiungibile. Mostra stato (rete / batteria / ricarica / batteria scarica), carica della batteria, autonomia stimata, percentuale di carico, tensione di ingresso, modello e firmware."
   },
   "dataCollected": {
     "heading": "Come vengono raccolti i dati",
diff --git a/web/messages/it/docs/monitor/dashboard/index.json b/web/messages/it/docs/monitor/dashboard/index.json
index 8345b722..9e9e9fe5 100644
--- a/web/messages/it/docs/monitor/dashboard/index.json
+++ b/web/messages/it/docs/monitor/dashboard/index.json
@@ -50,7 +50,7 @@
       },
       {
         "name": "Terminale",
-        "owns": "Shell dell'host o di qualsiasi VM/CT nel browser, con xterm.js tramite WebSocket."
+        "owns": "Shell nel browser sull’host Proxmox, con xterm.js tramite WebSocket. Le console LXC si aprono dalla scheda VM e LXC."
       },
       {
         "name": "Sicurezza",
diff --git a/web/messages/it/docs/monitor/dashboard/security.json b/web/messages/it/docs/monitor/dashboard/security.json
index 067c9ca9..efcbe9ec 100644
--- a/web/messages/it/docs/monitor/dashboard/security.json
+++ b/web/messages/it/docs/monitor/dashboard/security.json
@@ -183,7 +183,7 @@
       },
       {
         "card": "SSL/HTTPS",
-        "endpoint": "/api/auth/ssl/*",
+        "endpoint": "/api/ssl/*",
         "source": "openssl x509 su /etc/pve/local/pve-ssl.pem + /etc/proxmenux/ssl_config.json."
       },
       {
diff --git a/web/messages/it/docs/monitor/dashboard/settings.json b/web/messages/it/docs/monitor/dashboard/settings.json
index 4abed2a5..0a9f1f08 100644
--- a/web/messages/it/docs/monitor/dashboard/settings.json
+++ b/web/messages/it/docs/monitor/dashboard/settings.json
@@ -41,7 +41,7 @@
     "heading": "Unità rete",
     "imageAlt": "Scheda Unità di rete con il selettore di visualizzazione su Byte",
     "imageCaption": "Scegli tra bit al secondo e byte al secondo per ogni velocità di rete visualizzata nel cruscotto.",
-    "body": "Mostra la velocità in bit al secondo (Mbps/Gbps) o byte al secondo (MB/s/GB/s). I bit sono predefiniti, come nelle specifiche di schede e provider; i trasferimenti di file usano spesso byte. Grafici, etichette e suggerimenti cambiano subito senza ricaricare."
+    "body": "Mostra la velocità in bit al secondo (Mbps/Gbps) o byte al secondo (MB/s/GB/s). I byte sono predefiniti, come nei trasferimenti di file; schede e provider usano spesso i bit. La scelta viene salvata nel browser. Grafici, etichette e suggerimenti cambiano subito senza ricaricare."
   },
   "health": {
     "heading": "Monitoraggio integrità",
@@ -203,7 +203,7 @@
     "heading": "Rilevamento aggiornamenti LXC",
     "imageAlt": "Scheda di rilevamento aggiornamenti LXC con interruttore per cercare pacchetti in attesa nei container Debian, Ubuntu e Alpine attivi",
     "imageCaption": "Interruttore dell’analisi periodica con apt list --upgradable / apk list -u nei CT attivi. È acceso per impostazione predefinita. L’opzione in Notifiche → Servizi appare mentre il rilevamento è attivo.",
-    "intro": "L’interruttore tra Soglie e Notifiche controlla l’analisi LXC. Attivo, interroga i gestori di pacchetti dei CT in esecuzione; Hardware mostra i risultati e alimenta lxc_updates_available. Disattivo, ferma le chiamate pct exec e rimuove le voci LXC da managed_installs.json; /api/managed-installs smette di riportare lo stato senza attendere 24 ore.",
+    "intro": "L’interruttore tra Soglie e Notifiche controlla l’analisi LXC. Attivo, interroga i gestori di pacchetti dei CT in esecuzione; la scheda VM e LXC mostra i risultati e alimenta lxc_updates_available. Disattivo, ferma le chiamate pct exec e rimuove le voci LXC da managed_installs.json; /api/managed-installs smette di riportare lo stato senza attendere 24 ore.",
     "whatRunsTitle": "Cosa esegue l’analisi",
     "whatRunsIntro": "Per ogni CT in stato running con un gestore di pacchetti supportato:",
     "whatRunsItems": [
@@ -249,7 +249,7 @@
     "body1": "Qui si attivano le notifiche di ProxMenux Monitor e la riformulazione con IA. Attiva notifiche avvia l’invio in background, registra una destinazione webhook Proxmox VE per riceverne gli eventi e apre i canali: Telegram, Discord, email, Gotify e altri. La riformulazione con IA è in una sezione avanzata espandibile dello stesso pannello.",
     "body2": "Le opzioni sono spiegate in pagine dedicate: canali, controlli per evento, messaggi formattati, nome visualizzato, invio con deduplicazione, attesa, raggruppamento e ore silenziose, webhook PVE, provider IA e modalità delle istruzioni:",
     "items": [
-      "Notifiche: configurazione dei canali (Telegram, Discord, Gotify, email e password per app Gmail / Microsoft, ntfy, Slack, Teams e webhook generico), categorie di eventi, messaggi formattati, nome visualizzato, invio, webhook PVE, cronologia e API.",
+      "Notifiche: configurazione dei canali (Telegram, Discord, Gotify, email e password per app Gmail / Microsoft, Pushover e Apprise per ntfy, Slack, Teams e altri servizi), categorie di eventi, messaggi formattati, nome visualizzato, invio, webhook PVE, cronologia e API.",
       "Assistente IA: provider (OpenAI, Anthropic, Gemini, Groq, OpenRouter, Ollama), modelli, istruzioni predefinite o personalizzate, lingua di uscita, dettaglio per canale e suggerimenti IA."
     ]
   },
@@ -294,8 +294,8 @@
       },
       {
         "card": "Unità rete",
-        "endpoint": "/api/settings",
-        "source": "Salvato nella tabella SQLite delle impostazioni del pannello."
+        "endpoint": "localStorage",
+        "source": "Preferenza del browser proxmenux-network-unit."
       },
       {
         "card": "Durate di Health Monitor",
@@ -304,7 +304,7 @@
       },
       {
         "card": "Esclusioni di storage/interfaccia",
-        "endpoint": "/api/storage/exclusions, /api/network/exclusions",
+        "endpoint": "/api/health/storage-exclusions, /api/health/interface-exclusions",
         "source": "Tabelle SQLite excluded_storages e excluded_interfaces."
       },
       {
diff --git a/web/messages/it/docs/monitor/dashboard/storage.json b/web/messages/it/docs/monitor/dashboard/storage.json
index 2f6754d4..2f9f984d 100644
--- a/web/messages/it/docs/monitor/dashboard/storage.json
+++ b/web/messages/it/docs/monitor/dashboard/storage.json
@@ -10,7 +10,7 @@
   },
   "intro": {
     "title": "Supportato da tre fonti",
-    "body": "Gli storage Proxmox provengono da pvesm status, ZFS da zpool status, i dischi da lsblk + smartctl e i campi NVMe da nvme. La scheda si aggiorna circa ogni 60 secondi; aprire il dettaglio richiede una lettura SMART."
+    "body": "Gli storage Proxmox provengono da pvesm status, ZFS da zpool status, i dischi da lsblk + smartctl e i campi NVMe da nvme. La scheda si aggiorna circa ogni 30 secondi; aprire il dettaglio richiede una lettura SMART."
   },
   "thresholds": {
     "title": "Colori e soglie di stato applicate qui",
diff --git a/web/messages/it/docs/monitor/dashboard/system-logs.json b/web/messages/it/docs/monitor/dashboard/system-logs.json
index 056b6ced..7e54dc43 100644
--- a/web/messages/it/docs/monitor/dashboard/system-logs.json
+++ b/web/messages/it/docs/monitor/dashboard/system-logs.json
@@ -18,7 +18,7 @@
       "Voci totali — numero di record nel periodo del filtro attivo.",
       "Errori — numero di voci con gravità ≤ 3 (err / crit / alert / emerg).",
       "Avvisi — numero di voci con gravità 4 (warning).",
-      "Backup — numero di voci di attività vzdump / PBS nello stesso periodo."
+      "Backup: numero di archivi di backup negli storage di backup configurati."
     ]
   },
   "subtabs": {
@@ -26,10 +26,10 @@
     "logsTitle": "Log",
     "logsIntro": "Il journal di sistema, letto sul server con journalctl. Filtri disponibili nella barra degli strumenti:",
     "logsFilters": [
-      "Gravità — emerg / alert / crit / err / warning / notice / info / debug, o qualsiasi combinazione.",
-      "Time range — ultima 5 min / 15 min / 1 h / 6 h / 24 h / 7 d / personalizzato.",
-      "Ricerca libera — sottostringa o espressione regolare (journalctl --grep).",
-      "Filtro per unità — limita a una specifica unità systemd (pveproxy.service, nginx.service, …)."
+      "Livello: Tutti i livelli, Errore, Avviso o Informazioni.",
+      "Intervallo di tempo: 1 giorno / 3 giorni / 1 settimana / 2 settimane / 1 mese, oppure un numero personalizzato di giorni (fino a 90).",
+      "Ricerca libera: sottostringa senza distinzione tra maiuscole e minuscole su messaggio, servizio, PID, hostname e unità, applicata nel browser.",
+      "Filtro per servizio: limita a un servizio (il SYSLOG_IDENTIFIER del journal)."
     ],
     "logsRowsAfter": "Ogni riga mostra timestamp, tasso di gravità, unità sorgente e il messaggio. I messaggi lunghi crollano con un \"mostra di più\". L'azione Download raggruppa il filtro corrente in un unico file .txt tramite GET /api/logs/download — utile quando si desidera condividere una fetta di giornale con qualcuno.",
     "logDetailsModalTitle": "Finestra Dettagli del log",
@@ -50,10 +50,10 @@
     "maxLevelStoreTitle": "Journald MaxLevelStore",
     "maxLevelStoreBody": "Su una nuova installazione Proxmox, journald usa per impostazione predefinita MaxLevelStore=warning, scartando i messaggi info. Monitor lo rileva all'avvio e aggiunge una configurazione complementare (/etc/systemd/journald.conf.d/proxmenux-loglevel.conf) con soglia info, per conservare messaggi informativi e più gravi. Questo non include debug.",
     "backupsTitle": "Backup",
-    "backupsBody": "Cronologia delle attività Proxmox limitata ai backup. Una riga per attività (vzdump, trasferimenti PBS, Garbage Collect, Verify), con stato (OK / WARNINGS / ERROR), guest interessato, storage di origine, durata e UPID. Fai clic su una riga per caricare il log completo tramite GET /api/task-log/<upid>: gli stessi dati esposti da Proxmox in Datacenter → Tasks, limitati ai backup.",
+    "backupsBody": "Gli archivi di backup su ogni storage che supporta i backup (dir, NFS, CIFS, PBS), letti con pvesh get /nodes/localhost/storage/<id>/content. Le schede in alto mostrano il numero di VM e LXC e la dimensione totale; sotto, una riga per archivio con tipo, storage, dimensione e data. Il ripristino si esegue nell’interfaccia web di Proxmox.",
     "notificationsTitle": "Notifiche",
-    "notificationsBody1": "Tutte le notifiche inviate da Monitor: Telegram, Discord, e-mail, Gotify, ntfy, Slack, Teams e webhook. Ogni riga mostra data e ora, canale, tipo di evento, gravità, titolo e corpo generati e, se l'IA è attiva, un comando per confrontare la sua riscrittura con l'originale.",
-    "notificationsBody2": "Usa questa scheda per verificare che un canale recapiti le notifiche e confrontare la riscrittura AI con il modello di base. I canali si configurano nella pagina dettagliata Notifiche."
+    "notificationsBody1": "Attività di notifica di Proxmox: righe del journal dei servizi Proxmox che citano notifiche, e-mail, webhook o avvisi, le destinazioni in /etc/pve/notifications.cfg e le attività di backup dall’elenco delle attività del cluster. Le righe delle attività di backup permettono di scaricare il log completo. La cronologia di ciò che Monitor stesso ha inviato si trova in Impostazioni → Notifiche → Cronologia delle notifiche.",
+    "notificationsBody2": "Usa questa scheda per verificare cosa fa il sistema di notifiche di Proxmox. I canali di Monitor si configurano nella pagina dettagliata Notifiche."
   },
   "dataCollected": {
     "heading": "Come vengono raccolti i dati",
@@ -64,7 +64,7 @@
       {
         "subtab": "Registri (filtro in tempo reale)",
         "endpoint": "/api/logs",
-        "source": "journalctl --output json --since <range> con filtri per gravità, unità e ricerca applicati sul server."
+        "source": "La scheda richiede /api/logs?since_days=N; i filtri per livello, servizio e testo vengono applicati nel browser. L’API accetta anche priority e service, che la scheda non usa ancora."
       },
       {
         "subtab": "Scarica",
@@ -74,17 +74,17 @@
       {
         "subtab": "Backup",
         "endpoint": "/api/backups",
-        "source": "Cronologia delle attività PVE filtrata per vzdump, trasferimenti PBS, Garbage Collect e Verify."
+        "source": "Archivi di backup elencati da ogni storage di backup con pvesh get /nodes/localhost/storage/<id>/content."
       },
       {
-        "subtab": "Dettagli di un'attività di backup",
+        "subtab": "Log delle attività di backup (Notifiche)",
         "endpoint": "/api/task-log/<upid>",
         "source": "Log completo dell'attività in testo semplice, letto da /var/log/pve/tasks/<index>/<upid>."
       },
       {
-        "subtab": "Cronologia delle notifiche",
-        "endpoint": "/api/notifications/history",
-        "source": "Tabella SQLite notification_history alimentata dal ciclo di invio."
+        "subtab": "Notifiche",
+        "endpoint": "/api/notifications",
+        "source": "journalctl --grep sulle unità Proxmox, /etc/pve/notifications.cfg e le attività di backup da pvesh get /cluster/tasks."
       }
     ],
     "apiIntro": "Il filtro in tempo reale e i download sono accessibili anche tramite API:",
diff --git a/web/messages/it/docs/monitor/dashboard/system-overview.json b/web/messages/it/docs/monitor/dashboard/system-overview.json
index a2ed2e0d..974c64fc 100644
--- a/web/messages/it/docs/monitor/dashboard/system-overview.json
+++ b/web/messages/it/docs/monitor/dashboard/system-overview.json
@@ -46,7 +46,7 @@
     "thresholdsIntro": "Anelli, barre e minigrafici usano la stessa scala:  verde sotto Avviso,  ambra tra Avviso e Critico,  rosso da Critico in poi. Valori predefiniti consigliati inclusi in ProxMenux:",
     "thresholdsItems": [
       "Utilizzo CPU — avviso 85 %, critico 95 %.",
-      "Memoria — avviso 85 %, critico 95 %. Anche lo swap attiva Critico al 5 % di utilizzo; un host Proxmox sano dovrebbe usarlo raramente.",
+      "Memoria: avviso 85 %, critico 95 %. Lo swap attiva Critico solo quando l’uso dello swap supera l’80 % e la RAM disponibile è inferiore al 15 %.",
       "Temperatura CPU — avviso 80 °C, critico 90 °C."
     ],
     "thresholdsOutro": "Ogni valore è configurabile per host. Impostazioni → Soglie del monitor di integrità centralizza le impostazioni e spiega come regolarle.",
@@ -58,7 +58,7 @@
     "intro": "Le schede Utilizzo CPU e Memoria aprono un elenco ordinabile dei 25 processi principali: per utilizzo CPU dalla scheda CPU e per memoria residente dalla scheda Memoria.",
     "listTitle": "La finestra di dialogo elenco",
     "listItems": [
-      "Aggiornamento automatico — l'elenco si aggiorna ogni 5 secondi finché la finestra è aperta e interrompe le richieste alla chiusura.",
+      "Aggiornamento automatico — l'elenco si aggiorna ogni 3 secondi finché la finestra è aperta e interrompe le richieste alla chiusura.",
       "Filtro — la ricerca limita l'elenco per comando, utente o PID.",
       "Barra nella riga — la colonna della metrica principale disegna una piccola barra scalata al massimo valore nell'elenco filtrato. La graduatoria resta evidente anche se nessun processo si avvicina al 100 %.",
       "Disposizione mobile — sotto 640 px le colonne PID e Utente si nascondono, così Comando, CPU % e Memoria restano visibili senza scorrimento orizzontale."
@@ -79,7 +79,7 @@
   },
   "middle": {
     "heading": "Centro: grafici delle metriche del nodo",
-    "body1": "Sotto le schede, NodeMetricsCharts mostra grafici storici di CPU, memoria e I/O disco dal database RRD di Proxmox tramite /api/node/metrics. Il selettore offre 1 ora / 24 ore / 7 giorni / 30 giorni / 1 anno. La risoluzione diminuisce con periodi più ampi per mantenere fluido il grafico.",
+    "body1": "Sotto le schede, NodeMetricsCharts mostra grafici storici di CPU, memoria e I/O disco dal database RRD di Proxmox tramite /api/node/metrics. Il selettore offre 1 ora / 24 ore / 7 giorni / 30 giorni. La risoluzione diminuisce con periodi più ampi per mantenere fluido il grafico.",
     "body2": "Sono gli stessi grafici dell'interfaccia web Proxmox per un nodo, integrati nel tema scuro di Monitor e allineati agli altri pannelli."
   },
   "bottom": {
diff --git a/web/messages/it/docs/monitor/dashboard/vms-lxcs.json b/web/messages/it/docs/monitor/dashboard/vms-lxcs.json
index 4cd27750..23764231 100644
--- a/web/messages/it/docs/monitor/dashboard/vms-lxcs.json
+++ b/web/messages/it/docs/monitor/dashboard/vms-lxcs.json
@@ -117,7 +117,7 @@
       "Destinazione — storage (PBS-Cloud, PBS-Local, NFS-Backup, …), colorato secondo lo stato.",
       "Dimensione — dimensione finale del backup su disco."
     ],
-    "backupsOutro": "+ Crea backup avvia un backup sullo storage indicato come “Backup target” in Proxmox. Il ripristino si esegue nell'interfaccia web Proxmox; Monitor mostra i backup recenti del guest e permette di crearne.",
+    "backupsOutro": "Il pulsante + Crea backup in alto a destra apre una finestra per scegliere storage, modalità (snapshot / suspend / stop), protezione e note prima di avviare un vzdump. Il ripristino si esegue nell'interfaccia web Proxmox; Monitor mostra la vista “questo guest ha un backup recente?”, non il flusso di recupero.",
     "firewallTitle": "Scheda 7 — Firewall",
     "firewallIntro": "Legge il log firewall Proxmox del guest direttamente dall'host, senza servizi aggiuntivi né richieste periodiche. La scheda è sempre presente; il contenuto dipende dalla attivazione del firewall e dalla registrazione dei pacchetti da parte di una regola.",
     "firewallItems": [
diff --git a/web/messages/it/docs/monitor/health-monitor.json b/web/messages/it/docs/monitor/health-monitor.json
index c80eaf14..44eed2af 100644
--- a/web/messages/it/docs/monitor/health-monitor.json
+++ b/web/messages/it/docs/monitor/health-monitor.json
@@ -153,8 +153,8 @@
     "pillTitle": "L’indicatore riflette la categoria più grave",
     "pillBody": "Il colore dell’intestazione riflette la gravità massima: CRITICAL → rosso, altrimenti WARNING → giallo, altrimenti INFO → blu, altrimenti verde. La stessa logica controlla il punto della favicon e il badge PWA.",
     "updateNowTitle": "Pulsante Aggiorna ora nell’intestazione della panoramica",
-    "updateNowBody1": "Se Aggiornamenti di sistema segnala aggiornamenti in attesa, appare Aggiorna ora accanto all’indicatore. La finestra esegue la procedura di ProxMenux → Settings post-install Proxmox → Proxmox System Update: pulizia dei repository, apt full-upgrade tramite update-pve-safe.sh, ricompilazione DKMS dei driver gestiti con un nuovo kernel, autoremove/autoclean e richiesta di riavvio se cambia il kernel. La finestra mostra l’avanzamento in tempo reale.",
-    "updateNowBody2": "Al termine, chiudere la finestra richiama GET /api/health/details?refresh=1 e invalida la cache dell’endpoint. Aggiornamenti in attesa e gravità vengono ricalcolati dal nuovo stato dei pacchetti: il badge scompare senza attendere il ciclo successivo. Se l’host è già aggiornato, il pulsante resta nascosto."
+    "updateNowBody1": "Se Aggiornamenti di sistema segnala aggiornamenti in attesa, appare Aggiorna ora accanto all’indicatore. La finestra esegue la procedura di ProxMenux → Utilità e strumenti → Aggiornamento sistema Proxmox: pulizia dei repository, apt full-upgrade tramite update-pve-safe.sh, ricompilazione DKMS dei driver gestiti con un nuovo kernel, autoremove/autoclean e richiesta di riavvio se cambia il kernel. La finestra mostra l’avanzamento in tempo reale.",
+    "updateNowBody2": "Al termine, chiudere la finestra richiama GET /api/health/full?refresh=1 e invalida la cache dell’endpoint. Aggiornamenti in attesa e gravità vengono ricalcolati dal nuovo stato dei pacchetti: il badge scompare senza attendere il ciclo successivo. Se l’host è già aggiornato, il pulsante resta nascosto."
   },
   "dismiss": {
     "heading": "Silenziare gli avvisi e scegliere la durata",
diff --git a/web/messages/it/docs/monitor/index.json b/web/messages/it/docs/monitor/index.json
index e26ca62a..8d53c4fe 100644
--- a/web/messages/it/docs/monitor/index.json
+++ b/web/messages/it/docs/monitor/index.json
@@ -113,13 +113,13 @@
   "api": {
     "heading": "REST API e integrazioni",
     "intro": "Gli endpoint REST espongono dati di monitoraggio JSON via HTTP/HTTPS per Homepage, Home Assistant, Uptime Kuma e script. Grafana può usare l’esportatore testuale Prometheus separato su /api/prometheus. Le richieste possono usare curl.",
-    "tokens": "I token API a lunga durata (365 giorni) vengono generati in Impostazioni → Token di accesso API o tramite POST /api/auth/generate-api-token.",
+    "tokens": "I token API a lunga durata (365 giorni) vengono generati in Amministratore → Sicurezza → Token API o tramite POST /api/auth/generate-api-token.",
     "bearer": "Invia i token con Authorization: Bearer …. Anche /api/health richiede l’intestazione Bearer, quindi i controlli di disponibilità necessitano di un token read_only. Sotto /api/auth/* ci sono sia rotte pubbliche di configurazione/login/stato sia rotte protette per account e token; non sono tutte anonime.",
     "catalog": "Catalogo degli endpoint, rotazione dei token e sicurezza sono nel Riferimento API; esempi per Homepage, Home Assistant, Grafana, Uptime Kuma e cURL sono in Integrazioni."
   },
   "serviceControl": {
     "heading": "Controllo del servizio",
-    "intro": "Giorno per giorno, il Monitor è gestito esattamente come qualsiasi altro servizio systemd. Viene inoltre esposto come due voci all'interno della TUI ProxMenux in Impostazioni:",
+    "intro": "Giorno per giorno, il Monitor è gestito esattamente come qualsiasi altro servizio systemd. Viene inoltre esposto come tre voci all'interno della TUI ProxMenux in Impostazioni:",
     "codeComment": "# Controllo manuale",
     "footer": "Vedi Impostazioni → ProxMenux Monitor per l'attivazione/disattivazione del menu e il flusso di verifica dello stato."
   },
diff --git a/web/messages/it/docs/monitor/integrations.json b/web/messages/it/docs/monitor/integrations.json
index 563e98f9..359d2e80 100644
--- a/web/messages/it/docs/monitor/integrations.json
+++ b/web/messages/it/docs/monitor/integrations.json
@@ -20,13 +20,13 @@
     "heading": "Autenticazione: l'unica cosa che ogni ricetta ha bisogno",
     "intro": "La maggior parte degli endpoint utilizzati da queste integrazioni sono autenticati. Hai due modi per soddisfare questo requisito.",
     "optAtitle": "Opzione A — Token API (consigliato per le integrazioni)",
-    "optAbody1": "Apri Impostazioni → Sicurezza → Token API. Premi Genera token, assegna un nome come homepage, home-assistant o prometheus e copialo: appare una sola volta. Scade dopo un anno per impostazione predefinita ed è revocabile singolarmente. Usalo per i client esterni al browser.",
+    "optAbody1": "Apri la dashboard e vai in Amministratore → Sicurezza → Token API. Premi Genera nuovo token, assegna un nome come homepage, home-assistant o prometheus, inserisci la password (e il codice 2FA se attivo), premi Genera token e copialo: appare una sola volta. Scade dopo un anno per impostazione predefinita ed è revocabile singolarmente. Usalo per i client esterni al browser.",
     "optAbody2": "Da quel punto ogni richiesta è solo:",
     "optBtitle": "Opzione B — flusso di login (username + password)",
     "optBbody": "Utile per script che si autenticano come un utente. Il token restituito dura poco; per la maggior parte delle integrazioni scegli l’opzione A.",
     "outro": "Il campo TOTP è obbligatorio solo se sull’account è attiva la 2FA. Rotazione e revoca dei token, criteri delle password e registro di controllo sono descritti in Accesso e autenticazione.",
     "httpsTitle": "Utilizzando HTTPS invece di HTTP",
-    "httpsIntro": "Gli esempi usano http:// per brevità. Se hai attivato TLS sul Monitor (Impostazioni → Sicurezza → SSL/HTTPS), sostituisci http:// con https:// in ogni URL. Due note per alcuni strumenti:",
+    "httpsIntro": "Gli esempi usano http:// per brevità. Se hai attivato TLS sul Monitor (Amministratore → Sicurezza → SSL/HTTPS), sostituisci http:// con https:// in ogni URL. Due note per alcuni strumenti:",
     "httpsItems": [
       "Certificati autofirmati. L’integrazione Home Assistant rest: verifica TLS per impostazione predefinita. Per un certificato autofirmato del Monitor aggiungi verify_ssl: false a ogni blocco REST accanto a scan_interval:, oppure importa la CA del Monitor tra i certificati attendibili. Lo stesso vale per altri strumenti che verificano i certificati.",
       "Prometheus include già scheme: https nell’esempio sotto. Attiva o mantieni https se TLS è attivo sul Monitor."
@@ -43,7 +43,7 @@
     "basicTitle": "widget di base — nessuna autenticazione",
     "basicIntro": "Utilizzare questo quando ProxMenux Monitor è su una rete di fiducia e non hai ancora abilitato l'autenticazione sul lato Monitor. La voce services.yaml più semplice:",
     "authedTitle": "widget autenticato",
-    "authedIntro": "Generare un token API in Impostazioni → Sicurezza → Token API sul Monitor, copiarlo e incollarlo nell'intestazione Authorization di seguito — sostituire il token di esempio mostrato dopo Bearer con quello appena copiato:",
+    "authedIntro": "Generare un token API in Amministratore → Sicurezza → Token API sul Monitor, copiarlo e incollarlo nell'intestazione Authorization di seguito — sostituire il token di esempio mostrato dopo Bearer con quello appena copiato:",
     "authedOutro": "Riavvia Homepage per visualizzare i valori aggiornati. Riutilizza lo stesso token nei widget che interrogano la stessa istanza del Monitor.",
     "multiTitle": "Configurazione multi-widget — sistema, archiviazione, rete",
     "multiIntro": "Per più dettagli crea tre schede con endpoint diversi: sistema, archiviazione e rete. Usa lo stesso token per questa istanza del Monitor.",
diff --git a/web/messages/it/docs/monitor/notifications.json b/web/messages/it/docs/monitor/notifications.json
index 7173a53f..ca6da91d 100644
--- a/web/messages/it/docs/monitor/notifications.json
+++ b/web/messages/it/docs/monitor/notifications.json
@@ -314,7 +314,7 @@
   },
   "aiRewrite": {
     "heading": "Riscrittura IA facoltativa",
-    "body1": "Gli eventi idonei possono essere riscritti da un LLM. I risultati backup (backup_complete e backup_fail) sono esclusi. IA è disattivata inizialmente e, se attiva, lavora nel thread di invio. Se la chiamata fallisce o scade, usa il testo originale del modello.",
+    "body1": "Gli eventi idonei possono essere riscritti da un LLM. I risultati backup (backup_complete e backup_fail) non vengono riscritti; con i suggerimenti IA attivi, l’IA aggiunge solo una spiegazione quando un backup segnala errori. IA è disattivata inizialmente e, se attiva, lavora nel thread di invio. Se la chiamata fallisce o scade, usa il testo originale del modello.",
     "body2": "Sei fornitori: OpenAI, Anthropic, Google Gemini, Groq, OpenRouter e Ollama. Ogni canale sceglie dettaglio (brief, standard, detailed), lingua, modalità del prompt (default/custom) e prompt personalizzato. Vedi Assistente IA per configurazione ed esempi.",
     "privacyTitle": "Nota sulla privacy",
     "privacyBody": "Le richieste IA possono includere titolo, gravità, testo, prompt e contesto: nomi host, IP, utenti, errori e righe del registro. Vanno all’endpoint scelto. Ollama resta locale solo con server e modello entrambi locali; server remoti o modelli cloud inviano dati fuori dall’host. Per mantenerli nella rete, disattiva la riscrittura o usa endpoint e modello locali controllati."
diff --git a/web/messages/it/docs/security/ssl-letsencrypt.json b/web/messages/it/docs/security/ssl-letsencrypt.json
index 19f7938b..9ed51fcd 100644
--- a/web/messages/it/docs/security/ssl-letsencrypt.json
+++ b/web/messages/it/docs/security/ssl-letsencrypt.json
@@ -12,13 +12,13 @@
   },
   "intro": {
     "title": "Che cosa questa pagina copre",
-    "body": "Come cambiare il Monitor da HTTP a HTTPS nel pannello Impostazioni → Sicurezza, ciò che ogni fonte di certificato indica effettivamente sul disco, come l'opzione Proxmox si comporta quando l'host ha un cert Let's Encrypt e come impostare percorsi personalizzati se si gestisce certs al di fuori di Proxmox."
+    "body": "Come cambiare il Monitor da HTTP a HTTPS nel pannello Amministratore → Sicurezza, ciò che ogni fonte di certificato indica effettivamente sul disco, come l'opzione Proxmox si comporta quando l'host ha un cert Let's Encrypt e come impostare percorsi personalizzati se si gestisce certs al di fuori di Proxmox."
   },
   "wheresetting": {
     "heading": "Dove si trova l'impostazione",
-    "body": "Apri Monitor e vai in Impostazioni → Sicurezza → HTTPS / SSL. Il pannello mostra HTTP o HTTPS e, se HTTPS non è attivo, le fonti di certificato rilevate.",
-    "imageAlt": "Impostazioni → Sicurezza → HTTPS / Pannello SSL nel Monitor che mostra lo stato HTTPS-off, il certificato Proxmox auto-determinato (soggetto, emittente, scadenza) e l'opzione Usa Certificato personalizzato",
-    "caption": "Pannello Impostazioni → Sicurezza → HTTPS / SSL prima dell'attivazione. Monitor mostra il certificato usato da Proxmox e permette di sceglierlo con un clic."
+    "body": "Apri Monitor e vai in Amministratore → Sicurezza → SSL/HTTPS. Il pannello mostra HTTP o HTTPS e, se HTTPS non è attivo, le fonti di certificato rilevate.",
+    "imageAlt": "Pannello Amministratore → Sicurezza → SSL/HTTPS nel Monitor che mostra lo stato HTTPS-off, il certificato Proxmox auto-determinato (soggetto, emittente, scadenza) e l'opzione Usa Certificato personalizzato",
+    "caption": "Pannello Amministratore → Sicurezza → SSL/HTTPS prima dell'attivazione. Monitor mostra il certificato usato da Proxmox e permette di sceglierlo con un clic."
   },
   "twoways": {
     "heading": "Due modi per abilitare HTTPS",
@@ -117,7 +117,7 @@
   },
   "switchToHttps": {
     "heading": "Passare il Monitor a HTTPS",
-    "bodyRich": "Quando /etc/pve/local/pveproxy-ssl.pem è firmato da Let's Encrypt, apri Impostazioni → Sicurezza → HTTPS / SSL, verifica che l'emittente sia Let's Encrypt e non la CA locale Proxmox, poi fai clic su Usa certificato Proxmox. Monitor si riavvia e il caricamento successivo usa HTTPS su 8008 con una catena pubblicamente attendibile. I rinnovi ACME successivi sono convalidati e selezionati alla prossima nuova connessione TLS, senza controlli periodici né riavvio al rinnovo. Aggiorna certificato rimane disponibile in Sicurezza per diagnosi e recupero."
+    "bodyRich": "Quando /etc/pve/local/pveproxy-ssl.pem è firmato da Let's Encrypt, apri Amministratore → Sicurezza → SSL/HTTPS, verifica che l'emittente sia Let's Encrypt e non la CA locale Proxmox, poi fai clic su Usa certificato Proxmox. Monitor si riavvia e il caricamento successivo usa HTTPS su 8008 con una catena pubblicamente attendibile. I rinnovi ACME successivi sono convalidati e selezionati alla prossima nuova connessione TLS, senza controlli periodici né riavvio al rinnovo. Aggiorna certificato rimane disponibile in Sicurezza per diagnosi e recupero."
   },
   "custom": {
     "heading": "Certificato personalizzato — quando utilizzarlo",
diff --git a/web/messages/it/docs/settings/beta-program.json b/web/messages/it/docs/settings/beta-program.json
index 284cdcd0..c099da5b 100644
--- a/web/messages/it/docs/settings/beta-program.json
+++ b/web/messages/it/docs/settings/beta-program.json
@@ -51,7 +51,7 @@
     "intro": "Entrambe le direzioni passano attraverso lo stesso programma di installazione: non flussi di disinstallazione e reinstallazione speciali. Ciascuna direzione esegue semplicemente il programma di installazione del canale, progettato per essere idempotente e sicuro da eseguire nuovamente su un'installazione esistente. Conseguenze pratiche:",
     "items": [
       "La configurazione esistente, ProxMenux Monitor login (auth.json), canali di notifica, registro post-installazione, soglie personalizzate — nessuno di questi sono toccati. L'interruttore di canale scambia solo l'albero di script e il binario AppImage.",
-      "Un Monitor attivo prima del cambio rimane attivo dopo; uno disattivato rimane disattivato.",
+      "Il programma di installazione riavvia il servizio Monitor durante il cambio, quindi un Monitor che avevi disattivato torna in esecuzione. Disattivalo di nuovo dalle impostazioni se necessario.",
       "Il file auth.json, i token API e il segreto JWT di ProxMenux Monitor vengono conservati al cambio di canale. Le sessioni non vengono disconnesse.",
       "Puoi cambiare canale tutte le volte che vuoi: la finestra accetta ogni modifica ed esegue nuovamente il programma di installazione. Non è necessario riavviare Proxmox.",
       "Il cambio di canale sostituisce gli script di ProxMenux e non può essere eseguito nel terminale del browser di Monitor: il PTY verrebbe chiuso durante l'aggiornamento, bloccando la finestra. In quel terminale appare invece un messaggio con il comando ufficiale di una riga per il canale scelto. Eseguilo da SSH o dalla console dell'host Proxmox."
diff --git a/web/messages/it/docs/settings/index.json b/web/messages/it/docs/settings/index.json
index cf2673ec..9a888f5a 100644
--- a/web/messages/it/docs/settings/index.json
+++ b/web/messages/it/docs/settings/index.json
@@ -60,6 +60,12 @@
         "description": "Passa tra Stable (main) e Beta (develop) in entrambe le direzioni da una sola finestra. Esegue il programma di installazione ufficiale del canale scelto; è idempotente e sempre visibile.",
         "badge": "Sempre disponibile"
       },
+      {
+        "icon": "Languages",
+        "href": "/docs/settings/change-language",
+        "title": "Cambia lingua",
+        "description": "Passa l’interfaccia di ProxMenux tra otto lingue. La scelta viene salvata in config.json e il menu si ricarica nella nuova lingua."
+      },
       {
         "icon": "Info",
         "href": "/docs/settings/show-version-information",
@@ -70,7 +76,7 @@
         "icon": "Trash2",
         "href": "/docs/settings/uninstall-proxmenux",
         "title": "Disinstalla ProxMenux",
-        "description": "Rimuovere ProxMenux e (opzionalmente) le sue dipendenze. Inoltre ripristina i backup /root/.bashrc e /etc/motd creati al momento dell'installazione."
+        "description": "Rimuove ProxMenux e (facoltativamente) le sue dipendenze. Ripristina anche i backup di /root/.bashrc e /etc/motd creati da Post-Install."
       }
     ]
   },
diff --git a/web/messages/it/docs/settings/proxmenux-monitor.json b/web/messages/it/docs/settings/proxmenux-monitor.json
index 8ae9b6ea..4d33fd04 100644
--- a/web/messages/it/docs/settings/proxmenux-monitor.json
+++ b/web/messages/it/docs/settings/proxmenux-monitor.json
@@ -33,7 +33,7 @@
   },
   "warnConditional": {
     "title": "Opzioni delle impostazioni visibili solo quando Monitor è installato",
-    "body": "Le due voci del menu descritte di seguito appaiono solo in Impostazioni se proxmenux-monitor.service è registrato con systemd. Se non li vedi, il Monitor non è stato installato (è opzionale durante l'installazione ProxMenux). Per aggiungerlo in seguito, reinstallare ProxMenux con l'ultima versione e selezionare l'opzione Monitor."
+    "body": "Le tre voci del menu descritte di seguito appaiono solo in Impostazioni se proxmenux-monitor.service è registrato con systemd. Il programma di installazione lo configura sempre, quindi se non le vedi il passaggio del Monitor è fallito durante l'installazione. Esegui di nuovo il programma di installazione ufficiale per riprovare."
   },
   "toggle": {
     "heading": "Attivare / disattivare",
@@ -92,8 +92,8 @@
   },
   "troubleshoot": {
     "heading": "Risoluzione dei problemi",
-    "missingTitle": "Entrambe le opzioni di menu mancano",
-    "missingBody": "Il Monitor non è registrato con systemd. Confermare con systemctl list-unit-files | grep proxmenux-monitor — se non c'è uscita, reinstallare ProxMenux dal programma di installazione ufficiale con l'ultima versione e selezionare l'opzione Monitor durante l'installazione.",
+    "missingTitle": "Le opzioni di menu del Monitor mancano",
+    "missingBody": "Il Monitor non è registrato con systemd. Confermare con systemctl list-unit-files | grep proxmenux-monitor — se non c'è uscita, eseguire di nuovo il programma di installazione ufficiale; installa il Monitor a ogni esecuzione.",
     "unreachableTitle": "Servizio attivato ma l'URL non è raggiungibile",
     "unreachableBody": "L'uscita di stato delle impostazioni ti dà l'IP. Conferma:",
     "unreachableCmd": "ss -tlnp | grep 8008                # is anything listening?\njournalctl -u proxmenux-monitor -n 100 --no-pager  # recent errors\ncurl -sI http://localhost:8008      # local reachability",
diff --git a/web/messages/it/docs/settings/uninstall-proxmenux.json b/web/messages/it/docs/settings/uninstall-proxmenux.json
index 6a260ee5..5f73d96c 100644
--- a/web/messages/it/docs/settings/uninstall-proxmenux.json
+++ b/web/messages/it/docs/settings/uninstall-proxmenux.json
@@ -12,7 +12,7 @@
   },
   "scopeWarn": {
     "title": "Riguarda solo l'installazione di ProxMenux, non VM / CT / archiviazione",
-    "body": "La disinstallazione rimuove gli script ProxMenux, il lanciatore del menu, il servizio Monitor facoltativo e i backup dei file di sistema creati da ProxMenux durante l'installazione. Non modifica VM, container, archiviazione, configurazione di rete, Fail2Ban, driver NVIDIA o qualsiasi altro elemento installato tramite ProxMenux. Questi rimangono invariati."
+    "body": "La disinstallazione di ProxMenux rimuove gli script ProxMenux, il lanciatore del menu, il servizio Monitor e ripristina i backup di /root/.bashrc e /etc/motd creati da Post-Install, se esistono. Non modifica VM, container, archiviazione, configurazione di rete, Fail2Ban, driver NVIDIA o qualsiasi altro elemento installato tramite ProxMenux. Questi rimangono invariati."
   },
   "flow": {
     "heading": "Procedura di disinstallazione",
diff --git a/web/messages/it/docs/utils/export-vm.json b/web/messages/it/docs/utils/export-vm.json
index 4bc97c3a..e15d3853 100644
--- a/web/messages/it/docs/utils/export-vm.json
+++ b/web/messages/it/docs/utils/export-vm.json
@@ -59,8 +59,8 @@
   },
   "package": {
     "heading": "Cosa finisce nel pacchetto",
-    "intro": "Per un OVA / OVF chiamato myvm-100-2026-04-26_18-30-15:",
-    "code": "# OVA mode (single .ova file is a TAR of):\nmyvm-100-2026-04-26_18-30-15.ovf      # XML descriptor (DMTF OVF 1.0 + RASD + VSSD)\nmyvm-100-2026-04-26_18-30-15.mf       # SHA1 manifest\nmyvm-100-2026-04-26_18-30-15-disk0.vmdk\nmyvm-100-2026-04-26_18-30-15-disk1.vmdk\n...\n\n# OVF mode (a directory):\nmyvm-100-2026-04-26_18-30-15-ovf/\n├── myvm-100-2026-04-26_18-30-15.ovf\n├── myvm-100-2026-04-26_18-30-15.mf\n├── myvm-100-2026-04-26_18-30-15-disk0.vmdk\n└── myvm-100-2026-04-26_18-30-15-disk1.vmdk"
+    "intro": "Per un OVA / OVF chiamato myvm-100-20260426_183015:",
+    "code": "# OVA mode (single .ova file is a TAR of):\nmyvm-100-20260426_183015.ovf      # XML descriptor (DMTF OVF 1.0 + RASD + VSSD)\nmyvm-100-20260426_183015.mf       # SHA1 manifest\nmyvm-100-20260426_183015-disk1.vmdk\nmyvm-100-20260426_183015-disk2.vmdk\n...\n\n# OVF mode (a directory):\nmyvm-100-20260426_183015-ovf/\n├── myvm-100-20260426_183015.ovf\n├── myvm-100-20260426_183015.mf\n├── myvm-100-20260426_183015-disk1.vmdk\n└── myvm-100-20260426_183015-disk2.vmdk"
   },
   "exported": {
     "heading": "Cosa viene esportato",
@@ -90,7 +90,7 @@
   "manifest": {
     "heading": "Manifesto SHA1",
     "intro": "Ogni pacchetto include un file .mf con gli hash SHA1 del descrittore OVF e di ogni VMDK:",
-    "code": "SHA1(myvm-100-2026-04-26_18-30-15.ovf)= 3a7bd3e2360a3d29eea436fcfb7e44c735d117c9\nSHA1(myvm-100-2026-04-26_18-30-15-disk0.vmdk)= b7e23ec29af22b0b4e41da31e868d57226121c84\nSHA1(myvm-100-2026-04-26_18-30-15-disk1.vmdk)= a87ff679a2f3e71d9181a67b7542122c",
+    "code": "SHA1(myvm-100-20260426_183015.ovf)= 3a7bd3e2360a3d29eea436fcfb7e44c735d117c9\nSHA1(myvm-100-20260426_183015-disk1.vmdk)= b7e23ec29af22b0b4e41da31e868d57226121c84\nSHA1(myvm-100-20260426_183015-disk2.vmdk)= a87ff679a2f3e71d9181a67b7542122c",
     "outro": "È facoltativo nella specifica OVF e molti programmi lo ignorano, ma serve a verificare che il pacchetto sia stato trasferito integro. Verificalo manualmente con sha1sum -c file.mf."
   },
   "import": {
diff --git a/web/messages/it/docs/utils/index.json b/web/messages/it/docs/utils/index.json
index def93677..5f31a185 100644
--- a/web/messages/it/docs/utils/index.json
+++ b/web/messages/it/docs/utils/index.json
@@ -38,7 +38,7 @@
       "body": "Mantieni l’host in buone condizioni con strumenti CLI, aggiornamenti regolari e il passaggio di versione principale quando necessario.",
       "bullets": [
         "Installatore di utilità di sistema (25 strumenti CLI)",
-        "Aggiornamento del sistema Proxmox (apt + dist-upgrade)",
+        "Aggiornamento del sistema Proxmox (apt + full-upgrade)",
         "Passaggio di versione principale PVE 8 → PVE 9"
       ]
     },
@@ -58,11 +58,11 @@
   },
   "isoSection": {
     "heading": "Creazione ISO",
-    "body": "Crea una nuova ISO di installazione di Windows aggiornata direttamente sull’host Proxmox. Lo script automatizza l’intero processo UUP Dump: installazione delle dipendenze (aria2c, cabextract, wimlib-imagex, …), download parallelo dei file di aggiornamento tramite aria2 e conversione in una singola ISO avviabile. La ISO viene salvata in /var/lib/vz/template/iso, pronta da collegare a una VM.",
+    "body": "Crea una nuova ISO di installazione di Windows aggiornata direttamente sull’host Proxmox. Lo script automatizza l’intero processo UUP Dump: installazione delle dipendenze (aria2c, cabextract, wimlib-imagex, …), download parallelo dei file di aggiornamento tramite aria2 e conversione in una singola ISO avviabile. La ISO viene salvata nello storage ISO di Proxmox rilevato (alternativa /var/lib/vz/template/iso), pronta da collegare a una VM.",
     "options": [
       {
         "title": "Creatore di ISO UUP Dump",
-        "description": "Crea una ISO di installazione di Windows aggiornata da un link condiviso di UUP Dump. Dipendenze, download e conversione sono completamente automatizzati; la ISO risultante viene salvata in /var/lib/vz/template/iso, pronta per creare una VM.",
+        "description": "Crea una ISO di installazione di Windows aggiornata da un link condiviso di UUP Dump. Dipendenze, download e conversione sono completamente automatizzati; la ISO risultante viene salvata nello storage ISO di Proxmox rilevato, pronta per creare una VM.",
         "href": "/docs/utils/UUp-Dump-ISO-Creator"
       }
     ]
@@ -78,7 +78,7 @@
       },
       {
         "title": "Aggiornamento sistema Proxmox",
-        "description": "Controllo dei repository + apt update + apt dist-upgrade, con conferma prima del riavvio. Rileva la versione principale di PVE (8 o 9), mantiene i repository configurati e chiede conferma prima di passare un host senza abbonamento al repository no-subscription, quindi esegue autoremove.",
+        "description": "Controllo dei repository + apt update + apt full-upgrade, con conferma prima del riavvio. Rileva la versione principale di PVE (8 o 9), mantiene i repository configurati e chiede conferma prima di passare un host senza abbonamento al repository no-subscription, quindi esegue autoremove.",
         "href": "/docs/utils/system-update"
       },
       {
diff --git a/web/messages/it/docs/utils/system-update.json b/web/messages/it/docs/utils/system-update.json
index 993dfb6b..2e7e5a3f 100644
--- a/web/messages/it/docs/utils/system-update.json
+++ b/web/messages/it/docs/utils/system-update.json
@@ -128,7 +128,7 @@
         "body": "Il repository Enterprise è abilitato, ma l’host non ha un abbonamento attivo. Prima di apt-get update, lo script propone il passaggio a no-subscription. Se hai rifiutato, ripeti l’aggiornamento e accetta, oppure disabilita Enterprise in Node → Updates → Repositories e abilita no-subscription."
       },
       {
-        "title": "dist-upgrade si blocca su \"Configuring grub-pc\"",
+        "title": "full-upgrade si blocca su \"Configuring grub-pc\"",
         "body": "Se compare una richiesta interattiva di GRUB, chiede i dispositivi su cui installare il boot loader. L’opzione --force-confold riguarda i file di configurazione, non questa selezione. Usa Tab + Space per scegliere i dischi di avvio appropriati, poi OK. Puoi configurare la scelta in anticipo con dpkg-reconfigure grub-pc."
       },
       {
diff --git a/web/messages/it/docs/utils/system-utils.json b/web/messages/it/docs/utils/system-utils.json
index 4e693c13..25c59c3c 100644
--- a/web/messages/it/docs/utils/system-utils.json
+++ b/web/messages/it/docs/utils/system-utils.json
@@ -235,7 +235,7 @@
       {
         "href": "/docs/utils/system-update",
         "label": "Aggiornamento sistema Proxmox",
-        "tailRich": "— per il apt dist-upgrade effettivo sull'host."
+        "tailRich": "— per il apt full-upgrade effettivo sull'host."
       },
       {
         "href": "/docs/network/monitoring",
diff --git a/web/messages/it/docs/utils/upgrade-pve8-pve9.json b/web/messages/it/docs/utils/upgrade-pve8-pve9.json
index 56359c1c..8dba027c 100644
--- a/web/messages/it/docs/utils/upgrade-pve8-pve9.json
+++ b/web/messages/it/docs/utils/upgrade-pve8-pve9.json
@@ -16,9 +16,9 @@
     "items": [
       "Backup verificati di ogni VM e CT (PBS, vzdump, snapshot ZFS — verificare anche il ripristino).",
       "Accesso console / IPMI / iKVM all’host in caso di interruzione SSH durante l’aggiornamento.",
-      "L'host è già aggiornato su PVE 8.4.x (lo script applica questo).",
+      "L'host sull'ultimo livello di patch di PVE 8. Lo script aggiorna prima PVE 8; non verifica una versione specifica 8.4.x.",
       "Ceph (se presente) sulla versione 19.x Squid.",
-      "Almeno 5 GB liberi nel filesystem root."
+      "Almeno 5 GB liberi nel filesystem root, come raccomanda la guida Proxmox. Lo script stesso controlla solo 1 GB in /var/cache/apt/archives."
     ]
   },
   "modeMenu": {
@@ -63,13 +63,13 @@
   },
   "webTerminal": {
     "heading": "Perché il terminale web è bloccato",
-    "body": "Le tre modalità rilevano termproxy / vncshell nella catena dei processi padre e rifiutano l’avvio. Il terminale web dipende da pveproxy, servizio aggiornato durante l’operazione. Al riavvio di pveproxy termina la sessione e lo script al suo interno. L’host può rimanere aggiornato solo in parte.",
+    "body": "Le modalità Automatic e Interactive rilevano termproxy / vncshell nella catena dei processi padre e rifiutano l’avvio, a meno che la sessione arrivi via SSH o sia impostato PVE_ALLOW_WEBTERM=1. Il controllo preliminare e la guida manuale non cambiano nulla e funzionano ovunque. Il terminale web dipende da pveproxy, servizio aggiornato durante l’operazione. Al riavvio di pveproxy termina la sessione e lo script al suo interno. L’host può rimanere aggiornato solo in parte.",
     "warningTitle": "Usa SSH o una console fisica / IPMI / iKVM",
-    "warningBody": "SSH è la scelta più comune. Avvialo dentro tmux o screen per resistere a brevi interruzioni di rete. Se rileva una normale shell SSH, lo script mostra subito un promemoria su tmux/screen."
+    "warningBody": "SSH è la scelta più comune. Avvialo dentro tmux o screen per resistere a brevi interruzioni di rete. La finestra di conferma dell’aggiornamento ti ricorda di eseguirlo dalla console o da SSH dentro tmux/screen."
   },
   "auto": {
     "heading": "Modalità automatica / Senza intervento",
-    "intro": "Le domande su pacchetti e file di configurazione usano i valori predefiniti della tabella seguente, e un 401 dal repository Enterprise passa a no-subscription senza chiedere. La conferma dell’aggiornamento, il riepilogo dell’aggiornamento di PVE 8, le riparazioni dei controlli e la richiesta di riavvio vengono comunque mostrati.",
+    "intro": "Le domande su pacchetti e file di configurazione usano i valori predefiniti della tabella seguente. La conferma dell’aggiornamento, il riepilogo dell’aggiornamento di PVE 8, le riparazioni dei controlli e la richiesta di riavvio vengono comunque mostrati.",
     "behaviourHeading": "Comportamento specifico della modalità automatica",
     "behaviourTable": {
       "settingHeader": "Impostazione",
@@ -94,8 +94,8 @@
       ]
     },
     "flowHeading": "Percorso della modalità automatica",
-    "flowIntro": "Sei fasi consecutive. La fase 2 aggiorna i pacchetti PVE 8 e la fase 3 scrive la configurazione dei repository di PVE 9. Un errore interrompe il percorso senza annullare quelle modifiche; se l’aggiornamento di PVE 8 non riesce o viene annullato, i repository restano invariati. La fase 5 esegue dist-upgrade a PVE 9.",
-    "flowDiagram": "1. CONTROLLI\nBlocca termproxy / vncshell; conferma l’aggiornamento; rifiuta PVE 9.\n\n2. PREPARARE PVE 8\nscripts/global/update-pve8.sh\nAggiorna i pacchetti PVE 8; si ferma se l’aggiornamento non riesce o viene annullato.\npve8to9 --full (può proporre riparazioni).\nControlla spazio e connettività; Ceph 19.x Squid sui nodi iperconvergenti.\n\n3. MIGRARE I REPOSITORY\nBookworm → Trixie; scrive file deb822 .sources.\nCommenta i vecchi file .list; apt-get update.\n\n4. VERIFICARE IL CANDIDATO PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nSi ferma se il candidato non è 9.x o se proxmox-ve verrebbe rimosso.\n\n5. AGGIORNARE\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nSenza abbonamento: disattiva pve-enterprise.sources.\nRegistro: /var/log/pve8-a-pve9-.log\n\n6. VERIFICARE E RIAVVIARE\nEFI: apt install grub-efi-amd64\npve8to9 --full; esaminare risultati e richiesta di riavvio.",
+    "flowIntro": "Sei fasi consecutive. La fase 2 passa i repository di PVE 8 a no-subscription (elenchi Enterprise e Ceph disabilitati, /etc/apt/sources.list riscritto) prima di mostrare il riepilogo dell’aggiornamento, poi aggiorna i pacchetti; la fase 3 scrive la configurazione dei repository di PVE 9. Un errore interrompe il percorso senza annullare quelle modifiche; se l’aggiornamento di PVE 8 non riesce o viene annullato, la migrazione a PVE 9 non parte, ma le modifiche ai repository di PVE 8 restano. La fase 5 esegue dist-upgrade a PVE 9.",
+    "flowDiagram": "1. CONTROLLI\nBlocca termproxy / vncshell; conferma l’aggiornamento; rifiuta PVE 9.\n\n2. PREPARARE PVE 8\nscripts/global/update-pve8.sh\nPassa i repository di PVE 8 a no-subscription e riscrive sources.list;\naggiorna i pacchetti PVE 8; si ferma se l’aggiornamento non riesce o viene annullato.\npve8to9 --full (può proporre riparazioni).\nControlla spazio e connettività; Ceph 19.x Squid sui nodi iperconvergenti.\n\n3. MIGRARE I REPOSITORY\nBookworm → Trixie; scrive file deb822 .sources.\nCommenta i vecchi file .list; apt-get update.\n\n4. VERIFICARE IL CANDIDATO PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nSi ferma se il candidato non è 9.x o se proxmox-ve verrebbe rimosso.\n\n5. AGGIORNARE\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nSenza abbonamento: disattiva pve-enterprise.sources.\nRegistro: /var/log/pve8-a-pve9-.log\n\n6. VERIFICARE E RIAVVIARE\nEFI: apt install grub-efi-amd64\npve8to9 --full; esaminare risultati e richiesta di riavvio.",
     "preflightHeading": "Controlli prima dell’aggiornamento",
     "preflightTable": {
       "checkHeader": "Controllo",
@@ -158,7 +158,7 @@
   },
   "manual": {
     "heading": "Procedura di aggiornamento manuale",
-    "intro": "Questo è ciò che mostra l’opzione Guida all’aggiornamento manuale: il wiki ufficiale Proxmox per il passaggio da 8 a 9, diviso in quattro fasi. Lo script mostra i comandi; li esegui personalmente in una shell root separata. Ogni passaggio è visibile.",
+    "intro": "Questo segue ciò che mostra l’opzione di menu Guida all’aggiornamento manuale, basata sul wiki ufficiale Proxmox per il passaggio da 8 a 9 e organizzata in quattro fasi. La tabella delle risposte dpkg aggiunge le avvertenze del wiki per sshd_config e chrony.conf, che il menu mostra come un semplice Y. Lo script mostra i comandi; li esegui personalmente in una shell root separata. Nessuna automazione: piena visibilità.",
     "rootCalloutTitle": "Tutti i comandi hanno bisogno di root",
     "rootCalloutBody": "Usa una shell root per questi comandi, incluse redirezioni e here-document. Con sudo, assicurati che l’intero comando venga eseguito con privilegi root.",
     "phase1Heading": "Fase 1 — Preparazione",
@@ -230,7 +230,7 @@
   "clusterCeph": {
     "heading": "Cluster e Ceph",
     "clusterCalloutTitle": "Cluster: aggiornare i nodi uno alla volta",
-    "clusterCalloutBody": "Rilevato con pvecm status. Non aggiornare più nodi contemporaneamente. Sposta gli ospiti, aggiorna il nodo, riavvia e verifica l’appartenenza al cluster prima di proseguire. Durante l’aggiornamento funziona la migrazione live da versioni PVE precedenti a successive; la direzione inversa generalmente non è supportata. I gruppi HA diventano automaticamente regole HA quando tutti i nodi usano PVE 9.",
+    "clusterCalloutBody": "Non aggiornare più di un nodo alla volta. Sposta gli ospiti fuori dal nodo, aggiorna, riavvia e verifica l’appartenenza al cluster prima di proseguire. Durante l’aggiornamento funziona la migrazione live da versioni PVE precedenti a successive; la direzione inversa generalmente non è supportata. I gruppi HA diventano automaticamente regole HA quando tutti i nodi usano PVE 9.",
     "cephCalloutTitle": "Ceph: aggiornare PRIMA Ceph, poi Proxmox",
     "cephCalloutBody": "Ceph deve già usare 19.x Squid prima dell’aggiornamento Proxmox. Segui l’ordine ufficiale Quincy 17 → Reef 18 → Squid 19, aggiornando pacchetti e riavviando progressivamente i demoni Ceph nell’ordine documentato. Verifica ceph --version prima di ogni nodo Proxmox. Procedura completa: wiki per il passaggio da Ceph Reef a Squid."
   },
@@ -273,7 +273,7 @@
   },
   "files": {
     "heading": "File coinvolti",
-    "code": "scripts/utilities/upgrade_pve8_to_pve9.sh                         # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh                                # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh    # 17-step runbook\nscripts/global/update-pve8.sh                                     # PVE 8 worker (final patches)\n/etc/apt/sources.list                                             # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources                           # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources                            # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources                              # created if Ceph\n/etc/apt/sources.list.d/*.list                                    # legacy files commented out / removed\n/var/log/pve8-a-pve9-.log                              # full upgrade log"
+    "code": "scripts/utilities/upgrade_pve8_to_pve9.sh                         # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh                                # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh    # 17-step runbook\nscripts/global/update-pve8.sh                                     # PVE 8 worker (final patches)\n/etc/apt/sources.list                                             # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources                           # created (deb822 PVE 9)\n/etc/apt/sources.list.d/ceph.sources                              # created if Ceph\n/etc/apt/sources.list.d/*.list                                    # legacy files commented out / removed\n/var/log/pve8-a-pve9-.log                              # full upgrade log"
   },
   "references": {
     "heading": "Riferimenti ufficiali",
diff --git a/web/messages/pt/docs/backup-restore/creating-backups.json b/web/messages/pt/docs/backup-restore/creating-backups.json
index 6386b9ab..21e4eda6 100644
--- a/web/messages/pt/docs/backup-restore/creating-backups.json
+++ b/web/messages/pt/docs/backup-restore/creating-backups.json
@@ -21,8 +21,8 @@
     "rows": [
       {
         "entry": "ProxMenux Scripts (TUI)",
-        "path": "menu → Utilities → Host Config Backup",
-        "detail": "Fluxo baseado em diálogo, SSH-friendly. O menu principal apresenta as seis opções diretamente. Utiliza backup_menu em backup_host.sh."
+        "path": "menu → Host Backup & Restore → Backup host configuration",
+        "detail": "Fluxo baseado em diálogo, SSH-friendly. A entrada do menu principal abre Host Config Backup / Restore; Backup host configuration leva às seis opções. Utiliza backup_menu em backup_host.sh."
       },
       {
         "entry": "ProxMenux Monitor (UI Web)",
@@ -83,7 +83,7 @@
   "profiles": {
     "heading": "Perfil padrão vs personalizado",
     "defaultTitle": "Perfil padrão",
-    "defaultBody": "O perfil padrão é a lista com curadoria de hb_default_profile_paths (documentada em Como funciona sob Categorias de caminho) além de cada entrada no arquivo de extras persistente /usr/local/share/proxmenux/backup-extra-paths.txt. O usuário confirma as opções de destino e criptografia e o backup prossegue sem mais seleção de caminho.",
+    "defaultBody": "O perfil padrão é a lista curada de hb_default_profile_paths (documentada em How it works, em Path categories) mais cada entrada do arquivo persistente de extras /usr/local/share/proxmenux/backup-extra-paths.txt. O usuário escolhe o destino e as opções de criptografia e o backup prossegue sem mais seleção de caminhos.",
     "customTitle": "Perfil personalizado",
     "customBody": "O perfil personalizado abre uma lista com todos os caminhos do perfil padrão (desmarcados) e todos os extras persistentes (pré-marcados, com o prefixo [+]). O usuário marca os caminhos para esta execução e pode pressionar Adicionar caminho personalizado para incluir um novo caminho absoluto. Todo caminho adicionado durante esse processo é salvo em backup-extra-paths.txt, para que os próximos backups o incluam automaticamente. Desmarcar um extra persistente o exclui apenas desta execução, sem removê-lo do arquivo — a remoção é feita pela ação separada Gerenciar caminhos personalizados, fora do fluxo de backup.",
     "customPickerAlt": "Lista de verificação de perfil personalizada que mostra os caminhos de perfil padrão (desmarcados) e extras persistentes (pré-marcados com um prefixo [+]), além de botões para adicionar um novo caminho ou confirmar a seleção.",
@@ -124,14 +124,7 @@
   },
   "included": {
     "heading": "O que entra e o que fica fora",
-    "intro": "Cada caminho no perfil resolvido (padrão + extras persistentes + seleção de modo personalizado) é copiado com rsync -aAXH --numeric-ids. Uma lista de exclusão compartilhada aplica-se a cada caminho, e dois diretórios carregam exclusões adicionais específicas do caminho.",
-    "globalTitle": "Exclusões globais (aplicadas a cada caminho)",
-    "globalItems": [
-      "images/ — dumps de imagens.",
-      "dump/ — saídas vzdump.",
-      "tmp/ — ficheiros temporários.",
-      "*.log — ficheiros de registo."
-    ],
+    "intro": "Cada caminho no perfil resolvido (padrão + extras persistentes + seleção de modo personalizado) é copiado com rsync -aAXH --numeric-ids. Não há lista de exclusão global; alguns diretórios têm exclusões específicas do caminho.",
     "rootTitle": "Exclusões /root/",
     "rootBody": "/root/ faz parte do perfil padrão para incluir os scripts e a configuração do usuário no arquivo. Os subcaminhos voláteis são excluídos:",
     "rootItems": [
@@ -160,16 +153,16 @@
       "/home/USER — não no perfil padrão. Adicione-o como um caminho personalizado quando um sistema carrega diretórios domésticos do usuário que devem sobreviver a uma restauração."
     ],
     "customPathsTitle": "Como os caminhos personalizados são tratados",
-    "customPathsBody": "Um caminho personalizado adicionado inline em modo Personalizado ou persistido em backup-extra-paths.txt passa pelo mesmo pipeline rsync como caminhos de perfil padrão. Exclusões globais aplicam-se. Se o caminho personalizado estiver sob /root/ ou /usr/local/share/proxmenux/, as exclusões específicas do caminho acima ainda se aplicam. Cada caminho arquivado — padrão ou personalizado — é gravado em metadata/paths_archived.txt. Caminhos que não existem na fonte são gravados em metadata/missing_paths.txt sem parar o backup."
+    "customPathsBody": "Um caminho personalizado adicionado inline em modo Personalizado ou persistido em backup-extra-paths.txt passa pelo mesmo pipeline rsync dos caminhos de perfil padrão. Se o caminho personalizado estiver sob /root/ ou /usr/local/share/proxmenux/, as exclusões específicas do caminho acima ainda se aplicam. Cada caminho arquivado — padrão ou personalizado — é gravado em metadata/selected_paths.txt. Caminhos que não existem na fonte são gravados em metadata/missing_paths.txt sem parar o backup, e os caminhos cuja cópia falha são gravados em metadata/failed_paths.txt."
   },
   "archiveStructure": {
     "heading": "Estrutura do arquivo",
     "intro": "O diretório de preparação temporária produzido por cada infraestrutura segue o mesmo layout, independentemente do destino. O tarball, PBS .pxar ou arquivo Borg armazena esta árvore sem alterações.",
-    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state (kernel_params, hardware, storage, guests, components, source_host)\n├── metadata/\n│   ├── packages.manual.list       # output of apt-mark showmanual\n│   ├── run_info.env               # hostname, timestamp, kernel version, pmxcfs method\n│   ├── paths_archived.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, /etc/apt, ...\n    ├── root/                      # /root without volatile subpaths\n    ├── usr/local/                 # /usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux (state only)\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/crontabs"
+    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state (kernel_params, hardware, storage, guests, components, source_host)\n├── metadata/\n│   ├── packages.manual.list       # output of apt-mark showmanual\n│   ├── run_info.env               # hostname, timestamp, kernel version, pmxcfs method\n│   ├── selected_paths.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, /etc/apt, ...\n    ├── root/                      # /root without volatile subpaths\n    ├── usr/local/                 # /usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux (state only)\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/crontabs"
   },
   "confirmation": {
     "heading": "Resumo da confirmação",
-    "body": "Antes da infra- estrutura escrever qualquer coisa para o destino, o ProxMenux mostra uma janela sumária com o nome do destino, ID de backup ou arquivo, estado de criptografia e a lista de caminhos copiados. Cancelar aqui aborta o backup de forma limpa — o diretório de preparação temporária é removido pelo gancho trap definido na função de infraestrutura e nenhum dado parcial atinge o destino."
+    "body": "Antes de a infraestrutura escrever qualquer coisa no destino, o ProxMenux imprime no terminal um resumo com o destino, o ID de backup ou nome do arquivo, o estado de criptografia e a lista de caminhos copiados. Neste ponto não há etapa de confirmar ou cancelar; a cópia começa logo em seguida. Se o backup falhar ou for interrompido, o diretório de preparação temporária é removido pelo gancho trap definido na função de infraestrutura e nenhum dado parcial atinge o destino."
   },
   "writing": {
     "heading": "Gravando para o destino",
diff --git a/web/messages/pt/docs/backup-restore/destinations/local.json b/web/messages/pt/docs/backup-restore/destinations/local.json
index 01c392f8..3951f3a3 100644
--- a/web/messages/pt/docs/backup-restore/destinations/local.json
+++ b/web/messages/pt/docs/backup-restore/destinations/local.json
@@ -18,11 +18,11 @@
   },
   "targetConfig": {
     "heading": "Configurando o diretório alvo",
-    "intro": "O destino local é um único diretório de destino salvo de forma persistente — não uma lista. ProxMenux armazena a escolha do operador em /usr/local/share/proxmenux/local-target.conf e lê essa escolha em cada backup. Quando nenhum alvo foi configurado, o HB_LOCAL_TARGET_DEFAULT = /var/lib/vz/dump padrão é usado (o mesmo diretório que o Proxmox usa para suas próprias saídas vzdump). O alvo está configurado a partir de Configure destinos de backup → Destinos locais:",
+    "intro": "O destino local é um único diretório de destino salvo de forma persistente — não uma lista. ProxMenux armazena a escolha do operador em /usr/local/share/proxmenux/local-target.conf e lê essa escolha em cada backup. Quando nenhum alvo foi configurado, o HB_LOCAL_TARGET_DEFAULT = /var/lib/vz/dump padrão é usado (o mesmo diretório que o Proxmox usa para suas próprias saídas vzdump). O alvo é configurado a partir de Configure backup destinations → Local archive targets → Manage local backup target:",
     "options": [
       "Use o padrão (/var/lib/vz/dump). O armazenamento local Proxmox. Presente em cada instalação do Proxmox; o arquivo fica ao lado das saídas vzdump e é captado automaticamente pela aba Backups do Monitor.",
-      "Insira um caminho personalizado. Qualquer caminho absoluto do sistema de arquivos pode ser usado: uma montagem NFS, uma partilha SMB montada via fstab, um conjunto de dados dedicado ZFS, um segundo disco interno. ProxMenux valida que o caminho existe e é um diretório antes de persisti-lo.",
-      "Escolha uma unidade USB. Abre o submenu USB (abaixo), que detecta dispositivos removíveis e oferece para montar ou formatar um."
+      "Use um caminho personalizado. Qualquer caminho absoluto do sistema de arquivos pode ser usado: uma montagem NFS, uma partilha SMB montada via fstab, um conjunto de dados dedicado ZFS, um segundo disco interno. ProxMenux valida que o caminho existe e é um diretório antes de persisti-lo.",
+      "Use um disco USB. Abre o submenu USB (abaixo), que detecta dispositivos removíveis e oferece para montar ou formatar um."
     ]
   },
   "usbFlow": {
@@ -38,7 +38,7 @@
       {
         "state": "desmontado",
         "shown": "Tamanho · etiqueta · [fstype] · (não montado — será montado)",
-        "action": "Um sistema de ficheiros está presente mas não está montado. Na confirmação, o ProxMenux executa o hb_mount_usb_partition: cria o /mnt/backup-LABEL (ou um caminho baseado em UUID, se não houver rótulo), monta a partição e persiste o ponto de montagem como alvo local."
+        "action": "Um sistema de ficheiros está presente mas não está montado. Na confirmação, o ProxMenux executa o hb_mount_usb_partition: cria o /mnt/proxmenux-backup-LABEL (UUID ou nome do dispositivo, se não houver rótulo), monta a partição e persiste o ponto de montagem como alvo local."
       },
       {
         "state": "vazia",
@@ -63,7 +63,7 @@
   },
   "sidecar": {
     "heading": "O arquivo auxiliar JSON",
-    "intro": "Cada backup local bem sucedido produz um arquivo companheiro: HOSTNAME-TIMESTAMP.tar.zst.proxmenux.json, escrito ao lado do arquivo por hb_write_archive_sidecar. Este pequeno arquivo JSON permite que o ProxMenux Monitor identifique o arquivo como um backup de host ProxMenux mesmo que mais tarde seja movido, renomeado ou arquivado em outro lugar.",
+    "intro": "Cada backup local bem sucedido produz um arquivo companheiro: hostcfg-HOSTNAME-TIMESTAMP.tar.zst.proxmenux.json, escrito ao lado do arquivo por hb_write_archive_sidecar. Este pequeno arquivo JSON permite que o ProxMenux Monitor identifique o arquivo como um backup de host ProxMenux mesmo que mais tarde seja movido, renomeado ou arquivado em outro lugar.",
     "contentTitle": "Conteúdo do sidecar",
     "contentBody": "O sidecar armazena a versão do esquema, se o backup veio de uma execução interativa ou de uma tarefa agendada (kind), o ID do trabalho para as execuções agendadas, o modo de perfil usado (default ou custom), o nome de máquina de origem, o nome de base do arquivo original, um timestamp de criação ISO-8601 e o tamanho do arquivo em bytes.",
     "whyBody": "A aba Backups do Monitor procura arquivos auxiliares *.proxmenux.json nos diretórios locais configurados, e não arquivos *.tar.zst, pois um .tar.zst sem arquivo auxiliar pode não ser um backup ProxMenux. A busca é rápida (os arquivos JSON são pequenos) e a associação permanece válida após renomear o arquivo, desde que o auxiliar seja renomeado de forma correspondente."
diff --git a/web/messages/pt/docs/backup-restore/destinations/pbs.json b/web/messages/pt/docs/backup-restore/destinations/pbs.json
index 2a36cac8..f972477c 100644
--- a/web/messages/pt/docs/backup-restore/destinations/pbs.json
+++ b/web/messages/pt/docs/backup-restore/destinations/pbs.json
@@ -33,7 +33,7 @@
       {
         "source": "Configuração manual do ProxMenux",
         "path": "/usr/local/share/proxmenux/pbs-manual-configs.txt + pbs-pass-NAME.txt + pbs-fingerprint-NAME.txt",
-        "content": "Adicionado de Configure destinos de backup → destinos de PBS → Adicione PBS. Pede um nome, nome de usuário (root@pam ou user@pbs!token), host ou IP, datastore e senha. A senha re-prompts na entrada vazia — uma gravação vazia persistiria silenciosamente e cada backup subsequente falharia com um erro de autenticação opaco. Este caminho é usado quando o PBS alvo não é registrado como armazenamento Proxmox."
+        "content": "Adicionado de Configure backup destinations → Proxmox Backup Server (PBS) destinations → + Add new PBS manually. Pede um nome, nome de usuário (root@pam ou user@pbs!token), host ou IP, datastore e senha. A senha re-prompts na entrada vazia — uma gravação vazia persistiria silenciosamente e cada backup subsequente falharia com um erro de autenticação opaco. Este caminho é usado quando o PBS alvo não é registrado como armazenamento Proxmox."
       }
     ],
     "menuTitle": "Menu de seleção",
diff --git a/web/messages/pt/docs/backup-restore/how-it-works.json b/web/messages/pt/docs/backup-restore/how-it-works.json
index 681902e0..0f000322 100644
--- a/web/messages/pt/docs/backup-restore/how-it-works.json
+++ b/web/messages/pt/docs/backup-restore/how-it-works.json
@@ -20,7 +20,7 @@
     "heading": "Disposição do arquivo",
     "intro": "Cada arquivo segue o mesmo layout de árvore, independentemente do destino. O subdiretório metadata/ contém as cargas estruturadas; o subdiretório rootfs/ contém a cópia do sistema de arquivos.",
     "treeCaption": "O diretório de preparação criado durante o backup. Todos os destinos recebem a mesma árvore no formato nativo: tar para backups locais, blocos PBS para PBS e segmentos Borg para Borg.",
-    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state\n├── metadata/\n│   ├── packages.manual.list       # apt-mark showmanual\n│   ├── run_info.env               # backup run identity + kernel version + pmxcfs method\n│   ├── paths_archived.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, …\n    ├── root/                      # /root (with volatile subdirs excluded)\n    ├── usr/local/                 # /usr/local/bin, /usr/local/share/proxmenux, …\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/…"
+    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state\n├── metadata/\n│   ├── packages.manual.list       # apt-mark showmanual\n│   ├── run_info.env               # backup run identity + kernel version + pmxcfs method\n│   ├── selected_paths.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, …\n    ├── root/                      # /root (with volatile subdirs excluded)\n    ├── usr/local/                 # /usr/local/bin, /usr/local/share/proxmenux, …\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/…"
   },
   "rootfs": {
     "heading": "A carga útil do rootfs",
@@ -176,7 +176,7 @@
         "stage": "3",
         "name": "Preparações pendentes",
         "reads": "rootfs/ (reboot + caminhos perigosos)",
-        "action": "_rs_prepare_pending_restore prepara os caminhos de risco em /var/lib/proxmenux/pending-restore/, grava plan.env, apply-on-boot.list e rs-skip-paths.txt e ativa proxmenux-restore-onboot.service para a próxima inicialização."
+        "action": "_rs_prepare_pending_restore prepara os caminhos de risco em /var/lib/proxmenux/restore-pending/<restore_id>/ (vinculado como current), grava plan.env, apply-on-boot.list e rs-skip-paths.txt e ativa proxmenux-restore-onboot.service para a próxima inicialização."
       },
       {
         "stage": "3b",
diff --git a/web/messages/pt/docs/backup-restore/index.json b/web/messages/pt/docs/backup-restore/index.json
index 950b34e2..0b8cbbb5 100644
--- a/web/messages/pt/docs/backup-restore/index.json
+++ b/web/messages/pt/docs/backup-restore/index.json
@@ -50,7 +50,7 @@
     "heading": "Duas interfaces, um back-end",
     "intro": "Cada etapa do fluxo de trabalho de backup e restauração está disponível em dois pontos de entrada. Ambos chamam a mesma biblioteca shell, produzem arquivos idênticos e leem o mesmo registro de tarefas.",
     "cliLabel": "ProxMenux Scripts (TUI)",
-    "cliDetail": "menu → Utilitários →\nBackup/restauração de host\n\nFluxo baseado em diálogo,\nSSH amigável, programável\npara uso não supervisionado.",
+    "cliDetail": "menu →\nBackup/restauração de host\n\nFluxo baseado em diálogo,\nSSH amigável, programável\npara uso não supervisionado.",
     "webLabel": "ProxMenux Monitor (Web UI)",
     "webDetail": "Aba Backups na\ninterface web do Monitor.\n\nRestauração com um clique,\nacompanhamento dos logs ao vivo,\nintegração com notificações e o\nvisualizador de diferenças da reversão."
   },
diff --git a/web/messages/pt/docs/backup-restore/restoring.json b/web/messages/pt/docs/backup-restore/restoring.json
index 44ab6673..49194de1 100644
--- a/web/messages/pt/docs/backup-restore/restoring.json
+++ b/web/messages/pt/docs/backup-restore/restoring.json
@@ -92,11 +92,11 @@
   "fullFlow": {
     "heading": "O fluxo de trabalho completo de restauração",
     "intro": "O pipeline completo da seleção de arquivos para uma máquina de trabalho restaurada. Cada etapa alimenta a seguinte; o estado escrito por fases anteriores é consumido por fases posteriores.",
-    "diagram": "┌─────────────────────────────────────────────────────────────────┐\n│  Archive → staging_root/                                        │\n│    manifest.json   metadata/   rootfs/                          │\n└──────────────────────────────┬──────────────────────────────────┘\n                               │\n                               ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  1. hb_compat_check                                     │\n   │     · hardware drift → RS_SKIP_PATHS                    │\n   │     · kernel direction → same / bk_newer / bk_older     │\n   │     · NIC remap plan                                    │\n   │     · hydration plan (bk_older only)                    │\n   │     · rollback plan                                     │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  2. Confirmation dialog                                 │\n   │     Shows: hot count, pending count, drift, hydration,  │\n   │     NIC remap, cross-kernel note, reinstalls preview    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │ (accepted)\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  3. Apply hot paths → LIVE                              │\n   │     _rs_apply rsyncs each hot path                      │\n   │     from staging_root/rootfs to /                       │\n   │     (skips paths in RS_SKIP_PATHS)                      │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4. _rs_prepare_pending_restore                         │\n   │     /var/lib/proxmenux/pending-restore/                 │\n   │       ├── apply-on-boot.list                            │\n   │       ├── plan.env                                      │\n   │       ├── rs-skip-paths.txt                             │\n   │       └── rootfs/ (deferred paths)                      │\n   │     Enable proxmenux-restore-onboot.service             │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4b. _rs_import_data_pools                              │\n   │      Imports non-root ZFS pools whose disks are all     │\n   │      present; retries with -f on foreign hostid;        │\n   │      skips with a warning when disks are missing.       │\n   │      Log: /var/log/proxmenux/restore-datapools-*.log    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  5. _rs_run_complete_extras                             │\n   │     packages.manual.list                                │\n   │       → cascade-safe filter                             │\n   │       → apt-get install                                 │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  6. REBOOT                                              │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  7. apply_pending_restore.sh (on early boot)            │\n   │     Reads plan.env                                      │\n   │     Applies apply-on-boot.list (filtered)               │\n   │     Installs the postboot systemd unit                  │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  8. apply_cluster_postboot.sh (~10 minutes)             │\n   │     Runs after pve-cluster is up                        │\n   │     · Applies /etc/pve entries to pmxcfs                │\n   │     · update-initramfs -u -k all                        │\n   │     · update-grub / proxmox-boot-tool refresh           │\n   │     · component --auto-reinstall (nvidia, coral, ...)   │\n   │     · Boot sanity check                                 │\n   │     · Notification: \"Host restore finished\"             │\n   └─────────────────────────────────────────────────────────┘"
+    "diagram": "┌─────────────────────────────────────────────────────────────────┐\n│  Archive → staging_root/                                        │\n│    manifest.json   metadata/   rootfs/                          │\n└──────────────────────────────┬──────────────────────────────────┘\n                               │\n                               ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  1. hb_compat_check                                     │\n   │     · hardware drift → RS_SKIP_PATHS                    │\n   │     · kernel direction → same / bk_newer / bk_older     │\n   │     · NIC remap plan                                    │\n   │     · hydration plan (bk_older only)                    │\n   │     · rollback plan                                     │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  2. Confirmation dialog                                 │\n   │     Shows: hot count, pending count, drift, hydration,  │\n   │     NIC remap, cross-kernel note, reinstalls preview    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │ (accepted)\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  3. Apply hot paths → LIVE                              │\n   │     _rs_apply rsyncs each hot path                      │\n   │     from staging_root/rootfs to /                       │\n   │     (skips paths in RS_SKIP_PATHS)                      │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4. _rs_prepare_pending_restore                         │\n   │     /var/lib/proxmenux/restore-pending/'<'id'>'/            │\n   │       ├── apply-on-boot.list                            │\n   │       ├── plan.env                                      │\n   │       ├── rs-skip-paths.txt                             │\n   │       └── rootfs/ (deferred paths)                      │\n   │     Enable proxmenux-restore-onboot.service             │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4b. _rs_import_data_pools                              │\n   │      Imports non-root ZFS pools whose disks are all     │\n   │      present; retries with -f on foreign hostid;        │\n   │      skips with a warning when disks are missing.       │\n   │      Log: /var/log/proxmenux/restore-datapools-*.log    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  5. _rs_run_complete_extras                             │\n   │     packages.manual.list                                │\n   │       → cascade-safe filter                             │\n   │       → apt-get install                                 │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  6. REBOOT                                              │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  7. apply_pending_restore.sh (on early boot)            │\n   │     Reads plan.env                                      │\n   │     Applies apply-on-boot.list (filtered)               │\n   │     Installs the postboot systemd unit                  │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  8. apply_cluster_postboot.sh (~10 minutes)             │\n   │     Runs after pve-cluster is up                        │\n   │     · Applies /etc/pve entries to pmxcfs                │\n   │     · update-initramfs -u -k all                        │\n   │     · update-grub / proxmox-boot-tool refresh           │\n   │     · component --auto-reinstall (nvidia, coral, ...)   │\n   │     · Boot sanity check                                 │\n   │     · Notification: \"Host restore finished\"             │\n   └─────────────────────────────────────────────────────────┘"
   },
   "pendingMachinery": {
     "heading": "A máquina de restauro pendente",
-    "intro": "Os caminhos classificados como reboot ou dangerous, e a hidratação escreve, são encenados para o próximo boot em vez de serem aplicados ao vivo. Isto é feito através de um pequeno conjunto de arquivos auto-suficientes sob /var/lib/proxmenux/pending-restore/:",
+    "intro": "Os caminhos classificados como reboot ou dangerous, e a hidratação escreve, são encenados para o próximo boot em vez de serem aplicados ao vivo. Isto é feito através de um pequeno conjunto de arquivos auto-suficientes sob /var/lib/proxmenux/restore-pending/<restore_id>/:",
     "rows": [
       {
         "file": "apply-on-boot.list",
@@ -115,7 +115,7 @@
         "content": "Os próprios arquivos encenados — os bytes exatos que serão estabelecidos. Mantido no mesmo sistema de arquivos que / para que o rsync on-boot é rápido e não depende de armazenamento externo ainda sendo acessível no arranque."
       }
     ],
-    "unitBody": "A unidade systemd proxmenux-restore-onboot.service é ativada ao fim da etapa interativa. Esse serviço executa uma vez no início do próximo boot, chama apply_pending_restore.sh e depois se desativa. A condição ConditionPathExists=/var/lib/proxmenux/pending-restore/state impede qualquer ação quando não há restauração pendente."
+    "unitBody": "A unidade systemd proxmenux-restore-onboot.service é ativada ao fim da etapa interativa. Esse serviço executa uma vez no início do próximo boot (antes da rede), chama apply_pending_restore.sh e depois se desativa. A unidade não tem condição de caminho: o próprio script termina quando /var/lib/proxmenux/restore-pending/current não existe, portanto em qualquer boot sem restauração pendente não faz nada. O serviço separado proxmenux-apply-cluster-postboot.service é controlado por ConditionPathExists=/var/lib/proxmenux/cluster-apply-pending."
   },
   "postbootDispatcher": {
     "heading": "O expedidor pós-boot — apply_cluster_postboot.sh",
@@ -238,11 +238,11 @@
     "heading": "Onde ficam os logs",
     "rows": [
       {
-        "log": "/var/log/proxmenux/restore-YYYYMMDD_HHMMSS.log",
-        "detail": "Gravado na etapa interativa: resultados de compatibilidade, filtro de diferenças, plano de adaptação, saídas da aplicação em execução e da preparação pendente, instalação de pacotes."
+        "log": "/tmp/proxmenux--restore-YYYYMMDD_HHMMSS.log",
+        "detail": "Gravado na etapa interativa (pbs, borg ou local). As reinstalações de pacotes e as etapas de data-pool também registram em /var/log/proxmenux/restore-apt-*.log e restore-datapools-*.log."
       },
       {
-        "log": "/var/log/proxmenux/apply-pending-YYYYMMDD_HHMMSS.log",
+        "log": "/var/log/proxmenux/proxmenux-restore-onboot-YYYYMMDD_HHMMSS.log",
         "detail": "Gravado por apply_pending_restore.sh no início do boot: carregamento de plan.env, processamento de apply-on-boot e filtro dos caminhos excluídos."
       },
       {
diff --git a/web/messages/pt/docs/backup-restore/scheduled-jobs.json b/web/messages/pt/docs/backup-restore/scheduled-jobs.json
index 44c417e0..10dd4ed9 100644
--- a/web/messages/pt/docs/backup-restore/scheduled-jobs.json
+++ b/web/messages/pt/docs/backup-restore/scheduled-jobs.json
@@ -101,7 +101,7 @@
   },
   "management": {
     "heading": "Gestão de tarefas",
-    "intro": "O menu de agendamento (Scripts TUI) e a aba Monitor Backups expõem as mesmas ações em qualquer trabalho.",
+    "intro": "O menu de agendamento no Scripts TUI (Backup scheduler and retention) oferece Run now, Enable / Disable e Delete, além de uma lista de trabalhos com o estado da última execução. Edit e View log só estão disponíveis na aba Monitor Backups.",
     "rows": [
       {
         "action": "Executar agora",
@@ -112,7 +112,7 @@
         "detail": "Novo modo: systemctl enable/disable --now o temporizador. Modo Anexar: altera o sinalizador ENABLED no env de trabalho — o script gancho honra-o na próxima execução PVE."
       },
       {
-        "action": "Editar",
+        "action": "Editar (apenas no Monitor)",
         "detail": "Abre novamente o destino, agenda, perfil e avisos de retenção e reescreve os arquivos de trabalho. Preserva a identificação do trabalho."
       },
       {
@@ -120,7 +120,7 @@
         "detail": "Remove o env do trabalho, o arquivo de caminhos, o temporizador e o serviço systemd (modo novo) e desativa a associação ao hook (modo de anexação). Não altera os arquivos já existentes no destino."
       },
       {
-        "action": "Ver registo",
+        "action": "Ver registo (apenas no Monitor)",
         "detail": "Mostra em tempo real /var/log/proxmenux/backup-jobs/JOB_ID-YYYYMMDD_HHMMSS.log — um arquivo de log por execução. O executor também emite uma linha de status compacta para journald no serviço systemd."
       }
     ]
diff --git a/web/messages/pt/docs/installation.json b/web/messages/pt/docs/installation.json
index 16faddf9..7aa3fd1d 100644
--- a/web/messages/pt/docs/installation.json
+++ b/web/messages/pt/docs/installation.json
@@ -53,11 +53,11 @@
     "calloutBody": "Quer experimentar as novidades antes do lançamento oficial e ajudar a definir a versão final? O programa beta do ProxMenux dá acesso antecipado às novas funcionalidades, incluindo as últimas compilações do Monitor, a partir do ramo develop. As versões beta podem conter erros ou funções incompletas. Os seus comentários ajudam a corrigi-los antes da versão estável.",
     "intro": "Instale a versão beta em vez da estável com:",
     "code": "bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/develop/install_proxmenux_beta.sh)\"",
-    "outro": "A beta é opcional: tem de executar explicitamente o respetivo instalador. Quando é publicada uma versão estável, o ProxMenux avisa na próxima execução de menu e propõe a mudança automática. Para sair antes da beta, escolha Desativar programa beta nas definições."
+    "outro": "A beta é opcional: tem de executar explicitamente o respetivo instalador. Quando é publicada uma versão estável, a próxima execução de menu propõe-na com o aviso de atualização habitual. Para sair do canal beta, use Alterar canal de lançamento nas definições e escolha Stable."
   },
   "updating": {
     "heading": "Actualização",
-    "body": "O ProxMenux gere as suas atualizações. Quando há uma nova versão, a próxima execução de menu propõe a instalação. Ao aceitar, os ficheiros e a configuração da ferramenta são substituídos na instalação existente, sem downloads manuais. O canal estável recebe versões estáveis e a beta recebe versões beta, com a proposta de mudança descrita acima. O aviso depende de onde executa menu: por SSH ou na consola Proxmox, surge um diálogo sim/não e a aceitação aplica a atualização imediatamente. No terminal WebSocket do ProxMenux Monitor surge uma mensagem informativa, porque esse terminal seria interrompido durante a operação. A mensagem apresenta o instalador numa linha (bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\" para estável, ou o URL equivalente de install_proxmenux_beta.sh para beta), que deve executar por SSH ou na consola."
+    "body": "O ProxMenux gere as suas atualizações. Quando há uma nova versão, a próxima execução de menu propõe a instalação. Ao aceitar, os ficheiros e a configuração da ferramenta são substituídos na instalação existente, sem downloads manuais. O canal estável recebe versões estáveis; o beta recebe versões beta e também lhe são propostas as novas versões estáveis. O aviso depende de onde executa menu: por SSH ou na consola Proxmox, surge um diálogo sim/não e a aceitação aplica a atualização imediatamente. No terminal WebSocket do ProxMenux Monitor surge uma mensagem informativa, porque esse terminal seria interrompido durante a operação. A mensagem apresenta o instalador numa linha (bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\" para estável, ou o URL equivalente de install_proxmenux_beta.sh para beta), que deve executar por SSH ou na consola."
   },
   "uninstall": {
     "heading": "Desinstalar",
diff --git a/web/messages/pt/docs/introduction.json b/web/messages/pt/docs/introduction.json
index bba7927b..78876bec 100644
--- a/web/messages/pt/docs/introduction.json
+++ b/web/messages/pt/docs/introduction.json
@@ -21,11 +21,11 @@
     "intro": "O ProxMenux tem dois componentes na mesma árvore de código-fonte. O instalador coloca ambos no anfitrião: Scripts fornece a interface de terminal e Monitor o painel web:",
     "scripts": {
       "title": "ProxMenux Scripts",
-      "body": "A CLI/TUI original, iniciada com menu. Nove áreas abrangem otimizações após a instalação, VM e contentores LXC, passagem direta de GPU e Coral TPU, discos, armazenamento, rede, segurança e utilitários, com uma seleção de comandos de referência. Funciona em terminais locais, por SSH ou na web."
+      "body": "A CLI/TUI original, iniciada com menu. Áreas funcionais que abrangem otimizações após a instalação, criação de VM e LXC, passagem direta de GPU e Coral TPU, discos, armazenamento, aplicações OCI, rede, segurança, utilitários, backup e restauro do host, Proxmox VE Helper Scripts, com uma seleção de comandos de referência. Funciona em terminais locais, por SSH ou na web."
     },
     "monitor": {
       "title": "ProxMenux Monitor",
-      "body": "Painel web alojado no próprio servidor, distribuído como AppImage. Métricas do anfitrião em tempo real, monitorização proativa, notificações, reformulação opcional por IA com seis fornecedores, terminal web integrado e API REST. Ative-o no menu Scripts, em Definições → ProxMenux Monitor."
+      "body": "Painel web alojado no próprio servidor, distribuído como AppImage. Métricas do anfitrião em tempo real, monitorização proativa, notificações, reformulação opcional por IA com seis fornecedores, terminal web integrado e API REST. O instalador configura-o como um serviço que arranca com o sistema; ative-o, desative-o ou verifique-o no menu Scripts, em Definições → ProxMenux Monitor."
     },
     "calloutTitle": "O que o ProxMenux gere",
     "calloutIntro": "Três camadas, todas acessíveis a partir do mesmo projeto:",
@@ -37,7 +37,7 @@
   },
   "scriptsSection": {
     "heading": "ProxMenux Scripts — o que se obtém",
-    "intro": "Nove áreas funcionais, cada uma com o seu submenu em menu e uma página própria na documentação. Selecione um cartão para abrir diretamente a visão geral dessa secção.",
+    "intro": "As principais áreas funcionais, cada uma com o seu submenu em menu e uma página própria na documentação. Selecione um cartão para abrir diretamente a visão geral dessa secção.",
     "items": [
       {
         "title": "Após a instalação",
diff --git a/web/messages/pt/docs/monitor/access-auth.json b/web/messages/pt/docs/monitor/access-auth.json
index 371da883..25b2698e 100644
--- a/web/messages/pt/docs/monitor/access-auth.json
+++ b/web/messages/pt/docs/monitor/access-auth.json
@@ -18,7 +18,7 @@
   },
   "reaching": {
     "heading": "Alcançar o painel",
-    "intro": "ProxMenux Monitor liga-se a 0.0.0.0:8008. Há três maneiras comuns de alcançá-lo:",
+    "intro": "O ProxMenux Monitor escuta no TCP 8008 em todas as interfaces (::, IPv4 e IPv6). Há três maneiras comuns de alcançá-lo:",
     "outro": "O acesso direto corresponde à unidade systemd predefinida. O proxy inverso e o Secure Gateway são descritos abaixo. Confiar nos IP de cliente em X-Forwarded-For exige PROXMENUX_TRUST_PROXY=1 e um proxy sob o seu controlo. A simples presença de X-Forwarded-Proto ou X-Forwarded-Host não ativa a confiança no proxy. Nas portas 80/443, o frontend usa URL /api/* relativos; em qualquer outra porta, chama a porta 8008 no mesmo nome de anfitrião."
   },
   "firstLaunch": {
@@ -158,7 +158,7 @@
     "authJsonTitle": "auth.json — o que contém e como está protegido",
     "authJsonBody": "Os dados de autenticação ficam em /root/.config/proxmenux-monitor/auth.json, modo 0600, proprietário root. Contém hashes PBKDF2 e segredos criptográficos (jwt_secret, totp_secret), nunca a palavra-passe em claro. Cifre os destinos de cópia ou replicação e nunca inclua este ficheiro no controlo de versões.",
     "rotateTitle": "A rotação do jwt_secret invalida todos os JWTs existentes",
-    "rotateBody": "Se auth.json for recriado ou restaurado com outro segredo, jwt_secret muda. Os JWT antigos de sessão e API falham com \"Invalid or expired token\". A interface assinala-os com Inválido — gerar novamente. Revogue e substitua os tokens também no Home Assistant, scripts e outros clientes.",
+    "rotateBody": "Se auth.json for recriado ou restaurado com outro segredo, jwt_secret muda. Os JWT antigos de sessão e API falham com \"Invalid or expired token\". A interface assinala os tokens de API afetados a vermelho, com um selo que indica para os revogar e gerar novos. Home Assistant, scripts e outros clientes externos precisam de um token novo depois disso.",
     "recoverTitle": "Recuperar uma senha perdida",
     "recoverIntro": "Não há fluxo online de \"esquecimento de senha\" — por design, já que o painel é executado no próprio host do operador e o caminho de recuperação é o acesso de shell a esse host. ProxMenux envia uma reinicialização guiada dentro do menu de configuração para que você não tenha que editar manualmente auth.json:",
     "survivesTitle": "O que sobrevive ao reset",
@@ -284,14 +284,14 @@
     "heading": "Tokens API de longa duração",
     "intro": "As sessões expiram após 24 horas. Para Homepage, Home Assistant, Grafana ou Uptime Kuma, crie um token API válido por 365 dias. O JWT usa o mesmo segredo; o campo token_name permite identificá-lo e revogá-lo individualmente.",
     "imageAlt": "Painel de tokens API com nome, prefixo, data de criação e validade",
-    "imageCaption": "Lista nas definições: nome, prefixo com os quatro últimos caracteres de identificação, datas de criação e validade, ação de revogação.",
+    "imageCaption": "Lista em Administrador → Segurança: nome, prefixo com os quatro últimos caracteres de identificação, datas de criação e validade, ação de revogação.",
     "generateTitle": "Gerar um token",
     "generateIntro": "Do painel:",
     "generateSteps": [
-      "Abra Segurança → Tokens de acesso API.",
+      "Abra Administrador → Segurança → Tokens de API e clique em Gerar novo token.",
       "Introduza um nome descritivo, por exemplo \"Home Assistant\".",
       "Volta a inserir a tua senha. Se 2FA está ligado, também o código atual 6 dígitos.",
-      "Clique em Gerar token. Aparece uma única vez: copie-o imediatamente."
+      "Clique em Gerar token. O token aparece uma única vez: copie-o de imediato."
     ],
     "generateCli": "Da linha de comando:",
     "useTitle": "Usar um token",
@@ -310,7 +310,7 @@
     "intro": "Dois caminhos para o TLS:",
     "items": [
       "Proxy inverso (recomendado). Terminar TLS em Nginx / Caddy / Traefik e encaminhar HTTP na porta 8008 para o processo Flask. Excertos abaixo.",
-      "HTTPS direto na AppImage. Configure um certificado com POST /api/ssl/configure (interface: Definições → SSL). HTTPS usa gevent.pywsgi com o handler padrão; flask-sock trata WebSocket para WSS. Os caminhos ficam na configuração SSL."
+      "HTTPS direto na AppImage. Configure um certificado com POST /api/ssl/configure (interface: Administrador → Segurança → SSL/HTTPS). HTTPS usa gevent.pywsgi com o handler padrão; flask-sock trata WebSocket para WSS. Os caminhos ficam na configuração SSL."
     ],
     "calloutTitle": "Limitações diretas do HTTPS",
     "calloutBody": "O Monitor pode usar um certificado gerido pelo Proxmox ou ficheiros próprios. Não emite certificados ACME. Para emissão e renovação automáticas, use Proxmox ACME ou um proxy Caddy, Traefik ou Nginx e garanta que os certificados renovados são carregados."
diff --git a/web/messages/pt/docs/monitor/ai-assistant.json b/web/messages/pt/docs/monitor/ai-assistant.json
index d1bb67d3..fe794bc7 100644
--- a/web/messages/pt/docs/monitor/ai-assistant.json
+++ b/web/messages/pt/docs/monitor/ai-assistant.json
@@ -9,7 +9,7 @@
   },
   "header": {
     "title": "Assistente de IA",
-    "description": "A reformulação opcional por IA transforma as notificações elegíveis em mensagens claras, com detalhe por canal, treze idiomas na interface, prompts personalizados, biblioteca comunitária pública e contexto adicional. Os relatórios backup_complete e backup_fail não passam pela IA.",
+    "description": "A reformulação opcional por IA transforma as notificações elegíveis em mensagens claras, com detalhe por canal, treze idiomas na interface, prompts personalizados, biblioteca comunitária pública e contexto adicional. Os relatórios backup_complete e backup_fail nunca são reformulados; com as sugestões de IA ativadas, a IA apenas acrescenta uma explicação quando uma cópia reporta erros.",
     "section": "Monitor do ProxMenux"
   },
   "intro": {
@@ -83,7 +83,7 @@
     "intro1": "Os fornecedores comerciais cobram por token. Um token corresponde aproximadamente a quatro caracteres ingleses ou três quartos de uma palavra. A frase inglesa \"Backup completed on storage local-bak\" tem cerca de oito tokens. Dez linhas de log podem ocupar 200–400 tokens, conforme o conteúdo técnico.",
     "intro2": "Duas coisas são cobradas em cada chamada:",
     "items": [
-      "Tokens de entrada: prompt de sistema e mensagem do utilizador, com gravidade, título, texto do modelo e contexto adicional. A quantidade depende do prompt e do evento. Cada canal reformulado pode consumir os seus tokens; os relatórios de cópia concluída ou falhada não passam pela IA.",
+      "Tokens de entrada: prompt de sistema e mensagem do utilizador, com gravidade, título, texto do modelo e contexto adicional. A quantidade depende do prompt e do evento. Cada canal reformulado pode consumir os seus tokens; os relatórios de cópia concluída ou falhada só consomem tokens quando a IA explica erros da cópia.",
       "Tokens de saída: a resposta do modelo. O Monitor limita-os com max_tokens. O valor é um limite, não um consumo fixo: se o modelo produzir 250 tokens com limite de 1500, paga por 250."
     ],
     "capsIntro": "Estes são os limites aplicados pelo módulo de envio, definidos em AI_DETAIL_TOKENS em notification_templates.py:",
@@ -118,7 +118,7 @@
     "imageCaption": "O modal no aplicativo — seis cartões, um por provedor, com as mesmas descrições documentadas abaixo.",
     "groq": {
       "heading": "Groq",
-      "tagline": "Nível livre muito rápido e generoso (30 req/min). Ideal para começar.",
+      "tagline": "Muito rápido, com um nível gratuito generoso. Um bom lugar para começar.",
       "items": [
         "Chave API: console.groq.com/keys",
         "Exemplos da lista de modelos incluída: allam-2-7b, qwen/qwen3.8-27b, openai/gpt-oss-20b, openai/gpt-oss-120b.",
@@ -234,7 +234,7 @@
   },
   "detailLevel": {
     "heading": "Nível de detalhe por canal",
-    "intro": "Cada um dos quatro canais (Telegram, Discord, Gotify, Email) tem seu próprio nível de detalhes. Três valores estão disponíveis, mapeados para token caps de saída específicos e para instruções específicas no prompt padrão:",
+    "intro": "Cada um dos seis canais (Telegram, Discord, Gotify, Email, Pushover, Apprise) tem seu próprio nível de detalhes. Três valores estão disponíveis, mapeados para token caps de saída específicos e para instruções específicas no prompt padrão:",
     "headerLevel": "Nível",
     "headerLabel": "Rótulo UI",
     "headerCap": "Limite de saída",
@@ -261,7 +261,7 @@
     ],
     "defaultsIntro": "Predefinições na primeira instalação:",
     "defaults": [
-      "Telegram, Discord, Gotify — standard.",
+      "Telegram, Discord, Gotify, Pushover, Apprise — standard.",
       "Email — detailed. Email é o canal onde você normalmente quer a imagem completa para arquivo."
     ],
     "emailTitle": "Nível de detalhes de e- mail adiciona o original",
@@ -280,7 +280,7 @@
   },
   "templates": {
     "heading": "Uma nota sobre modelos",
-    "body1": "A IA recebe o texto já produzido pelo modelo, não dados brutos. Tipos como vm_start, auth_fail e health_degraded têm modelos em notification_templates.py. A IA reformula os textos elegíveis; backup_complete e backup_fail mantêm relatórios determinísticos sem IA.",
+    "body1": "A IA recebe o texto já produzido pelo modelo, não dados brutos. Tipos como vm_start, auth_fail e health_degraded têm modelos em notification_templates.py. A IA reformula os textos elegíveis; backup_complete e backup_fail mantêm relatórios determinísticos; com as sugestões de IA ativadas, a IA apenas acrescenta uma explicação quando a cópia reporta erros.",
     "body2": "Os nomes de máquina e VMID já são preenchidos pelo modelo antes da reformulação. Com IA desativada, o texto do modelo é enviado diretamente. A página Notificações documenta todo o processo de envio e o que acontece antes desta etapa."
   },
   "privacy": {
diff --git a/web/messages/pt/docs/monitor/api-reference.json b/web/messages/pt/docs/monitor/api-reference.json
index 91fb3a54..2737ed8e 100644
--- a/web/messages/pt/docs/monitor/api-reference.json
+++ b/web/messages/pt/docs/monitor/api-reference.json
@@ -76,7 +76,7 @@
       {
         "endpoint": "/api/system-info",
         "method": "GET",
-        "use": "Dados alargados usados no cabeçalho: métricas gerais e hora de arranque."
+        "use": "Informação leve do cabeçalho: nome do host, tempo de atividade e estado de saúde em cache. Público, sem necessidade de token."
       },
       {
         "endpoint": "/api/hardware",
@@ -401,7 +401,7 @@
       {
         "endpoint": "/api/logs",
         "method": "GET",
-        "use": "Entradas filtradas no diário. Consulta: ?level=&service=&limit=."
+        "use": "Entradas filtradas no diário. Consulta: ?priority=&service=&limit=&since_days= (priority 0-7, since_days até 90)."
       },
       {
         "endpoint": "/api/logs/download",
@@ -411,7 +411,7 @@
       {
         "endpoint": "/api/events",
         "method": "GET",
-        "use": "Fluxo de eventos internos — o mesmo que alimenta notificações."
+        "use": "Tarefas e eventos recentes do cluster Proxmox a partir de pvesh get /cluster/tasks. Consulta: ?limit= (predefinição 50)."
       },
       {
         "endpoint": "/api/task-log/",
@@ -462,7 +462,7 @@
       {
         "endpoint": "/api/notifications/send",
         "method": "POST",
-        "use": "Emite um evento personalizado. Corpo: '{'\"event_type\":\"custom\",\"severity\":\"WARNING\",\"title\":\"...\",\"body\":\"...\",\"data\":'{''}''}'."
+        "use": "Emite um evento personalizado. Corpo: '{'\"event_type\":\"custom\",\"severity\":\"WARNING\",\"title\":\"...\",\"message\":\"...\",\"data\":'{''}''}'."
       },
       {
         "endpoint": "/api/notifications/test-ai",
diff --git a/web/messages/pt/docs/monitor/architecture.json b/web/messages/pt/docs/monitor/architecture.json
index 20b28ae7..23ef6b17 100644
--- a/web/messages/pt/docs/monitor/architecture.json
+++ b/web/messages/pt/docs/monitor/architecture.json
@@ -64,7 +64,7 @@
     "items": [
       "User=root: o serviço usa privilégios root para SMART, pvesh e gestão ZFS. Os processos do terminal web herdam estas permissões.",
       "Restart=on-failure com espera de dez segundos: o serviço reinicia automaticamente após uma saída com erro.",
-      "After=network.target: ordena o arranque após esse alvo; não garante IP atribuído, DNS ou ligação externa."
+      "After=network.target pve-cluster.service: ordena o arranque após o alvo de rede e o sistema de ficheiros do cluster Proxmox; não garante IP atribuído, DNS ou ligação externa."
     ],
     "inspectTitle": "Consultar a unidade atual"
   },
diff --git a/web/messages/pt/docs/monitor/dashboard/hardware.json b/web/messages/pt/docs/monitor/dashboard/hardware.json
index 2c13dd4e..0fd5dfd4 100644
--- a/web/messages/pt/docs/monitor/dashboard/hardware.json
+++ b/web/messages/pt/docs/monitor/dashboard/hardware.json
@@ -168,7 +168,7 @@
   "ups": {
     "heading": "Status do no-break",
     "subHeading": "(condicional)",
-    "body": "Aparece com um servidor NUT (Network UPS Tools) configurado e acessível. Mostra estado (rede / bateria / carregamento / bateria fraca), carga da bateria, autonomia estimada, percentagem de carga suportada, tensão de entrada, modelo e firmware. Alimenta também Segurança e certificados do monitor de saúde: a passagem a bateria é assinalada imediatamente."
+    "body": "Aparece com um servidor NUT (Network UPS Tools) configurado e acessível. Mostra estado (rede / bateria / carregamento / bateria fraca), carga da bateria, autonomia estimada, percentagem de carga suportada, tensão de entrada, modelo e firmware."
   },
   "dataCollected": {
     "heading": "Como os dados são coletados",
diff --git a/web/messages/pt/docs/monitor/dashboard/index.json b/web/messages/pt/docs/monitor/dashboard/index.json
index 18d79d2d..2afbd18b 100644
--- a/web/messages/pt/docs/monitor/dashboard/index.json
+++ b/web/messages/pt/docs/monitor/dashboard/index.json
@@ -50,7 +50,7 @@
       },
       {
         "name": "Terminal",
-        "owns": "Shell do anfitrião ou de qualquer VM/CT no navegador, com xterm.js sobre WebSockets."
+        "owns": "Shell no navegador no host Proxmox, com xterm.js sobre WebSockets. As consolas LXC abrem a partir do separador VMs e LXCs."
       },
       {
         "name": "Segurança",
diff --git a/web/messages/pt/docs/monitor/dashboard/security.json b/web/messages/pt/docs/monitor/dashboard/security.json
index 01727fc5..d37ebafc 100644
--- a/web/messages/pt/docs/monitor/dashboard/security.json
+++ b/web/messages/pt/docs/monitor/dashboard/security.json
@@ -183,7 +183,7 @@
       },
       {
         "card": "SSL/HTTPS",
-        "endpoint": "/api/auth/ssl/*",
+        "endpoint": "/api/ssl/*",
         "source": "openssl x509 em /etc/pve/local/pve-ssl.pem + /etc/proxmenux/ssl_config.json."
       },
       {
diff --git a/web/messages/pt/docs/monitor/dashboard/settings.json b/web/messages/pt/docs/monitor/dashboard/settings.json
index fbad5728..f69855d2 100644
--- a/web/messages/pt/docs/monitor/dashboard/settings.json
+++ b/web/messages/pt/docs/monitor/dashboard/settings.json
@@ -41,7 +41,7 @@
     "heading": "Unidades de rede",
     "imageAlt": "Cartão Unidades de rede com o seletor de apresentação em Bytes",
     "imageCaption": "Escolha entre bits por segundo e bytes por segundo para cada taxa de rede exibida no painel.",
-    "body": "Mostra a velocidade em bits por segundo (Mbps/Gbps) ou bytes por segundo (MB/s/GB/s). Bits são a predefinição, como nas especificações de placas e fornecedores; as transferências de ficheiros usam frequentemente bytes. Gráficos, etiquetas e ajudas mudam de imediato sem recarregar."
+    "body": "Escolha como a velocidade de rede é mostrada no painel: bits por segundo (Mbps / Gbps) ou bytes por segundo (MB/s / GB/s). Bytes é a predefinição, como a maioria das ferramentas de transferência de ficheiros; bits é como os fabricantes de placas de rede e os ISP etiquetam os seus produtos. A escolha fica guardada no navegador, por isso cada navegador mantém a sua. Afeta todos os gráficos, etiquetas e ajudas que mostram a taxa de rede, de imediato e sem recarregar."
   },
   "health": {
     "heading": "Monitor de Saúde",
@@ -203,7 +203,7 @@
     "heading": "Detecção de atualização LXC",
     "imageAlt": "Cartão de deteção de atualizações LXC com interruptor para procurar pacotes pendentes em contentores Debian, Ubuntu e Alpine ativos",
     "imageCaption": "Interruptor da análise periódica com apt list --upgradable / apk list -u nos CT ativos. Vem ligado. A opção correspondente em Notificações → Serviços aparece com a deteção ativa.",
-    "intro": "O interruptor entre Limites e Notificações controla a análise LXC. Ativo, consulta os gestores de pacotes dos CT em execução; Hardware mostra os resultados e alimenta lxc_updates_available. Desativado, para as chamadas pct exec e remove entradas LXC de managed_installs.json; /api/managed-installs deixa de mostrar esse estado sem esperar 24 h.",
+    "intro": "O interruptor entre Limites e Notificações controla a análise LXC. Ativo, consulta os gestores de pacotes dos CT em execução; o separador VMs e LXCs mostra os resultados nos contadores e alimenta lxc_updates_available. Desativado, para as chamadas pct exec e remove entradas LXC de managed_installs.json; /api/managed-installs deixa de mostrar esse estado sem esperar 24 h.",
     "whatRunsTitle": "O que a análise executa",
     "whatRunsIntro": "Para cada CT no estado running com um gestor de pacotes suportado:",
     "whatRunsItems": [
@@ -249,7 +249,7 @@
     "body1": "Aqui se ativam as notificações do ProxMenux Monitor e a reformulação com IA. Ativar notificações inicia o envio em segundo plano, regista um destino webhook Proxmox VE para integrar os seus eventos e abre os canais: Telegram, Discord, email, Gotify e outros. A reformulação com IA está numa secção avançada expansível do mesmo painel.",
     "body2": "As opções são explicadas em páginas próprias: canais, controlos por evento, mensagens formatadas, nome apresentado, envio com eliminação de duplicados, intervalo, agregação e horas de silêncio, webhook PVE, fornecedores de IA e modos de instruções:",
     "items": [
-      "Notificações: configuração dos canais (Telegram, Discord, Gotify, email e palavras-passe de aplicação Gmail / Microsoft, ntfy, Slack, Teams e webhook genérico), categorias de eventos, mensagens formatadas, nome apresentado, envio, webhook PVE, histórico e API.",
+      "Notificações: configuração dos canais (Telegram, Discord, Gotify, email e palavras-passe de aplicação Gmail / Microsoft, Pushover e Apprise para ntfy, Slack, Teams e outros serviços), categorias de eventos, mensagens formatadas, nome apresentado, envio, webhook PVE, histórico e API.",
       "Assistente de IA: fornecedores (OpenAI, Anthropic, Gemini, Groq, OpenRouter, Ollama), modelos, instruções predefinidas ou personalizadas, idioma de saída, detalhe por canal e sugestões de IA."
     ]
   },
@@ -294,8 +294,8 @@
       },
       {
         "card": "Unidades de rede",
-        "endpoint": "/api/settings",
-        "source": "Guardado na tabela SQLite de definições do painel."
+        "endpoint": "localStorage",
+        "source": "Preferência do navegador proxmenux-network-unit."
       },
       {
         "card": "Durações do Health Monitor",
@@ -304,7 +304,7 @@
       },
       {
         "card": "Exclusões de armazenamento / interface",
-        "endpoint": "/api/storage/exclusions, /api/network/exclusions",
+        "endpoint": "/api/health/storage-exclusions, /api/health/interface-exclusions",
         "source": "Tabelas de SQLite excluded_storages e excluded_interfaces."
       },
       {
diff --git a/web/messages/pt/docs/monitor/dashboard/storage.json b/web/messages/pt/docs/monitor/dashboard/storage.json
index 9da13a25..a6096624 100644
--- a/web/messages/pt/docs/monitor/dashboard/storage.json
+++ b/web/messages/pt/docs/monitor/dashboard/storage.json
@@ -10,7 +10,7 @@
   },
   "intro": {
     "title": "Apoiado por três fontes",
-    "body": "Os armazenamentos Proxmox vêm de pvesm status, ZFS de zpool status, discos de lsblk + smartctl e campos NVMe de nvme. O separador atualiza-se aproximadamente a cada 60 segundos; abrir o detalhe pede uma leitura SMART."
+    "body": "Os armazenamentos Proxmox vêm de pvesm status, ZFS de zpool status, discos de lsblk + smartctl e campos NVMe de nvme. O separador atualiza-se aproximadamente a cada 30 segundos; abrir o detalhe pede uma leitura SMART."
   },
   "thresholds": {
     "title": "Cor do estado e limiares aplicados aqui",
diff --git a/web/messages/pt/docs/monitor/dashboard/system-logs.json b/web/messages/pt/docs/monitor/dashboard/system-logs.json
index e852138e..b6598811 100644
--- a/web/messages/pt/docs/monitor/dashboard/system-logs.json
+++ b/web/messages/pt/docs/monitor/dashboard/system-logs.json
@@ -18,7 +18,7 @@
       "Total de entradas — número de registos no intervalo do filtro ativo.",
       "Erros — número de entradas com gravidade ≤ 3 (err / crit / alert / emerg).",
       "Avisos — número de entradas com gravidade 4 (warning).",
-      "Cópias de segurança — número de entradas de tarefas vzdump / PBS no mesmo intervalo."
+      "Cópias de segurança: número de arquivos de backup nos armazenamentos de backup configurados."
     ]
   },
   "subtabs": {
@@ -26,10 +26,10 @@
     "logsTitle": "Registros",
     "logsIntro": "O journal do sistema, obtido no servidor com journalctl. Filtros disponíveis na barra de ferramentas:",
     "logsFilters": [
-      "Gravidade — emerg / alert / crit / err / warning / notice / info / debug, ou qualquer combinação.",
-      "Tempo de alcance — último 5 min / 15 min / 1 h / 6 h / 24 h / 7 d / custom.",
-      "Pesquisa livre — subcadeia ou expressão regular (journalctl --grep).",
-      "Filtro por unidade — restringe a uma unidade systemd específica (pveproxy.service, nginx.service, …)."
+      "Nível: Todos os níveis, Erro, Aviso ou Informações.",
+      "Intervalo de tempo: 1 dia / 3 dias / 1 semana / 2 semanas / 1 mês, ou um número personalizado de dias (até 90).",
+      "Pesquisa livre: subcadeia sem distinção entre maiúsculas e minúsculas sobre mensagem, serviço, PID, nome do host e unidade, aplicada no navegador.",
+      "Filtro por serviço: restringe a um serviço (o SYSLOG_IDENTIFIER do diário)."
     ],
     "logsRowsAfter": "Cada linha mostra a hora, o crachá de gravidade, a unidade de origem e a mensagem. Mensagens longas colapsam com uma opção \"mostrar mais\". A ação Download agrupa o filtro atual em um único arquivo .txt via GET /api/logs/download — útil quando você deseja compartilhar uma fatia de diário com alguém.",
     "logDetailsModalTitle": "Janela Detalhes do registo",
@@ -50,10 +50,10 @@
     "maxLevelStoreTitle": "Journald MaxLevelStore",
     "maxLevelStoreBody": "Numa instalação nova do Proxmox, journald utiliza por predefinição MaxLevelStore=warning, que descarta mensagens info. O Monitor deteta isto no arranque e acrescenta um ficheiro de configuração complementar (/etc/systemd/journald.conf.d/proxmenux-loglevel.conf) com o limiar info, para guardar mensagens informativas e mais graves. Isto não inclui debug.",
     "backupsTitle": "Cópias de segurança",
-    "backupsBody": "Histórico de tarefas do Proxmox limitado às cópias de segurança. Uma linha por tarefa (vzdump, transferências PBS, recolha de dados não utilizados e verificação), com estado (OK / WARNINGS / ERROR), sistema convidado envolvido, armazenamento de origem, duração e UPID. Clique numa linha para carregar o registo completo através de GET /api/task-log/<upid>: os mesmos dados de Datacenter → Tasks, limitados às cópias de segurança.",
+    "backupsBody": "Os arquivos de backup em cada armazenamento com capacidade de backup (dir, NFS, CIFS, PBS), lidos com pvesh get /nodes/localhost/storage/<id>/content. Os cartões no topo mostram o número de VMs e LXC e o tamanho total; abaixo, uma linha por arquivo com tipo, armazenamento, tamanho e data. A restauração está na interface web do Proxmox.",
     "notificationsTitle": "Notificações",
-    "notificationsBody1": "Todas as notificações enviadas pelo Monitor: Telegram, Discord, e-mail, Gotify, ntfy, Slack, Teams e webhook. Cada linha mostra data e hora, canal, tipo de evento, gravidade, título e corpo gerados e, se a IA estiver ativa, um controlo para comparar a sua reformulação com o original.",
-    "notificationsBody2": "Utilize este separador para verificar se um canal entrega notificações e comparar a reformulação da IA com o modelo de base. Os canais configuram-se na página detalhada Notificações."
+    "notificationsBody1": "Atividade de notificações do Proxmox: linhas do diário dos serviços Proxmox que mencionam notificações, email, webhooks ou alertas, os destinos em /etc/pve/notifications.cfg e as tarefas de backup da lista de tarefas do cluster. As linhas de tarefas de backup permitem descarregar o registo completo da tarefa. O histórico do que o próprio Monitor enviou está em Configurações → Notificações → Histórico de notificações.",
+    "notificationsBody2": "Utilize este separador para verificar o que o próprio sistema de notificações do Proxmox está a fazer. Os canais do Monitor configuram-se na página detalhada Notificações."
   },
   "dataCollected": {
     "heading": "Como os dados são coletados",
@@ -64,7 +64,7 @@
       {
         "subtab": "Registos (filtro em direto)",
         "endpoint": "/api/logs",
-        "source": "journalctl --output json --since <range> com filtros de gravidade, unidade e pesquisa aplicados no servidor."
+        "source": "O separador pede /api/logs?since_days=N; os filtros de nível, serviço e texto são aplicados no navegador. A API também aceita priority e service, que o separador ainda não usa."
       },
       {
         "subtab": "Transferir",
@@ -74,17 +74,17 @@
       {
         "subtab": "Cópias de segurança",
         "endpoint": "/api/backups",
-        "source": "Histórico de tarefas PVE filtrado por vzdump, transferências PBS, recolha de dados não utilizados e verificação."
+        "source": "Arquivos de backup listados de cada armazenamento de backup com pvesh get /nodes/localhost/storage/<id>/content."
       },
       {
-        "subtab": "Detalhes de uma tarefa de cópia de segurança",
+        "subtab": "Registo de tarefa de backup (Notificações)",
         "endpoint": "/api/task-log/<upid>",
         "source": "Registo completo da tarefa em texto simples, lido de /var/log/pve/tasks/<index>/<upid>."
       },
       {
-        "subtab": "Histórico de notificações",
-        "endpoint": "/api/notifications/history",
-        "source": "Tabela SQLite notification_history alimentada pelo ciclo de envio."
+        "subtab": "Notificações",
+        "endpoint": "/api/notifications",
+        "source": "journalctl --grep sobre unidades Proxmox, /etc/pve/notifications.cfg e tarefas de backup de pvesh get /cluster/tasks."
       }
     ],
     "apiIntro": "O filtro em direto e as transferências também estão acessíveis através da API:",
diff --git a/web/messages/pt/docs/monitor/dashboard/system-overview.json b/web/messages/pt/docs/monitor/dashboard/system-overview.json
index 41cc966f..3d93e773 100644
--- a/web/messages/pt/docs/monitor/dashboard/system-overview.json
+++ b/web/messages/pt/docs/monitor/dashboard/system-overview.json
@@ -46,7 +46,7 @@
     "thresholdsIntro": "Anéis, barras e minigráficos usam a mesma escala:  verde abaixo de Aviso,  âmbar entre Aviso e Crítico,  vermelho a partir de Crítico. Valores recomendados incluídos no ProxMenux:",
     "thresholdsItems": [
       "Utilização CPU — aviso 85 %, crítico 95 %.",
-      "Memória — aviso 85 %, crítico 95 %. O swap também ativa Crítico a 5 % de utilização; um host Proxmox saudável deverá utilizá-lo raramente.",
+      "Memória: aviso 85 %, crítico 95 %. O swap só ativa Crítico quando a utilização de swap está acima de 80 % e a RAM disponível abaixo de 15 %.",
       "Temperatura CPU — aviso 80 °C, crítico 90 °C."
     ],
     "thresholdsOutro": "Os valores são configuráveis por host. Definições → Limiares do monitor de saúde centraliza os ajustes e explica como os adaptar.",
@@ -58,7 +58,7 @@
     "intro": "Os cartões Utilização CPU e Memória abrem uma lista ordenável dos 25 processos principais: por utilização CPU no primeiro caso e por memória residente no segundo.",
     "listTitle": "A janela da lista",
     "listItems": [
-      "Atualização automática — a lista atualiza a cada 5 segundos enquanto a janela está aberta e deixa de consultar ao fechar.",
+      "Atualização automática — a lista atualiza a cada 3 segundos enquanto a janela está aberta e deixa de consultar ao fechar.",
       "Filtro — a pesquisa limita a lista por comando, utilizador ou PID.",
       "Barra inline — a coluna métrica primária desenha uma barra pequena escalonada para o valor mais alto da lista filtrada, então a classificação permanece visível mesmo quando nenhum processo está perto de 100 %.",
       "Disposição móvel — abaixo de 640 px, PID e Utilizador ficam ocultos, para Comando, CPU % e Memória caberem sem deslocamento horizontal."
@@ -79,7 +79,7 @@
   },
   "middle": {
     "heading": "Centro: gráficos de métricas do nó",
-    "body1": "Sob os cartões, NodeMetricsCharts apresenta históricos de CPU, memória e E/S de disco do armazenamento RRD do Proxmox através de /api/node/metrics. O seletor oferece 1 hora / 24 horas / 7 dias / 30 dias / 1 ano. A resolução diminui em períodos maiores para manter o gráfico fluido.",
+    "body1": "Sob os cartões, NodeMetricsCharts apresenta históricos de CPU, memória e E/S de disco do armazenamento RRD do Proxmox através de /api/node/metrics. O seletor oferece 1 hora / 24 horas / 7 dias / 30 dias. A resolução diminui em períodos maiores para manter o gráfico fluido.",
     "body2": "Os mesmos gráficos da interface web do Proxmox para um nó, integrados no tema escuro do Monitor e alinhados com os outros painéis."
   },
   "bottom": {
diff --git a/web/messages/pt/docs/monitor/dashboard/vms-lxcs.json b/web/messages/pt/docs/monitor/dashboard/vms-lxcs.json
index 5931276b..fd5c4653 100644
--- a/web/messages/pt/docs/monitor/dashboard/vms-lxcs.json
+++ b/web/messages/pt/docs/monitor/dashboard/vms-lxcs.json
@@ -117,7 +117,7 @@
       "Destino — armazenamento (PBS-Cloud, PBS-Local, NFS-Backup, …), colorido conforme o estado.",
       "Tamanho — tamanho final em disco da cópia."
     ],
-    "backupsOutro": "+ Criar cópia de segurança inicia uma cópia no armazenamento marcado “Backup target” no Proxmox. O restauro faz-se na interface web Proxmox; o Monitor mostra as cópias recentes do sistema e permite criar novas.",
+    "backupsOutro": "O botão + Criar backup, no canto superior direito, abre uma janela para escolher o armazenamento, o modo (snapshot / suspend / stop), a proteção e as notas antes de iniciar um vzdump. O restauro faz-se na interface web Proxmox; o Monitor mostra se o sistema tem cópias recentes, mas não o fluxo de recuperação.",
     "firewallTitle": "Página 7 — Firewall",
     "firewallIntro": "Lê o registo do firewall Proxmox de cada sistema diretamente do host, sem serviço adicional nem consultas periódicas. O separador está sempre presente; o conteúdo depende da ativação do firewall e de uma regra registar pacotes.",
     "firewallItems": [
diff --git a/web/messages/pt/docs/monitor/health-monitor.json b/web/messages/pt/docs/monitor/health-monitor.json
index 7b24f9d7..19baaa2b 100644
--- a/web/messages/pt/docs/monitor/health-monitor.json
+++ b/web/messages/pt/docs/monitor/health-monitor.json
@@ -153,8 +153,8 @@
     "pillTitle": "O indicador reflete a categoria mais grave",
     "pillBody": "A cor do cabeçalho reflete a gravidade máxima: CRITICAL → vermelho, senão WARNING → amarelo, senão INFO → azul, senão verde. A mesma lógica controla o ponto do favicon e o distintivo PWA.",
     "updateNowTitle": "Atualizar agora no cabeçalho Visão geral",
-    "updateNowBody1": "Se Atualizações do sistema indicar atualizações pendentes, surge Atualizar agora junto do indicador. A janela executa o fluxo de ProxMenux → Settings post-install Proxmox → Proxmox System Update: limpeza de repositórios, apt full-upgrade através de update-pve-safe.sh, reconstrução DKMS dos controladores geridos com um novo kernel, autoremove/autoclean e pedido de reinício se o kernel mudar. O progresso aparece em direto.",
-    "updateNowBody2": "No fim, fechar a janela chama GET /api/health/details?refresh=1 e invalida a cache do endpoint. As atualizações pendentes e a gravidade são recalculadas a partir do novo estado dos pacotes; o distintivo desaparece sem esperar pelo próximo ciclo. Se o anfitrião já estiver atualizado, o botão fica oculto."
+    "updateNowBody1": "Se Atualizações do sistema indicar atualizações pendentes, surge Atualizar agora junto do indicador. A janela executa o fluxo de ProxMenux → Utilitários e ferramentas → Atualização do sistema Proxmox: limpeza de repositórios, apt full-upgrade através de update-pve-safe.sh, reconstrução DKMS dos controladores geridos com um novo kernel, autoremove/autoclean e pedido de reinício se o kernel mudar. O progresso aparece em direto.",
+    "updateNowBody2": "No fim, fechar a janela chama GET /api/health/full?refresh=1 e invalida a cache do endpoint. As atualizações pendentes e a gravidade são recalculadas a partir do novo estado dos pacotes; o distintivo desaparece sem esperar pelo próximo ciclo. Se o anfitrião já estiver atualizado, o botão fica oculto."
   },
   "dismiss": {
     "heading": "Silenciar alertas e definir a duração da supressão",
diff --git a/web/messages/pt/docs/monitor/index.json b/web/messages/pt/docs/monitor/index.json
index 2d6eef39..77b90b79 100644
--- a/web/messages/pt/docs/monitor/index.json
+++ b/web/messages/pt/docs/monitor/index.json
@@ -113,13 +113,13 @@
   "api": {
     "heading": "REST API e integrações",
     "intro": "Os endpoints REST expõem dados de monitorização JSON por HTTP/HTTPS para Homepage, Home Assistant, Uptime Kuma e scripts. Grafana pode usar o exportador de texto Prometheus separado em /api/prometheus. As chamadas podem usar curl.",
-    "tokens": "Tokens API de longa duração (365 dias) são gerados em Configurações → API Tokens de acesso ou via POST /api/auth/generate-api-token.",
+    "tokens": "Tokens API de longa duração (365 dias) são gerados em Administrador → Segurança → Tokens de API ou via POST /api/auth/generate-api-token.",
     "bearer": "Envie tokens com Authorization: Bearer …. /api/health também espera o cabeçalho Bearer, pelo que os testes de disponibilidade precisam de um token read_only. Em /api/auth/* existem tanto rotas públicas de configuração/início/estado como rotas protegidas de contas e tokens; nem todas são anónimas.",
     "catalog": "O catálogo de endpoints, rotação de tokens e segurança estão na Referência da API; exemplos para Homepage, Home Assistant, Grafana, Uptime Kuma e cURL estão em Integrações."
   },
   "serviceControl": {
     "heading": "Controle de serviço",
-    "intro": "Diariamente, o Monitor é gerenciado exatamente como qualquer outro serviço systemd. Ele também é exposto como duas entradas dentro da TUI ProxMenux em Configurações:",
+    "intro": "Diariamente, o Monitor é gerenciado exatamente como qualquer outro serviço systemd. Ele também é exposto como três entradas dentro da TUI ProxMenux em Configurações:",
     "codeComment": "# Controle manual",
     "footer": "Consulte Configurações → ProxMenux Monitor para alternar no menu e fluxo de verificação de status."
   },
diff --git a/web/messages/pt/docs/monitor/integrations.json b/web/messages/pt/docs/monitor/integrations.json
index a1c33729..7d4c54b0 100644
--- a/web/messages/pt/docs/monitor/integrations.json
+++ b/web/messages/pt/docs/monitor/integrations.json
@@ -20,13 +20,13 @@
     "heading": "Autenticação: a única coisa que cada receita precisa",
     "intro": "A maioria dos endpoints usados por essas integrações são autenticados. Tem duas maneiras de satisfazer esse requisito.",
     "optAtitle": "Opção A — API token (recomendado para integrações)",
-    "optAbody1": "Abra Definições → Segurança → Tokens API. Clique em Gerar token, dê-lhe um nome como homepage, home-assistant ou prometheus e copie-o: é apresentado uma única vez. Expira ao fim de um ano por predefinição e pode ser revogado individualmente. Use-o em clientes externos ao navegador.",
+    "optAbody1": "Abra Administrador → Segurança → Tokens de API. Clique em Gerar novo token, dê-lhe um nome como homepage, home-assistant ou prometheus, introduza a sua senha (e o código 2FA, se estiver ativo), clique em Gerar token e copie-o: é apresentado uma única vez. Expira ao fim de um ano por predefinição e pode ser revogado individualmente. Use-o em clientes externos ao navegador.",
     "optAbody2": "A partir desse ponto cada pedido é apenas:",
     "optBtitle": "Opção B – fluxo de login (nome de usuário + senha)",
     "optBbody": "Útil para scripts que autenticam como um usuário humano. O token retornado é de curta duração; a maioria das integrações deve preferir a Opção A.",
     "outro": "O campo TOTP só é necessário com autenticação de dois fatores ativa. A rotação e revogação dos tokens, a política de palavras-passe e o registo de auditoria estão em Acesso e autenticação.",
     "httpsTitle": "Usando HTTPS em vez de HTTP",
-    "httpsIntro": "Cada receita abaixo usa http:// nas URLs para manter os exemplos curtos. Se você ativou TLS no Monitor (Configurações → Segurança → SSL/HTTPS), troque http:// para https:// em cada URL — essa é a única mudança. Duas notas específicas para certos instrumentos:",
+    "httpsIntro": "Cada receita abaixo usa http:// nas URLs para manter os exemplos curtos. Se você ativou TLS no Monitor (Administrador → Segurança → SSL/HTTPS), troque http:// para https:// em cada URL — essa é a única mudança. Duas notas específicas para certos instrumentos:",
     "httpsItems": [
       "Certificados autoassinados. A integração Home Assistant rest: verifica TLS por predefinição. Para um certificado autoassinado do Monitor, adicione verify_ssl: false a cada bloco REST junto de scan_interval:, ou importe a CA do Monitor para os certificados de confiança. O mesmo se aplica a outras ferramentas que verificam certificados.",
       "Prometheus já inclui scheme: https no exemplo abaixo. Ative ou mantenha https se TLS estiver ativo no Monitor."
@@ -43,7 +43,7 @@
     "basicTitle": "Elemento básico — sem autenticação",
     "basicIntro": "Use isso quando o ProxMenux Monitor estiver em uma rede confiável e você ainda não tiver habilitado a autenticação no lado Monitor. A entrada services.yaml mais simples possível:",
     "authedTitle": "Elemento autenticado",
-    "authedIntro": "Gere um token API em Configurações → Segurança → API Tokens no Monitor, copie-o e cole-o no cabeçalho Authorization abaixo — substitua o token de exemplo mostrado após Bearer pelo que você acabou de copiar:",
+    "authedIntro": "Gere um token API em Administrador → Segurança → Tokens de API no Monitor, copie-o e cole-o no cabeçalho Authorization abaixo — substitua o token de exemplo mostrado após Bearer pelo que você acabou de copiar:",
     "authedOutro": "Reinicie o Homepage para apresentar valores atualizados. Reutilize o token nos widgets que consultam a mesma instância do Monitor.",
     "multiTitle": "Configuração multi-widget — sistema, armazenamento, rede",
     "multiIntro": "Para mais detalhes, crie três cartões com endpoints diferentes: sistema, armazenamento e rede. Use o mesmo token para esta instância do Monitor.",
diff --git a/web/messages/pt/docs/monitor/notifications.json b/web/messages/pt/docs/monitor/notifications.json
index 4296dc37..8a8ab2b2 100644
--- a/web/messages/pt/docs/monitor/notifications.json
+++ b/web/messages/pt/docs/monitor/notifications.json
@@ -314,7 +314,7 @@
   },
   "aiRewrite": {
     "heading": "Reescrita IA opcional",
-    "body1": "Eventos elegíveis podem ser reescritos por um LLM. Resultados de cópias (backup_complete e backup_fail) ficam excluídos. IA está desativada por predefinição e, quando ativa, trabalha no thread de envio. Se a chamada falhar ou expirar, usa o texto original do modelo.",
+    "body1": "Eventos elegíveis podem ser reescritos por um LLM. Resultados de cópias (backup_complete e backup_fail) não são reformulados; com as sugestões de IA ativadas, a IA apenas acrescenta uma explicação quando uma cópia reporta erros. IA está desativada por predefinição e, quando ativa, trabalha no thread de envio. Se a chamada falhar ou expirar, usa o texto original do modelo.",
     "body2": "Seis fornecedores: OpenAI, Anthropic, Google Gemini, Groq, OpenRouter e Ollama. Cada canal escolhe detalhe (brief, standard, detailed), idioma, modo do prompt (default/custom) e prompt personalizado. Veja Assistente IA para configuração e exemplos.",
     "privacyTitle": "Nota de privacidade",
     "privacyBody": "Os pedidos IA podem incluir título, gravidade, texto, prompt e contexto: nomes de anfitrião, IP, utilizadores, erros e linhas do registo. Seguem para o endpoint escolhido. Ollama só permanece local com servidor e modelo ambos locais; servidores remotos ou modelos cloud enviam dados para fora do anfitrião. Para os manter na rede, desative a reescrita ou use endpoint e modelo locais controlados."
diff --git a/web/messages/pt/docs/security/ssl-letsencrypt.json b/web/messages/pt/docs/security/ssl-letsencrypt.json
index 3e8ba3c2..6ec75d26 100644
--- a/web/messages/pt/docs/security/ssl-letsencrypt.json
+++ b/web/messages/pt/docs/security/ssl-letsencrypt.json
@@ -12,13 +12,13 @@
   },
   "intro": {
     "title": "O que esta página cobre",
-    "body": "Como mudar o Monitor de HTTP para HTTPS no painel Configurações → Painel de segurança, o que cada fonte de certificado realmente aponta no disco, como a opção Proxmox se comporta quando o host tem um certificado Let's Encrypt, e como definir caminhos personalizados se você gerenciar certs fora do Proxmox."
+    "body": "Como mudar o Monitor de HTTP para HTTPS no painel Administrador → Segurança, o que cada fonte de certificado realmente aponta no disco, como a opção Proxmox se comporta quando o host tem um certificado Let's Encrypt, e como definir caminhos personalizados se você gerenciar certs fora do Proxmox."
   },
   "wheresetting": {
     "heading": "Onde se encontra a configuração",
-    "body": "Abra o Monitor e vá a Configurações → Segurança → HTTPS / SSL. O painel mostra HTTP ou HTTPS e, se HTTPS ainda estiver desativado, as fontes de certificados detetadas.",
-    "imageAlt": "Configurações → Segurança → Painel HTTPS / SSL no Monitor mostrando o estado HTTPS-off, o certificado Proxmox auto-detectado (sujeito, emissor, expiração) e a opção Use Custom Certificate",
-    "caption": "Painel Configurações → Segurança → HTTPS / SSL antes da ativação. O Monitor mostra o certificado utilizado pelo Proxmox e permite escolhê-lo com um clique."
+    "body": "Abra o Monitor e vá a Administrador → Segurança → SSL/HTTPS. O painel mostra HTTP ou HTTPS e, se HTTPS ainda estiver desativado, as fontes de certificados detetadas.",
+    "imageAlt": "Painel Administrador → Segurança → SSL/HTTPS no Monitor mostrando o estado HTTPS-off, o certificado Proxmox auto-detectado (sujeito, emissor, expiração) e a opção Use Custom Certificate",
+    "caption": "Painel Administrador → Segurança → SSL/HTTPS antes da ativação. O Monitor mostra o certificado utilizado pelo Proxmox e permite escolhê-lo com um clique."
   },
   "twoways": {
     "heading": "Duas maneiras de ativar o HTTPS",
@@ -117,7 +117,7 @@
   },
   "switchToHttps": {
     "heading": "Mudar o monitor para HTTPS",
-    "bodyRich": "Quando /etc/pve/local/pveproxy-ssl.pem estiver assinado por Let's Encrypt, abra Configurações → Segurança → HTTPS / SSL, confirme que o emissor é Let's Encrypt e não a CA local Proxmox, e clique em Usar certificado Proxmox. O Monitor reinicia e o próximo carregamento utiliza HTTPS em 8008 com uma cadeia de confiança pública. As renovações ACME seguintes são validadas e selecionadas na próxima nova ligação TLS, sem consultas periódicas nem reinício por renovação. Atualizar certificado continua disponível em Segurança para diagnóstico e recuperação."
+    "bodyRich": "Quando /etc/pve/local/pveproxy-ssl.pem estiver assinado por Let's Encrypt, abra Administrador → Segurança → SSL/HTTPS, confirme que o emissor é Let's Encrypt e não a CA local Proxmox, e clique em Usar certificado Proxmox. O Monitor reinicia e o próximo carregamento utiliza HTTPS em 8008 com uma cadeia de confiança pública. As renovações ACME seguintes são validadas e selecionadas na próxima nova ligação TLS, sem consultas periódicas nem reinício por renovação. Atualizar certificado continua disponível em Segurança para diagnóstico e recuperação."
   },
   "custom": {
     "heading": "Certificado personalizado — quando usá-lo",
diff --git a/web/messages/pt/docs/settings/beta-program.json b/web/messages/pt/docs/settings/beta-program.json
index 641546f6..4cc2daa8 100644
--- a/web/messages/pt/docs/settings/beta-program.json
+++ b/web/messages/pt/docs/settings/beta-program.json
@@ -51,7 +51,7 @@
     "intro": "Ambas as direções passam pelo mesmo instalador — elas não são fluxos de desinstalação e reinstalação de casos especiais. Cada direção simplesmente executa o próprio instalador do canal, que é projetado para ser idempotente e seguro para ser reexecutado em uma instalação existente. Consequências práticas:",
     "items": [
       "Sua configuração existente, login ProxMenux Monitor (auth.json), canais de notificação, registro pós-instalação, limiares personalizados — nenhum deles é tocado. O interruptor de canal só troca a árvore de scripts e o binário AppImage.",
-      "Um Monitor ativo antes da mudança continua ativo depois; um que estava desativado continua desativado.",
+      "O instalador reinicia o serviço Monitor durante a mudança, por isso um Monitor que você tinha desativado volta a executar. Desative-o novamente nas Definições, se necessário.",
       "ProxMenux Monitor auth.json, API tokens e o segredo JWT são preservados através de mudanças de canal. As sessões não são encerradas.",
       "Você pode inverter os canais quantas vezes quiser — o diálogo irá aceitá-lo toda vez e repetir o instalador. Não é necessário reiniciar o Proxmox.",
       "A mudança de canal substitui os scripts do ProxMenux e não pode ser executada no terminal do navegador do Monitor: o PTY seria encerrado durante a atualização e o diálogo ficaria bloqueado. Nesse terminal aparece antes uma mensagem com o comando oficial de uma linha do canal escolhido. Execute-o por SSH ou na consola do anfitrião Proxmox."
diff --git a/web/messages/pt/docs/settings/index.json b/web/messages/pt/docs/settings/index.json
index 4ae7a60c..58c7145f 100644
--- a/web/messages/pt/docs/settings/index.json
+++ b/web/messages/pt/docs/settings/index.json
@@ -60,6 +60,12 @@
         "description": "Mude entre Stable (main) e Beta (develop) nos dois sentidos a partir de uma única janela. Executa o instalador oficial do canal escolhido; é idempotente e está sempre visível.",
         "badge": "Sempre disponível"
       },
+      {
+        "icon": "Languages",
+        "href": "/docs/settings/change-language",
+        "title": "Alterar idioma",
+        "description": "Muda a interface do ProxMenux entre oito idiomas. A escolha é guardada em config.json e o menu recarrega no novo idioma."
+      },
       {
         "icon": "Info",
         "href": "/docs/settings/show-version-information",
@@ -70,7 +76,7 @@
         "icon": "Trash2",
         "href": "/docs/settings/uninstall-proxmenux",
         "title": "Desinstalar ProxMenux",
-        "description": "Remova ProxMenux e (opcionalmente) suas dependências. Também restaura backups /root/.bashrc e /etc/motd criados no momento da instalação."
+        "description": "Remova o ProxMenux e (opcionalmente) as suas dependências. Também restaura os backups de /root/.bashrc e /etc/motd criados pela pós-instalação."
       }
     ]
   },
diff --git a/web/messages/pt/docs/settings/proxmenux-monitor.json b/web/messages/pt/docs/settings/proxmenux-monitor.json
index 20ecc680..3398a451 100644
--- a/web/messages/pt/docs/settings/proxmenux-monitor.json
+++ b/web/messages/pt/docs/settings/proxmenux-monitor.json
@@ -33,7 +33,7 @@
   },
   "warnConditional": {
     "title": "Opções de configuração somente visíveis quando o Monitor está instalado",
-    "body": "Os dois itens de menu descritos abaixo só aparecem em Configurações se proxmenux-monitor.service estiver registrado com systemd. Se não os vir, o Monitor não foi instalado (é opcional durante a instalação do ProxMenux). Para adicioná-lo mais tarde, reinstale o ProxMenux com a versão mais recente e escolha a opção Monitor."
+    "body": "Os três itens de menu descritos abaixo só aparecem em Configurações se proxmenux-monitor.service estiver registrado com systemd. O instalador configura-o sempre, por isso, se não os vir, a etapa do Monitor falhou durante a instalação. Execute novamente o instalador oficial para repeti-la."
   },
   "toggle": {
     "heading": "Activar / Desactivar",
@@ -92,8 +92,8 @@
   },
   "troubleshoot": {
     "heading": "Resolução de Problemas",
-    "missingTitle": "Faltam ambas as opções de menu",
-    "missingBody": "O Monitor não está registado na systemd. Confirme com systemctl list-unit-files | grep proxmenux-monitor — se não houver saída, reinstale o ProxMenux do instalador oficial com a versão mais recente e escolha a opção Monitor durante a instalação.",
+    "missingTitle": "Faltam as opções de menu do Monitor",
+    "missingBody": "O Monitor não está registado na systemd. Confirme com systemctl list-unit-files | grep proxmenux-monitor — se não houver saída, execute novamente o instalador oficial; ele instala o Monitor em cada execução.",
     "unreachableTitle": "Serviço ativado, mas o URL não é acessível",
     "unreachableBody": "A saída de status Configurações lhe dá o IP. Confirmar:",
     "unreachableCmd": "ss -tlnp | grep 8008                # is anything listening?\njournalctl -u proxmenux-monitor -n 100 --no-pager  # recent errors\ncurl -sI http://localhost:8008      # local reachability",
diff --git a/web/messages/pt/docs/settings/uninstall-proxmenux.json b/web/messages/pt/docs/settings/uninstall-proxmenux.json
index 5d241746..812f0b71 100644
--- a/web/messages/pt/docs/settings/uninstall-proxmenux.json
+++ b/web/messages/pt/docs/settings/uninstall-proxmenux.json
@@ -12,7 +12,7 @@
   },
   "scopeWarn": {
     "title": "Afecta apenas a instalação ProxMenux — não as suas VMs / CTs / armazenamento",
-    "body": "A desinstalação remove os scripts do ProxMenux, o lançador do menu, o serviço Monitor opcional e as cópias de segurança dos ficheiros de sistema criadas pelo ProxMenux na instalação. Não altera as suas VMs, contentores, armazenamento, configuração de rede, Fail2Ban, controlador NVIDIA ou qualquer outro elemento instalado através do ProxMenux. Estes permanecem como estão."
+    "body": "A desinstalação remove os scripts do ProxMenux, o lançador do menu, o serviço Monitor e restaura os backups de /root/.bashrc e /etc/motd feitos pela pós-instalação, se existirem. Não altera as suas VMs, contentores, armazenamento, configuração de rede, Fail2Ban, controlador NVIDIA ou qualquer outro elemento instalado através do ProxMenux. Estes permanecem como estão."
   },
   "flow": {
     "heading": "Processo de desinstalação",
diff --git a/web/messages/pt/docs/utils/export-vm.json b/web/messages/pt/docs/utils/export-vm.json
index 0d07c6f4..35f5978e 100644
--- a/web/messages/pt/docs/utils/export-vm.json
+++ b/web/messages/pt/docs/utils/export-vm.json
@@ -59,8 +59,8 @@
   },
   "package": {
     "heading": "O que acaba no pacote",
-    "intro": "Para um OVA/OVF denominado myvm-100-2026-04-26_18-30-15:",
-    "code": "# OVA mode (single .ova file is a TAR of):\nmyvm-100-2026-04-26_18-30-15.ovf      # XML descriptor (DMTF OVF 1.0 + RASD + VSSD)\nmyvm-100-2026-04-26_18-30-15.mf       # SHA1 manifest\nmyvm-100-2026-04-26_18-30-15-disk0.vmdk\nmyvm-100-2026-04-26_18-30-15-disk1.vmdk\n...\n\n# OVF mode (a directory):\nmyvm-100-2026-04-26_18-30-15-ovf/\n├── myvm-100-2026-04-26_18-30-15.ovf\n├── myvm-100-2026-04-26_18-30-15.mf\n├── myvm-100-2026-04-26_18-30-15-disk0.vmdk\n└── myvm-100-2026-04-26_18-30-15-disk1.vmdk"
+    "intro": "Para um OVA/OVF denominado myvm-100-20260426_183015:",
+    "code": "# OVA mode (single .ova file is a TAR of):\nmyvm-100-20260426_183015.ovf      # XML descriptor (DMTF OVF 1.0 + RASD + VSSD)\nmyvm-100-20260426_183015.mf       # SHA1 manifest\nmyvm-100-20260426_183015-disk1.vmdk\nmyvm-100-20260426_183015-disk2.vmdk\n...\n\n# OVF mode (a directory):\nmyvm-100-20260426_183015-ovf/\n├── myvm-100-20260426_183015.ovf\n├── myvm-100-20260426_183015.mf\n├── myvm-100-20260426_183015-disk1.vmdk\n└── myvm-100-20260426_183015-disk2.vmdk"
   },
   "exported": {
     "heading": "O que é exportado",
@@ -90,7 +90,7 @@
   "manifest": {
     "heading": "Manifesto SHA1",
     "intro": "Cada pacote inclui um ficheiro .mf com os hashes SHA1 do descritor OVF e de cada VMDK:",
-    "code": "SHA1(myvm-100-2026-04-26_18-30-15.ovf)= 3a7bd3e2360a3d29eea436fcfb7e44c735d117c9\nSHA1(myvm-100-2026-04-26_18-30-15-disk0.vmdk)= b7e23ec29af22b0b4e41da31e868d57226121c84\nSHA1(myvm-100-2026-04-26_18-30-15-disk1.vmdk)= a87ff679a2f3e71d9181a67b7542122c",
+    "code": "SHA1(myvm-100-20260426_183015.ovf)= 3a7bd3e2360a3d29eea436fcfb7e44c735d117c9\nSHA1(myvm-100-20260426_183015-disk1.vmdk)= b7e23ec29af22b0b4e41da31e868d57226121c84\nSHA1(myvm-100-20260426_183015-disk2.vmdk)= a87ff679a2f3e71d9181a67b7542122c",
     "outro": "É opcional na especificação OVF e muitos programas ignoram-no, mas permite verificar se o pacote foi transferido intacto. Verifique manualmente com sha1sum -c file.mf."
   },
   "import": {
diff --git a/web/messages/pt/docs/utils/index.json b/web/messages/pt/docs/utils/index.json
index ecafb09e..a49a0932 100644
--- a/web/messages/pt/docs/utils/index.json
+++ b/web/messages/pt/docs/utils/index.json
@@ -38,7 +38,7 @@
       "body": "Mantenha o host saudável: ferramentas CLI, atualizações regulares e atualização de versão principal quando chegar a hora.",
       "bullets": [
         "Instalador de utilitários de sistema (25 ferramentas CLI)",
-        "Atualização do Sistema Proxmox (apt + dist-upgrade)",
+        "Atualização do Sistema Proxmox (apt + full-upgrade)",
         "Atualização de versão principal PVE 8 → PVE 9"
       ]
     },
@@ -58,11 +58,11 @@
   },
   "isoSection": {
     "heading": "Criação ISO",
-    "body": "Crie uma nova ISO de instalação do Windows atualizada diretamente no host Proxmox. O script automatiza todo o processo UUP Dump: instalação de dependências (aria2c, cabextract, wimlib-imagex, …), transferência paralela dos ficheiros de atualização com aria2 e conversão numa única ISO de arranque. A ISO é guardada em /var/lib/vz/template/iso, pronta para associar a uma VM.",
+    "body": "Crie uma nova ISO de instalação do Windows atualizada diretamente no host Proxmox. O script automatiza todo o processo UUP Dump: instalação de dependências (aria2c, cabextract, wimlib-imagex, …), transferência paralela dos ficheiros de atualização com aria2 e conversão numa única ISO de arranque. A ISO é guardada no armazenamento ISO do Proxmox detetado (alternativa /var/lib/vz/template/iso), pronta para associar a uma VM.",
     "options": [
       {
         "title": "Criador de ISO do UUP Dump",
-        "description": "Crie uma ISO de instalação do Windows atualizada a partir de um link partilhado do UUP Dump. As dependências, a transferência e a conversão são totalmente automatizadas; a ISO resultante é guardada em /var/lib/vz/template/iso, pronta para criar uma VM.",
+        "description": "Crie uma ISO de instalação do Windows atualizada a partir de um link partilhado do UUP Dump. As dependências, a transferência e a conversão são totalmente automatizadas; a ISO resultante é guardada no armazenamento ISO do Proxmox detetado, pronta para criar uma VM.",
         "href": "/docs/utils/UUp-Dump-ISO-Creator"
       }
     ]
@@ -78,7 +78,7 @@
       },
       {
         "title": "Atualização do sistema Proxmox",
-        "description": "Verificação dos repositórios + apt update + apt dist-upgrade, com confirmação antes de reiniciar. Deteta a versão principal do PVE (8 ou 9), mantém os repositórios configurados e pede confirmação antes de mudar um host sem subscrição para o repositório no-subscription; depois executa autoremove.",
+        "description": "Verificação dos repositórios + apt update + apt full-upgrade, com confirmação antes de reiniciar. Deteta a versão principal do PVE (8 ou 9), mantém os repositórios configurados e pede confirmação antes de mudar um host sem subscrição para o repositório no-subscription; depois executa autoremove.",
         "href": "/docs/utils/system-update"
       },
       {
diff --git a/web/messages/pt/docs/utils/system-update.json b/web/messages/pt/docs/utils/system-update.json
index a311a1ca..0b5a22c5 100644
--- a/web/messages/pt/docs/utils/system-update.json
+++ b/web/messages/pt/docs/utils/system-update.json
@@ -128,7 +128,7 @@
         "body": "O repositório Enterprise está ativo, mas o host não tem subscrição ativa. Antes de apt-get update, o script propõe passar a no-subscription. Se recusou, repita a atualização e aceite, ou desative Enterprise em Node → Updates → Repositories e ative no-subscription."
       },
       {
-        "title": "dist-upgrade trava em \"Configuring grub-pc\"",
+        "title": "full-upgrade trava em \"Configuring grub-pc\"",
         "body": "Se aparecer uma pergunta interativa do GRUB, pede os dispositivos onde instalar o gestor de arranque. A opção --force-confold aplica-se aos ficheiros de configuração, não à escolha dos dispositivos. Use Tab + Space para selecionar os discos de arranque adequados e depois OK. Pode configurar esta escolha antes com dpkg-reconfigure grub-pc."
       },
       {
diff --git a/web/messages/pt/docs/utils/system-utils.json b/web/messages/pt/docs/utils/system-utils.json
index 9390dc12..9bb9f130 100644
--- a/web/messages/pt/docs/utils/system-utils.json
+++ b/web/messages/pt/docs/utils/system-utils.json
@@ -235,7 +235,7 @@
       {
         "href": "/docs/utils/system-update",
         "label": "Atualização do sistema Proxmox",
-        "tailRich": " — para o apt dist-upgrade efectivo no hospedeiro."
+        "tailRich": " — para o apt full-upgrade efectivo no hospedeiro."
       },
       {
         "href": "/docs/network/monitoring",
diff --git a/web/messages/pt/docs/utils/upgrade-pve8-pve9.json b/web/messages/pt/docs/utils/upgrade-pve8-pve9.json
index 2873cb00..39ebaa95 100644
--- a/web/messages/pt/docs/utils/upgrade-pve8-pve9.json
+++ b/web/messages/pt/docs/utils/upgrade-pve8-pve9.json
@@ -16,9 +16,9 @@
     "items": [
       "Cópias de segurança verificadas de cada VM e CT (PBS, vzdump, snapshots ZFS — verifique também o restauro).",
       "Acesso por consola / IPMI / iKVM ao anfitrião caso a ligação SSH seja interrompida.",
-      "O host já está atualizado em PVE 8.4.x (o script obriga isso).",
+      "O host no nível de patch mais recente do PVE 8. O script atualiza primeiro o PVE 8; não verifica uma versão 8.4.x específica.",
       "Ceph (se presente) na versão 19.x Squid.",
-      "Pelo menos 5 GB livres no sistema de ficheiros raiz."
+      "Pelo menos 5 GB livres no sistema de ficheiros raiz, como recomenda o guia do Proxmox. O próprio script só verifica 1 GB em /var/cache/apt/archives."
     ]
   },
   "modeMenu": {
@@ -63,13 +63,13 @@
   },
   "webTerminal": {
     "heading": "Por que o terminal web está bloqueado",
-    "body": "Os três modos detetam termproxy / vncshell na cadeia de processos ascendentes e recusam iniciar. O terminal web depende de pveproxy, um serviço atualizado durante o processo. Quando pveproxy reinicia, a sessão termina e o script que nela decorre também. O anfitrião pode ficar parcialmente atualizado.",
+    "body": "Os modos Automático e Interativo detetam termproxy / vncshell na cadeia de processos ascendentes e recusam iniciar, a menos que a sessão venha por SSH ou que PVE_ALLOW_WEBTERM=1 esteja definido. A verificação prévia e o guia manual não alteram nada e funcionam em qualquer lado. O terminal web depende de pveproxy, um serviço atualizado durante o processo. Quando pveproxy reinicia, a sessão termina e o script que nela decorre também. O anfitrião pode ficar parcialmente atualizado, sem boa forma de retomar.",
     "warningTitle": "Utilize SSH ou uma consola física / IPMI / iKVM",
-    "warningBody": "SSH é a opção habitual. Inicie a sessão dentro de tmux ou screen para resistir a breves interrupções de rede. Se detetar uma shell SSH simples, o script apresenta primeiro um lembrete sobre tmux/screen."
+    "warningBody": "SSH é a opção habitual. Inicie a sessão dentro de tmux ou screen para resistir a breves interrupções de rede. A janela de confirmação da atualização lembra-o de executá-la a partir da consola ou de SSH dentro de tmux/screen."
   },
   "auto": {
     "heading": "Modo automático / sem vigilância",
-    "intro": "As perguntas sobre pacotes e ficheiros de configuração usam os valores predefinidos da tabela seguinte, e um 401 do repositório Enterprise muda para no-subscription sem perguntar. A confirmação da atualização, o resumo da atualização do PVE 8, as reparações das verificações e a pergunta de reinício continuam a ser apresentados.",
+    "intro": "As perguntas sobre pacotes e ficheiros de configuração usam os valores predefinidos da tabela seguinte. A confirmação da atualização, o resumo da atualização do PVE 8, as reparações das verificações e a pergunta de reinício continuam a ser apresentados.",
     "behaviourHeading": "Comportamento específico do modo automático",
     "behaviourTable": {
       "settingHeader": "Configuração",
@@ -94,8 +94,8 @@
       ]
     },
     "flowHeading": "Processo do modo automático",
-    "flowIntro": "Seis fases consecutivas. A fase 2 atualiza os pacotes PVE 8 e a fase 3 escreve a configuração dos repositórios do PVE 9. Uma falha interrompe o processo sem reverter essas alterações; se a atualização do PVE 8 falhar ou for cancelada, os repositórios ficam inalterados. A fase 5 executa dist-upgrade para PVE 9.",
-    "flowDiagram": "1. VERIFICAÇÕES\nBloqueia termproxy / vncshell; confirma a atualização; rejeita PVE 9.\n\n2. PREPARAR PVE 8\nscripts/global/update-pve8.sh\nAtualiza os pacotes PVE 8; para se a atualização falhar ou for cancelada.\npve8to9 --full (pode propor reparações).\nVerifica espaço e conectividade; Ceph 19.x Squid em nós hiperconvergentes.\n\n3. MIGRAR REPOSITÓRIOS\nBookworm → Trixie; escreve ficheiros deb822 .sources.\nComenta os ficheiros .list antigos; apt-get update.\n\n4. VALIDAR O CANDIDATO PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nPara se o candidato não for 9.x ou se proxmox-ve fosse removido.\n\n5. ATUALIZAR\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nSem subscrição: desativa pve-enterprise.sources.\nRegisto: /var/log/pve8-a-pve9-.log\n\n6. VERIFICAR E REINICIAR\nEFI: apt install grub-efi-amd64\npve8to9 --full; examine os resultados e a pergunta de reinício.",
+    "flowIntro": "Seis fases consecutivas. A fase 2 muda os repositórios do PVE 8 para no-subscription (listas Enterprise e Ceph desativadas, /etc/apt/sources.list reescrito) antes de mostrar o resumo da atualização e depois atualiza os pacotes; a fase 3 escreve a configuração dos repositórios do PVE 9. Uma falha interrompe o processo sem reverter essas alterações; se a atualização do PVE 8 falhar ou for cancelada, a migração para o PVE 9 não começa, mas as alterações nos repositórios do PVE 8 permanecem. A fase 5 executa dist-upgrade para PVE 9.",
+    "flowDiagram": "1. VERIFICAÇÕES\nBloqueia termproxy / vncshell; confirma a atualização; rejeita PVE 9.\n\n2. PREPARAR PVE 8\nscripts/global/update-pve8.sh\nMuda os repositórios PVE 8 para no-subscription e reescreve sources.list;\natualiza os pacotes PVE 8; para se a atualização falhar ou for cancelada.\npve8to9 --full (pode propor reparações).\nVerifica espaço e conectividade; Ceph 19.x Squid em nós hiperconvergentes.\n\n3. MIGRAR REPOSITÓRIOS\nBookworm → Trixie; escreve ficheiros deb822 .sources.\nComenta os ficheiros .list antigos; apt-get update.\n\n4. VALIDAR O CANDIDATO PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nPara se o candidato não for 9.x ou se proxmox-ve fosse removido.\n\n5. ATUALIZAR\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nSem subscrição: desativa pve-enterprise.sources.\nRegisto: /var/log/pve8-a-pve9-.log\n\n6. VERIFICAR E REINICIAR\nEFI: apt install grub-efi-amd64\npve8to9 --full; examine os resultados e a pergunta de reinício.",
     "preflightHeading": "Verificações antes da atualização",
     "preflightTable": {
       "checkHeader": "Verificar",
@@ -158,7 +158,7 @@
   },
   "manual": {
     "heading": "Procedimento de actualização manual",
-    "intro": "É isto que mostra a opção Guia de atualização manual: o wiki oficial Proxmox para passar de 8 para 9, organizado em quatro fases. O script mostra os comandos; executa-os pessoalmente numa shell root separada. Cada passo é visível.",
+    "intro": "Isto segue o que mostra a opção de menu Guia de atualização manual, com base no wiki oficial Proxmox para passar de 8 para 9 e organizado em quatro fases. A tabela de respostas do dpkg acrescenta as ressalvas do wiki para sshd_config e chrony.conf, que o menu mostra como um simples Y. O script mostra os comandos; executa-os pessoalmente numa shell root separada. Sem automação, cada passo é visível.",
     "rootCalloutTitle": "Todos os comandos precisam de root",
     "rootCalloutBody": "Utilize uma shell root para estes comandos, incluindo redirecionamentos e documentos here-document. Com sudo, garanta que todo o comando é executado com privilégios root.",
     "phase1Heading": "Fase 1 — Preparação",
@@ -230,7 +230,7 @@
   "clusterCeph": {
     "heading": "Cluster e Ceph",
     "clusterCalloutTitle": "Cluster: atualizar um nó de cada vez",
-    "clusterCalloutBody": "Detetado por pvecm status. Não atualize vários nós ao mesmo tempo. Mova os convidados, atualize o nó, reinicie e confirme a participação no cluster antes de continuar. Durante a atualização, funciona a migração em direto de versões PVE anteriores para posteriores; o sentido inverso geralmente não é suportado. Os grupos HA são convertidos automaticamente em regras HA quando todos os nós utilizam PVE 9.",
+    "clusterCalloutBody": "Não atualize vários nós ao mesmo tempo. Mova os convidados, atualize o nó, reinicie e confirme a participação no cluster antes de continuar. Durante a atualização, funciona a migração em direto de versões PVE anteriores para posteriores; o sentido inverso geralmente não é suportado. Os grupos HA são convertidos automaticamente em regras HA quando todos os nós utilizam PVE 9.",
     "cephCalloutTitle": "Ceph: atualizar Ceph PRIMEIRO, depois Proxmox",
     "cephCalloutBody": "O Ceph já deve estar em 19.x Squid antes de atualizar o Proxmox. Siga a sequência oficial Quincy 17 → Reef 18 → Squid 19, atualizando pacotes e reiniciando os daemons Ceph de forma gradual pela ordem documentada. Confirme ceph --version antes de cada nó Proxmox. Procedimento completo: wiki de atualização de Ceph Reef para Squid."
   },
@@ -273,7 +273,7 @@
   },
   "files": {
     "heading": "Ficheiros envolvidos",
-    "code": "scripts/utilities/upgrade_pve8_to_pve9.sh                         # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh                                # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh    # 17-step runbook\nscripts/global/update-pve8.sh                                     # PVE 8 worker (final patches)\n/etc/apt/sources.list                                             # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources                           # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources                            # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources                              # created if Ceph\n/etc/apt/sources.list.d/*.list                                    # legacy files commented out / removed\n/var/log/pve8-a-pve9-.log                              # full upgrade log"
+    "code": "scripts/utilities/upgrade_pve8_to_pve9.sh                         # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh                                # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh    # 17-step runbook\nscripts/global/update-pve8.sh                                     # PVE 8 worker (final patches)\n/etc/apt/sources.list                                             # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources                           # created (deb822 PVE 9)\n/etc/apt/sources.list.d/ceph.sources                              # created if Ceph\n/etc/apt/sources.list.d/*.list                                    # legacy files commented out / removed\n/var/log/pve8-a-pve9-.log                              # full upgrade log"
   },
   "references": {
     "heading": "Referências oficiais",
diff --git a/web/messages/sk/docs/backup-restore/creating-backups.json b/web/messages/sk/docs/backup-restore/creating-backups.json
index 3fc89c76..b9751e72 100644
--- a/web/messages/sk/docs/backup-restore/creating-backups.json
+++ b/web/messages/sk/docs/backup-restore/creating-backups.json
@@ -21,8 +21,8 @@
     "rows": [
       {
         "entry": "ProxMenux Scripts (TUI)",
-        "path": "menu → Utilities → Host Config Backup",
-        "detail": "Dialógový tok, SSH-priateľský. Hlavné menu predstavuje šesť možností priamo. Používa backup_menu v backup_host.sh."
+        "path": "menu → Host Backup & Restore → Backup host configuration",
+        "detail": "Dialógový tok, SSH-priateľský. Položka hlavného menu otvára Host Config Backup / Restore; Backup host configuration vedie k šiestim možnostiam. Používa backup_menu v backup_host.sh."
       },
       {
         "entry": "ProxMenux Monitor (webové rozhranie)",
@@ -83,7 +83,7 @@
   "profiles": {
     "heading": "Štandardný vs vlastný profil",
     "defaultTitle": "Predvolený profil",
-    "defaultBody": "Predvolený profil je pripravený zoznam z hb_default_profile_paths (opísaný v časti Ako to funguje pod nadpisom Kategórie ciest) spolu so všetkými položkami zo súboru trvalých doplnkov /usr/local/share/proxmenux/backup-extra-paths.txt. Používateľ potvrdí cieľ a možnosti šifrovania a záloha pokračuje bez ďalšieho výberu ciest.",
+    "defaultBody": "Predvolený profil je pripravený zoznam z hb_default_profile_paths (opísaný v časti Ako to funguje pod nadpisom Kategórie ciest) spolu so všetkými položkami zo súboru trvalých doplnkov /usr/local/share/proxmenux/backup-extra-paths.txt. Používateľ vyberie cieľ a možnosti šifrovania a záloha pokračuje bez ďalšieho výberu ciest.",
     "customTitle": "Vlastný profil",
     "customBody": "Vlastný profil otvorí zoznam so všetkými cestami predvoleného profilu (nezaškrtnuté) a všetkými trvalými doplnkami (vopred zaškrtnuté, s predponou [+]). Používateľ zaškrtne cesty pre toto spustenie a cez Pridať vlastnú cestu môže doplniť novú absolútnu cestu. Takto pridaná cesta sa uloží do backup-extra-paths.txt, aby ju budúce zálohy zahrnuli automaticky bez opätovného pridávania. Odškrtnutie trvalého doplnku ho vynechá len pri tomto spustení, ale neodstráni ho zo súboru — na odstránenie slúži samostatná akcia Spravovať vlastné cesty mimo postupu zálohovania.",
     "customPickerAlt": "Vlastný kontrolný zoznam profilu zobrazujúci predvolené cesty profilu (nezaškrtnuté) a trvalé doplnky (vopred zaškrtnuté s predponou [+]) plus tlačidlá na pridanie novej cesty alebo potvrdenie výberu.",
@@ -124,14 +124,7 @@
   },
   "included": {
     "heading": "Čo ide dnu a čo zostane vonku",
-    "intro": "Každá cesta v určenom profile (predvolené + trvalé doplnky + výber vo vlastnom režime) je skopírovaná s rsync -aAXH --numeric-ids. Spoločný zoznam vylúčení sa vzťahuje na každú cestu a dva adresáre majú dodatočné výnimky špecifické pre jednotlivé trasy.",
-    "globalTitle": "Globálne vylúčenia (uplatnené na každú cestu)",
-    "globalItems": [
-      "images/ — výpisy obrazov.",
-      "dump/ — výstupy vzdump.",
-      "tmp/ - dočasné súbory.",
-      "*.log — súbory záznamov."
-    ],
+    "intro": "Každá cesta v určenom profile (predvolené + trvalé doplnky + výber vo vlastnom režime) je skopírovaná s rsync -aAXH --numeric-ids. Neexistuje globálny zoznam vylúčení; niekoľko adresárov má vylúčenia špecifické pre danú cestu.",
     "rootTitle": "Výnimky /root/",
     "rootBody": "/root/ je súčasťou predvoleného profilu, aby sa do archívu zahrnuli používateľské skripty a konfigurácia. Dočasné podcesty sa vynechajú:",
     "rootItems": [
@@ -160,16 +153,16 @@
       "/home/USER Nie v štandardnom profile. Pridať ako vlastnú cestu, keď systém nesie užívateľské domovské adresáre, ktoré musia prežiť obnovenie."
     ],
     "customPathsTitle": "Ako sa spracúvajú vlastné cesty",
-    "customPathsBody": "Vlastná cesta pridaná priamo vo vlastnom režime alebo uložená v backup-extra-paths.txt prechádza rovnakým postupom rsync ako cesty predvoleného profilu. Platia globálne vylúčenia. Ak sa vlastná cesta nachádza pod /root/ alebo /usr/local/share/proxmenux/, naďalej platia aj uvedené vylúčenia pre konkrétnu cestu. Každá archivovaná cesta — predvolená aj vlastná — sa zaznamená v metadata/paths_archived.txt. Cesty, ktoré v zdroji neexistujú, sa zapíšu do metadata/missing_paths.txt bez zastavenia zálohy."
+    "customPathsBody": "Vlastná cesta pridaná priamo vo vlastnom režime alebo uložená v backup-extra-paths.txt prechádza rovnakým postupom rsync ako cesty predvoleného profilu. Ak sa vlastná cesta nachádza pod /root/ alebo /usr/local/share/proxmenux/, naďalej platia aj uvedené vylúčenia pre konkrétnu cestu. Každá archivovaná cesta — predvolená aj vlastná — sa zaznamená v metadata/selected_paths.txt. Cesty, ktoré v zdroji neexistujú, sa zapíšu do metadata/missing_paths.txt bez zastavenia zálohy a cesty, ktoré sa nepodarilo skopírovať, do metadata/failed_paths.txt."
   },
   "archiveStructure": {
     "heading": "Štruktúra archívu",
     "intro": "Dočasný prípravný adresár vytvorený každým backendom má rovnaké usporiadanie bez ohľadu na miesto určenia. Archív tarball, PBS .pxar alebo Borg uchováva tento strom doslovne.",
-    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state (kernel_params, hardware, storage, guests, components, source_host)\n├── metadata/\n│   ├── packages.manual.list       # output of apt-mark showmanual\n│   ├── run_info.env               # hostname, timestamp, kernel version, pmxcfs method\n│   ├── paths_archived.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, /etc/apt, ...\n    ├── root/                      # /root without volatile subpaths\n    ├── usr/local/                 # /usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux (state only)\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/crontabs"
+    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state (kernel_params, hardware, storage, guests, components, source_host)\n├── metadata/\n│   ├── packages.manual.list       # output of apt-mark showmanual\n│   ├── run_info.env               # hostname, timestamp, kernel version, pmxcfs method\n│   ├── selected_paths.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, /etc/apt, ...\n    ├── root/                      # /root without volatile subpaths\n    ├── usr/local/                 # /usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux (state only)\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/crontabs"
   },
   "confirmation": {
     "heading": "Zhrnutie potvrdenia",
-    "body": "Pred zápisom akýchkoľvek údajov do cieľa ProxMenux zobrazí súhrnný dialóg s cieľom, ID zálohy alebo názvom archívu, stavom šifrovania a zoznamom kopírovaných ciest. Zrušenie v tomto bode zálohovanie bezpečne ukončí — obslužný mechanizmus trap nastavený vo funkcii backendu odstráni dočasný prípravný adresár a do cieľa sa nedostanú žiadne čiastočné údaje."
+    "body": "Pred zápisom akýchkoľvek údajov do cieľa ProxMenux vypíše v termináli súhrn s cieľom, ID zálohy alebo názvom archívu, stavom šifrovania a zoznamom kopírovaných ciest. V tomto bode nie je krok potvrdenia ani zrušenia; kopírovanie sa začne hneď potom. Ak záloha zlyhá alebo sa preruší, obslužný mechanizmus trap nastavený vo funkcii backendu odstráni dočasný prípravný adresár a do cieľa sa nedostanú žiadne čiastočné údaje."
   },
   "writing": {
     "heading": "Písanie na miesto určenia",
diff --git a/web/messages/sk/docs/backup-restore/destinations/local.json b/web/messages/sk/docs/backup-restore/destinations/local.json
index 14df7a05..b8487647 100644
--- a/web/messages/sk/docs/backup-restore/destinations/local.json
+++ b/web/messages/sk/docs/backup-restore/destinations/local.json
@@ -18,11 +18,11 @@
   },
   "targetConfig": {
     "heading": "Nastavenie cieľového adresára",
-    "intro": "Miestny cieľ je jeden trvalý cieľový adresár — nie zoznam. ProxMenux ukladá voľbu operátora na /usr/local/share/proxmenux/local-target.conf a číta ju pri každej zálohe. Ak nie je nakonfigurovaný žiadny cieľ, použije sa predvolený HB_LOCAL_TARGET_DEFAULT = /var/lib/vz/dump (rovnaký adresár, ktorý Proxmox používa pre svoje vlastné výstupy vzdump). Cieľ sa konfiguruje z Konfigurovať ciele zálohovania → Lokálne ciele:",
+    "intro": "Miestny cieľ je jeden trvalý cieľový adresár — nie zoznam. ProxMenux ukladá voľbu operátora na /usr/local/share/proxmenux/local-target.conf a číta ju pri každej zálohe. Ak nie je nakonfigurovaný žiadny cieľ, použije sa predvolený HB_LOCAL_TARGET_DEFAULT = /var/lib/vz/dump (rovnaký adresár, ktorý Proxmox používa pre svoje vlastné výstupy vzdump). Cieľ sa konfiguruje z Konfigurovať ciele zálohovania → Lokálne archívne ciele → Spravovať lokálny cieľ zálohy:",
     "options": [
       "Použiť predvolené (/var/lib/vz/dump). Lokálne úložisko Proxmox. Prítomný pri každej inštalácii Proxmox; archív sa nachádza vedľa výstupov vzdump a automaticky ho preberá karta Zálohy monitora.",
-      "Zadajte vlastnú cestu. Môžete použiť akúkoľvek absolútnu cestu súborového systému: pripojenie NFS, zdieľanie SMB pripojené cez fstab, vyhradený súbor údajov ZFS, druhý interný disk. ProxMenux overí, že cesta existuje a je adresárom, pred jej uložením.",
-      "Vyberte jednotku USB. Otvorí podponuku USB (nižšie), ktorá detekuje vymeniteľné zariadenia a ponúka pripojenie alebo formátovanie."
+      "Použite vlastnú cestu. Môžete použiť akúkoľvek absolútnu cestu súborového systému: pripojenie NFS, zdieľanie SMB pripojené cez fstab, vyhradený súbor údajov ZFS, druhý interný disk. ProxMenux overí, že cesta existuje a je adresárom, pred jej uložením.",
+      "Použite disk USB. Otvorí podponuku USB (nižšie), ktorá detekuje vymeniteľné zariadenia a ponúka pripojenie alebo formátovanie."
     ]
   },
   "usbFlow": {
@@ -38,7 +38,7 @@
       {
         "state": "odmontované",
         "shown": "Veľkosť · menovka · [fstype] · (nepripojený — bude pripojený)",
-        "action": "Súborový systém je prítomný, ale nie je namontovaný. Po potvrdení, ProxMenux spustí hb_mount_usb_partition: vytvorí /mnt/backup-LABEL (alebo cestu založenú na UUID, ak nie je označenie), pripojí partíciu a uloží bod pripojenia ako miestny cieľ."
+        "action": "Súborový systém je prítomný, ale nie je namontovaný. Po potvrdení, ProxMenux spustí hb_mount_usb_partition: vytvorí /mnt/proxmenux-backup-LABEL (UUID alebo názov zariadenia, ak nie je označenie), pripojí partíciu a uloží bod pripojenia ako miestny cieľ."
       },
       {
         "state": "prázdny",
@@ -63,7 +63,7 @@
   },
   "sidecar": {
     "heading": "Sprievodný súbor JSON",
-    "intro": "Každá úspešná lokálna záloha produkuje sesterský súbor: HOSTNAME-TIMESTAMP.tar.zst.proxmenux.json, zapísaný vedľa archívu funkciou hb_write_archive_sidecar. Tento malý súbor JSON umožňuje ProxMenux Monitor identifikovať archív ako zálohu ProxMenux hostiteľa, aj keď je neskôr presunutý, premenovaný alebo archivovaný inde.",
+    "intro": "Každá úspešná lokálna záloha produkuje sesterský súbor: hostcfg-HOSTNAME-TIMESTAMP.tar.zst.proxmenux.json, zapísaný vedľa archívu funkciou hb_write_archive_sidecar. Tento malý súbor JSON umožňuje ProxMenux Monitor identifikovať archív ako zálohu ProxMenux hostiteľa, aj keď je neskôr presunutý, premenovaný alebo archivovaný inde.",
     "contentTitle": "Obsah sprievodného súboru",
     "contentBody": "Sprievodný súbor obsahuje verziu schémy, údaj o tom, či záloha vznikla interaktívne alebo plánovanou úlohou (kind), ID úlohy pri plánovaných spusteniach, použitý režim profilu (default alebo custom), názov zdrojového hostiteľa, pôvodný názov súboru archívu, časovú pečiatku vytvorenia ISO-8601 a veľkosť archívu v bajtoch.",
     "whyBody": "Karta Backups v Monitore prehľadáva nakonfigurované lokálne adresáre a hľadá sprievodné súbory *.proxmenux.json, nie súbory *.tar.zst, pretože .tar.zst bez sprievodného súboru nemusí byť zálohou ProxMenux. Hľadanie je rýchle (súbory JSON sú malé) a párovanie zostane platné aj po premenovaní archívu, ak sa zodpovedajúco premenuje aj sprievodný súbor."
diff --git a/web/messages/sk/docs/backup-restore/destinations/pbs.json b/web/messages/sk/docs/backup-restore/destinations/pbs.json
index 01ab2c8f..b6b8cecb 100644
--- a/web/messages/sk/docs/backup-restore/destinations/pbs.json
+++ b/web/messages/sk/docs/backup-restore/destinations/pbs.json
@@ -33,7 +33,7 @@
       {
         "source": "ProxMenux manuálna konfigurácia",
         "path": "/usr/local/share/proxmenux/pbs-manual-configs.txt + pbs-pass-NAME.txt + pbs-fingerprint-NAME.txt",
-        "content": "Pridáva sa cez Nastaviť ciele záloh → Ciele PBS → Pridať PBS. Zadáva sa názov, používateľské meno (root@pam alebo user@pbs!token), hostiteľ alebo IP adresa, dátové úložisko a heslo. Pri prázdnom hesle sa výzva zopakuje: inak by sa prázdna hodnota potichu uložila a každá ďalšia záloha by zlyhala s nejasnou chybou overenia. Táto možnosť slúži pre PBS, ktorý nie je zaregistrovaný ako úložisko Proxmoxu."
+        "content": "Pridáva sa cez Nastaviť ciele záloh → Ciele Proxmox Backup Server (PBS) → + Pridať nový PBS ručne. Zadáva sa názov, používateľské meno (root@pam alebo user@pbs!token), hostiteľ alebo IP adresa, dátové úložisko a heslo. Pri prázdnom hesle sa výzva zopakuje: inak by sa prázdna hodnota potichu uložila a každá ďalšia záloha by zlyhala s nejasnou chybou overenia. Táto možnosť slúži pre PBS, ktorý nie je zaregistrovaný ako úložisko Proxmoxu."
       }
     ],
     "menuTitle": "Výberové menu",
diff --git a/web/messages/sk/docs/backup-restore/how-it-works.json b/web/messages/sk/docs/backup-restore/how-it-works.json
index a3e816b8..c90f3a8c 100644
--- a/web/messages/sk/docs/backup-restore/how-it-works.json
+++ b/web/messages/sk/docs/backup-restore/how-it-works.json
@@ -20,7 +20,7 @@
     "heading": "Rozloženie archívu",
     "intro": "Každý archív sleduje rovnaké rozloženie stromov bez ohľadu na cieľ. Podadresár metadata/ má štruktúrované užitočné zaťaženie; podadresár rootfs/ má kópiu súborového systému.",
     "treeCaption": "Prípravný adresár vytvorený pri zálohe. Každý cieľ dostane rovnaký strom vo svojom formáte: tar pri lokálnom cieli, bloky PBS pri PBS a segmenty Borg pri Borgu.",
-    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state\n├── metadata/\n│   ├── packages.manual.list       # apt-mark showmanual\n│   ├── run_info.env               # backup run identity + kernel version + pmxcfs method\n│   ├── paths_archived.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, …\n    ├── root/                      # /root (with volatile subdirs excluded)\n    ├── usr/local/                 # /usr/local/bin, /usr/local/share/proxmenux, …\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/…"
+    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state\n├── metadata/\n│   ├── packages.manual.list       # apt-mark showmanual\n│   ├── run_info.env               # backup run identity + kernel version + pmxcfs method\n│   ├── selected_paths.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, …\n    ├── root/                      # /root (with volatile subdirs excluded)\n    ├── usr/local/                 # /usr/local/bin, /usr/local/share/proxmenux, …\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/…"
   },
   "rootfs": {
     "heading": "Časť rootfs",
@@ -176,7 +176,7 @@
         "stage": "3",
         "name": "Príprava odložených zmien",
         "reads": "rootfs/ (reboot + nebezpečné cesty)",
-        "action": "_rs_prepare_pending_restore pripraví rizikové cesty v /var/lib/proxmenux/pending-restore/, zapíše plan.env, apply-on-boot.list a rs-skip-paths.txt a povolí spustenie proxmenux-restore-onboot.service pri ďalšom štarte."
+        "action": "_rs_prepare_pending_restore pripraví rizikové cesty v /var/lib/proxmenux/restore-pending/<restore_id>/ (prepojené ako current), zapíše plan.env, apply-on-boot.list a rs-skip-paths.txt a povolí spustenie proxmenux-restore-onboot.service pri ďalšom štarte."
       },
       {
         "stage": "3b",
diff --git a/web/messages/sk/docs/backup-restore/index.json b/web/messages/sk/docs/backup-restore/index.json
index 0ec15d1c..a579c20d 100644
--- a/web/messages/sk/docs/backup-restore/index.json
+++ b/web/messages/sk/docs/backup-restore/index.json
@@ -50,7 +50,7 @@
     "heading": "Dve rozhrania, jeden backend",
     "intro": "Každý krok pracovného postupu zálohovania a obnovy je dostupný z dvoch vstupných bodov. Oba volajú rovnakú knižnicu shellu, vytvárajú identické archívy a čítajú rovnaký register úloh.",
     "cliLabel": "ProxMenux Scripts (TUI)",
-    "cliDetail": "menu → Nástroje →\nZálohovanie / obnovenie hostiteľa\n\nDialógový tok,\nSSH priateľský, skriptovateľný\nna použitie bez dozoru.",
+    "cliDetail": "menu →\nHost Backup & Restore\n\nDialógový tok,\nSSH priateľský, skriptovateľný\nna použitie bez dozoru.",
     "webLabel": "ProxMenux Monitor (Web UI)",
     "webDetail": "Karta Backups vo\nwebovom rozhraní Monitor.\n\nObnova jedným kliknutím,\npriebežný výpis záznamov,\nprepojenie s upozorneniami\na zobrazením rozdielov rollbacku."
   },
diff --git a/web/messages/sk/docs/backup-restore/restoring.json b/web/messages/sk/docs/backup-restore/restoring.json
index 8c5e01d0..8aab8ff9 100644
--- a/web/messages/sk/docs/backup-restore/restoring.json
+++ b/web/messages/sk/docs/backup-restore/restoring.json
@@ -92,11 +92,11 @@
   "fullFlow": {
     "heading": "Úplné obnovenie pracovného postupu",
     "intro": "Celý postup od výberu archívu po obnoveného funkčného hostiteľa. Každá fáza pripraví vstup ďalšej: stav zapísaný skoršími fázami používajú neskoršie.",
-    "diagram": "┌─────────────────────────────────────────────────────────────────┐\n│  Archive → staging_root/                                        │\n│    manifest.json   metadata/   rootfs/                          │\n└──────────────────────────────┬──────────────────────────────────┘\n                               │\n                               ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  1. hb_compat_check                                     │\n   │     · hardware drift → RS_SKIP_PATHS                    │\n   │     · kernel direction → same / bk_newer / bk_older     │\n   │     · NIC remap plan                                    │\n   │     · hydration plan (bk_older only)                    │\n   │     · rollback plan                                     │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  2. Confirmation dialog                                 │\n   │     Shows: hot count, pending count, drift, hydration,  │\n   │     NIC remap, cross-kernel note, reinstalls preview    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │ (accepted)\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  3. Apply hot paths → LIVE                              │\n   │     _rs_apply rsyncs each hot path                      │\n   │     from staging_root/rootfs to /                       │\n   │     (skips paths in RS_SKIP_PATHS)                      │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4. _rs_prepare_pending_restore                         │\n   │     /var/lib/proxmenux/pending-restore/                 │\n   │       ├── apply-on-boot.list                            │\n   │       ├── plan.env                                      │\n   │       ├── rs-skip-paths.txt                             │\n   │       └── rootfs/ (deferred paths)                      │\n   │     Enable proxmenux-restore-onboot.service             │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4b. _rs_import_data_pools                              │\n   │      Imports non-root ZFS pools whose disks are all     │\n   │      present; retries with -f on foreign hostid;        │\n   │      skips with a warning when disks are missing.       │\n   │      Log: /var/log/proxmenux/restore-datapools-*.log    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  5. _rs_run_complete_extras                             │\n   │     packages.manual.list                                │\n   │       → cascade-safe filter                             │\n   │       → apt-get install                                 │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  6. REBOOT                                              │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  7. apply_pending_restore.sh (on early boot)            │\n   │     Reads plan.env                                      │\n   │     Applies apply-on-boot.list (filtered)               │\n   │     Installs the postboot systemd unit                  │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  8. apply_cluster_postboot.sh (~10 minutes)             │\n   │     Runs after pve-cluster is up                        │\n   │     · Applies /etc/pve entries to pmxcfs                │\n   │     · update-initramfs -u -k all                        │\n   │     · update-grub / proxmox-boot-tool refresh           │\n   │     · component --auto-reinstall (nvidia, coral, ...)   │\n   │     · Boot sanity check                                 │\n   │     · Notification: \"Host restore finished\"             │\n   └─────────────────────────────────────────────────────────┘"
+    "diagram": "┌─────────────────────────────────────────────────────────────────┐\n│  Archive → staging_root/                                        │\n│    manifest.json   metadata/   rootfs/                          │\n└──────────────────────────────┬──────────────────────────────────┘\n                               │\n                               ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  1. hb_compat_check                                     │\n   │     · hardware drift → RS_SKIP_PATHS                    │\n   │     · kernel direction → same / bk_newer / bk_older     │\n   │     · NIC remap plan                                    │\n   │     · hydration plan (bk_older only)                    │\n   │     · rollback plan                                     │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  2. Confirmation dialog                                 │\n   │     Shows: hot count, pending count, drift, hydration,  │\n   │     NIC remap, cross-kernel note, reinstalls preview    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │ (accepted)\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  3. Apply hot paths → LIVE                              │\n   │     _rs_apply rsyncs each hot path                      │\n   │     from staging_root/rootfs to /                       │\n   │     (skips paths in RS_SKIP_PATHS)                      │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4. _rs_prepare_pending_restore                         │\n   │     /var/lib/proxmenux/restore-pending/'<'id'>'/            │\n   │       ├── apply-on-boot.list                            │\n   │       ├── plan.env                                      │\n   │       ├── rs-skip-paths.txt                             │\n   │       └── rootfs/ (deferred paths)                      │\n   │     Enable proxmenux-restore-onboot.service             │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4b. _rs_import_data_pools                              │\n   │      Imports non-root ZFS pools whose disks are all     │\n   │      present; retries with -f on foreign hostid;        │\n   │      skips with a warning when disks are missing.       │\n   │      Log: /var/log/proxmenux/restore-datapools-*.log    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  5. _rs_run_complete_extras                             │\n   │     packages.manual.list                                │\n   │       → cascade-safe filter                             │\n   │       → apt-get install                                 │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  6. REBOOT                                              │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  7. apply_pending_restore.sh (on early boot)            │\n   │     Reads plan.env                                      │\n   │     Applies apply-on-boot.list (filtered)               │\n   │     Installs the postboot systemd unit                  │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  8. apply_cluster_postboot.sh (~10 minutes)             │\n   │     Runs after pve-cluster is up                        │\n   │     · Applies /etc/pve entries to pmxcfs                │\n   │     · update-initramfs -u -k all                        │\n   │     · update-grub / proxmox-boot-tool refresh           │\n   │     · component --auto-reinstall (nvidia, coral, ...)   │\n   │     · Boot sanity check                                 │\n   │     · Notification: \"Host restore finished\"             │\n   └─────────────────────────────────────────────────────────┘"
   },
   "pendingMachinery": {
     "heading": "Mechanizmus odloženej obnovy",
-    "intro": "Cesty klasifikované ako reboot alebo dangerous, a hydratačné zápisy, sú fázované pre ďalšie boot miesto aplikácie naživo. To sa vykonáva prostredníctvom malého, samostatného súboru súborov v /var/lib/proxmenux/pending-restore/:",
+    "intro": "Cesty klasifikované ako reboot alebo dangerous, a hydratačné zápisy, sú fázované pre ďalšie boot miesto aplikácie naživo. To sa vykonáva prostredníctvom malého, samostatného súboru súborov v /var/lib/proxmenux/restore-pending/<restore_id>/:",
     "rows": [
       {
         "file": "apply-on-boot.list",
@@ -115,7 +115,7 @@
         "content": "Samotné pripravené súbory – presné bajty, ktoré sa zapíšu. Zostávajú na rovnakom súborovom systéme ako /, aby bol rsync pri štarte rýchly a nezávisel od dostupnosti externého úložiska počas štartu."
       }
     ],
-    "unitBody": "Jednotka systemd proxmenux-restore-onboot.service sa povolí na konci interaktívnej fázy. Táto jednorazová služba sa spustí na začiatku ďalšieho štartu, zavolá apply_pending_restore.sh a potom sa zakáže. Podmienka ConditionPathExists=/var/lib/proxmenux/pending-restore/state zabezpečí, že bez čakajúcej obnovy nevykoná nič."
+    "unitBody": "Jednotka systemd proxmenux-restore-onboot.service sa povolí na konci interaktívnej fázy. Táto jednorazová služba sa spustí na začiatku ďalšieho štartu (pred sieťou), zavolá apply_pending_restore.sh a potom sa zakáže. Jednotka nemá podmienku na cestu: samotný skript skončí, ak /var/lib/proxmenux/restore-pending/current neexistuje, takže pri štarte bez čakajúcej obnovy nevykoná nič. Samostatná služba proxmenux-apply-cluster-postboot.service je podmienená ConditionPathExists=/var/lib/proxmenux/cluster-apply-pending."
   },
   "postbootDispatcher": {
     "heading": "Post-boot dispečer apply_cluster_postboot.sh",
@@ -238,11 +238,11 @@
     "heading": "Kde záznamy žijú",
     "rows": [
       {
-        "log": "/var/log/proxmenux/restore-YYYYMMDD_HHMMSS.log",
-        "detail": "Zapisuje sa v interaktívnej fáze: výsledky kompatibility, filter rozdielov, plán prispôsobenia, výstup použitia za behu a prípravy odložených zmien, inštalácia balíkov."
+        "log": "/tmp/proxmenux--restore-YYYYMMDD_HHMMSS.log",
+        "detail": "Zapisuje sa v interaktívnej fáze (pbs, borg alebo local). Preinštalovanie balíkov a kroky s dátovými poolmi sa zapisujú aj do /var/log/proxmenux/restore-apt-*.log a restore-datapools-*.log."
       },
       {
-        "log": "/var/log/proxmenux/apply-pending-YYYYMMDD_HHMMSS.log",
+        "log": "/var/log/proxmenux/proxmenux-restore-onboot-YYYYMMDD_HHMMSS.log",
         "detail": "Zapisuje ho apply_pending_restore.sh na začiatku štartu: načítanie plan.env, spracovanie apply-on-boot a filtrovanie vylúčených ciest."
       },
       {
diff --git a/web/messages/sk/docs/backup-restore/scheduled-jobs.json b/web/messages/sk/docs/backup-restore/scheduled-jobs.json
index 03e011e3..5f63f1ae 100644
--- a/web/messages/sk/docs/backup-restore/scheduled-jobs.json
+++ b/web/messages/sk/docs/backup-restore/scheduled-jobs.json
@@ -101,7 +101,7 @@
   },
   "management": {
     "heading": "Správa úloh",
-    "intro": "Menu rozvrhu (Scripts TUI) a záložka Monitor Backup odhaľujú rovnaké akcie na akejkoľvek práci.",
+    "intro": "Menu plánovača v Scripts TUI (Backup scheduler and retention) ponúka Run now, Enable / Disable a Delete, plus zoznam úloh so stavom posledného spustenia. Edit a View log sú dostupné iba na záložke Monitor Backups.",
     "rows": [
       {
         "action": "Spustiť teraz",
@@ -112,7 +112,7 @@
         "detail": "Nový režim: časovač sa ovláda cez systemctl enable/disable --now. Režim pripojenia: zmení príznak ENABLED v súbore env úlohy — hook ho zohľadní pri ďalšom spustení PVE."
       },
       {
-        "action": "Upraviť",
+        "action": "Upraviť (iba Monitor)",
         "detail": "Znovu otvorí destináciu, rozvrh, profil a retenčný postup a prepíše pracovné súbory. Zachováva ID práce."
       },
       {
@@ -120,7 +120,7 @@
         "detail": "Odstráni súbor env úlohy, súbor ciest, časovač a službu systemd (nový režim) a vypne pripojenie k hooku (režim pripojenia). Existujúce archívy v cieli nemení."
       },
       {
-        "action": "Zobraziť záznam",
+        "action": "Zobraziť záznam (iba Monitor)",
         "detail": "Priebežne zobrazuje /var/log/proxmenux/backup-jobs/JOB_ID-YYYYMMDD_HHMMSS.log — jeden protokolový súbor na beh. Spúšťací skript tiež vydáva kompaktný stavový riadok na journald pod systemd služby."
       }
     ]
diff --git a/web/messages/sk/docs/installation.json b/web/messages/sk/docs/installation.json
index 5d067516..24ab7cce 100644
--- a/web/messages/sk/docs/installation.json
+++ b/web/messages/sk/docs/installation.json
@@ -53,11 +53,11 @@
     "calloutBody": "Chcete vyskúšať novinky pred oficiálnym vydaním a pomôcť vytvoriť konečnú verziu? Beta program ProxMenuxu sprístupňuje nové funkcie vrátane najnovších zostavení Monitora priamo z vetvy develop. Beta verzie môžu obsahovať chyby alebo nedokončené funkcie. Vaša spätná väzba pomáha opraviť ich pred stabilným vydaním.",
     "intro": "Namiesto stabilného nainštalujte beta verziu:",
     "code": "bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/develop/install_proxmenux_beta.sh)\"",
-    "outro": "Účasť v beta programe je dobrovoľná: musíte výslovne spustiť beta inštalátor. Po vydaní stabilnej verzie ProxMenux pri ďalšom spustení menu upozorní a ponúkne automatický prechod. Ak chcete beta program opustiť skôr, v nastaveniach vyberte Deaktivovať beta program."
+    "outro": "Účasť v beta programe je dobrovoľná: musíte výslovne spustiť beta inštalátor. Po vydaní stabilnej verzie ďalšie spustenie menu ponúkne ju s bežnou výzvou na aktualizáciu. Ak chcete beta kanál opustiť, v nastaveniach použite Zmeniť kanál vydaní a vyberte Stable."
   },
   "updating": {
     "heading": "Aktualizácia",
-    "body": "ProxMenux spravuje vlastné aktualizácie. Keď je dostupná nová verzia, ďalšie spustenie menu ponúkne jej inštaláciu. Po prijatí sa súbory a konfigurácia nástroja nahradia v existujúcej inštalácii, bez ručného sťahovania. Stabilný kanál dostáva stabilné vydania a beta kanál beta vydania spolu s vyššie opísanou ponukou prechodu. Podoba výzvy závisí od toho, kde spustíte menu: cez SSH alebo konzolu Proxmox sa zobrazí dialóg áno/nie a po potvrdení sa aktualizácia vykoná ihneď. V termináli WebSocket v ProxMenux Monitore sa namiesto neho zobrazí informačné okno, pretože tento terminál by sa počas aktualizácie prerušil. Okno ukazuje jednoriadkový inštalačný príkaz (bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\" pre stabilný kanál alebo zodpovedajúcu URL install_proxmenux_beta.sh pre beta kanál), ktorý spustíte cez SSH alebo konzolu."
+    "body": "ProxMenux spravuje vlastné aktualizácie. Keď je dostupná nová verzia, ďalšie spustenie menu ponúkne jej inštaláciu. Po prijatí sa súbory a konfigurácia nástroja nahradia v existujúcej inštalácii, bez ručného sťahovania. Stabilný kanál dostáva stabilné vydania; beta kanál dostáva beta zostavy a ponúkajú sa mu aj nové stabilné vydania. Podoba výzvy závisí od toho, kde spustíte menu: cez SSH alebo konzolu Proxmox sa zobrazí dialóg áno/nie a po potvrdení sa aktualizácia vykoná ihneď. V termináli WebSocket v ProxMenux Monitore sa namiesto neho zobrazí informačné okno, pretože tento terminál by sa počas aktualizácie prerušil. Okno ukazuje jednoriadkový inštalačný príkaz (bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\" pre stabilný kanál alebo zodpovedajúcu URL install_proxmenux_beta.sh pre beta kanál), ktorý spustíte cez SSH alebo konzolu."
   },
   "uninstall": {
     "heading": "Odinštalovanie",
diff --git a/web/messages/sk/docs/introduction.json b/web/messages/sk/docs/introduction.json
index 870fe71c..e9a55dcf 100644
--- a/web/messages/sk/docs/introduction.json
+++ b/web/messages/sk/docs/introduction.json
@@ -21,11 +21,11 @@
     "intro": "ProxMenux má dve súčasti v spoločnom zdrojovom strome. Inštalátor nainštaluje obe na hostiteľa: Scripts poskytuje terminálové rozhranie a Monitor webový panel:",
     "scripts": {
       "title": "ProxMenux Scripts",
-      "body": "Pôvodné rozhranie CLI/TUI, ktoré spustíte príkazom menu. Deväť oblastí pokrýva optimalizácie po inštalácii, VM a kontajnery LXC, priame priradenie GPU a Coral TPU, disky, úložiská, sieť, zabezpečenie a nástroje, spolu s prehľadom vybraných príkazov. Funguje v lokálnom termináli, cez SSH aj vo webovom termináli."
+      "body": "Pôvodné rozhranie CLI/TUI, ktoré spustíte príkazom menu. Funkčné oblasti pokrývajú optimalizácie po inštalácii, VM a kontajnery LXC, priame priradenie GPU a Coral TPU, disky, úložiská, aplikácie OCI, sieť, zabezpečenie, nástroje, zálohovanie a obnovu hostiteľa, Proxmox VE Helper Scripts, spolu s prehľadom vybraných príkazov. Funguje v lokálnom termináli, cez SSH aj vo webovom termináli."
     },
     "monitor": {
       "title": "ProxMenux Monitor",
-      "body": "Webový panel na vlastnom serveri, distribuovaný ako AppImage. Metriky hostiteľa v reálnom čase, proaktívne monitorovanie stavu, upozornenia, voliteľné preformulovanie pomocou AI so šiestimi poskytovateľmi, integrovaný webový terminál a REST API. Zapnete ho v ponuke Scripts cez Nastavenia → ProxMenux Monitor."
+      "body": "Webový panel na vlastnom serveri, distribuovaný ako AppImage. Metriky hostiteľa v reálnom čase, proaktívne monitorovanie stavu, upozornenia, voliteľné preformulovanie pomocou AI so šiestimi poskytovateľmi, integrovaný webový terminál a REST API. Inštalátor ho nastaví ako službu spúšťanú pri štarte; zapnete, vypnete alebo skontrolujete ho v ponuke Scripts cez Nastavenia → ProxMenux Monitor."
     },
     "calloutTitle": "Čo ProxMenux spravuje",
     "calloutIntro": "Tri vrstvy, všetky dosiahnuteľné z toho istého projektu:",
@@ -37,7 +37,7 @@
   },
   "scriptsSection": {
     "heading": "ProxMenux Scripts — čo ponúka",
-    "intro": "Deväť funkčných oblastí, každá s vlastnou ponukou v menu a podrobnou stránkou v dokumentácii. Kliknutím na kartu otvoríte prehľad príslušnej oblasti.",
+    "intro": "Hlavné funkčné oblasti, každá s vlastnou ponukou v menu a podrobnou stránkou v dokumentácii. Kliknutím na kartu otvoríte prehľad príslušnej oblasti.",
     "items": [
       {
         "title": "Po inštalácii",
diff --git a/web/messages/sk/docs/monitor/access-auth.json b/web/messages/sk/docs/monitor/access-auth.json
index 7f2adb94..e4a3359a 100644
--- a/web/messages/sk/docs/monitor/access-auth.json
+++ b/web/messages/sk/docs/monitor/access-auth.json
@@ -18,7 +18,7 @@
   },
   "reaching": {
     "heading": "Prístup k panelu",
-    "intro": "ProxMenux Monitor sa viaže na 0.0.0.0:8008. Existujú tri bežné spôsoby, ako ju dosiahnuť:",
+    "intro": "ProxMenux Monitor počúva na TCP 8008 na všetkých rozhraniach (::, IPv4 aj IPv6). Existujú tri bežné spôsoby, ako ho dosiahnuť:",
     "outro": "Priamy prístup zodpovedá predvolenej jednotke systemd. Reverzný proxy a Secure Gateway sú opísané nižšie. Dôvera v IP klientov z X-Forwarded-For vyžaduje PROXMENUX_TRUST_PROXY=1 a proxy pod vašou kontrolou. Samotná prítomnosť X-Forwarded-Proto alebo X-Forwarded-Host dôveru v proxy nezapne. Na portoch 80/443 frontend používa relatívne URL /api/*; na akomkoľvek inom porte volá port 8008 na rovnakom názve hostiteľa."
   },
   "firstLaunch": {
@@ -158,7 +158,7 @@
     "authJsonTitle": "auth.json — obsah a ochrana",
     "authJsonBody": "Údaje overenia sú v /root/.config/proxmenux-monitor/auth.json, režim 0600, vlastník root. Súbor obsahuje hashe PBKDF2 a tajné kryptografické údaje (jwt_secret, totp_secret), nikdy heslo v otvorenom texte. Cieľ zálohovania alebo replikácie šifrujte a súbor nikdy nepridávajte do systému správy verzií.",
     "rotateTitle": "Výmena jwt_secret zneplatní všetky existujúce JWT",
-    "rotateBody": "Pri novom vytvorení alebo obnove auth.json s iným kľúčom sa zmení jwt_secret. Staré JWT relácií aj API zlyhajú s \"Invalid or expired token\". Rozhranie ich označí Neplatný — vytvoriť znova. Zrušte ich a nahraďte aj v Home Assistant, skriptoch a ostatných klientoch.",
+    "rotateBody": "Pri novom vytvorení alebo obnove auth.json s iným kľúčom sa zmení jwt_secret. Staré JWT relácií aj API zlyhajú s \"Invalid or expired token\". Rozhranie ich označí červenou a štítkom, ktorý vás vyzve ich zrušiť a vytvoriť nové; zrušte ich a nahraďte aj v Home Assistant, skriptoch a ostatných klientoch.",
     "recoverTitle": "Obnovenie strateného hesla",
     "recoverIntro": "Obnova hesla vyžaduje prístup k shellu hostiteľa. ProxMenux ponúka sprievodcu resetovaním v konfiguračnej ponuke, aby ste nemuseli ručne upravovať auth.json:",
     "survivesTitle": "Čo prežíva reset",
@@ -284,14 +284,14 @@
     "heading": "API tokeny (dlhodobé)",
     "intro": "Relácie prehliadača platia 24 hodín. Pre Homepage, Home Assistant, Grafana či Uptime Kuma vytvorte samostatný token API platný 365 dní. JWT používa rovnaký podpisový kľúč; pole token_name umožňuje sledovať a jednotlivo zrušiť token.",
     "imageAlt": "Panel tokenov API znázorňujúci zoznam tokenov s názvom, predponou, vytvoreným dátumom a expiráciou",
-    "imageCaption": "Zoznam tokenov v nastaveniach: názov, predpona s poslednými štyrmi identifikačnými znakmi, dátum vytvorenia a platnosť, možnosť zrušenia.",
+    "imageCaption": "Zoznam tokenov API v Správa → Bezpečnosť: názov, predpona s poslednými štyrmi identifikačnými znakmi, dátum vytvorenia a platnosť, možnosť zrušenia.",
     "generateTitle": "Generovať token",
     "generateIntro": "Z panelu:",
     "generateSteps": [
-      "Otvorte Zabezpečenie → Prístupové tokeny API.",
+      "Otvorte Správa → Bezpečnosť → API tokeny a kliknite na Vytvoriť nový token.",
       "Zadajte opisný názov, napríklad \"Home Assistant\".",
       "Zadajte svoje heslo. Ak je zapnutá 2FA, aj súčasný 6-miestny kód.",
-      "Kliknite na Vytvoriť token. Zobrazí sa iba raz: hneď ho skopírujte."
+      "Kliknite na Vytvoriť token. Token sa zobrazí iba raz: hneď ho skopírujte."
     ],
     "generateCli": "Z príkazového riadku:",
     "useTitle": "Použitie tokenu",
@@ -310,7 +310,7 @@
     "intro": "Dve cesty k TLS:",
     "items": [
       "Odporúčaný reverzný proxy. Ukončite TLS na Nginx / Caddy / Traefik a posielajte HTTP procesu Flask na port 8008. Príklady sú nižšie.",
-      "Priame HTTPS v AppImage. Certifikát nastavte cez POST /api/ssl/configure (rozhranie: Nastavenia → SSL). HTTPS používa gevent.pywsgi so štandardným handlerom; flask-sock obsluhuje WebSocket pre WSS. Cesty certifikátov sa uložia v konfigurácii SSL."
+      "Priame HTTPS v AppImage. Certifikát nastavte cez POST /api/ssl/configure (rozhranie: Správa → Bezpečnosť → SSL / HTTPS). HTTPS používa gevent.pywsgi so štandardným handlerom; flask-sock obsluhuje WebSocket pre WSS. Cesty certifikátov sa uložia v konfigurácii SSL."
     ],
     "calloutTitle": "Priame obmedzenia HTTPS",
     "calloutBody": "Monitor môže použiť certifikát spravovaný Proxmoxom alebo vlastné súbory. Sám certifikáty ACME nevydáva. Na automatické vydávanie a obnovu použite Proxmox ACME alebo proxy Caddy, Traefik či Nginx a zabezpečte načítanie obnovených certifikátov."
diff --git a/web/messages/sk/docs/monitor/ai-assistant.json b/web/messages/sk/docs/monitor/ai-assistant.json
index 17993a92..b3d07c19 100644
--- a/web/messages/sk/docs/monitor/ai-assistant.json
+++ b/web/messages/sk/docs/monitor/ai-assistant.json
@@ -9,7 +9,7 @@
   },
   "header": {
     "title": "AI asistent",
-    "description": "Voliteľný prepis AI mení vhodné šablóny oznámení na zrozumiteľné správy s detailmi podľa kanála, trinástimi jazykmi rozhrania, vlastnými promptmi, verejnou komunitnou knižnicou a doplneným kontextom. Správy backup_complete a backup_fail AI úplne obchádzajú.",
+    "description": "Voliteľný prepis AI mení vhodné šablóny oznámení na zrozumiteľné správy s detailmi podľa kanála, trinástimi jazykmi rozhrania, vlastnými promptmi, verejnou komunitnou knižnicou a doplneným kontextom. Správy backup_complete a backup_fail sa nikdy neprepisujú; so zapnutými AI návrhmi AI len pridá vysvetlenie, keď záloha hlási chyby.",
     "section": "ProxMenux Monitor"
   },
   "intro": {
@@ -83,7 +83,7 @@
     "intro1": "Komerční poskytovatelia účtujú za token. Token zodpovedá približne štyrom znakom anglického textu alebo trom štvrtinám slova. Anglická veta \"Backup completed on storage local-bak\" má približne osem tokenov. Desať riadkov denníka môže podľa technického obsahu zabrať 200–400 tokenov.",
     "intro2": "Pri každom hovore sú fakturované dve veci:",
     "items": [
-      "Vstupné tokeny zahŕňajú systémový prompt a používateľskú správu: závažnosť, názov, text šablóny a doplnený kontext. Počet závisí od skutočného promptu a udalosti. Každý prepisovaný kanál môže spotrebovať vlastné vstupné tokeny; správy o dokončení alebo zlyhaní zálohy AI obchádzajú.",
+      "Vstupné tokeny zahŕňajú systémový prompt a používateľskú správu: závažnosť, názov, text šablóny a doplnený kontext. Počet závisí od skutočného promptu a udalosti. Každý prepisovaný kanál môže spotrebovať vlastné vstupné tokeny; správy o dokončení alebo zlyhaní zálohy spotrebujú tokeny len vtedy, keď AI vysvetľuje chyby zálohy.",
       "Výstupné tokeny – čo model odpíše. Monitor to uzatvára pomocou max_tokens (pozri tabuľku nižšie). Limit je limit, nie poplatok: ak model vyrába 250 tokeny s limitom 1500, zaplatíte za 250."
     ],
     "capsIntro": "Toto sú skutočné limity, ktoré používa dispečer, prevzaté priamo z AI_DETAIL_TOKENS v notification_templates.py:",
@@ -118,7 +118,7 @@
     "imageCaption": "Modál v aplikácii – šesť kariet, jedna na poskytovateľa, s rovnakými popismi zdokumentovanými nižšie.",
     "groq": {
       "heading": "Groq",
-      "tagline": "Veľmi rýchla, veľkorysá voľná úroveň (30 req/min). Ideálne začať.",
+      "tagline": "Veľmi rýchla, s veľkorysou bezplatnou úrovňou. Dobré miesto na začiatok.",
       "items": [
         "Kľúč API: console.groq.com/keys",
         "Príklady z dodaného zoznamu modelov: allam-2-7b, qwen/qwen3.8-27b, openai/gpt-oss-20b, openai/gpt-oss-120b.",
@@ -234,7 +234,7 @@
   },
   "detailLevel": {
     "heading": "Úroveň detailov pre každý kanál",
-    "intro": "Telegram, Discord, Gotify a e-mail majú vlastnú ponuku úrovne detailov. Tri hodnoty určujú limit výstupných tokenov a pokyny predvoleného promptu:",
+    "intro": "Každý zo šiestich kanálov (Telegram, Discord, Gotify, e-mail, Pushover, Apprise) má vlastnú ponuku úrovne detailov. Tri hodnoty určujú limit výstupných tokenov a pokyny predvoleného promptu:",
     "headerLevel": "Úroveň",
     "headerLabel": "Štítok UI",
     "headerCap": "Výstupný strop",
@@ -261,7 +261,7 @@
     ],
     "defaultsIntro": "Predvolené hodnoty pri prvej inštalácii:",
     "defaults": [
-      "Telegram, Discord, Gotify — standard.",
+      "Telegram, Discord, Gotify, Pushover, Apprise — standard.",
       "E-mail — detailed. E-mail je kanál, kde zvyčajne chcete úplný obraz pre archív."
     ],
     "emailTitle": "Podrobný e-mail pripája pôvodnú správu",
@@ -280,7 +280,7 @@
   },
   "templates": {
     "heading": "Poznámka k šablónam",
-    "body1": "AI dostáva text vytvorený zo šablóny, nie surové údaje udalosti. Typy ako vm_start, auth_fail a health_degraded majú šablóny v notification_templates.py. AI prepisuje vhodné výstupy; backup_complete a backup_fail si ponechávajú deterministické správy bez AI.",
+    "body1": "AI dostáva text vytvorený zo šablóny, nie surové údaje udalosti. Typy ako vm_start, auth_fail a health_degraded majú šablóny v notification_templates.py. AI prepisuje vhodné výstupy; backup_complete a backup_fail si ponechávajú deterministické správy; so zapnutými AI návrhmi AI len pridá vysvetlenie, keď záloha hlási chyby.",
     "body2": "Názvy hostiteľov a VMID vloží šablóna ešte pred prepisom; AI ich nemusí vymýšľať. Pri vypnutej AI sa odošle priamo text šablóny. Stránka Oznámenia opisuje celý postup odosielania a kroky pred touto vrstvou."
   },
   "privacy": {
diff --git a/web/messages/sk/docs/monitor/api-reference.json b/web/messages/sk/docs/monitor/api-reference.json
index 379dbcb3..09215908 100644
--- a/web/messages/sk/docs/monitor/api-reference.json
+++ b/web/messages/sk/docs/monitor/api-reference.json
@@ -76,7 +76,7 @@
       {
         "endpoint": "/api/system-info",
         "method": "GET",
-        "use": "Rozšírené systémové údaje pre hlavičku panela: celkové metriky a čas spustenia."
+        "use": "Ľahké informácie hlavičky: názov hostiteľa, uptime a vyrovnávací stav zdravia. Verejné, token nie je potrebný."
       },
       {
         "endpoint": "/api/hardware",
@@ -401,7 +401,7 @@
       {
         "endpoint": "/api/logs",
         "method": "GET",
-        "use": "Filtrované záznamy systémového denníka. Parametre: ?level=&service=&limit=."
+        "use": "Filtrované záznamy systémového denníka. Parametre: ?priority=&service=&limit=&since_days= (priority 0-7, since_days do 90)."
       },
       {
         "endpoint": "/api/logs/download",
@@ -411,7 +411,7 @@
       {
         "endpoint": "/api/events",
         "method": "GET",
-        "use": "Interný tok udalostí používaný aj na oznámenia."
+        "use": "Nedávne úlohy a udalosti klastra Proxmox z pvesh get /cluster/tasks. Parameter: ?limit= (predvolene 50)."
       },
       {
         "endpoint": "/api/task-log/",
@@ -462,7 +462,7 @@
       {
         "endpoint": "/api/notifications/send",
         "method": "POST",
-        "use": "Vytvorí vlastnú udalosť. Telo: '{'\"event_type\":\"custom\",\"severity\":\"WARNING\",\"title\":\"...\",\"body\":\"...\",\"data\":'{''}''}'."
+        "use": "Vytvorí vlastnú udalosť. Telo: '{'\"event_type\":\"custom\",\"severity\":\"WARNING\",\"title\":\"...\",\"message\":\"...\",\"data\":'{''}''}'."
       },
       {
         "endpoint": "/api/notifications/test-ai",
diff --git a/web/messages/sk/docs/monitor/architecture.json b/web/messages/sk/docs/monitor/architecture.json
index d160d4ad..3dc020db 100644
--- a/web/messages/sk/docs/monitor/architecture.json
+++ b/web/messages/sk/docs/monitor/architecture.json
@@ -64,7 +64,7 @@
     "items": [
       "User=root: služba používa oprávnenia root na SMART, pvesh a správu ZFS. Procesy webového terminálu dedia tieto oprávnenia.",
       "Restart=on-failure s odstupom desať sekúnd: po chybovom ukončení sa služba automaticky reštartuje.",
-      "After=network.target: zaradí štart po tomto cieli; nezaručuje pridelenú IP, DNS ani vonkajšie spojenie."
+      "After=network.target pve-cluster.service: zaradí štart po sieťovom cieli a súborovom systéme klastra Proxmox; nezaručuje pridelenú IP, DNS ani vonkajšie spojenie."
     ],
     "inspectTitle": "Kontrola aktuálnej jednotky"
   },
diff --git a/web/messages/sk/docs/monitor/dashboard/hardware.json b/web/messages/sk/docs/monitor/dashboard/hardware.json
index 42456689..701b8837 100644
--- a/web/messages/sk/docs/monitor/dashboard/hardware.json
+++ b/web/messages/sk/docs/monitor/dashboard/hardware.json
@@ -168,7 +168,7 @@
   "ups": {
     "heading": "Stav UPS",
     "subHeading": "(podmienečné)",
-    "body": "Zobrazí sa pri nastavenom a dostupnom serveri NUT (Network UPS Tools). Ukazuje stav (sieť / batéria / nabíjanie / slabá batéria), nabitie batérie, odhad výdrže, percento zaťaženia, vstupné napätie, model a firmvér. Rovnaké údaje používa Bezpečnosť a certifikáty monitora stavu; prechod na batériu sa hneď zobrazí."
+    "body": "Zobrazí sa pri nastavenom a dostupnom serveri NUT (Network UPS Tools). Ukazuje stav (sieť / batéria / nabíjanie / slabá batéria), nabitie batérie, odhad výdrže, percento zaťaženia, vstupné napätie, model a firmvér."
   },
   "dataCollected": {
     "heading": "Spôsob zberu údajov",
diff --git a/web/messages/sk/docs/monitor/dashboard/index.json b/web/messages/sk/docs/monitor/dashboard/index.json
index 36c6a2a7..21dc2cdf 100644
--- a/web/messages/sk/docs/monitor/dashboard/index.json
+++ b/web/messages/sk/docs/monitor/dashboard/index.json
@@ -50,7 +50,7 @@
       },
       {
         "name": "Terminál",
-        "owns": "Shell hostiteľa alebo ľubovoľnej VM/CT v prehliadači pomocou xterm.js cez WebSockets."
+        "owns": "Shell v prehliadači na hostiteľovi Proxmox pomocou xterm.js cez WebSockets. Konzoly LXC sa otvárajú z karty VM a LXC."
       },
       {
         "name": "Zabezpečenie",
diff --git a/web/messages/sk/docs/monitor/dashboard/security.json b/web/messages/sk/docs/monitor/dashboard/security.json
index 405314d5..cc5e939d 100644
--- a/web/messages/sk/docs/monitor/dashboard/security.json
+++ b/web/messages/sk/docs/monitor/dashboard/security.json
@@ -183,7 +183,7 @@
       },
       {
         "card": "SSL / HTTPS",
-        "endpoint": "/api/auth/ssl/*",
+        "endpoint": "/api/ssl/*",
         "source": "openssl x509 na /etc/pve/local/pve-ssl.pem + /etc/proxmenux/ssl_config.json."
       },
       {
diff --git a/web/messages/sk/docs/monitor/dashboard/settings.json b/web/messages/sk/docs/monitor/dashboard/settings.json
index a6241bca..6d3195e4 100644
--- a/web/messages/sk/docs/monitor/dashboard/settings.json
+++ b/web/messages/sk/docs/monitor/dashboard/settings.json
@@ -41,7 +41,7 @@
     "heading": "Jednotky siete",
     "imageAlt": "Karta Jednotky siete s výberom zobrazenia na Bajty",
     "imageCaption": "Vyberte bity alebo bajty za sekundu pre všetky sieťové rýchlosti v prehľade.",
-    "body": "Zobrazuje priepustnosť v bitoch za sekundu (Mbps/Gbps) alebo bajtoch za sekundu (MB/s/GB/s). Predvolené sú bity ako v špecifikáciách kariet a poskytovateľov; prenosy súborov často používajú bajty. Grafy, štítky a pomôcky sa zmenia ihneď bez obnovenia."
+    "body": "Zobrazuje priepustnosť v bitoch za sekundu (Mbps/Gbps) alebo bajtoch za sekundu (MB/s/GB/s). Predvolené sú bajty. Voľba sa ukladá v prehliadači. Grafy, štítky a pomôcky sa zmenia ihneď bez obnovenia."
   },
   "health": {
     "heading": "Monitor zdravia",
@@ -203,7 +203,7 @@
     "heading": "Kontrola aktualizácií v LXC",
     "imageAlt": "Karta zisťovania aktualizácií LXC s prepínačom hľadania balíkov v spustených kontajneroch Debian, Ubuntu a Alpine",
     "imageCaption": "Prepínač pravidelnej kontroly cez apt list --upgradable / apk list -u v spustených CT. Predvolene zapnutý. Súvisiaci prepínač v Oznámenia → Služby sa zobrazuje pri zapnutej detekcii.",
-    "intro": "Prepínač medzi Prahmi a Oznámeniami riadi kontrolu LXC. Zapnutý zisťuje balíky spustených CT; Hardvér zobrazuje výsledky a vstupujú do lxc_updates_available. Vypnutý zastaví volania pct exec a odstráni záznamy LXC z managed_installs.json; /api/managed-installs prestane hlásiť stav bez čakania 24 hodín.",
+    "intro": "Prepínač medzi prahmi monitora stavu a kartou Oznámenia riadi kontrolu aktualizácií LXC. Zapnutý zisťuje čakajúce aktualizácie balíkov v spustených CT; výsledky sa zobrazujú na karte VM a LXC a používajú sa v oznámení lxc_updates_available. Vypnutý zastaví volania pct exec a odstráni záznamy LXC z managed_installs.json; /api/managed-installs prestane hlásiť stav bez čakania na ďalší 24-hodinový cyklus.",
     "whatRunsTitle": "Čo kontrola spúšťa",
     "whatRunsIntro": "Pre každý CT v stave running s podporovaným správcom balíkov:",
     "whatRunsItems": [
@@ -249,7 +249,7 @@
     "body1": "Tu zapnete oznámenia ProxMenux Monitor a preformulovanie pomocou AI. Zapnúť oznámenia spustí odosielanie na pozadí, zaregistruje cieľ webhooku Proxmox VE na príjem jeho udalostí a otvorí nastavenia kanálov Telegram, Discord, email, Gotify a ďalších. AI je v rozbaľovacej rozšírenej časti toho istého panela.",
     "body2": "Možnosti vysvetľujú samostatné stránky: kanály, prepínače udalostí, formátované správy, zobrazované meno, odosielanie s odstránením duplicít, odstupom, zoskupením a tichým časom, webhook PVE, poskytovatelia AI a režimy pokynov:",
     "items": [
-      "Oznámenia: nastavenie kanálov (Telegram, Discord, Gotify, email a heslá aplikácií Gmail / Microsoft, ntfy, Slack, Teams a všeobecný webhook), kategórie udalostí, formátované správy, zobrazované meno, odosielanie, webhook PVE, história a API.",
+      "Oznámenia: nastavenie kanálov (Telegram, Discord, Gotify, e-mail a heslá aplikácií Gmail / Microsoft, Pushover a Apprise pre ntfy, Slack, Teams a ďalšie služby), kategórie udalostí, formátované správy, zobrazované meno, odosielanie, webhook PVE, história a API.",
       "Asistent AI: poskytovatelia (OpenAI, Anthropic, Gemini, Groq, OpenRouter, Ollama), modely, predvolené alebo vlastné pokyny, jazyk výstupu, podrobnosť pre kanály a návrhy AI."
     ]
   },
@@ -294,8 +294,8 @@
       },
       {
         "card": "Jednotky siete",
-        "endpoint": "/api/settings",
-        "source": "Uložené v tabuľke nastavení SQLite panela."
+        "endpoint": "localStorage",
+        "source": "Predvoľba prehliadača proxmenux-network-unit."
       },
       {
         "card": "Trvanie v Health Monitor",
@@ -304,7 +304,7 @@
       },
       {
         "card": "Vylúčenie úložiska / rozhrania",
-        "endpoint": "/api/storage/exclusions, /api/network/exclusions",
+        "endpoint": "/api/health/storage-exclusions, /api/health/interface-exclusions",
         "source": "SQLite tabuľky excluded_storages a excluded_interfaces."
       },
       {
diff --git a/web/messages/sk/docs/monitor/dashboard/storage.json b/web/messages/sk/docs/monitor/dashboard/storage.json
index 60e5e65d..0e99d1b2 100644
--- a/web/messages/sk/docs/monitor/dashboard/storage.json
+++ b/web/messages/sk/docs/monitor/dashboard/storage.json
@@ -10,7 +10,7 @@
   },
   "intro": {
     "title": "Podporované tromi zdrojmi",
-    "body": "Úložiská Proxmox pochádzajú z pvesm status, ZFS zo zpool status, disky z lsblk + smartctl a polia NVMe z nvme. Karta sa obnovuje približne každých 60 sekúnd; otvorenie detailu vyžiada načítanie SMART."
+    "body": "Úložiská Proxmox pochádzajú z pvesm status, ZFS zo zpool status, disky z lsblk + smartctl a polia NVMe z nvme. Karta sa obnovuje približne každých 30 sekúnd; otvorenie detailu vyžiada načítanie SMART."
   },
   "thresholds": {
     "title": "Farby stavu a prahové hodnoty, ktoré sa tu uplatňujú",
diff --git a/web/messages/sk/docs/monitor/dashboard/system-logs.json b/web/messages/sk/docs/monitor/dashboard/system-logs.json
index ff1b0f8b..6b496368 100644
--- a/web/messages/sk/docs/monitor/dashboard/system-logs.json
+++ b/web/messages/sk/docs/monitor/dashboard/system-logs.json
@@ -18,7 +18,7 @@
       "Celkový počet záznamov — počet položiek v časovom rozsahu aktívneho filtra.",
       "Chyby — počet záznamov so závažnosťou ≤ 3 (err / crit / alert / emerg).",
       "Upozornenia — počet záznamov so závažnosťou 4 (warning).",
-      "Zálohy — počet záznamov úloh vzdump / PBS v rovnakom časovom rozsahu."
+      "Zálohy: počet záložných archívov na nastavených úložiskách záloh."
     ]
   },
   "subtabs": {
@@ -26,10 +26,10 @@
     "logsTitle": "Logy",
     "logsIntro": "Systémový žurnál, ktorý server načítava cez journalctl. Na paneli nástrojov sú tieto filtre:",
     "logsFilters": [
-      "Závažnosť — emerg / alert / crit / err / warning / notice / info / debug alebo ľubovoľná kombinácia.",
-      "Časový rozsah Posledná 5 min/ 15 min/ 1 h / 6 h / 24 h / 7 d / vlastné.",
-      "Voľné vyhľadávanie textu — podreťazec alebo regulárny výraz (journalctl --grep).",
-      "Filter jednotky — obmedzí výsledky na konkrétnu jednotku systemd (pveproxy.service, nginx.service, …)."
+      "Úroveň: Všetky úrovne, Chyba, Upozornenie alebo Info.",
+      "Časový rozsah: 1 deň / 3 dni / 1 týždeň / 2 týždne / 1 mesiac alebo vlastný počet dní (do 90).",
+      "Voľné vyhľadávanie textu: podreťazec bez rozlišovania veľkosti písmen v správe, službe, PID, názve hostiteľa a jednotke, uplatnený v prehliadači.",
+      "Filter služby: obmedzí výsledky na jednu službu (SYSLOG_IDENTIFIER denníka)."
     ],
     "logsRowsAfter": "Každý riadok zobrazuje časovú pečiatku, označenie závažnosti, zdrojovú jednotku a správu. Dlhé správy sa zbalia s prepínačom „zobraziť viac“. Akcia Stiahnuť vytvorí z aktuálneho filtra jeden súbor .txt cez GET /api/logs/download, vhodný na zdieľanie časti žurnálu s ďalšou osobou.",
     "logDetailsModalTitle": "Okno Podrobnosti záznamu",
@@ -50,10 +50,10 @@
     "maxLevelStoreTitle": "Journald MaxLevelStore",
     "maxLevelStoreBody": "Na novej inštalácii Proxmox používa journald predvolene MaxLevelStore=warning, čím zahadzuje správy info. Monitor to pri štarte rozpozná a pridá doplnkovú konfiguráciu (/etc/systemd/journald.conf.d/proxmenux-loglevel.conf) s prahom info, aby sa uchovávali informačné a závažnejšie správy. Správy debug to nezahŕňa.",
     "backupsTitle": "Zálohy",
-    "backupsBody": "História úloh Proxmox obmedzená na zálohovanie. Jeden riadok na úlohu (vzdump, prenosy PBS, čistenie nepoužívaných dát a overenie), so stavom (OK / WARNINGS / ERROR), hosťovským systémom, zdrojovým úložiskom, trvaním a UPID. Kliknutím na riadok načítate úplný záznam úlohy cez GET /api/task-log/<upid> — rovnaké údaje ako v Datacenter → Úlohy, obmedzené na zálohy.",
+    "backupsBody": "Záložné archívy na každom úložisku schopnom ukladať zálohy (dir, NFS, CIFS, PBS), načítané cez pvesh get /nodes/localhost/storage/<id>/content. Karty hore ukazujú počty VM a LXC a celkovú veľkosť; pod nimi je jeden riadok na archív s typom, úložiskom, veľkosťou a dátumom. Obnova sa vykonáva vo webovom rozhraní Proxmox.",
     "notificationsTitle": "Oznámenia",
-    "notificationsBody1": "Všetky oznámenia odoslané Monitorom: Telegram, Discord, e-mail, Gotify, ntfy, Slack, Teams a webhook. Každý riadok uvádza časovú pečiatku, kanál, typ udalosti, závažnosť, vytvorený názov a telo a pri zapnutej AI prepínač na porovnanie jej prepracovanej verzie s originálom.",
-    "notificationsBody2": "Na tejto karte overíte doručovanie oznámení cez kanál a porovnáte prepracovanie AI s pôvodnou šablónou. Kanály sa nastavujú na podrobnej stránke Oznámenia."
+    "notificationsBody1": "Aktivita oznámení Proxmox: riadky denníka zo služieb Proxmox, ktoré spomínajú oznámenia, e-mail, webhooky alebo upozornenia, ciele v /etc/pve/notifications.cfg a úlohy zálohovania zo zoznamu úloh klastra. Riadky úloh zálohovania umožňujú stiahnuť úplný záznam úlohy. História toho, čo odoslal samotný Monitor, je v Nastavenia → Notifikácie → História upozornení.",
+    "notificationsBody2": "Táto karta slúži na kontrolu, čo robí vlastný systém oznámení Proxmoxu. Kanály Monitora sa nastavujú na podrobnej stránke Oznámenia."
   },
   "dataCollected": {
     "heading": "Spôsob zberu údajov",
@@ -64,7 +64,7 @@
       {
         "subtab": "Záznamy (živý filter)",
         "endpoint": "/api/logs",
-        "source": "journalctl --output json --since <range> s filtrami závažnosti, jednotky a vyhľadávania použitými na serveri."
+        "source": "Karta načíta /api/logs?since_days=N; filtre úrovne, služby a textu sa uplatňujú v prehliadači. API prijíma aj priority a service, ktoré karta zatiaľ nepoužíva."
       },
       {
         "subtab": "Stiahnuť",
@@ -74,17 +74,17 @@
       {
         "subtab": "Zálohy",
         "endpoint": "/api/backups",
-        "source": "História úloh PVE filtrovaná podľa vzdump, prenosov PBS, čistenia nepoužívaných dát a overenia."
+        "source": "Záložné archívy zo zoznamu každého úložiska záloh cez pvesh get /nodes/localhost/storage/<id>/content."
       },
       {
-        "subtab": "Podrobnosti úlohy zálohovania",
+        "subtab": "Záznam úlohy zálohovania (Notifikácie)",
         "endpoint": "/api/task-log/<upid>",
         "source": "Plný textový záznam úloh čítaný z /var/log/pve/tasks/<index>/<upid>."
       },
       {
-        "subtab": "História oznámení",
-        "endpoint": "/api/notifications/history",
-        "source": "Tabuľka SQLite notification_history, ktorú plní odosielacia slučka."
+        "subtab": "Oznámenia",
+        "endpoint": "/api/notifications",
+        "source": "journalctl --grep nad jednotkami Proxmox, /etc/pve/notifications.cfg a úlohy zálohovania z pvesh get /cluster/tasks."
       }
     ],
     "apiIntro": "Živý filter aj sťahovanie sú dostupné cez API:",
diff --git a/web/messages/sk/docs/monitor/dashboard/system-overview.json b/web/messages/sk/docs/monitor/dashboard/system-overview.json
index 3381d241..7959bcf9 100644
--- a/web/messages/sk/docs/monitor/dashboard/system-overview.json
+++ b/web/messages/sk/docs/monitor/dashboard/system-overview.json
@@ -46,7 +46,7 @@
     "thresholdsIntro": "Prstence, ukazovatele a minigrafy používajú rovnakú škálu:  zelená pod prahom Upozornenie,  jantárová medzi Upozornenie a Kritické,  červená od prahu Kritické vyššie. Odporúčané predvolené hodnoty ProxMenux:",
     "thresholdsItems": [
       "Využitie CPU — upozornenie 85 %, kritické 95 %.",
-      "Pamäť — upozornenie 85 %, kritické 95 %. Swap tiež vyvolá kritický stav pri 5 % využitia; správne fungujúci hostiteľ Proxmox by ho mal používať len zriedka.",
+      "Pamäť: upozornenie 85 %, kritické 95 %. Swap vyvolá kritický stav, iba ak využitie swapu presiahne 80 % a dostupná RAM klesne pod 15 %.",
       "Teplota CPU — upozornenie 80 °C, kritické 90 °C."
     ],
     "thresholdsOutro": "Hodnoty možno nastaviť pre každého hostiteľa. Nastavenia → Prahy monitora stavu sú hlavný zdroj a vysvetľujú ich úpravu.",
@@ -58,7 +58,7 @@
     "intro": "Karty Využitie CPU a Pamäť otvárajú triediteľný zoznam 25 najnáročnejších procesov: podľa využitia CPU pri otvorení z CPU alebo podľa rezidentnej pamäte pri otvorení z Pamäte.",
     "listTitle": "Dialóg zoznamu",
     "listItems": [
-      "Automatické obnovenie — zoznam sa aktualizuje každých 5 sekúnd, kým je okno otvorené; po zatvorení sa dotazy zastavia.",
+      "Automatické obnovenie — zoznam sa aktualizuje každé 3 sekundy, kým je okno otvorené; po zatvorení sa dotazy zastavia.",
       "Filter — vyhľadávanie obmedzí zoznam podľa príkazu, používateľa alebo PID.",
       "Ukazovateľ v riadku — stĺpec hlavnej metriky kreslí malý ukazovateľ škálovaný podľa najvyššej hodnoty vo filtrovanom zozname. Poradie zostáva zrejmé, aj keď žiadny proces nie je blízko 100 %.",
       "Mobilné rozloženie — pod 640 px sa skryjú stĺpce PID a Používateľ, aby sa Príkaz, CPU % a Pamäť zmestili na telefón bez vodorovného posúvania."
@@ -79,7 +79,7 @@
   },
   "middle": {
     "heading": "Stred: grafy metrík uzla",
-    "body1": "Pod kartami zobrazuje NodeMetricsCharts históriu CPU, pamäte a diskového I/O z úložiska RRD Proxmox cez /api/node/metrics. Volič ponúka 1 hodina / 24 hodín / 7 dní / 30 dní / 1 rok. Pri dlhšom období klesá rozlíšenie údajov, aby bol graf plynulý.",
+    "body1": "Pod kartami zobrazuje NodeMetricsCharts históriu CPU, pamäte a diskového I/O z úložiska RRD Proxmox cez /api/node/metrics. Volič ponúka 1 hodina / 24 hodín / 7 dní / 30 dní. Pri dlhšom období klesá rozlíšenie údajov, aby bol graf plynulý.",
     "body2": "Rovnaké grafy ako vo webovom rozhraní Proxmox pre uzol, začlenené do tmavej témy Monitora a zarovnané s ostatnými panelmi."
   },
   "bottom": {
diff --git a/web/messages/sk/docs/monitor/dashboard/vms-lxcs.json b/web/messages/sk/docs/monitor/dashboard/vms-lxcs.json
index c8fbb299..8e216683 100644
--- a/web/messages/sk/docs/monitor/dashboard/vms-lxcs.json
+++ b/web/messages/sk/docs/monitor/dashboard/vms-lxcs.json
@@ -117,7 +117,7 @@
       "Cieľ — úložisko (PBS-Cloud, PBS-Local, NFS-Backup, …), farebne podľa stavu.",
       "Veľkosť — konečná veľkosť zálohy na disku."
     ],
-    "backupsOutro": "+ Vytvoriť zálohu spustí zálohu na úložisku označenom „Backup target“ v Proxmoxe. Obnova sa vykonáva vo webovom rozhraní Proxmox; Monitor ukazuje nedávne zálohy systému a umožňuje vytvoriť nové.",
+    "backupsOutro": "Tlačidlo + Vytvoriť zálohu vpravo hore otvorí dialóg na výber úložiska, režimu (snapshot / suspend / stop), ochrany a poznámok pred spustením vzdump. Obnova sa vykonáva vo webovom rozhraní Proxmox; Monitor ukazuje pohľad „bola táto VM nedávno zálohovaná?“, nie postup obnovy.",
     "firewallTitle": "Karta 7 — Firewall",
     "firewallIntro": "Číta záznam firewallu Proxmox daného systému priamo z hostiteľa, bez ďalšej služby a pravidelných dotazov. Karta je vždy prítomná; obsah závisí od zapnutia firewallu a od toho, či pravidlo zaznamenáva pakety.",
     "firewallItems": [
diff --git a/web/messages/sk/docs/monitor/health-monitor.json b/web/messages/sk/docs/monitor/health-monitor.json
index 83b459a1..5cbd7a3f 100644
--- a/web/messages/sk/docs/monitor/health-monitor.json
+++ b/web/messages/sk/docs/monitor/health-monitor.json
@@ -153,8 +153,8 @@
     "pillTitle": "Indikátor zodpovedá najzávažnejšej kategórii",
     "pillBody": "Farba hlavičky zodpovedá najvyššej závažnosti: CRITICAL → červená, inak WARNING → žltá, inak INFO → modrá, inak zelená. Rovnaká logika riadi bod vo favikone aj odznak PWA.",
     "updateNowTitle": "Aktualizovať teraz tlačidlo v hlavičke Prehľad",
-    "updateNowBody1": "Ak Aktualizácie systému hlásia čakajúce aktualizácie, pri indikátore sa zobrazí Aktualizovať teraz. Okno vykoná postup z ProxMenux → Settings post-install Proxmox → Proxmox System Update: čistenie repozitárov, apt full-upgrade cez update-pve-safe.sh, opätovné zostavenie spravovaných ovládačov DKMS pri novom jadre, autoremove/autoclean a ponuku reštartu pri zmene jadra. Priebeh sa zobrazuje priebežne.",
-    "updateNowBody2": "Po dokončení zatvorenie okna zavolá GET /api/health/details?refresh=1 a zneplatní vyrovnávaciu pamäť koncového bodu. Čakajúce aktualizácie a závažnosť sa prepočítajú podľa nového stavu balíkov. Odznak zmizne bez čakania na ďalší cyklus. Ak je hostiteľ aktuálny, tlačidlo je skryté."
+    "updateNowBody1": "Ak Aktualizácie systému hlásia čakajúce aktualizácie, pri indikátore sa zobrazí Aktualizovať teraz. Okno vykoná postup z ProxMenux → Nástroje → Aktualizácia systému Proxmox: čistenie repozitárov, apt full-upgrade cez update-pve-safe.sh, opätovné zostavenie spravovaných ovládačov DKMS pri novom jadre, autoremove/autoclean a ponuku reštartu pri zmene jadra. Priebeh sa zobrazuje priebežne.",
+    "updateNowBody2": "Po dokončení zatvorenie okna zavolá GET /api/health/full?refresh=1 a zneplatní vyrovnávaciu pamäť koncového bodu. Čakajúce aktualizácie a závažnosť sa prepočítajú podľa nového stavu balíkov. Odznak zmizne bez čakania na ďalší cyklus. Ak je hostiteľ aktuálny, tlačidlo je skryté."
   },
   "dismiss": {
     "heading": "Potlačenie upozornení a jeho trvanie",
diff --git a/web/messages/sk/docs/monitor/index.json b/web/messages/sk/docs/monitor/index.json
index d948916c..2e5dbb30 100644
--- a/web/messages/sk/docs/monitor/index.json
+++ b/web/messages/sk/docs/monitor/index.json
@@ -113,13 +113,13 @@
   "api": {
     "heading": "REST API a integrácie",
     "intro": "Endpointy REST poskytujú monitorovacie dáta JSON cez HTTP/HTTPS pre Homepage, Home Assistant, Uptime Kuma a skripty. Grafana môže použiť samostatný textový exportér Prometheus na /api/prometheus. Požiadavky možno posielať cez curl.",
-    "tokens": "Tokeny API s dlhou životnosťou (365 dni) sa generujú z Nastavenia → API prístupových tokenov alebo prostredníctvom POST /api/auth/generate-api-token.",
+    "tokens": "Tokeny API s dlhou životnosťou (365 dni) sa generujú z Správa → Bezpečnosť → API tokeny alebo prostredníctvom POST /api/auth/generate-api-token.",
     "bearer": "Tokeny posielajte ako Authorization: Bearer …. Aj /api/health očakáva hlavičku Bearer, takže kontroly dostupnosti potrebujú token read_only. Pod /api/auth/* sú verejné trasy nastavenia/prihlásenia/stavu aj chránené trasy správy účtov/tokenov; nie všetky sú anonymné.",
     "catalog": "Katalóg endpointov, rotácia tokenov a zabezpečenie sú v Referenčnej príručke API; príklady pre Homepage, Home Assistant, Grafana, Uptime Kuma a cURL sú v Integrácie."
   },
   "serviceControl": {
     "heading": "Ovládanie služby",
-    "intro": "Každý deň je monitor spravovaný presne ako ktorákoľvek iná služba systemd. Je tiež vystavený ako dva záznamy v ProxMenux TUI pod Nastavenia:",
+    "intro": "Každý deň je monitor spravovaný presne ako ktorákoľvek iná služba systemd. Je tiež vystavený ako tri záznamy v ProxMenux TUI pod Nastavenia:",
     "codeComment": "# Manuálne ovládanie",
     "footer": "Pozrite si Nastavenia → ProxMenux Monitor, kde nájdete postup prepínania a overovania stavu v ponuke."
   },
diff --git a/web/messages/sk/docs/monitor/integrations.json b/web/messages/sk/docs/monitor/integrations.json
index 38748402..1bf6356b 100644
--- a/web/messages/sk/docs/monitor/integrations.json
+++ b/web/messages/sk/docs/monitor/integrations.json
@@ -20,13 +20,13 @@
     "heading": "Overenie: jedna vec, ktorú každý recept potrebuje",
     "intro": "Väčšina použitých koncových bodov vyžaduje overenie. Požiadavku možno splniť dvoma spôsobmi.",
     "optAtitle": "Možnosť A API token (odporúčané pre integráciu)",
-    "optAbody1": "Otvorte Nastavenia → Bezpečnosť → API tokeny. Kliknite na Generovať token, zadajte názov ako homepage, home-assistant alebo prometheus a skopírujte ho: zobrazí sa iba raz. Predvolene platí rok a možno ho samostatne odvolať. Používajte ho pre klientov mimo prehliadača.",
+    "optAbody1": "Otvorte Správa → Bezpečnosť → API tokeny. Kliknite na Vytvoriť nový token, zadajte názov ako homepage, home-assistant alebo prometheus, vyplňte heslo (a kód 2FA, ak je zapnutá), kliknite na Vytvoriť token a hneď ho skopírujte: zobrazí sa iba raz. Predvolene platí rok a možno ho samostatne odvolať. Používajte ho pre klientov mimo prehliadača.",
     "optAbody2": "Od tohto bodu je každá žiadosť jednoducho:",
     "optBtitle": "Možnosť B — prihlásenie menom a heslom",
     "optBbody": "Vhodné pre skripty, ktoré sa overujú ako používateľ. Vrátený token platí krátko; väčšina integrácií by mala použiť možnosť A.",
     "outro": "Pole TOTP treba iba pri zapnutom dvojfaktorovom overení. Výmenu a odvolanie tokenov, pravidlá hesiel a auditný denník opisuje Prístup a overenie.",
     "httpsTitle": "Používanie HTTPS namiesto HTTP",
-    "httpsIntro": "Príklady používajú pre stručnosť http://. Ak ste na Monitore zapli TLS (Nastavenia → Bezpečnosť → SSL/HTTPS), v každej URL nahraďte http:// za https://. Dve poznámky pre konkrétne nástroje:",
+    "httpsIntro": "Príklady používajú pre stručnosť http://. Ak ste na Monitore zapli TLS (Správa → Bezpečnosť → SSL / HTTPS), v každej URL nahraďte http:// za https://. Dve poznámky pre konkrétne nástroje:",
     "httpsItems": [
       "Certifikáty podpísané vlastným kľúčom. Integrácia Home Assistant rest: predvolene overuje TLS. Pri takomto certifikáte Monitora pridajte verify_ssl: false do každého bloku REST vedľa scan_interval: alebo importujte CA Monitora medzi dôveryhodné certifikáty. Rovnako postupujú iné nástroje overujúce certifikáty.",
       "Prometheus má v príklade nižšie pripravené scheme: https. Zapnite alebo ponechajte https, ak je TLS na Monitore zapnuté."
@@ -43,7 +43,7 @@
     "basicTitle": "Základný widget — bez overenia",
     "basicIntro": "Použite to, keď je ProxMenux Monitor na dôveryhodnej sieti a ešte ste nezaviedli autentifikáciu na strane monitora. Najjednoduchšia možná položka services.yaml:",
     "authedTitle": "Overený grafický prvok",
-    "authedIntro": "Na Monitore vytvorte token v Nastavenia → Bezpečnosť → API tokeny. Skopírujte ho do hlavičky Authorization nižšie: ukážkový token za Bearer nahraďte vlastným.",
+    "authedIntro": "Na Monitore vytvorte token v Správa → Bezpečnosť → API tokeny. Skopírujte ho do hlavičky Authorization nižšie: ukážkový token za Bearer nahraďte vlastným.",
     "authedOutro": "Reštartujte Homepage a karta zobrazí aktuálne hodnoty. Rovnaký token použite vo widgetoch, ktoré čítajú tú istú inštanciu Monitora.",
     "multiTitle": "Nastavenie multi- widgetu Systém, úložisko, sieť",
     "multiIntro": "Pre viac podrobností vytvorte tri karty s rôznymi koncovými bodmi: systém, úložisko a sieť. Pre tú istú inštanciu Monitora použite rovnaký token.",
diff --git a/web/messages/sk/docs/monitor/notifications.json b/web/messages/sk/docs/monitor/notifications.json
index 67654a7f..2122a959 100644
--- a/web/messages/sk/docs/monitor/notifications.json
+++ b/web/messages/sk/docs/monitor/notifications.json
@@ -314,7 +314,7 @@
   },
   "aiRewrite": {
     "heading": "Nepovinné prepísanie AI",
-    "body1": "Vhodné udalosti môže prepísať LLM. backup_complete a backup_fail sa neprepisujú. AI je predvolene vypnutá a beží v odosielacom vlákne. Pri chybe alebo časovom limite sa použije pôvodný text.",
+    "body1": "Vhodné udalosti môže prepísať LLM. backup_complete a backup_fail sa neprepisujú; so zapnutými AI návrhmi AI len pridá vysvetlenie, keď záloha hlási chyby. AI je predvolene vypnutá a beží v odosielacom vlákne. Pri chybe alebo časovom limite sa použije pôvodný text.",
     "body2": "Podporuje OpenAI, Anthropic, Google Gemini, Groq, OpenRouter a miestne Ollama. Pre kanál: podrobnosť (brief, standard, detailed), jazyk, režim pokynov (default alebo custom) a vlastné pokyny. Návod a príklady sú v Asistent AI.",
     "privacyTitle": "Poznámka o ochrane osobných údajov",
     "privacyBody": "Požiadavka AI môže obsahovať názov, závažnosť, text, systémové/vlastné pokyny a kontext s názvami hostiteľov, IP, používateľmi, chybami a denníkmi. Posiela sa zvolenému serveru. Ollama zostáva miestne len s miestnym serverom aj modelom; vzdialený server alebo cloudový model odosiela údaje mimo hostiteľa. Ak musia zostať v sieti, vypnite AI alebo použite riadený miestny server a model."
diff --git a/web/messages/sk/docs/security/ssl-letsencrypt.json b/web/messages/sk/docs/security/ssl-letsencrypt.json
index 418a3d3a..1d353068 100644
--- a/web/messages/sk/docs/security/ssl-letsencrypt.json
+++ b/web/messages/sk/docs/security/ssl-letsencrypt.json
@@ -12,13 +12,13 @@
   },
   "intro": {
     "title": "Čo táto stránka obsahuje",
-    "body": "Ako prepnúť Monitor z HTTP na HTTPS v paneli Nastavenia → Zabezpečenie, na aké súbory ukazujú zdroje certifikátov, ako sa správa možnosť Proxmox s certifikátom Let’s Encrypt a ako nastaviť vlastné cesty pri správe certifikátov mimo Proxmox."
+    "body": "Ako prepnúť Monitor z HTTP na HTTPS v paneli Správa → Bezpečnosť, na aké súbory ukazujú zdroje certifikátov, ako sa správa možnosť Proxmox s certifikátom Let’s Encrypt a ako nastaviť vlastné cesty pri správe certifikátov mimo Proxmox."
   },
   "wheresetting": {
     "heading": "Kde nájdete nastavenie",
-    "body": "Otvorte Monitor a prejdite na Nastavenia → Zabezpečenie → HTTPS / SSL. Panel zobrazuje HTTP alebo HTTPS a pri vypnutom HTTPS rozpoznané zdroje certifikátov.",
-    "imageAlt": "Nastavenia → Zabezpečenie → HTTPS / panel SSL na monitore zobrazujúci vypnutý stav HTTPS, automaticky zistený certifikát Proxmox (predmet, vydavateľ, expirácia) a možnosť Použiť vlastný certifikát",
-    "caption": "Panel Nastavenia → Zabezpečenie → HTTPS / SSL pred aktiváciou. Monitor zobrazí certifikát používaný Proxmoxom a umožní ho vybrať jedným kliknutím."
+    "body": "Otvorte Monitor a prejdite na Správa → Bezpečnosť → SSL / HTTPS. Panel zobrazuje HTTP alebo HTTPS a pri vypnutom HTTPS rozpoznané zdroje certifikátov.",
+    "imageAlt": "Panel Správa → Bezpečnosť → SSL / HTTPS na monitore zobrazujúci vypnutý stav HTTPS, automaticky zistený certifikát Proxmox (predmet, vydavateľ, expirácia) a možnosť Použiť vlastný certifikát",
+    "caption": "Panel Správa → Bezpečnosť → SSL / HTTPS pred aktiváciou. Monitor zobrazí certifikát používaný Proxmoxom a umožní ho vybrať jedným kliknutím."
   },
   "twoways": {
     "heading": "Dva spôsoby, ako umožniť HTTPS",
@@ -117,7 +117,7 @@
   },
   "switchToHttps": {
     "heading": "Prepnúť monitor na HTTPS",
-    "bodyRich": "Keď je /etc/pve/local/pveproxy-ssl.pem podpísaný Let's Encrypt, otvorte Nastavenia → Zabezpečenie → HTTPS / SSL, overte vydavateľa Let's Encrypt namiesto miestnej CA Proxmox a kliknite na Použiť certifikát Proxmox. Monitor sa reštartuje a ďalšie načítanie použije HTTPS na 8008 s verejne dôveryhodným reťazcom. Ďalšie obnovy ACME sa overia a vyberú pri nasledujúcom novom spojení TLS bez pravidelného dopytovania alebo reštartu služby pri obnove. Aktualizovať certifikát zostáva v Zabezpečení dostupné na diagnostiku a obnovu."
+    "bodyRich": "Keď je /etc/pve/local/pveproxy-ssl.pem podpísaný Let's Encrypt, otvorte Správa → Bezpečnosť → SSL / HTTPS, overte vydavateľa Let's Encrypt namiesto miestnej CA Proxmox a kliknite na Použiť certifikát Proxmox. Monitor sa reštartuje a ďalšie načítanie použije HTTPS na 8008 s verejne dôveryhodným reťazcom. Ďalšie obnovy ACME sa overia a vyberú pri nasledujúcom novom spojení TLS bez pravidelného dopytovania alebo reštartu služby pri obnove. Aktualizovať certifikát v časti Bezpečnosť umožňuje ručne skontrolovať a znovu načítať certifikát bez reštartu Monitora."
   },
   "custom": {
     "heading": "Vlastný certifikát — kedy ho použiť",
diff --git a/web/messages/sk/docs/settings/beta-program.json b/web/messages/sk/docs/settings/beta-program.json
index 15f322d7..aa26499e 100644
--- a/web/messages/sk/docs/settings/beta-program.json
+++ b/web/messages/sk/docs/settings/beta-program.json
@@ -51,7 +51,7 @@
     "intro": "Obidva smery prechádzajú rovnakým inštalačným programom — nie sú špeciálnymi postupmi odinštalovania a preinštalovania. Každý smer jednoducho spúšťa vlastný inštalačný program kanála, ktorý je navrhnutý tak, aby bol idempotentný a bezpečný na opätovné spustenie cez existujúcu inštaláciu. Praktické dôsledky:",
     "items": [
       "Existujúca konfigurácia, prihlásenie do ProxMenux Monitor (auth.json), kanály oznámení, register úprav po inštalácii a vlastné prahové hodnoty zostávajú nezmenené. Prepnutie kanála vymení iba strom skriptov a binárny AppImage.",
-      "Monitor, ktorý bol aktívny pred spínačom zostane aktívny po; ten, ktorý bol deaktivovaný, zostane deaktivovaný.",
+      "Inštalátor počas prepnutia reštartuje službu Monitor, takže Monitor, ktorý ste deaktivovali, sa spustí znova. V prípade potreby ho v Nastaveniach deaktivujte znova.",
       "Súbor auth.json, tokeny API a tajomstvo JWT ProxMenux Monitor sa pri zmene kanála zachovajú. Relácie sa neodhlásia.",
       "Kanály môžete prepínať ľubovoľne často; dialóg vždy prijme zmenu a znova spustí inštalátor. Reštart Proxmoxu nie je potrebný.",
       "Prepnutie kanála nahrádza skripty ProxMenux a nemožno ho vykonať v termináli Monitora v prehliadači: PTY by sa počas aktualizácie ukončil a dialóg by uviazol. V tomto termináli sa namiesto toho zobrazí informačné okno s oficiálnym jednoriadkovým príkazom pre zvolený kanál. Spustite ho cez SSH alebo konzolu hostiteľa Proxmox."
diff --git a/web/messages/sk/docs/settings/index.json b/web/messages/sk/docs/settings/index.json
index c713aab9..84970fd3 100644
--- a/web/messages/sk/docs/settings/index.json
+++ b/web/messages/sk/docs/settings/index.json
@@ -60,6 +60,12 @@
         "description": "Prepínajte medzi Stable (main) a Beta (develop) v oboch smeroch v jednom dialógu. Spustí oficiálny inštalátor zvoleného kanála; je idempotentný a vždy viditeľný.",
         "badge": "Vždy k dispozícii"
       },
+      {
+        "icon": "Languages",
+        "href": "/docs/settings/change-language",
+        "title": "Zmeniť jazyk",
+        "description": "Prepnite rozhranie ProxMenux medzi ôsmimi jazykmi. Voľba sa uloží do config.json a menu sa načíta znova v novom jazyku."
+      },
       {
         "icon": "Info",
         "href": "/docs/settings/show-version-information",
@@ -70,7 +76,7 @@
         "icon": "Trash2",
         "href": "/docs/settings/uninstall-proxmenux",
         "title": "Odinštalovať ProxMenux",
-        "description": "Odstrániť ProxMenux a (voliteľne) jeho závislosti. Tiež obnovuje zálohy /root/.bashrc a /etc/motd vytvorené v čase inštalácie."
+        "description": "Odstráni ProxMenux a (voliteľne) jeho závislosti. Obnoví tiež zálohy /root/.bashrc a /etc/motd vytvorené Post-Install."
       }
     ]
   },
diff --git a/web/messages/sk/docs/settings/proxmenux-monitor.json b/web/messages/sk/docs/settings/proxmenux-monitor.json
index caa61e93..078af680 100644
--- a/web/messages/sk/docs/settings/proxmenux-monitor.json
+++ b/web/messages/sk/docs/settings/proxmenux-monitor.json
@@ -33,7 +33,7 @@
   },
   "warnConditional": {
     "title": "Možnosti nastavenia iba viditeľné pri inštalácii monitora",
-    "body": "V Nastaveniach sa zobrazujú dva nižšie uvedené položky menu, len ak je proxmenux-monitor.service registrovaný systemd. Ak ich nevidíte, monitor nebol nainštalovaný (je voliteľný počas inštalácie ProxMenux). Ak chcete pridať neskôr, preinštalujte ProxMenux s najnovšou verziou a vyberte možnosť Monitor."
+    "body": "Tri nižšie uvedené položky menu sa v Nastaveniach zobrazujú, len ak je proxmenux-monitor.service registrovaný v systemd. Inštalátor ho nastaví vždy, takže ak ich nevidíte, krok Monitora počas inštalácie zlyhal. Spustite oficiálny inštalátor znova a skúste to opäť."
   },
   "toggle": {
     "heading": "Aktivovať / deaktivovať",
@@ -92,8 +92,8 @@
   },
   "troubleshoot": {
     "heading": "Riešenie problémov",
-    "missingTitle": "Obe možnosti menu chýbajú",
-    "missingBody": "Monitor nie je registrovaný na systemd. Potvrďte systemctl list-unit-files | grep proxmenux-monitor Ak nie je výstup, preinštalujte ProxMenux od oficiálneho inštalátora s najnovšou verziou a vyberte možnosť Monitor počas inštalácie.",
+    "missingTitle": "Možnosti menu Monitora chýbajú",
+    "missingBody": "Monitor nie je registrovaný v systemd. Potvrďte systemctl list-unit-files | grep proxmenux-monitor — ak nie je žiadny výstup, spustite oficiálny inštalátor znova; Monitor inštaluje pri každom spustení.",
     "unreachableTitle": "Služba aktivovaná, ale URL je nedostupná",
     "unreachableBody": "Stavový výstup Nastavenia vám dáva IP. Potvrdiť:",
     "unreachableCmd": "ss -tlnp | grep 8008                # is anything listening?\njournalctl -u proxmenux-monitor -n 100 --no-pager  # recent errors\ncurl -sI http://localhost:8008      # local reachability",
diff --git a/web/messages/sk/docs/settings/uninstall-proxmenux.json b/web/messages/sk/docs/settings/uninstall-proxmenux.json
index a32c8db3..44358b77 100644
--- a/web/messages/sk/docs/settings/uninstall-proxmenux.json
+++ b/web/messages/sk/docs/settings/uninstall-proxmenux.json
@@ -12,7 +12,7 @@
   },
   "scopeWarn": {
     "title": "Ovplyvní iba inštaláciu ProxMenux — nie vaše VM / CT / úložisko",
-    "body": "Odinštalovanie odstráni skripty ProxMenux, spúšťač menu, voliteľnú službu Monitor a zálohy systémových súborov, ktoré ProxMenux vytvoril počas inštalácie. Nemení vaše VM, kontajnery, úložisko, konfiguráciu siete, Fail2Ban, ovládač NVIDIA ani nič iné nainštalované cez ProxMenux. Tieto súčasti zostanú nezmenené."
+    "body": "Odinštalovanie odstráni skripty ProxMenux, spúšťač menu, službu Monitor a obnoví zálohy /root/.bashrc a /etc/motd vytvorené Post-Install, ak existujú. Nemení vaše VM, kontajnery, úložisko, konfiguráciu siete, Fail2Ban, ovládač NVIDIA ani nič iné nainštalované cez ProxMenux. Tieto súčasti zostanú nezmenené."
   },
   "flow": {
     "heading": "Postup odinštalovania",
diff --git a/web/messages/sk/docs/utils/export-vm.json b/web/messages/sk/docs/utils/export-vm.json
index 85037f00..b30702c8 100644
--- a/web/messages/sk/docs/utils/export-vm.json
+++ b/web/messages/sk/docs/utils/export-vm.json
@@ -59,8 +59,8 @@
   },
   "package": {
     "heading": "Čo balík obsahuje",
-    "intro": "Pre OVA / OVF s názvom myvm-100-2026-04-26_18-30-15:",
-    "code": "# OVA mode (single .ova file is a TAR of):\nmyvm-100-2026-04-26_18-30-15.ovf      # XML descriptor (DMTF OVF 1.0 + RASD + VSSD)\nmyvm-100-2026-04-26_18-30-15.mf       # SHA1 manifest\nmyvm-100-2026-04-26_18-30-15-disk0.vmdk\nmyvm-100-2026-04-26_18-30-15-disk1.vmdk\n...\n\n# OVF mode (a directory):\nmyvm-100-2026-04-26_18-30-15-ovf/\n├── myvm-100-2026-04-26_18-30-15.ovf\n├── myvm-100-2026-04-26_18-30-15.mf\n├── myvm-100-2026-04-26_18-30-15-disk0.vmdk\n└── myvm-100-2026-04-26_18-30-15-disk1.vmdk"
+    "intro": "Pre OVA / OVF s názvom myvm-100-20260426_183015:",
+    "code": "# OVA mode (single .ova file is a TAR of):\nmyvm-100-20260426_183015.ovf      # XML descriptor (DMTF OVF 1.0 + RASD + VSSD)\nmyvm-100-20260426_183015.mf       # SHA1 manifest\nmyvm-100-20260426_183015-disk1.vmdk\nmyvm-100-20260426_183015-disk2.vmdk\n...\n\n# OVF mode (a directory):\nmyvm-100-20260426_183015-ovf/\n├── myvm-100-20260426_183015.ovf\n├── myvm-100-20260426_183015.mf\n├── myvm-100-20260426_183015-disk1.vmdk\n└── myvm-100-20260426_183015-disk2.vmdk"
   },
   "exported": {
     "heading": "Čo sa exportuje",
@@ -90,7 +90,7 @@
   "manifest": {
     "heading": "Manifest SHA1",
     "intro": "Každý balík obsahuje súbor .mf s hashmi SHA1 deskriptora OVF a každého súboru VMDK:",
-    "code": "SHA1(myvm-100-2026-04-26_18-30-15.ovf)= 3a7bd3e2360a3d29eea436fcfb7e44c735d117c9\nSHA1(myvm-100-2026-04-26_18-30-15-disk0.vmdk)= b7e23ec29af22b0b4e41da31e868d57226121c84\nSHA1(myvm-100-2026-04-26_18-30-15-disk1.vmdk)= a87ff679a2f3e71d9181a67b7542122c",
+    "code": "SHA1(myvm-100-20260426_183015.ovf)= 3a7bd3e2360a3d29eea436fcfb7e44c735d117c9\nSHA1(myvm-100-20260426_183015-disk1.vmdk)= b7e23ec29af22b0b4e41da31e868d57226121c84\nSHA1(myvm-100-20260426_183015-disk2.vmdk)= a87ff679a2f3e71d9181a67b7542122c",
     "outro": "V špecifikácii OVF je manifest voliteľný a mnohé programy ho ignorujú. Pomáha však overiť, či sa balík preniesol neporušený. Overte ho ručne pomocou sha1sum -c file.mf."
   },
   "import": {
diff --git a/web/messages/sk/docs/utils/index.json b/web/messages/sk/docs/utils/index.json
index c006b4bc..b3024da5 100644
--- a/web/messages/sk/docs/utils/index.json
+++ b/web/messages/sk/docs/utils/index.json
@@ -38,7 +38,7 @@
       "body": "Udržiavajte hostiteľa v dobrom stave pomocou nástrojov CLI, pravidelných aktualizácií a prechodu na novú hlavnú verziu, keď príde čas.",
       "bullets": [
         "Inštalátor systémových nástrojov (25 nástrojov CLI)",
-        "Aktualizácia systému Proxmox (apt + dist-upgrade)",
+        "Aktualizácia systému Proxmox (apt + full-upgrade)",
         "Prechod z PVE 8 na PVE 9"
       ]
     },
@@ -58,11 +58,11 @@
   },
   "isoSection": {
     "heading": "Tvorba ISO",
-    "body": "Vytvorte nový, aktuálny inštalačný obraz ISO systému Windows priamo na hostiteľovi Proxmox. Skript automatizuje celý postup UUP Dump: inštaláciu závislostí (aria2c, cabextract, wimlib-imagex, …), paralelné sťahovanie aktualizačných súborov cez aria2 a konverziu na jeden zavádzací obraz ISO. Ten sa uloží do /var/lib/vz/template/iso a je pripravený na pripojenie k VM.",
+    "body": "Vytvorte nový, aktuálny inštalačný obraz ISO systému Windows priamo na hostiteľovi Proxmox. Skript automatizuje celý postup UUP Dump: inštaláciu závislostí (aria2c, cabextract, wimlib-imagex, …), paralelné sťahovanie aktualizačných súborov cez aria2 a konverziu na jeden zavádzací obraz ISO. Ten sa uloží do zisteného úložiska ISO Proxmoxu (záložne /var/lib/vz/template/iso) a je pripravený na pripojenie k VM.",
     "options": [
       {
         "title": "Tvorba ISO cez UUP Dump",
-        "description": "Vytvorte aktuálny inštalačný obraz ISO systému Windows zo zdieľaného odkazu UUP Dump. Závislosti, sťahovanie aj konverzia sú plne automatizované. Výsledný obraz ISO sa uloží do /var/lib/vz/template/iso a je pripravený na vytvorenie VM.",
+        "description": "Vytvorte aktuálny inštalačný obraz ISO systému Windows zo zdieľaného odkazu UUP Dump. Závislosti, sťahovanie aj konverzia sú plne automatizované. Výsledný obraz ISO sa uloží do zisteného úložiska ISO Proxmoxu a je pripravený na vytvorenie VM.",
         "href": "/docs/utils/UUp-Dump-ISO-Creator"
       }
     ]
@@ -78,7 +78,7 @@
       },
       {
         "title": "Aktualizácia systému Proxmox",
-        "description": "Kontrola repozitárov + apt update + apt dist-upgrade s potvrdením pred reštartom. Rozpozná hlavnú verziu PVE (8 alebo 9), zachová nakonfigurované repozitáre a pred prepnutím hostiteľa bez predplatného na repozitár no-subscription požiada o potvrdenie. Potom spustí autoremove.",
+        "description": "Kontrola repozitárov + apt update + apt full-upgrade s potvrdením pred reštartom. Rozpozná hlavnú verziu PVE (8 alebo 9), zachová nakonfigurované repozitáre a pred prepnutím hostiteľa bez predplatného na repozitár no-subscription požiada o potvrdenie. Potom spustí autoremove.",
         "href": "/docs/utils/system-update"
       },
       {
diff --git a/web/messages/sk/docs/utils/system-update.json b/web/messages/sk/docs/utils/system-update.json
index bab1ab9e..248b550a 100644
--- a/web/messages/sk/docs/utils/system-update.json
+++ b/web/messages/sk/docs/utils/system-update.json
@@ -128,7 +128,7 @@
         "body": "Repozitár Enterprise je zapnutý, ale hostiteľ nemá aktívne predplatné. Pred apt-get update skript ponúkne prechod na no-subscription. Ak ste ho odmietli, spustite aktualizáciu znova a potvrďte ho, alebo vypnite Enterprise v Node → Updates → Repositories a zapnite no-subscription."
       },
       {
-        "title": "dist-upgrade sa zastaví pri \"Configuring grub-pc\"",
+        "title": "full-upgrade sa zastaví pri \"Configuring grub-pc\"",
         "body": "Ak sa zobrazuje interaktívna otázka GRUB, žiada zariadenia na inštaláciu zavádzača. Voľba --force-confold platí pre konfiguračné súbory, nie pre tento výber zariadení. Pomocou Tab + Space vyberte príslušné zavádzacie disky a potom OK. Výber možno nastaviť vopred pomocou dpkg-reconfigure grub-pc."
       },
       {
diff --git a/web/messages/sk/docs/utils/system-utils.json b/web/messages/sk/docs/utils/system-utils.json
index 6a683e47..e7a2f764 100644
--- a/web/messages/sk/docs/utils/system-utils.json
+++ b/web/messages/sk/docs/utils/system-utils.json
@@ -235,7 +235,7 @@
       {
         "href": "/docs/utils/system-update",
         "label": "Aktualizácia systému Proxmox",
-        "tailRich": " — pre samotný apt dist-upgrade na hostiteľovi."
+        "tailRich": " — pre samotný apt full-upgrade na hostiteľovi."
       },
       {
         "href": "/docs/network/monitoring",
diff --git a/web/messages/sk/docs/utils/upgrade-pve8-pve9.json b/web/messages/sk/docs/utils/upgrade-pve8-pve9.json
index f75679de..0bb7d37a 100644
--- a/web/messages/sk/docs/utils/upgrade-pve8-pve9.json
+++ b/web/messages/sk/docs/utils/upgrade-pve8-pve9.json
@@ -16,9 +16,9 @@
     "items": [
       "Overené zálohy všetkých VM a CT (PBS, vzdump, snímky ZFS — overte aj obnovu).",
       "Prístup cez konzolu / IPMI / iKVM k hostiteľovi pre prípad výpadku SSH.",
-      "Hostiteľ už používa aktuálnu PVE 8.4.x (skript to vynucuje).",
+      "Hostiteľ s najnovšou úrovňou záplat PVE 8. Skript najprv aktualizuje PVE 8; nekontroluje konkrétnu verziu 8.4.x.",
       "Ceph (ak je k dispozícii) vo verzii 19.x Squid.",
-      "Aspoň 5 GB voľného miesta v koreňovom súborovom systéme."
+      "Aspoň 5 GB voľného miesta v koreňovom súborovom systéme, ako odporúča sprievodca Proxmoxu. Samotný skript kontroluje iba 1 GB v /var/cache/apt/archives."
     ]
   },
   "modeMenu": {
@@ -63,13 +63,13 @@
   },
   "webTerminal": {
     "heading": "Prečo je webový terminál zablokovaný?",
-    "body": "Všetky tri režimy zistia termproxy / vncshell v reťazci rodičovských procesov a odmietnu spustenie. Webový terminál závisí od služby pveproxy, ktorá sa počas aktualizácie mení. Po reštarte pveproxy sa ukončí terminál aj skript v ňom. Hostiteľ môže zostať aktualizovaný iba čiastočne.",
+    "body": "Automatický a interaktívny režim zistia termproxy / vncshell v reťazci rodičovských procesov a odmietnu spustenie, ak relácia nepochádza cez SSH alebo nie je nastavené PVE_ALLOW_WEBTERM=1. Predbežná kontrola a ručný návod nič nemenia a bežia kdekoľvek. Webový terminál závisí od služby pveproxy, ktorá sa počas aktualizácie mení. Po reštarte pveproxy sa ukončí terminál aj skript v ňom. Hostiteľ môže zostať aktualizovaný iba čiastočne a niet dobrého spôsobu, ako pokračovať.",
     "warningTitle": "Použite SSH alebo fyzickú konzolu / IPMI / iKVM",
-    "warningBody": "Najčastejšou voľbou je SSH. Spustite ho v tmux alebo screen, aby krátky výpadok siete neprerušil aktualizáciu. Pri bežnom SSH shelli skript najprv zobrazí pripomienku tmux/screen."
+    "warningBody": "Najčastejšou voľbou je SSH. Spustite ho v tmux alebo screen, aby krátky výpadok siete neprerušil aktualizáciu. Dialóg potvrdenia aktualizácie pripomína, že ju treba spustiť z konzoly alebo cez SSH v tmux/screen."
   },
   "auto": {
     "heading": "Automatický režim / Bezobslužne",
-    "intro": "Otázky k balíkom a konfiguračným súborom používajú predvolené hodnoty z nasledujúcej tabuľky a odpoveď 401 z repozitára Enterprise prepne na no-subscription bez opýtania. Potvrdenie aktualizácie, súhrn aktualizácie PVE 8, opravy z predbežnej kontroly a otázka na reštart sa naďalej zobrazujú.",
+    "intro": "Otázky k balíkom a konfiguračným súborom používajú predvolené hodnoty z nasledujúcej tabuľky. Potvrdenie aktualizácie, súhrn aktualizácie PVE 8, opravy z predbežnej kontroly a otázka na reštart sa naďalej zobrazujú.",
     "behaviourHeading": "Správanie automatického režimu",
     "behaviourTable": {
       "settingHeader": "Nastavenie",
@@ -94,8 +94,8 @@
       ]
     },
     "flowHeading": "Postup automatického režimu",
-    "flowIntro": "Šesť po sebe idúcich fáz. Fáza 2 aktualizuje balíky PVE 8 a fáza 3 zapíše konfiguráciu repozitárov PVE 9. Chyba postup zastaví, tieto zmeny však nevráti; ak aktualizácia PVE 8 zlyhá alebo sa zruší, repozitáre zostanú nezmenené. Fáza 5 vykoná dist-upgrade na PVE 9.",
-    "flowDiagram": "1. KONTROLY\nBlokuje termproxy / vncshell; potvrdí aktualizáciu; odmietne PVE 9.\n\n2. PRÍPRAVA PVE 8\nscripts/global/update-pve8.sh\nAktualizuje balíky PVE 8; zastaví sa, ak aktualizácia zlyhá alebo sa zruší.\npve8to9 --full (môže ponúknuť opravy).\nOverí miesto a pripojenie; Ceph 19.x Squid na hyperkonvergovaných uzloch.\n\n3. MIGRÁCIA REPOZITÁROV\nBookworm → Trixie; zapíše súbory deb822 .sources.\nZakomentuje staré súbory .list; apt-get update.\n\n4. OVERENIE KANDIDÁTA PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nZastaví sa, ak kandidát nie je 9.x alebo by sa odstránil proxmox-ve.\n\n5. AKTUALIZÁCIA\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nBez predplatného: vypne pve-enterprise.sources.\nProtokol: /var/log/pve8-a-pve9-.log\n\n6. OVERENIE A REŠTART\nEFI: apt install grub-efi-amd64\npve8to9 --full; skontrolujte výsledok a otázku na reštart.",
+    "flowIntro": "Šesť po sebe idúcich fáz. Fáza 2 prepne repozitáre PVE 8 na no-subscription (zoznamy Enterprise a Ceph vypnuté, /etc/apt/sources.list prepísaný) ešte pred zobrazením súhrnu aktualizácie a potom aktualizuje balíky; fáza 3 zapíše konfiguráciu repozitárov PVE 9. Chyba postup zastaví, tieto zmeny však nevráti; ak aktualizácia PVE 8 zlyhá alebo sa zruší, migrácia na PVE 9 sa nezačne, ale zmeny repozitárov PVE 8 zostanú. Fáza 5 vykoná dist-upgrade na PVE 9.",
+    "flowDiagram": "1. KONTROLY\nBlokuje termproxy / vncshell; potvrdí aktualizáciu; odmietne PVE 9.\n\n2. PRÍPRAVA PVE 8\nscripts/global/update-pve8.sh\nPrepne repozitáre PVE 8 na no-subscription a prepíše sources.list;\naktualizuje balíky PVE 8; zastaví sa, ak aktualizácia zlyhá alebo sa zruší.\npve8to9 --full (môže ponúknuť opravy).\nOverí miesto a pripojenie; Ceph 19.x Squid na hyperkonvergovaných uzloch.\n\n3. MIGRÁCIA REPOZITÁROV\nBookworm → Trixie; zapíše súbory deb822 .sources.\nZakomentuje staré súbory .list; apt-get update.\n\n4. OVERENIE KANDIDÁTA PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nZastaví sa, ak kandidát nie je 9.x alebo by sa odstránil proxmox-ve.\n\n5. AKTUALIZÁCIA\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nBez predplatného: vypne pve-enterprise.sources.\nProtokol: /var/log/pve8-a-pve9-.log\n\n6. OVERENIE A REŠTART\nEFI: apt install grub-efi-amd64\npve8to9 --full; skontrolujte výsledok a otázku na reštart.",
     "preflightHeading": "Kontroly pred aktualizáciou",
     "preflightTable": {
       "checkHeader": "Kontrola",
@@ -158,7 +158,7 @@
   },
   "manual": {
     "heading": "Postup manuálnej aktualizácie",
-    "intro": "Toto zobrazí možnosť Ručný návod na aktualizáciu: oficiálny wiki návod Proxmoxu z 8 na 9, rozdelený do štyroch fáz. Skript príkazy zobrazí; vykonáte ich sami v samostatnom shelli používateľa root. Každý krok máte pod kontrolou.",
+    "intro": "Toto zobrazí možnosť Ručný návod na aktualizáciu, vychádzajúca z oficiálneho wiki návodu Proxmoxu z 8 na 9 a rozdelená do štyroch fáz. Tabuľka odpovedí dpkg dopĺňa upozornenia wiki k sshd_config a chrony.conf, ktoré menu zobrazuje ako obyčajné Y. Skript príkazy zobrazí; vykonáte ich sami v samostatnom shelli používateľa root. Žiadna automatizácia, každý krok máte pod kontrolou.",
     "rootCalloutTitle": "Všetky príkazy vyžadujú root",
     "rootCalloutBody": "Použite shell používateľa root aj pre presmerovania a dokumenty here-document. Pri sudo zabezpečte, aby sa s oprávneniami root vykonal celý príkaz.",
     "phase1Heading": "Fáza 1 — Príprava",
@@ -230,7 +230,7 @@
   "clusterCeph": {
     "heading": "Klaster a Ceph",
     "clusterCalloutTitle": "Klaster: aktualizujte uzly po jednom",
-    "clusterCalloutBody": "Zistené cez pvecm status. Neaktualizujte viac uzlov naraz. Presuňte hostí, aktualizujte uzol, reštartujte a overte členstvo v klastri. Až potom pokračujte. Počas aktualizácie funguje živá migrácia zo staršej verzie PVE na novšiu; opačný smer spravidla nie je podporovaný. Skupiny HA sa automaticky prevedú na pravidlá HA, keď všetky uzly používajú PVE 9.",
+    "clusterCalloutBody": "Neaktualizujte viac uzlov naraz. Presuňte hostí z uzla, aktualizujte, reštartujte a overte členstvo v klastri. Až potom pokračujte. Počas aktualizácie funguje živá migrácia zo staršej verzie PVE na novšiu; opačný smer spravidla nie je podporovaný. Skupiny HA sa automaticky prevedú na pravidlá HA, keď všetky uzly používajú PVE 9.",
     "cephCalloutTitle": "Ceph: NAJPRV inovujte Ceph, potom Proxmox",
     "cephCalloutBody": "Ceph musí pred aktualizáciou Proxmoxu už používať 19.x Squid. Postupujte v oficiálnom poradí Quincy 17 → Reef 18 → Squid 19: aktualizujte balíky a postupne reštartujte démony Ceph podľa návodu. Pred každým uzlom Proxmoxu overte ceph --version. Celý postup: wiki aktualizácie Ceph Reef na Squid."
   },
@@ -273,7 +273,7 @@
   },
   "files": {
     "heading": "Príslušné súbory",
-    "code": "scripts/utilities/upgrade_pve8_to_pve9.sh                         # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh                                # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh    # 17-step runbook\nscripts/global/update-pve8.sh                                     # PVE 8 worker (final patches)\n/etc/apt/sources.list                                             # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources                           # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources                            # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources                              # created if Ceph\n/etc/apt/sources.list.d/*.list                                    # legacy files commented out / removed\n/var/log/pve8-a-pve9-.log                              # full upgrade log"
+    "code": "scripts/utilities/upgrade_pve8_to_pve9.sh                         # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh                                # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh    # 17-step runbook\nscripts/global/update-pve8.sh                                     # PVE 8 worker (final patches)\n/etc/apt/sources.list                                             # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources                           # created (deb822 PVE 9)\n/etc/apt/sources.list.d/ceph.sources                              # created if Ceph\n/etc/apt/sources.list.d/*.list                                    # legacy files commented out / removed\n/var/log/pve8-a-pve9-.log                              # full upgrade log"
   },
   "references": {
     "heading": "Oficiálne zdroje",
diff --git a/web/messages/sv/docs/backup-restore/creating-backups.json b/web/messages/sv/docs/backup-restore/creating-backups.json
index f11c2a6d..e4030798 100644
--- a/web/messages/sv/docs/backup-restore/creating-backups.json
+++ b/web/messages/sv/docs/backup-restore/creating-backups.json
@@ -21,8 +21,8 @@
     "rows": [
       {
         "entry": "ProxMenux Scripts (TUI)",
-        "path": "menu → Utilities → Host Config Backup",
-        "detail": "Dialogbaserat flöde, SSH-vänligt. Huvudmenyn presenterar de sex alternativen direkt. Använder backup_menu i backup_host.sh."
+        "path": "menu → Host Backup & Restore → Backup host configuration",
+        "detail": "Dialogbaserat flöde, SSH-vänligt. Huvudmenyposten öppnar Host Config Backup / Restore; Backup host configuration leder till de sex alternativen. Använder backup_menu i backup_host.sh."
       },
       {
         "entry": "ProxMenux Monitor (Webb UI)",
@@ -83,7 +83,7 @@
   "profiles": {
     "heading": "Standard vs anpassad profil",
     "defaultTitle": "Standardprofil",
-    "defaultBody": "Standardprofilen är den kurerade listan från hb_default_profile_paths (dokumenterad i Hur det fungerar under Sökvägskategorier) plus varje post i den filen med beständiga tillägg /usr/local/share/proxmenux/backup-extra-paths.txt. Användaren bekräftar destinationen och krypteringsalternativen och backupen fortsätter utan ytterligare sökvägsval.",
+    "defaultBody": "Standardprofilen är den kurerade listan från hb_default_profile_paths (dokumenterad i Hur det fungerar under Sökvägskategorier) plus varje post i den filen med beständiga tillägg /usr/local/share/proxmenux/backup-extra-paths.txt. Användaren väljer destination och krypteringsalternativ och backupen fortsätter utan ytterligare sökvägsval.",
     "customTitle": "Anpassad profil",
     "customBody": "Den anpassade profilen öppnar en checklista med alla sökvägar i standardprofilen (omarkerade) och alla beständiga tillägg (förmarkerade, med prefixet [+]). Användaren markerar sökvägarna för denna körning och kan trycka på Lägg till anpassad sökväg för att lägga till en ny absolut sökväg. Sökvägar som läggs till här sparas i backup-extra-paths.txt så att framtida säkerhetskopior tar med dem automatiskt. Att avmarkera ett beständigt tillägg utesluter det från denna körning men tar inte bort det ur filen — borttagning görs separat via Hantera anpassade sökvägar, utanför säkerhetskopieringsflödet.",
     "customPickerAlt": "Checklista för anpassad profil med standardprofilens sökvägar (omarkerade) och beständiga tillägg (förmarkerade med prefixet [+]), samt knappar för att lägga till en sökväg eller bekräfta valet.",
@@ -124,14 +124,7 @@
   },
   "included": {
     "heading": "Vad går in och vad stannar ut",
-    "intro": "Varje sökväg i den fastställda profilen (standard + beständiga tillägg + val i anpassat läge) kopieras med rsync -aAXH --numeric-ids. En delad uteslutningslista gäller för varje sökväg, och två kataloger har ytterligare sökvägsspecifika uteslutningar.",
-    "globalTitle": "Globala uteslutningar (tillämpas varje väg)",
-    "globalItems": [
-      "images/ - bilddumpar.",
-      "dump/ — utdata från vzdump.",
-      "tmp/ - tillfälliga filer.",
-      "*.log loggfiler."
-    ],
+    "intro": "Varje sökväg i den fastställda profilen (standard + beständiga tillägg + val i anpassat läge) kopieras med rsync -aAXH --numeric-ids. Det finns ingen global uteslutningslista; några kataloger har sökvägsspecifika uteslutningar.",
     "rootTitle": "/root/-uteslutningar",
     "rootBody": "/root/ ingår i standardprofilen så att användarens skript och konfiguration sparas i arkivet. Flyktiga undersökvägar utesluts:",
     "rootItems": [
@@ -160,16 +153,16 @@
       "/home/USER - inte i standardprofilen. Lägg till det som en anpassad väg när ett system bär hemkataloger som måste överleva en återställning."
     ],
     "customPathsTitle": "Hur anpassade vägar hanteras",
-    "customPathsBody": "En anpassad väg som läggs till i anpassat läge eller kvarstår i backup-extra-paths.txt går igenom samma rsync-pipeline som standardprofilvägar. Globala uteslutningar gäller. Om den anpassade vägen råkar vara under /root/ eller /usr/local/share/proxmenux/ gäller fortfarande de vägspecifika uteslutningarna ovan. Varje arkiverad väg - standard eller anpassad - registreras i metadata/paths_archived.txt. Sökvägar som inte finns på källan registreras i metadata/missing_paths.txt utan att stoppa backupen."
+    "customPathsBody": "En anpassad väg som läggs till i anpassat läge eller kvarstår i backup-extra-paths.txt går igenom samma rsync-pipeline som standardprofilvägar. Om den anpassade vägen råkar vara under /root/ eller /usr/local/share/proxmenux/ gäller fortfarande de vägspecifika uteslutningarna ovan. Varje arkiverad väg - standard eller anpassad - registreras i metadata/selected_paths.txt. Sökvägar som inte finns på källan registreras i metadata/missing_paths.txt utan att stoppa backupen, och sökvägar som inte kan kopieras registreras i metadata/failed_paths.txt."
   },
   "archiveStructure": {
     "heading": "Arkivstruktur",
     "intro": "Den tillfälliga katalog som varje backend skapar har samma struktur oavsett destination. Tar-arkivet, PBS-arkivet .pxar eller Borg-arkivet lagrar detta träd oförändrat.",
-    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state (kernel_params, hardware, storage, guests, components, source_host)\n├── metadata/\n│   ├── packages.manual.list       # output of apt-mark showmanual\n│   ├── run_info.env               # hostname, timestamp, kernel version, pmxcfs method\n│   ├── paths_archived.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, /etc/apt, ...\n    ├── root/                      # /root without volatile subpaths\n    ├── usr/local/                 # /usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux (state only)\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/crontabs"
+    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state (kernel_params, hardware, storage, guests, components, source_host)\n├── metadata/\n│   ├── packages.manual.list       # output of apt-mark showmanual\n│   ├── run_info.env               # hostname, timestamp, kernel version, pmxcfs method\n│   ├── selected_paths.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, /etc/apt, ...\n    ├── root/                      # /root without volatile subpaths\n    ├── usr/local/                 # /usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux (state only)\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/crontabs"
   },
   "confirmation": {
     "heading": "Sammanfattning för bekräftelse",
-    "body": "Innan backend skriver något till destinationen visar ProxMenux en sammanfattande dialog med destinationen, backup-ID eller arkivnamn, krypteringstillstånd och listan över vägar som kopieras. Avbokning här avbryter backupen rent - staging katalogen tas bort av trap kroken på backend funktionen och ingen partiell data når destinationen."
+    "body": "Innan backend skriver något till destinationen skriver ProxMenux ut en sammanfattning i terminalen med destinationen, backup-ID eller arkivnamn, krypteringstillstånd och listan över vägar som kopieras. Det finns inget bekräftelse- eller avbrytssteg här; kopieringen startar direkt efter. Om backupen misslyckas eller avbryts tas staging katalogen bort av trap kroken på backend funktionen och ingen partiell data når destinationen."
   },
   "writing": {
     "heading": "Skriva till destinationen",
diff --git a/web/messages/sv/docs/backup-restore/destinations/local.json b/web/messages/sv/docs/backup-restore/destinations/local.json
index 858d0b06..11901e40 100644
--- a/web/messages/sv/docs/backup-restore/destinations/local.json
+++ b/web/messages/sv/docs/backup-restore/destinations/local.json
@@ -18,11 +18,11 @@
   },
   "targetConfig": {
     "heading": "Konfigurera målkatalogen",
-    "intro": "Det lokala målet är en enda sparad målkatalog — inte en lista. ProxMenux sparar användarens val i /usr/local/share/proxmenux/local-target.conf och läser det vid varje säkerhetskopiering. Om inget mål har konfigurerats används standardvärdet HB_LOCAL_TARGET_DEFAULT = /var/lib/vz/dump (samma katalog som Proxmox använder för sina egna vzdump-utdata). Målet konfigureras via Konfigurera säkerhetskopieringsmål → Lokala mål:",
+    "intro": "Det lokala målet är en enda sparad målkatalog — inte en lista. ProxMenux sparar användarens val i /usr/local/share/proxmenux/local-target.conf och läser det vid varje säkerhetskopiering. Om inget mål har konfigurerats används standardvärdet HB_LOCAL_TARGET_DEFAULT = /var/lib/vz/dump (samma katalog som Proxmox använder för sina egna vzdump-utdata). Målet konfigureras via Konfigurera säkerhetskopieringsmål → Lokala arkivmål → Hantera lokalt säkerhetskopieringsmål:",
     "options": [
       "Använd standard (/var/lib/vz/dump). Proxmox lokal lagring. Finns på varje Proxmox-installation; arkivet sitter bredvid vzdump-utdata och hämtas av fliken Monitor's Backups automatiskt.",
-      "Ange en anpassad sökväg. Alla absoluta filsystemsökvägar kan användas: en NFS-montering, en SMB-delning monterad via fstab, ett särskilt ZFS-dataset eller en andra intern disk. ProxMenux kontrollerar att sökvägen finns och är en katalog innan valet sparas.",
-      "Välj en USB-enhet. Öppnar USB-undermenyn (nedan), som upptäcker flyttbara enheter och erbjuder sig att montera eller formatera en."
+      "Använd en anpassad sökväg. Alla absoluta filsystemsökvägar kan användas: en NFS-montering, en SMB-delning monterad via fstab, ett särskilt ZFS-dataset eller en andra intern disk. ProxMenux kontrollerar att sökvägen finns och är en katalog innan valet sparas.",
+      "Använd en USB-disk. Öppnar USB-undermenyn (nedan), som upptäcker flyttbara enheter och erbjuder sig att montera eller formatera en."
     ]
   },
   "usbFlow": {
@@ -38,7 +38,7 @@
       {
         "state": "ej monterad",
         "shown": "Storlek · etikett · [fstype] · (ej monterad — kommer att monteras)",
-        "action": "Ett filsystem är närvarande men inte monterat. På bekräftelse kör ProxMenux hb_mount_usb_partition: skapar /mnt/backup-LABEL (eller en UUID-baserad väg om det inte finns någon etikett), monterar partitionen och sparar monteringspunkten som det lokala målet."
+        "action": "Ett filsystem är närvarande men inte monterat. På bekräftelse kör ProxMenux hb_mount_usb_partition: skapar /mnt/proxmenux-backup-LABEL (UUID eller enhetsnamn om det inte finns någon etikett), monterar partitionen och sparar monteringspunkten som det lokala målet."
       },
       {
         "state": "tom",
@@ -63,7 +63,7 @@
   },
   "sidecar": {
     "heading": "JSON-sidofilen",
-    "intro": "Varje framgångsrik lokal backup producerar en följeslagare fil: HOSTNAME-TIMESTAMP.tar.zst.proxmenux.json, skriven bredvid arkivet av hb_write_archive_sidecar. Denna lilla JSON-fil låter ProxMenux Monitor identifiera arkivet som en ProxMenux-värdbackup även om det senare flyttas, bytas namn eller arkiveras någon annanstans.",
+    "intro": "Varje framgångsrik lokal backup producerar en följeslagare fil: hostcfg-HOSTNAME-TIMESTAMP.tar.zst.proxmenux.json, skriven bredvid arkivet av hb_write_archive_sidecar. Denna lilla JSON-fil låter ProxMenux Monitor identifiera arkivet som en ProxMenux-värdbackup även om det senare flyttas, bytas namn eller arkiveras någon annanstans.",
     "contentTitle": "Sidofilens innehåll",
     "contentBody": "Sidofilen lagrar schemaversionen, om säkerhetskopian kom från en interaktiv körning eller ett schemalagt jobb (kind), jobb-ID för schemalagda körningar, det profilläge som används (default eller custom), källvärdnamnet, den ursprungliga arkivbasnamnet, en tidsstämpel för skapandet i ISO-8601-format och arkivstorleken i byte.",
     "whyBody": "Monitors Backups-flik söker igenom konfigurerade lokala kataloger efter *.proxmenux.json-sidofiler, inte efter *.tar.zst-filer, eftersom en .tar.zst utan sidofil kanske inte är en ProxMenux-säkerhetskopia. Sökningen är snabb (JSON-filer är små) och kopplingen fungerar även efter namnbyte om sidofilen får ett motsvarande nytt namn."
diff --git a/web/messages/sv/docs/backup-restore/destinations/pbs.json b/web/messages/sv/docs/backup-restore/destinations/pbs.json
index 70e5c074..471a5753 100644
--- a/web/messages/sv/docs/backup-restore/destinations/pbs.json
+++ b/web/messages/sv/docs/backup-restore/destinations/pbs.json
@@ -33,7 +33,7 @@
       {
         "source": "ProxMenux manuell konfig",
         "path": "/usr/local/share/proxmenux/pbs-manual-configs.txt + pbs-pass-NAME.txt + pbs-fingerprint-NAME.txt",
-        "content": "Läggs till via Konfigurera säkerhetskopieringsmål → PBS-mål → Lägg till PBS. Du anger namn, användarnamn (root@pam eller user@pbs!token), värd eller IP-adress, datalager och lösenord. Om lösenordet lämnas tomt visas frågan igen; annars skulle en tom uppgift sparas utan varning och alla senare säkerhetskopior misslyckas med ett otydligt autentiseringsfel. Detta används när PBS-målet inte är registrerat som Proxmox-lagring."
+        "content": "Läggs till via Konfigurera säkerhetskopieringsmål → Proxmox Backup Server (PBS)-mål → + Lägg till ny PBS manuellt. Du anger namn, användarnamn (root@pam eller user@pbs!token), värd eller IP-adress, datalager och lösenord. Om lösenordet lämnas tomt visas frågan igen; annars skulle en tom uppgift sparas utan varning och alla senare säkerhetskopior misslyckas med ett otydligt autentiseringsfel. Detta används när PBS-målet inte är registrerat som Proxmox-lagring."
       }
     ],
     "menuTitle": "Urvalsmenyn",
diff --git a/web/messages/sv/docs/backup-restore/how-it-works.json b/web/messages/sv/docs/backup-restore/how-it-works.json
index 42415ca0..b29d1780 100644
--- a/web/messages/sv/docs/backup-restore/how-it-works.json
+++ b/web/messages/sv/docs/backup-restore/how-it-works.json
@@ -20,7 +20,7 @@
     "heading": "Arkivlayout",
     "intro": "Varje arkiv följer samma trädlayout oavsett destination. metadata/ underkatalog håller de strukturerade nyttolasterna; rootfs/ underkatalog håller filsystemet kopiera.",
     "treeCaption": "Förberedelsekatalogen som skapas vid säkerhetskopiering. Alla mål får samma träd i sitt eget format: tar för lokala mål, PBS-block för PBS och Borg-segment för Borg.",
-    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state\n├── metadata/\n│   ├── packages.manual.list       # apt-mark showmanual\n│   ├── run_info.env               # backup run identity + kernel version + pmxcfs method\n│   ├── paths_archived.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, …\n    ├── root/                      # /root (with volatile subdirs excluded)\n    ├── usr/local/                 # /usr/local/bin, /usr/local/share/proxmenux, …\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/…"
+    "tree": "backup-[timestamp]/\n├── manifest.json                  # structured host state\n├── metadata/\n│   ├── packages.manual.list       # apt-mark showmanual\n│   ├── run_info.env               # backup run identity + kernel version + pmxcfs method\n│   ├── selected_paths.txt         # exact list of paths that reached rootfs/\n│   └── missing_paths.txt          # paths from the profile absent on source\n└── rootfs/\n    ├── etc/                       # /etc/pve, /etc/network, /etc/systemd/network, /etc/ssh, …\n    ├── root/                      # /root (with volatile subdirs excluded)\n    ├── usr/local/                 # /usr/local/bin, /usr/local/share/proxmenux, …\n    └── var/                       # /var/lib/pve-cluster (config.db via sqlite3 .backup), /var/spool/cron/…"
   },
   "rootfs": {
     "heading": "Innehållet i rootfs",
@@ -176,7 +176,7 @@
         "stage": "3",
         "name": "Förbered uppskjutna ändringar",
         "reads": "rootfs/ (omstart + farliga vägar)",
-        "action": "_rs_prepare_pending_restore förbereder riskfyllda sökvägar under /var/lib/proxmenux/pending-restore/, skriver plan.env, apply-on-boot.list och rs-skip-paths.txt och aktiverar proxmenux-restore-onboot.service för nästa start."
+        "action": "_rs_prepare_pending_restore förbereder riskfyllda sökvägar under /var/lib/proxmenux/restore-pending/<restore_id>/ (länkad som current), skriver plan.env, apply-on-boot.list och rs-skip-paths.txt och aktiverar proxmenux-restore-onboot.service för nästa start."
       },
       {
         "stage": "3b",
diff --git a/web/messages/sv/docs/backup-restore/index.json b/web/messages/sv/docs/backup-restore/index.json
index 0c60947e..92334723 100644
--- a/web/messages/sv/docs/backup-restore/index.json
+++ b/web/messages/sv/docs/backup-restore/index.json
@@ -50,7 +50,7 @@
     "heading": "Två gränssnitt, en backend",
     "intro": "Varje steg i arbetsflödet för säkerhetskopiering och återställning är tillgängligt från två ingångspunkter. Båda anropar samma skalbibliotek, producerar identiska arkiv och läser samma jobbregister.",
     "cliLabel": "ProxMenux Scripts (TUI)",
-    "cliDetail": "meny → Verktyg →\nVärdsäkerhetskopiering/återställning\n\nDialogbaserat flöde,\nSSH-vänlig, skriptbar\nför obevakad användning.",
+    "cliDetail": "meny →\nVärdsäkerhetskopiering & återställning\n\nDialogbaserat flöde,\nSSH-vänlig, skriptbar\nför obevakad användning.",
     "webLabel": "ProxMenux Monitor (Webb UI)",
     "webDetail": "Backups-fliken i\nMonitors webbgränssnitt.\n\nÅterställning med ett klick,\nlöpande loggvisning, integrerat\nmed aviseringar och visning\nav skillnader vid återställning."
   },
diff --git a/web/messages/sv/docs/backup-restore/restoring.json b/web/messages/sv/docs/backup-restore/restoring.json
index 2d86fed6..880e5d2e 100644
--- a/web/messages/sv/docs/backup-restore/restoring.json
+++ b/web/messages/sv/docs/backup-restore/restoring.json
@@ -92,11 +92,11 @@
   "fullFlow": {
     "heading": "Fullständig återställning arbetsflöde",
     "intro": "Den kompletta pipeline från arkivval till en återställd arbetsvärd. Varje steg matar nästa, staten skriven av tidigare stadier konsumeras av senare.",
-    "diagram": "┌─────────────────────────────────────────────────────────────────┐\n│  Archive → staging_root/                                        │\n│    manifest.json   metadata/   rootfs/                          │\n└──────────────────────────────┬──────────────────────────────────┘\n                               │\n                               ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  1. hb_compat_check                                     │\n   │     · hardware drift → RS_SKIP_PATHS                    │\n   │     · kernel direction → same / bk_newer / bk_older     │\n   │     · NIC remap plan                                    │\n   │     · hydration plan (bk_older only)                    │\n   │     · rollback plan                                     │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  2. Confirmation dialog                                 │\n   │     Shows: hot count, pending count, drift, hydration,  │\n   │     NIC remap, cross-kernel note, reinstalls preview    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │ (accepted)\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  3. Apply hot paths → LIVE                              │\n   │     _rs_apply rsyncs each hot path                      │\n   │     from staging_root/rootfs to /                       │\n   │     (skips paths in RS_SKIP_PATHS)                      │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4. _rs_prepare_pending_restore                         │\n   │     /var/lib/proxmenux/pending-restore/                 │\n   │       ├── apply-on-boot.list                            │\n   │       ├── plan.env                                      │\n   │       ├── rs-skip-paths.txt                             │\n   │       └── rootfs/ (deferred paths)                      │\n   │     Enable proxmenux-restore-onboot.service             │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4b. _rs_import_data_pools                              │\n   │      Imports non-root ZFS pools whose disks are all     │\n   │      present; retries with -f on foreign hostid;        │\n   │      skips with a warning when disks are missing.       │\n   │      Log: /var/log/proxmenux/restore-datapools-*.log    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  5. _rs_run_complete_extras                             │\n   │     packages.manual.list                                │\n   │       → cascade-safe filter                             │\n   │       → apt-get install                                 │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  6. REBOOT                                              │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  7. apply_pending_restore.sh (on early boot)            │\n   │     Reads plan.env                                      │\n   │     Applies apply-on-boot.list (filtered)               │\n   │     Installs the postboot systemd unit                  │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  8. apply_cluster_postboot.sh (~10 minutes)             │\n   │     Runs after pve-cluster is up                        │\n   │     · Applies /etc/pve entries to pmxcfs                │\n   │     · update-initramfs -u -k all                        │\n   │     · update-grub / proxmox-boot-tool refresh           │\n   │     · component --auto-reinstall (nvidia, coral, ...)   │\n   │     · Boot sanity check                                 │\n   │     · Notification: \"Host restore finished\"             │\n   └─────────────────────────────────────────────────────────┘"
+    "diagram": "┌─────────────────────────────────────────────────────────────────┐\n│  Archive → staging_root/                                        │\n│    manifest.json   metadata/   rootfs/                          │\n└──────────────────────────────┬──────────────────────────────────┘\n                               │\n                               ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  1. hb_compat_check                                     │\n   │     · hardware drift → RS_SKIP_PATHS                    │\n   │     · kernel direction → same / bk_newer / bk_older     │\n   │     · NIC remap plan                                    │\n   │     · hydration plan (bk_older only)                    │\n   │     · rollback plan                                     │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  2. Confirmation dialog                                 │\n   │     Shows: hot count, pending count, drift, hydration,  │\n   │     NIC remap, cross-kernel note, reinstalls preview    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │ (accepted)\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  3. Apply hot paths → LIVE                              │\n   │     _rs_apply rsyncs each hot path                      │\n   │     from staging_root/rootfs to /                       │\n   │     (skips paths in RS_SKIP_PATHS)                      │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4. _rs_prepare_pending_restore                         │\n   │     /var/lib/proxmenux/restore-pending/'<'id'>'/            │\n   │       ├── apply-on-boot.list                            │\n   │       ├── plan.env                                      │\n   │       ├── rs-skip-paths.txt                             │\n   │       └── rootfs/ (deferred paths)                      │\n   │     Enable proxmenux-restore-onboot.service             │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  4b. _rs_import_data_pools                              │\n   │      Imports non-root ZFS pools whose disks are all     │\n   │      present; retries with -f on foreign hostid;        │\n   │      skips with a warning when disks are missing.       │\n   │      Log: /var/log/proxmenux/restore-datapools-*.log    │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  5. _rs_run_complete_extras                             │\n   │     packages.manual.list                                │\n   │       → cascade-safe filter                             │\n   │       → apt-get install                                 │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  6. REBOOT                                              │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  7. apply_pending_restore.sh (on early boot)            │\n   │     Reads plan.env                                      │\n   │     Applies apply-on-boot.list (filtered)               │\n   │     Installs the postboot systemd unit                  │\n   └──────────────────────────────┬──────────────────────────┘\n                                  │\n                                  ▼\n   ┌─────────────────────────────────────────────────────────┐\n   │  8. apply_cluster_postboot.sh (~10 minutes)             │\n   │     Runs after pve-cluster is up                        │\n   │     · Applies /etc/pve entries to pmxcfs                │\n   │     · update-initramfs -u -k all                        │\n   │     · update-grub / proxmox-boot-tool refresh           │\n   │     · component --auto-reinstall (nvidia, coral, ...)   │\n   │     · Boot sanity check                                 │\n   │     · Notification: \"Host restore finished\"             │\n   └─────────────────────────────────────────────────────────┘"
   },
   "pendingMachinery": {
     "heading": "Mekanismen för uppskjuten återställning",
-    "intro": "Paths som klassificeras som reboot eller dangerous, och hydrering skriver, iscensätts för nästa start istället för att appliceras live. Detta görs genom en liten, självinnehållen uppsättning filer under /var/lib/proxmenux/pending-restore/:",
+    "intro": "Paths som klassificeras som reboot eller dangerous, och hydrering skriver, iscensätts för nästa start istället för att appliceras live. Detta görs genom en liten, självinnehållen uppsättning filer under /var/lib/proxmenux/restore-pending/<restore_id>/:",
     "rows": [
       {
         "file": "apply-on-boot.list",
@@ -115,7 +115,7 @@
         "content": "Själva förberedda filerna: exakt de byte som ska skrivas. De lagras på samma filsystem som / så att rsync vid start går snabbt och inte kräver åtkomst till extern lagring."
       }
     ],
-    "unitBody": "Systemd-enheten proxmenux-restore-onboot.service aktiveras i slutet av det interaktiva steget. Engångstjänsten körs tidigt vid nästa start, anropar apply_pending_restore.sh och inaktiverar sedan sig själv. Villkoret ConditionPathExists=/var/lib/proxmenux/pending-restore/state gör att inget körs vid starter utan väntande återställning."
+    "unitBody": "Systemd-enheten proxmenux-restore-onboot.service aktiveras i slutet av det interaktiva steget. Engångstjänsten körs tidigt vid nästa start (före nätverket), anropar apply_pending_restore.sh och inaktiverar sedan sig själv. Enheten har inget sökvägsvillkor: skriptet avslutas självt när /var/lib/proxmenux/restore-pending/current inte finns, så vid starter utan väntande återställning görs ingenting. Den separata tjänsten proxmenux-apply-cluster-postboot.service styrs av ConditionPathExists=/var/lib/proxmenux/cluster-apply-pending."
   },
   "postbootDispatcher": {
     "heading": "Post-boot dispatcher - apply_cluster_postboot.sh",
@@ -238,11 +238,11 @@
     "heading": "Var loggarna finns",
     "rows": [
       {
-        "log": "/var/log/proxmenux/restore-YYYYMMDD_HHMMSS.log",
-        "detail": "Skrivs under det interaktiva steget: kompatibilitetsresultat, avvikelsefilter, anpassningsplan, utdata från direkt tillämpning och förberedelse av uppskjutna ändringar samt paketinstallation."
+        "log": "/tmp/proxmenux--restore-YYYYMMDD_HHMMSS.log",
+        "detail": "Skrivs under det interaktiva steget (pbs, borg eller local). Paketinstallation och datapoolsteg loggar även till /var/log/proxmenux/restore-apt-*.log och restore-datapools-*.log."
       },
       {
-        "log": "/var/log/proxmenux/apply-pending-YYYYMMDD_HHMMSS.log",
+        "log": "/var/log/proxmenux/proxmenux-restore-onboot-YYYYMMDD_HHMMSS.log",
         "detail": "Skrivs av apply_pending_restore.sh tidigt vid start: inläsning av plan.env, genomgång av apply-on-boot och filtrering av undantagna sökvägar."
       },
       {
diff --git a/web/messages/sv/docs/backup-restore/scheduled-jobs.json b/web/messages/sv/docs/backup-restore/scheduled-jobs.json
index cd4cd3b5..82e71ace 100644
--- a/web/messages/sv/docs/backup-restore/scheduled-jobs.json
+++ b/web/messages/sv/docs/backup-restore/scheduled-jobs.json
@@ -101,7 +101,7 @@
   },
   "management": {
     "heading": "Hantera jobb",
-    "intro": "Schemaläggarmenyn (Scripts TUI) och fliken Monitor Backups avslöjar samma åtgärder på något jobb.",
+    "intro": "Schemaläggarmenyn i Scripts TUI (Backup scheduler och retention) erbjuder Run now, Enable / Disable och Delete, samt en lista över jobb med status för senaste körning. Edit och View log finns bara på fliken Monitor Backups.",
     "rows": [
       {
         "action": "Kör nu",
@@ -112,7 +112,7 @@
         "detail": "Nytt läge: systemctl enable/disable --now timern. Bifoga läge: vänder ENABLED flaggan i jobbenv - krokskriptet respekterar den vid nästa PVE-körning."
       },
       {
-        "action": "Redigera",
+        "action": "Redigera (endast Monitor)",
         "detail": "Öppnar åter dialogerna för destination, schema, profil och bevarande och skriver om jobbfilerna. Jobb-ID:t behålls."
       },
       {
@@ -120,7 +120,7 @@
         "detail": "Tar bort jobbets env-fil, sökvägsfilen och systemd-timern med tjänsten (nytt läge) samt inaktiverar hookkopplingen (kopplingsläge). Arkiv som redan finns på destinationen påverkas inte."
       },
       {
-        "action": "Visa logg",
+        "action": "Visa logg (endast Monitor)",
         "detail": "Strömmar /var/log/proxmenux/backup-jobs/JOB_ID-YYYYMMDD_HHMMSS.log — en loggfil per körning. Körningsskriptet sänder också ut en kompakt statusrad till journald under tjänsten systemd."
       }
     ]
diff --git a/web/messages/sv/docs/installation.json b/web/messages/sv/docs/installation.json
index 9157dac6..87790d59 100644
--- a/web/messages/sv/docs/installation.json
+++ b/web/messages/sv/docs/installation.json
@@ -53,11 +53,11 @@
     "calloutBody": "Vill du testa nyheterna före den officiella utgåvan och påverka slutversionen? ProxMenux betaprogram ger tidig tillgång till nya funktioner, inklusive de senaste versionerna av Monitor, direkt från grenen develop. Betaversioner kan innehålla buggar och ofullständiga funktioner. Dina synpunkter hjälper till att rätta dem före den stabila utgåvan.",
     "intro": "Installera betaversionen istället för den stabila med:",
     "code": "bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/develop/install_proxmenux_beta.sh)\"",
-    "outro": "Betaprogrammet är frivilligt: du måste uttryckligen köra beta-installationsprogrammet. När en stabil version släpps meddelar ProxMenux det vid nästa start av menu och erbjuder automatiskt byte. Om du vill lämna betan tidigare väljer du Inaktivera betaprogrammet i inställningarna."
+    "outro": "Betaprogrammet är frivilligt: du måste uttryckligen köra beta-installationsprogrammet. När en stabil version släpps erbjuds den vid nästa start av menu med den vanliga uppdateringsfrågan. Om du vill lämna betakanalen väljer du Byt versionskanal i inställningarna och väljer Stable."
   },
   "updating": {
     "heading": "Uppdatering",
-    "body": "ProxMenux hanterar sina egna uppdateringar. När en ny version finns erbjuder nästa start av menu att installera den. Om du accepterar ersätts verktygets filer och konfiguration i den befintliga installationen, utan manuell nedladdning. Den stabila kanalen får stabila utgåvor och betakanalen betautgåvor, med erbjudandet om byte som beskrivs ovan. Dialogen beror på var du kör menu: via SSH eller i Proxmox-konsolen visas en ja/nej-dialog, och uppdateringen tillämpas direkt om du accepterar. I WebSocket-terminalen i ProxMenux Monitor visas i stället information, eftersom den terminalen skulle avbrytas under uppdateringen. Meddelandet visar installationskommandot på en rad (bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\" för stabil eller motsvarande adress till install_proxmenux_beta.sh för beta), som du kör via SSH eller konsolen."
+    "body": "ProxMenux hanterar sina egna uppdateringar. När en ny version finns erbjuder nästa start av menu att installera den. Om du accepterar ersätts verktygets filer och konfiguration i den befintliga installationen, utan manuell nedladdning. Den stabila kanalen får stabila utgåvor; betakanalen får betabyggen och erbjuds även nya stabila utgåvor. Dialogen beror på var du kör menu: via SSH eller i Proxmox-konsolen visas en ja/nej-dialog, och uppdateringen tillämpas direkt om du accepterar. I WebSocket-terminalen i ProxMenux Monitor visas i stället information, eftersom den terminalen skulle avbrytas under uppdateringen. Meddelandet visar installationskommandot på en rad (bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\" för stabil eller motsvarande adress till install_proxmenux_beta.sh för beta), som du kör via SSH eller konsolen."
   },
   "uninstall": {
     "heading": "Avinstallera",
diff --git a/web/messages/sv/docs/introduction.json b/web/messages/sv/docs/introduction.json
index eb8ebeac..24716153 100644
--- a/web/messages/sv/docs/introduction.json
+++ b/web/messages/sv/docs/introduction.json
@@ -21,11 +21,11 @@
     "intro": "ProxMenux har två delar i samma källkodsträd. Installationsprogrammet installerar båda på värdsystemet: Scripts ger terminalgränssnittet och Monitor webbpanelen:",
     "scripts": {
       "title": "ProxMenux Scripts",
-      "body": "Det ursprungliga CLI/TUI som du startar med menu. Nio områden täcker optimeringar efter installation, virtuella maskiner och LXC-containrar, GPU- och Coral TPU-genomkoppling, diskar, lagring, nätverk, säkerhet och verktyg samt en utvald kommandoreferens. Fungerar i lokala terminaler, via SSH och i webbterminaler."
+      "body": "Det ursprungliga CLI/TUI som du startar med menu. Områden som täcker optimeringar efter installation, skapande av virtuella maskiner och LXC-containrar, GPU- och Coral TPU-genomkoppling, diskar, lagring, OCI-appar, nätverk, säkerhet, verktyg, säkerhetskopiering och återställning av värden, Proxmox VE Helper Scripts samt en utvald kommandoreferens. Fungerar i lokala terminaler, via SSH och i webbterminaler."
     },
     "monitor": {
       "title": "ProxMenux Monitor",
-      "body": "Webbpanel på den egna servern, distribuerad som AppImage. Värdsystemets mätvärden i realtid, proaktiv övervakning, aviseringar, valfri AI-omformulering med sex leverantörer, inbyggd webbterminal och REST-API. Aktiveras i Scripts-menyn under Inställningar → ProxMenux Monitor."
+      "body": "Webbpanel på den egna servern, distribuerad som AppImage. Värdsystemets mätvärden i realtid, proaktiv övervakning, aviseringar, valfri AI-omformulering med sex leverantörer, inbyggd webbterminal och REST-API. Installationsprogrammet sätter upp den som en tjänst som startar vid uppstart; aktivera, inaktivera eller kontrollera den i Scripts-menyn under Inställningar → ProxMenux Monitor."
     },
     "calloutTitle": "Vad ProxMenux hanterar",
     "calloutIntro": "Tre lager, alla tillgängliga från samma projekt:",
@@ -37,7 +37,7 @@
   },
   "scriptsSection": {
     "heading": "ProxMenux Scripts - vad du får",
-    "intro": "Nio funktionella områden, var och en med sin egen meny i menu och en detaljsida i dokumentationen. Klicka på vilket kort som helst för att hoppa direkt till det avsnittets översikt.",
+    "intro": "De huvudsakliga funktionella områdena, var och ett med sin egen meny i menu och en detaljsida i dokumentationen. Klicka på vilket kort som helst för att hoppa direkt till det avsnittets översikt.",
     "items": [
       {
         "title": "Efter installationen",
diff --git a/web/messages/sv/docs/monitor/access-auth.json b/web/messages/sv/docs/monitor/access-auth.json
index f02a46b3..bfed5865 100644
--- a/web/messages/sv/docs/monitor/access-auth.json
+++ b/web/messages/sv/docs/monitor/access-auth.json
@@ -18,7 +18,7 @@
   },
   "reaching": {
     "heading": "Öppna instrumentpanelen",
-    "intro": "ProxMenux Monitor binder till 0.0.0.0:8008. Det finns tre vanliga sätt att nå den:",
+    "intro": "ProxMenux Monitor lyssnar på TCP 8008 på alla gränssnitt (::, IPv4 och IPv6). Det finns tre vanliga sätt att nå den:",
     "outro": "Direktåtkomst motsvarar standardenheten för systemd. Omvänd proxy och Secure Gateway beskrivs nedan. Förtroende för klient-IP i X-Forwarded-For kräver PROXMENUX_TRUST_PROXY=1 och en proxy du kontrollerar. Enbart förekomsten av X-Forwarded-Proto eller X-Forwarded-Host aktiverar inte proxyförtroende. På portarna 80/443 använder frontend relativa /api/*-URL:er; på alla andra portar anropas port 8008 på samma värdnamn."
   },
   "firstLaunch": {
@@ -158,7 +158,7 @@
     "authJsonTitle": "auth.json — innehåll och skydd",
     "authJsonBody": "Autentiseringsuppgifterna finns i /root/.config/proxmenux-monitor/auth.json, med läge 0600 och ägare root. Filen innehåller PBKDF2-hashar och kryptografiska hemligheter (jwt_secret, totp_secret), aldrig lösenord i klartext. Kryptera mål för säkerhetskopiering eller replikering och lägg aldrig filen i versionshantering.",
     "rotateTitle": "Byte av jwt_secret gör alla befintliga JWT ogiltiga",
-    "rotateBody": "Om auth.json återskapas eller återställs med en annan hemlighet ändras jwt_secret. Gamla sessions- och API-JWT misslyckas med \"Invalid or expired token\". Gränssnittet visar Ogiltigt — skapa på nytt. Återkalla och ersätt berörda token även i Home Assistant, skript och andra klienter.",
+    "rotateBody": "Om auth.json återskapas eller återställs med en annan hemlighet ändras jwt_secret. Gamla sessions- och API-JWT misslyckas med \"Invalid or expired token\". Gränssnittet markerar berörda API-token med rött och en etikett som uppmanar dig att återkalla dem och skapa nya. Home Assistant, skript och andra externa klienter behöver en ny token efteråt.",
     "recoverTitle": "Återställa ett förlorat lösenord",
     "recoverIntro": "Lösenordsåterställning kräver skalåtkomst till värden. ProxMenux har en guidad återställning i konfigurationsmenyn så att du slipper redigera auth.json manuellt:",
     "survivesTitle": "Vad överlever återställningen",
@@ -284,14 +284,14 @@
     "heading": "API-token med lång giltighetstid",
     "intro": "Sessioner löper ut efter 24 timmar. Skapa ett separat API-token giltigt i 365 dagar för Homepage, Home Assistant, Grafana eller Uptime Kuma. JWT:t använder samma signeringshemlighet; fältet token_name gör att det kan identifieras och återkallas separat.",
     "imageAlt": "API-tokenlista med namn, prefix, datum för skapande och utgångsdatum",
-    "imageCaption": "Listan i inställningarna visar namn, prefix med de fyra sista tecknen för identifiering, skapande- och utgångsdatum samt återkallelse.",
+    "imageCaption": "Listan över API-token under Administration → Säkerhet visar namn, prefix (de fyra sista tecknen visas för identifiering), skapande- och utgångsdatum samt återkallelse.",
     "generateTitle": "Generera en token",
     "generateIntro": "Från panelen:",
     "generateSteps": [
-      "Öppna Säkerhet → API-åtkomsttoken.",
+      "Öppna Administration → Säkerhet → API-tokens och välj Generera ny token.",
       "Skriv ett beskrivande namn (t.ex. \"Home Assistant\").",
       "Skriv in ditt lösenord. Om 2FA är på, även den nuvarande 6-siffriga koden.",
-      "Välj Generera token. Det visas endast en gång: kopiera direkt."
+      "Välj Generera token. Den visas endast en gång: kopiera direkt."
     ],
     "generateCli": "Från kommandoraden:",
     "useTitle": "Använd en token",
@@ -310,7 +310,7 @@
     "intro": "Två vägar till TLS:",
     "items": [
       "Omvänd proxy rekommenderas. Avsluta TLS på Nginx / Caddy / Traefik och vidarebefordra HTTP till Flask på port 8008. Exempel nedan.",
-      "Direkt HTTPS i AppImage. Ange certifikat via POST /api/ssl/configure (gränssnitt: Inställningar → SSL). HTTPS använder gevent.pywsgi med standardhanteraren; flask-sock hanterar WebSocket för WSS. Certifikatsökvägar sparas i SSL-konfigurationen."
+      "Direkt HTTPS i AppImage. Ange certifikat via POST /api/ssl/configure (gränssnitt: Administration → Säkerhet → SSL / HTTPS). HTTPS använder gevent.pywsgi med standardhanteraren; flask-sock hanterar WebSocket för WSS. Certifikatsökvägar sparas i SSL-konfigurationen."
     ],
     "calloutTitle": "Direkt HTTPS begränsningar",
     "calloutBody": "Monitor kan använda ett certifikat hanterat av Proxmox eller egna certifikatsfiler. Det utfärdar inte ACME-certifikat självt. Använd Proxmox ACME eller Caddy, Traefik eller Nginx för automatisk utfärdning och förnyelse, och se till att förnyade certifikat läses in."
diff --git a/web/messages/sv/docs/monitor/ai-assistant.json b/web/messages/sv/docs/monitor/ai-assistant.json
index e4e132d8..cfb6dd43 100644
--- a/web/messages/sv/docs/monitor/ai-assistant.json
+++ b/web/messages/sv/docs/monitor/ai-assistant.json
@@ -9,7 +9,7 @@
   },
   "header": {
     "title": "AI-assistent",
-    "description": "Valfri AI-omskrivning gör lämpliga aviseringsmallar till tydliga meddelanden med detaljnivå per kanal, tretton språk i gränssnittet, egna promptar, ett offentligt communitybibliotek och berikad kontext. Rapporterna backup_complete och backup_fail går helt förbi AI.",
+    "description": "Valfri AI-omskrivning gör lämpliga aviseringsmallar till tydliga meddelanden med detaljnivå per kanal, tretton språk i gränssnittet, egna promptar, ett offentligt communitybibliotek och berikad kontext. Rapporterna backup_complete och backup_fail skrivs aldrig om; med AI-förslag påslaget lägger AI bara till en förklaring när en säkerhetskopiering rapporterar fel.",
     "section": "ProxMenux-övervakning"
   },
   "intro": {
@@ -83,7 +83,7 @@
     "intro1": "Kommersiella leverantörer tar betalt per token. En token motsvarar ungefär fyra engelska tecken eller tre fjärdedelar av ett ord. Den engelska frasen \"Backup completed on storage local-bak\" är omkring åtta token. Tio loggrader kan vara 200–400 token beroende på det tekniska innehållet.",
     "intro2": "Två saker faktureras på varje samtal:",
     "items": [
-      "Indatatoken omfattar systemprompt och användarmeddelande med allvarlighetsgrad, rubrik, malltext och berikad kontext. Antalet beror på prompt och händelse. Varje omskriven kanal kan förbruka egna indatatoken; rapporter om slutförd eller misslyckad säkerhetskopiering går förbi AI.",
+      "Indatatoken omfattar systemprompt och användarmeddelande med allvarlighetsgrad, rubrik, malltext och berikad kontext. Antalet beror på prompt och händelse. Varje omskriven kanal kan förbruka egna indatatoken; rapporter om slutförd eller misslyckad säkerhetskopiering förbrukar bara token när AI förklarar fel i säkerhetskopieringen.",
       "Utdatatoken är modellens svar. Monitor begränsar dem med max_tokens. Taket är en gräns, inte fast förbrukning: ger modellen 250 token med gränsen 1500 betalar du för 250."
     ],
     "capsIntro": "Dessa är de gränser som sändningsmodulen använder, direkt från AI_DETAIL_TOKENS i notification_templates.py:",
@@ -118,7 +118,7 @@
     "imageCaption": "Informationsrutan i appen: sex kort, ett per leverantör, med beskrivningarna nedan.",
     "groq": {
       "heading": "Groq",
-      "tagline": "Mycket snabb, generös fri nivå (30 req/min). Perfekt att börja.",
+      "tagline": "Mycket snabb, med en generös fri nivå. Ett bra ställe att börja.",
       "items": [
         "API-nyckel: console.groq.com/keys",
         "Exempel från den medföljande modellistan: allam-2-7b, qwen/qwen3.8-27b, openai/gpt-oss-20b, openai/gpt-oss-120b.",
@@ -234,7 +234,7 @@
   },
   "detailLevel": {
     "heading": "Detaljnivå per kanal",
-    "intro": "Telegram, Discord, Gotify och e-post har varsin detaljväljare. Tre värden styr gränsen för utdatatoken och instruktionerna i standardprompten:",
+    "intro": "Var och en av de sex kanalerna (Telegram, Discord, Gotify, e-post, Pushover, Apprise) har en egen detaljväljare. Tre värden styr gränsen för utdatatoken och instruktionerna i standardprompten:",
     "headerLevel": "Nivå",
     "headerLabel": "UI-märkning",
     "headerCap": "Utdatagräns",
@@ -261,7 +261,7 @@
     ],
     "defaultsIntro": "Standarder som Monitorn tillämpar på första installationen:",
     "defaults": [
-      "Telegram, Discord, Gotify — standard.",
+      "Telegram, Discord, Gotify, Pushover, Apprise — standard.",
       "E-post — detailed. E-post är den kanal där du vanligtvis vill ha hela bilden för arkiv."
     ],
     "emailTitle": "Detaljerad e-post lägger till originalet",
@@ -280,7 +280,7 @@
   },
   "templates": {
     "heading": "En anteckning på mallar",
-    "body1": "AI får färdig malltext, inte rå händelsedata. Typer som vm_start, auth_fail och health_degraded har mallar i notification_templates.py. AI skriver om lämpliga mallutdata; backup_complete och backup_fail behåller deterministiska rapporter utan AI.",
+    "body1": "AI får färdig malltext, inte rå händelsedata. Typer som vm_start, auth_fail och health_degraded har mallar i notification_templates.py. AI skriver om lämpliga mallutdata; backup_complete och backup_fail behåller deterministiska rapporter; med AI-förslag påslaget lägger AI bara till en förklaring när säkerhetskopieringen rapporterar fel.",
     "body2": "Två praktiska konsekvenser: AI ser aldrig ett värdnamn eller VMID det måste uppfinna - dessa fält placeras av mallen innan omskrivningen. Och om AI är inaktiverad är den mallade kroppen det som skickas direkt. Notifications-sidan dokumenterar sändningspipeline i sin helhet och är rätt korsreferens för allt som händer en händelse innan det når detta lager."
   },
   "privacy": {
diff --git a/web/messages/sv/docs/monitor/api-reference.json b/web/messages/sv/docs/monitor/api-reference.json
index d4d2817c..9d04e8fe 100644
--- a/web/messages/sv/docs/monitor/api-reference.json
+++ b/web/messages/sv/docs/monitor/api-reference.json
@@ -76,7 +76,7 @@
       {
         "endpoint": "/api/system-info",
         "method": "GET",
-        "use": "Utökade systemdata för panelens sidhuvud: övergripande mätvärden och starttid."
+        "use": "Lättviktig sidhuvudinformation: värdnamn, drifttid och cachad hälsostatus. Offentlig, ingen token behövs."
       },
       {
         "endpoint": "/api/hardware",
@@ -401,7 +401,7 @@
       {
         "endpoint": "/api/logs",
         "method": "GET",
-        "use": "Filtrerade journalposter. Query: ?level=&service=&limit="
+        "use": "Filtrerade journalposter. Query: ?priority=&service=&limit=&since_days= (priority 0-7, since_days upp till 90)."
       },
       {
         "endpoint": "/api/logs/download",
@@ -411,7 +411,7 @@
       {
         "endpoint": "/api/events",
         "method": "GET",
-        "use": "Intern händelseström - samma som matar meddelanden."
+        "use": "Senaste Proxmox-klusteruppgifter och händelser från pvesh get /cluster/tasks. Query: ?limit= (standard 50)."
       },
       {
         "endpoint": "/api/task-log/",
@@ -462,7 +462,7 @@
       {
         "endpoint": "/api/notifications/send",
         "method": "POST",
-        "use": "Skapar en egen händelse. Begärans innehåll: '{'\"event_type\":\"custom\",\"severity\":\"WARNING\",\"title\":\"...\",\"body\":\"...\",\"data\":'{''}''}'."
+        "use": "Skapar en egen händelse. Begärans innehåll: '{'\"event_type\":\"custom\",\"severity\":\"WARNING\",\"title\":\"...\",\"message\":\"...\",\"data\":'{''}''}'."
       },
       {
         "endpoint": "/api/notifications/test-ai",
diff --git a/web/messages/sv/docs/monitor/architecture.json b/web/messages/sv/docs/monitor/architecture.json
index f8127df2..86621f15 100644
--- a/web/messages/sv/docs/monitor/architecture.json
+++ b/web/messages/sv/docs/monitor/architecture.json
@@ -64,7 +64,7 @@
     "items": [
       "User=root: tjänsten använder root-behörighet för SMART, pvesh och ZFS-hantering. Webbterminalens processer ärver behörigheten.",
       "Restart=on-failure med tio sekunders väntan: tjänsten startas om automatiskt efter ett fel.",
-      "After=network.target: ordnar start efter målet; garanterar inte tilldelad IP, DNS eller extern anslutning."
+      "After=network.target pve-cluster.service: ordnar start efter nätverksmålet och Proxmox klusterfilsystem; garanterar inte tilldelad IP, DNS eller extern anslutning."
     ],
     "inspectTitle": "Granska aktuell enhet"
   },
diff --git a/web/messages/sv/docs/monitor/dashboard/hardware.json b/web/messages/sv/docs/monitor/dashboard/hardware.json
index 95408fac..98b0f7b6 100644
--- a/web/messages/sv/docs/monitor/dashboard/hardware.json
+++ b/web/messages/sv/docs/monitor/dashboard/hardware.json
@@ -168,7 +168,7 @@
   "ups": {
     "heading": "UPS-status",
     "subHeading": "(villkorligt)",
-    "body": "Visas när en NUT-server (Network UPS Tools) är konfigurerad och nåbar. Visar status (nät / batteri / laddning / låg batterinivå), batterinivå, uppskattad drifttid, belastningsprocent, ingångsspänning, modell och firmware. Samma data används i Säkerhet och certifikat i hälsokontrollen; batteridrift uppmärksammas direkt."
+    "body": "Visas när en NUT-server (Network UPS Tools) är konfigurerad och nåbar. Visar status (nät / batteri / laddning / låg batterinivå), batterinivå, uppskattad drifttid, belastningsprocent, ingångsspänning, modell och firmware."
   },
   "dataCollected": {
     "heading": "Hur data samlas in",
diff --git a/web/messages/sv/docs/monitor/dashboard/index.json b/web/messages/sv/docs/monitor/dashboard/index.json
index a7a98c84..c6fe75b0 100644
--- a/web/messages/sv/docs/monitor/dashboard/index.json
+++ b/web/messages/sv/docs/monitor/dashboard/index.json
@@ -50,7 +50,7 @@
       },
       {
         "name": "Terminal",
-        "owns": "Skal till värden eller valfri VM/CT i webbläsaren, med xterm.js över WebSockets."
+        "owns": "Skal i webbläsaren på Proxmox-värden, med xterm.js över WebSockets. LXC-konsoler öppnas från fliken VM och LXC."
       },
       {
         "name": "Säkerhet",
diff --git a/web/messages/sv/docs/monitor/dashboard/security.json b/web/messages/sv/docs/monitor/dashboard/security.json
index 718d35a8..ccf02215 100644
--- a/web/messages/sv/docs/monitor/dashboard/security.json
+++ b/web/messages/sv/docs/monitor/dashboard/security.json
@@ -183,7 +183,7 @@
       },
       {
         "card": "SSL / HTTPS",
-        "endpoint": "/api/auth/ssl/*",
+        "endpoint": "/api/ssl/*",
         "source": "openssl x509 på /etc/pve/local/pve-ssl.pem + /etc/proxmenux/ssl_config.json."
       },
       {
diff --git a/web/messages/sv/docs/monitor/dashboard/settings.json b/web/messages/sv/docs/monitor/dashboard/settings.json
index ec7dba49..25660fe3 100644
--- a/web/messages/sv/docs/monitor/dashboard/settings.json
+++ b/web/messages/sv/docs/monitor/dashboard/settings.json
@@ -41,7 +41,7 @@
     "heading": "Nätverksenheter",
     "imageAlt": "Kortet Nätverksenheter med visningsvalet inställt på Byte",
     "imageCaption": "Välj mellan bitar per sekund och byte per sekund för varje nätverkshastighet som visas i instrumentpanelen.",
-    "body": "Visar nätverkshastighet i bitar per sekund (Mbps/Gbps) eller byte per sekund (MB/s/GB/s). Bitar är standard som i specifikationer för nätverkskort och internet; filöverföringar använder ofta byte. Diagram, etiketter och tips ändras direkt utan omladdning."
+    "body": "Välj hur nätverkshastighet visas i panelen: bitar per sekund (Mbps / Gbps) eller byte per sekund (MB/s / GB/s). Byte är standard, som de flesta filöverföringsverktyg visar; bitar är vad nätverkskortstillverkare och internetleverantörer använder. Valet sparas i webbläsaren, så varje webbläsare har sitt eget. Inställningen påverkar alla diagram, etiketter och tips som visar nätverkshastighet och ändras direkt utan omladdning."
   },
   "health": {
     "heading": "Hälsoövervakare",
@@ -203,7 +203,7 @@
     "heading": "Identifiering av LXC-uppdateringar",
     "imageAlt": "Kort för LXC-uppdateringar med reglage för att söka väntande paket i körande Debian-, Ubuntu- och Alpine-containrar",
     "imageCaption": "Reglage för regelbundna kontroller med apt list --upgradable / apk list -u i körande CT. Påslaget som standard. Motsvarande aviseringsreglage under Aviseringar → Tjänster visas när detektering är på.",
-    "intro": "Reglaget mellan Gränser och Aviseringar styr LXC-kontrollen. Påslaget frågar det pakethanterarna i körande CT; Hårdvara visar resultat och matar lxc_updates_available. Avstängt stoppar det sina pct exec-anrop och tar bort LXC-poster från managed_installs.json; /api/managed-installs slutar visa statusen utan att vänta 24 timmar.",
+    "intro": "Ett särskilt reglage för LXC-uppdateringskontrollen, placerat mellan Gränser och Aviseringar. Påslaget går ProxMenux igenom varje körande CT på värden och frågar pakethanteraren i containern efter väntande uppdateringar; resultaten visas i antalsmärkena på fliken VM och LXC och matar aviseringen lxc_updates_available. Avstängt stoppar kontrollen helt (inga pct exec-anrop) och befintliga LXC-poster i managed_installs.json tas bort direkt, så att panelen och /api/managed-installs slutar visa LXC-uppdateringsstatus utan att vänta på nästa 24-timmarscykel.",
     "whatRunsTitle": "Vad kontrollen kör",
     "whatRunsIntro": "För varje CT i tillståndet running med en pakethanterare som stöds:",
     "whatRunsItems": [
@@ -249,7 +249,7 @@
     "body1": "Här aktiveras ProxMenux Monitor-aviseringar och AI-omformulering. Aktivera aviseringar startar sändning i bakgrunden, registrerar ett Proxmox VE-webhookmål för dess händelser och öppnar kanalformuläret för Telegram, Discord, e-post, Gotify och fler. AI-omformulering finns i ett utfällbart avancerat avsnitt i samma panel.",
     "body2": "Alternativen förklaras på egna sidor: kanaler, händelsereglage, formaterade meddelanden, visningsnamn, sändning med dubblettfilter, väntetid, gruppering och tysta timmar, PVE-webhook, AI-leverantörer och instruktionslägen:",
     "items": [
-      "Aviseringar: kanaler (Telegram, Discord, Gotify, e-post och applösenord för Gmail / Microsoft, ntfy, Slack, Teams och allmän webhook), händelsekategorier, formaterade meddelanden, visningsnamn, sändning, PVE-webhook, historik och API.",
+      "Aviseringar: kanaler (Telegram, Discord, Gotify, e-post och applösenord för Gmail / Microsoft, Pushover samt Apprise för ntfy, Slack, Teams och andra tjänster), händelsekategorier, formaterade meddelanden, visningsnamn, sändning, PVE-webhook, historik och API.",
       "AI-assistent: leverantörer (OpenAI, Anthropic, Gemini, Groq, OpenRouter, Ollama), modeller, standardinstruktioner eller egna instruktioner, utmatningsspråk, detaljnivå per kanal och AI-förslag."
     ]
   },
@@ -294,8 +294,8 @@
       },
       {
         "card": "Nätverksenheter",
-        "endpoint": "/api/settings",
-        "source": "Sparas i panelens SQLite-tabell för inställningar."
+        "endpoint": "localStorage",
+        "source": "Webbläsarinställning, proxmenux-network-unit."
       },
       {
         "card": "Tider i Health Monitor",
@@ -304,7 +304,7 @@
       },
       {
         "card": "Undantag för lagring och gränssnitt",
-        "endpoint": "/api/storage/exclusions, /api/network/exclusions",
+        "endpoint": "/api/health/storage-exclusions, /api/health/interface-exclusions",
         "source": "SQLite-tabellerna excluded_storages och excluded_interfaces."
       },
       {
diff --git a/web/messages/sv/docs/monitor/dashboard/storage.json b/web/messages/sv/docs/monitor/dashboard/storage.json
index 522a9a65..55e4056b 100644
--- a/web/messages/sv/docs/monitor/dashboard/storage.json
+++ b/web/messages/sv/docs/monitor/dashboard/storage.json
@@ -10,7 +10,7 @@
   },
   "intro": {
     "title": "Uppbackad av tre källor",
-    "body": "Proxmox-lagring hämtas från pvesm status, ZFS från zpool status, diskar från lsblk + smartctl och NVMe-fält från nvme. Fliken uppdateras ungefär var 60:e sekund; detaljvyn begär en SMART-avläsning."
+    "body": "Proxmox-lagring hämtas från pvesm status, ZFS från zpool status, diskar från lsblk + smartctl och NVMe-fält från nvme. Fliken uppdateras ungefär var 30:e sekund; detaljvyn begär en SMART-avläsning."
   },
   "thresholds": {
     "title": "Statusfärger och trösklar som tillämpas här",
diff --git a/web/messages/sv/docs/monitor/dashboard/system-logs.json b/web/messages/sv/docs/monitor/dashboard/system-logs.json
index c3cbb9e7..8a78e0b7 100644
--- a/web/messages/sv/docs/monitor/dashboard/system-logs.json
+++ b/web/messages/sv/docs/monitor/dashboard/system-logs.json
@@ -18,7 +18,7 @@
       "Totalt antal poster — antal loggposter i det aktiva filtrets tidsintervall.",
       "Fel — antal poster med allvarlighetsgrad ≤ 3 (err / crit / alert / emerg).",
       "Varningar — antal poster med allvarlighetsgrad 4 (warning).",
-      "Säkerhetskopieringar — antal vzdump-/PBS-uppgifter i samma tidsintervall."
+      "Säkerhetskopieringar: antal säkerhetskopieringsarkiv på de konfigurerade lagringarna för säkerhetskopiering."
     ]
   },
   "subtabs": {
@@ -26,10 +26,10 @@
     "logsTitle": "Loggar",
     "logsIntro": "Systemjournalen hämtas på servern med journalctl. Följande filter finns i verktygsfältet:",
     "logsFilters": [
-      "Allvarlighetsgrad — emerg / alert / crit / err / warning / notice / info / debug, eller valfri kombination.",
-      "Time range - sista 5 min / 15 min / 1 h / 6 h / 24 h / 7 d / custom.",
-      "Fritextsökning — delsträng eller reguljärt uttryck (journalctl --grep).",
-      "Enhetsfilter — begränsa till en viss systemd-enhet (pveproxy.service, nginx.service, …)."
+      "Level: Alla nivåer, Fel, Varning eller Info.",
+      "Time range: 1 dag / 3 dagar / 1 vecka / 2 veckor / 1 månad, eller ett eget antal dagar (upp till 90).",
+      "Fritextsökning: skiftlägesokänslig delsträng över meddelande, tjänst, PID, värdnamn och enhet, tillämpad i webbläsaren.",
+      "Service filter: begränsa till en tjänst (journalens SYSLOG_IDENTIFIER)."
     ],
     "logsRowsAfter": "Varje rad visar tidsstämpel, svårighetsgrad, källenhet och meddelandet. Långa meddelanden kollapsar med en \"visa mer\" växel. Den är Ladda ner Åtgärder buntar det aktuella filtret i en enda .txt fil via GET /api/logs/download - användbar när du vill dela en bit av tidskrift med någon.",
     "logDetailsModalTitle": "Dialogrutan Loggdetaljer",
@@ -50,10 +50,10 @@
     "maxLevelStoreTitle": "Journald MaxLevelStore",
     "maxLevelStoreBody": "På en ny Proxmox-installation använder journald som standard MaxLevelStore=warning, vilket kastar bort info-meddelanden. Monitor upptäcker detta vid start och lägger till en kompletterande konfiguration (/etc/systemd/journald.conf.d/proxmenux-loglevel.conf) med gränsen info, så att informationsmeddelanden och allvarligare meddelanden sparas. Detta omfattar inte debug.",
     "backupsTitle": "Säkerhetskopieringar",
-    "backupsBody": "Proxmox uppgiftshistorik filtrerad till säkerhetskopieringar. En rad per uppgift (vzdump, PBS-överföringar, Garbage Collect, Verify) med status (OK / WARNINGS / ERROR), berörd gäst, källagring, varaktighet och UPID. Klicka på en rad för att läsa hela uppgiftsloggen via GET /api/task-log/<upid> — samma data som i Datacenter → Tasks, begränsade till säkerhetskopieringar.",
+    "backupsBody": "Säkerhetskopieringsarkiven på varje lagring som kan hålla säkerhetskopior (dir, NFS, CIFS, PBS), lästa med pvesh get /nodes/localhost/storage/<id>/content. Korten överst visar antal VM:ar och LXC samt total storlek; nedanför visas en rad per arkiv med typ, lagring, storlek och datum. Återställning sker i Proxmox webbgränssnitt.",
     "notificationsTitle": "Aviseringar",
-    "notificationsBody1": "Alla aviseringar som Monitor skickar: Telegram, Discord, e-post, Gotify, ntfy, Slack, Teams och webhook. Varje rad visar tidsstämpel, kanal, händelsetyp, allvarlighetsgrad, genererad rubrik och text samt, om AI är aktiverat, ett reglage för att jämföra AI-versionen med originalet.",
-    "notificationsBody2": "Använd fliken för att kontrollera att en kanal levererar aviseringar och jämföra AI-versionen med grundmallen. Kanaler ställs in på den detaljerade sidan Aviseringar."
+    "notificationsBody1": "Proxmox aviseringsaktivitet: journalrader från Proxmox-tjänster som nämner aviseringar, e-post, webhooks eller larm, målen i /etc/pve/notifications.cfg samt säkerhetskopieringsuppgifter från klustrets uppgiftslista. Rader för säkerhetskopieringsuppgifter kan ladda ner hela uppgiftsloggen. Historiken över vad Monitor själv har skickat finns under Inställningar → Aviseringar → Aviseringshistorik.",
+    "notificationsBody2": "Använd fliken för att kontrollera vad Proxmox egna aviseringssystem gör. Kanalinställningar för Monitor finns på den detaljerade sidan Aviseringar."
   },
   "dataCollected": {
     "heading": "Hur data samlas in",
@@ -64,7 +64,7 @@
       {
         "subtab": "Loggar (livefilter)",
         "endpoint": "/api/logs",
-        "source": "journalctl --output json --since <range> med filter för allvarlighetsgrad, enhet och sökning på servern."
+        "source": "Fliken hämtar /api/logs?since_days=N; filter för nivå, tjänst och text tillämpas i webbläsaren. API:et tar även emot priority och service, som fliken ännu inte använder."
       },
       {
         "subtab": "Ladda ner",
@@ -74,17 +74,17 @@
       {
         "subtab": "Säkerhetskopieringar",
         "endpoint": "/api/backups",
-        "source": "PVE-uppgiftshistorik filtrerad efter vzdump, PBS-överföringar, Garbage Collect och Verify."
+        "source": "Säkerhetskopieringsarkiv listade från varje lagring för säkerhetskopior med pvesh get /nodes/localhost/storage/<id>/content."
       },
       {
-        "subtab": "Detaljer för en säkerhetskopieringsuppgift",
+        "subtab": "Uppgiftslogg för säkerhetskopiering (Aviseringar)",
         "endpoint": "/api/task-log/<upid>",
         "source": "Hela uppgiftsloggen läses som oformaterad text från /var/log/pve/tasks/<index>/<upid>."
       },
       {
-        "subtab": "Aviseringshistorik",
-        "endpoint": "/api/notifications/history",
-        "source": "SQLite-tabellen notification_history fylls av sändningsslingan."
+        "subtab": "Aviseringar",
+        "endpoint": "/api/notifications",
+        "source": "journalctl --grep över Proxmox-enheter, /etc/pve/notifications.cfg och säkerhetskopieringsuppgifter från pvesh get /cluster/tasks."
       }
     ],
     "apiIntro": "Både livefiltret och nedladdningarna kan också nås via API:",
diff --git a/web/messages/sv/docs/monitor/dashboard/system-overview.json b/web/messages/sv/docs/monitor/dashboard/system-overview.json
index 6cd42fb9..4ed88114 100644
--- a/web/messages/sv/docs/monitor/dashboard/system-overview.json
+++ b/web/messages/sv/docs/monitor/dashboard/system-overview.json
@@ -46,7 +46,7 @@
     "thresholdsIntro": "Ringar, staplar och minidiagram använder samma skala:  grön under Varning,  bärnstensfärgad mellan Varning och Kritisk,  röd från Kritisk och uppåt. Rekommenderade standardvärden i ProxMenux:",
     "thresholdsItems": [
       "CPU-användning — varning 85 %, kritisk 95 %.",
-      "Minne — varning 85 %, kritisk 95 %. Swap utlöser också Kritisk vid 5 % användning; en välfungerande Proxmox-värd bör sällan behöva använda swap.",
+      "Minne: varning 85 %, kritisk 95 %. Swap utlöser Kritisk endast när swapanvändningen är över 80 % och tillgängligt RAM är under 15 %.",
       "CPU-temperatur — varning 80 °C, kritisk 90 °C."
     ],
     "thresholdsOutro": "Alla värden kan ställas in per värd. Inställningar → Tröskelvärden för hälsokontrollen är den centrala referensen och förklarar hur de anpassas.",
@@ -58,7 +58,7 @@
     "intro": "Korten CPU-användning och Minne öppnar en sorterbar lista över de 25 största processerna: efter CPU-användning från CPU-kortet eller resident minne från minneskortet.",
     "listTitle": "Listdialogrutan",
     "listItems": [
-      "Automatisk uppdatering — listan uppdateras var 5:e sekund medan dialogrutan är öppen och slutar hämta data när den stängs.",
+      "Automatisk uppdatering — listan uppdateras var 3:e sekund medan dialogrutan är öppen och slutar hämta data när den stängs.",
       "Filter — sökfältet begränsar listan efter kommando, användare eller PID.",
       "Staplar i raderna — huvudkolumnens stapel skalas mot det högsta värdet i den filtrerade listan. Rangordningen förblir tydlig även när ingen process är nära 100 %.",
       "Mobil layout — under 640 px döljs PID och Användare, så att Kommando, CPU % och Minne får plats utan horisontell rullning."
@@ -79,7 +79,7 @@
   },
   "middle": {
     "heading": "Mitten: diagram med nodens mätvärden",
-    "body1": "Under korten visar NodeMetricsCharts historik för CPU, minne och disk-I/O från Proxmox RRD-lager via /api/node/metrics. Välj 1 timme / 24 timmar / 7 dagar / 30 dagar / 1 år. Upplösningen sänks för längre perioder så att diagrammet förblir följsamt.",
+    "body1": "Under korten visar NodeMetricsCharts historik för CPU, minne och disk-I/O från Proxmox RRD-lager via /api/node/metrics. Välj 1 timme / 24 timmar / 7 dagar / 30 dagar. Upplösningen sänks för längre perioder så att diagrammet förblir följsamt.",
     "body2": "Samma diagram som Proxmox webbgränssnitt visar för en nod, infogade i Monitors mörka tema och placerade i linje med övriga paneler."
   },
   "bottom": {
diff --git a/web/messages/sv/docs/monitor/dashboard/vms-lxcs.json b/web/messages/sv/docs/monitor/dashboard/vms-lxcs.json
index f71d6a3c..028f6b8f 100644
--- a/web/messages/sv/docs/monitor/dashboard/vms-lxcs.json
+++ b/web/messages/sv/docs/monitor/dashboard/vms-lxcs.json
@@ -117,7 +117,7 @@
       "Destination — lagring (PBS-Cloud, PBS-Local, NFS-Backup, …), färgad efter status.",
       "Storlek — kopians slutliga storlek på disk."
     ],
-    "backupsOutro": "+ Skapa säkerhetskopia startar en kopiering på lagringen markerad ”Backup target” i Proxmox. Återställning görs i Proxmox webbgränssnitt; Monitor visar gästens senaste kopior och kan skapa nya.",
+    "backupsOutro": "Knappen + Skapa säkerhetskopia uppe till höger öppnar en dialog där du väljer lagring, läge (snapshot / suspend / stop), skydd och anteckningar innan en vzdump startas. Återställning görs i Proxmox webbgränssnitt; Monitor visar vyn ”har den här gästen säkerhetskopierats nyligen?”, inte återställningsflödet.",
     "firewallTitle": "Tab 7 - Brandvägg",
     "firewallIntro": "Läser gästens Proxmox-brandväggslogg direkt från värden utan extra tjänst eller periodiska hämtningar. Fliken finns alltid; innehållet beror på om brandväggen är aktiv och någon regel loggar paket.",
     "firewallItems": [
diff --git a/web/messages/sv/docs/monitor/health-monitor.json b/web/messages/sv/docs/monitor/health-monitor.json
index ae3a030e..589afaf7 100644
--- a/web/messages/sv/docs/monitor/health-monitor.json
+++ b/web/messages/sv/docs/monitor/health-monitor.json
@@ -153,8 +153,8 @@
     "pillTitle": "Indikatorn följer den allvarligaste kategorin",
     "pillBody": "Sidhuvudets färg följer högsta allvarlighetsgrad: CRITICAL → rött, annars WARNING → gult, annars INFO → blått, annars grönt. Samma logik styr punkten i faviconen och PWA-märket.",
     "updateNowTitle": "Uppdatera nu knappen i Översiktsrubriken",
-    "updateNowBody1": "När Systemuppdateringar visar väntande uppdateringar visas Uppdatera nu bredvid statusindikatorn. Fönstret kör samma värdflöde som ProxMenux → Settings post-install Proxmox → Proxmox System Update: rensning av paketkällor, apt full-upgrade via update-pve-safe.sh, DKMS-ombyggnad av hanterade drivrutiner vid ny kärna, autoremove/autoclean och fråga om omstart när kärnan ändrats. Förloppet visas löpande.",
-    "updateNowBody2": "När körningen är klar anropar stängningen GET /api/health/details?refresh=1 och ogiltigförklarar endpointens cache. Väntande uppdateringar och allvarlighetsgrad räknas om från det nya paketläget, så märket försvinner utan att invänta nästa cykel. På en uppdaterad värd är knappen dold."
+    "updateNowBody1": "När Systemuppdateringar visar väntande uppdateringar visas Uppdatera nu bredvid statusindikatorn. Fönstret kör samma värdflöde som ProxMenux → Verktyg och verktyg → Proxmox systemuppdatering: rensning av paketkällor, apt full-upgrade via update-pve-safe.sh, DKMS-ombyggnad av hanterade drivrutiner vid ny kärna, autoremove/autoclean och fråga om omstart när kärnan ändrats. Förloppet visas löpande.",
+    "updateNowBody2": "När körningen är klar anropar stängningen GET /api/health/full?refresh=1 och ogiltigförklarar endpointens cache. Väntande uppdateringar och allvarlighetsgrad räknas om från det nya paketläget, så märket försvinner utan att invänta nästa cykel. På en uppdaterad värd är knappen dold."
   },
   "dismiss": {
     "heading": "Avfärda larm och ange undertryckningstid",
diff --git a/web/messages/sv/docs/monitor/index.json b/web/messages/sv/docs/monitor/index.json
index fa2eab2f..f7b8e165 100644
--- a/web/messages/sv/docs/monitor/index.json
+++ b/web/messages/sv/docs/monitor/index.json
@@ -113,13 +113,13 @@
   "api": {
     "heading": "REST API och integrationer",
     "intro": "REST-endpointar ger övervakningsdata som JSON över HTTP/HTTPS för Homepage, Home Assistant, Uptime Kuma och skript. Grafana kan använda den separata Prometheus-textexportören på /api/prometheus. Anrop kan göras med curl.",
-    "tokens": "Långlivade API-token (365 dagar) skapas under Inställningar → API-åtkomsttoken eller via POST /api/auth/generate-api-token.",
+    "tokens": "Långlivade API-token (365 dagar) skapas under Administration → Säkerhet → API-token eller via POST /api/auth/generate-api-token.",
     "bearer": "Skicka token som Authorization: Bearer …. Även /api/health förväntar sig Bearer-headern, så tillgänglighetskontroller behöver en read_only-token. Under /api/auth/* finns både offentliga inställnings-/inloggnings-/statusrutter och skyddade rutter för konto-/tokenhantering; alla är inte anonyma.",
     "catalog": "Den fullständiga slutpunktskatalogen, vägledning för tokenrotation och bästa säkerhetspraxis finns i API Referens; Färdiga exempel för Homepage, Home Assistant, Grafana, Uptime Kuma och ett generiskt cURL-mönster finns i Integrationer."
   },
   "serviceControl": {
     "heading": "Tjänstekontroll",
-    "intro": "Monitor hanteras som andra systemd-tjänster. Det finns även två poster i ProxMenux textgränssnitt under Inställningar:",
+    "intro": "Monitor hanteras som andra systemd-tjänster. Det finns även tre poster i ProxMenux textgränssnitt under Inställningar:",
     "codeComment": "# Manuell kontroll",
     "footer": "Se Inställningar → ProxMenux Monitor för växling i menyn och statusverifieringsflöde."
   },
diff --git a/web/messages/sv/docs/monitor/integrations.json b/web/messages/sv/docs/monitor/integrations.json
index 60d5ee3b..cc4354ce 100644
--- a/web/messages/sv/docs/monitor/integrations.json
+++ b/web/messages/sv/docs/monitor/integrations.json
@@ -20,13 +20,13 @@
     "heading": "Autentisering: det enda varje recept behöver",
     "intro": "De flesta endpoints som används av dessa integrationer är autentiserade. Du har två sätt att tillfredsställa detta krav.",
     "optAtitle": "Alternativ A – API token (rekommenderas för integrationer)",
-    "optAbody1": "Öppna Inställningar → Säkerhet → API-token. Välj Skapa token, ge den ett namn som homepage, home-assistant eller prometheus och kopiera den: den visas bara en gång. Den gäller ett år som standard och kan återkallas separat. Använd den för klienter utanför webbläsaren.",
+    "optAbody1": "Öppna Administration → Säkerhet → API-tokens. Välj Generera ny token, ge den ett namn som homepage, home-assistant eller prometheus, ange ditt lösenord (och 2FA-koden om den är på), välj sedan Generera token och kopiera den: den visas bara en gång. Den gäller ett år som standard och kan återkallas separat. Använd den för klienter utanför webbläsaren.",
     "optAbody2": "Från den punkten är varje begäran bara:",
     "optBtitle": "Alternativ B – inloggningsflöde (användarnamn + lösenord)",
     "optBbody": "Användbart för skript som autentiserar som en mänsklig användare. Den returnerade token är kortlivad; de flesta integrationer bör föredra Alternativ A.",
     "outro": "TOTP-fältet behövs bara med tvåfaktorsautentisering påslagen. Rotation och återkallande av token, lösenordsregler och granskningslogg beskrivs i Åtkomst och autentisering.",
     "httpsTitle": "Använda HTTPS istället för HTTP",
-    "httpsIntro": "Exemplen använder http:// för korthet. Om TLS är påslaget i Monitor (Inställningar → Säkerhet → SSL/HTTPS), byt http:// mot https:// i varje URL. Två verktygsspecifika anmärkningar:",
+    "httpsIntro": "Exemplen använder http:// för korthet. Om TLS är påslaget i Monitor (Administration → Säkerhet → SSL / HTTPS), byt http:// mot https:// i varje URL. Två verktygsspecifika anmärkningar:",
     "httpsItems": [
       "Självsignerade certifikat. Home Assistants rest:-integration verifierar TLS som standard. Med Monitors självsignerade certifikat kan du lägga till verify_ssl: false i varje REST-block bredvid scan_interval: eller importera dess CA i betrodda certifikat. Detsamma gäller andra verktyg som verifierar certifikat.",
       "Prometheus har redan scheme: https i exemplet nedan. Aktivera eller behåll https när TLS är påslaget i Monitor."
@@ -43,7 +43,7 @@
     "basicTitle": "Grundläggande widget - ingen autentisering",
     "basicIntro": "Använd detta när ProxMenux Monitor är på ett pålitligt nätverk och du har inte aktiverat autentisering på Monitor sidan ännu. Den enklaste möjliga services.yaml-posten:",
     "authedTitle": "Autentiserad widget",
-    "authedIntro": "Skapa en API-token under Inställningar → Säkerhet → API-token i Monitor. Kopiera den till headern Authorization nedan och ersätt exempeltoken efter Bearer.",
+    "authedIntro": "Skapa en API-token under Administration → Säkerhet → API-token i Monitor. Kopiera den till headern Authorization nedan och ersätt exempeltoken efter Bearer.",
     "authedOutro": "Starta om Homepage så visar kortet aktuella värden. Återanvänd token i widgetar som läser samma Monitor-instans.",
     "multiTitle": "Multi-widget setup – system, lagring, nätverk",
     "multiIntro": "För fler detaljer skapar du tre kort med olika endpoints: system, lagring och nätverk. Använd samma token för denna Monitor-instans.",
diff --git a/web/messages/sv/docs/monitor/notifications.json b/web/messages/sv/docs/monitor/notifications.json
index 1f20b3f6..a72c37ca 100644
--- a/web/messages/sv/docs/monitor/notifications.json
+++ b/web/messages/sv/docs/monitor/notifications.json
@@ -314,7 +314,7 @@
   },
   "aiRewrite": {
     "heading": "Valfri AI-omskrivning",
-    "body1": "Lämpliga händelser kan skrivas om av en språkmodell före leverans. Säkerhetskopieringsresultaten backup_complete och backup_fail undantas. AI är avstängt som standard och körs i sändningstråden när det aktiveras. Vid fel eller tidsgräns används malltexten.",
+    "body1": "Lämpliga händelser kan skrivas om av en språkmodell före leverans. Säkerhetskopieringsresultaten backup_complete och backup_fail skrivs inte om; med AI-förslag påslaget lägger AI bara till en förklaring när en säkerhetskopiering rapporterar fel. AI är avstängt som standard och körs i sändningstråden när det aktiveras. Vid fel eller tidsgräns används malltexten.",
     "body2": "Sex leverantörer stöds: OpenAI, Anthropic, Google Gemini, Groq, OpenRouter och lokal Ollama. Per kanal väljs detaljnivå (brief, standard, detailed), språk, instruktionsläge (default eller custom) och egna instruktioner. Sidan AI-assistent visar konfiguration, bilder och exempel.",
     "privacyTitle": "Integritetsinformation",
     "privacyBody": "AI-anrop kan innehålla titel, allvarlighetsgrad, meddelandetext, systeminstruktioner, egna instruktioner och insamlad kontext med värdnamn, IP-adresser, användarnamn, fel och journalrader. De skickas till vald server. Ollama är lokalt endast med både lokal server och lokal modell; en fjärrserver eller molnmodell skickar data utanför värden. Stäng av omskrivning eller använd en kontrollerad lokal server och modell om innehållet måste stanna i nätverket."
diff --git a/web/messages/sv/docs/security/ssl-letsencrypt.json b/web/messages/sv/docs/security/ssl-letsencrypt.json
index 7d11d70d..267a00ec 100644
--- a/web/messages/sv/docs/security/ssl-letsencrypt.json
+++ b/web/messages/sv/docs/security/ssl-letsencrypt.json
@@ -12,13 +12,13 @@
   },
   "intro": {
     "title": "Vad denna sida täcker",
-    "body": "Hur man byter Monitor från HTTP till HTTPS i Inställningarna → Säkerhetspanel, vad varje certifikatkälla faktiskt pekar på på disken, hur Proxmox-alternativet beter sig när värden har en Let's Encrypt-cert och hur man ställer in anpassade vägar om du hanterar certer utanför Proxmox."
+    "body": "Hur man byter Monitor från HTTP till HTTPS i panelen Administration → Säkerhet, vad varje certifikatkälla faktiskt pekar på på disken, hur Proxmox-alternativet beter sig när värden har en Let's Encrypt-cert och hur man ställer in anpassade vägar om du hanterar certer utanför Proxmox."
   },
   "wheresetting": {
     "heading": "Var inställningen finns",
-    "body": "Öppna Monitor och gå till Inställningar → Säkerhet → HTTPS / SSL. Panelen visar HTTP eller HTTPS och, om HTTPS inte aktiverats, de upptäckta certifikatkällorna.",
-    "imageAlt": "Inställningar → Säkerhet → HTTPS / SSL-panelen i Monitorn som visar HTTPS-off-tillståndet, det automatiska upptäckta Proxmox-certifikatet (ämne, emittent, utgång) och alternativet Använd anpassat certifikat",
-    "caption": "Panelen Inställningar → Säkerhet → HTTPS / SSL före aktivering. Monitor visar certifikatet som Proxmox använder och låter dig välja det med ett klick."
+    "body": "Öppna Monitor och gå till Administration → Säkerhet → SSL / HTTPS. Panelen visar HTTP eller HTTPS och, om HTTPS inte aktiverats, de upptäckta certifikatkällorna.",
+    "imageAlt": "Panelen Administration → Säkerhet → SSL / HTTPS i Monitorn som visar HTTPS-off-tillståndet, det automatiska upptäckta Proxmox-certifikatet (ämne, emittent, utgång) och alternativet Använd anpassat certifikat",
+    "caption": "Panelen Administration → Säkerhet → SSL / HTTPS före aktivering. Monitor visar certifikatet som Proxmox använder och låter dig välja det med ett klick."
   },
   "twoways": {
     "heading": "Två sätt att aktivera HTTPS",
@@ -117,7 +117,7 @@
   },
   "switchToHttps": {
     "heading": "Byt Monitor till HTTPS",
-    "bodyRich": "När /etc/pve/local/pveproxy-ssl.pem signerats av Let's Encrypt öppnar du Inställningar → Säkerhet → HTTPS / SSL, kontrollerar att utfärdaren är Let's Encrypt och inte Proxmox lokala CA och klickar på Använd Proxmox-certifikat. Monitor startar om och nästa sidladdning använder HTTPS på 8008 med en offentligt betrodd certifikatkedja. Senare ACME-förnyelser valideras och väljs vid nästa nya TLS-anslutning, utan återkommande avfrågning eller omstart vid förnyelse. Uppdatera certifikat finns kvar under Säkerhet för diagnos och återställning."
+    "bodyRich": "När /etc/pve/local/pveproxy-ssl.pem signerats av Let's Encrypt öppnar du Administration → Säkerhet → SSL / HTTPS, kontrollerar att utfärdaren är Let's Encrypt och inte Proxmox lokala CA och klickar på Använd Proxmox-certifikat. Monitor startar om och nästa sidladdning använder HTTPS på 8008 med en offentligt betrodd certifikatkedja. Senare ACME-förnyelser valideras och väljs vid nästa nya TLS-anslutning, utan återkommande avfrågning eller omstart vid förnyelse. Uppdatera certifikat finns kvar under Säkerhet för diagnos och återställning."
   },
   "custom": {
     "heading": "Anpassat certifikat - när du ska använda det",
diff --git a/web/messages/sv/docs/settings/beta-program.json b/web/messages/sv/docs/settings/beta-program.json
index ed5b438f..e269f9b0 100644
--- a/web/messages/sv/docs/settings/beta-program.json
+++ b/web/messages/sv/docs/settings/beta-program.json
@@ -51,7 +51,7 @@
     "intro": "Båda riktningarna går igenom samma installationsprogram – de är inte specialcase avinstallations- + ominstallationsflöden. Varje riktning kör helt enkelt kanalens eget installationsprogram, som är designat för att vara idempotent och säkert att köra om över en befintlig installation. Praktiska konsekvenser:",
     "items": [
       "Din befintliga konfig, ProxMenux Monitor inloggning (auth.json), meddelandekanaler, post-install registret, anpassade trösklar - ingen av dem berörs. Kanalbrytaren byter bara skriptträdet och den binära AppImage.",
-      "En Monitor som var aktiv före bytet är aktiv även efteråt; en inaktiverad Monitor förblir inaktiverad.",
+      "Installationsprogrammet startar om Monitor-tjänsten under bytet, så en Monitor som du hade inaktiverat körs igen. Inaktivera den igen i Inställningar vid behov.",
       "ProxMenux Monitors auth.json, API-token och JWT-hemlighet bevaras vid kanalbyten. Sessionerna loggas inte ut.",
       "Du kan vända kanaler så många gånger du vill - dialogrutan accepterar det varje gång och kör installationsprogrammet. Ingen omstart av Proxmox krävs.",
       "Kanalbytet ersätter ProxMenux-skripten och kan inte köras i Monitors webbläsarterminal: PTY:n skulle stängas mitt i uppdateringen och lämna dialogen avbruten. Där visas i stället ett informationsfönster med det officiella enradsinstallationskommandot för målkanalen. Kör det via SSH eller Proxmox-värdens konsol."
diff --git a/web/messages/sv/docs/settings/index.json b/web/messages/sv/docs/settings/index.json
index 534b7d64..4473f416 100644
--- a/web/messages/sv/docs/settings/index.json
+++ b/web/messages/sv/docs/settings/index.json
@@ -60,6 +60,12 @@
         "description": "Växla mellan Stable (main) och Beta (develop) i båda riktningarna från en enda dialogruta. Kör den officiella installeraren för den valda kanalen; idempotent och alltid synlig.",
         "badge": "Alltid tillgänglig"
       },
+      {
+        "icon": "Languages",
+        "href": "/docs/settings/change-language",
+        "title": "Byt språk",
+        "description": "Byt ProxMenux gränssnitt mellan åtta språk. Valet sparas i config.json och menyn laddas om på det nya språket."
+      },
       {
         "icon": "Info",
         "href": "/docs/settings/show-version-information",
@@ -70,7 +76,7 @@
         "icon": "Trash2",
         "href": "/docs/settings/uninstall-proxmenux",
         "title": "Avinstallera ProxMenux",
-        "description": "Ta bort ProxMenux och (valfritt) dess beroenden. Återställer också /root/.bashrc och /etc/motd säkerhetskopior skapade vid installationstid."
+        "description": "Ta bort ProxMenux och (valfritt) dess beroenden. Återställer även säkerhetskopiorna av /root/.bashrc och /etc/motd som skapades av efterinstallationen."
       }
     ]
   },
diff --git a/web/messages/sv/docs/settings/proxmenux-monitor.json b/web/messages/sv/docs/settings/proxmenux-monitor.json
index 1576e8a7..ed7c6615 100644
--- a/web/messages/sv/docs/settings/proxmenux-monitor.json
+++ b/web/messages/sv/docs/settings/proxmenux-monitor.json
@@ -33,7 +33,7 @@
   },
   "warnConditional": {
     "title": "Inställningar endast synliga när Monitor är installerad",
-    "body": "De två menyposterna som beskrivs nedan visas endast i Inställningar om proxmenux-monitor.service är registrerad hos systemd. Om du inte ser dem installerades inte Monitor (det är valfritt under ProxMenux-installation). För att lägga till det senare, installera om ProxMenux med den senaste versionen och välj alternativet Monitor."
+    "body": "De tre menyposterna som beskrivs nedan visas endast i Inställningar om proxmenux-monitor.service är registrerad hos systemd. Installationsprogrammet sätter alltid upp den, så om du inte ser dem misslyckades Monitor-steget under installationen. Kör det officiella installationsprogrammet igen för att försöka på nytt."
   },
   "toggle": {
     "heading": "Aktivera / Deaktivera",
@@ -92,8 +92,8 @@
   },
   "troubleshoot": {
     "heading": "Felsökning",
-    "missingTitle": "Båda menyalternativen saknas",
-    "missingBody": "Monitorn är inte registrerad hos systemd. Bekräfta med systemctl list-unit-files | grep proxmenux-monitor - om det inte finns någon utgång, installera om ProxMenux från den officiella installatören med den senaste versionen och välj alternativet Monitor under installationen.",
+    "missingTitle": "Monitor-menyalternativen saknas",
+    "missingBody": "Monitorn är inte registrerad hos systemd. Bekräfta med systemctl list-unit-files | grep proxmenux-monitor - om det inte finns någon utgång, kör den officiella installatören igen; den installerar Monitor vid varje körning.",
     "unreachableTitle": "Tjänsten är aktiverad men webbadressen kan inte nås",
     "unreachableBody": "Statusutskriften i Inställningar visar IP-adressen. Kontrollera:",
     "unreachableCmd": "ss -tlnp | grep 8008                # is anything listening?\njournalctl -u proxmenux-monitor -n 100 --no-pager  # recent errors\ncurl -sI http://localhost:8008      # local reachability",
diff --git a/web/messages/sv/docs/settings/uninstall-proxmenux.json b/web/messages/sv/docs/settings/uninstall-proxmenux.json
index 803f9faa..4d230cd5 100644
--- a/web/messages/sv/docs/settings/uninstall-proxmenux.json
+++ b/web/messages/sv/docs/settings/uninstall-proxmenux.json
@@ -12,7 +12,7 @@
   },
   "scopeWarn": {
     "title": "Påverkar ProxMenux-installationen bara - inte dina VM / CT / lagringsutrymme",
-    "body": "Avinstallationen tar bort ProxMenux-skripten, menystartaren, den valfria Monitor-tjänsten och säkerhetskopior av systemfiler som ProxMenux skapade vid installationen. Den ändrar inte dina virtuella maskiner, containrar, lagring, nätverkskonfiguration, Fail2Ban, NVIDIA-drivrutiner eller annat som du installerat via ProxMenux. De förblir som de är."
+    "body": "Avinstallationen tar bort ProxMenux-skripten, menystartaren och Monitor-tjänsten, och återställer säkerhetskopiorna av /root/.bashrc och /etc/motd som efterinstallationen tog, om de finns. Den ändrar inte dina virtuella maskiner, containrar, lagring, nätverkskonfiguration, Fail2Ban, NVIDIA-drivrutiner eller annat som du installerat via ProxMenux. De förblir som de är."
   },
   "flow": {
     "heading": "Avinstallationsförloppet",
diff --git a/web/messages/sv/docs/utils/export-vm.json b/web/messages/sv/docs/utils/export-vm.json
index fca9883b..d05da20e 100644
--- a/web/messages/sv/docs/utils/export-vm.json
+++ b/web/messages/sv/docs/utils/export-vm.json
@@ -59,8 +59,8 @@
   },
   "package": {
     "heading": "Vad som hamnar i paketet",
-    "intro": "För en OVA / OVF som heter myvm-100-2026-04-26_18-30-15:",
-    "code": "# OVA mode (single .ova file is a TAR of):\nmyvm-100-2026-04-26_18-30-15.ovf      # XML descriptor (DMTF OVF 1.0 + RASD + VSSD)\nmyvm-100-2026-04-26_18-30-15.mf       # SHA1 manifest\nmyvm-100-2026-04-26_18-30-15-disk0.vmdk\nmyvm-100-2026-04-26_18-30-15-disk1.vmdk\n...\n\n# OVF mode (a directory):\nmyvm-100-2026-04-26_18-30-15-ovf/\n├── myvm-100-2026-04-26_18-30-15.ovf\n├── myvm-100-2026-04-26_18-30-15.mf\n├── myvm-100-2026-04-26_18-30-15-disk0.vmdk\n└── myvm-100-2026-04-26_18-30-15-disk1.vmdk"
+    "intro": "För en OVA / OVF som heter myvm-100-20260426_183015:",
+    "code": "# OVA mode (single .ova file is a TAR of):\nmyvm-100-20260426_183015.ovf      # XML descriptor (DMTF OVF 1.0 + RASD + VSSD)\nmyvm-100-20260426_183015.mf       # SHA1 manifest\nmyvm-100-20260426_183015-disk1.vmdk\nmyvm-100-20260426_183015-disk2.vmdk\n...\n\n# OVF mode (a directory):\nmyvm-100-20260426_183015-ovf/\n├── myvm-100-20260426_183015.ovf\n├── myvm-100-20260426_183015.mf\n├── myvm-100-20260426_183015-disk1.vmdk\n└── myvm-100-20260426_183015-disk2.vmdk"
   },
   "exported": {
     "heading": "Vad blir exporterat",
@@ -90,7 +90,7 @@
   "manifest": {
     "heading": "SHA1-manifest",
     "intro": "Varje paket innehåller en .mf-fil med SHA1-hashar för OVF-beskrivningsfilen och varje VMDK:",
-    "code": "SHA1(myvm-100-2026-04-26_18-30-15.ovf)= 3a7bd3e2360a3d29eea436fcfb7e44c735d117c9\nSHA1(myvm-100-2026-04-26_18-30-15-disk0.vmdk)= b7e23ec29af22b0b4e41da31e868d57226121c84\nSHA1(myvm-100-2026-04-26_18-30-15-disk1.vmdk)= a87ff679a2f3e71d9181a67b7542122c",
+    "code": "SHA1(myvm-100-20260426_183015.ovf)= 3a7bd3e2360a3d29eea436fcfb7e44c735d117c9\nSHA1(myvm-100-20260426_183015-disk1.vmdk)= b7e23ec29af22b0b4e41da31e868d57226121c84\nSHA1(myvm-100-20260426_183015-disk2.vmdk)= a87ff679a2f3e71d9181a67b7542122c",
     "outro": "Manifestet är valfritt i OVF-specifikationen och många program ignorerar det, men det kan användas för att kontrollera att paketet överfördes oskadat. Verifiera manuellt med sha1sum -c file.mf."
   },
   "import": {
diff --git a/web/messages/sv/docs/utils/index.json b/web/messages/sv/docs/utils/index.json
index a8bb8b57..1e07c193 100644
--- a/web/messages/sv/docs/utils/index.json
+++ b/web/messages/sv/docs/utils/index.json
@@ -38,7 +38,7 @@
       "body": "Håll värden frisk: CLI-verktyg, regelbundna uppdateringar och storversionsuppgraderingen när det är dags.",
       "bullets": [
         "Installationsprogram för systemverktyg (25 CLI-verktyg)",
-        "Proxmox systemuppdatering (apt + dist-upgrade)",
+        "Proxmox systemuppdatering (apt + full-upgrade)",
         "Uppgradering av huvudversion PVE 8 → PVE 9"
       ]
     },
@@ -58,11 +58,11 @@
   },
   "isoSection": {
     "heading": "Skapa ISO-filer",
-    "body": "Skapa en ny och aktuell Windows-installations-ISO direkt på Proxmox-värden. Skriptet automatiserar hela UUP Dump-flödet: installation av beroenden (aria2c, cabextract, wimlib-imagex, …), parallell nedladdning av uppdateringsfiler med aria2 och konvertering till en enda startbar ISO. Den sparas i /var/lib/vz/template/iso och är redo att anslutas till en VM.",
+    "body": "Skapa en ny och aktuell Windows-installations-ISO direkt på Proxmox-värden. Skriptet automatiserar hela UUP Dump-flödet: installation av beroenden (aria2c, cabextract, wimlib-imagex, …), parallell nedladdning av uppdateringsfiler med aria2 och konvertering till en enda startbar ISO. Den sparas i den identifierade Proxmox ISO-lagringen (reserv: /var/lib/vz/template/iso) och är redo att anslutas till en VM.",
     "options": [
       {
         "title": "UUP Dump ISO-skapare",
-        "description": "Skapa en aktuell Windows-installations-ISO från en delad UUP Dump-länk. Beroenden, nedladdning och konvertering hanteras helt automatiskt. Den färdiga ISO-filen sparas i /var/lib/vz/template/iso, redo för att skapa en VM.",
+        "description": "Skapa en aktuell Windows-installations-ISO från en delad UUP Dump-länk. Beroenden, nedladdning och konvertering hanteras helt automatiskt. Den färdiga ISO-filen sparas i den identifierade Proxmox ISO-lagringen, redo för att skapa en VM.",
         "href": "/docs/utils/UUp-Dump-ISO-Creator"
       }
     ]
@@ -78,7 +78,7 @@
       },
       {
         "title": "Proxmox systemuppdatering",
-        "description": "Kontroll av paketförråd + apt update + apt dist-upgrade med bekräftelse före omstart. Identifierar PVE-huvudversionen (8 eller 9), behåller konfigurerade paketförråd och frågar innan en värd utan abonnemang byter till no-subscription-förrådet. Därefter körs autoremove.",
+        "description": "Kontroll av paketförråd + apt update + apt full-upgrade med bekräftelse före omstart. Identifierar PVE-huvudversionen (8 eller 9), behåller konfigurerade paketförråd och frågar innan en värd utan abonnemang byter till no-subscription-förrådet. Därefter körs autoremove.",
         "href": "/docs/utils/system-update"
       },
       {
diff --git a/web/messages/sv/docs/utils/system-update.json b/web/messages/sv/docs/utils/system-update.json
index c91ebed9..f284f8ea 100644
--- a/web/messages/sv/docs/utils/system-update.json
+++ b/web/messages/sv/docs/utils/system-update.json
@@ -128,7 +128,7 @@
         "body": "Enterprise-förrådet är aktiverat utan ett aktivt abonnemang. Före apt-get update erbjuder arbetsskriptet ett byte till no-subscription. Om du avböjde kan du köra uppdateringen igen och acceptera, eller inaktivera Enterprise i Node → Updates → Repositories och aktivera no-subscription."
       },
       {
-        "title": "dist-upgrade fastnar vid \"Configuring grub-pc\"",
+        "title": "full-upgrade fastnar vid \"Configuring grub-pc\"",
         "body": "Om en interaktiv GRUB-fråga visas frågar den vilka enheter som ska få starthanteraren. Alternativet --force-confold gäller konfigurationsfiler, inte det här enhetsvalet. Välj lämpliga startdiskar med Tab + Space och sedan OK. Du kan konfigurera valet i förväg med dpkg-reconfigure grub-pc."
       },
       {
diff --git a/web/messages/sv/docs/utils/system-utils.json b/web/messages/sv/docs/utils/system-utils.json
index 8c70bd88..72096881 100644
--- a/web/messages/sv/docs/utils/system-utils.json
+++ b/web/messages/sv/docs/utils/system-utils.json
@@ -235,7 +235,7 @@
       {
         "href": "/docs/utils/system-update",
         "label": "Proxmox systemuppdatering",
-        "tailRich": " — för den faktiska apt dist-upgrade på värden."
+        "tailRich": " — för den faktiska apt full-upgrade på värden."
       },
       {
         "href": "/docs/network/monitoring",
diff --git a/web/messages/sv/docs/utils/upgrade-pve8-pve9.json b/web/messages/sv/docs/utils/upgrade-pve8-pve9.json
index 7b366c50..d783e185 100644
--- a/web/messages/sv/docs/utils/upgrade-pve8-pve9.json
+++ b/web/messages/sv/docs/utils/upgrade-pve8-pve9.json
@@ -16,9 +16,9 @@
     "items": [
       "Verifierade säkerhetskopior av varje VM och CT (PBS, vzdump, ZFS-ögonblicksbilder — kontrollera även att de går att återställa).",
       "Åtkomst via konsol / IPMI / iKVM till värden om SSH kopplas ned under uppgraderingen.",
-      "Värden ska redan vara uppdaterad till aktuell PVE 8.4.x, vilket skriptet kontrollerar.",
+      "Värden ska redan vara på den senaste PVE 8-patchnivån. Skriptet uppdaterar först PVE 8; det kontrollerar inte någon specifik 8.4.x-version.",
       "Ceph (om den finns) på version 19.x Squid.",
-      "Minst 5 GB ledigt i rotfilsystemet."
+      "Minst 5 GB ledigt i rotfilsystemet, som Proxmox-guiden rekommenderar. Skriptet själv kontrollerar bara 1 GB i /var/cache/apt/archives."
     ]
   },
   "modeMenu": {
@@ -63,13 +63,13 @@
   },
   "webTerminal": {
     "heading": "Varför webbterminalen blockeras",
-    "body": "Alla tre lägen upptäcker termproxy / vncshell i kedjan av överordnade processer och vägrar starta. Webbterminalen beror på pveproxy, en tjänst som uppdateras under förloppet. När pveproxy startas om avslutas terminalsessionen och skriptet i den. Värden kan då bli endast delvis uppgraderad.",
+    "body": "Lägena Automatic och Interactive upptäcker termproxy / vncshell i kedjan av överordnade processer och vägrar starta, om inte sessionen kommer via SSH eller PVE_ALLOW_WEBTERM=1 är satt. Förkontrollen och den manuella guiden ändrar ingenting och kan köras var som helst. Webbterminalen beror på pveproxy, en tjänst som uppdateras under förloppet. När pveproxy startas om avslutas terminalsessionen och skriptet i den. Värden kan då bli endast delvis uppgraderad.",
     "warningTitle": "Använd SSH eller fysisk konsol / IPMI / iKVM",
-    "warningBody": "SSH är det vanligaste valet. Kör det i tmux eller screen så att uppgraderingen klarar korta nätavbrott. Om skriptet upptäcker ett vanligt SSH-skal visas först en påminnelse om tmux/screen."
+    "warningBody": "SSH är det vanligaste valet. Kör det i tmux eller screen så att uppgraderingen klarar korta nätavbrott. Bekräftelsedialogen för uppgraderingen påminner dig om att köra den från konsolen eller från SSH i tmux/screen."
   },
   "auto": {
     "heading": "Automatiskt läge / Utan tillsyn",
-    "intro": "Frågor om paket och konfigurationsfiler använder standardvärdena i tabellen nedan, och ett 401 från Enterprise-källan byter till no-subscription utan att fråga. Bekräftelsen av uppgraderingen, sammanfattningen av PVE 8-uppdateringen, reparationer från förkontrollen och frågan om omstart visas fortfarande.",
+    "intro": "Frågor om paket och konfigurationsfiler använder standardvärdena i tabellen nedan. Bekräftelsen av uppgraderingen, sammanfattningen av PVE 8-uppdateringen, reparationer från förkontrollen och frågan om omstart visas fortfarande.",
     "behaviourHeading": "Beteende i automatiskt läge",
     "behaviourTable": {
       "settingHeader": "Inställning",
@@ -94,8 +94,8 @@
       ]
     },
     "flowHeading": "Förlopp i automatiskt läge",
-    "flowIntro": "Sex faser i följd. Fas 2 uppdaterar PVE 8-paketen och fas 3 skriver konfigurationen för PVE 9-paketkällorna. Ett fel stoppar förloppet men återställer inte ändringarna; om PVE 8-uppdateringen misslyckas eller avbryts lämnas paketkällorna orörda. Fas 5 kör dist-upgrade till PVE 9.",
-    "flowDiagram": "1. KONTROLLER\nBlockera termproxy / vncshell; bekräfta uppgraderingen; avvisa PVE 9.\n\n2. FÖRBERED PVE 8\nscripts/global/update-pve8.sh\nUppdatera PVE 8-paket; stoppa om uppdateringen misslyckas eller avbryts.\npve8to9 --full (kan erbjuda reparationer).\nKontrollera utrymme och anslutning; Ceph 19.x Squid på hyperkonvergerade noder.\n\n3. ÄNDRA PAKETKÄLLOR\nBookworm → Trixie; skriv deb822-filer .sources.\nKommentera bort gamla .list-filer; apt-get update.\n\n4. KONTROLLERA PVE 9-KANDIDATEN\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nStoppa om kandidaten inte är 9.x eller om proxmox-ve skulle tas bort.\n\n5. UPPGRADERA\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nUtan prenumeration: inaktivera pve-enterprise.sources.\nLogg: /var/log/pve8-a-pve9-.log\n\n6. KONTROLLERA OCH STARTA OM\nEFI: apt install grub-efi-amd64\npve8to9 --full; granska resultatet och frågan om omstart.",
+    "flowIntro": "Sex faser i följd. Fas 2 byter PVE 8-paketkällorna till no-subscription (Enterprise- och Ceph-listorna inaktiveras, /etc/apt/sources.list skrivs om) innan sammanfattningen av uppdateringen visas och uppdaterar sedan paketen; fas 3 skriver konfigurationen för PVE 9-paketkällorna. Ett fel stoppar förloppet men återställer inte ändringarna; om PVE 8-uppdateringen misslyckas eller avbryts startar inte PVE 9-migreringen, men ändringarna i PVE 8-paketkällorna finns kvar. Fas 5 kör dist-upgrade till PVE 9.",
+    "flowDiagram": "1. KONTROLLER\nBlockera termproxy / vncshell; bekräfta uppgraderingen; avvisa PVE 9.\n\n2. FÖRBERED PVE 8\nscripts/global/update-pve8.sh\nByt PVE 8-paketkällor till no-subscription och skriv om sources.list;\nuppdatera PVE 8-paket; stoppa om uppdateringen misslyckas eller avbryts.\npve8to9 --full (kan erbjuda reparationer).\nKontrollera utrymme och anslutning; Ceph 19.x Squid på hyperkonvergerade noder.\n\n3. ÄNDRA PAKETKÄLLOR\nBookworm → Trixie; skriv deb822-filer .sources.\nKommentera bort gamla .list-filer; apt-get update.\n\n4. KONTROLLERA PVE 9-KANDIDATEN\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nStoppa om kandidaten inte är 9.x eller om proxmox-ve skulle tas bort.\n\n5. UPPGRADERA\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nUtan prenumeration: inaktivera pve-enterprise.sources.\nLogg: /var/log/pve8-a-pve9-.log\n\n6. KONTROLLERA OCH STARTA OM\nEFI: apt install grub-efi-amd64\npve8to9 --full; granska resultatet och frågan om omstart.",
     "preflightHeading": "Förkontroll: vad som kontrolleras",
     "preflightTable": {
       "checkHeader": "Kontroll",
@@ -158,7 +158,7 @@
   },
   "manual": {
     "heading": "Manuellt uppgraderingsförfarande",
-    "intro": "Detta visar menyalternativet Manuell uppgraderingsguide: Proxmox officiella wiki för uppgradering från 8 till 9, uppdelad i fyra faser. Skriptet visar kommandona; du kör dem själv i ett separat root-skal. Varje steg är synligt.",
+    "intro": "Detta följer menyalternativet Manuell uppgraderingsguide, baserat på Proxmox officiella wiki för uppgradering från 8 till 9 och uppdelat i fyra faser. Tabellen med dpkg-svar lägger till wikins förbehåll för sshd_config och chrony.conf, som menyn visar som ett vanligt Y. Skriptet visar kommandona; du kör dem själv i ett separat root-skal. Ingen automatisering, full insyn.",
     "rootCalloutTitle": "Alla kommandon kräver root",
     "rootCalloutBody": "Använd ett root-skal för kommandona, även för omdirigeringar och here-dokument. Med sudo måste hela kommandot köras med root-behörighet.",
     "phase1Heading": "Fas 1 - Förberedelse",
@@ -230,7 +230,7 @@
   "clusterCeph": {
     "heading": "Kluster och Ceph",
     "clusterCalloutTitle": "Cluster: uppgradera noder en i taget",
-    "clusterCalloutBody": "Identifieras med pvecm status. Uppgradera inte flera noder samtidigt. Flytta gästerna, uppgradera noden, starta om och verifiera klustermedlemskapet innan du fortsätter. Under uppgraderingen fungerar livemigrering från äldre till nyare PVE-versioner; motsatt riktning stöds normalt inte. HA-grupper omvandlas automatiskt till HA-regler när alla noder kör PVE 9.",
+    "clusterCalloutBody": "Uppgradera inte flera noder samtidigt. Flytta gästerna, uppgradera noden, starta om och verifiera klustermedlemskapet innan du fortsätter. Under uppgraderingen fungerar livemigrering från äldre till nyare PVE-versioner; motsatt riktning stöds normalt inte. HA-grupper omvandlas automatiskt till HA-regler när alla noder kör PVE 9.",
     "cephCalloutTitle": "Ceph: uppgradera Ceph FÖRST, sedan Proxmox",
     "cephCalloutBody": "Ceph måste redan köra 19.x Squid före Proxmox-uppgraderingen. Följ den officiella ordningen Quincy 17 → Reef 18 → Squid 19: uppdatera paket och starta om Ceph-demoner stegvis i dokumenterad ordning. Kontrollera ceph --version före varje Proxmox-nod. Fullständigt förfarande: wikin för uppgradering av Ceph Reef till Squid."
   },
@@ -273,7 +273,7 @@
   },
   "files": {
     "heading": "Filer involverade",
-    "code": "scripts/utilities/upgrade_pve8_to_pve9.sh                         # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh                                # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh    # 17-step runbook\nscripts/global/update-pve8.sh                                     # PVE 8 worker (final patches)\n/etc/apt/sources.list                                             # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources                           # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources                            # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources                              # created if Ceph\n/etc/apt/sources.list.d/*.list                                    # legacy files commented out / removed\n/var/log/pve8-a-pve9-.log                              # full upgrade log"
+    "code": "scripts/utilities/upgrade_pve8_to_pve9.sh                         # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh                                # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh    # 17-step runbook\nscripts/global/update-pve8.sh                                     # PVE 8 worker (final patches)\n/etc/apt/sources.list                                             # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources                           # created (deb822 PVE 9)\n/etc/apt/sources.list.d/ceph.sources                              # created if Ceph\n/etc/apt/sources.list.d/*.list                                    # legacy files commented out / removed\n/var/log/pve8-a-pve9-.log                              # full upgrade log"
   },
   "references": {
     "heading": "Officiella referenser",